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