#!/usr/bin/env bash
set -euo pipefail

VERSION="0.2.6a"
CONFIRM_APPLY="APPLY_SENTINELOS_HARDENING_BASE"
CONFIRM_RESTORE="RESTORE_SENTINELOS_HARDENING_BASE"
POLICY="/usr/share/sentinelos/hardening/policy.json"
CONF="/etc/sysctl.d/99-sentinelos-hardening-base.conf"
STATE_DIR="/var/lib/sentinelos/hardening"
LOG_DIR="/var/log/sentinelos"

find_sysctl() {
  if command -v sysctl >/dev/null 2>&1; then
    command -v sysctl
    return 0
  fi
  if [ -x /usr/sbin/sysctl ]; then
    echo /usr/sbin/sysctl
    return 0
  fi
  if [ -x /sbin/sysctl ]; then
    echo /sbin/sysctl
    return 0
  fi
  return 1
}

usage() {
  cat <<EOF
sentinelos-hardening v$VERSION

Commands:
  --summary
  --audit
  --audit-json
  --status
  --verify
  --apply-safe-baseline --confirm $CONFIRM_APPLY
  --restore-safe-baseline --confirm $CONFIRM_RESTORE

Hotfix:
  PATH-safe sysctl lookup checks PATH, /usr/sbin/sysctl, then /sbin/sysctl.
EOF
}

need_root() {
  if [ "$(id -u)" != "0" ]; then
    echo "ERROR: this command requires root." >&2
    exit 2
  fi
}

summary() {
  local sc="missing"
  sc="$(find_sysctl 2>/dev/null || echo missing)"
  cat <<EOF
SentinelOS Hardening Base
version: $VERSION
policy: $POLICY
safe_baseline_file: $CONF
safe_baseline_applied: $([ -f "$CONF" ] && echo yes || echo no)
sysctl_resolved: $sc
apply_requires_confirmation: yes
restore_requires_confirmation: yes
aggressive_lockdown: no
mandatory_aegis_runtime: no
firewall_lockout: no
controls:
  sysctl_safe_baseline: available
  apparmor: audit-only
  firewall: audit-only
  ssh: audit-only
  suid_sgid: audit-only
  world_writable_paths: audit-only
EOF
}

audit_text() {
  echo "SentinelOS Hardening Audit"
  echo "version: $VERSION"
  echo "timestamp: $(date -Iseconds)"
  echo

  echo "[OS]"
  if [ -r /etc/os-release ]; then
    . /etc/os-release
    echo "id: ${ID:-unknown}"
    echo "version_codename: ${VERSION_CODENAME:-unknown}"
    echo "pretty_name: ${PRETTY_NAME:-unknown}"
  else
    echo "os-release: missing"
  fi
  echo

  echo "[Package integrity]"
  if command -v dpkg >/dev/null 2>&1; then
    echo "dpkg_audit:"
    dpkg --audit || true
  else
    echo "dpkg: missing"
  fi
  echo

  echo "[Sysctl resolver]"
  if sc="$(find_sysctl 2>/dev/null)"; then
    echo "sysctl: $sc"
  else
    echo "sysctl: missing"
  fi
  echo

  echo "[AppArmor]"
  if command -v aa-status >/dev/null 2>&1; then
    aa-status 2>/dev/null | sed -n '1,8p' || true
  elif systemctl list-unit-files apparmor.service >/dev/null 2>&1; then
    echo "apparmor_service_enabled: $(systemctl is-enabled apparmor.service 2>/dev/null || true)"
    echo "apparmor_service_active: $(systemctl is-active apparmor.service 2>/dev/null || true)"
  else
    echo "apparmor_status: not detected"
  fi
  echo

  echo "[Firewall]"
  if command -v ufw >/dev/null 2>&1; then
    ufw status verbose || true
  elif command -v nft >/dev/null 2>&1; then
    echo "nftables: available"
    systemctl is-active nftables.service 2>/dev/null | sed 's/^/nftables_service_active: /' || true
  else
    echo "firewall_tooling: not detected"
  fi
  echo

  echo "[SSH]"
  if [ -f /etc/ssh/sshd_config ]; then
    echo "sshd_config: present"
    grep -Ei '^\s*(PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|X11Forwarding)\s+' /etc/ssh/sshd_config || true
    if systemctl list-unit-files ssh.service >/dev/null 2>&1; then
      echo "ssh_service_active: $(systemctl is-active ssh.service 2>/dev/null || true)"
    fi
  else
    echo "sshd_config: not installed"
  fi
  echo

  echo "[Admin groups]"
  getent group sudo 2>/dev/null || true
  getent group admin 2>/dev/null || true
  getent group wheel 2>/dev/null || true
  echo

  echo "[Listening sockets]"
  if command -v ss >/dev/null 2>&1; then
    ss -tulpen 2>/dev/null | sed -n '1,20p' || true
  else
    echo "ss: missing"
  fi
  echo

  echo "[World-writable directories without sticky bit sample]"
  find /tmp /var/tmp /usr/local /opt -xdev -type d -perm -0002 ! -perm -1000 -print 2>/dev/null | sed -n '1,30p' || true
  echo

  echo "[SUID/SGID sample]"
  find /usr/bin /usr/sbin /bin /sbin -xdev \( -perm -4000 -o -perm -2000 \) -type f -print 2>/dev/null | sed -n '1,40p' || true
}

