geoip_shop_manager.py aktualisiert
This commit is contained in:
@@ -8,6 +8,7 @@ Supports two modes:
|
|||||||
- geoip: GeoIP blocking (only allowed regions can access)
|
- geoip: GeoIP blocking (only allowed regions can access)
|
||||||
- bot: Rate-limit bots by bot-type, shop remains globally accessible
|
- bot: Rate-limit bots by bot-type, shop remains globally accessible
|
||||||
|
|
||||||
|
v4.2.0: Korrekte Ownership für erstellte Dateien (nicht mehr root:root)
|
||||||
v4.1.0: IP-basierte Bot-Erkennung (für Bots die sich tarnen)
|
v4.1.0: IP-basierte Bot-Erkennung (für Bots die sich tarnen)
|
||||||
v4.0.0: Bot-Rate-Limiting nach Bot-Typ (nicht IP), CrowdSec entfernt
|
v4.0.0: Bot-Rate-Limiting nach Bot-Typ (nicht IP), CrowdSec entfernt
|
||||||
"""
|
"""
|
||||||
@@ -23,6 +24,7 @@ import socket
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
# ANSI Color Codes
|
# ANSI Color Codes
|
||||||
COLOR_GREEN = "\033[92m"
|
COLOR_GREEN = "\033[92m"
|
||||||
@@ -520,6 +522,70 @@ GENERIC_BOT_PATTERNS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# OWNERSHIP HELPER FUNCTIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def get_most_common_owner(httpdocs_path):
|
||||||
|
"""
|
||||||
|
Ermittelt die häufigste uid:gid-Kombination im httpdocs-Verzeichnis.
|
||||||
|
Gibt (uid, gid) zurück oder (None, None) wenn nicht ermittelbar.
|
||||||
|
"""
|
||||||
|
if not os.path.isdir(httpdocs_path):
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
owner_counts = Counter()
|
||||||
|
|
||||||
|
try:
|
||||||
|
for entry in os.listdir(httpdocs_path):
|
||||||
|
entry_path = os.path.join(httpdocs_path, entry)
|
||||||
|
try:
|
||||||
|
stat_info = os.stat(entry_path)
|
||||||
|
owner_counts[(stat_info.st_uid, stat_info.st_gid)] += 1
|
||||||
|
except (OSError, IOError):
|
||||||
|
continue
|
||||||
|
except (OSError, IOError):
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
if not owner_counts:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
# Häufigste Kombination zurückgeben
|
||||||
|
most_common = owner_counts.most_common(1)[0][0]
|
||||||
|
return most_common
|
||||||
|
|
||||||
|
|
||||||
|
def set_owner(path, uid, gid, recursive=False):
|
||||||
|
"""
|
||||||
|
Setzt Owner und Gruppe für eine Datei oder einen Ordner.
|
||||||
|
Optional rekursiv für Verzeichnisse.
|
||||||
|
"""
|
||||||
|
if uid is None or gid is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.chown(path, uid, gid)
|
||||||
|
|
||||||
|
if recursive and os.path.isdir(path):
|
||||||
|
for root, dirs, files in os.walk(path):
|
||||||
|
for d in dirs:
|
||||||
|
try:
|
||||||
|
os.chown(os.path.join(root, d), uid, gid)
|
||||||
|
except (OSError, IOError):
|
||||||
|
pass
|
||||||
|
for f in files:
|
||||||
|
try:
|
||||||
|
os.chown(os.path.join(root, f), uid, gid)
|
||||||
|
except (OSError, IOError):
|
||||||
|
pass
|
||||||
|
except (OSError, IOError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EXISTING HELPER FUNCTIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
def ip_in_cidr(ip_str, cidr_str):
|
def ip_in_cidr(ip_str, cidr_str):
|
||||||
"""Prüft ob eine IP in einem CIDR-Netz liegt."""
|
"""Prüft ob eine IP in einem CIDR-Netz liegt."""
|
||||||
try:
|
try:
|
||||||
@@ -624,7 +690,7 @@ def generate_php_bot_ip_ranges():
|
|||||||
# CACHE VALIDATION
|
# CACHE VALIDATION
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
def generate_and_validate_cache(httpdocs_path, geo_region):
|
def generate_and_validate_cache(httpdocs_path, geo_region, uid=None, gid=None):
|
||||||
cache_file = os.path.join(httpdocs_path, CACHE_FILE)
|
cache_file = os.path.join(httpdocs_path, CACHE_FILE)
|
||||||
region_info = get_geo_region_info(geo_region)
|
region_info = get_geo_region_info(geo_region)
|
||||||
countries = region_info["countries"]
|
countries = region_info["countries"]
|
||||||
@@ -662,6 +728,9 @@ if (count($ranges) >= {min_expected}) {{
|
|||||||
result = subprocess.run(['php', temp_php], capture_output=True, text=True, timeout=120)
|
result = subprocess.run(['php', temp_php], capture_output=True, text=True, timeout=120)
|
||||||
output = result.stdout.strip()
|
output = result.stdout.strip()
|
||||||
if output.startswith('OK:'):
|
if output.startswith('OK:'):
|
||||||
|
# Cache-Datei wurde erstellt, Ownership setzen
|
||||||
|
if os.path.isfile(cache_file):
|
||||||
|
set_owner(cache_file, uid, gid)
|
||||||
return True, int(output.split(':')[1]), None
|
return True, int(output.split(':')[1]), None
|
||||||
elif output.startswith('FAIL:'):
|
elif output.startswith('FAIL:'):
|
||||||
return False, int(output.split(':')[1]), f"Nur {output.split(':')[1]} Ranges (min. {min_expected} erwartet)"
|
return False, int(output.split(':')[1]), f"Nur {output.split(':')[1]} Ranges (min. {min_expected} erwartet)"
|
||||||
@@ -1227,6 +1296,18 @@ def activate_blocking(shop, silent=False, mode="geoip", geo_region="dach", rate_
|
|||||||
print(f"❌ index.php nicht gefunden")
|
print(f"❌ index.php nicht gefunden")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Ermittle den häufigsten Owner im httpdocs-Verzeichnis
|
||||||
|
uid, gid = get_most_common_owner(httpdocs)
|
||||||
|
if not silent and uid is not None:
|
||||||
|
import pwd
|
||||||
|
import grp
|
||||||
|
try:
|
||||||
|
user_name = pwd.getpwuid(uid).pw_name
|
||||||
|
group_name = grp.getgrgid(gid).gr_name
|
||||||
|
print(f"\n👤 Ownership: {user_name}:{group_name} (uid={uid}, gid={gid})")
|
||||||
|
except (KeyError, ImportError):
|
||||||
|
print(f"\n👤 Ownership: uid={uid}, gid={gid}")
|
||||||
|
|
||||||
if not silent:
|
if not silent:
|
||||||
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 bot_mode:
|
if bot_mode:
|
||||||
@@ -1242,6 +1323,7 @@ def activate_blocking(shop, silent=False, mode="geoip", geo_region="dach", rate_
|
|||||||
print("\n[1/3] Aktiviere PHP-Blocking...")
|
print("\n[1/3] Aktiviere PHP-Blocking...")
|
||||||
|
|
||||||
shutil.copy2(index_php, backup_php)
|
shutil.copy2(index_php, backup_php)
|
||||||
|
set_owner(backup_php, uid, gid)
|
||||||
|
|
||||||
with open(index_php, 'r', encoding='utf-8') as f:
|
with open(index_php, 'r', encoding='utf-8') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
@@ -1260,6 +1342,7 @@ def activate_blocking(shop, silent=False, mode="geoip", geo_region="dach", rate_
|
|||||||
lines.insert(insert_line, require_statement)
|
lines.insert(insert_line, require_statement)
|
||||||
with open(index_php, 'w', encoding='utf-8') as f:
|
with open(index_php, 'w', encoding='utf-8') as f:
|
||||||
f.write('\n'.join(lines))
|
f.write('\n'.join(lines))
|
||||||
|
set_owner(index_php, uid, gid)
|
||||||
|
|
||||||
expiry = datetime.now() + timedelta(hours=72)
|
expiry = datetime.now() + timedelta(hours=72)
|
||||||
|
|
||||||
@@ -1271,6 +1354,8 @@ def activate_blocking(shop, silent=False, mode="geoip", geo_region="dach", rate_
|
|||||||
os.chmod(ratelimit_path, 0o777)
|
os.chmod(ratelimit_path, 0o777)
|
||||||
os.chmod(os.path.join(ratelimit_path, 'bans'), 0o777)
|
os.chmod(os.path.join(ratelimit_path, 'bans'), 0o777)
|
||||||
os.chmod(os.path.join(ratelimit_path, 'counts'), 0o777)
|
os.chmod(os.path.join(ratelimit_path, 'counts'), 0o777)
|
||||||
|
# Set ownership for ratelimit directories
|
||||||
|
set_owner(ratelimit_path, uid, gid, recursive=True)
|
||||||
|
|
||||||
if rate_limit is None:
|
if rate_limit is None:
|
||||||
rate_limit = DEFAULT_RATE_LIMIT
|
rate_limit = DEFAULT_RATE_LIMIT
|
||||||
@@ -1304,6 +1389,8 @@ def activate_blocking(shop, silent=False, mode="geoip", geo_region="dach", rate_
|
|||||||
|
|
||||||
with open(blocking_file, 'w', encoding='utf-8') as f:
|
with open(blocking_file, 'w', encoding='utf-8') as f:
|
||||||
f.write(geoip_content)
|
f.write(geoip_content)
|
||||||
|
set_owner(blocking_file, uid, gid)
|
||||||
|
|
||||||
if not silent:
|
if not silent:
|
||||||
print(" ✅ PHP-Blocking aktiviert")
|
print(" ✅ PHP-Blocking aktiviert")
|
||||||
|
|
||||||
@@ -1316,7 +1403,7 @@ def activate_blocking(shop, silent=False, mode="geoip", geo_region="dach", rate_
|
|||||||
if not silent:
|
if not silent:
|
||||||
print(f"\n[2/3] 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)
|
success, range_count, error = generate_and_validate_cache(httpdocs, geo_region, uid, gid)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
if not silent:
|
if not silent:
|
||||||
@@ -1470,7 +1557,11 @@ def activate_all_shops():
|
|||||||
print(f" ⚠️ Übersprungen")
|
print(f" ⚠️ Übersprungen")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Ermittle Owner für diesen Shop
|
||||||
|
uid, gid = get_most_common_owner(httpdocs)
|
||||||
|
|
||||||
shutil.copy2(index_php, backup_php)
|
shutil.copy2(index_php, backup_php)
|
||||||
|
set_owner(backup_php, uid, gid)
|
||||||
|
|
||||||
with open(index_php, 'r', encoding='utf-8') as f:
|
with open(index_php, 'r', encoding='utf-8') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
@@ -1488,6 +1579,7 @@ def activate_all_shops():
|
|||||||
lines.insert(insert_line, require_statement)
|
lines.insert(insert_line, require_statement)
|
||||||
with open(index_php, 'w', encoding='utf-8') as f:
|
with open(index_php, 'w', encoding='utf-8') as f:
|
||||||
f.write('\n'.join(lines))
|
f.write('\n'.join(lines))
|
||||||
|
set_owner(index_php, uid, gid)
|
||||||
|
|
||||||
expiry = datetime.now() + timedelta(hours=72)
|
expiry = datetime.now() + timedelta(hours=72)
|
||||||
|
|
||||||
@@ -1497,6 +1589,7 @@ def activate_all_shops():
|
|||||||
os.chmod(ratelimit_path, 0o777)
|
os.chmod(ratelimit_path, 0o777)
|
||||||
os.chmod(os.path.join(ratelimit_path, 'bans'), 0o777)
|
os.chmod(os.path.join(ratelimit_path, 'bans'), 0o777)
|
||||||
os.chmod(os.path.join(ratelimit_path, 'counts'), 0o777)
|
os.chmod(os.path.join(ratelimit_path, 'counts'), 0o777)
|
||||||
|
set_owner(ratelimit_path, uid, gid, recursive=True)
|
||||||
|
|
||||||
geoip_content = BOT_SCRIPT_TEMPLATE.format(
|
geoip_content = BOT_SCRIPT_TEMPLATE.format(
|
||||||
expiry_date=expiry.strftime('%Y-%m-%d %H:%M:%S CET'),
|
expiry_date=expiry.strftime('%Y-%m-%d %H:%M:%S CET'),
|
||||||
@@ -1524,6 +1617,7 @@ def activate_all_shops():
|
|||||||
|
|
||||||
with open(blocking_file, 'w', encoding='utf-8') as f:
|
with open(blocking_file, 'w', encoding='utf-8') as f:
|
||||||
f.write(geoip_content)
|
f.write(geoip_content)
|
||||||
|
set_owner(blocking_file, uid, gid)
|
||||||
|
|
||||||
if bot_mode:
|
if bot_mode:
|
||||||
add_shop_to_active(shop, mode, geo_region, rate_limit, ban_duration)
|
add_shop_to_active(shop, mode, geo_region, rate_limit, ban_duration)
|
||||||
@@ -1531,7 +1625,7 @@ def activate_all_shops():
|
|||||||
print(f" ✅ Aktiviert ({len(BOT_PATTERNS)} Patterns, {total_ip_ranges} IP-Ranges, {rate_limit} req/min)")
|
print(f" ✅ Aktiviert ({len(BOT_PATTERNS)} Patterns, {total_ip_ranges} IP-Ranges, {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, uid, gid)
|
||||||
add_shop_to_active(shop, mode, geo_region)
|
add_shop_to_active(shop, mode, geo_region)
|
||||||
print(f" ✅ Aktiviert ({count:,} Ranges)" if cache_ok else f" ⚠️ Aktiviert (Cache-Warnung)")
|
print(f" ✅ Aktiviert ({count:,} Ranges)" if cache_ok else f" ⚠️ Aktiviert (Cache-Warnung)")
|
||||||
|
|
||||||
@@ -1620,7 +1714,11 @@ def activate_direct_shops_only():
|
|||||||
print(f" ⚠️ Übersprungen")
|
print(f" ⚠️ Übersprungen")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Ermittle Owner für diesen Shop
|
||||||
|
uid, gid = get_most_common_owner(httpdocs)
|
||||||
|
|
||||||
shutil.copy2(index_php, backup_php)
|
shutil.copy2(index_php, backup_php)
|
||||||
|
set_owner(backup_php, uid, gid)
|
||||||
|
|
||||||
with open(index_php, 'r', encoding='utf-8') as f:
|
with open(index_php, 'r', encoding='utf-8') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
@@ -1638,6 +1736,7 @@ def activate_direct_shops_only():
|
|||||||
lines.insert(insert_line, require_statement)
|
lines.insert(insert_line, require_statement)
|
||||||
with open(index_php, 'w', encoding='utf-8') as f:
|
with open(index_php, 'w', encoding='utf-8') as f:
|
||||||
f.write('\n'.join(lines))
|
f.write('\n'.join(lines))
|
||||||
|
set_owner(index_php, uid, gid)
|
||||||
|
|
||||||
expiry = datetime.now() + timedelta(hours=72)
|
expiry = datetime.now() + timedelta(hours=72)
|
||||||
|
|
||||||
@@ -1647,6 +1746,7 @@ def activate_direct_shops_only():
|
|||||||
os.chmod(ratelimit_path, 0o777)
|
os.chmod(ratelimit_path, 0o777)
|
||||||
os.chmod(os.path.join(ratelimit_path, 'bans'), 0o777)
|
os.chmod(os.path.join(ratelimit_path, 'bans'), 0o777)
|
||||||
os.chmod(os.path.join(ratelimit_path, 'counts'), 0o777)
|
os.chmod(os.path.join(ratelimit_path, 'counts'), 0o777)
|
||||||
|
set_owner(ratelimit_path, uid, gid, recursive=True)
|
||||||
|
|
||||||
geoip_content = BOT_SCRIPT_TEMPLATE.format(
|
geoip_content = BOT_SCRIPT_TEMPLATE.format(
|
||||||
expiry_date=expiry.strftime('%Y-%m-%d %H:%M:%S CET'),
|
expiry_date=expiry.strftime('%Y-%m-%d %H:%M:%S CET'),
|
||||||
@@ -1674,6 +1774,7 @@ def activate_direct_shops_only():
|
|||||||
|
|
||||||
with open(blocking_file, 'w', encoding='utf-8') as f:
|
with open(blocking_file, 'w', encoding='utf-8') as f:
|
||||||
f.write(geoip_content)
|
f.write(geoip_content)
|
||||||
|
set_owner(blocking_file, uid, gid)
|
||||||
|
|
||||||
if bot_mode:
|
if bot_mode:
|
||||||
add_shop_to_active(shop, mode, geo_region, rate_limit, ban_duration)
|
add_shop_to_active(shop, mode, geo_region, rate_limit, ban_duration)
|
||||||
@@ -1681,7 +1782,7 @@ def activate_direct_shops_only():
|
|||||||
print(f" ✅ Aktiviert ({len(BOT_PATTERNS)} Patterns, {total_ip_ranges} IP-Ranges, {rate_limit} req/min)")
|
print(f" ✅ Aktiviert ({len(BOT_PATTERNS)} Patterns, {total_ip_ranges} IP-Ranges, {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, uid, gid)
|
||||||
add_shop_to_active(shop, mode, geo_region)
|
add_shop_to_active(shop, mode, geo_region)
|
||||||
print(f" ✅ Aktiviert ({count:,} Ranges)" if cache_ok else f" ⚠️ Aktiviert (Cache-Warnung)")
|
print(f" ✅ Aktiviert ({count:,} Ranges)" if cache_ok else f" ⚠️ Aktiviert (Cache-Warnung)")
|
||||||
|
|
||||||
@@ -2056,11 +2157,12 @@ def main():
|
|||||||
total_ip_ranges = sum(len(ranges) for ranges in BOT_IP_RANGES.values())
|
total_ip_ranges = sum(len(ranges) for ranges in BOT_IP_RANGES.values())
|
||||||
|
|
||||||
print("\n" + "=" * 60)
|
print("\n" + "=" * 60)
|
||||||
print(" GeoIP Shop Blocker Manager v4.1.0")
|
print(" GeoIP Shop Blocker Manager v4.2.0")
|
||||||
print(" 🇩🇪🇦🇹🇨🇭 DACH | 🇪🇺 Eurozone+GB | 🤖 Bot-Rate-Limiting")
|
print(" 🇩🇪🇦🇹🇨🇭 DACH | 🇪🇺 Eurozone+GB | 🤖 Bot-Rate-Limiting")
|
||||||
print(" 🛡️ Mit Cache-Validierung und Fail-Open")
|
print(" 🛡️ Mit Cache-Validierung und Fail-Open")
|
||||||
print(" 🚦 Rate-Limiting nach BOT-TYP (nicht IP)")
|
print(" 🚦 Rate-Limiting nach BOT-TYP (nicht IP)")
|
||||||
print(f" 🌐 IP-basierte Erkennung: {total_ip_ranges} Ranges für {len(BOT_IP_RANGES)} Bot(s)")
|
print(f" 🌐 IP-basierte Erkennung: {total_ip_ranges} Ranges für {len(BOT_IP_RANGES)} Bot(s)")
|
||||||
|
print(" 👤 Korrekte Ownership für erstellte Dateien")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
Reference in New Issue
Block a user