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