#!/usr/bin/env bash
set -u
if [[ $# -ne 3 ]]; then echo "usage: bastionguard-webui-inotify-runner <user> <pathsfile> <log>" >&2; exit 64; fi
user="$1"; pathsfile="$2"; log="$3"
case "$user" in ""|*[!A-Za-z0-9_-]*) echo "invalid user" >&2; exit 64 ;; esac
[[ -r "$pathsfile" ]] || { echo "pathsfile not readable: $pathsfile" >&2; exit 66; }
alert_file=/var/lib/bastionguard-webui/alerts.tsv
runtime_dir=/run/bastionguard-webui/inotify-runtime
mkdir -p /var/lib/bastionguard-webui "$(dirname "$log")" "$runtime_dir" 2>/dev/null || true

ts_now(){ date -Is; }
append_alert() {
  local type="$1" family="$2" path="$3" source="$4" ts id bpath bfamily owner
  ts="$(date +%s)"; id="$(printf '%s|%s|%s|%s' "$ts" "$type" "$family" "$path" | sha256sum | awk '{print $1}')"
  bpath="$(printf '%s' "$path" | base64 -w0)"; bfamily="$(printf '%s' "$family" | base64 -w0)"
  printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$ts" "$id" "$type" "$bfamily" "$bpath" "$source" >> "$alert_file" 2>/dev/null || true
  owner="$(stat -c '%u:%g' /var/lib/bastionguard-webui 2>/dev/null || true)"
  [[ -n "$owner" ]] && chown "$owner" "$alert_file" 2>/dev/null || true
  chmod 0640 "$alert_file" 2>/dev/null || true
}
ransomware_scanner_bin() {
  local c
  for c in /usr/bin/BastionGuard-ransomware-scanner /usr/local/bin/BastionGuard-ransomware-scanner /bin/BastionGuard-ransomware-scanner; do
    [[ -x "$c" ]] && { printf '%s\n' "$c"; return 0; }
  done
  command -v BastionGuard-ransomware-scanner 2>/dev/null || true
}
is_build_artifact() {
  case "$1" in *.o|*.obj|*.a|*.lo|*.la|*.pyc|*.pyo|*.class|*.d|*.gch|*.pch|*.mod|*.swp|*.swo|*.rlib|*.rmeta|*.tmp|*.bc|*.ll) return 0 ;; esac
  return 1
}
is_noisy_dir() {
  case "$1" in */.git/*|*/.cache/*|*/.local/*|*/build/*|*/CMakeFiles/*|*/target/*|*/node_modules/*|*/dist/*|*/out/*|*/bin/*|*/obj/*|*/.gradle/*|*/.m2/*|*/.cargo/*|*/__pycache__/*|*/.pytest_cache/*|*/.next/*|*/.nuxt/*|*/.svelte-kit/*|*/.tox/*|*/.venv/*|*/venv/*|*/Debug/*|*/Release/*) return 0 ;; esac
  return 1
}
webui_trusted_script_path() {
  local p="${1:-}" base
  base="$(basename -- "$p" 2>/dev/null || true)"
  case "$p" in
    /usr/local/sbin/bastionguard-webui-*|/usr/local/libexec/bastionguard-webui-*|/usr/share/bastionguard-webui/scripts/*|/srv/http/webui/scripts/*|/srv/www/webui/scripts/*|/var/www/*/webui/scripts/*|*/webui/scripts/bastionguard-webui-*|*/webui/scripts/install-webui-helpers.sh|*/webui/scripts/install-user-service-helper.sh|*/webui/scripts/vendor-cantarell-font.sh)
      case "$base" in bastionguard-webui-*|install-webui-helpers.sh|install-user-service-helper.sh|vendor-cantarell-font.sh) return 0 ;; esac
      ;;
  esac
  return 1
}
recent_key() { printf '%s' "$1|$(stat -c '%s:%Y' "$1" 2>/dev/null || echo 0:0)" | sha256sum | awk '{print $1}'; }
recent_seen() {
  local f="$1" key stamp now
  key="$(recent_key "$f")"; stamp="$runtime_dir/$key"; now="$(date +%s)"
  find "$runtime_dir" -type f -mmin +5 -delete 2>/dev/null || true
  [[ -f "$stamp" ]] && return 0
  : > "$stamp" 2>/dev/null || true
  return 1
}
cloud_key() {
  local home conf
  home="$(getent passwd "$user" | cut -d: -f6)"; conf="$home/.config/BastionGuard/cloud.conf"
  [[ -r "$conf" ]] || return 0
  awk -F= '/^[[:space:]]*malware_bazaar_api_key[[:space:]]*=/{gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2; exit}' "$conf" 2>/dev/null || true
}
cloud_check_virustotal() {
  local f="$1" key sha out malicious suspicious
  command -v curl >/dev/null 2>&1 || return 0
  command -v sha256sum >/dev/null 2>&1 || return 0
  key="$(cloud_key)"
  [[ -n "$key" ]] || return 0
  sha="$(sha256sum -- "$f" 2>/dev/null | awk '{print $1}')"
  [[ -n "$sha" ]] || return 0
  out="$(curl -fsS --max-time 12 -H "x-apikey: $key" "https://www.virustotal.com/api/v3/files/$sha" 2>/dev/null || true)"
  [[ -n "$out" ]] || return 0
  malicious="$(printf '%s' "$out" | grep -o '"malicious"[[:space:]]*:[[:space:]]*[0-9]\+' | head -1 | grep -o '[0-9]\+$' || true)"
  suspicious="$(printf '%s' "$out" | grep -o '"suspicious"[[:space:]]*:[[:space:]]*[0-9]\+' | head -1 | grep -o '[0-9]\+$' || true)"
  malicious="${malicious:-0}"; suspicious="${suspicious:-0}"
  if [[ "$malicious" =~ ^[0-9]+$ && "$suspicious" =~ ^[0-9]+$ ]] && (( malicious > 0 || suspicious > 0 )); then
    echo "[$(ts_now)] CLOUD VirusTotal match malicious=$malicious suspicious=$suspicious $f" >> "$log"
    append_alert malware "VirusTotal.Match" "$f" inotify-cloud
  fi
}
import_native_ransomware_realtime_events() {
  local native=/var/log/BastionGuard/antiransom_inotify.log state line key payload path fam rest
  [[ -r "$native" ]] || return 0
  state="$runtime_dir/native-ransomware.seen"
  touch "$state" 2>/dev/null || true
  tail -n 300 "$native" 2>/dev/null | while IFS= read -r line; do
    [[ -n "$line" ]] || continue
    path=""; fam=""
    case "$line" in
      *"Alert inviato:"*" | "*)
        payload="${line#*Alert inviato: }"
        path="${payload%% | *}"
        fam="${payload##* | }"
        ;;
      *"ARCHIVE THREAT SIGNAL:"*)
        rest="${line#*ARCHIVE THREAT SIGNAL: }"
        path="${rest%% risk=*}"
        fam="BastionGuard.ARCHIVE_THREAT"
        ;;
      *)
        continue
        ;;
    esac
    [[ -n "$path" ]] || continue
    key="$(printf '%s' "$line" | sha256sum | awk '{print $1}')"
    grep -qx "$key" "$state" 2>/dev/null && continue
    printf '%s
