#!/bin/bash
# svcwatch — Credential & Config Integrity Watchdog
# Monitors critical files for unauthorized changes
# Reports tamper alerts to 558201 mesh-api

STATE_DIR="/run/se/state"
LOG="/run/se/logs/watchdog.log"
HASH_FILE="${STATE_DIR}/file_hashes"
AGENT_TOKEN="${STATE_DIR}/agent_rotating"
NODE_ID_FILE="/run/se/.nodeconf"
ORIGIN_WG="10.69.69.2"
HMAC_KEY_FILE="/opt/apache/tools/.mesh_hmac"
WATCH_FILES="/etc/shadow /etc/ssh/sshd_config /home/administrator/.ssh/authorized_keys /home/admin/.ssh/authorized_keys"

mkdir -p "${STATE_DIR}" "$(dirname "${LOG}")"

# Determine node ID
if [[ -f "${NODE_ID_FILE}" ]]; then
  NODE_ID=$(grep NODE_ID "${NODE_ID_FILE}" | cut -d'"' -f2 2>/dev/null || hostname -s)
else
  NODE_ID=$(hostname -s)
fi

log_wd() {
  printf "%s [svcwatch] %s\n" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$1" >> "${LOG}"
  logger -t "svcwatch" "$1" 2>/dev/null || true
}

compute_hashes() {
  for f in ${WATCH_FILES}; do
    [[ -f "$f" ]] && sha256sum "$f" 2>/dev/null
  done
}

report_alert() {
  local event_type="$1" severity="$2" details="$3"
  local ts=$(date +%s)
  local body="{\"node_id\":\"${NODE_ID}\",\"event_type\":\"${event_type}\",\"severity\":\"${severity}\",\"details\":\"${details}\",\"event_code\":\"WD-001\"}"

  if [[ -f "${HMAC_KEY_FILE}" ]]; then
    local hmac_key=$(cat "${HMAC_KEY_FILE}")
    local sig=$(echo -n "${ts}:${body}" | openssl dgst -sha256 -hmac "${hmac_key}" 2>/dev/null | awk '{print $NF}')
    curl -sf -X POST "http://${ORIGIN_WG}/api/mesh-api.php?action=report-watchdog-alert" \
      -H "Content-Type: application/json" \
      -H "X-Mesh-Sig: ${sig}" \
      -H "X-Mesh-Ts: ${ts}" \
      -d "${body}" --connect-timeout 5 -o /dev/null 2>/dev/null || true
  else
    curl -sf -X POST "http://${ORIGIN_WG}/api/mesh-api.php?action=report-watchdog-alert" \
      -H "Content-Type: application/json" \
      -d "${body}" --connect-timeout 5 -o /dev/null 2>/dev/null || true
  fi
  log_wd "REPORTED: ${event_type} [${severity}] ${details}"
}

# Take initial baseline
if [[ ! -f "${HASH_FILE}" ]]; then
  compute_hashes > "${HASH_FILE}"
  log_wd "INIT: baseline hashes recorded for ${NODE_ID}"
fi

log_wd "START: svcwatch active for node ${NODE_ID} (PID $$)"

