198 lines
6.8 KiB
Nix
198 lines
6.8 KiB
Nix
{ config, pkgs, lib, ... }:
|
|
|
|
let
|
|
mail_config = config.mailserver;
|
|
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 {
|
|
config = (lib.mkIf (mail_config.enable) {
|
|
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";
|
|
|
|
milter_default_action = "quarantine";
|
|
|
|
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}" ];
|
|
};
|
|
};
|
|
};
|
|
});
|
|
}
|