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 = ''
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 = "The type of the service";
58 };
59
60 private = mkOption {
61 type = types.bool;
62 example = false;
63 description = ''
64 Whether the service's sockets and storage directory is restricted to
65 be only available via the mail system. If <literal>null</literal> is
66 given it uses the postfix default <literal>true</literal>.
67 '';
68 };
69
70 privileged = mkOption {
71 type = types.bool;
72 example = true;
73 description = "";
74 };
75
76 chroot = mkOption {
77 type = types.bool;
78 example = true;
79 description = ''
80 Whether the service is chrooted to have only access to the
81 <option>services.postfix.queueDir</option> and the closure of
82 store paths specified by the <option>program</option> option.
83 '';
84 };
85
86 wakeup = mkOption {
87 type = types.int;
88 example = 60;
89 description = ''
90 Automatically wake up the service after the specified number of
91 seconds. If <literal>0</literal> is given, never wake the service
92 up.
93 '';
94 };
95
96 wakeupUnusedComponent = mkOption {
97 type = types.bool;
98 example = false;
99 description = ''
100 If set to <literal>false</literal> 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 <filename>master.cf</filename>
104 '';
105 };
106
107 maxproc = mkOption {
108 type = types.int;
109 example = 1;
110 description = ''
111 The maximum number of processes to spawn for this service. If the
112 value is <literal>0</literal> it doesn't have any limit. If
113 <literal>null</literal> is given it uses the postfix default of
114 <literal>100</literal>.
115 '';
116 };
117
118 command = mkOption {
119 type = types.str;
120 default = name;
121 example = "smtpd";
122 description = ''
123 A program name specifying a Postfix service/daemon process.
124 By default it's the attribute <option>name</option>.
125 '';
126 };
127
128 args = mkOption {
129 type = types.listOf types.str;
130 default = [];
131 example = [ "-o" "smtp_helo_timeout=5" ];
132 description = ''
133 Arguments to pass to the <option>command</option>. 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 = ''
144 The raw configuration line for the <filename>master.cf</filename>.
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 fold 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 = fold (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 = "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 = "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 seperator = if cfg.aliasMapType == "hash" then ":" else ""; in
238 optionalString (cfg.postmasterAlias != "") ''
239 postmaster${seperator} ${cfg.postmasterAlias}
240 ''
241 + optionalString (cfg.rootAlias != "") ''
242 root${seperator} ${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 = "Whether to run the Postfix mail server.";
271 };
272
273 enableSmtp = mkOption {
274 type = types.bool;
275 default = true;
276 description = "Whether to enable smtp in master.cf.";
277 };
278
279 enableSubmission = mkOption {
280 type = types.bool;
281 default = false;
282 description = "Whether to enable smtp submission.";
283 };
284
285 enableSubmissions = mkOption {
286 type = types.bool;
287 default = false;
288 description = ''
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 = types.attrs;
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 = "Options for the submission config in master.cf";
312 };
313
314 submissionsOptions = mkOption {
315 type = types.attrs;
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 = ''
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 = "Whether to set the system sendmail to postfix's.";
341 };
342
343 user = mkOption {
344 type = types.str;
345 default = "postfix";
346 description = "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 = "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 = "
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 = "
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 = "
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 ="
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 ="
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 ="
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 = "
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 = "
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 = "
433 Mail relay for outbound mail.
434 ";
435 };
436
437 relayPort = mkOption {
438 type = types.int;
439 default = 25;
440 description = "
441 SMTP port for relay mail relay.
442 ";
443 };
444
445 lookupMX = mkOption {
446 type = types.bool;
447 default = false;
448 description = "
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 = "
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 = "
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 = "
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 = "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 = ''
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 = "
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 description = ''
509 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.
510 '';
511 };
512
513 sslCert = mkOption {
514 type = types.str;
515 default = "";
516 description = "SSL certificate to use.";
517 };
518
519 sslKey = mkOption {
520 type = types.str;
521 default = "";
522 description = "SSL key to use.";
523 };
524
525 recipientDelimiter = mkOption {
526 type = types.str;
527 default = "";
528 example = "+";
529 description = "
530 Delimiter for address extension: so mail to user+test can be handled by ~user/.forward+test
531 ";
532 };
533
534 canonical = mkOption {
535 type = types.lines;
536 default = "";
537 description = ''
538 Entries for the <citerefentry><refentrytitle>canonical</refentrytitle>
539 <manvolnum>5</manvolnum></citerefentry> table.
540 '';
541 };
542
543 virtual = mkOption {
544 type = types.lines;
545 default = "";
546 description = "
547 Entries for the virtual alias map, cf. man-page virtual(8).
548 ";
549 };
550
551 virtualMapType = mkOption {
552 type = types.enum ["hash" "regexp" "pcre"];
553 default = "hash";
554 description = ''
555 What type of virtual alias map file to use. Use <literal>"regexp"</literal> for regular expressions.
556 '';
557 };
558
559 localRecipients = mkOption {
560 type = with types; nullOr (listOf str);
561 default = null;
562 description = ''
563 List of accepted local users. Specify a bare username, an
564 <literal>"@domain.tld"</literal> wild-card, or a complete
565 <literal>"user@domain.tld"</literal> 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 = "
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 = "dns blacklist servers to use with smtpd_client_restrictions";
584 };
585
586 dnsBlacklistOverrides = mkOption {
587 default = "";
588 type = types.lines;
589 description = "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 = ''
602 An attribute set of service options, which correspond to the service
603 definitions usually done within the Postfix
604 <filename>master.cf</filename> file.
605 '';
606 };
607
608 extraMasterConf = mkOption {
609 type = types.lines;
610 default = "";
611 example = "submission inet n - n - - smtpd";
612 description = "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 = "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 = "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 = "Extra lines to /etc/postfix/header_checks file.";
634 };
635
636 aliasFiles = mkOption {
637 type = types.attrsOf types.path;
638 default = {};
639 description = "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 = "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 = "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 group = setgidGroup;
677 setuid = false;
678 setgid = true;
679 };
680
681 security.wrappers.mailq = {
682 program = "mailq";
683 source = "${pkgs.postfix}/bin/mailq";
684 group = setgidGroup;
685 setuid = false;
686 setgid = true;
687 };
688
689 security.wrappers.postqueue = {
690 program = "postqueue";
691 source = "${pkgs.postfix}/bin/postqueue";
692 group = setgidGroup;
693 setuid = false;
694 setgid = true;
695 };
696
697 security.wrappers.postdrop = {
698 program = "postdrop";
699 source = "${pkgs.postfix}/bin/postdrop";
700 group = setgidGroup;
701 setuid = false;
702 setgid = true;
703 };
704
705 users.users = optionalAttrs (user == "postfix")
706 { postfix = {
707 description = "Postfix mail server user";
708 uid = config.ids.uids.postfix;
709 group = group;
710 };
711 };
712
713 users.groups =
714 optionalAttrs (group == "postfix")
715 { ${group}.gid = config.ids.gids.postfix;
716 }
717 // optionalAttrs (setgidGroup == "postdrop")
718 { ${setgidGroup}.gid = config.ids.gids.postdrop;
719 };
720
721 systemd.services.postfix =
722 { description = "Postfix mail server";
723
724 wantedBy = [ "multi-user.target" ];
725 after = [ "network.target" ];
726 path = [ pkgs.postfix ];
727
728 serviceConfig = {
729 Type = "forking";
730 Restart = "always";
731 PIDFile = "/var/lib/postfix/queue/pid/master.pid";
732 ExecStart = "${pkgs.postfix}/bin/postfix start";
733 ExecStop = "${pkgs.postfix}/bin/postfix stop";
734 ExecReload = "${pkgs.postfix}/bin/postfix reload";
735 };
736
737 preStart = ''
738 # Backwards compatibility
739 if [ ! -d /var/lib/postfix ] && [ -d /var/postfix ]; then
740 mkdir -p /var/lib
741 mv /var/postfix /var/lib/postfix
742 fi
743
744 # All permissions set according ${pkgs.postfix}/etc/postfix/postfix-files script
745 mkdir -p /var/lib/postfix /var/lib/postfix/queue/{pid,public,maildrop}
746 chmod 0755 /var/lib/postfix
747 chown root:root /var/lib/postfix
748
749 rm -rf /var/lib/postfix/conf
750 mkdir -p /var/lib/postfix/conf
751 chmod 0755 /var/lib/postfix/conf
752 ln -sf ${pkgs.postfix}/etc/postfix/postfix-files /var/lib/postfix/conf/postfix-files
753 ln -sf ${mainCfFile} /var/lib/postfix/conf/main.cf
754 ln -sf ${masterCfFile} /var/lib/postfix/conf/master.cf
755
756 ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
757 ln -sf ${from} /var/lib/postfix/conf/${to}
758 ${pkgs.postfix}/bin/postalias /var/lib/postfix/conf/${to}
759 '') cfg.aliasFiles)}
760 ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
761 ln -sf ${from} /var/lib/postfix/conf/${to}
762 ${pkgs.postfix}/bin/postmap /var/lib/postfix/conf/${to}
763 '') cfg.mapFiles)}
764
765 mkdir -p /var/spool/mail
766 chown root:root /var/spool/mail
767 chmod a+rwxt /var/spool/mail
768 ln -sf /var/spool/mail /var/
769
770 #Finally delegate to postfix checking remain directories in /var/lib/postfix and set permissions on them
771 ${pkgs.postfix}/bin/postfix set-permissions config_directory=/var/lib/postfix/conf
772 '';
773 };
774
775 services.postfix.config = (mapAttrs (_: v: mkDefault v) {
776 compatibility_level = pkgs.postfix.version;
777 mail_owner = cfg.user;
778 default_privs = "nobody";
779
780 # NixOS specific locations
781 data_directory = "/var/lib/postfix/data";
782 queue_directory = "/var/lib/postfix/queue";
783
784 # Default location of everything in package
785 meta_directory = "${pkgs.postfix}/etc/postfix";
786 command_directory = "${pkgs.postfix}/bin";
787 sample_directory = "/etc/postfix";
788 newaliases_path = "${pkgs.postfix}/bin/newaliases";
789 mailq_path = "${pkgs.postfix}/bin/mailq";
790 readme_directory = false;
791 sendmail_path = "${pkgs.postfix}/bin/sendmail";
792 daemon_directory = "${pkgs.postfix}/libexec/postfix";
793 manpage_directory = "${pkgs.postfix}/share/man";
794 html_directory = "${pkgs.postfix}/share/postfix/doc/html";
795 shlib_directory = false;
796 mail_spool_directory = "/var/spool/mail/";
797 setgid_group = cfg.setgidGroup;
798 })
799 // optionalAttrs (cfg.relayHost != "") { relayhost = if cfg.lookupMX
800 then "${cfg.relayHost}:${toString cfg.relayPort}"
801 else "[${cfg.relayHost}]:${toString cfg.relayPort}"; }
802 // optionalAttrs config.networking.enableIPv6 { inet_protocols = mkDefault "all"; }
803 // optionalAttrs (cfg.networks != null) { mynetworks = cfg.networks; }
804 // optionalAttrs (cfg.networksStyle != "") { mynetworks_style = cfg.networksStyle; }
805 // optionalAttrs (cfg.hostname != "") { myhostname = cfg.hostname; }
806 // optionalAttrs (cfg.domain != "") { mydomain = cfg.domain; }
807 // optionalAttrs (cfg.origin != "") { myorigin = cfg.origin; }
808 // optionalAttrs (cfg.destination != null) { mydestination = cfg.destination; }
809 // optionalAttrs (cfg.relayDomains != null) { relay_domains = cfg.relayDomains; }
810 // optionalAttrs (cfg.recipientDelimiter != "") { recipient_delimiter = cfg.recipientDelimiter; }
811 // optionalAttrs haveAliases { alias_maps = [ "${cfg.aliasMapType}:/etc/postfix/aliases" ]; }
812 // optionalAttrs haveTransport { transport_maps = [ "hash:/etc/postfix/transport" ]; }
813 // optionalAttrs haveVirtual { virtual_alias_maps = [ "${cfg.virtualMapType}:/etc/postfix/virtual" ]; }
814 // optionalAttrs haveLocalRecipients { local_recipient_maps = [ "hash:/etc/postfix/local_recipients" ] ++ optional haveAliases "$alias_maps"; }
815 // optionalAttrs (cfg.dnsBlacklists != []) { smtpd_client_restrictions = clientRestrictions; }
816 // optionalAttrs cfg.useSrs {
817 sender_canonical_maps = [ "tcp:127.0.0.1:10001" ];
818 sender_canonical_classes = [ "envelope_sender" ];
819 recipient_canonical_maps = [ "tcp:127.0.0.1:10002" ];
820 recipient_canonical_classes = [ "envelope_recipient" ];
821 }
822 // optionalAttrs cfg.enableHeaderChecks { header_checks = [ "regexp:/etc/postfix/header_checks" ]; }
823 // optionalAttrs (cfg.tlsTrustedAuthorities != "") {
824 smtp_tls_CAfile = cfg.tlsTrustedAuthorities;
825 smtp_tls_security_level = mkDefault "may";
826 }
827 // optionalAttrs (cfg.sslCert != "") {
828 smtp_tls_cert_file = cfg.sslCert;
829 smtp_tls_key_file = cfg.sslKey;
830
831 smtp_tls_security_level = mkDefault "may";
832
833 smtpd_tls_cert_file = cfg.sslCert;
834 smtpd_tls_key_file = cfg.sslKey;
835
836 smtpd_tls_security_level = "may";
837 };
838
839 services.postfix.masterConfig = {
840 pickup = {
841 private = false;
842 wakeup = 60;
843 maxproc = 1;
844 };
845 cleanup = {
846 private = false;
847 maxproc = 0;
848 };
849 qmgr = {
850 private = false;
851 wakeup = 300;
852 maxproc = 1;
853 };
854 tlsmgr = {
855 wakeup = 1000;
856 wakeupUnusedComponent = false;
857 maxproc = 1;
858 };
859 rewrite = {
860 command = "trivial-rewrite";
861 };
862 bounce = {
863 maxproc = 0;
864 };
865 defer = {
866 maxproc = 0;
867 command = "bounce";
868 };
869 trace = {
870 maxproc = 0;
871 command = "bounce";
872 };
873 verify = {
874 maxproc = 1;
875 };
876 flush = {
877 private = false;
878 wakeup = 1000;
879 wakeupUnusedComponent = false;
880 maxproc = 0;
881 };
882 proxymap = {
883 command = "proxymap";
884 };
885 proxywrite = {
886 maxproc = 1;
887 command = "proxymap";
888 };
889 showq = {
890 private = false;
891 };
892 error = {};
893 retry = {
894 command = "error";
895 };
896 discard = {};
897 local = {
898 privileged = true;
899 };
900 virtual = {
901 privileged = true;
902 };
903 lmtp = {
904 };
905 anvil = {
906 maxproc = 1;
907 };
908 scache = {
909 maxproc = 1;
910 };
911 } // optionalAttrs cfg.enableSubmission {
912 submission = {
913 type = "inet";
914 private = false;
915 command = "smtpd";
916 args = let
917 mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ];
918 in concatLists (mapAttrsToList mkKeyVal cfg.submissionOptions);
919 };
920 } // optionalAttrs cfg.enableSmtp {
921 smtp_inet = {
922 name = "smtp";
923 type = "inet";
924 private = false;
925 command = "smtpd";
926 };
927 smtp = {};
928 relay = {
929 command = "smtp";
930 args = [ "-o" "smtp_fallback_relay=" ];
931 };
932 } // optionalAttrs cfg.enableSubmissions {
933 submissions = {
934 type = "inet";
935 private = false;
936 command = "smtpd";
937 args = let
938 mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ];
939 adjustSmtpTlsSecurityLevel = !(cfg.submissionsOptions ? smtpd_tls_security_level) ||
940 cfg.submissionsOptions.smtpd_tls_security_level == "none" ||
941 cfg.submissionsOptions.smtpd_tls_security_level == "may";
942 submissionsOptions = cfg.submissionsOptions // {
943 smtpd_tls_wrappermode = "yes";
944 } // optionalAttrs adjustSmtpTlsSecurityLevel {
945 smtpd_tls_security_level = "encrypt";
946 };
947 in concatLists (mapAttrsToList mkKeyVal submissionsOptions);
948 };
949 };
950 }
951
952 (mkIf haveAliases {
953 services.postfix.aliasFiles.aliases = aliasesFile;
954 })
955 (mkIf haveCanonical {
956 services.postfix.mapFiles.canonical = canonicalFile;
957 })
958 (mkIf haveTransport {
959 services.postfix.mapFiles.transport = transportFile;
960 })
961 (mkIf haveVirtual {
962 services.postfix.mapFiles.virtual = virtualFile;
963 })
964 (mkIf haveLocalRecipients {
965 services.postfix.mapFiles.local_recipients = localRecipientMapFile;
966 })
967 (mkIf cfg.enableHeaderChecks {
968 services.postfix.mapFiles.header_checks = headerChecksFile;
969 })
970 (mkIf (cfg.dnsBlacklists != []) {
971 services.postfix.mapFiles.client_access = checkClientAccessFile;
972 })
973 ]);
974
975 imports = [
976 (mkRemovedOptionModule [ "services" "postfix" "sslCACert" ]
977 "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.")
978
979 (mkChangedOptionModule [ "services" "postfix" "useDane" ]
980 [ "services" "postfix" "config" "smtp_tls_security_level" ]
981 (config: mkIf config.services.postfix.useDane "dane"))
982 ];
983}