{ config, pkgs, lib, ... }:

let
  mail_config = config.mailserver;
  submissionHeaderCleanupRules =
    pkgs.writeText "submission_header_cleanup_rules" (''
      /^Received:/            IGNORE
      /^X-Originating-IP:/    IGNORE
      /^X-Mailer:/            IGNORE
      /^User-Agent:/          IGNORE
      /^X-Enigmail:/          IGNORE
      /^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${mail_config.fqdn}>
    '');

  inetSocket = addr: port: "inet:[${toString port}@${addr}]";
  unixSocket = sock: "unix:${sock}";

  # Merge several lookup tables. A lookup table is a attribute set where
  # - the key is an address (user@example.com) or a domain (@example.com)
  # - the value is a list of addresses
  mergeLookupTables = tables: lib.zipAttrsWith (n: v: lib.flatten v) tables;

  # valiases_postfix :: Map String [String]
  valiases_postfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
    (name: value:
      let to = name;
      in map (from: { "${from}" = to; }) (value.aliases ++ lib.singleton name))
    mail_config.accounts));

  # all_valiases_postfix :: Map String [String]
  all_valiases_postfix = mergeLookupTables [ valiases_postfix ];

  # lookupTableToString :: Map String [String] -> String
  lookupTableToString = attrs:
    let valueToString = value: lib.concatStringsSep ", " value;
    in lib.concatStringsSep "\n"
    (lib.mapAttrsToList (name: value: "${name} ${valueToString value}") attrs);

  # valiases_file :: Path
  valiases_file = let
    content = lookupTableToString (mergeLookupTables [ all_valiases_postfix ]);
  in builtins.toFile "valias" content;

  # vhosts_file :: Path
  vhosts_file =
    builtins.toFile "vhosts" (lib.concatStringsSep "\n" mail_config.domains);
  vaccounts_file =
    builtins.toFile "vaccounts" (lookupTableToString all_valiases_postfix);

  mappedFile = name: "hash:/var/lib/postfix/conf/${name}";

  policyd-spf = pkgs.writeText "policyd-spf.conf" mail_config.policyd_config;

  submissionOptions = {
    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 = "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";
  };

  tls_allowed = "TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
  tls_disallow = "MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL";
in {
  config = (lib.mkIf (mail_config.enable) {
    services.postfix = {
      enable = true;
      hostname = "${mail_config.fqdn}";
      networksStyle = "host";
      mapFiles."valias" = valiases_file;
      mapFiles."vaccounts" = vaccounts_file;
      sslCert = mail_config.ssl_config.cert;
      sslKey = mail_config.ssl_config.key;
      enableSubmission = true;
      enableSubmissions = true;
      virtual =
        lookupTableToString (mergeLookupTables [ all_valiases_postfix ]);

      config = {
        # Extra Config
        mydestination = "";
        recipient_delimiter = "+";
        smtpd_banner = "${mail_config.fqdn} ESMTP NO UCE";
        disable_vrfy_command = true;
        message_size_limit = "20971520";

        virtual_uid_maps =
          "static:${toString mail_config.vmail_config.user_group_id}";
        virtual_gid_maps =
          "static:${toString mail_config.vmail_config.user_group_id}";
        virtual_mailbox_base = "${mail_config.vmail_config.directory}";
        virtual_mailbox_domains = vhosts_file;
        virtual_mailbox_maps = mappedFile "valias";
        virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
        lmtp_destination_recipient_limit = "1";

        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"
        ];

        policy-spf_time_limit = "3600s";

        smtpd_recipient_restrictions = [
          #"check_recipient_access ${mappedFile "denied_recipients"}"
          #"check_recipient_access ${mappedFile "reject_recipients"}"
          "check_policy_service unix:private/policy-spf"
        ];

        # TLS settings, inspired by https://github.com/jeaye/nix-files
        # Submission by mail clients is handled in submissionOptions
        smtpd_tls_security_level = "may";

        # strong might suffice and is computationally less expensive
        smtpd_tls_eecdh_grade = "ultra";

        # Only Alow Modern TLS
        smtp_tls_protocols = tls_allowed;
        smtpd_tls_protocols = tls_allowed;
        smtp_tls_mandatory_protocols = tls_allowed;
        smtpd_tls_mandatory_protocols = tls_allowed;

        # Disable Old Ciphers
        smtp_tls_exclude_ciphers = tls_disallow;
        smtpd_tls_exclude_ciphers = tls_disallow;
        smtp_tls_mandatory_exclude_ciphers = tls_disallow;
        smtpd_tls_mandatory_exclude_ciphers = tls_disallow;

        smtp_tls_ciphers = "high";
        smtpd_tls_ciphers = "high";
        smtp_tls_mandatory_ciphers = "high";
        smtpd_tls_mandatory_ciphers = "high";

        tls_preempt_cipherlist = true;

        smtpd_tls_auth_only = true;
        smtpd_tls_loglevel = "1";

        tls_random_source = "dev:/dev/urandom";

        milter_default_action = "quarantine";

        smtpd_milters = [
          "unix:/run/opendkim/opendkim.sock"
          "unix:/run/rspamd/rspamd-milter.sock"
        ];
        non_smtpd_milters = [ "unix:/run/opendkim/opendkim.sock" ];

        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}";

      };

      submissionOptions = submissionOptions;
      submissionsOptions = submissionOptions;

      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" ];
        };
        "policy-spf" = {
          type = "unix";
          privileged = true;
          chroot = false;
          command = "spawn";
          args = [
            "user=nobody"
            "argv=${pkgs.pypolicyd-spf}/bin/policyd-spf"
            "${policyd-spf}"
          ];
        };
        "submission-header-cleanup" = {
          type = "unix";
          private = false;
          chroot = false;
          maxproc = 0;
          command = "cleanup";
          args = [ "-o" "header_checks=pcre:${submissionHeaderCleanupRules}" ];
        };
      };
    };
  });
}