audit_json() {
  python3 - <<'PY'
import json, os, subprocess, datetime, shutil
def run(cmd):
    try:
        return subprocess.run(cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=10).stdout.strip()
    except Exception as e:
        return f"ERROR: {e}"
sysctl_path = shutil.which("sysctl")
if not sysctl_path:
    for p in ("/usr/sbin/sysctl", "/sbin/sysctl"):
        if os.path.exists(p) and os.access(p, os.X_OK):
            sysctl_path = p
            break
data = {
    "name": "SentinelOS Hardening Audit",
    "version": "0.2.6a",
    "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
    "os_release": {},
    "safe_baseline_file": "/etc/sysctl.d/99-sentinelos-hardening-base.conf",
    "safe_baseline_applied": os.path.exists("/etc/sysctl.d/99-sentinelos-hardening-base.conf"),
    "tools": {
        "dpkg": bool(shutil.which("dpkg")),
        "sysctl": bool(sysctl_path),
        "sysctl_path": sysctl_path,
        "aa-status": bool(shutil.which("aa-status")),
        "ufw": bool(shutil.which("ufw")),
        "nft": bool(shutil.which("nft")),
        "ss": bool(shutil.which("ss")),
    },
    "service_status": {
        "apparmor": run(["systemctl","is-active","apparmor.service"]) if shutil.which("systemctl") else "unknown",
        "ssh": run(["systemctl","is-active","ssh.service"]) if shutil.which("systemctl") else "unknown",
    }
}
try:
    for line in open("/etc/os-release", "r", encoding="utf-8"):
        if "=" in line:
            k,v=line.rstrip("\n").split("=",1)
            data["os_release"][k]=v.strip('"')
except FileNotFoundError:
    pass
print(json.dumps(data, indent=2, sort_keys=True))
PY
}

write_sysctl_conf() {
  cat > "$CONF" <<'EOF'
# SentinelOS Hardening Base v0.2.6a
# Package-owned safe baseline. Restore/remove via:
#   sudo sentinelos-hardening --restore-safe-baseline --confirm RESTORE_SENTINELOS_HARDENING_BASE

fs.protected_hardlinks = 1
fs.protected_symlinks = 1
fs.protected_regular = 2
fs.protected_fifos = 2

kernel.kptr_restrict = 1
kernel.dmesg_restrict = 1
kernel.yama.ptrace_scope = 1

net.ipv4.tcp_syncookies = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
EOF
}

