#!/bin/bash
# freeipa-enroll.sh — Enroll a Linux host into FreeIPA
# Usage: sudo ./freeipa-enroll.sh [options]
#
# Options:
#   -d, --domain        IPA domain (e.g. corp.example.com)
#   -r, --realm         Kerberos realm (e.g. CORP.EXAMPLE.COM) [optional, derived from domain]
#   -s, --server        IPA server hostname (e.g. ipa01.corp.example.com)
#   -h, --hostname      This host's FQDN [optional, uses current hostname]
#   -p, --principal     Admin principal [default: admin]
#   -w, --password      Admin password (or use IPA_PASSWORD env var)
#   -m, --mkhomedir     Enable home directory creation on login [default: true]
#       --no-mkhomedir  Disable home directory creation
#       --sudo          Configure sudo rules via SSSD [default: true]
#       --no-sudo       Disable sudo via SSSD
#       --no-dns-update Skip DNS record update
#       --ntp-server    Custom NTP server [optional]
#       --fido2         Enable FIDO2 key authentication via PAM
#       --fido2-user    IPA username to register FIDO2 credential for (repeatable)
#       --uninstall     Remove FreeIPA client

set -euo pipefail

# Absolute path of this script's directory, so bundled build files (e.g. the
# autofs-pkgbuild/ fallback for Arch) resolve regardless of the caller's CWD.
SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# ─── Colours ────────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'

log()     { echo -e "${GREEN}[+]${NC} $*"; }
warn()    { echo -e "${YELLOW}[!]${NC} $*"; }
error()   { echo -e "${RED}[✗]${NC} $*" >&2; }
info()    { echo -e "${CYAN}[i]${NC} $*"; }
section() { echo -e "\n${BLUE}━━━ $* ━━━${NC}"; }

# ─── Defaults ───────────────────────────────────────────────────────────────
IPA_DOMAIN="freeipa.abdelbaki.eu"
IPA_REALM="FREEIPA.ABDELBAKI.EU"
IPA_SERVER="freeipa.abdelbaki.eu"
IPA_HOSTNAME=""
IPA_PRINCIPAL="admin"
IPA_PASSWORD="${IPA_PASSWORD:-}"
MKHOMEDIR=true
CONFIGURE_SUDO=true
DNS_UPDATE=true
NTP_SERVER=""
ENABLE_FIDO2=false
FIDO2_USERS=()
UNINSTALL=false

# ─── Argument parsing ────────────────────────────────────────────────────────
usage() {
    grep '^#' "$0" | grep -v '#!/' | sed 's/^# \{0,1\}//'
    exit 0
}

while [[ $# -gt 0 ]]; do
    case $1 in
        -d|--domain)        IPA_DOMAIN="$2";              shift 2 ;;
        -r|--realm)         IPA_REALM="$2";               shift 2 ;;
        -s|--server)        IPA_SERVER="$2";              shift 2 ;;
        -h|--hostname)      IPA_HOSTNAME="$2";            shift 2 ;;
        -p|--principal)     IPA_PRINCIPAL="$2";           shift 2 ;;
        -w|--password)      IPA_PASSWORD="$2";            shift 2 ;;
        -m|--mkhomedir)     MKHOMEDIR=true;               shift ;;
        --no-mkhomedir)     MKHOMEDIR=false;              shift ;;
        --sudo)             CONFIGURE_SUDO=true;          shift ;;
        --no-sudo)          CONFIGURE_SUDO=false;         shift ;;
        --no-dns-update)    DNS_UPDATE=false;             shift ;;
        --ntp-server)       NTP_SERVER="$2";              shift 2 ;;
        --fido2)            ENABLE_FIDO2=true;            shift ;;
        --fido2-user)       FIDO2_USERS+=("$2");          shift 2 ;;
        --uninstall)        UNINSTALL=true;               shift ;;
        --help)             usage ;;
        *) error "Unknown option: $1"; exit 1 ;;
    esac
done

# ─── Validation ──────────────────────────────────────────────────────────────
section "Validating inputs"

[[ $EUID -ne 0 ]] && { error "Must be run as root"; exit 1; }

