<?php
/**
 * IP Blacklist Loader
 * Automatically loads and checks IPs against blacklist files
 * Supports: IPv4, IPv6, CIDR notation, individual IPs
 * 
 * CONFIGURATION: Edit config_ip_blacklist.php to enable/disable
 */

// Load configuration
require_once __DIR__ . '/config_ip_blacklist.php';

class IPBlacklistLoader {
    
    private static $blacklistDir = null;
    private static $ipv4Ranges = [];
    private static $ipv6Ranges = [];
    private static $loaded = false;
    
    /**
     * Load all IP blacklist files
     */
    public static function loadBlacklists() {
        if (self::$loaded) {
            return; // Already loaded
        }
        
        // Use configuration settings
        if (!defined('IP_BLACKLIST_ENABLED') || !IP_BLACKLIST_ENABLED) {
            self::$loaded = true; // Mark as loaded even though disabled
            return; // IP blacklist disabled - skip loading
        }
        
        // Get directory from configuration
        self::$blacklistDir = defined('IP_BLACKLIST_DIR') ? IP_BLACKLIST_DIR : __DIR__ . '/all';
        
        // Get files from configuration
        $files = defined('IP_BLACKLIST_FILES') ? IP_BLACKLIST_FILES : [
            'ipv4.txt',
            'ipv4_merged.txt',
            'ipv6.txt',
            'ipv6_merged.txt'
        ];
        
        foreach ($files as $file) {
            $filepath = self::$blacklistDir . '/' . $file;
            if (file_exists($filepath)) {
                $lines = file($filepath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
                foreach ($lines as $line) {
                    $line = trim($line);
                    if (empty($line) || $line[0] === '#') {
                        continue; // Skip empty lines and comments
                    }
                    
                    // Detect IPv4 or IPv6
                    if (strpos($line, ':') !== false) {
                        // IPv6
                        self::$ipv6Ranges[] = $line;
                    } else {
                        // IPv4
                        self::$ipv4Ranges[] = $line;
                    }
                }
            }
        }
        
        self::$loaded = true;
    }
    
    /**
     * Check if an IP is blacklisted
     * @param string $ip IP address to check
     * @return bool True if blacklisted
     */
    public static function isBlacklisted($ip) {
        // Check if IP blacklist is enabled
        if (!defined('IP_BLACKLIST_ENABLED') || !IP_BLACKLIST_ENABLED) {
            return false; // IP blacklist disabled - skip checking
        }
        
        if (!self::$loaded) {
            self::loadBlacklists();
        }
        
        if (empty($ip)) {
            return false;
        }
        
        // Detect if IPv4 or IPv6
        $isBlocked = false;
        if (strpos($ip, ':') !== false) {
            // IPv6
            $isBlocked = self::checkIPv6($ip);
        } else {
            // IPv4
            $isBlocked = self::checkIPv4($ip);
        }
        
        // Log if enabled and IP is blocked
        if ($isBlocked && defined('IP_BLACKLIST_LOG') && IP_BLACKLIST_LOG) {
            error_log("IP Blacklist: Blocked IP - {$ip}");
        }
        
        return $isBlocked;
    }
    
    /**
     * Check IPv4 against blacklist
     * @param string $ip IPv4 address
     * @return bool True if blacklisted
     */
    private static function checkIPv4($ip) {
        foreach (self::$ipv4Ranges as $range) {
            if (self::ipv4InRange($ip, $range)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Check IPv6 against blacklist
     * @param string $ip IPv6 address
     * @return bool True if blacklisted
     */
    private static function checkIPv6($ip) {
        foreach (self::$ipv6Ranges as $range) {
            if (self::ipv6InRange($ip, $range)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Check if IPv4 is in CIDR range
     * Supports: 192.168.1.0/24, 192.168.1.100, 192.168.1.0-192.168.1.255
     * @param string $ip IP address to check
     * @param string $range CIDR range or single IP
     * @return bool True if in range
     */
    private static function ipv4InRange($ip, $range) {
        // Exact match
        if ($ip === $range) {
            return true;
        }
        
        // CIDR notation (e.g., 192.168.1.0/24)
        if (strpos($range, '/') !== false) {
            list($subnet, $mask) = explode('/', $range);
            
            $ip_long = ip2long($ip);
            $subnet_long = ip2long($subnet);
            $mask_long = -1 << (32 - (int)$mask);
            
            return ($ip_long & $mask_long) === ($subnet_long & $mask_long);
        }
        
        // IP range (e.g., 192.168.1.0-192.168.1.255)
        if (strpos($range, '-') !== false) {
            list($start, $end) = explode('-', $range);
            $ip_long = ip2long($ip);
            $start_long = ip2long(trim($start));
            $end_long = ip2long(trim($end));
            
            return ($ip_long >= $start_long && $ip_long <= $end_long);
        }
        
        // Wildcard (e.g., 192.168.1.*)
        if (strpos($range, '*') !== false) {
            $range_pattern = str_replace(['.', '*'], ['\.', '[0-9]+'], $range);
            return preg_match('/^' . $range_pattern . '$/', $ip) === 1;
        }
        
        return false;
    }
    
    /**
     * Check if IPv6 is in CIDR range
     * Supports: 2001:db8::/32, 2001:db8::1
     * @param string $ip IPv6 address to check
     * @param string $range CIDR range or single IP
     * @return bool True if in range
     */
    private static function ipv6InRange($ip, $range) {
        // Exact match
        if ($ip === $range) {
            return true;
        }
        
        // CIDR notation (e.g., 2001:db8::/32)
        if (strpos($range, '/') !== false) {
            list($subnet, $mask) = explode('/', $range);
            
            // Convert IPs to binary
            $ip_bin = inet_pton($ip);
            $subnet_bin = inet_pton($subnet);
            
            if ($ip_bin === false || $subnet_bin === false) {
                return false;
            }
            
            // Calculate the number of bits to compare
            $mask = (int)$mask;
            $bytes = floor($mask / 8);
            $bits = $mask % 8;
            
            // Compare full bytes
            for ($i = 0; $i < $bytes; $i++) {
                if ($ip_bin[$i] !== $subnet_bin[$i]) {
                    return false;
                }
            }
            
            // Compare remaining bits
            if ($bits > 0) {
                $ip_bits = ord($ip_bin[$bytes]);
                $subnet_bits = ord($subnet_bin[$bytes]);
                $mask_bits = (0xFF << (8 - $bits)) & 0xFF;
                
                if (($ip_bits & $mask_bits) !== ($subnet_bits & $mask_bits)) {
                    return false;
                }
            }
            
            return true;
        }
        
        return false;
    }
    
    /**
     * Get statistics about loaded blacklists
     * @return array Statistics
     */
    public static function getStats() {
        if (!self::$loaded) {
            self::loadBlacklists();
        }
        
        return [
            'ipv4_ranges' => count(self::$ipv4Ranges),
            'ipv6_ranges' => count(self::$ipv6Ranges),
            'total_ranges' => count(self::$ipv4Ranges) + count(self::$ipv6Ranges),
            'loaded' => self::$loaded
        ];
    }
}

/**
 * Helper function for easy IP blacklist checking
 * @param string $ip IP address to check
 * @return bool True if blacklisted
 */
function checkIPBlacklist($ip) {
    return IPBlacklistLoader::isBlacklisted($ip);
}
