Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c97e327f0d | |||
| c2d6f872f5 | |||
| 633331748f | |||
| df8b18ae08 | |||
| a79586de94 | |||
| b42f458d5a | |||
| 83075f2782 | |||
| 2a1d8ae975 | |||
| 0da5d01641 | |||
| 535be66b55 | |||
| 2e78b9c14e |
@@ -23,13 +23,14 @@ Wenn ein Client eine bestimmte Domain zu oft anfragt (z.B. >30x pro Minute), wir
|
||||
|
||||
- Automatische Erkennung und Sperre bei Rate-Limit-Verstößen
|
||||
- **Subdomain-Flood-Erkennung** — erkennt Random-Subdomain-Attacken (z.B. `abc123.microsoft.com`, `xyz456.microsoft.com`, ...)
|
||||
- **Progressive Sperren (Recidive)** — Wiederholungstäter werden stufenweise länger gesperrt (wie bei fail2ban)
|
||||
- **Progressive Sperren (Recidive)** — Wiederholungstäter werden stufenweise länger gesperrt (wie bei fail2ban), mit automatischem Cleanup abgelaufener Zähler
|
||||
- Unterstützt **alle DNS-Protokolle**: DNS (53), DoH (443), DoT (853), DoQ (784/853/8853)
|
||||
- **IPv4 + IPv6**
|
||||
- Eigene iptables Chain — greift nicht in bestehende Regeln ein
|
||||
- Automatisches Entsperren nach konfigurierbarer Dauer
|
||||
- **Externe Blocklisten** — IP-Adressen von externen Textdateien (URLs) laden und automatisch sperren
|
||||
- **Externe Whitelisten** — Domains/IPs aus externen Listen laden und automatisch whitelisten (ideal für DynDNS)
|
||||
- **GeoIP-Länderfilter** — Länder sperren oder erlauben (Blocklist/Allowlist), mit automatischem MaxMind-DB-Download
|
||||
- **AbuseIPDB Reporting** — permanent gesperrte IPs automatisch an AbuseIPDB melden
|
||||
- **E-Mail Reports** — periodische Statistik-Reports als HTML oder TXT (täglich, wöchentlich, zweiwöchentlich, monatlich)
|
||||
- **Ban-History** — lückenlose Protokollierung aller Sperren/Entsperrungen mit Zeitstempel
|
||||
@@ -94,6 +95,9 @@ sudo /opt/adguard-shield/adguard-shield.sh blocklist-status # Externe Blocklis
|
||||
sudo /opt/adguard-shield/adguard-shield.sh blocklist-sync # Blocklisten manuell synchronisieren
|
||||
sudo /opt/adguard-shield/adguard-shield.sh whitelist-status # Externe Whitelisten Status
|
||||
sudo /opt/adguard-shield/adguard-shield.sh whitelist-sync # Whitelisten manuell synchronisieren
|
||||
sudo /opt/adguard-shield/adguard-shield.sh geoip-status # GeoIP-Status anzeigen
|
||||
sudo /opt/adguard-shield/adguard-shield.sh geoip-sync # GeoIP einmalig prüfen
|
||||
sudo /opt/adguard-shield/adguard-shield.sh geoip-lookup IP # GeoIP-Lookup einer IP
|
||||
sudo /opt/adguard-shield/report-generator.sh send # Report jetzt senden
|
||||
sudo /opt/adguard-shield/report-generator.sh status # Report-Status anzeigen
|
||||
sudo /opt/adguard-shield/report-generator.sh install # Cron-Job einrichten
|
||||
|
||||
@@ -1,221 +1,102 @@
|
||||
###############################################################################
|
||||
# AdGuard Shield - Konfigurationsdatei
|
||||
# Schutz vor übermäßigen DNS-Anfragen einzelner Clients
|
||||
# Ausführliche Dokumentation: docs/konfiguration.md
|
||||
###############################################################################
|
||||
|
||||
# --- AdGuard Home API Einstellungen ---
|
||||
# URL der AdGuard Home Web-Oberfläche (ohne trailing slash)
|
||||
# --- AdGuard Home API ---
|
||||
ADGUARD_URL="https://dns1.domain.com"
|
||||
|
||||
# AdGuard Home Zugangsdaten (Web-UI Login)
|
||||
ADGUARD_USER="admin"
|
||||
ADGUARD_PASS='changeme'
|
||||
|
||||
# --- Rate-Limit Einstellungen ---
|
||||
# Maximale Anfragen pro Domain pro Client innerhalb des Zeitfensters
|
||||
RATE_LIMIT_MAX_REQUESTS=30
|
||||
# --- Rate-Limit ---
|
||||
RATE_LIMIT_MAX_REQUESTS=30 # Max. Anfragen pro Domain/Client im Zeitfenster
|
||||
RATE_LIMIT_WINDOW=60 # Zeitfenster in Sekunden
|
||||
CHECK_INTERVAL=10 # Prüfintervall in Sekunden
|
||||
|
||||
# Zeitfenster in Sekunden (60 = 1 Minute)
|
||||
RATE_LIMIT_WINDOW=60
|
||||
|
||||
# Wie oft das Script die Logs prüft (in Sekunden)
|
||||
CHECK_INTERVAL=10
|
||||
|
||||
# --- Subdomain-Flood-Erkennung (Random Subdomain Attack) ---
|
||||
# Erkennt Bots/Clients die massenhaft zufällige Subdomains einer Domain abfragen
|
||||
# Beispiel: abc123.microsoft.com, xyz456.microsoft.com, ...
|
||||
# Dabei wird pro Client gezählt, wie viele EINDEUTIGE Subdomains einer
|
||||
# Basisdomain (z.B. microsoft.com) im Zeitfenster aufgerufen werden.
|
||||
|
||||
# Subdomain-Flood-Erkennung aktivieren
|
||||
# --- Subdomain-Flood-Erkennung ---
|
||||
SUBDOMAIN_FLOOD_ENABLED=true
|
||||
|
||||
# Maximale Anzahl eindeutiger Subdomains pro Basisdomain pro Client im Zeitfenster
|
||||
# Beispiel: 50 = ein Client darf max. 50 verschiedene Subdomains von microsoft.com abfragen
|
||||
SUBDOMAIN_FLOOD_MAX_UNIQUE=50
|
||||
|
||||
# Zeitfenster in Sekunden für die Subdomain-Flood-Erkennung (60 = 1 Minute)
|
||||
SUBDOMAIN_FLOOD_WINDOW=60
|
||||
SUBDOMAIN_FLOOD_MAX_UNIQUE=50 # Max. eindeutige Subdomains pro Basisdomain/Client
|
||||
SUBDOMAIN_FLOOD_WINDOW=60 # Zeitfenster in Sekunden
|
||||
|
||||
# --- Sperr-Einstellungen ---
|
||||
# Wie lange ein Client gesperrt wird (in Sekunden, 3600 = 1 Stunde)
|
||||
BAN_DURATION=3600
|
||||
|
||||
# iptables Chain-Name für die Sperren
|
||||
BAN_DURATION=3600 # Basis-Sperrdauer in Sekunden
|
||||
IPTABLES_CHAIN="ADGUARD_SHIELD"
|
||||
|
||||
# Welche Ports gesperrt werden sollen (IPv4 + IPv6)
|
||||
# Port 53 = DNS (UDP + TCP)
|
||||
# Port 443 = DNS-over-HTTPS (DoH)
|
||||
# Port 853 = DNS-over-TLS (tls://...:853) / DNS-over-QUIC (quic://...:853)
|
||||
# Hinweis: Das verwendete Protokoll (DNS/DoH/DoT/DoQ) wird automatisch
|
||||
# aus der AdGuard Home API erkannt und in Logs/History angezeigt.
|
||||
BLOCKED_PORTS="53 443 853"
|
||||
BLOCKED_PORTS="53 443 853" # DNS(53), DoH(443), DoT/DoQ(853)
|
||||
|
||||
# --- Whitelist ---
|
||||
# IP-Adressen die NIEMALS gesperrt werden (kommagetrennt)
|
||||
# Lokale Netze und wichtige Server hier eintragen
|
||||
# IPs die niemals gesperrt werden (kommagetrennt)
|
||||
WHITELIST="127.0.0.1,::1"
|
||||
|
||||
# --- Logging ---
|
||||
# Log-Datei Pfad
|
||||
LOG_FILE="/var/log/adguard-shield.log"
|
||||
|
||||
# Log-Level: DEBUG, INFO, WARN, ERROR
|
||||
LOG_LEVEL="INFO"
|
||||
|
||||
# Maximale Größe der Log-Datei in MB (danach wird rotiert)
|
||||
LOG_MAX_SIZE_MB=50
|
||||
|
||||
# Ban-History Datei (protokolliert alle Sperren & Entsperrungen dauerhaft)
|
||||
LOG_LEVEL="INFO" # DEBUG, INFO, WARN, ERROR
|
||||
LOG_MAX_SIZE_MB=50 # Max. Größe in MB (danach Rotation)
|
||||
BAN_HISTORY_FILE="/var/log/adguard-shield-bans.log"
|
||||
BAN_HISTORY_RETENTION_DAYS=0 # 0 = unbegrenzt
|
||||
|
||||
# Maximale Aufbewahrungsdauer der Ban-History in Tagen
|
||||
# 0 = unbegrenzt (niemals automatisch löschen)
|
||||
# Beispiel: 90 = Einträge älter als 90 Tage werden beim nächsten Report entfernt
|
||||
BAN_HISTORY_RETENTION_DAYS=0
|
||||
|
||||
# --- Benachrichtigungen (optional) ---
|
||||
# Aktiviert Benachrichtigungen bei Sperren/Entsperrungen
|
||||
# --- Benachrichtigungen ---
|
||||
NOTIFY_ENABLED=false
|
||||
NOTIFY_TYPE="ntfy" # ntfy, discord, slack, gotify, generic
|
||||
NOTIFY_WEBHOOK_URL="" # Webhook-URL (nicht für ntfy)
|
||||
|
||||
# Benachrichtigungs-Typ: "ntfy", "discord", "slack", "gotify", "generic"
|
||||
NOTIFY_TYPE="ntfy"
|
||||
|
||||
# Webhook-URL (nur für discord, slack, gotify, generic – bei ntfy nicht nötig)
|
||||
# Discord: https://discord.com/api/webhooks/xxx/yyy
|
||||
# Gotify: https://gotify.example.com/message?token=xxx
|
||||
NOTIFY_WEBHOOK_URL=""
|
||||
|
||||
# --- Ntfy Einstellungen (nur bei NOTIFY_TYPE="ntfy") ---
|
||||
# Server-URL der Ntfy-Instanz (ohne trailing slash)
|
||||
# Ntfy-Einstellungen (nur bei NOTIFY_TYPE="ntfy")
|
||||
NTFY_SERVER_URL="https://ntfy.sh"
|
||||
|
||||
# Topic-Name für die Benachrichtigungen
|
||||
NTFY_TOPIC=""
|
||||
|
||||
# Optionaler Access-Token (leer lassen wenn nicht benötigt)
|
||||
NTFY_TOKEN=""
|
||||
NTFY_PRIORITY="4" # 1=min, 3=default, 5=max
|
||||
|
||||
# Priorität der Ntfy-Nachrichten (1=min, 3=default, 5=max)
|
||||
NTFY_PRIORITY="4"
|
||||
|
||||
# --- E-Mail Report (optional) ---
|
||||
# Regelmäßiger Statistik-Report per E-Mail
|
||||
# Voraussetzung: Ein funktionierender Mail-Transport (z.B. msmtp)
|
||||
# Anleitung für msmtp: https://www.cleveradmin.de/blog/2024/12/linux-einfach-emails-versenden-mit-msmtp/
|
||||
# --- E-Mail Report ---
|
||||
REPORT_ENABLED=false
|
||||
|
||||
# Report-Intervall: "daily", "weekly", "biweekly", "monthly"
|
||||
# daily = täglich um die konfigurierte Uhrzeit
|
||||
# weekly = wöchentlich am Montag
|
||||
# biweekly = alle zwei Wochen am Montag
|
||||
# monthly = monatlich am 1. des Monats
|
||||
REPORT_INTERVAL="weekly"
|
||||
|
||||
# Uhrzeit für den Report-Versand (Format: HH:MM, 24h)
|
||||
REPORT_INTERVAL="weekly" # daily, weekly, biweekly, monthly
|
||||
REPORT_TIME="08:00"
|
||||
|
||||
# E-Mail-Empfänger
|
||||
REPORT_EMAIL_TO="admin@example.com"
|
||||
|
||||
# E-Mail-Absender
|
||||
REPORT_EMAIL_FROM="adguard-shield@example.com"
|
||||
|
||||
# E-Mail-Format: "html" oder "txt"
|
||||
REPORT_FORMAT="html"
|
||||
|
||||
# Mail-Befehl (z.B. "msmtp", "sendmail", "mail")
|
||||
REPORT_FORMAT="html" # html, txt
|
||||
REPORT_MAIL_CMD="msmtp"
|
||||
REPORT_BUSIEST_DAY_RANGE=30 # Tage für "Aktivster Tag" (0 = nur Berichtszeitraum)
|
||||
|
||||
# Zeitraum für "Aktivster Tag" im Report (in Tagen)
|
||||
# Bestimmt, über wie viele Tage zurück der aktivste Tag ermittelt wird.
|
||||
# 30 = Aktivster Tag der letzten 30 Tage (empfohlen)
|
||||
# 0 = Nur innerhalb des Berichtszeitraums (altes Verhalten)
|
||||
REPORT_BUSIEST_DAY_RANGE=30
|
||||
|
||||
# --- Externe Whitelist (optional) ---
|
||||
# Ermöglicht das Einbinden externer Whitelist-Dateien mit Domains/IPs.
|
||||
# Domains werden regelmäßig per DNS aufgelöst (ideal für dynamische IPs/DynDNS).
|
||||
# --- Externe Whitelist ---
|
||||
# Externe Whitelist-Dateien mit Domains/IPs; Domains werden per DNS aufgelöst
|
||||
EXTERNAL_WHITELIST_ENABLED=false
|
||||
EXTERNAL_WHITELIST_URLS="" # URL(s) kommagetrennt
|
||||
EXTERNAL_WHITELIST_INTERVAL=300 # Prüfintervall in Sekunden
|
||||
EXTERNAL_WHITELIST_CACHE_DIR="/var/lib/adguard-shield/external-whitelist"
|
||||
|
||||
# URL(s) zu externen Textdateien mit Domains/IPs (eine pro Zeile)
|
||||
# Mehrere URLs kommagetrennt angeben
|
||||
# Beispiel: "https://example.com/whitelist.txt,https://other.com/trusted-hosts.txt"
|
||||
EXTERNAL_WHITELIST_URLS=""
|
||||
|
||||
# Wie oft die externe Whitelist geprüft und Domains neu aufgelöst werden (in Sekunden, 300 = 5 Minuten)
|
||||
# Kürzere Intervalle empfohlen bei vielen DynDNS-Einträgen
|
||||
EXTERNAL_WHITELIST_INTERVAL=300
|
||||
|
||||
# --- Externe Blocklist (optional) ---
|
||||
# Aktiviert den externen Blocklist-Worker
|
||||
# --- Externe Blocklist ---
|
||||
EXTERNAL_BLOCKLIST_ENABLED=false
|
||||
|
||||
# URL(s) zu externen Textdateien mit IP-Adressen (eine IP pro Zeile)
|
||||
# Mehrere URLs kommagetrennt angeben
|
||||
# Beispiel: "https://example.com/blocklist.txt,https://other.com/bad-ips.txt"
|
||||
EXTERNAL_BLOCKLIST_URLS=""
|
||||
|
||||
# Wie oft die externe Blocklist geprüft wird (in Sekunden, 300 = 5 Minuten)
|
||||
EXTERNAL_BLOCKLIST_INTERVAL=300
|
||||
|
||||
# Sperrdauer für externe Blocklist-IPs in Sekunden (0 = permanent bis IP aus Liste entfernt)
|
||||
EXTERNAL_BLOCKLIST_BAN_DURATION=0
|
||||
|
||||
# Automatisch IPs entsperren die aus der externen Liste entfernt wurden?
|
||||
EXTERNAL_BLOCKLIST_URLS="" # URL(s) kommagetrennt
|
||||
EXTERNAL_BLOCKLIST_INTERVAL=300 # Prüfintervall in Sekunden
|
||||
EXTERNAL_BLOCKLIST_BAN_DURATION=0 # 0 = permanent bis IP aus Liste entfernt
|
||||
EXTERNAL_BLOCKLIST_AUTO_UNBAN=true
|
||||
|
||||
# Benachrichtigungen bei Blocklist-Sperren senden?
|
||||
# Bei Listen mit vielen IPs empfiehlt sich false, da sonst beim Sync
|
||||
# hunderte Benachrichtigungen auf einmal verschickt werden.
|
||||
EXTERNAL_BLOCKLIST_NOTIFY=false
|
||||
|
||||
# Lokaler Cache-Pfad für die heruntergeladene Blocklist
|
||||
EXTERNAL_BLOCKLIST_NOTIFY=false # Bei großen Listen auf false lassen
|
||||
EXTERNAL_BLOCKLIST_CACHE_DIR="/var/lib/adguard-shield/external-blocklist"
|
||||
|
||||
# --- Progressive Sperren (Recidive) ---
|
||||
# Wiederholungstäter werden stufenweise länger gesperrt (wie bei fail2ban)
|
||||
# Aktiviert das progressive Sperrsystem
|
||||
# Wiederholungstäter werden stufenweise länger gesperrt
|
||||
PROGRESSIVE_BAN_ENABLED=true
|
||||
PROGRESSIVE_BAN_MULTIPLIER=2 # Multiplikator pro Stufe (2 = Verdopplung)
|
||||
PROGRESSIVE_BAN_MAX_LEVEL=5 # Ab dieser Stufe permanent sperren (0 = nie)
|
||||
PROGRESSIVE_BAN_RESET_AFTER=86400 # Zähler-Reset nach X Sekunden ohne Vergehen
|
||||
|
||||
# Multiplikator pro Wiederholung (2 = Verdopplung der Sperrdauer)
|
||||
# Stufe 1: BAN_DURATION × 1 (Standard-Sperrdauer)
|
||||
# Stufe 2: BAN_DURATION × 2
|
||||
# Stufe 3: BAN_DURATION × 4
|
||||
# Stufe 4: BAN_DURATION × 8 ... usw.
|
||||
PROGRESSIVE_BAN_MULTIPLIER=2
|
||||
|
||||
# Ab dieser Stufe wird die IP permanent gesperrt (0 = nie permanent sperren)
|
||||
# Beispiel: 5 = nach dem 5. Vergehen wird die IP dauerhaft gesperrt
|
||||
PROGRESSIVE_BAN_MAX_LEVEL=5
|
||||
|
||||
# Nach wie vielen Sekunden ohne erneutes Vergehen wird der Zähler zurückgesetzt
|
||||
# (86400 = 24 Stunden, 604800 = 7 Tage)
|
||||
PROGRESSIVE_BAN_RESET_AFTER=86400
|
||||
|
||||
# --- AbuseIPDB Reporting (optional) ---
|
||||
# Meldet permanent gesperrte IPs automatisch an AbuseIPDB
|
||||
# Nur bei PERMANENTEN Sperren wird ein Report gesendet.
|
||||
# --- AbuseIPDB Reporting ---
|
||||
# Meldet nur permanent gesperrte IPs an AbuseIPDB
|
||||
ABUSEIPDB_ENABLED=false
|
||||
|
||||
# AbuseIPDB API-Key (https://www.abuseipdb.com/account/api)
|
||||
ABUSEIPDB_API_KEY=""
|
||||
ABUSEIPDB_CATEGORIES="4" # 4 = DDoS Attack (siehe abuseipdb.com/categories)
|
||||
|
||||
# Kategorien für den Report (kommagetrennt)
|
||||
# 4 = DDoS Attack
|
||||
# Siehe: https://www.abuseipdb.com/categories
|
||||
ABUSEIPDB_CATEGORIES="4"
|
||||
# --- GeoIP-basierte Länderfilter ---
|
||||
# Sperrt/erlaubt DNS-Anfragen nach Herkunftsland (lokale DB, keine Online-API)
|
||||
GEOIP_ENABLED=false
|
||||
GEOIP_MODE="blocklist" # blocklist oder allowlist
|
||||
GEOIP_COUNTRIES="" # ISO 3166-1 Alpha-2 Codes, z.B. "CN,RU,KP,IR"
|
||||
GEOIP_CHECK_INTERVAL=0 # 0 = nutzt CHECK_INTERVAL
|
||||
GEOIP_NOTIFY=true
|
||||
GEOIP_SKIP_PRIVATE=true # Private IPs ausnehmen
|
||||
GEOIP_LICENSE_KEY="" # MaxMind GeoLite2 Key (optional, für Auto-Download)
|
||||
GEOIP_MMDB_PATH="" # Manueller DB-Pfad (optional, hat Vorrang)
|
||||
|
||||
# --- Erweiterte Einstellungen ---
|
||||
# Pfad zur State-Datei (speichert aktive Sperren)
|
||||
STATE_DIR="/var/lib/adguard-shield"
|
||||
|
||||
# Pfad zum PID-File
|
||||
PID_FILE="/var/run/adguard-shield.pid"
|
||||
|
||||
# Anzahl der API-Einträge die pro Abfrage geholt werden (max 5000)
|
||||
API_QUERY_LIMIT=500
|
||||
|
||||
# Dry-Run Modus: true = nur loggen, nicht sperren (zum Testen)
|
||||
DRY_RUN=false
|
||||
API_QUERY_LIMIT=500 # API-Einträge pro Abfrage (max 5000)
|
||||
DRY_RUN=false # true = nur loggen, nicht sperren
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# Lizenz: MIT
|
||||
###############################################################################
|
||||
|
||||
VERSION="v0.7.1"
|
||||
VERSION="v0.8.1"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -330,6 +330,8 @@ cleanup() {
|
||||
fi
|
||||
stop_blocklist_worker
|
||||
stop_whitelist_worker
|
||||
stop_geoip_worker
|
||||
stop_offense_cleanup_worker
|
||||
rm -f "$PID_FILE"
|
||||
exit 0
|
||||
}
|
||||
@@ -987,6 +989,17 @@ show_status() {
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# GeoIP-Filter Info
|
||||
if [[ "${GEOIP_ENABLED:-false}" == "true" ]]; then
|
||||
local geoip_mode_label
|
||||
[[ "${GEOIP_MODE:-blocklist}" == "blocklist" ]] && geoip_mode_label="Blocklist" || geoip_mode_label="Allowlist"
|
||||
echo " 🌍 GeoIP-Filter: AKTIV"
|
||||
echo " Modus: ${geoip_mode_label}"
|
||||
echo " Länder: ${GEOIP_COUNTRIES:-<keine>}"
|
||||
echo " Sperrdauer: PERMANENT (Auto-Unban bei Änderung der Länderliste)"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Aktive Sperren
|
||||
local ban_count=0
|
||||
if [[ -d "$STATE_DIR" ]]; then
|
||||
@@ -1212,6 +1225,72 @@ stop_whitelist_worker() {
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── GeoIP-Worker starten ────────────────────────────────────────────────────
|
||||
start_geoip_worker() {
|
||||
if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then
|
||||
log "DEBUG" "GeoIP-Worker ist deaktiviert"
|
||||
return
|
||||
fi
|
||||
|
||||
local worker_script="${SCRIPT_DIR}/geoip-worker.sh"
|
||||
if [[ ! -f "$worker_script" ]]; then
|
||||
log "WARN" "GeoIP-Worker Script nicht gefunden: $worker_script"
|
||||
return
|
||||
fi
|
||||
|
||||
log "INFO" "Starte GeoIP-Worker im Hintergrund..."
|
||||
bash "$worker_script" start &
|
||||
GEOIP_WORKER_PID=$!
|
||||
log "INFO" "GeoIP-Worker gestartet (PID: $GEOIP_WORKER_PID)"
|
||||
}
|
||||
|
||||
# ─── GeoIP-Worker stoppen ────────────────────────────────────────────────────
|
||||
stop_geoip_worker() {
|
||||
local worker_pid_file="/var/run/adguard-geoip-worker.pid"
|
||||
if [[ -f "$worker_pid_file" ]]; then
|
||||
local wpid
|
||||
wpid=$(cat "$worker_pid_file")
|
||||
if kill -0 "$wpid" 2>/dev/null; then
|
||||
log "INFO" "Stoppe GeoIP-Worker (PID: $wpid)..."
|
||||
kill "$wpid" 2>/dev/null || true
|
||||
rm -f "$worker_pid_file"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Offense-Cleanup-Worker starten ──────────────────────────────────────────
|
||||
start_offense_cleanup_worker() {
|
||||
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" != "true" ]]; then
|
||||
log "DEBUG" "Offense-Cleanup-Worker ist deaktiviert (Progressive Sperren inaktiv)"
|
||||
return
|
||||
fi
|
||||
|
||||
local worker_script="${SCRIPT_DIR}/offense-cleanup-worker.sh"
|
||||
if [[ ! -f "$worker_script" ]]; then
|
||||
log "WARN" "Offense-Cleanup-Worker Script nicht gefunden: $worker_script"
|
||||
return
|
||||
fi
|
||||
|
||||
log "INFO" "Starte Offense-Cleanup-Worker im Hintergrund (nice 19, idle I/O)..."
|
||||
nice -n 19 ionice -c 3 bash "$worker_script" start &
|
||||
OFFENSE_CLEANUP_WORKER_PID=$!
|
||||
log "INFO" "Offense-Cleanup-Worker gestartet (PID: $OFFENSE_CLEANUP_WORKER_PID)"
|
||||
}
|
||||
|
||||
# ─── Offense-Cleanup-Worker stoppen ──────────────────────────────────────────
|
||||
stop_offense_cleanup_worker() {
|
||||
local worker_pid_file="/var/run/adguard-offense-cleanup-worker.pid"
|
||||
if [[ -f "$worker_pid_file" ]]; then
|
||||
local wpid
|
||||
wpid=$(cat "$worker_pid_file")
|
||||
if kill -0 "$wpid" 2>/dev/null; then
|
||||
log "INFO" "Stoppe Offense-Cleanup-Worker (PID: $wpid)..."
|
||||
kill "$wpid" 2>/dev/null || true
|
||||
rm -f "$worker_pid_file"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Hauptschleife ──────────────────────────────────────────────────────────
|
||||
main_loop() {
|
||||
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||
@@ -1238,6 +1317,14 @@ main_loop() {
|
||||
else
|
||||
log "INFO" " AbuseIPDB Reporting: deaktiviert"
|
||||
fi
|
||||
if [[ "${GEOIP_ENABLED:-false}" == "true" ]]; then
|
||||
log "INFO" " GeoIP-Filter: AKTIV (Modus: ${GEOIP_MODE:-blocklist}, Länder: ${GEOIP_COUNTRIES:-<keine>})"
|
||||
else
|
||||
log "INFO" " GeoIP-Filter: deaktiviert"
|
||||
fi
|
||||
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]]; then
|
||||
log "INFO" " Offense-Cleanup: AKTIV (Reset: $(format_duration "${PROGRESSIVE_BAN_RESET_AFTER:-86400}"), Prüfintervall: 1h)"
|
||||
fi
|
||||
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||
|
||||
# Service-Start-Benachrichtigung senden
|
||||
@@ -1251,6 +1338,12 @@ main_loop() {
|
||||
# Whitelist-Worker als Hintergrundprozess starten
|
||||
start_whitelist_worker
|
||||
|
||||
# GeoIP-Worker als Hintergrundprozess starten
|
||||
start_geoip_worker
|
||||
|
||||
# Offense-Cleanup-Worker als Hintergrundprozess starten
|
||||
start_offense_cleanup_worker
|
||||
|
||||
while true; do
|
||||
# Abgelaufene Sperren prüfen
|
||||
check_expired_bans
|
||||
@@ -1344,6 +1437,47 @@ case "${1:-start}" in
|
||||
echo "Whitelist-Worker nicht gefunden"
|
||||
fi
|
||||
;;
|
||||
geoip-status)
|
||||
init_directories
|
||||
_worker_script="${SCRIPT_DIR}/geoip-worker.sh"
|
||||
if [[ -f "$_worker_script" ]]; then
|
||||
bash "$_worker_script" status
|
||||
else
|
||||
echo "GeoIP-Worker nicht gefunden"
|
||||
fi
|
||||
;;
|
||||
geoip-sync)
|
||||
init_directories
|
||||
setup_iptables_chain
|
||||
_worker_script="${SCRIPT_DIR}/geoip-worker.sh"
|
||||
if [[ -f "$_worker_script" ]]; then
|
||||
bash "$_worker_script" sync
|
||||
else
|
||||
echo "GeoIP-Worker nicht gefunden"
|
||||
fi
|
||||
;;
|
||||
geoip-flush)
|
||||
init_directories
|
||||
_worker_script="${SCRIPT_DIR}/geoip-worker.sh"
|
||||
if [[ -f "$_worker_script" ]]; then
|
||||
bash "$_worker_script" flush
|
||||
else
|
||||
echo "GeoIP-Worker nicht gefunden"
|
||||
fi
|
||||
;;
|
||||
geoip-lookup)
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "Nutzung: $0 geoip-lookup <IP-Adresse>" >&2
|
||||
exit 1
|
||||
fi
|
||||
init_directories
|
||||
_worker_script="${SCRIPT_DIR}/geoip-worker.sh"
|
||||
if [[ -f "$_worker_script" ]]; then
|
||||
bash "$_worker_script" lookup "$2"
|
||||
else
|
||||
echo "GeoIP-Worker nicht gefunden"
|
||||
fi
|
||||
;;
|
||||
status)
|
||||
init_directories
|
||||
show_status
|
||||
@@ -1407,7 +1541,7 @@ Service-Steuerung (empfohlen):
|
||||
sudo systemctl restart adguard-shield
|
||||
sudo systemctl status adguard-shield
|
||||
|
||||
Nutzung: $0 {status|history|flush|unban|reset-offenses|test|dry-run|blocklist-status|blocklist-sync|blocklist-flush|whitelist-status|whitelist-sync|whitelist-flush}
|
||||
Nutzung: $0 {status|history|flush|unban|reset-offenses|test|dry-run|blocklist-status|blocklist-sync|blocklist-flush|whitelist-status|whitelist-sync|whitelist-flush|geoip-status|geoip-sync|geoip-flush|geoip-lookup}
|
||||
|
||||
Verwaltungsbefehle:
|
||||
status Zeigt aktive Sperren, Regeln und Wiederholungstäter
|
||||
@@ -1423,6 +1557,10 @@ Verwaltungsbefehle:
|
||||
whitelist-status Zeigt Status der externen Whitelisten
|
||||
whitelist-sync Einmalige Synchronisation der externen Whitelisten
|
||||
whitelist-flush Entfernt alle aufgelösten Whitelist-IPs
|
||||
geoip-status Zeigt Status der GeoIP-Länderfilter
|
||||
geoip-sync Einmalige GeoIP-Prüfung aller aktiven Clients
|
||||
geoip-flush Alle GeoIP-Sperren aufheben
|
||||
geoip-lookup IP GeoIP-Lookup für eine einzelne IP-Adresse
|
||||
|
||||
Interne Befehle (nicht direkt verwenden — nur über systemd):
|
||||
start Startet den Monitor im Vordergrund
|
||||
|
||||
@@ -136,7 +136,10 @@ Das ermöglicht:
|
||||
├── iptables-helper.sh # iptables Verwaltung
|
||||
├── external-blocklist-worker.sh # Externer Blocklist-Worker
|
||||
├── external-whitelist-worker.sh # Externer Whitelist-Worker (DNS-Auflösung)
|
||||
└── unban-expired.sh # Cron-basiertes Entsperren
|
||||
├── geoip-worker.sh # GeoIP-Länderfilter-Worker
|
||||
├── offense-cleanup-worker.sh # Aufräumen abgelaufener Offense-Zähler (nice 19, idle I/O)
|
||||
├── unban-expired.sh # Cron-basiertes Entsperren
|
||||
└── geoip/ # Auto-Download MaxMind GeoLite2 DB (optional)
|
||||
|
||||
/etc/systemd/system/
|
||||
├── adguard-shield.service # systemd Service (Autostart aktiv)
|
||||
@@ -147,7 +150,8 @@ Das ermöglicht:
|
||||
├── *.ban # State-Dateien aktiver Sperren
|
||||
├── *.offenses # Offense-Zähler (Progressive Sperren)
|
||||
├── external-blocklist/ # Cache für externe Blocklisten
|
||||
└── external-whitelist/ # Cache für externe Whitelisten + aufgelöste IPs
|
||||
├── external-whitelist/ # Cache für externe Whitelisten + aufgelöste IPs
|
||||
└── geoip-cache/ # Cache für GeoIP-Lookups (24h)
|
||||
|
||||
/var/log/
|
||||
├── adguard-shield.log # Laufzeit-Log
|
||||
|
||||
@@ -239,6 +239,69 @@ sudo /opt/adguard-shield/external-blocklist-worker.sh status
|
||||
sudo /opt/adguard-shield/external-blocklist-worker.sh flush
|
||||
```
|
||||
|
||||
## GeoIP-Worker (Länderfilter)
|
||||
|
||||
Der GeoIP-Worker prüft Client-IPs auf ihr Herkunftsland und sperrt/erlaubt sie basierend auf der Konfiguration:
|
||||
|
||||
```bash
|
||||
# GeoIP-Status anzeigen (Modus, Länder, aktive Sperren, verfügbare Tools)
|
||||
sudo /opt/adguard-shield/adguard-shield.sh geoip-status
|
||||
|
||||
# Einmalige GeoIP-Prüfung aller aktiven Clients
|
||||
sudo /opt/adguard-shield/adguard-shield.sh geoip-sync
|
||||
|
||||
# Alle GeoIP-Sperren aufheben
|
||||
sudo /opt/adguard-shield/adguard-shield.sh geoip-flush
|
||||
|
||||
# GeoIP-Lookup für eine einzelne IP
|
||||
sudo /opt/adguard-shield/adguard-shield.sh geoip-lookup 8.8.8.8
|
||||
```
|
||||
|
||||
Der Worker kann auch standalone gesteuert werden:
|
||||
|
||||
```bash
|
||||
# Worker manuell starten (normalerweise automatisch per Hauptscript)
|
||||
sudo /opt/adguard-shield/geoip-worker.sh start
|
||||
|
||||
# Worker stoppen
|
||||
sudo /opt/adguard-shield/geoip-worker.sh stop
|
||||
|
||||
# Einmalige Synchronisation
|
||||
sudo /opt/adguard-shield/geoip-worker.sh sync
|
||||
|
||||
# Status anzeigen
|
||||
sudo /opt/adguard-shield/geoip-worker.sh status
|
||||
|
||||
# IP nachschlagen
|
||||
sudo /opt/adguard-shield/geoip-worker.sh lookup 1.2.3.4
|
||||
|
||||
# Alle GeoIP-Sperren aufheben
|
||||
sudo /opt/adguard-shield/geoip-worker.sh flush
|
||||
|
||||
# GeoIP-Lookup-Cache leeren
|
||||
sudo /opt/adguard-shield/geoip-worker.sh flush-cache
|
||||
```
|
||||
|
||||
## Offense-Cleanup-Worker
|
||||
|
||||
Der Offense-Cleanup-Worker räumt abgelaufene Offense-Zähler (progressive Sperren) automatisch auf. Er startet automatisch mit dem Hauptservice, wenn progressive Sperren aktiviert sind, und prüft stündlich ob Zähler aufgeräumt werden können. Der Worker läuft mit niedrigster CPU- und I/O-Priorität (`nice 19`, `ionice idle`), um den DNS-Dienst nicht zu beeinträchtigen.
|
||||
|
||||
Der Worker kann auch standalone gesteuert werden:
|
||||
|
||||
```bash
|
||||
# Worker manuell starten (normalerweise automatisch per Hauptscript)
|
||||
sudo /opt/adguard-shield/offense-cleanup-worker.sh start
|
||||
|
||||
# Worker stoppen
|
||||
sudo /opt/adguard-shield/offense-cleanup-worker.sh stop
|
||||
|
||||
# Einmaliger Cleanup-Durchlauf
|
||||
sudo /opt/adguard-shield/offense-cleanup-worker.sh run-once
|
||||
|
||||
# Status anzeigen (aktive/abgelaufene Zähler)
|
||||
sudo /opt/adguard-shield/offense-cleanup-worker.sh status
|
||||
```
|
||||
|
||||
## E-Mail Report
|
||||
|
||||
```bash
|
||||
|
||||
@@ -116,12 +116,12 @@ Bei Sperren aus der **externen Blocklist** werden Benachrichtigungen separat üb
|
||||
### Service gestartet
|
||||
**Überschrift:** ✅ AdGuard Shield
|
||||
|
||||
> 🟢 AdGuard Shield v0.7.1 wurde auf dns1 gestartet.
|
||||
> 🟢 AdGuard Shield v0.8.1 wurde auf dns1 gestartet.
|
||||
|
||||
### Service gestoppt
|
||||
**Überschrift:** 🚨 🛡️ AdGuard Shield
|
||||
|
||||
> 🔴 AdGuard Shield v0.7.1 wurde auf dns1 gestoppt.
|
||||
> 🔴 AdGuard Shield v0.8.1 wurde auf dns1 gestoppt.
|
||||
|
||||
### Watchdog — Service wiederhergestellt
|
||||
**Überschrift:** 🔄 AdGuard Shield Watchdog
|
||||
|
||||
@@ -100,7 +100,7 @@ Wiederholungstäter werden wie bei fail2ban stufenweise länger gesperrt. Wird e
|
||||
| 4. Mal | 4 | 8 Stunden | 3600 × 8 |
|
||||
| 5. Mal | 5 | **PERMANENT** | Max-Stufe erreicht |
|
||||
|
||||
> **Hinweis:** Der Offense-Zähler wird automatisch zurückgesetzt, wenn eine IP für den konfigurierten Zeitraum (`PROGRESSIVE_BAN_RESET_AFTER`) kein erneutes Vergehen begeht. Permanente Sperren werden **nicht** automatisch aufgehoben – sie müssen manuell mit `unban` oder `flush` entfernt werden.
|
||||
> **Hinweis:** Abgelaufene Offense-Zähler werden automatisch vom **Offense-Cleanup-Worker** aufgeräumt, der stündlich prüft, ob das letzte Vergehen einer IP länger als `PROGRESSIVE_BAN_RESET_AFTER` zurückliegt. Der Worker startet automatisch zusammen mit dem Hauptservice, wenn progressive Sperren aktiviert sind. Er läuft mit niedrigster CPU- und I/O-Priorität (`nice 19`, `ionice idle`), sodass andere Dienste nicht beeinträchtigt werden. Manuelles Zurücksetzen ist jederzeit mit `reset-offenses` möglich. Permanente Sperren werden **nicht** automatisch aufgehoben – sie müssen manuell mit `unban` oder `flush` entfernt werden.
|
||||
|
||||
### Logging
|
||||
|
||||
@@ -255,6 +255,93 @@ Der Report an AbuseIPDB enthält (auf Englisch):
|
||||
- **Bei Subdomain-Flood:** `DNS flooding on our DNS server: 85x *.microsoft.com in 60s (random subdomain attack). Banned by Adguard Shield 🔗 https://tnvs.de/as`
|
||||
|
||||
Die Kategorie `4` (DDoS Attack) wird standardmäßig verwendet. Weitere Kategorien können kommagetrennt angegeben werden (z.B. `"4,15"`).
|
||||
|
||||
### GeoIP-basierte Länderfilter
|
||||
|
||||
Ermöglicht das Sperren oder Erlauben von DNS-Anfragen basierend auf dem Herkunftsland der Client-IP. Unterstützt zwei Modi:
|
||||
|
||||
- **Blocklist-Modus:** Nur die gelisteten Länder werden gesperrt (alle anderen erlaubt)
|
||||
- **Allowlist-Modus:** Nur die gelisteten Länder werden erlaubt (alle anderen gesperrt)
|
||||
|
||||
| Parameter | Standard | Beschreibung |
|
||||
|-----------|----------|--------------|
|
||||
| `GEOIP_ENABLED` | `false` | GeoIP-Filter aktivieren |
|
||||
| `GEOIP_MODE` | `blocklist` | Modus: `blocklist` oder `allowlist` |
|
||||
| `GEOIP_COUNTRIES` | *(leer)* | ISO 3166-1 Alpha-2 Ländercodes (kommagetrennt), z.B. `CN,RU,KP,IR` |
|
||||
| `GEOIP_CHECK_INTERVAL` | `0` | Prüfintervall in Sekunden (`0` = nutzt `CHECK_INTERVAL`) |
|
||||
| `GEOIP_NOTIFY` | `true` | Benachrichtigungen bei GeoIP-Sperren senden |
|
||||
| `GEOIP_SKIP_PRIVATE` | `true` | Private/lokale IPs von der GeoIP-Prüfung ausnehmen |
|
||||
| `GEOIP_LICENSE_KEY` | *(leer)* | MaxMind License-Key für automatischen DB-Download (kostenlos) |
|
||||
| `GEOIP_MMDB_PATH` | *(leer)* | Manueller Pfad zur MaxMind GeoLite2 Datenbank (überschreibt Auto-Download) |
|
||||
|
||||
#### Voraussetzungen
|
||||
|
||||
Es muss mindestens eines der folgenden GeoIP-Tools installiert sein:
|
||||
|
||||
1. **Automatischer MaxMind-Download** (empfohlen):
|
||||
```bash
|
||||
# Kostenlosen Account erstellen: https://www.maxmind.com/en/geolite2/signup
|
||||
# License-Key generieren und in adguard-shield.conf eintragen:
|
||||
GEOIP_LICENSE_KEY="dein_license_key_hier"
|
||||
```
|
||||
Die GeoLite2-Country-Datenbank wird automatisch heruntergeladen und alle 24 Stunden aktualisiert.
|
||||
Es wird zusätzlich `mmdbinspect` oder `mmdblookup` benötigt:
|
||||
```bash
|
||||
sudo apt install mmdb-bin # für mmdblookup
|
||||
```
|
||||
|
||||
2. **geoiplookup** (einfachster Einstieg, weniger genau):
|
||||
```bash
|
||||
sudo apt install geoip-bin geoip-database
|
||||
```
|
||||
|
||||
3. **Manueller MaxMind-Pfad** (eigene Datenbank):
|
||||
```bash
|
||||
# mmdbinspect oder mmdblookup installieren
|
||||
# Datenbank manuell herunterladen: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data
|
||||
GEOIP_MMDB_PATH="/usr/share/GeoIP/GeoLite2-Country.mmdb"
|
||||
```
|
||||
|
||||
> **Priorität:** `GEOIP_MMDB_PATH` (manuell) → Auto-Download-DB → `geoiplookup` (Legacy-Fallback)
|
||||
|
||||
#### Beispiel: Bestimmte Länder sperren (Blocklist)
|
||||
|
||||
```bash
|
||||
GEOIP_ENABLED=true
|
||||
GEOIP_MODE="blocklist"
|
||||
GEOIP_COUNTRIES="CN,RU,KP,IR"
|
||||
GEOIP_LICENSE_KEY="dein_maxmind_license_key" # optional, für Auto-Download
|
||||
```
|
||||
|
||||
→ Alle Anfragen aus China, Russland, Nordkorea und Iran werden permanent gesperrt.
|
||||
|
||||
#### Beispiel: Nur bestimmte Länder erlauben (Allowlist)
|
||||
|
||||
```bash
|
||||
GEOIP_ENABLED=true
|
||||
GEOIP_MODE="allowlist"
|
||||
GEOIP_COUNTRIES="DE,AT,CH"
|
||||
```
|
||||
|
||||
→ Nur Anfragen aus Deutschland, Österreich und der Schweiz werden erlaubt. Alle anderen Länder werden gesperrt.
|
||||
|
||||
> **Hinweis:** Private IP-Adressen (10.x.x.x, 192.168.x.x, etc.) und Whitelist-IPs werden niemals durch GeoIP gesperrt. GeoIP-Sperren sind **immer permanent**.
|
||||
|
||||
> **Auto-Unban:** Wird ein Land aus `GEOIP_COUNTRIES` entfernt oder der Modus (`GEOIP_MODE`) geändert, werden die nicht mehr zutreffenden Sperren beim nächsten Sync **automatisch aufgehoben**. Dasselbe gilt, wenn GeoIP komplett deaktiviert wird (`GEOIP_ENABLED=false`).
|
||||
|
||||
> **Tipp:** GeoIP-Lookups werden für 24 Stunden gecacht. Mit `geoip-flush-cache` kann der Cache manuell geleert werden.
|
||||
|
||||
> **Auto-Download:** Ist `GEOIP_LICENSE_KEY` gesetzt, wird die GeoLite2-Country-Datenbank automatisch nach `<INSTALL_DIR>/geoip/` heruntergeladen und alle 24 Stunden aktualisiert. Bei einem Update wird der Download im Hintergrund durchgeführt — der Worker läuft während des Downloads normal weiter. Ein manuell gesetzter `GEOIP_MMDB_PATH` hat immer Vorrang vor der automatisch heruntergeladenen Datenbank.
|
||||
|
||||
#### GeoIP-Befehle
|
||||
|
||||
| Befehl | Beschreibung |
|
||||
|--------|--------------|
|
||||
| `adguard-shield.sh geoip-status` | Zeigt GeoIP-Status, aktive Sperren und verfügbare Tools |
|
||||
| `adguard-shield.sh geoip-sync` | Einmalige GeoIP-Prüfung aller aktiven Clients |
|
||||
| `adguard-shield.sh geoip-flush` | Alle GeoIP-Sperren aufheben |
|
||||
| `adguard-shield.sh geoip-lookup <IP>` | GeoIP-Lookup einer einzelnen IP-Adresse |
|
||||
|
||||
#### Externe Blocklist einrichten
|
||||
|
||||
1. Erstelle eine Textdatei auf einem Webserver. Pro Zeile ein Eintrag — IPv4, IPv6, CIDR oder Hostname:
|
||||
|
||||
932
geoip-worker.sh
Normal file
932
geoip-worker.sh
Normal file
@@ -0,0 +1,932 @@
|
||||
#!/bin/bash
|
||||
###############################################################################
|
||||
# AdGuard Shield - GeoIP Worker
|
||||
# Prüft Client-IPs auf Herkunftsland und sperrt/erlaubt basierend auf Konfig.
|
||||
# Wird als Hintergrundprozess vom Hauptscript gestartet.
|
||||
#
|
||||
# Autor: Patrick Asmus
|
||||
# E-Mail: support@techniverse.net
|
||||
# Lizenz: MIT
|
||||
###############################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_FILE="${SCRIPT_DIR}/adguard-shield.conf"
|
||||
|
||||
# ─── Konfiguration laden ───────────────────────────────────────────────────────
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
echo "FEHLER: Konfigurationsdatei nicht gefunden: $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
# shellcheck source=adguard-shield.conf
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
# ─── Worker PID-File ──────────────────────────────────────────────────────────
|
||||
WORKER_PID_FILE="/var/run/adguard-geoip-worker.pid"
|
||||
|
||||
# ─── GeoIP Cache ──────────────────────────────────────────────────────────────
|
||||
GEOIP_CACHE_DIR="${STATE_DIR}/geoip-cache"
|
||||
|
||||
# ─── MaxMind Auto-Download Verzeichnis ────────────────────────────────────────
|
||||
GEOIP_DB_DIR="${SCRIPT_DIR}/geoip"
|
||||
GEOIP_AUTO_DB="${GEOIP_DB_DIR}/GeoLite2-Country.mmdb"
|
||||
GEOIP_DB_UPDATE_INTERVAL=86400 # 24 Stunden (fest)
|
||||
|
||||
# ─── Logging (eigene Funktion, nutzt gleiche Log-Datei) ───────────────────────
|
||||
declare -A LOG_LEVELS=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3)
|
||||
|
||||
log() {
|
||||
local level="$1"
|
||||
shift
|
||||
local message="$*"
|
||||
local configured_level="${LOG_LEVEL:-INFO}"
|
||||
|
||||
if [[ ${LOG_LEVELS[$level]:-1} -ge ${LOG_LEVELS[$configured_level]:-1} ]]; then
|
||||
local timestamp
|
||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
local log_entry="[$timestamp] [$level] [GEOIP-WORKER] $message"
|
||||
echo "$log_entry" | tee -a "$LOG_FILE" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Ban-History ─────────────────────────────────────────────────────────────
|
||||
log_ban_history() {
|
||||
local action="$1"
|
||||
local client_ip="$2"
|
||||
local country="${3:-}"
|
||||
local reason="${4:-geoip}"
|
||||
local timestamp
|
||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
|
||||
if [[ ! -f "$BAN_HISTORY_FILE" ]]; then
|
||||
echo "# AdGuard Shield - Ban History" > "$BAN_HISTORY_FILE"
|
||||
echo "# Format: ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | PROTOKOLL | GRUND" >> "$BAN_HISTORY_FILE"
|
||||
echo "#──────────────────────────────────────────────────────────────────────────────────────────────────" >> "$BAN_HISTORY_FILE"
|
||||
fi
|
||||
|
||||
local duration="permanent"
|
||||
|
||||
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n" \
|
||||
"$timestamp" "$action" "$client_ip" "Land: ${country:-?}" "-" "$duration" "-" "$reason" \
|
||||
>> "$BAN_HISTORY_FILE"
|
||||
}
|
||||
|
||||
# ─── Verzeichnisse erstellen ──────────────────────────────────────────────────
|
||||
init_directories() {
|
||||
mkdir -p "$GEOIP_CACHE_DIR"
|
||||
mkdir -p "$GEOIP_DB_DIR"
|
||||
mkdir -p "$STATE_DIR"
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
}
|
||||
|
||||
# ─── Private IP-Adressen erkennen ────────────────────────────────────────────
|
||||
is_private_ip() {
|
||||
local ip="$1"
|
||||
|
||||
# IPv6 Loopback und Link-Local
|
||||
if [[ "$ip" == "::1" || "$ip" == fe80:* || "$ip" == fc00:* || "$ip" == fd00:* ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# IPv4 private Bereiche
|
||||
if [[ "$ip" =~ ^10\. || "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. || "$ip" =~ ^192\.168\. || "$ip" =~ ^127\. || "$ip" == "0.0.0.0" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# IPv4 CGNAT
|
||||
if [[ "$ip" =~ ^100\.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])\. ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# ─── Whitelist Prüfung ───────────────────────────────────────────────────────
|
||||
is_whitelisted() {
|
||||
local ip="$1"
|
||||
IFS=',' read -ra wl_entries <<< "$WHITELIST"
|
||||
for entry in "${wl_entries[@]}"; do
|
||||
entry=$(echo "$entry" | xargs) # trim
|
||||
if [[ "$ip" == "$entry" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Externe Whitelist prüfen
|
||||
local ext_wl_file="${EXTERNAL_WHITELIST_CACHE_DIR:-/var/lib/adguard-shield/external-whitelist}/resolved_ips.txt"
|
||||
if [[ -f "$ext_wl_file" ]] && grep -qxF "$ip" "$ext_wl_file" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# ─── MaxMind GeoLite2 Auto-Download & Update ─────────────────────────────────
|
||||
# Lädt die GeoLite2-Country.mmdb herunter, wenn GEOIP_LICENSE_KEY gesetzt ist
|
||||
# und kein eigener GEOIP_MMDB_PATH angegeben wurde.
|
||||
# Aktualisiert automatisch alle 24 Stunden.
|
||||
update_maxmind_db() {
|
||||
local license_key="${GEOIP_LICENSE_KEY:-}"
|
||||
|
||||
# Kein License-Key → nichts zu tun
|
||||
if [[ -z "$license_key" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# User hat eigenen Pfad gesetzt → kein Auto-Download
|
||||
if [[ -n "${GEOIP_MMDB_PATH:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Prüfen ob Update nötig (alle 24h)
|
||||
if [[ -f "$GEOIP_AUTO_DB" ]]; then
|
||||
local db_age
|
||||
db_age=$(( $(date '+%s') - $(stat -c '%Y' "$GEOIP_AUTO_DB" 2>/dev/null || stat -f '%m' "$GEOIP_AUTO_DB" 2>/dev/null || echo "0") ))
|
||||
if [[ "$db_age" -lt "$GEOIP_DB_UPDATE_INTERVAL" ]]; then
|
||||
log "DEBUG" "MaxMind DB ist aktuell (Alter: $((db_age / 3600))h, nächstes Update in $(( (GEOIP_DB_UPDATE_INTERVAL - db_age) / 3600 ))h)"
|
||||
return 0
|
||||
fi
|
||||
log "INFO" "MaxMind DB ist älter als 24h – starte Update..."
|
||||
else
|
||||
log "INFO" "MaxMind DB nicht vorhanden – starte Erstdownload..."
|
||||
fi
|
||||
|
||||
# Download-URL zusammenbauen (MaxMind Permalink)
|
||||
local download_url="https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${license_key}&suffix=tar.gz"
|
||||
local tmp_file="${GEOIP_DB_DIR}/GeoLite2-Country.tar.gz"
|
||||
local tmp_extract="${GEOIP_DB_DIR}/extract_tmp"
|
||||
|
||||
# Herunterladen
|
||||
local http_code
|
||||
http_code=$(curl -s -o "$tmp_file" -w "%{http_code}" \
|
||||
--connect-timeout 10 \
|
||||
--max-time 60 \
|
||||
"$download_url" 2>/dev/null) || true
|
||||
|
||||
if [[ "$http_code" != "200" ]]; then
|
||||
rm -f "$tmp_file"
|
||||
case "$http_code" in
|
||||
401) log "ERROR" "MaxMind Download fehlgeschlagen: Ungültiger License-Key (HTTP 401)" ;;
|
||||
403) log "ERROR" "MaxMind Download fehlgeschlagen: Zugriff verweigert (HTTP 403) – License-Key prüfen" ;;
|
||||
*) log "ERROR" "MaxMind Download fehlgeschlagen (HTTP ${http_code:-timeout})" ;;
|
||||
esac
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Entpacken
|
||||
rm -rf "$tmp_extract"
|
||||
mkdir -p "$tmp_extract"
|
||||
|
||||
if ! tar -xzf "$tmp_file" -C "$tmp_extract" 2>/dev/null; then
|
||||
log "ERROR" "MaxMind DB: tar-Archiv konnte nicht entpackt werden"
|
||||
rm -f "$tmp_file"
|
||||
rm -rf "$tmp_extract"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# .mmdb Datei finden und verschieben
|
||||
local mmdb_file
|
||||
mmdb_file=$(find "$tmp_extract" -name 'GeoLite2-Country.mmdb' -type f 2>/dev/null | head -1)
|
||||
|
||||
if [[ -z "$mmdb_file" || ! -f "$mmdb_file" ]]; then
|
||||
log "ERROR" "MaxMind DB: GeoLite2-Country.mmdb nicht im Archiv gefunden"
|
||||
rm -f "$tmp_file"
|
||||
rm -rf "$tmp_extract"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mv "$mmdb_file" "$GEOIP_AUTO_DB"
|
||||
rm -f "$tmp_file"
|
||||
rm -rf "$tmp_extract"
|
||||
|
||||
log "INFO" "MaxMind GeoLite2-Country DB erfolgreich aktualisiert: $GEOIP_AUTO_DB"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ─── Effektiven MMDB-Pfad ermitteln ──────────────────────────────────────────
|
||||
# Priorität: GEOIP_MMDB_PATH (User) > Auto-Download > leer (Fallback auf geoiplookup)
|
||||
resolve_mmdb_path() {
|
||||
# User hat eigenen Pfad gesetzt
|
||||
if [[ -n "${GEOIP_MMDB_PATH:-}" && -f "${GEOIP_MMDB_PATH:-}" ]]; then
|
||||
echo "$GEOIP_MMDB_PATH"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Auto-Download DB vorhanden
|
||||
if [[ -f "$GEOIP_AUTO_DB" ]]; then
|
||||
echo "$GEOIP_AUTO_DB"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Kein MMDB verfügbar
|
||||
echo ""
|
||||
return 1
|
||||
}
|
||||
|
||||
# ─── GeoIP Lookup ────────────────────────────────────────────────────────────
|
||||
# Gibt den ISO 3166-1 Alpha-2 Ländercode zurück (z.B. "DE", "US", "CN")
|
||||
# Nutzt Cache um wiederholte Lookups zu vermeiden
|
||||
geoip_lookup() {
|
||||
local ip="$1"
|
||||
local cache_file="${GEOIP_CACHE_DIR}/${ip//[:\/]/_}.country"
|
||||
|
||||
# Cache prüfen (max 24 Stunden alt)
|
||||
if [[ -f "$cache_file" ]]; then
|
||||
local cache_age
|
||||
cache_age=$(( $(date '+%s') - $(stat -c '%Y' "$cache_file" 2>/dev/null || stat -f '%m' "$cache_file" 2>/dev/null || echo "0") ))
|
||||
if [[ "$cache_age" -lt 86400 ]]; then
|
||||
cat "$cache_file"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
local country_code=""
|
||||
|
||||
# Effektiven MMDB-Pfad ermitteln (User-Pfad oder Auto-Download)
|
||||
local effective_mmdb
|
||||
effective_mmdb=$(resolve_mmdb_path 2>/dev/null) || true
|
||||
|
||||
# Methode 1: MaxMind mmdbinspect (bevorzugt, genauer)
|
||||
if [[ -n "$effective_mmdb" && -f "$effective_mmdb" ]] && command -v mmdbinspect &>/dev/null; then
|
||||
country_code=$(mmdbinspect -db "$effective_mmdb" -ip "$ip" 2>/dev/null \
|
||||
| jq -r '.[0].Records[0].Record.country.iso_code // empty' 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
# Methode 2: geoiplookup (GeoIP Legacy)
|
||||
if [[ -z "$country_code" ]] && command -v geoiplookup &>/dev/null; then
|
||||
if [[ "$ip" == *:* ]]; then
|
||||
# IPv6
|
||||
if command -v geoiplookup6 &>/dev/null; then
|
||||
country_code=$(geoiplookup6 "$ip" 2>/dev/null \
|
||||
| grep -oP '(?<=: )[A-Z]{2}(?=,)' | head -1 || true)
|
||||
fi
|
||||
else
|
||||
# IPv4
|
||||
country_code=$(geoiplookup "$ip" 2>/dev/null \
|
||||
| grep -oP '(?<=: )[A-Z]{2}(?=,)' | head -1 || true)
|
||||
fi
|
||||
fi
|
||||
|
||||
# Methode 3: mmdblookup (libmaxminddb)
|
||||
if [[ -z "$country_code" && -n "$effective_mmdb" && -f "$effective_mmdb" ]] && command -v mmdblookup &>/dev/null; then
|
||||
country_code=$(mmdblookup --file "$effective_mmdb" --ip "$ip" country iso_code 2>/dev/null \
|
||||
| grep -oP '"[A-Z]{2}"' | tr -d '"' | head -1 || true)
|
||||
fi
|
||||
|
||||
if [[ -n "$country_code" ]]; then
|
||||
echo "$country_code" > "$cache_file"
|
||||
echo "$country_code"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Unbekannt – nicht cachen (könnte temporärer Fehler sein)
|
||||
echo ""
|
||||
return 1
|
||||
}
|
||||
|
||||
# ─── GeoIP Prüfung: Soll eine IP gesperrt werden? ────────────────────────────
|
||||
# Return 0 = sperren, Return 1 = erlauben
|
||||
should_block_by_geoip() {
|
||||
local country_code="$1"
|
||||
local mode="${GEOIP_MODE:-blocklist}"
|
||||
local countries="${GEOIP_COUNTRIES:-}"
|
||||
|
||||
[[ -z "$country_code" || -z "$countries" ]] && return 1
|
||||
|
||||
# Länder-Liste in Array umwandeln
|
||||
IFS=',' read -ra country_list <<< "$countries"
|
||||
|
||||
local found=false
|
||||
for c in "${country_list[@]}"; do
|
||||
c=$(echo "$c" | xargs | tr '[:lower:]' '[:upper:]') # trim + uppercase
|
||||
if [[ "$country_code" == "$c" ]]; then
|
||||
found=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$mode" == "blocklist" ]]; then
|
||||
# Blocklist-Modus: Sperren wenn Land in der Liste
|
||||
[[ "$found" == "true" ]] && return 0 || return 1
|
||||
elif [[ "$mode" == "allowlist" ]]; then
|
||||
# Allowlist-Modus: Sperren wenn Land NICHT in der Liste
|
||||
[[ "$found" == "true" ]] && return 1 || return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# ─── IP via iptables sperren ─────────────────────────────────────────────────
|
||||
ban_ip_geoip() {
|
||||
local client_ip="$1"
|
||||
local country_code="$2"
|
||||
local mode="${GEOIP_MODE:-blocklist}"
|
||||
|
||||
# Prüfen ob bereits gesperrt
|
||||
local state_file="${STATE_DIR}/${client_ip//[:\/]/_}.ban"
|
||||
if [[ -f "$state_file" ]]; then
|
||||
log "DEBUG" "GeoIP: $client_ip ist bereits gesperrt"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# GeoIP-Sperren sind immer permanent
|
||||
local ban_until=0
|
||||
local ban_until_display="PERMANENT"
|
||||
|
||||
local reason_text
|
||||
if [[ "$mode" == "blocklist" ]]; then
|
||||
reason_text="geoip-blocklist (Land: $country_code)"
|
||||
else
|
||||
reason_text="geoip-allowlist (Land: $country_code)"
|
||||
fi
|
||||
|
||||
log "WARN" "GeoIP SPERRE: $client_ip (Land: $country_code, Modus: $mode) PERMANENT"
|
||||
|
||||
# iptables Regel setzen
|
||||
if [[ "$client_ip" == *:* ]]; then
|
||||
ip6tables -I "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||
else
|
||||
iptables -I "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# State-Datei erstellen
|
||||
cat > "$state_file" << EOF
|
||||
CLIENT_IP=$client_ip
|
||||
DOMAIN=GeoIP:${country_code}
|
||||
COUNT=-
|
||||
BAN_TIME=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
BAN_UNTIL_EPOCH=0
|
||||
BAN_UNTIL=PERMANENT
|
||||
BAN_DURATION=0
|
||||
OFFENSE_LEVEL=0
|
||||
IS_PERMANENT=true
|
||||
REASON=geoip
|
||||
PROTOCOL=-
|
||||
GEOIP_COUNTRY=$country_code
|
||||
GEOIP_MODE=$mode
|
||||
EOF
|
||||
|
||||
# Ban-History
|
||||
log_ban_history "BAN" "$client_ip" "$country_code" "$reason_text"
|
||||
|
||||
# Benachrichtigung senden
|
||||
if [[ "${GEOIP_NOTIFY:-true}" == "true" && "${NOTIFY_ENABLED:-false}" == "true" ]]; then
|
||||
send_geoip_notification "ban" "$client_ip" "$country_code" "PERMANENT" "$mode"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── GeoIP Benachrichtigung ──────────────────────────────────────────────────
|
||||
send_geoip_notification() {
|
||||
local action="$1"
|
||||
local client_ip="$2"
|
||||
local country_code="$3"
|
||||
local duration="${4:-PERMANENT}"
|
||||
local mode="${5:-blocklist}"
|
||||
local my_hostname
|
||||
my_hostname=$(hostname)
|
||||
|
||||
local title="🌍 🛡️ AdGuard Shield"
|
||||
local mode_label
|
||||
[[ "$mode" == "blocklist" ]] && mode_label="Blocklist" || mode_label="Allowlist"
|
||||
|
||||
local message="🌍 AdGuard Shield GeoIP-Sperre auf ${my_hostname}
|
||||
---
|
||||
IP: ${client_ip}
|
||||
Land: ${country_code}
|
||||
Modus: ${mode_label}
|
||||
Dauer: ${duration}
|
||||
|
||||
Whois: https://www.whois.com/whois/${client_ip}
|
||||
AbuseIPDB: https://www.abuseipdb.com/check/${client_ip}"
|
||||
|
||||
case "${NOTIFY_TYPE:-}" in
|
||||
discord)
|
||||
local json_payload
|
||||
json_payload=$(jq -nc --arg msg "$message" '{content: $msg}')
|
||||
curl -s -H "Content-Type: application/json" \
|
||||
-d "$json_payload" \
|
||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||
;;
|
||||
slack)
|
||||
local json_payload
|
||||
json_payload=$(jq -nc --arg msg "$message" '{text: $msg}')
|
||||
curl -s -H "Content-Type: application/json" \
|
||||
-d "$json_payload" \
|
||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||
;;
|
||||
gotify)
|
||||
local clean_message
|
||||
clean_message=$(echo "$message" | sed 's/\*\*//g')
|
||||
curl -s -X POST "$NOTIFY_WEBHOOK_URL" \
|
||||
-F "title=${title}" \
|
||||
-F "message=${clean_message}" \
|
||||
-F "priority=5" &>/dev/null &
|
||||
;;
|
||||
ntfy)
|
||||
local ntfy_url="${NTFY_SERVER_URL:-https://ntfy.sh}"
|
||||
local -a curl_args=(
|
||||
-s -X POST
|
||||
"${ntfy_url}/${NTFY_TOPIC}"
|
||||
-H "Title: 🛡️ AdGuard Shield GeoIP"
|
||||
-H "Priority: ${NTFY_PRIORITY:-4}"
|
||||
-H "Tags: globe_with_meridians,ban"
|
||||
-d "$message"
|
||||
)
|
||||
[[ -n "${NTFY_TOKEN:-}" ]] && curl_args+=(-H "Authorization: Bearer ${NTFY_TOKEN}")
|
||||
curl "${curl_args[@]}" &>/dev/null &
|
||||
;;
|
||||
generic)
|
||||
local json_payload
|
||||
json_payload=$(jq -nc --arg msg "$message" --arg cl "$client_ip" --arg cc "$country_code" \
|
||||
'{message: $msg, action: "geoip-ban", client: $cl, country: $cc}')
|
||||
curl -s -H "Content-Type: application/json" \
|
||||
-d "$json_payload" \
|
||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ─── iptables Chain Setup ────────────────────────────────────────────────────
|
||||
setup_iptables_chain() {
|
||||
if ! iptables -n -L "$IPTABLES_CHAIN" &>/dev/null; then
|
||||
iptables -N "$IPTABLES_CHAIN"
|
||||
for port in $BLOCKED_PORTS; do
|
||||
iptables -I INPUT -p tcp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
iptables -I INPUT -p udp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
done
|
||||
fi
|
||||
if ! ip6tables -n -L "$IPTABLES_CHAIN" &>/dev/null; then
|
||||
ip6tables -N "$IPTABLES_CHAIN"
|
||||
for port in $BLOCKED_PORTS; do
|
||||
ip6tables -I INPUT -p tcp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
ip6tables -I INPUT -p udp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── GeoIP-Tools Verfügbarkeit prüfen ────────────────────────────────────────
|
||||
check_geoip_tools() {
|
||||
# Effektiven MMDB-Pfad ermitteln
|
||||
local effective_mmdb
|
||||
effective_mmdb=$(resolve_mmdb_path 2>/dev/null) || true
|
||||
|
||||
# mmdbinspect + MMDB
|
||||
if [[ -n "$effective_mmdb" && -f "$effective_mmdb" ]]; then
|
||||
if command -v mmdbinspect &>/dev/null; then
|
||||
echo "mmdbinspect"
|
||||
return 0
|
||||
elif command -v mmdblookup &>/dev/null; then
|
||||
echo "mmdblookup"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# geoiplookup (Legacy GeoIP)
|
||||
if command -v geoiplookup &>/dev/null; then
|
||||
echo "geoiplookup"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "none"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ─── Client-IPs aus AdGuard API extrahieren ──────────────────────────────────
|
||||
get_active_clients() {
|
||||
local response
|
||||
response=$(curl -s -u "${ADGUARD_USER}:${ADGUARD_PASS}" \
|
||||
--connect-timeout 5 \
|
||||
--max-time 10 \
|
||||
-k "${ADGUARD_URL}/control/querylog?limit=${API_QUERY_LIMIT:-500}&response_status=all" 2>/dev/null)
|
||||
|
||||
if [[ -z "$response" || "$response" == "null" ]]; then
|
||||
log "ERROR" "Keine Antwort von AdGuard Home API"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Eindeutige Client-IPs extrahieren
|
||||
echo "$response" | jq -r '.data // [] | [.[].client // .[].client_info.ip] | unique | .[]' 2>/dev/null | sort -u
|
||||
}
|
||||
|
||||
# ─── Auto-Unban: GeoIP-Sperren aufheben bei Konfigurationsänderung ────────────
|
||||
# Prüft alle bestehenden GeoIP-Sperren und hebt sie auf, wenn:
|
||||
# - Das Land nicht mehr in GEOIP_COUNTRIES steht
|
||||
# - Der Modus gewechselt wurde (blocklist ↔ allowlist)
|
||||
# - GeoIP deaktiviert wurde
|
||||
auto_unban_geoip() {
|
||||
local unban_count=0
|
||||
|
||||
for f in "${STATE_DIR}"/*.ban; do
|
||||
[[ -f "$f" ]] || continue
|
||||
|
||||
local reason
|
||||
reason=$(grep '^REASON=' "$f" | cut -d= -f2 || true)
|
||||
[[ "$reason" != "geoip" ]] && continue
|
||||
|
||||
local client_ip country_code old_mode
|
||||
client_ip=$(grep '^CLIENT_IP=' "$f" | cut -d= -f2 || true)
|
||||
country_code=$(grep '^GEOIP_COUNTRY=' "$f" | cut -d= -f2 || true)
|
||||
old_mode=$(grep '^GEOIP_MODE=' "$f" | cut -d= -f2 || true)
|
||||
|
||||
local should_unban=false
|
||||
|
||||
# GeoIP deaktiviert → alle GeoIP-Sperren aufheben
|
||||
if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then
|
||||
should_unban=true
|
||||
# Modus gewechselt → alle GeoIP-Sperren aufheben und neu prüfen
|
||||
elif [[ -n "$old_mode" && "$old_mode" != "${GEOIP_MODE:-blocklist}" ]]; then
|
||||
should_unban=true
|
||||
# Prüfen ob das Land nach aktueller Konfiguration noch gesperrt sein soll
|
||||
elif [[ -n "$country_code" ]] && ! should_block_by_geoip "$country_code"; then
|
||||
should_unban=true
|
||||
fi
|
||||
|
||||
if [[ "$should_unban" == "true" ]]; then
|
||||
log "INFO" "GeoIP Auto-Unban: $client_ip (Land: ${country_code:-?}, war: ${old_mode:-?})"
|
||||
|
||||
# iptables Regel entfernen
|
||||
if [[ "$client_ip" == *:* ]]; then
|
||||
ip6tables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||
else
|
||||
iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||
fi
|
||||
|
||||
rm -f "$f"
|
||||
log_ban_history "UNBAN" "$client_ip" "$country_code" "geoip-auto-unban"
|
||||
unban_count=$((unban_count + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $unban_count -gt 0 ]]; then
|
||||
log "INFO" "GeoIP Auto-Unban: $unban_count Sperren aufgehoben (Länderliste/Modus geändert)"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Einmaliger GeoIP-Sync ──────────────────────────────────────────────────
|
||||
sync_geoip() {
|
||||
# Auto-Unban zuerst: bestehende Sperren prüfen, die nicht mehr zur Config passen
|
||||
auto_unban_geoip
|
||||
|
||||
if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then
|
||||
log "INFO" "GeoIP ist deaktiviert"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# MaxMind DB automatisch herunterladen/aktualisieren (falls License-Key gesetzt)
|
||||
update_maxmind_db || true
|
||||
|
||||
local countries="${GEOIP_COUNTRIES:-}"
|
||||
if [[ -z "$countries" ]]; then
|
||||
log "WARN" "GeoIP: Keine Länder konfiguriert (GEOIP_COUNTRIES ist leer)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local tool
|
||||
tool=$(check_geoip_tools) || {
|
||||
log "ERROR" "GeoIP: Kein GeoIP-Tool verfügbar. Installiere geoip-bin oder mmdbinspect."
|
||||
return 1
|
||||
}
|
||||
log "INFO" "GeoIP-Sync gestartet (Tool: $tool, Modus: ${GEOIP_MODE:-blocklist}, Länder: $countries)"
|
||||
|
||||
# Client-IPs aus der API holen
|
||||
local clients
|
||||
clients=$(get_active_clients) || {
|
||||
log "ERROR" "GeoIP: Konnte aktive Clients nicht ermitteln"
|
||||
return 1
|
||||
}
|
||||
|
||||
local checked=0
|
||||
local blocked=0
|
||||
local skipped=0
|
||||
|
||||
while IFS= read -r client_ip; do
|
||||
[[ -z "$client_ip" || "$client_ip" == "null" ]] && continue
|
||||
|
||||
# Private IPs überspringen
|
||||
if [[ "${GEOIP_SKIP_PRIVATE:-true}" == "true" ]] && is_private_ip "$client_ip"; then
|
||||
log "DEBUG" "GeoIP: Private IP übersprungen: $client_ip"
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Whitelist prüfen
|
||||
if is_whitelisted "$client_ip"; then
|
||||
log "DEBUG" "GeoIP: Whitelisted IP übersprungen: $client_ip"
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Bereits gesperrt?
|
||||
local state_file="${STATE_DIR}/${client_ip//[:\/]/_}.ban"
|
||||
if [[ -f "$state_file" ]]; then
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
checked=$((checked + 1))
|
||||
|
||||
# GeoIP Lookup
|
||||
local country_code
|
||||
country_code=$(geoip_lookup "$client_ip") || true
|
||||
|
||||
if [[ -z "$country_code" ]]; then
|
||||
log "DEBUG" "GeoIP: Kein Ergebnis für $client_ip"
|
||||
continue
|
||||
fi
|
||||
|
||||
log "DEBUG" "GeoIP: $client_ip → $country_code"
|
||||
|
||||
# Prüfen ob gesperrt werden soll
|
||||
if should_block_by_geoip "$country_code"; then
|
||||
ban_ip_geoip "$client_ip" "$country_code"
|
||||
blocked=$((blocked + 1))
|
||||
fi
|
||||
done <<< "$clients"
|
||||
|
||||
log "INFO" "GeoIP-Sync abgeschlossen: $checked geprüft, $blocked gesperrt, $skipped übersprungen"
|
||||
}
|
||||
|
||||
# ─── Worker-Hauptschleife ────────────────────────────────────────────────────
|
||||
start_worker() {
|
||||
if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then
|
||||
log "DEBUG" "GeoIP-Worker ist deaktiviert"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# PID schreiben
|
||||
echo $$ > "$WORKER_PID_FILE"
|
||||
trap 'rm -f "$WORKER_PID_FILE"; exit 0' SIGTERM SIGINT SIGHUP
|
||||
|
||||
local interval="${GEOIP_CHECK_INTERVAL:-0}"
|
||||
[[ "$interval" -le 0 ]] && interval="${CHECK_INTERVAL:-10}"
|
||||
|
||||
log "INFO" "GeoIP-Worker gestartet (PID: $$, Intervall: ${interval}s)"
|
||||
|
||||
# Beim Start: MaxMind DB herunterladen/aktualisieren (falls License-Key gesetzt)
|
||||
update_maxmind_db || true
|
||||
|
||||
while true; do
|
||||
sync_geoip
|
||||
sleep "$interval"
|
||||
done
|
||||
}
|
||||
|
||||
# ─── Status anzeigen ─────────────────────────────────────────────────────────
|
||||
show_status() {
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " AdGuard Shield - GeoIP Status"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then
|
||||
echo " ℹ️ GeoIP ist deaktiviert"
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
echo " Modus: ${GEOIP_MODE:-blocklist}"
|
||||
echo " Länder: ${GEOIP_COUNTRIES:-<keine>}"
|
||||
echo " Sperrdauer: PERMANENT (Auto-Unban bei Änderung der Länderliste)"
|
||||
echo " Private IPs überspringen: ${GEOIP_SKIP_PRIVATE:-true}"
|
||||
echo ""
|
||||
|
||||
# MaxMind DB Info
|
||||
local eff_mmdb
|
||||
eff_mmdb=$(resolve_mmdb_path)
|
||||
if [[ -n "${GEOIP_MMDB_PATH:-}" ]]; then
|
||||
echo " MMDB-Pfad: ${GEOIP_MMDB_PATH} (manuell konfiguriert)"
|
||||
elif [[ -n "${GEOIP_LICENSE_KEY:-}" ]]; then
|
||||
echo " MMDB-Pfad: ${GEOIP_AUTO_DB} (Auto-Download)"
|
||||
if [[ -f "${GEOIP_AUTO_DB}" ]]; then
|
||||
local db_age db_age_h
|
||||
db_age=$(( $(date +%s) - $(stat -c %Y "${GEOIP_AUTO_DB}" 2>/dev/null || echo 0) ))
|
||||
db_age_h=$(( db_age / 3600 ))
|
||||
echo " DB-Alter: ${db_age_h}h (Update alle 24h)"
|
||||
else
|
||||
echo " DB-Status: ⚠️ Noch nicht heruntergeladen"
|
||||
fi
|
||||
elif [[ -n "$eff_mmdb" ]]; then
|
||||
echo " MMDB-Pfad: ${eff_mmdb}"
|
||||
else
|
||||
echo " MMDB-Pfad: <nicht konfiguriert> (Fallback auf geoiplookup)"
|
||||
fi
|
||||
echo " License-Key: $(if [[ -n "${GEOIP_LICENSE_KEY:-}" ]]; then echo "✅ konfiguriert"; else echo "❌ nicht gesetzt (kein Auto-Download)"; fi)"
|
||||
echo ""
|
||||
|
||||
# GeoIP Tools prüfen
|
||||
echo " GeoIP Tools:"
|
||||
local tool
|
||||
tool=$(check_geoip_tools 2>/dev/null) || tool="none"
|
||||
case "$tool" in
|
||||
mmdbinspect) echo " ✅ mmdbinspect mit MaxMind DB" ;;
|
||||
mmdblookup) echo " ✅ mmdblookup mit MaxMind DB" ;;
|
||||
geoiplookup) echo " ✅ geoiplookup (Legacy GeoIP)" ;;
|
||||
none) echo " ❌ Kein GeoIP-Tool gefunden!" ;;
|
||||
esac
|
||||
echo ""
|
||||
|
||||
# Worker-Status
|
||||
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||||
local wpid
|
||||
wpid=$(cat "$WORKER_PID_FILE")
|
||||
if kill -0 "$wpid" 2>/dev/null; then
|
||||
echo " Worker: Läuft (PID: $wpid)"
|
||||
else
|
||||
echo " Worker: Abgestürzt (PID: $wpid existiert nicht mehr)"
|
||||
fi
|
||||
else
|
||||
echo " Worker: Nicht gestartet"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# GeoIP-Sperren anzeigen
|
||||
local geoip_bans=0
|
||||
if [[ -d "$STATE_DIR" ]]; then
|
||||
for f in "${STATE_DIR}"/*.ban; do
|
||||
[[ -f "$f" ]] || continue
|
||||
local reason
|
||||
reason=$(grep '^REASON=' "$f" | cut -d= -f2 || true)
|
||||
if [[ "$reason" == "geoip" ]]; then
|
||||
geoip_bans=$((geoip_bans + 1))
|
||||
local s_ip s_country s_until
|
||||
s_ip=$(grep '^CLIENT_IP=' "$f" | cut -d= -f2 || true)
|
||||
s_country=$(grep '^GEOIP_COUNTRY=' "$f" | cut -d= -f2 || true)
|
||||
s_until=$(grep '^BAN_UNTIL=' "$f" | cut -d= -f2 || true)
|
||||
echo " 🌍 $s_ip → Land: ${s_country:-?} (bis: ${s_until:-?})"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ $geoip_bans -eq 0 ]]; then
|
||||
echo " Keine aktiven GeoIP-Sperren"
|
||||
else
|
||||
echo ""
|
||||
echo " Gesamt: $geoip_bans aktive GeoIP-Sperren"
|
||||
fi
|
||||
|
||||
# Cache-Statistik
|
||||
if [[ -d "$GEOIP_CACHE_DIR" ]]; then
|
||||
local cache_count
|
||||
cache_count=$(find "$GEOIP_CACHE_DIR" -name '*.country' -type f 2>/dev/null | wc -l)
|
||||
echo ""
|
||||
echo " Cache: $cache_count IP-Lookups zwischengespeichert"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
}
|
||||
|
||||
# ─── Einzelne IP nachschlagen ────────────────────────────────────────────────
|
||||
lookup_ip() {
|
||||
local ip="$1"
|
||||
|
||||
local eff_mmdb
|
||||
eff_mmdb=$(resolve_mmdb_path)
|
||||
|
||||
local tool
|
||||
tool=$(check_geoip_tools 2>/dev/null) || tool="none"
|
||||
|
||||
if [[ "$tool" == "none" ]]; then
|
||||
echo "❌ Kein GeoIP-Tool verfügbar."
|
||||
echo " Installiere geoip-bin: sudo apt install geoip-bin geoip-database"
|
||||
echo " Oder mmdbinspect mit MaxMind GeoLite2 DB"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local country_code
|
||||
country_code=$(geoip_lookup "$ip") || true
|
||||
|
||||
if [[ -z "$country_code" ]]; then
|
||||
echo "IP: $ip → Land: unbekannt (kein GeoIP-Ergebnis)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "IP: $ip → Land: $country_code (Tool: $tool)"
|
||||
[[ -n "$eff_mmdb" ]] && echo " MMDB: $eff_mmdb"
|
||||
|
||||
# Prüfen ob diese IP gesperrt werden würde
|
||||
if [[ "${GEOIP_ENABLED:-false}" == "true" && -n "${GEOIP_COUNTRIES:-}" ]]; then
|
||||
if should_block_by_geoip "$country_code"; then
|
||||
echo "→ Würde GESPERRT werden (Modus: ${GEOIP_MODE:-blocklist}, Länder: ${GEOIP_COUNTRIES})"
|
||||
else
|
||||
echo "→ Würde ERLAUBT werden (Modus: ${GEOIP_MODE:-blocklist}, Länder: ${GEOIP_COUNTRIES})"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Cache leeren ────────────────────────────────────────────────────────────
|
||||
flush_cache() {
|
||||
if [[ -d "$GEOIP_CACHE_DIR" ]]; then
|
||||
local count
|
||||
count=$(find "$GEOIP_CACHE_DIR" -name '*.country' -type f 2>/dev/null | wc -l)
|
||||
rm -f "${GEOIP_CACHE_DIR}"/*.country 2>/dev/null || true
|
||||
echo "✅ GeoIP-Cache geleert ($count Einträge entfernt)"
|
||||
log "INFO" "GeoIP-Cache geleert ($count Einträge)"
|
||||
else
|
||||
echo "ℹ️ GeoIP-Cache-Verzeichnis existiert nicht"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── GeoIP-Sperren aufheben ─────────────────────────────────────────────────
|
||||
flush_geoip_bans() {
|
||||
local count=0
|
||||
if [[ -d "$STATE_DIR" ]]; then
|
||||
for f in "${STATE_DIR}"/*.ban; do
|
||||
[[ -f "$f" ]] || continue
|
||||
local reason
|
||||
reason=$(grep '^REASON=' "$f" | cut -d= -f2 || true)
|
||||
if [[ "$reason" == "geoip" ]]; then
|
||||
local client_ip
|
||||
client_ip=$(grep '^CLIENT_IP=' "$f" | cut -d= -f2 || true)
|
||||
|
||||
# iptables Regel entfernen
|
||||
if [[ "$client_ip" == *:* ]]; then
|
||||
ip6tables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||
else
|
||||
iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||
fi
|
||||
|
||||
rm -f "$f"
|
||||
log_ban_history "UNBAN" "$client_ip" "" "geoip-flush"
|
||||
count=$((count + 1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "✅ $count GeoIP-Sperren aufgehoben"
|
||||
log "INFO" "$count GeoIP-Sperren aufgehoben (flush)"
|
||||
}
|
||||
|
||||
# ─── Hauptprogramm ──────────────────────────────────────────────────────────
|
||||
case "${1:-help}" in
|
||||
start)
|
||||
init_directories
|
||||
setup_iptables_chain
|
||||
start_worker
|
||||
;;
|
||||
sync)
|
||||
init_directories
|
||||
setup_iptables_chain
|
||||
sync_geoip
|
||||
;;
|
||||
status)
|
||||
init_directories
|
||||
show_status
|
||||
;;
|
||||
lookup)
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "Nutzung: $0 lookup <IP-Adresse>" >&2
|
||||
exit 1
|
||||
fi
|
||||
init_directories
|
||||
lookup_ip "$2"
|
||||
;;
|
||||
flush)
|
||||
init_directories
|
||||
flush_geoip_bans
|
||||
;;
|
||||
flush-cache)
|
||||
init_directories
|
||||
flush_cache
|
||||
;;
|
||||
stop)
|
||||
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||||
local wpid
|
||||
wpid=$(cat "$WORKER_PID_FILE")
|
||||
if kill -0 "$wpid" 2>/dev/null; then
|
||||
kill "$wpid" 2>/dev/null || true
|
||||
rm -f "$WORKER_PID_FILE"
|
||||
echo "GeoIP-Worker gestoppt"
|
||||
else
|
||||
rm -f "$WORKER_PID_FILE"
|
||||
echo "GeoIP-Worker war nicht aktiv"
|
||||
fi
|
||||
else
|
||||
echo "GeoIP-Worker läuft nicht"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
cat << USAGE
|
||||
AdGuard Shield - GeoIP Worker
|
||||
|
||||
Nutzung: $0 {start|stop|sync|status|lookup|flush|flush-cache}
|
||||
|
||||
Befehle:
|
||||
start Startet den GeoIP-Worker (Hintergrundprozess)
|
||||
stop Stoppt den GeoIP-Worker
|
||||
sync Einmalige GeoIP-Prüfung aller aktiven Clients
|
||||
status Zeigt GeoIP-Status und aktive Sperren
|
||||
lookup <IP> GeoIP-Lookup für eine einzelne IP
|
||||
flush Alle GeoIP-Sperren aufheben
|
||||
flush-cache GeoIP-Lookup-Cache leeren
|
||||
|
||||
Konfiguration in: $CONFIG_FILE
|
||||
GEOIP_ENABLED=true/false
|
||||
GEOIP_MODE=blocklist/allowlist
|
||||
GEOIP_COUNTRIES="CN,RU,..."
|
||||
|
||||
USAGE
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
18
install.sh
18
install.sh
@@ -6,7 +6,7 @@
|
||||
# Lizenz: MIT
|
||||
###############################################################################
|
||||
|
||||
VERSION="v0.7.1"
|
||||
VERSION="v0.8.1"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -132,11 +132,18 @@ print_help() {
|
||||
echo -e " ${CYAN}sudo systemctl disable adguard-shield-watchdog.timer${NC} # Watchdog deaktivieren"
|
||||
echo -e " ${CYAN}sudo journalctl -u adguard-shield-watchdog.service${NC} # Watchdog-Logs"
|
||||
echo ""
|
||||
echo -e "${BOLD}GeoIP-Befehle:${NC}"
|
||||
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh geoip-status${NC} # GeoIP-Status anzeigen"
|
||||
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh geoip-sync${NC} # Einmalige GeoIP-Prüfung"
|
||||
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh geoip-flush${NC} # Alle GeoIP-Sperren aufheben"
|
||||
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh geoip-lookup IP${NC} # GeoIP-Lookup einer IP"
|
||||
echo ""
|
||||
echo -e "${BOLD}Voraussetzungen:${NC}"
|
||||
echo " - Linux Server (Debian/Ubuntu empfohlen)"
|
||||
echo " - Root-Zugriff (sudo)"
|
||||
echo " - AdGuard Home installiert und erreichbar"
|
||||
echo " - Pakete: curl, jq, iptables, gawk (werden bei Installation automatisch installiert)"
|
||||
echo " - GeoIP (optional): geoip-bin + geoip-database oder MaxMind GeoLite2 DB"
|
||||
echo ""
|
||||
echo -e "${BOLD}Dokumentation:${NC}"
|
||||
echo " https://git.techniverse.net/scriptos/adguard-shield"
|
||||
@@ -257,6 +264,8 @@ install_files() {
|
||||
cp "$SCRIPT_DIR/report-generator.sh" "$INSTALL_DIR/"
|
||||
cp "$SCRIPT_DIR/adguard-shield-watchdog.sh" "$INSTALL_DIR/"
|
||||
cp "$SCRIPT_DIR/uninstall.sh" "$INSTALL_DIR/"
|
||||
cp "$SCRIPT_DIR/geoip-worker.sh" "$INSTALL_DIR/"
|
||||
cp "$SCRIPT_DIR/offense-cleanup-worker.sh" "$INSTALL_DIR/"
|
||||
|
||||
# Templates kopieren
|
||||
mkdir -p "$INSTALL_DIR/templates"
|
||||
@@ -272,6 +281,8 @@ install_files() {
|
||||
chmod +x "$INSTALL_DIR/report-generator.sh"
|
||||
chmod +x "$INSTALL_DIR/adguard-shield-watchdog.sh"
|
||||
chmod +x "$INSTALL_DIR/uninstall.sh"
|
||||
chmod +x "$INSTALL_DIR/geoip-worker.sh"
|
||||
chmod +x "$INSTALL_DIR/offense-cleanup-worker.sh"
|
||||
|
||||
echo -e " ✅ Dateien installiert"
|
||||
echo ""
|
||||
@@ -801,8 +812,13 @@ do_uninstall() {
|
||||
rm -f "$INSTALL_DIR/unban-expired.sh"
|
||||
rm -f "$INSTALL_DIR/external-blocklist-worker.sh"
|
||||
rm -f "$INSTALL_DIR/external-whitelist-worker.sh"
|
||||
rm -f "$INSTALL_DIR/offense-cleanup-worker.sh"
|
||||
rm -f "$INSTALL_DIR/geoip-worker.sh"
|
||||
rm -f "$INSTALL_DIR/report-generator.sh"
|
||||
rm -f "$INSTALL_DIR/adguard-shield-watchdog.sh"
|
||||
rm -f "$INSTALL_DIR/uninstall.sh"
|
||||
rm -rf "$INSTALL_DIR/templates"
|
||||
rm -rf "$INSTALL_DIR/geoip"
|
||||
echo " ✅ Scripts entfernt (Konfiguration und Logs behalten)"
|
||||
else
|
||||
rm -rf "$INSTALL_DIR"
|
||||
|
||||
268
offense-cleanup-worker.sh
Normal file
268
offense-cleanup-worker.sh
Normal file
@@ -0,0 +1,268 @@
|
||||
#!/bin/bash
|
||||
###############################################################################
|
||||
# AdGuard Shield - Offense-Cleanup-Worker
|
||||
# Räumt abgelaufene Offense-Zähler (progressive Sperren) automatisch auf.
|
||||
# Entfernt .offenses-Dateien, deren letztes Vergehen länger als
|
||||
# PROGRESSIVE_BAN_RESET_AFTER zurückliegt.
|
||||
# Wird als Hintergrundprozess vom Hauptscript gestartet.
|
||||
#
|
||||
# Autor: Patrick Asmus
|
||||
# E-Mail: support@techniverse.net
|
||||
# Datum: 2026-04-16
|
||||
# Lizenz: MIT
|
||||
###############################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_FILE="${SCRIPT_DIR}/adguard-shield.conf"
|
||||
|
||||
# ─── Konfiguration laden ───────────────────────────────────────────────────────
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
echo "FEHLER: Konfigurationsdatei nicht gefunden: $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
# shellcheck source=adguard-shield.conf
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
# ─── Niedrigste Priorität setzen (CPU + I/O) ─────────────────────────────────
|
||||
# Stellt sicher, dass der Worker auch bei manuellem Start nie andere Dienste
|
||||
# verdrängt. nice 19 = niedrigste CPU-Priorität, ionice idle = nur bei freier I/O.
|
||||
renice -n 19 $$ >/dev/null 2>&1 || true
|
||||
ionice -c 3 -p $$ >/dev/null 2>&1 || true
|
||||
|
||||
# ─── Worker PID-File ──────────────────────────────────────────────────────────
|
||||
WORKER_PID_FILE="/var/run/adguard-offense-cleanup-worker.pid"
|
||||
|
||||
# ─── Prüfintervall ───────────────────────────────────────────────────────────
|
||||
# Prüft einmal pro Stunde – das ist völlig ausreichend für diese Aufgabe
|
||||
OFFENSE_CLEANUP_INTERVAL=3600
|
||||
|
||||
# ─── Logging (eigene Funktion, nutzt gleiche Log-Datei) ───────────────────────
|
||||
declare -A LOG_LEVELS=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3)
|
||||
|
||||
log() {
|
||||
local level="$1"
|
||||
shift
|
||||
local message="$*"
|
||||
local configured_level="${LOG_LEVEL:-INFO}"
|
||||
|
||||
if [[ ${LOG_LEVELS[$level]:-1} -ge ${LOG_LEVELS[$configured_level]:-1} ]]; then
|
||||
local timestamp
|
||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
local log_entry="[$timestamp] [$level] [OFFENSE-CLEANUP] $message"
|
||||
echo "$log_entry" | tee -a "$LOG_FILE" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Hilfsfunktionen ─────────────────────────────────────────────────────────
|
||||
format_duration() {
|
||||
local seconds="$1"
|
||||
if [[ "$seconds" -eq 0 ]]; then
|
||||
echo "PERMANENT"
|
||||
return
|
||||
fi
|
||||
if [[ "$seconds" -ge 86400 ]]; then
|
||||
echo "$((seconds / 86400))d $((seconds % 86400 / 3600))h"
|
||||
elif [[ "$seconds" -ge 3600 ]]; then
|
||||
echo "$((seconds / 3600))h $((seconds % 3600 / 60))m"
|
||||
elif [[ "$seconds" -ge 60 ]]; then
|
||||
echo "$((seconds / 60))m $((seconds % 60))s"
|
||||
else
|
||||
echo "${seconds}s"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Verzeichnisse erstellen ──────────────────────────────────────────────────
|
||||
init_directories() {
|
||||
mkdir -p "${STATE_DIR}"
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
}
|
||||
|
||||
# ─── Abgelaufene Offense-Zähler aufräumen ────────────────────────────────────
|
||||
cleanup_expired_offenses() {
|
||||
local reset_after="${PROGRESSIVE_BAN_RESET_AFTER:-86400}"
|
||||
local now
|
||||
now=$(date '+%s')
|
||||
local cleaned=0
|
||||
|
||||
local batch_count=0
|
||||
for offense_file in "${STATE_DIR}"/*.offenses; do
|
||||
[[ -f "$offense_file" ]] || continue
|
||||
|
||||
local last_offense_epoch client_ip offense_level
|
||||
last_offense_epoch=$(grep '^LAST_OFFENSE_EPOCH=' "$offense_file" | cut -d= -f2 || true)
|
||||
client_ip=$(grep '^CLIENT_IP=' "$offense_file" | cut -d= -f2 || true)
|
||||
offense_level=$(grep '^OFFENSE_LEVEL=' "$offense_file" | cut -d= -f2 || true)
|
||||
|
||||
# Kein Zeitstempel vorhanden → überspringen
|
||||
if [[ -z "$last_offense_epoch" ]]; then
|
||||
log "DEBUG" "Offense-Datei ohne Zeitstempel übersprungen: $offense_file"
|
||||
continue
|
||||
fi
|
||||
|
||||
local elapsed=$((now - last_offense_epoch))
|
||||
|
||||
if [[ $elapsed -gt $reset_after ]]; then
|
||||
log "INFO" "Offense-Zähler abgelaufen: $client_ip (Stufe $offense_level, letztes Vergehen vor $(format_duration $elapsed)) → entfernt"
|
||||
rm -f "$offense_file"
|
||||
cleaned=$((cleaned + 1))
|
||||
fi
|
||||
|
||||
# Alle 10 Dateien kurz pausieren, um I/O-Bursts zu vermeiden
|
||||
batch_count=$((batch_count + 1))
|
||||
if (( batch_count % 10 == 0 )); then
|
||||
sleep 0.1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $cleaned -gt 0 ]]; then
|
||||
log "INFO" "Offense-Cleanup: $cleaned abgelaufene Zähler entfernt"
|
||||
else
|
||||
log "DEBUG" "Offense-Cleanup: keine abgelaufenen Zähler gefunden"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── PID-Management ──────────────────────────────────────────────────────────
|
||||
write_pid() {
|
||||
echo $$ > "$WORKER_PID_FILE"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
log "INFO" "Offense-Cleanup-Worker wird beendet..."
|
||||
rm -f "$WORKER_PID_FILE"
|
||||
exit 0
|
||||
}
|
||||
|
||||
check_already_running() {
|
||||
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||||
local old_pid
|
||||
old_pid=$(cat "$WORKER_PID_FILE")
|
||||
if kill -0 "$old_pid" 2>/dev/null; then
|
||||
log "DEBUG" "Offense-Cleanup-Worker läuft bereits (PID: $old_pid)"
|
||||
return 1
|
||||
else
|
||||
rm -f "$WORKER_PID_FILE"
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# ─── Status anzeigen ─────────────────────────────────────────────────────────
|
||||
show_status() {
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " Offense-Cleanup-Worker - Status"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" != "true" ]]; then
|
||||
echo " ⚠️ Progressive Sperren sind deaktiviert"
|
||||
echo " Aktivieren: PROGRESSIVE_BAN_ENABLED=true in $CONFIG_FILE"
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
# Worker-Prozess Status
|
||||
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||||
local pid
|
||||
pid=$(cat "$WORKER_PID_FILE")
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
echo " 🟢 Worker läuft (PID: $pid)"
|
||||
else
|
||||
echo " 🔴 Worker nicht aktiv (veraltete PID-Datei)"
|
||||
fi
|
||||
else
|
||||
echo " 🔴 Worker nicht aktiv"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " Reset-Zeitraum: $(format_duration "${PROGRESSIVE_BAN_RESET_AFTER:-86400}")"
|
||||
echo " Prüfintervall: $(format_duration "$OFFENSE_CLEANUP_INTERVAL")"
|
||||
|
||||
# Aktuelle Offense-Dateien zählen
|
||||
local total=0
|
||||
local expired=0
|
||||
local now
|
||||
now=$(date '+%s')
|
||||
local reset_after="${PROGRESSIVE_BAN_RESET_AFTER:-86400}"
|
||||
|
||||
for offense_file in "${STATE_DIR}"/*.offenses; do
|
||||
[[ -f "$offense_file" ]] || continue
|
||||
total=$((total + 1))
|
||||
local last_epoch
|
||||
last_epoch=$(grep '^LAST_OFFENSE_EPOCH=' "$offense_file" | cut -d= -f2 || true)
|
||||
if [[ -n "$last_epoch" && $((now - last_epoch)) -gt $reset_after ]]; then
|
||||
expired=$((expired + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo " Offense-Zähler gesamt: $total"
|
||||
echo " Davon abgelaufen: $expired"
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
}
|
||||
|
||||
# ─── Hauptschleife ──────────────────────────────────────────────────────────
|
||||
main_loop() {
|
||||
init_directories
|
||||
|
||||
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||
log "INFO" "Offense-Cleanup-Worker gestartet"
|
||||
log "INFO" " Reset-Zeitraum: $(format_duration "${PROGRESSIVE_BAN_RESET_AFTER:-86400}")"
|
||||
log "INFO" " Prüfintervall: $(format_duration "$OFFENSE_CLEANUP_INTERVAL")"
|
||||
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||
|
||||
while true; do
|
||||
cleanup_expired_offenses
|
||||
sleep "$OFFENSE_CLEANUP_INTERVAL"
|
||||
done
|
||||
}
|
||||
|
||||
# ─── Signal-Handler ──────────────────────────────────────────────────────────
|
||||
trap cleanup SIGTERM SIGINT SIGHUP
|
||||
|
||||
# ─── Kommandozeilen-Argumente ────────────────────────────────────────────────
|
||||
case "${1:-start}" in
|
||||
start)
|
||||
if ! check_already_running; then
|
||||
exit 0
|
||||
fi
|
||||
write_pid
|
||||
main_loop
|
||||
;;
|
||||
stop)
|
||||
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||||
kill "$(cat "$WORKER_PID_FILE")" 2>/dev/null || true
|
||||
rm -f "$WORKER_PID_FILE"
|
||||
echo "Offense-Cleanup-Worker gestoppt"
|
||||
else
|
||||
echo "Offense-Cleanup-Worker läuft nicht"
|
||||
fi
|
||||
;;
|
||||
run-once)
|
||||
init_directories
|
||||
log "INFO" "Einmaliger Offense-Cleanup..."
|
||||
cleanup_expired_offenses
|
||||
log "INFO" "Cleanup abgeschlossen"
|
||||
;;
|
||||
status)
|
||||
init_directories
|
||||
show_status
|
||||
;;
|
||||
*)
|
||||
cat << USAGE
|
||||
AdGuard Shield - Offense-Cleanup-Worker
|
||||
|
||||
Nutzung: $0 {start|stop|run-once|status}
|
||||
|
||||
Befehle:
|
||||
start Startet den Worker (Dauerbetrieb)
|
||||
stop Stoppt den Worker
|
||||
run-once Einmaliger Cleanup-Durchlauf
|
||||
status Zeigt Status und aktuelle Offense-Zähler
|
||||
|
||||
Konfiguration: $CONFIG_FILE
|
||||
USAGE
|
||||
;;
|
||||
esac
|
||||
@@ -123,10 +123,13 @@ do_uninstall() {
|
||||
rm -f "$INSTALL_DIR/unban-expired.sh"
|
||||
rm -f "$INSTALL_DIR/external-blocklist-worker.sh"
|
||||
rm -f "$INSTALL_DIR/external-whitelist-worker.sh"
|
||||
rm -f "$INSTALL_DIR/offense-cleanup-worker.sh"
|
||||
rm -f "$INSTALL_DIR/report-generator.sh"
|
||||
rm -f "$INSTALL_DIR/adguard-shield-watchdog.sh"
|
||||
rm -f "$INSTALL_DIR/geoip-worker.sh"
|
||||
rm -f "$INSTALL_DIR/uninstall.sh"
|
||||
rm -rf "$INSTALL_DIR/templates"
|
||||
rm -rf "$INSTALL_DIR/geoip"
|
||||
echo " ✅ Scripts entfernt (Konfiguration und Logs behalten)"
|
||||
echo ""
|
||||
echo -e "${YELLOW} Konfiguration verbleibt in: $INSTALL_DIR/adguard-shield.conf${NC}"
|
||||
|
||||
Reference in New Issue
Block a user