#!/usr/bin/env bash

#########################################################################################
#### hetzner-firewall-auditor.sh — Audit Hetzner Cloud firewall rules                ####
#### Finds open ports, missing firewalls, overly permissive rules, and misconfigs    ####
#### Requires: bash 4+, curl, jq                                                    ####
####                                                                                 ####
#### Author: Phil Connor                                                             ####
#### Contact: contact@mylinux.work                                                   ####
#### License: MIT                                                                    ####
#### Version 1.01                                                                    ####
####                                                                                 ####
#### Usage:                                                                          ####
####   ./hetzner-firewall-auditor.sh --full                                          ####
####                                                                                 ####
#### See --help for all options.                                                     ####
#########################################################################################

set -euo pipefail

# ── Colors (pre-initialized) ─────────────────────────────────────────
RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET=""

setup_colors() {
    if [[ "${COLOR:-auto}" == "never" ]]; then
        return
    fi
    if [[ "${COLOR:-auto}" == "always" ]] || [[ -t 1 ]]; then
        RED='\033[0;31m'
        GREEN='\033[0;32m'
        YELLOW='\033[0;33m'
        BLUE='\033[0;34m'
        CYAN='\033[0;36m'
        BOLD='\033[1m'
        DIM='\033[2m'
        RESET='\033[0m'
    fi
}

# ── Logging ───────────────────────────────────────────────────────────
log()     { echo -e "${BLUE}[INFO]${RESET} $*"; }
warn()    { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
err()     { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${DIM}[DEBUG]${RESET} $*"; fi; }
die()     { err "$*"; exit 1; }

# ── Severity counters ────────────────────────────────────────────────
TOTAL_CRIT=0
TOTAL_WARN=0
TOTAL_INFO=0
TOTAL_OK=0

flag_crit() { ((TOTAL_CRIT++)) || true; }
flag_warn() { ((TOTAL_WARN++)) || true; }
flag_info() { ((TOTAL_INFO++)) || true; }
flag_ok()   { ((TOTAL_OK++)) || true; }

# ── Defaults ──────────────────────────────────────────────────────────
RUN_MODE=""
DANGEROUS_PORTS="${DANGEROUS_PORTS:-22,3389,3306,5432,1433,6379,27017,9200,8080,8443}"
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"

# ── Credentials ───────────────────────────────────────────────────────
HCLOUD_TOKEN="${HCLOUD_TOKEN:-}"

# ── State ─────────────────────────────────────────────────────────────
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
START_TIME=""

# ── API helpers ──────────────────────────────────────────────────────
hetzner_api() {
    local method="$1" endpoint="$2"
    shift 2
    local attempt=0 max_attempts=3

    while (( attempt < max_attempts )); do
        local http_code
        http_code=$(curl -s -o /tmp/hfa_resp.json -w "%{http_code}" \
            -X "$method" \
            -H "Authorization: Bearer ${HCLOUD_TOKEN}" \
            -H "Content-Type: application/json" \
            "https://api.hetzner.cloud/v1${endpoint}" "$@")

        verbose "API ${method} ${endpoint} → HTTP ${http_code}"

        if [[ "$http_code" == "429" ]]; then
            ((attempt++)) || true
            local wait=$(( attempt * 5 ))
            warn "Rate limited — retrying in ${wait}s (attempt ${attempt}/${max_attempts})"
            sleep "$wait"
            continue
        fi

        cat /tmp/hfa_resp.json
        return 0
    done

    err "API request failed after ${max_attempts} attempts: ${method} ${endpoint}"
    return 1
}

check_credentials() {
    [[ -z "$HCLOUD_TOKEN" ]] && die "HCLOUD_TOKEN not set"
}

check_deps() {
    command -v curl &>/dev/null || die "curl is required"
    command -v jq &>/dev/null || die "jq is required"
}

# ── Server helpers ───────────────────────────────────────────────────
get_all_servers() {
    local page=1 per_page=50 result="[]"
    while true; do
        local resp
        resp=$(hetzner_api GET "/servers?page=${page}&per_page=${per_page}")
        local page_data
        page_data=$(echo "$resp" | jq '.servers // []' 2>/dev/null)
        local count
        count=$(echo "$page_data" | jq 'length' 2>/dev/null || echo 0)
        [[ "$count" -eq 0 ]] && break
        result=$(echo "$result" "$page_data" | jq -s '.[0] + .[1]')
        local total
        total=$(echo "$resp" | jq '.meta.pagination.total_entries // 0' 2>/dev/null)
        (( page * per_page >= total )) && break
        ((page++)) || true
    done
    echo "$result"
}

# ── Firewall helpers ─────────────────────────────────────────────────
get_all_firewalls() {
    local page=1 per_page=50 result="[]"
    while true; do
        local resp
        resp=$(hetzner_api GET "/firewalls?page=${page}&per_page=${per_page}")
        local page_data
        page_data=$(echo "$resp" | jq '.firewalls // []' 2>/dev/null)
        local count
        count=$(echo "$page_data" | jq 'length' 2>/dev/null || echo 0)
        [[ "$count" -eq 0 ]] && break
        result=$(echo "$result" "$page_data" | jq -s '.[0] + .[1]')
        local total
        total=$(echo "$resp" | jq '.meta.pagination.total_entries // 0' 2>/dev/null)
        (( page * per_page >= total )) && break
        ((page++)) || true
    done
    echo "$result"
}

# ── Port-to-service mapping ─────────────────────────────────────────
port_to_service() {
    local port="$1"
    case "$port" in
        22)    echo "SSH" ;;
        80)    echo "HTTP" ;;
        443)   echo "HTTPS" ;;
        3306)  echo "MySQL" ;;
        5432)  echo "PostgreSQL" ;;
        1433)  echo "MSSQL" ;;
        3389)  echo "RDP" ;;
        6379)  echo "Redis" ;;
        27017) echo "MongoDB" ;;
        9200)  echo "Elasticsearch" ;;
        8080)  echo "HTTP-Alt" ;;
        8443)  echo "HTTPS-Alt" ;;
        53)    echo "DNS" ;;
        25)    echo "SMTP" ;;
        5900)  echo "VNC" ;;
        11211) echo "Memcached" ;;
        2379)  echo "etcd" ;;
        9090)  echo "Prometheus" ;;
        *)     echo "" ;;
    esac
}

