From 664ad571b666cecea96a4e3a036dd0f2a5539b4f Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 14 May 2026 18:16:15 +0200 Subject: [PATCH] feat!: remove passthrough options that allowed unencrypted mail to pass see https://github.com/chatmail/relay/pull/970/ for the related removal of chatmail.ini options --- filtermail/src/config.rs | 24 +----------------------- filtermail/src/message.rs | 32 -------------------------------- filtermail/src/outbound.rs | 17 +++-------------- 3 files changed, 4 insertions(+), 69 deletions(-) diff --git a/filtermail/src/config.rs b/filtermail/src/config.rs index 4707d356..86c7f9d9 100644 --- a/filtermail/src/config.rs +++ b/filtermail/src/config.rs @@ -1,6 +1,6 @@ //! Configuration file handling for filtermail. -use serde::{Deserialize, Deserializer}; +use serde::Deserialize; use std::net::IpAddr; use std::num::NonZeroU32; use std::path::{Path, PathBuf}; @@ -30,10 +30,6 @@ pub struct Config { pub max_user_send_per_minute: NonZeroU32, #[serde(default = "Config::default_max_user_send_burst_size")] pub max_user_send_burst_size: NonZeroU32, - #[serde(default, deserialize_with = "deserialize_sequence")] - pub passthrough_senders: Vec, - #[serde(default, deserialize_with = "deserialize_sequence")] - pub passthrough_recipients: Vec, pub mail_domain: String, mailboxes_dir: Option, } @@ -44,22 +40,6 @@ struct ConfigWrapper { pub params: Config, } -/// Custom deserializer to parse space-separated strings into [`Vec`]. -fn deserialize_sequence<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let s: Option = Deserialize::deserialize(deserializer)?; - Ok(match s { - Some(v) => v - .split(' ') - .map(|item| item.trim().to_string()) - .filter(|item| !item.is_empty()) - .collect(), - None => Vec::new(), - }) -} - impl Config { /// Load configuration from a file. pub fn from_file(path: impl AsRef) -> Result { @@ -156,8 +136,6 @@ impl Default for Config { max_message_size: Self::default_max_message_size(), max_user_send_per_minute: Self::default_max_user_send_per_minute(), max_user_send_burst_size: Self::default_max_user_send_burst_size(), - passthrough_senders: Vec::new(), - passthrough_recipients: Vec::new(), mail_domain: "example.org".to_string(), mailboxes_dir: None, } diff --git a/filtermail/src/message.rs b/filtermail/src/message.rs index b9ce9fcf..da7c7a99 100644 --- a/filtermail/src/message.rs +++ b/filtermail/src/message.rs @@ -131,19 +131,6 @@ pub fn check_encrypted(mail: &mailparse::ParsedMail, outgoing: bool) -> bool { true } -/// Check if recipient matches a passthrough pattern -pub fn recipient_matches_passthrough(recipient: &str, passthrough_recipients: &[String]) -> bool { - for addr in passthrough_recipients { - if recipient == addr { - return true; - } - if addr.starts_with('@') && recipient.ends_with(addr) { - return true; - } - } - false -} - #[cfg(test)] mod tests { use super::*; @@ -151,11 +138,6 @@ mod tests { use rstest::*; use testresult::TestResult; - #[fixture] - fn passthrough_recipients() -> Vec { - vec!["pass@example.org".to_string(), "@example.com".to_string()] - } - #[rstest] #[case::asm("test_data/asm.eml", false)] #[case::encrypted("test_data/encrypted.eml", false)] @@ -189,18 +171,4 @@ mod tests { assert_eq!(check_encrypted(&parsed, false), expected); Ok(()) } - - #[rstest] - #[case("pass@example.org", true)] - #[case("other@example.org", false)] - #[case("anything@example.com", true)] - #[case("anything@sub.example.com", false)] - fn test_recipient_matches_passthrough( - #[case] recipient: &str, - #[case] expected: bool, - passthrough_recipients: Vec, - ) { - let result = recipient_matches_passthrough(recipient, &passthrough_recipients); - assert_eq!(result, expected); - } } diff --git a/filtermail/src/outbound.rs b/filtermail/src/outbound.rs index 6b6adba0..d6e32fc5 100644 --- a/filtermail/src/outbound.rs +++ b/filtermail/src/outbound.rs @@ -2,7 +2,7 @@ use crate::ENCRYPTION_NEEDED_523; use crate::config::Config; -use crate::message::{check_encrypted, is_securejoin, recipient_matches_passthrough}; +use crate::message::{check_encrypted, is_securejoin}; use crate::smtp_client::SmtpConnectionPool; pub use crate::smtp_server::Envelope; use crate::smtp_server::SmtpHandler; @@ -114,11 +114,6 @@ impl SmtpHandler for OutgoingBeforeQueueHandler { log::info!("Outgoing: Filtering unencrypted mail."); - // Allow passthrough senders - if self.config.passthrough_senders.contains(&from_addr) { - return Ok(()); - } - // Allow self-sent Autocrypt Setup Message if envelope.rcpt_to.len() == 1 && let Some(rcpt_to) = envelope.rcpt_to.first() @@ -133,14 +128,8 @@ impl SmtpHandler for OutgoingBeforeQueueHandler { } } - for recipient in &envelope.rcpt_to { - if !recipient_matches_passthrough(recipient, &self.config.passthrough_recipients) { - log::warn!("Rejected unencrypted mail from: {from_addr}"); - return Err(ENCRYPTION_NEEDED_523.to_string()); - } - } - - Ok(()) + log::warn!("Rejected unencrypted mail from: {from_addr}"); + Err(ENCRYPTION_NEEDED_523.to_string()) } async fn reinject_mail(&self, envelope: &Envelope) -> Result<(), String> {