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 != ""
13 || cfg.extraAliases != "";
14 haveCanonical = cfg.canonical != "";
15 haveTransport = cfg.transport != "";
16 haveVirtual = cfg.virtual != "";
17 haveLocalRecipients = cfg.localRecipients != null;
18
19 clientAccess =
20 optional (cfg.dnsBlacklistOverrides != "")
21 "check_client_access hash:/etc/postfix/client_access";
22
23 dnsBl =
24 optionals (cfg.dnsBlacklists != [])
25 (map (s: "reject_rbl_client " + s) cfg.dnsBlacklists);
26
27 clientRestrictions = concatStringsSep ", " (clientAccess ++ dnsBl);
28
29 mainCf = let
30 escape = replaceStrings ["$"] ["$$"];
31 mkList = items: "\n " + concatStringsSep ",\n " items;
32 mkVal = value:
33 if isList value then mkList value
34 else " " + (if value == true then "yes"
35 else if value == false then "no"
36 else toString value);
37 mkEntry = name: value: "${escape name} =${mkVal value}";
38 in
39 concatStringsSep "\n" (mapAttrsToList mkEntry cfg.config)
40 + "\n" + cfg.extraConfig;
41
42 masterCfOptions = { options, config, name, ... }: {
43 options = {
44 name = mkOption {
45 type = types.str;
46 default = name;
47 example = "smtp";
48 description = lib.mdDoc ''
49 The name of the service to run. Defaults to the attribute set key.
50 '';
51 };
52
53 type = mkOption {
54 type = types.enum [ "inet" "unix" "unix-dgram" "fifo" "pass" ];
55 default = "unix";
56 example = "inet";
57 description = lib.mdDoc "The type of the service";
58 };
59
60 private = mkOption {
61 type = types.bool;
62 example = false;
63 description = lib.mdDoc ''
64 Whether the service's sockets and storage directory is restricted to
65 be only available via the mail system. If `null` is
66 given it uses the postfix default `true`.
67 '';
68 };
69
70 privileged = mkOption {
71 type = types.bool;
72 example = true;
73 description = lib.mdDoc "";
74 };
75
76 chroot = mkOption {
77 type = types.bool;
78 example = true;
79 description = lib.mdDoc ''
80 Whether the service is chrooted to have only access to the
81 {option}`services.postfix.queueDir` and the closure of
82 store paths specified by the {option}`program` option.
83 '';
84 };
85
86 wakeup = mkOption {
87 type = types.int;
88 example = 60;
89 description = lib.mdDoc ''
90 Automatically wake up the service after the specified number of
91 seconds. If `0` is given, never wake the service
92 up.
93 '';
94 };
95
96 wakeupUnusedComponent = mkOption {
97 type = types.bool;
98 example = false;
99 description = lib.mdDoc ''
100 If set to `false` the component will only be woken
101 up if it is used. This is equivalent to postfix' notion of adding a
102 question mark behind the wakeup time in
103 {file}`master.cf`
104 '';
105 };
106
107 maxproc = mkOption {
108 type = types.int;
109 example = 1;
110 description = lib.mdDoc ''
111 The maximum number of processes to spawn for this service. If the
112 value is `0` it doesn't have any limit. If
113 `null` is given it uses the postfix default of
114 `100`.
115 '';
116 };
117
118 command = mkOption {
119 type = types.str;
120 default = name;
121 example = "smtpd";
122 description = lib.mdDoc ''
123 A program name specifying a Postfix service/daemon process.
124 By default it's the attribute {option}`name`.
125 '';
126 };
127
128 args = mkOption {
129 type = types.listOf types.str;
130 default = [];
131 example = [ "-o" "smtp_helo_timeout=5" ];
132 description = lib.mdDoc ''
133 Arguments to pass to the {option}`command`. There is no shell
134 processing involved and shell syntax is passed verbatim to the
135 process.
136 '';
137 };
138
139 rawEntry = mkOption {
140 type = types.listOf types.str;
141 default = [];
142 internal = true;
143 description = lib.mdDoc ''
144 The raw configuration line for the {file}`master.cf`.
145 '';
146 };
147 };
148
149 config.rawEntry = let
150 mkBool = bool: if bool then "y" else "n";
151 mkArg = arg: "${optionalString (hasPrefix "-" arg) "\n "}${arg}";
152
153 maybeOption = fun: option:
154 if options.${option}.isDefined then fun config.${option} else "-";
155
156 # This is special, because we have two options for this value.
157 wakeup = let
158 wakeupDefined = options.wakeup.isDefined;
159 wakeupUCDefined = options.wakeupUnusedComponent.isDefined;
160 finalValue = toString config.wakeup
161 + optionalString (wakeupUCDefined && !config.wakeupUnusedComponent) "?";
162 in if wakeupDefined then finalValue else "-";
163
164 in [
165 config.name
166 config.type
167 (maybeOption mkBool "private")
168 (maybeOption (b: mkBool (!b)) "privileged")
169 (maybeOption mkBool "chroot")
170 wakeup
171 (maybeOption toString "maxproc")
172 (config.command + " " + concatMapStringsSep " " mkArg config.args)
173 ];
174 };
175
176 masterCfContent = let
177
178 labels = [
179 "# service" "type" "private" "unpriv" "chroot" "wakeup" "maxproc"
180 "command + args"
181 ];
182
183 labelDefaults = [
184 "# " "" "(yes)" "(yes)" "(no)" "(never)" "(100)" "" ""
185 ];
186
187 masterCf = mapAttrsToList (const (getAttr "rawEntry")) cfg.masterConfig;
188
189 # A list of the maximum width of the columns across all lines and labels
190 maxWidths = let
191 foldLine = line: acc: let
192 columnLengths = map stringLength line;
193 in zipListsWith max acc columnLengths;
194 # We need to handle the last column specially here, because it's
195 # open-ended (command + args).
196 lines = [ labels labelDefaults ] ++ (map (l: init l ++ [""]) masterCf);
197 in foldr foldLine (genList (const 0) (length labels)) lines;
198
199 # Pad a string with spaces from the right (opposite of fixedWidthString).
200 pad = width: str: let
201 padWidth = width - stringLength str;
202 padding = concatStrings (genList (const " ") padWidth);
203 in str + optionalString (padWidth > 0) padding;
204
205 # It's + 2 here, because that's the amount of spacing between columns.
206 fullWidth = foldr (width: acc: acc + width + 2) 0 maxWidths;
207
208 formatLine = line: concatStringsSep " " (zipListsWith pad maxWidths line);
209
210 formattedLabels = let
211 sep = "# " + concatStrings (genList (const "=") (fullWidth + 5));
212 lines = [ sep (formatLine labels) (formatLine labelDefaults) sep ];
213 in concatStringsSep "\n" lines;
214
215 in formattedLabels + "\n" + concatMapStringsSep "\n" formatLine masterCf + "\n" + cfg.extraMasterConf;
216
217 headerCheckOptions = { ... }:
218 {
219 options = {
220 pattern = mkOption {
221 type = types.str;
222 default = "/^.*/";
223 example = "/^X-Mailer:/";
224 description = lib.mdDoc "A regexp pattern matching the header";
225 };
226 action = mkOption {
227 type = types.str;
228 default = "DUNNO";
229 example = "BCC mail@example.com";
230 description = lib.mdDoc "The action to be executed when the pattern is matched";
231 };
232 };
233 };
234
235 headerChecks = concatStringsSep "\n" (map (x: "${x.pattern} ${x.action}") cfg.headerChecks) + cfg.extraHeaderChecks;
236
237 aliases = let separator = optionalString (cfg.aliasMapType == "hash") ":"; in
238 optionalString (cfg.postmasterAlias != "") ''
239 postmaster${separator} ${cfg.postmasterAlias}
240 ''
241 + optionalString (cfg.rootAlias != "") ''
242 root${separator} ${cfg.rootAlias}
243 ''
244 + cfg.extraAliases
245 ;
246
247 aliasesFile = pkgs.writeText "postfix-aliases" aliases;
248 canonicalFile = pkgs.writeText "postfix-canonical" cfg.canonical;
249 virtualFile = pkgs.writeText "postfix-virtual" cfg.virtual;
250 localRecipientMapFile = pkgs.writeText "postfix-local-recipient-map" (concatMapStrings (x: x + " ACCEPT\n") cfg.localRecipients);
251 checkClientAccessFile = pkgs.writeText "postfix-check-client-access" cfg.dnsBlacklistOverrides;
252 mainCfFile = pkgs.writeText "postfix-main.cf" mainCf;
253 masterCfFile = pkgs.writeText "postfix-master.cf" masterCfContent;
254 transportFile = pkgs.writeText "postfix-transport" cfg.transport;
255 headerChecksFile = pkgs.writeText "postfix-header-checks" headerChecks;
256
257in
258
259{
260
261 ###### interface
262
263 options = {
264
265 services.postfix = {
266
267 enable = mkOption {
268 type = types.bool;
269 default = false;
270 description = lib.mdDoc "Whether to run the Postfix mail server.";
271 };
272
273 enableSmtp = mkOption {
274 type = types.bool;
275 default = true;
276 description = lib.mdDoc "Whether to enable smtp in master.cf.";
277 };
278
279 enableSubmission = mkOption {
280 type = types.bool;
281 default = false;
282 description = lib.mdDoc "Whether to enable smtp submission.";
283 };
284
285 enableSubmissions = mkOption {
286 type = types.bool;
287 default = false;
288 description = lib.mdDoc ''
289 Whether to enable smtp submission via smtps.
290
291 According to RFC 8314 this should be preferred
292 over STARTTLS for submission of messages by end user clients.
293 '';
294 };
295
296 submissionOptions = mkOption {
297 type = with types; attrsOf str;
298 default = {
299 smtpd_tls_security_level = "encrypt";
300 smtpd_sasl_auth_enable = "yes";
301 smtpd_client_restrictions = "permit_sasl_authenticated,reject";
302 milter_macro_daemon_name = "ORIGINATING";
303 };
304 example = {
305 smtpd_tls_security_level = "encrypt";
306 smtpd_sasl_auth_enable = "yes";
307 smtpd_sasl_type = "dovecot";
308 smtpd_client_restrictions = "permit_sasl_authenticated,reject";
309 milter_macro_daemon_name = "ORIGINATING";
310 };
311 description = lib.mdDoc "Options for the submission config in master.cf";
312 };
313
314 submissionsOptions = mkOption {
315 type = with types; attrsOf str;
316 default = {
317 smtpd_sasl_auth_enable = "yes";
318 smtpd_client_restrictions = "permit_sasl_authenticated,reject";
319 milter_macro_daemon_name = "ORIGINATING";
320 };
321 example = {
322 smtpd_sasl_auth_enable = "yes";
323 smtpd_sasl_type = "dovecot";
324 smtpd_client_restrictions = "permit_sasl_authenticated,reject";
325 milter_macro_daemon_name = "ORIGINATING";
326 };
327 description = lib.mdDoc ''
328 Options for the submission config via smtps in master.cf.
329
330 smtpd_tls_security_level will be set to encrypt, if it is missing
331 or has one of the values "may" or "none".
332
333 smtpd_tls_wrappermode with value "yes" will be added automatically.
334 '';
335 };
336
337 setSendmail = mkOption {
338 type = types.bool;
339 default = true;
340 description = lib.mdDoc "Whether to set the system sendmail to postfix's.";
341 };
342
343 user = mkOption {
344 type = types.str;
345 default = "postfix";
346 description = lib.mdDoc "What to call the Postfix user (must be used only for postfix).";
347 };
348
349 group = mkOption {
350 type = types.str;
351 default = "postfix";
352 description = lib.mdDoc "What to call the Postfix group (must be used only for postfix).";
353 };
354
355 setgidGroup = mkOption {
356 type = types.str;
357 default = "postdrop";
358 description = lib.mdDoc ''
359 How to call postfix setgid group (for postdrop). Should
360 be uniquely used group.
361 '';
362 };
363
364 networks = mkOption {
365 type = types.nullOr (types.listOf types.str);
366 default = null;
367 example = ["192.168.0.1/24"];
368 description = lib.mdDoc ''
369 Net masks for trusted - allowed to relay mail to third parties -
370 hosts. Leave empty to use mynetworks_style configuration or use
371 default (localhost-only).
372 '';
373 };
374
375 networksStyle = mkOption {
376 type = types.str;
377 default = "";
378 description = lib.mdDoc ''
379 Name of standard way of trusted network specification to use,
380 leave blank if you specify it explicitly or if you want to use
381 default (localhost-only).
382 '';
383 };
384
385 hostname = mkOption {
386 type = types.str;
387 default = "";
388 description = lib.mdDoc ''
389 Hostname to use. Leave blank to use just the hostname of machine.
390 It should be FQDN.
391 '';
392 };
393
394 domain = mkOption {
395 type = types.str;
396 default = "";
397 description = lib.mdDoc ''
398 Domain to use. Leave blank to use hostname minus first component.
399 '';
400 };
401
402 origin = mkOption {
403 type = types.str;
404 default = "";
405 description = lib.mdDoc ''
406 Origin to use in outgoing e-mail. Leave blank to use hostname.
407 '';
408 };
409
410 destination = mkOption {
411 type = types.nullOr (types.listOf types.str);
412 default = null;
413 example = ["localhost"];
414 description = lib.mdDoc ''
415 Full (!) list of domains we deliver locally. Leave blank for
416 acceptable Postfix default.
417 '';
418 };
419
420 relayDomains = mkOption {
421 type = types.nullOr (types.listOf types.str);
422 default = null;
423 example = ["localdomain"];
424 description = lib.mdDoc ''
425 List of domains we agree to relay to. Default is empty.
426 '';
427 };
428
429 relayHost = mkOption {
430 type = types.str;
431 default = "";
432 description = lib.mdDoc ''
433 Mail relay for outbound mail.
434 '';
435 };
436
437 relayPort = mkOption {
438 type = types.int;
439 default = 25;
440 description = lib.mdDoc ''
441 SMTP port for relay mail relay.
442 '';
443 };
444
445 lookupMX = mkOption {
446 type = types.bool;
447 default = false;
448 description = lib.mdDoc ''
449 Whether relay specified is just domain whose MX must be used.
450 '';
451 };
452
453 postmasterAlias = mkOption {
454 type = types.str;
455 default = "root";
456 description = lib.mdDoc ''
457 Who should receive postmaster e-mail. Multiple values can be added by
458 separating values with comma.
459 '';
460 };
461
462 rootAlias = mkOption {
463 type = types.str;
464 default = "";
465 description = lib.mdDoc ''
466 Who should receive root e-mail. Blank for no redirection.
467 Multiple values can be added by separating values with comma.
468 '';
469 };
470
471 extraAliases = mkOption {
472 type = types.lines;
473 default = "";
474 description = lib.mdDoc ''
475 Additional entries to put verbatim into aliases file, cf. man-page aliases(8).
476 '';
477 };
478
479 aliasMapType = mkOption {
480 type = with types; enum [ "hash" "regexp" "pcre" ];
481 default = "hash";
482 example = "regexp";
483 description = lib.mdDoc "The format the alias map should have. Use regexp if you want to use regular expressions.";
484 };
485
486 config = mkOption {
487 type = with types; attrsOf (oneOf [ bool str (listOf str) ]);
488 description = lib.mdDoc ''
489 The main.cf configuration file as key value set.
490 '';
491 example = {
492 mail_owner = "postfix";
493 smtp_tls_security_level = "may";
494 };
495 };
496
497 extraConfig = mkOption {
498 type = types.lines;
499 default = "";
500 description = lib.mdDoc ''
501 Extra lines to be added verbatim to the main.cf configuration file.
502 '';
503 };
504
505 tlsTrustedAuthorities = mkOption {
506 type = types.str;
507 default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
508 defaultText = literalExpression ''"''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"'';
509 description = lib.mdDoc ''
510 File containing trusted certification authorities (CA) to verify certificates of mailservers contacted for mail delivery. This basically sets smtp_tls_CAfile and enables opportunistic tls. Defaults to NixOS trusted certification authorities.
511 '';
512 };
513
514 sslCert = mkOption {
515 type = types.str;
516 default = "";
517 description = lib.mdDoc "SSL certificate to use.";
518 };
519
520 sslKey = mkOption {
521 type = types.str;
522 default = "";
523 description = lib.mdDoc "SSL key to use.";
524 };
525
526 recipientDelimiter = mkOption {
527 type = types.str;
528 default = "";
529 example = "+";
530 description = lib.mdDoc ''
531 Delimiter for address extension: so mail to user+test can be handled by ~user/.forward+test
532 '';
533 };
534
535 canonical = mkOption {
536 type = types.lines;
537 default = "";
538 description = lib.mdDoc ''
539 Entries for the {manpage}`canonical(5)` table.
540 '';
541 };
542
543 virtual = mkOption {
544 type = types.lines;
545 default = "";
546 description = lib.mdDoc ''
547 Entries for the virtual alias map, cf. man-page virtual(5).
548 '';
549 };
550
551 virtualMapType = mkOption {
552 type = types.enum ["hash" "regexp" "pcre"];
553 default = "hash";
554 description = lib.mdDoc ''
555 What type of virtual alias map file to use. Use `"regexp"` for regular expressions.
556 '';
557 };
558
559 localRecipients = mkOption {
560 type = with types; nullOr (listOf str);
561 default = null;
562 description = lib.mdDoc ''
563 List of accepted local users. Specify a bare username, an
564 `"@domain.tld"` wild-card, or a complete
565 `"user@domain.tld"` address. If set, these names end
566 up in the local recipient map -- see the local(8) man-page -- and
567 effectively replace the system user database lookup that's otherwise
568 used by default.
569 '';
570 };
571
572 transport = mkOption {
573 default = "";
574 type = types.lines;
575 description = lib.mdDoc ''
576 Entries for the transport map, cf. man-page transport(8).
577 '';
578 };
579
580 dnsBlacklists = mkOption {
581 default = [];
582 type = with types; listOf str;
583 description = lib.mdDoc "dns blacklist servers to use with smtpd_client_restrictions";
584 };
585
586 dnsBlacklistOverrides = mkOption {
587 default = "";
588 type = types.lines;
589 description = lib.mdDoc "contents of check_client_access for overriding dnsBlacklists";
590 };
591
592 masterConfig = mkOption {
593 type = types.attrsOf (types.submodule masterCfOptions);
594 default = {};
595 example =
596 { submission = {
597 type = "inet";
598 args = [ "-o" "smtpd_tls_security_level=encrypt" ];
599 };
600 };
601 description = lib.mdDoc ''
602 An attribute set of service options, which correspond to the service
603 definitions usually done within the Postfix
604 {file}`master.cf` file.
605 '';
606 };
607
608 extraMasterConf = mkOption {
609 type = types.lines;
610 default = "";
611 example = "submission inet n - n - - smtpd";
612 description = lib.mdDoc "Extra lines to append to the generated master.cf file.";
613 };
614
615 enableHeaderChecks = mkOption {
616 type = types.bool;
617 default = false;
618 example = true;
619 description = lib.mdDoc "Whether to enable postfix header checks";
620 };
621
622 headerChecks = mkOption {
623 type = types.listOf (types.submodule headerCheckOptions);
624 default = [];
625 example = [ { pattern = "/^X-Spam-Flag:/"; action = "REDIRECT spam@example.com"; } ];
626 description = lib.mdDoc "Postfix header checks.";
627 };
628
629 extraHeaderChecks = mkOption {
630 type = types.lines;
631 default = "";
632 example = "/^X-Spam-Flag:/ REDIRECT spam@example.com";
633 description = lib.mdDoc "Extra lines to /etc/postfix/header_checks file.";
634 };
635
636 aliasFiles = mkOption {
637 type = types.attrsOf types.path;
638 default = {};
639 description = lib.mdDoc "Aliases' tables to be compiled and placed into /var/lib/postfix/conf.";
640 };
641
642 mapFiles = mkOption {
643 type = types.attrsOf types.path;
644 default = {};
645 description = lib.mdDoc "Maps to be compiled and placed into /var/lib/postfix/conf.";
646 };
647
648 useSrs = mkOption {
649 type = types.bool;
650 default = false;
651 description = lib.mdDoc "Whether to enable sender rewriting scheme";
652 };
653
654 };
655
656 };
657
658
659 ###### implementation
660
661 config = mkIf config.services.postfix.enable (mkMerge [
662 {
663
664 environment = {
665 etc.postfix.source = "/var/lib/postfix/conf";
666
667 # This makes it comfortable to run 'postqueue/postdrop' for example.
668 systemPackages = [ pkgs.postfix ];
669 };
670
671 services.pfix-srsd.enable = config.services.postfix.useSrs;
672
673 services.mail.sendmailSetuidWrapper = mkIf config.services.postfix.setSendmail {
674 program = "sendmail";
675 source = "${pkgs.postfix}/bin/sendmail";
676 owner = "root";
677 group = setgidGroup;
678 setuid = false;
679 setgid = true;
680 };
681
682 security.wrappers.mailq = {
683 program = "mailq";
684 source = "${pkgs.postfix}/bin/mailq";
685 owner = "root";
686 group = setgidGroup;
687 setuid = false;
688 setgid = true;
689 };
690
691 security.wrappers.postqueue = {
692 program = "postqueue";
693 source = "${pkgs.postfix}/bin/postqueue";
694 owner = "root";
695 group = setgidGroup;
696 setuid = false;
697 setgid = true;
698 };
699
700 security.wrappers.postdrop = {
701 program = "postdrop";
702 source = "${pkgs.postfix}/bin/postdrop";
703 owner = "root";
704 group = setgidGroup;
705 setuid = false;
706 setgid = true;
707 };
708
709 users.users = optionalAttrs (user == "postfix")
710 { postfix = {
711 description = "Postfix mail server user";
712 uid = config.ids.uids.postfix;
713 group = group;
714 };
715 };
716
717 users.groups =
718 optionalAttrs (group == "postfix")
719 { ${group}.gid = config.ids.gids.postfix;
720 }
721 // optionalAttrs (setgidGroup == "postdrop")
722 { ${setgidGroup}.gid = config.ids.gids.postdrop;
723 };
724
725 systemd.services.postfix-setup =
726 { description = "Setup for Postfix mail server";
727 serviceConfig.RemainAfterExit = true;
728 serviceConfig.Type = "oneshot";
729 script = ''
730 # Backwards compatibility
731 if [ ! -d /var/lib/postfix ] && [ -d /var/postfix ]; then
732 mkdir -p /var/lib
733 mv /var/postfix /var/lib/postfix
734 fi
735
736 # All permissions set according ${pkgs.postfix}/etc/postfix/postfix-files script
737 mkdir -p /var/lib/postfix /var/lib/postfix/queue/{pid,public,maildrop}
738 chmod 0755 /var/lib/postfix
739 chown root:root /var/lib/postfix
740
741 rm -rf /var/lib/postfix/conf
742 mkdir -p /var/lib/postfix/conf
743 chmod 0755 /var/lib/postfix/conf
744 ln -sf ${pkgs.postfix}/etc/postfix/postfix-files /var/lib/postfix/conf/postfix-files
745 ln -sf ${mainCfFile} /var/lib/postfix/conf/main.cf
746 ln -sf ${masterCfFile} /var/lib/postfix/conf/master.cf
747
748 ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
749 ln -sf ${from} /var/lib/postfix/conf/${to}
750 ${pkgs.postfix}/bin/postalias /var/lib/postfix/conf/${to}
751 '') cfg.aliasFiles)}
752 ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
753 ln -sf ${from} /var/lib/postfix/conf/${to}
754 ${pkgs.postfix}/bin/postmap /var/lib/postfix/conf/${to}
755 '') cfg.mapFiles)}
756
757 mkdir -p /var/spool/mail
758 chown root:root /var/spool/mail
759 chmod a+rwxt /var/spool/mail
760 ln -sf /var/spool/mail /var/
761
762 #Finally delegate to postfix checking remain directories in /var/lib/postfix and set permissions on them
763 ${pkgs.postfix}/bin/postfix set-permissions config_directory=/var/lib/postfix/conf
764 '';
765 };
766
767 systemd.services.postfix =
768 { description = "Postfix mail server";
769
770 wantedBy = [ "multi-user.target" ];
771 after = [ "network.target" "postfix-setup.service" ];
772 requires = [ "postfix-setup.service" ];
773 path = [ pkgs.postfix ];
774
775 serviceConfig = {
776 Type = "forking";
777 Restart = "always";
778 PIDFile = "/var/lib/postfix/queue/pid/master.pid";
779 ExecStart = "${pkgs.postfix}/bin/postfix start";
780 ExecStop = "${pkgs.postfix}/bin/postfix stop";
781 ExecReload = "${pkgs.postfix}/bin/postfix reload";
782 };
783 };
784
785 services.postfix.config = (mapAttrs (_: v: mkDefault v) {
786 compatibility_level = pkgs.postfix.version;
787 mail_owner = cfg.user;
788 default_privs = "nobody";
789
790 # NixOS specific locations
791 data_directory = "/var/lib/postfix/data";
792 queue_directory = "/var/lib/postfix/queue";
793
794 # Default location of everything in package
795 meta_directory = "${pkgs.postfix}/etc/postfix";
796 command_directory = "${pkgs.postfix}/bin";
797 sample_directory = "/etc/postfix";
798 newaliases_path = "${pkgs.postfix}/bin/newaliases";
799 mailq_path = "${pkgs.postfix}/bin/mailq";
800 readme_directory = false;
801 sendmail_path = "${pkgs.postfix}/bin/sendmail";
802 daemon_directory = "${pkgs.postfix}/libexec/postfix";
803 manpage_directory = "${pkgs.postfix}/share/man";
804 html_directory = "${pkgs.postfix}/share/postfix/doc/html";
805 shlib_directory = false;
806 mail_spool_directory = "/var/spool/mail/";
807 setgid_group = cfg.setgidGroup;
808 })
809 // optionalAttrs (cfg.relayHost != "") { relayhost = if cfg.lookupMX
810 then "${cfg.relayHost}:${toString cfg.relayPort}"
811 else "[${cfg.relayHost}]:${toString cfg.relayPort}"; }
812 // optionalAttrs (!config.networking.enableIPv6) { inet_protocols = mkDefault "ipv4"; }
813 // optionalAttrs (cfg.networks != null) { mynetworks = cfg.networks; }
814 // optionalAttrs (cfg.networksStyle != "") { mynetworks_style = cfg.networksStyle; }
815 // optionalAttrs (cfg.hostname != "") { myhostname = cfg.hostname; }
816 // optionalAttrs (cfg.domain != "") { mydomain = cfg.domain; }
817 // optionalAttrs (cfg.origin != "") { myorigin = cfg.origin; }
818 // optionalAttrs (cfg.destination != null) { mydestination = cfg.destination; }
819 // optionalAttrs (cfg.relayDomains != null) { relay_domains = cfg.relayDomains; }
820 // optionalAttrs (cfg.recipientDelimiter != "") { recipient_delimiter = cfg.recipientDelimiter; }
821 // optionalAttrs haveAliases { alias_maps = [ "${cfg.aliasMapType}:/etc/postfix/aliases" ]; }
822 // optionalAttrs haveTransport { transport_maps = [ "hash:/etc/postfix/transport" ]; }
823 // optionalAttrs haveVirtual { virtual_alias_maps = [ "${cfg.virtualMapType}:/etc/postfix/virtual" ]; }
824 // optionalAttrs haveLocalRecipients { local_recipient_maps = [ "hash:/etc/postfix/local_recipients" ] ++ optional haveAliases "$alias_maps"; }
825 // optionalAttrs (cfg.dnsBlacklists != []) { smtpd_client_restrictions = clientRestrictions; }
826 // optionalAttrs cfg.useSrs {
827 sender_canonical_maps = [ "tcp:127.0.0.1:10001" ];
828 sender_canonical_classes = [ "envelope_sender" ];
829 recipient_canonical_maps = [ "tcp:127.0.0.1:10002" ];
830 recipient_canonical_classes = [ "envelope_recipient" ];
831 }
832 // optionalAttrs cfg.enableHeaderChecks { header_checks = [ "regexp:/etc/postfix/header_checks" ]; }
833 // optionalAttrs (cfg.tlsTrustedAuthorities != "") {
834 smtp_tls_CAfile = cfg.tlsTrustedAuthorities;
835 smtp_tls_security_level = mkDefault "may";
836 }
837 // optionalAttrs (cfg.sslCert != "") {
838 smtp_tls_cert_file = cfg.sslCert;
839 smtp_tls_key_file = cfg.sslKey;
840
841 smtp_tls_security_level = mkDefault "may";
842
843 smtpd_tls_cert_file = cfg.sslCert;
844 smtpd_tls_key_file = cfg.sslKey;
845
846 smtpd_tls_security_level = "may";
847 };
848
849 services.postfix.masterConfig = {
850 pickup = {
851 private = false;
852 wakeup = 60;
853 maxproc = 1;
854 };
855 cleanup = {
856 private = false;
857 maxproc = 0;
858 };
859 qmgr = {
860 private = false;
861 wakeup = 300;
862 maxproc = 1;
863 };
864 tlsmgr = {
865 wakeup = 1000;
866 wakeupUnusedComponent = false;
867 maxproc = 1;
868 };
869 rewrite = {
870 command = "trivial-rewrite";
871 };
872 bounce = {
873 maxproc = 0;
874 };
875 defer = {
876 maxproc = 0;
877 command = "bounce";
878 };
879 trace = {
880 maxproc = 0;
881 command = "bounce";
882 };
883 verify = {
884 maxproc = 1;
885 };
886 flush = {
887 private = false;
888 wakeup = 1000;
889 wakeupUnusedComponent = false;
890 maxproc = 0;
891 };
892 proxymap = {
893 command = "proxymap";
894 };
895 proxywrite = {
896 maxproc = 1;
897 command = "proxymap";
898 };
899 showq = {
900 private = false;
901 };
902 error = {};
903 retry = {
904 command = "error";
905 };
906 discard = {};
907 local = {
908 privileged = true;
909 };
910 virtual = {
911 privileged = true;
912 };
913 lmtp = {
914 };
915 anvil = {
916 maxproc = 1;
917 };
918 scache = {
919 maxproc = 1;
920 };
921 } // optionalAttrs cfg.enableSubmission {
922 submission = {
923 type = "inet";
924 private = false;
925 command = "smtpd";
926 args = let
927 mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ];
928 in concatLists (mapAttrsToList mkKeyVal cfg.submissionOptions);
929 };
930 } // optionalAttrs cfg.enableSmtp {
931 smtp_inet = {
932 name = "smtp";
933 type = "inet";
934 private = false;
935 command = "smtpd";
936 };
937 smtp = {};
938 relay = {
939 command = "smtp";
940 args = [ "-o" "smtp_fallback_relay=" ];
941 };
942 } // optionalAttrs cfg.enableSubmissions {
943 submissions = {
944 type = "inet";
945 private = false;
946 command = "smtpd";
947 args = let
948 mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ];
949 adjustSmtpTlsSecurityLevel = !(cfg.submissionsOptions ? smtpd_tls_security_level) ||
950 cfg.submissionsOptions.smtpd_tls_security_level == "none" ||
951 cfg.submissionsOptions.smtpd_tls_security_level == "may";
952 submissionsOptions = cfg.submissionsOptions // {
953 smtpd_tls_wrappermode = "yes";
954 } // optionalAttrs adjustSmtpTlsSecurityLevel {
955 smtpd_tls_security_level = "encrypt";
956 };
957 in concatLists (mapAttrsToList mkKeyVal submissionsOptions);
958 };
959 };
960 }
961
962 (mkIf haveAliases {
963 services.postfix.aliasFiles.aliases = aliasesFile;
964 })
965 (mkIf haveCanonical {
966 services.postfix.mapFiles.canonical = canonicalFile;
967 })
968 (mkIf haveTransport {
969 services.postfix.mapFiles.transport = transportFile;
970 })
971 (mkIf haveVirtual {
972 services.postfix.mapFiles.virtual = virtualFile;
973 })
974 (mkIf haveLocalRecipients {
975 services.postfix.mapFiles.local_recipients = localRecipientMapFile;
976 })
977 (mkIf cfg.enableHeaderChecks {
978 services.postfix.mapFiles.header_checks = headerChecksFile;
979 })
980 (mkIf (cfg.dnsBlacklists != []) {
981 services.postfix.mapFiles.client_access = checkClientAccessFile;
982 })
983 ]);
984
985 imports = [
986 (mkRemovedOptionModule [ "services" "postfix" "sslCACert" ]
987 "services.postfix.sslCACert was replaced by services.postfix.tlsTrustedAuthorities. In case you intend that your server should validate requested client certificates use services.postfix.extraConfig.")
988
989 (mkChangedOptionModule [ "services" "postfix" "useDane" ]
990 [ "services" "postfix" "config" "smtp_tls_security_level" ]
991 (config: mkIf config.services.postfix.useDane "dane"))
992 ];
993}