#!/bin/bash

################################################################################
# cPHulk Enablement Script with CSF Whitelist Import
################################################################################
# Purpose: Enable cPHulk brute force protection and import CSF allowed IPs
# Requirements: cPanel server with CSF installed
# Author: Server Toolkit
#
# IMPORTANT NOTES:
# - cPHulk operates SYSTEM-WIDE (not per-user or per-domain)
# - Protects all authentication services: cPanel, WHM, SSH, FTP, Email
# - This script intelligently discovers ALL CSF whitelist files including:
#   * Standard files (/etc/csf/csf.allow, cpanel.allow, etc.)
#   * Include directives (follows recursively)
#   * Hosting panel integrations (LiquidWeb, cPanel, InterWorx, etc.)
#   * Custom locations configured in csf.conf
# - Supports multiple IP formats: simple IPs, s=IP, d=IP, CIDR notation
################################################################################

# Get script directory (go up 2 levels: /modules/security -> /modules -> /root/server-toolkit)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
source "$SCRIPT_DIR/lib/common-functions.sh"
source "$SCRIPT_DIR/lib/system-detect.sh"

# Require root
if [ "$EUID" -ne 0 ]; then
    print_error "This script must be run as root"
    exit 1
fi

print_banner "cPHulk Enablement with CSF Whitelist Import"

# System detection happens automatically when sourcing system-detect.sh
# Just verify it completed
if [ -z "$SYS_CONTROL_PANEL" ]; then
    print_error "System detection failed"
    exit 1
fi

# Check if cPanel
if [ "$SYS_CONTROL_PANEL" != "cpanel" ]; then
    print_error "This script is for cPanel servers only"
    print_info "Detected control panel: ${SYS_CONTROL_PANEL:-none}"
    exit 1
fi

# Check if cPHulk exists
if [ ! -x "/usr/local/cpanel/bin/cphulk_pam_ctl" ]; then
    print_error "cPHulk not found. This may not be a standard cPanel installation."
    exit 1
fi

# Check if CSF is installed
if [ ! -x "/usr/sbin/csf" ]; then
    print_warning "CSF not found - will skip CSF whitelist import"
    CSF_AVAILABLE=false
else
    CSF_AVAILABLE=true
fi

print_section "Current cPHulk Status"

# Get current status
CPHULK_STATUS=$(/usr/local/cpanel/bin/cphulk_pam_ctl --status 2>/dev/null)
if echo "$CPHULK_STATUS" | grep -qi "enabled"; then
    print_success "cPHulk is currently ENABLED"
    ALREADY_ENABLED=true
else
    print_info "cPHulk is currently DISABLED"
    ALREADY_ENABLED=false
fi

# Show current whitelist count (only if enabled)
if [ "$ALREADY_ENABLED" = true ]; then
    CURRENT_WHITELIST=$(/usr/local/cpanel/scripts/cphulkdwhitelist --list 2>/dev/null | grep -vE "^$|not enabled" | wc -l)
    print_info "Current cPHulk whitelist entries: $CURRENT_WHITELIST"
else
    print_info "Current cPHulk whitelist entries: N/A (cPHulk disabled)"
fi