# ── Check if port is in dangerous list ───────────────────────────────
is_dangerous_port() {
    local port="$1"
    local IFS=','
    for dp in $DANGEROUS_PORTS; do
        if [[ "$port" == "$dp" ]]; then
            return 0
        fi
    done
    return 1
}

# ══════════════════════════════════════════════════════════════════════
#                       OPEN PORTS AUDIT
# ══════════════════════════════════════════════════════════════════════
audit_open_ports() {
    log "Auditing firewall rules for dangerous open ports..."
    log "Dangerous ports: ${DANGEROUS_PORTS}"
    echo ""

    printf "  %-10s %-22s %-8s %-8s %-18s %-12s %s\n" \
        "FW_ID" "FW_NAME" "PORT" "PROTO" "SOURCE" "SERVICE" "SEVERITY"
    printf "  %s\n" "$(printf '%.0s─' {1..95})"

    local fw_json
    fw_json=$(get_all_firewalls)

    echo "$fw_json" | jq -c '.[]' 2>/dev/null | while IFS= read -r fw; do
        local fw_id fw_name
        fw_id=$(echo "$fw" | jq -r '.id' 2>/dev/null)
        fw_name=$(echo "$fw" | jq -r '.name // "unnamed"' 2>/dev/null)

        echo "$fw" | jq -c '.rules[]? // empty' 2>/dev/null | while IFS= read -r rule; do
            local direction protocol port_str
            direction=$(echo "$rule" | jq -r '.direction // "in"' 2>/dev/null)
            protocol=$(echo "$rule" | jq -r '.protocol // "tcp"' 2>/dev/null)
            port_str=$(echo "$rule" | jq -r '.port // ""' 2>/dev/null)

            [[ "$direction" != "in" ]] && continue

            local has_open="false"
            while IFS= read -r src; do
                if [[ "$src" == "0.0.0.0/0" || "$src" == "::/0" ]]; then
                    has_open="true"
                    break
                fi
            done < <(echo "$rule" | jq -r '.source_ips[]? // empty' 2>/dev/null)

            [[ "$has_open" != "true" ]] && continue

            if [[ -z "$port_str" || "$port_str" == "null" ]]; then
                local IFS=','
                for dp in $DANGEROUS_PORTS; do
                    local svc
                    svc=$(port_to_service "$dp")
                    printf "  %-10s %-22s %-8s %-8s %-18s %-12s %b%s%b\n" \
                        "$fw_id" "${fw_name:0:20}" "$dp" "$protocol" \
                        "0.0.0.0/0" "${svc:-unknown}" "$RED" "CRITICAL" "$RESET"
                    flag_crit
                done
                continue
            fi

            if [[ "$port_str" == *-* ]]; then
                local range_start range_end
                range_start="${port_str%-*}"
                range_end="${port_str#*-}"
                local IFS=','
                for dp in $DANGEROUS_PORTS; do
                    if [[ "$dp" -ge "$range_start" && "$dp" -le "$range_end" ]]; then
                        local svc
                        svc=$(port_to_service "$dp")
                        printf "  %-10s %-22s %-8s %-8s %-18s %-12s %b%s%b\n" \
                            "$fw_id" "${fw_name:0:20}" "$dp" "$protocol" \
                            "0.0.0.0/0" "${svc:-unknown}" "$RED" "CRITICAL" "$RESET"
                        flag_crit
                    fi
                done
                continue
            fi

            if is_dangerous_port "$port_str"; then
                local svc
                svc=$(port_to_service "$port_str")
                printf "  %-10s %-22s %-8s %-8s %-18s %-12s %b%s%b\n" \
                    "$fw_id" "${fw_name:0:20}" "$port_str" "$protocol" \
                    "0.0.0.0/0" "${svc:-unknown}" "$RED" "CRITICAL" "$RESET"
                flag_crit
            elif [[ "$port_str" == "80" || "$port_str" == "443" ]]; then
                local svc
                svc=$(port_to_service "$port_str")
                printf "  %-10s %-22s %-8s %-8s %-18s %-12s %b%s%b\n" \
                    "$fw_id" "${fw_name:0:20}" "$port_str" "$protocol" \
                    "0.0.0.0/0" "${svc:-$port_str}" "$CYAN" "INFO" "$RESET"
                flag_info
            fi
        done
    done

    echo ""
}

