at master 42 kB view raw
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}