nixpkgs/nixos/modules/services/mail/postfix.nix
Nikolay Amiantov 52c7e647ab postfix service: don't empty local_recipient_maps
From Postfix documentation:

With this setting, the Postfix SMTP server will not reject mail with "User
unknown in local recipient table". Don't do this on systems that receive mail
directly from the Internet. With today's worms and viruses, Postfix will become
a backscatter source: it accepts mail for non-existent recipients and then
tries to return that mail as "undeliverable" to the often forged sender
address.
2017-02-06 01:41:27 +03:00

549 lines
17 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 =
if (cfg.dnsBlacklistOverrides != "")
then [ "check_client_access hash:/etc/postfix/client_access" ]
else [];
dnsBl =
if (cfg.dnsBlacklists != [])
then [ (concatStringsSep ", " (map (s: "reject_rbl_client " + s) cfg.dnsBlacklists)) ]
else [];
clientRestrictions = concatStringsSep ", " (clientAccess ++ dnsBl);
mainCf =
''
compatibility_level = 9999
mail_owner = ${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 = no
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 = no
''
+ optionalString config.networking.enableIPv6 ''
inet_protocols = all
''
+ (if cfg.networks != null then
''
mynetworks = ${concatStringsSep ", " cfg.networks}
''
else if cfg.networksStyle != "" then
''
mynetworks_style = ${cfg.networksStyle}
''
else
"")
+ optionalString (cfg.hostname != "") ''
myhostname = ${cfg.hostname}
''
+ optionalString (cfg.domain != "") ''
mydomain = ${cfg.domain}
''
+ optionalString (cfg.origin != "") ''
myorigin = ${cfg.origin}
''
+ optionalString (cfg.destination != null) ''
mydestination = ${concatStringsSep ", " cfg.destination}
''
+ optionalString (cfg.relayDomains != null) ''
relay_domains = ${concatStringsSep ", " cfg.relayDomains}
''
+ ''
relayhost = ${if cfg.lookupMX || cfg.relayHost == "" then
cfg.relayHost
else
"[" + cfg.relayHost + "]"}
mail_spool_directory = /var/spool/mail/
setgid_group = ${setgidGroup}
''
+ optionalString (cfg.sslCert != "") ''
smtp_tls_CAfile = ${cfg.sslCACert}
smtp_tls_cert_file = ${cfg.sslCert}
smtp_tls_key_file = ${cfg.sslKey}
smtp_use_tls = yes
smtpd_tls_CAfile = ${cfg.sslCACert}
smtpd_tls_cert_file = ${cfg.sslCert}
smtpd_tls_key_file = ${cfg.sslKey}
smtpd_use_tls = yes
''
+ optionalString (cfg.recipientDelimiter != "") ''
recipient_delimiter = ${cfg.recipientDelimiter}
''
+ optionalString haveAliases ''
alias_maps = hash:/etc/postfix/aliases
''
+ optionalString haveTransport ''
transport_maps = hash:/etc/postfix/transport
''
+ optionalString haveVirtual ''
virtual_alias_maps = hash:/etc/postfix/virtual
''
+ optionalString (cfg.dnsBlacklists != []) ''
smtpd_client_restrictions = ${clientRestrictions}
''
+ cfg.extraConfig;
masterCf = ''
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (no) (never) (100)
# ==========================================================================
smtp inet n - n - - smtpd
'' + optionalString cfg.enableSubmission ''
submission inet n - n - - smtpd
${concatStringsSep "\n " (mapAttrsToList (x: y: "-o " + x + "=" + y) cfg.submissionOptions)}
''
+ ''
pickup unix n - n 60 1 pickup
cleanup unix n - n - 0 cleanup
qmgr unix n - n 300 1 qmgr
tlsmgr unix - - n 1000? 1 tlsmgr
rewrite unix - - n - - trivial-rewrite
bounce unix - - n - 0 bounce
defer unix - - n - 0 bounce
trace unix - - n - 0 bounce
verify unix - - n - 1 verify
flush unix n - n 1000? 0 flush
proxymap unix - - n - - proxymap
proxywrite unix - - n - 1 proxymap
''
+ optionalString cfg.enableSmtp ''
smtp unix - - n - - smtp
relay unix - - n - - smtp
-o smtp_fallback_relay=
# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
''
+ ''
showq unix n - n - - showq
error unix - - n - - error
retry unix - - n - - error
discard unix - - n - - discard
local unix - n n - - local
virtual unix - n n - - virtual
lmtp unix - - n - - lmtp
anvil unix - - n - 1 anvil
scache unix - - n - 1 scache
${cfg.extraMasterConf}
'';
aliases =
optionalString (cfg.postmasterAlias != "") ''
postmaster: ${cfg.postmasterAlias}
''
+ optionalString (cfg.rootAlias != "") ''
root: ${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" masterCf;
transportFile = pkgs.writeText "postfix-transport" cfg.transport;
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";
};
description = "Options for the submission config in master.cf";
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";
};
};
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.
";
};
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.";
};
rootAlias = mkOption {
type = types.str;
default = "";
description = "
Who should receive root e-mail. Blank for no redirection.
";
};
extraAliases = mkOption {
type = types.lines;
default = "";
description = "
Additional entries to put verbatim into aliases file, cf. man-page aliases(8).
";
};
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).
";
};
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";
};
extraMasterConf = mkOption {
type = types.lines;
default = "";
example = "submission inet n - n - - smtpd";
description = "Extra lines to append to the generated master.cf 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.";
};
};
};
###### 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.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
'';
};
}
(mkIf haveAliases {
services.postfix.aliasFiles."aliases" = aliasesFile;
})
(mkIf haveTransport {
services.postfix.mapFiles."transport" = transportFile;
})
(mkIf haveVirtual {
services.postfix.mapFiles."virtual" = virtualFile;
})
(mkIf (cfg.dnsBlacklists != []) {
services.postfix.mapFiles."client_access" = checkClientAccessFile;
})
]);
}