if [[ "$UNINSTALL" == false ]]; then
    [[ -z "$IPA_DOMAIN" ]] && { error "--domain is required"; exit 1; }
    [[ -z "$IPA_SERVER" ]] && { error "--server is required"; exit 1; }

    if [[ -z "$IPA_REALM" ]]; then
        IPA_REALM="${IPA_DOMAIN^^}"
        log "Realm derived from domain: $IPA_REALM"
    fi

    if [[ -z "$IPA_HOSTNAME" ]]; then
        IPA_HOSTNAME=$(hostname -f)
        log "Using current hostname: $IPA_HOSTNAME"
    fi

    if [[ -z "$IPA_PASSWORD" ]]; then
        read -rsp "Enter password for ${IPA_PRINCIPAL}@${IPA_REALM}: " IPA_PASSWORD
        echo
    fi

    [[ -z "$IPA_PASSWORD" ]] && { error "Password is required"; exit 1; }

    # If --fido2-user was passed, implicitly enable fido2
    [[ ${#FIDO2_USERS[@]} -gt 0 ]] && ENABLE_FIDO2=true
fi

# ─── Uninstall path ───────────────────────────────────────────────────────────
if [[ "$UNINSTALL" == true ]]; then
    section "Uninstalling FreeIPA client"
    if command -v ipa-client-install &>/dev/null; then
        ipa-client-install --uninstall --unattended
        log "Client uninstalled"
    else
        warn "ipa-client-install not found — may not have been installed"
    fi

    # Clean up FIDO2 PAM config if present
    if [[ -f /etc/pam.d/fido2-auth ]]; then
        rm -f /etc/pam.d/fido2-auth
        log "Removed FIDO2 PAM config"
    fi
    exit 0
fi

# ─── Detect OS ───────────────────────────────────────────────────────────────
section "Detecting OS"

if [[ -f /etc/os-release ]]; then
    source /etc/os-release
    OS_ID="${ID}"
    OS_VERSION="${VERSION_ID:+${VERSION_ID%%.*}}"
    log "Detected: $PRETTY_NAME"
else
    error "Cannot detect OS — /etc/os-release missing"
    exit 1
fi

case "$OS_ID" in
    fedora)
        PKG_MANAGER="dnf"
        IPA_CLIENT_PKG="freeipa-client"
        SSSD_PKGS="sssd sssd-ipa sssd-tools"
        ODDJOB_PKGS="oddjob oddjob-mkhomedir"
        CHRONY_PKG="chrony"
        FIDO2_PKGS="pam-u2f"
        FIDO2_TOOLS_PKGS="pamu2fcfg"
        ;;
    rhel|centos|rocky|almalinux)
        PKG_MANAGER="dnf"
        # RHEL 8+ (and derivatives Rocky/Alma/CentOS Stream) renamed the
        # freeipa-client package to ipa-client — freeipa-client does not
        # exist in these repos, so `dnf install freeipa-client` fails outright.
        IPA_CLIENT_PKG="ipa-client"
        SSSD_PKGS="sssd sssd-ipa sssd-tools"
        ODDJOB_PKGS="oddjob oddjob-mkhomedir"
        CHRONY_PKG="chrony"
        FIDO2_PKGS="pam-u2f"
        FIDO2_TOOLS_PKGS="pamu2fcfg"
        ;;
    debian|ubuntu)
        PKG_MANAGER="apt-get"
        IPA_CLIENT_PKG="freeipa-client"
        SSSD_PKGS="sssd sssd-ipa"
        ODDJOB_PKGS=""
        CHRONY_PKG="chrony"
        FIDO2_PKGS="libpam-u2f"
        FIDO2_TOOLS_PKGS="pamu2fcfg"
        warn "Debian/Ubuntu support is best-effort — Rocky/Alma recommended"
        ;;
    arch)
        PKG_MANAGER="pacman"
        # freeipa-client is AUR-only on Arch (the official repos only carry
        # sssd); it must be installed via an AUR helper, NOT plain pacman —
        # `pacman -S freeipa-client` fails with "target not found".
        IPA_CLIENT_PKG="freeipa-client"
        SSSD_PKGS="sssd"
        ODDJOB_PKGS="oddjob"
        CHRONY_PKG="chrony"
        FIDO2_PKGS="pam-u2f"
        FIDO2_TOOLS_PKGS="pamu2fcfg"
        warn "Arch Linux: ensure an AUR helper (yay/paru) is available for AUR packages"
        ;;
    *)
        error "Unsupported OS: $OS_ID"
        exit 1
        ;;
