1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 inherit (lib)
9 literalExpression
10 mkOption
11 types
12 ;
13
14 cfg = config.services.postfix;
15 user = cfg.user;
16 group = cfg.group;
17 setgidGroup = cfg.setgidGroup;
18
19 haveAliases = cfg.postmasterAlias != "" || cfg.rootAlias != "" || cfg.extraAliases != "";
20 haveCanonical = cfg.canonical != "";
21 haveTransport = cfg.transport != "";
22 haveVirtual = cfg.virtual != "";
23 haveLocalRecipients = cfg.localRecipients != null;
24
25 clientAccess = lib.optional (
26 cfg.dnsBlacklistOverrides != ""
27 ) "check_client_access hash:/etc/postfix/client_access";
28
29 dnsBl = lib.optionals (cfg.dnsBlacklists != [ ]) (
30 map (s: "reject_rbl_client " + s) cfg.dnsBlacklists
31 );
32
33 clientRestrictions = lib.concatStringsSep ", " (clientAccess ++ dnsBl);
34
35 mainCf =
36 let
37 escape = lib.replaceStrings [ "$" ] [ "$$" ];
38 mkList = items: "\n " + lib.concatStringsSep ",\n " items;
39 mkVal =
40 value:
41 if lib.isList value then
42 mkList value
43 else
44 " "
45 + (
46 if value == true then
47 "yes"
48 else if value == false then
49 "no"
50 else
51 toString value
52 );
53 mkEntry = name: value: "${escape name} =${mkVal value}";
54 in
55 lib.concatStringsSep "\n" (
56 lib.mapAttrsToList mkEntry (lib.filterAttrsRecursive (_: value: value != null) cfg.settings.main)
57 );
58
59 masterCfOptions =
60 {
61 options,
62 config,
63 name,
64 ...
65 }:
66 {
67 options = {
68 name = lib.mkOption {
69 type = lib.types.str;
70 default = name;
71 example = "smtp";
72 description = ''
73 The name of the service to run. Defaults to the attribute set key.
74 '';
75 };
76
77 type = lib.mkOption {
78 type = lib.types.enum [
79 "inet"
80 "unix"
81 "unix-dgram"
82 "fifo"
83 "pass"
84 ];
85 default = "unix";
86 example = "inet";
87 description = "The type of the service";
88 };
89
90 private = lib.mkOption {
91 type = lib.types.bool;
92 example = false;
93 description = ''
94 Whether the service's sockets and storage directory is restricted to
95 be only available via the mail system. If `null` is
96 given it uses the postfix default `true`.
97 '';
98 };
99
100 privileged = lib.mkOption {
101 type = lib.types.bool;
102 example = true;
103 description = "";
104 };
105
106 chroot = lib.mkOption {
107 type = lib.types.bool;
108 example = true;
109 description = ''
110 Whether the service is chrooted to have only access to the
111 {option}`services.postfix.queueDir` and the closure of
112 store paths specified by the {option}`program` option.
113 '';
114 };
115
116 wakeup = lib.mkOption {
117 type = lib.types.int;
118 example = 60;
119 description = ''
120 Automatically wake up the service after the specified number of
121 seconds. If `0` is given, never wake the service
122 up.
123 '';
124 };
125
126 wakeupUnusedComponent = lib.mkOption {
127 type = lib.types.bool;
128 example = false;
129 description = ''
130 If set to `false` the component will only be woken
131 up if it is used. This is equivalent to postfix' notion of adding a
132 question mark behind the wakeup time in
133 {file}`master.cf`
134 '';
135 };
136
137 maxproc = lib.mkOption {
138 type = lib.types.int;
139 example = 1;
140 description = ''
141 The maximum number of processes to spawn for this service. If the
142 value is `0` it doesn't have any limit. If
143 `null` is given it uses the postfix default of
144 `100`.
145 '';
146 };
147
148 command = lib.mkOption {
149 type = lib.types.str;
150 default = name;
151 example = "smtpd";
152 description = ''
153 A program name specifying a Postfix service/daemon process.
154 By default it's the attribute {option}`name`.
155 '';
156 };
157
158 args = lib.mkOption {
159 type = lib.types.listOf lib.types.str;
160 default = [ ];
161 example = [
162 "-o"
163 "smtp_helo_timeout=5"
164 ];
165 description = ''
166 Arguments to pass to the {option}`command`. There is no shell
167 processing involved and shell syntax is passed verbatim to the
168 process.
169 '';
170 };
171
172 rawEntry = lib.mkOption {
173 type = lib.types.listOf lib.types.str;
174 default = [ ];
175 internal = true;
176 description = ''
177 The raw configuration line for the {file}`master.cf`.
178 '';
179 };
180 };
181
182 config.rawEntry =
183 let
184 mkBool = bool: if bool then "y" else "n";
185 mkArg = arg: "${lib.optionalString (lib.hasPrefix "-" arg) "\n "}${arg}";
186
187 maybeOption = fun: option: if options.${option}.isDefined then fun config.${option} else "-";
188
189 # This is special, because we have two options for this value.
190 wakeup =
191 let
192 wakeupDefined = options.wakeup.isDefined;
193 wakeupUCDefined = options.wakeupUnusedComponent.isDefined;
194 finalValue =
195 toString config.wakeup + lib.optionalString (wakeupUCDefined && !config.wakeupUnusedComponent) "?";
196 in
197 if wakeupDefined then finalValue else "-";
198
199 in
200 [
201 config.name
202 config.type
203 (maybeOption mkBool "private")
204 (maybeOption (b: mkBool (!b)) "privileged")
205 (maybeOption mkBool "chroot")
206 wakeup
207 (maybeOption toString "maxproc")
208 (config.command + " " + lib.concatMapStringsSep " " mkArg config.args)
209 ];
210 };
211
212 masterCfContent =
213 let
214
215 labels = [
216 "# service"
217 "type"
218 "private"
219 "unpriv"
220 "chroot"
221 "wakeup"
222 "maxproc"
223 "command + args"
224 ];
225
226 labelDefaults = [
227 "# "
228 ""
229 "(yes)"
230 "(yes)"
231 "(no)"
232 "(never)"
233 "(100)"
234 ""
235 ""
236 ];
237
238 masterCf = lib.mapAttrsToList (lib.const (lib.getAttr "rawEntry")) cfg.settings.master;
239
240 # A list of the maximum width of the columns across all lines and labels
241 maxWidths =
242 let
243 foldLine =
244 line: acc:
245 let
246 columnLengths = map lib.stringLength line;
247 in
248 lib.zipListsWith lib.max acc columnLengths;
249 # We need to handle the last column specially here, because it's
250 # open-ended (command + args).
251 lines = [
252 labels
253 labelDefaults
254 ]
255 ++ (map (l: lib.init l ++ [ "" ]) masterCf);
256 in
257 lib.foldr foldLine (lib.genList (lib.const 0) (lib.length labels)) lines;
258
259 # Pad a string with spaces from the right (opposite of fixedWidthString).
260 pad =
261 width: str:
262 let
263 padWidth = width - lib.stringLength str;
264 padding = lib.concatStrings (lib.genList (lib.const " ") padWidth);
265 in
266 str + lib.optionalString (padWidth > 0) padding;
267
268 # It's + 2 here, because that's the amount of spacing between columns.
269 fullWidth = lib.foldr (width: acc: acc + width + 2) 0 maxWidths;
270
271 formatLine = line: lib.concatStringsSep " " (lib.zipListsWith pad maxWidths line);
272
273 formattedLabels =
274 let
275 sep = "# " + lib.concatStrings (lib.genList (lib.const "=") (fullWidth + 5));
276 lines = [
277 sep
278 (formatLine labels)
279 (formatLine labelDefaults)
280 sep
281 ];
282 in
283 lib.concatStringsSep "\n" lines;
284
285 in
286 formattedLabels
287 + "\n"
288 + lib.concatMapStringsSep "\n" formatLine masterCf
289 + "\n"
290 + cfg.extraMasterConf;
291
292 headerCheckOptions =
293 { ... }:
294 {
295 options = {
296 pattern = lib.mkOption {
297 type = lib.types.str;
298 default = "/^.*/";
299 example = "/^X-Mailer:/";
300 description = "A regexp pattern matching the header";
301 };
302 action = lib.mkOption {
303 type = lib.types.str;
304 default = "DUNNO";
305 example = "BCC mail@example.com";
306 description = "The action to be executed when the pattern is matched";
307 };
308 };
309 };
310
311 headerChecks =
312 lib.concatStringsSep "\n" (map (x: "${x.pattern} ${x.action}") cfg.headerChecks)
313 + cfg.extraHeaderChecks;
314
315 aliases =
316 let
317 separator = lib.optionalString (cfg.aliasMapType == "hash") ":";
318 in
319 lib.optionalString (cfg.postmasterAlias != "") ''
320 postmaster${separator} ${cfg.postmasterAlias}
321 ''
322 + lib.optionalString (cfg.rootAlias != "") ''
323 root${separator} ${cfg.rootAlias}
324 ''
325 + cfg.extraAliases;
326
327 aliasesFile = pkgs.writeText "postfix-aliases" aliases;
328 canonicalFile = pkgs.writeText "postfix-canonical" cfg.canonical;
329 virtualFile = pkgs.writeText "postfix-virtual" cfg.virtual;
330 localRecipientMapFile = pkgs.writeText "postfix-local-recipient-map" (
331 lib.concatMapStrings (x: x + " ACCEPT\n") cfg.localRecipients
332 );
333 checkClientAccessFile = pkgs.writeText "postfix-check-client-access" cfg.dnsBlacklistOverrides;
334 mainCfFile = pkgs.writeText "postfix-main.cf" mainCf;
335 masterCfFile = pkgs.writeText "postfix-master.cf" masterCfContent;
336 transportFile = pkgs.writeText "postfix-transport" cfg.transport;
337 headerChecksFile = pkgs.writeText "postfix-header-checks" headerChecks;
338
339in
340
341{
342
343 meta.maintainers = with lib.maintainers; [
344 dotlambda
345 hexa
346 ];
347
348 ###### interface
349
350 options = {
351
352 services.postfix = {
353
354 enable = lib.mkOption {
355 type = lib.types.bool;
356 default = false;
357 description = "Whether to run the Postfix mail server.";
358 };
359
360 package = lib.mkPackageOption pkgs "postfix" { };
361
362 enableSmtp = lib.mkOption {
363 type = lib.types.bool;
364 default = true;
365 description = ''
366 Whether to enable the `smtp` service configured in the master.cf.
367
368 This service listens for plain text SMTP connections on port 25
369 and supports explicit TLS via StartTLS.
370
371 It is the primary port used by SMTP servers to exchange mail.
372 '';
373 };
374
375 enableSubmission = lib.mkOption {
376 type = lib.types.bool;
377 default = false;
378 description = "
379 Whether to enable the `submission` service configured in master.cf.
380
381 This service listens for plain text SMTP connections on port 587
382 and supports explicit TLS via StartTLS.
383
384 It is a way for clients to login and submit mails after an inband
385 connection upgrade using StartTLS.
386
387 ::: {.warning}
388 [RFC 8314](https://www.rfc-editor.org/rfc/rfc8314) discourages the use
389 of explicit TLS for mail submissionn.
390 :::
391 ";
392 };
393
394 enableSubmissions = lib.mkOption {
395 type = lib.types.bool;
396 default = false;
397 description = ''
398 Whether to enable the `submissions` service configured in master.cf.
399
400 This service listen for implicit TLS connections on port 465.
401
402 ::: {.info}
403 Per [RFC 8314](https://www.rfc-editor.org/rfc/rfc8314) implicit TLS
404 is recommended for mail submission.
405 :::
406 '';
407 };
408
409 submissionOptions = lib.mkOption {
410 type = with lib.types; attrsOf str;
411 default = {
412 smtpd_tls_security_level = "encrypt";
413 smtpd_sasl_auth_enable = "yes";
414 smtpd_client_restrictions = "permit_sasl_authenticated,reject";
415 milter_macro_daemon_name = "ORIGINATING";
416 };
417 example = {
418 smtpd_tls_security_level = "encrypt";
419 smtpd_sasl_auth_enable = "yes";
420 smtpd_sasl_type = "dovecot";
421 smtpd_client_restrictions = "permit_sasl_authenticated,reject";
422 milter_macro_daemon_name = "ORIGINATING";
423 };
424 description = "Options for the submission config in master.cf";
425 };
426
427 submissionsOptions = lib.mkOption {
428 type = with lib.types; attrsOf str;
429 default = {
430 smtpd_sasl_auth_enable = "yes";
431 smtpd_client_restrictions = "permit_sasl_authenticated,reject";
432 milter_macro_daemon_name = "ORIGINATING";
433 };
434 example = {
435 smtpd_sasl_auth_enable = "yes";
436 smtpd_sasl_type = "dovecot";
437 smtpd_client_restrictions = "permit_sasl_authenticated,reject";
438 milter_macro_daemon_name = "ORIGINATING";
439 };
440 description = ''
441 Options for the submission config via smtps in master.cf.
442
443 smtpd_tls_security_level will be set to encrypt, if it is missing
444 or has one of the values "may" or "none".
445
446 smtpd_tls_wrappermode with value "yes" will be added automatically.
447 '';
448 };
449
450 setSendmail = lib.mkOption {
451 type = lib.types.bool;
452 default = true;
453 description = "Whether to set the system sendmail to postfix's.";
454 };
455
456 user = lib.mkOption {
457 type = lib.types.str;
458 default = "postfix";
459 description = "What to call the Postfix user (must be used only for postfix).";
460 };
461
462 group = lib.mkOption {
463 type = lib.types.str;
464 default = "postfix";
465 description = "What to call the Postfix group (must be used only for postfix).";
466 };
467
468 setgidGroup = lib.mkOption {
469 type = lib.types.str;
470 default = "postdrop";
471 description = ''
472 How to call postfix setgid group (for postdrop). Should
473 be uniquely used group.
474 '';
475 };
476
477 postmasterAlias = lib.mkOption {
478 type = lib.types.str;
479 default = "root";
480 description = ''
481 Who should receive postmaster e-mail. Multiple values can be added by
482 separating values with comma.
483 '';
484 };
485
486 rootAlias = lib.mkOption {
487 type = lib.types.str;
488 default = "";
489 description = ''
490 Who should receive root e-mail. Blank for no redirection.
491 Multiple values can be added by separating values with comma.
492 '';
493 };
494
495 extraAliases = lib.mkOption {
496 type = lib.types.lines;
497 default = "";
498 description = ''
499 Additional entries to put verbatim into aliases file, cf. man-page {manpage}`aliases(8)`.
500 '';
501 };
502
503 aliasMapType = lib.mkOption {
504 type =
505 with lib.types;
506 enum [
507 "hash"
508 "regexp"
509 "pcre"
510 ];
511 default = "hash";
512 example = "regexp";
513 description = "The format the alias map should have. Use regexp if you want to use regular expressions.";
514 };
515
516 settings = {
517 main = lib.mkOption {
518 type = lib.types.submodule {
519 freeformType =
520 with types;
521 attrsOf (
522 nullOr (oneOf [
523 bool
524 int
525 str
526 (listOf str)
527 ])
528 );
529 options = {
530 message_size_limit = mkOption {
531 type = with types; nullOr int;
532 default = 10240000; # 10 MiB
533 example = 52428800; # 50 MiB
534 description = ''
535 Maximum size of an email message in bytes.
536
537 <https://www.postfix.org/postconf.5.html#message_size_limit>
538 '';
539 };
540
541 mydestination = mkOption {
542 type =
543 with types;
544 nullOr (oneOf [
545 str
546 (listOf str)
547 ]);
548 default = [
549 "$myhostname"
550 "localhost.$mydomain"
551 "localhost"
552 ];
553 description = ''
554 List of domain names intended for local delivery using /etc/passwd and /etc/aliases.
555
556 ::: {.warning}
557 Do not include [virtual](https://www.postfix.org/VIRTUAL_README.html) domains in this list.
558 :::
559
560 <https://www.postfix.org/postconf.5.html#mydestination>
561 '';
562 };
563
564 myhostname = mkOption {
565 type = with types; nullOr types.str;
566 default = null;
567 example = "mail.example.com";
568 description = ''
569 The internet hostname of this mail system.
570
571 Leave unset to default to the system hostname with the {option}`mydomain` suffix.
572
573 <https://www.postfix.org/postconf.5.html#myhostname>
574 '';
575 };
576
577 mynetworks = mkOption {
578 type = with types; nullOr (listOf str);
579 default = null;
580 example = [
581 "127.0.0.0/8"
582 "::1"
583 ];
584 description = ''
585 List of trusted remote SMTP clients, that are allowed to relay mail.
586
587 Leave unset to let Postfix populate this list based on the {option}`mynetworks_style` setting.
588
589 <https://www.postfix.org/postconf.5.html#mynetworks>
590 '';
591 };
592
593 mynetworks_style = mkOption {
594 type =
595 with types;
596 nullOr (enum [
597 "host"
598 "subnet"
599 "class"
600 ]);
601 default = "host";
602 description = ''
603 The method used for generating the default value for {option}`mynetworks`, if that option is unset.
604
605 <https://www.postfix.org/postconf.5.html#mynetworks_style>
606 '';
607 };
608
609 recipient_delimiter = lib.mkOption {
610 type = with types; nullOr str;
611 default = "";
612 example = "+";
613 description = ''
614 Set of characters used as the delimiters for address extensions.
615
616 This allows creating different forwarding rules per extension.
617
618 <https://www.postfix.org/postconf.5.html#recipient_delimiter>
619 '';
620 };
621
622 relayhost = mkOption {
623 type = with types; nullOr (listOf str);
624 default = [ ];
625 example = [ "[relay.example.com]:587" ];
626 description = ''
627 List of hosts to use for relaying outbound mail.
628
629 ::: {.note}
630 Putting the hostname in angled brackets, e.g. `[relay.example.com]`, turns off MX and SRV lookups for the hostname.
631 :::
632
633 <https://www.postfix.org/postconf.5.html#relayhost>
634 '';
635 };
636
637 relay_domains = mkOption {
638 type = with types; nullOr (listOf str);
639 default = [ ];
640 example = [ "lists.example.com" ];
641 description = ''
642 List of domains delivered via the relay transport.
643
644 <https://www.postfix.org/postconf.5.html#relay_domains>
645 '';
646 };
647
648 smtp_tls_CAfile = mkOption {
649 type = types.path;
650 default = config.security.pki.caBundle;
651 defaultText = literalExpression ''
652 config.security.pki.caBundle
653 '';
654 example = literalExpression ''
655 ''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
656 '';
657 description = ''
658 File containing CA certificates of root CAs trusted to sign either remote SMTP server certificates or intermediate CA certificates.
659
660 Defaults to the system CA bundle that is managed through the `security.pki` options.
661
662 <https://www.postfix.org/postconf.5.html#smtp_tls_CAfile>
663 '';
664 };
665
666 smtp_tls_security_level = mkOption {
667 type = types.enum [
668 "none"
669 "may"
670 "encrypt"
671 "dane"
672 "dane-only"
673 "fingerprint"
674 "verify"
675 "secure"
676 ];
677 default = "may";
678 description = ''
679 The client TLS security level.
680
681 ::: {.tip}
682 Use `dane` with a local DNSSEC validating DNS resolver enabled.
683 :::
684
685 <https://www.postfix.org/postconf.5.html#smtp_tls_security_level>
686 '';
687 };
688
689 smtpd_tls_chain_files = mkOption {
690 type = with types; listOf path;
691 default = [ ];
692 example = [
693 "/var/lib/acme/mail.example.com/privkey.pem"
694 "/var/lib/acme/mail.example.com/fullchain.pem"
695 ];
696 description = ''
697 List of paths to the server private keys and certificates.
698
699 ::: {.caution}
700 The order of items matters and a private key must always be followed by the corresponding certificate.
701 :::
702
703 <https://www.postfix.org/postconf.5.html#smtpd_tls_chain_files>
704 '';
705 };
706
707 smtpd_tls_security_level = mkOption {
708 type = types.enum [
709 "none"
710 "may"
711 "encrypt"
712 ];
713 default =
714 if config.services.postfix.settings.main.smtpd_tls_chain_files != [ ] then "may" else "none";
715 defaultText = lib.literalExpression ''
716 if config.services.postfix.settings.main.smtpd_tls_chain_files != [ ] then "may" else "none"
717 '';
718 example = "may";
719 description = ''
720 The server TLS security level. Enable TLS by configuring at least `may`.
721
722 <https://www.postfix.org/postconf.5.html#smtpd_tls_security_level>
723 '';
724 };
725 };
726 };
727
728 description = ''
729 The main.cf configuration file as key value set.
730
731 Null values will not be rendered.
732
733 ::: {.tip}
734 Check `postconf -d` for the default values of all settings.
735 :::
736 '';
737 example = {
738 mail_owner = "postfix";
739 smtp_tls_security_level = "may";
740 };
741 };
742
743 master = lib.mkOption {
744 type = lib.types.attrsOf (lib.types.submodule masterCfOptions);
745 default = { };
746 example = {
747 submission = {
748 type = "inet";
749 args = [
750 "-o"
751 "smtpd_tls_security_level=encrypt"
752 ];
753 };
754 };
755 description = ''
756 The {file}`master.cf` configuration file as an attribute set of service
757 defitions
758
759 ::: {.tip}
760 Check <https://www.postfix.org/master.5.html> for possible settings.
761 :::
762 '';
763 };
764
765 };
766
767 canonical = lib.mkOption {
768 type = lib.types.lines;
769 default = "";
770 description = ''
771 Entries for the {manpage}`canonical(5)` table.
772 '';
773 };
774
775 virtual = lib.mkOption {
776 type = lib.types.lines;
777 default = "";
778 description = ''
779 Entries for the virtual alias map, cf. man-page {manpage}`virtual(5)`.
780 '';
781 };
782
783 virtualMapType = lib.mkOption {
784 type = lib.types.enum [
785 "hash"
786 "regexp"
787 "pcre"
788 ];
789 default = "hash";
790 description = ''
791 What type of virtual alias map file to use. Use `"regexp"` for regular expressions.
792 '';
793 };
794
795 localRecipients = lib.mkOption {
796 type = with lib.types; nullOr (listOf str);
797 default = null;
798 description = ''
799 List of accepted local users. Specify a bare username, an
800 `"@domain.tld"` wild-card, or a complete
801 `"user@domain.tld"` address. If set, these names end
802 up in the local recipient map -- see the {manpage}`local(8)` man-page -- and
803 effectively replace the system user database lookup that's otherwise
804 used by default.
805 '';
806 };
807
808 transport = lib.mkOption {
809 default = "";
810 type = lib.types.lines;
811 description = ''
812 Entries for the transport map, cf. man-page {manpage}`transport(5)`.
813 '';
814 };
815
816 dnsBlacklists = lib.mkOption {
817 default = [ ];
818 type = with lib.types; listOf str;
819 description = "dns blacklist servers to use with smtpd_client_restrictions";
820 };
821
822 dnsBlacklistOverrides = lib.mkOption {
823 default = "";
824 type = lib.types.lines;
825 description = "contents of check_client_access for overriding dnsBlacklists";
826 };
827
828 extraMasterConf = lib.mkOption {
829 type = lib.types.lines;
830 default = "";
831 example = "submission inet n - n - - smtpd";
832 description = "Extra lines to append to the generated master.cf file.";
833 };
834
835 enableHeaderChecks = lib.mkOption {
836 type = lib.types.bool;
837 default = false;
838 example = true;
839 description = "Whether to enable postfix header checks";
840 };
841
842 headerChecks = lib.mkOption {
843 type = lib.types.listOf (lib.types.submodule headerCheckOptions);
844 default = [ ];
845 example = [
846 {
847 pattern = "/^X-Spam-Flag:/";
848 action = "REDIRECT spam@example.com";
849 }
850 ];
851 description = "Postfix header checks.";
852 };
853
854 extraHeaderChecks = lib.mkOption {
855 type = lib.types.lines;
856 default = "";
857 example = "/^X-Spam-Flag:/ REDIRECT spam@example.com";
858 description = "Extra lines to /etc/postfix/header_checks file.";
859 };
860
861 aliasFiles = lib.mkOption {
862 type = lib.types.attrsOf lib.types.path;
863 default = { };
864 description = "Aliases' tables to be compiled and placed into /var/lib/postfix/conf.";
865 };
866
867 mapFiles = lib.mkOption {
868 type = lib.types.attrsOf lib.types.path;
869 default = { };
870 description = "Maps to be compiled and placed into /var/lib/postfix/conf.";
871 };
872
873 };
874
875 };
876
877 ###### implementation
878
879 config = lib.mkIf config.services.postfix.enable (
880 lib.mkMerge [
881 {
882
883 environment = {
884 etc.postfix.source = "/var/lib/postfix/conf";
885
886 # This makes it comfortable to run 'postqueue/postdrop' for example.
887 systemPackages = [ cfg.package ];
888 };
889
890 services.mail.sendmailSetuidWrapper = lib.mkIf config.services.postfix.setSendmail {
891 program = "sendmail";
892 source = lib.getExe' cfg.package "sendmail";
893 owner = "root";
894 group = setgidGroup;
895 setuid = false;
896 setgid = true;
897 };
898
899 security.wrappers.mailq = {
900 program = "mailq";
901 source = lib.getExe' cfg.package "mailq";
902 owner = "root";
903 group = setgidGroup;
904 setuid = false;
905 setgid = true;
906 };
907
908 security.wrappers.postqueue = {
909 program = "postqueue";
910 source = lib.getExe' cfg.package "postqueue";
911 owner = "root";
912 group = setgidGroup;
913 setuid = false;
914 setgid = true;
915 };
916
917 security.wrappers.postdrop = {
918 program = "postdrop";
919 source = lib.getExe' cfg.package "postdrop";
920 owner = "root";
921 group = setgidGroup;
922 setuid = false;
923 setgid = true;
924 };
925
926 users.users = lib.optionalAttrs (user == "postfix") {
927 postfix = {
928 description = "Postfix mail server user";
929 uid = config.ids.uids.postfix;
930 group = group;
931 };
932 };
933
934 users.groups =
935 lib.optionalAttrs (group == "postfix") {
936 ${group}.gid = config.ids.gids.postfix;
937 }
938 // lib.optionalAttrs (setgidGroup == "postdrop") {
939 ${setgidGroup}.gid = config.ids.gids.postdrop;
940 };
941
942 systemd.services.postfix-setup = {
943 description = "Setup for Postfix mail server";
944 serviceConfig.RemainAfterExit = true;
945 serviceConfig.Type = "oneshot";
946 script = ''
947 # Backwards compatibility
948 if [ ! -d /var/lib/postfix ] && [ -d /var/postfix ]; then
949 mkdir -p /var/lib
950 mv /var/postfix /var/lib/postfix
951 fi
952
953 # All permissions set according ${cfg.package}/etc/postfix/postfix-files script
954 mkdir -p /var/lib/postfix /var/lib/postfix/queue/{pid,public,maildrop}
955 chmod 0755 /var/lib/postfix
956 chown root:root /var/lib/postfix
957
958 rm -rf /var/lib/postfix/conf
959 mkdir -p /var/lib/postfix/conf
960 chmod 0755 /var/lib/postfix/conf
961 ln -sf ${cfg.package}/etc/postfix/postfix-files /var/lib/postfix/conf/postfix-files
962 ln -sf ${mainCfFile} /var/lib/postfix/conf/main.cf
963 ln -sf ${masterCfFile} /var/lib/postfix/conf/master.cf
964
965 ${lib.concatStringsSep "\n" (
966 lib.mapAttrsToList (to: from: ''
967 ln -sf ${from} /var/lib/postfix/conf/${to}
968 ${lib.getExe' cfg.package "postalias"} -o -p /var/lib/postfix/conf/${to}
969 '') cfg.aliasFiles
970 )}
971 ${lib.concatStringsSep "\n" (
972 lib.mapAttrsToList (to: from: ''
973 ln -sf ${from} /var/lib/postfix/conf/${to}
974 ${lib.getExe' cfg.package "postmap"} -o -p /var/lib/postfix/conf/${to}
975 '') cfg.mapFiles
976 )}
977
978 mkdir -p /var/spool/mail
979 chown root:root /var/spool/mail
980 chmod a+rwxt /var/spool/mail
981 ln -sf /var/spool/mail /var/
982
983 #Finally delegate to postfix checking remain directories in /var/lib/postfix and set permissions on them
984 ${lib.getExe' cfg.package "postfix"} set-permissions config_directory=/var/lib/postfix/conf
985 '';
986 };
987
988 systemd.services.postfix = {
989 description = "Postfix mail server";
990
991 documentation = [ "man:postfix(1)" ];
992 wantedBy = [ "multi-user.target" ];
993 after = [
994 "network.target"
995 "postfix-setup.service"
996 ];
997 requires = [ "postfix-setup.service" ];
998 path = [ cfg.package ];
999
1000 serviceConfig = {
1001 Type = "forking";
1002 Restart = "always";
1003 PIDFile = "/var/lib/postfix/queue/pid/master.pid";
1004 ExecStart = "${lib.getExe' cfg.package "postfix"} start";
1005 ExecStop = "${lib.getExe' cfg.package "postfix"} stop";
1006 ExecReload = "${lib.getExe' cfg.package "postfix"} reload";
1007
1008 # Hardening
1009 PrivateTmp = true;
1010 PrivateDevices = true;
1011 ProtectSystem = "full";
1012 CapabilityBoundingSet = [ "~CAP_NET_ADMIN CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_MODULE" ];
1013 MemoryDenyWriteExecute = true;
1014 ProtectKernelModules = true;
1015 ProtectKernelTunables = true;
1016 ProtectControlGroups = true;
1017 RestrictAddressFamilies = [
1018 "AF_INET"
1019 "AF_INET6"
1020 "AF_NETLINK"
1021 "AF_UNIX"
1022 ];
1023 RestrictNamespaces = true;
1024 RestrictRealtime = true;
1025 };
1026 };
1027
1028 services.postfix.settings.main =
1029 (lib.mapAttrs (_: v: lib.mkDefault v) {
1030 compatibility_level = cfg.package.version;
1031 mail_owner = cfg.user;
1032 default_privs = "nobody";
1033
1034 # NixOS specific locations
1035 data_directory = "/var/lib/postfix/data";
1036 queue_directory = "/var/lib/postfix/queue";
1037
1038 # Default location of everything in package
1039 meta_directory = "${cfg.package}/etc/postfix";
1040 command_directory = "${cfg.package}/bin";
1041 sample_directory = "/etc/postfix";
1042 newaliases_path = lib.getExe' cfg.package "newaliases";
1043 mailq_path = lib.getExe' cfg.package "mailq";
1044 readme_directory = false;
1045 sendmail_path = lib.getExe' cfg.package "sendmail";
1046 daemon_directory = "${cfg.package}/libexec/postfix";
1047 manpage_directory = "${cfg.package}/share/man";
1048 html_directory = "${cfg.package}/share/postfix/doc/html";
1049 shlib_directory = false;
1050 mail_spool_directory = "/var/spool/mail/";
1051 setgid_group = cfg.setgidGroup;
1052 })
1053 // lib.optionalAttrs haveAliases { alias_maps = [ "${cfg.aliasMapType}:/etc/postfix/aliases" ]; }
1054 // lib.optionalAttrs haveTransport { transport_maps = [ "hash:/etc/postfix/transport" ]; }
1055 // lib.optionalAttrs haveVirtual {
1056 virtual_alias_maps = [ "${cfg.virtualMapType}:/etc/postfix/virtual" ];
1057 }
1058 // lib.optionalAttrs haveLocalRecipients {
1059 local_recipient_maps = [
1060 "hash:/etc/postfix/local_recipients"
1061 ]
1062 ++ lib.optional haveAliases "$alias_maps";
1063 }
1064 // lib.optionalAttrs (cfg.dnsBlacklists != [ ]) { smtpd_client_restrictions = clientRestrictions; }
1065 // lib.optionalAttrs cfg.enableHeaderChecks {
1066 header_checks = [ "regexp:/etc/postfix/header_checks" ];
1067 };
1068
1069 services.postfix.settings.master = {
1070 pickup = {
1071 private = false;
1072 wakeup = 60;
1073 maxproc = 1;
1074 };
1075 cleanup = {
1076 private = false;
1077 maxproc = 0;
1078 };
1079 qmgr = {
1080 private = false;
1081 wakeup = 300;
1082 maxproc = 1;
1083 };
1084 tlsmgr = {
1085 wakeup = 1000;
1086 wakeupUnusedComponent = false;
1087 maxproc = 1;
1088 };
1089 rewrite = {
1090 command = "trivial-rewrite";
1091 };
1092 bounce = {
1093 maxproc = 0;
1094 };
1095 defer = {
1096 maxproc = 0;
1097 command = "bounce";
1098 };
1099 trace = {
1100 maxproc = 0;
1101 command = "bounce";
1102 };
1103 verify = {
1104 maxproc = 1;
1105 };
1106 flush = {
1107 private = false;
1108 wakeup = 1000;
1109 wakeupUnusedComponent = false;
1110 maxproc = 0;
1111 };
1112 proxymap = {
1113 command = "proxymap";
1114 };
1115 proxywrite = {
1116 maxproc = 1;
1117 command = "proxymap";
1118 };
1119 showq = {
1120 private = false;
1121 };
1122 error = { };
1123 retry = {
1124 command = "error";
1125 };
1126 discard = { };
1127 local = {
1128 privileged = true;
1129 };
1130 virtual = {
1131 privileged = true;
1132 };
1133 lmtp = {
1134 };
1135 anvil = {
1136 maxproc = 1;
1137 };
1138 scache = {
1139 maxproc = 1;
1140 };
1141 }
1142 // lib.optionalAttrs cfg.enableSubmission {
1143 submission = {
1144 type = "inet";
1145 private = false;
1146 command = "smtpd";
1147 args =
1148 let
1149 mkKeyVal = opt: val: [
1150 "-o"
1151 (opt + "=" + val)
1152 ];
1153 in
1154 lib.concatLists (lib.mapAttrsToList mkKeyVal cfg.submissionOptions);
1155 };
1156 }
1157 // lib.optionalAttrs cfg.enableSmtp {
1158 smtp_inet = {
1159 name = "smtp";
1160 type = "inet";
1161 private = false;
1162 command = "smtpd";
1163 };
1164 smtp = { };
1165 relay = {
1166 command = "smtp";
1167 args = [
1168 "-o"
1169 "smtp_fallback_relay="
1170 ];
1171 };
1172 }
1173 // lib.optionalAttrs cfg.enableSubmissions {
1174 submissions = {
1175 type = "inet";
1176 private = false;
1177 command = "smtpd";
1178 args =
1179 let
1180 mkKeyVal = opt: val: [
1181 "-o"
1182 (opt + "=" + val)
1183 ];
1184 adjustSmtpTlsSecurityLevel =
1185 !(cfg.submissionsOptions ? smtpd_tls_security_level)
1186 || cfg.submissionsOptions.smtpd_tls_security_level == "none"
1187 || cfg.submissionsOptions.smtpd_tls_security_level == "may";
1188 submissionsOptions =
1189 cfg.submissionsOptions
1190 // {
1191 smtpd_tls_wrappermode = "yes";
1192 }
1193 // lib.optionalAttrs adjustSmtpTlsSecurityLevel {
1194 smtpd_tls_security_level = "encrypt";
1195 };
1196 in
1197 lib.concatLists (lib.mapAttrsToList mkKeyVal submissionsOptions);
1198 };
1199 };
1200 }
1201
1202 (lib.mkIf haveAliases {
1203 services.postfix.aliasFiles.aliases = aliasesFile;
1204 })
1205 (lib.mkIf haveCanonical {
1206 services.postfix.mapFiles.canonical = canonicalFile;
1207 })
1208 (lib.mkIf haveTransport {
1209 services.postfix.mapFiles.transport = transportFile;
1210 })
1211 (lib.mkIf haveVirtual {
1212 services.postfix.mapFiles.virtual = virtualFile;
1213 })
1214 (lib.mkIf haveLocalRecipients {
1215 services.postfix.mapFiles.local_recipients = localRecipientMapFile;
1216 })
1217 (lib.mkIf cfg.enableHeaderChecks {
1218 services.postfix.mapFiles.header_checks = headerChecksFile;
1219 })
1220 (lib.mkIf (cfg.dnsBlacklists != [ ]) {
1221 services.postfix.mapFiles.client_access = checkClientAccessFile;
1222 })
1223 ]
1224 );
1225
1226 imports = [
1227 (lib.mkRemovedOptionModule [ "services" "postfix" "sslCACert" ]
1228 "services.postfix.sslCACert was replaced by services.postfix.tlsTrustedAuthorities. In case you intend that your server should validate requested client certificates use services.postfix.settings.main.smtp_tls_CAfile."
1229 )
1230 (lib.mkRemovedOptionModule [ "services" "postfix" "sslCert" ]
1231 "services.postfix.sslCert was removed. Use services.postfix.settings.main.smtpd_tls_chain_files for the server certificate, or services.postfix.settings.main.smtp_tls_chain_files for the client certificate."
1232 )
1233 (lib.mkRemovedOptionModule [ "services" "postfix" "sslKey" ]
1234 "services.postfix.sslKey was removed. Use services.postfix.settings.main.smtpd_tls_chain_files for server private key, or services.postfix.settings.main.smtp_tls_chain_files for the client private key."
1235 )
1236 (lib.mkRemovedOptionModule [ "services" "postfix" "lookupMX" ]
1237 "services.postfix.lookupMX was removed. Use services.postfix.settings.main.relayhost and put the hostname in angled brackets, if you need to turn off MX and SRV lookups."
1238 )
1239 (lib.mkRemovedOptionModule [ "services" "postfix" "relayHost" ]
1240 "services.postfix.relayHost was removed in favor of services.postfix.settings.main.relayhost, which now takes a list of host:port."
1241 )
1242 (lib.mkRemovedOptionModule [ "services" "postfix" "relayPort" ]
1243 "services.postfix.relayPort was removed in favor of services.postfix.settings.main.relayhost, which now takes a list of host:port."
1244 )
1245 (lib.mkRemovedOptionModule [ "services" "postfix" "extraConfig" ]
1246 "services.postfix.extraConfig was replaced by the structured freeform service.postfix.settings.main option."
1247 )
1248 (lib.mkRenamedOptionModule
1249 [ "services" "postfix" "networks" ]
1250 [ "services" "postfix" "settings" "main" "mynetworks" ]
1251 )
1252 (lib.mkRenamedOptionModule
1253 [ "services" "postfix" "networksStyle" ]
1254 [ "services" "postfix" "settings" "main" "mynetworks_style" ]
1255 )
1256 (lib.mkRenamedOptionModule
1257 [ "services" "postfix" "hostname" ]
1258 [ "services" "postfix" "settings" "main" "myhostname" ]
1259 )
1260 (lib.mkRenamedOptionModule
1261 [ "services" "postfix" "domain" ]
1262 [ "services" "postfix" "settings" "main" "mydomain" ]
1263 )
1264 (lib.mkRenamedOptionModule
1265 [ "services" "postfix" "origin" ]
1266 [ "services" "postfix" "settings" "main" "myorigin" ]
1267 )
1268 (lib.mkRenamedOptionModule
1269 [ "services" "postfix" "destination" ]
1270 [ "services" "postfix" "settings" "main" "mydestination" ]
1271 )
1272 (lib.mkRenamedOptionModule
1273 [ "services" "postfix" "relayDomains" ]
1274 [ "services" "postfix" "settings" "main" "relay_domains" ]
1275 )
1276 (lib.mkRenamedOptionModule
1277 [ "services" "postfix" "recipientDelimiter" ]
1278 [ "services" "postfix" "settings" "main" "recipient_delimiter" ]
1279 )
1280 (lib.mkRenamedOptionModule
1281 [ "services" "postfix" "tlsTrustedAuthoriies" ]
1282 [ "services" "postfix" "settings" "main" "smtp_tls_CAfile" ]
1283 )
1284 (lib.mkRenamedOptionModule
1285 [ "services" "postfix" "config" ]
1286 [ "services" "postfix" "settings" "main" ]
1287 )
1288 (lib.mkRenamedOptionModule
1289 [ "services" "postfix" "masterConfig" ]
1290 [ "services" "postfix" "settings" "master" ]
1291 )
1292
1293 (lib.mkChangedOptionModule
1294 [ "services" "postfix" "useDane" ]
1295 [ "services" "postfix" "settings" "main" "smtp_tls_security_level" ]
1296 (config: lib.mkIf config.services.postfix.useDane "dane")
1297 )
1298 (lib.mkRenamedOptionModule [ "services" "postfix" "useSrs" ] [ "services" "pfix-srsd" "enable" ])
1299 ];
1300}