{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 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       = 0
        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       = 0
        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 = "";
      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}"
        "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"; # FIXME: TLSv1.3
      # 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}
  };
};
}