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}