esac

# ─── Helper: install packages ─────────────────────────────────────────────────
pkg_install() {
    local pkgs=("$@")
    [[ ${#pkgs[@]} -eq 0 ]] && return 0
    case "$PKG_MANAGER" in
        dnf)     dnf install -y "${pkgs[@]}" ;;
        apt-get) DEBIAN_FRONTEND=noninteractive apt-get install -y "${pkgs[@]}" ;;
        pacman)  pacman -Sy --noconfirm "${pkgs[@]}" ;;
    esac
}

# ─── Pre-flight checks ────────────────────────────────────────────────────────
section "Pre-flight checks"

log "Checking connectivity to IPA server $IPA_SERVER..."
if ! ping -c 2 -W 3 "$IPA_SERVER" &>/dev/null; then
    error "Cannot reach IPA server $IPA_SERVER — check network/DNS"
    exit 1
fi

for port in 443 88; do
    if ! timeout 5 bash -c "echo >/dev/tcp/$IPA_SERVER/$port" 2>/dev/null; then
        warn "Port $port on $IPA_SERVER unreachable — enrollment may fail"
    fi
done

# FIDO2 pre-flight: check a key is actually plugged in
if [[ "$ENABLE_FIDO2" == true ]]; then
    if ! command -v fido2-token &>/dev/null; then
        warn "fido2-token not found yet — will install libfido2 tooling"
    else
        FIDO2_DEVICES=$(fido2-token -L 2>/dev/null | wc -l)
        if [[ "$FIDO2_DEVICES" -eq 0 ]]; then
            warn "No FIDO2 devices detected. Plug in the key before the registration step."
        else
            log "Detected $FIDO2_DEVICES FIDO2 device(s)"
        fi
    fi
fi

log "Checking time synchronization..."
if command -v chronyc &>/dev/null; then
    OFFSET=$(chronyc tracking 2>/dev/null | awk '/System time/ {print $4}' | tr -d '-')
    if [[ -n "$OFFSET" ]]; then
        INT_OFFSET=${OFFSET%.*}
        if [[ "${INT_OFFSET:-0}" -gt 60 ]]; then
            warn "Time offset is ${OFFSET}s — Kerberos requires <300s skew"
        else
            log "Time offset: ${OFFSET}s — OK"
        fi
    fi
else
    warn "chrony not found — installing and syncing"
    pkg_install "$CHRONY_PKG"
    systemctl enable --now chronyd
    sleep 3
fi

if [[ -n "$NTP_SERVER" ]]; then
    log "Configuring NTP server: $NTP_SERVER"
    echo "server $NTP_SERVER iburst" >> /etc/chrony.conf
    systemctl restart chronyd
    chronyc makestep
fi

if [[ -f /etc/ipa/default.conf ]]; then
    warn "This host appears to already be enrolled in an IPA domain"
    read -rp "Re-enroll? This will uninstall first. [y/N]: " CONFIRM
    if [[ "$CONFIRM" =~ ^[Yy]$ ]]; then
        log "Uninstalling existing client..."
        ipa-client-install --uninstall --unattended || true
    else
        log "Aborting"
        exit 0
    fi
fi

# ─── Install packages ─────────────────────────────────────────────────────────
section "Installing packages"

# aur_install <pkg...> — install AUR packages via yay/paru. AUR helpers refuse
# to run as root, so drop to the invoking sudo user (makepkg builds as them,
# their sudo rights handle the final pacman -U).
aur_install() {
    local helper=""
    command -v yay  &>/dev/null && helper="yay"
    [[ -z "$helper" ]] && command -v paru &>/dev/null && helper="paru"
    if [[ -z "$helper" ]]; then
        error "No AUR helper (yay/paru) found — cannot install: $*"
        error "Install one first, e.g.: pacman -S --needed base-devel git && <build yay>"
        return 1
    fi
    local run_as="${SUDO_USER:-}"
    if [[ -n "$run_as" && "$run_as" != "root" ]]; then
        sudo -u "$run_as" "$helper" -S --noconfirm --needed "$@"
    else
        # Running as plain root (no sudo user to drop to) — makepkg forbids root.
        error "AUR install of '$*' needs a non-root user; re-run via sudo from a regular account."
        return 1
    fi
}

