treewide: reformat with nixfmt-rfc-style
This commit is contained in:
parent
03433d472f
commit
1a7f3d718c
21 changed files with 2086 additions and 1680 deletions
|
@ -14,28 +14,44 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.mailserver.borgbackup;
|
||||
|
||||
methodFragment = lib.optional (cfg.compression.method != null) cfg.compression.method;
|
||||
autoFragment =
|
||||
if cfg.compression.auto && cfg.compression.method == null
|
||||
then throw "compression.method must be set when using auto."
|
||||
else lib.optional cfg.compression.auto "auto";
|
||||
if cfg.compression.auto && cfg.compression.method == null then
|
||||
throw "compression.method must be set when using auto."
|
||||
else
|
||||
lib.optional cfg.compression.auto "auto";
|
||||
levelFragment =
|
||||
if cfg.compression.level != null && cfg.compression.method == null
|
||||
then throw "compression.method must be set when using compression.level."
|
||||
else lib.optional (cfg.compression.level != null) (toString cfg.compression.level);
|
||||
compressionFragment = lib.concatStringsSep "," (lib.flatten [autoFragment methodFragment levelFragment]);
|
||||
if cfg.compression.level != null && cfg.compression.method == null then
|
||||
throw "compression.method must be set when using compression.level."
|
||||
else
|
||||
lib.optional (cfg.compression.level != null) (toString cfg.compression.level);
|
||||
compressionFragment = lib.concatStringsSep "," (
|
||||
lib.flatten [
|
||||
autoFragment
|
||||
methodFragment
|
||||
levelFragment
|
||||
]
|
||||
);
|
||||
compression = lib.optionalString (compressionFragment != "") "--compression ${compressionFragment}";
|
||||
|
||||
encryptionFragment = cfg.encryption.method;
|
||||
passphraseFile = lib.escapeShellArg cfg.encryption.passphraseFile;
|
||||
passphraseFragment = lib.optionalString (cfg.encryption.method != "none")
|
||||
(if cfg.encryption.passphraseFile != null then ''env BORG_PASSPHRASE="$(cat ${passphraseFile})"''
|
||||
else throw "passphraseFile must be set when using encryption.");
|
||||
passphraseFragment = lib.optionalString (cfg.encryption.method != "none") (
|
||||
if cfg.encryption.passphraseFile != null then
|
||||
''env BORG_PASSPHRASE="$(cat ${passphraseFile})"''
|
||||
else
|
||||
throw "passphraseFile must be set when using encryption."
|
||||
);
|
||||
|
||||
locations = lib.escapeShellArgs cfg.locations;
|
||||
name = lib.escapeShellArg cfg.name;
|
||||
|
@ -55,7 +71,8 @@ let
|
|||
${passphraseFragment} ${pkgs.borgbackup}/bin/borg create ${extraCreateArgs} ${compression} ::${name} ${locations}
|
||||
${cmdPostexec}
|
||||
'';
|
||||
in {
|
||||
in
|
||||
{
|
||||
config = lib.mkIf (config.mailserver.enable && cfg.enable) {
|
||||
environment.systemPackages = with pkgs; [
|
||||
borgbackup
|
||||
|
|
|
@ -14,57 +14,76 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
{ config, pkgs, lib }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.mailserver;
|
||||
in
|
||||
{
|
||||
# cert :: PATH
|
||||
certificatePath = if cfg.certificateScheme == "manual"
|
||||
then cfg.certificateFile
|
||||
else if cfg.certificateScheme == "selfsigned"
|
||||
then "${cfg.certificateDirectory}/cert-${cfg.fqdn}.pem"
|
||||
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"
|
||||
then "${config.security.acme.certs.${cfg.acmeCertificateName}.directory}/fullchain.pem"
|
||||
else throw "unknown certificate scheme";
|
||||
certificatePath =
|
||||
if cfg.certificateScheme == "manual" then
|
||||
cfg.certificateFile
|
||||
else if cfg.certificateScheme == "selfsigned" then
|
||||
"${cfg.certificateDirectory}/cert-${cfg.fqdn}.pem"
|
||||
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx" then
|
||||
"${config.security.acme.certs.${cfg.acmeCertificateName}.directory}/fullchain.pem"
|
||||
else
|
||||
throw "unknown certificate scheme";
|
||||
|
||||
# key :: PATH
|
||||
keyPath = if cfg.certificateScheme == "manual"
|
||||
then cfg.keyFile
|
||||
else if cfg.certificateScheme == "selfsigned"
|
||||
then "${cfg.certificateDirectory}/key-${cfg.fqdn}.pem"
|
||||
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"
|
||||
then "${config.security.acme.certs.${cfg.acmeCertificateName}.directory}/key.pem"
|
||||
else throw "unknown certificate scheme";
|
||||
keyPath =
|
||||
if cfg.certificateScheme == "manual" then
|
||||
cfg.keyFile
|
||||
else if cfg.certificateScheme == "selfsigned" then
|
||||
"${cfg.certificateDirectory}/key-${cfg.fqdn}.pem"
|
||||
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx" then
|
||||
"${config.security.acme.certs.${cfg.acmeCertificateName}.directory}/key.pem"
|
||||
else
|
||||
throw "unknown certificate scheme";
|
||||
|
||||
passwordFiles = let
|
||||
mkHashFile = name: hash: pkgs.writeText "${builtins.hashString "sha256" name}-password-hash" hash;
|
||||
in
|
||||
lib.mapAttrs (name: value:
|
||||
if value.hashedPasswordFile == null then
|
||||
builtins.toString (mkHashFile name value.hashedPassword)
|
||||
else value.hashedPasswordFile) cfg.loginAccounts;
|
||||
passwordFiles =
|
||||
let
|
||||
mkHashFile = name: hash: pkgs.writeText "${builtins.hashString "sha256" name}-password-hash" hash;
|
||||
in
|
||||
lib.mapAttrs (
|
||||
name: value:
|
||||
if value.hashedPasswordFile == null then
|
||||
builtins.toString (mkHashFile name value.hashedPassword)
|
||||
else
|
||||
value.hashedPasswordFile
|
||||
) cfg.loginAccounts;
|
||||
|
||||
# Appends the LDAP bind password to files to avoid writing this
|
||||
# password into the Nix store.
|
||||
appendLdapBindPwd = {
|
||||
name, file, prefix, suffix ? "", passwordFile, destination
|
||||
}: pkgs.writeScript "append-ldap-bind-pwd-in-${name}" ''
|
||||
#!${pkgs.stdenv.shell}
|
||||
set -euo pipefail
|
||||
appendLdapBindPwd =
|
||||
{
|
||||
name,
|
||||
file,
|
||||
prefix,
|
||||
suffix ? "",
|
||||
passwordFile,
|
||||
destination,
|
||||
}:
|
||||
pkgs.writeScript "append-ldap-bind-pwd-in-${name}" ''
|
||||
#!${pkgs.stdenv.shell}
|
||||
set -euo pipefail
|
||||
|
||||
baseDir=$(dirname ${destination})
|
||||
if (! test -d "$baseDir"); then
|
||||
mkdir -p $baseDir
|
||||
chmod 755 $baseDir
|
||||
fi
|
||||
baseDir=$(dirname ${destination})
|
||||
if (! test -d "$baseDir"); then
|
||||
mkdir -p $baseDir
|
||||
chmod 755 $baseDir
|
||||
fi
|
||||
|
||||
cat ${file} > ${destination}
|
||||
echo -n '${prefix}' >> ${destination}
|
||||
cat ${passwordFile} | tr -d '\n' >> ${destination}
|
||||
echo -n '${suffix}' >> ${destination}
|
||||
chmod 600 ${destination}
|
||||
'';
|
||||
cat ${file} > ${destination}
|
||||
echo -n '${prefix}' >> ${destination}
|
||||
cat ${passwordFile} | tr -d '\n' >> ${destination}
|
||||
echo -n '${suffix}' >> ${destination}
|
||||
chmod 600 ${destination}
|
||||
'';
|
||||
|
||||
}
|
||||
|
|
|
@ -14,7 +14,12 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
with (import ./common.nix { inherit config pkgs lib; });
|
||||
|
||||
|
@ -28,10 +33,14 @@ let
|
|||
ldapConfFile = "${passwdDir}/dovecot-ldap.conf.ext";
|
||||
boolToYesNo = x: if x then "yes" else "no";
|
||||
listToLine = lib.concatStringsSep " ";
|
||||
listToMultiAttrs = keyPrefix: attrs: lib.listToAttrs (lib.imap1 (n: x: {
|
||||
name = "${keyPrefix}${if n==1 then "" else toString n}";
|
||||
value = x;
|
||||
}) attrs);
|
||||
listToMultiAttrs =
|
||||
keyPrefix: attrs:
|
||||
lib.listToAttrs (
|
||||
lib.imap1 (n: x: {
|
||||
name = "${keyPrefix}${if n == 1 then "" else toString n}";
|
||||
value = x;
|
||||
}) attrs
|
||||
);
|
||||
|
||||
maildirLayoutAppendix = lib.optionalString cfg.useFsLayout ":LAYOUT=fs";
|
||||
maildirUTF8FolderNames = lib.optionalString cfg.useUTF8FolderNames ":UTF-8";
|
||||
|
@ -39,9 +48,7 @@ let
|
|||
# maildir in format "/${domain}/${user}"
|
||||
dovecotMaildir =
|
||||
"maildir:${cfg.mailDirectory}/%{domain}/%{username}${maildirLayoutAppendix}${maildirUTF8FolderNames}"
|
||||
+ (lib.optionalString (cfg.indexDir != null)
|
||||
":INDEX=${cfg.indexDir}/%{domain}/%{username}"
|
||||
);
|
||||
+ (lib.optionalString (cfg.indexDir != null) ":INDEX=${cfg.indexDir}/%{domain}/%{username}");
|
||||
|
||||
postfixCfg = config.services.postfix;
|
||||
|
||||
|
@ -51,7 +58,7 @@ let
|
|||
ldap_version = 3
|
||||
uris = ${lib.concatStringsSep " " cfg.ldap.uris}
|
||||
${lib.optionalString cfg.ldap.startTls ''
|
||||
tls = yes
|
||||
tls = yes
|
||||
''}
|
||||
tls_require_cert = hard
|
||||
tls_ca_cert_file = ${cfg.ldap.tlsCAFile}
|
||||
|
@ -61,11 +68,11 @@ let
|
|||
base = ${cfg.ldap.searchBase}
|
||||
scope = ${mkLdapSearchScope cfg.ldap.searchScope}
|
||||
${lib.optionalString (cfg.ldap.dovecot.userAttrs != null) ''
|
||||
user_attrs = ${cfg.ldap.dovecot.userAttrs}
|
||||
user_attrs = ${cfg.ldap.dovecot.userAttrs}
|
||||
''}
|
||||
user_filter = ${cfg.ldap.dovecot.userFilter}
|
||||
${lib.optionalString (cfg.ldap.dovecot.passAttrs != "") ''
|
||||
pass_attrs = ${cfg.ldap.dovecot.passAttrs}
|
||||
pass_attrs = ${cfg.ldap.dovecot.passAttrs}
|
||||
''}
|
||||
pass_filter = ${cfg.ldap.dovecot.passFilter}
|
||||
'';
|
||||
|
@ -93,7 +100,9 @@ let
|
|||
# Prevent world-readable password files, even temporarily.
|
||||
umask 077
|
||||
|
||||
for f in ${builtins.toString (lib.mapAttrsToList (name: _: passwordFiles."${name}") cfg.loginAccounts)}; do
|
||||
for f in ${
|
||||
builtins.toString (lib.mapAttrsToList (name: _: passwordFiles."${name}") cfg.loginAccounts)
|
||||
}; do
|
||||
if [ ! -f "$f" ]; then
|
||||
echo "Expected password hash file $f does not exist!"
|
||||
exit 1
|
||||
|
@ -101,34 +110,49 @@ let
|
|||
done
|
||||
|
||||
cat <<EOF > ${passwdFile}
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: _:
|
||||
"${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::"
|
||||
) cfg.loginAccounts)}
|
||||
${lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList (
|
||||
name: _: "${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::"
|
||||
) cfg.loginAccounts
|
||||
)}
|
||||
EOF
|
||||
|
||||
cat <<EOF > ${userdbFile}
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value:
|
||||
"${name}:::::::"
|
||||
${lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList (
|
||||
name: value:
|
||||
"${name}:::::::"
|
||||
+ lib.optionalString (value.quota != null) "userdb_quota_rule=*:storage=${value.quota}"
|
||||
) cfg.loginAccounts)}
|
||||
) cfg.loginAccounts
|
||||
)}
|
||||
EOF
|
||||
'';
|
||||
|
||||
junkMailboxes = builtins.attrNames (lib.filterAttrs (_: v: v ? "specialUse" && v.specialUse == "Junk") cfg.mailboxes);
|
||||
junkMailboxes = builtins.attrNames (
|
||||
lib.filterAttrs (_: v: v ? "specialUse" && v.specialUse == "Junk") cfg.mailboxes
|
||||
);
|
||||
junkMailboxNumber = builtins.length junkMailboxes;
|
||||
# The assertion garantees there is exactly one Junk mailbox.
|
||||
junkMailboxName = if junkMailboxNumber == 1 then builtins.elemAt junkMailboxes 0 else "";
|
||||
|
||||
mkLdapSearchScope = scope: (
|
||||
if scope == "sub" then "subtree"
|
||||
else if scope == "one" then "onelevel"
|
||||
else scope
|
||||
);
|
||||
mkLdapSearchScope =
|
||||
scope:
|
||||
(
|
||||
if scope == "sub" then
|
||||
"subtree"
|
||||
else if scope == "one" then
|
||||
"onelevel"
|
||||
else
|
||||
scope
|
||||
);
|
||||
|
||||
ftsPluginSettings = {
|
||||
fts = "flatcurve";
|
||||
fts_languages = listToLine cfg.fullTextSearch.languages;
|
||||
fts_tokenizers = listToLine [ "generic" "email-address" ];
|
||||
fts_tokenizers = listToLine [
|
||||
"generic"
|
||||
"email-address"
|
||||
];
|
||||
fts_tokenizer_email_address = "maxlen=100"; # default 254 too large for Xapian
|
||||
fts_flatcurve_substring_search = boolToYesNo cfg.fullTextSearch.substringSearch;
|
||||
fts_filters = listToLine cfg.fullTextSearch.filters;
|
||||
|
@ -139,255 +163,283 @@ let
|
|||
|
||||
in
|
||||
{
|
||||
config = with cfg; lib.mkIf enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = junkMailboxNumber == 1;
|
||||
message = "nixos-mailserver requires exactly one dovecot mailbox with the 'special use' flag set to 'Junk' (${builtins.toString junkMailboxNumber} have been found)";
|
||||
}
|
||||
];
|
||||
|
||||
warnings =
|
||||
lib.optional (
|
||||
(builtins.length cfg.fullTextSearch.languages > 1) &&
|
||||
(builtins.elem "stopwords" cfg.fullTextSearch.filters)
|
||||
) ''
|
||||
Using stopwords in `mailserver.fullTextSearch.filters` with multiple
|
||||
languages in `mailserver.fullTextSearch.languages` configured WILL
|
||||
cause some searches to fail.
|
||||
|
||||
The recommended solution is to NOT use the stopword filter when
|
||||
multiple languages are present in the configuration.
|
||||
''
|
||||
;
|
||||
|
||||
# for sieve-test. Shelling it in on demand usually doesnt' work, as it reads
|
||||
# the global config and tries to open shared libraries configured in there,
|
||||
# which are usually not compatible.
|
||||
environment.systemPackages = [
|
||||
pkgs.dovecot_pigeonhole
|
||||
] ++ lib.optional cfg.fullTextSearch.enable pkgs.dovecot-fts-flatcurve;
|
||||
|
||||
# For compatibility with python imaplib
|
||||
environment.etc."dovecot/modules".source = "/run/current-system/sw/lib/dovecot/modules";
|
||||
|
||||
services.dovecot2 = {
|
||||
enable = true;
|
||||
enableImap = enableImap || enableImapSsl;
|
||||
enablePop3 = enablePop3 || enablePop3Ssl;
|
||||
enablePAM = false;
|
||||
enableQuota = true;
|
||||
mailGroup = vmailGroupName;
|
||||
mailUser = vmailUserName;
|
||||
mailLocation = dovecotMaildir;
|
||||
sslServerCert = certificatePath;
|
||||
sslServerKey = keyPath;
|
||||
enableDHE = lib.mkDefault false;
|
||||
enableLmtp = true;
|
||||
mailPlugins.globally.enable = lib.optionals cfg.fullTextSearch.enable [
|
||||
"fts"
|
||||
"fts_flatcurve"
|
||||
config =
|
||||
with cfg;
|
||||
lib.mkIf enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = junkMailboxNumber == 1;
|
||||
message = "nixos-mailserver requires exactly one dovecot mailbox with the 'special use' flag set to 'Junk' (${builtins.toString junkMailboxNumber} have been found)";
|
||||
}
|
||||
];
|
||||
protocols = lib.optional cfg.enableManageSieve "sieve";
|
||||
|
||||
pluginSettings = {
|
||||
sieve = "file:${cfg.sieveDirectory}/%{user}/scripts;active=${cfg.sieveDirectory}/%{user}/active.sieve";
|
||||
sieve_default = "file:${cfg.sieveDirectory}/%{user}/default.sieve";
|
||||
sieve_default_name = "default";
|
||||
} // (lib.optionalAttrs cfg.fullTextSearch.enable ftsPluginSettings);
|
||||
warnings =
|
||||
lib.optional
|
||||
(
|
||||
(builtins.length cfg.fullTextSearch.languages > 1)
|
||||
&& (builtins.elem "stopwords" cfg.fullTextSearch.filters)
|
||||
)
|
||||
''
|
||||
Using stopwords in `mailserver.fullTextSearch.filters` with multiple
|
||||
languages in `mailserver.fullTextSearch.languages` configured WILL
|
||||
cause some searches to fail.
|
||||
|
||||
sieve = {
|
||||
extensions = [
|
||||
"fileinto"
|
||||
The recommended solution is to NOT use the stopword filter when
|
||||
multiple languages are present in the configuration.
|
||||
'';
|
||||
|
||||
# for sieve-test. Shelling it in on demand usually doesnt' work, as it reads
|
||||
# the global config and tries to open shared libraries configured in there,
|
||||
# which are usually not compatible.
|
||||
environment.systemPackages = [
|
||||
pkgs.dovecot_pigeonhole
|
||||
] ++ lib.optional cfg.fullTextSearch.enable pkgs.dovecot-fts-flatcurve;
|
||||
|
||||
# For compatibility with python imaplib
|
||||
environment.etc."dovecot/modules".source = "/run/current-system/sw/lib/dovecot/modules";
|
||||
|
||||
services.dovecot2 = {
|
||||
enable = true;
|
||||
enableImap = enableImap || enableImapSsl;
|
||||
enablePop3 = enablePop3 || enablePop3Ssl;
|
||||
enablePAM = false;
|
||||
enableQuota = true;
|
||||
mailGroup = vmailGroupName;
|
||||
mailUser = vmailUserName;
|
||||
mailLocation = dovecotMaildir;
|
||||
sslServerCert = certificatePath;
|
||||
sslServerKey = keyPath;
|
||||
enableDHE = lib.mkDefault false;
|
||||
enableLmtp = true;
|
||||
mailPlugins.globally.enable = lib.optionals cfg.fullTextSearch.enable [
|
||||
"fts"
|
||||
"fts_flatcurve"
|
||||
];
|
||||
protocols = lib.optional cfg.enableManageSieve "sieve";
|
||||
|
||||
scripts.after = builtins.toFile "spam.sieve" ''
|
||||
require "fileinto";
|
||||
pluginSettings = {
|
||||
sieve = "file:${cfg.sieveDirectory}/%{user}/scripts;active=${cfg.sieveDirectory}/%{user}/active.sieve";
|
||||
sieve_default = "file:${cfg.sieveDirectory}/%{user}/default.sieve";
|
||||
sieve_default_name = "default";
|
||||
} // (lib.optionalAttrs cfg.fullTextSearch.enable ftsPluginSettings);
|
||||
|
||||
if header :is "X-Spam" "Yes" {
|
||||
fileinto "${junkMailboxName}";
|
||||
stop;
|
||||
sieve = {
|
||||
extensions = [
|
||||
"fileinto"
|
||||
];
|
||||
|
||||
scripts.after = builtins.toFile "spam.sieve" ''
|
||||
require "fileinto";
|
||||
|
||||
if header :is "X-Spam" "Yes" {
|
||||
fileinto "${junkMailboxName}";
|
||||
stop;
|
||||
}
|
||||
'';
|
||||
|
||||
pipeBins = map lib.getExe [
|
||||
(pkgs.writeShellScriptBin "rspamd-learn-ham.sh" "exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_ham")
|
||||
(pkgs.writeShellScriptBin "rspamd-learn-spam.sh" "exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_spam")
|
||||
];
|
||||
};
|
||||
|
||||
imapsieve.mailbox = [
|
||||
{
|
||||
name = junkMailboxName;
|
||||
causes = [
|
||||
"COPY"
|
||||
"APPEND"
|
||||
];
|
||||
before = ./dovecot/imap_sieve/report-spam.sieve;
|
||||
}
|
||||
{
|
||||
name = "*";
|
||||
from = junkMailboxName;
|
||||
causes = [ "COPY" ];
|
||||
before = ./dovecot/imap_sieve/report-ham.sieve;
|
||||
}
|
||||
'';
|
||||
|
||||
pipeBins = map lib.getExe [
|
||||
(pkgs.writeShellScriptBin "rspamd-learn-ham.sh"
|
||||
"exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_ham")
|
||||
(pkgs.writeShellScriptBin "rspamd-learn-spam.sh"
|
||||
"exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_spam")
|
||||
];
|
||||
|
||||
mailboxes = cfg.mailboxes;
|
||||
|
||||
extraConfig = ''
|
||||
#Extra Config
|
||||
${lib.optionalString debug ''
|
||||
mail_debug = yes
|
||||
auth_debug = yes
|
||||
verbose_ssl = yes
|
||||
''}
|
||||
|
||||
${lib.optionalString (cfg.enableImap || cfg.enableImapSsl) ''
|
||||
service imap-login {
|
||||
inet_listener imap {
|
||||
${
|
||||
if cfg.enableImap then
|
||||
''
|
||||
port = 143
|
||||
''
|
||||
else
|
||||
''
|
||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||
port = 0
|
||||
''
|
||||
}
|
||||
}
|
||||
inet_listener imaps {
|
||||
${
|
||||
if cfg.enableImapSsl then
|
||||
''
|
||||
port = 993
|
||||
ssl = yes
|
||||
''
|
||||
else
|
||||
''
|
||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||
port = 0
|
||||
''
|
||||
}
|
||||
}
|
||||
}
|
||||
''}
|
||||
${lib.optionalString (cfg.enablePop3 || cfg.enablePop3Ssl) ''
|
||||
service pop3-login {
|
||||
inet_listener pop3 {
|
||||
${
|
||||
if cfg.enablePop3 then
|
||||
''
|
||||
port = 110
|
||||
''
|
||||
else
|
||||
''
|
||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||
port = 0
|
||||
''
|
||||
}
|
||||
}
|
||||
inet_listener pop3s {
|
||||
${
|
||||
if cfg.enablePop3Ssl then
|
||||
''
|
||||
port = 995
|
||||
ssl = yes
|
||||
''
|
||||
else
|
||||
''
|
||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||
port = 0
|
||||
''
|
||||
}
|
||||
}
|
||||
}
|
||||
''}
|
||||
|
||||
protocol imap {
|
||||
mail_max_userip_connections = ${toString cfg.maxConnectionsPerUser}
|
||||
mail_plugins = $mail_plugins imap_sieve
|
||||
}
|
||||
|
||||
service imap {
|
||||
vsz_limit = ${builtins.toString cfg.imapMemoryLimit} MB
|
||||
}
|
||||
|
||||
protocol pop3 {
|
||||
mail_max_userip_connections = ${toString cfg.maxConnectionsPerUser}
|
||||
}
|
||||
|
||||
mail_access_groups = ${vmailGroupName}
|
||||
|
||||
# https://ssl-config.mozilla.org/#server=dovecot&version=2.3.21&config=intermediate&openssl=3.4.1&guideline=5.7
|
||||
ssl = required
|
||||
ssl_min_protocol = TLSv1.2
|
||||
ssl_prefer_server_ciphers = no
|
||||
ssl_curve_list = X25519:prime256v1:secp384r1
|
||||
|
||||
service lmtp {
|
||||
unix_listener dovecot-lmtp {
|
||||
group = ${postfixCfg.group}
|
||||
mode = 0600
|
||||
user = ${postfixCfg.user}
|
||||
}
|
||||
vsz_limit = ${builtins.toString cfg.lmtpMemoryLimit} MB
|
||||
}
|
||||
|
||||
service quota-status {
|
||||
inet_listener {
|
||||
port = 0
|
||||
}
|
||||
unix_listener quota-status {
|
||||
user = postfix
|
||||
}
|
||||
vsz_limit = ${builtins.toString cfg.quotaStatusMemoryLimit} MB
|
||||
}
|
||||
|
||||
recipient_delimiter = ${cfg.recipientDelimiter}
|
||||
lmtp_save_to_detail_mailbox = ${cfg.lmtpSaveToDetailMailbox}
|
||||
|
||||
protocol lmtp {
|
||||
mail_plugins = $mail_plugins sieve
|
||||
}
|
||||
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
args = ${passwdFile}
|
||||
}
|
||||
|
||||
userdb {
|
||||
driver = passwd-file
|
||||
args = ${userdbFile}
|
||||
default_fields = uid=${builtins.toString cfg.vmailUID} gid=${builtins.toString cfg.vmailUID} home=${cfg.mailDirectory}
|
||||
}
|
||||
|
||||
${lib.optionalString cfg.ldap.enable ''
|
||||
passdb {
|
||||
driver = ldap
|
||||
args = ${ldapConfFile}
|
||||
}
|
||||
|
||||
userdb {
|
||||
driver = ldap
|
||||
args = ${ldapConfFile}
|
||||
default_fields = home=${cfg.mailDirectory}/ldap/%{user} uid=${toString cfg.vmailUID} gid=${toString cfg.vmailUID}
|
||||
}
|
||||
''}
|
||||
|
||||
service auth {
|
||||
unix_listener auth {
|
||||
mode = 0660
|
||||
user = ${postfixCfg.user}
|
||||
group = ${postfixCfg.group}
|
||||
}
|
||||
}
|
||||
|
||||
auth_mechanisms = plain login
|
||||
|
||||
namespace inbox {
|
||||
separator = ${cfg.hierarchySeparator}
|
||||
inbox = yes
|
||||
}
|
||||
|
||||
service indexer-worker {
|
||||
${lib.optionalString (cfg.fullTextSearch.memoryLimit != null) ''
|
||||
vsz_limit = ${toString (cfg.fullTextSearch.memoryLimit * 1024 * 1024)}
|
||||
''}
|
||||
}
|
||||
|
||||
lda_mailbox_autosubscribe = yes
|
||||
lda_mailbox_autocreate = yes
|
||||
'';
|
||||
};
|
||||
|
||||
imapsieve.mailbox = [
|
||||
{
|
||||
name = junkMailboxName;
|
||||
causes = [ "COPY" "APPEND" ];
|
||||
before = ./dovecot/imap_sieve/report-spam.sieve;
|
||||
}
|
||||
{
|
||||
name = "*";
|
||||
from = junkMailboxName;
|
||||
causes = [ "COPY" ];
|
||||
before = ./dovecot/imap_sieve/report-ham.sieve;
|
||||
}
|
||||
];
|
||||
systemd.services.dovecot2 = {
|
||||
preStart =
|
||||
''
|
||||
${genPasswdScript}
|
||||
''
|
||||
+ (lib.optionalString cfg.ldap.enable setPwdInLdapConfFile);
|
||||
};
|
||||
|
||||
mailboxes = cfg.mailboxes;
|
||||
|
||||
extraConfig = ''
|
||||
#Extra Config
|
||||
${lib.optionalString debug ''
|
||||
mail_debug = yes
|
||||
auth_debug = yes
|
||||
verbose_ssl = yes
|
||||
''}
|
||||
|
||||
${lib.optionalString (cfg.enableImap || cfg.enableImapSsl) ''
|
||||
service imap-login {
|
||||
inet_listener imap {
|
||||
${if cfg.enableImap then ''
|
||||
port = 143
|
||||
'' else ''
|
||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||
port = 0
|
||||
''}
|
||||
}
|
||||
inet_listener imaps {
|
||||
${if cfg.enableImapSsl then ''
|
||||
port = 993
|
||||
ssl = yes
|
||||
'' else ''
|
||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||
port = 0
|
||||
''}
|
||||
}
|
||||
}
|
||||
''}
|
||||
${lib.optionalString (cfg.enablePop3 || cfg.enablePop3Ssl) ''
|
||||
service pop3-login {
|
||||
inet_listener pop3 {
|
||||
${if cfg.enablePop3 then ''
|
||||
port = 110
|
||||
'' else ''
|
||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||
port = 0
|
||||
''}
|
||||
}
|
||||
inet_listener pop3s {
|
||||
${if cfg.enablePop3Ssl then ''
|
||||
port = 995
|
||||
ssl = yes
|
||||
'' else ''
|
||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||
port = 0
|
||||
''}
|
||||
}
|
||||
}
|
||||
''}
|
||||
|
||||
protocol imap {
|
||||
mail_max_userip_connections = ${toString cfg.maxConnectionsPerUser}
|
||||
mail_plugins = $mail_plugins imap_sieve
|
||||
}
|
||||
|
||||
service imap {
|
||||
vsz_limit = ${builtins.toString cfg.imapMemoryLimit} MB
|
||||
}
|
||||
|
||||
protocol pop3 {
|
||||
mail_max_userip_connections = ${toString cfg.maxConnectionsPerUser}
|
||||
}
|
||||
|
||||
mail_access_groups = ${vmailGroupName}
|
||||
|
||||
# https://ssl-config.mozilla.org/#server=dovecot&version=2.3.21&config=intermediate&openssl=3.4.1&guideline=5.7
|
||||
ssl = required
|
||||
ssl_min_protocol = TLSv1.2
|
||||
ssl_prefer_server_ciphers = no
|
||||
ssl_curve_list = X25519:prime256v1:secp384r1
|
||||
|
||||
service lmtp {
|
||||
unix_listener dovecot-lmtp {
|
||||
group = ${postfixCfg.group}
|
||||
mode = 0600
|
||||
user = ${postfixCfg.user}
|
||||
}
|
||||
vsz_limit = ${builtins.toString cfg.lmtpMemoryLimit} MB
|
||||
}
|
||||
|
||||
service quota-status {
|
||||
inet_listener {
|
||||
port = 0
|
||||
}
|
||||
unix_listener quota-status {
|
||||
user = postfix
|
||||
}
|
||||
vsz_limit = ${builtins.toString cfg.quotaStatusMemoryLimit} MB
|
||||
}
|
||||
|
||||
recipient_delimiter = ${cfg.recipientDelimiter}
|
||||
lmtp_save_to_detail_mailbox = ${cfg.lmtpSaveToDetailMailbox}
|
||||
|
||||
protocol lmtp {
|
||||
mail_plugins = $mail_plugins sieve
|
||||
}
|
||||
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
args = ${passwdFile}
|
||||
}
|
||||
|
||||
userdb {
|
||||
driver = passwd-file
|
||||
args = ${userdbFile}
|
||||
default_fields = uid=${builtins.toString cfg.vmailUID} gid=${builtins.toString cfg.vmailUID} home=${cfg.mailDirectory}
|
||||
}
|
||||
|
||||
${lib.optionalString cfg.ldap.enable ''
|
||||
passdb {
|
||||
driver = ldap
|
||||
args = ${ldapConfFile}
|
||||
}
|
||||
|
||||
userdb {
|
||||
driver = ldap
|
||||
args = ${ldapConfFile}
|
||||
default_fields = home=${cfg.mailDirectory}/ldap/%{user} uid=${toString cfg.vmailUID} gid=${toString cfg.vmailUID}
|
||||
}
|
||||
''}
|
||||
|
||||
service auth {
|
||||
unix_listener auth {
|
||||
mode = 0660
|
||||
user = ${postfixCfg.user}
|
||||
group = ${postfixCfg.group}
|
||||
}
|
||||
}
|
||||
|
||||
auth_mechanisms = plain login
|
||||
|
||||
namespace inbox {
|
||||
separator = ${cfg.hierarchySeparator}
|
||||
inbox = yes
|
||||
}
|
||||
|
||||
service indexer-worker {
|
||||
${lib.optionalString (cfg.fullTextSearch.memoryLimit != null) ''
|
||||
vsz_limit = ${toString (cfg.fullTextSearch.memoryLimit*1024*1024)}
|
||||
''}
|
||||
}
|
||||
|
||||
lda_mailbox_autosubscribe = yes
|
||||
lda_mailbox_autocreate = yes
|
||||
'';
|
||||
systemd.services.postfix.restartTriggers = [
|
||||
genPasswdScript
|
||||
] ++ (lib.optional cfg.ldap.enable [ setPwdInLdapConfFile ]);
|
||||
};
|
||||
|
||||
systemd.services.dovecot2 = {
|
||||
preStart = ''
|
||||
${genPasswdScript}
|
||||
'' + (lib.optionalString cfg.ldap.enable setPwdInLdapConfFile);
|
||||
};
|
||||
|
||||
systemd.services.postfix.restartTriggers = [ genPasswdScript ] ++ (lib.optional cfg.ldap.enable [setPwdInLdapConfFile]);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,15 +14,28 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.mailserver;
|
||||
in
|
||||
{
|
||||
config = with cfg; lib.mkIf enable {
|
||||
environment.systemPackages = with pkgs; [
|
||||
dovecot openssh postfix rspamd
|
||||
] ++ (if certificateScheme == "selfsigned" then [ openssl ] else []);
|
||||
};
|
||||
config =
|
||||
with cfg;
|
||||
lib.mkIf enable {
|
||||
environment.systemPackages =
|
||||
with pkgs;
|
||||
[
|
||||
dovecot
|
||||
openssh
|
||||
postfix
|
||||
rspamd
|
||||
]
|
||||
++ (if certificateScheme == "selfsigned" then [ openssl ] else [ ]);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,4 +24,3 @@ in
|
|||
services.kresd.enable = true;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -20,18 +20,21 @@ let
|
|||
cfg = config.mailserver;
|
||||
in
|
||||
{
|
||||
config = with cfg; lib.mkIf (enable && openFirewall) {
|
||||
config =
|
||||
with cfg;
|
||||
lib.mkIf (enable && openFirewall) {
|
||||
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [ 25 ]
|
||||
++ lib.optional enableSubmission 587
|
||||
++ lib.optional enableSubmissionSsl 465
|
||||
++ lib.optional enableImap 143
|
||||
++ lib.optional enableImapSsl 993
|
||||
++ lib.optional enablePop3 110
|
||||
++ lib.optional enablePop3Ssl 995
|
||||
++ lib.optional enableManageSieve 4190
|
||||
++ lib.optional (certificateScheme == "acme-nginx") 80;
|
||||
networking.firewall = {
|
||||
allowedTCPPorts =
|
||||
[ 25 ]
|
||||
++ lib.optional enableSubmission 587
|
||||
++ lib.optional enableSubmissionSsl 465
|
||||
++ lib.optional enableImap 143
|
||||
++ lib.optional enableImapSsl 993
|
||||
++ lib.optional enablePop3 110
|
||||
++ lib.optional enablePop3Ssl 995
|
||||
++ lib.optional enableManageSieve 4190
|
||||
++ lib.optional (certificateScheme == "acme-nginx") 80;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,8 +14,12 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
with (import ./common.nix { inherit config lib pkgs; });
|
||||
|
||||
|
@ -23,20 +27,22 @@ let
|
|||
cfg = config.mailserver;
|
||||
in
|
||||
{
|
||||
config = lib.mkIf (cfg.enable && (cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx")) {
|
||||
services.nginx = lib.mkIf (cfg.certificateScheme == "acme-nginx") {
|
||||
enable = true;
|
||||
virtualHosts."${cfg.fqdn}" = {
|
||||
serverName = cfg.fqdn;
|
||||
serverAliases = cfg.certificateDomains;
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
};
|
||||
};
|
||||
config =
|
||||
lib.mkIf (cfg.enable && (cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"))
|
||||
{
|
||||
services.nginx = lib.mkIf (cfg.certificateScheme == "acme-nginx") {
|
||||
enable = true;
|
||||
virtualHosts."${cfg.fqdn}" = {
|
||||
serverName = cfg.fqdn;
|
||||
serverAliases = cfg.certificateDomains;
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
};
|
||||
};
|
||||
|
||||
security.acme.certs."${cfg.acmeCertificateName}".reloadServices = [
|
||||
"postfix.service"
|
||||
"dovecot2.service"
|
||||
];
|
||||
};
|
||||
security.acme.certs."${cfg.acmeCertificateName}".reloadServices = [
|
||||
"postfix.service"
|
||||
"dovecot2.service"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,7 +14,12 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
with (import ./common.nix { inherit config pkgs lib; });
|
||||
|
||||
|
@ -28,31 +33,55 @@ let
|
|||
mergeLookupTables = tables: lib.zipAttrsWith (_: v: lib.flatten v) tables;
|
||||
|
||||
# valiases_postfix :: Map String [String]
|
||||
valiases_postfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
|
||||
(name: value:
|
||||
let to = name;
|
||||
in map (from: {"${from}" = to;}) (value.aliases ++ lib.singleton name))
|
||||
cfg.loginAccounts));
|
||||
regex_valiases_postfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
|
||||
(name: value:
|
||||
let to = name;
|
||||
in map (from: {"${from}" = to;}) value.aliasesRegexp)
|
||||
cfg.loginAccounts));
|
||||
valiases_postfix = mergeLookupTables (
|
||||
lib.flatten (
|
||||
lib.mapAttrsToList (
|
||||
name: value:
|
||||
let
|
||||
to = name;
|
||||
in
|
||||
map (from: { "${from}" = to; }) (value.aliases ++ lib.singleton name)
|
||||
) cfg.loginAccounts
|
||||
)
|
||||
);
|
||||
regex_valiases_postfix = mergeLookupTables (
|
||||
lib.flatten (
|
||||
lib.mapAttrsToList (
|
||||
name: value:
|
||||
let
|
||||
to = name;
|
||||
in
|
||||
map (from: { "${from}" = to; }) value.aliasesRegexp
|
||||
) cfg.loginAccounts
|
||||
)
|
||||
);
|
||||
|
||||
# catchAllPostfix :: Map String [String]
|
||||
catchAllPostfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
|
||||
(name: value:
|
||||
let to = name;
|
||||
in map (from: {"@${from}" = to;}) value.catchAll)
|
||||
cfg.loginAccounts));
|
||||
catchAllPostfix = mergeLookupTables (
|
||||
lib.flatten (
|
||||
lib.mapAttrsToList (
|
||||
name: value:
|
||||
let
|
||||
to = name;
|
||||
in
|
||||
map (from: { "@${from}" = to; }) value.catchAll
|
||||
) cfg.loginAccounts
|
||||
)
|
||||
);
|
||||
|
||||
# all_valiases_postfix :: Map String [String]
|
||||
all_valiases_postfix = mergeLookupTables [valiases_postfix extra_valiases_postfix];
|
||||
all_valiases_postfix = mergeLookupTables [
|
||||
valiases_postfix
|
||||
extra_valiases_postfix
|
||||
];
|
||||
|
||||
# attrsToLookupTable :: Map String (Either String [ String ]) -> Map String [String]
|
||||
attrsToLookupTable = aliases: let
|
||||
lookupTables = lib.mapAttrsToList (from: to: {"${from}" = to;}) aliases;
|
||||
in mergeLookupTables lookupTables;
|
||||
attrsToLookupTable =
|
||||
aliases:
|
||||
let
|
||||
lookupTables = lib.mapAttrsToList (from: to: { "${from}" = to; }) aliases;
|
||||
in
|
||||
mergeLookupTables lookupTables;
|
||||
|
||||
# extra_valiases_postfix :: Map String [String]
|
||||
extra_valiases_postfix = attrsToLookupTable cfg.extraVirtualAliases;
|
||||
|
@ -61,37 +90,49 @@ let
|
|||
forwards = attrsToLookupTable cfg.forwards;
|
||||
|
||||
# lookupTableToString :: Map String [String] -> String
|
||||
lookupTableToString = attrs: let
|
||||
valueToString = value: lib.concatStringsSep ", " value;
|
||||
in lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name} ${valueToString value}") attrs);
|
||||
lookupTableToString =
|
||||
attrs:
|
||||
let
|
||||
valueToString = value: lib.concatStringsSep ", " value;
|
||||
in
|
||||
lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList (name: value: "${name} ${valueToString value}") attrs
|
||||
);
|
||||
|
||||
# valiases_file :: Path
|
||||
valiases_file = let
|
||||
content = lookupTableToString (mergeLookupTables [all_valiases_postfix catchAllPostfix]);
|
||||
in builtins.toFile "valias" content;
|
||||
valiases_file =
|
||||
let
|
||||
content = lookupTableToString (mergeLookupTables [
|
||||
all_valiases_postfix
|
||||
catchAllPostfix
|
||||
]);
|
||||
in
|
||||
builtins.toFile "valias" content;
|
||||
|
||||
regex_valiases_file = let
|
||||
content = lookupTableToString regex_valiases_postfix;
|
||||
in builtins.toFile "regex_valias" content;
|
||||
regex_valiases_file =
|
||||
let
|
||||
content = lookupTableToString regex_valiases_postfix;
|
||||
in
|
||||
builtins.toFile "regex_valias" content;
|
||||
|
||||
# denied_recipients_postfix :: [ String ]
|
||||
denied_recipients_postfix = map
|
||||
(acct: "${acct.name} REJECT ${acct.sendOnlyRejectMessage}")
|
||||
(lib.filter (acct: acct.sendOnly) (lib.attrValues cfg.loginAccounts));
|
||||
denied_recipients_file = builtins.toFile "denied_recipients" (lib.concatStringsSep "\n" denied_recipients_postfix);
|
||||
denied_recipients_postfix = map (acct: "${acct.name} REJECT ${acct.sendOnlyRejectMessage}") (
|
||||
lib.filter (acct: acct.sendOnly) (lib.attrValues cfg.loginAccounts)
|
||||
);
|
||||
denied_recipients_file = builtins.toFile "denied_recipients" (
|
||||
lib.concatStringsSep "\n" denied_recipients_postfix
|
||||
);
|
||||
|
||||
reject_senders_postfix = map
|
||||
(sender:
|
||||
"${sender} REJECT")
|
||||
cfg.rejectSender;
|
||||
reject_senders_file = builtins.toFile "reject_senders" (lib.concatStringsSep "\n" reject_senders_postfix) ;
|
||||
reject_senders_postfix = map (sender: "${sender} REJECT") cfg.rejectSender;
|
||||
reject_senders_file = builtins.toFile "reject_senders" (
|
||||
lib.concatStringsSep "\n" reject_senders_postfix
|
||||
);
|
||||
|
||||
reject_recipients_postfix = map
|
||||
(recipient:
|
||||
"${recipient} REJECT")
|
||||
cfg.rejectRecipients;
|
||||
reject_recipients_postfix = map (recipient: "${recipient} REJECT") cfg.rejectRecipients;
|
||||
# rejectRecipients :: [ Path ]
|
||||
reject_recipients_file = builtins.toFile "reject_recipients" (lib.concatStringsSep "\n" reject_recipients_postfix) ;
|
||||
reject_recipients_file = builtins.toFile "reject_recipients" (
|
||||
lib.concatStringsSep "\n" reject_recipients_postfix
|
||||
);
|
||||
|
||||
# vhosts_file :: Path
|
||||
vhosts_file = builtins.toFile "vhosts" (concatStringsSep "\n" cfg.domains);
|
||||
|
@ -103,45 +144,51 @@ let
|
|||
# every alias is owned (uniquely) by its user.
|
||||
# The user's own address is already in all_valiases_postfix.
|
||||
vaccounts_file = builtins.toFile "vaccounts" (lookupTableToString all_valiases_postfix);
|
||||
regex_vaccounts_file = builtins.toFile "regex_vaccounts" (lookupTableToString regex_valiases_postfix);
|
||||
regex_vaccounts_file = builtins.toFile "regex_vaccounts" (
|
||||
lookupTableToString regex_valiases_postfix
|
||||
);
|
||||
|
||||
submissionHeaderCleanupRules = pkgs.writeText "submission_header_cleanup_rules" (''
|
||||
# Removes sensitive headers from mails handed in via the submission port.
|
||||
# See https://thomas-leister.de/mailserver-debian-stretch/
|
||||
# Uses "pcre" style regex.
|
||||
submissionHeaderCleanupRules = pkgs.writeText "submission_header_cleanup_rules" (
|
||||
''
|
||||
# Removes sensitive headers from mails handed in via the submission port.
|
||||
# See https://thomas-leister.de/mailserver-debian-stretch/
|
||||
# Uses "pcre" style regex.
|
||||
|
||||
/^Received:/ IGNORE
|
||||
/^X-Originating-IP:/ IGNORE
|
||||
/^X-Mailer:/ IGNORE
|
||||
/^User-Agent:/ IGNORE
|
||||
/^X-Enigmail:/ IGNORE
|
||||
'' + lib.optionalString cfg.rewriteMessageId ''
|
||||
/^Received:/ IGNORE
|
||||
/^X-Originating-IP:/ IGNORE
|
||||
/^X-Mailer:/ IGNORE
|
||||
/^User-Agent:/ IGNORE
|
||||
/^X-Enigmail:/ IGNORE
|
||||
''
|
||||
+ lib.optionalString cfg.rewriteMessageId ''
|
||||
|
||||
# Replaces the user submitted hostname with the server's FQDN to hide the
|
||||
# user's host or network.
|
||||
# Replaces the user submitted hostname with the server's FQDN to hide the
|
||||
# user's host or network.
|
||||
|
||||
/^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${cfg.fqdn}>
|
||||
'');
|
||||
/^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${cfg.fqdn}>
|
||||
''
|
||||
);
|
||||
|
||||
smtpdMilters = [ "unix:/run/rspamd/rspamd-milter.sock" ];
|
||||
|
||||
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
|
||||
mappedRegexFile = name: "pcre:/var/lib/postfix/conf/${name}";
|
||||
|
||||
submissionOptions =
|
||||
{
|
||||
smtpd_tls_security_level = "encrypt";
|
||||
smtpd_sasl_auth_enable = "yes";
|
||||
smtpd_sasl_type = "dovecot";
|
||||
smtpd_sasl_path = "/run/dovecot2/auth";
|
||||
smtpd_sasl_security_options = "noanonymous";
|
||||
smtpd_sasl_local_domain = "$myhostname";
|
||||
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
|
||||
smtpd_sender_login_maps = "hash:/etc/postfix/vaccounts${lib.optionalString cfg.ldap.enable ",ldap:${ldapSenderLoginMapFile}"}${lib.optionalString (regex_valiases_postfix != {}) ",pcre:/etc/postfix/regex_vaccounts"}";
|
||||
smtpd_sender_restrictions = "reject_sender_login_mismatch";
|
||||
smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
|
||||
cleanup_service_name = "submission-header-cleanup";
|
||||
};
|
||||
submissionOptions = {
|
||||
smtpd_tls_security_level = "encrypt";
|
||||
smtpd_sasl_auth_enable = "yes";
|
||||
smtpd_sasl_type = "dovecot";
|
||||
smtpd_sasl_path = "/run/dovecot2/auth";
|
||||
smtpd_sasl_security_options = "noanonymous";
|
||||
smtpd_sasl_local_domain = "$myhostname";
|
||||
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
|
||||
smtpd_sender_login_maps = "hash:/etc/postfix/vaccounts${lib.optionalString cfg.ldap.enable ",ldap:${ldapSenderLoginMapFile}"}${
|
||||
lib.optionalString (regex_valiases_postfix != { }) ",pcre:/etc/postfix/regex_vaccounts"
|
||||
}";
|
||||
smtpd_sender_restrictions = "reject_sender_login_mismatch";
|
||||
smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
|
||||
cleanup_service_name = "submission-header-cleanup";
|
||||
};
|
||||
|
||||
commonLdapConfig = ''
|
||||
server_host = ${lib.concatStringsSep " " cfg.ldap.uris}
|
||||
|
@ -186,164 +233,183 @@ let
|
|||
};
|
||||
in
|
||||
{
|
||||
config = with cfg; lib.mkIf enable {
|
||||
config =
|
||||
with cfg;
|
||||
lib.mkIf enable {
|
||||
|
||||
systemd.services.postfix-setup = lib.mkIf cfg.ldap.enable {
|
||||
preStart = ''
|
||||
${appendPwdInVirtualMailboxMap}
|
||||
${appendPwdInSenderLoginMap}
|
||||
'';
|
||||
restartTriggers = [ appendPwdInVirtualMailboxMap appendPwdInSenderLoginMap ];
|
||||
};
|
||||
|
||||
services.postfix = {
|
||||
enable = true;
|
||||
hostname = "${sendingFqdn}";
|
||||
networksStyle = "host";
|
||||
mapFiles."valias" = valiases_file;
|
||||
mapFiles."regex_valias" = regex_valiases_file;
|
||||
mapFiles."vaccounts" = vaccounts_file;
|
||||
mapFiles."regex_vaccounts" = regex_vaccounts_file;
|
||||
mapFiles."denied_recipients" = denied_recipients_file;
|
||||
mapFiles."reject_senders" = reject_senders_file;
|
||||
mapFiles."reject_recipients" = reject_recipients_file;
|
||||
enableSubmission = cfg.enableSubmission;
|
||||
enableSubmissions = cfg.enableSubmissionSsl;
|
||||
virtual = lookupTableToString (mergeLookupTables [all_valiases_postfix catchAllPostfix forwards]);
|
||||
|
||||
config = {
|
||||
smtpd_tls_chain_files = [
|
||||
"${keyPath}"
|
||||
"${certificatePath}"
|
||||
systemd.services.postfix-setup = lib.mkIf cfg.ldap.enable {
|
||||
preStart = ''
|
||||
${appendPwdInVirtualMailboxMap}
|
||||
${appendPwdInSenderLoginMap}
|
||||
'';
|
||||
restartTriggers = [
|
||||
appendPwdInVirtualMailboxMap
|
||||
appendPwdInSenderLoginMap
|
||||
];
|
||||
};
|
||||
|
||||
# Extra Config
|
||||
mydestination = "";
|
||||
recipient_delimiter = cfg.recipientDelimiter;
|
||||
smtpd_banner = "${fqdn} ESMTP NO UCE";
|
||||
disable_vrfy_command = true;
|
||||
message_size_limit = toString cfg.messageSizeLimit;
|
||||
|
||||
# virtual mail system
|
||||
virtual_uid_maps = "static:5000";
|
||||
virtual_gid_maps = "static:5000";
|
||||
virtual_mailbox_base = mailDirectory;
|
||||
virtual_mailbox_domains = vhosts_file;
|
||||
virtual_mailbox_maps = [
|
||||
(mappedFile "valias")
|
||||
] ++ lib.optionals cfg.ldap.enable [
|
||||
"ldap:${ldapVirtualMailboxMapFile}"
|
||||
] ++ lib.optionals (regex_valiases_postfix != {}) [
|
||||
(mappedRegexFile "regex_valias")
|
||||
];
|
||||
virtual_alias_maps = lib.mkAfter (lib.optionals (regex_valiases_postfix != {}) [
|
||||
(mappedRegexFile "regex_valias")
|
||||
services.postfix = {
|
||||
enable = true;
|
||||
hostname = "${sendingFqdn}";
|
||||
networksStyle = "host";
|
||||
mapFiles."valias" = valiases_file;
|
||||
mapFiles."regex_valias" = regex_valiases_file;
|
||||
mapFiles."vaccounts" = vaccounts_file;
|
||||
mapFiles."regex_vaccounts" = regex_vaccounts_file;
|
||||
mapFiles."denied_recipients" = denied_recipients_file;
|
||||
mapFiles."reject_senders" = reject_senders_file;
|
||||
mapFiles."reject_recipients" = reject_recipients_file;
|
||||
enableSubmission = cfg.enableSubmission;
|
||||
enableSubmissions = cfg.enableSubmissionSsl;
|
||||
virtual = lookupTableToString (mergeLookupTables [
|
||||
all_valiases_postfix
|
||||
catchAllPostfix
|
||||
forwards
|
||||
]);
|
||||
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
|
||||
# Avoid leakage of X-Original-To, X-Delivered-To headers between recipients
|
||||
lmtp_destination_recipient_limit = "1";
|
||||
|
||||
# sasl with dovecot
|
||||
smtpd_sasl_type = "dovecot";
|
||||
smtpd_sasl_path = "/run/dovecot2/auth";
|
||||
smtpd_sasl_auth_enable = true;
|
||||
smtpd_relay_restrictions = [
|
||||
"permit_mynetworks" "permit_sasl_authenticated" "reject_unauth_destination"
|
||||
];
|
||||
config = {
|
||||
smtpd_tls_chain_files = [
|
||||
"${keyPath}"
|
||||
"${certificatePath}"
|
||||
];
|
||||
|
||||
# reject selected senders
|
||||
smtpd_sender_restrictions = [
|
||||
"check_sender_access ${mappedFile "reject_senders"}"
|
||||
];
|
||||
# Extra Config
|
||||
mydestination = "";
|
||||
recipient_delimiter = cfg.recipientDelimiter;
|
||||
smtpd_banner = "${fqdn} ESMTP NO UCE";
|
||||
disable_vrfy_command = true;
|
||||
message_size_limit = toString cfg.messageSizeLimit;
|
||||
|
||||
smtpd_recipient_restrictions = [
|
||||
# reject selected recipients
|
||||
"check_recipient_access ${mappedFile "denied_recipients"}"
|
||||
"check_recipient_access ${mappedFile "reject_recipients"}"
|
||||
# quota checking
|
||||
"check_policy_service unix:/run/dovecot2/quota-status"
|
||||
];
|
||||
# virtual mail system
|
||||
virtual_uid_maps = "static:5000";
|
||||
virtual_gid_maps = "static:5000";
|
||||
virtual_mailbox_base = mailDirectory;
|
||||
virtual_mailbox_domains = vhosts_file;
|
||||
virtual_mailbox_maps =
|
||||
[
|
||||
(mappedFile "valias")
|
||||
]
|
||||
++ lib.optionals cfg.ldap.enable [
|
||||
"ldap:${ldapVirtualMailboxMapFile}"
|
||||
]
|
||||
++ lib.optionals (regex_valiases_postfix != { }) [
|
||||
(mappedRegexFile "regex_valias")
|
||||
];
|
||||
virtual_alias_maps = lib.mkAfter (
|
||||
lib.optionals (regex_valiases_postfix != { }) [
|
||||
(mappedRegexFile "regex_valias")
|
||||
]
|
||||
);
|
||||
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
|
||||
# Avoid leakage of X-Original-To, X-Delivered-To headers between recipients
|
||||
lmtp_destination_recipient_limit = "1";
|
||||
|
||||
# TLS for incoming mail is optional
|
||||
smtpd_tls_security_level = "may";
|
||||
# sasl with dovecot
|
||||
smtpd_sasl_type = "dovecot";
|
||||
smtpd_sasl_path = "/run/dovecot2/auth";
|
||||
smtpd_sasl_auth_enable = true;
|
||||
smtpd_relay_restrictions = [
|
||||
"permit_mynetworks"
|
||||
"permit_sasl_authenticated"
|
||||
"reject_unauth_destination"
|
||||
];
|
||||
|
||||
# But required for authentication attempts
|
||||
smtpd_tls_auth_only = true;
|
||||
# reject selected senders
|
||||
smtpd_sender_restrictions = [
|
||||
"check_sender_access ${mappedFile "reject_senders"}"
|
||||
];
|
||||
|
||||
# TLS versions supported for the SMTP server
|
||||
smtpd_tls_protocols = ">=TLSv1.2";
|
||||
smtpd_tls_mandatory_protocols = ">=TLSv1.2";
|
||||
smtpd_recipient_restrictions = [
|
||||
# reject selected recipients
|
||||
"check_recipient_access ${mappedFile "denied_recipients"}"
|
||||
"check_recipient_access ${mappedFile "reject_recipients"}"
|
||||
# quota checking
|
||||
"check_policy_service unix:/run/dovecot2/quota-status"
|
||||
];
|
||||
|
||||
# Require ciphersuites that OpenSSL classifies as "High"
|
||||
smtpd_tls_ciphers = "high";
|
||||
smtpd_tls_mandatory_ciphers = "high";
|
||||
# TLS for incoming mail is optional
|
||||
smtpd_tls_security_level = "may";
|
||||
|
||||
# Exclude cipher suites with undesirable properties
|
||||
smtpd_tls_exclude_ciphers = "eNULL, aNULL";
|
||||
smtpd_tls_mandatory_exclude_ciphers = "eNULL, aNULL";
|
||||
# But required for authentication attempts
|
||||
smtpd_tls_auth_only = true;
|
||||
|
||||
# Opportunistic DANE support when delivering mail to other servers
|
||||
# https://www.postfix.org/postconf.5.html#smtp_tls_security_level
|
||||
smtp_dns_support_level = "dnssec";
|
||||
smtp_tls_security_level = "dane";
|
||||
# TLS versions supported for the SMTP server
|
||||
smtpd_tls_protocols = ">=TLSv1.2";
|
||||
smtpd_tls_mandatory_protocols = ">=TLSv1.2";
|
||||
|
||||
# TLS versions supported for the SMTP client
|
||||
smtp_tls_protocols = ">=TLSv1.2";
|
||||
smtp_tls_mandatory_protocols = ">=TLSv1.2";
|
||||
# Require ciphersuites that OpenSSL classifies as "High"
|
||||
smtpd_tls_ciphers = "high";
|
||||
smtpd_tls_mandatory_ciphers = "high";
|
||||
|
||||
# Require ciphersuites that OpenSSL classifies as "High"
|
||||
smtp_tls_ciphers = "high";
|
||||
smtp_tls_mandatory_ciphers = "high";
|
||||
# Exclude cipher suites with undesirable properties
|
||||
smtpd_tls_exclude_ciphers = "eNULL, aNULL";
|
||||
smtpd_tls_mandatory_exclude_ciphers = "eNULL, aNULL";
|
||||
|
||||
# Exclude ciphersuites with undesirable properties
|
||||
smtp_tls_exclude_ciphers = "eNULL, aNULL";
|
||||
smtp_tls_mandatory_exclude_ciphers = "eNULL, aNULL";
|
||||
# Opportunistic DANE support when delivering mail to other servers
|
||||
# https://www.postfix.org/postconf.5.html#smtp_tls_security_level
|
||||
smtp_dns_support_level = "dnssec";
|
||||
smtp_tls_security_level = "dane";
|
||||
|
||||
# Restrict and prioritize the following curves in the given order
|
||||
# Excludes curves that have no widespread support, so we don't bloat the handshake needlessly.
|
||||
# https://www.postfix.org/postconf.5.html#tls_eecdh_auto_curves
|
||||
# https://ssl-config.mozilla.org/#server=postfix&version=3.10&config=intermediate&openssl=3.4.1&guideline=5.7
|
||||
tls_eecdh_auto_curves = [
|
||||
"X25519"
|
||||
"prime256v1"
|
||||
"secp384r1"
|
||||
];
|
||||
# TLS versions supported for the SMTP client
|
||||
smtp_tls_protocols = ">=TLSv1.2";
|
||||
smtp_tls_mandatory_protocols = ">=TLSv1.2";
|
||||
|
||||
# Disable FFDHE on TLSv1.3 because it is slower than elliptic curves
|
||||
# https://www.postfix.org/postconf.5.html#tls_ffdhe_auto_groups
|
||||
tls_ffdhe_auto_groups = [ ];
|
||||
# Require ciphersuites that OpenSSL classifies as "High"
|
||||
smtp_tls_ciphers = "high";
|
||||
smtp_tls_mandatory_ciphers = "high";
|
||||
|
||||
# As long as all cipher suites are considered safe, let the client use its preferred cipher
|
||||
tls_preempt_cipherlist = false;
|
||||
# Exclude ciphersuites with undesirable properties
|
||||
smtp_tls_exclude_ciphers = "eNULL, aNULL";
|
||||
smtp_tls_mandatory_exclude_ciphers = "eNULL, aNULL";
|
||||
|
||||
# Log only a summary message on TLS handshake completion
|
||||
smtp_tls_loglevel = "1";
|
||||
smtpd_tls_loglevel = "1";
|
||||
# Restrict and prioritize the following curves in the given order
|
||||
# Excludes curves that have no widespread support, so we don't bloat the handshake needlessly.
|
||||
# https://www.postfix.org/postconf.5.html#tls_eecdh_auto_curves
|
||||
# https://ssl-config.mozilla.org/#server=postfix&version=3.10&config=intermediate&openssl=3.4.1&guideline=5.7
|
||||
tls_eecdh_auto_curves = [
|
||||
"X25519"
|
||||
"prime256v1"
|
||||
"secp384r1"
|
||||
];
|
||||
|
||||
smtpd_milters = smtpdMilters;
|
||||
non_smtpd_milters = lib.mkIf cfg.dkimSigning [ "unix:/run/rspamd/rspamd-milter.sock" ];
|
||||
milter_protocol = "6";
|
||||
milter_mail_macros = "i {mail_addr} {client_addr} {client_name} {auth_authen}";
|
||||
};
|
||||
# Disable FFDHE on TLSv1.3 because it is slower than elliptic curves
|
||||
# https://www.postfix.org/postconf.5.html#tls_ffdhe_auto_groups
|
||||
tls_ffdhe_auto_groups = [ ];
|
||||
|
||||
submissionOptions = submissionOptions;
|
||||
submissionsOptions = submissionOptions;
|
||||
# As long as all cipher suites are considered safe, let the client use its preferred cipher
|
||||
tls_preempt_cipherlist = false;
|
||||
|
||||
masterConfig = {
|
||||
"lmtp" = {
|
||||
# Add headers when delivering, see http://www.postfix.org/smtp.8.html
|
||||
# D => Delivered-To, O => X-Original-To, R => Return-Path
|
||||
args = [ "flags=O" ];
|
||||
# Log only a summary message on TLS handshake completion
|
||||
smtp_tls_loglevel = "1";
|
||||
smtpd_tls_loglevel = "1";
|
||||
|
||||
smtpd_milters = smtpdMilters;
|
||||
non_smtpd_milters = lib.mkIf cfg.dkimSigning [ "unix:/run/rspamd/rspamd-milter.sock" ];
|
||||
milter_protocol = "6";
|
||||
milter_mail_macros = "i {mail_addr} {client_addr} {client_name} {auth_authen}";
|
||||
};
|
||||
"submission-header-cleanup" = {
|
||||
type = "unix";
|
||||
private = false;
|
||||
chroot = false;
|
||||
maxproc = 0;
|
||||
command = "cleanup";
|
||||
args = ["-o" "header_checks=pcre:${submissionHeaderCleanupRules}"];
|
||||
|
||||
submissionOptions = submissionOptions;
|
||||
submissionsOptions = submissionOptions;
|
||||
|
||||
masterConfig = {
|
||||
"lmtp" = {
|
||||
# Add headers when delivering, see http://www.postfix.org/smtp.8.html
|
||||
# D => Delivered-To, O => X-Original-To, R => Return-Path
|
||||
args = [ "flags=O" ];
|
||||
};
|
||||
"submission-header-cleanup" = {
|
||||
type = "unix";
|
||||
private = false;
|
||||
chroot = false;
|
||||
maxproc = 0;
|
||||
command = "cleanup";
|
||||
args = [
|
||||
"-o"
|
||||
"header_checks=pcre:${submissionHeaderCleanupRules}"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,7 +14,12 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
with lib;
|
||||
|
||||
|
@ -38,7 +43,8 @@ let
|
|||
${cfg.backup.cmdPostexec}
|
||||
'';
|
||||
postexecString = optionalString postexecDefined "cmd_postexec ${postexecWrapped}";
|
||||
in {
|
||||
in
|
||||
{
|
||||
config = mkIf (cfg.enable && cfg.backup.enable) {
|
||||
services.rsnapshot = {
|
||||
enable = true;
|
||||
|
|
|
@ -14,7 +14,12 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.mailserver;
|
||||
|
@ -26,56 +31,74 @@ let
|
|||
rspamdUser = config.services.rspamd.user;
|
||||
rspamdGroup = config.services.rspamd.group;
|
||||
|
||||
createDkimKeypair = domain: let
|
||||
privateKey = "${cfg.dkimKeyDirectory}/${domain}.${cfg.dkimSelector}.key";
|
||||
publicKey = "${cfg.dkimKeyDirectory}/${domain}.${cfg.dkimSelector}.txt";
|
||||
in pkgs.writeShellScript "dkim-keygen-${domain}" ''
|
||||
if [ ! -f "${privateKey}" ]
|
||||
then
|
||||
${lib.getExe' pkgs.rspamd "rspamadm"} dkim_keygen \
|
||||
--domain "${domain}" \
|
||||
--selector "${cfg.dkimSelector}" \
|
||||
--type "${cfg.dkimKeyType}" \
|
||||
--bits ${toString cfg.dkimKeyBits} \
|
||||
--privkey "${privateKey}" > "${publicKey}"
|
||||
chmod 0644 "${publicKey}"
|
||||
echo "Generated key for domain ${domain} and selector ${cfg.dkimSelector}"
|
||||
fi
|
||||
'';
|
||||
createDkimKeypair =
|
||||
domain:
|
||||
let
|
||||
privateKey = "${cfg.dkimKeyDirectory}/${domain}.${cfg.dkimSelector}.key";
|
||||
publicKey = "${cfg.dkimKeyDirectory}/${domain}.${cfg.dkimSelector}.txt";
|
||||
in
|
||||
pkgs.writeShellScript "dkim-keygen-${domain}" ''
|
||||
if [ ! -f "${privateKey}" ]
|
||||
then
|
||||
${lib.getExe' pkgs.rspamd "rspamadm"} dkim_keygen \
|
||||
--domain "${domain}" \
|
||||
--selector "${cfg.dkimSelector}" \
|
||||
--type "${cfg.dkimKeyType}" \
|
||||
--bits ${toString cfg.dkimKeyBits} \
|
||||
--privkey "${privateKey}" > "${publicKey}"
|
||||
chmod 0644 "${publicKey}"
|
||||
echo "Generated key for domain ${domain} and selector ${cfg.dkimSelector}"
|
||||
fi
|
||||
'';
|
||||
in
|
||||
{
|
||||
config = with cfg; lib.mkIf enable {
|
||||
environment.systemPackages = lib.mkBefore [
|
||||
(pkgs.runCommand "rspamc-wrapped" {
|
||||
nativeBuildInputs = with pkgs; [ makeWrapper ];
|
||||
}''
|
||||
makeWrapper ${pkgs.rspamd}/bin/rspamc $out/bin/rspamc \
|
||||
--add-flags "-h /run/rspamd/worker-controller.sock"
|
||||
'')
|
||||
];
|
||||
config =
|
||||
with cfg;
|
||||
lib.mkIf enable {
|
||||
environment.systemPackages = lib.mkBefore [
|
||||
(pkgs.runCommand "rspamc-wrapped"
|
||||
{
|
||||
nativeBuildInputs = with pkgs; [ makeWrapper ];
|
||||
}
|
||||
''
|
||||
makeWrapper ${pkgs.rspamd}/bin/rspamc $out/bin/rspamc \
|
||||
--add-flags "-h /run/rspamd/worker-controller.sock"
|
||||
''
|
||||
)
|
||||
];
|
||||
|
||||
services.rspamd = {
|
||||
enable = true;
|
||||
inherit debug;
|
||||
locals = {
|
||||
"milter_headers.conf" = { text = ''
|
||||
services.rspamd = {
|
||||
enable = true;
|
||||
inherit debug;
|
||||
locals = {
|
||||
"milter_headers.conf" = {
|
||||
text = ''
|
||||
extended_spam_headers = true;
|
||||
''; };
|
||||
"redis.conf" = { text = ''
|
||||
servers = "${if cfg.redis.port == null
|
||||
then
|
||||
cfg.redis.address
|
||||
else
|
||||
"${cfg.redis.address}:${toString cfg.redis.port}"}";
|
||||
'' + (lib.optionalString (cfg.redis.password != null) ''
|
||||
password = "${cfg.redis.password}";
|
||||
''); };
|
||||
"classifier-bayes.conf" = { text = ''
|
||||
'';
|
||||
};
|
||||
"redis.conf" = {
|
||||
text =
|
||||
''
|
||||
servers = "${
|
||||
if cfg.redis.port == null then
|
||||
cfg.redis.address
|
||||
else
|
||||
"${cfg.redis.address}:${toString cfg.redis.port}"
|
||||
}";
|
||||
''
|
||||
+ (lib.optionalString (cfg.redis.password != null) ''
|
||||
password = "${cfg.redis.password}";
|
||||
'');
|
||||
};
|
||||
"classifier-bayes.conf" = {
|
||||
text = ''
|
||||
cache {
|
||||
backend = "redis";
|
||||
}
|
||||
''; };
|
||||
"antivirus.conf" = lib.mkIf cfg.virusScanning { text = ''
|
||||
'';
|
||||
};
|
||||
"antivirus.conf" = lib.mkIf cfg.virusScanning {
|
||||
text = ''
|
||||
clamav {
|
||||
action = "reject";
|
||||
symbol = "CLAM_VIRUS";
|
||||
|
@ -84,157 +107,168 @@ in
|
|||
servers = "/run/clamav/clamd.ctl";
|
||||
scan_mime_parts = false; # scan mail as a whole unit, not parts. seems to be needed to work at all
|
||||
}
|
||||
''; };
|
||||
"dkim_signing.conf" = { text = ''
|
||||
enabled = ${lib.boolToString cfg.dkimSigning};
|
||||
path = "${cfg.dkimKeyDirectory}/$domain.$selector.key";
|
||||
selector = "${cfg.dkimSelector}";
|
||||
# Allow for usernames w/o domain part
|
||||
allow_username_mismatch = true
|
||||
''; };
|
||||
"dmarc.conf" = { text = ''
|
||||
'';
|
||||
};
|
||||
"dkim_signing.conf" = {
|
||||
text = ''
|
||||
enabled = ${lib.boolToString cfg.dkimSigning};
|
||||
path = "${cfg.dkimKeyDirectory}/$domain.$selector.key";
|
||||
selector = "${cfg.dkimSelector}";
|
||||
# Allow for usernames w/o domain part
|
||||
allow_username_mismatch = true
|
||||
'';
|
||||
};
|
||||
"dmarc.conf" = {
|
||||
text = ''
|
||||
${lib.optionalString cfg.dmarcReporting.enable ''
|
||||
reporting {
|
||||
enabled = true;
|
||||
email = "${cfg.dmarcReporting.email}";
|
||||
domain = "${cfg.dmarcReporting.domain}";
|
||||
org_name = "${cfg.dmarcReporting.organizationName}";
|
||||
from_name = "${cfg.dmarcReporting.fromName}";
|
||||
msgid_from = "${cfg.dmarcReporting.domain}";
|
||||
${lib.optionalString (cfg.dmarcReporting.excludeDomains != []) ''
|
||||
exclude_domains = ${builtins.toJSON cfg.dmarcReporting.excludeDomains};
|
||||
''}
|
||||
}''}
|
||||
''; };
|
||||
reporting {
|
||||
enabled = true;
|
||||
email = "${cfg.dmarcReporting.email}";
|
||||
domain = "${cfg.dmarcReporting.domain}";
|
||||
org_name = "${cfg.dmarcReporting.organizationName}";
|
||||
from_name = "${cfg.dmarcReporting.fromName}";
|
||||
msgid_from = "${cfg.dmarcReporting.domain}";
|
||||
${lib.optionalString (cfg.dmarcReporting.excludeDomains != [ ]) ''
|
||||
exclude_domains = ${builtins.toJSON cfg.dmarcReporting.excludeDomains};
|
||||
''}
|
||||
}''}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
workers.rspamd_proxy = {
|
||||
type = "rspamd_proxy";
|
||||
bindSockets = [
|
||||
{
|
||||
socket = "/run/rspamd/rspamd-milter.sock";
|
||||
mode = "0664";
|
||||
}
|
||||
];
|
||||
count = 1; # Do not spawn too many processes of this type
|
||||
extraConfig = ''
|
||||
milter = yes; # Enable milter mode
|
||||
timeout = 120s; # Needed for Milter usually
|
||||
|
||||
upstream "local" {
|
||||
default = yes; # Self-scan upstreams are always default
|
||||
self_scan = yes; # Enable self-scan
|
||||
}
|
||||
'';
|
||||
};
|
||||
workers.controller = {
|
||||
type = "controller";
|
||||
count = 1;
|
||||
bindSockets = [
|
||||
{
|
||||
socket = "/run/rspamd/worker-controller.sock";
|
||||
mode = "0666";
|
||||
}
|
||||
];
|
||||
includes = [ ];
|
||||
extraConfig = ''
|
||||
static_dir = "''${WWWDIR}"; # Serve the web UI static assets
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
workers.rspamd_proxy = {
|
||||
type = "rspamd_proxy";
|
||||
bindSockets = [{
|
||||
socket = "/run/rspamd/rspamd-milter.sock";
|
||||
mode = "0664";
|
||||
}];
|
||||
count = 1; # Do not spawn too many processes of this type
|
||||
extraConfig = ''
|
||||
milter = yes; # Enable milter mode
|
||||
timeout = 120s; # Needed for Milter usually
|
||||
services.redis.servers.rspamd.enable = lib.mkDefault true;
|
||||
|
||||
upstream "local" {
|
||||
default = yes; # Self-scan upstreams are always default
|
||||
self_scan = yes; # Enable self-scan
|
||||
systemd.tmpfiles.settings."10-rspamd.conf" = {
|
||||
"${cfg.dkimKeyDirectory}" = {
|
||||
d = {
|
||||
# Create /var/dkim owned by rspamd user/group
|
||||
user = rspamdUser;
|
||||
group = rspamdGroup;
|
||||
};
|
||||
Z = {
|
||||
# Recursively adjust permissions in /var/dkim
|
||||
user = rspamdUser;
|
||||
group = rspamdGroup;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.rspamd = {
|
||||
requires = [ "redis-rspamd.service" ] ++ (lib.optional cfg.virusScanning "clamav-daemon.service");
|
||||
after = [ "redis-rspamd.service" ] ++ (lib.optional cfg.virusScanning "clamav-daemon.service");
|
||||
serviceConfig = lib.mkMerge [
|
||||
{
|
||||
SupplementaryGroups = [ config.services.redis.servers.rspamd.group ];
|
||||
}
|
||||
'';
|
||||
};
|
||||
workers.controller = {
|
||||
type = "controller";
|
||||
count = 1;
|
||||
bindSockets = [{
|
||||
socket = "/run/rspamd/worker-controller.sock";
|
||||
mode = "0666";
|
||||
}];
|
||||
includes = [];
|
||||
extraConfig = ''
|
||||
static_dir = "''${WWWDIR}"; # Serve the web UI static assets
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
services.redis.servers.rspamd.enable = lib.mkDefault true;
|
||||
|
||||
systemd.tmpfiles.settings."10-rspamd.conf" = {
|
||||
"${cfg.dkimKeyDirectory}" = {
|
||||
d = {
|
||||
# Create /var/dkim owned by rspamd user/group
|
||||
user = rspamdUser;
|
||||
group = rspamdGroup;
|
||||
};
|
||||
Z = {
|
||||
# Recursively adjust permissions in /var/dkim
|
||||
user = rspamdUser;
|
||||
group = rspamdGroup;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.rspamd = {
|
||||
requires = [ "redis-rspamd.service" ] ++ (lib.optional cfg.virusScanning "clamav-daemon.service");
|
||||
after = [ "redis-rspamd.service" ] ++ (lib.optional cfg.virusScanning "clamav-daemon.service");
|
||||
serviceConfig = lib.mkMerge [
|
||||
{
|
||||
SupplementaryGroups = [ config.services.redis.servers.rspamd.group ];
|
||||
}
|
||||
(lib.optionalAttrs cfg.dkimSigning {
|
||||
ExecStartPre = map createDkimKeypair cfg.domains;
|
||||
ReadWritePaths = [ cfg.dkimKeyDirectory ];
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services.rspamd-dmarc-reporter = lib.optionalAttrs cfg.dmarcReporting.enable {
|
||||
# Explicitly select yesterday's date to work around broken
|
||||
# default behaviour when called without a date.
|
||||
# https://github.com/rspamd/rspamd/issues/4062
|
||||
script = ''
|
||||
${pkgs.rspamd}/bin/rspamadm dmarc_report $(date -d "yesterday" "+%Y%m%d")
|
||||
'';
|
||||
serviceConfig = {
|
||||
User = "${config.services.rspamd.user}";
|
||||
Group = "${config.services.rspamd.group}";
|
||||
|
||||
AmbientCapabilities = [];
|
||||
CapabilityBoundingSet = "";
|
||||
DevicePolicy = "closed";
|
||||
IPAddressAllow = "localhost";
|
||||
LockPersonality = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = true;
|
||||
PrivateMounts = true;
|
||||
PrivateTmp = true;
|
||||
PrivateUsers = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
ProtectSystem = "strict";
|
||||
RemoveIPC = true;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@privileged"
|
||||
(lib.optionalAttrs cfg.dkimSigning {
|
||||
ExecStartPre = map createDkimKeypair cfg.domains;
|
||||
ReadWritePaths = [ cfg.dkimKeyDirectory ];
|
||||
})
|
||||
];
|
||||
UMask = "0077";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.rspamd-dmarc-reporter = lib.optionalAttrs cfg.dmarcReporting.enable {
|
||||
description = "Daily delivery of aggregated DMARC reports";
|
||||
wantedBy = [
|
||||
"timers.target"
|
||||
];
|
||||
timerConfig = {
|
||||
OnCalendar = "daily";
|
||||
Persistent = true;
|
||||
RandomizedDelaySec = 86400;
|
||||
FixedRandomDelay = true;
|
||||
systemd.services.rspamd-dmarc-reporter = lib.optionalAttrs cfg.dmarcReporting.enable {
|
||||
# Explicitly select yesterday's date to work around broken
|
||||
# default behaviour when called without a date.
|
||||
# https://github.com/rspamd/rspamd/issues/4062
|
||||
script = ''
|
||||
${pkgs.rspamd}/bin/rspamadm dmarc_report $(date -d "yesterday" "+%Y%m%d")
|
||||
'';
|
||||
serviceConfig = {
|
||||
User = "${config.services.rspamd.user}";
|
||||
Group = "${config.services.rspamd.group}";
|
||||
|
||||
AmbientCapabilities = [ ];
|
||||
CapabilityBoundingSet = "";
|
||||
DevicePolicy = "closed";
|
||||
IPAddressAllow = "localhost";
|
||||
LockPersonality = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = true;
|
||||
PrivateMounts = true;
|
||||
PrivateTmp = true;
|
||||
PrivateUsers = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
ProtectSystem = "strict";
|
||||
RemoveIPC = true;
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@privileged"
|
||||
];
|
||||
UMask = "0077";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.postfix = {
|
||||
after = [ rspamdSocket ];
|
||||
requires = [ rspamdSocket ];
|
||||
};
|
||||
systemd.timers.rspamd-dmarc-reporter = lib.optionalAttrs cfg.dmarcReporting.enable {
|
||||
description = "Daily delivery of aggregated DMARC reports";
|
||||
wantedBy = [
|
||||
"timers.target"
|
||||
];
|
||||
timerConfig = {
|
||||
OnCalendar = "daily";
|
||||
Persistent = true;
|
||||
RandomizedDelaySec = 86400;
|
||||
FixedRandomDelay = true;
|
||||
};
|
||||
};
|
||||
|
||||
users.extraUsers.${postfixCfg.user}.extraGroups = [ rspamdCfg.group ];
|
||||
};
|
||||
systemd.services.postfix = {
|
||||
after = [ rspamdSocket ];
|
||||
requires = [ rspamdSocket ];
|
||||
};
|
||||
|
||||
users.extraUsers.${postfixCfg.user}.extraGroups = [ rspamdCfg.group ];
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -14,72 +14,79 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.mailserver;
|
||||
certificatesDeps =
|
||||
if cfg.certificateScheme == "manual" then
|
||||
[]
|
||||
[ ]
|
||||
else if cfg.certificateScheme == "selfsigned" then
|
||||
[ "mailserver-selfsigned-certificate.service" ]
|
||||
else
|
||||
[ "acme-finished-${cfg.fqdn}.target" ];
|
||||
in
|
||||
{
|
||||
config = with cfg; lib.mkIf enable {
|
||||
# Create self signed certificate
|
||||
systemd.services.mailserver-selfsigned-certificate = lib.mkIf (cfg.certificateScheme == "selfsigned") {
|
||||
after = [ "local-fs.target" ];
|
||||
script = ''
|
||||
# Create certificates if they do not exist yet
|
||||
dir="${cfg.certificateDirectory}"
|
||||
fqdn="${cfg.fqdn}"
|
||||
[[ $fqdn == /* ]] && fqdn=$(< "$fqdn")
|
||||
key="$dir/key-${cfg.fqdn}.pem";
|
||||
cert="$dir/cert-${cfg.fqdn}.pem";
|
||||
config =
|
||||
with cfg;
|
||||
lib.mkIf enable {
|
||||
# Create self signed certificate
|
||||
systemd.services.mailserver-selfsigned-certificate =
|
||||
lib.mkIf (cfg.certificateScheme == "selfsigned")
|
||||
{
|
||||
after = [ "local-fs.target" ];
|
||||
script = ''
|
||||
# Create certificates if they do not exist yet
|
||||
dir="${cfg.certificateDirectory}"
|
||||
fqdn="${cfg.fqdn}"
|
||||
[[ $fqdn == /* ]] && fqdn=$(< "$fqdn")
|
||||
key="$dir/key-${cfg.fqdn}.pem";
|
||||
cert="$dir/cert-${cfg.fqdn}.pem";
|
||||
|
||||
if [[ ! -f $key || ! -f $cert ]]; then
|
||||
mkdir -p "${cfg.certificateDirectory}"
|
||||
(umask 077; "${pkgs.openssl}/bin/openssl" genrsa -out "$key" 2048) &&
|
||||
"${pkgs.openssl}/bin/openssl" req -new -key "$key" -x509 -subj "/CN=$fqdn" \
|
||||
-days 3650 -out "$cert"
|
||||
fi
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
PrivateTmp = true;
|
||||
if [[ ! -f $key || ! -f $cert ]]; then
|
||||
mkdir -p "${cfg.certificateDirectory}"
|
||||
(umask 077; "${pkgs.openssl}/bin/openssl" genrsa -out "$key" 2048) &&
|
||||
"${pkgs.openssl}/bin/openssl" req -new -key "$key" -x509 -subj "/CN=$fqdn" \
|
||||
-days 3650 -out "$cert"
|
||||
fi
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
PrivateTmp = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Create maildir folder before dovecot startup
|
||||
systemd.services.dovecot2 = {
|
||||
wants = certificatesDeps;
|
||||
after = certificatesDeps;
|
||||
preStart =
|
||||
let
|
||||
directories = lib.strings.escapeShellArgs (
|
||||
[ mailDirectory ] ++ lib.optional (cfg.indexDir != null) cfg.indexDir
|
||||
);
|
||||
in
|
||||
''
|
||||
# Create mail directory and set permissions. See
|
||||
# <https://doc.dovecot.org/main/core/config/shared_mailboxes.html#filesystem-permissions-1>.
|
||||
# Prevent world-readable paths, even temporarily.
|
||||
umask 007
|
||||
mkdir -p ${directories}
|
||||
chgrp "${vmailGroupName}" ${directories}
|
||||
chmod 02770 ${directories}
|
||||
'';
|
||||
};
|
||||
|
||||
# Postfix requires dovecot lmtp socket, dovecot auth socket and certificate to work
|
||||
systemd.services.postfix = {
|
||||
wants = certificatesDeps;
|
||||
after = [ "dovecot2.service" ] ++ lib.optional cfg.dkimSigning "rspamd.service" ++ certificatesDeps;
|
||||
requires = [ "dovecot2.service" ] ++ lib.optional cfg.dkimSigning "rspamd.service";
|
||||
};
|
||||
};
|
||||
|
||||
# Create maildir folder before dovecot startup
|
||||
systemd.services.dovecot2 = {
|
||||
wants = certificatesDeps;
|
||||
after = certificatesDeps;
|
||||
preStart = let
|
||||
directories = lib.strings.escapeShellArgs (
|
||||
[ mailDirectory ]
|
||||
++ lib.optional (cfg.indexDir != null) cfg.indexDir
|
||||
);
|
||||
in ''
|
||||
# Create mail directory and set permissions. See
|
||||
# <https://doc.dovecot.org/main/core/config/shared_mailboxes.html#filesystem-permissions-1>.
|
||||
# Prevent world-readable paths, even temporarily.
|
||||
umask 007
|
||||
mkdir -p ${directories}
|
||||
chgrp "${vmailGroupName}" ${directories}
|
||||
chmod 02770 ${directories}
|
||||
'';
|
||||
};
|
||||
|
||||
# Postfix requires dovecot lmtp socket, dovecot auth socket and certificate to work
|
||||
systemd.services.postfix = {
|
||||
wants = certificatesDeps;
|
||||
after = [ "dovecot2.service" ]
|
||||
++ lib.optional cfg.dkimSigning "rspamd.service"
|
||||
++ certificatesDeps;
|
||||
requires = [ "dovecot2.service" ]
|
||||
++ lib.optional cfg.dkimSigning "rspamd.service";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,7 +14,12 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
with config.mailserver;
|
||||
|
||||
|
@ -28,7 +33,6 @@ let
|
|||
group = vmailGroupName;
|
||||
};
|
||||
|
||||
|
||||
virtualMailUsersActivationScript = pkgs.writeScript "activate-virtual-mail-users" ''
|
||||
#!${pkgs.stdenv.shell}
|
||||
|
||||
|
@ -46,28 +50,33 @@ let
|
|||
|
||||
# Copy user's sieve script to the correct location (if it exists). If it
|
||||
# is null, remove the file.
|
||||
${lib.concatMapStringsSep "\n" ({ name, sieveScript }:
|
||||
if lib.isString sieveScript then ''
|
||||
if (! test -d "${sieveDirectory}/${name}"); then
|
||||
mkdir -p "${sieveDirectory}/${name}"
|
||||
chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}"
|
||||
chmod 770 "${sieveDirectory}/${name}"
|
||||
fi
|
||||
cat << 'EOF' > "${sieveDirectory}/${name}/default.sieve"
|
||||
${sieveScript}
|
||||
EOF
|
||||
chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}/default.sieve"
|
||||
'' else ''
|
||||
if (test -f "${sieveDirectory}/${name}/default.sieve"); then
|
||||
rm "${sieveDirectory}/${name}/default.sieve"
|
||||
fi
|
||||
if (test -f "${sieveDirectory}/${name}.svbin"); then
|
||||
rm "${sieveDirectory}/${name}/default.svbin"
|
||||
fi
|
||||
'') (map (user: { inherit (user) name sieveScript; })
|
||||
(lib.attrValues loginAccounts))}
|
||||
${lib.concatMapStringsSep "\n" (
|
||||
{ name, sieveScript }:
|
||||
if lib.isString sieveScript then
|
||||
''
|
||||
if (! test -d "${sieveDirectory}/${name}"); then
|
||||
mkdir -p "${sieveDirectory}/${name}"
|
||||
chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}"
|
||||
chmod 770 "${sieveDirectory}/${name}"
|
||||
fi
|
||||
cat << 'EOF' > "${sieveDirectory}/${name}/default.sieve"
|
||||
${sieveScript}
|
||||
EOF
|
||||
chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}/default.sieve"
|
||||
''
|
||||
else
|
||||
''
|
||||
if (test -f "${sieveDirectory}/${name}/default.sieve"); then
|
||||
rm "${sieveDirectory}/${name}/default.sieve"
|
||||
fi
|
||||
if (test -f "${sieveDirectory}/${name}.svbin"); then
|
||||
rm "${sieveDirectory}/${name}/default.svbin"
|
||||
fi
|
||||
''
|
||||
) (map (user: { inherit (user) name sieveScript; }) (lib.attrValues loginAccounts))}
|
||||
'';
|
||||
in {
|
||||
in
|
||||
{
|
||||
config = lib.mkIf enable {
|
||||
# assert that all accounts provide a password
|
||||
assertions = map (acct: {
|
||||
|
@ -76,15 +85,19 @@ in {
|
|||
}) (lib.attrValues loginAccounts);
|
||||
|
||||
# warn for accounts that specify both password and file
|
||||
warnings = map
|
||||
(acct: "${acct.name} specifies both a password hash and hash file; hash file will be used")
|
||||
(lib.filter
|
||||
(acct: (acct.hashedPassword != null && acct.hashedPasswordFile != null))
|
||||
(lib.attrValues loginAccounts));
|
||||
warnings =
|
||||
map (acct: "${acct.name} specifies both a password hash and hash file; hash file will be used")
|
||||
(
|
||||
lib.filter (acct: (acct.hashedPassword != null && acct.hashedPasswordFile != null)) (
|
||||
lib.attrValues loginAccounts
|
||||
)
|
||||
);
|
||||
|
||||
# set the vmail gid to a specific value
|
||||
users.groups = {
|
||||
"${vmailGroupName}" = { gid = vmailUID; };
|
||||
"${vmailGroupName}" = {
|
||||
gid = vmailUID;
|
||||
};
|
||||
};
|
||||
|
||||
# define all users
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue