1 {pkgs, lib, config, system, ...}:
2 let inherit (builtins) toString toFile attrNames;
4 inherit (config.services) dovecot2 postfix x509;
5 unlines = lib.concatStringsSep "\n";
6 when = x: y: if x == null then "" else y;
7 extSep = postfix.recipientDelimiter;
9 libDir = "/var/lib/dovecot";
10 mailDir = "${libDir}/mail";
11 sieveDir = "${libDir}/sieve";
12 authDir = "${libDir}/auth";
13 authUser = dovecot2.mailUser; # TODO: php_roundcube
14 authGroup = dovecot2.mailGroup; # TODO: php_roundcube
15 escapeGroup = lib.stringAsChars (c: if "a"<=c && c<="z"
22 #environment.etc."nginx/site.d/autoconfig.conf".source =
23 # let servers = lib.concatMapStringsSep " "
24 # (dom: "autoconfig.${dom}")
25 # (attrNames dovecot2.domains);
26 # autoconfigSite = pkgs.writeTextFile {
27 # name = "autoconfig";
28 # destination = "/mail/config-v1.1.xml";
30 # <?xml version="1.0"?>
31 # <clientConfig version="1.1">
32 # <emailProvider id="%EMAILDOMAIN%">
33 # <!-- <displayName></displayName> -->
34 # <!-- <displayShortName></displayShortName> -->
35 # <domain>%EMAILDOMAIN%</domain>
36 # <incomingServer type="imap">
37 # <hostname>imap.%EMAILDOMAIN%</hostname>
39 # <socketType>SSL</socketType>
40 # <username>%EMAILADDRESS%</username>
41 # <authentication>password-cleartext</authentication>
43 # <incomingServer type="pop3">
44 # <hostname>pop.%EMAILDOMAIN%</hostname>
46 # <socketType>SSL</socketType>
47 # <username>%EMAILADDRESS%</username>
48 # <authentication>password-cleartext</authentication>
50 # <leaveMessagesOnServer>false</leaveMessagesOnServer>
51 # <downloadOnBiff>true</downloadOnBiff>
54 # <outgoingServer type="smtp">
55 # <hostname>smtp.%EMAILDOMAIN%</hostname>
57 # <socketType>SSL</socketType> <!-- see above -->
58 # <username>%EMAILADDRESS%</username> <!-- if smtp-auth -->
59 # <authentication>password-cleartext</authentication>
60 # <!-- <restriction>client-IP-address</restriction> -->
61 # <addThisServer>true</addThisServer>
62 # <useGlobalPreferredServer>false</useGlobalPreferredServer>
65 # <!-- <clientConfigUpdate url="https://www.example.com/config/mozilla.xml" /> -->
70 # pkgs.writeText "autoconfig.conf" ''
73 # server_name ${servers};
74 # root ${autoconfigSite};
79 # listen 443 ssl http2;
81 # server_name ${servers};
82 # root ${autoconfigSite};
87 #services.postfix.mapFiles."transport-dovecot" =
88 # toFile "transport-dovecot"
91 # (dom: {...}: "${transportSubDomain}.${dom} lmtp:unix:private/dovecot-lmtp")
93 systemd.services.dovecot2.after = [ "postfix.service" ];
96 # uid = config.ids.uids.dovecot2;
97 # description = "Dovecot user";
98 # group = dovecot2.group;
101 users.extraGroups = lib.mapAttrs
103 { name = escapeGroup "${dovecot2.mailGroup}-${domain}";
106 systemd.services.dovecot2.preStart =
108 pkgs.writeText "list.sieve" ''
116 if currentdate :matches "year" "*" { set "year" "''${1}"; }
117 if currentdate :matches "month" "*" { set "month" "''${1}"; }
119 if exists "List-ID" {
120 if header :matches "List-ID" "*<*.*.*.*>*" {
122 set "domain" "''${4}";
124 elsif header :matches "List-ID" "*<*.*.*>*" {
126 set "domain" "''${3}";
128 fileinto :create "Listes+''${domain}+''${list}+''${year}+''${month}";
133 pkgs.writeText "spam.sieve" ''
138 if header :contains "X-Spam-Level" "***" {
143 pkgs.writeText "extension.sieve" ''
151 if envelope :matches :detail "TO" "*" {
152 set "extension" "''${1}";
154 if not string :is "''${extension}" "" {
155 fileinto :create "Plus+''${extension}";
160 pkgs.writeText "dovecot-virtual" ''
166 # SEE: http://wiki2.dovecot.org/SharedMailboxes/Permissions
167 # The sticky bit is to allow the acl.db{.lock,} done by dovecot
168 install -D -d -m 2771 \
169 -o ${dovecot2.mailUser} \
170 -g ${dovecot2.mailGroup} \
173 # Install global sieves
174 install -D -d -m 0755 \
178 ${sieveDir}/after.d \
179 ${sieveDir}/before.d \
181 ln -fns ${sieveList} ${sieveDir}/global.d/list.sieve
182 ln -fns ${sieveExtension} ${sieveDir}/global.d/extension.sieve
183 ln -fns ${sieveSpam} ${sieveDir}/global.d/spam.sieve
184 for f in ${sieveDir}/*/*.sieve
185 do ${pkgs.dovecot_pigeonhole}/bin/sievec $f
193 ${libDir}/pop3/INBOX/dovecot-virtual
199 + unlines (lib.mapAttrsToList (domain: {accounts, ...}:
200 let domainGroup = escapeGroup "${dovecot2.mailGroup}-${domain}"; in
202 install -D -d -m 1770 \
203 -o ${dovecot2.mailUser} \
205 ${mailDir}/${domain} \
206 ${libDir}/control/${domain} \
207 ${libDir}/index/${domain}
208 install -D -d -m 1770 \
209 -o ${dovecot2.mailUser} \
212 ${libDir}/auth/${domain}
213 dir_passwd=${libDir}/auth/${domain}
214 old_passwd=$dir_passwd/passwd
215 new_passwd=$(TMPDIR= mktemp --tmpdir=$dir_passwd -t passwd.XXXXXXXX.tmp)
219 + unlines (lib.mapAttrsToList (user: acct: ''
221 home=${mailDir}/${domain}/${user}
223 shell=/run/current-system/sw/bin/nologin
226 uid=$(stat -c %u $home)
227 gid=$(stat -c %g $home)
229 [ "''${uid:+set}" ] || {
230 while test exists = "$(find $(dirname $home) -mindepth 1 -maxdepth 1 -uid $new_uid -printf exists -quit)"
231 do new_uid=$((new_uid + 1))
236 install -D -d -o $uid -g $gid -m 2770 $home $home/Maildir
237 install -d -o $uid -g $gid -m 0700 $home/sieve
239 + unlines (lib.mapAttrsToList
241 install -D -m 640 -o $uid -g $gid \
242 ${pkgs.writeText "${n}.sieve" v} \
243 $home/sieve/${n}.sieve
244 ${pkgs.dovecot_pigeonhole}/bin/sievec \
245 $home/sieve/${n}.sieve
249 mail_access_groups=${lib.concatStringsSep "," ([domainGroup] ++ acct.groups)}
250 quota=${if lib.isString acct.quota
251 then ''"userdb_quota_rule=*:storage=${acct.quota}"''
253 extra_fields="userdb_uid=$uid userdb_gid=$gid userdb_mail_access_groups=$mail_access_groups $quota"
254 #test ! -e $old_passwd || {
255 # # Preserve password changed by another mechanism, eg. roundcube.
256 # # But this also does not overwrite any old password set by this config.
257 # pass="$(sed -ne "s/^${user}:\([^:]*\):.*/\1/p" $old_passwd)"
259 [ "''${pass:+set}" ] || {
260 pass=${lib.escapeShellArg acct.password}
262 printf '%s\n' >>$new_passwd \
263 "${user}:$pass:$uid:$gid:$gecos:$home:$shell:$extra_fields"
267 install -o ${authUser} -g ${authGroup} -m 0640 $new_passwd $old_passwd
271 services.dovecot2 = {
273 mailUser = "dovemail";
274 mailGroup = "dovemail";
276 #pkgs.dovecot_antispam
277 pkgs.dovecot_pigeonhole
279 # ${lib.concatMapStringsSep "\n"
281 # local_name imap.${dom} {
282 # #ssl_ca = <''${caPath}
283 # ssl_cert = <${x509.cert dom}
284 # ssl_key = <${x509.key dom}
286 # local_name pop.${dom} {
287 # #ssl_ca = <''${caPath}
288 # ssl_cert = <${x509.cert dom}
289 # ssl_key = <${x509.key dom}
295 configFile = toString (pkgs.writeText "dovecot.conf" ''
298 args = scheme=crypt username_format=%n ${authDir}/%d/passwd
304 # NOTE: this userdb is only used by lda.
306 args = username_format=%n ${authDir}/%d/passwd
307 #default_fields = home=${mailDir}/%d/%n
309 mail_home = ${mailDir}/%d/%n
310 auth_mechanisms = plain login
311 # postfix does not supply a client cert.
312 auth_ssl_require_client_cert = no
313 auth_ssl_username_from_cert = yes
315 ${lib.optionalString dovecot2.debug ''
320 default_internal_user = ${dovecot2.user}
321 default_internal_group = ${dovecot2.group}
322 disable_plaintext_auth = yes
323 first_valid_uid = 1000
324 lda_mailbox_autocreate = yes
325 lda_mailbox_autosubscribe = yes
327 log_timestamp = "%Y-%m-%d %H:%M:%S "
328 # NOTE: INDEX and CONTROL are on a partition without quota, as explain in the doc.
329 # SEE: http://wiki2.dovecot.org/Quota/FS
330 mail_location = maildir:${mailDir}/%d/%n/Maildir:LAYOUT=fs:INDEX=${libDir}/index/%d/%n:CONTROL=${libDir}/control/%d/%n
332 # NOTE: here because protocol sieve {namespace inbox{}} does not seem to work.
337 separator = ${dirSep}
342 location = maildir:${mailDir}/%%d/%%n/Maildir:LAYOUT=fs:INDEX=${libDir}/index/%d/%n/Shared/%%n:CONTROL=${libDir}/control/%d/%n/Shared/%%n
343 prefix = Partages+%%n+
344 separator = ${dirSep}
348 mail_plugins = $mail_plugins acl quota virtual
349 #mail_uid = ${dovecot2.mailUser}
350 #mail_gid = ${dovecot2.mailGroup}
351 #mail_privileged_group = mail
352 #mail_access_groups =
354 acl = vfile:/etc/dovecot/acl/global.d
356 acl_shared_dict = file:${mailDir}/%d/acl.db
357 ##antispam_allow_append_to_spam = yes
358 # # NOTE: pour offlineimap
359 #antispam_backend = pipe
360 ##antispam_crm_args = -u;${mailDir}/%d/.crm114;/usr/share/crm114/mailfilter.crm
361 #antispam_crm_args = -u;${mailDir}/crm114;/usr/share/crm114/mailfilter.crm
362 #antispam_crm_binary = /usr/bin/crm
363 #antispam_debug_target = syslog
364 ##antispam_crm_env = HOME=%h;USER=%u
365 #antispam_ham_keywords = NonJunk
366 #antispam_pipe_program = /usr/bin/crm
367 #antispam_pipe_program_args = -u;${mailDir}/crm114;/usr/share/crm114/mailfilter.crm;--stats_only;--force
368 #antispam_pipe_program_notspam_arg = --learnnonspam
369 #antispam_pipe_program_spam_arg = --learnspam
370 #antispam_pipe_program_unlearn_spam_args = --unlearn;--learnspam
371 #antispam_pipe_program_unlearn_notspam_args = --unlearn;--learnnonspam
372 #antispam_pipe_tmpdir = ${mailDir}/crm114/tmp
373 #antispam_signature = X-CRM114-CacheID
374 #antispam_signature_missing = move
375 #antispam_spam = Junk
376 #antispam_spam_keywords = Junk
377 #antispam_trash = Trash
378 #antispam_unsure = Unsure
379 #antispam_verbose_debug = 0
380 quota = maildir:User quota
381 quota_rule = *:storage=256M
382 quota_rule2 = Trash:storage=+64M
383 recipient_delimiter = ${extSep}
384 sieve = file:${mailDir}/%d/%n/sieve;active=${mailDir}/%d/%n/sieve/main.sieve
385 #sieve_default = file:${mailDir}/%u/default.sieve
386 #sieve_default_name = default
387 sieve_after = ${sieveDir}/after.d/
388 sieve_before = ${sieveDir}/before.d/
389 sieve_dir = ${mailDir}/%d/%n/sieve/
390 #sieve_extensions = +spamtest +spamtestplus
391 sieve_global_dir = ${sieveDir}/global.d/
392 sieve_max_script_size = 1M
393 sieve_quota_max_scripts = 0
394 sieve_quota_max_storage = 10M
395 sieve_spamtest_max_value = 10
396 sieve_spamtest_status_header = X-Spam-Score
397 sieve_spamtest_status_type = strlen
398 sieve_user_log = /var/log/dovecot/%d/sieve.%n.log
401 #mail_max_userip_connections = 10
402 mail_plugins = $mail_plugins imap_acl imap_quota # antispam
408 special_use = \Drafts
416 mailbox "Sent Messages" {
423 separator = ${dirSep}
427 auth_socket_path = /var/run/dovecot/auth-userdb
428 hostname = ${config.networking.domain}
431 mail_plugins = $mail_plugins sieve
437 separator = ${dirSep}
439 postmaster_address = postmaster${extSep}dovecot${extSep}lda@${config.networking.domain}
440 syslog_facility = mail
443 #info_log_path = /tmp/dovecot-lmtp.log
444 mail_plugins = $mail_plugins sieve
450 separator = ${dirSep}
452 postmaster_address = postmaster${extSep}dovecot${extSep}lmtp@${config.networking.domain}
455 #mail_max_userip_connections = 10
456 # Used by ${libDir}/pop3/INBOX/dovecot-virtual
462 separator = ${dirSep}
464 # Virtual namespace for the virtual INBOX.
465 # Use a global directory for dovecot-virtual files.
470 location = virtual:${libDir}/pop3:INDEX=${libDir}/index/%d/%n/POP3:LAYOUT=fs
472 separator = ${dirSep}
474 pop3_client_workarounds =
475 pop3_fast_size_lookups = yes
476 pop3_lock_session = yes
477 pop3_no_flag_updates = yes
478 # Use GUIDs to avoid accidental POP3 UIDL changes instead of IMAP UIDs.
479 pop3_uidl_format = %g
482 #mail_max_userip_connections = 10
483 #managesieve_implementation_string = Dovecot Pigeonhole
484 managesieve_max_compile_errors = 5
485 #managesieve_max_line_length = 65536
486 #managesieve_notify_capability = mailto
487 #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
489 protocols = imap lmtp pop3 sieve
491 #executable = lmtp -L
492 process_min_avail = 2
493 unix_listener /var/lib/postfix/queue/private/dovecot-lmtp {
494 user = ${postfix.user}
495 group = ${postfix.group}
502 unix_listener auth-userdb {
503 user = ${dovecot2.user}
504 group = ${dovecot2.group}
507 unix_listener /var/lib/postfix/queue/private/auth {
508 user = ${postfix.user}
509 group = ${postfix.group}
514 # Most of the memory goes to mmap()ing files.
515 # You may need to increase this limit if you have huge mailboxes.
520 #inet_listener imap {
521 # address = 127.0.0.1
525 inet_listener imaps {
534 inet_listener pop3s {
540 #ssl_ca = <''${caPath}
541 ssl_cert = <${x509.cert}
542 ssl_dh = <${x509.dir}/dh.pem
543 # gOTE: only with dovecot >= 2.3
544 ssl_cipher_list = ALL:!LOW:!SSLv2:!EXP:!aNULL
545 ssl_key = <${x509.key}
546 #ssl_verify_client_cert = yes