# build_local_pkg <pkgbuild-dir> — build a bundled PKGBUILD and pacman -U it.
# makepkg refuses to run as root, so the build runs as the invoking sudo user in
# a writable copy of the dir (the repo copy may be read-only); we then install
# the resulting package as root. Used to satisfy freeipa-client's autofs
# dependency without the broken AUR autofs (see autofs-pkgbuild/PKGBUILD).
build_local_pkg() {
    local src_dir="$1" run_as="${SUDO_USER:-}"
    [[ -d "$src_dir" ]] || { error "PKGBUILD dir not found: $src_dir"; return 1; }
    if [[ -z "$run_as" || "$run_as" == "root" ]]; then
        error "Local package build needs a non-root user; re-run via sudo from a regular account."
        return 1
    fi
    local build_dir
    build_dir=$(sudo -u "$run_as" mktemp -d) || return 1
    cp -r "$src_dir"/. "$build_dir/"
    chown -R "$run_as": "$build_dir"
    # Build only (as the user); -s pulls makedepends, -f overwrites a stale build.
    if ! sudo -u "$run_as" bash -c "cd '$build_dir' && makepkg -sf --noconfirm --needed"; then
        error "makepkg failed in $build_dir"
        rm -rf "$build_dir"
        return 1
    fi
    # Install every package artifact produced (as root — we already are).
    pacman -U --noconfirm "$build_dir"/*.pkg.tar.* || { rm -rf "$build_dir"; return 1; }
    rm -rf "$build_dir"
}

# On Arch, sssd comes from the official repos but freeipa-client is AUR-only
if [[ "$OS_ID" == "arch" ]]; then
    # Ensure pacman db is fresh
    pacman -Sy --noconfirm
    # sssd and Kerberos tooling are in the official repos
    pkg_install $SSSD_PKGS krb5 cyrus-sasl-gssapi
    # freeipa-client is AUR-only; skip if ipa-client-install already present
    if command -v ipa-client-install &>/dev/null; then
        log "ipa-client-install already present — skipping AUR freeipa-client install"
    else
        # freeipa-client (AUR) hard-depends on autofs, but the AUR autofs package
        # is currently unbuildable: its pinned source tarball (autofs-5.1.9.tar.xz)
        # was pruned from the kernel.org mirror when 5.2.0 released, so it 404s and
        # takes freeipa-client — and thus the whole enrollment — down with it.
        # Build autofs ourselves from upstream git (see autofs-pkgbuild/) and install
        # it first so the AUR helper sees the dependency already satisfied and never
        # touches the broken AUR autofs.
        if ! pacman -Qq autofs &>/dev/null; then
            log "Building autofs from bundled git PKGBUILD (works around broken AUR autofs)..."
            build_local_pkg "$SELF_DIR/autofs-pkgbuild" \
                || { error "autofs build failed — cannot satisfy freeipa-client dependency"; exit 1; }
        fi
        log "Installing $IPA_CLIENT_PKG from the AUR (this builds from source — can take a while)..."
        aur_install $IPA_CLIENT_PKG || { error "freeipa-client install failed"; exit 1; }
    fi
    # oddjob (for mkhomedir) — skip if not available in official repos
    if pacman -Si oddjob &>/dev/null 2>&1; then
        pkg_install $ODDJOB_PKGS
    else
        warn "oddjob not in official repos — mkhomedir via pam_mkhomedir fallback"
    fi

    # Arch: pam-u2f may be in AUR — try pacman first, fall back to yay/paru
    if [[ "$ENABLE_FIDO2" == true ]]; then
        if ! pacman -Qi pam-u2f &>/dev/null; then
            if command -v yay &>/dev/null; then
                log "Installing pam-u2f via yay (AUR)"
                sudo -u nobody yay -S --noconfirm pam-u2f pamu2fcfg 2>/dev/null || \
                    warn "AUR install failed — install pam-u2f manually from AUR"
            elif command -v paru &>/dev/null; then
                log "Installing pam-u2f via paru (AUR)"
                sudo -u nobody paru -S --noconfirm pam-u2f pamu2fcfg 2>/dev/null || \
                    warn "AUR install failed — install pam-u2f manually from AUR"
            else
                warn "pam-u2f not in official repos and no AUR helper found."
                warn "Install manually: yay -S pam-u2f pamu2fcfg"
            fi
        fi
        # libfido2 for fido2-token tooling
        pkg_install libfido2
    fi
else
    pkg_install $IPA_CLIENT_PKG $SSSD_PKGS

    if [[ -n "$ODDJOB_PKGS" ]]; then
        pkg_install $ODDJOB_PKGS
    fi

    if [[ "$ENABLE_FIDO2" == true ]]; then
        pkg_install $FIDO2_PKGS libfido2
        # pamu2fcfg may be a separate package on some distros
        pkg_install $FIDO2_TOOLS_PKGS 2>/dev/null || true
    fi
fi

log "Packages installed"

# ─── Set hostname ─────────────────────────────────────────────────────────────
section "Configuring hostname"

CURRENT_HOSTNAME=$(hostname -f 2>/dev/null || hostnamectl hostname 2>/dev/null || cat /etc/hostname 2>/dev/null || echo "unknown")
if [[ "$CURRENT_HOSTNAME" != "$IPA_HOSTNAME" ]]; then
    log "Setting hostname to $IPA_HOSTNAME"
    hostnamectl set-hostname "$IPA_HOSTNAME"
fi

HOST_IP=$(ip route get 1 | awk '{print $7; exit}')
SHORT_NAME="${IPA_HOSTNAME%%.*}"

sed -i "/\b${IPA_HOSTNAME}\b/d" /etc/hosts
sed -i "/\b${SHORT_NAME}\b/d" /etc/hosts
echo "$HOST_IP  $IPA_HOSTNAME  $SHORT_NAME" >> /etc/hosts
log "Updated /etc/hosts: $HOST_IP  $IPA_HOSTNAME"

# ─── Arch-specific pre-enrollment setup ──────────────────────────────────────
if [[ "$OS_ID" == "arch" ]]; then
    section "Arch-specific setup"

    # Arch ships nscd rather than sssd providing nss by default;
    # make sure nsswitch.conf will work with sss
    if ! grep -q "sss" /etc/nsswitch.conf; then
        sed -i 's/^passwd:.*/passwd: files sss/' /etc/nsswitch.conf
        sed -i 's/^group:.*/group: files sss/' /etc/nsswitch.conf
        sed -i 's/^shadow:.*/shadow: files sss/' /etc/nsswitch.conf
        log "Updated nsswitch.conf for SSSD"
    fi

    # Ensure the IPA CA cert directory exists (ipa-client-install expects it)
    mkdir -p /etc/ipa
fi

# ─── Run ipa-client-install ───────────────────────────────────────────────────
section "Enrolling with FreeIPA"

ENROLL_ARGS=(
    --domain="$IPA_DOMAIN"
    --realm="$IPA_REALM"
    --server="$IPA_SERVER"
    --hostname="$IPA_HOSTNAME"
    --principal="$IPA_PRINCIPAL"
    --password="$IPA_PASSWORD"
    --unattended
)

[[ "$MKHOMEDIR" == true ]]    && ENROLL_ARGS+=(--mkhomedir)
[[ "$DNS_UPDATE" == true ]]   && ENROLL_ARGS+=(--enable-dns-updates)
[[ "$CONFIGURE_SUDO" == true ]] && true  # sudo via SSSD is configured post-enrollment

log "Running ipa-client-install..."
if ipa-client-install "${ENROLL_ARGS[@]}"; then
    log "Enrollment successful"
else
    error "ipa-client-install failed"
    exit 1
fi

# ─── SSSD configuration ───────────────────────────────────────────────────────
section "Configuring SSSD"

if [[ "$CONFIGURE_SUDO" == true ]]; then
    if grep -q "^services" /etc/sssd/sssd.conf; then
        if ! grep -q "sudo" /etc/sssd/sssd.conf; then
            sed -i 's/^services = .*/& , sudo/' /etc/sssd/sssd.conf
            log "Added sudo to SSSD services"
        fi
    fi

    if ! grep -q "^sudoers:.*sss" /etc/nsswitch.conf; then
        if grep -q "^sudoers:" /etc/nsswitch.conf; then
            sed -i 's/^sudoers:.*/sudoers: files sss/' /etc/nsswitch.conf
        else
            echo "sudoers: files sss" >> /etc/nsswitch.conf
        fi
        log "Configured nsswitch.conf for sudo via SSSD"
    fi
fi

systemctl restart sssd
systemctl enable sssd
log "SSSD restarted"

# ─── PAM / homedir ────────────────────────────────────────────────────────────
section "Configuring PAM"

if [[ "$MKHOMEDIR" == true ]]; then
    case "$OS_ID" in
        rhel|centos|rocky|almalinux|fedora)
            systemctl enable --now oddjobd 2>/dev/null || true
            authselect enable-feature with-mkhomedir 2>/dev/null || \
                authconfig --enablemkhomedir --update 2>/dev/null || true
            ;;
        arch)
            # Arch uses pam_mkhomedir directly in PAM stack
            if ! grep -q "pam_mkhomedir" /etc/pam.d/system-login; then
                sed -i '/^session.*pam_systemd/a session    optional   pam_mkhomedir.so skel=/etc/skel umask=077' \
                    /etc/pam.d/system-login
                log "Added pam_mkhomedir to /etc/pam.d/system-login"
            fi
            ;;
        debian|ubuntu)
            pam-auth-update --enable mkhomedir 2>/dev/null || true
            ;;
    esac
    log "Home directory auto-creation enabled"
