{pkgs, lib, config, system, ...}: let inherit (builtins) toString toFile; inherit (lib) types; inherit (pkgs.lib) unlines unlinesAttrs unlinesValues unwords; inherit (config) networking; inherit (config.services) dovecot2 postfix x509 openldap; when = x: y: if x == null then "" else y; extSep = postfix.recipientDelimiter; dirSep = extSep; stateDir = "/var/lib/dovecot"; # NOTE: nixpkgs' dovecot2.stateDir is currently not exported mailDir = "${stateDir}/mail"; sieveDir = "${stateDir}/sieve"; authDir = "${stateDir}/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 "_"); domainGroup = escapeGroup "${networking.domainBase}"; etc_dovecot = [ { target = "dovecot/${networking.domain}/dovecot-ldap.conf"; source = pkgs.writeText "dovecot-ldap.conf" '' ${lib.optionalString dovecot2.debug '' debug_level = 1 ''} # LDAP database uris = ldapi:// base = ou=posix,${openldap.domainSuffix} scope = subtree deref = never blocking = no # NOTE: sufficient for small systems and uses less resources. # LDAP auth sasl_bind = yes sasl_mech = EXTERNAL #dn = cn=admin,${openldap.domainSuffix} #dnpass = useless with sasl_mech=EXTERNAL auth_bind = no #auth_bind_userdn = cn=%n,ou=accounts,ou=posix,dc=${openldap.domainSuffix} # dovecot passdb query # DOC: http://wiki2.dovecot.org/PasswordDatabase/ExtraFields pass_filter = (&(objectClass=posixAccount)(uid=%n)(mailEnabled=TRUE)) pass_attrs = userPassword=password,\ uidNumber=userdb_uid,\ gidNumber=userdb_gid,\ mailGroupMember=userdb_mail_access_groups=${domainGroup},\ quotaBytes=userdb_quota_rule=*:bytes=%{ldap:quotaBytes},\ =user=%n@%d #homeDirectory=userdb_home # TODO: userdb_quota_rule=*:storage= default_pass_scheme = CRYPT # dovecot userdb query # DOC: http://wiki2.dovecot.org/UserDatabase/ExtraFields user_filter = (&(objectClass=posixAccount)(uid=%n)(mailEnabled=TRUE)) #user_filter = (&(objectClass=inetOrgPerson)(uid=%n)) #user_attrs = homeDirectory=home,uidNumber=uid,gidNumber=gid #user_attrs = mailHomeDirectory=home,\ # mailStorageDirectory=mail,\ # mailUidNumber=uid,\ # mailGidNumber=gid,\ # mailQuota=quota_rule=*:bytes=%$ # doveadm user query iterate_attrs = =user=%{ldap:uid}@${networking.domain} iterate_filter = (&(objectClass=posixAccount)(mailEnabled=TRUE)) ''; } ]; dovecot-virtual = pkgs.writeTextFile { name = "dovecot-virtual"; destination = "/pop3/INBOX/dovecot-virtual"; text = '' all all+* all ''; }; sieve-rspamd-filter = pkgs.stdenv.mkDerivation { name = "sieve-rspamd-filter"; nativeBuildInputs = [ pkgs.makeWrapper ]; phases = [ "installPhase" ]; installPhase = '' mkdir -p $out/bin cat > $out/bin/learn-spam.sh < $out/bin/learn-ham.sh </, to let dovecot # rename acl.db.lock (own by new user) # to acl.db (own by old user) install -D -d -m 0770 \ -o ${dovecot2.mailUser} \ -g ${domainGroup} \ ${stateDir}/acl/${networking.domain} # NOTE: domainAliases point to the very same mailboxes as domain's. for domainAlias in ${unwords networking.domainAliases} do ln -fns ${networking.domain} ${mailDir}/$domainAlias ln -fns ${networking.domain} ${stateDir}/control/$domainAlias ln -fns ${networking.domain} ${stateDir}/index/$domainAlias ln -fns ${networking.domain} ${stateDir}/acl/$domainAlias done ''; }; }; services.dovecot2 = { enable = true; debug = true; mailUser = "dovemail"; mailGroup = "dovemail"; modules = [ #pkgs.dovecot_antispam pkgs.dovecot_pigeonhole ]; sieves = { global = { list = '' 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; } ''; spam = '' require [ "imap4flags" ]; if header :contains "X-Spam-Level" "***" { addflag "Junk"; } ''; /* require ["fileinto","mailbox"]; if header :contains "X-Spam" "Yes" { fileinto :create "INBOX.Junk"; stop; } */ extension = '' require [ "envelope" , "fileinto" , "mailbox" , "subaddress" , "variables" ]; if envelope :matches :detail "TO" "*" { set "extension" "''${1}"; } if not string :is "''${extension}" "" { fileinto :create "Plus+''${extension}"; stop; } ''; report-spam = '' require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"]; if environment :matches "imap.user" "*" { set "username" "''${1}"; } pipe :copy "learn-spam.sh" [ "''${username}" ]; ''; report-ham = '' require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"]; if environment :matches "imap.mailbox" "*" { set "mailbox" "''${1}"; } if string "''${mailbox}" "Trash" { stop; } if environment :matches "imap.user" "*" { set "username" "''${1}"; } pipe :copy "learn-ham.sh" [ "''${username}" ]; ''; }; }; configFile = toString (pkgs.writeText "dovecot.conf" '' passdb { driver = ldap args = /etc/dovecot/${networking.domain}/dovecot-ldap.conf default_fields = userdb_mail_access_groups=${domainGroup} override_fields = } userdb { driver = prefetch } userdb { # NOTE: this userdb is only used by lda. driver = ldap args = /etc/dovecot/${networking.domain}/dovecot-ldap.conf default_fields = mail_access_groups=${domainGroup} override_fields = skip = found } #passdb { # driver = passwd-file # args = scheme=crypt username_format=%n ${authDir}/%d/passwd #} #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 # skip = found #} mail_home = ${mailDir}/%d/%n # NOTE: if needed, may be overrided by userdb_mail mail_location = maildir:${mailDir}/%d/%n/Maildir:LAYOUT=fs:INDEX=${stateDir}/index/%d/%n:INDEXPVT=${stateDir}/index/%d/%n:CONTROL=${stateDir}/control/%d/%n # NOTE: if needed, may be overrided by userdb_mail # NOTE: INDEX and CONTROL are on a partition without quota, as explain in the doc. # SEE: http://wiki2.dovecot.org/Quota/FS auth_mechanisms = plain login auth_ssl_require_client_cert = no # NOTE: postfix does not supply a client cert. auth_ssl_username_from_cert = yes auth_verbose = yes auth_username_format = %Lu # NOTE: lowercase the username, help with LDAP? ${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 = 10000 # NOTE: sync with LDAP's data. lda_mailbox_autocreate = yes lda_mailbox_autosubscribe = yes listen = * log_timestamp = "%Y-%m-%d %H:%M:%S " #maildir_copy_with_hardlinks = yes namespace inbox { # NOTE: here because protocol sieve {namespace inbox{}} does not seem to work. type = private inbox = yes location = list = yes prefix = separator = ${dirSep} } namespace { type = shared #list = children list = yes # NOTE: always listed in the LIST command. location = maildir:${mailDir}/%%d/%%n/Maildir:LAYOUT=fs:INDEX=${stateDir}/index/%%d/%%n/Shared:INDEXPVT=${stateDir}/index/%d/%n/Shared/%%n:CONTROL=${stateDir}/control/%d/%n/Shared # NOTE: how to access the other users' mailboxes. # NOTE: %var expands to the logged in user's variable, while # %%var expands to the other users' variables. # NOTE: INDEX and CONTROL are shared, INDEXPVT is not. prefix = Partages+%%n+ separator = ${dirSep} subscriptions = yes } mail_plugins = $mail_plugins acl quota virtual #mail_uid = ${dovecot2.mailUser} #mail_gid = ${dovecot2.mailGroup} # NOTE: each user has a dedicated (uid,gid) pair #mail_privileged_group = mail #mail_access_groups = plugin { acl = vfile:/etc/dovecot/acl/global.d acl_anyone = allow acl_shared_dict = file:${stateDir}/acl/%d/acl.db # NOTE: to let users LIST mailboxes shared by other users, # Dovecot needs a shared mailbox dictionary. # FIXME: %d not working with userdb ldap ##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 quota_max_mail_size = 20M #quota_exceeded_message =