#!/usr/bin/bash

# uuidgen --namespace @dns --sha1 --name "kirmes-localkdc"
LOCALKDC_APP_UUID="35b20ad5-c75c-5eaa-9fcd-8f3cb8e33185"

LOCALKDC_HOSTNAME="$(hostname --short | tr '[:upper:]' '[:lower:]')"
#LOCALKDC_HOSTNAME_UPPER="$(echo "${LOCALKDC_HOSTNAME}" | tr '[:lower:]' '[:upper:]')"

# Add the hostname and mdns name to the list of fqdns we want to add
LOCALKDC_HOSTNAME_ALL="${LOCALKDC_HOSTNAME} ${LOCALKDC_HOSTNAME}.local localhost $(hostname --all-fqdns)"
# This will remove duplicates
LOCALKDC_HOSTNAME_FQDNS="$(echo "${LOCALKDC_HOSTNAME_ALL}" | awk -v RS=' ' '!seen[$0]++')"

SYSCONF_DIR="/etc/localkdc"
LOCALSTATE_DIR="/var/kerberos/localkdc"
RUNTIME_DIR="/run/localkdc"
DATA_DIR="/usr/share/localkdc"
KRB5_CONFIG_DIR="/etc/krb5.conf.d"

LOCAL_GETCERT="$(command -v local-getcert)"
KDB5_UTIL="$(command -v kdb5_util)"
KADMIN_LOCAL="$(command -v kadmin.local)"
SYSTEMD_ID128="$(command -v systemd-id128)"
SMBD="$(command -v smbd)"
SSSD="$(command -v sssd)"

LOCALKDC_MACHINE_APP_UUID="$(${SYSTEMD_ID128} --value --uuid --app-specific=${LOCALKDC_APP_UUID} machine-id | tr '[:lower:]' '[:upper:]')"
LOCALKDC_REALM="LOCALKDC.${LOCALKDC_MACHINE_APP_UUID}"
LOCALKDC_FQDN="${LOCALKDC_HOSTNAME}.localkdc.site"

# Generate a random master password
MASTER_PASSWORD="$(tr -cd '[:alnum:]' < /dev/urandom | fold -w256 | head -n1)"

check_requirements()
{
    if [ -z "${LOCALKDC_HOSTNAME}" ]; then
        echo "Failed to find a hostname"
        return 1
    fi

    if [ -z "${LOCAL_GETCERT}" ]; then
        echo "Failed to find local-getcert, please install certmonger"
        return 1
    fi

    if [ -z "${KDB5_UTIL}" ]; then
        echo "Failed to find kdb5_util, please install krb5-server"
        return 1
    fi

    if [ -z "${KADMIN_LOCAL}" ]; then
        echo "Failed to find kadmin.local, please install krb5-server"
        return 1
    fi

    if [ -z "${SYSTEMD_ID128}" ]; then
        echo "Failed to find systemd-id128, please install systemd"
        return 1
    fi

    return 0
}

install_kdc_conf()
{
    mkdir -p ${LOCALSTATE_DIR}
    sed -e "s/%REALM%/${LOCALKDC_REALM}/" ${DATA_DIR}/templates/kdc.conf > ${LOCALSTATE_DIR}/kdc.conf
    ret=$?
    if [ ${ret} -ne 0 ]; then
        echo "Failed to create ${LOCALSTATE_DIR}/kdc.conf"
        return 1
    fi

    sed -e "s/%REALM%/${LOCALKDC_REALM}/" ${DATA_DIR}/templates/kadmin.conf > ${LOCALSTATE_DIR}/kadmin.conf
    ret=$?
    if [ ${ret} -ne 0 ]; then
        echo "Failed to create ${LOCALSTATE_DIR}/kadmin.conf"
        return 1
    fi

    return 0
}

