{ config, pkgs, lib, ... }: let mail_config = config.mailserver; vmail_config = mail_config.vmail_config; passwdDir = "/run/dovecot2"; passwdFile = "${passwdDir}/passwd"; postfixCfg = config.services.postfix; genPasswdScript = pkgs.writeScript "generate-password-file" '' #!${pkgs.stdenv.shell} set -euo pipefail if (! test -d "${passwdDir}"); then mkdir "${passwdDir}" chmod 755 "${passwdDir}" fi for f in ${ builtins.toString (lib.mapAttrsToList (_: value: value.passwordFile) mail_config.accounts) }; do if [ ! -f "$f" ]; then echo "Expected password hash file $f does not exist!" exit 1 fi done cat < ${passwdFile} ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name}:$(head -n 1 ${value.passwordFile})") mail_config.accounts) } EOF chmod 600 ${passwdFile} ''; pipeBin = pkgs.stdenv.mkDerivation { name = "pipe_bin"; src = ./pipe_bin; buildInputs = with pkgs; [makeWrapper coreutils bash rspamd]; buildCommand = '' mkdir -p $out/pipe/bin cp $src/* $out/pipe/bin/ chmod a+x $out/pipe/bin/* patchShebangs $out/pipe/bin for file in $out/pipe/bin/*; do wrapProgram $file \ --set PATH "${pkgs.coreutils}/bin:${pkgs.rspamd}/bin" done ''; }; in { config = lib.mkIf (mail_config.enable) { services.dovecot2 = { enable = true; enableImap = true; enableLmtp = true; enableQuota = true; enablePop3 = false; enablePAM = false; # Not using PAM for Auth mailUser = vmail_config.user; mailGroup = vmail_config.group; mailLocation = "maildir:${vmail_config.directory}/%d/%n"; sslServerCert = mail_config.ssl_config.cert; sslServerKey = mail_config.ssl_config.key; # For Sieve modules = with pkgs; [dovecot_pigeonhole]; protocols = ["sieve"]; sieveScripts = { after = builtins.toFile "spam.sieve" '' require "fileinto"; if header :is "X-Spam" "Yes" { fileinto "Junk"; stop; } ''; }; mailboxes = { Trash = { auto = "no"; specialUse = "Trash"; }; Junk = { auto = "subscribe"; specialUse = "Junk"; }; Drafts = { auto = "subscribe"; specialUse = "Drafts"; }; Sent = { auto = "subscribe"; specialUse = "Sent"; }; }; extraConfig = '' ${lib.optionalString mail_config.debug_mode '' mail_debug = yes auth_debug = yes verbose_ssl = yes ''} service imap-login { inet_listener imap { port = 143 } inet_listener imaps { port = 993 ssl = yes } } protocol imap { mail_max_userip_connections = 100 mail_plugins = $mail_plugins imap_sieve } ssl = required ssl_min_protocol = TLSv1.2 ssl_prefer_server_ciphers = yes service lmtp { unix_listener dovecot-lmtp { group = ${postfixCfg.group} mode = 0600 user = ${postfixCfg.user} } } recipient_delimiter = "+" lmtp_save_to_detail_mailbox = "no" protocol lmtp { mail_plugins = $mail_plugins sieve } mail_access_groups = "${vmail_config.group}" userdb { driver = static args = uid=${toString vmail_config.user_id} gid=${toString vmail_config.group_id} } passdb { driver = passwd-file args = ${passwdFile} } service auth { unix_listener auth { mode = 0660 user = ${postfixCfg.user} group = ${postfixCfg.group} } } auth_mechanisms = plain login namespace inbox { separator = "." inbox = yes } plugin { sieve_plugins = sieve_imapsieve sieve_extprograms sieve = file:${mail_config.sieve_directory}/%u/scripts;active=${mail_config.sieve_directory}/%u/active.sieve sieve_default = file:${mail_config.sieve_directory}/%u/default.sieve sieve_default_name = default # From elsewhere to Spam folder imapsieve_mailbox1_name = Junk imapsieve_mailbox1_causes = COPY imapsieve_mailbox1_before = file:${./spam_sieve/report-spam.sieve} # From Spam folder to elsewhere imapsieve_mailbox2_name = * imapsieve_mailbox2_from = Junk imapsieve_mailbox2_causes = COPY imapsieve_mailbox2_before = file:${./spam_sieve/report-ham.sieve} sieve_pipe_bin_dir = ${pipeBin}/pipe/bin sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment } lda_mailbox_autosubscribe = yes lda_mailbox_autocreate = yes ''; }; systemd.services.dovecot2 = { preStart = '' ${genPasswdScript} ''; }; systemd.services.postfix.restartTriggers = [genPasswdScript]; }; }