if [ "$CSF_AVAILABLE" = true ]; then
    print_section "CSF Whitelist Analysis"

    # Get CSF allowed IPs
    echo "Scanning CSF allow files and following Include directives..."
    echo ""

    # Start with main CSF allow file and follow all includes recursively
    declare -A PROCESSED_FILES  # Track processed files to avoid duplicates
    CSF_FILES=()

    # Function to recursively find included files
    find_csf_files() {
        local current_file="$1"

        # Skip if already processed or doesn't exist
        [ -n "${PROCESSED_FILES[$current_file]}" ] && return
        [ ! -f "$current_file" ] && return

        # Mark as processed
        PROCESSED_FILES["$current_file"]=1
        CSF_FILES+=("$current_file")

        # Look for Include directives
        while IFS= read -r line; do
            if [[ "$line" =~ ^Include[[:space:]]+(.+)$ ]]; then
                local included_file="${BASH_REMATCH[1]}"
                # Trim whitespace
                included_file=$(echo "$included_file" | xargs)
                # Recursively process included file
                find_csf_files "$included_file"
            fi
        done < "$current_file"
    }

    # Method 1: Start with main csf.allow and follow all includes
    if [ -f "/etc/csf/csf.allow" ]; then
        find_csf_files "/etc/csf/csf.allow"
    fi

    # Method 2: Scan for all .allow files in /etc/csf/ (catches files not included)
    while IFS= read -r file; do
        find_csf_files "$file"
    done < <(find /etc/csf/ -maxdepth 1 -type f -name "*.allow" 2>/dev/null)

    # Method 3: Check csf.conf for ALLOWIPS_INCLUDE directive
    # This directive can point to custom include directories
    if [ -f "/etc/csf/csf.conf" ]; then
        ALLOWIPS_INCLUDE=$(grep "^ALLOWIPS_INCLUDE" /etc/csf/csf.conf | cut -d'=' -f2 | tr -d '"' | tr -d ' ')
        if [ -n "$ALLOWIPS_INCLUDE" ] && [ -d "$ALLOWIPS_INCLUDE" ]; then
            echo "Found ALLOWIPS_INCLUDE directory: $ALLOWIPS_INCLUDE"
            while IFS= read -r file; do
                find_csf_files "$file"
            done < <(find "$ALLOWIPS_INCLUDE" -type f 2>/dev/null)
        fi
    fi

    # Method 4: Common custom CSF include locations (hosting panel integrations)
    COMMON_CSF_LOCATIONS=(
        "/usr/local/lp/etc/csf"           # LiquidWeb
        "/usr/local/cpanel/etc/csf"        # cPanel custom
        "/usr/local/interworx/etc/csf"     # InterWorx
        "/var/cpanel/csf"                  # cPanel var
        "/root/.csf"                       # Custom user location
    )

    for location in "${COMMON_CSF_LOCATIONS[@]}"; do
        if [ -d "$location" ]; then
            # Look for all .allow files in these locations
            while IFS= read -r file; do
                find_csf_files "$file"
            done < <(find "$location" -type f -name "*allow*" 2>/dev/null)
        fi
    done

    # Method 5: Parse all Include directives from csf.conf CUSTOM variables
    if [ -f "/etc/csf/csf.conf" ]; then
        # Some systems use CUSTOM1_LOG through CUSTOM9_LOG which may reference allow files
        while IFS= read -r include_path; do
            if [ -f "$include_path" ]; then
                find_csf_files "$include_path"
            fi
        done < <(grep -E "^(CUSTOM|GENERIC).*Include" /etc/csf/csf.conf 2>/dev/null | grep -oE '\/[^"]+' || true)
    fi

    if [ ${#CSF_FILES[@]} -eq 0 ]; then
        print_warning "No CSF .allow files found"
        CSF_AVAILABLE=false
    else
        echo "Found ${#CSF_FILES[@]} CSF allow files (including includes):"
        for file in "${CSF_FILES[@]}"; do
            if [[ "$file" == /etc/csf/* ]]; then
                echo "  • $(basename "$file")"
            else
                echo "  • $file"
            fi
        done
        echo ""
    fi

    # Extract IPs from all CSF files
    CSF_ALLOW_IPS=()
    declare -A IP_SOURCE_COUNT  # Track which files contributed IPs

    if [ ${#CSF_FILES[@]} -gt 0 ]; then
        for csf_file in "${CSF_FILES[@]}"; do
            file_ip_count=0
            file_display_name=""

            if [[ "$csf_file" == /etc/csf/* ]]; then
                file_display_name="$(basename "$csf_file")"
            else
                file_display_name="$csf_file"
            fi

            while IFS= read -r line; do
                # Skip comments and empty lines
                [[ "$line" =~ ^#.*$ ]] && continue
                [[ -z "$line" ]] && continue

                # Skip Include directives (already processed)
                [[ "$line" =~ ^Include ]] && continue

                # Try multiple IP extraction methods:
                ip=""

                # Method 1: Simple IP format (IP at start of line)
                # Example: 192.168.100.1 # comment
                # Also handles CIDR: 10.20.4.0/22 # comment (strip /CIDR, just get IP)
                if [ -z "$ip" ]; then
                    ip=$(echo "$line" | awk '{print $1}' | grep -oE '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
                fi

                # Method 2: CSF source format (s=IP)
                # Example: tcp|in|d=4|s=208.74.123.2 # cPanel Auth Server
                if [ -z "$ip" ]; then
                    ip=$(echo "$line" | grep -oE 's=[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | cut -d'=' -f2)
                fi

                # Method 3: CSF destination format (d=IP)
                # Example: tcp|out|d=443|d=45.11.128.61/32
                if [ -z "$ip" ]; then
                    # Extract last d= parameter that contains an IP (not a port)
                    ip=$(echo "$line" | grep -oE 'd=[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | tail -1 | cut -d'=' -f2)
                fi

                if [ -n "$ip" ]; then
                    # Check if not already in array (deduplicate)
                    if [[ ! " ${CSF_ALLOW_IPS[@]} " =~ " ${ip} " ]]; then
                        CSF_ALLOW_IPS+=("$ip")
                        file_ip_count=$((file_ip_count + 1))
                    fi
                fi
            done < "$csf_file"

            # Track count per file
            if [ "${file_ip_count:-0}" -gt 0 ]; then
                IP_SOURCE_COUNT["$file_display_name"]=$file_ip_count
            fi
        done

        # Show breakdown by file
        echo "IP breakdown by source file:"
        for file in $(printf '%s\n' "${!IP_SOURCE_COUNT[@]}" | sort); do
            printf "  • %-45s %3d IPs\n" "$file" "${IP_SOURCE_COUNT[$file]}"
        done
        echo ""

        print_success "Found ${#CSF_ALLOW_IPS[@]} total unique IPs across all CSF files"
    fi

    if [ ${#CSF_ALLOW_IPS[@]} -gt 0 ]; then
        echo ""
        echo "Sample IPs to be imported:"
        for i in {0..4}; do
            if [ -n "${CSF_ALLOW_IPS[$i]}" ]; then
                echo "  • ${CSF_ALLOW_IPS[$i]}"
            fi
        done
        if [ ${#CSF_ALLOW_IPS[@]} -gt 5 ]; then
            echo "  ... and $((${#CSF_ALLOW_IPS[@]} - 5)) more"
        fi
    fi
fi

echo ""
print_section "Actions to Perform"

if [ "$ALREADY_ENABLED" = false ]; then
    echo "  1. Enable cPHulk brute force protection"
else
    echo "  1. cPHulk already enabled (skip)"
fi

if [ "$CSF_AVAILABLE" = true ] && [ ${#CSF_ALLOW_IPS[@]} -gt 0 ]; then
    echo "  2. Import ${#CSF_ALLOW_IPS[@]} IPs from CSF to cPHulk whitelist"
else
    echo "  2. No CSF IPs to import (skip)"
fi

echo "  3. Display final configuration"

echo ""
read -p "Proceed with these actions? (yes/no): " confirm

if [ "$confirm" != "yes" ]; then
    print_info "Operation cancelled"
    exit 0
fi

echo ""
print_section "Execution"

# Step 1: Enable cPHulk
if [ "$ALREADY_ENABLED" = false ]; then
    print_info "Enabling cPHulk..."

    # Enable via PAM control
    /usr/local/cpanel/bin/cphulk_pam_ctl --enable >/dev/null 2>&1

    # Enable and start the cphulkd service via WHM API
    whmapi1 configureservice service=cphulkd enabled=1 monitored=1 >/dev/null 2>&1

    # Wait for service to start
    sleep 2

    # Verify it's running
    if systemctl is-active cphulkd >/dev/null 2>&1 || service cphulkd status >/dev/null 2>&1; then
        print_success "cPHulk enabled successfully"
    else
        print_warning "cPHulk enabled but service may not be running"
        print_info "You may need to start it manually: service cphulkd start"
    fi
else
    print_info "cPHulk already enabled, skipping"
fi

# Step 2: Import CSF whitelist
if [ "$CSF_AVAILABLE" = true ] && [ ${#CSF_ALLOW_IPS[@]} -gt 0 ]; then
    print_info "Importing CSF whitelist to cPHulk..."

    IMPORTED=0
    SKIPPED=0
    FAILED=0

    # Get existing whitelist from SQLite database
    EXISTING_IPS=$(sqlite3 /var/cpanel/hulkd/cphulk.sqlite "SELECT ip FROM ip_lists WHERE type=1" 2>/dev/null || echo "")

    for ip in "${CSF_ALLOW_IPS[@]}"; do
        # Check if already in cPHulk whitelist
        if echo "$EXISTING_IPS" | grep -q "^$ip\$"; then
            SKIPPED=$((SKIPPED + 1))
            echo "  [SKIP] $ip (already whitelisted)"
        else
            # Add to cPHulk whitelist - cphulkdwhitelist doesn't give useful output
            # Just run it and assume success if no error
            if /usr/local/cpanel/scripts/cphulkdwhitelist "$ip" >/dev/null 2>&1; then
                IMPORTED=$((IMPORTED + 1))
                echo "  [OK] $ip"
            else
                FAILED=$((FAILED + 1))
                echo "  [FAIL] $ip"
            fi
        fi
    done

    echo ""
    print_success "Import complete:"
    echo "  • Imported: $IMPORTED"
    echo "  • Skipped (already whitelisted): $SKIPPED"
    if [ "${FAILED:-0}" -gt 0 ]; then
        print_warning "Failed: $FAILED"
    fi
fi

# Step 3: Display final status
echo ""
print_section "Final Configuration"

# Check if service is running
if systemctl is-active cphulkd >/dev/null 2>&1 || service cphulkd status >/dev/null 2>&1; then
    print_success "cPHulk Status: ENABLED and RUNNING"
else
    print_warning "cPHulk Status: Service not running"
fi

# Count whitelist entries from SQLite database
FINAL_WHITELIST=$(sqlite3 /var/cpanel/hulkd/cphulk.sqlite "SELECT COUNT(*) FROM ip_lists WHERE type=1" 2>/dev/null || echo "0")
print_info "cPHulk whitelist entries: $FINAL_WHITELIST"

echo ""
print_section "Next Steps"

echo "1. Configure cPHulk settings in WHM:"
echo "   WHM → Security Center → cPHulk Brute Force Protection"
echo ""
echo "2. Recommended settings:"
echo "   • Brute Force Protection Period: 5 minutes"
echo "   • Maximum Failures per Account: 5"
echo "   • Maximum Failures per IP: 10"
echo ""
echo "3. Add more IPs to whitelist:"
echo "   /usr/local/cpanel/scripts/cphulkdwhitelist YOUR.IP.ADDRESS"
echo ""
echo "4. View current whitelist (via SQLite database):"
echo "   sqlite3 /var/cpanel/hulkd/cphulk.sqlite 'SELECT * FROM ip_lists WHERE type=1'"
echo ""
echo "5. View currently blocked IPs (via database):"
echo "   sqlite3 /var/cpanel/hulkd/cphulk.sqlite 'SELECT * FROM auths'"

echo ""
print_success "cPHulk setup complete!"
