 91ddc9d27f
			
		
	
	
		91ddc9d27f
		
			
		
	
	
	
	
		
			
			Both postqueue[1] and postdrop[2] implement a subset of administration task that are supposed to be run unprivileged users and require the setgid bit to full-fill this task. [1] http://www.postfix.org/postqueue.1.html [2] http://www.postfix.org/postdrop.1.html
		
			
				
	
	
		
			880 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			880 lines
		
	
	
		
			27 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 it comfortable to run 'postqueue/postdrop' 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;
 | |
|       };
 | |
| 
 | |
|       security.wrappers.postqueue = {
 | |
|         program = "postqueue";
 | |
|         source = "${pkgs.postfix}/bin/postqueue";
 | |
|         group = setgidGroup;
 | |
|         setuid = false;
 | |
|         setgid = true;
 | |
|       };
 | |
| 
 | |
|       security.wrappers.postdrop = {
 | |
|         program = "postdrop";
 | |
|         source = "${pkgs.postfix}/bin/postdrop";
 | |
|         group = setgidGroup;
 | |
|         setuid = false;
 | |
|         setgid = true;
 | |
|       };
 | |
| 
 | |
|       users.users = optional (user == "postfix")
 | |
|         { name = "postfix";
 | |
|           description = "Postfix mail server user";
 | |
|           uid = config.ids.uids.postfix;
 | |
|           group = group;
 | |
|         };
 | |
| 
 | |
|       users.groups =
 | |
|         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;
 | |
|     })
 | |
|   ]);
 | |
| }
 |