From c1b19d6e3355e8ed6c11fc75f1ebb92a81d8f87d Mon Sep 17 00:00:00 2001 From: Thomas Preisner Date: Sun, 23 Feb 2025 16:31:02 +0100 Subject: [PATCH] mailsystem: Use newly added 'mailnix' package to generate postfix/dovecot files --- mailsystem/common.nix | 11 +++++++- mailsystem/dovecot.nix | 48 +++++---------------------------- mailsystem/nginx.nix | 2 +- mailsystem/postfix.nix | 54 +++++++++++++++++--------------------- mailsystem/roundcube.nix | 2 +- mailsystem/rspamd.nix | 2 +- mailsystem/selfsigned.nix | 2 +- pkgs/mailnix/src/config.rs | 14 +++++----- tests/common/lib.nix | 2 +- 9 files changed, 52 insertions(+), 85 deletions(-) diff --git a/mailsystem/common.nix b/mailsystem/common.nix index ab7174c..2ff0a5e 100644 --- a/mailsystem/common.nix +++ b/mailsystem/common.nix @@ -1,4 +1,8 @@ -{config, ...}: let +{ + config, + pkgs, + ... +}: let cfg = config.mailsystem; in rec { certificateDirectory = "/var/certs"; @@ -17,6 +21,11 @@ in rec { then ["acme-finished-${cfg.fqdn}.target"] else ["mailsystem-selfsigned-certificate.service"]; + mailnixCfgFile = pkgs.writeText "mailnix-public.json" (builtins.toJSON { + inherit (cfg) accounts domains; + aliases = cfg.virtualAliases; + }); + dovecotDynamicStateDir = "/var/lib/dovecot"; dovecotDynamicPasswdFile = "${dovecotDynamicStateDir}/passwd"; diff --git a/mailsystem/dovecot.nix b/mailsystem/dovecot.nix index ac9ea3e..3b05719 100644 --- a/mailsystem/dovecot.nix +++ b/mailsystem/dovecot.nix @@ -4,7 +4,7 @@ pkgs, ... }: -with (import ./common.nix {inherit config;}); let +with (import ./common.nix {inherit config pkgs;}); let cfg = config.mailsystem; postfixCfg = config.services.postfix; dovecot2Cfg = config.services.dovecot2; @@ -39,43 +39,11 @@ with (import ./common.nix {inherit config;}); let # Ensure passwd files are not world-readable at any time umask 077 - # Ensure we have a file for every user's (initial) password hash. - for f in ${builtins.toString (lib.mapAttrsToList (user: value: value.hashedPasswordFile) cfg.accounts)}; do - if [ ! -f "$f" ]; then - echo "Expected password hash file $f does not exist!" - exit 1 - fi - done - # Prepare static passwd-file for system users - cat < "${staticPasswdFile}" - ${lib.concatStringsSep "\n" (lib.mapAttrsToList genPasswdEntry systemUsers)} - EOF + ${pkgs.mailnix}/bin/mailnix ${mailnixCfgFile} generate-static-passdb > "${staticPasswdFile}" - # Prepare initial passwd-file for dynamic users - # (used for lookup during actual passwd-file generation) - cat < "${initialPasswdFile}" - ${lib.concatStringsSep "\n" (lib.mapAttrsToList genPasswdEntry normalUsers)} - EOF - - # Check for existence of dynamic passwd-file - touch "${dovecotDynamicPasswdFile}" - if (! test -f "${dovecotDynamicPasswdFile}"); then - echo "${dovecotDynamicPasswdFile} exists and is no regular file" - exit 1 - fi - # Ensure that only configured users are actually present and remove any others - truncate -s 0 "${dovecotDynamicPasswdFile}-filtered" - for u in ${builtins.toString (lib.mapAttrsToList (user: value: value.name) normalUsers)}; do - if grep -q "^$u:" "${dovecotDynamicPasswdFile}"; then - # User already has some password set -> Keep currently set password - grep "^$u:" "${dovecotDynamicPasswdFile}" >> "${dovecotDynamicPasswdFile}-filtered" - else - # User has no password set, yet -> Take password from initialPasswdFile - grep "^$u:" "${initialPasswdFile}" >> "${dovecotDynamicPasswdFile}-filtered" - fi - done - mv "${dovecotDynamicPasswdFile}-filtered" "${dovecotDynamicPasswdFile}" + # Prepare/Update passwd-file for dynamic users + ${pkgs.mailnix}/bin/mailnix ${mailnixCfgFile} update-dynamic-passdb ${dovecotDynamicPasswdFile} > "${dovecotDynamicPasswdFile}" ${lib.optionalString cfg.roundcube.enable '' # Ensure roundcube has access to dynamic passwd file @@ -83,12 +51,10 @@ with (import ./common.nix {inherit config;}); let ''} # Prepare userdb-file - cat < "${userdbFile}" - ${lib.concatStringsSep "\n" (lib.mapAttrsToList genUserdbEntry cfg.accounts)} - EOF + ${pkgs.mailnix}/bin/mailnix ${mailnixCfgFile} generate-userdb > "${userdbFile}" ''; - genMaildir = pkgs.writeScript "generate-maildir" '' + genMaildirScript = pkgs.writeScript "generate-maildir" '' #!${pkgs.stdenv.shell} # Create mail directory and set permissions accordingly. @@ -295,7 +261,7 @@ in { systemd.services.dovecot2 = { preStart = '' ${genAuthDbsScript} - ${genMaildir} + ${genMaildirScript} ''; wants = sslCertService; after = sslCertService; diff --git a/mailsystem/nginx.nix b/mailsystem/nginx.nix index c4f01a0..919bf7c 100644 --- a/mailsystem/nginx.nix +++ b/mailsystem/nginx.nix @@ -4,7 +4,7 @@ lib, ... }: -with (import ./common.nix {inherit config;}); let +with (import ./common.nix {inherit config pkgs;}); let cfg = config.mailsystem; in { config = diff --git a/mailsystem/postfix.nix b/mailsystem/postfix.nix index e5ebbfa..f6c532f 100644 --- a/mailsystem/postfix.nix +++ b/mailsystem/postfix.nix @@ -4,42 +4,29 @@ pkgs, ... }: -with (import ./common.nix {inherit config;}); let +with (import ./common.nix {inherit config pkgs;}); let cfg = config.mailsystem; mappedFile = name: "hash:/var/lib/postfix/conf/${name}"; - attrsToLookupTable = aliases: let - lookupTables = lib.mapAttrsToList (from: to: {"${from}" = to;}) aliases; - in - mergeLookupTables lookupTables; + runtimeDir = "/run/postfix"; + aliases_file = "${runtimeDir}/virtual_aliases"; + virtual_domains_file = "${runtimeDir}/virtual_domains"; + denied_recipients_file = "${runtimeDir}/denied_recipients"; - lookupTableToString = attrs: let - isDomain = value: !(lib.hasInfix "@" value); - valueToString = value: - if (isDomain value) - then "@${value}" - else value; - listToString = list: lib.concatStringsSep ", " (map valueToString list); - in - lib.concatStringsSep "\n" (lib.mapAttrsToList (name: list: "${valueToString name} ${listToString list}") attrs); + genPostmapsScript = pkgs.writeScript "generate-postfix-postmaps" '' + #!${pkgs.stdenv.shell} + set -euo pipefail - mergeLookupTables = tables: lib.zipAttrsWith (n: v: lib.flatten v) tables; + if (! test -d "${runtimeDir}"); then + mkdir "${runtimeDir}" + chmod 755 "${runtimeDir}" + fi - virtual_accounts = mergeLookupTables (lib.map (name: {"${name}" = name;}) (lib.attrNames cfg.accounts)); - virtual_aliases = attrsToLookupTable cfg.virtualAliases; - all_virtual_aliases = mergeLookupTables [virtual_accounts virtual_aliases]; - - # File containing all mappings of aliases/authenticated accounts and their sender mail addresses. - aliases_file = let - content = lookupTableToString all_virtual_aliases; - in - builtins.toFile "virtual_aliases" content; - - virtual_domains_file = builtins.toFile "virtual_domains" (lib.concatStringsSep "\n" cfg.domains); - - denied_recipients = map (account: "${account.name} REJECT ${account.rejectMessage}") (lib.filter (account: account.isSystemUser) (lib.attrValues cfg.accounts)); - denied_recipients_file = builtins.toFile "denied_recipients" (lib.concatStringsSep "\n" denied_recipients); + ${pkgs.mailnix}/bin/mailnix "${mailnixCfgFile}" "generate-aliases" > "${aliases_file}" + ${pkgs.mailnix}/bin/mailnix "${mailnixCfgFile}" "generate-domains" > "${virtual_domains_file}" + ${pkgs.mailnix}/bin/mailnix "${mailnixCfgFile}" "generate-denied-recipients" > "${denied_recipients_file}" + ''; submission_header_cleanup_rules = pkgs.writeText "submission_header_cleanup_rules" '' # Removes sensitive headers from mails handed in via the submission port. @@ -83,7 +70,6 @@ in { mapFiles."virtual_aliases" = aliases_file; mapFiles."denied_recipients" = denied_recipients_file; - virtual = lookupTableToString all_virtual_aliases; submissionsOptions = { smtpd_tls_security_level = "encrypt"; @@ -110,6 +96,9 @@ in { virtual_gid_maps = "static:${toString cfg.vmailUID}"; virtual_mailbox_base = cfg.mailDirectory; virtual_mailbox_domains = virtual_domains_file; + virtual_alias_maps = [ + (mappedFile "virtual_aliases") + ]; virtual_mailbox_maps = [ (mappedFile "virtual_aliases") ]; @@ -193,6 +182,11 @@ in { }; }; + systemd.services.postfix-setup = { + preStart = '' + ${genPostmapsScript} + ''; + }; systemd.services.postfix = { wants = sslCertService; after = diff --git a/mailsystem/roundcube.nix b/mailsystem/roundcube.nix index b05fb25..4c11c51 100644 --- a/mailsystem/roundcube.nix +++ b/mailsystem/roundcube.nix @@ -4,7 +4,7 @@ pkgs, ... }: -with (import ./common.nix {inherit config;}); let +with (import ./common.nix {inherit config pkgs;}); let cfg = config.mailsystem; roundcubeCfg = config.mailsystem.roundcube; in { diff --git a/mailsystem/rspamd.nix b/mailsystem/rspamd.nix index 847aba4..7955b9f 100644 --- a/mailsystem/rspamd.nix +++ b/mailsystem/rspamd.nix @@ -4,7 +4,7 @@ pkgs, ... }: -with (import ./common.nix {inherit config;}); let +with (import ./common.nix {inherit config pkgs;}); let cfg = config.mailsystem; nginxCfg = config.services.nginx; postfixCfg = config.services.postfix; diff --git a/mailsystem/selfsigned.nix b/mailsystem/selfsigned.nix index 4506fcf..1a27318 100644 --- a/mailsystem/selfsigned.nix +++ b/mailsystem/selfsigned.nix @@ -4,7 +4,7 @@ lib, ... }: -with (import ./common.nix {inherit config;}); let +with (import ./common.nix {inherit config pkgs;}); let cfg = config.mailsystem; in { config = lib.mkIf (cfg.enable && cfg.certificateScheme == "selfsigned") { diff --git a/pkgs/mailnix/src/config.rs b/pkgs/mailnix/src/config.rs index 0fc016d..9e3240f 100644 --- a/pkgs/mailnix/src/config.rs +++ b/pkgs/mailnix/src/config.rs @@ -134,18 +134,16 @@ impl Config { // check whether aliases have corresponding accounts/domains for (from, dests) in self.aliases.iter() { - let is_domain = from.contains("@"); - if is_domain && !self.accounts.contains_key(from) { - panic!("Aliased from-account \"{from}\" does not exist"); - } else if !is_domain && !self.domains.contains(from) { + let is_domain = !from.contains("@"); + if is_domain && !self.domains.contains(from) { panic!("Aliased from-domain \"{from}\" does not exist"); } for dest in dests.iter() { - let is_domain = dest.contains("@"); - if is_domain && !self.accounts.contains_key(dest) { - panic!("Aliased dest-account \"{dest}\" does not exist"); - } else if !is_domain && !self.domains.contains(dest) { + let is_domain = !dest.contains("@"); + if is_domain && !self.domains.contains(dest) { panic!("Aliased dest-domain \"{dest}\" does not exist"); + } else if !is_domain && !self.accounts.contains_key(dest) { + panic!("Aliased dest-account \"{dest}\" does not exist"); } } } diff --git a/tests/common/lib.nix b/tests/common/lib.nix index 1930be9..a2b1a15 100644 --- a/tests/common/lib.nix +++ b/tests/common/lib.nix @@ -2,7 +2,7 @@ lib = pkgs.lib; in rec { waitForRspamd = node: let - inherit (import ../../mailsystem/common.nix {inherit (node) config;}) rspamdProxySocket; + inherit (import ../../mailsystem/common.nix {inherit (node) config pkgs;}) rspamdProxySocket; in "set +e; timeout 1 ${node.nixpkgs.pkgs.netcat}/bin/nc -U ${rspamdProxySocket} < /dev/null; [ $? -eq 124 ]"; mkHashedPasswordFile = password: