#!/usr/bin/env bash
# Sensor Linux (defensif, hanya-baca). Mengirim metrik, log autentikasi gagal, dan hash berkas penting ke mesin deteksi.
# Konfigurasi lewat environment: ENGINE_URL, SENSOR_CODE, SENSOR_KEY, WATCH_FILES (dipisah spasi), AUTH_LOG, SERVICES
# Pemakaian: ENGINE_URL=http://127.0.0.1:8000 SENSOR_CODE=sim-linux-01 SENSOR_KEY=... ./collect_linux.sh [--once|--loop 60]
set -euo pipefail

ENGINE_URL="${ENGINE_URL:-http://127.0.0.1:8000}"
: "${SENSOR_CODE:?SENSOR_CODE wajib diisi}"
: "${SENSOR_KEY:?SENSOR_KEY wajib diisi}"
WATCH_FILES="${WATCH_FILES:-/etc/passwd /etc/hosts /etc/ssh/sshd_config}"
AUTH_LOG="${AUTH_LOG:-/var/log/auth.log}"
SERVICES="${SERVICES:-ssh nginx apache2 mysql mariadb}"
STATE_DIR="${STATE_DIR:-${TMPDIR:-/tmp}/csm-sensor-$SENSOR_CODE}"
mkdir -p "$STATE_DIR"

json_escape() { # escape minimal untuk string JSON
  local s=${1//\\/\\\\}; s=${s//\"/\\\"}; s=${s//$'\n'/ }; s=${s//$'\r'/ }; s=${s//$'\t'/ }
  printf '%s' "${s:0:500}"
}

cpu_percent() {
  read -r _ u1 n1 s1 i1 w1 q1 sq1 _ < /proc/stat; sleep 1
  read -r _ u2 n2 s2 i2 w2 q2 sq2 _ < /proc/stat
  local idle=$(( (i2 + w2) - (i1 + w1) ))
  local total=$(( (u2+n2+s2+i2+w2+q2+sq2) - (u1+n1+s1+i1+w1+q1+sq1) ))
  (( total > 0 )) && echo $(( 100 * (total - idle) / total )) || echo 0
}
mem_percent() { awk '/MemTotal/{t=$2} /MemAvailable/{a=$2} END{ if(t>0) printf "%d", 100*(t-a)/t; else print 0 }' /proc/meminfo; }
disk_percent() { df -P / | awk 'NR==2{gsub("%","",$5); print $5}'; }

services_json() {
  local out="" sep=""
  for s in $SERVICES; do
    if command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files "$s.service" 2>/dev/null | grep -q "$s.service"; then
      local st; st=$(systemctl is-active "$s" 2>/dev/null || true)
      out+="$sep\"$(json_escape "$s")\":\"$(json_escape "$st")\""; sep=","
    fi
  done
  printf '{%s}' "$out"
}

files_json() {
  local out="" sep=""
  for f in $WATCH_FILES; do
    local h="null"
    if [[ -r "$f" ]]; then h="\"$(sha256sum "$f" | cut -d' ' -f1)\""; fi
    out+="$sep{\"path\":\"$(json_escape "$f")\",\"hash\":$h}"; sep=","
  done
  printf '[%s]' "$out"
}

auth_events_json() { # baris baru sejak run terakhir; hanya yang berisi kegagalan autentikasi
  [[ -r "$AUTH_LOG" ]] || { printf '[]'; return; }
  local pos_file="$STATE_DIR/auth.pos" size="$1" pos=0
  [[ -f "$pos_file" ]] && pos=$(cat "$pos_file")
  (( pos > size )) && pos=0
  local out="" sep="" n=0
  while IFS= read -r line && (( n < 100 )); do
    if [[ "$line" =~ [Ff]ailed\ password|authentication\ failure|[Ii]nvalid\ user ]]; then
      local ip=""; [[ "$line" =~ from\ ([0-9a-fA-F.:]+) ]] && ip="${BASH_REMATCH[1]}"
      local user=""
      if [[ "$line" =~ invalid\ user\ ([A-Za-z0-9_.-]+) ]]; then user="${BASH_REMATCH[1]}"
      elif [[ "$line" =~ for\ ([A-Za-z0-9_.-]+) ]]; then user="${BASH_REMATCH[1]}"; fi
      out+="$sep{\"type\":\"login\",\"success\":false,\"username\":\"$(json_escape "${user:-unknown}")\""
      [[ -n "$ip" ]] && out+=",\"ip\":\"$ip\""
      out+="}"; sep=","; n=$((n+1))
    fi
  done < <(tail -c +"$((pos+1))" "$AUTH_LOG" | head -c "$(( size - pos ))")
  printf '[%s]' "$out"
}

send_once() {
  local cpu mem disk auth auth_size=0
  [[ -r "$AUTH_LOG" ]] && auth_size=$(stat -c %s "$AUTH_LOG")
  cpu=$(cpu_percent); mem=$(mem_percent); disk=$(disk_percent); auth=$(auth_events_json "$auth_size")
  local events="{\"type\":\"metrics\",\"metrics\":{\"cpu\":$cpu,\"mem\":$mem,\"disk\":$disk},\"services\":$(services_json)}"
  events+=",{\"type\":\"file_integrity\",\"files\":$(files_json)}"
  [[ "$auth" != "[]" ]] && events+=",${auth:1:${#auth}-2}"
  local payload="{\"sensor_code\":\"$(json_escape "$SENSOR_CODE")\",\"hostname\":\"$(json_escape "$(hostname)")\",\"os\":\"linux\",\"events\":[$events]}"
  curl -fsS --max-time 15 -X POST "$ENGINE_URL/api/v1/ingest" \
       -H "Content-Type: application/json" -H "X-Sensor-Key: $SENSOR_KEY" -d "$payload"
  echo
  # posisi log baru disimpan HANYA setelah pengiriman berhasil (set -e menghentikan fungsi jika curl gagal)
  (( auth_size > 0 )) && echo "$auth_size" > "$STATE_DIR/auth.pos"
  return 0
}

case "${1:---once}" in
  --once) send_once ;;
  --loop) interval="${2:-60}"; while true; do send_once || echo "gagal kirim, coba lagi" >&2; sleep "$interval"; done ;;
  *) echo "Pemakaian: $0 [--once | --loop DETIK]" >&2; exit 2 ;;
esac
