]> Git — Sourcephile - sourcephile-nix.git/blob - install/logical/friot/dovecot.nix
update
[sourcephile-nix.git] / install / logical / friot / dovecot.nix
1 {pkgs, lib, config, system, ...}:
2 let inherit (builtins) toString toFile attrNames;
3 inherit (lib) types;
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;
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 in
20 {
21 config = {
22 services.nginx = {
23 virtualHosts."autoconfig" =
24 let servers = lib.concatMapStringsSep " "
25 (dom: "autoconfig.${dom}")
26 (attrNames dovecot2.domains);
27 autoconfigSite = pkgs.writeTextFile {
28 name = "autoconfig";
29 destination = "/mail/config-v1.1.xml";
30 text = ''
31 <?xml version="1.0"?>
32 <clientConfig version="1.1">
33 <emailProvider id="%EMAILDOMAIN%">
34 <!-- <displayName></displayName> -->
35 <!-- <displayShortName></displayShortName> -->
36 <domain>%EMAILDOMAIN%</domain>
37 <incomingServer type="imap">
38 <hostname>imap.%EMAILDOMAIN%</hostname>
39 <port>993</port>
40 <socketType>SSL</socketType>
41 <username>%EMAILADDRESS%</username>
42 <authentication>password-cleartext</authentication>
43 </incomingServer>
44 <incomingServer type="pop3">
45 <hostname>pop.%EMAILDOMAIN%</hostname>
46 <port>995</port>
47 <socketType>SSL</socketType>
48 <username>%EMAILADDRESS%</username>
49 <authentication>password-cleartext</authentication>
50 <pop3>
51 <leaveMessagesOnServer>false</leaveMessagesOnServer>
52 <downloadOnBiff>true</downloadOnBiff>
53 </pop3>
54 </incomingServer>
55 <outgoingServer type="smtp">
56 <hostname>smtp.%EMAILDOMAIN%</hostname>
57 <port>465</port>
58 <socketType>SSL</socketType> <!-- see above -->
59 <username>%EMAILADDRESS%</username> <!-- if smtp-auth -->
60 <authentication>password-cleartext</authentication>
61 <!-- <restriction>client-IP-address</restriction> -->
62 <addThisServer>true</addThisServer>
63 <useGlobalPreferredServer>false</useGlobalPreferredServer>
64 </outgoingServer>
65 </emailProvider>
66 <!-- <clientConfigUpdate url="https://www.example.com/config/mozilla.xml" /> -->
67 </clientConfig>
68 '';
69 };
70 in
71 {
72 serverName = "autoconfig.${config.networking.domain}";
73 root = autoconfigSite;
74 #addSSL = true;
75 extraConfig = ''
76 access_log off;
77 log_not_found off;
78 '';
79 };
80 };
81 #services.postfix.mapFiles."transport-dovecot" =
82 # toFile "transport-dovecot"
83 # (unlines
84 # (lib.mapAttrsToList
85 # (dom: {...}: "${transportSubDomain}.${dom} lmtp:unix:private/dovecot-lmtp")
86 # dovecot2.domains));
87 systemd.services.dovecot2.after = [ "postfix.service" ];
88 #users.extraUsers = [
89 # { name = "dovecot";
90 # uid = config.ids.uids.dovecot2;
91 # description = "Dovecot user";
92 # group = dovecot2.group;
93 # }
94 #];
95 users.extraGroups = lib.mapAttrs
96 (domain: {...}:
97 { name = escapeGroup "${dovecot2.mailGroup}-${domain}";
98 })
99 dovecot2.domains;
100 systemd.services.dovecot2.preStart =
101 let sieveList =
102 pkgs.writeText "list.sieve" ''
103 require
104 [ "date"
105 , "fileinto"
106 , "mailbox"
107 , "variables"
108 ];
109
110 if currentdate :matches "year" "*" { set "year" "''${1}"; }
111 if currentdate :matches "month" "*" { set "month" "''${1}"; }
112
113 if exists "List-ID" {
114 if header :matches "List-ID" "*<*.*.*.*>*" {
115 set "list" "''${2}";
116 set "domain" "''${4}";
117 }
118 elsif header :matches "List-ID" "*<*.*.*>*" {
119 set "list" "''${2}";
120 set "domain" "''${3}";
121 }
122 fileinto :create "Listes+''${domain}+''${list}+''${year}+''${month}";
123 stop;
124 }
125 '';
126 sieveSpam =
127 pkgs.writeText "spam.sieve" ''
128 require
129 [ "imap4flags"
130 ];
131
132 if header :contains "X-Spam-Level" "***" {
133 addflag "Junk";
134 }
135 '';
136 sieveExtension =
137 pkgs.writeText "extension.sieve" ''
138 require
139 [ "envelope"
140 , "fileinto"
141 , "mailbox"
142 , "subaddress"
143 , "variables"
144 ];
145 if envelope :matches :detail "TO" "*" {
146 set "extension" "''${1}";
147 }
148 if not string :is "''${extension}" "" {
149 fileinto :create "Plus+''${extension}";
150 stop;
151 }
152 '';
153 dovecot-virtual =
154 pkgs.writeText "dovecot-virtual" ''
155 all
156 all+*
157 all
158 '';
159 in ''
160 # SEE: http://wiki2.dovecot.org/SharedMailboxes/Permissions
161 # The sticky bit is to allow the acl.db{.lock,} done by dovecot
162 install -D -d -m 2771 \
163 -o ${dovecot2.mailUser} \
164 -g ${dovecot2.mailGroup} \
165 ${mailDir}
166
167 # Install global sieves
168 install -D -d -m 0755 \
169 -o root \
170 -g root \
171 ${sieveDir} \
172 ${sieveDir}/after.d \
173 ${sieveDir}/before.d \
174 ${sieveDir}/global.d
175 ln -fns ${sieveList} ${sieveDir}/global.d/list.sieve
176 ln -fns ${sieveExtension} ${sieveDir}/global.d/extension.sieve
177 ln -fns ${sieveSpam} ${sieveDir}/global.d/spam.sieve
178 for f in ${sieveDir}/*/*.sieve
179 do ${pkgs.dovecot_pigeonhole}/bin/sievec $f
180 done
181
182 # Install pop3 Inbox
183 install -D -m 0644 \
184 -o root \
185 -g root \
186 ${dovecot-virtual} \
187 ${libDir}/pop3/INBOX/dovecot-virtual
188 ''
189 + ''
190 # Install domains
191 new_uid=5000
192 ''
193 + unlines (lib.mapAttrsToList (domain: {accounts, ...}:
194 let domainGroup = escapeGroup "${dovecot2.mailGroup}-${domain}"; in
195 ''
196 install -D -d -m 1770 \
197 -o ${dovecot2.mailUser} \
198 -g ${domainGroup} \
199 ${mailDir}/${domain} \
200 ${libDir}/control/${domain} \
201 ${libDir}/index/${domain}
202 install -D -d -m 1770 \
203 -o ${dovecot2.mailUser} \
204 -g ${authGroup} \
205 ${libDir}/auth \
206 ${libDir}/auth/${domain}
207 dir_passwd=${libDir}/auth/${domain}
208 old_passwd=$dir_passwd/passwd
209 new_passwd=$(TMPDIR= mktemp --tmpdir=$dir_passwd -t passwd.XXXXXXXX.tmp)
210
211 # Install users
212 ''
213 + unlines (lib.mapAttrsToList (user: acct: ''
214 (
215 home=${mailDir}/${domain}/${user}
216 gecos=
217 shell=/run/current-system/sw/bin/nologin
218 if test -e $home
219 then
220 uid=$(stat -c %u $home)
221 gid=$(stat -c %g $home)
222 fi
223 [ "''${uid:+set}" ] || {
224 while test exists = "$(find $(dirname $home) -mindepth 1 -maxdepth 1 -uid $new_uid -printf exists -quit)"
225 do new_uid=$((new_uid + 1))
226 done
227 uid=$new_uid
228 gid=$new_uid
229 }
230 install -D -d -o $uid -g $gid -m 2770 $home $home/Maildir
231 install -d -o $uid -g $gid -m 0700 $home/sieve
232 ''
233 + unlines (lib.mapAttrsToList
234 (n: v: ''
235 install -D -m 640 -o $uid -g $gid \
236 ${pkgs.writeText "${n}.sieve" v} \
237 $home/sieve/${n}.sieve
238 ${pkgs.dovecot_pigeonhole}/bin/sievec \
239 $home/sieve/${n}.sieve
240 '')
241 acct.sieves)
242 + ''
243 mail_access_groups=${lib.concatStringsSep "," ([domainGroup] ++ acct.groups)}
244 quota=${if lib.isString acct.quota
245 then ''"userdb_quota_rule=*:storage=${acct.quota}"''
246 else ""}
247 extra_fields="userdb_uid=$uid userdb_gid=$gid userdb_mail_access_groups=$mail_access_groups $quota"
248 #test ! -e $old_passwd || {
249 # # Preserve password changed by another mechanism, eg. roundcube.
250 # # But this also does not overwrite any old password set by this config.
251 # pass="$(sed -ne "s/^${user}:\([^:]*\):.*/\1/p" $old_passwd)"
252 #}
253 [ "''${pass:+set}" ] || {
254 pass=${lib.escapeShellArg acct.password}
255 }
256 printf '%s\n' >>$new_passwd \
257 "${user}:$pass:$uid:$gid:$gecos:$home:$shell:$extra_fields"
258 )
259 '') accounts)
260 + ''
261 install -o ${authUser} -g ${authGroup} -m 0640 $new_passwd $old_passwd
262 rm $new_passwd
263 ''
264 ) dovecot2.domains);
265 services.dovecot2 = {
266 enable = true;
267 mailUser = "dovemail";
268 mailGroup = "dovemail";
269 modules = [
270 #pkgs.dovecot_antispam
271 pkgs.dovecot_pigeonhole
272 ];
273 # ${lib.concatMapStringsSep "\n"
274 # (dom: ''
275 # local_name imap.${dom} {
276 # #ssl_ca = <''${caPath}
277 # ssl_cert = <${x509.cert dom}
278 # ssl_key = <${x509.key dom}
279 # }
280 # local_name pop.${dom} {
281 # #ssl_ca = <''${caPath}
282 # ssl_cert = <${x509.cert dom}
283 # ssl_key = <${x509.key dom}
284 # }
285 # '')
286 # dovecot2.domains
287 # }
288
289 configFile = toString (pkgs.writeText "dovecot.conf" ''
290 passdb {
291 driver = passwd-file
292 args = scheme=crypt username_format=%n ${authDir}/%d/passwd
293 }
294 userdb {
295 driver = prefetch
296 }
297 userdb {
298 # NOTE: this userdb is only used by lda.
299 driver = passwd-file
300 args = username_format=%n ${authDir}/%d/passwd
301 #default_fields = home=${mailDir}/%d/%n
302 }
303 mail_home = ${mailDir}/%d/%n
304 auth_mechanisms = plain login
305 # postfix does not supply a client cert.
306 auth_ssl_require_client_cert = no
307 auth_ssl_username_from_cert = yes
308 auth_verbose = yes
309 ${lib.optionalString dovecot2.debug ''
310 auth_debug = yes
311 mail_debug = yes
312 verbose_ssl = yes
313 ''}
314 default_internal_user = ${dovecot2.user}
315 default_internal_group = ${dovecot2.group}
316 disable_plaintext_auth = yes
317 first_valid_uid = 1000
318 lda_mailbox_autocreate = yes
319 lda_mailbox_autosubscribe = yes
320 listen = *
321 log_timestamp = "%Y-%m-%d %H:%M:%S "
322 # NOTE: INDEX and CONTROL are on a partition without quota, as explain in the doc.
323 # SEE: http://wiki2.dovecot.org/Quota/FS
324 mail_location = maildir:${mailDir}/%d/%n/Maildir:LAYOUT=fs:INDEX=${libDir}/index/%d/%n:CONTROL=${libDir}/control/%d/%n
325 namespace inbox {
326 # NOTE: here because protocol sieve {namespace inbox{}} does not seem to work.
327 inbox = yes
328 location =
329 list = yes
330 prefix =
331 separator = ${dirSep}
332 }
333 namespace {
334 #list = children
335 list = yes
336 location = maildir:${mailDir}/%%d/%%n/Maildir:LAYOUT=fs:INDEX=${libDir}/index/%d/%n/Shared/%%n:CONTROL=${libDir}/control/%d/%n/Shared/%%n
337 prefix = Partages+%%n+
338 separator = ${dirSep}
339 subscriptions = yes
340 type = shared
341 }
342 mail_plugins = $mail_plugins acl quota virtual
343 #mail_uid = ${dovecot2.mailUser}
344 #mail_gid = ${dovecot2.mailGroup}
345 #mail_privileged_group = mail
346 #mail_access_groups =
347 plugin {
348 acl = vfile:/etc/dovecot/acl/global.d
349 acl_anyone = allow
350 acl_shared_dict = file:${mailDir}/%d/acl.db
351 ##antispam_allow_append_to_spam = yes
352 # # NOTE: pour offlineimap
353 #antispam_backend = pipe
354 ##antispam_crm_args = -u;${mailDir}/%d/.crm114;/usr/share/crm114/mailfilter.crm
355 #antispam_crm_args = -u;${mailDir}/crm114;/usr/share/crm114/mailfilter.crm
356 #antispam_crm_binary = /usr/bin/crm
357 #antispam_debug_target = syslog
358 ##antispam_crm_env = HOME=%h;USER=%u
359 #antispam_ham_keywords = NonJunk
360 #antispam_pipe_program = /usr/bin/crm
361 #antispam_pipe_program_args = -u;${mailDir}/crm114;/usr/share/crm114/mailfilter.crm;--stats_only;--force
362 #antispam_pipe_program_notspam_arg = --learnnonspam
363 #antispam_pipe_program_spam_arg = --learnspam
364 #antispam_pipe_program_unlearn_spam_args = --unlearn;--learnspam
365 #antispam_pipe_program_unlearn_notspam_args = --unlearn;--learnnonspam
366 #antispam_pipe_tmpdir = ${mailDir}/crm114/tmp
367 #antispam_signature = X-CRM114-CacheID
368 #antispam_signature_missing = move
369 #antispam_spam = Junk
370 #antispam_spam_keywords = Junk
371 #antispam_trash = Trash
372 #antispam_unsure = Unsure
373 #antispam_verbose_debug = 0
374 quota = maildir:User quota
375 quota_rule = *:storage=256M
376 quota_rule2 = Trash:storage=+64M
377 recipient_delimiter = ${extSep}
378 sieve = file:${mailDir}/%d/%n/sieve;active=${mailDir}/%d/%n/sieve/main.sieve
379 #sieve_default = file:${mailDir}/%u/default.sieve
380 #sieve_default_name = default
381 sieve_after = ${sieveDir}/after.d/
382 sieve_before = ${sieveDir}/before.d/
383 sieve_dir = ${mailDir}/%d/%n/sieve/
384 #sieve_extensions = +spamtest +spamtestplus
385 sieve_global_dir = ${sieveDir}/global.d/
386 sieve_max_script_size = 1M
387 sieve_quota_max_scripts = 0
388 sieve_quota_max_storage = 10M
389 sieve_spamtest_max_value = 10
390 sieve_spamtest_status_header = X-Spam-Score
391 sieve_spamtest_status_type = strlen
392 sieve_user_log = /var/log/dovecot/%d/sieve.%n.log
393 }
394 protocol imap {
395 #mail_max_userip_connections = 10
396 mail_plugins = $mail_plugins imap_acl imap_quota # antispam
397 namespace inbox {
398 inbox = yes
399 location =
400 list = yes
401 mailbox Drafts {
402 special_use = \Drafts
403 }
404 mailbox Junk {
405 special_use = \Junk
406 }
407 mailbox Sent {
408 special_use = \Sent
409 }
410 mailbox "Sent Messages" {
411 special_use = \Sent
412 }
413 mailbox Trash {
414 special_use = \Trash
415 }
416 prefix =
417 separator = ${dirSep}
418 }
419 }
420 protocol lda {
421 auth_socket_path = /var/run/dovecot/auth-userdb
422 hostname = ${config.networking.domain}
423 info_log_path =
424 log_path =
425 mail_plugins = $mail_plugins sieve
426 namespace inbox {
427 inbox = yes
428 location =
429 list = yes
430 prefix =
431 separator = ${dirSep}
432 }
433 postmaster_address = postmaster${extSep}dovecot${extSep}lda@${config.networking.domain}
434 syslog_facility = mail
435 }
436 protocol lmtp {
437 #info_log_path = /tmp/dovecot-lmtp.log
438 mail_plugins = $mail_plugins sieve
439 namespace inbox {
440 inbox = yes
441 location =
442 list = yes
443 prefix =
444 separator = ${dirSep}
445 }
446 postmaster_address = postmaster${extSep}dovecot${extSep}lmtp@${config.networking.domain}
447 }
448 protocol pop3 {
449 #mail_max_userip_connections = 10
450 # Used by ${libDir}/pop3/INBOX/dovecot-virtual
451 namespace all {
452 hidden = yes
453 list = no
454 location =
455 prefix = all+
456 separator = ${dirSep}
457 }
458 # Virtual namespace for the virtual INBOX.
459 # Use a global directory for dovecot-virtual files.
460 namespace inbox {
461 inbox = yes
462 hidden = yes
463 list = no
464 location = virtual:${libDir}/pop3:INDEX=${libDir}/index/%d/%n/POP3:LAYOUT=fs
465 prefix = pop3+
466 separator = ${dirSep}
467 }
468 pop3_client_workarounds =
469 pop3_fast_size_lookups = yes
470 pop3_lock_session = yes
471 pop3_no_flag_updates = yes
472 # Use GUIDs to avoid accidental POP3 UIDL changes instead of IMAP UIDs.
473 pop3_uidl_format = %g
474 }
475 protocol sieve {
476 #mail_max_userip_connections = 10
477 #managesieve_implementation_string = Dovecot Pigeonhole
478 managesieve_max_compile_errors = 5
479 #managesieve_max_line_length = 65536
480 #managesieve_notify_capability = mailto
481 #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
482 }
483 protocols = imap lmtp pop3 sieve
484 service lmtp {
485 #executable = lmtp -L
486 process_min_avail = 2
487 unix_listener /var/lib/postfix/queue/private/dovecot-lmtp {
488 user = ${postfix.user}
489 group = ${postfix.group}
490 mode = 0600
491 }
492 #user = mail
493 }
494 service auth {
495 user = root
496 unix_listener auth-userdb {
497 user = ${dovecot2.user}
498 group = ${dovecot2.group}
499 mode = 0660
500 }
501 unix_listener /var/lib/postfix/queue/private/auth {
502 user = ${postfix.user}
503 group = ${postfix.group}
504 mode = 0660
505 }
506 }
507 service imap {
508 # Most of the memory goes to mmap()ing files.
509 # You may need to increase this limit if you have huge mailboxes.
510 #vsz_limit =
511 process_limit = 1024
512 }
513 service imap-login {
514 #inet_listener imap {
515 # address = 127.0.0.1
516 # port = 143
517 # ssl = no
518 # }
519 inet_listener imaps {
520 port = 993
521 ssl = yes
522 }
523 }
524 service pop3 {
525 process_limit = 1024
526 }
527 service pop3-login {
528 inet_listener pop3s {
529 port = 995
530 ssl = yes
531 }
532 }
533 ssl = required
534 #ssl_ca = <''${caPath}
535 ssl_cert = <${x509.cert}
536 ssl_dh = <${x509.dir}/dh.pem
537 # gOTE: only with dovecot >= 2.3
538 ssl_cipher_list = ALL:!LOW:!SSLv2:!EXP:!aNULL
539 ssl_key = <${x509.key}
540 #ssl_verify_client_cert = yes
541 '');
542 };
543 };
544 }