From 5451c01603e837676143749621ce0aa4109dd953 Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Sat, 4 Apr 2026 19:57:07 +0200 Subject: [PATCH 1/2] =?UTF-8?q?Externe=20Whitelist=20mit=20DNS-Aufl=C3=B6s?= =?UTF-8?q?ung=20f=C3=BCr=20dynamische=20IPs=20(DynDNS)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 + adguard-shield.conf | 14 + adguard-shield.sh | 77 ++++- docs/architektur.md | 4 +- docs/befehle.md | 34 +++ docs/konfiguration.md | 58 ++++ external-blocklist-worker.sh | 7 + external-whitelist-worker.sh | 532 +++++++++++++++++++++++++++++++++++ install.sh | 8 + uninstall.sh | 1 + 10 files changed, 737 insertions(+), 2 deletions(-) create mode 100644 external-whitelist-worker.sh diff --git a/README.md b/README.md index eec43e3..cdbacee 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Wenn ein Client eine bestimmte Domain zu oft anfragt (z.B. >30x pro Minute), wir - 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) - **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 @@ -90,6 +91,8 @@ sudo /opt/adguard-shield/adguard-shield.sh reset-offenses # Offense-Zähler sudo /opt/adguard-shield/adguard-shield.sh test # API-Verbindung testen sudo /opt/adguard-shield/adguard-shield.sh blocklist-status # Externe Blocklisten Status 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/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 @@ -103,6 +106,7 @@ sudo journalctl -u adguard-shield -f # Logs live ver ├── adguard-shield.conf # Konfiguration ├── adguard-shield.service # systemd Unit ├── external-blocklist-worker.sh # Externer Blocklist-Worker +├── external-whitelist-worker.sh # Externer Whitelist-Worker (DynDNS-Auflösung) ├── iptables-helper.sh # Manuelle iptables-Verwaltung ├── unban-expired.sh # Cron-basiertes Entsperren ├── report-generator.sh # E-Mail Report Generator diff --git a/adguard-shield.conf b/adguard-shield.conf index bafb553..f261a06 100644 --- a/adguard-shield.conf +++ b/adguard-shield.conf @@ -134,6 +134,20 @@ REPORT_MAIL_CMD="msmtp" # 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). +EXTERNAL_WHITELIST_ENABLED=false + +# 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 EXTERNAL_BLOCKLIST_ENABLED=false diff --git a/adguard-shield.sh b/adguard-shield.sh index 2963949..4ed0467 100644 --- a/adguard-shield.sh +++ b/adguard-shield.sh @@ -329,6 +329,7 @@ cleanup() { sleep 1 fi stop_blocklist_worker + stop_whitelist_worker rm -f "$PID_FILE" exit 0 } @@ -356,6 +357,13 @@ is_whitelisted() { return 0 fi done + + # Externe Whitelist prüfen (aufgelöste IPs aus dem Whitelist-Worker) + 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 } @@ -1171,6 +1179,39 @@ stop_blocklist_worker() { fi } +# ─── Externer Whitelist-Worker starten ─────────────────────────────────────── +start_whitelist_worker() { + if [[ "${EXTERNAL_WHITELIST_ENABLED:-false}" != "true" ]]; then + log "DEBUG" "Externer Whitelist-Worker ist deaktiviert" + return + fi + + local worker_script="${SCRIPT_DIR}/external-whitelist-worker.sh" + if [[ ! -f "$worker_script" ]]; then + log "WARN" "Whitelist-Worker Script nicht gefunden: $worker_script" + return + fi + + log "INFO" "Starte externen Whitelist-Worker im Hintergrund..." + bash "$worker_script" start & + WHITELIST_WORKER_PID=$! + log "INFO" "Whitelist-Worker gestartet (PID: $WHITELIST_WORKER_PID)" +} + +# ─── Externer Whitelist-Worker stoppen ─────────────────────────────────────── +stop_whitelist_worker() { + local worker_pid_file="/var/run/adguard-whitelist-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 Whitelist-Worker (PID: $wpid)..." + kill "$wpid" 2>/dev/null || true + rm -f "$worker_pid_file" + fi + fi +} + # ─── Hauptschleife ────────────────────────────────────────────────────────── main_loop() { log "INFO" "═══════════════════════════════════════════════════════════" @@ -1181,6 +1222,7 @@ main_loop() { log "INFO" " Dry-Run: ${DRY_RUN}" log "INFO" " Whitelist: ${WHITELIST}" log "INFO" " Externe Blocklist: ${EXTERNAL_BLOCKLIST_ENABLED:-false}" + log "INFO" " Externe Whitelist: ${EXTERNAL_WHITELIST_ENABLED:-false}" if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]]; then log "INFO" " Progressive Sperren: AKTIV (×${PROGRESSIVE_BAN_MULTIPLIER:-2}, Max-Stufe: ${PROGRESSIVE_BAN_MAX_LEVEL:-0}, Reset: $(format_duration "${PROGRESSIVE_BAN_RESET_AFTER:-86400}"))" else @@ -1206,6 +1248,9 @@ main_loop() { # Blocklist-Worker als Hintergrundprozess starten start_blocklist_worker + # Whitelist-Worker als Hintergrundprozess starten + start_whitelist_worker + while true; do # Abgelaufene Sperren prüfen check_expired_bans @@ -1272,6 +1317,33 @@ case "${1:-start}" in echo "Blocklist-Worker nicht gefunden" fi ;; + whitelist-status) + init_directories + _worker_script="${SCRIPT_DIR}/external-whitelist-worker.sh" + if [[ -f "$_worker_script" ]]; then + bash "$_worker_script" status + else + echo "Whitelist-Worker nicht gefunden" + fi + ;; + whitelist-sync) + init_directories + _worker_script="${SCRIPT_DIR}/external-whitelist-worker.sh" + if [[ -f "$_worker_script" ]]; then + bash "$_worker_script" sync + else + echo "Whitelist-Worker nicht gefunden" + fi + ;; + whitelist-flush) + init_directories + _worker_script="${SCRIPT_DIR}/external-whitelist-worker.sh" + if [[ -f "$_worker_script" ]]; then + bash "$_worker_script" flush + else + echo "Whitelist-Worker nicht gefunden" + fi + ;; status) init_directories show_status @@ -1335,7 +1407,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} +Nutzung: $0 {status|history|flush|unban|reset-offenses|test|dry-run|blocklist-status|blocklist-sync|blocklist-flush|whitelist-status|whitelist-sync|whitelist-flush} Verwaltungsbefehle: status Zeigt aktive Sperren, Regeln und Wiederholungstäter @@ -1348,6 +1420,9 @@ Verwaltungsbefehle: blocklist-status Zeigt Status der externen Blocklisten blocklist-sync Einmalige Synchronisation der externen Blocklisten blocklist-flush Entfernt alle Sperren der externen Blocklisten + whitelist-status Zeigt Status der externen Whitelisten + whitelist-sync Einmalige Synchronisation der externen Whitelisten + whitelist-flush Entfernt alle aufgelösten Whitelist-IPs Interne Befehle (nicht direkt verwenden — nur über systemd): start Startet den Monitor im Vordergrund diff --git a/docs/architektur.md b/docs/architektur.md index 9594b35..c2d6ada 100644 --- a/docs/architektur.md +++ b/docs/architektur.md @@ -134,6 +134,7 @@ Das ermöglicht: ├── adguard-shield.conf.old # Backup der Konfig nach Update ├── 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 /etc/systemd/system/ @@ -142,7 +143,8 @@ Das ermöglicht: /var/lib/adguard-shield/ ├── *.ban # State-Dateien aktiver Sperren ├── *.offenses # Offense-Zähler (Progressive Sperren) -└── external-blocklist/ # Cache für externe Blocklisten +├── external-blocklist/ # Cache für externe Blocklisten +└── external-whitelist/ # Cache für externe Whitelisten + aufgelöste IPs /var/log/ ├── adguard-shield.log # Laufzeit-Log diff --git a/docs/befehle.md b/docs/befehle.md index a5f6818..c8c606f 100644 --- a/docs/befehle.md +++ b/docs/befehle.md @@ -158,6 +158,40 @@ sudo /opt/adguard-shield/iptables-helper.sh save sudo /opt/adguard-shield/iptables-helper.sh restore ``` +## Externer Whitelist-Worker + +Der Whitelist-Worker löst Domains aus externen Listen regelmäßig per DNS auf und stellt die IPs als dynamische Whitelist bereit: + +```bash +# Status anzeigen (aufgelöste IPs, konfigurierte Listen) +sudo /opt/adguard-shield/adguard-shield.sh whitelist-status + +# Einmalige Synchronisation (z.B. nach Konfigurationsänderung) +sudo /opt/adguard-shield/adguard-shield.sh whitelist-sync + +# Alle aufgelösten Whitelist-IPs entfernen +sudo /opt/adguard-shield/adguard-shield.sh whitelist-flush +``` + +Der Worker kann auch standalone gesteuert werden: + +```bash +# Worker manuell starten (normalerweise automatisch per Hauptscript) +sudo /opt/adguard-shield/external-whitelist-worker.sh start + +# Worker stoppen +sudo /opt/adguard-shield/external-whitelist-worker.sh stop + +# Einmalige Synchronisation +sudo /opt/adguard-shield/external-whitelist-worker.sh sync + +# Status anzeigen +sudo /opt/adguard-shield/external-whitelist-worker.sh status + +# Aufgelöste IPs entfernen +sudo /opt/adguard-shield/external-whitelist-worker.sh flush +``` + ## Externer Blocklist-Worker Der Worker kann auch standalone gesteuert werden: diff --git a/docs/konfiguration.md b/docs/konfiguration.md index 9dc41bd..7903779 100644 --- a/docs/konfiguration.md +++ b/docs/konfiguration.md @@ -150,6 +150,60 @@ Regelmäßige Statistik-Reports per E-Mail. Voraussetzung ist ein funktionierend | `STATE_DIR` | `/var/lib/adguard-shield` | Verzeichnis für State-Dateien | | `PID_FILE` | `/var/run/adguard-shield.pid` | PID-Datei | | `DRY_RUN` | `false` | Testmodus — nur loggen, nicht sperren | + +### Externe Whitelist + +Ermöglicht das Einbinden externer Whitelist-Dateien mit Domains und IP-Adressen. Der Worker löst Domains regelmäßig per DNS auf — ideal für DynDNS-Einträge mit wechselnden IP-Adressen. Aufgelöste IPs werden automatisch zur Whitelist hinzugefügt und bei jeder Prüfung aktualisiert. + +| Parameter | Standard | Beschreibung | +|-----------|----------|--------------| +| `EXTERNAL_WHITELIST_ENABLED` | `false` | Aktiviert den externen Whitelist-Worker | +| `EXTERNAL_WHITELIST_URLS` | *(leer)* | URL(s) zu Whitelist-Textdateien (kommagetrennt). Unterstützt IPv4, IPv6, CIDR und Hostnamen | +| `EXTERNAL_WHITELIST_INTERVAL` | `300` | Prüfintervall in Sekunden (300 = 5 Min.). Bei DynDNS-Einträgen ggf. kürzer wählen | +| `EXTERNAL_WHITELIST_CACHE_DIR` | `/var/lib/adguard-shield/external-whitelist` | Lokaler Cache für heruntergeladene Listen und aufgelöste IPs | + +#### Externe Whitelist einrichten + +1. Erstelle eine Textdatei auf einem Webserver. Pro Zeile ein Eintrag — Domain, IPv4, IPv6 oder CIDR: + +```text +# Domains (werden regelmäßig per DNS aufgelöst — ideal für DynDNS) +mein-router.dyndns.org +homeserver.example.com + +# Feste IPs +192.168.1.100 +10.0.0.0/24 +2001:db8::1 + +# Kommentare und Inline-Kommentare werden unterstützt +192.168.1.200 # Backup-Server +``` + +2. Aktiviere die Whitelist in der Konfiguration: + +```bash +EXTERNAL_WHITELIST_ENABLED=true +EXTERNAL_WHITELIST_URLS="https://example.com/whitelist.txt" +EXTERNAL_WHITELIST_INTERVAL=300 +``` + +3. Mehrere Listen können kommagetrennt angegeben werden: + +```bash +EXTERNAL_WHITELIST_URLS="https://example.com/trusted.txt,https://other.com/whitelist.txt" +``` + +4. Service neustarten: + +```bash +sudo systemctl restart adguard-shield +``` + +> **Hinweis:** Da Domains bei jedem Prüfintervall neu aufgelöst werden, eignet sich diese Funktion besonders für DynDNS-Einträge. Ändert sich die IP eines DynDNS-Hostnamens, wird die neue IP beim nächsten Sync automatisch erkannt und in die Whitelist aufgenommen. + +> **Wichtig:** Wird eine bereits gesperrte IP durch eine Whitelist-Aktualisierung gewhitelistet, wird sie **automatisch entsperrt**. + ### Externe Blocklist Ermöglicht das Einbinden externer Blocklisten, die IPv4-Adressen, IPv6-Adressen und Hostnamen enthalten können. Der Worker läuft als Hintergrundprozess, prüft periodisch auf Änderungen und löst Hostnamen automatisch über den lokalen DNS-Resolver auf. @@ -339,3 +393,7 @@ Beispiel: ``` WHITELIST="127.0.0.1,::1,192.168.1.1,192.168.1.10,fd00::1" ``` + +### Externe Whitelist für dynamische IPs + +Für Clients mit wechselnden IP-Adressen (z.B. DynDNS) kann eine **externe Whitelist** genutzt werden. Der Whitelist-Worker löst Domains regelmäßig per DNS auf und fügt die aktuellen IPs automatisch zur Whitelist hinzu. Siehe [Externe Whitelist](#externe-whitelist) für die Konfiguration. diff --git a/external-blocklist-worker.sh b/external-blocklist-worker.sh index 4ed041f..7847c44 100644 --- a/external-blocklist-worker.sh +++ b/external-blocklist-worker.sh @@ -82,6 +82,13 @@ is_whitelisted() { return 0 fi done + + # Externe Whitelist prüfen (aufgelöste IPs aus dem Whitelist-Worker) + 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 } diff --git a/external-whitelist-worker.sh b/external-whitelist-worker.sh new file mode 100644 index 0000000..890fe3e --- /dev/null +++ b/external-whitelist-worker.sh @@ -0,0 +1,532 @@ +#!/bin/bash +############################################################################### +# AdGuard Shield - Externer Whitelist-Worker +# Lädt externe Whitelist-Dateien herunter, löst Domains zu IPs auf und +# stellt diese dem Hauptscript als dynamische Whitelist zur Verfügung. +# Ideal für DynDNS-Domains mit wechselnden IP-Adressen. +# Wird als Hintergrundprozess vom Hauptscript gestartet. +# +# Autor: Patrick Asmus +# E-Mail: support@techniverse.net +# Datum: 2026-04-04 +# 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" + +# ─── Standardwerte ──────────────────────────────────────────────────────────── +EXTERNAL_WHITELIST_CACHE_DIR="${EXTERNAL_WHITELIST_CACHE_DIR:-/var/lib/adguard-shield/external-whitelist}" +EXTERNAL_WHITELIST_RESOLVED_FILE="${EXTERNAL_WHITELIST_CACHE_DIR}/resolved_ips.txt" + +# ─── Worker PID-File ────────────────────────────────────────────────────────── +WORKER_PID_FILE="/var/run/adguard-whitelist-worker.pid" + +# ─── 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] [WHITELIST-WORKER] $message" + echo "$log_entry" | tee -a "$LOG_FILE" >&2 + fi +} + +# ─── Verzeichnisse erstellen ────────────────────────────────────────────────── +init_directories() { + mkdir -p "$EXTERNAL_WHITELIST_CACHE_DIR" + mkdir -p "$(dirname "$LOG_FILE")" +} + +# ─── Eintrag-Validierung ───────────────────────────────────────────────────── + +# Prüft IPv4-Adresse mit optionalem CIDR +_is_valid_ipv4() { + local ip="$1" addr="$1" prefix="" + if [[ "$ip" == */* ]]; then + addr="${ip%/*}" + prefix="${ip#*/}" + { [[ "$prefix" =~ ^[0-9]+$ ]] && [[ "$prefix" -le 32 ]]; } || return 1 + fi + local IFS='.' + read -ra _octets <<< "$addr" + [[ ${#_octets[@]} -eq 4 ]] || return 1 + local o + for o in "${_octets[@]}"; do + [[ "$o" =~ ^[0-9]+$ ]] || return 1 + [[ "$o" -le 255 ]] || return 1 + done + return 0 +} + +# Prüft IPv6-Adresse mit optionalem CIDR +_is_valid_ipv6() { + local ip="$1" addr="$1" + if [[ "$ip" == */* ]]; then + addr="${ip%/*}" + local prefix="${ip#*/}" + { [[ "$prefix" =~ ^[0-9]+$ ]] && [[ "$prefix" -le 128 ]]; } || return 1 + fi + [[ "$addr" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9] ]] && return 1 + [[ "$addr" == *:* ]] || return 1 + [[ "$addr" =~ ^[0-9a-fA-F:\.]+$ ]] || return 1 + return 0 +} + +# Prüft ob ein Hostname syntaktisch plausibel ist +_is_valid_hostname() { + local host="$1" + host="${host%.}" # trailing dot entfernen + [[ -z "$host" ]] && return 1 + [[ ${#host} -gt 253 ]] && return 1 + [[ "$host" =~ ^[a-zA-Z0-9._-]+$ ]] || return 1 + [[ "$host" =~ ^[.\-] ]] && return 1 + [[ "$host" == *.* ]] || return 1 + return 0 +} + +# ─── Externe Whitelist herunterladen ───────────────────────────────────────── +download_whitelist() { + local url="$1" + local index="$2" + local cache_file="${EXTERNAL_WHITELIST_CACHE_DIR}/whitelist_${index}.txt" + local etag_file="${EXTERNAL_WHITELIST_CACHE_DIR}/whitelist_${index}.etag" + local tmp_file="${EXTERNAL_WHITELIST_CACHE_DIR}/whitelist_${index}.tmp" + + log "DEBUG" "Prüfe externe Whitelist: $url" + + local -a curl_args=( + -s + -L + --connect-timeout 10 + --max-time 30 + -o "$tmp_file" + -w "%{http_code}" + ) + + if [[ -f "$etag_file" ]]; then + local stored_etag + stored_etag=$(cat "$etag_file") + curl_args+=(-H "If-None-Match: ${stored_etag}") + fi + + local http_code + http_code=$(curl "${curl_args[@]}" -D "${tmp_file}.headers" "$url" 2>/dev/null) || { + log "WARN" "Fehler beim Download der Whitelist: $url" + rm -f "$tmp_file" "${tmp_file}.headers" + return 1 + } + + if [[ "$http_code" == "304" ]]; then + log "DEBUG" "Whitelist nicht geändert (HTTP 304): $url" + rm -f "$tmp_file" "${tmp_file}.headers" + # Auch bei 304 müssen wir DNS neu auflösen (dynamische IPs!) + return 0 + fi + + if [[ "$http_code" != "200" ]]; then + log "WARN" "Whitelist Download fehlgeschlagen (HTTP $http_code): $url" + rm -f "$tmp_file" "${tmp_file}.headers" + return 1 + fi + + if [[ -f "${tmp_file}.headers" ]]; then + local new_etag + new_etag=$(grep -i '^etag:' "${tmp_file}.headers" | head -1 | sed 's/^[^:]*: *//;s/\r$//') + if [[ -n "$new_etag" ]]; then + echo "$new_etag" > "$etag_file" + fi + fi + rm -f "${tmp_file}.headers" + + if [[ -f "$cache_file" ]]; then + if diff -q "$tmp_file" "$cache_file" &>/dev/null; then + log "DEBUG" "Whitelist Inhalt unverändert: $url" + rm -f "$tmp_file" + return 0 + fi + fi + + mv "$tmp_file" "$cache_file" + log "INFO" "Whitelist aktualisiert: $url" + return 0 +} + +# ─── Einträge aus Whitelist-Datei parsen und IPs auflösen ─────────────────── +# Gibt pro Zeile eine IP-Adresse aus (aufgelöste Domains + direkte IPs) +parse_whitelist_entries() { + local cache_file="$1" + + [[ -f "$cache_file" ]] || return + + while IFS= read -r line; do + line="${line%$'\r'}" + line="${line#$'\xef\xbb\xbf'}" + + [[ -z "$line" ]] && continue + [[ "$line" =~ ^[[:space:]]*# ]] && continue + + line=$(echo "$line" | xargs) + line=$(echo "$line" | sed 's/[[:space:]]*[#;].*$//' | xargs) + [[ -z "$line" ]] && continue + + # URLs ablehnen + if [[ "$line" =~ ^[a-zA-Z][a-zA-Z0-9+.-]*:// ]]; then + log "WARN" "Whitelist-Eintrag übersprungen (URL nicht erlaubt): $line" + continue + fi + + # Hosts-Datei-Format erkennen + if [[ "$line" =~ ^[^[:space:]]+[[:space:]]+[^[:space:]] ]]; then + local _first="${line%% *}" + local _rest="${line#* }" + local _second="${_rest%% *}" + if [[ "$_first" == "0.0.0.0" || "$_first" =~ ^127\. || + "$_first" == "::1" || "$_first" == "::0" || + "$_first" == "::" ]]; then + log "DEBUG" "Whitelist Hosts-Format erkannt, extrahiere: $_second" + line="$_second" + else + log "WARN" "Whitelist-Eintrag übersprungen (unbekanntes Format): $line" + continue + fi + fi + + # Klassifizieren und validieren + if [[ "$line" == *:* ]]; then + # IPv6 + if _is_valid_ipv6 "$line"; then + echo "$line" + else + log "WARN" "Whitelist-Eintrag übersprungen (ungültige IPv6): $line" + fi + + elif [[ "$line" =~ ^[0-9] ]]; then + # IPv4 + [[ "$line" == "0.0.0.0"* ]] && continue + if _is_valid_ipv4 "$line"; then + echo "$line" + else + log "WARN" "Whitelist-Eintrag übersprungen (ungültige IPv4): $line" + fi + + else + # Hostname → DNS-Auflösung (wird bei jedem Durchlauf neu aufgelöst!) + if ! _is_valid_hostname "$line"; then + log "WARN" "Whitelist-Eintrag übersprungen (kein gültiger Hostname): $line" + continue + fi + local resolved + resolved=$(getent ahosts "$line" 2>/dev/null | awk '{print $1}' | sort -u) || resolved="" + if [[ -z "$resolved" ]]; then + log "WARN" "Whitelist-Hostname konnte nicht aufgelöst werden: $line" + continue + fi + local resolved_count=0 + while IFS= read -r resolved_ip; do + [[ -z "$resolved_ip" ]] && continue + [[ "$resolved_ip" == "0.0.0.0" ]] && continue + [[ "$resolved_ip" == "::" ]] && continue + [[ "$resolved_ip" == "::0" ]] && continue + echo "$resolved_ip" + resolved_count=$((resolved_count + 1)) + done <<< "$resolved" + if [[ $resolved_count -gt 0 ]]; then + log "DEBUG" "Whitelist-Hostname aufgelöst: $line → $resolved_count IP(s)" + else + log "WARN" "Whitelist-Hostname lieferte nur ungültige Adressen: $line" + fi + fi + done < "$cache_file" +} + +# ─── Whitelisten synchronisieren ───────────────────────────────────────────── +sync_whitelists() { + # Alle URLs herunterladen + IFS=',' read -ra urls <<< "$EXTERNAL_WHITELIST_URLS" + local index=0 + + for url in "${urls[@]}"; do + url=$(echo "$url" | xargs) + [[ -z "$url" ]] && continue + + download_whitelist "$url" "$index" || true + index=$((index + 1)) + done + + # Alle Einträge aus Cache-Dateien parsen und IPs auflösen + local all_ips_file="${EXTERNAL_WHITELIST_CACHE_DIR}/.all_ips.tmp" + > "$all_ips_file" + + for cache_file in "${EXTERNAL_WHITELIST_CACHE_DIR}"/whitelist_*.txt; do + [[ -f "$cache_file" ]] || continue + parse_whitelist_entries "$cache_file" >> "$all_ips_file" + done + + # Duplikate entfernen und in die resolved-Datei schreiben + local unique_count + sort -u "$all_ips_file" > "${EXTERNAL_WHITELIST_RESOLVED_FILE}.tmp" + mv "${EXTERNAL_WHITELIST_RESOLVED_FILE}.tmp" "$EXTERNAL_WHITELIST_RESOLVED_FILE" + unique_count=$(wc -l < "$EXTERNAL_WHITELIST_RESOLVED_FILE" | xargs) + + rm -f "$all_ips_file" + + log "DEBUG" "Externe Whitelist: $unique_count eindeutige IPs aufgelöst" + + # Prüfe ob gesperrte IPs jetzt auf der Whitelist stehen und entsperrt werden müssen + check_banned_whitelist_ips +} + +# ─── Gesperrte IPs prüfen die jetzt gewhitelistet sind ────────────────────── +# Wenn eine IP nach einer Whitelist-Aktualisierung nun auf der externen +# Whitelist steht, wird sie automatisch entsperrt. +check_banned_whitelist_ips() { + local state_dir="${STATE_DIR:-/var/lib/adguard-shield}" + [[ -d "$state_dir" ]] || return + [[ -f "$EXTERNAL_WHITELIST_RESOLVED_FILE" ]] || return + + for state_file in "${state_dir}"/*.ban "${state_dir}"/ext_*.ban; do + [[ -f "$state_file" ]] || continue + local client_ip + client_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2) + [[ -z "$client_ip" ]] && continue + + if grep -qxF "$client_ip" "$EXTERNAL_WHITELIST_RESOLVED_FILE" 2>/dev/null; then + log "INFO" "Gesperrte IP $client_ip ist jetzt auf externer Whitelist – entsperre automatisch" + + # 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 "$state_file" + + # Ban-History Eintrag + if [[ -f "${BAN_HISTORY_FILE:-/var/log/adguard-shield-bans.log}" ]]; then + local timestamp + timestamp="$(date '+%Y-%m-%d %H:%M:%S')" + printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n" \ + "$timestamp" "UNBAN" "$client_ip" "-" "-" "-" "-" "external-whitelist" \ + >> "${BAN_HISTORY_FILE:-/var/log/adguard-shield-bans.log}" + fi + fi + done +} + +# ─── PID-Management ────────────────────────────────────────────────────────── +write_pid() { + echo $$ > "$WORKER_PID_FILE" +} + +cleanup() { + log "INFO" "Externer Whitelist-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" "Whitelist-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 " Externer Whitelist-Worker - Status" + echo "═══════════════════════════════════════════════════════════════" + echo "" + + if [[ "$EXTERNAL_WHITELIST_ENABLED" != "true" ]]; then + echo " ⚠️ Externer Whitelist-Worker ist deaktiviert" + echo " Aktivieren: EXTERNAL_WHITELIST_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 "" + + # Konfigurierte URLs + echo " Konfigurierte Whitelisten:" + IFS=',' read -ra urls <<< "$EXTERNAL_WHITELIST_URLS" + local index=0 + for url in "${urls[@]}"; do + url=$(echo "$url" | xargs) + [[ -z "$url" ]] && continue + local cache_file="${EXTERNAL_WHITELIST_CACHE_DIR}/whitelist_${index}.txt" + if [[ -f "$cache_file" ]]; then + local entry_count + entry_count=$(grep -cv '^\s*#\|^\s*$' "$cache_file" 2>/dev/null || echo "0") + local last_modified + last_modified=$(date -r "$cache_file" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unbekannt") + echo " [$index] $url" + echo " Einträge: $entry_count | Zuletzt aktualisiert: $last_modified" + else + echo " [$index] $url (noch nicht heruntergeladen)" + fi + index=$((index + 1)) + done + + echo "" + + # Aufgelöste IPs + if [[ -f "$EXTERNAL_WHITELIST_RESOLVED_FILE" ]]; then + local resolved_count + resolved_count=$(wc -l < "$EXTERNAL_WHITELIST_RESOLVED_FILE" | xargs) + local last_resolved + last_resolved=$(date -r "$EXTERNAL_WHITELIST_RESOLVED_FILE" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unbekannt") + echo " Aufgelöste IPs: $resolved_count" + echo " Letzte Auflösung: $last_resolved" + + if [[ "$resolved_count" -gt 0 && "$resolved_count" -le 20 ]]; then + echo "" + echo " Aktuelle IPs:" + while IFS= read -r ip; do + echo " ✅ $ip" + done < "$EXTERNAL_WHITELIST_RESOLVED_FILE" + elif [[ "$resolved_count" -gt 20 ]]; then + echo "" + echo " Erste 20 IPs:" + head -20 "$EXTERNAL_WHITELIST_RESOLVED_FILE" | while IFS= read -r ip; do + echo " ✅ $ip" + done + echo " ... ($((resolved_count - 20)) weitere)" + fi + else + echo " Aufgelöste IPs: 0 (noch keine Synchronisation durchgeführt)" + fi + + echo "" + echo " Prüfintervall: ${EXTERNAL_WHITELIST_INTERVAL}s" + echo "" + echo "═══════════════════════════════════════════════════════════════" +} + +# ─── Einmalig synchronisieren ──────────────────────────────────────────────── +run_once() { + init_directories + + if [[ -z "${EXTERNAL_WHITELIST_URLS:-}" ]]; then + log "ERROR" "Keine externen Whitelist-URLs konfiguriert (EXTERNAL_WHITELIST_URLS)" + exit 1 + fi + + log "INFO" "Einmalige Whitelist-Synchronisation..." + sync_whitelists + log "INFO" "Whitelist-Synchronisation abgeschlossen" +} + +# ─── Hauptschleife ────────────────────────────────────────────────────────── +main_loop() { + init_directories + + if [[ -z "${EXTERNAL_WHITELIST_URLS:-}" ]]; then + log "ERROR" "Keine externen Whitelist-URLs konfiguriert (EXTERNAL_WHITELIST_URLS)" + exit 1 + fi + + log "INFO" "═══════════════════════════════════════════════════════════" + log "INFO" "Externer Whitelist-Worker gestartet" + log "INFO" " URLs: ${EXTERNAL_WHITELIST_URLS}" + log "INFO" " Prüfintervall: ${EXTERNAL_WHITELIST_INTERVAL}s" + log "INFO" "═══════════════════════════════════════════════════════════" + + while true; do + sync_whitelists + sleep "$EXTERNAL_WHITELIST_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 "Whitelist-Worker gestoppt" + else + echo "Whitelist-Worker läuft nicht" + fi + ;; + sync) + run_once + ;; + status) + init_directories + show_status + ;; + flush) + init_directories + echo "Entferne aufgelöste externe Whitelist-IPs..." + rm -f "$EXTERNAL_WHITELIST_RESOLVED_FILE" + echo "Externe Whitelist-IPs entfernt" + ;; + *) + cat << USAGE +AdGuard Shield - Externer Whitelist-Worker + +Nutzung: $0 {start|stop|sync|status|flush} + +Befehle: + start Startet den Worker (Dauerbetrieb) + stop Stoppt den Worker + sync Einmalige Synchronisation (DNS-Auflösung) + status Zeigt Status und aufgelöste IPs + flush Entfernt alle aufgelösten Whitelist-IPs + +Konfiguration: $CONFIG_FILE + +USAGE + exit 0 + ;; +esac diff --git a/install.sh b/install.sh index 8fbdb93..39b2333 100644 --- a/install.sh +++ b/install.sh @@ -103,6 +103,11 @@ print_help() { echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh test${NC} # API-Verbindung testen" echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh dry-run${NC} # Testmodus (nur loggen)" echo "" + echo -e "${BOLD}Externe Whitelist-Befehle:${NC}" + echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh whitelist-status${NC} # Status der externen Whitelisten" + echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh whitelist-sync${NC} # Einmalige Synchronisation" + echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh whitelist-flush${NC} # Aufgelöste IPs entfernen" + echo "" echo -e "${BOLD}iptables-Befehle:${NC}" echo -e " ${CYAN}sudo /opt/adguard-shield/iptables-helper.sh status${NC} # Firewall-Regeln anzeigen" echo -e " ${CYAN}sudo /opt/adguard-shield/iptables-helper.sh ban IP${NC} # IP manuell sperren" @@ -241,6 +246,7 @@ install_files() { cp "$SCRIPT_DIR/iptables-helper.sh" "$INSTALL_DIR/" cp "$SCRIPT_DIR/unban-expired.sh" "$INSTALL_DIR/" cp "$SCRIPT_DIR/external-blocklist-worker.sh" "$INSTALL_DIR/" + cp "$SCRIPT_DIR/external-whitelist-worker.sh" "$INSTALL_DIR/" cp "$SCRIPT_DIR/report-generator.sh" "$INSTALL_DIR/" cp "$SCRIPT_DIR/uninstall.sh" "$INSTALL_DIR/" @@ -254,6 +260,7 @@ install_files() { chmod +x "$INSTALL_DIR/iptables-helper.sh" chmod +x "$INSTALL_DIR/unban-expired.sh" chmod +x "$INSTALL_DIR/external-blocklist-worker.sh" + chmod +x "$INSTALL_DIR/external-whitelist-worker.sh" chmod +x "$INSTALL_DIR/report-generator.sh" chmod +x "$INSTALL_DIR/uninstall.sh" @@ -751,6 +758,7 @@ do_uninstall() { rm -f "$INSTALL_DIR/iptables-helper.sh" 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/report-generator.sh" rm -rf "$INSTALL_DIR/templates" echo " ✅ Scripts entfernt (Konfiguration und Logs behalten)" diff --git a/uninstall.sh b/uninstall.sh index a0c82b8..473f86f 100644 --- a/uninstall.sh +++ b/uninstall.sh @@ -106,6 +106,7 @@ do_uninstall() { rm -f "$INSTALL_DIR/iptables-helper.sh" 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/report-generator.sh" rm -f "$INSTALL_DIR/uninstall.sh" rm -rf "$INSTALL_DIR/templates" From 6bdeb5bc31e0d32e6e7d296134a3f43b02c3ca1f Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Sat, 4 Apr 2026 19:59:46 +0200 Subject: [PATCH 2/2] Release: Version v0.7.0 --- adguard-shield.sh | 2 +- docs/benachrichtigungen.md | 4 ++-- install.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/adguard-shield.sh b/adguard-shield.sh index 4ed0467..3e2abb3 100644 --- a/adguard-shield.sh +++ b/adguard-shield.sh @@ -8,7 +8,7 @@ # Lizenz: MIT ############################################################################### -VERSION="v0.6.2" +VERSION="v0.7.0" set -euo pipefail diff --git a/docs/benachrichtigungen.md b/docs/benachrichtigungen.md index 9c6ec53..96ff2e9 100644 --- a/docs/benachrichtigungen.md +++ b/docs/benachrichtigungen.md @@ -116,12 +116,12 @@ Bei Sperren aus der **externen Blocklist** werden Benachrichtigungen separat üb ### Service gestartet **Überschrift:** ✅ AdGuard Shield -> 🟢 AdGuard Shield v0.6.2 wurde auf dns1 gestartet. +> 🟢 AdGuard Shield v0.7.0 wurde auf dns1 gestartet. ### Service gestoppt **Überschrift:** 🚨 🛡️ AdGuard Shield -> 🔴 AdGuard Shield v0.6.2 wurde auf dns1 gestoppt. +> 🔴 AdGuard Shield v0.7.0 wurde auf dns1 gestoppt. ### Sperre (Ban) **Überschrift:** 🚨 🛡️ AdGuard Shield diff --git a/install.sh b/install.sh index 39b2333..d8eac1a 100644 --- a/install.sh +++ b/install.sh @@ -6,7 +6,7 @@ # Lizenz: MIT ############################################################################### -VERSION="v0.6.2" +VERSION="v0.7.0" set -euo pipefail