#!/bin/bash
# ============================================================================================================
# Script: fail2ban-manager.sh
# Description: Manage Fail2Ban configuration based on database settings
# Author: Rodrigo Cuadra - SoftSwitch LLC
# Date: 2025-12-06
# ============================================================================================================

set -euo pipefail

# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FAIL2BAN_DIR="/etc/fail2ban"
JAIL_LOCAL="${FAIL2BAN_DIR}/jail.local"
JAIL_D_DIR="${FAIL2BAN_DIR}/jail.d"
ODBC_INI="/etc/odbc.ini"

# Read database config from odbc.ini (same as generate-nftables.sh)
read_odbc_config() {
    local dsn="ss_admin"
    
    if [ -f "$ODBC_INI" ]; then
        # Use exact field names to avoid matching wrong lines
        # Also strip carriage returns (\r) in case of Windows line endings
        DB_HOST=$(grep -A 20 "^\[$dsn\]" "$ODBC_INI" | grep "^Servername " | cut -d'=' -f2 | tr -d ' \r' | head -1 || true)
        DB_PORT=$(grep -A 20 "^\[$dsn\]" "$ODBC_INI" | grep "^Port " | cut -d'=' -f2 | tr -d ' \r' | head -1 || true)
        DB_USER=$(grep -A 20 "^\[$dsn\]" "$ODBC_INI" | grep "^Username " | cut -d'=' -f2 | tr -d ' \r' | head -1 || true)
        DB_PASS=$(grep -A 20 "^\[$dsn\]" "$ODBC_INI" | grep "^Password " | cut -d'=' -f2 | tr -d ' \r' | head -1 || true)
        DB_NAME=$(grep -A 20 "^\[$dsn\]" "$ODBC_INI" | grep "^Database " | cut -d'=' -f2 | tr -d ' \r' | head -1 || true)
    fi
    
    # Fallback to environment variables or defaults
    DB_HOST="${DB_HOST:-${PGHOST:-localhost}}"
    DB_PORT="${DB_PORT:-${PGPORT:-5432}}"
    DB_USER="${DB_USER:-${PGUSER:-ss_user}}"
    DB_PASS="${DB_PASS:-${PGPASSWORD:-ss2025}}"
    DB_NAME="${DB_NAME:-${PGDATABASE:-ss_admin}}"
    
    # Export PGPASSWORD so psql doesn't ask for password
    export PGPASSWORD="$DB_PASS"
}

# Initialize database configuration
read_odbc_config

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Logging functions
log_info() {
    echo -e "${GREEN}[INFO]${NC} $1"
}

log_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1" >&2
}

# Check if fail2ban is installed
check_fail2ban_installed() {
    if ! command -v fail2ban-client &> /dev/null; then
        log_warning "Fail2Ban is not installed. Installing..."
        apt-get update -qq
        apt-get install -y fail2ban
        log_info "Fail2Ban installed successfully"
    fi
}

# Get firewall settings from database
get_firewall_settings() {
    local query_result=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -A -F'|' -c "
        SELECT 
            intrusion_detection_enabled,
            failed_attempts_allowed,
            find_time,
            ban_time,
            COALESCE(notification_email, '') as notification_email
        FROM firewall_settings
        WHERE tenant_id = 1
        LIMIT 1;
    " 2>&1)
    
    local psql_exit_code=$?
    
    if [ $psql_exit_code -ne 0 ]; then
        log_error "Failed to query firewall settings from database: $query_result"
        echo ""
        return 1
    fi
    
    echo "$query_result"
}

