at 23.11-pre 15 kB view raw
1{ options, config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.dovecot2; 7 dovecotPkg = pkgs.dovecot; 8 9 baseDir = "/run/dovecot2"; 10 stateDir = "/var/lib/dovecot"; 11 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 '' 20 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 ) 32 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 ) 45 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}"} 51 52 mail_location = ${cfg.mailLocation} 53 54 maildir_copy_with_hardlinks = yes 55 pop3_uidl_format = %08Xv%08Xu 56 57 auth_mechanisms = plain login 58 59 service auth { 60 user = root 61 } 62 '' 63 64 ( 65 optionalString cfg.enablePAM '' 66 userdb { 67 driver = passwd 68 } 69 70 passdb { 71 driver = pam 72 args = ${optionalString cfg.showPAMFailure "failure_show_msg=yes"} dovecot2 73 } 74 '' 75 ) 76 77 ( 78 optionalString (cfg.sieveScripts != {}) '' 79 plugin { 80 ${concatStringsSep "\n" (mapAttrsToList (to: from: "sieve_${to} = ${stateDir}/sieve/${to}") cfg.sieveScripts)} 81 } 82 '' 83 ) 84 85 ( 86 optionalString (cfg.mailboxes != {}) '' 87 namespace inbox { 88 inbox=yes 89 ${concatStringsSep "\n" (map mailboxConfig (attrValues cfg.mailboxes))} 90 } 91 '' 92 ) 93 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 } 103 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 ) 115 116 cfg.extraConfig 117 ]; 118 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 }; 123 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 '' + "}"; 132 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 }; 165in 166{ 167 imports = [ 168 (mkRemovedOptionModule [ "services" "dovecot2" "package" ] "") 169 ]; 170 171 options.services.dovecot2 = { 172 enable = mkEnableOption (lib.mdDoc "the dovecot 2.x POP3/IMAP server"); 173 174 enablePop3 = mkEnableOption (lib.mdDoc "starting the POP3 listener (when Dovecot is enabled)"); 175 176 enableImap = mkEnableOption (lib.mdDoc "starting the IMAP listener (when Dovecot is enabled)") // { default = true; }; 177 178 enableLmtp = mkEnableOption (lib.mdDoc "starting the LMTP listener (when Dovecot is enabled)"); 179 180 protocols = mkOption { 181 type = types.listOf types.str; 182 default = []; 183 description = lib.mdDoc "Additional listeners to start when Dovecot is enabled."; 184 }; 185 186 user = mkOption { 187 type = types.str; 188 default = "dovecot2"; 189 description = lib.mdDoc "Dovecot user name."; 190 }; 191 192 group = mkOption { 193 type = types.str; 194 default = "dovecot2"; 195 description = lib.mdDoc "Dovecot group name."; 196 }; 197 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 }; 204 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 }; 241 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 }; 248 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 }; 257 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 }; 263 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 }; 269 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; }; 273 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 }; 284 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 }; 290 291 sslServerCert = mkOption { 292 type = types.nullOr types.str; 293 default = null; 294 description = lib.mdDoc "Path to the server's public key."; 295 }; 296 297 sslServerKey = mkOption { 298 type = types.nullOr types.str; 299 default = null; 300 description = lib.mdDoc "Path to the server's private key."; 301 }; 302 303 enablePAM = mkEnableOption (lib.mdDoc "creating a own Dovecot PAM service and configure PAM user logins") // { default = true; }; 304 305 enableDHE = mkEnableOption (lib.mdDoc "enable ssl_dh and generation of primes for the key exchange") // { default = true; }; 306 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 }; 312 313 showPAMFailure = mkEnableOption (lib.mdDoc "showing the PAM failure message on authentication error (useful for OTPW)"); 314 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 }; 328 329 enableQuota = mkEnableOption (lib.mdDoc "the dovecot quota service"); 330 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 }; 345 346 }; 347 348 349 config = mkIf cfg.enable { 350 security.pam.services.dovecot2 = mkIf cfg.enablePAM {}; 351 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"; 360 361 services.dovecot2.mailPlugins = mkIf cfg.enableQuota { 362 globally.enable = [ "quota" ]; 363 perProtocol.imap.enable = [ "imap_quota" ]; 364 }; 365 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 }; 385 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 }; 393 394 environment.etc."dovecot/modules".source = modulesDir; 395 environment.etc."dovecot/dovecot.conf".source = cfg.configFile; 396 397 systemd.services.dovecot2 = { 398 description = "Dovecot IMAP/POP3 server"; 399 400 after = [ "network.target" ]; 401 wantedBy = [ "multi-user.target" ]; 402 restartTriggers = [ cfg.configFile modulesDir ]; 403 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 }; 413 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 }; 437 438 environment.systemPackages = [ dovecotPkg ]; 439 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 ]; 443 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 ]; 459 460 }; 461 462}