From eb86511fe7a13cf964eb669d4868afa87f9a0a28 Mon Sep 17 00:00:00 2001 From: Gonne Date: Sun, 30 Mar 2025 09:35:09 +0200 Subject: [PATCH 1/4] Disallow apostrophies in mail addresses of alias files --- packages/alias-to-sieve/src/lib.rs | 90 +++++++++++++------ .../testdata/apostrophe_destination.aliases | 2 + .../testdata/apostrophe_redirect.aliases | 2 + 3 files changed, 65 insertions(+), 29 deletions(-) create mode 100644 packages/alias-to-sieve/testdata/apostrophe_destination.aliases create mode 100644 packages/alias-to-sieve/testdata/apostrophe_redirect.aliases diff --git a/packages/alias-to-sieve/src/lib.rs b/packages/alias-to-sieve/src/lib.rs index 35ebea2..c0d6842 100644 --- a/packages/alias-to-sieve/src/lib.rs +++ b/packages/alias-to-sieve/src/lib.rs @@ -12,29 +12,59 @@ pub struct AliasFile { pub default_domain: FQDN, } -#[derive(PartialEq, Eq, Clone)] -pub struct OrdEmailAddress(EmailAddress); +#[derive(PartialEq, Eq, Clone, Debug)] +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,20 +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, ); } @@ -95,29 +128,11 @@ 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>> @@ -190,6 +205,23 @@ mod tests { assert!(result.is_err()); } + #[test] + fn apostrophe_destination_detection() { + let result = parse_alias_to_map(vec![AliasFile { + 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()); + } + #[test] fn basic_parsing() { let result = parse_alias_to_map(vec![AliasFile { 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 From 7f5496c9c935f16ecc637b1efd8be3a84ee5c6df Mon Sep 17 00:00:00 2001 From: Dennis Frieberg Date: Mon, 31 Mar 2025 02:28:53 +0200 Subject: [PATCH 2/4] populate ip address based on hostname and populate the hostfile with other vm ips --- nixos/machines/ghatanothoa/configuration.nix | 2 - nixos/machines/lobon/configuration.nix | 2 - nixos/machines/nodens/configuration.nix | 2 - nixos/machines/nyarlathotep/configuration.nix | 2 - nixos/modules/vmNetwork.nix | 48 ---------------- nixos/roles/hostmap.nix | 57 +++++++++++++++++++ nixos/roles/vm.nix | 1 + nixos/roles/vmNetwork.nix | 46 +++++++++++++++ 8 files changed, 104 insertions(+), 56 deletions(-) delete mode 100644 nixos/modules/vmNetwork.nix create mode 100644 nixos/roles/hostmap.nix create mode 100644 nixos/roles/vmNetwork.nix diff --git a/nixos/machines/ghatanothoa/configuration.nix b/nixos/machines/ghatanothoa/configuration.nix index 8364bea..57cc0cb 100644 --- a/nixos/machines/ghatanothoa/configuration.nix +++ b/nixos/machines/ghatanothoa/configuration.nix @@ -4,7 +4,6 @@ ../../modules/jitsi.nix ../../roles ../../roles/vm.nix - ../../modules/vmNetwork.nix ]; services.mathebau-jitsi = { @@ -14,6 +13,5 @@ # System configuration here networking.hostName = "ghatanothoa"; - vmNetwork.ipv4 = "192.168.0.25"; system.stateVersion = "23.11"; } diff --git a/nixos/machines/lobon/configuration.nix b/nixos/machines/lobon/configuration.nix index 6cb371a..e0662be 100644 --- a/nixos/machines/lobon/configuration.nix +++ b/nixos/machines/lobon/configuration.nix @@ -4,7 +4,6 @@ ../../modules/mailman.nix ../../roles ../../roles/vm.nix - ../../modules/vmNetwork.nix ]; # System configuration here @@ -16,7 +15,6 @@ }; networking.hostName = "lobon"; - vmNetwork.ipv4 = "192.168.0.22"; system.stateVersion = "23.11"; sops.secrets = { diff --git a/nixos/machines/nodens/configuration.nix b/nixos/machines/nodens/configuration.nix index 554e407..a9ae8a2 100644 --- a/nixos/machines/nodens/configuration.nix +++ b/nixos/machines/nodens/configuration.nix @@ -3,7 +3,6 @@ ./hardware-configuration.nix ../../roles ../../roles/vm.nix - ../../modules/vmNetwork.nix ]; # System configuration here @@ -11,6 +10,5 @@ environment.systemPackages = [pkgs.git]; networking.hostName = "nodens"; - vmNetwork.ipv4 = "192.168.0.18"; system.stateVersion = "24.11"; } diff --git a/nixos/machines/nyarlathotep/configuration.nix b/nixos/machines/nyarlathotep/configuration.nix index 57d00ff..88bb1b0 100644 --- a/nixos/machines/nyarlathotep/configuration.nix +++ b/nixos/machines/nyarlathotep/configuration.nix @@ -4,7 +4,6 @@ ../../modules/mail.nix ../../roles ../../roles/vm.nix - ../../modules/vmNetwork.nix ]; # System configuration here @@ -40,7 +39,6 @@ }; networking.hostName = "nyarlathotep"; - vmNetwork.ipv4 = "192.168.0.17"; system.stateVersion = "24.05"; sops.secrets = let diff --git a/nixos/modules/vmNetwork.nix b/nixos/modules/vmNetwork.nix deleted file mode 100644 index 133d101..0000000 --- a/nixos/modules/vmNetwork.nix +++ /dev/null @@ -1,48 +0,0 @@ -{ - lib, - config, - ... -}: let - inherit - (lib) - mkOption - types - last - init - ; - inherit - (lib.strings) - splitString - concatStringsSep - toInt - ; - cfg = config.vmNetwork; -in { - imports = []; - - options.vmNetwork = { - ipv4 = mkOption { - type = types.str; - description = "the ipv4 adress of this machine"; - }; - }; - - config = { - networking = { - interfaces.enX0.ipv4.addresses = [ - { - address = cfg.ipv4; - prefixLength = 16; - } - ]; - defaultGateway = let - addr = splitString "." cfg.ipv4; - addrInit = init addr; - addrLastInt = builtins.toString (toInt (last addr) + 127); - in - concatStringsSep "." (addrInit ++ [addrLastInt]); - # https://www.hrz.tu-darmstadt.de/services/it_services/nameserver_dns/index.de.jsp - nameservers = ["130.83.22.63" "130.83.22.60" "130.83.56.60"]; - }; - }; -} diff --git a/nixos/roles/hostmap.nix b/nixos/roles/hostmap.nix new file mode 100644 index 0000000..66f8615 --- /dev/null +++ b/nixos/roles/hostmap.nix @@ -0,0 +1,57 @@ +# This data is taken from /etc/hosts from azatoth +{ + bragi = { + ipv4 = "192.168.1.11"; + }; + tsathoggua = { + ipv4 = "192.168.0.13"; + }; + nyogtha = { + ipv4 = "192.168.0.14"; + }; + hastur = { + ipv4 = "192.168.0.15"; + }; + cthulhu = { + ipv4 = "192.168.0.16"; + }; + nyarlathotep = { + ipv4 = "192.168.0.17"; + }; + nodens = { + ipv4 = "192.168.0.18"; + }; + uvhash = { + ipv4 = "192.168.0.19"; + }; + aphoom-zhah = { + ipv4 = "192.168.0.20"; + }; + dagon = { + ipv4 = "192.168.0.21"; + }; + lobon = { + ipv4 = "192.168.0.22"; + }; + yibb-tstll = { + ipv4 = "192.168.0.23"; + }; + eihort = { + ipv4 = "192.168.0.24"; + }; + ghatanothoa = { + ipv4 = "192.168.0.25"; + }; + toth = { + ipv4 = "192.168.0.26"; + }; + ithaqua = { + ipv4 = "192.168.0.27"; + }; + cthugha = { + ipv4 = "192.168.0.30"; + }; + sanctamariamaterdei = { + ipv4 = "192.168.0.92"; + }; +} diff --git a/nixos/roles/vm.nix b/nixos/roles/vm.nix index 32fba6d..aff9f22 100644 --- a/nixos/roles/vm.nix +++ b/nixos/roles/vm.nix @@ -1,5 +1,6 @@ {modulesPath, ...}: { imports = [ (modulesPath + "/virtualisation/xen-domU.nix") + ./vmNetwork.nix ]; } diff --git a/nixos/roles/vmNetwork.nix b/nixos/roles/vmNetwork.nix new file mode 100644 index 0000000..d4a0ff7 --- /dev/null +++ b/nixos/roles/vmNetwork.nix @@ -0,0 +1,46 @@ +{ + lib, + config, + ... +}: let + inherit (lib) mapAttrsToList; + inherit (lib.attrsets) foldAttrs concatMapAttrs; + inherit (lib.asserts) assertMsg; + inherit (lib.lists) filter last init; + inherit (lib.strings) splitString toInt concatStringsSep; + inherit (builtins) elem toString; + hostmap = import ./hostmap.nix; + myhostName = config.networking.hostName; + # To turn the hostmap around suitable for networking.hosts the following simple code almost works + # concatMapAttrs (hostname: ipData: { ${ipData.ipv4} = [hostname]; }) hostmap + # but breaks as soon as we want to map two different names to the same ip. + # So the code looks uglier than one would expect. + globalhosts = foldAttrs (a: b: a ++ b) [] (mapAttrsToList (hostname: ipData: {${ipData.ipv4} = [hostname];}) hostmap); + # We replace our own ip with 127.0.0.1 in /etc/hosts + myhosts = concatMapAttrs (ip: hosts: + if (elem myhostName hosts) + # nixos maps the hostname to the loopback 127.0.0.2 by default, so we exclude it here. + # there is also a default localhost to 127.0.0.1 in place + then {"127.0.0.1" = filter (x: x != myhostName) hosts;} + else {${ip} = hosts;}) + globalhosts; + myIp = assert (assertMsg (hostmap ? ${myhostName}.ipv4) "${myhostName} has no ip configured in nixos/roles/hostmap.nix"); hostmap.${myhostName}.ipv4; +in { + networking = { + hosts = myhosts; + interfaces.enX0.ipv4.addresses = [ + { + address = myIp; + prefixLength = 16; + } + ]; + defaultGateway = let + addr = splitString "." myIp; + addrInit = init addr; + addrLastInt = toString (toInt (last addr) + 127); + in + concatStringsSep "." (addrInit ++ [addrLastInt]); + # https://www.hrz.tu-darmstadt.de/services/it_services/nameserver_dns/index.de.jsp + nameservers = ["130.83.22.63" "130.83.22.60" "130.83.56.60"]; + }; +} From 79c731ccd3b6b3171da9979c90dcd72e541bb3e8 Mon Sep 17 00:00:00 2001 From: Dennis Frieberg Date: Tue, 1 Apr 2025 17:03:18 +0200 Subject: [PATCH 3/4] replaced ip addresses with hostnames --- nixos/modules/mail.nix | 2 +- nixos/modules/mailman.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nixos/modules/mail.nix b/nixos/modules/mail.nix index b8e51f1..5384c40 100644 --- a/nixos/modules/mail.nix +++ b/nixos/modules/mail.nix @@ -360,7 +360,7 @@ in { # We don't want this in order to not need to persist borg cache and simplify new deployments. BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK = "yes"; }; - repo = "borg@192.168.1.11:nyarlathotep"; # TODO for https://gitea.mathebau.de/Fachschaft/nixConfig/issues/33 + repo = "borg@bragi:nyarlathotep"; # TODO for https://gitea.mathebau.de/Fachschaft/nixConfig/issues/33 startAt = "daily"; user = "root"; group = "root"; diff --git a/nixos/modules/mailman.nix b/nixos/modules/mailman.nix index 66ee109..5383374 100644 --- a/nixos/modules/mailman.nix +++ b/nixos/modules/mailman.nix @@ -117,7 +117,7 @@ in { # We don't want this in order to not need to persist borg cache and simplify new deployments. BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK = "yes"; }; - repo = "borg@192.168.1.11:lobon"; # TODO for https://gitea.mathebau.de/Fachschaft/nixConfig/issues/33 + repo = "borg@bragi:lobon"; # TODO for https://gitea.mathebau.de/Fachschaft/nixConfig/issues/33 startAt = "daily"; user = "root"; group = "root"; From aa210d868b0126d504aa9a496fe624ffc4cd278d Mon Sep 17 00:00:00 2001 From: Gonne Date: Sun, 30 Mar 2025 09:35:09 +0200 Subject: [PATCH 4/4] Disallow apostrophies in mail addresses of alias files --- packages/alias-to-sieve/src/lib.rs | 90 +++++++++++++------ .../testdata/apostrophe_destination.aliases | 2 + .../testdata/apostrophe_redirect.aliases | 2 + 3 files changed, 65 insertions(+), 29 deletions(-) create mode 100644 packages/alias-to-sieve/testdata/apostrophe_destination.aliases create mode 100644 packages/alias-to-sieve/testdata/apostrophe_redirect.aliases diff --git a/packages/alias-to-sieve/src/lib.rs b/packages/alias-to-sieve/src/lib.rs index 35ebea2..c0d6842 100644 --- a/packages/alias-to-sieve/src/lib.rs +++ b/packages/alias-to-sieve/src/lib.rs @@ -12,29 +12,59 @@ pub struct AliasFile { pub default_domain: FQDN, } -#[derive(PartialEq, Eq, Clone)] -pub struct OrdEmailAddress(EmailAddress); +#[derive(PartialEq, Eq, Clone, Debug)] +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,20 +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, ); } @@ -95,29 +128,11 @@ 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>> @@ -190,6 +205,23 @@ mod tests { assert!(result.is_err()); } + #[test] + fn apostrophe_destination_detection() { + let result = parse_alias_to_map(vec![AliasFile { + 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()); + } + #[test] fn basic_parsing() { let result = parse_alias_to_map(vec![AliasFile { 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