install_krb5_conf()
{
    # Client-facing snippet included by /etc/krb5.conf via includedir.
    mkdir -p ${KRB5_CONFIG_DIR}
    sed -e "s/%REALM%/${LOCALKDC_REALM}/" ${DATA_DIR}/templates/krb5.conf.hostname > ${KRB5_CONFIG_DIR}/localkdc
    ret=$?
    if [ ${ret} -ne 0 ]; then
        echo "Failed to create ${KRB5_CONFIG_DIR}/localkdc"
        return 1
    fi

    # KDC-only krb5.conf: deliberately minimal so that the system-wide
    # /etc/krb5.conf [logging] section (which points at /var/log/krb5kdc.log)
    # is never read by the localkdc KDC process.  The KDC service sets
    # KRB5_CONFIG to this file; logging is configured via [logging] in kdc.conf.
    mkdir -p ${LOCALSTATE_DIR}
    sed -e "s/%REALM%/${LOCALKDC_REALM}/" ${DATA_DIR}/templates/krb5.conf.kdc > ${LOCALSTATE_DIR}/krb5.conf
    ret=$?
    if [ ${ret} -ne 0 ]; then
        echo "Failed to create ${LOCALSTATE_DIR}/krb5.conf"
        return 1
    fi

    return 0
}

configure_sssd()
{
    # sssd.conf must be mode 0600 or SSSD will refuse to start.
    # Only write it when not already present — the admin may have customised it.
    if [ ! -f /etc/sssd/sssd.conf ]; then
        install -d -m 0711 /etc/sssd
        install -m 0600 "${DATA_DIR}/templates/sssd.conf" \
            /etc/sssd/sssd.conf
    else
        echo "localkdc: /etc/sssd/sssd.conf already exists, not overwriting"
    fi

    # Install the localkdc domain snippet with the realm substituted.
    install -d -m 0711 /etc/sssd/conf.d
    sed -e "s/%REALM%/${LOCALKDC_REALM}/" \
        "${DATA_DIR}/templates/sssd-localkdc.conf" | \
        install -m 0600 /dev/stdin /etc/sssd/conf.d/localkdc.conf
    ret=$?
    if [ ${ret} -ne 0 ]; then
        echo "Failed to install /etc/sssd/conf.d/localkdc.conf"
        return 1
    fi

    return 0
}

configure_samba()
{
    if [ -z "${SMBD}" ]; then
        echo "localkdc: Samba not installed, skipping Samba configuration"
        return 0
    fi

    cat > /etc/samba/localkdc.conf << EOF
    localkdc realm = ${LOCALKDC_REALM}
    kerberos method = secrets and keytabs
    keytab file list = /etc/samba/localkdc.keytab
EOF

    return 0
}

configure_localkdc_pam_auth()
{
    # Write the RADIUS shared secret to the file referenced by kdc.conf's
    # [otp] localkdc-pam section.  Both the KDC OTP plugin and
    # localkdc-pam-auth read the secret from this file so that the two
    # sides always use the same value.
    echo "${LOCALKDC_REALM}" | \
        install -m 0600 /dev/stdin "${LOCALSTATE_DIR}/pam-radius.secret"

    # Expose the path (not the secret value) via the environment file read
    # by the localkdc-pam-auth service.  The binary reads the secret from
    # the file at startup.
    install -d -m 0755 /etc/localkdc
    cat > /etc/localkdc/radius.env << EOF
RADIUS_SECRET=${LOCALSTATE_DIR}/pam-radius.secret
EOF

    install -m 0644 /etc/pki/ca-trust/source/anchors/localkdc-ca.crt \
                    /etc/localkdc/localkdc-ca.crt

    return 0
}

