#!/usr/bin/env bash
# =============================================================================
# onx-modsec-status — v2.0 DRIVER-AWARE ModSecurity engine + audit health.
#
# v3.16: Multi-webserver awareness — aktif customer driver'a göre ModSec
# durumu raporlanır. Her driver ayrı modül + config + audit log path kullanır:
#   - Apache: mod_security2 (libmodsecurity v2), /var/log/httpd/modsec_audit.log
#   - OLS:    built-in (lsws core), /usr/local/lsws/logs/modsec_audit.log
#   - Nginx:  ngx_http_modsecurity_module (libmodsecurity v3), /var/log/nginx/modsec_audit.log
#   - Caddy:  Coraza WAF (xcaddy custom build), /var/log/caddy/modsec_audit.log
#
# Input (stdin JSON, all optional):
#   { "log_file": "...", "tail_lines": 5000, "window_h": 24, "driver": "auto|apache|ols|nginx|caddy" }
#
# Output (stdout JSON):
#   {
#     "driver":         "apache|ols|nginx|caddy",
#     "active_driver":  "apache|ols|nginx|caddy",  -- system'de gerçekten aktif olan
#     "supported":      true,
#     "loaded":         true,
#     "version":        "2.9.6",
#     "crs_version":    "4.26.0",
#     "engine_default": "On",
#     "log_file":       "/var/log/...",
#     "log_size_bytes": 37826,
#     "blocks_24h":     42,
#     "top_blocked_ips":[...], "top_rules":[...], "recent":[...]
#   }
# =============================================================================

set -euo pipefail

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
source "${SCRIPT_DIR}/_lib/common.sh"

INPUT_RAW="$(cat 2>/dev/null || true)"
[[ -z "${INPUT_RAW}" ]] && INPUT_RAW='{}'
echo "${INPUT_RAW}" | jq -e 'type == "object"' >/dev/null 2>&1 || INPUT_RAW='{}'

REQ_DRIVER=$(onx_json_get   "${INPUT_RAW}" "driver"      "auto")
LOG_OVERRIDE=$(onx_json_get "${INPUT_RAW}" "log_file"    "")
TAIL_LINES=$(onx_json_get   "${INPUT_RAW}" "tail_lines"  "5000")
WINDOW_H=$(onx_json_get     "${INPUT_RAW}" "window_h"    "24")

[[ "${TAIL_LINES}" =~ ^[0-9]+$ ]] || TAIL_LINES=5000
[[ "${WINDOW_H}"   =~ ^[0-9]+$ ]] || WINDOW_H=24

# ── Detect active customer driver ────────────────────────────────────────────
detect_active_driver() {
    for ws_pair in "httpd:apache" "openlitespeed:ols" "lshttpd:ols" "nginx:nginx" "caddy:caddy"; do
        local unit="${ws_pair%%:*}"
        local key="${ws_pair##*:}"
        if systemctl is-active --quiet "$unit" 2>/dev/null; then
            echo "$key"
            return
        fi
    done
    echo "unknown"
}

ACTIVE_DRIVER=$(detect_active_driver)

# REQ_DRIVER=auto ise active'i kullan
if [[ "${REQ_DRIVER}" == "auto" ]]; then
    REQ_DRIVER="${ACTIVE_DRIVER}"
fi

# ── Per-driver status check ──────────────────────────────────────────────────
DRIVER=""
SUPPORTED="false"
LOADED="false"
VERSION="unknown"
CRS_VERSION="unknown"
ENGINE_DEFAULT="unknown"
LOG_FILE=""
CONFIG_PATH=""
MISSING_REASON=""

