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

with lib;
let
  cfg = config.fudo.auth.kdc;

  database-file = "${cfg.state-directory}/principals.db";
  iprop-log = "${cfg.state-directory}/iprop.log";
  acl-file = generate-acl-file cfg.acl;
  kdc-conf =
    generate-kdc-conf cfg.realm database-file cfg.master-key-file acl-file;

  get-domain-hosts = domain:
    attrNames
    (filterAttrs (host: hostOpts: hostOpts.domain == domain) config.fudo.hosts);

  get-host-principals = realm: hostname:
    let host = config.fudo.hosts.${hostname};
    in map (service: "${service}/${hostname}.${toLower realm}@${realm}")
    host.kerberos-services;

  add-principal-str = kdc-conf: principal:
    "${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- add --random-key --use-defaults ${principal}";

  add-hosts-principals = realm: kdc-conf:
    concatStringsSep "\n" (map (add-principal-str kdc-conf)
      (concatMap (get-host-principals realm)
        (get-domain-hosts (toLower realm))));

  initialize-db =
    realm: user: group: kdc-conf: key-file: db-name: max-lifetime: max-renewal: primary-keytab: kadmin-keytab: kpasswd-keytab: local-hostname:
    pkgs.writeShellScript "initialize-kdc-db.sh" ''
      if [ ! -e ${key-file} ]; then
        ${pkgs.heimdalFull}/bin/kstash --key-file=${key-file} --random-key
        ${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- init --realm-max-ticket-life="${max-lifetime}" --realm-max-renewable-life="${max-renewal}" RUS.SELBY.CA
        ${add-hosts-principals realm kdc-conf}
        ${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- ext_keytab --keytab=${primary-keytab} */${local-hostname}@${realm}
        ${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- ext_keytab --keytab=${kadmin-keytab} kadmin/admin@${realm}
        ${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- ext_keytab --keytab=${kpasswd-keytab} kadmin/changepw@${realm}
        ${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- ext_keytab --keytab=${kpasswd-keytab} kadmin/changepw@${realm}
        #${pkgs.coreutils}/bin/chown ${user}:${group} ${key-file}
        #${pkgs.coreutils}/bin/chown ${user}:${group} ${db-name}
        #${pkgs.coreutils}/bin/chown ${user}:${group} ${iprop-log}
        #${pkgs.coreutils}/bin/chown ${user}:${group} ${primary-keytab}
        #${pkgs.coreutils}/bin/chown ${user}:${group} ${kadmin-keytab}
      fi
    '';

  generate-kdc-conf = realm: db-file: key-file: acl-file:
    pkgs.writeText "kdc.conf" ''
      [kdc]
        database = {
          dbname = sqlite:${db-file}
          realm = ${realm}
          mkey_file = ${key-file}
          acl_file = ${acl-file}
          log_file = ${iprop-log}
        }

      [realms]
        ${realm} = {
          enable-http = false
        }

      [logging]
        kdc = FILE:/var/kerberos/kerberos.log
        default = FILE:/var/kerberos/kerberos.log
    '';

  aclEntry = { principal, ... }: {
    options = with types; {
      perms = let
        perms = [
          "change-password"
          "add"
          "list"
          "delete"
          "modify"
          "get"
          "get-keys"
          "all"
        ];
      in mkOption {
        type = listOf (enum perms);
        description = "List of permissions.";
        default = [ ];
      };

      target = mkOption {
        type = nullOr str;
        description = "Target principals.";
        default = null;
        example = "hosts/*@REALM.COM";
      };
    };
  };

  perms-to-permstring = perms: concatStringsSep "," perms;

  generate-acl-file = acl-entries:
    pkgs.writeText "kdc.acl" (concatStringsSep "\n" (mapAttrsToList
      (principal: opts:
        "${principal} ${perms-to-permstring opts.perms}${
          optionalString (opts.target != null) " ${opts.target}"
        }") acl-entries));

  kadmin-local = kdc-conf: kadmin-keytab:
    pkgs.writeShellScriptBin "kadmin.local" ''
      ${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} --keytab=${kadmin-keytab}
    '';

in {

  options.fudo.auth.kdc = with types; {
    enable = mkEnableOption "Fudo KDC";

    realm = mkOption {
      type = str;
      description = "The realm for which we are the acting KDC.";
    };

    acl = mkOption {
      type = attrsOf (submodule aclEntry);
      description = "Mapping of pricipals to a list of permissions.";
      default = { "*/admin" = [ "all" ]; };
      example = {
        "*/root" = [ "all" ];
        "admin-user" = [ "add" "list" "modify" ];
      };
    };

    bind-addresses = mkOption {
      type = listOf str;
      description = "A list of IP addresses on which to bind.";
      default = [ ];
    };

    user = mkOption {
      type = str;
      description = "User as which to run Heimdal servers.";
      default = "kerberos";
    };

    group = mkOption {
      type = str;
      description = "Group as which to run Heimdal servers.";
      default = "kerberos";
    };

    state-directory = mkOption {
      type = str;
      description = "Path at which to store kerberos database.";
      default = "/var/kerberos";
    };

    master-key-file = mkOption {
      type = str;
      description = "File containing the master key for the realm.";
      default = "/var/kerberos/master.key";
    };

    primary-keytab = mkOption {
      type = str;
      description = "Location of keytab for kadmind.";
      default = "/var/kerberos/host.keytab";
    };

    kadmin-keytab = mkOption {
      type = str;
      description = "Location of keytab for kadmind.";
      default = "/var/kerberos/kadmind.keytab";
    };

    kpasswdd-keytab = mkOption {
      type = str;
      description = "Location of keytab for kpasswdd.";
      default = "/var/kerberos/kpasswdd.keytab";
    };

    kdc-internal-port = mkOption {
      type = port;
      description =
        "Localhost port on which to listen for KDC traffic. Port 88 will be forwarded";
      default = 4088;
    };

    # k5login-directory = mkOption {
    #   type = str;
    #   description =
    #     "Directory in which k5login files are stored for local users (equivalent to ~/.k5login).";
    #   default = "/var/kerberos/k5login";
    # };

    max-ticket-lifetime = mkOption {
      type = str;
      description = "Maximum lifetime of a single ticket in this realm.";
      default = "1d";
    };

    max-ticket-renewal = mkOption {
      type = str;
      description = "Maximum time a ticket may be renewed in this realm.";
      default = "7d";
    };
  };

  config = mkIf cfg.enable {
    users = {
      users.${cfg.user} = {
        isSystemUser = true;
        home = "/var/kerberos";
        group = cfg.group;
      };

      groups.${cfg.group} = { members = [ cfg.user ]; };
    };

    krb5 = {
      libdefaults = {
        # Stick to ~/.k5login
        # k5login_directory = cfg.k5login-directory;
        ticket_lifetime = cfg.max-ticket-lifetime;
        renew_lifetime = cfg.max-ticket-renewal;
      };
      realms = { ${cfg.realm} = { enable-http = false; }; };
      extraConfig = ''
        default = FILE:/var/kerberos/kerberos.log
      '';
    };

    environment = {
      systemPackages =
        [ pkgs.heimdalFull (kadmin-local kdc-conf cfg.kadmin-keytab) ];

      etc = {
        "krb5.keytab" = {
          user = "root";
          group = "root";
          mode = "0400";
          source = cfg.primary-keytab;
        };
      };
    };

    fudo.system = {
      ensure-directories = {
        "${cfg.state-directory}" = {
          user = cfg.user;
          group = cfg.group;
          perms = "0740";
        };
      };

      services = {
        heimdal-kdc = let
          listen-addrs = concatStringsSep " "
            (map (addr: "--addresses=${addr}") cfg.bind-addresses);
          command =
            "${pkgs.heimdalFull}/libexec/heimdal/kdc -c ${kdc-conf} --ports=88 ${listen-addrs}";
        in {
          wantedBy = [ "multi-user.target" ];
          after = [ "network.target" ];
          description =
            "Heimdal Kerberos Key Distribution Center (ticket server).";
          execStart = command;
          user = cfg.user;
          group = cfg.group;
          workingDirectory = cfg.state-directory;
          privateNetwork = false;
          addressFamilies = [ "AF_INET" "AF_INET6" ];
          requiredCapabilities = [ "CAP_NET_BIND_SERVICE" ];
          environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
        };

        heimdal-kdc-init = {
          requires = [ "heimdal-kdc.service" ];
          wantedBy = [ "multi-user.target" ];
          description = "Initialization script for Heimdal KDC.";
          type = "oneshot";
          execStart = "${initialize-db cfg.realm cfg.user cfg.group kdc-conf
            cfg.master-key-file database-file cfg.max-ticket-lifetime
            cfg.max-ticket-renewal cfg.primary-keytab cfg.kadmin-keytab
            cfg.kpasswdd-keytab
            "${config.networking.hostName}.${toLower cfg.realm}"}";
          user = cfg.user;
          group = cfg.group;
          protectSystem = "full";
          addressFamilies = [ "AF_INET" "AF_INET6" ];
          workingDirectory = cfg.state-directory;
          environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
        };
      };
    };

    # FIXME: is this even allowed to be a link?
    # systemd.tmpfiles.rules = mkIf (cfg.primary-keytab != "/etc/krb5.keytab")
    #   [ "L /etc/krb5.keytab - - - - ${cfg.primary-keytab}" ];

    services.xinetd = {
      enable = true;

      services = [
        {
          name = "kerberos-adm";
          user = cfg.user;
          server = "${pkgs.heimdalFull}/libexec/heimdal/kadmind";
          protocol = "tcp";
          serverArgs =
            "--config-file=${kdc-conf} --keytab=${cfg.kadmin-keytab}";
        }
        {
          name = "kpasswd";
          user = cfg.user;
          server = "${pkgs.heimdalFull}/libexec/heimdal/kpasswdd";
          protocol = "udp";
          serverArgs =
            "--config-file=${kdc-conf} --keytab=${cfg.kpasswdd-keytab}";
        }
      ];
    };

    networking.firewall = {
      allowedTCPPorts = [ 88 749 ];
      allowedUDPPorts = [ 88 464 ];
    };
  };
}