{ config, lib, pkgs, ... }: with (import ./common.nix {inherit config;}); let cfg = config.mailsystem; mappedFile = name: "hash:/var/lib/postfix/conf/${name}"; attrsToLookupTable = aliases: let lookupTables = lib.mapAttrsToList (from: to: {"${from}" = to;}) aliases; in mergeLookupTables lookupTables; lookupTableToString = attrs: let valueToString = value: lib.concatStringsSep ", " value; in lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name} ${valueToString value}") attrs); mergeLookupTables = tables: lib.zipAttrsWith (n: v: lib.flatten v) tables; account_virtual_aliases = mergeLookupTables (lib.flatten (lib.mapAttrsToList (name: value: let to = name; in map (from: {"${from}" = to;}) (value.aliases ++ lib.singleton name)) cfg.accounts)); extra_virtual_aliases = attrsToLookupTable cfg.extraVirtualAliases; all_virtual_aliases = mergeLookupTables [account_virtual_aliases extra_virtual_aliases]; aliases_file = let content = lookupTableToString all_virtual_aliases; in builtins.toFile "virtual_aliases" content; # File containing all mappings of authenticated accounts and their sender mail addresses. virtual_accounts_file = let content = lookupTableToString all_virtual_aliases; in builtins.toFile "virtual_accounts" content; virtual_domains_file = builtins.toFile "virtual_domains" (lib.concatStringsSep "\n" cfg.domains); 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 { services.postfix = { enable = true; hostname = "${cfg.reverseFqdn}"; networksStyle = "host"; sslCert = sslCertPath; sslKey = sslKeyPath; enableSubmissions = true; # TODO: create function to simplify this? mapFiles."virtual_aliases" = aliases_file; mapFiles."virtual_accounts" = virtual_accounts_file; virtual = lookupTableToString all_virtual_aliases; 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"; # use mappedFile -> different path? smtpd_sender_login_maps = "hash:/etc/postfix/virtual_accounts"; 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_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" ]; # 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"; # 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 = { wants = sslCertService; after = ["dovecot2.service"] ++ sslCertService; requires = ["dovecot2.service"]; }; networking.firewall = lib.mkIf cfg.openFirewall { allowedTCPPorts = [ 25 # smtp 465 # submissions ]; }; }; }