at 25.11-pre 36 kB view raw
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}