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