at 18.09-beta 12 kB view raw
1{ 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 '' 18 19 (if isNull cfg.sslServerCert then '' 20 ssl = no 21 disable_plaintext_auth = no 22 '' else '' 23 ssl_cert = <${cfg.sslServerCert} 24 ssl_key = <${cfg.sslServerKey} 25 ${optionalString (!(isNull cfg.sslCACert)) ("ssl_ca = <" + cfg.sslCACert)} 26 ssl_dh = <${config.security.dhparams.params.dovecot2.path} 27 disable_plaintext_auth = yes 28 '') 29 30 '' 31 default_internal_user = ${cfg.user} 32 default_internal_group = ${cfg.group} 33 ${optionalString (cfg.mailUser != null) "mail_uid = ${cfg.mailUser}"} 34 ${optionalString (cfg.mailGroup != null) "mail_gid = ${cfg.mailGroup}"} 35 36 mail_location = ${cfg.mailLocation} 37 38 maildir_copy_with_hardlinks = yes 39 pop3_uidl_format = %08Xv%08Xu 40 41 auth_mechanisms = plain login 42 43 service auth { 44 user = root 45 } 46 '' 47 48 (optionalString cfg.enablePAM '' 49 userdb { 50 driver = passwd 51 } 52 53 passdb { 54 driver = pam 55 args = ${optionalString cfg.showPAMFailure "failure_show_msg=yes"} dovecot2 56 } 57 '') 58 59 (optionalString (cfg.sieveScripts != {}) '' 60 plugin { 61 ${concatStringsSep "\n" (mapAttrsToList (to: from: "sieve_${to} = ${stateDir}/sieve/${to}") cfg.sieveScripts)} 62 } 63 '') 64 65 (optionalString (cfg.mailboxes != []) '' 66 protocol imap { 67 namespace inbox { 68 inbox=yes 69 ${concatStringsSep "\n" (map mailboxConfig cfg.mailboxes)} 70 } 71 } 72 '') 73 74 (optionalString cfg.enableQuota '' 75 mail_plugins = $mail_plugins quota 76 service quota-status { 77 executable = ${dovecotPkg}/libexec/dovecot/quota-status -p postfix 78 inet_listener { 79 port = ${cfg.quotaPort} 80 } 81 client_limit = 1 82 } 83 84 protocol imap { 85 mail_plugins = $mail_plugins imap_quota 86 } 87 88 plugin { 89 quota_rule = *:storage=${cfg.quotaGlobalPerUser} 90 quota = maildir:User quota # per virtual mail user quota # BUG/FIXME broken, we couldn't get this working 91 quota_status_success = DUNNO 92 quota_status_nouser = DUNNO 93 quota_status_overquota = "552 5.2.2 Mailbox is full" 94 quota_grace = 10%% 95 } 96 '') 97 98 cfg.extraConfig 99 ]; 100 101 modulesDir = pkgs.symlinkJoin { 102 name = "dovecot-modules"; 103 paths = map (pkg: "${pkg}/lib/dovecot") ([ dovecotPkg ] ++ map (module: module.override { dovecot = dovecotPkg; }) cfg.modules); 104 }; 105 106 mailboxConfig = mailbox: '' 107 mailbox "${mailbox.name}" { 108 auto = ${toString mailbox.auto} 109 '' + optionalString (mailbox.specialUse != null) '' 110 special_use = \${toString mailbox.specialUse} 111 '' + "}"; 112 113 mailboxes = { ... }: { 114 options = { 115 name = mkOption { 116 type = types.strMatching ''[^"]+''; 117 example = "Spam"; 118 description = "The name of the mailbox."; 119 }; 120 auto = mkOption { 121 type = types.enum [ "no" "create" "subscribe" ]; 122 default = "no"; 123 example = "subscribe"; 124 description = "Whether to automatically create or create and subscribe to the mailbox or not."; 125 }; 126 specialUse = mkOption { 127 type = types.nullOr (types.enum [ "All" "Archive" "Drafts" "Flagged" "Junk" "Sent" "Trash" ]); 128 default = null; 129 example = "Junk"; 130 description = "Null if no special use flag is set. Other than that every use flag mentioned in the RFC is valid."; 131 }; 132 }; 133 }; 134in 135{ 136 137 options.services.dovecot2 = { 138 enable = mkEnableOption "Dovecot 2.x POP3/IMAP server"; 139 140 enablePop3 = mkOption { 141 type = types.bool; 142 default = false; 143 description = "Start the POP3 listener (when Dovecot is enabled)."; 144 }; 145 146 enableImap = mkOption { 147 type = types.bool; 148 default = true; 149 description = "Start the IMAP listener (when Dovecot is enabled)."; 150 }; 151 152 enableLmtp = mkOption { 153 type = types.bool; 154 default = false; 155 description = "Start the LMTP listener (when Dovecot is enabled)."; 156 }; 157 158 protocols = mkOption { 159 type = types.listOf types.str; 160 default = [ ]; 161 description = "Additional listeners to start when Dovecot is enabled."; 162 }; 163 164 user = mkOption { 165 type = types.str; 166 default = "dovecot2"; 167 description = "Dovecot user name."; 168 }; 169 170 group = mkOption { 171 type = types.str; 172 default = "dovecot2"; 173 description = "Dovecot group name."; 174 }; 175 176 extraConfig = mkOption { 177 type = types.lines; 178 default = ""; 179 example = "mail_debug = yes"; 180 description = "Additional entries to put verbatim into Dovecot's config file."; 181 }; 182 183 configFile = mkOption { 184 type = types.nullOr types.str; 185 default = null; 186 description = "Config file used for the whole dovecot configuration."; 187 apply = v: if v != null then v else pkgs.writeText "dovecot.conf" dovecotConf; 188 }; 189 190 mailLocation = mkOption { 191 type = types.str; 192 default = "maildir:/var/spool/mail/%u"; /* Same as inbox, as postfix */ 193 example = "maildir:~/mail:INBOX=/var/spool/mail/%u"; 194 description = '' 195 Location that dovecot will use for mail folders. Dovecot mail_location option. 196 ''; 197 }; 198 199 mailUser = mkOption { 200 type = types.nullOr types.str; 201 default = null; 202 description = "Default user to store mail for virtual users."; 203 }; 204 205 mailGroup = mkOption { 206 type = types.nullOr types.str; 207 default = null; 208 description = "Default group to store mail for virtual users."; 209 }; 210 211 createMailUser = mkOption { 212 type = types.bool; 213 default = true; 214 description = ''Whether to automatically create the user 215 given in <option>services.dovecot.user</option> and the group 216 given in <option>services.dovecot.group</option>.''; 217 }; 218 219 modules = mkOption { 220 type = types.listOf types.package; 221 default = []; 222 example = literalExample "[ pkgs.dovecot_pigeonhole ]"; 223 description = '' 224 Symlinks the contents of lib/dovecot of every given package into 225 /etc/dovecot/modules. This will make the given modules available 226 if a dovecot package with the module_dir patch applied is being used. 227 ''; 228 }; 229 230 sslCACert = mkOption { 231 type = types.nullOr types.str; 232 default = null; 233 description = "Path to the server's CA certificate key."; 234 }; 235 236 sslServerCert = mkOption { 237 type = types.nullOr types.str; 238 default = null; 239 description = "Path to the server's public key."; 240 }; 241 242 sslServerKey = mkOption { 243 type = types.nullOr types.str; 244 default = null; 245 description = "Path to the server's private key."; 246 }; 247 248 enablePAM = mkOption { 249 type = types.bool; 250 default = true; 251 description = "Whether to create a own Dovecot PAM service and configure PAM user logins."; 252 }; 253 254 sieveScripts = mkOption { 255 type = types.attrsOf types.path; 256 default = {}; 257 description = "Sieve scripts to be executed. Key is a sequence, e.g. 'before2', 'after' etc."; 258 }; 259 260 showPAMFailure = mkOption { 261 type = types.bool; 262 default = false; 263 description = "Show the PAM failure message on authentication error (useful for OTPW)."; 264 }; 265 266 mailboxes = mkOption { 267 type = types.listOf (types.submodule mailboxes); 268 default = []; 269 example = [ { name = "Spam"; specialUse = "Junk"; auto = "create"; } ]; 270 description = "Configure mailboxes and auto create or subscribe them."; 271 }; 272 273 enableQuota = mkOption { 274 type = types.bool; 275 default = false; 276 example = true; 277 description = "Whether to enable the dovecot quota service."; 278 }; 279 280 quotaPort = mkOption { 281 type = types.str; 282 default = "12340"; 283 description = '' 284 The Port the dovecot quota service binds to. 285 If using postfix, add check_policy_service inet:localhost:12340 to your smtpd_recipient_restrictions in your postfix config. 286 ''; 287 }; 288 quotaGlobalPerUser = mkOption { 289 type = types.str; 290 default = "100G"; 291 example = "10G"; 292 description = "Quota limit for the user in bytes. Supports suffixes b, k, M, G, T and %."; 293 }; 294 295 }; 296 297 298 config = mkIf cfg.enable { 299 security.pam.services.dovecot2 = mkIf cfg.enablePAM {}; 300 301 security.dhparams = mkIf (! isNull cfg.sslServerCert) { 302 enable = true; 303 params.dovecot2 = {}; 304 }; 305 services.dovecot2.protocols = 306 optional cfg.enableImap "imap" 307 ++ optional cfg.enablePop3 "pop3" 308 ++ optional cfg.enableLmtp "lmtp"; 309 310 users.users = [ 311 { name = "dovenull"; 312 uid = config.ids.uids.dovenull2; 313 description = "Dovecot user for untrusted logins"; 314 group = cfg.group; 315 } 316 ] ++ optional (cfg.user == "dovecot2") 317 { name = "dovecot2"; 318 uid = config.ids.uids.dovecot2; 319 description = "Dovecot user"; 320 group = cfg.group; 321 } 322 ++ optional (cfg.createMailUser && cfg.mailUser != null) 323 ({ name = cfg.mailUser; 324 description = "Virtual Mail User"; 325 } // optionalAttrs (cfg.mailGroup != null) { 326 group = cfg.mailGroup; 327 }); 328 329 users.groups = optional (cfg.group == "dovecot2") 330 { name = "dovecot2"; 331 gid = config.ids.gids.dovecot2; 332 } 333 ++ optional (cfg.createMailUser && cfg.mailGroup != null) 334 { name = cfg.mailGroup; 335 }; 336 337 environment.etc."dovecot/modules".source = modulesDir; 338 environment.etc."dovecot/dovecot.conf".source = cfg.configFile; 339 340 systemd.services.dovecot2 = { 341 description = "Dovecot IMAP/POP3 server"; 342 343 after = [ "keys.target" "network.target" ]; 344 wants = [ "keys.target" ]; 345 wantedBy = [ "multi-user.target" ]; 346 restartTriggers = [ cfg.configFile ]; 347 348 serviceConfig = { 349 ExecStart = "${dovecotPkg}/sbin/dovecot -F"; 350 ExecReload = "${dovecotPkg}/sbin/doveadm reload"; 351 Restart = "on-failure"; 352 RestartSec = "1s"; 353 StartLimitInterval = "1min"; 354 RuntimeDirectory = [ "dovecot2" ]; 355 }; 356 357 # When copying sieve scripts preserve the original time stamp 358 # (should be 0) so that the compiled sieve script is newer than 359 # the source file and Dovecot won't try to compile it. 360 preStart = '' 361 rm -rf ${stateDir}/sieve 362 '' + optionalString (cfg.sieveScripts != {}) '' 363 mkdir -p ${stateDir}/sieve 364 ${concatStringsSep "\n" (mapAttrsToList (to: from: '' 365 if [ -d '${from}' ]; then 366 mkdir '${stateDir}/sieve/${to}' 367 cp -p "${from}/"*.sieve '${stateDir}/sieve/${to}' 368 else 369 cp -p '${from}' '${stateDir}/sieve/${to}' 370 fi 371 ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/sieve/${to}' 372 '') cfg.sieveScripts)} 373 chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/sieve' 374 ''; 375 }; 376 377 environment.systemPackages = [ dovecotPkg ]; 378 379 assertions = [ 380 { assertion = intersectLists cfg.protocols [ "pop3" "imap" ] != []; 381 message = "dovecot needs at least one of the IMAP or POP3 listeners enabled"; 382 } 383 { assertion = isNull cfg.sslServerCert == isNull cfg.sslServerKey 384 && (!(isNull cfg.sslCACert) -> !(isNull cfg.sslServerCert || isNull cfg.sslServerKey)); 385 message = "dovecot needs both sslServerCert and sslServerKey defined for working crypto"; 386 } 387 { assertion = cfg.showPAMFailure -> cfg.enablePAM; 388 message = "dovecot is configured with showPAMFailure while enablePAM is disabled"; 389 } 390 { assertion = (cfg.sieveScripts != {}) -> ((cfg.mailUser != null) && (cfg.mailGroup != null)); 391 message = "dovecot requires mailUser and mailGroup to be set when sieveScripts is set"; 392 } 393 ]; 394 395 }; 396 397}