{pkgs, lib, config, system, ...}: let inherit (builtins) toString toFile attrNames; inherit (lib) types; inherit (config.services) dovecot2 postfix x509; unlines = lib.concatStringsSep "\n"; when = x: y: if x == null then "" else y; extSep = postfix.recipientDelimiter; dirSep = extSep; libDir = "/var/lib/dovecot"; mailDir = "${libDir}/mail"; sieveDir = "${libDir}/sieve"; authDir = "${libDir}/auth"; authUser = dovecot2.mailUser; # TODO: php_roundcube authGroup = dovecot2.mailGroup; # TODO: php_roundcube escapeGroup = lib.stringAsChars (c: if "a"<=c && c<="z" || "0"<=c && c<="9" || c=="-" then c else "_"); in { config = { #environment.etc."nginx/site.d/autoconfig.conf".source = # let servers = lib.concatMapStringsSep " " # (dom: "autoconfig.${dom}") # (attrNames dovecot2.domains); # autoconfigSite = pkgs.writeTextFile { # name = "autoconfig"; # destination = "/mail/config-v1.1.xml"; # text = '' # # # # # # %EMAILDOMAIN% # # imap.%EMAILDOMAIN% # 993 # SSL # %EMAILADDRESS% # password-cleartext # # # pop.%EMAILDOMAIN% # 995 # SSL # %EMAILADDRESS% # password-cleartext # # false # true # # # # smtp.%EMAILDOMAIN% # 465 # SSL # %EMAILADDRESS% # password-cleartext # # true # false # # # # # ''; # }; # in # pkgs.writeText "autoconfig.conf" '' # server { # listen 80; # server_name ${servers}; # root ${autoconfigSite}; # access_log off; # log_not_found off; # } # server { # listen 443 ssl http2; # ssl on; # server_name ${servers}; # root ${autoconfigSite}; # access_log off; # log_not_found off; # } # ''; #services.postfix.mapFiles."transport-dovecot" = # toFile "transport-dovecot" # (unlines # (lib.mapAttrsToList # (dom: {...}: "${transportSubDomain}.${dom} lmtp:unix:private/dovecot-lmtp") # dovecot2.domains)); systemd.services.dovecot2.after = [ "postfix.service" ]; #users.extraUsers = [ # { name = "dovecot"; # uid = config.ids.uids.dovecot2; # description = "Dovecot user"; # group = dovecot2.group; # } #]; users.extraGroups = lib.mapAttrs (domain: {...}: { name = escapeGroup "${dovecot2.mailGroup}-${domain}"; }) dovecot2.domains; systemd.services.dovecot2.preStart = let sieveList = pkgs.writeText "list.sieve" '' require [ "date" , "fileinto" , "mailbox" , "variables" ]; if currentdate :matches "year" "*" { set "year" "''${1}"; } if currentdate :matches "month" "*" { set "month" "''${1}"; } if exists "List-ID" { if header :matches "List-ID" "*<*.*.*.*>*" { set "list" "''${2}"; set "domain" "''${4}"; } elsif header :matches "List-ID" "*<*.*.*>*" { set "list" "''${2}"; set "domain" "''${3}"; } fileinto :create "Listes+''${domain}+''${list}+''${year}+''${month}"; stop; } ''; sieveSpam = pkgs.writeText "spam.sieve" '' require [ "imap4flags" ]; if header :contains "X-Spam-Level" "***" { addflag "Junk"; } ''; sieveExtension = pkgs.writeText "extension.sieve" '' require [ "envelope" , "fileinto" , "mailbox" , "subaddress" , "variables" ]; if envelope :matches :detail "TO" "*" { set "extension" "''${1}"; } if not string :is "''${extension}" "" { fileinto :create "Plus+''${extension}"; stop; } ''; dovecot-virtual = pkgs.writeText "dovecot-virtual" '' all all+* all ''; in '' # SEE: http://wiki2.dovecot.org/SharedMailboxes/Permissions # The sticky bit is to allow the acl.db{.lock,} done by dovecot install -D -d -m 2771 \ -o ${dovecot2.mailUser} \ -g ${dovecot2.mailGroup} \ ${mailDir} # Install global sieves install -D -d -m 0755 \ -o root \ -g root \ ${sieveDir} \ ${sieveDir}/after.d \ ${sieveDir}/before.d \ ${sieveDir}/global.d ln -fns ${sieveList} ${sieveDir}/global.d/list.sieve ln -fns ${sieveExtension} ${sieveDir}/global.d/extension.sieve ln -fns ${sieveSpam} ${sieveDir}/global.d/spam.sieve for f in ${sieveDir}/*/*.sieve do ${pkgs.dovecot_pigeonhole}/bin/sievec $f done # Install pop3 Inbox install -D -m 0644 \ -o root \ -g root \ ${dovecot-virtual} \ ${libDir}/pop3/INBOX/dovecot-virtual '' + '' # Install domains new_uid=5000 '' + unlines (lib.mapAttrsToList (domain: {accounts, ...}: let domainGroup = escapeGroup "${dovecot2.mailGroup}-${domain}"; in '' install -D -d -m 1770 \ -o ${dovecot2.mailUser} \ -g ${domainGroup} \ ${mailDir}/${domain} \ ${libDir}/control/${domain} \ ${libDir}/index/${domain} install -D -d -m 1770 \ -o ${dovecot2.mailUser} \ -g ${authGroup} \ ${libDir}/auth \ ${libDir}/auth/${domain} dir_passwd=${libDir}/auth/${domain} old_passwd=$dir_passwd/passwd new_passwd=$(TMPDIR= mktemp --tmpdir=$dir_passwd -t passwd.XXXXXXXX.tmp) # Install users '' + unlines (lib.mapAttrsToList (user: acct: '' ( home=${mailDir}/${domain}/${user} gecos= shell=/run/current-system/sw/bin/nologin if test -e $home then uid=$(stat -c %u $home) gid=$(stat -c %g $home) fi [ "''${uid:+set}" ] || { while test exists = "$(find $(dirname $home) -mindepth 1 -maxdepth 1 -uid $new_uid -printf exists -quit)" do new_uid=$((new_uid + 1)) done uid=$new_uid gid=$new_uid } install -D -d -o $uid -g $gid -m 2770 $home $home/Maildir install -d -o $uid -g $gid -m 0700 $home/sieve '' + unlines (lib.mapAttrsToList (n: v: '' install -D -m 640 -o $uid -g $gid \ ${pkgs.writeText "${n}.sieve" v} \ $home/sieve/${n}.sieve ${pkgs.dovecot_pigeonhole}/bin/sievec \ $home/sieve/${n}.sieve '') acct.sieves) + '' mail_access_groups=${lib.concatStringsSep "," ([domainGroup] ++ acct.groups)} quota=${if lib.isString acct.quota then ''"userdb_quota_rule=*:storage=${acct.quota}"'' else ""} extra_fields="userdb_uid=$uid userdb_gid=$gid userdb_mail_access_groups=$mail_access_groups $quota" #test ! -e $old_passwd || { # # Preserve password changed by another mechanism, eg. roundcube. # # But this also does not overwrite any old password set by this config. # pass="$(sed -ne "s/^${user}:\([^:]*\):.*/\1/p" $old_passwd)" #} [ "''${pass:+set}" ] || { pass=${lib.escapeShellArg acct.password} } printf '%s\n' >>$new_passwd \ "${user}:$pass:$uid:$gid:$gecos:$home:$shell:$extra_fields" ) '') accounts) + '' install -o ${authUser} -g ${authGroup} -m 0640 $new_passwd $old_passwd rm $new_passwd '' ) dovecot2.domains); services.dovecot2 = { enable = true; mailUser = "dovemail"; mailGroup = "dovemail"; modules = [ #pkgs.dovecot_antispam pkgs.dovecot_pigeonhole ]; # ${lib.concatMapStringsSep "\n" # (dom: '' # local_name imap.${dom} { # #ssl_ca = <''${caPath} # ssl_cert = <${x509.cert dom} # ssl_key = <${x509.key dom} # } # local_name pop.${dom} { # #ssl_ca = <''${caPath} # ssl_cert = <${x509.cert dom} # ssl_key = <${x509.key dom} # } # '') # dovecot2.domains # } configFile = toString (pkgs.writeText "dovecot.conf" '' passdb { driver = passwd-file args = scheme=crypt username_format=%n ${authDir}/%d/passwd } userdb { driver = prefetch } userdb { # NOTE: this userdb is only used by lda. driver = passwd-file args = username_format=%n ${authDir}/%d/passwd #default_fields = home=${mailDir}/%d/%n } mail_home = ${mailDir}/%d/%n auth_mechanisms = plain login # postfix does not supply a client cert. auth_ssl_require_client_cert = no auth_ssl_username_from_cert = yes auth_verbose = yes ${lib.optionalString dovecot2.debug '' auth_debug = yes mail_debug = yes verbose_ssl = yes ''} default_internal_user = ${dovecot2.user} default_internal_group = ${dovecot2.group} disable_plaintext_auth = yes first_valid_uid = 1000 lda_mailbox_autocreate = yes lda_mailbox_autosubscribe = yes listen = * log_timestamp = "%Y-%m-%d %H:%M:%S " # NOTE: INDEX and CONTROL are on a partition without quota, as explain in the doc. # SEE: http://wiki2.dovecot.org/Quota/FS mail_location = maildir:${mailDir}/%d/%n/Maildir:LAYOUT=fs:INDEX=${libDir}/index/%d/%n:CONTROL=${libDir}/control/%d/%n namespace inbox { # NOTE: here because protocol sieve {namespace inbox{}} does not seem to work. inbox = yes location = list = yes prefix = separator = ${dirSep} } namespace { #list = children list = yes location = maildir:${mailDir}/%%d/%%n/Maildir:LAYOUT=fs:INDEX=${libDir}/index/%d/%n/Shared/%%n:CONTROL=${libDir}/control/%d/%n/Shared/%%n prefix = Partages+%%n+ separator = ${dirSep} subscriptions = yes type = shared } mail_plugins = $mail_plugins acl quota virtual #mail_uid = ${dovecot2.mailUser} #mail_gid = ${dovecot2.mailGroup} #mail_privileged_group = mail #mail_access_groups = plugin { acl = vfile:/etc/dovecot/acl/global.d acl_anyone = allow acl_shared_dict = file:${mailDir}/%d/acl.db ##antispam_allow_append_to_spam = yes # # NOTE: pour offlineimap #antispam_backend = pipe ##antispam_crm_args = -u;${mailDir}/%d/.crm114;/usr/share/crm114/mailfilter.crm #antispam_crm_args = -u;${mailDir}/crm114;/usr/share/crm114/mailfilter.crm #antispam_crm_binary = /usr/bin/crm #antispam_debug_target = syslog ##antispam_crm_env = HOME=%h;USER=%u #antispam_ham_keywords = NonJunk #antispam_pipe_program = /usr/bin/crm #antispam_pipe_program_args = -u;${mailDir}/crm114;/usr/share/crm114/mailfilter.crm;--stats_only;--force #antispam_pipe_program_notspam_arg = --learnnonspam #antispam_pipe_program_spam_arg = --learnspam #antispam_pipe_program_unlearn_spam_args = --unlearn;--learnspam #antispam_pipe_program_unlearn_notspam_args = --unlearn;--learnnonspam #antispam_pipe_tmpdir = ${mailDir}/crm114/tmp #antispam_signature = X-CRM114-CacheID #antispam_signature_missing = move #antispam_spam = Junk #antispam_spam_keywords = Junk #antispam_trash = Trash #antispam_unsure = Unsure #antispam_verbose_debug = 0 quota = maildir:User quota quota_rule = *:storage=256M quota_rule2 = Trash:storage=+64M recipient_delimiter = ${extSep} sieve = file:${mailDir}/%d/%n/sieve;active=${mailDir}/%d/%n/sieve/main.sieve #sieve_default = file:${mailDir}/%u/default.sieve #sieve_default_name = default sieve_after = ${sieveDir}/after.d/ sieve_before = ${sieveDir}/before.d/ sieve_dir = ${mailDir}/%d/%n/sieve/ #sieve_extensions = +spamtest +spamtestplus sieve_global_dir = ${sieveDir}/global.d/ sieve_max_script_size = 1M sieve_quota_max_scripts = 0 sieve_quota_max_storage = 10M sieve_spamtest_max_value = 10 sieve_spamtest_status_header = X-Spam-Score sieve_spamtest_status_type = strlen sieve_user_log = /var/log/dovecot/%d/sieve.%n.log } protocol imap { #mail_max_userip_connections = 10 mail_plugins = $mail_plugins imap_acl imap_quota # antispam namespace inbox { inbox = yes location = list = yes mailbox Drafts { special_use = \Drafts } mailbox Junk { special_use = \Junk } mailbox Sent { special_use = \Sent } mailbox "Sent Messages" { special_use = \Sent } mailbox Trash { special_use = \Trash } prefix = separator = ${dirSep} } } protocol lda { auth_socket_path = /var/run/dovecot/auth-userdb hostname = ${config.networking.domain} info_log_path = log_path = mail_plugins = $mail_plugins sieve namespace inbox { inbox = yes location = list = yes prefix = separator = ${dirSep} } postmaster_address = postmaster${extSep}dovecot${extSep}lda@${config.networking.domain} syslog_facility = mail } protocol lmtp { #info_log_path = /tmp/dovecot-lmtp.log mail_plugins = $mail_plugins sieve namespace inbox { inbox = yes location = list = yes prefix = separator = ${dirSep} } postmaster_address = postmaster${extSep}dovecot${extSep}lmtp@${config.networking.domain} } protocol pop3 { #mail_max_userip_connections = 10 # Used by ${libDir}/pop3/INBOX/dovecot-virtual namespace all { hidden = yes list = no location = prefix = all+ separator = ${dirSep} } # Virtual namespace for the virtual INBOX. # Use a global directory for dovecot-virtual files. namespace inbox { inbox = yes hidden = yes list = no location = virtual:${libDir}/pop3:INDEX=${libDir}/index/%d/%n/POP3:LAYOUT=fs prefix = pop3+ separator = ${dirSep} } pop3_client_workarounds = pop3_fast_size_lookups = yes pop3_lock_session = yes pop3_no_flag_updates = yes # Use GUIDs to avoid accidental POP3 UIDL changes instead of IMAP UIDs. pop3_uidl_format = %g } protocol sieve { #mail_max_userip_connections = 10 #managesieve_implementation_string = Dovecot Pigeonhole managesieve_max_compile_errors = 5 #managesieve_max_line_length = 65536 #managesieve_notify_capability = mailto #managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date ihave } protocols = imap lmtp pop3 sieve service lmtp { #executable = lmtp -L process_min_avail = 2 unix_listener /var/lib/postfix/queue/private/dovecot-lmtp { user = ${postfix.user} group = ${postfix.group} mode = 0600 } #user = mail } service auth { user = root unix_listener auth-userdb { user = ${dovecot2.user} group = ${dovecot2.group} mode = 0660 } unix_listener /var/lib/postfix/queue/private/auth { user = ${postfix.user} group = ${postfix.group} mode = 0660 } } service imap { # Most of the memory goes to mmap()ing files. # You may need to increase this limit if you have huge mailboxes. #vsz_limit = process_limit = 1024 } service imap-login { #inet_listener imap { # address = 127.0.0.1 # port = 143 # ssl = no # } inet_listener imaps { port = 993 ssl = yes } } service pop3 { process_limit = 1024 } service pop3-login { inet_listener pop3s { port = 995 ssl = yes } } ssl = required #ssl_ca = <''${caPath} ssl_cert = <${x509.cert} ssl_dh = <${x509.dir}/dh.pem # gOTE: only with dovecot >= 2.3 ssl_cipher_list = ALL:!LOW:!SSLv2:!EXP:!aNULL ssl_key = <${x509.key} #ssl_verify_client_cert = yes ''); }; }; }