configure_localkdc()
{
    local _kadmin_input_file
    local _master_pass_file

    export KRB5_KDC_PROFILE="${LOCALSTATE_DIR}/kadmin.conf"

    install -d -m 0755 "${SYSCONF_DIR}"
    echo "${LOCALKDC_REALM}" | \
        install -m 0644 /dev/stdin "${SYSCONF_DIR}/realm"

    # Prepare Anonymous PKINIT configuration for the realm
    ${LOCAL_GETCERT} request \
        -K "krbtgt/${LOCALKDC_REALM}@${LOCALKDC_REALM}" \
        -N "cn=${LOCALKDC_FQDN}" \
        -F /etc/pki/ca-trust/source/anchors/localkdc-ca.crt \
        -k "${LOCALSTATE_DIR}/kdc.key" \
        -f "${LOCALSTATE_DIR}/kdc.crt" \
        -U{id-kp-serverAuth,id-pkinit-KPKdc} \
        -u{digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment}
    ret=$?
    if [ ${ret} -ne 0 ]; then
        echo "${LOCAL_GETCERT}: Failed to create pkinit certificates"
        return 1
    fi

    _master_pass_file="${LOCALSTATE_DIR}/.master.pass"
    echo "${MASTER_PASSWORD}" | \
        install -m 0600 /dev/stdin "${_master_pass_file}"

    # Create krb5 database
    cat "${_master_pass_file}" "${_master_pass_file}" | \
        ${KDB5_UTIL} -r "${LOCALKDC_REALM}" -d "${LOCALSTATE_DIR}/principal" create -s

    _kadmin_input_file="$(mktemp)"
    cat > "${_kadmin_input_file}" <<EOF
addprinc -randkey WELLKNOWN/ANONYMOUS
addprinc -randkey "host/${LOCALKDC_FQDN}"
addprinc -randkey "cifs/${LOCALKDC_FQDN}"
addprinc -randkey "HTTP/${LOCALKDC_FQDN}"

ktadd -norandkey host/${LOCALKDC_FQDN}

ktadd -k /etc/samba/localkdc.keytab -norandkey host/${LOCALKDC_FQDN}
ktadd -k /etc/samba/localkdc.keytab -norandkey cifs/${LOCALKDC_FQDN}

ktadd -k /etc/localkdc-http.keytab -norandkey host/${LOCALKDC_FQDN}
ktadd -k /etc/localkdc-http.keytab -norandkey HTTP/${LOCALKDC_FQDN}

EOF

for h in ${LOCALKDC_HOSTNAME_FQDNS}; do
    if [ "${h}" == "${LOCALKDC_FQDN}" ]; then
        continue
    fi

    cat >> "${_kadmin_input_file}" <<EOF
add_alias host/${h} host/${LOCALKDC_FQDN}@${LOCALKDC_REALM}
add_alias cifs/${h} cifs/${LOCALKDC_FQDN}@${LOCALKDC_REALM}
add_alias HTTP/${h} HTTP/${LOCALKDC_FQDN}@${LOCALKDC_REALM}

ktadd -norandkey host/${h}
ktadd -k /etc/samba/localkdc.keytab -norandkey host/${h}
ktadd -k /etc/samba/localkdc.keytab -norandkey cifs/${h}
ktadd -k /etc/localkdc-http.keytab -norandkey host/${h}
ktadd -k /etc/localkdc-http.keytab -norandkey HTTP/${h}

EOF
done

    # Create keytab files
    ${KADMIN_LOCAL} -r "${LOCALKDC_REALM}" < "${_kadmin_input_file}"
    ret=$?
    if [ ${ret} -ne 0 ]; then
        echo "${KADMIN_LOCAL}: Failed to create database entries"
        return 1
    fi

    echo

    rm -f "${_kadmin_input_file}"

    unset KRB5_KDC_PROFILE

    return 0
}

check_requirements
ret=$?
if [ ${ret} -ne 0 ]; then
    exit 1
fi
install_kdc_conf
ret=$?
if [ ${ret} -ne 0 ]; then
    exit 1
fi
install_krb5_conf
ret=$?
if [ ${ret} -ne 0 ]; then
    exit 1
fi
configure_localkdc
ret=$?
if [ ${ret} -ne 0 ]; then
    exit 1
fi
configure_localkdc_pam_auth
ret=$?
if [ ${ret} -ne 0 ]; then
    exit 1
fi
configure_samba
ret=$?
if [ ${ret} -ne 0 ]; then
    exit 1
fi
configure_sssd
ret=$?
if [ ${ret} -ne 0 ]; then
    exit 1
fi

systemctl enable --now localkdc.socket
systemctl enable --now localkdc-pam-auth.socket
if [ -n "${SSSD}" ]; then
    authselect select sssd ||:
    authselect is-feature-enabled with-gssapi || authselect enable-feature with-gssapi
    systemctl enable --now sssd.service ||:
fi

echo "Successfully set up localkdc"
