#!/usr/bin/env bash
#
# dnssec_check - prüft für hinterlegte Domains, ob DNSSEC aktiviert und
#                gültig signiert ist.
#
# Zielsystem : Ubuntu 22.04
# Benötigt   : dig  (Paket: bind9-dnsutils  bzw.  dnsutils)
#              Installation:  sudo apt install bind9-dnsutils
#
# Status-Bedeutung:
#   aktiv    - Domain ist DNSSEC-signiert UND wurde erfolgreich validiert
#              (Antwort NOERROR mit gesetztem AD-Flag = Authenticated Data)
#   inaktiv  - Domain ist NICHT signiert (kein DNSSEC vorhanden)
#              (Antwort NOERROR, aber KEIN AD-Flag)
#   fail     - DNSSEC vorhanden, aber Validierung schlägt fehl / Fehler
#              (SERVFAIL vom validierenden Resolver o. ä.)
#
# Wichtig: Das Skript fragt einen *validierenden* Resolver ab (Default der
# lokale BIND auf 127.0.0.1). Ohne validierenden Resolver ist die Prüfung
# nicht aussagekräftig.
#
# Aufruf:
#   ./dnssec_check                 # nutzt die unten hinterlegte Liste
#   ./dnssec_check -r 192.168.1.1  # anderer Resolver
#   ./dnssec_check example.com     # nur diese Domain(s) prüfen
#

set -u

# ------------------------------------------------------------------
# Konfiguration
# ------------------------------------------------------------------

# Validierender Resolver (z. B. der lokale BIND). Per -r überschreibbar.
RESOLVER="127.0.0.1"

# >>> Hier die zu prüfenden Domains eintragen <<<
DOMAINS=(
    "isc.org"              # signiert  -> erwartet: aktiv
    "dnssec-failed.org"    # kaputt    -> erwartet: fail
    "maier3000.de"          # signiert  -> erwartet: aktiv
    "maierch.de"          # signiert  -> erwartet: aktiv
    "maierchr.de"          # signiert  -> erwartet: aktiv
    "citeq.de"           # unsigniert-> erwartet: inaktiv
)

# Abfragetyp und dig-Optionen (Timeout/Versuche)
QTYPE="A"
DIG_OPTS=(+time=3 +tries=2)

# ------------------------------------------------------------------
# Ab hier nichts mehr ändern
# ------------------------------------------------------------------

usage() {
    cat >&2 <<EOF
Aufruf: $0 [-r resolver] [-t qtype] [-s|--self-check] [domain ...]
  -r resolver     DNS-Resolver, der geprüft wird (Default: $RESOLVER)
  -t qtype        Abfragetyp (Default: $QTYPE)
  -s, --self-check  nur prüfen, ob der Resolver DNSSEC validiert (keine Domainliste)
EOF
    exit 1
}

SELF_ONLY=0

# Lange Option --self-check auf -s abbilden
# (Guard fuer leere Argumentliste wegen bash 3.2 unter macOS + 'set -u')
if [[ $# -gt 0 ]]; then
    args=()
    for a in "$@"; do
        case "$a" in
            --self-check) args+=("-s") ;;
            *) args+=("$a") ;;
        esac
    done
    set -- "${args[@]}"
fi

while getopts ":r:t:sh" opt; do
    case "$opt" in
        r) RESOLVER="$OPTARG" ;;
        t) QTYPE="$OPTARG" ;;
        s) SELF_ONLY=1 ;;
        h) usage ;;
        *) usage ;;
    esac
done
shift $((OPTIND - 1))

