python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / mail / dovecot.nix
blobf6a167572f7222848822aebe9883e10f848a89e5
1 { options, config, lib, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.services.dovecot2;
7   dovecotPkg = pkgs.dovecot;
9   baseDir = "/run/dovecot2";
10   stateDir = "/var/lib/dovecot";
12   dovecotConf = concatStrings [
13     ''
14       base_dir = ${baseDir}
15       protocols = ${concatStringsSep " " cfg.protocols}
16       sendmail_path = /run/wrappers/bin/sendmail
17       # 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
18       mail_plugins = $mail_plugins ${concatStringsSep " " cfg.mailPlugins.globally.enable}
19     ''
21     (
22       concatStringsSep "\n" (
23         mapAttrsToList (
24           protocol: plugins: ''
25             protocol ${protocol} {
26               mail_plugins = $mail_plugins ${concatStringsSep " " plugins.enable}
27             }
28           ''
29         ) cfg.mailPlugins.perProtocol
30       )
31     )
33     (
34       if cfg.sslServerCert == null then ''
35         ssl = no
36         disable_plaintext_auth = no
37       '' else ''
38         ssl_cert = <${cfg.sslServerCert}
39         ssl_key = <${cfg.sslServerKey}
40         ${optionalString (cfg.sslCACert != null) ("ssl_ca = <" + cfg.sslCACert)}
41         ${optionalString cfg.enableDHE ''ssl_dh = <${config.security.dhparams.params.dovecot2.path}''}
42         disable_plaintext_auth = yes
43       ''
44     )
46     ''
47       default_internal_user = ${cfg.user}
48       default_internal_group = ${cfg.group}
49       ${optionalString (cfg.mailUser != null) "mail_uid = ${cfg.mailUser}"}
50       ${optionalString (cfg.mailGroup != null) "mail_gid = ${cfg.mailGroup}"}
52       mail_location = ${cfg.mailLocation}
54       maildir_copy_with_hardlinks = yes
55       pop3_uidl_format = %08Xv%08Xu
57       auth_mechanisms = plain login
59       service auth {
60         user = root
61       }
62     ''
64     (
65       optionalString cfg.enablePAM ''
66         userdb {
67           driver = passwd
68         }
70         passdb {
71           driver = pam
72           args = ${optionalString cfg.showPAMFailure "failure_show_msg=yes"} dovecot2
73         }
74       ''
75     )
77     (
78       optionalString (cfg.sieveScripts != {}) ''
79         plugin {
80           ${concatStringsSep "\n" (mapAttrsToList (to: from: "sieve_${to} = ${stateDir}/sieve/${to}") cfg.sieveScripts)}
81         }
82       ''
83     )
85     (
86       optionalString (cfg.mailboxes != {}) ''
87         namespace inbox {
88           inbox=yes
89           ${concatStringsSep "\n" (map mailboxConfig (attrValues cfg.mailboxes))}
90         }
91       ''
92     )
94     (
95       optionalString cfg.enableQuota ''
96         service quota-status {
97           executable = ${dovecotPkg}/libexec/dovecot/quota-status -p postfix
98           inet_listener {
99             port = ${cfg.quotaPort}
100           }
101           client_limit = 1
102         }
104         plugin {
105           quota_rule = *:storage=${cfg.quotaGlobalPerUser}
106           quota = count:User quota # per virtual mail user quota
107           quota_status_success = DUNNO
108           quota_status_nouser = DUNNO
109           quota_status_overquota = "552 5.2.2 Mailbox is full"
110           quota_grace = 10%%
111           quota_vsizes = yes
112         }
113       ''
114     )
116     cfg.extraConfig
117   ];
119   modulesDir = pkgs.symlinkJoin {
120     name = "dovecot-modules";
121     paths = map (pkg: "${pkg}/lib/dovecot") ([ dovecotPkg ] ++ map (module: module.override { dovecot = dovecotPkg; }) cfg.modules);
122   };
124   mailboxConfig = mailbox: ''
125     mailbox "${mailbox.name}" {
126       auto = ${toString mailbox.auto}
127   '' + optionalString (mailbox.autoexpunge != null) ''
128     autoexpunge = ${mailbox.autoexpunge}
129   '' + optionalString (mailbox.specialUse != null) ''
130     special_use = \${toString mailbox.specialUse}
131   '' + "}";
133   mailboxes = { name, ... }: {
134     options = {
135       name = mkOption {
136         type = types.strMatching ''[^"]+'';
137         example = "Spam";
138         default = name;
139         readOnly = true;
140         description = lib.mdDoc "The name of the mailbox.";
141       };
142       auto = mkOption {
143         type = types.enum [ "no" "create" "subscribe" ];
144         default = "no";
145         example = "subscribe";
146         description = lib.mdDoc "Whether to automatically create or create and subscribe to the mailbox or not.";
147       };
148       specialUse = mkOption {
149         type = types.nullOr (types.enum [ "All" "Archive" "Drafts" "Flagged" "Junk" "Sent" "Trash" ]);
150         default = null;
151         example = "Junk";
152         description = lib.mdDoc "Null if no special use flag is set. Other than that every use flag mentioned in the RFC is valid.";
153       };
154       autoexpunge = mkOption {
155         type = types.nullOr types.str;
156         default = null;
157         example = "60d";
158         description = lib.mdDoc ''
159           To automatically remove all email from the mailbox which is older than the
160           specified time.
161         '';
162       };
163     };
164   };
167   imports = [
168     (mkRemovedOptionModule [ "services" "dovecot2" "package" ] "")
169   ];
171   options.services.dovecot2 = {
172     enable = mkEnableOption (lib.mdDoc "the dovecot 2.x POP3/IMAP server");
174     enablePop3 = mkEnableOption (lib.mdDoc "starting the POP3 listener (when Dovecot is enabled).");
176     enableImap = mkEnableOption (lib.mdDoc "starting the IMAP listener (when Dovecot is enabled).") // { default = true; };
178     enableLmtp = mkEnableOption (lib.mdDoc "starting the LMTP listener (when Dovecot is enabled).");
180     protocols = mkOption {
181       type = types.listOf types.str;
182       default = [];
183       description = lib.mdDoc "Additional listeners to start when Dovecot is enabled.";
184     };
186     user = mkOption {
187       type = types.str;
188       default = "dovecot2";
189       description = lib.mdDoc "Dovecot user name.";
190     };
192     group = mkOption {
193       type = types.str;
194       default = "dovecot2";
195       description = lib.mdDoc "Dovecot group name.";
196     };
198     extraConfig = mkOption {
199       type = types.lines;
200       default = "";
201       example = "mail_debug = yes";
202       description = lib.mdDoc "Additional entries to put verbatim into Dovecot's config file.";
203     };
205     mailPlugins =
206       let
207         plugins = hint: types.submodule {
208           options = {
209             enable = mkOption {
210               type = types.listOf types.str;
211               default = [];
212               description = lib.mdDoc "mail plugins to enable as a list of strings to append to the ${hint} `$mail_plugins` configuration variable";
213             };
214           };
215         };
216       in
217         mkOption {
218           type = with types; submodule {
219             options = {
220               globally = mkOption {
221                 description = lib.mdDoc "Additional entries to add to the mail_plugins variable for all protocols";
222                 type = plugins "top-level";
223                 example = { enable = [ "virtual" ]; };
224                 default = { enable = []; };
225               };
226               perProtocol = mkOption {
227                 description = lib.mdDoc "Additional entries to add to the mail_plugins variable, per protocol";
228                 type = attrsOf (plugins "corresponding per-protocol");
229                 default = {};
230                 example = { imap = [ "imap_acl" ]; };
231               };
232             };
233           };
234           description = lib.mdDoc "Additional entries to add to the mail_plugins variable, globally and per protocol";
235           example = {
236             globally.enable = [ "acl" ];
237             perProtocol.imap.enable = [ "imap_acl" ];
238           };
239           default = { globally.enable = []; perProtocol = {}; };
240         };
242     configFile = mkOption {
243       type = types.nullOr types.path;
244       default = null;
245       description = lib.mdDoc "Config file used for the whole dovecot configuration.";
246       apply = v: if v != null then v else pkgs.writeText "dovecot.conf" dovecotConf;
247     };
249     mailLocation = mkOption {
250       type = types.str;
251       default = "maildir:/var/spool/mail/%u"; /* Same as inbox, as postfix */
252       example = "maildir:~/mail:INBOX=/var/spool/mail/%u";
253       description = lib.mdDoc ''
254         Location that dovecot will use for mail folders. Dovecot mail_location option.
255       '';
256     };
258     mailUser = mkOption {
259       type = types.nullOr types.str;
260       default = null;
261       description = lib.mdDoc "Default user to store mail for virtual users.";
262     };
264     mailGroup = mkOption {
265       type = types.nullOr types.str;
266       default = null;
267       description = lib.mdDoc "Default group to store mail for virtual users.";
268     };
270     createMailUser = mkEnableOption (lib.mdDoc ''automatically creating the user
271       given in {option}`services.dovecot.user` and the group
272       given in {option}`services.dovecot.group`.'') // { default = true; };
274     modules = mkOption {
275       type = types.listOf types.package;
276       default = [];
277       example = literalExpression "[ pkgs.dovecot_pigeonhole ]";
278       description = lib.mdDoc ''
279         Symlinks the contents of lib/dovecot of every given package into
280         /etc/dovecot/modules. This will make the given modules available
281         if a dovecot package with the module_dir patch applied is being used.
282       '';
283     };
285     sslCACert = mkOption {
286       type = types.nullOr types.str;
287       default = null;
288       description = lib.mdDoc "Path to the server's CA certificate key.";
289     };
291     sslServerCert = mkOption {
292       type = types.nullOr types.str;
293       default = null;
294       description = lib.mdDoc "Path to the server's public key.";
295     };
297     sslServerKey = mkOption {
298       type = types.nullOr types.str;
299       default = null;
300       description = lib.mdDoc "Path to the server's private key.";
301     };
303     enablePAM = mkEnableOption (lib.mdDoc "creating a own Dovecot PAM service and configure PAM user logins.") // { default = true; };
305     enableDHE = mkEnableOption (lib.mdDoc "enable ssl_dh and generation of primes for the key exchange.") // { default = true; };
307     sieveScripts = mkOption {
308       type = types.attrsOf types.path;
309       default = {};
310       description = lib.mdDoc "Sieve scripts to be executed. Key is a sequence, e.g. 'before2', 'after' etc.";
311     };
313     showPAMFailure = mkEnableOption (lib.mdDoc "showing the PAM failure message on authentication error (useful for OTPW).");
315     mailboxes = mkOption {
316       type = with types; coercedTo
317         (listOf unspecified)
318         (list: listToAttrs (map (entry: { name = entry.name; value = removeAttrs entry ["name"]; }) list))
319         (attrsOf (submodule mailboxes));
320       default = {};
321       example = literalExpression ''
322         {
323           Spam = { specialUse = "Junk"; auto = "create"; };
324         }
325       '';
326       description = lib.mdDoc "Configure mailboxes and auto create or subscribe them.";
327     };
329     enableQuota = mkEnableOption (lib.mdDoc "the dovecot quota service.");
331     quotaPort = mkOption {
332       type = types.str;
333       default = "12340";
334       description = lib.mdDoc ''
335         The Port the dovecot quota service binds to.
336         If using postfix, add check_policy_service inet:localhost:12340 to your smtpd_recipient_restrictions in your postfix config.
337       '';
338     };
339     quotaGlobalPerUser = mkOption {
340       type = types.str;
341       default = "100G";
342       example = "10G";
343       description = lib.mdDoc "Quota limit for the user in bytes. Supports suffixes b, k, M, G, T and %.";
344     };
346   };
349   config = mkIf cfg.enable {
350     security.pam.services.dovecot2 = mkIf cfg.enablePAM {};
352     security.dhparams = mkIf (cfg.sslServerCert != null && cfg.enableDHE) {
353       enable = true;
354       params.dovecot2 = {};
355     };
356     services.dovecot2.protocols =
357       optional cfg.enableImap "imap"
358       ++ optional cfg.enablePop3 "pop3"
359       ++ optional cfg.enableLmtp "lmtp";
361     services.dovecot2.mailPlugins = mkIf cfg.enableQuota {
362       globally.enable = [ "quota" ];
363       perProtocol.imap.enable = [ "imap_quota" ];
364     };
366     users.users = {
367       dovenull =
368         {
369           uid = config.ids.uids.dovenull2;
370           description = "Dovecot user for untrusted logins";
371           group = "dovenull";
372         };
373     } // optionalAttrs (cfg.user == "dovecot2") {
374       dovecot2 =
375         {
376           uid = config.ids.uids.dovecot2;
377           description = "Dovecot user";
378           group = cfg.group;
379         };
380     } // optionalAttrs (cfg.createMailUser && cfg.mailUser != null) {
381       ${cfg.mailUser} =
382         { description = "Virtual Mail User"; isSystemUser = true; } // optionalAttrs (cfg.mailGroup != null)
383           { group = cfg.mailGroup; };
384     };
386     users.groups = {
387       dovenull.gid = config.ids.gids.dovenull2;
388     } // optionalAttrs (cfg.group == "dovecot2") {
389       dovecot2.gid = config.ids.gids.dovecot2;
390     } // optionalAttrs (cfg.createMailUser && cfg.mailGroup != null) {
391       ${cfg.mailGroup} = {};
392     };
394     environment.etc."dovecot/modules".source = modulesDir;
395     environment.etc."dovecot/dovecot.conf".source = cfg.configFile;
397     systemd.services.dovecot2 = {
398       description = "Dovecot IMAP/POP3 server";
400       after = [ "network.target" ];
401       wantedBy = [ "multi-user.target" ];
402       restartTriggers = [ cfg.configFile modulesDir ];
404       startLimitIntervalSec = 60;  # 1 min
405       serviceConfig = {
406         Type = "notify";
407         ExecStart = "${dovecotPkg}/sbin/dovecot -F";
408         ExecReload = "${dovecotPkg}/sbin/doveadm reload";
409         Restart = "on-failure";
410         RestartSec = "1s";
411         RuntimeDirectory = [ "dovecot2" ];
412       };
414       # When copying sieve scripts preserve the original time stamp
415       # (should be 0) so that the compiled sieve script is newer than
416       # the source file and Dovecot won't try to compile it.
417       preStart = ''
418         rm -rf ${stateDir}/sieve
419       '' + optionalString (cfg.sieveScripts != {}) ''
420         mkdir -p ${stateDir}/sieve
421         ${concatStringsSep "\n" (
422         mapAttrsToList (
423           to: from: ''
424             if [ -d '${from}' ]; then
425               mkdir '${stateDir}/sieve/${to}'
426               cp -p "${from}/"*.sieve '${stateDir}/sieve/${to}'
427             else
428               cp -p '${from}' '${stateDir}/sieve/${to}'
429             fi
430             ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/sieve/${to}'
431           ''
432         ) cfg.sieveScripts
433       )}
434         chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/sieve'
435       '';
436     };
438     environment.systemPackages = [ dovecotPkg ];
440     warnings = mkIf (any isList options.services.dovecot2.mailboxes.definitions) [
441       "Declaring `services.dovecot2.mailboxes' as a list is deprecated and will break eval in 21.05! See the release notes for more info for migration."
442     ];
444     assertions = [
445       {
446         assertion = (cfg.sslServerCert == null) == (cfg.sslServerKey == null)
447         && (cfg.sslCACert != null -> !(cfg.sslServerCert == null || cfg.sslServerKey == null));
448         message = "dovecot needs both sslServerCert and sslServerKey defined for working crypto";
449       }
450       {
451         assertion = cfg.showPAMFailure -> cfg.enablePAM;
452         message = "dovecot is configured with showPAMFailure while enablePAM is disabled";
453       }
454       {
455         assertion = cfg.sieveScripts != {} -> (cfg.mailUser != null && cfg.mailGroup != null);
456         message = "dovecot requires mailUser and mailGroup to be set when sieveScripts is set";
457       }
458     ];
460   };