Simon Bruder
10b8d432d5
This applies the REUSE specification to the repository, so the licensing information can be tracked for every file individually.
187 lines
5.4 KiB
Nix
187 lines
5.4 KiB
Nix
# SPDX-FileCopyrightText: 2021-2023 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 {
|
||
security.dhparams.params.postfix = { };
|
||
services.postfix = {
|
||
enable = true;
|
||
|
||
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;
|
||
};
|
||
|
||
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"
|
||
];
|
||
|
||
# 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";
|
||
|
||
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";
|
||
|
||
smtpd_tls_dh1024_param_file = config.security.dhparams.params.postfix.path;
|
||
};
|
||
|
||
# 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 = listToString [
|
||
"reject_sender_login_mismatch"
|
||
];
|
||
|
||
cleanup_service_name = "submission-header-cleanup";
|
||
};
|
||
# implicit TLS
|
||
submissionsOptions = config.services.postfix.submissionOptions;
|
||
|
||
masterConfig = {
|
||
# Postscreen
|
||
smtpd = {
|
||
type = "pass";
|
||
};
|
||
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
|
||
'';
|
||
}
|