#!/bin/bash
#
# Author:  Daniel 'dzatoah' Teichmann <daniel.teichmann@das-netzwerkteam.de>
# Date:    2026-03-21
# License: GNU General Public License v2 or later (GPL-2+)
#

# =============================================================================
# Queries LDAP for cNAMERecord entries under the Debian Edu DNS zone and
# smartly merges them into the Apache2 TJENER alias map file.
#
# Usage:   /usr/libexec/debian-edu-config/debian-edu-apache2-update-tjener-aliases
# =============================================================================

set -euo pipefail

# -----------------------------------------------------------------------------
# Configuration
# -----------------------------------------------------------------------------
SCRIPT_PATH="/usr/libexec/debian-edu-config/debian-edu-apache2-update-tjener-aliases"
MAP_FILE="/usr/share/debian-edu-config/apache2_tjener-aliases.map"
BACKUP_DIR="/var/backups/apache2-tjener-aliases"
LDAP_BASE="relativeDomainName=tjener,zoneName=intern,cn=tjener,ou=servers,ou=systems,dc=skole,dc=skolelinux,dc=no"
LDAP_OPTS="-x"
LOG_TAG="update-apache2-tjener-aliases"

# Debian Edu default hostnames (short + .intern variants) — always included
# in the auto-generated block regardless of LDAP output.
DEBIAN_EDU_DEFAULTS=(
    "www"              "www.intern"
    "tjener"           "tjener.intern"
    "ldap"             "ldap.intern"
)

# -----------------------------------------------------------------------------
# Helpers
# -----------------------------------------------------------------------------
log()  { logger -t "$LOG_TAG" "$*"; echo "[$(date '+%F %T')] $*"; }
die()  { log "ERROR: $*"; exit 1; }

# -----------------------------------------------------------------------------
# Sanity checks
# -----------------------------------------------------------------------------
command -v ldapsearch >/dev/null 2>&1 || die "ldapsearch not found. Install ldap-utils."
[[ -d "$(dirname "$MAP_FILE")" ]]    || die "Target directory does not exist: $(dirname "$MAP_FILE")"

# -----------------------------------------------------------------------------
# Backup existing map file
# -----------------------------------------------------------------------------
mkdir -p "$BACKUP_DIR"
if [[ -f "$MAP_FILE" ]]; then
    BACKUP_FILE="$BACKUP_DIR/apache2_tjener-aliases.map.$(date '+%Y%m%d_%H%M%S')"
    cp "$MAP_FILE" "$BACKUP_FILE"
    log "Backed up existing map to: $BACKUP_FILE"

    # Prune backups older than 30 days
    find "$BACKUP_DIR" -name "apache2_tjener-aliases.map.*" -mtime +30 -delete
fi

# -----------------------------------------------------------------------------
# Parse HAND-EDIT block from existing file (preserve user additions)
# -----------------------------------------------------------------------------
HAND_EDIT_BLOCK=""
if [[ -f "$MAP_FILE" ]]; then
    # Extract everything from the HAND-EDIT marker to end of file
    HAND_EDIT_BLOCK=$(awk '/^# ----- IMPORTANT HAND-EDIT ADDITIONS -----/{found=1} found{print}' "$MAP_FILE")
fi

# If there was no hand-edit block yet, use the default placeholder
if [[ -z "$HAND_EDIT_BLOCK" ]]; then
    HAND_EDIT_BLOCK='# ----- IMPORTANT HAND-EDIT ADDITIONS -----
# If a user browses to the raw IP and it is not here, it will trigger a 302
# response to a pre-defined server name.
# Add any other IP addresses assigned to Tjeners interfaces:
# 192.168.0.1        ALLOW'
fi

# -----------------------------------------------------------------------------
# Query LDAP for cNAMERecord entries
# -----------------------------------------------------------------------------
log "Querying LDAP for cNAMERecord entries..."
LDAP_OUTPUT=$(ldapsearch $LDAP_OPTS -b "$LDAP_BASE" 2>&1) \
    || die "ldapsearch failed: $LDAP_OUTPUT"

# Parse relativeDomainName values that have a cNAMERecord attribute.
# Strategy: track the current relativeDomainName per stanza; only emit it
# if a cNAMERecord line is also present in the same stanza.
mapfile -t LDAP_CNAMES < <(
    awk '
        /^$/ { if (has_cname && rdn != "" && rdn != "tjener") print rdn; rdn=""; has_cname=0; next }
        /^relativeDomainName:/ { rdn=$2 }
        /^cNAMERecord:/        { has_cname=1 }
        END { if (has_cname && rdn != "" && rdn != "tjener") print rdn }
    ' <<< "$LDAP_OUTPUT" | sort -u
)

