#!/bin/bash
# svcfile — Hardened file integrity monitor
set -euo pipefail
shopt -s inherit_errexit 2>/dev/null || true
IFS=$'\n\t'

readonly NODE_ID="${NODE_ID:-}"
readonly ENGINE_IP="${ENGINE_IP:-10.69.69.6}"
readonly VAULT_KEY_FILE="${VAULT_KEY_FILE:-/dev/shm/vault.key}"
readonly INT_STATE="/run/se/state/integrity"
readonly LOG_DIR="/run/se/logs"
readonly CONF="/run/se/.nodeconf"
readonly INT_TARGETS="${INT_TARGETS:-/etc/passwd /etc/shadow /etc/ssh /etc/nginx /etc/wireguard}"
readonly LOCK_FILE="/run/se/.svcfile.lock"

if [[ -z "${NODE_ID}" ]]; then printf 'ERR: NODE_ID not set\n' >&2; exit 1; fi

exec 300>"${LOCK_FILE}"
if ! flock -n 300; then printf 'ERR: svcfile already running\n' >&2; exit 1; fi

t007_loadconf() {
    if [[ -f "${CONF}" ]]; then
        local perms
        perms="$(stat -c '%a' "${CONF}" 2>/dev/null || printf '%s' '000')"
        [[ "${perms}" == "600" || "${perms}" == "400" ]] && source "${CONF}"
    fi
}

t007_log() {
    local m="${1:-}"
    logger -t "integ-${NODE_ID}" "${m}" 2>/dev/null || true
    mkdir -p "${LOG_DIR}"
    printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "${m}" >> "${LOG_DIR}/integrity.log"
}

t007_report() {
    local ev="${1:-}" fp="${2:-}"
    local ts n body sig_input s
    ts="$(date +%s)"
    n="$(openssl rand -hex 16)"
    body="{\"node\":\"${NODE_ID}\",\"event\":\"${ev}\",\"file\":\"${fp}\"}"
    sig_input="${ts}.${n}.POST./a/security/event.${body}"
    if [[ -f "${VAULT_KEY_FILE}" ]]; then
        s="$(printf '%s' "${sig_input}" | openssl dgst -sha256 -hmac "$(cat "${VAULT_KEY_FILE}")" -hex | awk '{print $NF}')"
    else
        s='none'
    fi
    curl -sf --connect-timeout 3 --max-time 10 \
        -X POST "http://${ENGINE_IP}:8301/a/security/event" \
        -H 'Content-Type: application/json' \
        -H "x-engine-ts: ${ts}" \
        -H "x-engine-nonce: ${n}" \
        -H "x-engine-sig: ${s}" \
        -H "x-engine-node: ${NODE_ID}" \
        -d "${body}" \
        2>/dev/null || true
}

t007_baseline() {
    t007_loadconf
    mkdir -p "$(dirname "${INT_STATE}")"
    local db
    db="${INT_STATE}.db"
    >"${db}"
    local t
    for t in ${INT_TARGETS}; do
        if [[ -d "${t}" ]]; then
            find "${t}" -type f -print0 2>/dev/null | while IFS= read -r -d '' f; do
                sha256sum "${f}" 2>/dev/null || true
            done >>"${db}"
        elif [[ -f "${t}" ]]; then
            sha256sum "${t}" 2>/dev/null >>"${db}" || true
        fi
    done
    chmod 600 "${db}"
    t007_log "baseline created"
}

t007_verify() {
    t007_loadconf
    local db m h f ch
    db="${INT_STATE}.db"
    m=0
    if [[ ! -f "${db}" ]]; then
        t007_baseline
        return 0
    fi
    while IFS='  ' read -r h f; do
        [[ -n "${h}" && -n "${f}" ]] || continue
        if [[ ! -f "${f}" ]]; then
            t007_log "missing: ${f}"
            t007_report 'file-missing' "${f}"
            m=1
            continue
        fi
        ch="$(sha256sum "${f}" | awk '{print $1}')"
        if [[ "${h}" != "${ch}" ]]; then
            t007_log "modified: ${f}"
            t007_report 'file-modified' "${f}"
            m=1
        fi
    done <"${db}"
    if [[ "${m}" -eq 0 ]]; then
        t007_log 'integrity ok'
    fi
}

t007_watch() {
    t007_loadconf
    # Use inotifywait with proper error handling
    if ! command -v inotifywait &>/dev/null; then
        t007_log 'inotifywait not available'
        return 1
    fi
    inotifywait -m -r -e modify,move,create,delete,attrib \
        --format '%w%f %e' ${INT_TARGETS} 2>/dev/null | while IFS= read -r line; do
        local ev filepath
        filepath="$(printf '%s' "${line}" | awk '{print $1}')"
        ev="$(printf '%s' "${line}" | awk '{print $2}')"
        t007_log "inotify: ${filepath} ${ev}"
        t007_report "file-event:${ev}" "${filepath}"
        t007_verify
    done
}

t007_init() {
    mkdir -p "${LOG_DIR}" "$(dirname "${INT_STATE}")"
    t007_baseline
    t007_log 'integrity init'
}

t007_start() {
    (t007_watch) &
    printf '%s\n' "$!" >"${INT_STATE}.pid"
    (
        while true; do
            t007_verify
            sleep 300
        done
    ) &
    printf '%s\n' "$!" >>"${INT_STATE}.pid"
    t007_log 'integrity monitor started'
}

t007_stop() {
    if [[ -f "${INT_STATE}.pid" ]]; then
        local p
        while IFS= read -r p; do
            if kill -0 "${p}" 2>/dev/null; then
                kill "${p}" 2>/dev/null || true
                wait "${p}" 2>/dev/null || true
            fi
        done <"${INT_STATE}.pid"
        rm -f "${INT_STATE}.pid"
    fi
    t007_log 'integrity monitor stopped'
}

t007_status() {
    if [[ -f "${INT_STATE}.pid" ]]; then
        local a=0 p
        while IFS= read -r p; do
            if kill -0 "${p}" 2>/dev/null; then
                a=$((a+1))
            fi
        done <"${INT_STATE}.pid"
        if [[ "${a}" -gt 0 ]]; then
            printf '%s\n' 'running'
        else
            printf '%s\n' 'stopped'
        fi
    else
        printf '%s\n' 'stopped'
    fi
    wc -l <"${INT_STATE}.db" 2>/dev/null || printf '%s\n' '0'
}

t007_run() {
    t007_verify
}

cleanup_file() { flock -u 300 2>/dev/null || true; }
trap cleanup_file EXIT

case "${1:-}" in
    init)   t007_init ;;
    start)  t007_start ;;
    stop)   t007_stop ;;
    status) t007_status ;;
    run)    t007_run ;;
    *)      printf 'usage: %s init|start|stop|status|run\n' "$0"; exit 1 ;;
esac
