1 {pkgs, lib, config, system, ...}:
2 let inherit (builtins) toString toFile;
4 inherit (pkgs.lib) unlines unlinesAttrs unlinesValues unwords;
5 inherit (config) networking;
6 inherit (config.services) dovecot2 postfix x509 openldap;
7 when = x: y: if x == null then "" else y;
8 extSep = postfix.recipientDelimiter;
10 stateDir = "/var/lib/dovecot";
11 # NOTE: nixpkgs' dovecot2.stateDir is currently not exported
12 mailDir = "${stateDir}/mail";
13 sieveDir = "${stateDir}/sieve";
14 authDir = "${stateDir}/auth";
15 authUser = dovecot2.mailUser; # TODO: php_roundcube
16 authGroup = dovecot2.mailGroup; # TODO: php_roundcube
17 escapeGroup = lib.stringAsChars (c: if "a"<=c && c<="z"
21 domainGroup = escapeGroup "${networking.domainBase}";
23 { target = "dovecot/${networking.domain}/dovecot-ldap.conf";
24 source = pkgs.writeText "dovecot-ldap.conf" ''
25 ${lib.optionalString dovecot2.debug ''
31 base = ou=posix,${openldap.domainSuffix}
35 # NOTE: sufficient for small systems and uses less resources.
40 #dn = cn=admin,${openldap.domainSuffix}
41 #dnpass = useless with sasl_mech=EXTERNAL
43 #auth_bind_userdn = cn=%n,ou=accounts,ou=posix,dc=${openldap.domainSuffix}
45 # dovecot passdb query
46 # DOC: http://wiki2.dovecot.org/PasswordDatabase/ExtraFields
47 pass_filter = (&(objectClass=posixAccount)(uid=%n)(mailEnabled=TRUE))
48 pass_attrs = userPassword=password,\
49 uidNumber=userdb_uid,\
50 gidNumber=userdb_gid,\
51 mailGroupMember=userdb_mail_access_groups=${domainGroup},\
52 quotaBytes=userdb_quota_rule=*:bytes=%{ldap:quotaBytes},\
54 #homeDirectory=userdb_home
55 # TODO: userdb_quota_rule=*:storage=
56 default_pass_scheme = CRYPT
58 # dovecot userdb query
59 # DOC: http://wiki2.dovecot.org/UserDatabase/ExtraFields
60 user_filter = (&(objectClass=posixAccount)(uid=%n)(mailEnabled=TRUE))
61 #user_filter = (&(objectClass=inetOrgPerson)(uid=%n))
62 #user_attrs = homeDirectory=home,uidNumber=uid,gidNumber=gid
63 #user_attrs = mailHomeDirectory=home,\
64 # mailStorageDirectory=mail,\
67 # mailQuota=quota_rule=*:bytes=%$
70 iterate_attrs = =user=%{ldap:uid}@${networking.domain}
71 iterate_filter = (&(objectClass=posixAccount)(mailEnabled=TRUE))
75 dovecot-virtual = pkgs.writeTextFile {
76 name = "dovecot-virtual";
77 destination = "/pop3/INBOX/dovecot-virtual";
87 dovecot/autoconfig.nix
90 services.dovecot2.sieves = {
91 global = lib.mkOption {
92 description = "global scripts.";
93 type = types.attrsOf types.str;
96 before = lib.mkOption {
97 description = "before scripts.";
98 type = types.attrsOf types.str;
101 after = lib.mkOption {
102 description = "after scripts.";
103 type = types.attrsOf types.str;
109 environment.etc = etc_dovecot;
110 #services.postfix.mapFiles."transport-dovecot" =
111 # toFile "transport-dovecot"
113 # (lib.mapAttrsToList
114 # (dom: {...}: "${transportSubDomain}.${dom} lmtp:unix:private/dovecot-lmtp")
115 # dovecot2.domains));
116 systemd.services.dovecot2 = {
122 map (f: f.source) etc_dovecot;
124 #LD_LIBRARY_PATH = config.system.nssModules.path;
126 preStart = unlinesValues {
128 # SEE: http://wiki2.dovecot.org/SharedMailboxes/Permissions
129 install -D -d -m 0771 \
130 -o ${dovecot2.mailUser} \
131 -g ${dovecot2.mailGroup} \
137 '' + unlinesAttrs (dir: sieves: ''
138 install -D -d -m 0755 -o root -g root \
139 ${sieveDir} ${sieveDir}/${dir}.d
140 '' + unlinesAttrs (name: text: ''
141 src=${pkgs.writeText "${name}.sieve" text}
142 dst="${sieveDir}/${dir}.d/${name}.sieve"
143 ln -fns "$src" "$dst"
144 ${pkgs.dovecot_pigeonhole}/bin/sievec "$dst"
149 # NOTE: make sure nslcd cache is in sync with the LDAP data
150 systemctl restart nslcd
151 install -D -d -m 1770 \
152 -o ${dovecot2.mailUser} \
154 ${mailDir}/${networking.domain} \
155 ${stateDir}/control/${networking.domain} \
156 ${stateDir}/index/${networking.domain}
158 # NOTE: do not set the sticky bit (+t)
159 # on acl/<domain>/, to let dovecot
160 # rename acl.db.lock (own by new user)
161 # to acl.db (own by old user)
162 install -D -d -m 0770 \
163 -o ${dovecot2.mailUser} \
165 ${stateDir}/acl/${networking.domain}
167 # NOTE: domainAliases point to the very same mailboxes as domain's.
168 for domainAlias in ${unwords networking.domainAliases}
170 ln -fns ${networking.domain} ${mailDir}/$domainAlias
171 ln -fns ${networking.domain} ${stateDir}/control/$domainAlias
172 ln -fns ${networking.domain} ${stateDir}/index/$domainAlias
173 ln -fns ${networking.domain} ${stateDir}/acl/$domainAlias
178 services.dovecot2 = {
181 mailUser = "dovemail";
182 mailGroup = "dovemail";
184 #pkgs.dovecot_antispam
185 pkgs.dovecot_pigeonhole
197 if currentdate :matches "year" "*" { set "year" "''${1}"; }
198 if currentdate :matches "month" "*" { set "month" "''${1}"; }
200 if exists "List-ID" {
201 if header :matches "List-ID" "*<*.*.*.*>*" {
203 set "domain" "''${4}";
205 elsif header :matches "List-ID" "*<*.*.*>*" {
207 set "domain" "''${3}";
209 fileinto :create "Listes+''${domain}+''${list}+''${year}+''${month}";
218 if header :contains "X-Spam-Level" "***" {
230 if envelope :matches :detail "TO" "*" {
231 set "extension" "''${1}";
233 if not string :is "''${extension}" "" {
234 fileinto :create "Plus+''${extension}";
240 configFile = toString (pkgs.writeText "dovecot.conf" ''
243 args = /etc/dovecot/${networking.domain}/dovecot-ldap.conf
244 default_fields = userdb_mail_access_groups=${domainGroup}
251 # NOTE: this userdb is only used by lda.
253 args = /etc/dovecot/${networking.domain}/dovecot-ldap.conf
254 default_fields = mail_access_groups=${domainGroup}
259 # driver = passwd-file
260 # args = scheme=crypt username_format=%n ${authDir}/%d/passwd
263 # # NOTE: this userdb is only used by lda.
264 # driver = passwd-file
265 # args = username_format=%n ${authDir}/%d/passwd
266 # #default_fields = home=${mailDir}/%d/%n
269 mail_home = ${mailDir}/%d/%n
270 # NOTE: if needed, may be overrided by userdb_mail
271 mail_location = maildir:${mailDir}/%d/%n/Maildir:LAYOUT=fs:INDEX=${stateDir}/index/%d/%n:INDEXPVT=${stateDir}/index/%d/%n:CONTROL=${stateDir}/control/%d/%n
272 # NOTE: if needed, may be overrided by userdb_mail
273 # NOTE: INDEX and CONTROL are on a partition without quota, as explain in the doc.
274 # SEE: http://wiki2.dovecot.org/Quota/FS
275 auth_mechanisms = plain login
276 auth_ssl_require_client_cert = no
277 # NOTE: postfix does not supply a client cert.
278 auth_ssl_username_from_cert = yes
280 auth_username_format = %Lu
281 # NOTE: lowercase the username, help with LDAP?
282 ${lib.optionalString dovecot2.debug ''
287 default_internal_user = ${dovecot2.user}
288 default_internal_group = ${dovecot2.group}
289 disable_plaintext_auth = yes
290 first_valid_uid = 10000
291 # NOTE: sync with LDAP's data.
292 lda_mailbox_autocreate = yes
293 lda_mailbox_autosubscribe = yes
295 log_timestamp = "%Y-%m-%d %H:%M:%S "
296 #maildir_copy_with_hardlinks = yes
298 # NOTE: here because protocol sieve {namespace inbox{}} does not seem to work.
304 separator = ${dirSep}
310 # NOTE: always listed in the LIST command.
311 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
312 # NOTE: how to access the other users' mailboxes.
313 # NOTE: %var expands to the logged in user's variable, while
314 # %%var expands to the other users' variables.
315 # NOTE: INDEX and CONTROL are shared, INDEXPVT is not.
316 prefix = Partages+%%n+
317 separator = ${dirSep}
320 mail_plugins = $mail_plugins acl quota virtual
321 #mail_uid = ${dovecot2.mailUser}
322 #mail_gid = ${dovecot2.mailGroup}
323 # NOTE: each user has a dedicated (uid,gid) pair
324 #mail_privileged_group = mail
325 #mail_access_groups =
327 acl = vfile:/etc/dovecot/acl/global.d
329 acl_shared_dict = file:${stateDir}/acl/%d/acl.db
330 # NOTE: to let users LIST mailboxes shared by other users,
331 # Dovecot needs a shared mailbox dictionary.
332 # FIXME: %d not working with userdb ldap
333 ##antispam_allow_append_to_spam = yes
334 # # NOTE: pour offlineimap
335 #antispam_backend = pipe
336 ##antispam_crm_args = -u;${mailDir}/%d/.crm114;/usr/share/crm114/mailfilter.crm
337 #antispam_crm_args = -u;${mailDir}/crm114;/usr/share/crm114/mailfilter.crm
338 #antispam_crm_binary = /usr/bin/crm
339 #antispam_debug_target = syslog
340 ##antispam_crm_env = HOME=%h;USER=%u
341 #antispam_ham_keywords = NonJunk
342 #antispam_pipe_program = /usr/bin/crm
343 #antispam_pipe_program_args = -u;${mailDir}/crm114;/usr/share/crm114/mailfilter.crm;--stats_only;--force
344 #antispam_pipe_program_notspam_arg = --learnnonspam
345 #antispam_pipe_program_spam_arg = --learnspam
346 #antispam_pipe_program_unlearn_spam_args = --unlearn;--learnspam
347 #antispam_pipe_program_unlearn_notspam_args = --unlearn;--learnnonspam
348 #antispam_pipe_tmpdir = ${mailDir}/crm114/tmp
349 #antispam_signature = X-CRM114-CacheID
350 #antispam_signature_missing = move
351 #antispam_spam = Junk
352 #antispam_spam_keywords = Junk
353 #antispam_trash = Trash
354 #antispam_unsure = Unsure
355 #antispam_verbose_debug = 0
356 quota = maildir:User quota
357 quota_rule = *:storage=256M
358 quota_rule2 = Trash:storage=+64M
359 quota_max_mail_size = 20M
360 #quota_exceeded_message = </path/to/quota_exceeded_message.txt
361 quota_warning = storage=95%% quota-warning 95 %u
362 quota_warning2 = storage=80%% quota-warning 80 %u
363 quota_warning3 = -storage=100%% quota-warning below %u
364 # NOTE: user is no longer over quota
365 recipient_delimiter = ${extSep}
366 sieve = file:${mailDir}/%d/%n/sieve;active=${mailDir}/%d/%n/sieve/main.sieve
367 #sieve_default = file:${mailDir}/%u/default.sieve
368 #sieve_default_name = default
369 sieve_after = ${sieveDir}/after.d/
370 sieve_before = ${sieveDir}/before.d/
371 sieve_dir = ${mailDir}/%d/%n/sieve/
372 #sieve_extensions = +spamtest +spamtestplus
373 sieve_global_dir = ${sieveDir}/global.d/
374 sieve_max_script_size = 1M
375 sieve_quota_max_scripts = 0
376 sieve_quota_max_storage = 10M
377 sieve_spamtest_max_value = 10
378 sieve_spamtest_status_header = X-Spam-Score
379 sieve_spamtest_status_type = strlen
380 sieve_user_log = /var/log/dovecot/%d/sieve.%n.log
382 service quota-warning {
383 executable = script ${
384 pkgs.writeScript "quota-warning" ''
388 cat << EOF | ${pkgs.dovecot}/libexec/dovecot/dovecot-lda -d $USER -o
389 "plugin/quota=maildir:User quota:noenforcing"
390 From: postmaster@${networking.domain}
391 Subject: [WARNING] your mailbox is now $PERCENT% full.
393 Please remove some mails to make room for new ones.
397 # use some unprivileged user for executing the quota warnings
398 user = ${dovecot2.user}
399 unix_listener quota-warning {
403 #mail_max_userip_connections = 10
404 mail_plugins = $mail_plugins imap_acl imap_quota # antispam
410 special_use = \Drafts
418 mailbox "Sent Messages" {
425 separator = ${dirSep}
429 auth_socket_path = /var/run/dovecot/auth-userdb
430 hostname = ${networking.domain}
433 mail_plugins = $mail_plugins sieve
439 separator = ${dirSep}
441 postmaster_address = postmaster${extSep}dovecot${extSep}lda@${networking.domain}
442 syslog_facility = mail
445 #info_log_path = /tmp/dovecot-lmtp.log
446 mail_plugins = $mail_plugins sieve
452 separator = ${dirSep}
454 postmaster_address = postmaster${extSep}dovecot${extSep}lmtp@${networking.domain}
457 #mail_max_userip_connections = 10
459 # NOTE: used by ${dovecot-virtual}/pop3/INBOX/dovecot-virtual
464 separator = ${dirSep}
466 # Virtual namespace for the virtual INBOX.
467 # Use a global directory for dovecot-virtual files.
472 location = virtual:${dovecot-virtual}/pop3:INDEX=${stateDir}/index/%d/%n/POP3:LAYOUT=fs
474 separator = ${dirSep}
476 pop3_client_workarounds =
477 pop3_fast_size_lookups = yes
478 pop3_lock_session = yes
479 pop3_no_flag_updates = yes
480 # Use GUIDs to avoid accidental POP3 UIDL changes instead of IMAP UIDs.
481 pop3_uidl_format = %g
484 #mail_max_userip_connections = 10
485 #managesieve_implementation_string = Dovecot Pigeonhole
486 managesieve_max_compile_errors = 5
487 #managesieve_max_line_length = 65536
488 #managesieve_notify_capability = mailto
489 #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
491 protocols = imap lmtp pop3 sieve
493 #executable = lmtp -L
494 process_min_avail = 2
495 unix_listener /var/lib/postfix/queue/private/dovecot-lmtp {
496 user = ${postfix.user}
497 group = ${postfix.group}
504 # FIXME: may be user=dovecot-auth with LDAP?
505 unix_listener auth-userdb {
506 user = ${dovecot2.user}
507 group = ${dovecot2.group}
510 unix_listener /var/lib/postfix/queue/private/auth {
511 user = ${postfix.user}
512 group = ${postfix.group}
517 # Most of the memory goes to mmap()ing files.
518 # You may need to increase this limit if you have huge mailboxes.
523 #inet_listener imap {
524 # address = 127.0.0.1
528 inet_listener imaps {
537 inet_listener pop3s {
543 ssl_dh = <${x509.dir}/dh.pem
544 ssl_cipher_list = HIGH:!LOW:!SSLv2:!EXP:!aNULL
545 ssl_cert = <${x509.cert}
546 ssl_key = <${x509.key}
547 #ssl_ca = <''${caPath}
548 #ssl_verify_client_cert = yes
550 #${lib.concatMapStringsSep "\n"
552 # local_name mail.${dom} {
553 # #ssl_ca = <''${caPath}
554 # ssl_cert = <${x509.cert dom}
555 # ssl_key = <${x509.key dom}