{ pkgs, lib, config, system, ... }: let inherit (builtins) toString toFile; inherit (builtins.extraBuiltins) pass; inherit (lib) types; inherit (pkgs.lib) loadFile unlines unlinesAttrs unlinesValues unwords; inherit (config) networking; inherit (config.services) dovecot2 postfix openldap; when = x: y: if x == null then "" else y; extSep = postfix.recipientDelimiter; dirSep = extSep; # NOTE: nixpkgs' dovecot2.stateDir is currently not exported stateDir = "/var/lib/dovecot"; 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 # NOTE: sufficient for small systems and uses less resources. blocking = no # 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)) # TODO: userdb_quota_rule=*:storage= 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 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 ''; }; learn-spam = pkgs.writeShellScriptBin "learn-spam.sh" '' exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/learner.sock learn_spam ''; learn-ham = pkgs.writeShellScriptBin "learn-ham.sh" '' exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/learner.sock learn_ham ''; sieve_pipe_bin_dir = pkgs.buildEnv { name = "sieve_pipe_bin_dir"; pathsToLink = [ "/bin" ]; paths = [ learn-spam learn-ham ]; }; in { imports = [ dovecot/autoconfig.nix ]; #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" "openldap.service" "dovecot.${networking.domainBase}.key.pem-key.service" ]; restartTriggers = map (f: f.source) etc_dovecot; }; deployment.keys = { "dovecot.${networking.domainBase}.key.pem" = { text = pass "x509/${networking.domainBase}/key.pem"; user = dovecot2.user; group = "root"; destDir = "/run/keys/"; permissions = "0400"; # WARNING: not enforced when deployment.storeKeysOnMachine = true }; }; environment.etc = etc_dovecot; users.users."${dovecot2.mailUser}".isSystemUser = true; # Fix nixpkgs 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; } ''; spam-or-ham = '' require ["vnd.dovecot.pipe", "copy", "imapsieve", "variables", "imap4flags", "environment"]; if environment :is "imap.changedflags" "Junk" { if hasflag :is "Junk" { pipe :copy :try "learn-spam.sh"; } elsif not hasflag :is "Junk" { pipe :copy :try "learn-ham.sh"; } } ''; report-spam = '' require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"]; if environment :matches "imap.user" "*" { set "username" "''${1}"; } pipe :copy :try "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 :try "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 #} # Store METADATA information within user's HOME directory mail_attribute_dict = file:%h/dovecot-attributes # If needed, may be overrided by userdb_mail mail_home = ${mailDir}/%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 mail_location = maildir:${mailDir}/%d/%n/mail.d:LAYOUT=fs:INDEX=${stateDir}/index/%d/%n:INDEXPVT=${stateDir}/index/%d/%n:CONTROL=${stateDir}/control/%d/%n auth_mechanisms = plain login # NOTE: postfix does not supply a client cert. auth_ssl_require_client_cert = no #auth_ssl_username_from_cert = yes auth_verbose = yes # NOTE: lowercase the username, help with LDAP? auth_username_format = %Lu ${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 # NOTE: sync with LDAP's data. first_valid_uid = 1000 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 # NOTE: always listed in the LIST command. list = yes # 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. location = maildir:${mailDir}/%%d/%%n/mail.d:LAYOUT=fs:INDEX=${stateDir}/index/%%d/%%n/Shared:INDEXPVT=${stateDir}/index/%d/%n/Shared/%%n:CONTROL=${stateDir}/control/%d/%n/Shared 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 # NOTE: to let users LIST mailboxes shared by other users, # Dovecot needs a shared mailbox dictionary. # FIXME: %d not working with userdb ldap acl_shared_dict = file:${stateDir}/acl/%d/acl.db ## NOTE: pour offlineimap ##antispam_allow_append_to_spam = yes #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 = service_count = 1 # Number of processes to always keep waiting for more connections. process_min_avail = 0 # If you set service_count=0, you probably need to grow this. #vsz_limit = 64M } ssl = required ssl_dh = <${../../../sec/openssl/dh.pem} ssl_cipher_list = HIGH:!LOW:!SSLv2:!EXP:!aNULL ssl_cert = <${loadFile (../../../sec + "/openssl/${networking.domainBase}/cert.self-signed.pem")} ssl_key =