{ config, lib, pkgs, ... }: with (import ./common.nix {inherit config pkgs;}); let cfg = config.mailsystem; mappedFile = name: "hash:/var/lib/postfix/conf/${name}"; runtimeDir = "/run/postfix"; aliases_file = "${runtimeDir}/virtual_aliases"; virtual_domains_file = "${runtimeDir}/virtual_domains"; denied_recipients_file = "${runtimeDir}/denied_recipients"; genPostmapsScript = pkgs.writeScript "generate-postfix-postmaps" '' #!${pkgs.stdenv.shell} set -euo pipefail if (! test -d "${runtimeDir}"); then mkdir "${runtimeDir}" chmod 755 "${runtimeDir}" fi ${pkgs.mailnix}/bin/mailnix "${mailnixCfgFile}" "generate-aliases" > "${aliases_file}" ${pkgs.mailnix}/bin/mailnix "${mailnixCfgFile}" "generate-domains" > "${virtual_domains_file}" ${pkgs.mailnix}/bin/mailnix "${mailnixCfgFile}" "generate-denied-recipients" > "${denied_recipients_file}" ''; submission_header_cleanup_rules = pkgs.writeText "submission_header_cleanup_rules" '' # Removes sensitive headers from mails handed in via the submission port. # See https://thomas-leister.de/mailserver-debian-stretch/ # Uses "pcre" style regex. /^Received:/ IGNORE /^X-Originating-IP:/ IGNORE /^X-Mailer:/ IGNORE /^User-Agent:/ IGNORE /^X-Enigmail:/ IGNORE # Replace the user's submitted hostname with the server's FQDN to hide the # user's host/network. /^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${cfg.fqdn}> ''; tls_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3"; tls_exclude_ciphers = "MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL"; in { config = lib.mkIf cfg.enable { assertions = let isDomain = value: !lib.hasInfix "@" value; aliasedDomains = builtins.filter isDomain (builtins.attrNames cfg.virtualAliases); in map (domain: { assertion = builtins.elem domain cfg.domains; message = "The domain to be aliased (${domain}) must be managed by the mailserver."; }) aliasedDomains; services.postfix = { enable = true; hostname = "${cfg.reverseFqdn}"; networksStyle = "host"; sslCert = sslCertPath; sslKey = sslKeyPath; enableSubmissions = true; mapFiles."virtual_aliases" = aliases_file; mapFiles."denied_recipients" = denied_recipients_file; submissionsOptions = { 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 = mappedFile "virtual_aliases"; 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"; }; config = { mydestination = ""; recipient_delimiter = "+"; smtpd_banner = "${cfg.fqdn} ESMTP NO UCE"; disable_vrfy_command = true; message_size_limit = toString cfg.messageSizeLimit; virtual_uid_maps = "static:${toString cfg.vmailUID}"; virtual_gid_maps = "static:${toString cfg.vmailUID}"; virtual_mailbox_base = cfg.mailDirectory; virtual_mailbox_domains = virtual_domains_file; virtual_alias_maps = [ (mappedFile "virtual_aliases") ]; virtual_mailbox_maps = [ (mappedFile "virtual_aliases") ]; virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp"; # Avoid leakage of X-Original-To, X-Delivered-To headers between recipients lmtp_destination_recipient_limit = "1"; # sasl with dovecot (enforce authentication via dovecot) 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" ]; smtpd_recipient_restrictions = [ "check_recipient_access ${mappedFile "denied_recipients"}" ]; # TLS settings, inspired by https://github.com/jeaye/nix-files # Submission by mail clients is handled in submissionOptions smtpd_tls_security_level = "may"; # Disable obsolete protocols smtpd_tls_protocols = tls_protocols; smtp_tls_protocols = tls_protocols; smtpd_tls_mandatory_protocols = tls_protocols; smtp_tls_mandatory_protocols = tls_protocols; smtp_tls_ciphers = "high"; smtpd_tls_ciphers = "high"; smtp_tls_mandatory_ciphers = "high"; smtpd_tls_mandatory_ciphers = "high"; # Disable deprecated ciphers smtpd_tls_mandatory_exclude_ciphers = tls_exclude_ciphers; smtpd_tls_exclude_ciphers = tls_exclude_ciphers; smtp_tls_mandatory_exclude_ciphers = tls_exclude_ciphers; smtp_tls_exclude_ciphers = tls_exclude_ciphers; tls_preempt_cipherlist = true; # Allowing AUTH on a non-encrypted connection poses a security risk smtpd_tls_auth_only = true; # Log only a summary message on TLS handshake completion smtpd_tls_loglevel = "1"; # Configure a non-blocking source of randomness tls_random_source = "dev:/dev/urandom"; smtpd_milters = [ "unix:${rspamdProxySocket}" ]; # Also use milter for outgoing mails (for e.g., dkim) non_smtpd_milters = [ "unix:${rspamdProxySocket}" ]; 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}"; # Fix for https://www.postfix.org/smtp-smuggling.html smtpd_forbid_bare_newline = "yes"; smtpd_forbid_bare_newline_exclusions = "$mynetworks"; }; 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"]; }; "submission-header-cleanup" = { type = "unix"; private = false; chroot = false; maxproc = 0; command = "cleanup"; args = ["-o" "header_checks=pcre:${submission_header_cleanup_rules}"]; }; }; }; systemd.services.postfix-setup = { preStart = '' ${genPostmapsScript} ''; }; systemd.services.postfix = { wants = sslCertService; after = ["dovecot2.service" "rspamd.service"] ++ sslCertService; requires = ["dovecot2.service" "rspamd.service"]; }; networking.firewall = lib.mkIf cfg.openFirewall { allowedTCPPorts = [ 25 # smtp 465 # submissions ]; }; }; }