]> Git — Sourcephile - sourcephile-nix.git/blob - install/logical/friot/dovecot.nix
nix: add nixpkgs-commonsoft
[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 in
85 {
86 imports = [
87 dovecot/autoconfig.nix
88 ];
89 options = {
90 services.dovecot2.sieves = {
91 global = lib.mkOption {
92 description = "global scripts.";
93 type = types.attrsOf types.str;
94 default = {};
95 };
96 before = lib.mkOption {
97 description = "before scripts.";
98 type = types.attrsOf types.str;
99 default = {};
100 };
101 after = lib.mkOption {
102 description = "after scripts.";
103 type = types.attrsOf types.str;
104 default = {};
105 };
106 };
107 };
108 config = {
109 environment.etc = etc_dovecot;
110 #services.postfix.mapFiles."transport-dovecot" =
111 # toFile "transport-dovecot"
112 # (unlines
113 # (lib.mapAttrsToList
114 # (dom: {...}: "${transportSubDomain}.${dom} lmtp:unix:private/dovecot-lmtp")
115 # dovecot2.domains));
116 systemd.services.dovecot2 = {
117 after = [
118 "postfix.service"
119 "openldap.service"
120 ];
121 restartTriggers =
122 map (f: f.source) etc_dovecot;
123 environment = {
124 #LD_LIBRARY_PATH = config.system.nssModules.path;
125 };
126 preStart = unlinesValues {
127 installMailDir = ''
128 # SEE: http://wiki2.dovecot.org/SharedMailboxes/Permissions
129 install -D -d -m 0771 \
130 -o ${dovecot2.mailUser} \
131 -g ${dovecot2.mailGroup} \
132 ${mailDir}
133 '';
134
135 installSieve = ''
136 rm -rf "${sieveDir}"
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"
145 '') sieves
146 ) dovecot2.sieves;
147
148 installDomains = ''
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} \
153 -g ${domainGroup} \
154 ${mailDir}/${networking.domain} \
155 ${stateDir}/control/${networking.domain} \
156 ${stateDir}/index/${networking.domain}
157
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} \
164 -g ${domainGroup} \
165 ${stateDir}/acl/${networking.domain}
166
167 # NOTE: domainAliases point to the very same mailboxes as domain's.
168 for domainAlias in ${unwords networking.domainAliases}
169 do
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
174 done
175 '';
176 };
177 };
178 services.dovecot2 = {
179 enable = true;
180 debug = true;
181 mailUser = "dovemail";
182 mailGroup = "dovemail";
183 modules = [
184 #pkgs.dovecot_antispam
185 pkgs.dovecot_pigeonhole
186 ];
187 sieves = {
188 global = {
189 list = ''
190 require
191 [ "date"
192 , "fileinto"
193 , "mailbox"
194 , "variables"
195 ];
196
197 if currentdate :matches "year" "*" { set "year" "''${1}"; }
198 if currentdate :matches "month" "*" { set "month" "''${1}"; }
199
200 if exists "List-ID" {
201 if header :matches "List-ID" "*<*.*.*.*>*" {
202 set "list" "''${2}";
203 set "domain" "''${4}";
204 }
205 elsif header :matches "List-ID" "*<*.*.*>*" {
206 set "list" "''${2}";
207 set "domain" "''${3}";
208 }
209 fileinto :create "Listes+''${domain}+''${list}+''${year}+''${month}";
210 stop;
211 }
212 '';
213 spam = ''
214 require
215 [ "imap4flags"
216 ];
217
218 if header :contains "X-Spam-Level" "***" {
219 addflag "Junk";
220 }
221 '';
222 extension = ''
223 require
224 [ "envelope"
225 , "fileinto"
226 , "mailbox"
227 , "subaddress"
228 , "variables"
229 ];
230 if envelope :matches :detail "TO" "*" {
231 set "extension" "''${1}";
232 }
233 if not string :is "''${extension}" "" {
234 fileinto :create "Plus+''${extension}";
235 stop;
236 }
237 '';
238 };
239 };
240 configFile = toString (pkgs.writeText "dovecot.conf" ''
241 passdb {
242 driver = ldap
243 args = /etc/dovecot/${networking.domain}/dovecot-ldap.conf
244 default_fields = userdb_mail_access_groups=${domainGroup}
245 override_fields =
246 }
247 userdb {
248 driver = prefetch
249 }
250 userdb {
251 # NOTE: this userdb is only used by lda.
252 driver = ldap
253 args = /etc/dovecot/${networking.domain}/dovecot-ldap.conf
254 default_fields = mail_access_groups=${domainGroup}
255 override_fields =
256 skip = found
257 }
258 #passdb {
259 # driver = passwd-file
260 # args = scheme=crypt username_format=%n ${authDir}/%d/passwd
261 #}
262 #userdb {
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
267 # skip = found
268 #}
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
279 auth_verbose = yes
280 auth_username_format = %Lu
281 # NOTE: lowercase the username, help with LDAP?
282 ${lib.optionalString dovecot2.debug ''
283 auth_debug = yes
284 mail_debug = yes
285 verbose_ssl = yes
286 ''}
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
294 listen = *
295 log_timestamp = "%Y-%m-%d %H:%M:%S "
296 #maildir_copy_with_hardlinks = yes
297 namespace inbox {
298 # NOTE: here because protocol sieve {namespace inbox{}} does not seem to work.
299 type = private
300 inbox = yes
301 location =
302 list = yes
303 prefix =
304 separator = ${dirSep}
305 }
306 namespace {
307 type = shared
308 #list = children
309 list = yes
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}
318 subscriptions = yes
319 }
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 =
326 plugin {
327 acl = vfile:/etc/dovecot/acl/global.d
328 acl_anyone = allow
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
381 }
382 service quota-warning {
383 executable = script ${
384 pkgs.writeScript "quota-warning" ''
385 #!/bin/sh -eu
386 PERCENT=$1
387 USER=$2
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.
392
393 Please remove some mails to make room for new ones.
394 EOF
395 ''
396 }
397 # use some unprivileged user for executing the quota warnings
398 user = ${dovecot2.user}
399 unix_listener quota-warning {
400 }
401 }
402 protocol imap {
403 #mail_max_userip_connections = 10
404 mail_plugins = $mail_plugins imap_acl imap_quota # antispam
405 namespace inbox {
406 inbox = yes
407 location =
408 list = yes
409 mailbox Drafts {
410 special_use = \Drafts
411 }
412 mailbox Junk {
413 special_use = \Junk
414 }
415 mailbox Sent {
416 special_use = \Sent
417 }
418 mailbox "Sent Messages" {
419 special_use = \Sent
420 }
421 mailbox Trash {
422 special_use = \Trash
423 }
424 prefix =
425 separator = ${dirSep}
426 }
427 }
428 protocol lda {
429 auth_socket_path = /var/run/dovecot/auth-userdb
430 hostname = ${networking.domain}
431 info_log_path =
432 log_path =
433 mail_plugins = $mail_plugins sieve
434 namespace inbox {
435 inbox = yes
436 location =
437 list = yes
438 prefix =
439 separator = ${dirSep}
440 }
441 postmaster_address = postmaster${extSep}dovecot${extSep}lda@${networking.domain}
442 syslog_facility = mail
443 }
444 protocol lmtp {
445 #info_log_path = /tmp/dovecot-lmtp.log
446 mail_plugins = $mail_plugins sieve
447 namespace inbox {
448 inbox = yes
449 location =
450 list = yes
451 prefix =
452 separator = ${dirSep}
453 }
454 postmaster_address = postmaster${extSep}dovecot${extSep}lmtp@${networking.domain}
455 }
456 protocol pop3 {
457 #mail_max_userip_connections = 10
458 namespace all {
459 # NOTE: used by ${dovecot-virtual}/pop3/INBOX/dovecot-virtual
460 hidden = yes
461 list = no
462 location =
463 prefix = all+
464 separator = ${dirSep}
465 }
466 # Virtual namespace for the virtual INBOX.
467 # Use a global directory for dovecot-virtual files.
468 namespace inbox {
469 inbox = yes
470 hidden = yes
471 list = no
472 location = virtual:${dovecot-virtual}/pop3:INDEX=${stateDir}/index/%d/%n/POP3:LAYOUT=fs
473 prefix = pop3+
474 separator = ${dirSep}
475 }
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
482 }
483 protocol sieve {
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
490 }
491 protocols = imap lmtp pop3 sieve
492 service lmtp {
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}
498 mode = 0600
499 }
500 #user = mail
501 }
502 service auth {
503 user = root
504 # FIXME: may be user=dovecot-auth with LDAP?
505 unix_listener auth-userdb {
506 user = ${dovecot2.user}
507 group = ${dovecot2.group}
508 mode = 0660
509 }
510 unix_listener /var/lib/postfix/queue/private/auth {
511 user = ${postfix.user}
512 group = ${postfix.group}
513 mode = 0660
514 }
515 }
516 service imap {
517 # Most of the memory goes to mmap()ing files.
518 # You may need to increase this limit if you have huge mailboxes.
519 #vsz_limit =
520 process_limit = 1024
521 }
522 service imap-login {
523 #inet_listener imap {
524 # address = 127.0.0.1
525 # port = 143
526 # ssl = no
527 #}
528 inet_listener imaps {
529 port = 993
530 ssl = yes
531 }
532 }
533 service pop3 {
534 process_limit = 1024
535 }
536 service pop3-login {
537 inet_listener pop3s {
538 port = 995
539 ssl = yes
540 }
541 }
542 ssl = required
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
549 '');
550 #${lib.concatMapStringsSep "\n"
551 # (dom: ''
552 # local_name mail.${dom} {
553 # #ssl_ca = <''${caPath}
554 # ssl_cert = <${x509.cert dom}
555 # ssl_key = <${x509.key dom}
556 # }
557 # '')
558 # dovecot2.domains
559 #}
560 };
561 };
562 }