From 82618de311b706c16992b4740ae2d6eeb325d698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jagoda=20Estera=20=C5=9Al=C4=85zak?= <128227338+j-g00da@users.noreply.github.com> Date: Wed, 25 Feb 2026 16:20:08 +0100 Subject: [PATCH] feat: Check incoming email return address (#72) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the MAIL FROM doesn't match the From header, we do not reject the mail, as this can be caused by e.g. SRS forwarding. Instead, we reset the envelope address, so it is reinjected as `MAIL FROM:<>` to prevent sending a bounce message. Closes #67 Signed-off-by: Jagoda Ślązak --- filtermail/src/inbound.rs | 11 ++++++++++- filtermail/src/outbound.rs | 2 +- filtermail/src/smtp_server.rs | 8 +++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/filtermail/src/inbound.rs b/filtermail/src/inbound.rs index 90d91ff2..7b4c28cd 100644 --- a/filtermail/src/inbound.rs +++ b/filtermail/src/inbound.rs @@ -72,7 +72,7 @@ impl SmtpHandler for IncomingBeforeQueueHandler { Ok(()) } - async fn check_data(&self, envelope: &Envelope) -> Result<(), String> { + async fn check_data(&self, envelope: &mut Envelope) -> Result<(), String> { let message = match parse_mail(&envelope.data) { Ok(m) => m, Err(e) => return Err(format!("500 Failed to parse message: {}", e)), @@ -91,6 +91,15 @@ impl SmtpHandler for IncomingBeforeQueueHandler { log::debug!("Processing DATA message from {from_addr}"); + if !envelope.mail_from.eq_ignore_ascii_case(&from_addr) { + // If the MAIL FROM doesn't match the From header, we do not reject the mail, + // as this can be caused by e.g. SRS forwarding. + // Instead, we reset the envelope address, so it is reinjected as + // `MAIL FROM:<>` to prevent sending a bounce message. + // + envelope.mail_from = String::new(); + } + let mail_encrypted = check_encrypted(&message, false); log::debug!("mail_encrypted: {mail_encrypted}"); log::debug!("is_securejoin: {}", is_securejoin(&message)); diff --git a/filtermail/src/outbound.rs b/filtermail/src/outbound.rs index a654fe52..6614da66 100644 --- a/filtermail/src/outbound.rs +++ b/filtermail/src/outbound.rs @@ -55,7 +55,7 @@ impl SmtpHandler for OutgoingBeforeQueueHandler { Ok(()) } - async fn check_data(&self, envelope: &Envelope) -> Result<(), String> { + async fn check_data(&self, envelope: &mut Envelope) -> Result<(), String> { let message = match parse_mail(&envelope.data) { Ok(m) => m, Err(e) => return Err(format!("500 Failed to parse message: {}", e)), diff --git a/filtermail/src/smtp_server.rs b/filtermail/src/smtp_server.rs index ddb3d9fb..e3411590 100644 --- a/filtermail/src/smtp_server.rs +++ b/filtermail/src/smtp_server.rs @@ -22,13 +22,15 @@ pub trait SmtpHandler: Send + Sync { fn handle_mail(&self, address: &str) -> Result<(), String>; /// Checks the DATA command before reinjection. - async fn check_data(&self, envelope: &Envelope) -> Result<(), String>; + /// + /// Can optionally modify the envelope before reinjection. + async fn check_data(&self, envelope: &mut Envelope) -> Result<(), String>; /// Reinjects the mail back to postfix. async fn reinject_mail(&self, envelope: &Envelope) -> Result<(), String>; /// Handles the DATA command. - async fn handle_data(&self, envelope: &Envelope) -> Result { + async fn handle_data(&self, envelope: &mut Envelope) -> Result { log::debug!("handle_DATA before-queue"); self.check_data(envelope).await?; self.reinject_mail(envelope).await.map_err(|e| { @@ -182,7 +184,7 @@ where envelope.data = data; // Process the message - match handler.handle_data(&envelope).await { + match handler.handle_data(&mut envelope).await { Ok(response) => { log::debug!("Sent: {response}"); writer