278 lines
7.7 KiB
Nix
278 lines
7.7 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
with (import ./common.nix {inherit config pkgs;}); 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
|
|
|
|
# Prepare static passwd-file for system users
|
|
${mailnixCmd} generate-static-passdb > "${staticPasswdFile}"
|
|
|
|
# Prepare/Update passwd-file for dynamic users
|
|
${mailnixCmd} update-dynamic-passdb ${dovecotDynamicPasswdFile} > "${dovecotDynamicPasswdFile}"
|
|
|
|
${lib.optionalString cfg.roundcube.enable ''
|
|
# Ensure roundcube has access to dynamic passwd file
|
|
${pkgs.acl.bin}/bin/setfacl -m "u:${config.services.phpfpm.pools.roundcube.user}:rw" "${dovecotDynamicPasswdFile}"
|
|
''}
|
|
|
|
# Prepare userdb-file
|
|
${mailnixCmd} generate-userdb > "${userdbFile}"
|
|
'';
|
|
|
|
genMaildirScript = 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}
|
|
'';
|
|
|
|
junkMailboxes = builtins.attrNames (lib.filterAttrs (n: v: v ? "specialUse" && v.specialUse == "Junk") dovecot2Cfg.mailboxes);
|
|
junkMailboxNumber = builtins.length junkMailboxes;
|
|
# The assertion guarantees that there is exactly one Junk mailbox.
|
|
junkMailboxName =
|
|
if junkMailboxNumber == 1
|
|
then builtins.elemAt junkMailboxes 0
|
|
else "";
|
|
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 =
|
|
[
|
|
{
|
|
assertion = junkMailboxNumber == 1;
|
|
message = "mailnix requires exactly one dovecot mailbox with the 'special use' flag to 'Junk' (${builtins.toString junkMailboxNumber} have been found)";
|
|
}
|
|
]
|
|
++ 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";
|
|
};
|
|
|
|
sieve = {
|
|
extensions = [
|
|
"fileinto"
|
|
"mailbox"
|
|
];
|
|
|
|
scripts.after = builtins.toFile "spam.sieve" ''
|
|
require "fileinto";
|
|
require "mailbox";
|
|
|
|
if header :is "X-Spam" "Yes" {
|
|
fileinto :create "${junkMailboxName}";
|
|
stop;
|
|
}
|
|
'';
|
|
|
|
pipeBins = map lib.getExe [
|
|
(pkgs.writeShellScriptBin "learn-ham.sh"
|
|
"exec ${pkgs.rspamd}/bin/rspamc -h ${rspamdControllerSocket} learn_ham")
|
|
(pkgs.writeShellScriptBin "learn-spam.sh"
|
|
"exec ${pkgs.rspamd}/bin/rspamc -h ${rspamdControllerSocket} learn_spam")
|
|
];
|
|
};
|
|
|
|
imapsieve.mailbox = [
|
|
{
|
|
name = junkMailboxName;
|
|
causes = ["COPY" "APPEND"];
|
|
before = ./dovecot/report-spam.sieve;
|
|
}
|
|
{
|
|
name = "*";
|
|
from = junkMailboxName;
|
|
causes = ["COPY"];
|
|
before = ./dovecot/report-ham.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}
|
|
${genMaildirScript}
|
|
'';
|
|
wants = sslCertService;
|
|
after = sslCertService;
|
|
};
|
|
systemd.services.postfix.restartTriggers = [genAuthDbsScript];
|
|
|
|
networking.firewall = lib.mkIf cfg.openFirewall {
|
|
allowedTCPPorts = [
|
|
993 # imaps
|
|
4190 #managesieve
|
|
];
|
|
};
|
|
};
|
|
}
|