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`.
|
- 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`).
|
- Reject invalid duration values (e.g. `1h30m`).
|
||||||
- Branding: Custom logos for domains do not work.
|
- 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
|
## [0.16.12] - 2026-07-06
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,11 @@ use crate::{
|
|||||||
ingest::{EmailIngest, IngestEmail, IngestSource, IngestedEmail},
|
ingest::{EmailIngest, IngestEmail, IngestSource, IngestedEmail},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use common::{Server, auth::AccessToken, scripts::plugins::PluginContext};
|
use common::{
|
||||||
use mail_parser::MessageParser;
|
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 sieve::{Envelope, Event, Input, Mailbox, Recipient, Sieve, SpamStatus};
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{borrow::Cow, sync::Arc};
|
||||||
use std::{future::Future, str::FromStr};
|
use std::{future::Future, str::FromStr};
|
||||||
@@ -26,7 +29,7 @@ use store::{
|
|||||||
AlignedBytes, Archive, ArchiveVersion, Archiver, BatchBuilder, BlobLink, BlobOp, ValueClass,
|
AlignedBytes, Archive, ArchiveVersion, Archiver, BatchBuilder, BlobLink, BlobOp, ValueClass,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use trc::{AddContext, SieveEvent};
|
use trc::{AddContext, SieveEvent, SmtpEvent};
|
||||||
use types::{
|
use types::{
|
||||||
blob_hash::BlobHash,
|
blob_hash::BlobHash,
|
||||||
collection::Collection,
|
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
|
// Obtain mailboxIds
|
||||||
let account_id = access_token.account_id();
|
let account_id = access_token.account_id();
|
||||||
let mut cache = self
|
let mut cache = self
|
||||||
@@ -387,6 +404,18 @@ 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 {
|
||||||
|
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 {
|
let recipients: Vec<String> = match recipient {
|
||||||
Recipient::Address(rcpt) => vec![rcpt],
|
Recipient::Address(rcpt) => vec![rcpt],
|
||||||
Recipient::Group(rcpts) => rcpts,
|
Recipient::Group(rcpts) => rcpts,
|
||||||
@@ -408,10 +437,19 @@ impl SieveScriptIngest for Server {
|
|||||||
SpanId = session_id
|
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 {
|
autogenerated.push(AutogeneratedMessage {
|
||||||
sender_address: mail_from.clone(),
|
sender_address: mail_from.clone(),
|
||||||
recipients,
|
recipients,
|
||||||
message: message.raw_message.to_vec(),
|
message: raw_message,
|
||||||
});
|
});
|
||||||
do_redirect = true;
|
do_redirect = true;
|
||||||
} else {
|
} 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 struct CompiledScript {
|
||||||
pub script: Sieve,
|
pub script: Sieve,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
Reference in New Issue
Block a user