1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib)
10 attrValues
11 concatMapStringsSep
12 concatStrings
13 concatStringsSep
14 flatten
15 imap1
16 literalExpression
17 mapAttrsToList
18 mkEnableOption
19 mkIf
20 mkOption
21 mkRemovedOptionModule
22 optional
23 optionalAttrs
24 optionalString
25 singleton
26 types
27 mkRenamedOptionModule
28 nameValuePair
29 mapAttrs'
30 listToAttrs
31 filter
32 ;
33 inherit (lib.strings) match;
34
35 cfg = config.services.dovecot2;
36 dovecotPkg = pkgs.dovecot;
37
38 baseDir = "/run/dovecot2";
39 stateDir = "/var/lib/dovecot";
40
41 sieveScriptSettings = mapAttrs' (
42 to: _: nameValuePair "sieve_${to}" "${stateDir}/sieve/${to}"
43 ) cfg.sieve.scripts;
44 imapSieveMailboxSettings = listToAttrs (
45 flatten (
46 imap1 (
47 idx: el:
48 singleton {
49 name = "imapsieve_mailbox${toString idx}_name";
50 value = el.name;
51 }
52 ++ optional (el.from != null) {
53 name = "imapsieve_mailbox${toString idx}_from";
54 value = el.from;
55 }
56 ++ optional (el.causes != [ ]) {
57 name = "imapsieve_mailbox${toString idx}_causes";
58 value = concatStringsSep "," el.causes;
59 }
60 ++ optional (el.before != null) {
61 name = "imapsieve_mailbox${toString idx}_before";
62 value = "file:${stateDir}/imapsieve/before/${baseNameOf el.before}";
63 }
64 ++ optional (el.after != null) {
65 name = "imapsieve_mailbox${toString idx}_after";
66 value = "file:${stateDir}/imapsieve/after/${baseNameOf el.after}";
67 }
68 ) cfg.imapsieve.mailbox
69 )
70 );
71
72 mkExtraConfigCollisionWarning = term: ''
73 You referred to ${term} in `services.dovecot2.extraConfig`.
74
75 Due to gradual transition to structured configuration for plugin configuration, it is possible
76 this will cause your plugin configuration to be ignored.
77
78 Consider setting `services.dovecot2.pluginSettings.${term}` instead.
79 '';
80
81 # Those settings are automatically set based on other parts
82 # of this module.
83 automaticallySetPluginSettings = [
84 "sieve_plugins"
85 "sieve_extensions"
86 "sieve_global_extensions"
87 "sieve_pipe_bin_dir"
88 ]
89 ++ (builtins.attrNames sieveScriptSettings)
90 ++ (builtins.attrNames imapSieveMailboxSettings);
91
92 # The idea is to match everything that looks like `$term =`
93 # but not `# $term something something`
94 # or `# $term = some value` because those are comments.
95 configContainsSetting = lines: term: (match "[[:blank:]]*${term}[[:blank:]]*=.*" lines) != null;
96
97 warnAboutExtraConfigCollisions = map mkExtraConfigCollisionWarning (
98 filter (configContainsSetting cfg.extraConfig) automaticallySetPluginSettings
99 );
100
101 sievePipeBinScriptDirectory = pkgs.linkFarm "sieve-pipe-bins" (
102 map (el: {
103 name = builtins.unsafeDiscardStringContext (baseNameOf el);
104 path = el;
105 }) cfg.sieve.pipeBins
106 );
107
108 dovecotConf = concatStrings [
109 ''
110 base_dir = ${baseDir}
111 protocols = ${concatStringsSep " " cfg.protocols}
112 sendmail_path = /run/wrappers/bin/sendmail
113 mail_plugin_dir = /run/current-system/sw/lib/dovecot/modules
114 # defining mail_plugins must be done before the first protocol {} filter because of https://doc.dovecot.org/configuration_manual/config_file/config_file_syntax/#variable-expansion
115 mail_plugins = $mail_plugins ${concatStringsSep " " cfg.mailPlugins.globally.enable}
116 ''
117
118 (concatStringsSep "\n" (
119 mapAttrsToList (protocol: plugins: ''
120 protocol ${protocol} {
121 mail_plugins = $mail_plugins ${concatStringsSep " " plugins.enable}
122 }
123 '') cfg.mailPlugins.perProtocol
124 ))
125
126 (
127 if cfg.sslServerCert == null then
128 ''
129 ssl = no
130 disable_plaintext_auth = no
131 ''
132 else
133 ''
134 ssl_cert = <${cfg.sslServerCert}
135 ssl_key = <${cfg.sslServerKey}
136 ${optionalString (cfg.sslCACert != null) ("ssl_ca = <" + cfg.sslCACert)}
137 ${optionalString cfg.enableDHE ''ssl_dh = <${config.security.dhparams.params.dovecot2.path}''}
138 disable_plaintext_auth = yes
139 ''
140 )
141
142 ''
143 default_internal_user = ${cfg.user}
144 default_internal_group = ${cfg.group}
145 ${optionalString (cfg.mailUser != null) "mail_uid = ${cfg.mailUser}"}
146 ${optionalString (cfg.mailGroup != null) "mail_gid = ${cfg.mailGroup}"}
147
148 mail_location = ${cfg.mailLocation}
149
150 maildir_copy_with_hardlinks = yes
151 pop3_uidl_format = %08Xv%08Xu
152
153 auth_mechanisms = plain login
154
155 service auth {
156 user = root
157 }
158 ''
159
160 (optionalString cfg.enablePAM ''
161 userdb {
162 driver = passwd
163 }
164
165 passdb {
166 driver = pam
167 args = ${optionalString cfg.showPAMFailure "failure_show_msg=yes"} dovecot2
168 }
169 '')
170
171 (optionalString (cfg.mailboxes != { }) ''
172 namespace inbox {
173 inbox=yes
174 ${concatStringsSep "\n" (map mailboxConfig (attrValues cfg.mailboxes))}
175 }
176 '')
177
178 (optionalString cfg.enableQuota ''
179 service quota-status {
180 executable = ${dovecotPkg}/libexec/dovecot/quota-status -p postfix
181 inet_listener {
182 port = ${cfg.quotaPort}
183 }
184 client_limit = 1
185 }
186
187 plugin {
188 quota_rule = *:storage=${cfg.quotaGlobalPerUser}
189 quota = count:User quota # per virtual mail user quota
190 quota_status_success = DUNNO
191 quota_status_nouser = DUNNO
192 quota_status_overquota = "552 5.2.2 Mailbox is full"
193 quota_grace = 10%%
194 quota_vsizes = yes
195 }
196 '')
197
198 # General plugin settings:
199 # - sieve is mostly generated here, refer to `pluginSettings` to follow
200 # the control flow.
201 ''
202 plugin {
203 ${concatStringsSep "\n" (mapAttrsToList (key: value: " ${key} = ${value}") cfg.pluginSettings)}
204 }
205 ''
206
207 cfg.extraConfig
208 ];
209
210 mailboxConfig =
211 mailbox:
212 ''
213 mailbox "${mailbox.name}" {
214 auto = ${toString mailbox.auto}
215 ''
216 + optionalString (mailbox.autoexpunge != null) ''
217 autoexpunge = ${mailbox.autoexpunge}
218 ''
219 + optionalString (mailbox.specialUse != null) ''
220 special_use = \${toString mailbox.specialUse}
221 ''
222 + "}";
223
224 mailboxes =
225 { name, ... }:
226 {
227 options = {
228 name = mkOption {
229 type = types.strMatching ''[^"]+'';
230 example = "Spam";
231 default = name;
232 readOnly = true;
233 description = "The name of the mailbox.";
234 };
235 auto = mkOption {
236 type = types.enum [
237 "no"
238 "create"
239 "subscribe"
240 ];
241 default = "no";
242 example = "subscribe";
243 description = "Whether to automatically create or create and subscribe to the mailbox or not.";
244 };
245 specialUse = mkOption {
246 type = types.nullOr (
247 types.enum [
248 "All"
249 "Archive"
250 "Drafts"
251 "Flagged"
252 "Junk"
253 "Sent"
254 "Trash"
255 ]
256 );
257 default = null;
258 example = "Junk";
259 description = "Null if no special use flag is set. Other than that every use flag mentioned in the RFC is valid.";
260 };
261 autoexpunge = mkOption {
262 type = types.nullOr types.str;
263 default = null;
264 example = "60d";
265 description = ''
266 To automatically remove all email from the mailbox which is older than the
267 specified time.
268 '';
269 };
270 };
271 };
272in
273{
274 imports = [
275 (mkRemovedOptionModule [ "services" "dovecot2" "package" ] "")
276 (mkRemovedOptionModule [
277 "services"
278 "dovecot2"
279 "modules"
280 ] "Now need to use `environment.systemPackages` to load additional Dovecot modules")
281 (mkRenamedOptionModule
282 [ "services" "dovecot2" "sieveScripts" ]
283 [ "services" "dovecot2" "sieve" "scripts" ]
284 )
285 ];
286
287 options.services.dovecot2 = {
288 enable = mkEnableOption "the dovecot 2.x POP3/IMAP server";
289
290 enablePop3 = mkEnableOption "starting the POP3 listener (when Dovecot is enabled)";
291
292 enableImap = mkEnableOption "starting the IMAP listener (when Dovecot is enabled)" // {
293 default = true;
294 };
295
296 enableLmtp = mkEnableOption "starting the LMTP listener (when Dovecot is enabled)";
297
298 hasNewUnitName = mkOption {
299 type = types.bool;
300 default = true;
301 readOnly = true;
302 internal = true;
303 description = ''
304 Inspectable option to confirm that the dovecot module uses the new
305 `dovecot.service` name, instead of `dovecot2.service`.
306
307 This is a helper added for the nixos-mailserver project and can be
308 removed after branching off nixos-25.11.
309 '';
310 };
311
312 protocols = mkOption {
313 type = types.listOf types.str;
314 default = [ ];
315 description = "Additional listeners to start when Dovecot is enabled.";
316 };
317
318 user = mkOption {
319 type = types.str;
320 default = "dovecot2";
321 description = "Dovecot user name.";
322 };
323
324 group = mkOption {
325 type = types.str;
326 default = "dovecot2";
327 description = "Dovecot group name.";
328 };
329
330 extraConfig = mkOption {
331 type = types.lines;
332 default = "";
333 example = "mail_debug = yes";
334 description = "Additional entries to put verbatim into Dovecot's config file.";
335 };
336
337 mailPlugins =
338 let
339 plugins =
340 hint:
341 types.submodule {
342 options = {
343 enable = mkOption {
344 type = types.listOf types.str;
345 default = [ ];
346 description = "mail plugins to enable as a list of strings to append to the ${hint} `$mail_plugins` configuration variable";
347 };
348 };
349 };
350 in
351 mkOption {
352 type =
353 with types;
354 submodule {
355 options = {
356 globally = mkOption {
357 description = "Additional entries to add to the mail_plugins variable for all protocols";
358 type = plugins "top-level";
359 example = {
360 enable = [ "virtual" ];
361 };
362 default = {
363 enable = [ ];
364 };
365 };
366 perProtocol = mkOption {
367 description = "Additional entries to add to the mail_plugins variable, per protocol";
368 type = attrsOf (plugins "corresponding per-protocol");
369 default = { };
370 example = {
371 imap = [ "imap_acl" ];
372 };
373 };
374 };
375 };
376 description = "Additional entries to add to the mail_plugins variable, globally and per protocol";
377 example = {
378 globally.enable = [ "acl" ];
379 perProtocol.imap.enable = [ "imap_acl" ];
380 };
381 default = {
382 globally.enable = [ ];
383 perProtocol = { };
384 };
385 };
386
387 configFile = mkOption {
388 type = types.nullOr types.path;
389 default = null;
390 description = "Config file used for the whole dovecot configuration.";
391 apply = v: if v != null then v else pkgs.writeText "dovecot.conf" dovecotConf;
392 };
393
394 mailLocation = mkOption {
395 type = types.str;
396 default = "maildir:/var/spool/mail/%u"; # Same as inbox, as postfix
397 example = "maildir:~/mail:INBOX=/var/spool/mail/%u";
398 description = ''
399 Location that dovecot will use for mail folders. Dovecot mail_location option.
400 '';
401 };
402
403 mailUser = mkOption {
404 type = types.nullOr types.str;
405 default = null;
406 description = "Default user to store mail for virtual users.";
407 };
408
409 mailGroup = mkOption {
410 type = types.nullOr types.str;
411 default = null;
412 description = "Default group to store mail for virtual users.";
413 };
414
415 createMailUser =
416 mkEnableOption ''
417 automatically creating the user
418 given in {option}`services.dovecot.user` and the group
419 given in {option}`services.dovecot.group`''
420 // {
421 default = true;
422 };
423
424 sslCACert = mkOption {
425 type = types.nullOr types.str;
426 default = null;
427 description = "Path to the server's CA certificate key.";
428 };
429
430 sslServerCert = mkOption {
431 type = types.nullOr types.str;
432 default = null;
433 description = "Path to the server's public key.";
434 };
435
436 sslServerKey = mkOption {
437 type = types.nullOr types.str;
438 default = null;
439 description = "Path to the server's private key.";
440 };
441
442 enablePAM = mkEnableOption "creating a own Dovecot PAM service and configure PAM user logins" // {
443 default = true;
444 };
445
446 enableDHE = mkEnableOption "ssl_dh and generation of primes for the key exchange" // {
447 default = true;
448 };
449
450 showPAMFailure = mkEnableOption "showing the PAM failure message on authentication error (useful for OTPW)";
451
452 mailboxes = mkOption {
453 type =
454 with types;
455 coercedTo (listOf unspecified) (
456 list:
457 listToAttrs (
458 map (entry: {
459 name = entry.name;
460 value = removeAttrs entry [ "name" ];
461 }) list
462 )
463 ) (attrsOf (submodule mailboxes));
464 default = { };
465 example = literalExpression ''
466 {
467 Spam = { specialUse = "Junk"; auto = "create"; };
468 }
469 '';
470 description = "Configure mailboxes and auto create or subscribe them.";
471 };
472
473 enableQuota = mkEnableOption "the dovecot quota service";
474
475 quotaPort = mkOption {
476 type = types.str;
477 default = "12340";
478 description = ''
479 The Port the dovecot quota service binds to.
480 If using postfix, add check_policy_service inet:localhost:12340 to your smtpd_recipient_restrictions in your postfix config.
481 '';
482 };
483 quotaGlobalPerUser = mkOption {
484 type = types.str;
485 default = "100G";
486 example = "10G";
487 description = "Quota limit for the user in bytes. Supports suffixes b, k, M, G, T and %.";
488 };
489
490 pluginSettings = mkOption {
491 # types.str does not coerce from packages, like `sievePipeBinScriptDirectory`.
492 type = types.attrsOf (
493 types.oneOf [
494 types.str
495 types.package
496 ]
497 );
498 default = { };
499 example = literalExpression ''
500 {
501 sieve = "file:~/sieve;active=~/.dovecot.sieve";
502 }
503 '';
504 description = ''
505 Plugin settings for dovecot in general, e.g. `sieve`, `sieve_default`, etc.
506
507 Some of the other knobs of this module will influence by default the plugin settings, but you
508 can still override any plugin settings.
509
510 If you override a plugin setting, its value is cleared and you have to copy over the defaults.
511 '';
512 };
513
514 imapsieve.mailbox = mkOption {
515 default = [ ];
516 description = "Configure Sieve filtering rules on IMAP actions";
517 type = types.listOf (
518 types.submodule (
519 { config, ... }:
520 {
521 options = {
522 name = mkOption {
523 description = ''
524 This setting configures the name of a mailbox for which administrator scripts are configured.
525
526 The settings defined hereafter with matching sequence numbers apply to the mailbox named by this setting.
527
528 This setting supports wildcards with a syntax compatible with the IMAP LIST command, meaning that this setting can apply to multiple or even all ("*") mailboxes.
529 '';
530 example = "Junk";
531 type = types.str;
532 };
533
534 from = mkOption {
535 default = null;
536 description = ''
537 Only execute the administrator Sieve scripts for the mailbox configured with services.dovecot2.imapsieve.mailbox.<name>.name when the message originates from the indicated mailbox.
538
539 This setting supports wildcards with a syntax compatible with the IMAP LIST command, meaning that this setting can apply to multiple or even all ("*") mailboxes.
540 '';
541 example = "*";
542 type = types.nullOr types.str;
543 };
544
545 causes = mkOption {
546 default = [ ];
547 description = ''
548 Only execute the administrator Sieve scripts for the mailbox configured with services.dovecot2.imapsieve.mailbox.<name>.name when one of the listed IMAPSIEVE causes apply.
549
550 This has no effect on the user script, which is always executed no matter the cause.
551 '';
552 example = [
553 "COPY"
554 "APPEND"
555 ];
556 type = types.listOf (
557 types.enum [
558 "APPEND"
559 "COPY"
560 "FLAG"
561 ]
562 );
563 };
564
565 before = mkOption {
566 default = null;
567 description = ''
568 When an IMAP event of interest occurs, this sieve script is executed before any user script respectively.
569
570 This setting each specify the location of a single sieve script. The semantics of this setting is similar to sieve_before: the specified scripts form a sequence together with the user script in which the next script is only executed when an (implicit) keep action is executed.
571 '';
572 example = literalExpression "./report-spam.sieve";
573 type = types.nullOr types.path;
574 };
575
576 after = mkOption {
577 default = null;
578 description = ''
579 When an IMAP event of interest occurs, this sieve script is executed after any user script respectively.
580
581 This setting each specify the location of a single sieve script. The semantics of this setting is similar to sieve_after: the specified scripts form a sequence together with the user script in which the next script is only executed when an (implicit) keep action is executed.
582 '';
583 example = literalExpression "./report-spam.sieve";
584 type = types.nullOr types.path;
585 };
586 };
587 }
588 )
589 );
590 };
591
592 sieve = {
593 plugins = mkOption {
594 default = [ ];
595 example = [ "sieve_extprograms" ];
596 description = "Sieve plugins to load";
597 type = types.listOf types.str;
598 };
599
600 extensions = mkOption {
601 default = [ ];
602 description = "Sieve extensions for use in user scripts";
603 example = [
604 "notify"
605 "imapflags"
606 "vnd.dovecot.filter"
607 ];
608 type = types.listOf types.str;
609 };
610
611 globalExtensions = mkOption {
612 default = [ ];
613 example = [ "vnd.dovecot.environment" ];
614 description = "Sieve extensions for use in global scripts";
615 type = types.listOf types.str;
616 };
617
618 scripts = mkOption {
619 type = types.attrsOf types.path;
620 default = { };
621 description = "Sieve scripts to be executed. Key is a sequence, e.g. 'before2', 'after' etc.";
622 };
623
624 pipeBins = mkOption {
625 default = [ ];
626 example = literalExpression ''
627 map lib.getExe [
628 (pkgs.writeShellScriptBin "learn-ham.sh" "exec ''${pkgs.rspamd}/bin/rspamc learn_ham")
629 (pkgs.writeShellScriptBin "learn-spam.sh" "exec ''${pkgs.rspamd}/bin/rspamc learn_spam")
630 ]
631 '';
632 description = "Programs available for use by the vnd.dovecot.pipe extension";
633 type = types.listOf types.path;
634 };
635 };
636 };
637
638 config = mkIf cfg.enable {
639 security.pam.services.dovecot2 = mkIf cfg.enablePAM { };
640
641 security.dhparams = mkIf (cfg.sslServerCert != null && cfg.enableDHE) {
642 enable = true;
643 params.dovecot2 = { };
644 };
645
646 services.dovecot2 = {
647 protocols =
648 optional cfg.enableImap "imap" ++ optional cfg.enablePop3 "pop3" ++ optional cfg.enableLmtp "lmtp";
649
650 mailPlugins = mkIf cfg.enableQuota {
651 globally.enable = [ "quota" ];
652 perProtocol.imap.enable = [ "imap_quota" ];
653 };
654
655 sieve.plugins =
656 optional (cfg.imapsieve.mailbox != [ ]) "sieve_imapsieve"
657 ++ optional (cfg.sieve.pipeBins != [ ]) "sieve_extprograms";
658
659 sieve.globalExtensions = optional (cfg.sieve.pipeBins != [ ]) "vnd.dovecot.pipe";
660
661 pluginSettings = lib.mapAttrs (n: lib.mkDefault) (
662 {
663 sieve_plugins = concatStringsSep " " cfg.sieve.plugins;
664 sieve_extensions = concatStringsSep " " (map (el: "+${el}") cfg.sieve.extensions);
665 sieve_global_extensions = concatStringsSep " " (map (el: "+${el}") cfg.sieve.globalExtensions);
666 sieve_pipe_bin_dir = sievePipeBinScriptDirectory;
667 }
668 // sieveScriptSettings
669 // imapSieveMailboxSettings
670 );
671 };
672
673 users.users = {
674 dovenull = {
675 uid = config.ids.uids.dovenull2;
676 description = "Dovecot user for untrusted logins";
677 group = "dovenull";
678 };
679 }
680 // optionalAttrs (cfg.user == "dovecot2") {
681 dovecot2 = {
682 uid = config.ids.uids.dovecot2;
683 description = "Dovecot user";
684 group = cfg.group;
685 };
686 }
687 // optionalAttrs (cfg.createMailUser && cfg.mailUser != null) {
688 ${cfg.mailUser} = {
689 description = "Virtual Mail User";
690 isSystemUser = true;
691 }
692 // optionalAttrs (cfg.mailGroup != null) { group = cfg.mailGroup; };
693 };
694
695 users.groups = {
696 dovenull.gid = config.ids.gids.dovenull2;
697 }
698 // optionalAttrs (cfg.group == "dovecot2") {
699 dovecot2.gid = config.ids.gids.dovecot2;
700 }
701 // optionalAttrs (cfg.createMailUser && cfg.mailGroup != null) {
702 ${cfg.mailGroup} = { };
703 };
704
705 environment.etc."dovecot/dovecot.conf".source = cfg.configFile;
706
707 systemd.services.dovecot = {
708 aliases = [ "dovecot2.service" ];
709 description = "Dovecot IMAP/POP3 server";
710 documentation = [
711 "man:dovecot(1)"
712 "https://doc.dovecot.org"
713 ];
714
715 after = [ "network.target" ];
716 wantedBy = [ "multi-user.target" ];
717 restartTriggers = [ cfg.configFile ];
718
719 startLimitIntervalSec = 60; # 1 min
720 serviceConfig = {
721 Type = "notify";
722 ExecStart = "${dovecotPkg}/sbin/dovecot -F";
723 ExecReload = "${dovecotPkg}/sbin/doveadm reload";
724
725 CapabilityBoundingSet = [
726 "CAP_CHOWN"
727 "CAP_DAC_OVERRIDE"
728 "CAP_FOWNER"
729 "CAP_KILL" # Required for child process management
730 "CAP_NET_BIND_SERVICE"
731 "CAP_SETGID"
732 "CAP_SETUID"
733 "CAP_SYS_CHROOT"
734 "CAP_SYS_RESOURCE"
735 ];
736 LockPersonality = true;
737 MemoryDenyWriteExecute = true;
738 NoNewPrivileges = false; # e.g for sendmail
739 OOMPolicy = "continue";
740 PrivateTmp = true;
741 ProcSubset = "pid";
742 ProtectClock = true;
743 ProtectControlGroups = true;
744 ProtectHome = lib.mkDefault false;
745 ProtectHostname = true;
746 ProtectKernelLogs = true;
747 ProtectKernelModules = true;
748 ProtectKernelTunables = true;
749 ProtectProc = "invisible";
750 ProtectSystem = "full";
751 PrivateDevices = true;
752 Restart = "on-failure";
753 RestartSec = "1s";
754 RestrictAddressFamilies = [
755 "AF_INET"
756 "AF_INET6"
757 "AF_NETLINK" # e.g. getifaddrs in sieve handling
758 "AF_UNIX"
759 ];
760 RestrictNamespaces = true;
761 RestrictRealtime = true;
762 RestrictSUIDSGID = false; # sets sgid on maildirs
763 RuntimeDirectory = [ "dovecot2" ];
764 SystemCallArchitectures = "native";
765 SystemCallFilter = [
766 "@system-service @resources"
767 "~@privileged"
768 "@chown @setuid capset chroot"
769 ];
770 };
771
772 # When copying sieve scripts preserve the original time stamp
773 # (should be 0) so that the compiled sieve script is newer than
774 # the source file and Dovecot won't try to compile it.
775 preStart = ''
776 rm -rf ${stateDir}/sieve ${stateDir}/imapsieve
777 ''
778 + optionalString (cfg.sieve.scripts != { }) ''
779 mkdir -p ${stateDir}/sieve
780 ${concatStringsSep "\n" (
781 mapAttrsToList (to: from: ''
782 if [ -d '${from}' ]; then
783 mkdir '${stateDir}/sieve/${to}'
784 cp -p "${from}/"*.sieve '${stateDir}/sieve/${to}'
785 else
786 cp -p '${from}' '${stateDir}/sieve/${to}'
787 fi
788 ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/sieve/${to}'
789 '') cfg.sieve.scripts
790 )}
791 chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/sieve'
792 ''
793 + optionalString (cfg.imapsieve.mailbox != [ ]) ''
794 mkdir -p ${stateDir}/imapsieve/{before,after}
795
796 ${concatMapStringsSep "\n" (
797 el:
798 optionalString (el.before != null) ''
799 cp -p ${el.before} ${stateDir}/imapsieve/before/${baseNameOf el.before}
800 ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/imapsieve/before/${baseNameOf el.before}'
801 ''
802 + optionalString (el.after != null) ''
803 cp -p ${el.after} ${stateDir}/imapsieve/after/${baseNameOf el.after}
804 ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/imapsieve/after/${baseNameOf el.after}'
805 ''
806 ) cfg.imapsieve.mailbox}
807
808 ${optionalString (
809 cfg.mailUser != null && cfg.mailGroup != null
810 ) "chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/imapsieve'"}
811 '';
812 };
813
814 environment.systemPackages = [ dovecotPkg ];
815
816 warnings = warnAboutExtraConfigCollisions;
817
818 assertions = [
819 {
820 assertion =
821 (cfg.sslServerCert == null) == (cfg.sslServerKey == null)
822 && (cfg.sslCACert != null -> !(cfg.sslServerCert == null || cfg.sslServerKey == null));
823 message = "dovecot needs both sslServerCert and sslServerKey defined for working crypto";
824 }
825 {
826 assertion = cfg.showPAMFailure -> cfg.enablePAM;
827 message = "dovecot is configured with showPAMFailure while enablePAM is disabled";
828 }
829 {
830 assertion = cfg.sieve.scripts != { } -> (cfg.mailUser != null && cfg.mailGroup != null);
831 message = "dovecot requires mailUser and mailGroup to be set when `sieve.scripts` is set";
832 }
833 {
834 assertion = config.systemd.services ? dovecot2 == false;
835 message = ''
836 Your configuration sets options on the `dovecot2` systemd service. These have no effect until they're migrated to the `dovecot` service.
837 '';
838 }
839 ];
840
841 };
842
843 meta.maintainers = [ lib.maintainers.dblsaiko ];
844}