#!/bin/bash
# =============================================================================
# direct-ip-detect.sh — Direct IP Connection Detector
# =============================================================================
# Detects connections arriving on public interface that bypass WireGuard tunnels.
# Silently drops suspicious traffic (no RST, no ICMP) and logs to sec_events.
#
# CRITICAL: 10.69.69.10 (operator VPN) is ALWAYS whitelisted. NEVER block.
# =============================================================================
set -euo pipefail

TOOL_DIR="$(dirname "$(readlink -f "$0")")"
[ -f "$TOOL_DIR/common.sh" ] && source "$TOOL_DIR/common.sh" 2>/dev/null || true
[ -f "$TOOL_DIR/log_to_db.sh" ] && source "$TOOL_DIR/log_to_db.sh" 2>/dev/null || true

NODE_ID="${NODE_ID:-$(cat /etc/binary/node_id 2>/dev/null || hostname -s)}"
TOOL_ID="f6a4"

# ── DB Config (Store DB on 558201) ──
DB_HOST="${STORE_DB_HOST:-172.28.50.1}"
DB_PORT="${STORE_DB_PORT:-3306}"
DB_NAME="${STORE_DB_NAME:-shared_engine}"
DB_USER="${STORE_DB_USER:-tooling}"
DB_PASS="${STORE_DB_PASS:-}"

CRED_FILE="/etc/binary/tooling.env"
[ -f "$CRED_FILE" ] && source "$CRED_FILE" 2>/dev/null || true

# ── Whitelisted subnets (NEVER block) ──
WHITELIST_SUBNETS=(
    "10.69.69.0/24"       # wg7 management mesh
    "172.28.47.0/28"      # compass tunnel (ALL→869377)
    "172.28.41.0/30"      # lantern tunnel (558201↔669427)
    "172.23.83.0/30"      # rain tunnel (414342↔669427)
    "10.83.47.0/30"       # umbrella tunnel (414342↔669427)
    "172.29.11.0/30"      # gravel tunnel (669427↔199335)
    "127.0.0.0/8"         # loopback
)

# ── Explicitly whitelisted IPs ──
WHITELIST_IPS=(
    "10.69.69.10"         # OPERATOR VPN — ABSOLUTELY NEVER BLOCK
)

# ── Known node public IPs (bypass = HIGH severity) ──
declare -A NODE_PUBLIC_IPS=(
    ["199335"]="80.78.19.57"
    ["244939"]="107.189.23.194"
    ["333941"]="91.229.239.29"
    ["414342"]="194.36.190.111"
    ["558201"]="89.127.235.207"
    ["669427"]="76.13.222.157"
    ["714820"]="45.143.166.118"
    ["869377"]="104.194.144.66"
)

# ── SQL escape helper ──
sql_escape() {
    echo "$1" | sed "s/'/''/g"
}

# ── Check if IP is in a CIDR subnet ──
ip_in_subnet() {
    local ip="$1" subnet="$2"
    python3 -c "
import ipaddress, sys
try:
    net = ipaddress.ip_network('$subnet', strict=False)
    addr = ipaddress.ip_address('$ip')
    sys.exit(0 if addr in net else 1)
except:
    sys.exit(1)
" 2>/dev/null
}

# ── Check if IP is whitelisted ──
is_whitelisted() {
    local ip="$1"
    
    # Check explicit whitelist IPs
    for wl_ip in "${WHITELIST_IPS[@]}"; do
        [[ "$ip" == "$wl_ip" ]] && return 0
    done
    
    # Check whitelist subnets
    for subnet in "${WHITELIST_SUBNETS[@]}"; do
        if ip_in_subnet "$ip" "$subnet"; then
            return 0
        fi
    done
    
    return 1
}

# ── Check if IP is a known node public IP ──
is_node_public_ip() {
    local ip="$1"
    for nid in "${!NODE_PUBLIC_IPS[@]}"; do
        if [[ "${NODE_PUBLIC_IPS[$nid]}" == "$ip" ]]; then
            echo "$nid"
            return 0
        fi
    done
    return 1
}

# ── Log to sec_events (Store DB) ──
log_sec_event() {
    local severity="$1" event_type="$2" details="$3" src_ip="$4"
    
    [[ -z "${DB_PASS:-}" ]] && return 0
    
    {
        MYSQL_PWD="$DB_PASS" mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -D"$DB_NAME" \
            --connect-timeout=3 -e "
            INSERT INTO sec_events
                (node_id, source_tag, event_type, severity, details_enc, ip_address_enc, status_code, created_at)
            VALUES
                ('$(sql_escape "$NODE_ID")', 'direct-ip-detect', '$(sql_escape "$event_type")',
                 '$(sql_escape "$severity")', '$(sql_escape "$details")',
                 '$(sql_escape "$src_ip")', 1, NOW())
        " 2>/dev/null
    } &
    disown 2>/dev/null
}