# Generate fail2ban jail.local configuration
generate_fail2ban_config() {
    log_info "Generating Fail2Ban configuration from database..."
    
    local settings=$(get_firewall_settings)
    local get_settings_exit_code=$?
    
    if [ $get_settings_exit_code -ne 0 ] || [ -z "$settings" ]; then
        log_error "Failed to retrieve firewall settings from database"
        return 1
    fi
    
    # Parse settings (format: enabled|maxretry|findtime|bantime|email)
    IFS='|' read -r enabled maxretry findtime bantime email <<< "$settings"
    
    # Trim whitespace
    enabled=$(echo "$enabled" | xargs)
    maxretry=$(echo "$maxretry" | xargs)
    findtime=$(echo "$findtime" | xargs)
    bantime=$(echo "$bantime" | xargs)
    email=$(echo "$email" | xargs)
    
    if [ "$enabled" != "t" ] && [ "$enabled" != "true" ]; then
        log_info "Intrusion detection is disabled in database"
        # Only disable the FreeSWITCH SIP jail, keep fail2ban running for SSH
        if [ -f "${FAIL2BAN_DIR}/filter.d/freeswitch-sip.conf" ]; then
            rm -f "${FAIL2BAN_DIR}/filter.d/freeswitch-sip.conf"
            log_info "Removed FreeSWITCH SIP filter"
        fi
        # Generate minimal jail.local with only sshd
        cat > "$JAIL_LOCAL" << EOF
# SoftSwitch Platform - Fail2Ban Configuration (Intrusion Detection Disabled)
# Auto-generated: $(date '+%Y-%m-%d %H:%M:%S')
# Only SSH protection is active. Enable Intrusion Detection in UI for SIP protection.

[DEFAULT]
banaction = nftables-multiport
banaction_allports = nftables-allports
backend = systemd

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 5
findtime = 600
bantime = 3600
EOF
        log_info "Generated minimal jail.local (SSH only)"
        # Reload fail2ban if running
        if systemctl is-active --quiet fail2ban; then
            systemctl reload fail2ban
        fi
        return 0
    fi
    
    log_info "Intrusion detection is enabled"
    log_info "Settings: maxretry=$maxretry, findtime=${findtime}s, bantime=${bantime}s"
    
    # Create jail.local configuration
    mkdir -p "$FAIL2BAN_DIR"
    mkdir -p "${FAIL2BAN_DIR}/filter.d"
    
    # Create FreeSWITCH SIP filter
    cat > "${FAIL2BAN_DIR}/filter.d/freeswitch-sip.conf" <<'FILTER_EOF'
# Fail2Ban filter for FreeSWITCH SIP authentication failures
# Matches failed registration attempts in FreeSWITCH logs

[Definition]

# Common prefixes for log lines
_daemon = freeswitch

# Regex patterns to match auth failures
# FreeSWITCH logs auth failures like:
# 2026-02-07 16:48:47.088640 97.63% [WARNING] sofia_reg.c:3210 Can't find user [1001@64.225.54.63] from 223.165.6.138
# 2026-02-07 16:48:47.088640 97.63% [WARNING] sofia_reg.c:xxx SIP auth failure (REGISTER) on sofia profile 'internal' for [user@host] from ip 1.2.3.4
# 2026-02-07 16:48:47.088640 97.63% [WARNING] sofia_reg.c:xxx Registration Failed for user@domain (bad password) from 1.2.3.4

failregex = \[WARNING\]\s+sofia_reg\.c:\d+\s+SIP\s+auth\s+failure.*from\s+ip\s+<HOST>
            \[WARNING\]\s+sofia_reg\.c:\d+\s+Can.t\s+find\s+user.*from\s+<HOST>
            \[WARNING\]\s+sofia_reg\.c:\d+\s+Registration\s+Failed.*from\s+<HOST>
            \[WARNING\]\s+sofia\.c:\d+\s+Can.t\s+find\s+user.*from\s+<HOST>
            \[WARNING\]\s+mod_sofia\.c:\d+\s+.*AUTH\s+FAILED.*<HOST>
            \[WARNING\]\s+sofia\.c:\d+\s+IP\s+<HOST>\s+Rejected\s+by\s+acl.*
            \[WARNING\]\s+switch_core_state_machine\.c:\d+\s+\S+\s+sofia/\S+@<HOST>\s+Abandoned

# Lines to ignore
ignoreregex =

# Let fail2ban auto-detect the date format (handles microseconds correctly)
FILTER_EOF
    log_info "Created FreeSWITCH SIP filter: ${FAIL2BAN_DIR}/filter.d/freeswitch-sip.conf"
    
    # Determine email action
    local email_action
    if [ -n "$email" ] && [ "$email" != "" ]; then
        email_action="%(action_mwl)s"
    else
        email_action="%(action_)s"
    fi
    
    cat > "$JAIL_LOCAL" << EOF
# =====================================================
# SoftSwitch Platform - Fail2Ban Configuration
# Auto-generated: $(date '+%Y-%m-%d %H:%M:%S')
# =====================================================
# This file is auto-generated from database settings
# Do not edit manually - changes will be overwritten
# =====================================================

[DEFAULT]
# Default ban action (using nftables)
# Fail2Ban 0.11+ (Debian 13) has native nftables support
# Try nftables-multiport first, fallback to nftables if not available
banaction = nftables-multiport
banaction_allports = nftables-allports
# Alternative: banaction = nftables (simpler, works on Debian 13)

# Email notifications
$(if [ -n "$email" ] && [ "$email" != "" ]; then
    echo "destemail = $email"
    echo "sender = fail2ban@$(hostname -f 2>/dev/null || hostname)"
    echo "action = $email_action"
else
    echo "# No email configured - notifications disabled"
    echo "action = $email_action"
fi)

# Global settings from database
maxretry = $maxretry
findtime = $findtime
bantime = $bantime

# Backend
backend = systemd

# Logging
loglevel = INFO
logtarget = /var/log/fail2ban.log

# =====================================================
# SSH Jail
# =====================================================
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = $maxretry
findtime = $findtime
bantime = $bantime

# =====================================================
# HTTP/HTTPS Jails (optional - can be enabled later)
# =====================================================
[nginx-http-auth]
enabled = false
port = http,https
logpath = /var/log/nginx/error.log

[nginx-limit-req]
enabled = false
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 10

# =====================================================
# PostgreSQL Jail (optional)
# =====================================================
[postgresql]
enabled = false
port = 5432
logpath = /var/log/postgresql/postgresql-*.log
maxretry = 5

# =====================================================
# FreeSWITCH SIP Jail
# Blocks failed SIP registration attempts
# =====================================================
[freeswitch-sip]
enabled = true
port = 5060,5061,5080,5081
protocol = udp
filter = freeswitch-sip
logpath = /var/log/freeswitch/freeswitch.log
backend = auto
maxretry = $maxretry
findtime = $findtime
bantime = $bantime

# =====================================================
# Recidive Jail
# Blocks repeat offenders who are banned multiple times
# =====================================================
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime = 1w
findtime = 1d
maxretry = 2

EOF

    log_info "Fail2Ban configuration generated: $JAIL_LOCAL"
    
    # Validate configuration
    if fail2ban-client -t 2>&1 | grep -q "error"; then
        log_error "Fail2Ban configuration validation failed"
        fail2ban-client -t
        return 1
    fi
    
    log_info "Fail2Ban configuration validated successfully"
    return 0
}