log "LDAP returned ${#LDAP_CNAMES[@]} cNAME entries: ${LDAP_CNAMES[*]:-<none>}"

# -----------------------------------------------------------------------------
# Build the de-duplicated LDAP-only section
# (skip anything already in the Debian Edu defaults block)
# -----------------------------------------------------------------------------

# Build a lookup set of already-covered hostnames (short names only)
declare -A COVERED
for h in "${DEBIAN_EDU_DEFAULTS[@]}"; do
    # Strip .intern suffix for comparison
    short="${h%.intern}"
    COVERED["$short"]=1
done
# Also mark static entries
for h in localhost tjener; do COVERED["$h"]=1; done

LDAP_EXTRA_LINES=()
for cname in "${LDAP_CNAMES[@]}"; do
    if [[ -z "${COVERED[$cname]+_}" ]]; then
        LDAP_EXTRA_LINES+=("$cname")
        COVERED["$cname"]=1
    fi
done

# -----------------------------------------------------------------------------
# Format helper: pad hostname to column 20 then append ALLOW
# -----------------------------------------------------------------------------
fmt_allow() {
    printf "%-20s ALLOW\n" "$1"
}

# -----------------------------------------------------------------------------
# Assemble the new map file in a temp file, then atomically replace
# -----------------------------------------------------------------------------
TMPFILE=$(mktemp "${MAP_FILE}.tmp.XXXXXX")
trap 'rm -f "$TMPFILE"' EXIT

{
# ── Static header ─────────────────────────────────────────────────────────────
cat <<HEADER
# /usr/share/debian-edu-config/apache2_tjener-aliases.map
# Format: <server_alias_or_IP> ALLOW
#
# IMPORTANT: This file will be changed daily by a script using cron/systemd.
#            It contains allowed HTTP Host: header values (TJENER aliases
#            and IPs), not client machines.
#            The script tries to smartly merge existing hand-made entries,
#            but you should execute the script manually once to make sure your
#            changes are sticking:
#            $ ${SCRIPT_PATH}

# Local loopback devices
$(fmt_allow localhost)
$(fmt_allow "::1")
$(fmt_allow "127.0.0.1")
HEADER

# ── Auto-generated Debian Edu defaults ────────────────────────────────────────
cat <<'AUTOGEN_HEADER'

# ---------------------------
# |   Debian Edu defaults   |
# ---------------------------
AUTOGEN_HEADER

echo "# NOTE: 10.x.x.x/8 network is allowed anyway, so next line is redundand."
fmt_allow "10.0.2.2"
fmt_allow "www"
fmt_allow "www.intern"
fmt_allow "tjener"
fmt_allow "tjener.intern"
fmt_allow "ldap"
fmt_allow "ldap.intern"
echo "# ---------------------------"

# ── LDAP-discovered cNAMEs (not already in defaults) ──────────────────────────
if [[ ${#LDAP_EXTRA_LINES[@]} -gt 0 ]]; then
    echo ""
    echo ""
    echo "# ---------------------------"
    echo "# |   LDAP-discovered CNAMEs  |"
    echo "# |   (auto-updated daily)    |"
    echo "# ---------------------------"
    echo "# Last updated: $(date '+%Y-%m-%d %H:%M:%S')"
    for h in "${LDAP_EXTRA_LINES[@]}"; do
        fmt_allow "$h"
        # Also emit the .intern variant if not already covered
        intern_variant="${h}.intern"
        if [[ -z "${COVERED[$intern_variant]+_}" ]]; then
            fmt_allow "$intern_variant"
            COVERED["$intern_variant"]=1
        fi
    done
    echo "# ---------------------------"
fi

# ── Preserved hand-edit block ─────────────────────────────────────────────────
echo ""
echo ""
echo "$HAND_EDIT_BLOCK"

} > "$TMPFILE"

# Validate the temp file is non-empty before replacing
[[ -s "$TMPFILE" ]] || die "Generated file is empty — aborting."

# Atomic replace
mv "$TMPFILE" "$MAP_FILE"
chmod 644 "$MAP_FILE"
log "Successfully updated: $MAP_FILE"

# -----------------------------------------------------------------------------
# Reload Apache2 if running (graceful — no dropped connections)
# -----------------------------------------------------------------------------
if systemctl is-active --quiet apache2; then
    log "Reloading Apache2..."
    systemctl reload apache2 \
        && log "Apache2 reloaded successfully." \
        || log "WARNING: Apache2 reload failed. Check 'systemctl status apache2'."
else
    log "Apache2 is not running — skipping reload."
fi
