From 2e33bca4f60baf0f6872866516cf7a6ad0ca1739 Mon Sep 17 00:00:00 2001 From: thomasciesla Date: Tue, 16 Dec 2025 12:52:14 +0100 Subject: [PATCH] geoip_shop_manager.py aktualisiert --- geoip_shop_manager.py | 820 ++++++++++-------------------------------- 1 file changed, 186 insertions(+), 634 deletions(-) diff --git a/geoip_shop_manager.py b/geoip_shop_manager.py index c123871..e3932c1 100644 --- a/geoip_shop_manager.py +++ b/geoip_shop_manager.py @@ -1,16 +1,14 @@ #!/usr/bin/env python3 """ GeoIP Shop Blocker Manager - DACH & Eurozone Version -2-Component System: PHP blocking + Python watcher (systemd service) Supports two geo regions: - DACH: Germany, Austria, Switzerland (3 countries) - Eurozone+GB: All Eurozone countries + GB + CH (22 countries) -Supports three modes: - - php+crowdsec: GeoIP blocking with CrowdSec integration - - php-only: GeoIP blocking without CrowdSec - - bot-only: Rate-limit bots, shop remains globally accessible +Supports two modes: + - geoip: GeoIP blocking (only allowed regions can access) + - bot: Rate-limit bots by bot-type, shop remains globally accessible -v3.5.0: Erweiterte Bot-Erkennung mit 300+ Bots und generischen Fallback-Patterns +v4.0.0: Bot-Rate-Limiting nach Bot-Typ (nicht IP), CrowdSec entfernt """ import os @@ -44,11 +42,8 @@ BACKUP_SUFFIX = ".geoip_backup" BLOCKING_FILE = "geoip_blocking.php" CACHE_FILE = "geoip_ip_ranges.cache" LOG_FILE = "geoip_blocked.log" -CROWDSEC_QUEUE_FILE = "geoip_crowdsec_queue.log" RATELIMIT_DIR = "geoip_ratelimit" -WATCHER_SCRIPT = "/usr/local/bin/geoip_crowdsec_watcher.py" -SYSTEMD_SERVICE = "/etc/systemd/system/geoip-crowdsec-watcher.service" -ACTIVE_SHOPS_FILE = "/var/lib/crowdsec/geoip_active_shops.json" +ACTIVE_SHOPS_FILE = "/var/lib/geoip/active_shops.json" # Rate-Limit Defaults DEFAULT_RATE_LIMIT = 30 # Requests pro Minute @@ -523,10 +518,18 @@ def generate_php_bot_patterns(): patterns = [] for bot_name, pattern in BOT_PATTERNS.items(): escaped_pattern = pattern.replace("'", "\\'").replace("/", "\\/") - patterns.append(f"'{bot_name}' => '/{escaped_pattern}/i'") + safe_bot_name = bot_name.replace("'", "\\'") + patterns.append(f"'{safe_bot_name}' => '/{escaped_pattern}/i'") return ",\n ".join(patterns) +def generate_php_generic_patterns(): + patterns = [] + for pattern in GENERIC_BOT_PATTERNS: + patterns.append(f"'{pattern}'") + return ", ".join(patterns) + + # ============================================================================= # CACHE VALIDATION # ============================================================================= @@ -625,92 +628,6 @@ $visitor_ip = $_SERVER['REMOTE_ADDR'] ?? ''; if (empty($visitor_ip)) return; if (filter_var($visitor_ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) return; -$cache_file = __DIR__ . '/{cache_file}'; -$cache_duration = 86400; -$log_file = __DIR__ . '/{log_file}'; -$crowdsec_queue = __DIR__ . '/{crowdsec_queue}'; -$min_ranges = {min_ranges}; -$allowed_countries = [{countries_array}]; - -function download_allowed_ranges($countries) {{ - $ranges = []; - foreach ($countries as $country) {{ - $url = "https://www.ipdeny.com/ipblocks/data/aggregated/$country-aggregated.zone"; - $ctx = stream_context_create(['http' => ['timeout' => 30]]); - $content = @file_get_contents($url, false, $ctx); - if ($content !== false) {{ - foreach (explode("\\n", trim($content)) as $line) {{ - $line = trim($line); - if (!empty($line) && strpos($line, '/') !== false) $ranges[] = $line; - }} - }} - }} - return $ranges; -}} - -function ip_in_range($ip, $cidr) {{ - list($subnet, $mask) = explode('/', $cidr); - $mask_long = -1 << (32 - (int)$mask); - return (ip2long($ip) & $mask_long) == (ip2long($subnet) & $mask_long); -}} - -$allowed_ranges = []; -$cache_valid = false; - -if (file_exists($cache_file) && (time() - filemtime($cache_file)) < $cache_duration) {{ - $cached_data = @file_get_contents($cache_file); - if ($cached_data !== false) {{ - $allowed_ranges = @unserialize($cached_data); - if (is_array($allowed_ranges) && count($allowed_ranges) >= $min_ranges) {{ - $cache_valid = true; - }} else {{ - @unlink($cache_file); - $allowed_ranges = []; - }} - }} -}} - -if (!$cache_valid) {{ - $allowed_ranges = download_allowed_ranges($allowed_countries); - if (is_array($allowed_ranges) && count($allowed_ranges) >= $min_ranges) {{ - @file_put_contents($cache_file, serialize($allowed_ranges)); - $cache_valid = true; - }} else {{ - error_log("GeoIP FAIL-OPEN: Could not load valid IP ranges (got " . count($allowed_ranges) . ", need $min_ranges)"); - return; - }} -}} - -$is_allowed = false; -foreach ($allowed_ranges as $range) {{ - if (ip_in_range($visitor_ip, $range)) {{ $is_allowed = true; break; }} -}} - -if (!$is_allowed) {{ - $timestamp = date('Y-m-d H:i:s'); - $ua = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown'; - $uri = $_SERVER['REQUEST_URI'] ?? '/'; - @file_put_contents($log_file, "[$timestamp] IP: $visitor_ip | UA: $ua | URI: $uri\\n", FILE_APPEND | LOCK_EX); - @file_put_contents($crowdsec_queue, "$timestamp|$visitor_ip|{shop_name}\\n", FILE_APPEND | LOCK_EX); - header('HTTP/1.1 403 Forbidden'); - exit; -}} -''' - -GEOIP_SCRIPT_TEMPLATE_PHP_ONLY = ''' $expiry_date) return; - -$visitor_ip = $_SERVER['REMOTE_ADDR'] ?? ''; -if (empty($visitor_ip)) return; -if (filter_var($visitor_ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) return; - $cache_file = __DIR__ . '/{cache_file}'; $cache_duration = 86400; $log_file = __DIR__ . '/{log_file}'; @@ -782,16 +699,15 @@ if (!$is_allowed) {{ ''' # ============================================================================= -# PHP TEMPLATES - BOT RATE-LIMITING (FIXED: Bots under limit ALLOWED through) +# PHP TEMPLATES - BOT RATE-LIMITING (By Bot-Type, not IP) # ============================================================================= -BOT_ONLY_SCRIPT_TEMPLATE = ''' $expiry_date) return; $log_file = __DIR__ . '/{log_file}'; -$crowdsec_queue = __DIR__ . '/{crowdsec_queue}'; $ratelimit_dir = __DIR__ . '/{ratelimit_dir}'; $bans_dir = $ratelimit_dir . '/bans'; $counts_dir = $ratelimit_dir . '/counts'; // Rate-Limit Configuration -$rate_limit = {rate_limit}; // Requests per minute +$rate_limit = {rate_limit}; // Requests per minute for this bot-type $ban_duration = {ban_duration}; // Ban duration in seconds $window_size = 60; // Window size in seconds (1 minute) $cleanup_probability = 100; // 1 in X chance to run cleanup @@ -813,35 +728,22 @@ $cleanup_probability = 100; // 1 in X chance to run cleanup $visitor_ip = $_SERVER['REMOTE_ADDR'] ?? ''; $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? ''; -// Create hash for this visitor (IP + User-Agent) -$visitor_hash = md5($visitor_ip . '|' . $user_agent); - // Ensure directories exist if (!is_dir($bans_dir)) @mkdir($bans_dir, 0777, true); if (!is_dir($counts_dir)) @mkdir($counts_dir, 0777, true); -// === STEP 1: Check if visitor is banned === -$ban_file = "$bans_dir/$visitor_hash.ban"; -if (file_exists($ban_file)) {{ - $ban_until = (int)@file_get_contents($ban_file); - if (time() < $ban_until) {{ - // Still banned - immediate 403 - header('HTTP/1.1 403 Forbidden'); - header('Retry-After: ' . ($ban_until - time())); - exit; - }} - // Ban expired - remove file - @unlink($ban_file); -}} - -// === STEP 2: Bot detection === +// === Bot Detection === $bot_patterns = [ {bot_patterns} ]; +$generic_patterns = [{generic_patterns}]; + if (empty($user_agent)) return; $detected_bot = null; + +// Check specific patterns first foreach ($bot_patterns as $bot_name => $pattern) {{ if (preg_match($pattern, $user_agent)) {{ $detected_bot = $bot_name; @@ -849,11 +751,42 @@ foreach ($bot_patterns as $bot_name => $pattern) {{ }} }} +// Check generic patterns as fallback +if ($detected_bot === null) {{ + $ua_lower = strtolower($user_agent); + foreach ($generic_patterns as $pattern) {{ + if (strpos($ua_lower, $pattern) !== false) {{ + $detected_bot = "Bot ($pattern)"; + break; + }} + }} +}} + // Not a bot - allow through without any rate limiting if ($detected_bot === null) return; -// === STEP 3: Rate-Limit Check for detected bot === -$count_file = "$counts_dir/$visitor_hash.count"; +// === Create hash based on BOT-TYPE only (not IP!) === +$bot_hash = md5($detected_bot); + +// === STEP 1: Check if this bot-type is banned === +$ban_file = "$bans_dir/$bot_hash.ban"; +if (file_exists($ban_file)) {{ + $ban_until = (int)@file_get_contents($ban_file); + if (time() < $ban_until) {{ + // Bot-type is banned - log and block + $timestamp = date('Y-m-d H:i:s'); + $remaining = $ban_until - time(); + @file_put_contents($log_file, "[$timestamp] BLOCKED (banned): $detected_bot | IP: $visitor_ip | Remaining: {{$remaining}}s\\n", FILE_APPEND | LOCK_EX); + header('HTTP/1.1 403 Forbidden'); + header('Retry-After: ' . $remaining); + exit; + }} + // Ban expired - remove file + @unlink($ban_file); +}} + +// === STEP 2: Rate-Limit Check for this bot-type === +$count_file = "$counts_dir/$bot_hash.count"; $current_time = time(); $count = 1; $window_start = $current_time; @@ -888,17 +821,16 @@ if (file_exists($count_file)) {{ @file_put_contents($count_file, "$window_start|$count", LOCK_EX); }} -// === STEP 4: Check if limit exceeded === +// === STEP 3: Check if limit exceeded === if ($count > $rate_limit) {{ - // Create ban + // Create ban for this bot-type $ban_until = $current_time + $ban_duration; @file_put_contents($ban_file, $ban_until, LOCK_EX); // Log the ban $timestamp = date('Y-m-d H:i:s'); $ban_minutes = $ban_duration / 60; - @file_put_contents($log_file, "[$timestamp] BANNED: $detected_bot | IP: $visitor_ip | Exceeded $rate_limit req/min | Ban: {{$ban_minutes}}m | UA: $user_agent\\n", FILE_APPEND | LOCK_EX); - @file_put_contents($crowdsec_queue, "$timestamp|$visitor_ip|{shop_name}\\n", FILE_APPEND | LOCK_EX); + @file_put_contents($log_file, "[$timestamp] BANNED: $detected_bot | IP: $visitor_ip | Exceeded $rate_limit req/min | Ban: {{$ban_minutes}}m | Total requests: $count\\n", FILE_APPEND | LOCK_EX); // Block this request header('HTTP/1.1 403 Forbidden'); @@ -906,12 +838,12 @@ if ($count > $rate_limit) {{ exit; }} -// === STEP 5: Under limit - log and ALLOW through === +// === STEP 4: Under limit - log and ALLOW through === $timestamp = date('Y-m-d H:i:s'); $uri = $_SERVER['REQUEST_URI'] ?? '/'; @file_put_contents($log_file, "[$timestamp] BOT: $detected_bot | IP: $visitor_ip | Count: $count/$rate_limit | URI: $uri\\n", FILE_APPEND | LOCK_EX); -// === STEP 6: Probabilistic cleanup === +// === STEP 5: Probabilistic cleanup === if (rand(1, $cleanup_probability) === 1) {{ $now = time(); foreach (glob("$bans_dir/*.ban") as $f) {{ @@ -927,219 +859,6 @@ if (rand(1, $cleanup_probability) === 1) {{ return; ''' -BOT_ONLY_SCRIPT_TEMPLATE_NO_CROWDSEC = ''' $expiry_date) return; - -$log_file = __DIR__ . '/{log_file}'; -$ratelimit_dir = __DIR__ . '/{ratelimit_dir}'; -$bans_dir = $ratelimit_dir . '/bans'; -$counts_dir = $ratelimit_dir . '/counts'; - -// Rate-Limit Configuration -$rate_limit = {rate_limit}; // Requests per minute -$ban_duration = {ban_duration}; // Ban duration in seconds -$window_size = 60; // Window size in seconds (1 minute) -$cleanup_probability = 100; // 1 in X chance to run cleanup - -$visitor_ip = $_SERVER['REMOTE_ADDR'] ?? ''; -$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? ''; - -// Create hash for this visitor (IP + User-Agent) -$visitor_hash = md5($visitor_ip . '|' . $user_agent); - -// Ensure directories exist -if (!is_dir($bans_dir)) @mkdir($bans_dir, 0777, true); -if (!is_dir($counts_dir)) @mkdir($counts_dir, 0777, true); - -// === STEP 1: Check if visitor is banned === -$ban_file = "$bans_dir/$visitor_hash.ban"; -if (file_exists($ban_file)) {{ - $ban_until = (int)@file_get_contents($ban_file); - if (time() < $ban_until) {{ - header('HTTP/1.1 403 Forbidden'); - header('Retry-After: ' . ($ban_until - time())); - exit; - }} - @unlink($ban_file); -}} - -// === STEP 2: Bot detection === -$bot_patterns = [ - {bot_patterns} -]; - -if (empty($user_agent)) return; - -$detected_bot = null; -foreach ($bot_patterns as $bot_name => $pattern) {{ - if (preg_match($pattern, $user_agent)) {{ - $detected_bot = $bot_name; - break; - }} -}} - -// Not a bot - allow through -if ($detected_bot === null) return; - -// === STEP 3: Rate-Limit Check === -$count_file = "$counts_dir/$visitor_hash.count"; -$current_time = time(); -$count = 1; -$window_start = $current_time; - -if (file_exists($count_file)) {{ - $fp = @fopen($count_file, 'c+'); - if ($fp && flock($fp, LOCK_EX)) {{ - $content = fread($fp, 100); - if (!empty($content)) {{ - $parts = explode('|', $content); - if (count($parts) === 2) {{ - $window_start = (int)$parts[0]; - $count = (int)$parts[1]; - - if ($current_time - $window_start > $window_size) {{ - $window_start = $current_time; - $count = 1; - }} else {{ - $count++; - }} - }} - }} - ftruncate($fp, 0); - rewind($fp); - fwrite($fp, "$window_start|$count"); - flock($fp, LOCK_UN); - fclose($fp); - }} -}} else {{ - @file_put_contents($count_file, "$window_start|$count", LOCK_EX); -}} - -// === STEP 4: Check if limit exceeded === -if ($count > $rate_limit) {{ - $ban_until = $current_time + $ban_duration; - @file_put_contents($ban_file, $ban_until, LOCK_EX); - - $timestamp = date('Y-m-d H:i:s'); - $ban_minutes = $ban_duration / 60; - @file_put_contents($log_file, "[$timestamp] BANNED: $detected_bot | IP: $visitor_ip | Exceeded $rate_limit req/min | Ban: {{$ban_minutes}}m | UA: $user_agent\\n", FILE_APPEND | LOCK_EX); - - header('HTTP/1.1 403 Forbidden'); - header('Retry-After: ' . $ban_duration); - exit; -}} - -// === STEP 5: Under limit - log and ALLOW through === -$timestamp = date('Y-m-d H:i:s'); -$uri = $_SERVER['REQUEST_URI'] ?? '/'; -@file_put_contents($log_file, "[$timestamp] BOT: $detected_bot | IP: $visitor_ip | Count: $count/$rate_limit | URI: $uri\\n", FILE_APPEND | LOCK_EX); - -// === STEP 6: Probabilistic cleanup === -if (rand(1, $cleanup_probability) === 1) {{ - $now = time(); - foreach (glob("$bans_dir/*.ban") as $f) {{ - $ban_time = (int)@file_get_contents($f); - if ($now > $ban_time) @unlink($f); - }} - foreach (glob("$counts_dir/*.count") as $f) {{ - if ($now - filemtime($f) > $window_size * 2) @unlink($f); - }} -}} - -// Bot is under rate limit - ALLOW through (no exit, no 403) -return; -''' - -# ============================================================================= -# WATCHER SERVICE -# ============================================================================= - -WATCHER_SCRIPT_CONTENT = '''#!/usr/bin/env python3 -import os, sys, time, subprocess, json -from datetime import datetime - -VHOSTS_DIR = "/var/www/vhosts" -QUEUE_FILE = "geoip_crowdsec_queue.log" -ACTIVE_SHOPS_FILE = "/var/lib/crowdsec/geoip_active_shops.json" -PROCESSED_IPS = {} -CHECK_INTERVAL = 5 - -def log(msg): print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}", flush=True) - -def get_active_shops(): - if not os.path.isfile(ACTIVE_SHOPS_FILE): return {} - try: - with open(ACTIVE_SHOPS_FILE, 'r') as f: return json.load(f) - except: return {} - -def add_to_crowdsec(ip, shop): - now = time.time() - if ip in PROCESSED_IPS and (now - PROCESSED_IPS[ip]) < 3600: return True - try: - result = subprocess.run(['cscli', 'decisions', 'add', '--ip', ip, '--duration', '72h', '--type', 'ban', '--reason', f'GeoIP: blocked by {shop}'], capture_output=True, text=True, timeout=10) - if result.returncode == 0: - PROCESSED_IPS[ip] = now - log(f"Added {ip} to CrowdSec (from {shop})") - return True - except: pass - return False - -def process_queue_file(shop_path, shop): - queue_file = os.path.join(shop_path, 'httpdocs', QUEUE_FILE) - if not os.path.isfile(queue_file): return 0 - processed = 0 - try: - with open(queue_file, 'r') as f: lines = f.readlines() - if not lines: return 0 - for line in lines: - parts = line.strip().split('|') - if len(parts) >= 2 and add_to_crowdsec(parts[1], shop): processed += 1 - if processed > 0: - with open(queue_file, 'w') as f: f.write('') - except: pass - return processed - -def main(): - log("GeoIP CrowdSec Watcher started") - while True: - try: - active_shops = get_active_shops() - for shop, info in active_shops.items(): - mode = info.get('mode', 'php+crowdsec') - if mode in ['php+crowdsec', 'bot+crowdsec']: - shop_path = os.path.join(VHOSTS_DIR, shop) - if os.path.isdir(shop_path): process_queue_file(shop_path, shop) - time.sleep(CHECK_INTERVAL) - except KeyboardInterrupt: break - except: time.sleep(CHECK_INTERVAL) - -if __name__ == "__main__": main() -''' - -SYSTEMD_SERVICE_CONTENT = '''[Unit] -Description=GeoIP CrowdSec Watcher Service -After=network.target crowdsec.service - -[Service] -Type=simple -ExecStart=/usr/bin/python3 /usr/local/bin/geoip_crowdsec_watcher.py -Restart=always -RestartSec=10 - -[Install] -WantedBy=multi-user.target -''' - # ============================================================================= # HELPER FUNCTIONS @@ -1155,42 +874,7 @@ def run_command(cmd, capture_output=True): return -1, "", str(e) -def check_crowdsec(): - code, stdout, _ = run_command("systemctl is-active crowdsec") - return code == 0 and stdout.strip() == "active" - - -def install_watcher_service(): - print(" 📦 Installiere CrowdSec-Watcher-Service...") - with open(WATCHER_SCRIPT, 'w') as f: - f.write(WATCHER_SCRIPT_CONTENT) - os.chmod(WATCHER_SCRIPT, 0o755) - with open(SYSTEMD_SERVICE, 'w') as f: - f.write(SYSTEMD_SERVICE_CONTENT) - run_command("systemctl daemon-reload") - run_command("systemctl enable geoip-crowdsec-watcher.service") - run_command("systemctl start geoip-crowdsec-watcher.service") - time.sleep(1) - code, stdout, _ = run_command("systemctl is-active geoip-crowdsec-watcher.service") - if code == 0 and stdout.strip() == "active": - print(" ✅ Service gestartet") - return True - print(" ⚠️ Service konnte nicht gestartet werden") - return False - - -def uninstall_watcher_service(): - print(" 📦 Deinstalliere CrowdSec-Watcher-Service...") - run_command("systemctl stop geoip-crowdsec-watcher.service") - run_command("systemctl disable geoip-crowdsec-watcher.service") - for f in [SYSTEMD_SERVICE, WATCHER_SCRIPT]: - if os.path.isfile(f): - os.remove(f) - run_command("systemctl daemon-reload") - print(" ✅ Service deinstalliert") - - -def add_shop_to_active(shop, mode="php+crowdsec", geo_region="dach", rate_limit=None, ban_duration=None): +def add_shop_to_active(shop, mode="geoip", geo_region="dach", rate_limit=None, ban_duration=None): os.makedirs(os.path.dirname(ACTIVE_SHOPS_FILE), exist_ok=True) shops = {} if os.path.isfile(ACTIVE_SHOPS_FILE): @@ -1213,12 +897,12 @@ def add_shop_to_active(shop, mode="php+crowdsec", geo_region="dach", rate_limit= def get_shop_mode(shop): if not os.path.isfile(ACTIVE_SHOPS_FILE): - return "php+crowdsec" + return "geoip" try: with open(ACTIVE_SHOPS_FILE, 'r') as f: - return json.load(f).get(shop, {}).get("mode", "php+crowdsec") + return json.load(f).get(shop, {}).get("mode", "geoip") except: - return "php+crowdsec" + return "geoip" def get_shop_geo_region(shop): @@ -1273,32 +957,6 @@ def remove_shop_from_active(shop): json.dump(shops, f, indent=2) -def cleanup_crowdsec_decisions(shop): - if not check_crowdsec(): - return - print(f" 🔍 Entferne CrowdSec-Decisions für {shop}...") - total_removed = 0 - for _ in range(50): - code, stdout, _ = run_command(f"cscli decisions list -o raw --limit 0 | grep '{shop}'") - if code != 0 or not stdout.strip(): - break - batch_count = 0 - for line in stdout.strip().split('\n')[:100]: - try: - parts = line.split(',') - if len(parts) >= 3: - ip_field = parts[2].strip() - ip = ip_field.split(':', 1)[1] if ':' in ip_field else ip_field - if ip and run_command(f"cscli decisions delete --ip {ip}")[0] == 0: - batch_count += 1 - except: - continue - total_removed += batch_count - if batch_count == 0: - break - print(f" ✅ {total_removed} Decisions entfernt" if total_removed else " ℹ️ Keine Decisions gefunden") - - def get_available_shops(): shops = [] if not os.path.exists(VHOSTS_DIR): @@ -1330,24 +988,19 @@ def select_geo_region(): def select_mode(): print(f"\n🔧 Wähle den Blocking-Modus:") - print(f" [1] 🌍 GeoIP + CrowdSec (IPs werden an CrowdSec gemeldet)") - print(f" [2] 🌍 Nur GeoIP (keine CrowdSec-Synchronisation)") - print(f" [3] 🤖 Bot-Rate-Limiting (weltweit erreichbar, mit CrowdSec)") - print(f" [4] 🤖 Bot-Rate-Limiting (weltweit erreichbar, ohne CrowdSec)") + print(f" [1] 🌍 GeoIP-Blocking (nur erlaubte Regionen)") + print(f" [2] 🤖 Bot-Rate-Limiting (weltweit erreichbar, Bots limitiert)") - choice = input(f"\nModus wählen [1/2/3/4]: ").strip() + choice = input(f"\nModus wählen [1/2]: ").strip() if choice == "2": - return "php-only" - elif choice == "3": - return "bot+crowdsec" - elif choice == "4": - return "bot-only" - return "php+crowdsec" + return "bot" + return "geoip" def select_rate_limit(): print(f"\n🚦 Rate-Limit Konfiguration:") print(f" (0 = Bots sofort bannen, kein Rate-Limiting)") + print(f" ⚠️ ACHTUNG: Limit gilt pro Bot-TYP, nicht pro IP!") rate_input = input(f" Requests pro Minute bevor Ban [{DEFAULT_RATE_LIMIT}]: ").strip() try: @@ -1374,7 +1027,7 @@ def select_rate_limit(): if rate_limit == 0: print(f"\n 🚫 Rate-Limit: 0 (Bots werden SOFORT gebannt!)") else: - print(f"\n ✅ Rate-Limit: {rate_limit} req/min") + print(f"\n ✅ Rate-Limit: {rate_limit} req/min pro Bot-Typ") print(f" ✅ Ban-Dauer: {ban_minutes} Minuten") return rate_limit, ban_seconds @@ -1382,30 +1035,22 @@ def select_rate_limit(): def get_mode_icon(mode): icons = { - 'php+crowdsec': '🛡️', - 'php-only': '📝', - 'bot+crowdsec': '🤖🛡️', - 'bot-only': '🤖' + 'geoip': '🌍', + 'bot': '🤖' } return icons.get(mode, '❓') def get_mode_description(mode): descriptions = { - 'php+crowdsec': 'GeoIP + CrowdSec', - 'php-only': 'Nur GeoIP', - 'bot+crowdsec': 'Bot-Rate-Limit + CrowdSec', - 'bot-only': 'Nur Bot-Rate-Limit' + 'geoip': 'GeoIP-Blocking', + 'bot': 'Bot-Rate-Limiting' } return descriptions.get(mode, mode) -def is_bot_only_mode(mode): - return mode in ['bot-only', 'bot+crowdsec'] - - -def uses_crowdsec(mode): - return mode in ['php+crowdsec', 'bot+crowdsec'] +def is_bot_mode(mode): + return mode == 'bot' def get_direct_shops(available_shops): @@ -1430,16 +1075,16 @@ def get_link11_shops(available_shops): # MAIN FUNCTIONS # ============================================================================= -def activate_blocking(shop, silent=False, mode="php+crowdsec", geo_region="dach", rate_limit=None, ban_duration=None): +def activate_blocking(shop, silent=False, mode="geoip", geo_region="dach", rate_limit=None, ban_duration=None): httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs') index_php = os.path.join(httpdocs, 'index.php') backup_php = os.path.join(httpdocs, f'index.php{BACKUP_SUFFIX}') blocking_file = os.path.join(httpdocs, BLOCKING_FILE) ratelimit_path = os.path.join(httpdocs, RATELIMIT_DIR) - is_bot_mode = is_bot_only_mode(mode) + bot_mode = is_bot_mode(mode) - if is_bot_mode: + if bot_mode: region_info = get_geo_region_info("none") geo_region = "none" else: @@ -1459,30 +1104,17 @@ def activate_blocking(shop, silent=False, mode="php+crowdsec", geo_region="dach" if not silent: print(f"\n🔧 Aktiviere {region_info['icon']} {region_info['name']} für: {shop}") - if is_bot_mode: - print(f" Modus: Bot-Rate-Limiting (weltweit erreichbar)") - if rate_limit and ban_duration: - print(f" Rate-Limit: {rate_limit} req/min, Ban: {ban_duration // 60} min") + if bot_mode: + print(f" Modus: Bot-Rate-Limiting nach Bot-Typ (weltweit erreichbar)") + if rate_limit is not None and ban_duration is not None: + print(f" Rate-Limit: {rate_limit} req/min pro Bot-Typ, Ban: {ban_duration // 60} min") else: print(f" Erlaubt: {region_info['description']}") - print(f" CrowdSec: {'Ja' if uses_crowdsec(mode) else 'Nein'}") print("=" * 60) - # Step 1: Watcher service - if uses_crowdsec(mode): - crowdsec_shops = [s for s in get_active_shops() if uses_crowdsec(get_shop_mode(s))] - if not crowdsec_shops and check_crowdsec(): - if not silent: - print("\n[1/4] Installiere CrowdSec-Watcher-Service...") - install_watcher_service() - elif not silent: - print("\n[1/4] CrowdSec-Watcher-Service bereits aktiv") - elif not silent: - print("\n[1/4] CrowdSec-Synchronisation deaktiviert") - - # Step 2: PHP blocking + # Step 1: PHP blocking if not silent: - print("\n[2/4] Aktiviere PHP-Blocking...") + print("\n[1/3] Aktiviere PHP-Blocking...") shutil.copy2(index_php, backup_php) @@ -1507,7 +1139,7 @@ def activate_blocking(shop, silent=False, mode="php+crowdsec", geo_region="dach" expiry = datetime.now() + timedelta(hours=72) # Select appropriate template - if is_bot_mode: + if bot_mode: # Create rate-limit directories with open permissions os.makedirs(os.path.join(ratelimit_path, 'bans'), mode=0o777, exist_ok=True) os.makedirs(os.path.join(ratelimit_path, 'counts'), mode=0o777, exist_ok=True) @@ -1520,35 +1152,26 @@ def activate_blocking(shop, silent=False, mode="php+crowdsec", geo_region="dach" if ban_duration is None: ban_duration = DEFAULT_BAN_DURATION * 60 - if uses_crowdsec(mode): - template = BOT_ONLY_SCRIPT_TEMPLATE - else: - template = BOT_ONLY_SCRIPT_TEMPLATE_NO_CROWDSEC - - geoip_content = template.format( + geoip_content = BOT_SCRIPT_TEMPLATE.format( expiry_date=expiry.strftime('%Y-%m-%d %H:%M:%S CET'), expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'), log_file=LOG_FILE, - crowdsec_queue=CROWDSEC_QUEUE_FILE, ratelimit_dir=RATELIMIT_DIR, - shop_name=shop, bot_patterns=generate_php_bot_patterns(), + generic_patterns=generate_php_generic_patterns(), rate_limit=rate_limit, ban_duration=ban_duration, ban_duration_min=ban_duration // 60 ) else: countries_array = generate_php_countries_array(geo_region) - template = GEOIP_SCRIPT_TEMPLATE if uses_crowdsec(mode) else GEOIP_SCRIPT_TEMPLATE_PHP_ONLY - geoip_content = template.format( + geoip_content = GEOIP_SCRIPT_TEMPLATE.format( region_name=region_info['name'], region_description=region_info['description'], expiry_date=expiry.strftime('%Y-%m-%d %H:%M:%S CET'), expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'), cache_file=CACHE_FILE, log_file=LOG_FILE, - crowdsec_queue=CROWDSEC_QUEUE_FILE, - shop_name=shop, countries_array=countries_array, min_ranges=min_ranges ) @@ -1558,14 +1181,14 @@ def activate_blocking(shop, silent=False, mode="php+crowdsec", geo_region="dach" if not silent: print(" ✅ PHP-Blocking aktiviert") - # Step 3: Generate cache (only for GeoIP modes) - if is_bot_mode: + # Step 2: Generate cache (only for GeoIP modes) + if bot_mode: if not silent: - print(f"\n[3/4] Cache-Generierung nicht erforderlich (Bot-Only)") + print(f"\n[2/3] Cache-Generierung nicht erforderlich (Bot-Only)") range_count = 0 else: if not silent: - print(f"\n[3/4] Generiere IP-Range-Cache ({len(region_info['countries'])} Länder)...") + print(f"\n[2/3] Generiere IP-Range-Cache ({len(region_info['countries'])} Länder)...") success, range_count, error = generate_and_validate_cache(httpdocs, geo_region) @@ -1577,11 +1200,11 @@ def activate_blocking(shop, silent=False, mode="php+crowdsec", geo_region="dach" print(f" ⚠️ Cache-Generierung: {error}") print(f" ℹ️ Fail-Open aktiv - Cache wird beim ersten Request neu generiert") - # Step 4: Register + # Step 3: Register if not silent: - print("\n[4/4] Registriere Shop...") + print("\n[3/3] Registriere Shop...") - if is_bot_mode: + if bot_mode: add_shop_to_active(shop, mode, geo_region, rate_limit, ban_duration) else: add_shop_to_active(shop, mode, geo_region) @@ -1591,16 +1214,17 @@ def activate_blocking(shop, silent=False, mode="php+crowdsec", geo_region="dach" print(f"✅ {region_info['icon']} {region_info['name']} aktiviert") print(f" Shop: {shop}") print(f" Modus: {get_mode_description(mode)} {get_mode_icon(mode)}") - if not is_bot_mode: + if not bot_mode: print(f" IP-Ranges: {range_count:,}") print(f" 🛡️ Fail-Open: Bei Cache-Fehlern wird Traffic durchgelassen") else: - print(f" 🤖 {len(BOT_PATTERNS)} Bot-Patterns aktiv") + print(f" 🤖 {len(BOT_PATTERNS)} Bot-Patterns + {len(GENERIC_BOT_PATTERNS)} generische Patterns") if rate_limit == 0: print(f" 🚫 Rate-Limit: 0 (Bots werden SOFORT gebannt!)") else: - print(f" 🚦 Rate-Limit: {rate_limit} req/min, Ban: {ban_duration // 60} min") - print(f" ℹ️ Bots unter dem Limit werden durchgelassen") + print(f" 🚦 Rate-Limit: {rate_limit} req/min PRO BOT-TYP") + print(f" ⏱️ Ban-Dauer: {ban_duration // 60} min") + print(f" ℹ️ Alle Googlebot-Requests teilen sich EIN Limit!") print(f" Gültig bis: {expiry.strftime('%Y-%m-%d %H:%M:%S CET')}") print("=" * 60) @@ -1622,7 +1246,7 @@ def deactivate_blocking(shop, silent=False): print("=" * 60) if not silent: - print("\n[1/5] PHP-Blocking entfernen...") + print("\n[1/3] PHP-Blocking entfernen...") if os.path.isfile(backup_php): shutil.move(backup_php, index_php) @@ -1634,12 +1258,12 @@ def deactivate_blocking(shop, silent=False): with open(index_php, 'w') as f: f.write('\n'.join(lines)) - for f in [os.path.join(httpdocs, x) for x in [BLOCKING_FILE, CACHE_FILE, LOG_FILE, CROWDSEC_QUEUE_FILE]]: + for f in [os.path.join(httpdocs, x) for x in [BLOCKING_FILE, CACHE_FILE, LOG_FILE]]: if os.path.isfile(f): os.remove(f) if not silent: - print("\n[2/5] Rate-Limit Daten entfernen...") + print("\n[2/3] Rate-Limit Daten entfernen...") if os.path.isdir(ratelimit_path): shutil.rmtree(ratelimit_path) if not silent: @@ -1648,24 +1272,9 @@ def deactivate_blocking(shop, silent=False): print(" ℹ️ Kein Rate-Limit Verzeichnis vorhanden") if not silent: - print("\n[3/5] Deregistriere Shop...") + print("\n[3/3] Deregistriere Shop...") remove_shop_from_active(shop) - if not silent: - print("\n[4/5] CrowdSec-Decisions entfernen...") - if uses_crowdsec(shop_mode) and check_crowdsec(): - cleanup_crowdsec_decisions(shop) - elif not silent: - print(" ℹ️ Keine CrowdSec-Synchronisation aktiv") - - if not silent: - print("\n[5/5] Prüfe Watcher-Service...") - crowdsec_shops = [s for s in get_active_shops() if uses_crowdsec(get_shop_mode(s))] - if not crowdsec_shops: - uninstall_watcher_service() - elif not silent: - print(f" ℹ️ Service bleibt aktiv ({len(crowdsec_shops)} Shop(s))") - if not silent: print("\n" + "=" * 60) print(f"✅ Blocking deaktiviert für: {shop}") @@ -1689,9 +1298,9 @@ def activate_all_shops(): print(format_shop_with_link11(shop, prefix=" • ")) mode = select_mode() - is_bot_mode = is_bot_only_mode(mode) + bot_mode = is_bot_mode(mode) - if is_bot_mode: + if bot_mode: geo_region = "none" region_info = get_geo_region_info("none") rate_limit, ban_duration = select_rate_limit() @@ -1701,14 +1310,14 @@ def activate_all_shops(): rate_limit, ban_duration = None, None print(f"\n⚠️ Modus: {get_mode_description(mode)} {get_mode_icon(mode)}") - if not is_bot_mode: + if not bot_mode: print(f" Region: {region_info['icon']} {region_info['name']}") else: if rate_limit == 0: print(f" 🚫 Rate-Limit: 0 (Bots werden SOFORT gebannt!)") else: - print(f" Rate-Limit: {rate_limit} req/min, Ban: {ban_duration // 60} min") - print(f" ℹ️ Bots unter dem Limit werden durchgelassen") + print(f" Rate-Limit: {rate_limit} req/min pro Bot-Typ") + print(f" Ban-Dauer: {ban_duration // 60} min") if input(f"\nFortfahren? (ja/nein): ").strip().lower() not in ['ja', 'j', 'yes', 'y']: print("\n❌ Abgebrochen") @@ -1716,11 +1325,6 @@ def activate_all_shops(): print(f"\n{'=' * 60}") - if uses_crowdsec(mode) and check_crowdsec(): - if not [s for s in get_active_shops() if uses_crowdsec(get_shop_mode(s))]: - print("\n📦 Installiere CrowdSec-Watcher-Service...") - install_watcher_service() - success_count = 0 for i, shop in enumerate(available_shops, 1): print(f"\n[{i}/{len(available_shops)}] {shop}") @@ -1756,41 +1360,32 @@ def activate_all_shops(): expiry = datetime.now() + timedelta(hours=72) - if is_bot_mode: + if bot_mode: os.makedirs(os.path.join(ratelimit_path, 'bans'), mode=0o777, exist_ok=True) os.makedirs(os.path.join(ratelimit_path, 'counts'), mode=0o777, exist_ok=True) os.chmod(ratelimit_path, 0o777) os.chmod(os.path.join(ratelimit_path, 'bans'), 0o777) os.chmod(os.path.join(ratelimit_path, 'counts'), 0o777) - if uses_crowdsec(mode): - template = BOT_ONLY_SCRIPT_TEMPLATE - else: - template = BOT_ONLY_SCRIPT_TEMPLATE_NO_CROWDSEC - - geoip_content = template.format( + geoip_content = BOT_SCRIPT_TEMPLATE.format( expiry_date=expiry.strftime('%Y-%m-%d %H:%M:%S CET'), expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'), log_file=LOG_FILE, - crowdsec_queue=CROWDSEC_QUEUE_FILE, ratelimit_dir=RATELIMIT_DIR, - shop_name=shop, bot_patterns=generate_php_bot_patterns(), + generic_patterns=generate_php_generic_patterns(), rate_limit=rate_limit, ban_duration=ban_duration, ban_duration_min=ban_duration // 60 ) else: - template = GEOIP_SCRIPT_TEMPLATE if uses_crowdsec(mode) else GEOIP_SCRIPT_TEMPLATE_PHP_ONLY - geoip_content = template.format( + geoip_content = GEOIP_SCRIPT_TEMPLATE.format( region_name=region_info['name'], region_description=region_info['description'], expiry_date=expiry.strftime('%Y-%m-%d %H:%M:%S CET'), expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'), cache_file=CACHE_FILE, log_file=LOG_FILE, - crowdsec_queue=CROWDSEC_QUEUE_FILE, - shop_name=shop, countries_array=generate_php_countries_array(geo_region), min_ranges=MIN_RANGES.get(geo_region, 1000) ) @@ -1798,7 +1393,7 @@ def activate_all_shops(): with open(blocking_file, 'w', encoding='utf-8') as f: f.write(geoip_content) - if is_bot_mode: + if bot_mode: add_shop_to_active(shop, mode, geo_region, rate_limit, ban_duration) print(f" ✅ Aktiviert ({len(BOT_PATTERNS)} Bot-Patterns, {rate_limit} req/min)") else: @@ -1811,14 +1406,13 @@ def activate_all_shops(): print(f"\n{'=' * 60}") print(f" ✅ {success_count} Shop(s) aktiviert") - if not is_bot_mode: + if not bot_mode: print(f" 🛡️ Fail-Open bei Cache-Fehlern aktiv") else: if rate_limit == 0: print(f" 🚫 Rate-Limit: 0 (Bots werden SOFORT gebannt!)") else: - print(f" 🚦 Rate-Limit: {rate_limit} req/min, Ban: {ban_duration // 60} min") - print(f" ℹ️ Bots unter dem Limit werden durchgelassen") + print(f" 🚦 Rate-Limit: {rate_limit} req/min pro Bot-Typ") print(f"{'=' * 60}") @@ -1852,9 +1446,9 @@ def activate_direct_shops_only(): print(f" {COLOR_GREEN}• {shop} [Link11]{COLOR_RESET}") mode = select_mode() - is_bot_mode = is_bot_only_mode(mode) + bot_mode = is_bot_mode(mode) - if is_bot_mode: + if bot_mode: geo_region = "none" region_info = get_geo_region_info("none") rate_limit, ban_duration = select_rate_limit() @@ -1864,14 +1458,14 @@ def activate_direct_shops_only(): rate_limit, ban_duration = None, None print(f"\n⚠️ Modus: {get_mode_description(mode)} {get_mode_icon(mode)}") - if not is_bot_mode: + if not bot_mode: print(f" Region: {region_info['icon']} {region_info['name']}") else: if rate_limit == 0: print(f" 🚫 Rate-Limit: 0 (Bots werden SOFORT gebannt!)") else: - print(f" Rate-Limit: {rate_limit} req/min, Ban: {ban_duration // 60} min") - print(f" ℹ️ Bots unter dem Limit werden durchgelassen") + print(f" Rate-Limit: {rate_limit} req/min pro Bot-Typ") + print(f" Ban-Dauer: {ban_duration // 60} min") if input(f"\nFortfahren? (ja/nein): ").strip().lower() not in ['ja', 'j', 'yes', 'y']: print("\n❌ Abgebrochen") @@ -1879,11 +1473,6 @@ def activate_direct_shops_only(): print(f"\n{'=' * 60}") - if uses_crowdsec(mode) and check_crowdsec(): - if not [s for s in get_active_shops() if uses_crowdsec(get_shop_mode(s))]: - print("\n📦 Installiere CrowdSec-Watcher-Service...") - install_watcher_service() - success_count = 0 for i, shop in enumerate(direct_shops, 1): print(f"\n[{i}/{len(direct_shops)}] {shop}") @@ -1919,41 +1508,32 @@ def activate_direct_shops_only(): expiry = datetime.now() + timedelta(hours=72) - if is_bot_mode: + if bot_mode: os.makedirs(os.path.join(ratelimit_path, 'bans'), mode=0o777, exist_ok=True) os.makedirs(os.path.join(ratelimit_path, 'counts'), mode=0o777, exist_ok=True) os.chmod(ratelimit_path, 0o777) os.chmod(os.path.join(ratelimit_path, 'bans'), 0o777) os.chmod(os.path.join(ratelimit_path, 'counts'), 0o777) - if uses_crowdsec(mode): - template = BOT_ONLY_SCRIPT_TEMPLATE - else: - template = BOT_ONLY_SCRIPT_TEMPLATE_NO_CROWDSEC - - geoip_content = template.format( + geoip_content = BOT_SCRIPT_TEMPLATE.format( expiry_date=expiry.strftime('%Y-%m-%d %H:%M:%S CET'), expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'), log_file=LOG_FILE, - crowdsec_queue=CROWDSEC_QUEUE_FILE, ratelimit_dir=RATELIMIT_DIR, - shop_name=shop, bot_patterns=generate_php_bot_patterns(), + generic_patterns=generate_php_generic_patterns(), rate_limit=rate_limit, ban_duration=ban_duration, ban_duration_min=ban_duration // 60 ) else: - template = GEOIP_SCRIPT_TEMPLATE if uses_crowdsec(mode) else GEOIP_SCRIPT_TEMPLATE_PHP_ONLY - geoip_content = template.format( + geoip_content = GEOIP_SCRIPT_TEMPLATE.format( region_name=region_info['name'], region_description=region_info['description'], expiry_date=expiry.strftime('%Y-%m-%d %H:%M:%S CET'), expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'), cache_file=CACHE_FILE, log_file=LOG_FILE, - crowdsec_queue=CROWDSEC_QUEUE_FILE, - shop_name=shop, countries_array=generate_php_countries_array(geo_region), min_ranges=MIN_RANGES.get(geo_region, 1000) ) @@ -1961,7 +1541,7 @@ def activate_direct_shops_only(): with open(blocking_file, 'w', encoding='utf-8') as f: f.write(geoip_content) - if is_bot_mode: + if bot_mode: add_shop_to_active(shop, mode, geo_region, rate_limit, ban_duration) print(f" ✅ Aktiviert ({len(BOT_PATTERNS)} Bot-Patterns, {rate_limit} req/min)") else: @@ -1975,14 +1555,13 @@ def activate_direct_shops_only(): print(f"\n{'=' * 60}") print(f" ✅ {success_count} direkte Shop(s) aktiviert") print(f" ⏭️ {len(link11_shops)} Link11-Shop(s) übersprungen") - if not is_bot_mode: + if not bot_mode: print(f" 🛡️ Fail-Open bei Cache-Fehlern aktiv") else: if rate_limit == 0: print(f" 🚫 Rate-Limit: 0 (Bots werden SOFORT gebannt!)") else: - print(f" 🚦 Rate-Limit: {rate_limit} req/min, Ban: {ban_duration // 60} min") - print(f" ℹ️ Bots unter dem Limit werden durchgelassen") + print(f" 🚦 Rate-Limit: {rate_limit} req/min pro Bot-Typ") print(f"{'=' * 60}") @@ -2017,7 +1596,7 @@ def deactivate_all_shops(): if os.path.isfile(backup_php): shutil.move(backup_php, index_php) - for f in [os.path.join(httpdocs, x) for x in [BLOCKING_FILE, CACHE_FILE, LOG_FILE, CROWDSEC_QUEUE_FILE]]: + for f in [os.path.join(httpdocs, x) for x in [BLOCKING_FILE, CACHE_FILE, LOG_FILE]]: if os.path.isfile(f): os.remove(f) @@ -2025,11 +1604,8 @@ def deactivate_all_shops(): shutil.rmtree(ratelimit_path) remove_shop_from_active(shop) - if check_crowdsec(): - cleanup_crowdsec_decisions(shop) print(f" ✅ Deaktiviert") - uninstall_watcher_service() print(f"\n{'=' * 60}") print(f" ✅ Alle Shops deaktiviert") print(f"{'=' * 60}") @@ -2039,7 +1615,7 @@ def get_shop_log_stats(shop): httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs') log_file = os.path.join(httpdocs, LOG_FILE) ratelimit_path = os.path.join(httpdocs, RATELIMIT_DIR) - php_blocks = 0 + log_entries = 0 ips = {} bots = {} bans = 0 @@ -2047,7 +1623,7 @@ def get_shop_log_stats(shop): if os.path.isfile(log_file): with open(log_file, 'r') as f: for line in f: - php_blocks += 1 + log_entries += 1 ip, ua = None, 'Unknown' detected_bot = None @@ -2062,6 +1638,11 @@ def get_shop_log_stats(shop): detected_bot = line.split('BOT: ')[1].split(' |')[0].strip() except: pass + elif 'BLOCKED (banned): ' in line: + try: + detected_bot = line.split('BLOCKED (banned): ')[1].split(' |')[0].strip() + except: + pass if 'IP: ' in line: try: @@ -2091,6 +1672,7 @@ def get_shop_log_stats(shop): bots[bot_name] = bots.get(bot_name, 0) + 1 active_bans = 0 + banned_bots = [] bans_dir = os.path.join(ratelimit_path, 'bans') if os.path.isdir(bans_dir): now = time.time() @@ -2101,10 +1683,13 @@ def get_shop_log_stats(shop): ban_until = int(f.read().strip()) if now < ban_until: active_bans += 1 + # Try to find bot name from hash + bot_hash = ban_file.replace('.ban', '') + banned_bots.append(bot_hash[:8]) except: pass - return php_blocks, ips, bots, get_shop_activation_time(shop), bans, active_bans + return log_entries, ips, bots, get_shop_activation_time(shop), bans, active_bans, banned_bots def show_logs(shop): @@ -2113,13 +1698,13 @@ def show_logs(shop): shop_mode = get_shop_mode(shop) shop_geo = get_shop_geo_region(shop) region_info = get_geo_region_info(shop_geo) - is_bot_mode = is_bot_only_mode(shop_mode) + bot_mode = is_bot_mode(shop_mode) - blocks, ips, bots, activation_time, total_bans, active_bans = get_shop_log_stats(shop) + entries, ips, bots, activation_time, total_bans, active_bans, banned_bots = get_shop_log_stats(shop) if activation_time: runtime = (datetime.now() - activation_time).total_seconds() / 60 - req_min = blocks / runtime if runtime > 0 else 0 + req_min = entries / runtime if runtime > 0 else 0 else: runtime, req_min = 0, 0 @@ -2127,21 +1712,21 @@ def show_logs(shop): print(f"📊 {shop} | {region_info['icon']} {region_info['name']} {get_mode_icon(shop_mode)}") print(f"{'═' * 70}") print(f"⏱️ Laufzeit: {format_duration(runtime)}") - print(f"📈 Log-Einträge: {blocks} ({req_min:.1f} req/min)") + print(f"📈 Log-Einträge: {entries} ({req_min:.1f} req/min)") - if not is_bot_mode: + if not bot_mode: valid, count, err = validate_existing_cache(httpdocs, shop_geo) print(f"✅ Cache: {count:,} Ranges" if valid else f"⚠️ Cache: {err}") else: rate_limit, ban_duration = get_shop_rate_limit_config(shop) - print(f"🤖 Bot-Patterns: {len(BOT_PATTERNS)} aktiv") - if rate_limit and ban_duration: - print(f"🚦 Rate-Limit: {rate_limit} req/min, Ban: {ban_duration // 60} min") - print(f"🚫 Bans: {total_bans} ausgelöst, {active_bans} aktiv") + print(f"🤖 Bot-Patterns: {len(BOT_PATTERNS)} + {len(GENERIC_BOT_PATTERNS)} generische") + if rate_limit is not None and ban_duration is not None: + print(f"🚦 Rate-Limit: {rate_limit} req/min PRO BOT-TYP, Ban: {ban_duration // 60} min") + print(f"🚫 Bans: {total_bans} ausgelöst, {active_bans} Bot-Typen aktuell gebannt") if bots: - print(f"\n🤖 Bot-Statistik:") - for bot_name, count in sorted(bots.items(), key=lambda x: x[1], reverse=True)[:10]: + print(f"\n🤖 Bot-Statistik (nach Bot-Typ):") + for bot_name, count in sorted(bots.items(), key=lambda x: x[1], reverse=True)[:15]: bar = "█" * min(count // 5, 20) if count > 0 else "█" print(f" {bot_name}: {count}x {bar}") @@ -2170,29 +1755,29 @@ def show_all_logs(): print(f"{'═' * 70}") print(f" {COLOR_GREEN}Grün = hinter Link11{COLOR_RESET} | {COLOR_RED}Rot = Direkt{COLOR_RESET}") - total_php_blocks = 0 + total_log_entries = 0 total_bans = 0 total_active_bans = 0 - shop_php_stats = {} + shop_stats = {} all_ips = {} all_bots = {} total_minutes = 0 for shop in active_shops: - blocks, ips, bots, activation_time, bans, active_bans = get_shop_log_stats(shop) - total_php_blocks += blocks + entries, ips, bots, activation_time, bans, active_bans, banned_bots = get_shop_log_stats(shop) + total_log_entries += entries total_bans += bans total_active_bans += active_bans if activation_time: runtime_minutes = (datetime.now() - activation_time).total_seconds() / 60 - req_min = blocks / runtime_minutes if runtime_minutes > 0 else 0 + req_min = entries / runtime_minutes if runtime_minutes > 0 else 0 else: runtime_minutes = 0 req_min = 0 - shop_php_stats[shop] = { - 'blocks': blocks, + shop_stats[shop] = { + 'entries': entries, 'runtime_minutes': runtime_minutes, 'req_min': req_min, 'ips': ips, @@ -2217,24 +1802,13 @@ def show_all_logs(): for bot_name, count in bots.items(): all_bots[bot_name] = all_bots.get(bot_name, 0) + count - total_req_min = total_php_blocks / total_minutes if total_minutes > 0 else 0 + total_req_min = total_log_entries / total_minutes if total_minutes > 0 else 0 - crowdsec_stats = {} - if check_crowdsec(): - code, stdout, _ = run_command("cscli decisions list -o raw --limit 0") - if code == 0 and stdout: - for line in stdout.strip().split('\n')[1:]: - for shop in active_shops: - if shop in line: - crowdsec_stats[shop] = crowdsec_stats.get(shop, 0) + 1 - break - total_crowdsec = sum(crowdsec_stats.values()) - - print(f"\n📝 Log-Einträge gesamt: {total_php_blocks} (⌀ {total_req_min:.1f} req/min, Laufzeit: {format_duration(total_minutes)})") - if shop_php_stats: - for shop in sorted(shop_php_stats.keys()): - stats = shop_php_stats[shop] - count = stats['blocks'] + print(f"\n📝 Log-Einträge gesamt: {total_log_entries} (⌀ {total_req_min:.1f} req/min, Laufzeit: {format_duration(total_minutes)})") + if shop_stats: + for shop in sorted(shop_stats.keys()): + stats = shop_stats[shop] + count = stats['entries'] req_min = stats['req_min'] runtime = stats['runtime_minutes'] bar = "█" * min(int(req_min * 2), 20) if req_min > 0 else "" @@ -2270,9 +1844,9 @@ def show_all_logs(): print(f" │ └─➤ Top: {top_ip_addr} ({display_name}) - {top_ip_count}x, {top_ip_req_min:.1f} req/min") if total_bans > 0 or total_active_bans > 0: - print(f"\n🚫 Rate-Limit Bans: {total_bans} ausgelöst, {total_active_bans} aktiv") - for shop in sorted(shop_php_stats.keys()): - stats = shop_php_stats[shop] + print(f"\n🚫 Rate-Limit Bans: {total_bans} ausgelöst, {total_active_bans} Bot-Typen aktuell gebannt") + for shop in sorted(shop_stats.keys()): + stats = shop_stats[shop] if stats['bans'] > 0 or stats['active_bans'] > 0: link11_info = check_link11(shop) if link11_info['is_link11']: @@ -2281,28 +1855,10 @@ def show_all_logs(): shop_colored = f"{COLOR_RED}{shop}{COLOR_RESET}" bar = "█" * min(stats['bans'] // 2, 20) if stats['bans'] > 0 else "" - print(f" ├─ {shop_colored}: {stats['bans']} bans ({stats['active_bans']} aktiv) {bar}") - - print(f"\n🛡️ CrowdSec-Bans gesamt: {total_crowdsec}") - if crowdsec_stats: - for shop in sorted(crowdsec_stats.keys()): - count = crowdsec_stats[shop] - bar = "█" * min(count // 10, 20) if count > 0 else "" - - link11_info = check_link11(shop) - if link11_info['is_link11']: - shop_colored = f"{COLOR_GREEN}{shop}{COLOR_RESET}" - else: - shop_colored = f"{COLOR_RED}{shop}{COLOR_RESET}" - - print(f" ├─ {shop_colored}: {count} {bar}") - elif check_crowdsec(): - print(" └─ Keine aktiven Bans") - else: - print(" └─ CrowdSec nicht verfügbar") + print(f" ├─ {shop_colored}: {stats['bans']} bans ({stats['active_bans']} Bot-Typen aktiv) {bar}") if all_bots: - print(f"\n🤖 Bot-Statistik (alle Shops):") + print(f"\n🤖 Bot-Statistik nach Bot-Typ (alle Shops):") for bot_name, count in sorted(all_bots.items(), key=lambda x: x[1], reverse=True)[:15]: bar = "█" * min(count // 5, 20) if count > 0 else "█" print(f" {bot_name}: {count}x {bar}") @@ -2352,16 +1908,12 @@ def show_all_logs(): def main(): print("\n" + "=" * 60) - print(" GeoIP Shop Blocker Manager v3.5.0") + print(" GeoIP Shop Blocker Manager v4.0.0") print(" 🇩🇪🇦🇹🇨🇭 DACH | 🇪🇺 Eurozone+GB | 🤖 Bot-Rate-Limiting") print(" 🛡️ Mit Cache-Validierung und Fail-Open") - print(" 🚦 Bots unter Rate-Limit werden durchgelassen") + print(" 🚦 Rate-Limiting nach BOT-TYP (nicht IP)") print("=" * 60) - print(f" {'✅' if check_crowdsec() else '⚠️ '} CrowdSec") - code, stdout, _ = run_command("systemctl is-active geoip-crowdsec-watcher.service") - print(f" {'✅' if code == 0 and stdout.strip() == 'active' else '⚠️ '} Watcher-Service") - while True: print("\n" + "-" * 40) print("[1] Aktivieren (einzeln)") @@ -2389,9 +1941,9 @@ def main(): idx = int(input("\nShop wählen: ").strip()) - 1 if 0 <= idx < len(available): mode = select_mode() - is_bot_mode = is_bot_only_mode(mode) + bot_mode = is_bot_mode(mode) - if is_bot_mode: + if bot_mode: geo = "none" region_info = get_geo_region_info("none") rate_limit, ban_duration = select_rate_limit() @@ -2452,23 +2004,23 @@ def main(): region_info = get_geo_region_info(get_shop_geo_region(shop)) shop_mode = get_shop_mode(shop) mode_icon = get_mode_icon(shop_mode) - is_bot_mode = is_bot_only_mode(shop_mode) - blocks, _, bots, activation_time, total_bans, active_bans = get_shop_log_stats(shop) + bot_mode = is_bot_mode(shop_mode) + entries, _, bots, activation_time, total_bans, active_bans, _ = get_shop_log_stats(shop) runtime = (datetime.now() - activation_time).total_seconds() / 60 if activation_time else 0 httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs') print(f" {format_shop_with_link11(shop)} {region_info['icon']} {mode_icon}") - if is_bot_mode: + if bot_mode: rate_limit, ban_duration = get_shop_rate_limit_config(shop) - rl_str = f", {rate_limit} req/min" if rate_limit else "" - ban_str = f", {active_bans} aktive Bans" if active_bans > 0 else "" - print(f" {blocks} log entries, {format_duration(runtime)}, {len(BOT_PATTERNS)} Bot-Patterns{rl_str}{ban_str}") + rl_str = f", {rate_limit} req/min/Bot-Typ" if rate_limit else "" + ban_str = f", {active_bans} Bot-Typen gebannt" if active_bans > 0 else "" + print(f" {entries} log entries, {format_duration(runtime)}, {len(BOT_PATTERNS)} Patterns{rl_str}{ban_str}") else: valid, count, _ = validate_existing_cache(httpdocs, get_shop_geo_region(shop)) cache_str = f"✅{count:,}" if valid else "⚠️" - print(f" {blocks} blocks, {format_duration(runtime)}, Cache: {cache_str}") + print(f" {entries} blocks, {format_duration(runtime)}, Cache: {cache_str}") elif choice == "5": activate_all_shops()