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