apply_safe_baseline() {
  local confirm="${1:-}"
  need_root
  if [ "$confirm" != "$CONFIRM_APPLY" ]; then
    echo "ERROR: Missing confirmation. Use --confirm $CONFIRM_APPLY" >&2
    exit 2
  fi

  local sc
  sc="$(find_sysctl)" || { echo "ERROR: sysctl not found in PATH, /usr/sbin, or /sbin" >&2; exit 1; }

  mkdir -p "$STATE_DIR" "$LOG_DIR"
  if [ -f "$CONF" ]; then
    cp -a "$CONF" "$STATE_DIR/99-sentinelos-hardening-base.conf.$(date +%Y%m%d-%H%M%S).bak"
  fi

  write_sysctl_conf
  chmod 0644 "$CONF"

  "$sc" --system >/tmp/sentinelos-hardening-sysctl.log 2>&1 || {
    cat /tmp/sentinelos-hardening-sysctl.log
    echo "ERROR: sysctl reload failed" >&2
    exit 1
  }

  audit_text > "$LOG_DIR/hardening-audit-$(date +%Y%m%d-%H%M%S).txt" || true
  echo "applied: SentinelOS hardening safe baseline"
  echo "file: $CONF"
  echo "sysctl: $sc"
  echo "audit_log_dir: $LOG_DIR"
}

restore_safe_baseline() {
  local confirm="${1:-}"
  need_root
  if [ "$confirm" != "$CONFIRM_RESTORE" ]; then
    echo "ERROR: Missing confirmation. Use --confirm $CONFIRM_RESTORE" >&2
    exit 2
  fi

  local sc=""
  sc="$(find_sysctl 2>/dev/null || true)"

  mkdir -p "$STATE_DIR"
  if [ -f "$CONF" ]; then
    cp -a "$CONF" "$STATE_DIR/99-sentinelos-hardening-base.conf.removed.$(date +%Y%m%d-%H%M%S).bak"
    rm -f "$CONF"
  fi
  if [ -n "$sc" ]; then
    "$sc" --system >/tmp/sentinelos-hardening-restore-sysctl.log 2>&1 || true
  fi
  echo "restored: SentinelOS hardening safe baseline removed"
  [ -n "$sc" ] && echo "sysctl: $sc"
}

status() {
  echo "SentinelOS Hardening Status"
  echo "version: $VERSION"
  echo "safe_baseline_file: $CONF"
  echo "safe_baseline_applied: $([ -f "$CONF" ] && echo yes || echo no)"
  local sc=""
  sc="$(find_sysctl 2>/dev/null || true)"
  echo "sysctl_resolved: ${sc:-missing}"
  if [ -n "$sc" ]; then
    for key in fs.protected_hardlinks fs.protected_symlinks fs.protected_regular fs.protected_fifos kernel.kptr_restrict kernel.dmesg_restrict kernel.yama.ptrace_scope net.ipv4.tcp_syncookies; do
      "$sc" -n "$key" 2>/dev/null | sed "s/^/$key: /" || true
    done
  fi
}

verify() {
  local ok=1
  [ -f "$POLICY" ] || { echo "missing: $POLICY"; ok=0; }
  command -v sentinelos-hardening >/dev/null 2>&1 || { echo "missing command: sentinelos-hardening"; ok=0; }
  if sc="$(find_sysctl 2>/dev/null)"; then
    echo "sysctl: $sc"
  else
    echo "missing command: sysctl"
    ok=0
  fi
  if [ "$ok" = "1" ]; then
    echo "verify: ok"
  else
    echo "verify: failed"
    exit 1
  fi
}

case "${1:-}" in
  --summary) summary ;;
  --audit) audit_text ;;
  --audit-json) audit_json ;;
  --status) status ;;
  --verify) verify ;;
  --apply-safe-baseline)
    shift
    if [ "${1:-}" = "--confirm" ]; then
      apply_safe_baseline "${2:-}"
    else
      echo "ERROR: Missing confirmation. Use --confirm $CONFIRM_APPLY" >&2
      exit 2
    fi
    ;;
  --restore-safe-baseline)
    shift
    if [ "${1:-}" = "--confirm" ]; then
      restore_safe_baseline "${2:-}"
    else
      echo "ERROR: Missing confirmation. Use --confirm $CONFIRM_RESTORE" >&2
      exit 2
    fi
    ;;
  --help|-h|"") usage ;;
  *) usage; exit 2 ;;
esac
