Fix Sieve: add Received headers to auto-generated messages and detect loops

This commit is contained in:
Maurus Decimus
2026-07-12 16:03:53 +02:00
parent ca5971e9a1
commit 602025464b
7 changed files with 22 additions and 19 deletions

View File

@@ -32,6 +32,7 @@ pub struct Scripting {
pub untrusted_runtime: Runtime, pub untrusted_runtime: Runtime,
pub trusted_runtime: Runtime, pub trusted_runtime: Runtime,
pub trusted_compiler: Compiler, pub trusted_compiler: Compiler,
pub max_received_headers: usize,
pub from_addr: IfBlock, pub from_addr: IfBlock,
pub from_name: IfBlock, pub from_name: IfBlock,
pub return_path: IfBlock, pub return_path: IfBlock,
@@ -65,7 +66,7 @@ impl Scripting {
.with_cpu_limit(untrusted.max_cpu_cycles as usize) .with_cpu_limit(untrusted.max_cpu_cycles as usize)
.with_max_variable_size(untrusted.max_var_size as usize) .with_max_variable_size(untrusted.max_var_size as usize)
.with_max_redirects(untrusted.max_redirects as usize) .with_max_redirects(untrusted.max_redirects as usize)
.with_max_received_headers(untrusted.max_received_headers as usize) .with_max_received_headers(usize::MAX) // This is set to usize::MAX here, but the actual limit is enforced during ingestion.
.with_max_header_size(untrusted.max_header_size as usize) .with_max_header_size(untrusted.max_header_size as usize)
.with_max_out_messages(untrusted.max_out_messages as usize) .with_max_out_messages(untrusted.max_out_messages as usize)
.with_default_vacation_expiry(untrusted.default_expiry_vacation.into_inner().as_secs()) .with_default_vacation_expiry(untrusted.default_expiry_vacation.into_inner().as_secs())
@@ -180,6 +181,7 @@ impl Scripting {
trusted_compiler, trusted_compiler,
untrusted_scripts, untrusted_scripts,
trusted_scripts, trusted_scripts,
max_received_headers: untrusted.max_received_headers as usize,
from_addr: bp.compile_expr( from_addr: bp.compile_expr(
ObjectType::SieveSystemScript.singleton(), ObjectType::SieveSystemScript.singleton(),
&trusted.ctx_default_from_address(), &trusted.ctx_default_from_address(),
@@ -209,6 +211,7 @@ impl Clone for Scripting {
from_addr: self.from_addr.clone(), from_addr: self.from_addr.clone(),
from_name: self.from_name.clone(), from_name: self.from_name.clone(),
return_path: self.return_path.clone(), return_path: self.return_path.clone(),
max_received_headers: self.max_received_headers,
sign: self.sign.clone(), sign: self.sign.clone(),
trusted_scripts: self.trusted_scripts.clone(), trusted_scripts: self.trusted_scripts.clone(),
untrusted_scripts: self.untrusted_scripts.clone(), untrusted_scripts: self.untrusted_scripts.clone(),

View File

@@ -146,7 +146,11 @@ impl LdapDirectory {
} }
} }
} }
result.account.groups = if groups.is_empty() { None } else { Some(groups) }; result.account.groups = if groups.is_empty() {
None
} else {
Some(groups)
};
} else if let Some(filter) = &self.mappings.filter_member_of { } else if let Some(filter) = &self.mappings.filter_member_of {
let filter = filter.build(&result.dn); let filter = filter.build(&result.dn);
let rs = conn let rs = conn

View File

@@ -13,9 +13,7 @@ use crate::{
ingest::{EmailIngest, IngestEmail, IngestSource, IngestedEmail}, ingest::{EmailIngest, IngestEmail, IngestSource, IngestedEmail},
}, },
}; };
use common::{ use common::{Server, auth::AccessToken, scripts::plugins::PluginContext};
Server, auth::AccessToken, expr::functions::EmptyResolver, scripts::plugins::PluginContext,
};
use mail_builder::headers::date::Date; use mail_builder::headers::date::Date;
use mail_parser::{HeaderName, MessageParser}; use mail_parser::{HeaderName, MessageParser};
use sieve::{Envelope, Event, Input, Mailbox, Recipient, Sieve, SpamStatus}; use sieve::{Envelope, Event, Input, Mailbox, Recipient, Sieve, SpamStatus};
@@ -114,14 +112,6 @@ impl SieveScriptIngest for Server {
.iter() .iter()
.filter(|header| matches!(header.name, HeaderName::Received)) .filter(|header| matches!(header.name, HeaderName::Received))
.count(); .count();
let max_received_headers = self
.eval_if(
&self.core.smtp.session.data.max_received_headers,
&EmptyResolver,
session_id,
)
.await
.unwrap_or(50);
// Obtain mailboxIds // Obtain mailboxIds
let account_id = access_token.account_id(); let account_id = access_token.account_id();
@@ -404,12 +394,12 @@ impl SieveScriptIngest for Server {
} => { } => {
input = true.into(); input = true.into();
if let Some(message) = messages.get(message_id) { if let Some(message) = messages.get(message_id) {
if received_headers >= max_received_headers { if received_headers >= self.core.sieve.max_received_headers {
trc::event!( trc::event!(
Smtp(SmtpEvent::LoopDetected), Smtp(SmtpEvent::LoopDetected),
From = mail_from.clone(), From = mail_from.clone(),
Total = received_headers, Total = received_headers,
Limit = max_received_headers, Limit = self.core.sieve.max_received_headers,
SpanId = session_id, SpanId = session_id,
); );

View File

@@ -36267,7 +36267,7 @@ impl Default for SieveUserInterpreter {
max_nested_includes: 3u64, max_nested_includes: 3u64,
max_nested_tests: 15u64, max_nested_tests: 15u64,
max_out_messages: 3u64, max_out_messages: 3u64,
max_received_headers: 10u64, max_received_headers: 50u64,
max_redirects: 1u64, max_redirects: 1u64,
max_script_size: 102400, max_script_size: 102400,
max_string_length: 4096u64, max_string_length: 4096u64,

Binary file not shown.

View File

@@ -1 +1 @@
lV3eX4039eN4Y1KtcoXv5vnm6_-nGt_bHuLB6jlkFZQ sEuYGmxMEGuoMkdYNGUNw9kpFzscVRfeSRk44yRmcU8

View File

@@ -45,7 +45,10 @@ pub async fn test() {
email: "jane.smith@example.org".into(), email: "jane.smith@example.org".into(),
email_aliases: vec![], email_aliases: vec![],
secret: Some("$app$4096614298472586996$".into()), secret: Some("$app$4096614298472586996$".into()),
groups: Some(vec!["sales@example.org".into(), "corporate@example.org".into()]), groups: Some(vec![
"sales@example.org".into(),
"corporate@example.org".into()
]),
description: Some("Jane Smith".into()), description: Some("Jane Smith".into()),
} }
); );
@@ -107,7 +110,10 @@ pub async fn test() {
email: "jane.smith@example.org".into(), email: "jane.smith@example.org".into(),
email_aliases: vec![], email_aliases: vec![],
secret: Some("this is Jane's LDAP password".into()), secret: Some("this is Jane's LDAP password".into()),
groups: Some(vec!["sales@example.org".into(), "corporate@example.org".into()]), groups: Some(vec![
"sales@example.org".into(),
"corporate@example.org".into()
]),
description: Some("Jane Smith".into()) description: Some("Jane Smith".into())
}) })
); );