feat: Check incoming email return address (#72)

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 <jslazak@jslazak.com>
This commit is contained in:
Jagoda Estera Ślązak
2026-02-25 16:20:08 +01:00
committed by GitHub
parent a5c6880ceb
commit 82618de311
3 changed files with 16 additions and 5 deletions
+10 -1
View File
@@ -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.
// <https://github.com/chatmail/filtermail/issues/67>
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));
+1 -1
View File
@@ -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)),
+5 -3
View File
@@ -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<String, String> {
async fn handle_data(&self, envelope: &mut Envelope) -> Result<String, String> {
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