{ pkgs, lib, config, ... }:
let
  inherit (builtins) baseNameOf readFile;
  inherit (lib) types;
  inherit (config.services) openldap;
  inherit (config.users) ldap;
  unlines = lib.concatStringsSep "\n";
  unlinesAttrs = f: as: unlines (lib.mapAttrsToList f as);
in
{
options = {
services.openldap.cnConfig = lib.mkOption {
  type = types.lines;
  description = "The cn=config in LDIF";
  apply = lines: pkgs.writeText "cn=config.ldif"
    (lines + "\n" + unlinesAttrs (olcSuffix: {conf, olcDbDirectory, ...}:
      "include: file://" + pkgs.writeText "config.ldif" (conf + ''
        olcSuffix: ${olcSuffix}
        olcDbDirectory: ${olcDbDirectory}
      '')
    ) openldap.databases);
  default = ''
    dn: cn=config
    objectClass: olcGlobal
    olcLogLevel: none
    olcToolThreads: 1

    dn: cn={0}module,cn=config
    objectClass: olcModuleList
    olcModulePath: ${pkgs.openldap}/lib/modules
    #olcModuleLoad: pw-sha2
    #olcModuleLoad: pw-pbkdf2
    olcModuleLoad: back_mdb

    dn: olcDatabase={-1}frontend,cn=config
    objectClass: olcDatabaseConfig
    objectClass: olcFrontendConfig
    olcSizeLimit: 500
    # Allow unlimited access to local connection from the local root user
    olcAccess: to *
      by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
      by * break
    # Allow unauthenticated read access for schema and base DN autodiscovery
    olcAccess: to dn.exact=""
      by * read
    olcAccess: to dn.base="cn=Subschema"
      by * read
    # Hash algorithm to be used by LDAP Password Modify Extended Operation or the ppolicy overlay
    #olcPasswordHash: {PBKDF2-SHA256}
    olcPasswordHash: {SSHA}

    dn: olcDatabase={0}config,cn=config
    objectClass: olcDatabaseConfig
    olcRootDN: cn=admin,cn=config
    # Access to cn=config, system root can be manager
    # with SASL mechanism (-Y EXTERNAL) over unix socket (-H ldapi://)
    olcAccess: to *
      by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
      by * break

    dn: cn=schema,cn=config
    objectClass: olcSchemaConfig

    include: file://${pkgs.openldap}/etc/schema/core.ldif
    include: file://${pkgs.openldap}/etc/schema/cosine.ldif
    include: file://${pkgs.openldap}/etc/schema/nis.ldif
    include: file://${pkgs.openldap}/etc/schema/inetorgperson.ldif
  '';
};
services.openldap.databases = lib.mkOption {
  default = {};
  type = types.attrsOf (types.submodule ({name, options, config, ...}: {
    options = {
      conf = lib.mkOption {
        type = types.lines;
        description = "The database's config in LDIF.";
      };
      data = lib.mkOption {
        type = types.nullOr types.lines;
        description = "The database's data in LDIF.";
      };
      olcDbDirectory = lib.mkOption {
        type = types.str;
        description = "The directory where the database is stored.";
        default = "${openldap.dataDir}/${name}";
      };
    };
  }));
};
};
config = lib.mkIf openldap.enable {
systemd.services.openldap.preStart =
  # olcDbDirectory must be created before adding the config.
  ''
  set -e
  install -D -d -m 0700 -o "${openldap.user}" -g "${openldap.group}" "${openldap.configDir}"
  '' +
  unlinesAttrs (olcSuffix: {data, olcDbDirectory, ...}: lib.optionalString (data != null) ''
    rm -rf "${olcDbDirectory}"
    install -D -d -m 0700 -o "${openldap.user}" -g "${openldap.group}" "${olcDbDirectory}"
  '') openldap.databases
  # slapd is supposed to have been stopped by systemd
  # before entering this preStart,
  # hence slap* commands can safely be used.
  #
  # slapadd(8):
  # To populate the config database slapd-config(5),
  # use -n 0 as it is always the first database.
  # It must physically exist on the filesystem prior to this, however.
  + ''
  umask 0077
  rm -rf "${openldap.configDir}"/cn=config \
         "${openldap.configDir}"/cn=config.ldif
  ${pkgs.openldap}/bin/slapadd -n 0 \
   -F "${openldap.configDir}" \
   -l ${openldap.cnConfig}
  chown -R "${openldap.user}:${openldap.group}" "${openldap.configDir}"
  '' +
  unlinesAttrs (olcSuffix: {data, olcDbDirectory, ...}: lib.optionalString (data != null) ''
    ${pkgs.openldap}/bin/slapadd \
     -F "${openldap.configDir}" \
     -b ${olcSuffix} \
     -l ${pkgs.writeText "data.ldif" data}
    '' + ''
    test ! -e "${olcDbDirectory}" ||
    chown -R "${openldap.user}:${openldap.group}" "${olcDbDirectory}"
  '') openldap.databases;
};
}