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