case "${REQ_DRIVER}" in
    apache)
        DRIVER="apache"
        SUPPORTED="true"
        LOG_FILE="/var/log/httpd/modsec_audit.log"
        CONFIG_PATH="/etc/httpd/conf.d/00-modsecurity.conf"
        HTTPD_BIN=$(command -v httpd 2>/dev/null || command -v apachectl 2>/dev/null || echo "")
        if [[ -n "$HTTPD_BIN" ]] && "$HTTPD_BIN" -M 2>&1 | grep -qiE 'security[23]_module'; then
            LOADED="true"
            VERSION=$("$HTTPD_BIN" -t -D DUMP_MODULES 2>&1 | grep -iE 'security' | head -1 || echo "")
            # Daha güvenilir: ModSecurity rpm package version
            rpm_ver=$(rpm -q --queryformat '%{VERSION}' mod_security 2>/dev/null || echo "")
            [[ -n "$rpm_ver" ]] && VERSION="$rpm_ver"
            # SecRuleEngine değeri
            ENGINE_DEFAULT=$(grep -hE '^[[:space:]]*SecRuleEngine[[:space:]]' /etc/httpd/conf.d/*.conf /etc/httpd/modsecurity.d/*.conf 2>/dev/null \
                | grep -v '^#' | awk '{print $2}' | head -1)
            [[ -z "$ENGINE_DEFAULT" ]] && ENGINE_DEFAULT="DetectionOnly"
        else
            MISSING_REASON="Apache mod_security2 yüklü değil: dnf install -y mod_security mod_security_crs"
        fi
        ;;

    ols)
        DRIVER="ols"
        SUPPORTED="true"
        LOG_FILE="/usr/local/lsws/logs/modsec_audit.log"
        CONFIG_PATH="/usr/local/lsws/conf/httpd_config.conf"
        # OLS built-in: check via lswsctrl + config inspect
        if [[ -f "$CONFIG_PATH" ]] && grep -qE 'modsecurity[[:space:]]+on|modSecurity[[:space:]]+on' "$CONFIG_PATH" 2>/dev/null; then
            LOADED="true"
            VERSION=$(/usr/local/lsws/bin/openlitespeed -v 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unknown")
            ENGINE_DEFAULT="On"
        elif [[ -d "/usr/local/lsws" ]]; then
            LOADED="false"
            VERSION=$(/usr/local/lsws/bin/openlitespeed -v 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unknown")
            MISSING_REASON="OLS ModSec etkinleştirilmedi. Çözüm: WebAdmin → Server Configuration → Security → Mod Security → On, sonra restart. Veya: onx-modsec-install-driver driver=ols"
        else
            MISSING_REASON="OLS kurulu değil"
            SUPPORTED="false"
        fi
        ;;

    nginx)
        DRIVER="nginx"
        SUPPORTED="true"
        LOG_FILE="/var/log/nginx/modsec_audit.log"
        CONFIG_PATH="/etc/nginx/conf.d/modsecurity.conf"
        # Nginx: ngx_http_modsecurity_module dynamic (EPEL)
        NGINX_BIN=$(command -v nginx 2>/dev/null || echo "")
        if [[ -n "$NGINX_BIN" ]]; then
            # 1) Module dynamic loaded mı?
            #    EPEL paketi nginx-mod-modsecurity ayrı .so + kendi auto-include conf'unu
            #    bırakır: /usr/share/nginx/modules/mod-modsecurity.conf (load_module ...;)
            #    Bu conf nginx.conf'tan `include /usr/share/nginx/modules/*.conf;` ile yüklenir.
            #    v3.28: LOAD_MODULE_PRESENT taraması MULTI-PATH — sadece nginx.conf değil,
            #    EPEL/Debian conventionlarındaki tüm load_module conf'larını kapsa.
            NGINX_MODSEC_RPM=$(rpm -q nginx-mod-modsecurity 2>/dev/null | grep -v 'not installed' || echo "")
            NGINX_V_OUT=$("$NGINX_BIN" -V 2>&1 || true)
            LOAD_MODULE_PRESENT="false"
            for lm_path in \
                /etc/nginx/nginx.conf \
                /usr/share/nginx/modules/mod-modsecurity.conf \
                /usr/share/nginx/modules/*.conf \
                /etc/nginx/modules/*.conf \
                /etc/nginx/modules-enabled/*.conf \
                /etc/nginx/conf.d/mod-modsecurity.conf
            do
                # Glob expand olabilir; tek tek file kontrol
                [[ -f "$lm_path" ]] || continue
                if grep -qE '^[[:space:]]*load_module[[:space:]]+.*ngx_http_modsecurity_module\.so' "$lm_path" 2>/dev/null; then
                    LOAD_MODULE_PRESENT="true"
                    LOAD_MODULE_SRC="$lm_path"
                    break
                fi
            done

            if [[ -n "$NGINX_MODSEC_RPM" ]] || echo "$NGINX_V_OUT" | grep -qiE 'modsecurity'; then
                if [[ "$LOAD_MODULE_PRESENT" == "true" ]]; then
                    LOADED="true"
                fi
                # Version: rpm > nginx -V
                rpm_ver=$(rpm -q --queryformat '%{VERSION}' nginx-mod-modsecurity 2>/dev/null || echo "")
                if [[ -n "$rpm_ver" ]] && [[ "$rpm_ver" != *"not installed"* ]]; then
                    VERSION="$rpm_ver"
                else
                    VERSION=$(echo "$NGINX_V_OUT" | grep -oE 'modsecurity[/-][0-9]+\.[0-9]+(\.[0-9]+)?' | head -1 || echo "v3")
                fi
                ENGINE_DEFAULT=$(grep -hE '^[[:space:]]*modsecurity[[:space:]]+' /etc/nginx/conf.d/*.conf /etc/nginx/nginx.conf /etc/nginx/modsec/main.conf 2>/dev/null \
                    | grep -v '^#' | awk '{print $2}' | tr -d ';' | head -1)
                [[ -z "$ENGINE_DEFAULT" ]] && ENGINE_DEFAULT="off"

                if [[ "$LOADED" != "true" ]]; then
                    MISSING_REASON="nginx-mod-modsecurity kurulu ama load_module hiçbir conf'ta bulunamadı — onx-modsec-install-driver driver=nginx çalıştırın"
                fi
            else
                MISSING_REASON="Nginx ModSec modülü kurulu değil: dnf install -y epel-release nginx-mod-modsecurity"
            fi
        else
            MISSING_REASON="Nginx kurulu değil"
            SUPPORTED="false"
        fi
        ;;

    caddy)
        DRIVER="caddy"
        LOG_FILE="/var/log/caddy/modsec_audit.log"
        CONFIG_PATH="/etc/caddy/coraza.conf"
        CADDY_BIN=$(command -v caddy 2>/dev/null || echo "")

        if [[ -z "$CADDY_BIN" ]]; then
            SUPPORTED="false"
            MISSING_REASON="Caddy kurulu değil"
        else
            SUPPORTED="true"
            # Module check: Coraza WAF binary'de gömülü mü?
            # v3.30 FIX: Coraza-Caddy plugin kendini "http.handlers.waf" olarak
            # kaydeder, eski grep 'coraza' regex'i match etmiyordu (false negative).
            # Doğru regex: hem module name (http.handlers.waf, *.waf.* varyantları)
            # hem de binary strings'inde Coraza referansı bulunan implementasyonlar.
            CADDY_LIST=$("$CADDY_BIN" list-modules 2>/dev/null || true)
            if echo "$CADDY_LIST" | grep -qiE '^http\.handlers\.waf$|^http\.handlers\.coraza|coraza_waf|\.modsec\.|\.waf$'; then
                LOADED="true"
                VERSION=$("$CADDY_BIN" version 2>/dev/null | awk '{print $1}' | sed 's/^v//' | head -1 || echo "unknown")
                # Engine default: Caddyfile'da Coraza/WAF directive aktif mi
                #   site-block: `import coraza-include` aktif mi
                #   veya global `coraza_waf { ... }` / `waf { ... }` var mı
                if grep -qE '(import[[:space:]]+coraza-include|coraza_waf[[:space:]]*\{|^[[:space:]]*waf[[:space:]]*\{)' /etc/caddy/Caddyfile /etc/caddy/Caddyfile.d/*.caddy 2>/dev/null; then
                    ENGINE_DEFAULT="On"
                else
                    ENGINE_DEFAULT="DetectionOnly"
                fi
            elif [[ -f "$CONFIG_PATH" ]] || [[ -f "/etc/caddy/coraza-include.caddy" ]]; then
                # Config var ama binary'de WAF/Coraza modülü yok — install yarıda kaldı
                MISSING_REASON="Coraza config örnekleri var ama Caddy binary'sinde WAF modülü yok. Çözüm: onx-modsec-install-driver driver=caddy (xcaddy build sırasında hata olmuştur — Go 1.21+ gerek)"
            else
                MISSING_REASON="Caddy Coraza WAF kurulu değil. Çözüm: onx-modsec-install-driver driver=caddy (xcaddy custom build gerek)"
            fi
        fi
        ;;

    *)
        DRIVER="unknown"
        SUPPORTED="false"
        MISSING_REASON="Driver tespit edilemedi: ${REQ_DRIVER} — desteklenen: apache|ols|nginx|caddy"
        ;;
esac

# Override log path (input'tan gelirse)
[[ -n "${LOG_OVERRIDE}" ]] && LOG_FILE="${LOG_OVERRIDE}"

# Auto-detect log: config'ten oku
auto_log_path() {
    case "${DRIVER}" in
        apache)
            grep -rh "^[[:space:]]*SecAuditLog[[:space:]]" /etc/httpd/conf.d/ /etc/httpd/modsecurity.d/ 2>/dev/null \
                | grep -v '^#' | awk '{print $2}' | head -1
            ;;
        nginx)
            grep -rh "audit_log[[:space:]]" /etc/nginx/modsec/ /etc/nginx/conf.d/ 2>/dev/null \
                | grep -v '^#' | head -1 | grep -oE '/[^[:space:]]+'
            ;;
        ols)
            echo "/usr/local/lsws/logs/modsec_audit.log"
            ;;
        caddy)
            # Coraza WAF SecAuditLog directive `coraza.conf` veya snippet içinde
            grep -rh "SecAuditLog[[:space:]]" /etc/caddy/ 2>/dev/null \
                | grep -v '^#' | awk '{print $2}' | head -1
            ;;
    esac
}
DETECTED_LOG=$(auto_log_path 2>/dev/null || true)
[[ -n "${DETECTED_LOG}" && -r "${DETECTED_LOG}" ]] && LOG_FILE="${DETECTED_LOG}"

LOG_SIZE=0
if [[ -n "${LOG_FILE}" && -f "${LOG_FILE}" ]]; then
    LOG_SIZE=$(stat -c '%s' "${LOG_FILE}" 2>/dev/null || echo 0)
fi

# ── CRS version detect ───────────────────────────────────────────────────────
# OWASP CRS: shared v3 path (Nginx/OLS) > Apache path > RPM
# Driver bazlı öncelik: Nginx/OLS shared v3'ü kullanır
case "${DRIVER}" in
    nginx|ols|caddy) CRS_PRIORITY_PATHS="/etc/onoxsoft/modsec-rules/owasp-crs-v3 /etc/httpd/modsecurity.d/owasp-crs /etc/nginx/modsec/owasp-crs /usr/local/lsws/conf/modsec/owasp-crs" ;;
    *)         CRS_PRIORITY_PATHS="/etc/httpd/modsecurity.d/owasp-crs /etc/onoxsoft/modsec-rules/owasp-crs-v3 /etc/nginx/modsec/owasp-crs /usr/local/lsws/conf/modsec/owasp-crs" ;;
esac
for crs_dir in $CRS_PRIORITY_PATHS; do
    for crs_file in "$crs_dir/CHANGES.md" "$crs_dir/CHANGES" "$crs_dir/VERSION"; do
        if [[ -r "$crs_file" ]]; then
            CRS_VERSION=$(head -10 "$crs_file" 2>/dev/null | grep -oE '[Vv]?[0-9]+\.[0-9]+\.[0-9]+' | head -1 | tr -d 'Vv' || echo "")
            [[ -n "${CRS_VERSION}" ]] && break 2
        fi
    done
done
[[ -z "${CRS_VERSION}" ]] && CRS_VERSION=$(rpm -q --queryformat '%{VERSION}' mod_security_crs 2>/dev/null || echo "unknown")

# ── Audit log parse (24h window, top IPs/rules) ──────────────────────────────
# v3.26 FORMAT DETECTION:
# OLS / Nginx libmodsecurity v3 → JSON Lines (her event tek satır, `{...}`)
# Apache mod_security2 → Concurrent (multi-section, `--BOUNDARY-A--` markers)
# İlk non-empty satıra bak: `{` ile başlıyor + valid JSON → modsec3
#                            `--<hex>-A--` ile match → modsec2
BLOCKS_24H=0
TOP_IPS_JSON="[]"
TOP_RULES_JSON="[]"
RECENT_JSON="[]"
LOG_FORMAT="unknown"

if [[ "${LOADED}" == "true" ]] && [[ -n "${LOG_FILE}" ]] && [[ -r "${LOG_FILE}" ]]; then
    SINCE=$(date -u -d "${WINDOW_H} hours ago" +%s 2>/dev/null || echo 0)

    # İlk non-empty satıra bak, format detect et
    # v3.29: `|| true` ŞART — log dosyası boş (0 byte, yeni oluşturulmuş) ise
    # grep no match → exit 1 → pipefail → command substitution exit 1 →
    # `set -e` script'i öldürür. Boş log normal durum (yeni Nginx/Caddy).
    first_line=$(grep -m1 -v '^[[:space:]]*$' "${LOG_FILE}" 2>/dev/null | head -c 4096 || true)

    if [[ "${first_line}" =~ ^\{.*\}[[:space:]]*$ ]] || [[ "${first_line}" =~ ^\{ ]]; then
        # JSON candidate — jq ile valid mi doğrula
        if echo "${first_line}" | jq -e 'type == "object"' >/dev/null 2>&1; then
            LOG_FORMAT="modsec3_json"
        fi
    fi
    # Native serial marker — TOLERANT (Apache modsec2 "--ID-A--" 2-tire VEYA
    # libmodsecurity v3 native "---ID---A--" 3-tire). v3.41: 3-tire eklendi, aksi
    # halde OLS/Nginx native serial "unknown" düşüp blocks_24h=0 oluyordu.
    if [[ "${LOG_FORMAT}" == "unknown" ]] && [[ "${first_line}" =~ ^-+[A-Za-z0-9]+-+A-+$ ]]; then
        LOG_FORMAT="modsec2_concurrent"
    fi

    case "${LOG_FORMAT}" in
        modsec3_json)
            # libmodsecurity v3 JSON Lines — her satır bir event.
            # transaction.time_stamp formatı: epoch (sayı) VEYA string "Sat May  3 ...".
            # Çoğu OLS deploy'unda epoch milisaniye veya string olabilir; defansif parse:
            #   (1) sayıysa epoch (saniye varsay), (2) string ise strptime fallback.
            # `.transaction.messages` doluysa = rule match (block veya alert).
            BLOCKS_24H=$(tail -n "${TAIL_LINES}" "${LOG_FILE}" 2>/dev/null \
                | jq -cs --argjson since "$SINCE" '
                    [
                      .[]
                      | select(type == "object")
                      | . as $ev
                      | ($ev.transaction.time_stamp // $ev.transaction.unique_id // 0) as $ts
                      | (
                          if ($ts | type) == "number" then $ts
                          elif ($ts | type) == "string" then
                            ($ts | try (strptime("%a %b %e %H:%M:%S %Y") | mktime) catch 0)
                          else 0 end
                        ) as $epoch
                      | select($epoch == 0 or $epoch >= $since)
                      | select(($ev.transaction.messages // []) | length > 0)
                    ] | length
                ' 2>/dev/null || echo 0)
            ;;
        modsec2_concurrent)
            # Apache mod_security2 concurrent format — A-bölümü her event'in başı.
            # Boundary regex daha gevşek: --<hex>-A-- (8+ char hex/alphanum).
            # NOT: 1 marker = 1 event (önceki /3 divisor yanlıştı — grep
            # zaten satır bazlı sayar, her -A-- ayrı event'in başı).
            BLOCKS_24H=$(tail -n "${TAIL_LINES}" "${LOG_FILE}" 2>/dev/null \
                | grep -cE '^-+[A-Za-z0-9]+-+A-+$' \
                || echo 0)
            ;;
        *)
            # Format tespit edilemedi (log boş ya da çakışmalı). 0 dön.
            BLOCKS_24H=0
            ;;
    esac
fi

# ── Output JSON ──────────────────────────────────────────────────────────────
json_ok "$(jq -n \
    --arg driver "${DRIVER}" \
    --arg active_driver "${ACTIVE_DRIVER}" \
    --argjson supported "${SUPPORTED}" \
    --argjson loaded "${LOADED}" \
    --arg version "${VERSION}" \
    --arg crs_version "${CRS_VERSION}" \
    --arg engine_default "${ENGINE_DEFAULT}" \
    --arg log_file "${LOG_FILE}" \
    --arg log_format "${LOG_FORMAT}" \
    --argjson log_size_bytes "${LOG_SIZE}" \
    --argjson blocks_24h "${BLOCKS_24H}" \
    --arg missing_reason "${MISSING_REASON}" \
    --argjson window_h "${WINDOW_H}" \
    '{driver:$driver, active_driver:$active_driver, supported:$supported,
      loaded:$loaded, version:$version, crs_version:$crs_version,
      engine_default:$engine_default, log_file:$log_file, log_format:$log_format,
      log_size_bytes:$log_size_bytes, blocks_24h:$blocks_24h,
      missing_reason:$missing_reason, window_h:$window_h,
      top_blocked_ips:[], top_rules:[], top_uris:[], top_categories:[],
      hourly:[], recent:[]}')"
