{ pkgs, lib, config, ... }: let inherit (builtins) attrNames concatStringsSep readFile toPath; inherit (lib) types; inherit (pkgs.lib) loadFile unlines unwords unlinesAttrs; inherit (config) networking users; inherit (config.services) postfix dovecot2 openldap; domains = [ "sourcephile.fr" "autogeree.net" ]; in { imports = map (domain: (./postfix + "/${domain}.nix")) domains; options = { services.postfix = { tls_server_sni_maps = lib.mkOption { type = types.attrsOf (types.listOf types.path); default = {}; apply = m: pkgs.writeText "sni" (lib.concatStringsSep "\n" (lib.mapAttrsToList (domain: x509: '' ${domain} ${unwords x509} '') m)); }; }; }; config = { users.groups.acme.members = [ postfix.user ]; systemd.services.postfix = { wants = ["openldap.service"]; after = ["openldap.service"]; preStart = '' install -m 400 -o root -g root ${postfix.tls_server_sni_maps} /run/keys/postfix-sni ${pkgs.postfix}/bin/postmap -F hash:/run/keys/postfix-sni ''; }; networking.nftables.ruleset = '' add rule inet filter net2fw tcp dport 25 counter accept comment "SMTP" add rule inet filter net2fw tcp dport 465 counter accept comment "submissions" add rule inet filter fw2net meta skuid ${postfix.user} tcp dport 25 counter accept comment "SMTP" ''; services.postfix = { enable = true; networksStyle = "host"; hostname ="${networking.hostName}.${networking.domain}"; domain = networking.domain; origin = "$myhostname"; destination = [ "localhost" "localhost.localdomain" "$myhostname" ]; postmasterAlias = "root"; rootAlias = "root@${networking.domain}"; sslKey = "/var/lib/acme/${networking.domain}/key.pem"; sslCert = "/var/lib/acme/${networking.domain}/fullchain.pem"; networks = [ "127.0.0.0/8" "[::1]/128" ]; setSendmail = true; # Parse the extension in email address, eg. contact+extension@ recipientDelimiter = "+"; config = { debug_peer_level = "4"; debug_peer_list = [ #"chomsky.autogeree.net" #"localhost" #"mail.sourcephile.fr" ]; # # Sending to the world # # Appending .domain is the MUA's job append_dot_mydomain = false; smtp_body_checks = ""; #smtp_cname_overrides_servername = false; smtp_connect_timeout = "60s"; #smtp_header_checks = "regexp:/var/lib/postfix/smtp_header_checks"; smtp_mime_header_checks = ""; smtp_nested_header_checks = ""; smtp_tls_exclude_ciphers = [ "ADH" "MD5" "CAMELLIA" "SEED" "3DES" "DES" "RC4" "eNULL" "aNULL" ]; #smtp_tls_fingerprint_digest = "sha1"; smtp_tls_loglevel = "1"; #smtp_tls_note_starttls_offer = true; #smtp_tls_policy_maps = "hash:/var/lib/postfix/conf/tls_policy"; # Only allow TLSv* protocols smtp_tls_protocols = [ "!SSLv2" "!SSLv3" ]; #smtp_tls_scert_verifydepth = "5"; #smtp_tls_secure_cert_match = [ "nexthop" "dot-nexthop" ]; smtp_tls_security_level = "may"; smtp_tls_session_cache_database = "btree:$data_directory/smtp_tls_session_cache"; #smtp_tls_session_cache_timeout = "3600s"; #smtp_tls_verify_cert_match = "hostname"; # # Receiving from the world # message_size_limit = "20480000"; maximal_queue_lifetime = "5d"; default_extra_recipient_limit = "5000"; line_length_limit = "2048"; duplicate_filter_limit = "5000"; # Stops mail from poorly written software strict_rfc821_envelopes = true; mime_header_checks = []; milter_header_checks = []; nested_header_checks = []; body_checks = []; content_filter = ""; permit_mx_backup_networks = []; propagate_unmatched_extensions = [ "canonical" "virtual" "alias" ]; #masquerade_classes = [ "envelope_sender" "header_sender" "header_recipient" ]; #masquerade_domains = ""; #masquerade_exceptions = "root"; queue_minfree = "0"; # Stops some techniques used to harvest email addresses disable_vrfy_command = true; enable_long_queue_ids = false; # Useful to test restrictions smtpd_authorized_xclient_hosts = "127.0.0.1"; smtpd_banner = "$myhostname ESMTP $mail_name (NixOS)"; smtpd_client_connection_count_limit = "50"; smtpd_client_connection_rate_limit = "0"; smtpd_client_event_limit_exceptions = "$mynetworks"; smtpd_client_message_rate_limit = "0"; smtpd_client_new_tls_session_rate_limit = "0"; smtpd_client_port_logging = false; smtpd_client_recipient_rate_limit = "0"; # Ban 5 sec on error smtpd_error_sleep_time = "5"; # Needed to enforce reject_unknown_helo_hostname smtpd_helo_required = true; smtpd_helo_restrictions = [ "reject_invalid_helo_hostname" "reject_non_fqdn_helo_hostname" # Don't talk to mail systems that don't know their own hostname. "reject_unknown_helo_hostname" "permit" ]; smtpd_client_restrictions = [ ]; # Set in postfix/*.nix and used in submissions/smptd # with reject_sender_login_mismatch smtpd_sender_login_maps = []; smtpd_sender_restrictions = [ "reject_non_fqdn_sender" "permit" ]; smtpd_reject_unlisted_recipient = true; # Check the RCPT TO, before smtpd_recipient_restrictions # Restrictions based on what is allowed or not, # these are applied before smtpd_recipient_restrictions smtpd_relay_restrictions = [ "permit_mynetworks" # Check the recipient's address in virtual_mailbox_domains and virtual_mailbox_maps "permit_auth_destination" # The world is only authorized to use our relay for the above destinations. "reject" ]; # Restrictions based on what is working or not smtpd_recipient_restrictions = [ # Reject if the domain is not fully qualified "reject_non_fqdn_recipient" # Reject if the domain is not working, even before bothering to check the address "reject_unknown_recipient_domain" # Reject if the address is not working # WARNING: this does not work if the recipient is greylisting. # WARNING: verify(8) has a cache, dumpable if verify(8) is stopped, with: # postmap -s btree:/var/lib/postfix/data/verify_cache #"reject_unverified_recipient" "permit" ]; # Trust the verify database #unverified_recipient_reject_code = "550"; smtpd_data_restrictions = [ # Force the smtpd's client to wait OK before sending "reject_unauth_pipelining" "permit" ]; smtpd_end_of_data_restrictions = [ # Enforce mail volume quota via policy service callouts. #check_policy_service unix:private/policy ]; #smtpd_milters = ""; smtpd_peername_lookup = true; smtpd_recipient_limit = "5000"; smtpd_recipient_overshoot_limit = "5000"; #smtpd_restriction_classes = ""; #smtpd_sasl_auth_enable = true; #smtpd_sasl_path = "private/auth"; #smtpd_sasl_security_options = "noanonymous"; #smtpd_sasl_type = "dovecot"; smtpd_starttls_timeout = "300s"; #smtpd_tls_always_issue_session_ids = true; #smtpd_tls_CApath = "/etc/postfix/x509/ca/"; smtpd_tls_ask_ccert = false; #smtpd_tls_ccert_verifydepth = "5"; smtpd_tls_ciphers = "high"; smtpd_tls_eecdh_grade = "auto"; # Disable weak ciphers as reported by https://ssl-tools.net # https://serverfault.com/questions/744168/how-to-disable-rc4-on-postfix smtpd_tls_exclude_ciphers = [ "ADH" "MD5" "CAMELLIA" "SEED" "3DES" "DES" "RC4" "eNULL" "aNULL" ]; smtpd_tls_fingerprint_digest = "sha512"; # Log only a summary message on TLS handshake completion smtpd_tls_loglevel = "1"; smtpd_tls_mandatory_ciphers = "high"; smtpd_tls_mandatory_protocols = [ "!SSLv2" "!SSLv3" ]; # Only allow TLSv* smtpd_tls_protocols = [ "!SSLv2" "!SSLv3" ]; #smtpd_tls_received_header = false; smtpd_tls_req_ccert = false; # Postfix 2.3 and later # encrypt # Mandatory TLS encryption: announce STARTTLS support to SMTP clients, and require that clients use TLS # encryption. According to [1720]RFC 2487 this MUST NOT be applied in case of a publicly-referenced # SMTP server. Instead, this option should be used only on dedicated servers. smtpd_tls_security_level = "may"; smtpd_tls_session_cache_database = "btree:$data_directory/smtpd_tls_session_cache"; #smtpd_tls_session_cache_timeout = "3600s"; #smtpd_tls_chain_files = relayhost = []; #relay_clientcerts = hash:/var/lib/postfix/conf/relay_clientcerts # This is where to put backup MX domains relay_domains = []; relay_recipient_maps = []; # Use a non blocking source of randomness tls_random_source = "dev:/dev/urandom"; # Map each domain to a specific X.509 certificate tls_server_sni_maps = "hash:/run/keys/postfix-sni"; # Only explicitely aliased accounts have a mail, not all the passwd local_recipient_maps = "$alias_maps"; # Note that the local transport rewrites the envelope recipient # according to the alias_maps, and thus the aliasing is transparent # to the nexthop (eg. dovecot) #local_transport = local:$myhostname # No console bell on new mail biff = false; forward_path = [ /* "$home/.forward''${recipient_delimiter}''${extension}" */ "$home/.forward" ]; # Filled by the postfix/*.nix virtual_mailbox_domains = []; # Completed by the postfix/*.nix virtual_mailbox_maps = [ "hash:/etc/postfix/virtual" ]; virtual_transport = "lmtp:unix:private/dovecot-lmtp"; /* dovecot_destination_recipient_limit = "1"; virtual_transport = "dovecot"; */ # There is no fallback fallback_transport = ""; }; virtualMapType = "hash"; masterConfig = let mkVal = value: if lib.isList value then concatStringsSep "," value else if value == true then "yes" else if value == false then "no" else toString value; mkKeyVal = opt: val: [ "-o" (opt + "=" + mkVal val) ]; mkArgs = args: lib.concatLists (lib.mapAttrsToList mkKeyVal args); in { pickup = { args = mkArgs { cleanup_service_name = "submissions-header-cleanup"; }; }; # Implicit TLS on port 465 # https://tools.ietf.org/html/rfc8314#section-3.3 submissions = { type = "inet"; private = false; command = "smtpd"; args = mkArgs { syslog_name = "postfix/submissions"; # Implicit TLS, not STARTTLS smtpd_tls_wrappermode = true; smtpd_tls_mandatory_protocols = [ "TLSv1.3" # K-9 Mail 5.600 still requires this.. "TLSv1.2" ]; milter_macro_daemon_name = "ORIGINATING"; smtpd_helo_restrictions = [ "permit_sasl_authenticated" ] ++ postfix.config.smtpd_helo_restrictions; smtpd_relay_restrictions = [ # SASL authorizes to send to the world "permit_sasl_authenticated" "reject" ]; smtpd_sasl_auth_enable = true; smtpd_sasl_type = "dovecot"; smtpd_sasl_path = "private/auth"; smtpd_sasl_local_domain = ""; # Offer SASL authentication only after a TLS-encrypted session has been established smtpd_tls_auth_only = true; smtpd_sasl_tls_security_options = [ "noanonymous" ]; # Do not put SASL logins in mail headers smtpd_sasl_authenticated_header = false; # Who cares about (old) Outlook broken_sasl_auth_clients = false; smtpd_sender_restrictions = [ "reject_non_fqdn_sender" # Check that the SASL user is using only its own # mail addresses on the envelope, as indicated in smtpd_sender_login_maps "reject_sender_login_mismatch" "permit" ]; # No X.509 certificates for users, for now smtpd_tls_ask_ccert = false; smtpd_tls_ccert_verifydepth = 0; smtpd_tls_loglevel = 1; smtpd_tls_req_ccert = false; cleanup_service_name = "submissions-header-cleanup"; }; }; submissions-header-cleanup = { type = "unix"; private = false; maxproc = 0; command = "cleanup"; args = mkArgs { header_checks = "pcre:" + pkgs.writeText "submission_header_cleanup_rules" '' # Removes sensitive headers from mails handed in via the submission or smtps port. # See https://thomas-leister.de/mailserver-debian-stretch/ # Uses "pcre" style regex. /^Received:/ IGNORE /^User-Agent:/ IGNORE /^X-Enigmail:/ IGNORE /^X-Mailer:/ IGNORE /^X-Originating-IP:/ IGNORE ''; }; }; }; extraMasterConf = '' #spfcheck unix - n n - 0 spawn # user=policyd-spf argv=/usr/sbin/postfix-policyd-spf-perl # -o smtpd_sender_restrictions=reject_sender_login_mismatch # -o smtpd_sender_login_maps=hash:/etc/postfix/vaccounts # -o cleanup_service_name=submissions-header-cleanup #spfcheck unix - n n - 0 spawn # user=policyd-spf argv=/usr/bin/postfix-policyd-spf-perl #uucp unix - n n - - pipe # flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient) #smtp inet n - - - - smtpd # -o cleanup_service_name=pre-cleanup # -o content_filter=amavis:[127.0.0.1]:10024 # -o smtpd_sender_restrictions=reject_unauth_pipelining,reject_non_fqdn_sender,permit # -o receive_override_options=no_address_mappings #amavis unix - - n - 2 lmtp # -o lmtp_data_done_timeout=1200 # -o lmtp_send_xforward_command=yes # -o lmtp_tls_note_starttls_offer=no #127.0.0.1:10025 inet n - n - - smtpd # -o content_filter= # -o local_header_rewrite_clients= # -o local_recipient_maps= # -o mynetworks=127.0.0.0/8 # -o receive_override_options=no_header_body_checks,no_milters,no_unknown_recipient_checks # -o relay_recipient_maps= # -o smtpd_client_connection_count_limit=0 # -o smtpd_client_connection_rate_limit=0 # -o smtpd_client_restrictions=permit_mynetworks,reject # -o smtpd_data_restrictions=reject_unauth_pipelining # -o smtpd_delay_reject=no # -o smtpd_end_of_data_restrictions= # -o smtpd_error_sleep_time=0 # -o smtpd_hard_error_limit=1000 # -o smtpd_helo_restrictions= # -o smtpd_milters= # -o smtpd_recipient_restrictions=permit_mynetworks,reject # -o smtpd_restriction_classes= # -o smtpd_sender_restrictions= # -o smtpd_soft_error_limit=1001 # -o strict_rfc821_envelopes=yes #submission inet n - - - - smtpd # -o cleanup_service_name=pre-cleanup # -o content_filter=amavis:[127.0.0.1]:10024 # -o milter_macro_daemon_name=ORIGINATING # -o receive_override_options=no_address_mappings # -o smtpd_sender_restrictions=permit_tls_clientcerts,reject # -o smtpd_tls_ask_ccert=yes # -o smtpd_tls_auth_only=yes # -o smtpd_tls_ccert_verifydepth=2 # -o smtpd_tls_loglevel=1 # -o smtpd_tls_req_ccert=yes # -o smtpd_tls_security_level=encrypt #smtps inet n - - - - smtpd # -o milter_macro_daemon_name=ORIGINATING # -o smtpd_client_restrictions=permit_sasl_authenticated,reject # -o smtpd_sasl_auth_enable=yes # -o smtpd_tls_ask_ccert=yes # -o smtpd_tls_auth_only=yes # -o smtpd_tls_ccert_verifydepth=0 # -o smtpd_tls_loglevel=1 # -o smtpd_tls_req_ccert=no # -o smtpd_tls_security_level=encrypt # -o smtpd_tls_wrappermode=yes #pickup fifo n - - 60 1 pickup # -o cleanup_service_name=pre-cleanup # -o content_filter=amavis:[127.0.0.1]:10024 #pre-cleanup unix n - - - 0 cleanup # -o virtual_alias_maps= #cleanup unix n - - - 0 cleanup # -o mime_header_checks= # -o nested_header_checks= # -o body_checks= # -o header_checks= #-- SYMPA begin #sympa unix - n n - - pipe # flags=R user=sympa argv=/usr/lib/sympa/bin/queue ''${recipient} #sympabounce unix - n n - - pipe # flags=R user=sympa argv=/usr/lib/sympa/bin/bouncequeue ''${recipient} #-- SYMPA end ''; #noclue unix - n n - - pipe # flags=q user=noclue argv=/usr/local/bin/noclue-delivery ${recipient} ${sender} }; }; }