mailsystem: Add minimal dovecot configuration
This commit is contained in:
parent
56feea5754
commit
bc01d4d2d0
4 changed files with 309 additions and 0 deletions
255
mailsystem/dovecot.nix
Normal file
255
mailsystem/dovecot.nix
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with (import ./common.nix {inherit config;}); let
|
||||
cfg = config.mailsystem;
|
||||
postfixCfg = config.services.postfix;
|
||||
dovecot2Cfg = config.services.dovecot2;
|
||||
|
||||
runtimeStateDir = "/run/dovecot2";
|
||||
|
||||
staticPasswdFile = "${runtimeStateDir}/passwd";
|
||||
initialPasswdFile = "${runtimeStateDir}/initial-passwd";
|
||||
userdbFile = "${runtimeStateDir}/userdb";
|
||||
# dovecotDynamicStateDir and dovecotDynamicPasswdFile are defined in common.nix
|
||||
|
||||
systemUsers = lib.filterAttrs (user: value: value.isSystemUser) cfg.accounts;
|
||||
normalUsers = lib.filterAttrs (user: value: !value.isSystemUser) cfg.accounts;
|
||||
|
||||
genUserdbEntry = user: value: "${user}:::::::";
|
||||
genPasswdEntry = user: value: "${user}:${"$(head -n 1 ${value.hashedPasswordFile})"}::::::";
|
||||
|
||||
genAuthDbsScript = pkgs.writeScript "generate-dovecot-auth-dbs" ''
|
||||
#!${pkgs.stdenv.shell}
|
||||
set -euo pipefail
|
||||
|
||||
if (! test -d "${runtimeStateDir}"); then
|
||||
mkdir "${runtimeStateDir}"
|
||||
chmod 755 "${runtimeStateDir}"
|
||||
fi
|
||||
|
||||
if (! test -d "${dovecotDynamicStateDir}"); then
|
||||
mkdir "${dovecotDynamicStateDir}"
|
||||
chmod 755 "${dovecotDynamicStateDir}"
|
||||
fi
|
||||
|
||||
# Ensure passwd files are not world-readable at any time
|
||||
umask 077
|
||||
|
||||
# Ensure we have a file for every user's (initial) password hash.
|
||||
for f in ${builtins.toString (lib.mapAttrsToList (user: value: value.hashedPasswordFile) cfg.accounts)}; do
|
||||
if [ ! -f "$f" ]; then
|
||||
echo "Expected password hash file $f does not exist!"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Prepare static passwd-file for system users
|
||||
cat <<EOF > "${staticPasswdFile}"
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList genPasswdEntry systemUsers)}
|
||||
EOF
|
||||
|
||||
# Prepare initial passwd-file for dynamic users
|
||||
# (used for lookup during actual passwd-file generation)
|
||||
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}"
|
||||
|
||||
# Prepare userdb-file
|
||||
cat <<EOF > "${userdbFile}"
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList genUserdbEntry cfg.accounts)}
|
||||
EOF
|
||||
'';
|
||||
|
||||
genMaildir = pkgs.writeScript "generate-maildir" ''
|
||||
#!${pkgs.stdenv.shell}
|
||||
|
||||
# Create mail directory and set permissions accordingly.
|
||||
umask 007
|
||||
mkdir -p ${cfg.mailDirectory}
|
||||
chgrp "${cfg.vmailGroupName}" ${cfg.mailDirectory}
|
||||
chmod 02770 ${cfg.mailDirectory}
|
||||
'';
|
||||
in {
|
||||
options.mailsystem.dovecot.dhparamSize = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 2048;
|
||||
description = "The bit size for the prime that is used during a Diffie-Hellman key exchange by dovecot.";
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions =
|
||||
lib.mapAttrsToList (user: value: [
|
||||
{
|
||||
assertion = value.hashedPasswordFile != null;
|
||||
message = "A file containing the hashed password for user ${user} needs to be set.";
|
||||
}
|
||||
])
|
||||
cfg.accounts;
|
||||
|
||||
services.dovecot2 = {
|
||||
enable = true;
|
||||
enableImap = true;
|
||||
enablePAM = false;
|
||||
# TODO: enable quota and setup quota warnings
|
||||
#enableQuota = true;
|
||||
mailUser = cfg.vmailUserName;
|
||||
mailGroup = cfg.vmailGroupName;
|
||||
mailLocation = "maildir:~/Maildir";
|
||||
|
||||
sslServerCert = sslCertPath;
|
||||
sslServerKey = sslKeyPath;
|
||||
|
||||
enableLmtp = true;
|
||||
|
||||
modules = [
|
||||
# sieves + managesieve
|
||||
pkgs.dovecot_pigeonhole
|
||||
];
|
||||
|
||||
# enable managesieve
|
||||
protocols = ["sieve"];
|
||||
|
||||
pluginSettings = {
|
||||
sieve = "file:~/sieve;active=~/.dovecot.sieve";
|
||||
};
|
||||
|
||||
# TODO: move configuration to default.nix?
|
||||
mailboxes = {
|
||||
Drafts = {
|
||||
auto = "subscribe";
|
||||
specialUse = "Drafts";
|
||||
};
|
||||
Junk = {
|
||||
auto = "subscribe";
|
||||
specialUse = "Junk";
|
||||
};
|
||||
Trash = {
|
||||
auto = "subscribe";
|
||||
specialUse = "Trash";
|
||||
};
|
||||
Sent = {
|
||||
auto = "subscribe";
|
||||
specialUse = "Sent";
|
||||
};
|
||||
};
|
||||
|
||||
extraConfig = ''
|
||||
service imap-login {
|
||||
inet_listener imap {
|
||||
# Using starttls for encryption shifts the responsibility for
|
||||
# encrypted communication over to the client in the face of MITM.
|
||||
# However, it has shown that clients are often not enforcing it.
|
||||
port = 0 # clients should use starttls instaed
|
||||
}
|
||||
inet_listener imaps {
|
||||
port = 993
|
||||
ssl = yes
|
||||
}
|
||||
}
|
||||
|
||||
protocol imap {
|
||||
mail_max_userip_connections = 100
|
||||
mail_plugins = $mail_plugins imap_sieve
|
||||
}
|
||||
|
||||
mail_access_groups = ${cfg.vmailGroupName}
|
||||
ssl = required
|
||||
ssl_min_protocol = TLSv1.2
|
||||
ssl_prefer_server_ciphers = yes
|
||||
|
||||
service lmtp {
|
||||
unix_listener dovecot-lmtp {
|
||||
user = ${postfixCfg.user}
|
||||
group = ${postfixCfg.group}
|
||||
mode = 0600
|
||||
}
|
||||
}
|
||||
|
||||
recipient_delimiter = +
|
||||
lmtp_save_to_detail_mailbox = no
|
||||
|
||||
protocol lmtp {
|
||||
mail_plugins = $mail_plugins sieve
|
||||
}
|
||||
|
||||
# Passwords stored among two passwd-files: One for users allowed to
|
||||
# change their password and one for any other user (mostly system
|
||||
# users) with immutable passwords.
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
args = ${dovecotDynamicPasswdFile}
|
||||
}
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
args = ${staticPasswdFile}
|
||||
}
|
||||
|
||||
userdb {
|
||||
driver = passwd-file
|
||||
args = ${userdbFile}
|
||||
default_fields = uid=${builtins.toString cfg.vmailUID} gid=${builtins.toString cfg.vmailUID} home=${cfg.mailDirectory}/%d/%n
|
||||
}
|
||||
|
||||
service auth {
|
||||
unix_listener auth {
|
||||
user = ${postfixCfg.user}
|
||||
group = ${postfixCfg.group}
|
||||
mode = 0660
|
||||
}
|
||||
}
|
||||
|
||||
auth_mechanisms = plain login
|
||||
|
||||
namespace inbox {
|
||||
separator = /
|
||||
inbox = yes
|
||||
}
|
||||
|
||||
lda_mailbox_autosubscribe = yes
|
||||
lda_mailbox_autocreate = yes
|
||||
'';
|
||||
};
|
||||
|
||||
security.dhparams.params.dovecot2.bits = cfg.dovecot.dhparamSize;
|
||||
|
||||
systemd.services.dovecot2 = {
|
||||
preStart = ''
|
||||
${genAuthDbsScript}
|
||||
${genMaildir}
|
||||
'';
|
||||
wants = sslCertService;
|
||||
after = sslCertService;
|
||||
};
|
||||
systemd.services.postfix.restartTriggers = [genAuthDbsScript];
|
||||
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [
|
||||
993 # imaps
|
||||
4190 #managesieve
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue