nixfiles/hosts/hetzner-vm/services/mailserver/postfix.nix

194 lines
6.6 KiB
Nix

{ config, pkgs, lib, ... }:
let
mail_config = (import ./config.nix { config = config; });
submissionHeaderCleanupRules =
pkgs.writeText "submission_header_cleanup_rules" (''
/^Received:/ IGNORE
/^X-Originating-IP:/ IGNORE
/^X-Mailer:/ IGNORE
/^User-Agent:/ IGNORE
/^X-Enigmail:/ IGNORE
/^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${mail_config.fqdn}>
'');
inetSocket = addr: port: "inet:[${toString port}@${addr}]";
unixSocket = sock: "unix:${sock}";
# Merge several lookup tables. A lookup table is a attribute set where
# - the key is an address (user@example.com) or a domain (@example.com)
# - the value is a list of addresses
mergeLookupTables = tables: lib.zipAttrsWith (n: v: lib.flatten v) tables;
# valiases_postfix :: Map String [String]
valiases_postfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
(name: value:
let to = name;
in map (from: { "${from}" = to; }) (value.aliases ++ lib.singleton name))
mail_config.accounts));
# all_valiases_postfix :: Map String [String]
all_valiases_postfix = mergeLookupTables [ valiases_postfix ];
# lookupTableToString :: Map String [String] -> String
lookupTableToString = attrs:
let valueToString = value: lib.concatStringsSep ", " value;
in lib.concatStringsSep "\n"
(lib.mapAttrsToList (name: value: "${name} ${valueToString value}") attrs);
# valiases_file :: Path
valiases_file = let
content = lookupTableToString (mergeLookupTables [ all_valiases_postfix ]);
in builtins.toFile "valias" content;
# vhosts_file :: Path
vhosts_file =
builtins.toFile "vhosts" (lib.concatStringsSep "\n" mail_config.domains);
vaccounts_file =
builtins.toFile "vaccounts" (lookupTableToString all_valiases_postfix);
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
policyd-spf = pkgs.writeText "policyd-spf.conf" mail_config.policyd_config;
submissionOptions = {
smtpd_tls_security_level = "encrypt";
smtpd_sasl_auth_enable = "yes";
smtpd_sasl_type = "dovecot";
smtpd_sasl_path = "/run/dovecot2/auth";
smtpd_sasl_security_options = "noanonymous";
smtpd_sasl_local_domain = "$myhostname";
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
smtpd_sender_login_maps = "hash:/etc/postfix/vaccounts";
smtpd_sender_restrictions = "reject_sender_login_mismatch";
smtpd_recipient_restrictions =
"reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
cleanup_service_name = "submission-header-cleanup";
};
tls_allowed = "TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
tls_disallow = "MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL";
in {
services.postfix = {
enable = true;
hostname = "${mail_config.fqdn}";
networksStyle = "host";
mapFiles."valias" = valiases_file;
mapFiles."vaccounts" = vaccounts_file;
sslCert = mail_config.ssl_config.cert;
sslKey = mail_config.ssl_config.key;
enableSubmission = true;
enableSubmissions = true;
virtual = lookupTableToString (mergeLookupTables [ all_valiases_postfix ]);
config = {
# Extra Config
mydestination = "";
recipient_delimiter = "+";
smtpd_banner = "${mail_config.fqdn} ESMTP NO UCE";
disable_vrfy_command = true;
message_size_limit = "20971520";
virtual_uid_maps =
"static:${toString mail_config.vmail_config.user_group_id}";
virtual_gid_maps =
"static:${toString mail_config.vmail_config.user_group_id}";
virtual_mailbox_base = "${mail_config.vmail_config.directory}";
virtual_mailbox_domains = vhosts_file;
virtual_mailbox_maps = mappedFile "valias";
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
lmtp_destination_recipient_limit = "1";
smtpd_sasl_type = "dovecot";
smtpd_sasl_path = "/run/dovecot2/auth";
smtpd_sasl_auth_enable = true;
smtpd_relay_restrictions = [
"permit_mynetworks"
"permit_sasl_authenticated"
"reject_unauth_destination"
];
policy-spf_time_limit = "3600s";
smtpd_recipient_restrictions = [
#"check_recipient_access ${mappedFile "denied_recipients"}"
#"check_recipient_access ${mappedFile "reject_recipients"}"
"check_policy_service unix:private/policy-spf"
];
# TLS settings, inspired by https://github.com/jeaye/nix-files
# Submission by mail clients is handled in submissionOptions
smtpd_tls_security_level = "may";
# strong might suffice and is computationally less expensive
smtpd_tls_eecdh_grade = "ultra";
# Only Alow Modern TLS
smtp_tls_protocols = tls_allowed;
smtpd_tls_protocols = tls_allowed;
smtp_tls_mandatory_protocols = tls_allowed;
smtpd_tls_mandatory_protocols = tls_allowed;
# Disable Old Ciphers
smtp_tls_exclude_ciphers = tls_disallow;
smtpd_tls_exclude_ciphers = tls_disallow;
smtp_tls_mandatory_exclude_ciphers = tls_disallow;
smtpd_tls_mandatory_exclude_ciphers = tls_disallow;
smtp_tls_ciphers = "high";
smtpd_tls_ciphers = "high";
smtp_tls_mandatory_ciphers = "high";
smtpd_tls_mandatory_ciphers = "high";
tls_preempt_cipherlist = true;
smtpd_tls_auth_only = true;
smtpd_tls_loglevel = "1";
tls_random_source = "dev:/dev/urandom";
smtpd_milters = [
"unix:/run/opendkim/opendkim.sock"
"unix:/run/rspamd/rspamd-milter.sock"
];
non_smtpd_milters = [ "unix:/run/opendkim/opendkim.sock" ];
milter_protocol = "6";
milter_mail_macros =
"i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}";
};
submissionOptions = submissionOptions;
submissionsOptions = submissionOptions;
masterConfig = {
"lmtp" = {
# Add headers when delivering, see http://www.postfix.org/smtp.8.html
# D => Delivered-To, O => X-Original-To, R => Return-Path
args = [ "flags=O" ];
};
"policy-spf" = {
type = "unix";
privileged = true;
chroot = false;
command = "spawn";
args = [
"user=nobody"
"argv=${pkgs.pypolicyd-spf}/bin/policyd-spf"
"${policyd-spf}"
];
};
"submission-header-cleanup" = {
type = "unix";
private = false;
chroot = false;
maxproc = 0;
command = "cleanup";
args = [ "-o" "header_checks=pcre:${submissionHeaderCleanupRules}" ];
};
};
};
}