From 05cbf2908c38a315976bdc10149a47d0e6504d35 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, 1 Apr 2026 22:57:02 +0200 Subject: [PATCH] feat: Add experimental option to disable mailboxes (#108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jagoda Ślązak --- filtermail/src/config.rs | 26 +++++++++++++++++++------- filtermail/src/inbound.rs | 7 +++++++ filtermail/src/outbound.rs | 25 +++++++++++++++++++++++++ filtermail/src/smtp_server.rs | 4 ++++ 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/filtermail/src/config.rs b/filtermail/src/config.rs index 46c3c9f1..ad6831c5 100644 --- a/filtermail/src/config.rs +++ b/filtermail/src/config.rs @@ -72,17 +72,29 @@ impl Config { } } - /// Check if not encrypted mail is allowed for the given address. - pub fn is_cleartext_ok(&self, addr: &str) -> bool { + /// Check if a specific flag file exists for the given address. + /// + /// Returns `default` if the address is invalid. + fn check_flag(&self, addr: &str, flag: &str, default: bool) -> bool { if addr.is_empty() || !addr.contains('@') || addr.contains('/') { - return false; + return default; } - let mut enforce_e2ee = self.mailboxes_dir(); - enforce_e2ee.push(addr); - enforce_e2ee.push("enforceE2EEincoming"); + let mut path = self.mailboxes_dir(); + path.push(addr); + path.push(flag); - !enforce_e2ee.exists() + path.exists() + } + + /// Check if not encrypted mail is allowed for the given address. + pub fn is_cleartext_ok(&self, addr: &str) -> bool { + !self.check_flag(addr, "enforceE2EEincoming", true) + } + + /// Check if the given address is disabled. + pub fn is_disabled(&self, addr: &str) -> bool { + self.check_flag(addr, "DISABLED", false) } // Following are needed since serde does not support default literals. diff --git a/filtermail/src/inbound.rs b/filtermail/src/inbound.rs index 747157a4..9af1f1e8 100644 --- a/filtermail/src/inbound.rs +++ b/filtermail/src/inbound.rs @@ -98,6 +98,13 @@ impl SmtpHandler for IncomingBeforeQueueHandler { envelope.mail_from = String::new(); } + envelope.rcpt_to = envelope + .rcpt_to + .iter() + .filter(|s| !self.config.is_disabled(s)) + .cloned() + .collect(); + 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 6b2ab0f7..5db04e05 100644 --- a/filtermail/src/outbound.rs +++ b/filtermail/src/outbound.rs @@ -78,6 +78,13 @@ impl SmtpHandler for OutgoingBeforeQueueHandler { let from_addr = extract_address(&from_header) .ok_or(format!("500 Invalid FROM header: {from_header}"))?; + envelope.rcpt_to = envelope + .rcpt_to + .iter() + .filter(|s| !self.config.is_disabled(s)) + .cloned() + .collect(); + // MAIL FROM is our source of truth for outbound messages, // as this address is checked by postfix against the username before sending it // to filtermail. @@ -139,4 +146,22 @@ impl SmtpHandler for OutgoingBeforeQueueHandler { Ok(()) } + + async fn handle_data(&self, envelope: &mut Envelope) -> Result { + log::debug!("handle_DATA before-queue"); + self.check_data(envelope).await?; + if self.config.is_disabled(&envelope.mail_from) { + log::warn!("Dropping mail; Sender {} is disabled.", envelope.mail_from); + return Ok("250 OK".to_string()); + } + if envelope.rcpt_to.is_empty() { + log::warn!("Dropping mail; All recipients disabled."); + return Ok("250 OK".to_string()); + } + self.reinject_mail(envelope).await.map_err(|e| { + log::warn!("Failed to reinject mail: {e}"); + e + })?; + Ok("250 OK".to_string()) + } } diff --git a/filtermail/src/smtp_server.rs b/filtermail/src/smtp_server.rs index 53fc9ae6..ef7357a2 100644 --- a/filtermail/src/smtp_server.rs +++ b/filtermail/src/smtp_server.rs @@ -33,6 +33,10 @@ pub trait SmtpHandler: Send + Sync { async fn handle_data(&self, envelope: &mut Envelope) -> Result { log::debug!("handle_DATA before-queue"); self.check_data(envelope).await?; + if envelope.rcpt_to.is_empty() { + log::warn!("Dropping mail; All recipients disabled."); + return Ok("250 OK".to_string()); + } self.reinject_mail(envelope).await.map_err(|e| { log::warn!("Failed to reinject mail: {e}"); e