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