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

# CentOS SELinux Troubleshooting Helper
#
# Purpose:
#   Collect common evidence used to diagnose SELinux enforcement issues:
#   - current SELinux mode
#   - recent SELinux denials from audit logs or journal
#   - file and process SELinux contexts
#   - relevant service status and listening ports
#
# Safety:
#   - Read-only by default
#   - No policy changes
#   - No relabeling
#   - No service restarts
#   - No destructive actions
#
# Usage:
#   ./centos-selinux-troubleshoot.sh [service_name] [target_path]
#
# Examples:
#   ./centos-selinux-troubleshoot.sh httpd /srv/app/content
#   ./centos-selinux-troubleshoot.sh mysqld /var/lib/mysql

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

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

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

require_readable_path() {
  local path="$1"
  if [[ -z "$path" ]]; then
    return 0
  fi
  if [[ ! -e "$path" ]]; then
    printf 'Warning: target path does not exist: %s\n' "$path" >&2
    return 1
  fi
  return 0
}

print_section "SELinux state"
if have_cmd getenforce; then
  getenforce
else
  echo "getenforce not available"
fi

if have_cmd sestatus; then
  sestatus || true
fi

print_section "Kernel and OS context"
uname -a || true
if [[ -r /etc/centos-release ]]; then
  cat /etc/centos-release
elif [[ -r /etc/redhat-release ]]; then
  cat /etc/redhat-release
fi

if [[ -n "$SERVICE_NAME" ]]; then
  print_section "Service status: $SERVICE_NAME"
  if have_cmd systemctl; then
    systemctl status "$SERVICE_NAME" --no-pager || true
    systemctl show "$SERVICE_NAME" -p MainPID -p FragmentPath -p User -p Group -p ExecStart || true
  else
    echo "systemctl not available"
  fi
fi

if [[ -n "$TARGET_PATH" ]]; then
  print_section "Target path metadata: $TARGET_PATH"
  require_readable_path "$TARGET_PATH" || true
  ls -ldZ "$TARGET_PATH" 2>/dev/null || ls -ld "$TARGET_PATH" 2>/dev/null || true
  if [[ -d "$TARGET_PATH" ]]; then
    find "$TARGET_PATH" -maxdepth 2 -printf '%M %u %g %p\n' 2>/dev/null | head -n 50 || true
    if have_cmd ls; then
      ls -laZ "$TARGET_PATH" 2>/dev/null || ls -la "$TARGET_PATH" 2>/dev/null || true
    fi
  fi
fi

print_section "Recent SELinux denials"
if have_cmd ausearch; then
  # Last 24 hours, if audit logs are available
  ausearch -m avc -m user_avc -ts recent 2>/dev/null | tail -n 80 || true
elif have_cmd journalctl; then
  journalctl -t setroubleshoot --since '24 hours ago' 2>/dev/null | tail -n 80 || true
  journalctl --since '24 hours ago' 2>/dev/null | grep -Ei 'avc:|selinux|denied' | tail -n 80 || true
else
  echo "Neither ausearch nor journalctl is available"
fi

print_section "SELinux-related files and labels"
if have_cmd matchpathcon; then
  if [[ -n "$TARGET_PATH" && -e "$TARGET_PATH" ]]; then
    matchpathcon "$TARGET_PATH" || true
  fi
fi

if have_cmd ps; then
  echo "Top processes with SELinux contexts (if supported):"
  ps -eZ 2>/dev/null | head -n 20 || true
fi

print_section "Listening services and ports"
if have_cmd ss; then
  ss -tulpn || true
elif have_cmd netstat; then
  netstat -tulpn || true
else
  echo "ss/netstat not available"
fi

print_section "Suggested next checks"
cat <<'EOF'
1. Compare the denied source and target contexts with the service's expected domain.
2. If the target path has the wrong label, verify whether it should be relabeled or restored to a default context.
3. If the denial involves a custom path or port, determine whether a narrow policy adjustment is justified.
4. Re-test the service in enforcing mode after any remediation.
5. Avoid disabling SELinux permanently unless you have documented a stronger compensating control.
EOF

exit 0