#!/bin/bash
# svclog — Hardened log monitor with anomaly detection
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 LOGMON_STATE="/run/se/state/logmon"
readonly LOG_DIR="/run/se/logs"
readonly CONF="/run/se/.nodeconf"
readonly ANOMALY_THRESHOLD="${ANOMALY_THRESHOLD:-50}"
readonly LOCK_FILE="/run/se/.svclog.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: svclog already running\n' >&2; exit 1; fi

t008_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
}

t008_log() {
    local m="${1:-}"
    logger -t "logmon-${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}/logmon.log"
}

t008_report() {
    local ev="${1:-}" sc="${2:-0}" det="${3:-}"
    local ts n p s
    ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
    n="$(openssl rand -hex 16)"
    p="${ts}:${n}:${NODE_ID}"
    if [[ -f "${VAULT_KEY_FILE}" ]]; then
        s="$(printf '%s' "${p}" | openssl dgst -sha256 -hmac "$(cat "${VAULT_KEY_FILE}")" | awk '{print $2}')"
    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' \
        -d "{\"node\":\"${NODE_ID}\",\"event\":\"${ev}\",\"score\":${sc},\"detail\":\"${det}\",\"sig\":\"${ts}:${n}:${s}\"}" \
        2>/dev/null || true
}

t008_score() {
    local line="${1:-}" sc=0
    if printf '%s' "${line}" | grep -qiE 'failed password|authentication failure|invalid user'; then sc=$((sc+20)); fi
    if printf '%s' "${line}" | grep -qiE 'sudo:.*command not allowed|unauthorized|permission denied'; then sc=$((sc+15)); fi
    if printf '%s' "${line}" | grep -qiE 'connection refused|timeout|no route'; then sc=$((sc+5)); fi
    if printf '%s' "${line}" | grep -qiE 'reverse mapping checking|could not resolve|spoofing'; then sc=$((sc+25)); fi
    if printf '%s' "${line}" | grep -qiE 'pam_unix.*session opened|Accepted publickey|Accepted password'; then sc=$((sc+10)); fi
    if printf '%s' "${line}" | grep -qiE 'kernel.*oom-kill|segfault|critical'; then sc=$((sc+30)); fi
    if printf '%s' "${line}" | grep -qiE 'canary|honeypot|tripwire'; then sc=$((sc+50)); fi
    printf '%s' "${sc}"
}

t008_analyze() {
    t008_loadconf
    local lf s t
    lf='/var/log/syslog'
    [[ ! -f "${lf}" ]] && lf='/var/log/messages'
    [[ ! -f "${lf}" ]] && return 0
    s="$(tail -n 100 "${lf}" 2>/dev/null | while IFS= read -r line; do
        t008_score "${line}"
    done | awk '{s+=$1}END{print s}')"
    t="$(cat "${LOGMON_STATE}.score" 2>/dev/null || printf '%s' '0')"
    s=$((s + t))
    printf '%s' "${s}" >"${LOGMON_STATE}.score"
    if [[ "${s}" -ge "${ANOMALY_THRESHOLD}" ]]; then
        t008_log "anomaly score=${s}"
        t008_report 'anomaly-detected' "${s}" 'syslog patterns'
        printf '%s' '0' >"${LOGMON_STATE}.score"
    fi
}

t008_watch() {
    t008_loadconf
    local lf
    lf='/var/log/syslog'
    [[ ! -f "${lf}" ]] && lf='/var/log/messages'
    [[ ! -f "${lf}" ]] && return 0
    tail -n0 -F "${lf}" 2>/dev/null | while IFS= read -r line; do
        local sc ts
        sc="$(t008_score "${line}")"
        if [[ "${sc}" -gt 0 ]]; then
            ts="$(cat "${LOGMON_STATE}.score" 2>/dev/null || printf '%s' '0')"
            ts=$((ts + sc))
            printf '%s' "${ts}" >"${LOGMON_STATE}.score"
            if [[ "${ts}" -ge "${ANOMALY_THRESHOLD}" ]]; then
                t008_log "anomaly score=${ts}: ${line}"
                t008_report 'anomaly-detected' "${ts}" "${line}"
                printf '%s' '0' >"${LOGMON_STATE}.score"
            fi
        fi
    done
}

t008_init() {
    mkdir -p "${LOG_DIR}" "$(dirname "${LOGMON_STATE}")"
    printf '%s' '0' >"${LOGMON_STATE}.score"
    t008_log 'logmon init'
}

t008_start() {
    (t008_watch) &
    printf '%s\n' "$!" >"${LOGMON_STATE}.pid"
    (
        while true; do
            t008_analyze
            sleep 60
        done
    ) &
    printf '%s\n' "$!" >>"${LOGMON_STATE}.pid"
    t008_log 'logmon started'
}

t008_stop() {
    if [[ -f "${LOGMON_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 <"${LOGMON_STATE}.pid"
        rm -f "${LOGMON_STATE}.pid"
    fi
    t008_log 'logmon stopped'
}

t008_status() {
    if [[ -f "${LOGMON_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 <"${LOGMON_STATE}.pid"
        if [[ "${a}" -gt 0 ]]; then
            printf '%s\n' 'running'
        else
            printf '%s\n' 'stopped'
        fi
    else
        printf '%s\n' 'stopped'
    fi
    cat "${LOGMON_STATE}.score" 2>/dev/null || printf '%s\n' '0'
}

t008_run() {
    t008_analyze
}

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

case "${1:-}" in
    init)   t008_init ;;
    start)  t008_start ;;
    stop)   t008_stop ;;
    status) t008_status ;;
    run)    t008_run ;;
    *)      printf 'usage: %s init|start|stop|status|run\n' "$0"; exit 1 ;;
esac
