nixos-config/modules/mailserver/postfix.nix

215 lines
6.3 KiB
Nix
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SPDX-FileCopyrightText: 2021-2024 Simon Bruder <simon@sbruder.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
{ config, lib, pkgs, ... }:
let
cfg = config.sbruder.mailserver;
listToString = lib.concatStringsSep ",";
# List of attribute sets with single key-value pair
plainAliases = (lib.flatten
(map
({ address, aliases, ... }:
map
(alias: { "${alias}" = address; })
(aliases ++ lib.singleton address))
cfg.users));
# Attribute set with every alias mapped to a list of receivers
mergedAliases = (lib.attrsets.foldAttrs
(val: col: lib.singleton val ++ col)
[ ]
plainAliases);
# Contents of the aliases file
aliasesString = (lib.concatStringsSep
"\n"
(lib.mapAttrsToList
(alias: addresses: "${alias} ${listToString addresses}")
mergedAliases));
valiases = pkgs.writeText "valiases" aliasesString;
submissionHeaderCleanupRules = pkgs.writeText "submission_header_cleanup_rules"
(lib.concatMapStringsSep
"\n"
(regex: "${regex} IGNORE")
cfg.cleanHeaders);
in
lib.mkIf cfg.enable {
services.postfix = {
enable = true;
setSendmail = lib.mkForce false;
enableSubmission = true; # plain/STARTTLS (latter is forced in submissionOptions)
enableSubmissions = true; # submission with implicit TLS (TCP/465)
hostname = cfg.fqdn;
networksStyle = "host";
sslCert = "${cfg.certDir}/fullchain.pem";
sslKey = "${cfg.certDir}/key.pem";
recipientDelimiter = "+";
mapFiles = {
inherit valiases;
restricted_senders = pkgs.writeText "restricted_senders"
(lib.concatStringsSep
"\n"
(lib.flatten
(map
(user: (map (address: "${address} local_only") ([ user.address ] ++ user.aliases)))
(lib.filter (user: user.localOnly) cfg.users))));
local_domains = pkgs.writeText "local_domains"
(lib.concatMapStringsSep
"\n"
(domain: "${domain} OK")
cfg.domains);
};
config = {
# General
smtpd_banner = "${cfg.fqdn} ESMTP NO UCE";
disable_vrfy_command = true; # disable check if mailbox exists
enable_long_queue_ids = true; # better for debugging
strict_rfc821_envelopes = true; # only accept properly formatted envelope
message_size_limit = "50331648"; # 48MiB
virtual_mailbox_domains = listToString cfg.domains;
virtual_mailbox_maps = "hash:/var/lib/postfix/conf/valiases";
virtual_alias_maps = "hash:/var/lib/postfix/conf/valiases";
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
smtpd_recipient_restrictions = listToString [
"reject_non_fqdn_recipient"
"reject_rbl_client ix.dnsbl.manitu.net"
"reject_unknown_recipient_domain"
"reject_unverified_recipient"
];
smtpd_client_restrictions = listToString [
"reject_rbl_client ix.dnsbl.manitu.net"
"reject_unknown_client_hostname"
];
smtpd_sender_restrictions = listToString [
"reject_non_fqdn_sender"
"reject_unknown_sender_domain"
];
# cant be in submissionOptions (which does not support spaces in NixOS)
submission_sender_restrictions = listToString [
"reject_sender_login_mismatch"
"check_sender_access hash:/etc/postfix/restricted_senders"
];
smtpd_restriction_classes = listToString [
"local_only"
];
local_only = listToString [
"check_recipient_access hash:/etc/postfix/local_domains"
"reject"
];
# generated 2021-02-04, Mozilla Guideline v5.6, Postfix 3.5.6, OpenSSL 1.1.1i, intermediate configuration
# https://ssl-config.mozilla.org/#server=postfix&version=3.5.6&config=intermediate&openssl=1.1.1i&guideline=5.6
smtpd_tls_security_level = "may";
smtpd_tls_auth_only = "yes";
smtpd_tls_mandatory_protocols = "!SSLv2, !SSLv3, !TLSv1, !TLSv1.1";
smtpd_tls_protocols = "!SSLv2, !SSLv3, !TLSv1, !TLSv1.1";
smtpd_tls_mandatory_ciphers = "medium";
smtpd_tls_loglevel = "1";
smtpd_tls_received_header = "yes"; # add TLS connection details to Received header
tls_medium_cipherlist = listToString [
"ECDHE-ECDSA-AES128-GCM-SHA256"
"ECDHE-RSA-AES128-GCM-SHA256"
"ECDHE-ECDSA-AES256-GCM-SHA384"
"ECDHE-RSA-AES256-GCM-SHA384"
"ECDHE-ECDSA-CHACHA20-POLY1305"
"ECDHE-RSA-CHACHA20-POLY1305"
"DHE-RSA-AES128-GCM-SHA256"
"DHE-RSA-AES256-GCM-SHA384"
];
tls_preempt_cipherlist = "no";
};
# plain/STARTTLS (forced with smtpd_tls_security_level)
submissionOptions = {
smtpd_tls_security_level = "encrypt";
smtpd_sasl_auth_enable = "yes";
smtpd_sasl_type = "dovecot";
smtpd_sasl_path = "/run/dovecot2/auth";
smtpd_sender_login_maps = "hash:/etc/postfix/valiases";
smtpd_recipient_restrictions = listToString [ ];
smtpd_client_restrictions = listToString [
"permit_sasl_authenticated"
"reject"
];
smtpd_sender_restrictions = "$submission_sender_restrictions";
cleanup_service_name = "submission-header-cleanup";
};
# implicit TLS
submissionsOptions = config.services.postfix.submissionOptions;
masterConfig = {
# Postscreen
smtpd = {
type = "pass";
args = [ "-o" "smtpd_discard_ehlo_keywords=silent-discard,dsn" ];
};
smtp_inet = {
# Partially overrides upstream
name = "smtp";
type = "inet";
private = false;
command = lib.mkForce "postscreen";
maxproc = 1;
};
tlsproxy = {
maxproc = 0;
};
dnsblog = {
maxproc = 0;
};
# Heder cleanup
submission-header-cleanup = {
private = false;
maxproc = 0;
command = "cleanup";
args = [ "-o" "header_checks=pcre:${submissionHeaderCleanupRules}" ];
};
};
};
networking.firewall.allowedTCPPorts = [
25 # SMTP
587 # SMTP submission
465 # SMTP submission (implicit TLS)
];
systemd.services.postfix = {
wants = [ "acme-finished-${cfg.fqdn}.target" ];
requires = [ "dovecot2.service" ];
after = [ "acme-finished-${cfg.fqdn}.target" "dovecot2.service" ];
};
security.acme.certs."${cfg.fqdn}".postRun = ''
if systemctl is-active postfix; then
systemctl --no-block reload postfix
fi
'';
}