mailsystem: Use newly added 'mailnix' package to generate postfix/dovecot files
This commit is contained in:
parent
955a0ec8ba
commit
c1b19d6e33
9 changed files with 52 additions and 85 deletions
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 =
|
||||||
|
|
|
||||||
|
|
@ -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 =
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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") {
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue