{pkgs, lib, config, nodes, ...}: let inherit (builtins) attrNames toFile; inherit (lib) types; inherit (pkgs.lib) unlines unlinesAttrs; inherit (config) networking; inherit (config.services) x509 postfix dovecot2 postgrey openldap; unwords = lib.concatStringsSep " "; when = x: y: if x == null then "" else y; submissionHeaderCleanupRules = 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 ''; in { options.services.postfix.aliases = lib.mkOption { type = with types; attrsOf (listOf str); default = {}; example = { "root@${networking.domain}" = [ "user1@${networking.domain}" "user2@${networking.domain}" ]; "@example.coop" = ["user1@${networking.domain}"]; }; }; config = { systemd.services.postfix.after = [ "openldap.service" ] ++ (if x509.scheme == "letsencrypt" then [ "nginx.service" ] # XXX: not sure if this is enough else []); services.postfix = { enable = true; #hostname = networking.domain; #domain = "localdomain"; networksStyle = "host"; #mapFiles."valias" = toFile "valias" (unlines (all_valiases_postfix ++ catchAllPostfix)); # See https://blog.grimneko.de/2011/12/24/a-bunch-of-tips-for-improving-your-postfix-setup/ # for details on how this file looks. By using the same file as valias, # every alias is uniquely owned by its user. # The user's own address is already in all_valiases_postfix. #mapFiles."vaccounts" = toFile "vaccounts" (unlines all_valiases_postfix); mapFiles."virtual_alias_maps" = toFile "virtual_alias_maps" (unlinesAttrs (from: to: "${from} ${unwords to}") postfix.aliases); mapFiles."ldap-virtual_alias_maps.cf" = toFile "ldap-virtual_alias_maps.cf" '' version = 3 debuglevel = 3 server_host = ldapi:// bind = sasl sasl_mechs = EXTERNAL search_base = ou=posix,${openldap.domainSuffix} scope = sub dereference = 0 query_filter = (&(mailAlias=%s)(mailEnabled=TRUE)) result_format = %s result_attribute = mail ''; mapFiles."ldap-forward.cf" = toFile "ldap-forward.cf" '' version = 3 debuglevel = 3 server_host = ldapi:// bind = sasl sasl_mechs = EXTERNAL search_base = ou=posix,${openldap.domainSuffix} scope = sub dereference = 0 query_filter = (&(mail=%s)(mailEnabled=TRUE)) result_format = %s result_attribute = mailForwardingAddress ''; sslCert = x509.cert; sslKey = x509.key; #enableSubmission = true; #enableSmtp = true; destination = [ "localhost" "localhost.localdomain" networking.hostName "${networking.hostName}.localdomain" ]; networks = [ "127.0.0.0/8" "[::1]/128" ]; recipientDelimiter = "+"; config = { # Appending .domain is the MUA's job append_dot_mydomain = false; # No console bell on new mail biff = false; body_checks = ""; #content_filter = "amavisfeed:[127.0.0.1]:10024"; #debug_peer_level = 4; #debug_peer_list = ".$myhostname"; default_extra_recipient_limit = "5000"; # Uncomment the next line to generate "delayed mail" warnings #delay_warning_time = "4h"; # Stops some techniques used to harvest email addresses disable_vrfy_command = true; duplicate_filter_limit = "5000"; enable_long_queue_ids = false; # Pass unexisting $mydestination recipients to dovecot fallback_transport = "lmtp:unix:private/dovecot-lmtp"; forward_path = [ ''$home/.forward''${recipient_delimiter}''${extension}'' "$home/.forward" ]; #header_checks = "regexp:/var/lib/postfix/conf/header_checks"; #inet_interfaces = "all"; line_length_limit = "2048"; # Let $fallback_transport check existence of recipients local_recipient_maps = ""; #mail_spool_directory = "/var/spool/mail"; # NOTE: nixpkgs's default #local_header_rewrite_clients = ""; #home_mailbox = "Maildir/"; #mailbox_command = '' # ${pkgs.procmail}/bin/procmail -t -a "$SENDER" -a "$RECIPIENT" -a "$USER" -a "$EXTENSION" -a "$DOMAIN" -a "$ORIGINAL_RECIPIENT" "$HOME/.procmailrc" #''; mailbox_size_limit = "204800000"; masquerade_classes = [ "envelope_sender" "header_sender" "header_recipient" ]; masquerade_domains = ""; masquerade_exceptions = "root"; maximal_queue_lifetime = "5d"; message_size_limit = "20480000"; mime_header_checks = ""; milter_header_checks = ""; nested_header_checks = ""; non_smtpd_milters = ""; parent_domain_matches_subdomains = [ #"debug_peer_list" #"fast_flush_domains" #"mynetworks" #"permit_mx_backup_networks" #"qmqpd_authorized_clients" #"smtpd_access_maps" ]; permit_mx_backup_networks = ""; #policy-spf_time_limit = "3600s"; propagate_unmatched_extensions = [ "canonical" "virtual" "alias" ]; queue_minfree = "0"; #receive_override_options = "no_address_mappings"; # no_unknown_recipient_checks # Do not try to reject unknown recipients (SMTP server only). # This is typically specified AFTER an external content filter. # no_address_mappings # Disable canonical address mapping, virtual alias map expansion, # address masquerading, and automatic BCC (blind carbon-copy) recipients. # This is typically specified BEFORE an external content filter (eg. amavis). # no_header_body_checks # Disable header/body_checks. This is typically specified AFTER # an external content filter. # no_milters # Disable Milter (mail filter) applications. # This is typically specified AFTER an external content filter. # Parse the extension in email address, eg. contact+extension@ relayhost = ""; #relay_clientcerts = hash:/var/lib/postfix/conf/relay_clientcerts # This is where to put backup MX domains relay_domains = "$mydestination"; relay_recipient_maps = ""; 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"; # Useful to test restrictions smtpd_authorized_xclient_hosts = "127.0.0.1"; smtpd_banner = "${networking.fqdn} 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"; smtpd_client_restrictions = [ #"check_client_access hash:/var/lib/postfix/conf/client_blacklist" ]; smtpd_data_restrictions = [ "reject_unauth_pipelining" # Force the smtp client to wait OK before sending "permit" ]; # Disable opportunistic encryption smtpd_discard_ehlo_keywords = "starttls"; #smtpd_end_of_data_restrictions = ""; # Ban 5 sec on error smtpd_error_sleep_time = "5"; smtpd_helo_required = true; smtpd_helo_restrictions = [ "reject_invalid_helo_hostname" "reject_non_fqdn_helo_hostname" #"reject_unknown_helo_hostname" # May be useful to fight spam "permit" ]; #smtpd_milters = ""; # Needed by postgrey smtpd_peername_lookup = true; smtpd_recipient_limit = "5000"; smtpd_recipient_overshoot_limit = "5000"; smtpd_recipient_restrictions = [ "reject_non_fqdn_recipient" #"reject_invalid_hostname" "reject_unknown_recipient_domain" #"reject_non_fqdn_sender" "reject_unauth_pipelining" #"check_policy_service inet:localhost:12340" # check quota "permit_mynetworks" "permit_tls_clientcerts" "permit_sasl_authenticated" "reject_unverified_recipient" # $fallback_transport is responsible of checking the existence of the recipient # WARNING: verify(8) has a cache, dumpable if verify(8) is stopped, with: # postmap -s btree:/var/lib/postfix/data/verify_cache # Bypass SPF check and postgrey if the recipient is not for us or someone in backup_mx "reject_unauth_destination" # Check SPF #"check_policy_service unix:private/spfcheck" # Greylisting using postgrey "check_policy_service unix:${postgrey.socket.path}" # Once postgrey passed, permit what is for us "permit_auth_destination" "reject" #"reject_unknown_sender_domain" # Maybe better in smtpd_sender_restrictions #"reject_rbl_client bl.spamcop.net" #"reject_rbl_client list.dsbl.org" #"reject_rbl_client zen.spamhaus.org" #"reject_rbl_client dnsbl.sorbs.net" ]; smtpd_relay_restrictions = [ "permit_mynetworks" "permit_sasl_authenticated" # NOTE: permit auth through dovecot's SASL "reject_unauth_destination" ]; #smtpd_restriction_classes = ""; broken_sasl_auth_clients = false; #smtpd_sasl_auth_enable = true; #smtpd_sasl_path = "private/auth"; #smtpd_sasl_security_options = "noanonymous"; #smtpd_sasl_type = "dovecot"; smtpd_sender_restrictions = [ "permit_mynetworks" "permit_tls_clientcerts" "permit_sasl_authenticated" # NOTE: permit auth through dovecot's SASL #"check_sender_access hash:/var/lib/postfix/conf/sender_access" "reject_unauth_pipelining" "reject_non_fqdn_sender" #"reject_sender_login_mismatch" #"reject_unknown_sender_domain" "permit" ]; smtpd_starttls_timeout = "300s"; #smtpd_tls_always_issue_session_ids = true; # No SASL AUTH without TLS smtpd_tls_auth_only = 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 = "ultra"; # 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 = [ "RC4" "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 = "TLSv1"; # 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"; # Stops mail from poorly written software strict_rfc821_envelopes = true; #sympa_destination_recipient_limit = "1"; #sympabounce_destination_recipient_limit = "1"; # postconf(5) discourages to change this #tls_high_cipherlist = "AES256-SHA"; #tls_random_bytes = "32"; # Must not be in a chroot #tls_random_exchange_name = "$data_directory/prng_exch"; #tls_random_prng_update_period = "3600s"; #tls_random_reseed_period = "3600s"; # Use a non blocking source of randomness tls_random_source = "dev:/dev/urandom"; transport_maps = [ #"ldap:transport" #"hash:/etc/postfix/transport-dovecot" #"hash:/etc/postfix/$mydomain/transport" #"hash:/etc/dovecot/transport" #"regexp:/etc/sympa/transport" ]; # Rejects immediately what $fallback_transport rejects unverified_recipient_reject_code = "550"; # Do not specify virtual alias domain names in mydestination # or relay_domains configuration parameters # # With a virtual alias domain, the Postfix SMTP server # accepts mail for known-user@virtual-alias.domain, and # rejects mail for unknown-user@virtual-alias.domain as # undeliverable. virtual_alias_domains = []; virtual_alias_maps = [ #"hash:/etc/postfix/virtual_alias_maps" "hash:/etc/postfix/virtual_domain_alias_maps" "ldap:/etc/postfix/ldap-forward.cf" "ldap:/etc/postfix/ldap-virtual_alias_maps.cf" #"hash:/etc/postfix/virtual_alias-dovecot" #"hash:/var/lib/postfix/conf/valias" #"regexp:/etc/sympa/virtual_alias" ]; #virtual_uid_maps = "static:5000"; #virtual_gid_maps = "static:5000"; #virtual_mailbox_base = dovecot2.mailDir; virtual_mailbox_domains = [networking.domain] ++ networking.domainAliases; #virtual_mailbox_maps = "hash:/etc/postfix/virtual_mailbox_maps"; virtual_transport = "lmtp:unix:private/dovecot-lmtp"; }; #submissionOptions = { # smtpd_tls_security_level = "encrypt"; # smtpd_sasl_auth_enable = "yes"; # smtpd_sasl_type = "dovecot"; # smtpd_sasl_path = "private/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"; #}; extraMasterConf = '' #spfcheck unix - n n - 0 spawn # user=policyd-spf argv=/usr/sbin/postfix-policyd-spf-perl 465 inet n - - - - smtpd -o milter_macro_daemon_name=ORIGINATING -o smtpd_client_restrictions=permit_sasl_authenticated,reject -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject -o smtpd_sasl_auth_enable=yes -o smtpd_sasl_local_domain=$myhostname -o smtpd_sasl_path=private/auth -o smtpd_sasl_security_options=noanonymous -o smtpd_sasl_type=dovecot -o smtpd_tls_ask_ccert=no -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 # -o smtpd_sender_restrictions=reject_sender_login_mismatch # -o smtpd_sender_login_maps=hash:/etc/postfix/vaccounts # -o cleanup_service_name=submission-header-cleanup submission-header-cleanup unix n - n - 0 cleanup -o header_checks=pcre:${submissionHeaderCleanupRules} #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} }; }; }