fi

# ─── FIDO2 configuration ──────────────────────────────────────────────────────
if [[ "$ENABLE_FIDO2" == true ]]; then
    section "Configuring FIDO2 authentication"

    # Check pam_u2f.so is actually present after install
    PAM_U2F_SO=$(find /usr/lib /lib -name "pam_u2f.so" 2>/dev/null | head -1)
    if [[ -z "$PAM_U2F_SO" ]]; then
        error "pam_u2f.so not found — FIDO2 PAM module not installed correctly"
        warn "Skipping FIDO2 configuration. Install pam-u2f manually and re-run with --fido2"
    else
        log "Found pam_u2f.so at: $PAM_U2F_SO"

        # ── Central credential store ─────────────────────────────────────────
        # We use a central /etc/u2f_mappings file so credentials are stored
        # system-wide (not per-user ~/.config/Yubico/u2f_keys), which is
        # easier to manage and distribute via Ansible/Puppet.
        U2F_MAPPINGS="/etc/u2f_mappings"
        touch "$U2F_MAPPINGS"
        chmod 600 "$U2F_MAPPINGS"

        # ── PAM stack integration ─────────────────────────────────────────────
        # Strategy: FIDO2 as a SECOND factor (after password) for sudo and sshd.
        # Change 'required' to 'sufficient' if you want FIDO2 as sole factor.
        #
        # We write a drop-in snippet and include it rather than patching
        # distro PAM files directly, to survive pam-config updates.

        PAM_FIDO2_SNIPPET="/etc/pam.d/fido2-u2f"
        cat > "$PAM_FIDO2_SNIPPET" <<'PAMEOF'