# ══════════════════════════════════════════════════════════════════════
#                       UNPROTECTED SERVERS
# ══════════════════════════════════════════════════════════════════════
audit_unprotected() {
    log "Checking for servers without firewalls..."
    echo ""

    printf "  %-10s %-22s %-16s %-10s %s\n" \
        "SRV_ID" "NAME" "IP" "STATUS" "FIREWALL"
    printf "  %s\n" "$(printf '%.0s─' {1..75})"

    local servers
    servers=$(get_all_servers)

    local fw_json
    fw_json=$(get_all_firewalls)

    local protected_ids
    protected_ids=$(echo "$fw_json" | jq -r \
        '[.[].applied_to[]? | select(.type == "server") | .server.id] | unique | .[]' 2>/dev/null || true)

    echo "$servers" | jq -c '.[]' 2>/dev/null | while IFS= read -r srv; do
        local sid sname ip status
        sid=$(echo "$srv" | jq -r '.id' 2>/dev/null)
        sname=$(echo "$srv" | jq -r '.name // "unknown"' 2>/dev/null)
        ip=$(echo "$srv" | jq -r '.public_net.ipv4.ip // "N/A"' 2>/dev/null)
        status=$(echo "$srv" | jq -r '.status // "unknown"' 2>/dev/null)

        local has_fw="false"
        if echo "$protected_ids" | grep -q "^${sid}$" 2>/dev/null; then
            has_fw="true"
        fi

        if [[ "$has_fw" == "false" ]]; then
            printf "  %-10s %-22s %-16s %-10s %b%s%b\n" \
                "$sid" "${sname:0:20}" "$ip" "$status" \
                "$RED" "NONE — UNPROTECTED" "$RESET"
            flag_crit
        else
            printf "  %-10s %-22s %-16s %-10s %b%s%b\n" \
                "$sid" "${sname:0:20}" "$ip" "$status" \
                "$GREEN" "✓ Protected" "$RESET"
            flag_ok
        fi
    done

    echo ""
}

