geoip_shop_manager.py aktualisiert
This commit is contained in:
@@ -10,7 +10,7 @@ Supports three modes:
|
|||||||
- php-only: GeoIP blocking without CrowdSec
|
- php-only: GeoIP blocking without CrowdSec
|
||||||
- bot-only: Block only bots, shop remains globally accessible
|
- bot-only: Block only bots, shop remains globally accessible
|
||||||
|
|
||||||
v3.3.0: Added option to activate only direct shops (not behind Link11)
|
v3.4.0: Added file-based rate-limiting for bot blocking
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -45,10 +45,15 @@ BLOCKING_FILE = "geoip_blocking.php"
|
|||||||
CACHE_FILE = "geoip_ip_ranges.cache"
|
CACHE_FILE = "geoip_ip_ranges.cache"
|
||||||
LOG_FILE = "geoip_blocked.log"
|
LOG_FILE = "geoip_blocked.log"
|
||||||
CROWDSEC_QUEUE_FILE = "geoip_crowdsec_queue.log"
|
CROWDSEC_QUEUE_FILE = "geoip_crowdsec_queue.log"
|
||||||
|
RATELIMIT_DIR = "geoip_ratelimit"
|
||||||
WATCHER_SCRIPT = "/usr/local/bin/geoip_crowdsec_watcher.py"
|
WATCHER_SCRIPT = "/usr/local/bin/geoip_crowdsec_watcher.py"
|
||||||
SYSTEMD_SERVICE = "/etc/systemd/system/geoip-crowdsec-watcher.service"
|
SYSTEMD_SERVICE = "/etc/systemd/system/geoip-crowdsec-watcher.service"
|
||||||
ACTIVE_SHOPS_FILE = "/var/lib/crowdsec/geoip_active_shops.json"
|
ACTIVE_SHOPS_FILE = "/var/lib/crowdsec/geoip_active_shops.json"
|
||||||
|
|
||||||
|
# Rate-Limit Defaults
|
||||||
|
DEFAULT_RATE_LIMIT = 30 # Requests pro Minute
|
||||||
|
DEFAULT_BAN_DURATION = 5 # Minuten
|
||||||
|
|
||||||
# Minimum expected IP ranges per region (for validation)
|
# Minimum expected IP ranges per region (for validation)
|
||||||
MIN_RANGES = {
|
MIN_RANGES = {
|
||||||
"dach": 1000, # DE+AT+CH should have at least 1000 ranges
|
"dach": 1000, # DE+AT+CH should have at least 1000 ranges
|
||||||
@@ -239,7 +244,7 @@ else {{ echo "OK:" . count($data); }}
|
|||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# PHP TEMPLATES WITH FAIL-OPEN
|
# PHP TEMPLATES WITH FAIL-OPEN AND RATE-LIMITING
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
GEOIP_SCRIPT_TEMPLATE = '''<?php
|
GEOIP_SCRIPT_TEMPLATE = '''<?php
|
||||||
@@ -414,9 +419,10 @@ if (!$is_allowed) {{
|
|||||||
|
|
||||||
BOT_ONLY_SCRIPT_TEMPLATE = '''<?php
|
BOT_ONLY_SCRIPT_TEMPLATE = '''<?php
|
||||||
/**
|
/**
|
||||||
* Bot Blocking Script - Worldwide Access
|
* Bot Blocking Script with Rate-Limiting - Worldwide Access
|
||||||
* Valid until: {expiry_date}
|
* Valid until: {expiry_date}
|
||||||
* Blocks known bots/crawlers, allows all human traffic globally
|
* Blocks known bots/crawlers, allows all human traffic globally
|
||||||
|
* Rate-Limit: {rate_limit} req/min, Ban: {ban_duration} min
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$expiry_date = strtotime('{expiry_timestamp}');
|
$expiry_date = strtotime('{expiry_timestamp}');
|
||||||
@@ -424,12 +430,45 @@ if (time() > $expiry_date) return;
|
|||||||
|
|
||||||
$log_file = __DIR__ . '/{log_file}';
|
$log_file = __DIR__ . '/{log_file}';
|
||||||
$crowdsec_queue = __DIR__ . '/{crowdsec_queue}';
|
$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
|
||||||
|
$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, 0755, true);
|
||||||
|
if (!is_dir($counts_dir)) @mkdir($counts_dir, 0755, 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_patterns = [
|
$bot_patterns = [
|
||||||
{bot_patterns}
|
{bot_patterns}
|
||||||
];
|
];
|
||||||
|
|
||||||
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
|
||||||
if (empty($user_agent)) return;
|
if (empty($user_agent)) return;
|
||||||
|
|
||||||
$detected_bot = null;
|
$detected_bot = null;
|
||||||
@@ -440,34 +479,139 @@ foreach ($bot_patterns as $bot_name => $pattern) {{
|
|||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
if ($detected_bot !== null) {{
|
// Not a bot - allow through
|
||||||
$timestamp = date('Y-m-d H:i:s');
|
if ($detected_bot === null) return;
|
||||||
$visitor_ip = $_SERVER['REMOTE_ADDR'] ?? 'Unknown';
|
|
||||||
$uri = $_SERVER['REQUEST_URI'] ?? '/';
|
// === STEP 3: Rate-Limit Check ===
|
||||||
@file_put_contents($log_file, "[$timestamp] BOT: $detected_bot | IP: $visitor_ip | UA: $user_agent | URI: $uri\\n", FILE_APPEND | LOCK_EX);
|
$count_file = "$counts_dir/$visitor_hash.count";
|
||||||
@file_put_contents($crowdsec_queue, "$timestamp|$visitor_ip|{shop_name}\\n", FILE_APPEND | LOCK_EX);
|
$current_time = time();
|
||||||
header('HTTP/1.1 403 Forbidden');
|
$count = 1;
|
||||||
exit;
|
$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) {{
|
||||||
|
// New window
|
||||||
|
$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 ===
|
||||||
|
$is_banned = false;
|
||||||
|
if ($count > $rate_limit) {{
|
||||||
|
// Create ban
|
||||||
|
$ban_until = $current_time + $ban_duration;
|
||||||
|
@file_put_contents($ban_file, $ban_until, LOCK_EX);
|
||||||
|
$is_banned = true;
|
||||||
|
}}
|
||||||
|
|
||||||
|
// === STEP 5: Log and block ===
|
||||||
|
$timestamp = date('Y-m-d H:i:s');
|
||||||
|
$uri = $_SERVER['REQUEST_URI'] ?? '/';
|
||||||
|
|
||||||
|
if ($is_banned) {{
|
||||||
|
$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);
|
||||||
|
}} else {{
|
||||||
|
@file_put_contents($log_file, "[$timestamp] BOT: $detected_bot | IP: $visitor_ip | Count: $count/$rate_limit | URI: $uri\\n", FILE_APPEND | LOCK_EX);
|
||||||
|
}}
|
||||||
|
|
||||||
|
@file_put_contents($crowdsec_queue, "$timestamp|$visitor_ip|{shop_name}\\n", FILE_APPEND | LOCK_EX);
|
||||||
|
|
||||||
|
// === STEP 6: Probabilistic cleanup ===
|
||||||
|
if (rand(1, $cleanup_probability) === 1) {{
|
||||||
|
$now = time();
|
||||||
|
// Clean expired bans
|
||||||
|
foreach (glob("$bans_dir/*.ban") as $f) {{
|
||||||
|
$ban_time = (int)@file_get_contents($f);
|
||||||
|
if ($now > $ban_time) @unlink($f);
|
||||||
|
}}
|
||||||
|
// Clean old count files (older than 2x window)
|
||||||
|
foreach (glob("$counts_dir/*.count") as $f) {{
|
||||||
|
if ($now - filemtime($f) > $window_size * 2) @unlink($f);
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
header('HTTP/1.1 403 Forbidden');
|
||||||
|
if ($is_banned) {{
|
||||||
|
header('Retry-After: ' . $ban_duration);
|
||||||
|
}}
|
||||||
|
exit;
|
||||||
'''
|
'''
|
||||||
|
|
||||||
BOT_ONLY_SCRIPT_TEMPLATE_NO_CROWDSEC = '''<?php
|
BOT_ONLY_SCRIPT_TEMPLATE_NO_CROWDSEC = '''<?php
|
||||||
/**
|
/**
|
||||||
* Bot Blocking Script - Worldwide Access (No CrowdSec)
|
* Bot Blocking Script with Rate-Limiting - Worldwide Access (No CrowdSec)
|
||||||
* Valid until: {expiry_date}
|
* Valid until: {expiry_date}
|
||||||
* Blocks known bots/crawlers, allows all human traffic globally
|
* Blocks known bots/crawlers, allows all human traffic globally
|
||||||
|
* Rate-Limit: {rate_limit} req/min, Ban: {ban_duration} min
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$expiry_date = strtotime('{expiry_timestamp}');
|
$expiry_date = strtotime('{expiry_timestamp}');
|
||||||
if (time() > $expiry_date) return;
|
if (time() > $expiry_date) return;
|
||||||
|
|
||||||
$log_file = __DIR__ . '/{log_file}';
|
$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, 0755, true);
|
||||||
|
if (!is_dir($counts_dir)) @mkdir($counts_dir, 0755, 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_patterns = [
|
$bot_patterns = [
|
||||||
{bot_patterns}
|
{bot_patterns}
|
||||||
];
|
];
|
||||||
|
|
||||||
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
|
||||||
if (empty($user_agent)) return;
|
if (empty($user_agent)) return;
|
||||||
|
|
||||||
$detected_bot = null;
|
$detected_bot = null;
|
||||||
@@ -478,14 +622,84 @@ foreach ($bot_patterns as $bot_name => $pattern) {{
|
|||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
if ($detected_bot !== null) {{
|
// Not a bot - allow through
|
||||||
$timestamp = date('Y-m-d H:i:s');
|
if ($detected_bot === null) return;
|
||||||
$visitor_ip = $_SERVER['REMOTE_ADDR'] ?? 'Unknown';
|
|
||||||
$uri = $_SERVER['REQUEST_URI'] ?? '/';
|
// === STEP 3: Rate-Limit Check ===
|
||||||
@file_put_contents($log_file, "[$timestamp] BOT: $detected_bot | IP: $visitor_ip | UA: $user_agent | URI: $uri\\n", FILE_APPEND | LOCK_EX);
|
$count_file = "$counts_dir/$visitor_hash.count";
|
||||||
header('HTTP/1.1 403 Forbidden');
|
$current_time = time();
|
||||||
exit;
|
$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) {{
|
||||||
|
// New window
|
||||||
|
$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 ===
|
||||||
|
$is_banned = false;
|
||||||
|
if ($count > $rate_limit) {{
|
||||||
|
// Create ban
|
||||||
|
$ban_until = $current_time + $ban_duration;
|
||||||
|
@file_put_contents($ban_file, $ban_until, LOCK_EX);
|
||||||
|
$is_banned = true;
|
||||||
|
}}
|
||||||
|
|
||||||
|
// === STEP 5: Log and block ===
|
||||||
|
$timestamp = date('Y-m-d H:i:s');
|
||||||
|
$uri = $_SERVER['REQUEST_URI'] ?? '/';
|
||||||
|
|
||||||
|
if ($is_banned) {{
|
||||||
|
$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);
|
||||||
|
}} else {{
|
||||||
|
@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();
|
||||||
|
// Clean expired bans
|
||||||
|
foreach (glob("$bans_dir/*.ban") as $f) {{
|
||||||
|
$ban_time = (int)@file_get_contents($f);
|
||||||
|
if ($now > $ban_time) @unlink($f);
|
||||||
|
}}
|
||||||
|
// Clean old count files (older than 2x window)
|
||||||
|
foreach (glob("$counts_dir/*.count") as $f) {{
|
||||||
|
if ($now - filemtime($f) > $window_size * 2) @unlink($f);
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
header('HTTP/1.1 403 Forbidden');
|
||||||
|
if ($is_banned) {{
|
||||||
|
header('Retry-After: ' . $ban_duration);
|
||||||
|
}}
|
||||||
|
exit;
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -619,18 +833,23 @@ def uninstall_watcher_service():
|
|||||||
print(" ✅ Service deinstalliert")
|
print(" ✅ Service deinstalliert")
|
||||||
|
|
||||||
|
|
||||||
def add_shop_to_active(shop, mode="php+crowdsec", geo_region="dach"):
|
def add_shop_to_active(shop, mode="php+crowdsec", geo_region="dach", rate_limit=None, ban_duration=None):
|
||||||
os.makedirs(os.path.dirname(ACTIVE_SHOPS_FILE), exist_ok=True)
|
os.makedirs(os.path.dirname(ACTIVE_SHOPS_FILE), exist_ok=True)
|
||||||
shops = {}
|
shops = {}
|
||||||
if os.path.isfile(ACTIVE_SHOPS_FILE):
|
if os.path.isfile(ACTIVE_SHOPS_FILE):
|
||||||
with open(ACTIVE_SHOPS_FILE, 'r') as f:
|
with open(ACTIVE_SHOPS_FILE, 'r') as f:
|
||||||
shops = json.load(f)
|
shops = json.load(f)
|
||||||
shops[shop] = {
|
shop_data = {
|
||||||
"activated": datetime.now().isoformat(),
|
"activated": datetime.now().isoformat(),
|
||||||
"expiry": (datetime.now() + timedelta(hours=72)).isoformat(),
|
"expiry": (datetime.now() + timedelta(hours=72)).isoformat(),
|
||||||
"mode": mode,
|
"mode": mode,
|
||||||
"geo_region": geo_region
|
"geo_region": geo_region
|
||||||
}
|
}
|
||||||
|
if rate_limit is not None:
|
||||||
|
shop_data["rate_limit"] = rate_limit
|
||||||
|
if ban_duration is not None:
|
||||||
|
shop_data["ban_duration"] = ban_duration
|
||||||
|
shops[shop] = shop_data
|
||||||
with open(ACTIVE_SHOPS_FILE, 'w') as f:
|
with open(ACTIVE_SHOPS_FILE, 'w') as f:
|
||||||
json.dump(shops, f, indent=2)
|
json.dump(shops, f, indent=2)
|
||||||
|
|
||||||
@@ -655,6 +874,18 @@ def get_shop_geo_region(shop):
|
|||||||
return "dach"
|
return "dach"
|
||||||
|
|
||||||
|
|
||||||
|
def get_shop_rate_limit_config(shop):
|
||||||
|
"""Get rate limit configuration for a shop"""
|
||||||
|
if not os.path.isfile(ACTIVE_SHOPS_FILE):
|
||||||
|
return None, None
|
||||||
|
try:
|
||||||
|
with open(ACTIVE_SHOPS_FILE, 'r') as f:
|
||||||
|
shop_data = json.load(f).get(shop, {})
|
||||||
|
return shop_data.get("rate_limit"), shop_data.get("ban_duration")
|
||||||
|
except:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
def get_shop_activation_time(shop):
|
def get_shop_activation_time(shop):
|
||||||
if not os.path.isfile(ACTIVE_SHOPS_FILE):
|
if not os.path.isfile(ACTIVE_SHOPS_FILE):
|
||||||
return None
|
return None
|
||||||
@@ -763,6 +994,36 @@ def select_mode():
|
|||||||
return "php+crowdsec"
|
return "php+crowdsec"
|
||||||
|
|
||||||
|
|
||||||
|
def select_rate_limit():
|
||||||
|
"""Ask user for rate-limit configuration. Returns (rate_limit, ban_duration_seconds)"""
|
||||||
|
print(f"\n🚦 Rate-Limit Konfiguration:")
|
||||||
|
|
||||||
|
# Rate limit
|
||||||
|
rate_input = input(f" Requests pro Minute bevor Ban [{DEFAULT_RATE_LIMIT}]: ").strip()
|
||||||
|
try:
|
||||||
|
rate_limit = int(rate_input) if rate_input else DEFAULT_RATE_LIMIT
|
||||||
|
if rate_limit < 1:
|
||||||
|
rate_limit = DEFAULT_RATE_LIMIT
|
||||||
|
except ValueError:
|
||||||
|
rate_limit = DEFAULT_RATE_LIMIT
|
||||||
|
|
||||||
|
# Ban duration
|
||||||
|
ban_input = input(f" Ban-Dauer in Minuten [{DEFAULT_BAN_DURATION}]: ").strip()
|
||||||
|
try:
|
||||||
|
ban_minutes = int(ban_input) if ban_input else DEFAULT_BAN_DURATION
|
||||||
|
if ban_minutes < 1:
|
||||||
|
ban_minutes = DEFAULT_BAN_DURATION
|
||||||
|
except ValueError:
|
||||||
|
ban_minutes = DEFAULT_BAN_DURATION
|
||||||
|
|
||||||
|
ban_seconds = ban_minutes * 60
|
||||||
|
|
||||||
|
print(f"\n ✅ Rate-Limit: {rate_limit} req/min")
|
||||||
|
print(f" ✅ Ban-Dauer: {ban_minutes} Minuten")
|
||||||
|
|
||||||
|
return rate_limit, ban_seconds
|
||||||
|
|
||||||
|
|
||||||
def get_mode_icon(mode):
|
def get_mode_icon(mode):
|
||||||
"""Return icon for mode"""
|
"""Return icon for mode"""
|
||||||
icons = {
|
icons = {
|
||||||
@@ -819,11 +1080,12 @@ def get_link11_shops(available_shops):
|
|||||||
# MAIN FUNCTIONS
|
# MAIN FUNCTIONS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
def activate_blocking(shop, silent=False, mode="php+crowdsec", geo_region="dach"):
|
def activate_blocking(shop, silent=False, mode="php+crowdsec", geo_region="dach", rate_limit=None, ban_duration=None):
|
||||||
httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs')
|
httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs')
|
||||||
index_php = os.path.join(httpdocs, 'index.php')
|
index_php = os.path.join(httpdocs, 'index.php')
|
||||||
backup_php = os.path.join(httpdocs, f'index.php{BACKUP_SUFFIX}')
|
backup_php = os.path.join(httpdocs, f'index.php{BACKUP_SUFFIX}')
|
||||||
blocking_file = os.path.join(httpdocs, BLOCKING_FILE)
|
blocking_file = os.path.join(httpdocs, BLOCKING_FILE)
|
||||||
|
ratelimit_path = os.path.join(httpdocs, RATELIMIT_DIR)
|
||||||
|
|
||||||
is_bot_mode = is_bot_only_mode(mode)
|
is_bot_mode = is_bot_only_mode(mode)
|
||||||
|
|
||||||
@@ -849,6 +1111,8 @@ def activate_blocking(shop, silent=False, mode="php+crowdsec", geo_region="dach"
|
|||||||
print(f"\n🔧 Aktiviere {region_info['icon']} {region_info['name']} für: {shop}")
|
print(f"\n🔧 Aktiviere {region_info['icon']} {region_info['name']} für: {shop}")
|
||||||
if is_bot_mode:
|
if is_bot_mode:
|
||||||
print(f" Modus: Nur Bot-Blocking (weltweit erreichbar)")
|
print(f" Modus: Nur Bot-Blocking (weltweit erreichbar)")
|
||||||
|
if rate_limit and ban_duration:
|
||||||
|
print(f" Rate-Limit: {rate_limit} req/min, Ban: {ban_duration // 60} min")
|
||||||
else:
|
else:
|
||||||
print(f" Erlaubt: {region_info['description']}")
|
print(f" Erlaubt: {region_info['description']}")
|
||||||
print(f" CrowdSec: {'Ja' if uses_crowdsec(mode) else 'Nein'}")
|
print(f" CrowdSec: {'Ja' if uses_crowdsec(mode) else 'Nein'}")
|
||||||
@@ -894,6 +1158,16 @@ def activate_blocking(shop, silent=False, mode="php+crowdsec", geo_region="dach"
|
|||||||
|
|
||||||
# Select appropriate template
|
# Select appropriate template
|
||||||
if is_bot_mode:
|
if is_bot_mode:
|
||||||
|
# Create rate-limit directories
|
||||||
|
os.makedirs(os.path.join(ratelimit_path, 'bans'), exist_ok=True)
|
||||||
|
os.makedirs(os.path.join(ratelimit_path, 'counts'), exist_ok=True)
|
||||||
|
|
||||||
|
# Use defaults if not specified
|
||||||
|
if rate_limit is None:
|
||||||
|
rate_limit = DEFAULT_RATE_LIMIT
|
||||||
|
if ban_duration is None:
|
||||||
|
ban_duration = DEFAULT_BAN_DURATION * 60 # Convert to seconds
|
||||||
|
|
||||||
if uses_crowdsec(mode):
|
if uses_crowdsec(mode):
|
||||||
template = BOT_ONLY_SCRIPT_TEMPLATE
|
template = BOT_ONLY_SCRIPT_TEMPLATE
|
||||||
else:
|
else:
|
||||||
@@ -904,8 +1178,11 @@ def activate_blocking(shop, silent=False, mode="php+crowdsec", geo_region="dach"
|
|||||||
expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'),
|
expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
log_file=LOG_FILE,
|
log_file=LOG_FILE,
|
||||||
crowdsec_queue=CROWDSEC_QUEUE_FILE,
|
crowdsec_queue=CROWDSEC_QUEUE_FILE,
|
||||||
|
ratelimit_dir=RATELIMIT_DIR,
|
||||||
shop_name=shop,
|
shop_name=shop,
|
||||||
bot_patterns=generate_php_bot_patterns()
|
bot_patterns=generate_php_bot_patterns(),
|
||||||
|
rate_limit=rate_limit,
|
||||||
|
ban_duration=ban_duration
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
countries_array = generate_php_countries_array(geo_region)
|
countries_array = generate_php_countries_array(geo_region)
|
||||||
@@ -950,6 +1227,10 @@ def activate_blocking(shop, silent=False, mode="php+crowdsec", geo_region="dach"
|
|||||||
# Step 4: Register
|
# Step 4: Register
|
||||||
if not silent:
|
if not silent:
|
||||||
print("\n[4/4] Registriere Shop...")
|
print("\n[4/4] Registriere Shop...")
|
||||||
|
|
||||||
|
if is_bot_mode:
|
||||||
|
add_shop_to_active(shop, mode, geo_region, rate_limit, ban_duration)
|
||||||
|
else:
|
||||||
add_shop_to_active(shop, mode, geo_region)
|
add_shop_to_active(shop, mode, geo_region)
|
||||||
|
|
||||||
if not silent:
|
if not silent:
|
||||||
@@ -962,6 +1243,7 @@ def activate_blocking(shop, silent=False, mode="php+crowdsec", geo_region="dach"
|
|||||||
print(f" 🛡️ Fail-Open: Bei Cache-Fehlern wird Traffic durchgelassen")
|
print(f" 🛡️ Fail-Open: Bei Cache-Fehlern wird Traffic durchgelassen")
|
||||||
else:
|
else:
|
||||||
print(f" 🤖 {len(BOT_PATTERNS)} Bot-Patterns aktiv")
|
print(f" 🤖 {len(BOT_PATTERNS)} Bot-Patterns aktiv")
|
||||||
|
print(f" 🚦 Rate-Limit: {rate_limit} req/min, Ban: {ban_duration // 60} min")
|
||||||
print(f" Gültig bis: {expiry.strftime('%Y-%m-%d %H:%M:%S CET')}")
|
print(f" Gültig bis: {expiry.strftime('%Y-%m-%d %H:%M:%S CET')}")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|
||||||
@@ -972,6 +1254,7 @@ def deactivate_blocking(shop, silent=False):
|
|||||||
httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs')
|
httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs')
|
||||||
index_php = os.path.join(httpdocs, 'index.php')
|
index_php = os.path.join(httpdocs, 'index.php')
|
||||||
backup_php = os.path.join(httpdocs, f'index.php{BACKUP_SUFFIX}')
|
backup_php = os.path.join(httpdocs, f'index.php{BACKUP_SUFFIX}')
|
||||||
|
ratelimit_path = os.path.join(httpdocs, RATELIMIT_DIR)
|
||||||
|
|
||||||
shop_mode = get_shop_mode(shop)
|
shop_mode = get_shop_mode(shop)
|
||||||
shop_geo = get_shop_geo_region(shop)
|
shop_geo = get_shop_geo_region(shop)
|
||||||
@@ -983,7 +1266,7 @@ def deactivate_blocking(shop, silent=False):
|
|||||||
|
|
||||||
# Restore backup
|
# Restore backup
|
||||||
if not silent:
|
if not silent:
|
||||||
print("\n[1/4] PHP-Blocking entfernen...")
|
print("\n[1/5] PHP-Blocking entfernen...")
|
||||||
|
|
||||||
if os.path.isfile(backup_php):
|
if os.path.isfile(backup_php):
|
||||||
shutil.move(backup_php, index_php)
|
shutil.move(backup_php, index_php)
|
||||||
@@ -995,23 +1278,34 @@ def deactivate_blocking(shop, silent=False):
|
|||||||
with open(index_php, 'w') as f:
|
with open(index_php, 'w') as f:
|
||||||
f.write('\n'.join(lines))
|
f.write('\n'.join(lines))
|
||||||
|
|
||||||
|
# Remove files
|
||||||
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, CROWDSEC_QUEUE_FILE]]:
|
||||||
if os.path.isfile(f):
|
if os.path.isfile(f):
|
||||||
os.remove(f)
|
os.remove(f)
|
||||||
|
|
||||||
|
# Remove rate-limit directory
|
||||||
if not silent:
|
if not silent:
|
||||||
print("\n[2/4] Deregistriere Shop...")
|
print("\n[2/5] Rate-Limit Daten entfernen...")
|
||||||
|
if os.path.isdir(ratelimit_path):
|
||||||
|
shutil.rmtree(ratelimit_path)
|
||||||
|
if not silent:
|
||||||
|
print(" ✅ Rate-Limit Verzeichnis gelöscht")
|
||||||
|
elif not silent:
|
||||||
|
print(" ℹ️ Kein Rate-Limit Verzeichnis vorhanden")
|
||||||
|
|
||||||
|
if not silent:
|
||||||
|
print("\n[3/5] Deregistriere Shop...")
|
||||||
remove_shop_from_active(shop)
|
remove_shop_from_active(shop)
|
||||||
|
|
||||||
if not silent:
|
if not silent:
|
||||||
print("\n[3/4] CrowdSec-Decisions entfernen...")
|
print("\n[4/5] CrowdSec-Decisions entfernen...")
|
||||||
if uses_crowdsec(shop_mode) and check_crowdsec():
|
if uses_crowdsec(shop_mode) and check_crowdsec():
|
||||||
cleanup_crowdsec_decisions(shop)
|
cleanup_crowdsec_decisions(shop)
|
||||||
elif not silent:
|
elif not silent:
|
||||||
print(" ℹ️ Keine CrowdSec-Synchronisation aktiv")
|
print(" ℹ️ Keine CrowdSec-Synchronisation aktiv")
|
||||||
|
|
||||||
if not silent:
|
if not silent:
|
||||||
print("\n[4/4] Prüfe Watcher-Service...")
|
print("\n[5/5] Prüfe Watcher-Service...")
|
||||||
crowdsec_shops = [s for s in get_active_shops() if uses_crowdsec(get_shop_mode(s))]
|
crowdsec_shops = [s for s in get_active_shops() if uses_crowdsec(get_shop_mode(s))]
|
||||||
if not crowdsec_shops:
|
if not crowdsec_shops:
|
||||||
uninstall_watcher_service()
|
uninstall_watcher_service()
|
||||||
@@ -1046,13 +1340,17 @@ def activate_all_shops():
|
|||||||
if is_bot_mode:
|
if is_bot_mode:
|
||||||
geo_region = "none"
|
geo_region = "none"
|
||||||
region_info = get_geo_region_info("none")
|
region_info = get_geo_region_info("none")
|
||||||
|
rate_limit, ban_duration = select_rate_limit()
|
||||||
else:
|
else:
|
||||||
geo_region = select_geo_region()
|
geo_region = select_geo_region()
|
||||||
region_info = get_geo_region_info(geo_region)
|
region_info = get_geo_region_info(geo_region)
|
||||||
|
rate_limit, ban_duration = None, None
|
||||||
|
|
||||||
print(f"\n⚠️ Modus: {get_mode_description(mode)} {get_mode_icon(mode)}")
|
print(f"\n⚠️ Modus: {get_mode_description(mode)} {get_mode_icon(mode)}")
|
||||||
if not is_bot_mode:
|
if not is_bot_mode:
|
||||||
print(f" Region: {region_info['icon']} {region_info['name']}")
|
print(f" Region: {region_info['icon']} {region_info['name']}")
|
||||||
|
else:
|
||||||
|
print(f" Rate-Limit: {rate_limit} req/min, Ban: {ban_duration // 60} min")
|
||||||
|
|
||||||
if input(f"\nFortfahren? (ja/nein): ").strip().lower() not in ['ja', 'j', 'yes', 'y']:
|
if input(f"\nFortfahren? (ja/nein): ").strip().lower() not in ['ja', 'j', 'yes', 'y']:
|
||||||
print("\n❌ Abgebrochen")
|
print("\n❌ Abgebrochen")
|
||||||
@@ -1073,6 +1371,7 @@ def activate_all_shops():
|
|||||||
index_php = os.path.join(httpdocs, 'index.php')
|
index_php = os.path.join(httpdocs, 'index.php')
|
||||||
backup_php = os.path.join(httpdocs, f'index.php{BACKUP_SUFFIX}')
|
backup_php = os.path.join(httpdocs, f'index.php{BACKUP_SUFFIX}')
|
||||||
blocking_file = os.path.join(httpdocs, BLOCKING_FILE)
|
blocking_file = os.path.join(httpdocs, BLOCKING_FILE)
|
||||||
|
ratelimit_path = os.path.join(httpdocs, RATELIMIT_DIR)
|
||||||
|
|
||||||
if os.path.isfile(backup_php) or not os.path.isfile(index_php):
|
if os.path.isfile(backup_php) or not os.path.isfile(index_php):
|
||||||
print(f" ⚠️ Übersprungen")
|
print(f" ⚠️ Übersprungen")
|
||||||
@@ -1100,6 +1399,10 @@ def activate_all_shops():
|
|||||||
expiry = datetime.now() + timedelta(hours=72)
|
expiry = datetime.now() + timedelta(hours=72)
|
||||||
|
|
||||||
if is_bot_mode:
|
if is_bot_mode:
|
||||||
|
# Create rate-limit directories
|
||||||
|
os.makedirs(os.path.join(ratelimit_path, 'bans'), exist_ok=True)
|
||||||
|
os.makedirs(os.path.join(ratelimit_path, 'counts'), exist_ok=True)
|
||||||
|
|
||||||
if uses_crowdsec(mode):
|
if uses_crowdsec(mode):
|
||||||
template = BOT_ONLY_SCRIPT_TEMPLATE
|
template = BOT_ONLY_SCRIPT_TEMPLATE
|
||||||
else:
|
else:
|
||||||
@@ -1110,8 +1413,11 @@ def activate_all_shops():
|
|||||||
expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'),
|
expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
log_file=LOG_FILE,
|
log_file=LOG_FILE,
|
||||||
crowdsec_queue=CROWDSEC_QUEUE_FILE,
|
crowdsec_queue=CROWDSEC_QUEUE_FILE,
|
||||||
|
ratelimit_dir=RATELIMIT_DIR,
|
||||||
shop_name=shop,
|
shop_name=shop,
|
||||||
bot_patterns=generate_php_bot_patterns()
|
bot_patterns=generate_php_bot_patterns(),
|
||||||
|
rate_limit=rate_limit,
|
||||||
|
ban_duration=ban_duration
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
template = GEOIP_SCRIPT_TEMPLATE if uses_crowdsec(mode) else GEOIP_SCRIPT_TEMPLATE_PHP_ONLY
|
template = GEOIP_SCRIPT_TEMPLATE if uses_crowdsec(mode) else GEOIP_SCRIPT_TEMPLATE_PHP_ONLY
|
||||||
@@ -1132,8 +1438,8 @@ def activate_all_shops():
|
|||||||
f.write(geoip_content)
|
f.write(geoip_content)
|
||||||
|
|
||||||
if is_bot_mode:
|
if is_bot_mode:
|
||||||
add_shop_to_active(shop, mode, geo_region)
|
add_shop_to_active(shop, mode, geo_region, rate_limit, ban_duration)
|
||||||
print(f" ✅ Aktiviert ({len(BOT_PATTERNS)} Bot-Patterns)")
|
print(f" ✅ Aktiviert ({len(BOT_PATTERNS)} Bot-Patterns, {rate_limit} req/min)")
|
||||||
else:
|
else:
|
||||||
print(f" ⏳ Cache generieren...")
|
print(f" ⏳ Cache generieren...")
|
||||||
cache_ok, count, _ = generate_and_validate_cache(httpdocs, geo_region)
|
cache_ok, count, _ = generate_and_validate_cache(httpdocs, geo_region)
|
||||||
@@ -1146,6 +1452,8 @@ def activate_all_shops():
|
|||||||
print(f" ✅ {success_count} Shop(s) aktiviert")
|
print(f" ✅ {success_count} Shop(s) aktiviert")
|
||||||
if not is_bot_mode:
|
if not is_bot_mode:
|
||||||
print(f" 🛡️ Fail-Open bei Cache-Fehlern aktiv")
|
print(f" 🛡️ Fail-Open bei Cache-Fehlern aktiv")
|
||||||
|
else:
|
||||||
|
print(f" 🚦 Rate-Limit: {rate_limit} req/min, Ban: {ban_duration // 60} min")
|
||||||
print(f"{'=' * 60}")
|
print(f"{'=' * 60}")
|
||||||
|
|
||||||
|
|
||||||
@@ -1190,13 +1498,17 @@ def activate_direct_shops_only():
|
|||||||
if is_bot_mode:
|
if is_bot_mode:
|
||||||
geo_region = "none"
|
geo_region = "none"
|
||||||
region_info = get_geo_region_info("none")
|
region_info = get_geo_region_info("none")
|
||||||
|
rate_limit, ban_duration = select_rate_limit()
|
||||||
else:
|
else:
|
||||||
geo_region = select_geo_region()
|
geo_region = select_geo_region()
|
||||||
region_info = get_geo_region_info(geo_region)
|
region_info = get_geo_region_info(geo_region)
|
||||||
|
rate_limit, ban_duration = None, None
|
||||||
|
|
||||||
print(f"\n⚠️ Modus: {get_mode_description(mode)} {get_mode_icon(mode)}")
|
print(f"\n⚠️ Modus: {get_mode_description(mode)} {get_mode_icon(mode)}")
|
||||||
if not is_bot_mode:
|
if not is_bot_mode:
|
||||||
print(f" Region: {region_info['icon']} {region_info['name']}")
|
print(f" Region: {region_info['icon']} {region_info['name']}")
|
||||||
|
else:
|
||||||
|
print(f" Rate-Limit: {rate_limit} req/min, Ban: {ban_duration // 60} min")
|
||||||
print(f" Aktiviert: {len(direct_shops)} direkte Shop(s)")
|
print(f" Aktiviert: {len(direct_shops)} direkte Shop(s)")
|
||||||
print(f" Übersprungen: {len(link11_shops)} Link11-Shop(s)")
|
print(f" Übersprungen: {len(link11_shops)} Link11-Shop(s)")
|
||||||
|
|
||||||
@@ -1219,6 +1531,7 @@ def activate_direct_shops_only():
|
|||||||
index_php = os.path.join(httpdocs, 'index.php')
|
index_php = os.path.join(httpdocs, 'index.php')
|
||||||
backup_php = os.path.join(httpdocs, f'index.php{BACKUP_SUFFIX}')
|
backup_php = os.path.join(httpdocs, f'index.php{BACKUP_SUFFIX}')
|
||||||
blocking_file = os.path.join(httpdocs, BLOCKING_FILE)
|
blocking_file = os.path.join(httpdocs, BLOCKING_FILE)
|
||||||
|
ratelimit_path = os.path.join(httpdocs, RATELIMIT_DIR)
|
||||||
|
|
||||||
if os.path.isfile(backup_php) or not os.path.isfile(index_php):
|
if os.path.isfile(backup_php) or not os.path.isfile(index_php):
|
||||||
print(f" ⚠️ Übersprungen")
|
print(f" ⚠️ Übersprungen")
|
||||||
@@ -1246,6 +1559,10 @@ def activate_direct_shops_only():
|
|||||||
expiry = datetime.now() + timedelta(hours=72)
|
expiry = datetime.now() + timedelta(hours=72)
|
||||||
|
|
||||||
if is_bot_mode:
|
if is_bot_mode:
|
||||||
|
# Create rate-limit directories
|
||||||
|
os.makedirs(os.path.join(ratelimit_path, 'bans'), exist_ok=True)
|
||||||
|
os.makedirs(os.path.join(ratelimit_path, 'counts'), exist_ok=True)
|
||||||
|
|
||||||
if uses_crowdsec(mode):
|
if uses_crowdsec(mode):
|
||||||
template = BOT_ONLY_SCRIPT_TEMPLATE
|
template = BOT_ONLY_SCRIPT_TEMPLATE
|
||||||
else:
|
else:
|
||||||
@@ -1256,8 +1573,11 @@ def activate_direct_shops_only():
|
|||||||
expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'),
|
expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
log_file=LOG_FILE,
|
log_file=LOG_FILE,
|
||||||
crowdsec_queue=CROWDSEC_QUEUE_FILE,
|
crowdsec_queue=CROWDSEC_QUEUE_FILE,
|
||||||
|
ratelimit_dir=RATELIMIT_DIR,
|
||||||
shop_name=shop,
|
shop_name=shop,
|
||||||
bot_patterns=generate_php_bot_patterns()
|
bot_patterns=generate_php_bot_patterns(),
|
||||||
|
rate_limit=rate_limit,
|
||||||
|
ban_duration=ban_duration
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
template = GEOIP_SCRIPT_TEMPLATE if uses_crowdsec(mode) else GEOIP_SCRIPT_TEMPLATE_PHP_ONLY
|
template = GEOIP_SCRIPT_TEMPLATE if uses_crowdsec(mode) else GEOIP_SCRIPT_TEMPLATE_PHP_ONLY
|
||||||
@@ -1278,8 +1598,8 @@ def activate_direct_shops_only():
|
|||||||
f.write(geoip_content)
|
f.write(geoip_content)
|
||||||
|
|
||||||
if is_bot_mode:
|
if is_bot_mode:
|
||||||
add_shop_to_active(shop, mode, geo_region)
|
add_shop_to_active(shop, mode, geo_region, rate_limit, ban_duration)
|
||||||
print(f" ✅ Aktiviert ({len(BOT_PATTERNS)} Bot-Patterns)")
|
print(f" ✅ Aktiviert ({len(BOT_PATTERNS)} Bot-Patterns, {rate_limit} req/min)")
|
||||||
else:
|
else:
|
||||||
print(f" ⏳ Cache generieren...")
|
print(f" ⏳ Cache generieren...")
|
||||||
cache_ok, count, _ = generate_and_validate_cache(httpdocs, geo_region)
|
cache_ok, count, _ = generate_and_validate_cache(httpdocs, geo_region)
|
||||||
@@ -1293,6 +1613,8 @@ def activate_direct_shops_only():
|
|||||||
print(f" ⏭️ {len(link11_shops)} Link11-Shop(s) übersprungen")
|
print(f" ⏭️ {len(link11_shops)} Link11-Shop(s) übersprungen")
|
||||||
if not is_bot_mode:
|
if not is_bot_mode:
|
||||||
print(f" 🛡️ Fail-Open bei Cache-Fehlern aktiv")
|
print(f" 🛡️ Fail-Open bei Cache-Fehlern aktiv")
|
||||||
|
else:
|
||||||
|
print(f" 🚦 Rate-Limit: {rate_limit} req/min, Ban: {ban_duration // 60} min")
|
||||||
print(f"{'=' * 60}")
|
print(f"{'=' * 60}")
|
||||||
|
|
||||||
|
|
||||||
@@ -1322,6 +1644,7 @@ def deactivate_all_shops():
|
|||||||
httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs')
|
httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs')
|
||||||
backup_php = os.path.join(httpdocs, f'index.php{BACKUP_SUFFIX}')
|
backup_php = os.path.join(httpdocs, f'index.php{BACKUP_SUFFIX}')
|
||||||
index_php = os.path.join(httpdocs, 'index.php')
|
index_php = os.path.join(httpdocs, 'index.php')
|
||||||
|
ratelimit_path = os.path.join(httpdocs, RATELIMIT_DIR)
|
||||||
|
|
||||||
if os.path.isfile(backup_php):
|
if os.path.isfile(backup_php):
|
||||||
shutil.move(backup_php, index_php)
|
shutil.move(backup_php, index_php)
|
||||||
@@ -1330,6 +1653,10 @@ def deactivate_all_shops():
|
|||||||
if os.path.isfile(f):
|
if os.path.isfile(f):
|
||||||
os.remove(f)
|
os.remove(f)
|
||||||
|
|
||||||
|
# Remove rate-limit directory
|
||||||
|
if os.path.isdir(ratelimit_path):
|
||||||
|
shutil.rmtree(ratelimit_path)
|
||||||
|
|
||||||
remove_shop_from_active(shop)
|
remove_shop_from_active(shop)
|
||||||
if check_crowdsec():
|
if check_crowdsec():
|
||||||
cleanup_crowdsec_decisions(shop)
|
cleanup_crowdsec_decisions(shop)
|
||||||
@@ -1344,9 +1671,11 @@ def deactivate_all_shops():
|
|||||||
def get_shop_log_stats(shop):
|
def get_shop_log_stats(shop):
|
||||||
httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs')
|
httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs')
|
||||||
log_file = os.path.join(httpdocs, LOG_FILE)
|
log_file = os.path.join(httpdocs, LOG_FILE)
|
||||||
|
ratelimit_path = os.path.join(httpdocs, RATELIMIT_DIR)
|
||||||
php_blocks = 0
|
php_blocks = 0
|
||||||
ips = {}
|
ips = {}
|
||||||
bots = {} # Track bot statistics
|
bots = {} # Track bot statistics
|
||||||
|
bans = 0 # Track rate-limit bans
|
||||||
|
|
||||||
shop_mode = get_shop_mode(shop)
|
shop_mode = get_shop_mode(shop)
|
||||||
is_bot_mode = is_bot_only_mode(shop_mode)
|
is_bot_mode = is_bot_only_mode(shop_mode)
|
||||||
@@ -1358,8 +1687,15 @@ def get_shop_log_stats(shop):
|
|||||||
ip, ua = None, 'Unknown'
|
ip, ua = None, 'Unknown'
|
||||||
detected_bot = None
|
detected_bot = None
|
||||||
|
|
||||||
|
# Check if this is a ban line
|
||||||
|
if 'BANNED: ' in line:
|
||||||
|
bans += 1
|
||||||
|
try:
|
||||||
|
detected_bot = line.split('BANNED: ')[1].split(' |')[0].strip()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
# Parse bot-only format: BOT: botname | IP: ...
|
# Parse bot-only format: BOT: botname | IP: ...
|
||||||
if 'BOT: ' in line:
|
elif 'BOT: ' in line:
|
||||||
try:
|
try:
|
||||||
detected_bot = line.split('BOT: ')[1].split(' |')[0].strip()
|
detected_bot = line.split('BOT: ')[1].split(' |')[0].strip()
|
||||||
except:
|
except:
|
||||||
@@ -1391,7 +1727,22 @@ def get_shop_log_stats(shop):
|
|||||||
if bot_name != 'Unbekannt':
|
if bot_name != 'Unbekannt':
|
||||||
bots[bot_name] = bots.get(bot_name, 0) + 1
|
bots[bot_name] = bots.get(bot_name, 0) + 1
|
||||||
|
|
||||||
return php_blocks, ips, bots, get_shop_activation_time(shop)
|
# Count active bans from file system
|
||||||
|
active_bans = 0
|
||||||
|
bans_dir = os.path.join(ratelimit_path, 'bans')
|
||||||
|
if os.path.isdir(bans_dir):
|
||||||
|
now = time.time()
|
||||||
|
for ban_file in os.listdir(bans_dir):
|
||||||
|
if ban_file.endswith('.ban'):
|
||||||
|
try:
|
||||||
|
with open(os.path.join(bans_dir, ban_file), 'r') as f:
|
||||||
|
ban_until = int(f.read().strip())
|
||||||
|
if now < ban_until:
|
||||||
|
active_bans += 1
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return php_blocks, ips, bots, get_shop_activation_time(shop), bans, active_bans
|
||||||
|
|
||||||
|
|
||||||
def show_logs(shop):
|
def show_logs(shop):
|
||||||
@@ -1402,7 +1753,7 @@ def show_logs(shop):
|
|||||||
region_info = get_geo_region_info(shop_geo)
|
region_info = get_geo_region_info(shop_geo)
|
||||||
is_bot_mode = is_bot_only_mode(shop_mode)
|
is_bot_mode = is_bot_only_mode(shop_mode)
|
||||||
|
|
||||||
blocks, ips, bots, activation_time = get_shop_log_stats(shop)
|
blocks, ips, bots, activation_time, total_bans, active_bans = get_shop_log_stats(shop)
|
||||||
|
|
||||||
if activation_time:
|
if activation_time:
|
||||||
runtime = (datetime.now() - activation_time).total_seconds() / 60
|
runtime = (datetime.now() - activation_time).total_seconds() / 60
|
||||||
@@ -1420,7 +1771,11 @@ def show_logs(shop):
|
|||||||
valid, count, err = validate_existing_cache(httpdocs, shop_geo)
|
valid, count, err = validate_existing_cache(httpdocs, shop_geo)
|
||||||
print(f"✅ Cache: {count:,} Ranges" if valid else f"⚠️ Cache: {err}")
|
print(f"✅ Cache: {count:,} Ranges" if valid else f"⚠️ Cache: {err}")
|
||||||
else:
|
else:
|
||||||
|
rate_limit, ban_duration = get_shop_rate_limit_config(shop)
|
||||||
print(f"🤖 Bot-Patterns: {len(BOT_PATTERNS)} aktiv")
|
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")
|
||||||
|
|
||||||
# Show bot statistics for bot-only mode
|
# Show bot statistics for bot-only mode
|
||||||
if bots:
|
if bots:
|
||||||
@@ -1456,6 +1811,8 @@ def show_all_logs():
|
|||||||
print(f" {COLOR_GREEN}Grün = hinter Link11{COLOR_RESET} | {COLOR_RED}Rot = Direkt{COLOR_RESET}")
|
print(f" {COLOR_GREEN}Grün = hinter Link11{COLOR_RESET} | {COLOR_RED}Rot = Direkt{COLOR_RESET}")
|
||||||
|
|
||||||
total_php_blocks = 0
|
total_php_blocks = 0
|
||||||
|
total_bans = 0
|
||||||
|
total_active_bans = 0
|
||||||
shop_php_stats = {} # shop -> {'blocks': N, 'runtime_minutes': float, 'req_min': float, 'ips': {}, 'bots': {}}
|
shop_php_stats = {} # shop -> {'blocks': N, 'runtime_minutes': float, 'req_min': float, 'ips': {}, 'bots': {}}
|
||||||
all_ips = {} # ip -> {'count': N, 'ua': user_agent, 'shops': {shop: count}}
|
all_ips = {} # ip -> {'count': N, 'ua': user_agent, 'shops': {shop: count}}
|
||||||
all_bots = {} # bot_name -> count
|
all_bots = {} # bot_name -> count
|
||||||
@@ -1463,8 +1820,10 @@ def show_all_logs():
|
|||||||
|
|
||||||
# Collect PHP stats
|
# Collect PHP stats
|
||||||
for shop in active_shops:
|
for shop in active_shops:
|
||||||
blocks, ips, bots, activation_time = get_shop_log_stats(shop)
|
blocks, ips, bots, activation_time, bans, active_bans = get_shop_log_stats(shop)
|
||||||
total_php_blocks += blocks
|
total_php_blocks += blocks
|
||||||
|
total_bans += bans
|
||||||
|
total_active_bans += active_bans
|
||||||
|
|
||||||
# Calculate runtime and req/min
|
# Calculate runtime and req/min
|
||||||
if activation_time:
|
if activation_time:
|
||||||
@@ -1479,7 +1838,9 @@ def show_all_logs():
|
|||||||
'runtime_minutes': runtime_minutes,
|
'runtime_minutes': runtime_minutes,
|
||||||
'req_min': req_min,
|
'req_min': req_min,
|
||||||
'ips': ips,
|
'ips': ips,
|
||||||
'bots': bots
|
'bots': bots,
|
||||||
|
'bans': bans,
|
||||||
|
'active_bans': active_bans
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime_minutes > total_minutes:
|
if runtime_minutes > total_minutes:
|
||||||
@@ -1554,6 +1915,26 @@ def show_all_logs():
|
|||||||
|
|
||||||
print(f" │ └─➤ Top: {top_ip_addr} ({display_name}) - {top_ip_count}x, {top_ip_req_min:.1f} req/min")
|
print(f" │ └─➤ Top: {top_ip_addr} ({display_name}) - {top_ip_count}x, {top_ip_req_min:.1f} req/min")
|
||||||
|
|
||||||
|
# Display Rate-Limit Bans
|
||||||
|
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]
|
||||||
|
if stats['bans'] > 0 or stats['active_bans'] > 0:
|
||||||
|
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}"
|
||||||
|
|
||||||
|
geo_region = get_shop_geo_region(shop)
|
||||||
|
region_info = get_geo_region_info(geo_region)
|
||||||
|
geo_icon = region_info['icon']
|
||||||
|
mode_icon = get_mode_icon(get_shop_mode(shop))
|
||||||
|
|
||||||
|
bar = "█" * min(stats['bans'] // 2, 20) if stats['bans'] > 0 else ""
|
||||||
|
print(f" ├─ {shop_colored} {geo_icon} {mode_icon}: {stats['bans']} bans ({stats['active_bans']} aktiv) {bar}")
|
||||||
|
|
||||||
# Display CrowdSec bans
|
# Display CrowdSec bans
|
||||||
print(f"\n🛡️ CrowdSec-Bans gesamt: {total_crowdsec}")
|
print(f"\n🛡️ CrowdSec-Bans gesamt: {total_crowdsec}")
|
||||||
if crowdsec_stats:
|
if crowdsec_stats:
|
||||||
@@ -1633,9 +2014,10 @@ def show_all_logs():
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
print("\n" + "=" * 60)
|
print("\n" + "=" * 60)
|
||||||
print(" GeoIP Shop Blocker Manager v3.3.0")
|
print(" GeoIP Shop Blocker Manager v3.4.0")
|
||||||
print(" 🇩🇪🇦🇹🇨🇭 DACH | 🇪🇺 Eurozone+GB | 🤖 Bot-Only")
|
print(" 🇩🇪🇦🇹🇨🇭 DACH | 🇪🇺 Eurozone+GB | 🤖 Bot-Only")
|
||||||
print(" 🛡️ Mit Cache-Validierung und Fail-Open")
|
print(" 🛡️ Mit Cache-Validierung und Fail-Open")
|
||||||
|
print(" 🚦 Mit File-basiertem Rate-Limiting")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|
||||||
print(f" {'✅' if check_crowdsec() else '⚠️ '} CrowdSec")
|
print(f" {'✅' if check_crowdsec() else '⚠️ '} CrowdSec")
|
||||||
@@ -1674,13 +2056,15 @@ def main():
|
|||||||
if is_bot_mode:
|
if is_bot_mode:
|
||||||
geo = "none"
|
geo = "none"
|
||||||
region_info = get_geo_region_info("none")
|
region_info = get_geo_region_info("none")
|
||||||
|
rate_limit, ban_duration = select_rate_limit()
|
||||||
else:
|
else:
|
||||||
geo = select_geo_region()
|
geo = select_geo_region()
|
||||||
region_info = get_geo_region_info(geo)
|
region_info = get_geo_region_info(geo)
|
||||||
|
rate_limit, ban_duration = None, None
|
||||||
|
|
||||||
confirm_msg = f"\n{region_info['icon']} {get_mode_description(mode)} aktivieren für '{available[idx]}'? (ja/nein): "
|
confirm_msg = f"\n{region_info['icon']} {get_mode_description(mode)} aktivieren für '{available[idx]}'? (ja/nein): "
|
||||||
if input(confirm_msg).lower() in ['ja', 'j']:
|
if input(confirm_msg).lower() in ['ja', 'j']:
|
||||||
activate_blocking(available[idx], mode=mode, geo_region=geo)
|
activate_blocking(available[idx], mode=mode, geo_region=geo, rate_limit=rate_limit, ban_duration=ban_duration)
|
||||||
except:
|
except:
|
||||||
print("❌ Ungültig")
|
print("❌ Ungültig")
|
||||||
|
|
||||||
@@ -1731,7 +2115,7 @@ def main():
|
|||||||
shop_mode = get_shop_mode(shop)
|
shop_mode = get_shop_mode(shop)
|
||||||
mode_icon = get_mode_icon(shop_mode)
|
mode_icon = get_mode_icon(shop_mode)
|
||||||
is_bot_mode = is_bot_only_mode(shop_mode)
|
is_bot_mode = is_bot_only_mode(shop_mode)
|
||||||
blocks, _, bots, activation_time = get_shop_log_stats(shop)
|
blocks, _, 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
|
runtime = (datetime.now() - activation_time).total_seconds() / 60 if activation_time else 0
|
||||||
|
|
||||||
httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs')
|
httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs')
|
||||||
@@ -1739,7 +2123,10 @@ def main():
|
|||||||
print(f" {format_shop_with_link11(shop)} {region_info['icon']} {mode_icon}")
|
print(f" {format_shop_with_link11(shop)} {region_info['icon']} {mode_icon}")
|
||||||
|
|
||||||
if is_bot_mode:
|
if is_bot_mode:
|
||||||
print(f" {blocks} blocks, {format_duration(runtime)}, {len(BOT_PATTERNS)} Bot-Patterns")
|
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} blocks, {format_duration(runtime)}, {len(BOT_PATTERNS)} Bot-Patterns{rl_str}{ban_str}")
|
||||||
else:
|
else:
|
||||||
valid, count, _ = validate_existing_cache(httpdocs, get_shop_geo_region(shop))
|
valid, count, _ = validate_existing_cache(httpdocs, get_shop_geo_region(shop))
|
||||||
cache_str = f"✅{count:,}" if valid else "⚠️"
|
cache_str = f"✅{count:,}" if valid else "⚠️"
|
||||||
|
|||||||
Reference in New Issue
Block a user