#!/bin/bash
#
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at 
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.

. /usr/sbin/so-common

RULES_FILE="/opt/so/rules/suricata/all-rulesets.rules"
SOCKET="/var/run/suricata/suricata-command.socket"
SURICATASC="docker exec so-suricata /opt/suricata/bin/suricatasc"

# Format an epoch as a human-readable local timestamp for log messages.
fmt_time() { date -d "@$1" '+%Y-%m-%d %H:%M:%S %Z' 2>/dev/null; }

# Prefix each input line with the current timestamp.
timestamp_lines() { while IFS= read -r line; do printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S %Z')" "$line"; done; }

# Epoch of Suricata's last *completed* ruleset reload; non-zero return on failure.
suricata_reload_epoch() {
  local out ts
  out=$($SURICATASC -c ruleset-reload-time "$SOCKET" 2>/dev/null)
  ts=$(echo "$out" | jq -r '.message[0].last_reload // empty' 2>/dev/null)
  [ -n "$ts" ] || return 1
  date -d "$ts" +%s 2>/dev/null
}

# Trigger a fresh reload and confirm Suricata is running a ruleset at least as new
# as the rules file. Returns 0 only when both hold, so retry keeps going until an
# in-progress reload clears and our own reload completes.
reload_and_verify() {
  local out reload_epoch
  out=$($SURICATASC -c reload-rules "$SOCKET")
  echo "reload-rules: $out"

  if [[ "$out" =~ "Reload already in progress" ]]; then
    echo "A reload is already in progress; waiting for it to clear so a fresh reload can load the current ruleset."
    return 1
  fi
  if [[ ! "$out" =~ '{"message":"done","return":"OK"}' ]]; then
    echo "Suricata not ready or unexpected reload output; will retry."
    return 1
  fi

  reload_epoch=$(suricata_reload_epoch) || { echo "Could not read ruleset-reload-time; will retry."; return 1; }
  if [ "$reload_epoch" -ge "$target_mtime" ]; then
    echo "Loaded ruleset is current: last reload ($(fmt_time "$reload_epoch")) is newer than rules file ($(fmt_time "$target_mtime"))."
    return 0
  fi
  echo "Loaded ruleset is stale: last reload ($(fmt_time "$reload_epoch")) is older than rules file ($(fmt_time "$target_mtime")); retrying."
  return 1
}

# Run the reload/verify, timestamping every line of output (ours and the
# retry/fail helpers') so reload.log shows when each step ran. The pipeline is
# synchronous, so the log is fully flushed and ordered before we exit; the
# script's real exit code is preserved via PIPESTATUS.
{
  # Epoch mtime of the ruleset we need Suricata to have loaded. Captured once so
  # a file update mid-reload does not move the goalpost.
  target_mtime=$(stat -c %Y "$RULES_FILE") || fail "Could not stat the Suricata rules file: $RULES_FILE"
  retry 60 3 'reload_and_verify' || fail "Suricata did not load the current ruleset in time."
} 2>&1 | timestamp_lines
exit "${PIPESTATUS[0]}"