# ══════════════════════════════════════════════════════════════════════
#                       PERMISSIVE RULES AUDIT
# ══════════════════════════════════════════════════════════════════════
audit_permissive() {
    log "Auditing overly permissive firewall rules..."
    echo ""

    printf "  %-10s %-22s %-10s %-8s %-18s %-14s %s\n" \
        "FW_ID" "FW_NAME" "PORTS" "PROTO" "SOURCE" "ISSUE" "SEVERITY"
    printf "  %s\n" "$(printf '%.0s─' {1..100})"

    local fw_json
    fw_json=$(get_all_firewalls)

    echo "$fw_json" | jq -c '.[]' 2>/dev/null | while IFS= read -r fw; do
        local fw_id fw_name
        fw_id=$(echo "$fw" | jq -r '.id' 2>/dev/null)
        fw_name=$(echo "$fw" | jq -r '.name // "unnamed"' 2>/dev/null)

        echo "$fw" | jq -c '.rules[]? // empty' 2>/dev/null | while IFS= read -r rule; do
            local direction protocol port_str
            direction=$(echo "$rule" | jq -r '.direction // "in"' 2>/dev/null)
            protocol=$(echo "$rule" | jq -r '.protocol // "tcp"' 2>/dev/null)
            port_str=$(echo "$rule" | jq -r '.port // ""' 2>/dev/null)

            [[ "$direction" != "in" ]] && continue

            local has_open="false"
            local source_display=""
            while IFS= read -r src; do
                if [[ "$src" == "0.0.0.0/0" || "$src" == "::/0" ]]; then
                    has_open="true"
                    source_display="$src"
                    break
                fi
            done < <(echo "$rule" | jq -r '.source_ips[]? // empty' 2>/dev/null)

            if [[ "$has_open" == "true" ]] && [[ -z "$port_str" || "$port_str" == "null" ]]; then
                printf "  %-10s %-22s %-10s %-8s %-18s %-14s %b%s%b\n" \
                    "$fw_id" "${fw_name:0:20}" "ALL" "$protocol" \
                    "$source_display" "all-ports" "$RED" "CRITICAL" "$RESET"
                flag_crit
                continue
            fi

            while IFS= read -r src; do
                if [[ -n "$src" && "$src" != "null" ]]; then
                    if [[ "$src" == *"/8" || "$src" == *"/16" ]]; then
                        printf "  %-10s %-22s %-10s %-8s %-18s %-14s %b%s%b\n" \
                            "$fw_id" "${fw_name:0:20}" "${port_str:-ALL}" "$protocol" \
                            "${src:0:16}" "wide-cidr" "$YELLOW" "WARN" "$RESET"
                        flag_warn
                    fi
                fi
            done < <(echo "$rule" | jq -r '.source_ips[]? // empty' 2>/dev/null)
        done
    done

    echo ""
}

# ══════════════════════════════════════════════════════════════════════
#                       UNUSED FIREWALLS
# ══════════════════════════════════════════════════════════════════════
audit_unused() {
    log "Checking for unused firewalls..."
    echo ""

    printf "  %-10s %-28s %-8s %s\n" \
        "FW_ID" "FW_NAME" "RULES" "STATUS"
    printf "  %s\n" "$(printf '%.0s─' {1..60})"

    local fw_json
    fw_json=$(get_all_firewalls)

    echo "$fw_json" | jq -c '.[]' 2>/dev/null | while IFS= read -r fw; do
        local fw_id fw_name rule_count applied_count
        fw_id=$(echo "$fw" | jq -r '.id' 2>/dev/null)
        fw_name=$(echo "$fw" | jq -r '.name // "unnamed"' 2>/dev/null)
        rule_count=$(echo "$fw" | jq '[.rules[]?] | length' 2>/dev/null || echo 0)
        applied_count=$(echo "$fw" | jq '[.applied_to[]?] | length' 2>/dev/null || echo 0)

        if [[ "$applied_count" -eq 0 ]]; then
            printf "  %-10s %-28s %-8s %b%s%b\n" \
                "$fw_id" "${fw_name:0:26}" "$rule_count" \
                "$YELLOW" "UNUSED" "$RESET"
            flag_warn
        else
            verbose "Firewall ${fw_id} (${fw_name}): applied to ${applied_count} resource(s)"
            flag_ok
        fi
    done

    echo ""
}

