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 = 9999
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 '' + optionalString cfg.enableSubmission ''
131 submission inet n - n - - smtpd
132 ${concatStringsSep "\n " (mapAttrsToList (x: y: "-o " + x + "=" + y) cfg.submissionOptions)}
133 ''
134 + ''
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 enableSubmission = mkOption {
206 type = types.bool;
207 default = false;
208 description = "Whether to enable smtp submission";
209 };
210
211 submissionOptions = mkOption {
212 type = types.attrs;
213 default = { "smtpd_tls_security_level" = "encrypt";
214 "smtpd_sasl_auth_enable" = "yes";
215 "smtpd_client_restrictions" = "permit_sasl_authenticated,reject";
216 "milter_macro_daemon_name" = "ORIGINATING";
217 };
218 description = "Options for the submission config in master.cf";
219 example = { "smtpd_tls_security_level" = "encrypt";
220 "smtpd_sasl_auth_enable" = "yes";
221 "smtpd_sasl_type" = "dovecot";
222 "smtpd_client_restrictions" = "permit_sasl_authenticated,reject";
223 "milter_macro_daemon_name" = "ORIGINATING";
224 };
225 };
226
227 setSendmail = mkOption {
228 type = types.bool;
229 default = true;
230 description = "Whether to set the system sendmail to postfix's.";
231 };
232
233 user = mkOption {
234 type = types.str;
235 default = "postfix";
236 description = "What to call the Postfix user (must be used only for postfix).";
237 };
238
239 group = mkOption {
240 type = types.str;
241 default = "postfix";
242 description = "What to call the Postfix group (must be used only for postfix).";
243 };
244
245 setgidGroup = mkOption {
246 type = types.str;
247 default = "postdrop";
248 description = "
249 How to call postfix setgid group (for postdrop). Should
250 be uniquely used group.
251 ";
252 };
253
254 networks = mkOption {
255 type = types.nullOr (types.listOf types.str);
256 default = null;
257 example = ["192.168.0.1/24"];
258 description = "
259 Net masks for trusted - allowed to relay mail to third parties -
260 hosts. Leave empty to use mynetworks_style configuration or use
261 default (localhost-only).
262 ";
263 };
264
265 networksStyle = mkOption {
266 type = types.str;
267 default = "";
268 description = "
269 Name of standard way of trusted network specification to use,
270 leave blank if you specify it explicitly or if you want to use
271 default (localhost-only).
272 ";
273 };
274
275 hostname = mkOption {
276 type = types.str;
277 default = "";
278 description ="
279 Hostname to use. Leave blank to use just the hostname of machine.
280 It should be FQDN.
281 ";
282 };
283
284 domain = mkOption {
285 type = types.str;
286 default = "";
287 description ="
288 Domain to use. Leave blank to use hostname minus first component.
289 ";
290 };
291
292 origin = mkOption {
293 type = types.str;
294 default = "";
295 description ="
296 Origin to use in outgoing e-mail. Leave blank to use hostname.
297 ";
298 };
299
300 destination = mkOption {
301 type = types.nullOr (types.listOf types.str);
302 default = null;
303 example = ["localhost"];
304 description = "
305 Full (!) list of domains we deliver locally. Leave blank for
306 acceptable Postfix default.
307 ";
308 };
309
310 relayDomains = mkOption {
311 type = types.nullOr (types.listOf types.str);
312 default = null;
313 example = ["localdomain"];
314 description = "
315 List of domains we agree to relay to. Default is empty.
316 ";
317 };
318
319 relayHost = mkOption {
320 type = types.str;
321 default = "";
322 description = "
323 Mail relay for outbound mail.
324 ";
325 };
326
327 lookupMX = mkOption {
328 type = types.bool;
329 default = false;
330 description = "
331 Whether relay specified is just domain whose MX must be used.
332 ";
333 };
334
335 postmasterAlias = mkOption {
336 type = types.str;
337 default = "root";
338 description = "Who should receive postmaster e-mail.";
339 };
340
341 rootAlias = mkOption {
342 type = types.str;
343 default = "";
344 description = "
345 Who should receive root e-mail. Blank for no redirection.
346 ";
347 };
348
349 extraAliases = mkOption {
350 type = types.lines;
351 default = "";
352 description = "
353 Additional entries to put verbatim into aliases file, cf. man-page aliases(8).
354 ";
355 };
356
357 extraConfig = mkOption {
358 type = types.lines;
359 default = "";
360 description = "
361 Extra lines to be added verbatim to the main.cf configuration file.
362 ";
363 };
364
365 sslCert = mkOption {
366 type = types.str;
367 default = "";
368 description = "SSL certificate to use.";
369 };
370
371 sslCACert = mkOption {
372 type = types.str;
373 default = "";
374 description = "SSL certificate of CA.";
375 };
376
377 sslKey = mkOption {
378 type = types.str;
379 default = "";
380 description = "SSL key to use.";
381 };
382
383 recipientDelimiter = mkOption {
384 type = types.str;
385 default = "";
386 example = "+";
387 description = "
388 Delimiter for address extension: so mail to user+test can be handled by ~user/.forward+test
389 ";
390 };
391
392 virtual = mkOption {
393 type = types.lines;
394 default = "";
395 description = "
396 Entries for the virtual alias map, cf. man-page virtual(8).
397 ";
398 };
399
400 transport = mkOption {
401 default = "";
402 description = "
403 Entries for the transport map, cf. man-page transport(8).
404 ";
405 };
406
407 dnsBlacklists = mkOption {
408 default = [];
409 type = with types; listOf string;
410 description = "dns blacklist servers to use with smtpd_client_restrictions";
411 };
412
413 dnsBlacklistOverrides = mkOption {
414 default = "";
415 description = "contents of check_client_access for overriding dnsBlacklists";
416 };
417
418 extraMasterConf = mkOption {
419 type = types.lines;
420 default = "";
421 example = "submission inet n - n - - smtpd";
422 description = "Extra lines to append to the generated master.cf file.";
423 };
424
425 aliasFiles = mkOption {
426 type = types.attrsOf types.path;
427 default = {};
428 description = "Aliases' tables to be compiled and placed into /var/lib/postfix/conf.";
429 };
430
431 mapFiles = mkOption {
432 type = types.attrsOf types.path;
433 default = {};
434 description = "Maps to be compiled and placed into /var/lib/postfix/conf.";
435 };
436
437 };
438
439 };
440
441
442 ###### implementation
443
444 config = mkIf config.services.postfix.enable (mkMerge [
445 {
446
447 environment = {
448 etc = singleton
449 { source = "/var/lib/postfix/conf";
450 target = "postfix";
451 };
452
453 # This makes comfortable for root to run 'postqueue' for example.
454 systemPackages = [ pkgs.postfix ];
455 };
456
457 services.mail.sendmailSetuidWrapper = mkIf config.services.postfix.setSendmail {
458 program = "sendmail";
459 source = "${pkgs.postfix}/bin/sendmail";
460 group = setgidGroup;
461 setuid = false;
462 setgid = true;
463 };
464
465 users.extraUsers = optional (user == "postfix")
466 { name = "postfix";
467 description = "Postfix mail server user";
468 uid = config.ids.uids.postfix;
469 group = group;
470 };
471
472 users.extraGroups =
473 optional (group == "postfix")
474 { name = group;
475 gid = config.ids.gids.postfix;
476 }
477 ++ optional (setgidGroup == "postdrop")
478 { name = setgidGroup;
479 gid = config.ids.gids.postdrop;
480 };
481
482 systemd.services.postfix =
483 { description = "Postfix mail server";
484
485 wantedBy = [ "multi-user.target" ];
486 after = [ "network.target" ];
487 path = [ pkgs.postfix ];
488
489 serviceConfig = {
490 Type = "forking";
491 Restart = "always";
492 PIDFile = "/var/lib/postfix/queue/pid/master.pid";
493 ExecStart = "${pkgs.postfix}/bin/postfix start";
494 ExecStop = "${pkgs.postfix}/bin/postfix stop";
495 ExecReload = "${pkgs.postfix}/bin/postfix reload";
496 };
497
498 preStart = ''
499 # Backwards compatibility
500 if [ ! -d /var/lib/postfix ] && [ -d /var/postfix ]; then
501 mkdir -p /var/lib
502 mv /var/postfix /var/lib/postfix
503 fi
504
505 # All permissions set according ${pkgs.postfix}/etc/postfix/postfix-files script
506 mkdir -p /var/lib/postfix /var/lib/postfix/queue/{pid,public,maildrop}
507 chmod 0755 /var/lib/postfix
508 chown root:root /var/lib/postfix
509
510 rm -rf /var/lib/postfix/conf
511 mkdir -p /var/lib/postfix/conf
512 chmod 0755 /var/lib/postfix/conf
513 ln -sf ${pkgs.postfix}/etc/postfix/postfix-files /var/lib/postfix/conf/postfix-files
514 ln -sf ${mainCfFile} /var/lib/postfix/conf/main.cf
515 ln -sf ${masterCfFile} /var/lib/postfix/conf/master.cf
516
517 ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
518 ln -sf ${from} /var/lib/postfix/conf/${to}
519 ${pkgs.postfix}/bin/postalias /var/lib/postfix/conf/${to}
520 '') cfg.aliasFiles)}
521 ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
522 ln -sf ${from} /var/lib/postfix/conf/${to}
523 ${pkgs.postfix}/bin/postmap /var/lib/postfix/conf/${to}
524 '') cfg.mapFiles)}
525
526 mkdir -p /var/spool/mail
527 chown root:root /var/spool/mail
528 chmod a+rwxt /var/spool/mail
529 ln -sf /var/spool/mail /var/
530
531 #Finally delegate to postfix checking remain directories in /var/lib/postfix and set permissions on them
532 ${pkgs.postfix}/bin/postfix set-permissions config_directory=/var/lib/postfix/conf
533 '';
534 };
535 }
536
537 (mkIf haveAliases {
538 services.postfix.aliasFiles."aliases" = aliasesFile;
539 })
540 (mkIf haveTransport {
541 services.postfix.mapFiles."transport" = transportFile;
542 })
543 (mkIf haveVirtual {
544 services.postfix.mapFiles."virtual" = virtualFile;
545 })
546 (mkIf (cfg.dnsBlacklists != []) {
547 services.postfix.mapFiles."client_access" = checkClientAccessFile;
548 })
549 ]);
550
551}