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

# selinux-boolean-triage.sh
#
# Purpose:
#   Safely inspect and modify a SELinux boolean on Red Hat Enterprise Linux.
#   The script defaults to read-only checks and requires explicit flags to change
#   SELinux policy state. It is designed to support operational triage:
#   - confirm SELinux is active
#   - inspect a candidate boolean
#   - optionally set it temporarily
#   - optionally persist the change
#   - optionally roll back
#
# Safe defaults:
#   - No changes are made unless you pass --set or --rollback.
#   - Persistent changes require --persist.
#   - Boolean name and desired state must be provided explicitly for changes.
#
# Examples:
#   ./selinux-boolean-triage.sh --boolean httpd_can_network_connect
#   ./selinux-boolean-triage.sh --boolean httpd_can_network_connect --set on
#   ./selinux-boolean-triage.sh --boolean httpd_can_network_connect --set on --persist
#   ./selinux-boolean-triage.sh --boolean httpd_can_network_connect --rollback
#   ./selinux-boolean-triage.sh --list 'httpd|ftp|nis|rsync|samba'
#
# Notes:
#   - This script assumes getsebool/setsebool/getenforce/sestatus are available.
#   - It does not attempt to diagnose AVCs or modify labels, ports, or firewall rules.

usage() {
  cat <<'EOF'
Usage:
  selinux-boolean-triage.sh [options]

Options:
  -b, --boolean NAME      SELinux boolean name to inspect or change.
  -s, --set STATE         Set boolean state: on or off.
  -p, --persist           Persist the change with setsebool -P.
  -r, --rollback          Set the boolean to off. Use with --persist to make it permanent.
  -l, --list REGEX        List booleans matching a grep extended regular expression.
  -q, --quiet             Reduce output.
  -h, --help              Show this help message.

Examples:
  selinux-boolean-triage.sh --boolean httpd_can_network_connect
  selinux-boolean-triage.sh --boolean httpd_can_network_connect --set on
  selinux-boolean-triage.sh --boolean httpd_can_network_connect --set on --persist
  selinux-boolean-triage.sh --boolean httpd_can_network_connect --rollback
  selinux-boolean-triage.sh --list 'httpd|ftp|nis|rsync|samba'
EOF
}

log() {
  if [[ "${QUIET:-0}" -ne 1 ]]; then
    printf '%s\n' "$*"
  fi
}

err() {
  printf 'ERROR: %s\n' "$*" >&2
}

require_cmd() {
  command -v "$1" >/dev/null 2>&1 || {
    err "Required command not found: $1"
    exit 1
  }
}

show_selinux_status() {
  log "== SELinux status =="
  if command -v getenforce >/dev/null 2>&1; then
    log "getenforce: $(getenforce)"
  fi
  if command -v sestatus >/dev/null 2>&1; then
    log "sestatus:"
    sestatus | sed 's/^/  /'
  fi
}

list_booleans() {
  local regex="$1"
  log "== Matching booleans: ${regex} =="
  if ! getsebool -a 2>/dev/null | grep -E "$regex"; then
    log "No matching booleans found."
  fi
}

show_boolean() {
  local boolean_name="$1"
  log "== Current boolean state =="
  if ! getsebool "$boolean_name"; then
    err "Unable to query boolean: $boolean_name"
    exit 1
  fi
}

set_boolean() {
  local boolean_name="$1"
  local state="$2"
  local persistent_flag=""

  if [[ "$PERSIST" -eq 1 ]]; then
    persistent_flag='-P'
  fi

  log "== Applying change =="
  if [[ -n "$persistent_flag" ]]; then
    log "Command: setsebool -P $boolean_name $state"
    setsebool -P "$boolean_name" "$state"
  else
    log "Command: setsebool $boolean_name $state"
    setsebool "$boolean_name" "$state"
  fi
}

main() {
  local boolean_name=""
  local set_state=""
  local list_regex=""
  local rollback=0
  PERSIST=0
  QUIET=0

  while [[ $# -gt 0 ]]; do
    case "$1" in
      -b|--boolean)
        boolean_name="${2:-}"
        shift 2
        ;;
      -s|--set)
        set_state="${2:-}"
        shift 2
        ;;
      -p|--persist)
        PERSIST=1
        shift
        ;;
      -r|--rollback)
        rollback=1
        shift
        ;;
      -l|--list)
        list_regex="${2:-}"
        shift 2
        ;;
      -q|--quiet)
        QUIET=1
        shift
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      *)
        err "Unknown option: $1"
        usage
        exit 1
        ;;
    esac
  done

  require_cmd getsebool
  require_cmd setsebool
  require_cmd getenforce
  require_cmd sestatus

  show_selinux_status

  if [[ -n "$list_regex" ]]; then
    list_booleans "$list_regex"
  fi

  if [[ -n "$boolean_name" ]]; then
    show_boolean "$boolean_name"
  fi

  if [[ "$rollback" -eq 1 ]]; then
    if [[ -z "$boolean_name" ]]; then
      err "--rollback requires --boolean NAME"
      exit 1
    fi
    log "== Rollback requested =="
    set_boolean "$boolean_name" "off"
    log "== Post-change state =="
    show_boolean "$boolean_name"
  elif [[ -n "$set_state" ]]; then
    if [[ -z "$boolean_name" ]]; then
      err "--set requires --boolean NAME"
      exit 1
    fi
    case "$set_state" in
      on|off)
        ;;
      *)
        err "--set must be 'on' or 'off'"
        exit 1
        ;;
    esac
    set_boolean "$boolean_name" "$set_state"
    log "== Post-change state =="
    show_boolean "$boolean_name"
  else
    if [[ -n "$boolean_name" ]]; then
      log "No change requested. Use --set on|off or --rollback to modify the boolean."
      log "Tip: validate the real service path and review AVC logs before making persistent changes."
    fi
  fi

  if [[ "$PERSIST" -eq 1 ]] && [[ -z "$set_state" ]] && [[ "$rollback" -ne 1 ]]; then
    log "Note: --persist has no effect without --set or --rollback."
  fi

  log "Done."
}

main "$@"