#!/bin/bash
# Samihost fail2ban watchdog — auto-unban whitelisted IPs and keep ignoreip list in sync.
# Deployed to /usr/local/bin/samihost-fail2ban-watchdog.sh on 194.163.161.162
# Cron: every 30 min (0,30 * * * *)

set -euo pipefail

JAIL_LOCAL=/etc/fail2ban/jail.local
BACKUP=/etc/fail2ban/jail.local.watchdog.bak
EXPECTED_IGNOREIP="127.0.0.1/8 ::1 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 fc00::/7 fe80::/10 100.64.0.0/10 100.121.150.22 100.85.236.10 100.71.97.12 100.81.59.99 100.98.123.59 194.233.88.206 173.212.201.200 194.163.161.162"
LOG=/var/log/samihost-fail2ban-watchdog.log
TELEGRAM_LOG=/tmp/fail2ban-watchdog-last-action

ts() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
log() { echo "$(ts) $*" | tee -a "$LOG"; }

mkdir -p "$(dirname "$LOG")"
touch "$LOG"

# --- 1. Verify ignoreip line is intact and matches expected ---
CURRENT=$(grep '^ignoreip' "$JAIL_LOCAL" | sed 's/^ignoreip[[:space:]]*=[[:space:]]*//' || true)
EXPECTED_NORMALIZED=$(echo "$EXPECTED_IGNOREIP" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/ $//')
CURRENT_NORMALIZED=$(echo "$CURRENT" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/ $//')

if [ "$CURRENT_NORMALIZED" != "$EXPECTED_NORMALIZED" ]; then
  log "ALERT: ignoreip line drifted. Restoring."
  cp "$JAIL_LOCAL" "$BACKUP"
  sed -i "s|^ignoreip = .*|ignoreip = $EXPECTED_IGNOREIP|" "$JAIL_LOCAL"
  fail2ban-client reload
  echo "ignoreip restored at $(ts)" > "$TELEGRAM_LOG"
  log "ignoreip restored, fail2ban reloaded"
fi

# --- 2. Unban any currently-banned IPs that match our trusted set ---
BANNED=$(fail2ban-client status sshd 2>/dev/null | awk -F: '/Banned IP list/{print $2}' | tr ' ' '\n' | grep -v '^$' || true)
UNBANNED=0
for ip in $BANNED; do
  # Match against any trusted network
  is_trusted=0
  for net in 127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 100.64.0.0/10 ::1 fc00::/7 fe80::/10 100.121.150.22 100.85.236.10 100.71.97.12 100.81.59.99 100.98.123.59 194.233.88.206 173.212.201.200 194.163.161.162; do
    if [[ "$net" == *"/"* ]]; then
      # CIDR match (simple IPv4 only — IPv6 needs python or ipcalc, skip for now)
      base="${net%/*}"
      mask="${net#*/}"
      if [[ "$ip" == "$base"* ]] || python3 -c "import ipaddress,sys; sys.exit(0 if ipaddress.ip_address('$ip') in ipaddress.ip_network('$net', strict=False) else 1)" 2>/dev/null; then
        is_trusted=1
        break
      fi
    else
      if [ "$ip" = "$net" ]; then
        is_trusted=1
        break
      fi
    fi
  done
  if [ "$is_trusted" = "1" ]; then
    if fail2ban-client set sshd unbanip "$ip" >/dev/null 2>&1; then
      log "auto-unbanned trusted IP: $ip"
      UNBANNED=$((UNBANNED+1))
    fi
  fi
done

[ "$UNBANNED" -gt 0 ] && echo "auto-unbanned $UNBANNED trusted IPs at $(ts)" > "$TELEGRAM_LOG"

# --- 3. Cap the ban count — if more than 200 are banned, mass-unban stale ones ---
TOTAL_BANNED=$(fail2ban-client status sshd 2>/dev/null | awk '/Currently banned/{print $NF}' || echo 0)
if [ "$TOTAL_BANNED" -gt 200 ]; then
  log "ALERT: $TOTAL_BANNED IPs banned. Mass-unbanning all."
  for ip in $BANNED; do
    fail2ban-client set sshd unbanip "$ip" >/dev/null 2>&1 || true
  done
  echo "mass-unbanned $TOTAL_BANNED stale bans at $(ts)" > "$TELEGRAM_LOG"
fi

log "watchdog run complete (unbanned=$UNBANNED, total_banned=$TOTAL_BANNED)"
