1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.postfix;
8 user = cfg.user;
9 group = cfg.group;
10 setgidGroup = cfg.setgidGroup;
11
12 haveAliases = cfg.postmasterAlias != "" || cfg.rootAlias != "" || cfg.extraAliases != "";
13 haveTransport = cfg.transport != "";
14 haveVirtual = cfg.virtual != "";
15
16 clientAccess =
17 if (cfg.dnsBlacklistOverrides != "")
18 then [ "check_client_access hash:/etc/postfix/client_access" ]
19 else [];
20
21 dnsBl =
22 if (cfg.dnsBlacklists != [])
23 then [ (concatStringsSep ", " (map (s: "reject_rbl_client " + s) cfg.dnsBlacklists)) ]
24 else [];
25
26 clientRestrictions = concatStringsSep ", " (clientAccess ++ dnsBl);
27
28 mainCf =
29 ''
30 compatibility_level = 2
31
32 mail_owner = ${user}
33 default_privs = nobody
34
35 # NixOS specific locations
36 data_directory = /var/lib/postfix/data
37 queue_directory = /var/lib/postfix/queue
38
39 # Default location of everything in package
40 meta_directory = ${pkgs.postfix}/etc/postfix
41 command_directory = ${pkgs.postfix}/bin
42 sample_directory = /etc/postfix
43 newaliases_path = ${pkgs.postfix}/bin/newaliases
44 mailq_path = ${pkgs.postfix}/bin/mailq
45 readme_directory = no
46 sendmail_path = ${pkgs.postfix}/bin/sendmail
47 daemon_directory = ${pkgs.postfix}/libexec/postfix
48 manpage_directory = ${pkgs.postfix}/share/man
49 html_directory = ${pkgs.postfix}/share/postfix/doc/html
50 shlib_directory = no
51
52 ''
53 + optionalString config.networking.enableIPv6 ''
54 inet_protocols = all
55 ''
56 + (if cfg.networks != null then
57 ''
58 mynetworks = ${concatStringsSep ", " cfg.networks}
59 ''
60 else if cfg.networksStyle != "" then
61 ''
62 mynetworks_style = ${cfg.networksStyle}
63 ''
64 else
65 "")
66 + optionalString (cfg.hostname != "") ''
67 myhostname = ${cfg.hostname}
68 ''
69 + optionalString (cfg.domain != "") ''
70 mydomain = ${cfg.domain}
71 ''
72 + optionalString (cfg.origin != "") ''
73 myorigin = ${cfg.origin}
74 ''
75 + optionalString (cfg.destination != null) ''
76 mydestination = ${concatStringsSep ", " cfg.destination}
77 ''
78 + optionalString (cfg.relayDomains != null) ''
79 relay_domains = ${concatStringsSep ", " cfg.relayDomains}
80 ''
81 + ''
82 local_recipient_maps =
83
84 relayhost = ${if cfg.lookupMX || cfg.relayHost == "" then
85 cfg.relayHost
86 else
87 "[" + cfg.relayHost + "]"}
88
89 mail_spool_directory = /var/spool/mail/
90
91 setgid_group = ${setgidGroup}
92 ''
93 + optionalString (cfg.sslCert != "") ''
94
95 smtp_tls_CAfile = ${cfg.sslCACert}
96 smtp_tls_cert_file = ${cfg.sslCert}
97 smtp_tls_key_file = ${cfg.sslKey}
98
99 smtp_use_tls = yes
100
101 smtpd_tls_CAfile = ${cfg.sslCACert}
102 smtpd_tls_cert_file = ${cfg.sslCert}
103 smtpd_tls_key_file = ${cfg.sslKey}
104
105 smtpd_use_tls = yes
106 ''
107 + optionalString (cfg.recipientDelimiter != "") ''
108 recipient_delimiter = ${cfg.recipientDelimiter}
109 ''
110 + optionalString haveAliases ''
111 alias_maps = hash:/etc/postfix/aliases
112 ''
113 + optionalString haveTransport ''
114 transport_maps = hash:/etc/postfix/transport
115 ''
116 + optionalString haveVirtual ''
117 virtual_alias_maps = hash:/etc/postfix/virtual
118 ''
119 + optionalString (cfg.dnsBlacklists != []) ''
120 smtpd_client_restrictions = ${clientRestrictions}
121 ''
122 + cfg.extraConfig;
123
124 masterCf = ''
125 # ==========================================================================
126 # service type private unpriv chroot wakeup maxproc command + args
127 # (yes) (yes) (no) (never) (100)
128 # ==========================================================================
129 smtp inet n - n - - smtpd
130 #submission inet n - n - - smtpd
131 # -o smtpd_tls_security_level=encrypt
132 # -o smtpd_sasl_auth_enable=yes
133 # -o smtpd_client_restrictions=permit_sasl_authenticated,reject
134 # -o milter_macro_daemon_name=ORIGINATING
135 pickup unix n - n 60 1 pickup
136 cleanup unix n - n - 0 cleanup
137 qmgr unix n - n 300 1 qmgr
138 tlsmgr unix - - n 1000? 1 tlsmgr
139 rewrite unix - - n - - trivial-rewrite
140 bounce unix - - n - 0 bounce
141 defer unix - - n - 0 bounce
142 trace unix - - n - 0 bounce
143 verify unix - - n - 1 verify
144 flush unix n - n 1000? 0 flush
145 proxymap unix - - n - - proxymap
146 proxywrite unix - - n - 1 proxymap
147 ''
148 + optionalString cfg.enableSmtp ''
149 smtp unix - - n - - smtp
150 relay unix - - n - - smtp
151 -o smtp_fallback_relay=
152 # -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
153 ''
154 + ''
155 showq unix n - n - - showq
156 error unix - - n - - error
157 retry unix - - n - - error
158 discard unix - - n - - discard
159 local unix - n n - - local
160 virtual unix - n n - - virtual
161 lmtp unix - - n - - lmtp
162 anvil unix - - n - 1 anvil
163 scache unix - - n - 1 scache
164 ${cfg.extraMasterConf}
165 '';
166
167 aliases =
168 optionalString (cfg.postmasterAlias != "") ''
169 postmaster: ${cfg.postmasterAlias}
170 ''
171 + optionalString (cfg.rootAlias != "") ''
172 root: ${cfg.rootAlias}
173 ''
174 + cfg.extraAliases
175 ;
176
177 aliasesFile = pkgs.writeText "postfix-aliases" aliases;
178 virtualFile = pkgs.writeText "postfix-virtual" cfg.virtual;
179 checkClientAccessFile = pkgs.writeText "postfix-check-client-access" cfg.dnsBlacklistOverrides;
180 mainCfFile = pkgs.writeText "postfix-main.cf" mainCf;
181 masterCfFile = pkgs.writeText "postfix-master.cf" masterCf;
182 transportFile = pkgs.writeText "postfix-transport" cfg.transport;
183
184in
185
186{
187
188 ###### interface
189
190 options = {
191
192 services.postfix = {
193
194 enable = mkOption {
195 type = types.bool;
196 default = false;
197 description = "Whether to run the Postfix mail server.";
198 };
199
200 enableSmtp = mkOption {
201 default = true;
202 description = "Whether to enable smtp in master.cf.";
203 };
204
205 setSendmail = mkOption {
206 type = types.bool;
207 default = true;
208 description = "Whether to set the system sendmail to postfix's.";
209 };
210
211 user = mkOption {
212 type = types.str;
213 default = "postfix";
214 description = "What to call the Postfix user (must be used only for postfix).";
215 };
216
217 group = mkOption {
218 type = types.str;
219 default = "postfix";
220 description = "What to call the Postfix group (must be used only for postfix).";
221 };
222
223 setgidGroup = mkOption {
224 type = types.str;
225 default = "postdrop";
226 description = "
227 How to call postfix setgid group (for postdrop). Should
228 be uniquely used group.
229 ";
230 };
231
232 networks = mkOption {
233 type = types.nullOr (types.listOf types.str);
234 default = null;
235 example = ["192.168.0.1/24"];
236 description = "
237 Net masks for trusted - allowed to relay mail to third parties -
238 hosts. Leave empty to use mynetworks_style configuration or use
239 default (localhost-only).
240 ";
241 };
242
243 networksStyle = mkOption {
244 type = types.str;
245 default = "";
246 description = "
247 Name of standard way of trusted network specification to use,
248 leave blank if you specify it explicitly or if you want to use
249 default (localhost-only).
250 ";
251 };
252
253 hostname = mkOption {
254 type = types.str;
255 default = "";
256 description ="
257 Hostname to use. Leave blank to use just the hostname of machine.
258 It should be FQDN.
259 ";
260 };
261
262 domain = mkOption {
263 type = types.str;
264 default = "";
265 description ="
266 Domain to use. Leave blank to use hostname minus first component.
267 ";
268 };
269
270 origin = mkOption {
271 type = types.str;
272 default = "";
273 description ="
274 Origin to use in outgoing e-mail. Leave blank to use hostname.
275 ";
276 };
277
278 destination = mkOption {
279 type = types.nullOr (types.listOf types.str);
280 default = null;
281 example = ["localhost"];
282 description = "
283 Full (!) list of domains we deliver locally. Leave blank for
284 acceptable Postfix default.
285 ";
286 };
287
288 relayDomains = mkOption {
289 type = types.nullOr (types.listOf types.str);
290 default = null;
291 example = ["localdomain"];
292 description = "
293 List of domains we agree to relay to. Default is empty.
294 ";
295 };
296
297 relayHost = mkOption {
298 type = types.str;
299 default = "";
300 description = "
301 Mail relay for outbound mail.
302 ";
303 };
304
305 lookupMX = mkOption {
306 type = types.bool;
307 default = false;
308 description = "
309 Whether relay specified is just domain whose MX must be used.
310 ";
311 };
312
313 postmasterAlias = mkOption {
314 type = types.str;
315 default = "root";
316 description = "Who should receive postmaster e-mail.";
317 };
318
319 rootAlias = mkOption {
320 type = types.str;
321 default = "";
322 description = "
323 Who should receive root e-mail. Blank for no redirection.
324 ";
325 };
326
327 extraAliases = mkOption {
328 type = types.lines;
329 default = "";
330 description = "
331 Additional entries to put verbatim into aliases file, cf. man-page aliases(8).
332 ";
333 };
334
335 extraConfig = mkOption {
336 type = types.lines;
337 default = "";
338 description = "
339 Extra lines to be added verbatim to the main.cf configuration file.
340 ";
341 };
342
343 sslCert = mkOption {
344 type = types.str;
345 default = "";
346 description = "SSL certificate to use.";
347 };
348
349 sslCACert = mkOption {
350 type = types.str;
351 default = "";
352 description = "SSL certificate of CA.";
353 };
354
355 sslKey = mkOption {
356 type = types.str;
357 default = "";
358 description = "SSL key to use.";
359 };
360
361 recipientDelimiter = mkOption {
362 type = types.str;
363 default = "";
364 example = "+";
365 description = "
366 Delimiter for address extension: so mail to user+test can be handled by ~user/.forward+test
367 ";
368 };
369
370 virtual = mkOption {
371 type = types.lines;
372 default = "";
373 description = "
374 Entries for the virtual alias map, cf. man-page virtual(8).
375 ";
376 };
377
378 transport = mkOption {
379 default = "";
380 description = "
381 Entries for the transport map, cf. man-page transport(8).
382 ";
383 };
384
385 dnsBlacklists = mkOption {
386 default = [];
387 type = with types; listOf string;
388 description = "dns blacklist servers to use with smtpd_client_restrictions";
389 };
390
391 dnsBlacklistOverrides = mkOption {
392 default = "";
393 description = "contents of check_client_access for overriding dnsBlacklists";
394 };
395
396 extraMasterConf = mkOption {
397 type = types.lines;
398 default = "";
399 example = "submission inet n - n - - smtpd";
400 description = "Extra lines to append to the generated master.cf file.";
401 };
402
403 aliasFiles = mkOption {
404 type = types.attrsOf types.path;
405 default = {};
406 description = "Aliases' tables to be compiled and placed into /var/lib/postfix/conf.";
407 };
408
409 mapFiles = mkOption {
410 type = types.attrsOf types.path;
411 default = {};
412 description = "Maps to be compiled and placed into /var/lib/postfix/conf.";
413 };
414
415 };
416
417 };
418
419
420 ###### implementation
421
422 config = mkIf config.services.postfix.enable (mkMerge [
423 {
424
425 environment = {
426 etc = singleton
427 { source = "/var/lib/postfix/conf";
428 target = "postfix";
429 };
430
431 # This makes comfortable for root to run 'postqueue' for example.
432 systemPackages = [ pkgs.postfix ];
433 };
434
435 services.mail.sendmailSetuidWrapper = mkIf config.services.postfix.setSendmail {
436 program = "sendmail";
437 source = "${pkgs.postfix}/bin/sendmail";
438 group = setgidGroup;
439 setuid = false;
440 setgid = true;
441 };
442
443 users.extraUsers = optional (user == "postfix")
444 { name = "postfix";
445 description = "Postfix mail server user";
446 uid = config.ids.uids.postfix;
447 group = group;
448 };
449
450 users.extraGroups =
451 optional (group == "postfix")
452 { name = group;
453 gid = config.ids.gids.postfix;
454 }
455 ++ optional (setgidGroup == "postdrop")
456 { name = setgidGroup;
457 gid = config.ids.gids.postdrop;
458 };
459
460 systemd.services.postfix =
461 { description = "Postfix mail server";
462
463 wantedBy = [ "multi-user.target" ];
464 after = [ "network.target" ];
465 path = [ pkgs.postfix ];
466
467 serviceConfig = {
468 Type = "forking";
469 Restart = "always";
470 PIDFile = "/var/lib/postfix/queue/pid/master.pid";
471 ExecStart = "${pkgs.postfix}/bin/postfix start";
472 ExecStop = "${pkgs.postfix}/bin/postfix stop";
473 ExecReload = "${pkgs.postfix}/bin/postfix reload";
474 };
475
476 preStart = ''
477 # Backwards compatibility
478 if [ ! -d /var/lib/postfix ] && [ -d /var/postfix ]; then
479 mkdir -p /var/lib
480 mv /var/postfix /var/lib/postfix
481 fi
482
483 # All permissions set according ${pkgs.postfix}/etc/postfix/postfix-files script
484 mkdir -p /var/lib/postfix /var/lib/postfix/queue/{pid,public,maildrop}
485 chmod 0755 /var/lib/postfix
486 chown root:root /var/lib/postfix
487
488 rm -rf /var/lib/postfix/conf
489 mkdir -p /var/lib/postfix/conf
490 chmod 0755 /var/lib/postfix/conf
491 ln -sf ${pkgs.postfix}/etc/postfix/postfix-files /var/lib/postfix/conf/postfix-files
492 ln -sf ${mainCfFile} /var/lib/postfix/conf/main.cf
493 ln -sf ${masterCfFile} /var/lib/postfix/conf/master.cf
494
495 ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
496 ln -sf ${from} /var/lib/postfix/conf/${to}
497 ${pkgs.postfix}/bin/postalias /var/lib/postfix/conf/${to}
498 '') cfg.aliasFiles)}
499 ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
500 ln -sf ${from} /var/lib/postfix/conf/${to}
501 ${pkgs.postfix}/bin/postmap /var/lib/postfix/conf/${to}
502 '') cfg.mapFiles)}
503
504 mkdir -p /var/spool/mail
505 chown root:root /var/spool/mail
506 chmod a+rwxt /var/spool/mail
507 ln -sf /var/spool/mail /var/
508
509 #Finally delegate to postfix checking remain directories in /var/lib/postfix and set permissions on them
510 ${pkgs.postfix}/bin/postfix set-permissions config_directory=/var/lib/postfix/conf
511 '';
512 };
513 }
514
515 (mkIf haveAliases {
516 services.postfix.aliasFiles."aliases" = aliasesFile;
517 })
518 (mkIf haveTransport {
519 services.postfix.mapFiles."transport" = transportFile;
520 })
521 (mkIf haveVirtual {
522 services.postfix.mapFiles."virtual" = virtualFile;
523 })
524 (mkIf (cfg.dnsBlacklists != []) {
525 services.postfix.mapFiles."client_access" = checkClientAccessFile;
526 })
527 ]);
528
529}