# Apply fail2ban configuration
apply_fail2ban_config() {
    log_info "Applying Fail2Ban configuration..."
    
    # Generate configuration
    if ! generate_fail2ban_config; then
        log_error "Failed to generate Fail2Ban configuration"
        return 1
    fi
    
    # Reload fail2ban
    if systemctl is-active --quiet fail2ban; then
        log_info "Reloading Fail2Ban service..."
        systemctl reload fail2ban
    else
        log_info "Starting Fail2Ban service..."
        systemctl enable fail2ban
        systemctl start fail2ban
    fi
    
    # Wait a moment for service to start
    sleep 2
    
    # Verify service is running
    if systemctl is-active --quiet fail2ban; then
        log_info "Fail2Ban service is running"
        
        # Show status
        log_info "Fail2Ban status:"
        fail2ban-client status | head -10
        
        return 0
    else
        log_error "Fail2Ban service failed to start"
        systemctl status fail2ban --no-pager -l
        return 1
    fi
}

# Main function
main() {
    local command="${1:-apply}"
    
    case "$command" in
        generate)
            check_fail2ban_installed
            generate_fail2ban_config
            ;;
        apply)
            check_fail2ban_installed
            apply_fail2ban_config
            ;;
        status)
            if systemctl is-active --quiet fail2ban; then
                log_info "Fail2Ban is running"
                fail2ban-client status
            else
                log_warning "Fail2Ban is not running"
            fi
            ;;
        stop)
            log_info "Stopping Fail2Ban..."
            systemctl stop fail2ban
            systemctl disable fail2ban
            ;;
        start)
            check_fail2ban_installed
            apply_fail2ban_config
            ;;
        *)
            echo "Usage: $0 {generate|apply|status|start|stop}"
            echo ""
            echo "Commands:"
            echo "  generate  - Generate Fail2Ban configuration from database"
            echo "  apply     - Generate and apply Fail2Ban configuration"
            echo "  status    - Show Fail2Ban status"
            echo "  start     - Start Fail2Ban service"
            echo "  stop      - Stop Fail2Ban service"
            exit 1
            ;;
    esac
}

# Run main function
main "$@"