# ══════════════════════════════════════════════════════════════════════
#                       LIST ALL RULES
# ══════════════════════════════════════════════════════════════════════
list_rules() {
    log "Listing all firewall rules..."
    echo ""

    printf "  %-10s %-20s %-8s %-8s %-12s %-18s %s\n" \
        "FW_ID" "FW_NAME" "DIR" "PROTO" "PORTS" "SOURCE/DEST" "SERVICE"
    printf "  %s\n" "$(printf '%.0s─' {1..90})"

    local fw_json
    fw_json=$(get_all_firewalls)

    echo "$fw_json" | jq -c '.[]' 2>/dev/null | while IFS= read -r fw; do
        local fw_id fw_name
        fw_id=$(echo "$fw" | jq -r '.id' 2>/dev/null)
        fw_name=$(echo "$fw" | jq -r '.name // "unnamed"' 2>/dev/null)

        echo "$fw" | jq -c '.rules[]? // empty' 2>/dev/null | while IFS= read -r rule; do
            local direction protocol port_str
            direction=$(echo "$rule" | jq -r '.direction // "in"' 2>/dev/null)
            protocol=$(echo "$rule" | jq -r '.protocol // "tcp"' 2>/dev/null)
            port_str=$(echo "$rule" | jq -r '.port // "all"' 2>/dev/null)

            [[ "$port_str" == "null" ]] && port_str="all"

            local cidr_list
            if [[ "$direction" == "in" ]]; then
                cidr_list=$(echo "$rule" | jq -r '.source_ips[]? // empty' 2>/dev/null | head -1)
            else
                cidr_list=$(echo "$rule" | jq -r '.destination_ips[]? // empty' 2>/dev/null | head -1)
            fi
            [[ -z "$cidr_list" ]] && cidr_list="any"

            local svc=""
            if [[ "$port_str" =~ ^[0-9]+$ ]]; then
                svc=$(port_to_service "$port_str")
            fi

            local dir_color="$CYAN"
            [[ "$direction" == "out" ]] && dir_color="$YELLOW"

            printf "  %-10s %-20s %b%-8s%b %-8s %-12s %-18s %s\n" \
                "$fw_id" "${fw_name:0:18}" "$dir_color" "$direction" "$RESET" \
                "$protocol" "${port_str:0:10}" "${cidr_list:0:16}" "${svc}"
        done
    done

    echo ""
}

