From dc4191a542b2db5f6ffc48be8cbcf119f95b398d Mon Sep 17 00:00:00 2001 From: Maurus Decimus <11444311+mdecimus@users.noreply.github.com> Date: Tue, 26 May 2026 19:07:33 +0200 Subject: [PATCH] Fix MTA: Expand lists and resolve catch-all addresses when building autogenerated messages --- CHANGELOG.md | 4 ++- crates/smtp/src/outbound/local.rs | 2 +- crates/smtp/src/queue/dsn.rs | 2 +- crates/smtp/src/queue/spool.rs | 35 ++++++++++++++++++++++++--- crates/smtp/src/reporting/send.rs | 4 +-- crates/smtp/src/scripts/event_loop.rs | 4 +-- 6 files changed, 41 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c00a06c..50796033 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,9 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If ## Fixed - Log rejected messages to tracing store. -- MTA: Always update next DSN notify times. +- MTA: + - Always update next DSN notify times. + - Expand lists and resolve catch-all addresses when building autogenerated messages. - Sharing: Includes resource that themselves carry a direct ACL grant and are leaves. ## [0.16.6] - 2026-05-20 diff --git a/crates/smtp/src/outbound/local.rs b/crates/smtp/src/outbound/local.rs index 3ae354e4..fdc564b8 100644 --- a/crates/smtp/src/outbound/local.rs +++ b/crates/smtp/src/outbound/local.rs @@ -100,7 +100,7 @@ impl MessageWrapper { for autogenerated in delivery_result.autogenerated { let mut message = server.new_message(autogenerated.sender_address, self.span_id); for rcpt in autogenerated.recipients { - message.add_recipient(rcpt, server).await; + message.expand_and_add_recipient(rcpt, server).await; } // Sign message diff --git a/crates/smtp/src/queue/dsn.rs b/crates/smtp/src/queue/dsn.rs index cabf9c25..9624f08c 100644 --- a/crates/smtp/src/queue/dsn.rs +++ b/crates/smtp/src/queue/dsn.rs @@ -39,7 +39,7 @@ impl SendDsn for Server { if let Some(dsn) = message.build_dsn(self).await { let mut dsn_message = self.new_message("", message.span_id); dsn_message - .add_recipient(message.message.return_path.as_ref(), self) + .expand_and_add_recipient(message.message.return_path.as_ref(), self) .await; // Sign message diff --git a/crates/smtp/src/queue/spool.rs b/crates/smtp/src/queue/spool.rs index bee4e364..aaae4921 100644 --- a/crates/smtp/src/queue/spool.rs +++ b/crates/smtp/src/queue/spool.rs @@ -16,6 +16,7 @@ use crate::queue::{ use ahash::AHashSet; use common::config::smtp::queue::{ArchivedQueueExpiry, QueueName}; use common::ipc::QueueEvent; +use common::network::RcptResolution; use common::{KV_LOCK_QUEUE_MESSAGE, Server}; use registry::schema::prelude::{ObjectType, Property}; use registry::schema::structs::SpamTrainingSample; @@ -568,9 +569,37 @@ impl MessageWrapper { true } - pub async fn add_recipient(&mut self, rcpt: impl AsRef, server: &Server) { - // Resolve queue - self.message.recipients.push(Recipient::new(rcpt)); + pub async fn expand_and_add_recipient(&mut self, rcpt: impl AsRef, server: &Server) { + let rcpt = rcpt.as_ref(); + match server + .rcpt_resolve(&rcpt.to_lowercase(), self.span_id) + .await + { + Ok(RcptResolution::Rewrite(rewritten)) => { + self.add_expanded_recipient(&rewritten, server).await; + } + Ok(RcptResolution::Expand(addrs)) => { + for addr in addrs.as_ref() { + self.add_expanded_recipient(addr, server).await; + } + } + Ok(_) => { + self.add_expanded_recipient(rcpt, server).await; + } + Err(err) => { + trc::error!( + err.span_id(self.span_id) + .caused_by(trc::location!()) + .details("Failed to resolve recipient.") + .ctx(trc::Key::To, rcpt.to_string()) + ); + self.add_expanded_recipient(rcpt, server).await; + } + } + } + + pub async fn add_expanded_recipient(&mut self, rcpt: impl AsRef, server: &Server) { + self.message.recipients.push(Recipient::new(rcpt.as_ref())); let queue = server.get_queue_or_default( &server .eval_if::( diff --git a/crates/smtp/src/reporting/send.rs b/crates/smtp/src/reporting/send.rs index 75a10066..3953db29 100644 --- a/crates/smtp/src/reporting/send.rs +++ b/crates/smtp/src/reporting/send.rs @@ -57,7 +57,7 @@ impl MtaReportSend for Server { // Build message let mut message = self.new_message(from_addr, parent_session_id); for rcpt_ in rcpts { - message.add_recipient(rcpt_.as_ref(), self).await; + message.add_expanded_recipient(rcpt_.as_ref(), self).await; } // Sign message @@ -104,7 +104,7 @@ impl MtaReportSend for Server { // Build message let mut message = self.new_message(from_addr.as_ref(), parent_session_id); for rcpt in rcpts { - message.add_recipient(rcpt, self).await; + message.add_expanded_recipient(rcpt, self).await; } // Sign message diff --git a/crates/smtp/src/scripts/event_loop.rs b/crates/smtp/src/scripts/event_loop.rs index 2933d531..f61b12b1 100644 --- a/crates/smtp/src/scripts/event_loop.rs +++ b/crates/smtp/src/scripts/event_loop.rs @@ -161,11 +161,11 @@ impl RunScript for Server { let mut message = self.new_message(params.return_path.as_str(), session_id); match recipient { Recipient::Address(rcpt) => { - message.add_recipient(rcpt, self).await; + message.expand_and_add_recipient(rcpt, self).await; } Recipient::Group(rcpt_list) => { for rcpt in rcpt_list { - message.add_recipient(rcpt, self).await; + message.expand_and_add_recipient(rcpt, self).await; } } Recipient::List(list) => {