864 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			864 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{ config, lib, pkgs, ... }:
 | 
						|
 | 
						|
with lib;
 | 
						|
 | 
						|
let
 | 
						|
 | 
						|
  cfg = config.services.postfix;
 | 
						|
  user = cfg.user;
 | 
						|
  group = cfg.group;
 | 
						|
  setgidGroup = cfg.setgidGroup;
 | 
						|
 | 
						|
  haveAliases = cfg.postmasterAlias != "" || cfg.rootAlias != ""
 | 
						|
                      || cfg.extraAliases != "";
 | 
						|
  haveTransport = cfg.transport != "";
 | 
						|
  haveVirtual = cfg.virtual != "";
 | 
						|
 | 
						|
  clientAccess =
 | 
						|
    optional (cfg.dnsBlacklistOverrides != "")
 | 
						|
      "check_client_access hash:/etc/postfix/client_access";
 | 
						|
 | 
						|
  dnsBl =
 | 
						|
    optionals (cfg.dnsBlacklists != [])
 | 
						|
      (map (s: "reject_rbl_client " + s) cfg.dnsBlacklists);
 | 
						|
 | 
						|
  clientRestrictions = concatStringsSep ", " (clientAccess ++ dnsBl);
 | 
						|
 | 
						|
  mainCf = let
 | 
						|
    escape = replaceStrings ["$"] ["$$"];
 | 
						|
    mkList = items: "\n  " + concatStringsSep ",\n  " items;
 | 
						|
    mkVal = value:
 | 
						|
      if isList value then mkList value
 | 
						|
        else " " + (if value == true then "yes"
 | 
						|
        else if value == false then "no"
 | 
						|
        else toString value);
 | 
						|
    mkEntry = name: value: "${escape name} =${mkVal value}";
 | 
						|
  in
 | 
						|
    concatStringsSep "\n" (mapAttrsToList mkEntry cfg.config)
 | 
						|
      + "\n" + cfg.extraConfig;
 | 
						|
 | 
						|
  masterCfOptions = { options, config, name, ... }: {
 | 
						|
    options = {
 | 
						|
      name = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = name;
 | 
						|
        example = "smtp";
 | 
						|
        description = ''
 | 
						|
          The name of the service to run. Defaults to the attribute set key.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      type = mkOption {
 | 
						|
        type = types.enum [ "inet" "unix" "fifo" "pass" ];
 | 
						|
        default = "unix";
 | 
						|
        example = "inet";
 | 
						|
        description = "The type of the service";
 | 
						|
      };
 | 
						|
 | 
						|
      private = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        example = false;
 | 
						|
        description = ''
 | 
						|
          Whether the service's sockets and storage directory is restricted to
 | 
						|
          be only available via the mail system. If <literal>null</literal> is
 | 
						|
          given it uses the postfix default <literal>true</literal>.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      privileged = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        example = true;
 | 
						|
        description = "";
 | 
						|
      };
 | 
						|
 | 
						|
      chroot = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        example = true;
 | 
						|
        description = ''
 | 
						|
          Whether the service is chrooted to have only access to the
 | 
						|
          <option>services.postfix.queueDir</option> and the closure of
 | 
						|
          store paths specified by the <option>program</option> option.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      wakeup = mkOption {
 | 
						|
        type = types.int;
 | 
						|
        example = 60;
 | 
						|
        description = ''
 | 
						|
          Automatically wake up the service after the specified number of
 | 
						|
          seconds. If <literal>0</literal> is given, never wake the service
 | 
						|
          up.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      wakeupUnusedComponent = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        example = false;
 | 
						|
        description = ''
 | 
						|
          If set to <literal>false</literal> the component will only be woken
 | 
						|
          up if it is used. This is equivalent to postfix' notion of adding a
 | 
						|
          question mark behind the wakeup time in
 | 
						|
          <filename>master.cf</filename>
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      maxproc = mkOption {
 | 
						|
        type = types.int;
 | 
						|
        example = 1;
 | 
						|
        description = ''
 | 
						|
          The maximum number of processes to spawn for this service. If the
 | 
						|
          value is <literal>0</literal> it doesn't have any limit. If
 | 
						|
          <literal>null</literal> is given it uses the postfix default of
 | 
						|
          <literal>100</literal>.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      command = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = name;
 | 
						|
        example = "smtpd";
 | 
						|
        description = ''
 | 
						|
          A program name specifying a Postfix service/daemon process.
 | 
						|
          By default it's the attribute <option>name</option>.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      args = mkOption {
 | 
						|
        type = types.listOf types.str;
 | 
						|
        default = [];
 | 
						|
        example = [ "-o" "smtp_helo_timeout=5" ];
 | 
						|
        description = ''
 | 
						|
          Arguments to pass to the <option>command</option>. There is no shell
 | 
						|
          processing involved and shell syntax is passed verbatim to the
 | 
						|
          process.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      rawEntry = mkOption {
 | 
						|
        type = types.listOf types.str;
 | 
						|
        default = [];
 | 
						|
        internal = true;
 | 
						|
        description = ''
 | 
						|
          The raw configuration line for the <filename>master.cf</filename>.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
    config.rawEntry = let
 | 
						|
      mkBool = bool: if bool then "y" else "n";
 | 
						|
      mkArg = arg: "${optionalString (hasPrefix "-" arg) "\n  "}${arg}";
 | 
						|
 | 
						|
      maybeOption = fun: option:
 | 
						|
        if options.${option}.isDefined then fun config.${option} else "-";
 | 
						|
 | 
						|
      # This is special, because we have two options for this value.
 | 
						|
      wakeup = let
 | 
						|
        wakeupDefined = options.wakeup.isDefined;
 | 
						|
        wakeupUCDefined = options.wakeupUnusedComponent.isDefined;
 | 
						|
        finalValue = toString config.wakeup
 | 
						|
                   + optionalString (wakeupUCDefined && !config.wakeupUnusedComponent) "?";
 | 
						|
      in if wakeupDefined then finalValue else "-";
 | 
						|
 | 
						|
    in [
 | 
						|
      config.name
 | 
						|
      config.type
 | 
						|
      (maybeOption mkBool "private")
 | 
						|
      (maybeOption (b: mkBool (!b)) "privileged")
 | 
						|
      (maybeOption mkBool "chroot")
 | 
						|
      wakeup
 | 
						|
      (maybeOption toString "maxproc")
 | 
						|
      (config.command + " " + concatMapStringsSep " " mkArg config.args)
 | 
						|
    ];
 | 
						|
  };
 | 
						|
 | 
						|
  masterCfContent = let
 | 
						|
 | 
						|
    labels = [
 | 
						|
      "# service" "type" "private" "unpriv" "chroot" "wakeup" "maxproc"
 | 
						|
      "command + args"
 | 
						|
    ];
 | 
						|
 | 
						|
    labelDefaults = [
 | 
						|
      "# " "" "(yes)" "(yes)" "(no)" "(never)" "(100)" "" ""
 | 
						|
    ];
 | 
						|
 | 
						|
    masterCf = mapAttrsToList (const (getAttr "rawEntry")) cfg.masterConfig;
 | 
						|
 | 
						|
    # A list of the maximum width of the columns across all lines and labels
 | 
						|
    maxWidths = let
 | 
						|
      foldLine = line: acc: let
 | 
						|
        columnLengths = map stringLength line;
 | 
						|
      in zipListsWith max acc columnLengths;
 | 
						|
      # We need to handle the last column specially here, because it's
 | 
						|
      # open-ended (command + args).
 | 
						|
      lines = [ labels labelDefaults ] ++ (map (l: init l ++ [""]) masterCf);
 | 
						|
    in fold foldLine (genList (const 0) (length labels)) lines;
 | 
						|
 | 
						|
    # Pad a string with spaces from the right (opposite of fixedWidthString).
 | 
						|
    pad = width: str: let
 | 
						|
      padWidth = width - stringLength str;
 | 
						|
      padding = concatStrings (genList (const " ") padWidth);
 | 
						|
    in str + optionalString (padWidth > 0) padding;
 | 
						|
 | 
						|
    # It's + 2 here, because that's the amount of spacing between columns.
 | 
						|
    fullWidth = fold (width: acc: acc + width + 2) 0 maxWidths;
 | 
						|
 | 
						|
    formatLine = line: concatStringsSep "  " (zipListsWith pad maxWidths line);
 | 
						|
 | 
						|
    formattedLabels = let
 | 
						|
      sep = "# " + concatStrings (genList (const "=") (fullWidth + 5));
 | 
						|
      lines = [ sep (formatLine labels) (formatLine labelDefaults) sep ];
 | 
						|
    in concatStringsSep "\n" lines;
 | 
						|
 | 
						|
  in formattedLabels + "\n" + concatMapStringsSep "\n" formatLine masterCf + "\n" + cfg.extraMasterConf;
 | 
						|
 | 
						|
  headerCheckOptions = { ... }:
 | 
						|
  {
 | 
						|
    options = {
 | 
						|
      pattern = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "/^.*/";
 | 
						|
        example = "/^X-Mailer:/";
 | 
						|
        description = "A regexp pattern matching the header";
 | 
						|
      };
 | 
						|
      action = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "DUNNO";
 | 
						|
        example = "BCC mail@example.com";
 | 
						|
        description = "The action to be executed when the pattern is matched";
 | 
						|
      };
 | 
						|
    };
 | 
						|
  };
 | 
						|
 | 
						|
  headerChecks = concatStringsSep "\n" (map (x: "${x.pattern} ${x.action}") cfg.headerChecks) + cfg.extraHeaderChecks;
 | 
						|
 | 
						|
  aliases = let seperator = if cfg.aliasMapType == "hash" then ":" else ""; in
 | 
						|
    optionalString (cfg.postmasterAlias != "") ''
 | 
						|
      postmaster${seperator} ${cfg.postmasterAlias}
 | 
						|
    ''
 | 
						|
    + optionalString (cfg.rootAlias != "") ''
 | 
						|
      root${seperator} ${cfg.rootAlias}
 | 
						|
    ''
 | 
						|
    + cfg.extraAliases
 | 
						|
  ;
 | 
						|
 | 
						|
  aliasesFile = pkgs.writeText "postfix-aliases" aliases;
 | 
						|
  virtualFile = pkgs.writeText "postfix-virtual" cfg.virtual;
 | 
						|
  checkClientAccessFile = pkgs.writeText "postfix-check-client-access" cfg.dnsBlacklistOverrides;
 | 
						|
  mainCfFile = pkgs.writeText "postfix-main.cf" mainCf;
 | 
						|
  masterCfFile = pkgs.writeText "postfix-master.cf" masterCfContent;
 | 
						|
  transportFile = pkgs.writeText "postfix-transport" cfg.transport;
 | 
						|
  headerChecksFile = pkgs.writeText "postfix-header-checks" headerChecks;
 | 
						|
 | 
						|
in
 | 
						|
 | 
						|
{
 | 
						|
 | 
						|
  ###### interface
 | 
						|
 | 
						|
  options = {
 | 
						|
 | 
						|
    services.postfix = {
 | 
						|
 | 
						|
      enable = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        default = false;
 | 
						|
        description = "Whether to run the Postfix mail server.";
 | 
						|
      };
 | 
						|
 | 
						|
      enableSmtp = mkOption {
 | 
						|
        default = true;
 | 
						|
        description = "Whether to enable smtp in master.cf.";
 | 
						|
      };
 | 
						|
 | 
						|
      enableSubmission = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        default = false;
 | 
						|
        description = "Whether to enable smtp submission.";
 | 
						|
      };
 | 
						|
 | 
						|
      submissionOptions = mkOption {
 | 
						|
        type = types.attrs;
 | 
						|
        default = {
 | 
						|
          smtpd_tls_security_level = "encrypt";
 | 
						|
          smtpd_sasl_auth_enable = "yes";
 | 
						|
          smtpd_client_restrictions = "permit_sasl_authenticated,reject";
 | 
						|
          milter_macro_daemon_name = "ORIGINATING";
 | 
						|
        };
 | 
						|
        example = {
 | 
						|
          smtpd_tls_security_level = "encrypt";
 | 
						|
          smtpd_sasl_auth_enable = "yes";
 | 
						|
          smtpd_sasl_type = "dovecot";
 | 
						|
          smtpd_client_restrictions = "permit_sasl_authenticated,reject";
 | 
						|
          milter_macro_daemon_name = "ORIGINATING";
 | 
						|
        };
 | 
						|
        description = "Options for the submission config in master.cf";
 | 
						|
      };
 | 
						|
 | 
						|
      setSendmail = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        default = true;
 | 
						|
        description = "Whether to set the system sendmail to postfix's.";
 | 
						|
      };
 | 
						|
 | 
						|
      user = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "postfix";
 | 
						|
        description = "What to call the Postfix user (must be used only for postfix).";
 | 
						|
      };
 | 
						|
 | 
						|
      group = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "postfix";
 | 
						|
        description = "What to call the Postfix group (must be used only for postfix).";
 | 
						|
      };
 | 
						|
 | 
						|
      setgidGroup = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "postdrop";
 | 
						|
        description = "
 | 
						|
          How to call postfix setgid group (for postdrop). Should
 | 
						|
          be uniquely used group.
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      networks = mkOption {
 | 
						|
        type = types.nullOr (types.listOf types.str);
 | 
						|
        default = null;
 | 
						|
        example = ["192.168.0.1/24"];
 | 
						|
        description = "
 | 
						|
          Net masks for trusted - allowed to relay mail to third parties -
 | 
						|
          hosts. Leave empty to use mynetworks_style configuration or use
 | 
						|
          default (localhost-only).
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      networksStyle = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "";
 | 
						|
        description = "
 | 
						|
          Name of standard way of trusted network specification to use,
 | 
						|
          leave blank if you specify it explicitly or if you want to use
 | 
						|
          default (localhost-only).
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      hostname = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "";
 | 
						|
        description ="
 | 
						|
          Hostname to use. Leave blank to use just the hostname of machine.
 | 
						|
          It should be FQDN.
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      domain = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "";
 | 
						|
        description ="
 | 
						|
          Domain to use. Leave blank to use hostname minus first component.
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      origin = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "";
 | 
						|
        description ="
 | 
						|
          Origin to use in outgoing e-mail. Leave blank to use hostname.
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      destination = mkOption {
 | 
						|
        type = types.nullOr (types.listOf types.str);
 | 
						|
        default = null;
 | 
						|
        example = ["localhost"];
 | 
						|
        description = "
 | 
						|
          Full (!) list of domains we deliver locally. Leave blank for
 | 
						|
          acceptable Postfix default.
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      relayDomains = mkOption {
 | 
						|
        type = types.nullOr (types.listOf types.str);
 | 
						|
        default = null;
 | 
						|
        example = ["localdomain"];
 | 
						|
        description = "
 | 
						|
          List of domains we agree to relay to. Default is empty.
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      relayHost = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "";
 | 
						|
        description = "
 | 
						|
          Mail relay for outbound mail.
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      relayPort = mkOption {
 | 
						|
        type = types.int;
 | 
						|
        default = 25;
 | 
						|
        description = "
 | 
						|
          SMTP port for relay mail relay.
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      lookupMX = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        default = false;
 | 
						|
        description = "
 | 
						|
          Whether relay specified is just domain whose MX must be used.
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      postmasterAlias = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "root";
 | 
						|
        description = "
 | 
						|
          Who should receive postmaster e-mail. Multiple values can be added by
 | 
						|
          separating values with comma.
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      rootAlias = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "";
 | 
						|
        description = "
 | 
						|
          Who should receive root e-mail. Blank for no redirection.
 | 
						|
          Multiple values can be added by separating values with comma.
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      extraAliases = mkOption {
 | 
						|
        type = types.lines;
 | 
						|
        default = "";
 | 
						|
        description = "
 | 
						|
          Additional entries to put verbatim into aliases file, cf. man-page aliases(8).
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      aliasMapType = mkOption {
 | 
						|
        type = with types; enum [ "hash" "regexp" "pcre" ];
 | 
						|
        default = "hash";
 | 
						|
        example = "regexp";
 | 
						|
        description = "The format the alias map should have. Use regexp if you want to use regular expressions.";
 | 
						|
      };
 | 
						|
 | 
						|
      config = mkOption {
 | 
						|
        type = with types; attrsOf (either bool (either str (listOf str)));
 | 
						|
        description = ''
 | 
						|
          The main.cf configuration file as key value set.
 | 
						|
        '';
 | 
						|
        example = {
 | 
						|
          mail_owner = "postfix";
 | 
						|
          smtp_use_tls = true;
 | 
						|
        };
 | 
						|
      };
 | 
						|
 | 
						|
      extraConfig = mkOption {
 | 
						|
        type = types.lines;
 | 
						|
        default = "";
 | 
						|
        description = "
 | 
						|
          Extra lines to be added verbatim to the main.cf configuration file.
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      sslCert = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "";
 | 
						|
        description = "SSL certificate to use.";
 | 
						|
      };
 | 
						|
 | 
						|
      sslCACert = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "";
 | 
						|
        description = "SSL certificate of CA.";
 | 
						|
      };
 | 
						|
 | 
						|
      sslKey = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "";
 | 
						|
        description = "SSL key to use.";
 | 
						|
      };
 | 
						|
 | 
						|
      recipientDelimiter = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "";
 | 
						|
        example = "+";
 | 
						|
        description = "
 | 
						|
          Delimiter for address extension: so mail to user+test can be handled by ~user/.forward+test
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      virtual = mkOption {
 | 
						|
        type = types.lines;
 | 
						|
        default = "";
 | 
						|
        description = "
 | 
						|
          Entries for the virtual alias map, cf. man-page virtual(8).
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      virtualMapType = mkOption {
 | 
						|
        type = types.enum ["hash" "regexp" "pcre"];
 | 
						|
        default = "hash";
 | 
						|
        description = ''
 | 
						|
          What type of virtual alias map file to use. Use <literal>"regexp"</literal> for regular expressions.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      transport = mkOption {
 | 
						|
        default = "";
 | 
						|
        description = "
 | 
						|
          Entries for the transport map, cf. man-page transport(8).
 | 
						|
        ";
 | 
						|
      };
 | 
						|
 | 
						|
      dnsBlacklists = mkOption {
 | 
						|
        default = [];
 | 
						|
        type = with types; listOf string;
 | 
						|
        description = "dns blacklist servers to use with smtpd_client_restrictions";
 | 
						|
      };
 | 
						|
 | 
						|
      dnsBlacklistOverrides = mkOption {
 | 
						|
        default = "";
 | 
						|
        description = "contents of check_client_access for overriding dnsBlacklists";
 | 
						|
      };
 | 
						|
 | 
						|
      masterConfig = mkOption {
 | 
						|
        type = types.attrsOf (types.submodule masterCfOptions);
 | 
						|
        default = {};
 | 
						|
        example =
 | 
						|
          { submission = {
 | 
						|
              type = "inet";
 | 
						|
              args = [ "-o" "smtpd_tls_security_level=encrypt" ];
 | 
						|
            };
 | 
						|
          };
 | 
						|
        description = ''
 | 
						|
          An attribute set of service options, which correspond to the service
 | 
						|
          definitions usually done within the Postfix
 | 
						|
          <filename>master.cf</filename> file.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      extraMasterConf = mkOption {
 | 
						|
        type = types.lines;
 | 
						|
        default = "";
 | 
						|
        example = "submission inet n - n - - smtpd";
 | 
						|
        description = "Extra lines to append to the generated master.cf file.";
 | 
						|
      };
 | 
						|
 | 
						|
      enableHeaderChecks = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        default = false;
 | 
						|
        example = true;
 | 
						|
        description = "Whether to enable postfix header checks";
 | 
						|
      };
 | 
						|
 | 
						|
      headerChecks = mkOption {
 | 
						|
        type = types.listOf (types.submodule headerCheckOptions);
 | 
						|
        default = [];
 | 
						|
        example = [ { pattern = "/^X-Spam-Flag:/"; action = "REDIRECT spam@example.com"; } ];
 | 
						|
        description = "Postfix header checks.";
 | 
						|
      };
 | 
						|
 | 
						|
      extraHeaderChecks = mkOption {
 | 
						|
        type = types.lines;
 | 
						|
        default = "";
 | 
						|
        example = "/^X-Spam-Flag:/ REDIRECT spam@example.com";
 | 
						|
        description = "Extra lines to /etc/postfix/header_checks file.";
 | 
						|
      };
 | 
						|
 | 
						|
      aliasFiles = mkOption {
 | 
						|
        type = types.attrsOf types.path;
 | 
						|
        default = {};
 | 
						|
        description = "Aliases' tables to be compiled and placed into /var/lib/postfix/conf.";
 | 
						|
      };
 | 
						|
 | 
						|
      mapFiles = mkOption {
 | 
						|
        type = types.attrsOf types.path;
 | 
						|
        default = {};
 | 
						|
        description = "Maps to be compiled and placed into /var/lib/postfix/conf.";
 | 
						|
      };
 | 
						|
 | 
						|
      useSrs = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        default = false;
 | 
						|
        description = "Whether to enable sender rewriting scheme";
 | 
						|
      };
 | 
						|
 | 
						|
    };
 | 
						|
 | 
						|
  };
 | 
						|
 | 
						|
 | 
						|
  ###### implementation
 | 
						|
 | 
						|
  config = mkIf config.services.postfix.enable (mkMerge [
 | 
						|
    {
 | 
						|
 | 
						|
      environment = {
 | 
						|
        etc = singleton
 | 
						|
          { source = "/var/lib/postfix/conf";
 | 
						|
            target = "postfix";
 | 
						|
          };
 | 
						|
 | 
						|
        # This makes comfortable for root to run 'postqueue' for example.
 | 
						|
        systemPackages = [ pkgs.postfix ];
 | 
						|
      };
 | 
						|
 | 
						|
      services.pfix-srsd.enable = config.services.postfix.useSrs;
 | 
						|
 | 
						|
      services.mail.sendmailSetuidWrapper = mkIf config.services.postfix.setSendmail {
 | 
						|
        program = "sendmail";
 | 
						|
        source = "${pkgs.postfix}/bin/sendmail";
 | 
						|
        group = setgidGroup;
 | 
						|
        setuid = false;
 | 
						|
        setgid = true;
 | 
						|
      };
 | 
						|
 | 
						|
      users.extraUsers = optional (user == "postfix")
 | 
						|
        { name = "postfix";
 | 
						|
          description = "Postfix mail server user";
 | 
						|
          uid = config.ids.uids.postfix;
 | 
						|
          group = group;
 | 
						|
        };
 | 
						|
 | 
						|
      users.extraGroups =
 | 
						|
        optional (group == "postfix")
 | 
						|
        { name = group;
 | 
						|
          gid = config.ids.gids.postfix;
 | 
						|
        }
 | 
						|
        ++ optional (setgidGroup == "postdrop")
 | 
						|
        { name = setgidGroup;
 | 
						|
          gid = config.ids.gids.postdrop;
 | 
						|
        };
 | 
						|
 | 
						|
      systemd.services.postfix =
 | 
						|
        { description = "Postfix mail server";
 | 
						|
 | 
						|
          wantedBy = [ "multi-user.target" ];
 | 
						|
          after = [ "network.target" ];
 | 
						|
          path = [ pkgs.postfix ];
 | 
						|
 | 
						|
          serviceConfig = {
 | 
						|
            Type = "forking";
 | 
						|
            Restart = "always";
 | 
						|
            PIDFile = "/var/lib/postfix/queue/pid/master.pid";
 | 
						|
            ExecStart = "${pkgs.postfix}/bin/postfix start";
 | 
						|
            ExecStop = "${pkgs.postfix}/bin/postfix stop";
 | 
						|
            ExecReload = "${pkgs.postfix}/bin/postfix reload";
 | 
						|
          };
 | 
						|
 | 
						|
          preStart = ''
 | 
						|
            # Backwards compatibility
 | 
						|
            if [ ! -d /var/lib/postfix ] && [ -d /var/postfix ]; then
 | 
						|
              mkdir -p /var/lib
 | 
						|
              mv /var/postfix /var/lib/postfix
 | 
						|
            fi
 | 
						|
 | 
						|
            # All permissions set according ${pkgs.postfix}/etc/postfix/postfix-files script
 | 
						|
            mkdir -p /var/lib/postfix /var/lib/postfix/queue/{pid,public,maildrop}
 | 
						|
            chmod 0755 /var/lib/postfix
 | 
						|
            chown root:root /var/lib/postfix
 | 
						|
 | 
						|
            rm -rf /var/lib/postfix/conf
 | 
						|
            mkdir -p /var/lib/postfix/conf
 | 
						|
            chmod 0755 /var/lib/postfix/conf
 | 
						|
            ln -sf ${pkgs.postfix}/etc/postfix/postfix-files /var/lib/postfix/conf/postfix-files
 | 
						|
            ln -sf ${mainCfFile} /var/lib/postfix/conf/main.cf
 | 
						|
            ln -sf ${masterCfFile} /var/lib/postfix/conf/master.cf
 | 
						|
 | 
						|
            ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
 | 
						|
              ln -sf ${from} /var/lib/postfix/conf/${to}
 | 
						|
              ${pkgs.postfix}/bin/postalias /var/lib/postfix/conf/${to}
 | 
						|
            '') cfg.aliasFiles)}
 | 
						|
            ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
 | 
						|
              ln -sf ${from} /var/lib/postfix/conf/${to}
 | 
						|
              ${pkgs.postfix}/bin/postmap /var/lib/postfix/conf/${to}
 | 
						|
            '') cfg.mapFiles)}
 | 
						|
 | 
						|
            mkdir -p /var/spool/mail
 | 
						|
            chown root:root /var/spool/mail
 | 
						|
            chmod a+rwxt /var/spool/mail
 | 
						|
            ln -sf /var/spool/mail /var/
 | 
						|
 | 
						|
            #Finally delegate to postfix checking remain directories in /var/lib/postfix and set permissions on them
 | 
						|
            ${pkgs.postfix}/bin/postfix set-permissions config_directory=/var/lib/postfix/conf
 | 
						|
          '';
 | 
						|
        };
 | 
						|
 | 
						|
      services.postfix.config = (mapAttrs (_: v: mkDefault v) {
 | 
						|
        compatibility_level  = "9999";
 | 
						|
        mail_owner           = cfg.user;
 | 
						|
        default_privs        = "nobody";
 | 
						|
 | 
						|
        # NixOS specific locations
 | 
						|
        data_directory       = "/var/lib/postfix/data";
 | 
						|
        queue_directory      = "/var/lib/postfix/queue";
 | 
						|
 | 
						|
        # Default location of everything in package
 | 
						|
        meta_directory       = "${pkgs.postfix}/etc/postfix";
 | 
						|
        command_directory    = "${pkgs.postfix}/bin";
 | 
						|
        sample_directory     = "/etc/postfix";
 | 
						|
        newaliases_path      = "${pkgs.postfix}/bin/newaliases";
 | 
						|
        mailq_path           = "${pkgs.postfix}/bin/mailq";
 | 
						|
        readme_directory     = false;
 | 
						|
        sendmail_path        = "${pkgs.postfix}/bin/sendmail";
 | 
						|
        daemon_directory     = "${pkgs.postfix}/libexec/postfix";
 | 
						|
        manpage_directory    = "${pkgs.postfix}/share/man";
 | 
						|
        html_directory       = "${pkgs.postfix}/share/postfix/doc/html";
 | 
						|
        shlib_directory      = false;
 | 
						|
        mail_spool_directory = "/var/spool/mail/";
 | 
						|
        setgid_group         = cfg.setgidGroup;
 | 
						|
      })
 | 
						|
      // optionalAttrs (cfg.relayHost != "") { relayhost = if cfg.lookupMX
 | 
						|
                                                           then "${cfg.relayHost}:${toString cfg.relayPort}"
 | 
						|
                                                           else "[${cfg.relayHost}]:${toString cfg.relayPort}"; }
 | 
						|
      // optionalAttrs config.networking.enableIPv6 { inet_protocols = mkDefault "all"; }
 | 
						|
      // optionalAttrs (cfg.networks != null) { mynetworks = cfg.networks; }
 | 
						|
      // optionalAttrs (cfg.networksStyle != "") { mynetworks_style = cfg.networksStyle; }
 | 
						|
      // optionalAttrs (cfg.hostname != "") { myhostname = cfg.hostname; }
 | 
						|
      // optionalAttrs (cfg.domain != "") { mydomain = cfg.domain; }
 | 
						|
      // optionalAttrs (cfg.origin != "") { myorigin =  cfg.origin; }
 | 
						|
      // optionalAttrs (cfg.destination != null) { mydestination = cfg.destination; }
 | 
						|
      // optionalAttrs (cfg.relayDomains != null) { relay_domains = cfg.relayDomains; }
 | 
						|
      // optionalAttrs (cfg.recipientDelimiter != "") { recipient_delimiter = cfg.recipientDelimiter; }
 | 
						|
      // optionalAttrs haveAliases { alias_maps = [ "${cfg.aliasMapType}:/etc/postfix/aliases" ]; }
 | 
						|
      // optionalAttrs haveTransport { transport_maps = [ "hash:/etc/postfix/transport" ]; }
 | 
						|
      // optionalAttrs haveVirtual { virtual_alias_maps = [ "${cfg.virtualMapType}:/etc/postfix/virtual" ]; }
 | 
						|
      // optionalAttrs (cfg.dnsBlacklists != []) { smtpd_client_restrictions = clientRestrictions; }
 | 
						|
      // optionalAttrs cfg.useSrs {
 | 
						|
        sender_canonical_maps = [ "tcp:127.0.0.1:10001" ];
 | 
						|
        sender_canonical_classes = [ "envelope_sender" ];
 | 
						|
        recipient_canonical_maps = [ "tcp:127.0.0.1:10002" ];
 | 
						|
        recipient_canonical_classes = [ "envelope_recipient" ];
 | 
						|
      }
 | 
						|
      // optionalAttrs cfg.enableHeaderChecks { header_checks = [ "regexp:/etc/postfix/header_checks" ]; }
 | 
						|
      // optionalAttrs (cfg.sslCert != "") {
 | 
						|
        smtp_tls_CAfile = cfg.sslCACert;
 | 
						|
        smtp_tls_cert_file = cfg.sslCert;
 | 
						|
        smtp_tls_key_file = cfg.sslKey;
 | 
						|
 | 
						|
        smtp_use_tls = true;
 | 
						|
 | 
						|
        smtpd_tls_CAfile = cfg.sslCACert;
 | 
						|
        smtpd_tls_cert_file = cfg.sslCert;
 | 
						|
        smtpd_tls_key_file = cfg.sslKey;
 | 
						|
 | 
						|
        smtpd_use_tls = true;
 | 
						|
      };
 | 
						|
 | 
						|
      services.postfix.masterConfig = {
 | 
						|
        smtp_inet = {
 | 
						|
          name = "smtp";
 | 
						|
          type = "inet";
 | 
						|
          private = false;
 | 
						|
          command = "smtpd";
 | 
						|
        };
 | 
						|
        pickup = {
 | 
						|
          private = false;
 | 
						|
          wakeup = 60;
 | 
						|
          maxproc = 1;
 | 
						|
        };
 | 
						|
        cleanup = {
 | 
						|
          private = false;
 | 
						|
          maxproc = 0;
 | 
						|
        };
 | 
						|
        qmgr = {
 | 
						|
          private = false;
 | 
						|
          wakeup = 300;
 | 
						|
          maxproc = 1;
 | 
						|
        };
 | 
						|
        tlsmgr = {
 | 
						|
          wakeup = 1000;
 | 
						|
          wakeupUnusedComponent = false;
 | 
						|
          maxproc = 1;
 | 
						|
        };
 | 
						|
        rewrite = {
 | 
						|
          command = "trivial-rewrite";
 | 
						|
        };
 | 
						|
        bounce = {
 | 
						|
          maxproc = 0;
 | 
						|
        };
 | 
						|
        defer = {
 | 
						|
          maxproc = 0;
 | 
						|
          command = "bounce";
 | 
						|
        };
 | 
						|
        trace = {
 | 
						|
          maxproc = 0;
 | 
						|
          command = "bounce";
 | 
						|
        };
 | 
						|
        verify = {
 | 
						|
          maxproc = 1;
 | 
						|
        };
 | 
						|
        flush = {
 | 
						|
          private = false;
 | 
						|
          wakeup = 1000;
 | 
						|
          wakeupUnusedComponent = false;
 | 
						|
          maxproc = 0;
 | 
						|
        };
 | 
						|
        proxymap = {
 | 
						|
          command = "proxymap";
 | 
						|
        };
 | 
						|
        proxywrite = {
 | 
						|
          maxproc = 1;
 | 
						|
          command = "proxymap";
 | 
						|
        };
 | 
						|
        showq = {
 | 
						|
          private = false;
 | 
						|
        };
 | 
						|
        error = {};
 | 
						|
        retry = {
 | 
						|
          command = "error";
 | 
						|
        };
 | 
						|
        discard = {};
 | 
						|
        local = {
 | 
						|
          privileged = true;
 | 
						|
        };
 | 
						|
        virtual = {
 | 
						|
          privileged = true;
 | 
						|
        };
 | 
						|
        lmtp = {
 | 
						|
        };
 | 
						|
        anvil = {
 | 
						|
          maxproc = 1;
 | 
						|
        };
 | 
						|
        scache = {
 | 
						|
          maxproc = 1;
 | 
						|
        };
 | 
						|
      } // optionalAttrs cfg.enableSubmission {
 | 
						|
        submission = {
 | 
						|
          type = "inet";
 | 
						|
          private = false;
 | 
						|
          command = "smtpd";
 | 
						|
          args = let
 | 
						|
            mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ];
 | 
						|
          in concatLists (mapAttrsToList mkKeyVal cfg.submissionOptions);
 | 
						|
        };
 | 
						|
      } // optionalAttrs cfg.enableSmtp {
 | 
						|
        smtp = {};
 | 
						|
        relay = {
 | 
						|
          command = "smtp";
 | 
						|
          args = [ "-o" "smtp_fallback_relay=" ];
 | 
						|
        };
 | 
						|
      };
 | 
						|
    }
 | 
						|
 | 
						|
    (mkIf haveAliases {
 | 
						|
      services.postfix.aliasFiles."aliases" = aliasesFile;
 | 
						|
    })
 | 
						|
    (mkIf haveTransport {
 | 
						|
      services.postfix.mapFiles."transport" = transportFile;
 | 
						|
    })
 | 
						|
    (mkIf haveVirtual {
 | 
						|
      services.postfix.mapFiles."virtual" = virtualFile;
 | 
						|
    })
 | 
						|
    (mkIf cfg.enableHeaderChecks {
 | 
						|
      services.postfix.mapFiles."header_checks" = headerChecksFile;
 | 
						|
    })
 | 
						|
    (mkIf (cfg.dnsBlacklists != []) {
 | 
						|
      services.postfix.mapFiles."client_access" = checkClientAccessFile;
 | 
						|
    })
 | 
						|
  ]);
 | 
						|
}
 |