mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-11-10 09:43:30 +01:00
The replacement instructions for the removed services.postfix.extraConfig option suggests using the services.postfix.extraConfig option instead, which doesn't exist.
1301 lines
41 KiB
Nix
1301 lines
41 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
let
|
|
inherit (lib)
|
|
literalExpression
|
|
mkOption
|
|
types
|
|
;
|
|
|
|
cfg = config.services.postfix;
|
|
user = cfg.user;
|
|
group = cfg.group;
|
|
setgidGroup = cfg.setgidGroup;
|
|
|
|
haveAliases = cfg.postmasterAlias != "" || cfg.rootAlias != "" || cfg.extraAliases != "";
|
|
haveCanonical = cfg.canonical != "";
|
|
haveTransport = cfg.transport != "";
|
|
haveVirtual = cfg.virtual != "";
|
|
haveLocalRecipients = cfg.localRecipients != null;
|
|
|
|
clientAccess = lib.optional (
|
|
cfg.dnsBlacklistOverrides != ""
|
|
) "check_client_access hash:/etc/postfix/client_access";
|
|
|
|
dnsBl = lib.optionals (cfg.dnsBlacklists != [ ]) (
|
|
map (s: "reject_rbl_client " + s) cfg.dnsBlacklists
|
|
);
|
|
|
|
clientRestrictions = lib.concatStringsSep ", " (clientAccess ++ dnsBl);
|
|
|
|
mainCf =
|
|
let
|
|
escape = lib.replaceStrings [ "$" ] [ "$$" ];
|
|
mkList = items: "\n " + lib.concatStringsSep ",\n " items;
|
|
mkVal =
|
|
value:
|
|
if lib.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
|
|
lib.concatStringsSep "\n" (
|
|
lib.mapAttrsToList mkEntry (lib.filterAttrsRecursive (_: value: value != null) cfg.settings.main)
|
|
);
|
|
|
|
masterCfOptions =
|
|
{
|
|
options,
|
|
config,
|
|
name,
|
|
...
|
|
}:
|
|
{
|
|
options = {
|
|
name = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = name;
|
|
example = "smtp";
|
|
description = ''
|
|
The name of the service to run. Defaults to the attribute set key.
|
|
'';
|
|
};
|
|
|
|
type = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"inet"
|
|
"unix"
|
|
"unix-dgram"
|
|
"fifo"
|
|
"pass"
|
|
];
|
|
default = "unix";
|
|
example = "inet";
|
|
description = "The type of the service";
|
|
};
|
|
|
|
private = lib.mkOption {
|
|
type = lib.types.bool;
|
|
example = false;
|
|
description = ''
|
|
Whether the service's sockets and storage directory is restricted to
|
|
be only available via the mail system. If `null` is
|
|
given it uses the postfix default `true`.
|
|
'';
|
|
};
|
|
|
|
privileged = lib.mkOption {
|
|
type = lib.types.bool;
|
|
example = true;
|
|
description = "";
|
|
};
|
|
|
|
chroot = lib.mkOption {
|
|
type = lib.types.bool;
|
|
example = true;
|
|
description = ''
|
|
Whether the service is chrooted to have only access to the
|
|
{option}`services.postfix.queueDir` and the closure of
|
|
store paths specified by the {option}`program` option.
|
|
'';
|
|
};
|
|
|
|
wakeup = lib.mkOption {
|
|
type = lib.types.int;
|
|
example = 60;
|
|
description = ''
|
|
Automatically wake up the service after the specified number of
|
|
seconds. If `0` is given, never wake the service
|
|
up.
|
|
'';
|
|
};
|
|
|
|
wakeupUnusedComponent = lib.mkOption {
|
|
type = lib.types.bool;
|
|
example = false;
|
|
description = ''
|
|
If set to `false` 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
|
|
{file}`master.cf`
|
|
'';
|
|
};
|
|
|
|
maxproc = lib.mkOption {
|
|
type = lib.types.int;
|
|
example = 1;
|
|
description = ''
|
|
The maximum number of processes to spawn for this service. If the
|
|
value is `0` it doesn't have any limit. If
|
|
`null` is given it uses the postfix default of
|
|
`100`.
|
|
'';
|
|
};
|
|
|
|
command = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = name;
|
|
example = "smtpd";
|
|
description = ''
|
|
A program name specifying a Postfix service/daemon process.
|
|
By default it's the attribute {option}`name`.
|
|
'';
|
|
};
|
|
|
|
args = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
example = [
|
|
"-o"
|
|
"smtp_helo_timeout=5"
|
|
];
|
|
description = ''
|
|
Arguments to pass to the {option}`command`. There is no shell
|
|
processing involved and shell syntax is passed verbatim to the
|
|
process.
|
|
'';
|
|
};
|
|
|
|
rawEntry = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
internal = true;
|
|
description = ''
|
|
The raw configuration line for the {file}`master.cf`.
|
|
'';
|
|
};
|
|
};
|
|
|
|
config.rawEntry =
|
|
let
|
|
mkBool = bool: if bool then "y" else "n";
|
|
mkArg = arg: "${lib.optionalString (lib.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 + lib.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 + " " + lib.concatMapStringsSep " " mkArg config.args)
|
|
];
|
|
};
|
|
|
|
masterCfContent =
|
|
let
|
|
|
|
labels = [
|
|
"# service"
|
|
"type"
|
|
"private"
|
|
"unpriv"
|
|
"chroot"
|
|
"wakeup"
|
|
"maxproc"
|
|
"command + args"
|
|
];
|
|
|
|
labelDefaults = [
|
|
"# "
|
|
""
|
|
"(yes)"
|
|
"(yes)"
|
|
"(no)"
|
|
"(never)"
|
|
"(100)"
|
|
""
|
|
""
|
|
];
|
|
|
|
masterCf = lib.mapAttrsToList (lib.const (lib.getAttr "rawEntry")) cfg.settings.master;
|
|
|
|
# A list of the maximum width of the columns across all lines and labels
|
|
maxWidths =
|
|
let
|
|
foldLine =
|
|
line: acc:
|
|
let
|
|
columnLengths = map lib.stringLength line;
|
|
in
|
|
lib.zipListsWith lib.max acc columnLengths;
|
|
# We need to handle the last column specially here, because it's
|
|
# open-ended (command + args).
|
|
lines = [
|
|
labels
|
|
labelDefaults
|
|
]
|
|
++ (map (l: lib.init l ++ [ "" ]) masterCf);
|
|
in
|
|
lib.foldr foldLine (lib.genList (lib.const 0) (lib.length labels)) lines;
|
|
|
|
# Pad a string with spaces from the right (opposite of fixedWidthString).
|
|
pad =
|
|
width: str:
|
|
let
|
|
padWidth = width - lib.stringLength str;
|
|
padding = lib.concatStrings (lib.genList (lib.const " ") padWidth);
|
|
in
|
|
str + lib.optionalString (padWidth > 0) padding;
|
|
|
|
# It's + 2 here, because that's the amount of spacing between columns.
|
|
fullWidth = lib.foldr (width: acc: acc + width + 2) 0 maxWidths;
|
|
|
|
formatLine = line: lib.concatStringsSep " " (lib.zipListsWith pad maxWidths line);
|
|
|
|
formattedLabels =
|
|
let
|
|
sep = "# " + lib.concatStrings (lib.genList (lib.const "=") (fullWidth + 5));
|
|
lines = [
|
|
sep
|
|
(formatLine labels)
|
|
(formatLine labelDefaults)
|
|
sep
|
|
];
|
|
in
|
|
lib.concatStringsSep "\n" lines;
|
|
|
|
in
|
|
formattedLabels
|
|
+ "\n"
|
|
+ lib.concatMapStringsSep "\n" formatLine masterCf
|
|
+ "\n"
|
|
+ cfg.extraMasterConf;
|
|
|
|
headerCheckOptions =
|
|
{ ... }:
|
|
{
|
|
options = {
|
|
pattern = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "/^.*/";
|
|
example = "/^X-Mailer:/";
|
|
description = "A regexp pattern matching the header";
|
|
};
|
|
action = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "DUNNO";
|
|
example = "BCC mail@example.com";
|
|
description = "The action to be executed when the pattern is matched";
|
|
};
|
|
};
|
|
};
|
|
|
|
headerChecks =
|
|
lib.concatStringsSep "\n" (map (x: "${x.pattern} ${x.action}") cfg.headerChecks)
|
|
+ cfg.extraHeaderChecks;
|
|
|
|
aliases =
|
|
let
|
|
separator = lib.optionalString (cfg.aliasMapType == "hash") ":";
|
|
in
|
|
lib.optionalString (cfg.postmasterAlias != "") ''
|
|
postmaster${separator} ${cfg.postmasterAlias}
|
|
''
|
|
+ lib.optionalString (cfg.rootAlias != "") ''
|
|
root${separator} ${cfg.rootAlias}
|
|
''
|
|
+ cfg.extraAliases;
|
|
|
|
aliasesFile = pkgs.writeText "postfix-aliases" aliases;
|
|
canonicalFile = pkgs.writeText "postfix-canonical" cfg.canonical;
|
|
virtualFile = pkgs.writeText "postfix-virtual" cfg.virtual;
|
|
localRecipientMapFile = pkgs.writeText "postfix-local-recipient-map" (
|
|
lib.concatMapStrings (x: x + " ACCEPT\n") cfg.localRecipients
|
|
);
|
|
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
|
|
|
|
{
|
|
|
|
meta.maintainers = with lib.maintainers; [
|
|
dotlambda
|
|
hexa
|
|
];
|
|
|
|
###### interface
|
|
|
|
options = {
|
|
|
|
services.postfix = {
|
|
|
|
enable = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Whether to run the Postfix mail server.";
|
|
};
|
|
|
|
package = lib.mkPackageOption pkgs "postfix" { };
|
|
|
|
enableSmtp = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = ''
|
|
Whether to enable the `smtp` service configured in the master.cf.
|
|
|
|
This service listens for plain text SMTP connections on port 25
|
|
and supports explicit TLS via StartTLS.
|
|
|
|
It is the primary port used by SMTP servers to exchange mail.
|
|
'';
|
|
};
|
|
|
|
enableSubmission = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "
|
|
Whether to enable the `submission` service configured in master.cf.
|
|
|
|
This service listens for plain text SMTP connections on port 587
|
|
and supports explicit TLS via StartTLS.
|
|
|
|
It is a way for clients to login and submit mails after an inband
|
|
connection upgrade using StartTLS.
|
|
|
|
::: {.warning}
|
|
[RFC 8314](https://www.rfc-editor.org/rfc/rfc8314) discourages the use
|
|
of explicit TLS for mail submissionn.
|
|
:::
|
|
";
|
|
};
|
|
|
|
enableSubmissions = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to enable the `submissions` service configured in master.cf.
|
|
|
|
This service listen for implicit TLS connections on port 465.
|
|
|
|
::: {.info}
|
|
Per [RFC 8314](https://www.rfc-editor.org/rfc/rfc8314) implicit TLS
|
|
is recommended for mail submission.
|
|
:::
|
|
'';
|
|
};
|
|
|
|
submissionOptions = lib.mkOption {
|
|
type = with lib.types; attrsOf str;
|
|
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";
|
|
};
|
|
|
|
submissionsOptions = lib.mkOption {
|
|
type = with lib.types; attrsOf str;
|
|
default = {
|
|
smtpd_sasl_auth_enable = "yes";
|
|
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
|
|
milter_macro_daemon_name = "ORIGINATING";
|
|
};
|
|
example = {
|
|
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 via smtps in master.cf.
|
|
|
|
smtpd_tls_security_level will be set to encrypt, if it is missing
|
|
or has one of the values "may" or "none".
|
|
|
|
smtpd_tls_wrappermode with value "yes" will be added automatically.
|
|
'';
|
|
};
|
|
|
|
setSendmail = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Whether to set the system sendmail to postfix's.";
|
|
};
|
|
|
|
user = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "postfix";
|
|
description = "What to call the Postfix user (must be used only for postfix).";
|
|
};
|
|
|
|
group = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "postfix";
|
|
description = "What to call the Postfix group (must be used only for postfix).";
|
|
};
|
|
|
|
setgidGroup = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "postdrop";
|
|
description = ''
|
|
How to call postfix setgid group (for postdrop). Should
|
|
be uniquely used group.
|
|
'';
|
|
};
|
|
|
|
postmasterAlias = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "root";
|
|
description = ''
|
|
Who should receive postmaster e-mail. Multiple values can be added by
|
|
separating values with comma.
|
|
'';
|
|
};
|
|
|
|
rootAlias = lib.mkOption {
|
|
type = lib.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 = lib.mkOption {
|
|
type = lib.types.lines;
|
|
default = "";
|
|
description = ''
|
|
Additional entries to put verbatim into aliases file, cf. man-page {manpage}`aliases(8)`.
|
|
'';
|
|
};
|
|
|
|
aliasMapType = lib.mkOption {
|
|
type =
|
|
with lib.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.";
|
|
};
|
|
|
|
settings = {
|
|
main = lib.mkOption {
|
|
type = lib.types.submodule {
|
|
freeformType =
|
|
with types;
|
|
attrsOf (
|
|
nullOr (oneOf [
|
|
bool
|
|
int
|
|
str
|
|
(listOf str)
|
|
])
|
|
);
|
|
options = {
|
|
message_size_limit = mkOption {
|
|
type = with types; nullOr int;
|
|
default = 10240000; # 10 MiB
|
|
example = 52428800; # 50 MiB
|
|
description = ''
|
|
Maximum size of an email message in bytes.
|
|
|
|
<https://www.postfix.org/postconf.5.html#message_size_limit>
|
|
'';
|
|
};
|
|
|
|
mydestination = mkOption {
|
|
type =
|
|
with types;
|
|
nullOr (oneOf [
|
|
str
|
|
(listOf str)
|
|
]);
|
|
default = [
|
|
"$myhostname"
|
|
"localhost.$mydomain"
|
|
"localhost"
|
|
];
|
|
description = ''
|
|
List of domain names intended for local delivery using /etc/passwd and /etc/aliases.
|
|
|
|
::: {.warning}
|
|
Do not include [virtual](https://www.postfix.org/VIRTUAL_README.html) domains in this list.
|
|
:::
|
|
|
|
<https://www.postfix.org/postconf.5.html#mydestination>
|
|
'';
|
|
};
|
|
|
|
myhostname = mkOption {
|
|
type = with types; nullOr types.str;
|
|
default = null;
|
|
example = "mail.example.com";
|
|
description = ''
|
|
The internet hostname of this mail system.
|
|
|
|
Leave unset to default to the system hostname with the {option}`mydomain` suffix.
|
|
|
|
<https://www.postfix.org/postconf.5.html#myhostname>
|
|
'';
|
|
};
|
|
|
|
mynetworks = mkOption {
|
|
type = with types; nullOr (listOf str);
|
|
default = null;
|
|
example = [
|
|
"127.0.0.0/8"
|
|
"::1"
|
|
];
|
|
description = ''
|
|
List of trusted remote SMTP clients, that are allowed to relay mail.
|
|
|
|
Leave unset to let Postfix populate this list based on the {option}`mynetworks_style` setting.
|
|
|
|
<https://www.postfix.org/postconf.5.html#mynetworks>
|
|
'';
|
|
};
|
|
|
|
mynetworks_style = mkOption {
|
|
type =
|
|
with types;
|
|
nullOr (enum [
|
|
"host"
|
|
"subnet"
|
|
"class"
|
|
]);
|
|
default = "host";
|
|
description = ''
|
|
The method used for generating the default value for {option}`mynetworks`, if that option is unset.
|
|
|
|
<https://www.postfix.org/postconf.5.html#mynetworks_style>
|
|
'';
|
|
};
|
|
|
|
recipient_delimiter = lib.mkOption {
|
|
type = with types; nullOr str;
|
|
default = "";
|
|
example = "+";
|
|
description = ''
|
|
Set of characters used as the delimiters for address extensions.
|
|
|
|
This allows creating different forwarding rules per extension.
|
|
|
|
<https://www.postfix.org/postconf.5.html#recipient_delimiter>
|
|
'';
|
|
};
|
|
|
|
relayhost = mkOption {
|
|
type = with types; nullOr (listOf str);
|
|
default = [ ];
|
|
example = [ "[relay.example.com]:587" ];
|
|
description = ''
|
|
List of hosts to use for relaying outbound mail.
|
|
|
|
::: {.note}
|
|
Putting the hostname in angled brackets, e.g. `[relay.example.com]`, turns off MX and SRV lookups for the hostname.
|
|
:::
|
|
|
|
<https://www.postfix.org/postconf.5.html#relayhost>
|
|
'';
|
|
};
|
|
|
|
relay_domains = mkOption {
|
|
type = with types; nullOr (listOf str);
|
|
default = [ ];
|
|
example = [ "lists.example.com" ];
|
|
description = ''
|
|
List of domains delivered via the relay transport.
|
|
|
|
<https://www.postfix.org/postconf.5.html#relay_domains>
|
|
'';
|
|
};
|
|
|
|
smtp_tls_CAfile = mkOption {
|
|
type = types.path;
|
|
default = config.security.pki.caBundle;
|
|
defaultText = literalExpression ''
|
|
config.security.pki.caBundle
|
|
'';
|
|
example = literalExpression ''
|
|
''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
|
|
'';
|
|
description = ''
|
|
File containing CA certificates of root CAs trusted to sign either remote SMTP server certificates or intermediate CA certificates.
|
|
|
|
Defaults to the system CA bundle that is managed through the `security.pki` options.
|
|
|
|
<https://www.postfix.org/postconf.5.html#smtp_tls_CAfile>
|
|
'';
|
|
};
|
|
|
|
smtp_tls_security_level = mkOption {
|
|
type = types.enum [
|
|
"none"
|
|
"may"
|
|
"encrypt"
|
|
"dane"
|
|
"dane-only"
|
|
"fingerprint"
|
|
"verify"
|
|
"secure"
|
|
];
|
|
default = "may";
|
|
description = ''
|
|
The client TLS security level.
|
|
|
|
::: {.tip}
|
|
Use `dane` with a local DNSSEC validating DNS resolver enabled.
|
|
:::
|
|
|
|
<https://www.postfix.org/postconf.5.html#smtp_tls_security_level>
|
|
'';
|
|
};
|
|
|
|
smtpd_tls_chain_files = mkOption {
|
|
type = with types; listOf path;
|
|
default = [ ];
|
|
example = [
|
|
"/var/lib/acme/mail.example.com/privkey.pem"
|
|
"/var/lib/acme/mail.example.com/fullchain.pem"
|
|
];
|
|
description = ''
|
|
List of paths to the server private keys and certificates.
|
|
|
|
::: {.caution}
|
|
The order of items matters and a private key must always be followed by the corresponding certificate.
|
|
:::
|
|
|
|
<https://www.postfix.org/postconf.5.html#smtpd_tls_chain_files>
|
|
'';
|
|
};
|
|
|
|
smtpd_tls_security_level = mkOption {
|
|
type = types.enum [
|
|
"none"
|
|
"may"
|
|
"encrypt"
|
|
];
|
|
default =
|
|
if config.services.postfix.settings.main.smtpd_tls_chain_files != [ ] then "may" else "none";
|
|
defaultText = lib.literalExpression ''
|
|
if config.services.postfix.settings.main.smtpd_tls_chain_files != [ ] then "may" else "none"
|
|
'';
|
|
example = "may";
|
|
description = ''
|
|
The server TLS security level. Enable TLS by configuring at least `may`.
|
|
|
|
<https://www.postfix.org/postconf.5.html#smtpd_tls_security_level>
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
description = ''
|
|
The main.cf configuration file as key value set.
|
|
|
|
Null values will not be rendered.
|
|
|
|
::: {.tip}
|
|
Check `postconf -d` for the default values of all settings.
|
|
:::
|
|
'';
|
|
example = {
|
|
mail_owner = "postfix";
|
|
smtp_tls_security_level = "may";
|
|
};
|
|
};
|
|
|
|
master = lib.mkOption {
|
|
type = lib.types.attrsOf (lib.types.submodule masterCfOptions);
|
|
default = { };
|
|
example = {
|
|
submission = {
|
|
type = "inet";
|
|
args = [
|
|
"-o"
|
|
"smtpd_tls_security_level=encrypt"
|
|
];
|
|
};
|
|
};
|
|
description = ''
|
|
The {file}`master.cf` configuration file as an attribute set of service
|
|
defitions
|
|
|
|
::: {.tip}
|
|
Check <https://www.postfix.org/master.5.html> for possible settings.
|
|
:::
|
|
'';
|
|
};
|
|
|
|
};
|
|
|
|
canonical = lib.mkOption {
|
|
type = lib.types.lines;
|
|
default = "";
|
|
description = ''
|
|
Entries for the {manpage}`canonical(5)` table.
|
|
'';
|
|
};
|
|
|
|
virtual = lib.mkOption {
|
|
type = lib.types.lines;
|
|
default = "";
|
|
description = ''
|
|
Entries for the virtual alias map, cf. man-page {manpage}`virtual(5)`.
|
|
'';
|
|
};
|
|
|
|
virtualMapType = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"hash"
|
|
"regexp"
|
|
"pcre"
|
|
];
|
|
default = "hash";
|
|
description = ''
|
|
What type of virtual alias map file to use. Use `"regexp"` for regular expressions.
|
|
'';
|
|
};
|
|
|
|
localRecipients = lib.mkOption {
|
|
type = with lib.types; nullOr (listOf str);
|
|
default = null;
|
|
description = ''
|
|
List of accepted local users. Specify a bare username, an
|
|
`"@domain.tld"` wild-card, or a complete
|
|
`"user@domain.tld"` address. If set, these names end
|
|
up in the local recipient map -- see the {manpage}`local(8)` man-page -- and
|
|
effectively replace the system user database lookup that's otherwise
|
|
used by default.
|
|
'';
|
|
};
|
|
|
|
transport = lib.mkOption {
|
|
default = "";
|
|
type = lib.types.lines;
|
|
description = ''
|
|
Entries for the transport map, cf. man-page {manpage}`transport(5)`.
|
|
'';
|
|
};
|
|
|
|
dnsBlacklists = lib.mkOption {
|
|
default = [ ];
|
|
type = with lib.types; listOf str;
|
|
description = "dns blacklist servers to use with smtpd_client_restrictions";
|
|
};
|
|
|
|
dnsBlacklistOverrides = lib.mkOption {
|
|
default = "";
|
|
type = lib.types.lines;
|
|
description = "contents of check_client_access for overriding dnsBlacklists";
|
|
};
|
|
|
|
extraMasterConf = lib.mkOption {
|
|
type = lib.types.lines;
|
|
default = "";
|
|
example = "submission inet n - n - - smtpd";
|
|
description = "Extra lines to append to the generated master.cf file.";
|
|
};
|
|
|
|
enableHeaderChecks = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
example = true;
|
|
description = "Whether to enable postfix header checks";
|
|
};
|
|
|
|
headerChecks = lib.mkOption {
|
|
type = lib.types.listOf (lib.types.submodule headerCheckOptions);
|
|
default = [ ];
|
|
example = [
|
|
{
|
|
pattern = "/^X-Spam-Flag:/";
|
|
action = "REDIRECT spam@example.com";
|
|
}
|
|
];
|
|
description = "Postfix header checks.";
|
|
};
|
|
|
|
extraHeaderChecks = lib.mkOption {
|
|
type = lib.types.lines;
|
|
default = "";
|
|
example = "/^X-Spam-Flag:/ REDIRECT spam@example.com";
|
|
description = "Extra lines to /etc/postfix/header_checks file.";
|
|
};
|
|
|
|
aliasFiles = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.path;
|
|
default = { };
|
|
description = "Aliases' tables to be compiled and placed into /var/lib/postfix/conf.";
|
|
};
|
|
|
|
mapFiles = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.path;
|
|
default = { };
|
|
description = "Maps to be compiled and placed into /var/lib/postfix/conf.";
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
###### implementation
|
|
|
|
config = lib.mkIf config.services.postfix.enable (
|
|
lib.mkMerge [
|
|
{
|
|
|
|
environment = {
|
|
etc.postfix.source = "/var/lib/postfix/conf";
|
|
|
|
# This makes it comfortable to run 'postqueue/postdrop' for example.
|
|
systemPackages = [ cfg.package ];
|
|
};
|
|
|
|
services.mail.sendmailSetuidWrapper = lib.mkIf config.services.postfix.setSendmail {
|
|
program = "sendmail";
|
|
source = lib.getExe' cfg.package "sendmail";
|
|
owner = "root";
|
|
group = setgidGroup;
|
|
setuid = false;
|
|
setgid = true;
|
|
};
|
|
|
|
security.wrappers.mailq = {
|
|
program = "mailq";
|
|
source = lib.getExe' cfg.package "mailq";
|
|
owner = "root";
|
|
group = setgidGroup;
|
|
setuid = false;
|
|
setgid = true;
|
|
};
|
|
|
|
security.wrappers.postqueue = {
|
|
program = "postqueue";
|
|
source = lib.getExe' cfg.package "postqueue";
|
|
owner = "root";
|
|
group = setgidGroup;
|
|
setuid = false;
|
|
setgid = true;
|
|
};
|
|
|
|
security.wrappers.postdrop = {
|
|
program = "postdrop";
|
|
source = lib.getExe' cfg.package "postdrop";
|
|
owner = "root";
|
|
group = setgidGroup;
|
|
setuid = false;
|
|
setgid = true;
|
|
};
|
|
|
|
users.users = lib.optionalAttrs (user == "postfix") {
|
|
postfix = {
|
|
description = "Postfix mail server user";
|
|
uid = config.ids.uids.postfix;
|
|
group = group;
|
|
};
|
|
};
|
|
|
|
users.groups =
|
|
lib.optionalAttrs (group == "postfix") {
|
|
${group}.gid = config.ids.gids.postfix;
|
|
}
|
|
// lib.optionalAttrs (setgidGroup == "postdrop") {
|
|
${setgidGroup}.gid = config.ids.gids.postdrop;
|
|
};
|
|
|
|
systemd.services.postfix-setup = {
|
|
description = "Setup for Postfix mail server";
|
|
serviceConfig.RemainAfterExit = true;
|
|
serviceConfig.Type = "oneshot";
|
|
script = ''
|
|
# 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 ${cfg.package}/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 ${cfg.package}/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
|
|
|
|
${lib.concatStringsSep "\n" (
|
|
lib.mapAttrsToList (to: from: ''
|
|
ln -sf ${from} /var/lib/postfix/conf/${to}
|
|
${lib.getExe' cfg.package "postalias"} -o -p /var/lib/postfix/conf/${to}
|
|
'') cfg.aliasFiles
|
|
)}
|
|
${lib.concatStringsSep "\n" (
|
|
lib.mapAttrsToList (to: from: ''
|
|
ln -sf ${from} /var/lib/postfix/conf/${to}
|
|
${lib.getExe' cfg.package "postmap"} -o -p /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
|
|
${lib.getExe' cfg.package "postfix"} set-permissions config_directory=/var/lib/postfix/conf
|
|
'';
|
|
};
|
|
|
|
systemd.services.postfix = {
|
|
description = "Postfix mail server";
|
|
|
|
documentation = [ "man:postfix(1)" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [
|
|
"network.target"
|
|
"postfix-setup.service"
|
|
];
|
|
requires = [ "postfix-setup.service" ];
|
|
path = [ cfg.package ];
|
|
|
|
serviceConfig = {
|
|
Type = "forking";
|
|
Restart = "always";
|
|
PIDFile = "/var/lib/postfix/queue/pid/master.pid";
|
|
ExecStart = "${lib.getExe' cfg.package "postfix"} start";
|
|
ExecStop = "${lib.getExe' cfg.package "postfix"} stop";
|
|
ExecReload = "${lib.getExe' cfg.package "postfix"} reload";
|
|
|
|
# Hardening
|
|
PrivateTmp = true;
|
|
PrivateDevices = true;
|
|
ProtectSystem = "full";
|
|
CapabilityBoundingSet = [ "~CAP_NET_ADMIN CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_MODULE" ];
|
|
MemoryDenyWriteExecute = true;
|
|
ProtectKernelModules = true;
|
|
ProtectKernelTunables = true;
|
|
ProtectControlGroups = true;
|
|
RestrictAddressFamilies = [
|
|
"AF_INET"
|
|
"AF_INET6"
|
|
"AF_NETLINK"
|
|
"AF_UNIX"
|
|
];
|
|
RestrictNamespaces = true;
|
|
RestrictRealtime = true;
|
|
};
|
|
};
|
|
|
|
services.postfix.settings.main =
|
|
(lib.mapAttrs (_: v: lib.mkDefault v) {
|
|
compatibility_level = cfg.package.version;
|
|
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 = "${cfg.package}/etc/postfix";
|
|
command_directory = "${cfg.package}/bin";
|
|
sample_directory = "/etc/postfix";
|
|
newaliases_path = lib.getExe' cfg.package "newaliases";
|
|
mailq_path = lib.getExe' cfg.package "mailq";
|
|
readme_directory = false;
|
|
sendmail_path = lib.getExe' cfg.package "sendmail";
|
|
daemon_directory = "${cfg.package}/libexec/postfix";
|
|
manpage_directory = "${cfg.package}/share/man";
|
|
html_directory = "${cfg.package}/share/postfix/doc/html";
|
|
shlib_directory = false;
|
|
mail_spool_directory = "/var/spool/mail/";
|
|
setgid_group = cfg.setgidGroup;
|
|
})
|
|
// lib.optionalAttrs haveAliases { alias_maps = [ "${cfg.aliasMapType}:/etc/postfix/aliases" ]; }
|
|
// lib.optionalAttrs haveTransport { transport_maps = [ "hash:/etc/postfix/transport" ]; }
|
|
// lib.optionalAttrs haveVirtual {
|
|
virtual_alias_maps = [ "${cfg.virtualMapType}:/etc/postfix/virtual" ];
|
|
}
|
|
// lib.optionalAttrs haveLocalRecipients {
|
|
local_recipient_maps = [
|
|
"hash:/etc/postfix/local_recipients"
|
|
]
|
|
++ lib.optional haveAliases "$alias_maps";
|
|
}
|
|
// lib.optionalAttrs (cfg.dnsBlacklists != [ ]) { smtpd_client_restrictions = clientRestrictions; }
|
|
// lib.optionalAttrs cfg.enableHeaderChecks {
|
|
header_checks = [ "regexp:/etc/postfix/header_checks" ];
|
|
};
|
|
|
|
services.postfix.settings.master = {
|
|
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;
|
|
};
|
|
}
|
|
// lib.optionalAttrs cfg.enableSubmission {
|
|
submission = {
|
|
type = "inet";
|
|
private = false;
|
|
command = "smtpd";
|
|
args =
|
|
let
|
|
mkKeyVal = opt: val: [
|
|
"-o"
|
|
(opt + "=" + val)
|
|
];
|
|
in
|
|
lib.concatLists (lib.mapAttrsToList mkKeyVal cfg.submissionOptions);
|
|
};
|
|
}
|
|
// lib.optionalAttrs cfg.enableSmtp {
|
|
smtp_inet = {
|
|
name = "smtp";
|
|
type = "inet";
|
|
private = false;
|
|
command = "smtpd";
|
|
};
|
|
smtp = { };
|
|
relay = {
|
|
command = "smtp";
|
|
args = [
|
|
"-o"
|
|
"smtp_fallback_relay="
|
|
];
|
|
};
|
|
}
|
|
// lib.optionalAttrs cfg.enableSubmissions {
|
|
submissions = {
|
|
type = "inet";
|
|
private = false;
|
|
command = "smtpd";
|
|
args =
|
|
let
|
|
mkKeyVal = opt: val: [
|
|
"-o"
|
|
(opt + "=" + val)
|
|
];
|
|
adjustSmtpTlsSecurityLevel =
|
|
!(cfg.submissionsOptions ? smtpd_tls_security_level)
|
|
|| cfg.submissionsOptions.smtpd_tls_security_level == "none"
|
|
|| cfg.submissionsOptions.smtpd_tls_security_level == "may";
|
|
submissionsOptions =
|
|
cfg.submissionsOptions
|
|
// {
|
|
smtpd_tls_wrappermode = "yes";
|
|
}
|
|
// lib.optionalAttrs adjustSmtpTlsSecurityLevel {
|
|
smtpd_tls_security_level = "encrypt";
|
|
};
|
|
in
|
|
lib.concatLists (lib.mapAttrsToList mkKeyVal submissionsOptions);
|
|
};
|
|
};
|
|
}
|
|
|
|
(lib.mkIf haveAliases {
|
|
services.postfix.aliasFiles.aliases = aliasesFile;
|
|
})
|
|
(lib.mkIf haveCanonical {
|
|
services.postfix.mapFiles.canonical = canonicalFile;
|
|
})
|
|
(lib.mkIf haveTransport {
|
|
services.postfix.mapFiles.transport = transportFile;
|
|
})
|
|
(lib.mkIf haveVirtual {
|
|
services.postfix.mapFiles.virtual = virtualFile;
|
|
})
|
|
(lib.mkIf haveLocalRecipients {
|
|
services.postfix.mapFiles.local_recipients = localRecipientMapFile;
|
|
})
|
|
(lib.mkIf cfg.enableHeaderChecks {
|
|
services.postfix.mapFiles.header_checks = headerChecksFile;
|
|
})
|
|
(lib.mkIf (cfg.dnsBlacklists != [ ]) {
|
|
services.postfix.mapFiles.client_access = checkClientAccessFile;
|
|
})
|
|
]
|
|
);
|
|
|
|
imports = [
|
|
(lib.mkRemovedOptionModule [ "services" "postfix" "sslCACert" ]
|
|
"services.postfix.sslCACert was replaced by services.postfix.tlsTrustedAuthorities. In case you intend that your server should validate requested client certificates use services.postfix.settings.main.smtp_tls_CAfile."
|
|
)
|
|
(lib.mkRemovedOptionModule [ "services" "postfix" "sslCert" ]
|
|
"services.postfix.sslCert was removed. Use services.postfix.settings.main.smtpd_tls_chain_files for the server certificate, or services.postfix.settings.main.smtp_tls_chain_files for the client certificate."
|
|
)
|
|
(lib.mkRemovedOptionModule [ "services" "postfix" "sslKey" ]
|
|
"services.postfix.sslKey was removed. Use services.postfix.settings.main.smtpd_tls_chain_files for server private key, or services.postfix.settings.main.smtp_tls_chain_files for the client private key."
|
|
)
|
|
(lib.mkRemovedOptionModule [ "services" "postfix" "lookupMX" ]
|
|
"services.postfix.lookupMX was removed. Use services.postfix.settings.main.relayhost and put the hostname in angled brackets, if you need to turn off MX and SRV lookups."
|
|
)
|
|
(lib.mkRemovedOptionModule [ "services" "postfix" "relayHost" ]
|
|
"services.postfix.relayHost was removed in favor of services.postfix.settings.main.relayhost, which now takes a list of host:port."
|
|
)
|
|
(lib.mkRemovedOptionModule [ "services" "postfix" "relayPort" ]
|
|
"services.postfix.relayPort was removed in favor of services.postfix.settings.main.relayhost, which now takes a list of host:port."
|
|
)
|
|
(lib.mkRemovedOptionModule [ "services" "postfix" "extraConfig" ]
|
|
"services.postfix.extraConfig was replaced by the structured freeform services.postfix.settings.main option."
|
|
)
|
|
(lib.mkRenamedOptionModule
|
|
[ "services" "postfix" "networks" ]
|
|
[ "services" "postfix" "settings" "main" "mynetworks" ]
|
|
)
|
|
(lib.mkRenamedOptionModule
|
|
[ "services" "postfix" "networksStyle" ]
|
|
[ "services" "postfix" "settings" "main" "mynetworks_style" ]
|
|
)
|
|
(lib.mkRenamedOptionModule
|
|
[ "services" "postfix" "hostname" ]
|
|
[ "services" "postfix" "settings" "main" "myhostname" ]
|
|
)
|
|
(lib.mkRenamedOptionModule
|
|
[ "services" "postfix" "domain" ]
|
|
[ "services" "postfix" "settings" "main" "mydomain" ]
|
|
)
|
|
(lib.mkRenamedOptionModule
|
|
[ "services" "postfix" "origin" ]
|
|
[ "services" "postfix" "settings" "main" "myorigin" ]
|
|
)
|
|
(lib.mkRenamedOptionModule
|
|
[ "services" "postfix" "destination" ]
|
|
[ "services" "postfix" "settings" "main" "mydestination" ]
|
|
)
|
|
(lib.mkRenamedOptionModule
|
|
[ "services" "postfix" "relayDomains" ]
|
|
[ "services" "postfix" "settings" "main" "relay_domains" ]
|
|
)
|
|
(lib.mkRenamedOptionModule
|
|
[ "services" "postfix" "recipientDelimiter" ]
|
|
[ "services" "postfix" "settings" "main" "recipient_delimiter" ]
|
|
)
|
|
(lib.mkRenamedOptionModule
|
|
[ "services" "postfix" "tlsTrustedAuthoriies" ]
|
|
[ "services" "postfix" "settings" "main" "smtp_tls_CAfile" ]
|
|
)
|
|
(lib.mkRenamedOptionModule
|
|
[ "services" "postfix" "config" ]
|
|
[ "services" "postfix" "settings" "main" ]
|
|
)
|
|
(lib.mkRenamedOptionModule
|
|
[ "services" "postfix" "masterConfig" ]
|
|
[ "services" "postfix" "settings" "master" ]
|
|
)
|
|
|
|
(lib.mkChangedOptionModule
|
|
[ "services" "postfix" "useDane" ]
|
|
[ "services" "postfix" "settings" "main" "smtp_tls_security_level" ]
|
|
(config: lib.mkIf config.services.postfix.useDane "dane")
|
|
)
|
|
(lib.mkRenamedOptionModule [ "services" "postfix" "useSrs" ] [ "services" "pfix-srsd" "enable" ])
|
|
];
|
|
}
|