1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.dovecot2; 7 dovecotPkg = cfg.package; 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 = /var/setuid-wrappers/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 disable_plaintext_auth = yes 27 '') 28 29 '' 30 default_internal_user = ${cfg.user} 31 ${optionalString (cfg.mailUser != null) "mail_uid = ${cfg.mailUser}"} 32 ${optionalString (cfg.mailGroup != null) "mail_gid = ${cfg.mailGroup}"} 33 34 mail_location = ${cfg.mailLocation} 35 36 maildir_copy_with_hardlinks = yes 37 pop3_uidl_format = %08Xv%08Xu 38 39 auth_mechanisms = plain login 40 41 service auth { 42 user = root 43 } 44 '' 45 46 (optionalString cfg.enablePAM '' 47 userdb { 48 driver = passwd 49 } 50 51 passdb { 52 driver = pam 53 args = ${optionalString cfg.showPAMFailure "failure_show_msg=yes"} dovecot2 54 } 55 '') 56 57 (optionalString (cfg.sieveScripts != {}) '' 58 plugin { 59 ${concatStringsSep "\n" (mapAttrsToList (to: from: "sieve_${to} = ${stateDir}/sieve/${to}") cfg.sieveScripts)} 60 } 61 '') 62 63 cfg.extraConfig 64 ]; 65 66 modulesDir = pkgs.symlinkJoin "dovecot-modules" 67 (map (pkg: "${pkg}/lib/dovecot") ([ dovecotPkg ] ++ map (module: module.override { dovecot = dovecotPkg; }) cfg.modules)); 68 69in 70{ 71 72 options.services.dovecot2 = { 73 enable = mkEnableOption "Dovecot 2.x POP3/IMAP server"; 74 75 enablePop3 = mkOption { 76 type = types.bool; 77 default = true; 78 description = "Start the POP3 listener (when Dovecot is enabled)."; 79 }; 80 81 enableImap = mkOption { 82 type = types.bool; 83 default = true; 84 description = "Start the IMAP listener (when Dovecot is enabled)."; 85 }; 86 87 enableLmtp = mkOption { 88 type = types.bool; 89 default = false; 90 description = "Start the LMTP listener (when Dovecot is enabled)."; 91 }; 92 93 protocols = mkOption { 94 type = types.listOf types.str; 95 default = [ ]; 96 description = "Additional listeners to start when Dovecot is enabled."; 97 }; 98 99 package = mkOption { 100 type = types.package; 101 default = pkgs.dovecot22; 102 defaultText = "pkgs.dovecot22"; 103 description = "Dovecot package to use."; 104 }; 105 106 user = mkOption { 107 type = types.str; 108 default = "dovecot2"; 109 description = "Dovecot user name."; 110 }; 111 112 group = mkOption { 113 type = types.str; 114 default = "dovecot2"; 115 description = "Dovecot group name."; 116 }; 117 118 extraConfig = mkOption { 119 type = types.str; 120 default = ""; 121 example = "mail_debug = yes"; 122 description = "Additional entries to put verbatim into Dovecot's config file."; 123 }; 124 125 configFile = mkOption { 126 type = types.nullOr types.str; 127 default = null; 128 description = "Config file used for the whole dovecot configuration."; 129 apply = v: if v != null then v else pkgs.writeText "dovecot.conf" dovecotConf; 130 }; 131 132 mailLocation = mkOption { 133 type = types.str; 134 default = "maildir:/var/spool/mail/%u"; /* Same as inbox, as postfix */ 135 example = "maildir:~/mail:INBOX=/var/spool/mail/%u"; 136 description = '' 137 Location that dovecot will use for mail folders. Dovecot mail_location option. 138 ''; 139 }; 140 141 mailUser = mkOption { 142 type = types.nullOr types.str; 143 default = null; 144 description = "Default user to store mail for virtual users."; 145 }; 146 147 mailGroup = mkOption { 148 type = types.nullOr types.str; 149 default = null; 150 description = "Default group to store mail for virtual users."; 151 }; 152 153 modules = mkOption { 154 type = types.listOf types.package; 155 default = []; 156 example = literalExample "[ pkgs.dovecot_pigeonhole ]"; 157 description = '' 158 Symlinks the contents of lib/dovecot of every given package into 159 /etc/dovecot/modules. This will make the given modules available 160 if a dovecot package with the module_dir patch applied (like 161 pkgs.dovecot22, the default) is being used. 162 ''; 163 }; 164 165 sslCACert = mkOption { 166 type = types.nullOr types.str; 167 default = null; 168 description = "Path to the server's CA certificate key."; 169 }; 170 171 sslServerCert = mkOption { 172 type = types.nullOr types.str; 173 default = null; 174 description = "Path to the server's public key."; 175 }; 176 177 sslServerKey = mkOption { 178 type = types.nullOr types.str; 179 default = null; 180 description = "Path to the server's private key."; 181 }; 182 183 enablePAM = mkOption { 184 type = types.bool; 185 default = true; 186 description = "Whether to create a own Dovecot PAM service and configure PAM user logins."; 187 }; 188 189 sieveScripts = mkOption { 190 type = types.attrsOf types.path; 191 default = {}; 192 description = "Sieve scripts to be executed. Key is a sequence, e.g. 'before2', 'after' etc."; 193 }; 194 195 showPAMFailure = mkOption { 196 type = types.bool; 197 default = false; 198 description = "Show the PAM failure message on authentication error (useful for OTPW)."; 199 }; 200 }; 201 202 203 config = mkIf cfg.enable { 204 205 security.pam.services.dovecot2 = mkIf cfg.enablePAM {}; 206 207 services.dovecot2.protocols = 208 optional cfg.enableImap "imap" 209 ++ optional cfg.enablePop3 "pop3" 210 ++ optional cfg.enableLmtp "lmtp"; 211 212 users.extraUsers = [ 213 { name = "dovenull"; 214 uid = config.ids.uids.dovenull2; 215 description = "Dovecot user for untrusted logins"; 216 group = cfg.group; 217 } 218 ] ++ optional (cfg.user == "dovecot2") 219 { name = "dovecot2"; 220 uid = config.ids.uids.dovecot2; 221 description = "Dovecot user"; 222 group = cfg.group; 223 }; 224 225 users.extraGroups = optional (cfg.group == "dovecot2") 226 { name = "dovecot2"; 227 gid = config.ids.gids.dovecot2; 228 }; 229 230 environment.etc."dovecot/modules".source = modulesDir; 231 environment.etc."dovecot/dovecot.conf".source = cfg.configFile; 232 233 systemd.services.dovecot2 = { 234 description = "Dovecot IMAP/POP3 server"; 235 236 after = [ "keys.target" "network.target" ]; 237 wants = [ "keys.target" ]; 238 wantedBy = [ "multi-user.target" ]; 239 restartTriggers = [ cfg.configFile ]; 240 241 serviceConfig = { 242 ExecStart = "${dovecotPkg}/sbin/dovecot -F"; 243 ExecReload = "${dovecotPkg}/sbin/doveadm reload"; 244 Restart = "on-failure"; 245 RestartSec = "1s"; 246 StartLimitInterval = "1min"; 247 RuntimeDirectory = [ "dovecot2" ]; 248 }; 249 250 preStart = '' 251 rm -rf ${stateDir}/sieve 252 '' + optionalString (cfg.sieveScripts != {}) '' 253 mkdir -p ${stateDir}/sieve 254 ${concatStringsSep "\n" (mapAttrsToList (to: from: '' 255 if [ -d '${from}' ]; then 256 mkdir '${stateDir}/sieve/${to}' 257 cp ${from}/*.sieve '${stateDir}/sieve/${to}' 258 else 259 cp '${from}' '${stateDir}/sieve/${to}' 260 fi 261 ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/sieve/${to}' 262 '') cfg.sieveScripts)} 263 chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/sieve' 264 ''; 265 }; 266 267 environment.systemPackages = [ dovecotPkg ]; 268 269 assertions = [ 270 { assertion = intersectLists cfg.protocols [ "pop3" "imap" ] != []; 271 message = "dovecot needs at least one of the IMAP or POP3 listeners enabled"; 272 } 273 { assertion = isNull cfg.sslServerCert == isNull cfg.sslServerKey 274 && (!(isNull cfg.sslCACert) -> !(isNull cfg.sslServerCert || isNull cfg.sslServerKey)); 275 message = "dovecot needs both sslServerCert and sslServerKey defined for working crypto"; 276 } 277 { assertion = cfg.showPAMFailure -> cfg.enablePAM; 278 message = "dovecot is configured with showPAMFailure while enablePAM is disabled"; 279 } 280 ]; 281 282 }; 283 284}