Fix Sieve: add Received headers to auto-generated messages and detect loops
This commit is contained in:
@@ -17,6 +17,7 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If
|
||||
- JMAP: Return RFC-3339-conformant UTCDate literals in capabilities: min `0001-01-01T00:00:00Z`, max `9999-12-31T23:59:59Z`.
|
||||
- Reject invalid duration values (e.g. `1h30m`).
|
||||
- Branding: Custom logos for domains do not work.
|
||||
- Sieve: add `Received` headers to auto-generated messages and detect loops.
|
||||
|
||||
## [0.16.12] - 2026-07-06
|
||||
|
||||
|
||||
@@ -13,8 +13,11 @@ use crate::{
|
||||
ingest::{EmailIngest, IngestEmail, IngestSource, IngestedEmail},
|
||||
},
|
||||
};
|
||||
use common::{Server, auth::AccessToken, scripts::plugins::PluginContext};
|
||||
use mail_parser::MessageParser;
|
||||
use common::{
|
||||
Server, auth::AccessToken, expr::functions::EmptyResolver, scripts::plugins::PluginContext,
|
||||
};
|
||||
use mail_builder::headers::date::Date;
|
||||
use mail_parser::{HeaderName, MessageParser};
|
||||
use sieve::{Envelope, Event, Input, Mailbox, Recipient, Sieve, SpamStatus};
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
use std::{future::Future, str::FromStr};
|
||||
@@ -26,7 +29,7 @@ use store::{
|
||||
AlignedBytes, Archive, ArchiveVersion, Archiver, BatchBuilder, BlobLink, BlobOp, ValueClass,
|
||||
},
|
||||
};
|
||||
use trc::{AddContext, SieveEvent};
|
||||
use trc::{AddContext, SieveEvent, SmtpEvent};
|
||||
use types::{
|
||||
blob_hash::BlobHash,
|
||||
collection::Collection,
|
||||
@@ -106,6 +109,20 @@ impl SieveScriptIngest for Server {
|
||||
);
|
||||
};
|
||||
|
||||
let received_headers = message
|
||||
.headers()
|
||||
.iter()
|
||||
.filter(|header| matches!(header.name, HeaderName::Received))
|
||||
.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
|
||||
let account_id = access_token.account_id();
|
||||
let mut cache = self
|
||||
@@ -387,6 +404,18 @@ impl SieveScriptIngest for Server {
|
||||
} => {
|
||||
input = true.into();
|
||||
if let Some(message) = messages.get(message_id) {
|
||||
if received_headers >= max_received_headers {
|
||||
trc::event!(
|
||||
Smtp(SmtpEvent::LoopDetected),
|
||||
From = mail_from.clone(),
|
||||
Total = received_headers,
|
||||
Limit = max_received_headers,
|
||||
SpanId = session_id,
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let recipients: Vec<String> = match recipient {
|
||||
Recipient::Address(rcpt) => vec![rcpt],
|
||||
Recipient::Group(rcpts) => rcpts,
|
||||
@@ -408,10 +437,19 @@ impl SieveScriptIngest for Server {
|
||||
SpanId = session_id
|
||||
);
|
||||
|
||||
let mut raw_message =
|
||||
Vec::with_capacity(160 + message.raw_message.len());
|
||||
write_received_header(
|
||||
&mut raw_message,
|
||||
&self.core.network.server_name,
|
||||
session_id,
|
||||
);
|
||||
raw_message.extend_from_slice(message.raw_message.as_ref());
|
||||
|
||||
autogenerated.push(AutogeneratedMessage {
|
||||
sender_address: mail_from.clone(),
|
||||
recipients,
|
||||
message: message.raw_message.to_vec(),
|
||||
message: raw_message,
|
||||
});
|
||||
do_redirect = true;
|
||||
} else {
|
||||
@@ -750,6 +788,16 @@ impl SieveScriptIngest for Server {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_received_header(buf: &mut Vec<u8>, hostname: &str, id: u64) {
|
||||
buf.extend_from_slice(b"Received: from localhost (localhost [127.0.0.1])\r\n\tby ");
|
||||
buf.extend_from_slice(hostname.as_bytes());
|
||||
buf.extend_from_slice(b" (Stalwart SMTP) with LMTP id ");
|
||||
buf.extend_from_slice(format!("{id:X}").as_bytes());
|
||||
buf.extend_from_slice(b";\r\n\t");
|
||||
buf.extend_from_slice(Date::now().to_rfc822().as_bytes());
|
||||
buf.extend_from_slice(b"\r\n");
|
||||
}
|
||||
|
||||
pub struct CompiledScript {
|
||||
pub script: Sieve,
|
||||
pub name: String,
|
||||
|
||||
Reference in New Issue
Block a user