{ 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; environment.systemPackages = [ # sieves + managesieve pkgs.dovecot_pigeonhole ]; # For compatibility with python imaplib environment.etc."dovecot/modules".source = "/run/current-system/sw/lib/dovecot/modules"; 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; # 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}/%{domain}/%{username} } 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 ]; }; }; }