2024-02-15 13:10:42 +01:00
|
|
|
|
# SPDX-FileCopyrightText: 2021-2024 Simon Bruder <simon@sbruder.de>
|
2024-01-06 01:19:35 +01:00
|
|
|
|
#
|
|
|
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
|
|
2023-05-31 13:11:12 +02:00
|
|
|
|
{ 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;
|
|
|
|
|
|
2024-08-28 12:22:41 +02:00
|
|
|
|
setSendmail = lib.mkForce false;
|
|
|
|
|
|
2023-05-31 13:11:12 +02:00
|
|
|
|
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 = {
|
2023-05-31 13:40:48 +02:00
|
|
|
|
inherit valiases;
|
2024-08-28 11:30:05 +02:00
|
|
|
|
|
|
|
|
|
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);
|
2023-05-31 13:11:12 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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"; # 48 MiB
|
|
|
|
|
|
|
|
|
|
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"
|
|
|
|
|
];
|
|
|
|
|
|
2024-08-28 11:30:05 +02:00
|
|
|
|
# can’t 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"
|
|
|
|
|
];
|
|
|
|
|
|
2023-05-31 13:11:12 +02:00
|
|
|
|
# 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";
|
2024-02-03 00:11:27 +01:00
|
|
|
|
smtpd_tls_received_header = "yes"; # add TLS connection details to Received header
|
2023-05-31 13:11:12 +02:00
|
|
|
|
|
|
|
|
|
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"
|
|
|
|
|
];
|
|
|
|
|
|
2024-08-28 11:30:05 +02:00
|
|
|
|
smtpd_sender_restrictions = "$submission_sender_restrictions";
|
2023-05-31 13:11:12 +02:00
|
|
|
|
|
|
|
|
|
cleanup_service_name = "submission-header-cleanup";
|
|
|
|
|
};
|
|
|
|
|
# implicit TLS
|
|
|
|
|
submissionsOptions = config.services.postfix.submissionOptions;
|
|
|
|
|
|
|
|
|
|
masterConfig = {
|
2023-08-18 15:15:07 +02:00
|
|
|
|
# Postscreen
|
|
|
|
|
smtpd = {
|
|
|
|
|
type = "pass";
|
2024-02-03 01:07:49 +01:00
|
|
|
|
args = [ "-o" "smtpd_discard_ehlo_keywords=silent-discard,dsn" ];
|
2023-08-18 15:15:07 +02:00
|
|
|
|
};
|
|
|
|
|
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
|
2023-05-31 13:11:12 +02:00
|
|
|
|
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
|
|
|
|
|
'';
|
|
|
|
|
}
|