at 21.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 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 = maildir:User quota # per virtual mail user quota # BUG/FIXME broken, we couldn't get this working 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 } 112 '' 113 ) 114 115 cfg.extraConfig 116 ]; 117 118 modulesDir = pkgs.symlinkJoin { 119 name = "dovecot-modules"; 120 paths = map (pkg: "${pkg}/lib/dovecot") ([ dovecotPkg ] ++ map (module: module.override { dovecot = dovecotPkg; }) cfg.modules); 121 }; 122 123 mailboxConfig = mailbox: '' 124 mailbox "${mailbox.name}" { 125 auto = ${toString mailbox.auto} 126 '' + optionalString (mailbox.autoexpunge != null) '' 127 autoexpunge = ${mailbox.autoexpunge} 128 '' + optionalString (mailbox.specialUse != null) '' 129 special_use = \${toString mailbox.specialUse} 130 '' + "}"; 131 132 mailboxes = { name, ... }: { 133 options = { 134 name = mkOption { 135 type = types.strMatching ''[^"]+''; 136 example = "Spam"; 137 default = name; 138 readOnly = true; 139 description = "The name of the mailbox."; 140 }; 141 auto = mkOption { 142 type = types.enum [ "no" "create" "subscribe" ]; 143 default = "no"; 144 example = "subscribe"; 145 description = "Whether to automatically create or create and subscribe to the mailbox or not."; 146 }; 147 specialUse = mkOption { 148 type = types.nullOr (types.enum [ "All" "Archive" "Drafts" "Flagged" "Junk" "Sent" "Trash" ]); 149 default = null; 150 example = "Junk"; 151 description = "Null if no special use flag is set. Other than that every use flag mentioned in the RFC is valid."; 152 }; 153 autoexpunge = mkOption { 154 type = types.nullOr types.str; 155 default = null; 156 example = "60d"; 157 description = '' 158 To automatically remove all email from the mailbox which is older than the 159 specified time. 160 ''; 161 }; 162 }; 163 }; 164in 165{ 166 imports = [ 167 (mkRemovedOptionModule [ "services" "dovecot2" "package" ] "") 168 ]; 169 170 options.services.dovecot2 = { 171 enable = mkEnableOption "Dovecot 2.x POP3/IMAP server"; 172 173 enablePop3 = mkOption { 174 type = types.bool; 175 default = false; 176 description = "Start the POP3 listener (when Dovecot is enabled)."; 177 }; 178 179 enableImap = mkOption { 180 type = types.bool; 181 default = true; 182 description = "Start the IMAP listener (when Dovecot is enabled)."; 183 }; 184 185 enableLmtp = mkOption { 186 type = types.bool; 187 default = false; 188 description = "Start the LMTP listener (when Dovecot is enabled)."; 189 }; 190 191 protocols = mkOption { 192 type = types.listOf types.str; 193 default = []; 194 description = "Additional listeners to start when Dovecot is enabled."; 195 }; 196 197 user = mkOption { 198 type = types.str; 199 default = "dovecot2"; 200 description = "Dovecot user name."; 201 }; 202 203 group = mkOption { 204 type = types.str; 205 default = "dovecot2"; 206 description = "Dovecot group name."; 207 }; 208 209 extraConfig = mkOption { 210 type = types.lines; 211 default = ""; 212 example = "mail_debug = yes"; 213 description = "Additional entries to put verbatim into Dovecot's config file."; 214 }; 215 216 mailPlugins = 217 let 218 plugins = hint: types.submodule { 219 options = { 220 enable = mkOption { 221 type = types.listOf types.str; 222 default = []; 223 description = "mail plugins to enable as a list of strings to append to the ${hint} <literal>$mail_plugins</literal> configuration variable"; 224 }; 225 }; 226 }; 227 in 228 mkOption { 229 type = with types; submodule { 230 options = { 231 globally = mkOption { 232 description = "Additional entries to add to the mail_plugins variable for all protocols"; 233 type = plugins "top-level"; 234 example = { enable = [ "virtual" ]; }; 235 default = { enable = []; }; 236 }; 237 perProtocol = mkOption { 238 description = "Additional entries to add to the mail_plugins variable, per protocol"; 239 type = attrsOf (plugins "corresponding per-protocol"); 240 default = {}; 241 example = { imap = [ "imap_acl" ]; }; 242 }; 243 }; 244 }; 245 description = "Additional entries to add to the mail_plugins variable, globally and per protocol"; 246 example = { 247 globally.enable = [ "acl" ]; 248 perProtocol.imap.enable = [ "imap_acl" ]; 249 }; 250 default = { globally.enable = []; perProtocol = {}; }; 251 }; 252 253 configFile = mkOption { 254 type = types.nullOr types.path; 255 default = null; 256 description = "Config file used for the whole dovecot configuration."; 257 apply = v: if v != null then v else pkgs.writeText "dovecot.conf" dovecotConf; 258 }; 259 260 mailLocation = mkOption { 261 type = types.str; 262 default = "maildir:/var/spool/mail/%u"; /* Same as inbox, as postfix */ 263 example = "maildir:~/mail:INBOX=/var/spool/mail/%u"; 264 description = '' 265 Location that dovecot will use for mail folders. Dovecot mail_location option. 266 ''; 267 }; 268 269 mailUser = mkOption { 270 type = types.nullOr types.str; 271 default = null; 272 description = "Default user to store mail for virtual users."; 273 }; 274 275 mailGroup = mkOption { 276 type = types.nullOr types.str; 277 default = null; 278 description = "Default group to store mail for virtual users."; 279 }; 280 281 createMailUser = mkOption { 282 type = types.bool; 283 default = true; 284 description = ''Whether to automatically create the user 285 given in <option>services.dovecot.user</option> and the group 286 given in <option>services.dovecot.group</option>.''; 287 }; 288 289 modules = mkOption { 290 type = types.listOf types.package; 291 default = []; 292 example = literalExample "[ pkgs.dovecot_pigeonhole ]"; 293 description = '' 294 Symlinks the contents of lib/dovecot of every given package into 295 /etc/dovecot/modules. This will make the given modules available 296 if a dovecot package with the module_dir patch applied is being used. 297 ''; 298 }; 299 300 sslCACert = mkOption { 301 type = types.nullOr types.str; 302 default = null; 303 description = "Path to the server's CA certificate key."; 304 }; 305 306 sslServerCert = mkOption { 307 type = types.nullOr types.str; 308 default = null; 309 description = "Path to the server's public key."; 310 }; 311 312 sslServerKey = mkOption { 313 type = types.nullOr types.str; 314 default = null; 315 description = "Path to the server's private key."; 316 }; 317 318 enablePAM = mkOption { 319 type = types.bool; 320 default = true; 321 description = "Whether to create a own Dovecot PAM service and configure PAM user logins."; 322 }; 323 324 sieveScripts = mkOption { 325 type = types.attrsOf types.path; 326 default = {}; 327 description = "Sieve scripts to be executed. Key is a sequence, e.g. 'before2', 'after' etc."; 328 }; 329 330 showPAMFailure = mkOption { 331 type = types.bool; 332 default = false; 333 description = "Show the PAM failure message on authentication error (useful for OTPW)."; 334 }; 335 336 mailboxes = mkOption { 337 type = with types; coercedTo 338 (listOf unspecified) 339 (list: listToAttrs (map (entry: { name = entry.name; value = removeAttrs entry ["name"]; }) list)) 340 (attrsOf (submodule mailboxes)); 341 default = {}; 342 example = literalExample '' 343 { 344 Spam = { specialUse = "Junk"; auto = "create"; }; 345 } 346 ''; 347 description = "Configure mailboxes and auto create or subscribe them."; 348 }; 349 350 enableQuota = mkOption { 351 type = types.bool; 352 default = false; 353 example = true; 354 description = "Whether to enable the dovecot quota service."; 355 }; 356 357 quotaPort = mkOption { 358 type = types.str; 359 default = "12340"; 360 description = '' 361 The Port the dovecot quota service binds to. 362 If using postfix, add check_policy_service inet:localhost:12340 to your smtpd_recipient_restrictions in your postfix config. 363 ''; 364 }; 365 quotaGlobalPerUser = mkOption { 366 type = types.str; 367 default = "100G"; 368 example = "10G"; 369 description = "Quota limit for the user in bytes. Supports suffixes b, k, M, G, T and %."; 370 }; 371 372 }; 373 374 375 config = mkIf cfg.enable { 376 security.pam.services.dovecot2 = mkIf cfg.enablePAM {}; 377 378 security.dhparams = mkIf (cfg.sslServerCert != null) { 379 enable = true; 380 params.dovecot2 = {}; 381 }; 382 services.dovecot2.protocols = 383 optional cfg.enableImap "imap" 384 ++ optional cfg.enablePop3 "pop3" 385 ++ optional cfg.enableLmtp "lmtp"; 386 387 services.dovecot2.mailPlugins = mkIf cfg.enableQuota { 388 globally.enable = [ "quota" ]; 389 perProtocol.imap.enable = [ "imap_quota" ]; 390 }; 391 392 users.users = { 393 dovenull = 394 { 395 uid = config.ids.uids.dovenull2; 396 description = "Dovecot user for untrusted logins"; 397 group = "dovenull"; 398 }; 399 } // optionalAttrs (cfg.user == "dovecot2") { 400 dovecot2 = 401 { 402 uid = config.ids.uids.dovecot2; 403 description = "Dovecot user"; 404 group = cfg.group; 405 }; 406 } // optionalAttrs (cfg.createMailUser && cfg.mailUser != null) { 407 ${cfg.mailUser} = 408 { description = "Virtual Mail User"; isSystemUser = true; } // optionalAttrs (cfg.mailGroup != null) 409 { group = cfg.mailGroup; }; 410 }; 411 412 users.groups = { 413 dovenull.gid = config.ids.gids.dovenull2; 414 } // optionalAttrs (cfg.group == "dovecot2") { 415 dovecot2.gid = config.ids.gids.dovecot2; 416 } // optionalAttrs (cfg.createMailUser && cfg.mailGroup != null) { 417 ${cfg.mailGroup} = {}; 418 }; 419 420 environment.etc."dovecot/modules".source = modulesDir; 421 environment.etc."dovecot/dovecot.conf".source = cfg.configFile; 422 423 systemd.services.dovecot2 = { 424 description = "Dovecot IMAP/POP3 server"; 425 426 after = [ "network.target" ]; 427 wantedBy = [ "multi-user.target" ]; 428 restartTriggers = [ cfg.configFile modulesDir ]; 429 430 startLimitIntervalSec = 60; # 1 min 431 serviceConfig = { 432 ExecStart = "${dovecotPkg}/sbin/dovecot -F"; 433 ExecReload = "${dovecotPkg}/sbin/doveadm reload"; 434 Restart = "on-failure"; 435 RestartSec = "1s"; 436 RuntimeDirectory = [ "dovecot2" ]; 437 }; 438 439 # When copying sieve scripts preserve the original time stamp 440 # (should be 0) so that the compiled sieve script is newer than 441 # the source file and Dovecot won't try to compile it. 442 preStart = '' 443 rm -rf ${stateDir}/sieve 444 '' + optionalString (cfg.sieveScripts != {}) '' 445 mkdir -p ${stateDir}/sieve 446 ${concatStringsSep "\n" ( 447 mapAttrsToList ( 448 to: from: '' 449 if [ -d '${from}' ]; then 450 mkdir '${stateDir}/sieve/${to}' 451 cp -p "${from}/"*.sieve '${stateDir}/sieve/${to}' 452 else 453 cp -p '${from}' '${stateDir}/sieve/${to}' 454 fi 455 ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/sieve/${to}' 456 '' 457 ) cfg.sieveScripts 458 )} 459 chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/sieve' 460 ''; 461 }; 462 463 environment.systemPackages = [ dovecotPkg ]; 464 465 warnings = mkIf (any isList options.services.dovecot2.mailboxes.definitions) [ 466 "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." 467 ]; 468 469 assertions = [ 470 { 471 assertion = intersectLists cfg.protocols [ "pop3" "imap" ] != []; 472 message = "dovecot needs at least one of the IMAP or POP3 listeners enabled"; 473 } 474 { 475 assertion = (cfg.sslServerCert == null) == (cfg.sslServerKey == null) 476 && (cfg.sslCACert != null -> !(cfg.sslServerCert == null || cfg.sslServerKey == null)); 477 message = "dovecot needs both sslServerCert and sslServerKey defined for working crypto"; 478 } 479 { 480 assertion = cfg.showPAMFailure -> cfg.enablePAM; 481 message = "dovecot is configured with showPAMFailure while enablePAM is disabled"; 482 } 483 { 484 assertion = cfg.sieveScripts != {} -> (cfg.mailUser != null && cfg.mailGroup != null); 485 message = "dovecot requires mailUser and mailGroup to be set when sieveScripts is set"; 486 } 487 ]; 488 489 }; 490 491}