mailsystem: Use newly added 'mailnix' package to generate postfix/dovecot files

This commit is contained in:
Thomas Preisner 2025-02-23 16:31:02 +01:00
parent 955a0ec8ba
commit c1b19d6e33
9 changed files with 52 additions and 85 deletions

View file

@ -1,4 +1,8 @@
{config, ...}: let {
config,
pkgs,
...
}: let
cfg = config.mailsystem; cfg = config.mailsystem;
in rec { in rec {
certificateDirectory = "/var/certs"; certificateDirectory = "/var/certs";
@ -17,6 +21,11 @@ in rec {
then ["acme-finished-${cfg.fqdn}.target"] then ["acme-finished-${cfg.fqdn}.target"]
else ["mailsystem-selfsigned-certificate.service"]; else ["mailsystem-selfsigned-certificate.service"];
mailnixCfgFile = pkgs.writeText "mailnix-public.json" (builtins.toJSON {
inherit (cfg) accounts domains;
aliases = cfg.virtualAliases;
});
dovecotDynamicStateDir = "/var/lib/dovecot"; dovecotDynamicStateDir = "/var/lib/dovecot";
dovecotDynamicPasswdFile = "${dovecotDynamicStateDir}/passwd"; dovecotDynamicPasswdFile = "${dovecotDynamicStateDir}/passwd";

View file

@ -4,7 +4,7 @@
pkgs, pkgs,
... ...
}: }:
with (import ./common.nix {inherit config;}); let with (import ./common.nix {inherit config pkgs;}); let
cfg = config.mailsystem; cfg = config.mailsystem;
postfixCfg = config.services.postfix; postfixCfg = config.services.postfix;
dovecot2Cfg = config.services.dovecot2; 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 # Ensure passwd files are not world-readable at any time
umask 077 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 # Prepare static passwd-file for system users
cat <<EOF > "${staticPasswdFile}" ${pkgs.mailnix}/bin/mailnix ${mailnixCfgFile} generate-static-passdb > "${staticPasswdFile}"
${lib.concatStringsSep "\n" (lib.mapAttrsToList genPasswdEntry systemUsers)}
EOF
# Prepare initial passwd-file for dynamic users # Prepare/Update passwd-file for dynamic users
# (used for lookup during actual passwd-file generation) ${pkgs.mailnix}/bin/mailnix ${mailnixCfgFile} update-dynamic-passdb ${dovecotDynamicPasswdFile} > "${dovecotDynamicPasswdFile}"
cat <<EOF > "${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}"
${lib.optionalString cfg.roundcube.enable '' ${lib.optionalString cfg.roundcube.enable ''
# Ensure roundcube has access to dynamic passwd file # Ensure roundcube has access to dynamic passwd file
@ -83,12 +51,10 @@ with (import ./common.nix {inherit config;}); let
''} ''}
# Prepare userdb-file # Prepare userdb-file
cat <<EOF > "${userdbFile}" ${pkgs.mailnix}/bin/mailnix ${mailnixCfgFile} generate-userdb > "${userdbFile}"
${lib.concatStringsSep "\n" (lib.mapAttrsToList genUserdbEntry cfg.accounts)}
EOF
''; '';
genMaildir = pkgs.writeScript "generate-maildir" '' genMaildirScript = pkgs.writeScript "generate-maildir" ''
#!${pkgs.stdenv.shell} #!${pkgs.stdenv.shell}
# Create mail directory and set permissions accordingly. # Create mail directory and set permissions accordingly.
@ -295,7 +261,7 @@ in {
systemd.services.dovecot2 = { systemd.services.dovecot2 = {
preStart = '' preStart = ''
${genAuthDbsScript} ${genAuthDbsScript}
${genMaildir} ${genMaildirScript}
''; '';
wants = sslCertService; wants = sslCertService;
after = sslCertService; after = sslCertService;

View file

@ -4,7 +4,7 @@
lib, lib,
... ...
}: }:
with (import ./common.nix {inherit config;}); let with (import ./common.nix {inherit config pkgs;}); let
cfg = config.mailsystem; cfg = config.mailsystem;
in { in {
config = config =

View file

@ -4,42 +4,29 @@
pkgs, pkgs,
... ...
}: }:
with (import ./common.nix {inherit config;}); let with (import ./common.nix {inherit config pkgs;}); let
cfg = config.mailsystem; cfg = config.mailsystem;
mappedFile = name: "hash:/var/lib/postfix/conf/${name}"; mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
attrsToLookupTable = aliases: let runtimeDir = "/run/postfix";
lookupTables = lib.mapAttrsToList (from: to: {"${from}" = to;}) aliases; aliases_file = "${runtimeDir}/virtual_aliases";
in virtual_domains_file = "${runtimeDir}/virtual_domains";
mergeLookupTables lookupTables; denied_recipients_file = "${runtimeDir}/denied_recipients";
lookupTableToString = attrs: let genPostmapsScript = pkgs.writeScript "generate-postfix-postmaps" ''
isDomain = value: !(lib.hasInfix "@" value); #!${pkgs.stdenv.shell}
valueToString = value: set -euo pipefail
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);
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)); ${pkgs.mailnix}/bin/mailnix "${mailnixCfgFile}" "generate-aliases" > "${aliases_file}"
virtual_aliases = attrsToLookupTable cfg.virtualAliases; ${pkgs.mailnix}/bin/mailnix "${mailnixCfgFile}" "generate-domains" > "${virtual_domains_file}"
all_virtual_aliases = mergeLookupTables [virtual_accounts virtual_aliases]; ${pkgs.mailnix}/bin/mailnix "${mailnixCfgFile}" "generate-denied-recipients" > "${denied_recipients_file}"
'';
# 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);
submission_header_cleanup_rules = pkgs.writeText "submission_header_cleanup_rules" '' submission_header_cleanup_rules = pkgs.writeText "submission_header_cleanup_rules" ''
# Removes sensitive headers from mails handed in via the submission port. # Removes sensitive headers from mails handed in via the submission port.
@ -83,7 +70,6 @@ in {
mapFiles."virtual_aliases" = aliases_file; mapFiles."virtual_aliases" = aliases_file;
mapFiles."denied_recipients" = denied_recipients_file; mapFiles."denied_recipients" = denied_recipients_file;
virtual = lookupTableToString all_virtual_aliases;
submissionsOptions = { submissionsOptions = {
smtpd_tls_security_level = "encrypt"; smtpd_tls_security_level = "encrypt";
@ -110,6 +96,9 @@ in {
virtual_gid_maps = "static:${toString cfg.vmailUID}"; virtual_gid_maps = "static:${toString cfg.vmailUID}";
virtual_mailbox_base = cfg.mailDirectory; virtual_mailbox_base = cfg.mailDirectory;
virtual_mailbox_domains = virtual_domains_file; virtual_mailbox_domains = virtual_domains_file;
virtual_alias_maps = [
(mappedFile "virtual_aliases")
];
virtual_mailbox_maps = [ virtual_mailbox_maps = [
(mappedFile "virtual_aliases") (mappedFile "virtual_aliases")
]; ];
@ -193,6 +182,11 @@ in {
}; };
}; };
systemd.services.postfix-setup = {
preStart = ''
${genPostmapsScript}
'';
};
systemd.services.postfix = { systemd.services.postfix = {
wants = sslCertService; wants = sslCertService;
after = after =

View file

@ -4,7 +4,7 @@
pkgs, pkgs,
... ...
}: }:
with (import ./common.nix {inherit config;}); let with (import ./common.nix {inherit config pkgs;}); let
cfg = config.mailsystem; cfg = config.mailsystem;
roundcubeCfg = config.mailsystem.roundcube; roundcubeCfg = config.mailsystem.roundcube;
in { in {

View file

@ -4,7 +4,7 @@
pkgs, pkgs,
... ...
}: }:
with (import ./common.nix {inherit config;}); let with (import ./common.nix {inherit config pkgs;}); let
cfg = config.mailsystem; cfg = config.mailsystem;
nginxCfg = config.services.nginx; nginxCfg = config.services.nginx;
postfixCfg = config.services.postfix; postfixCfg = config.services.postfix;

View file

@ -4,7 +4,7 @@
lib, lib,
... ...
}: }:
with (import ./common.nix {inherit config;}); let with (import ./common.nix {inherit config pkgs;}); let
cfg = config.mailsystem; cfg = config.mailsystem;
in { in {
config = lib.mkIf (cfg.enable && cfg.certificateScheme == "selfsigned") { config = lib.mkIf (cfg.enable && cfg.certificateScheme == "selfsigned") {

View file

@ -134,18 +134,16 @@ impl Config {
// check whether aliases have corresponding accounts/domains // check whether aliases have corresponding accounts/domains
for (from, dests) in self.aliases.iter() { for (from, dests) in self.aliases.iter() {
let is_domain = from.contains("@"); let is_domain = !from.contains("@");
if is_domain && !self.accounts.contains_key(from) { if is_domain && !self.domains.contains(from) {
panic!("Aliased from-account \"{from}\" does not exist");
} else if !is_domain && !self.domains.contains(from) {
panic!("Aliased from-domain \"{from}\" does not exist"); panic!("Aliased from-domain \"{from}\" does not exist");
} }
for dest in dests.iter() { for dest in dests.iter() {
let is_domain = dest.contains("@"); let is_domain = !dest.contains("@");
if is_domain && !self.accounts.contains_key(dest) { if is_domain && !self.domains.contains(dest) {
panic!("Aliased dest-account \"{dest}\" does not exist");
} else if !is_domain && !self.domains.contains(dest) {
panic!("Aliased dest-domain \"{dest}\" does not exist"); 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");
} }
} }
} }

View file

@ -2,7 +2,7 @@
lib = pkgs.lib; lib = pkgs.lib;
in rec { in rec {
waitForRspamd = node: let 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 ]"; in "set +e; timeout 1 ${node.nixpkgs.pkgs.netcat}/bin/nc -U ${rspamdProxySocket} < /dev/null; [ $? -eq 124 ]";
mkHashedPasswordFile = password: mkHashedPasswordFile = password: