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

# CentOS SELinux Troubleshooting Helper
#
# Purpose:
#   Collect the most useful first-pass evidence for SELinux denials and
#   validate common causes such as file labels, port labels, and booleans.
#
# Safe defaults:
#   - Read-only checks only
#   - No policy changes
#   - No relabels
#   - No service restarts
#
# Usage:
#   ./centos-selinux-troubleshoot.sh [service_name] [path_or_port]
#
# Examples:
#   ./centos-selinux-troubleshoot.sh httpd /var/www/html
#   ./centos-selinux-troubleshoot.sh nginx 8080
#   ./centos-selinux-troubleshoot.sh
#
# Notes:
#   - If a second argument is numeric, it is treated as a port.
#   - If a second argument is non-numeric, it is treated as a path.
#   - The script attempts to use common SELinux utilities when available.

SERVICE_NAME="${1:-}"
TARGET="${2:-}"

info() {
  printf '[INFO] %s\n' "$*"
}

warn() {
  printf '[WARN] %s\n' "$*" >&2
}

have() {
  command -v "$1" >/dev/null 2>&1
}

print_section() {
  printf '\n=== %s ===\n' "$1"
}

is_number() {
  [[ "${1:-}" =~ ^[0-9]+$ ]]
}

print_section "SELinux mode"
if have sestatus; then
  sestatus || true
else
  warn "sestatus not found; install policycoreutils to inspect SELinux mode."
fi

print_section "Recent AVC / SELinux denials"
if have ausearch; then
  # Recent denials are often the most useful first signal.
  if ! ausearch -m AVC,USER_AVC,SELINUX_ERR -ts recent 2>/dev/null; then
    info "No recent AVC records found or audit access is restricted."
  fi
else
  warn "ausearch not found; install audit to query AVC records."
  info "Fallback: try 'journalctl -t setroubleshoot --since "'"'1 hour ago'"'"'" if available."
fi

print_section "Quick context checks"
if [[ -n "$SERVICE_NAME" ]]; then
  info "Service hint: $SERVICE_NAME"
  if have systemctl; then
    systemctl status "$SERVICE_NAME" --no-pager -l || true
  fi
else
  info "No service name provided. Pass one as the first argument for a focused check."
fi

if [[ -n "$TARGET" ]]; then
  if is_number "$TARGET"; then
    print_section "Port label check"
    if have semanage; then
      info "Looking for SELinux port type for port $TARGET"
      semanage port -l 2>/dev/null | awk -v p="$TARGET" '
        $0 ~ ("\\b" p "\\b") { print }
      ' || true
      info "If the port is absent from the expected service type, map it to the correct port label rather than widening policy."
    else
      warn "semanage not found; install policycoreutils-python-utils or equivalent package for port inspection."
    fi
  else
    print_section "File context check"
    if have ls; then
      if [[ -e "$TARGET" ]]; then
        ls -lZ "$TARGET" || true
      else
        warn "Path does not exist: $TARGET"
      fi
    fi

    if have matchpathcon && [[ -e "$TARGET" ]]; then
      info "Expected context for the path (if policy has a default):"
      matchpathcon "$TARGET" || true
    fi

    if have restorecon && [[ -e "$TARGET" ]]; then
      info "Dry-run relabel check (no changes made):"
      restorecon -n -v "$TARGET" || true
    fi
  fi
else
  info "No path or port target provided. Pass a path or port as the second argument for a focused check."
fi

print_section "Useful boolean inventory"
if have getsebool; then
  # Show a compact list of enabled booleans that are commonly relevant.
  getsebool -a 2>/dev/null | awk '
    /--> on$/ { print }
  ' | sort || true
else
  warn "getsebool not found; install policycoreutils to inspect SELinux booleans."
fi

print_section "Suggested next steps"
cat <<'EOF'
1. Match the denial in the audit log to the service behavior.
2. Confirm whether the blocked access is expected.
3. Prefer the smallest safe correction:
   - fix file labels for content or data paths
   - map custom ports to the correct SELinux port type
   - enable a documented boolean when appropriate
   - write custom policy only for truly unsupported behavior
4. Re-test with SELinux still enforcing.
5. Keep a record of the root cause so the same deployment does not repeat it.
EOF

print_section "Reminder"
info "Do not disable SELinux as a troubleshooting shortcut unless you are temporarily isolating a test system and understand the risk."