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

# selinux-boolean-helper.sh
#
# Purpose:
#   Inspect and safely change SELinux booleans on Red Hat Enterprise Linux.
#   The script supports temporary or persistent changes, validates SELinux mode,
#   and helps confirm the resulting state.
#
# Usage:
#   ./selinux-boolean-helper.sh --list [keyword]
#   ./selinux-boolean-helper.sh --show BOOLEAN
#   ./selinux-boolean-helper.sh --set BOOLEAN --value on|off [--persistent]
#   ./selinux-boolean-helper.sh --check BOOLEAN
#
# Examples:
#   ./selinux-boolean-helper.sh --list httpd
#   ./selinux-boolean-helper.sh --show httpd_can_network_connect
#   ./selinux-boolean-helper.sh --set httpd_can_network_connect --value on
#   ./selinux-boolean-helper.sh --set httpd_can_network_connect --value on --persistent
#   ./selinux-boolean-helper.sh --check httpd_can_network_connect
#
# Notes:
#   - Temporary changes use: setsebool BOOLEAN on|off
#   - Persistent changes use: setsebool -P BOOLEAN on|off
#   - This script does not restart services or change labels/ports.

usage() {
  cat <<'EOF'
Usage:
  selinux-boolean-helper.sh --list [keyword]
  selinux-boolean-helper.sh --show BOOLEAN
  selinux-boolean-helper.sh --set BOOLEAN --value on|off [--persistent]
  selinux-boolean-helper.sh --check BOOLEAN

Options:
  --list [keyword]    List SELinux booleans, optionally filtered by keyword
  --show BOOLEAN      Show the current value of a boolean
  --set BOOLEAN       Set a boolean value (requires --value on|off)
  --value VALUE       Boolean value to apply: on or off
  --persistent        Make the change persistent across reboots
  --check BOOLEAN     Verify a boolean exists and display its current state
  -h, --help          Show this help message
EOF
}

need_cmd() {
  local cmd="$1"
  if ! command -v "$cmd" >/dev/null 2>&1; then
    echo "Error: required command not found: $cmd" >&2
    exit 1
  fi
}

check_selinux_mode() {
  if command -v getenforce >/dev/null 2>&1; then
    local mode
    mode="$(getenforce 2>/dev/null || true)"
    echo "SELinux mode: ${mode:-unknown}"
    if [[ "$mode" == "Disabled" ]]; then
      echo "Warning: SELinux is disabled. Booleans will not be effective." >&2
    fi
  else
    echo "Warning: getenforce is not available; cannot confirm SELinux mode." >&2
  fi
}

list_booleans() {
  local keyword="${1:-}"
  if command -v semanage >/dev/null 2>&1; then
    if [[ -n "$keyword" ]]; then
      semanage boolean -l | grep -i -- "$keyword" || true
    else
      semanage boolean -l
    fi
  else
    if [[ -n "$keyword" ]]; then
      getsebool -a | grep -i -- "$keyword" || true
    else
      getsebool -a
    fi
  fi
}

show_boolean() {
  local boolean="$1"
  getsebool "$boolean"
}

set_boolean() {
  local boolean="$1"
  local value="$2"
  local persistent="$3"

  if [[ "$value" != "on" && "$value" != "off" ]]; then
    echo "Error: --value must be 'on' or 'off'" >&2
    exit 1
  fi

  if [[ "$persistent" == "true" ]]; then
    setsebool -P "$boolean" "$value"
  else
    setsebool "$boolean" "$value"
  fi
}

check_boolean_exists() {
  local boolean="$1"
  if ! getsebool "$boolean" >/dev/null 2>&1; then
    echo "Error: boolean not found: $boolean" >&2
    exit 1
  fi
}

main() {
  need_cmd getsebool
  need_cmd setsebool
  check_selinux_mode

  local action=""
  local boolean=""
  local value=""
  local persistent="false"
  local keyword=""

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --list)
        action="list"
        keyword="${2:-}"
        if [[ -n "$keyword" && "$keyword" != --* ]]; then
          shift 2
        else
          shift
        fi
        ;;
      --show)
        action="show"
        boolean="${2:-}"
        shift 2
        ;;
      --set)
        action="set"
        boolean="${2:-}"
        shift 2
        ;;
      --value)
        value="${2:-}"
        shift 2
        ;;
      --persistent)
        persistent="true"
        shift
        ;;
      --check)
        action="check"
        boolean="${2:-}"
        shift 2
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      *)
        echo "Error: unknown option: $1" >&2
        usage
        exit 1
        ;;
    esac
  done

  case "$action" in
    list)
      list_booleans "$keyword"
      ;;
    show)
      [[ -n "$boolean" ]] || { echo "Error: --show requires BOOLEAN" >&2; exit 1; }
      show_boolean "$boolean"
      ;;
    set)
      [[ -n "$boolean" ]] || { echo "Error: --set requires BOOLEAN" >&2; exit 1; }
      [[ -n "$value" ]] || { echo "Error: --set requires --value on|off" >&2; exit 1; }
      check_boolean_exists "$boolean"
      echo "Applying SELinux boolean change: $boolean -> $value (${persistent})"
      set_boolean "$boolean" "$value" "$persistent"
      echo "Current state:"
      getsebool "$boolean"
      echo
      echo "Tip: retest the affected service or workflow and review AVC logs if needed."
      ;;
    check)
      [[ -n "$boolean" ]] || { echo "Error: --check requires BOOLEAN" >&2; exit 1; }
      check_boolean_exists "$boolean"
      show_boolean "$boolean"
      ;;
    *)
      usage
      exit 1
      ;;
  esac
}

main "$@"