]> Git — Sourcephile - sourcephile-nix.git/blob - install/logical/friot/dovecot.nix
nsf: try to limit hardcoding.
[sourcephile-nix.git] / install / logical / friot / dovecot.nix
1 {pkgs, lib, config, system, ...}:
2 let inherit (builtins) toString toFile;
3 inherit (lib) types;
4 inherit (pkgs.lib) unlines;
5 inherit (config.services) dovecot2 postfix x509 openldap;
6 when = x: y: if x == null then "" else y;
7 extSep = postfix.recipientDelimiter;
8 dirSep = extSep;
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"
16 || "0"<=c && c<="9"
17 || c=="-"
18 then c else "_");
19 etc_dovecot = [
20 { target = "dovecot/${config.networking.domain}/dovecot-ldap.conf";
21 source = pkgs.writeText "dovecot-ldap.conf" ''
22 ${lib.optionalString dovecot2.debug ''
23 debug_level = 1
24 ''}
25
26 # LDAP database
27 uris = ldapi://
28 base = ou=posix,${openldap.domainSuffix}
29 scope = subtree
30 deref = never
31 blocking = no
32 # NOTE: sufficient for small systems and uses less resources.
33
34 # LDAP auth
35 sasl_bind = yes
36 sasl_mech = EXTERNAL
37 #dn = cn=admin,${openldap.domainSuffix}
38 #dnpass = useless with sasl_mech=EXTERNAL
39 auth_bind = no
40 #auth_bind_userdn = cn=%n,ou=accounts,ou=posix,dc=${openldap.domainSuffix}
41
42 # dovecot passdb query
43 pass_filter = (&(objectClass=posixAccount)(uid=%n))
44 pass_attrs = userPassword=password,\
45 uidNumber=userdb_uid,\
46 gidNumber=userdb_gid,\
47 mailGroupMember=mail_access_groups,\
48 =user=%n@%d
49 #homeDirectory=userdb_home
50 #quotaBytes=userdb_quota_rule=*:bytes=%{ldap:quotaBytes}
51 # TODO: userdb_quota_rule=*:storage=
52 # TODO: userdb_mail_access_groups
53 # DOC: http://wiki2.dovecot.org/PasswordDatabase/ExtraFields
54 default_pass_scheme = CRYPT
55
56 # dovecot userdb query
57 user_filter = (&(objectClass=posixAccount)(uid=%n))
58 #user_filter = (&(objectClass=inetOrgPerson)(uid=%n)(mailEnabled=TRUE))
59 # # DOC: http://wiki2.dovecot.org/Variables
60 #user_attrs = homeDirectory=home,uidNumber=uid,gidNumber=gid
61 #user_attrs = mailHomeDirectory=home,\
62 # mailStorageDirectory=mail,\
63 # mailUidNumber=uid,\
64 # mailGidNumber=gid,\
65 # mailQuota=quota_rule=*:bytes=%$
66 # # DOC: http://wiki2.dovecot.org/UserDatabase/ExtraFields
67
68 # doveadm user query
69 iterate_attrs = =user=%{ldap:uid}@${config.networking.domain}
70 iterate_filter = (objectClass=posixAccount)
71 '';
72 }
73 ];
74 in
75 {
76 imports = [
77 dovecot/autoconfig.nix
78 ];
79 config = {
80 systemd.services.dovecot2.after = [ "postfix.service" ];
81 systemd.services.dovecot2.restartTriggers =
82 map (f: f.source) etc_dovecot;
83 environment.etc = etc_dovecot;
84 #services.postfix.mapFiles."transport-dovecot" =
85 # toFile "transport-dovecot"
86 # (unlines
87 # (lib.mapAttrsToList
88 # (dom: {...}: "${transportSubDomain}.${dom} lmtp:unix:private/dovecot-lmtp")
89 # dovecot2.domains));
90 #users.extraUsers = [
91 # { name = "dovecot";
92 # uid = config.ids.uids.dovecot2;
93 # description = "Dovecot user";
94 # group = dovecot2.group;
95 # }
96 #];
97 users.extraGroups =
98 lib.mapAttrs
99 (domain: {...}:
100 { name = escapeGroup "${dovecot2.mailGroup}-${domain}";
101 })
102 dovecot2.domains;
103 systemd.services.dovecot2.preStart =
104 let sieveList =
105 pkgs.writeText "list.sieve" ''
106 require
107 [ "date"
108 , "fileinto"
109 , "mailbox"
110 , "variables"
111 ];
112
113 if currentdate :matches "year" "*" { set "year" "''${1}"; }
114 if currentdate :matches "month" "*" { set "month" "''${1}"; }
115
116 if exists "List-ID" {
117 if header :matches "List-ID" "*<*.*.*.*>*" {
118 set "list" "''${2}";
119 set "domain" "''${4}";
120 }
121 elsif header :matches "List-ID" "*<*.*.*>*" {
122 set "list" "''${2}";
123 set "domain" "''${3}";
124 }
125 fileinto :create "Listes+''${domain}+''${list}+''${year}+''${month}";
126 stop;
127 }
128 '';
129 sieveSpam =
130 pkgs.writeText "spam.sieve" ''
131 require
132 [ "imap4flags"
133 ];
134
135 if header :contains "X-Spam-Level" "***" {
136 addflag "Junk";
137 }
138 '';
139 sieveExtension =
140 pkgs.writeText "extension.sieve" ''
141 require
142 [ "envelope"
143 , "fileinto"
144 , "mailbox"
145 , "subaddress"
146 , "variables"
147 ];
148 if envelope :matches :detail "TO" "*" {
149 set "extension" "''${1}";
150 }
151 if not string :is "''${extension}" "" {
152 fileinto :create "Plus+''${extension}";
153 stop;
154 }
155 '';
156 dovecot-virtual =
157 pkgs.writeText "dovecot-virtual" ''
158 all
159 all+*
160 all
161 '';
162 installSieve = ''
163 # SEE: http://wiki2.dovecot.org/SharedMailboxes/Permissions
164 # NOTE: the sticky bit is to allow the acl.db{.lock,} done by dovecot
165 install -D -d -m 2771 \
166 -o ${dovecot2.mailUser} \
167 -g ${dovecot2.mailGroup} \
168 ${mailDir}
169
170 # Install global sieves
171 install -D -d -m 0755 \
172 -o root \
173 -g root \
174 ${sieveDir} \
175 ${sieveDir}/after.d \
176 ${sieveDir}/before.d \
177 ${sieveDir}/global.d
178 ln -fns ${sieveList} ${sieveDir}/global.d/list.sieve
179 ln -fns ${sieveExtension} ${sieveDir}/global.d/extension.sieve
180 ln -fns ${sieveSpam} ${sieveDir}/global.d/spam.sieve
181 for f in ${sieveDir}/*/*.sieve
182 do ${pkgs.dovecot_pigeonhole}/bin/sievec $f
183 done
184 '';
185 installPOP3 = ''
186 # Install pop3 Inbox
187 install -D -m 0644 \
188 -o root \
189 -g root \
190 ${dovecot-virtual} \
191 ${libDir}/pop3/INBOX/dovecot-virtual
192 '';
193 installDomains = ''
194 # Install domains
195 new_uid=5000
196 ''
197 + unlines (lib.mapAttrsToList (domain: {accounts, ...}:
198 let domainGroup = escapeGroup "${dovecot2.mailGroup}-${domain}"; in
199 ''
200 install -D -d -m 1770 \
201 -o ${dovecot2.mailUser} \
202 -g ${domainGroup} \
203 ${mailDir}/${domain} \
204 ${libDir}/control/${domain} \
205 ${libDir}/index/${domain}
206 install -D -d -m 1770 \
207 -o ${dovecot2.mailUser} \
208 -g ${authGroup} \
209 ${libDir}/auth \
210 ${libDir}/auth/${domain}
211 dir_passwd=${authDir}/${domain}
212 old_passwd=$dir_passwd/passwd
213 new_passwd=$(TMPDIR= mktemp --tmpdir=$dir_passwd -t passwd.XXXXXXXX.tmp)
214
215 # Install users
216 ''
217 + unlines (lib.mapAttrsToList (user: acct: ''
218 (
219 home=${mailDir}/${domain}/${user}
220 gecos=
221 shell=/run/current-system/sw/bin/nologin
222 if test -e $home
223 then
224 uid=$(stat -c %u $home)
225 gid=$(stat -c %g $home)
226 fi
227 [ "''${uid:+set}" ] || {
228 while test exists = "$(find $(dirname $home) -mindepth 1 -maxdepth 1 -uid $new_uid -printf exists -quit)"
229 do new_uid=$((new_uid + 1))
230 done
231 uid=$new_uid
232 gid=$new_uid
233 }
234 install -D -d -o $uid -g $gid -m 2770 $home $home/Maildir
235 install -d -o $uid -g $gid -m 0700 $home/sieve
236 ''
237 + unlines (lib.mapAttrsToList
238 (n: v: ''
239 install -D -m 640 -o $uid -g $gid \
240 ${pkgs.writeText "${n}.sieve" v} \
241 $home/sieve/${n}.sieve
242 ${pkgs.dovecot_pigeonhole}/bin/sievec \
243 $home/sieve/${n}.sieve
244 '')
245 acct.sieves)
246 + ''
247 mail_access_groups=${lib.concatStringsSep "," ([domainGroup] ++ acct.groups)}
248 quota=${if lib.isString acct.quota
249 then ''"userdb_quota_rule=*:storage=${acct.quota}"''
250 else ""}
251 extra_fields="userdb_uid=$uid userdb_gid=$gid userdb_mail_access_groups=$mail_access_groups $quota"
252 #test ! -e $old_passwd || {
253 # # Preserve password changed by another mechanism, eg. roundcube.
254 # # But this also does not overwrite any old password set by this config.
255 # pass="$(sed -ne "s/^${user}:\([^:]*\):.*/\1/p" $old_passwd)"
256 #}
257 [ "''${pass:+set}" ] || {
258 pass=${lib.escapeShellArg acct.password}
259 }
260 printf '%s\n' >>$new_passwd \
261 "${user}:$pass:$uid:$gid:$gecos:$home:$shell:$extra_fields"
262 )
263 '') accounts)
264 + ''
265 install -o ${authUser} -g ${authGroup} -m 0640 $new_passwd $old_passwd
266 rm $new_passwd
267 ''
268 ) dovecot2.domains);
269 in
270 unlines [
271 installSieve
272 installPOP3
273 installDomains
274 ];
275 services.dovecot2 = {
276 enable = true;
277 debug = true;
278 mailUser = "dovemail";
279 mailGroup = "dovemail";
280 modules = [
281 #pkgs.dovecot_antispam
282 pkgs.dovecot_pigeonhole
283 ];
284 configFile = toString (pkgs.writeText "dovecot.conf" ''
285 passdb {
286 driver = ldap
287 args = /etc/dovecot/${config.networking.domain}/dovecot-ldap.conf
288 default_fields =
289 override_fields =
290 }
291 userdb {
292 driver = prefetch
293 }
294 userdb {
295 # NOTE: this userdb is only used by lda.
296 driver = ldap
297 args = /etc/dovecot/${config.networking.domain}/dovecot-ldap.conf
298 default_fields =
299 override_fields =
300 skip = found
301 }
302 #passdb {
303 # driver = passwd-file
304 # args = scheme=crypt username_format=%n ${authDir}/%d/passwd
305 #}
306 #userdb {
307 # # NOTE: this userdb is only used by lda.
308 # driver = passwd-file
309 # args = username_format=%n ${authDir}/%d/passwd
310 # #default_fields = home=${mailDir}/%d/%n
311 # skip = found
312 #}
313 mail_home = ${mailDir}/%d/%n
314 # NOTE: if needed, may be overrided by userdb_mail
315 mail_location = maildir:${mailDir}/%d/%n/Maildir:LAYOUT=fs:INDEX=${libDir}/index/%d/%n:CONTROL=${libDir}/control/%d/%n
316 # NOTE: if needed, may be overrided by userdb_mail
317 # NOTE: INDEX and CONTROL are on a partition without quota, as explain in the doc.
318 # SEE: http://wiki2.dovecot.org/Quota/FS
319 auth_mechanisms = plain login
320 auth_ssl_require_client_cert = no
321 # NOTE: postfix does not supply a client cert.
322 auth_ssl_username_from_cert = yes
323 auth_verbose = yes
324 auth_username_format = %Lu
325 # NOTE: lowercase the username, help with LDAP?
326 ${lib.optionalString dovecot2.debug ''
327 auth_debug = yes
328 mail_debug = yes
329 verbose_ssl = yes
330 ''}
331 default_internal_user = ${dovecot2.user}
332 default_internal_group = ${dovecot2.group}
333 disable_plaintext_auth = yes
334 first_valid_uid = 1000
335 lda_mailbox_autocreate = yes
336 lda_mailbox_autosubscribe = yes
337 listen = *
338 log_timestamp = "%Y-%m-%d %H:%M:%S "
339 #maildir_copy_with_hardlinks = yes
340 namespace inbox {
341 # NOTE: here because protocol sieve {namespace inbox{}} does not seem to work.
342 inbox = yes
343 location =
344 list = yes
345 prefix =
346 separator = ${dirSep}
347 }
348 namespace {
349 #list = children
350 list = yes
351 location = maildir:${mailDir}/%d/%n/Maildir:LAYOUT=fs:INDEX=${libDir}/index/%d/%n/Shared/%n:CONTROL=${libDir}/control/%d/%n/Shared/%n
352 # FIXME: %d not working
353 prefix = Partages+%%n+
354 separator = ${dirSep}
355 subscriptions = yes
356 type = shared
357 }
358 mail_plugins = $mail_plugins acl quota virtual
359 #mail_uid = ${dovecot2.mailUser}
360 #mail_gid = ${dovecot2.mailGroup}
361 # NOTE: each user has a dedicated (uid,gid) pair
362 #mail_privileged_group = mail
363 #mail_access_groups =
364 plugin {
365 acl = vfile:/etc/dovecot/acl/global.d
366 acl_anyone = allow
367 acl_shared_dict = file:${mailDir}/%d/acl.db
368 # NOTE: to let users LIST mailboxes shared by other users,
369 # Dovecot needs a shared mailbox dictionary.
370 # FIXME: %d not working with userdb ldap
371 ##antispam_allow_append_to_spam = yes
372 # # NOTE: pour offlineimap
373 #antispam_backend = pipe
374 ##antispam_crm_args = -u;${mailDir}/%d/.crm114;/usr/share/crm114/mailfilter.crm
375 #antispam_crm_args = -u;${mailDir}/crm114;/usr/share/crm114/mailfilter.crm
376 #antispam_crm_binary = /usr/bin/crm
377 #antispam_debug_target = syslog
378 ##antispam_crm_env = HOME=%h;USER=%u
379 #antispam_ham_keywords = NonJunk
380 #antispam_pipe_program = /usr/bin/crm
381 #antispam_pipe_program_args = -u;${mailDir}/crm114;/usr/share/crm114/mailfilter.crm;--stats_only;--force
382 #antispam_pipe_program_notspam_arg = --learnnonspam
383 #antispam_pipe_program_spam_arg = --learnspam
384 #antispam_pipe_program_unlearn_spam_args = --unlearn;--learnspam
385 #antispam_pipe_program_unlearn_notspam_args = --unlearn;--learnnonspam
386 #antispam_pipe_tmpdir = ${mailDir}/crm114/tmp
387 #antispam_signature = X-CRM114-CacheID
388 #antispam_signature_missing = move
389 #antispam_spam = Junk
390 #antispam_spam_keywords = Junk
391 #antispam_trash = Trash
392 #antispam_unsure = Unsure
393 #antispam_verbose_debug = 0
394 quota = maildir:User quota
395 quota_rule = *:storage=256M
396 quota_rule2 = Trash:storage=+64M
397 quota_max_mail_size = 20M
398 #quota_exceeded_message = </path/to/quota_exceeded_message.txt
399 quota_warning = storage=95%% quota-warning 95 %u
400 quota_warning2 = storage=80%% quota-warning 80 %u
401 quota_warning3 = -storage=100%% quota-warning below %u
402 # NOTE: user is no longer over quota
403 recipient_delimiter = ${extSep}
404 sieve = file:${mailDir}/%d/%n/sieve;active=${mailDir}/%d/%n/sieve/main.sieve
405 #sieve_default = file:${mailDir}/%u/default.sieve
406 #sieve_default_name = default
407 sieve_after = ${sieveDir}/after.d/
408 sieve_before = ${sieveDir}/before.d/
409 sieve_dir = ${mailDir}/%d/%n/sieve/
410 #sieve_extensions = +spamtest +spamtestplus
411 sieve_global_dir = ${sieveDir}/global.d/
412 sieve_max_script_size = 1M
413 sieve_quota_max_scripts = 0
414 sieve_quota_max_storage = 10M
415 sieve_spamtest_max_value = 10
416 sieve_spamtest_status_header = X-Spam-Score
417 sieve_spamtest_status_type = strlen
418 sieve_user_log = /var/log/dovecot/%d/sieve.%n.log
419 }
420 service quota-warning {
421 executable = script ${
422 pkgs.writeScript "quota-warning" ''
423 #!/bin/sh -eu
424 PERCENT=$1
425 USER=$2
426 cat << EOF | ${pkgs.dovecot}/libexec/dovecot/dovecot-lda -d $USER -o
427 "plugin/quota=maildir:User quota:noenforcing"
428 From: postmaster@${config.networking.domain}
429 Subject: [WARNING] your mailbox is now $PERCENT% full.
430
431 Please remove some mails to make room for new ones.
432 EOF
433 ''
434 }
435 # use some unprivileged user for executing the quota warnings
436 user = ${dovecot2.user}
437 unix_listener quota-warning {
438 }
439 }
440 protocol imap {
441 #mail_max_userip_connections = 10
442 mail_plugins = $mail_plugins imap_acl imap_quota # antispam
443 namespace inbox {
444 inbox = yes
445 location =
446 list = yes
447 mailbox Drafts {
448 special_use = \Drafts
449 }
450 mailbox Junk {
451 special_use = \Junk
452 }
453 mailbox Sent {
454 special_use = \Sent
455 }
456 mailbox "Sent Messages" {
457 special_use = \Sent
458 }
459 mailbox Trash {
460 special_use = \Trash
461 }
462 prefix =
463 separator = ${dirSep}
464 }
465 }
466 protocol lda {
467 auth_socket_path = /var/run/dovecot/auth-userdb
468 hostname = ${config.networking.domain}
469 info_log_path =
470 log_path =
471 mail_plugins = $mail_plugins sieve
472 namespace inbox {
473 inbox = yes
474 location =
475 list = yes
476 prefix =
477 separator = ${dirSep}
478 }
479 postmaster_address = postmaster${extSep}dovecot${extSep}lda@${config.networking.domain}
480 syslog_facility = mail
481 }
482 protocol lmtp {
483 #info_log_path = /tmp/dovecot-lmtp.log
484 mail_plugins = $mail_plugins sieve
485 namespace inbox {
486 inbox = yes
487 location =
488 list = yes
489 prefix =
490 separator = ${dirSep}
491 }
492 postmaster_address = postmaster${extSep}dovecot${extSep}lmtp@${config.networking.domain}
493 }
494 protocol pop3 {
495 #mail_max_userip_connections = 10
496 namespace all {
497 # NOTE: used by ${libDir}/pop3/INBOX/dovecot-virtual
498 hidden = yes
499 list = no
500 location =
501 prefix = all+
502 separator = ${dirSep}
503 }
504 # Virtual namespace for the virtual INBOX.
505 # Use a global directory for dovecot-virtual files.
506 namespace inbox {
507 inbox = yes
508 hidden = yes
509 list = no
510 location = virtual:${libDir}/pop3:INDEX=${libDir}/index/%d/%n/POP3:LAYOUT=fs
511 prefix = pop3+
512 separator = ${dirSep}
513 }
514 pop3_client_workarounds =
515 pop3_fast_size_lookups = yes
516 pop3_lock_session = yes
517 pop3_no_flag_updates = yes
518 # Use GUIDs to avoid accidental POP3 UIDL changes instead of IMAP UIDs.
519 pop3_uidl_format = %g
520 }
521 protocol sieve {
522 #mail_max_userip_connections = 10
523 #managesieve_implementation_string = Dovecot Pigeonhole
524 managesieve_max_compile_errors = 5
525 #managesieve_max_line_length = 65536
526 #managesieve_notify_capability = mailto
527 #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
528 }
529 protocols = imap lmtp pop3 sieve
530 service lmtp {
531 #executable = lmtp -L
532 process_min_avail = 2
533 unix_listener /var/lib/postfix/queue/private/dovecot-lmtp {
534 user = ${postfix.user}
535 group = ${postfix.group}
536 mode = 0600
537 }
538 #user = mail
539 }
540 service auth {
541 user = root
542 # FIXME: may be user=dovecot-auth with LDAP?
543 unix_listener auth-userdb {
544 user = ${dovecot2.user}
545 group = ${dovecot2.group}
546 mode = 0660
547 }
548 unix_listener /var/lib/postfix/queue/private/auth {
549 user = ${postfix.user}
550 group = ${postfix.group}
551 mode = 0660
552 }
553 }
554 service imap {
555 # Most of the memory goes to mmap()ing files.
556 # You may need to increase this limit if you have huge mailboxes.
557 #vsz_limit =
558 process_limit = 1024
559 }
560 service imap-login {
561 #inet_listener imap {
562 # address = 127.0.0.1
563 # port = 143
564 # ssl = no
565 #}
566 inet_listener imaps {
567 port = 993
568 ssl = yes
569 }
570 }
571 service pop3 {
572 process_limit = 1024
573 }
574 service pop3-login {
575 inet_listener pop3s {
576 port = 995
577 ssl = yes
578 }
579 }
580 ssl = required
581 ssl_dh = <${x509.dir}/dh.pem
582 ssl_cipher_list = HIGH:!LOW:!SSLv2:!EXP:!aNULL
583 ssl_cert = <${x509.cert}
584 ssl_key = <${x509.key}
585 #ssl_ca = <''${caPath}
586 #ssl_verify_client_cert = yes
587 '');
588 #${lib.concatMapStringsSep "\n"
589 # (dom: ''
590 # local_name mail.${dom} {
591 # #ssl_ca = <''${caPath}
592 # ssl_cert = <${x509.cert dom}
593 # ssl_key = <${x509.key dom}
594 # }
595 # '')
596 # dovecot2.domains
597 #}
598 };
599 };
600 }