# pam-u2f FIDO2 second factor
# auth required   = must present FIDO2 key (touch required by default)
# nouserok        = allow login even if user has no key registered
#                   (remove this once all users have keys enrolled)
# authfile        = path to central credential mapping file
auth    required    pam_u2f.so authfile=/etc/u2f_mappings nouserok cue
PAMEOF

        # Inject into sudo PAM — after the first 'auth' line (after password)
        configure_pam_sudo() {
            local pam_file="$1"
            if [[ -f "$pam_file" ]] && ! grep -q "pam_u2f" "$pam_file"; then
                # Insert after the last 'auth' line that isn't pam_u2f
                sed -i '/^auth.*pam_sss\|^auth.*pam_unix/a auth    include    fido2-u2f' "$pam_file"
                log "Injected FIDO2 into $pam_file"
            fi
        }

        case "$OS_ID" in
            rhel|centos|rocky|almalinux|fedora)
                configure_pam_sudo /etc/pam.d/sudo
                configure_pam_sudo /etc/pam.d/sshd
                ;;
            arch)
                configure_pam_sudo /etc/pam.d/sudo
                configure_pam_sudo /etc/pam.d/sshd
                ;;
            debian|ubuntu)
                configure_pam_sudo /etc/pam.d/sudo
                configure_pam_sudo /etc/pam.d/sshd
                ;;
        esac

        # ── udev rule for FIDO2 devices ───────────────────────────────────────
        # Without this, non-root users can't talk to the USB HID device
        UDEV_RULE="/etc/udev/rules.d/70-u2f.rules"
        if [[ ! -f "$UDEV_RULE" ]]; then
            cat > "$UDEV_RULE" <<'UDEVEOF'