# Domains von der Kommandozeile überschreiben die Liste
if [[ $# -gt 0 ]]; then
    DOMAINS=("$@")
fi

# dig vorhanden?
if ! command -v dig >/dev/null 2>&1; then
    echo "Fehler: 'dig' nicht gefunden. Installieren mit: sudo apt install bind9-dnsutils" >&2
    exit 2
fi

# Farben nur, wenn Ausgabe ins Terminal geht
if [[ -t 1 ]]; then
    C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'; C_RED=$'\033[31m'; C_RST=$'\033[0m'
else
    C_GREEN=""; C_YELLOW=""; C_RED=""; C_RST=""
fi

# Feld aus einer dig-Ausgabe ziehen ------------------------------------
get_status() { sed -n 's/.*status: \([A-Z]*\).*/\1/p' | head -n1; }
get_flags()  { sed -n 's/.*flags: \([a-z ]*\);.*/\1/p'  | head -n1; }

# Kernprüfung: liefert aktiv | inaktiv | fail -------------------------
check_domain() {
    local domain="$1" out status flags cdstatus

    out=$(dig "@$RESOLVER" "${DIG_OPTS[@]}" +dnssec "$domain" "$QTYPE" 2>/dev/null)
    if [[ -z "$out" ]]; then
        echo "fail"; return
    fi

    status=$(printf '%s\n' "$out" | get_status)
    flags=$(printf '%s\n' "$out" | get_flags)

    case "$status" in
        NOERROR|NXDOMAIN)
            # AD-Flag = vom Resolver erfolgreich validiert (= signiert)
            if [[ " $flags " == *" ad "* ]]; then
                echo "aktiv"
            else
                echo "inaktiv"
            fi
            ;;
        SERVFAIL)
            # Gegenprobe mit +cd (checking disabled): kommt dann eine
            # gültige Antwort, lag der SERVFAIL an der DNSSEC-Validierung.
            cdstatus=$(dig "@$RESOLVER" "${DIG_OPTS[@]}" +cd "$domain" "$QTYPE" 2>/dev/null | get_status)
            if [[ "$cdstatus" == "NOERROR" || "$cdstatus" == "NXDOMAIN" ]]; then
                echo "fail"     # DNSSEC-Signatur/Kette ungültig
            else
                echo "fail"     # sonstiger Auflösungsfehler
            fi
            ;;
        *)
            echo "fail"
            ;;
    esac
}

# Selbsttest: validiert der Resolver überhaupt? ----------------------
# Nutzt zwei Referenzdomains:
#   - eine absichtlich kaputt signierte (muss SERVFAIL liefern)
#   - eine korrekt signierte (muss AD-Flag liefern)
# Rückgabe: 0 = validiert, 1 = validiert nicht, 2 = unklar/Netzfehler
SELF_BAD_DOMAIN="dnssec-failed.org"   # bewusst falsch signiert (Verisign)
SELF_GOOD_DOMAIN="isc.org"            # korrekt signiert

self_check() {
    local bad_status cd_status good good_status good_flags

    bad_status=$(dig "@$RESOLVER" "${DIG_OPTS[@]}" +dnssec "$SELF_BAD_DOMAIN" A 2>/dev/null | get_status)
    cd_status=$( dig "@$RESOLVER" "${DIG_OPTS[@]}" +cd     "$SELF_BAD_DOMAIN" A 2>/dev/null | get_status)
    good=$(      dig "@$RESOLVER" "${DIG_OPTS[@]}" +dnssec "$SELF_GOOD_DOMAIN" A 2>/dev/null)
    good_status=$(printf '%s\n' "$good" | get_status)
    good_flags=$( printf '%s\n' "$good" | get_flags)

    # Netz-/Erreichbarkeitsproblem?
    if [[ -z "$bad_status" || -z "$good_status" ]]; then
        printf 'Resolver-Selbsttest: %sUNKLAR%s (Resolver %s nicht erreichbar?)\n' \
               "$C_YELLOW" "$C_RST" "$RESOLVER"
        return 2
    fi

    # Kriterien fuer "validiert":
    #   kaputte Domain -> SERVFAIL, mit +cd aber aufloesbar
    #   gute  Domain   -> NOERROR mit AD-Flag
    if [[ "$bad_status" == "SERVFAIL" && ( "$cd_status" == "NOERROR" || "$cd_status" == "NXDOMAIN" ) \
          && "$good_status" == "NOERROR" && " $good_flags " == *" ad "* ]]; then
        printf 'Resolver-Selbsttest: %sVALIDIERT DNSSEC%s  (%s=SERVFAIL, %s=AD)\n' \
               "$C_GREEN" "$C_RST" "$SELF_BAD_DOMAIN" "$SELF_GOOD_DOMAIN"
        return 0
    else
        printf 'Resolver-Selbsttest: %sKEINE VALIDIERUNG%s  (%s=%s, +cd=%s, %s=%s flags:%s)\n' \
               "$C_RED" "$C_RST" \
               "$SELF_BAD_DOMAIN" "$bad_status" "$cd_status" \
               "$SELF_GOOD_DOMAIN" "$good_status" "$good_flags"
        return 1
    fi
}

# ------------------------------------------------------------------
# Ausgabe
# ------------------------------------------------------------------

# Nur Selbsttest gewuenscht?
if [[ "$SELF_ONLY" -eq 1 ]]; then
    printf 'Resolver: %s   (%s)\n' "$RESOLVER" "$(date '+%Y-%m-%d %H:%M:%S')"
    self_check
    exit $?
fi

printf '%s\n' "DNSSEC-Prüfung über Resolver: $RESOLVER   ($(date '+%Y-%m-%d %H:%M:%S'))"

# Immer zuerst prüfen, ob der Resolver validiert – sonst sind die
# Domain-Ergebnisse nicht aussagekräftig.
self_check
sc_rc=$?
if [[ "$sc_rc" -ne 0 ]]; then
    printf '%sHinweis:%s Resolver validiert nicht/unklar – "aktiv/fail" unten sind nur eingeschränkt verlässlich.\n' \
           "$C_YELLOW" "$C_RST"
fi
echo
printf '%-40s %s\n' "DOMAIN" "STATUS"
printf '%-40s %s\n' "----------------------------------------" "--------"

rc=0
for d in "${DOMAINS[@]}"; do
    st=$(check_domain "$d")
    case "$st" in
        aktiv)   col="$C_GREEN" ;;
        inaktiv) col="$C_YELLOW" ;;
        *)       col="$C_RED"; rc=1 ;;
    esac
    printf '%-40s %s%s%s\n' "$d" "$col" "$st" "$C_RST"
done

# Exit-Code: 0 = kein fail, 1 = mindestens ein fail (nützlich für Monitoring/cron)
exit $rc
