diff --git a/nixos/modules/mail.nix b/nixos/modules/mail.nix index 71a6559..b8e51f1 100644 --- a/nixos/modules/mail.nix +++ b/nixos/modules/mail.nix @@ -98,8 +98,10 @@ in { domains = ["fb04184.mathematik.tu-darmstadt.de" "imap.mathebau.de" "smtp.mathebau.de" "mathebau.de"]; default = true; }; + # HRZ/DFN does spam checking for us and this way we don't need to deal with their possibly broken forwarding setup. + spam-filter.enable = false; + # Reevaluate after DKIM and DMARC deployment - spam.header.is-spam = "Dummyheader"; # disable moving to spam which would conflict with forwarding auth = { # TODO check if HRZ and our own VMs conform to these standards and we can validate them strictly dkim.verify = "relaxed"; diff --git a/packages/alias-to-sieve/src/lib.rs b/packages/alias-to-sieve/src/lib.rs index 6622a8a..c0d6842 100644 --- a/packages/alias-to-sieve/src/lib.rs +++ b/packages/alias-to-sieve/src/lib.rs @@ -13,28 +13,58 @@ pub struct AliasFile { } #[derive(PartialEq, Eq, Clone, Debug)] -pub struct OrdEmailAddress(EmailAddress); +pub struct AliasEmailAddress(EmailAddress); -impl PartialOrd for OrdEmailAddress { +impl AliasEmailAddress { + /// Create an `AliasEmailAddress` from some alias entry. + /// Return parameter for complete mail addresses and append the default domain for local parts. + pub fn new( + alias_entry: &str, + default_domain: &FQDN, + ) -> Result> { + let mut addr = alias_entry.trim().to_string(); + addr = addr.replace(',', ""); + + // The domain already fails on instantiation of the FQDN type if it contains an apostrophe. + if addr.contains('\'') { + return Err(format!( + "Mailaddress {addr} contains an apostrophe which breaks the script generation." + ) + .into()); + } + + if addr.contains('@') { + return Ok(AliasEmailAddress( + EmailAddress::parse(&addr, None).ok_or::>( + String::from("Mailaddress {addr} not parsable.").into(), + )?, + )); + } + let unsortable_mail = EmailAddress::new(&addr, &default_domain.to_string(), None)?; + Ok(AliasEmailAddress(unsortable_mail)) + } +} + +impl PartialOrd for AliasEmailAddress { fn partial_cmp(&self, other: &Self) -> Option { Some(self.0.to_string().cmp(&other.0.to_string())) } } -impl Ord for OrdEmailAddress { +impl Ord for AliasEmailAddress { fn cmp(&self, other: &Self) -> Ordering { self.0.to_string().cmp(&other.0.to_string()) } } -pub type AliasMap = BTreeMap>; +pub type AliasMap = BTreeMap>; /// Read a virtual alias file /// and convert it to a map of destination addresses to a list of their final forwarding addresses. pub fn parse_alias_to_map(alias_files: Vec) -> Result> { // File must exist in the current path let mut redirect_map: AliasMap = AliasMap::new(); - let mut destinations: Vec = Vec::new(); + let mut destinations: Vec = Vec::new(); // Extract all pairs (destination to redirect addresses) from the alias files for alias_file in alias_files { @@ -48,25 +78,23 @@ pub fn parse_alias_to_map(alias_files: Vec) -> Result = line + let redirects: Vec = line .split_at(line.find(char::is_whitespace).unwrap_or(0)) .1 .split(' ') .filter(|address| !address.trim().to_string().replace(',', "").is_empty()) - .map(|addr| to_mailaddress(addr, &alias_file.default_domain)) + .map(|addr| AliasEmailAddress::new(addr, &alias_file.default_domain)) .collect::, _>>()?; if redirects.is_empty() { continue; } - destinations.push(to_mailaddress(destination, &alias_file.default_domain)?); + destinations.push(AliasEmailAddress::new( + destination, + &alias_file.default_domain, + )?); redirect_map.insert( - to_mailaddress(destination, &alias_file.default_domain)?, + AliasEmailAddress::new(destination, &alias_file.default_domain)?, redirects, ); } @@ -105,24 +133,6 @@ pub fn parse_alias_to_map(alias_files: Vec) -> Result Result> { - let mut addr = alias_entry.trim().to_string(); - addr = addr.replace(',', ""); - if addr.contains('@') { - return Ok(OrdEmailAddress( - EmailAddress::parse(&addr, None) - .ok_or::>(String::from("Mailaddress {addr} not parsable.").into())?, - )); - } - let unsortable_mail = EmailAddress::new(&addr, &default_domain.to_string(), None)?; - Ok(OrdEmailAddress(unsortable_mail)) -} - // The output is wrapped in a Result to allow matching on errors. // Returns an Iterator to the Reader of the lines of the file. pub fn read_lines

(filename: P) -> io::Result>> @@ -196,9 +206,17 @@ mod tests { } #[test] - fn apostrophe_detection() { + fn apostrophe_destination_detection() { let result = parse_alias_to_map(vec![AliasFile { - content: read_lines("testdata/apostrophe.aliases").unwrap(), + content: read_lines("testdata/apostrophe_destination.aliases").unwrap(), + default_domain: FQDN::from_str("example.com").unwrap(), + }]); + assert!(result.is_err()); + } + #[test] + fn apostrophe_redirect_detection() { + let result = parse_alias_to_map(vec![AliasFile { + content: read_lines("testdata/apostrophe_redirect.aliases").unwrap(), default_domain: FQDN::from_str("example.com").unwrap(), }]); assert!(result.is_err()); diff --git a/packages/alias-to-sieve/testdata/apostrophe.aliases b/packages/alias-to-sieve/testdata/apostrophe.aliases deleted file mode 100644 index 37a6f91..0000000 --- a/packages/alias-to-sieve/testdata/apostrophe.aliases +++ /dev/null @@ -1,2 +0,0 @@ -# Apostrophes are not allowed by HRZ -'orga me@example.com diff --git a/packages/alias-to-sieve/testdata/apostrophe_destination.aliases b/packages/alias-to-sieve/testdata/apostrophe_destination.aliases new file mode 100644 index 0000000..5cc3e26 --- /dev/null +++ b/packages/alias-to-sieve/testdata/apostrophe_destination.aliases @@ -0,0 +1,2 @@ +# Apostrophes are not allowed +'orga me@example.com diff --git a/packages/alias-to-sieve/testdata/apostrophe_redirect.aliases b/packages/alias-to-sieve/testdata/apostrophe_redirect.aliases new file mode 100644 index 0000000..d6b499f --- /dev/null +++ b/packages/alias-to-sieve/testdata/apostrophe_redirect.aliases @@ -0,0 +1,2 @@ +# Apostrophes are not allowed +orga me@e'xample.com