# ══════════════════════════════════════════════════════════════════════
#                       SUMMARY
# ══════════════════════════════════════════════════════════════════════
print_summary() {
    local elapsed
    elapsed=$(( $(date +%s) - START_TIME ))

    echo ""
    echo "  ══════════════════════════════════════════"
    echo "  Firewall Audit Summary"
    echo "  ══════════════════════════════════════════"
    printf "  %-20s %b%d%b\n" "CRITICAL:" "$RED" "$TOTAL_CRIT" "$RESET"
    printf "  %-20s %b%d%b\n" "WARN:" "$YELLOW" "$TOTAL_WARN" "$RESET"
    printf "  %-20s %b%d%b\n" "INFO:" "$CYAN" "$TOTAL_INFO" "$RESET"
    printf "  %-20s %b%d%b\n" "OK:" "$GREEN" "$TOTAL_OK" "$RESET"
    echo "  ──────────────────────────────────────────"
    printf "  Completed in %ds\n" "$elapsed"
    echo ""

    if [[ "$TOTAL_CRIT" -gt 0 ]]; then
        echo -e "  ${RED}${BOLD}Action required:${RESET} ${TOTAL_CRIT} critical finding(s)"
        echo ""
        echo "  Top recommendations:"
        echo "    • Assign firewalls to all unprotected servers"
        echo "    • Close 0.0.0.0/0 rules on SSH (22), RDP (3389), and database ports"
        echo "    • Replace all-port allow rules with specific port lists"
        echo "    • Remove unused firewalls to reduce configuration sprawl"
        echo ""
    elif [[ "$TOTAL_WARN" -gt 0 ]]; then
        echo -e "  ${YELLOW}Review recommended:${RESET} ${TOTAL_WARN} warning(s)"
        echo ""
        echo "  Suggestions:"
        echo "    • Review wide CIDR rules and narrow where possible"
        echo "    • Delete unused firewalls"
        echo "    • Restrict outbound where applicable"
        echo ""
    else
        echo -e "  ${GREEN}All checks passed${RESET}"
        echo ""
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       USAGE
# ══════════════════════════════════════════════════════════════════════
show_help() {
    cat <<EOF
${BOLD}${SCRIPT_NAME}${RESET} — Hetzner Cloud Firewall Auditor

Audit Hetzner Cloud firewall rules across all servers for risky
configurations via the REST API.

${BOLD}MODES${RESET}
  --full              Run all audits
  --open-ports        Find dangerous ports open to 0.0.0.0/0
  --unprotected       Find servers with no firewall assigned
  --permissive        Find overly broad allow rules
  --unused            Find firewalls not applied to any resource
  --rules             List all firewall rules

${BOLD}OPTIONS${RESET}
  --ports PORTS       Override dangerous ports (comma-separated)
  --verbose           Debug output
  --no-color          Disable colored output
  --help              Show this help message

${BOLD}ENVIRONMENT VARIABLES${RESET}
  HCLOUD_TOKEN         Hetzner Cloud API token (required)
  DANGEROUS_PORTS      Comma-separated ports to flag (default: 22,3389,...)
  VERBOSE              Enable verbose output (true/false)
  COLOR                Color mode: auto, always, never

${BOLD}EXAMPLES${RESET}
  # Full audit
  ${SCRIPT_NAME} --full

  # Check open ports only
  ${SCRIPT_NAME} --open-ports

  # Find unprotected servers
  ${SCRIPT_NAME} --unprotected

  # Custom dangerous ports
  ${SCRIPT_NAME} --open-ports --ports "22,3389,5432,6379"

  # List all firewall rules
  ${SCRIPT_NAME} --rules

${BOLD}EXIT CODES${RESET}
  0   All checks passed
  1   Warnings found (review recommended)
  2   Critical findings (action required)
EOF
}

# ══════════════════════════════════════════════════════════════════════
#                       PARSE ARGS
# ══════════════════════════════════════════════════════════════════════
parse_args() {
    local modes=()

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --full)
                modes=(open-ports unprotected permissive unused)
                shift ;;
            --open-ports)
                modes+=(open-ports); shift ;;
            --unprotected)
                modes+=(unprotected); shift ;;
            --permissive)
                modes+=(permissive); shift ;;
            --unused)
                modes+=(unused); shift ;;
            --rules)
                modes+=(rules); shift ;;
            --ports)
                DANGEROUS_PORTS="${2:?--ports requires a value}"; shift 2 ;;
            --verbose)
                VERBOSE="true"; shift ;;
            --no-color)
                COLOR="never"; shift ;;
            --help|-h)
                setup_colors; show_help; exit 0 ;;
            *)
                die "Unknown option: $1 (see --help)" ;;
        esac
    done

    if [[ ${#modes[@]} -eq 0 ]]; then
        err "No audit mode specified"
        echo "Run ${SCRIPT_NAME} --help for usage" >&2
        exit 1
    fi

    RUN_MODE="${modes[*]}"
}

# ══════════════════════════════════════════════════════════════════════
#                       MAIN
# ══════════════════════════════════════════════════════════════════════
main() {
    parse_args "$@"
    setup_colors
    check_deps
    check_credentials

    START_TIME=$(date +%s)

    echo ""
    echo -e "${BOLD}Hetzner Cloud Firewall Auditor${RESET}"
    echo -e "Mode:   ${RUN_MODE}"
    echo -e "Time:   $(date -u +%Y-%m-%dT%H:%M:%SZ)"
    echo ""

    for mode in $RUN_MODE; do
        case "$mode" in
            open-ports)   audit_open_ports ;;
            unprotected)  audit_unprotected ;;
            permissive)   audit_permissive ;;
            unused)       audit_unused ;;
            rules)        list_rules ;;
        esac
    done

    print_summary

    if [[ "$TOTAL_CRIT" -gt 0 ]]; then
        exit 2
    elif [[ "$TOTAL_WARN" -gt 0 ]]; then
        exit 1
    fi
    exit 0
}

main "$@"