# FIDO2/U2F device access for all users
# Covers YubiKey, Nitrokey, SoloKey, Google Titan, and generic FIDO2 keys
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", TAG+="uaccess", GROUP="plugdev", MODE="0660"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", TAG+="uaccess", GROUP="plugdev", MODE="0660"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", TAG+="uaccess", GROUP="plugdev", MODE="0660"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1d6b", TAG+="uaccess", GROUP="plugdev", MODE="0660"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2581", TAG+="uaccess", GROUP="plugdev", MODE="0660"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", TAG+="uaccess", GROUP="plugdev", MODE="0660"
UDEVEOF
            udevadm control --reload-rules
            udevadm trigger
            log "Installed udev rules for FIDO2 devices"
        fi

        # ── plugdev group ─────────────────────────────────────────────────────
        if ! getent group plugdev &>/dev/null; then
            groupadd plugdev
            log "Created plugdev group"
        fi

        # ── Register keys for specified users ─────────────────────────────────
        if [[ ${#FIDO2_USERS[@]} -gt 0 ]]; then
            section "Registering FIDO2 keys"
            info "You will be prompted to touch your FIDO2 key for each user."
            info "Credentials are stored in $U2F_MAPPINGS"
            echo

            for IPA_USER in "${FIDO2_USERS[@]}"; do
                echo -e "${CYAN}━━━ Registering key for user: $IPA_USER ━━━${NC}"

                # Check if user already has an entry
                if grep -q "^${IPA_USER}:" "$U2F_MAPPINGS" 2>/dev/null; then
                    warn "User $IPA_USER already has a FIDO2 credential in $U2F_MAPPINGS"
                    read -rp "  Add an additional key for $IPA_USER? [y/N]: " ADD_MORE
                    [[ ! "$ADD_MORE" =~ ^[Yy]$ ]] && continue
                fi

                info "Insert the FIDO2 key for $IPA_USER and press Enter when ready..."
                read -r

                # Check key is present
                if command -v fido2-token &>/dev/null; then
                    DETECTED=$(fido2-token -L 2>/dev/null | wc -l)
                    if [[ "$DETECTED" -eq 0 ]]; then
                        warn "No FIDO2 device detected — skipping $IPA_USER"
                        continue
                    fi
                fi

                info "Touch the key when it blinks..."

                # pamu2fcfg outputs: username:credentialID,publicKey
                if NEW_CRED=$(pamu2fcfg -u "$IPA_USER" -o "pam://$(hostname -f)" 2>/dev/null); then
                    if grep -q "^${IPA_USER}:" "$U2F_MAPPINGS"; then
                        # Append additional key to existing line (pamu2fcfg format)
                        EXTRA=$(echo "$NEW_CRED" | cut -d: -f2-)
                        sed -i "s|^${IPA_USER}:.*|&:${EXTRA}|" "$U2F_MAPPINGS"
                        log "Added additional FIDO2 key for $IPA_USER"
                    else
                        echo "$NEW_CRED" >> "$U2F_MAPPINGS"
                        log "Registered FIDO2 key for $IPA_USER"
                    fi

                    # Add user to plugdev group
                    usermod -aG plugdev "$IPA_USER" 2>/dev/null || true
                else
                    error "pamu2fcfg failed for $IPA_USER — key may not support this operation"
                    warn "You can register manually later:"
                    warn "  pamu2fcfg -u $IPA_USER -o pam://\$(hostname -f) >> $U2F_MAPPINGS"
                fi
            done
        else
            # No users specified — print instructions for manual registration
            info "No --fido2-user specified. To register keys manually:"
            echo
            echo "  # As root, for each user:"
            echo "  pamu2fcfg -u <username> -o pam://\$(hostname -f) >> $U2F_MAPPINGS"
            echo
            echo "  # Then add user to plugdev group:"
            echo "  usermod -aG plugdev <username>"
            echo
        fi

        log "FIDO2 PAM configuration complete"
        info "Note: 'nouserok' is set — users without a registered key can still log in."
        info "Remove 'nouserok' from $PAM_FIDO2_SNIPPET once all users have keys enrolled."
    fi
fi

# ─── SSH configuration ────────────────────────────────────────────────────────
section "Configuring SSH"

SSHD_CONFIG="/etc/ssh/sshd_config"

set_sshd_option() {
    local key="$1" val="$2"
    if grep -qE "^#?${key}" "$SSHD_CONFIG"; then
        sed -i "s|^#\?${key}.*|${key} ${val}|" "$SSHD_CONFIG"
    else
        echo "${key} ${val}" >> "$SSHD_CONFIG"
    fi
}

set_sshd_option "GSSAPIAuthentication" "yes"
set_sshd_option "GSSAPICleanupCredentials" "yes"
set_sshd_option "AuthorizedKeysCommand" "/usr/bin/sss_ssh_authorizedkeys"
set_sshd_option "AuthorizedKeysCommandUser" "nobody"

# If FIDO2 is enabled, allow keyboard-interactive so pam_u2f can prompt for touch
if [[ "$ENABLE_FIDO2" == true ]]; then
    set_sshd_option "ChallengeResponseAuthentication" "yes"
    set_sshd_option "AuthenticationMethods" "publickey,keyboard-interactive"
    log "SSH configured for FIDO2 second factor (keyboard-interactive)"
fi

systemctl restart sshd
log "SSH configured"

# ─── Verify enrollment ────────────────────────────────────────────────────────
section "Verifying enrollment"

if echo "$IPA_PASSWORD" | kinit "${IPA_PRINCIPAL}@${IPA_REALM}" &>/dev/null; then
    log "Kerberos authentication: OK"
    kdestroy &>/dev/null
else
    warn "Could not obtain Kerberos ticket — check credentials/connectivity"
fi

if id "admin" &>/dev/null; then
    log "SSSD user lookup: OK (resolved 'admin' from IPA)"
else
    warn "SSSD user lookup failed — SSSD may still be starting up"
fi

# ─── Summary ──────────────────────────────────────────────────────────────────
section "Enrollment complete"

cat <<EOF

  Host:      $IPA_HOSTNAME
  Domain:    $IPA_DOMAIN
  Realm:     $IPA_REALM
  Server:    $IPA_SERVER
  OS:        $PRETTY_NAME
  Homedir:   $MKHOMEDIR
  Sudo:      $CONFIGURE_SUDO
  FIDO2:     $ENABLE_FIDO2
EOF

if [[ "$ENABLE_FIDO2" == true ]] && [[ ${#FIDO2_USERS[@]} -gt 0 ]]; then
    echo "  FIDO2 users registered: ${FIDO2_USERS[*]}"
fi

cat <<'EOF'

Next steps:
  • Test login:        ssh <ipa-user>@$(hostname -f)
  • Check SSSD:        sssctl domain-status <domain>
  • Check Kerberos:    kinit <user>@<REALM>
  • List FIDO2 creds:  cat /etc/u2f_mappings
  • Register key:      pamu2fcfg -u <user> -o pam://$(hostname -f) >> /etc/u2f_mappings

EOF