# ── Check blacklist threshold (3+ events in 5 min) ──
check_blacklist_threshold() {
    local ip="$1"
    
    [[ -z "${DB_PASS:-}" ]] && return 1
    
    local count
    count=$(MYSQL_PWD="$DB_PASS" mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -D"$DB_NAME" \
        --batch --skip-column-names -e "
        SELECT COUNT(*) FROM sec_events
        WHERE ip_address_enc = '$(sql_escape "$ip")'
          AND source_tag = 'direct-ip-detect'
          AND status_code = 1
          AND created_at >= NOW() - INTERVAL 5 MINUTE
    " 2>/dev/null) || count=0
    
    [[ "$count" -ge 3 ]]
}

# ── Add to blacklist ──
add_to_blacklist() {
    local ip="$1" reason="$2" severity="$3"
    
    [[ -z "${DB_PASS:-}" ]] && return 0
    
    # Check not already blacklisted
    local exists
    exists=$(MYSQL_PWD="$DB_PASS" mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -D"$DB_NAME" \
        --batch --skip-column-names -e "
        SELECT COUNT(*) FROM sec_blacklist_nft
        WHERE ip_address = '$(sql_escape "$ip")'
          AND status_code = 1
          AND (expires_at IS NULL OR expires_at > NOW())
    " 2>/dev/null) || exists=0
    
    [[ "$exists" -gt 0 ]] && return 0
    
    MYSQL_PWD="$DB_PASS" mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -D"$DB_NAME" \
        --connect-timeout=3 -e "
        INSERT INTO sec_blacklist_nft
            (ip_address, reason, severity, added_by, added_at, expires_at, status_code, node_id)
        VALUES
            ('$(sql_escape "$ip")', '$(sql_escape "$reason")', '$(sql_escape "$severity")',
             'direct-ip-detect', NOW(), DATE_ADD(NOW(), INTERVAL 24 HOUR), 1, '$(sql_escape "$NODE_ID")')
    " 2>/dev/null || true
    
    log_sec_event "high" "ip_blacklisted" "IP $ip added to blacklist: $reason" "$ip"
}

# ── Apply iptables DROP rule ──
apply_silent_drop() {
    local ip="$1"
    
    # Check if rule already exists
    if iptables -C INPUT -s "$ip" -j DROP 2>/dev/null; then
        return 0
    fi
    
    # Add DROP rule (silent — no RST, no ICMP)
    iptables -I INPUT -s "$ip" -j DROP 2>/dev/null || true
}

# ── Main detection loop ──
detect_direct_connections() {
    local detected=0
    
    # Get current established connections on public interfaces (not WG)
    # ss -tnp shows TCP connections with process info
    local connections
    connections=$(ss -tnp state established 2>/dev/null | tail -n +2) || connections=""
    
    [[ -z "$connections" ]] && return 0
    
    while IFS= read -r line; do
        [[ -z "$line" ]] && continue
        
        # Extract source IP (peer address = column 5)
        local peer_addr
        peer_addr=$(echo "$line" | awk '{print $5}' | sed 's/:.*$//')
        
        [[ -z "$peer_addr" ]] && continue
        [[ "$peer_addr" == "*" ]] && continue
        
        # Skip whitelisted
        if is_whitelisted "$peer_addr"; then
            continue
        fi
        
        # Check if it's a node public IP (WG bypass)
        local bypass_node
        if bypass_node=$(is_node_public_ip "$peer_addr"); then
            log_sec_event "high" "direct_connection" \
                "Node $bypass_node ($peer_addr) connected via public IP (WG bypass detected)" \
                "$peer_addr"
            
            detected=$((detected + 1))
            
            # Check threshold for blacklist
            if check_blacklist_threshold "$peer_addr"; then
                add_to_blacklist "$peer_addr" "Repeated WG bypass from node $bypass_node" "high"
                apply_silent_drop "$peer_addr"
            fi
            
            continue
        fi
        
        # Unknown public IP on established connection — check if on non-service port
        local local_addr local_port
        local_addr=$(echo "$line" | awk '{print $4}')
        local_port=$(echo "$local_addr" | grep -oP ':\K[0-9]+$' || echo "0")
        
        # Service ports that legitimately accept public connections
        local service_ports="80 443 25 587 993 53"
        local is_service=false
        for sp in $service_ports; do
            [[ "$local_port" == "$sp" ]] && is_service=true && break
        done
        
        if ! $is_service; then
            log_sec_event "medium" "suspicious_connection" \
                "Unknown IP $peer_addr connected on non-service port $local_port" \
                "$peer_addr"
            
            detected=$((detected + 1))
            
            if check_blacklist_threshold "$peer_addr"; then
                add_to_blacklist "$peer_addr" "Repeated suspicious connections on port $local_port" "medium"
                apply_silent_drop "$peer_addr"
            fi
        fi
        
    done <<< "$connections"
    
    echo "$detected"
}

# ── Entrypoint ──
case "${1:-run}" in
    run|detect)
        result=$(detect_direct_connections)
        if [[ "$result" -gt 0 ]]; then
            echo "{\"tool\":\"direct-ip-detect\",\"node\":\"$NODE_ID\",\"detected\":$result,\"action\":\"logged\",\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"
        else
            echo "{\"tool\":\"direct-ip-detect\",\"node\":\"$NODE_ID\",\"detected\":0,\"status\":\"clean\",\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"
        fi
        ;;
    status)
        # Count current DROP rules
        local drops
        drops=$(iptables -L INPUT -n 2>/dev/null | grep -c "DROP" || echo 0)
        echo "{\"tool\":\"direct-ip-detect\",\"node\":\"$NODE_ID\",\"active_drops\":$drops,\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"
        ;;
    *)
        echo "Usage: $0 {run|detect|status}"
        exit 1
        ;;
esac