while true; do
  sleep 60

  # Skip if agent is performing authorized rotation
  if [[ -f "${AGENT_TOKEN}" ]]; then
    AGE=$(( $(date +%s) - $(stat -c %Y "${AGENT_TOKEN}" 2>/dev/null || echo 0) ))
    if [[ ${AGE} -lt 300 ]]; then
      log_wd "INFO: agent rotation in progress (${AGE}s old), skipping check"
      continue
    else
      rm -f "${AGENT_TOKEN}"
      log_wd "INFO: stale agent token removed (${AGE}s old)"
    fi
  fi

  # Compute current hashes
  CURRENT=$(compute_hashes)
  SAVED=$(cat "${HASH_FILE}" 2>/dev/null || echo "")

  if [[ "${CURRENT}" != "${SAVED}" ]]; then
    # Diff to find changed files
    CHANGED=""
    while IFS= read -r line; do
      [[ -z "$line" ]] && continue
      hash=$(echo "$line" | awk '{print $1}')
      file=$(echo "$line" | awk '{print $2}')
      old_hash=$(grep " ${file}$" "${HASH_FILE}" 2>/dev/null | awk '{print $1}')
      if [[ "${hash}" != "${old_hash}" ]]; then
        CHANGED="${CHANGED} ${file}"
      fi
    done <<< "${CURRENT}"

    if [[ -n "${CHANGED}" ]]; then
      log_wd "ALERT: UNAUTHORIZED CHANGE DETECTED:${CHANGED}"

      # Determine severity
      SEVERITY="warning"
      if echo "${CHANGED}" | grep -q "shadow"; then
        SEVERITY="critical"
        log_wd "CRITICAL: /etc/shadow modified without agent token — possible rescue mode intrusion"

        # AUTO-RESPONSE: Re-rotate all passwords
        chattr -i /etc/shadow 2>/dev/null || true
        NEW_ROOT=$(openssl rand -base64 24)
        NEW_ADMIN=$(openssl rand -base64 24)
        NEW_ADMINISTRATOR=$(openssl rand -base64 24)
        echo "root:${NEW_ROOT}" | chpasswd 2>/dev/null
        echo "admin:${NEW_ADMIN}" | chpasswd 2>/dev/null
        echo "administrator:${NEW_ADMINISTRATOR}" | chpasswd 2>/dev/null
        chattr +i /etc/shadow
        log_wd "RESPONSE: all passwords re-rotated after tampering"

        # Save emergency creds for svcbeat pickup
        echo "${NEW_ROOT}|${NEW_ADMIN}|${NEW_ADMINISTRATOR}" > "${STATE_DIR}/emergency_creds"
        chmod 600 "${STATE_DIR}/emergency_creds"

        report_alert "shadow_tamper" "critical" "Password file modified without agent authorization. All passwords auto-rotated."
      fi

      if echo "${CHANGED}" | grep -q "sshd_config"; then
        log_wd "CRITICAL: sshd_config tampered"
        # Ensure hardened settings persist
        grep -q "^PermitRootLogin no" /etc/ssh/sshd_config 2>/dev/null || echo "PermitRootLogin no" >> /etc/ssh/sshd_config
        grep -q "^PasswordAuthentication no" /etc/ssh/sshd_config 2>/dev/null || echo "PasswordAuthentication no" >> /etc/ssh/sshd_config
        sshd -t 2>/dev/null && systemctl restart sshd 2>/dev/null
        report_alert "sshd_tamper" "${SEVERITY}" "sshd_config tampered. Hardened settings re-applied."
      fi

      if echo "${CHANGED}" | grep -q "authorized_keys"; then
        log_wd "CRITICAL: authorized_keys tampered — possible key injection"
        report_alert "authkeys_tamper" "critical" "authorized_keys modified without agent authorization. Possible key injection."
      fi

      # Report generic if not already reported
      if ! echo "${CHANGED}" | grep -qE "shadow|sshd_config|authorized_keys"; then
        report_alert "file_tamper" "${SEVERITY}" "Watched files changed:${CHANGED}"
      fi
    fi

    # Update baseline
    compute_hashes > "${HASH_FILE}"
  fi

  # Verify immutable flag on shadow
  ATTR=$(lsattr /etc/shadow 2>/dev/null | awk '{print $1}' || echo "")
  if [[ -n "${ATTR}" ]] && [[ "${ATTR}" != *"i"* ]]; then
    log_wd "ALERT: /etc/shadow immutable flag removed — re-setting"
    chattr +i /etc/shadow 2>/dev/null
    report_alert "immutable_removed" "warning" "Immutable flag on /etc/shadow was removed. Re-applied."
  fi
done
