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