' "$key" >> "$state" 2>/dev/null || true
    append_alert ransomware "${fam:-BastionGuard.RansomwareRealtime}" "$path" ransomware-realtime
    echo "[$(ts_now)] imported BastionGuard-ransomware-realtime alert ${fam:-BastionGuard.RansomwareRealtime} $path" >> "$log"
  done
  # Keep cache small.
  tail -n 1000 "$state" > "$state.tmp" 2>/dev/null && mv "$state.tmp" "$state" 2>/dev/null || true
}
scan_ransomware_file() {
  local f="$1" scanner out rc fam
  scanner="$(ransomware_scanner_bin)"
  [[ -n "$scanner" ]] || return 0
  out="$($scanner --scan "$f" 2>&1)"; rc=$?
  printf '%s\n' "$out" >> "$log"
  if printf '%s\n' "$out" | grep -Eiq '(Ransomware rilevato|Ransomware detected|\[YARA\]|YARA_SAMBA|YARA_FILE_MATCH|RILEVATO|MATCH:)'; then
    if ! printf '%s\n' "$out" | grep -Eiq '(Nessuna|No rule|No YARA|nessuna regola|No match)'; then
      fam="$(printf '%s\n' "$out" | sed -n 's/.*\(YARA[^ ]*\).*/\1/p' | head -1)"
      append_alert ransomware "${fam:-BastionGuard.YARA.Ransomware}" "$f" inotify
    fi
  fi
  return 0
}
scan_file() {
  local f="$1" event="${2:-EVENT}" out rc fam atype size
  [[ -f "$f" ]] || return 0
  case "$f" in *.part|*.crdownload) return 0 ;; esac
  is_noisy_dir "$f" && return 0
  is_build_artifact "$f" && return 0
  if webui_trusted_script_path "$f"; then
    echo "[$(ts_now)] skip trusted BastionGuard WebUI helper script $f" >> "$log"
    return 0
  fi

  # Source ScanPage.cpp waits for close/move/attrib and then de-duplicates the same file.
  recent_seen "$f" && return 0
  echo "[$(ts_now)] $event scan $f" >> "$log"

  # Run ransomware checks before ClamAV/cloud so ransomware events are imported promptly.
  scan_ransomware_file "$f" || true
  import_native_ransomware_realtime_events || true

  size="$(stat -c '%s' "$f" 2>/dev/null || echo 0)"
  if [[ "$size" =~ ^[0-9]+$ ]] && (( size < 64 )); then
    echo "[$(ts_now)] skip ClamAV/cloud tiny file size=$size $f" >> "$log"
    return 0
  fi

  if command -v clamdscan >/dev/null 2>&1; then out="$(clamdscan --fdpass --no-summary --infected "$f" 2>&1)"; rc=$?; elif command -v clamscan >/dev/null 2>&1; then out="$(clamscan --infected "$f" 2>&1)"; rc=$?; else echo "[$(ts_now)] WARN clamdscan/clamscan not found; ransomware checks still run" >> "$log"; out=""; rc=0; fi
  [[ -n "$out" ]] && printf '%s\n' "$out" >> "$log"
  if printf '%s\n' "$out" | grep -q ' FOUND'; then
    fam="$(printf '%s\n' "$out" | sed -n 's/^.*: \(.*\) FOUND.*/\1/p' | head -1)"
    atype="malware"; [[ "${fam,,}" == *ransom* || "${f,,}" == *ransom* ]] && atype="ransomware"
    append_alert "$atype" "${fam:-Malware}" "$f" inotify
  elif [[ "$rc" == "0" ]]; then
    cloud_check_virustotal "$f" || true
  fi
  return 0
}
scan_directory_once() {
  local d="$1"
  [[ -d "$d" ]] || return 0
  echo "[$(ts_now)] new directory detected; scanning regular files under $d" >> "$log"
  find "$d" -type f -maxdepth 3 -print0 2>/dev/null | while IFS= read -r -d '' f; do scan_file "$f" "DIR"; done
}
export -f ts_now append_alert ransomware_scanner_bin is_build_artifact is_noisy_dir webui_trusted_script_path recent_key recent_seen cloud_key cloud_check_virustotal import_native_ransomware_realtime_events scan_ransomware_file scan_file scan_directory_once
while true; do
  import_native_ransomware_realtime_events || true
  # Restart periodically so newly-created subdirectories are picked up by the recursive watch set.
  timeout 30s inotifywait -m -r -e create,moved_to,close_write,attrib --format '%e|%w%f' --fromfile "$pathsfile" 2>>"$log" | while IFS='|' read -r event file; do
    [[ -n "$file" ]] || continue
    if [[ "$event" == *ISDIR* ]]; then
      case "$event" in *CREATE*|*MOVED_TO*) scan_directory_once "$file" ;; esac
      continue
    fi
    case "$event" in *CLOSE_WRITE*|*MOVED_TO*|*ATTRIB*) scan_file "$file" "$event" ;; esac
  done
  echo "[$(ts_now)] inotifywait refreshed" >> "$log"
  sleep 1
done