From 91f86c76e58fb87f656db2b42087b41ebacf1791 Mon Sep 17 00:00:00 2001 From: thomasciesla Date: Tue, 9 Dec 2025 07:49:46 +0100 Subject: [PATCH] geoip_shop_manager.py aktualisiert --- geoip_shop_manager.py | 163 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 146 insertions(+), 17 deletions(-) diff --git a/geoip_shop_manager.py b/geoip_shop_manager.py index 0cb430d..ce1ca7e 100644 --- a/geoip_shop_manager.py +++ b/geoip_shop_manager.py @@ -12,9 +12,23 @@ import subprocess import json import time import re +import socket from datetime import datetime, timedelta from pathlib import Path +# ANSI Color Codes +COLOR_GREEN = "\033[92m" +COLOR_RED = "\033[91m" +COLOR_YELLOW = "\033[93m" +COLOR_RESET = "\033[0m" +COLOR_BOLD = "\033[1m" + +# Link11 IP +LINK11_IP = "128.65.223.106" + +# Cache for DNS lookups (to avoid repeated lookups) +DNS_CACHE = {} + # Configuration VHOSTS_DIR = "/var/www/vhosts" BACKUP_SUFFIX = ".geoip_backup" @@ -172,6 +186,41 @@ def detect_bot(user_agent): return 'Unbekannt' +def check_link11(domain): + """Check if domain resolves to Link11 IP""" + global DNS_CACHE + + # Check cache first + if domain in DNS_CACHE: + return DNS_CACHE[domain] + + try: + ip = socket.gethostbyname(domain) + is_link11 = (ip == LINK11_IP) + DNS_CACHE[domain] = {'is_link11': is_link11, 'ip': ip} + return DNS_CACHE[domain] + except socket.gaierror: + DNS_CACHE[domain] = {'is_link11': False, 'ip': 'N/A'} + return DNS_CACHE[domain] + + +def format_shop_with_link11(shop, prefix="", show_index=None): + """Format shop name with Link11 color coding""" + link11_info = check_link11(shop) + + if link11_info['is_link11']: + color = COLOR_GREEN + suffix = " [Link11]" + else: + color = COLOR_RED + suffix = " [Direkt]" + + if show_index is not None: + return f" [{show_index}] {color}{shop}{suffix}{COLOR_RESET}" + else: + return f"{prefix}{color}{shop}{suffix}{COLOR_RESET}" + + # PHP GeoIP blocking script (no exec, just logging) GEOIP_SCRIPT = ''' {'blocks': N, 'activation': datetime, 'req_min': float} - all_ips = {} # ip -> {'count': N, 'ua': user_agent} + shop_php_stats = {} # shop -> {'blocks': N, 'activation': datetime, 'req_min': float, 'ips': {}} + all_ips = {} # ip -> {'count': N, 'ua': user_agent, 'shops': {shop: count}} total_minutes = 0 # Collect PHP stats @@ -1312,7 +1369,8 @@ def show_all_logs(): 'blocks': blocks, 'activation': activation_time, 'runtime_minutes': runtime_minutes, - 'req_min': req_min + 'req_min': req_min, + 'ips': ips # Store IPs per shop for top IP display } if runtime_minutes > total_minutes: @@ -1320,8 +1378,9 @@ def show_all_logs(): for ip, data in ips.items(): if ip not in all_ips: - all_ips[ip] = {'count': 0, 'ua': data['ua']} + all_ips[ip] = {'count': 0, 'ua': data['ua'], 'shops': {}} all_ips[ip]['count'] += data['count'] + all_ips[ip]['shops'][shop] = data['count'] # Keep the most informative UA if data['ua'] != 'Unknown' and all_ips[ip]['ua'] == 'Unknown': all_ips[ip]['ua'] = data['ua'] @@ -1333,7 +1392,7 @@ def show_all_logs(): crowdsec_stats = get_crowdsec_stats_by_shop() total_crowdsec = sum(crowdsec_stats.values()) - # Display PHP blocks with req/min + # Display PHP blocks with req/min and top IP per shop print(f"\n📝 PHP-Blocks 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()): @@ -1344,6 +1403,24 @@ def show_all_logs(): bar = "█" * min(int(req_min * 2), 20) if req_min > 0 else "" runtime_str = format_duration(runtime) if runtime > 0 else "?" print(f" ├─ {shop}: {count} ({req_min:.1f} req/min, seit {runtime_str}) {bar}") + + # Show top IP for this shop + shop_ips = stats['ips'] + if shop_ips and count > 0: + top_ip = max(shop_ips.items(), key=lambda x: x[1]['count']) + top_ip_addr = top_ip[0] + top_ip_count = top_ip[1]['count'] + top_ip_ua = top_ip[1]['ua'] + top_ip_bot = detect_bot(top_ip_ua) + top_ip_req_min = top_ip_count / runtime if runtime > 0 else 0 + + # Show bot name or shortened UA if unknown + if top_ip_bot == 'Unbekannt': + display_name = (top_ip_ua[:40] + '...') if len(top_ip_ua) > 43 else top_ip_ua + else: + display_name = top_ip_bot + + print(f" │ └─➤ Top: {top_ip_addr} ({display_name}) - {top_ip_count}x, {top_ip_req_min:.1f} req/min") # Display CrowdSec bans print(f"\n🛡️ CrowdSec-Bans gesamt: {total_crowdsec}") @@ -1357,7 +1434,7 @@ def show_all_logs(): else: print(" └─ CrowdSec nicht verfügbar") - # Top blocked IPs with bot detection + # Top blocked IPs with bot detection, req/min, and top shop if all_ips: print(f"\n🔥 Top 100 blockierte IPs (alle Shops):") sorted_ips = sorted(all_ips.items(), key=lambda x: x[1]['count'], reverse=True)[:100] @@ -1365,8 +1442,35 @@ def show_all_logs(): count = data['count'] ua = data['ua'] bot_name = detect_bot(ua) + shops_data = data['shops'] + + # Calculate req/min for this IP + ip_req_min = count / total_minutes if total_minutes > 0 else 0 + + # Find top shop for this IP + if shops_data: + top_shop = max(shops_data.items(), key=lambda x: x[1]) + top_shop_name = top_shop[0] + top_shop_count = top_shop[1] + # Shorten shop name if too long + if len(top_shop_name) > 25: + top_shop_display = top_shop_name[:22] + '...' + else: + top_shop_display = top_shop_name + else: + top_shop_display = "?" + top_shop_count = 0 + + # Show bot name or shortened UA if unknown + if bot_name == 'Unbekannt': + display_name = (ua[:35] + '...') if len(ua) > 38 else ua + if display_name == 'Unknown': + display_name = 'Unbekannt' + else: + display_name = bot_name + bar = "█" * min(count // 5, 20) if count > 0 else "█" - print(f" {ip} ({bot_name}): {count} {bar}") + print(f" {ip} ({display_name}): {count} ({ip_req_min:.1f} req/min) → {top_shop_display} [{top_shop_count}x] {bar}") print(f"\n{'═' * 70}") @@ -1414,7 +1518,7 @@ def show_logs(shop): print("=" * 70) print(f"Gesamt: {len(lines)}") - # Show top IPs with bot detection + # Show top IPs with bot detection and req/min if ips: print(f"\n🔥 Top 20 blockierte IPs:") sorted_ips = sorted(ips.items(), key=lambda x: x[1]['count'], reverse=True)[:20] @@ -1422,8 +1526,18 @@ def show_logs(shop): count = data['count'] ua = data['ua'] bot_name = detect_bot(ua) + ip_req_min = count / runtime_minutes if runtime_minutes > 0 else 0 + + # Show bot name or shortened UA if unknown + if bot_name == 'Unbekannt': + display_name = (ua[:40] + '...') if len(ua) > 43 else ua + if display_name == 'Unknown': + display_name = 'Unbekannt' + else: + display_name = bot_name + bar = "█" * min(count // 5, 20) if count > 0 else "█" - print(f" {ip} ({bot_name}): {count} {bar}") + print(f" {ip} ({display_name}): {count} ({ip_req_min:.1f} req/min) {bar}") else: print(f"\nℹ️ Keine PHP-Logs für {shop}") @@ -1516,8 +1630,9 @@ def main(): continue print("\n📋 Verfügbare Shops:") + print(f" {COLOR_GREEN}Grün = hinter Link11{COLOR_RESET} | {COLOR_RED}Rot = Direkt{COLOR_RESET}") for i, shop in enumerate(available_shops, 1): - print(f" [{i}] {shop}") + print(format_shop_with_link11(shop, show_index=i)) shop_choice = input("\nWähle einen Shop: ").strip() try: @@ -1554,10 +1669,14 @@ def main(): continue print("\n📋 Aktive Shops:") + print(f" {COLOR_GREEN}Grün = hinter Link11{COLOR_RESET} | {COLOR_RED}Rot = Direkt{COLOR_RESET}") for i, shop in enumerate(active_shops, 1): mode = get_shop_mode(shop) mode_icon = "🛡️" if mode == "php+crowdsec" else "📝" - print(f" [{i}] {shop} {mode_icon}") + link11_info = check_link11(shop) + color = COLOR_GREEN if link11_info['is_link11'] else COLOR_RED + link11_tag = "[Link11]" if link11_info['is_link11'] else "[Direkt]" + print(f" [{i}] {color}{shop} {link11_tag}{COLOR_RESET} {mode_icon}") shop_choice = input("\nWähle einen Shop: ").strip() try: @@ -1579,11 +1698,15 @@ def main(): continue print("\n📋 Logs anzeigen für:") + print(f" {COLOR_GREEN}Grün = hinter Link11{COLOR_RESET} | {COLOR_RED}Rot = Direkt{COLOR_RESET}") print(f" [0] 📊 ALLE Shops (Zusammenfassung)") for i, shop in enumerate(active_shops, 1): mode = get_shop_mode(shop) mode_icon = "🛡️" if mode == "php+crowdsec" else "📝" - print(f" [{i}] {shop} {mode_icon}") + link11_info = check_link11(shop) + color = COLOR_GREEN if link11_info['is_link11'] else COLOR_RED + link11_tag = "[Link11]" if link11_info['is_link11'] else "[Direkt]" + print(f" [{i}] {color}{shop} {link11_tag}{COLOR_RESET} {mode_icon}") shop_choice = input("\nWähle eine Option: ").strip() try: @@ -1603,6 +1726,7 @@ def main(): print(f"\n📊 Status:") print(f" Shops gesamt: {len(shops)}") print(f" Aktive DACH-Blockings: {len(active_shops)}") + print(f" {COLOR_GREEN}Grün = hinter Link11{COLOR_RESET} | {COLOR_RED}Rot = Direkt{COLOR_RESET}") if active_shops: for shop in active_shops: mode = get_shop_mode(shop) @@ -1619,7 +1743,12 @@ def main(): req_min = 0 runtime_str = "?" - print(f" ✓ {shop} [{mode_text}] {mode_icon} - {blocks} blocks ({req_min:.1f} req/min, {runtime_str})") + # Link11 check + link11_info = check_link11(shop) + color = COLOR_GREEN if link11_info['is_link11'] else COLOR_RED + link11_tag = "[Link11]" if link11_info['is_link11'] else "[Direkt]" + + print(f" ✓ {color}{shop} {link11_tag}{COLOR_RESET} [{mode_text}] {mode_icon} - {blocks} blocks ({req_min:.1f} req/min, {runtime_str})") elif choice == "5": activate_all_shops()