Merge pull request 'v0.5.0' (#4) from v0.5.0 into main

Reviewed-on: #4
This commit is contained in:
2026-03-05 19:13:10 +00:00
12 changed files with 1914 additions and 29 deletions
+10
View File
@@ -30,6 +30,7 @@ Wenn ein Client eine bestimmte Domain zu oft anfragt (z.B. >30x pro Minute), wir
- Automatisches Entsperren nach konfigurierbarer Dauer
- **Externe Blocklisten** — IP-Adressen von externen Textdateien (URLs) laden und automatisch sperren
- **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
- Whitelist für vertrauenswürdige IPs
- Dry-Run Modus zum gefahrlosen Testen
@@ -86,6 +87,9 @@ 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/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
sudo journalctl -u adguard-shield -f # Logs live verfolgen
```
@@ -98,13 +102,18 @@ sudo journalctl -u adguard-shield -f # Logs live ver
├── external-blocklist-worker.sh # Externer Blocklist-Worker
├── iptables-helper.sh # Manuelle iptables-Verwaltung
├── unban-expired.sh # Cron-basiertes Entsperren
├── report-generator.sh # E-Mail Report Generator
├── install.sh # Installer / Updater / Uninstaller
├── templates/
│ ├── report.html # HTML-Report-Template
│ └── report.txt # TXT-Report-Template
├── README.md
└── doc/
├── architektur.md # Architektur & Funktionsweise
├── konfiguration.md # Alle Parameter erklärt + Konfig-Migration
├── befehle.md # Vollständige Befehlsreferenz inkl. Installer
├── benachrichtigungen.md # Webhook-Setup (Discord, Slack, Gotify, Ntfy)
├── report.md # E-Mail Report Setup & Konfiguration
└── tipps-und-troubleshooting.md
```
@@ -116,6 +125,7 @@ sudo journalctl -u adguard-shield -f # Logs live ver
| [Konfiguration](doc/konfiguration.md) | Alle Parameter, Ports, Whitelist-Pflege, automatische Migration |
| [Befehle](doc/befehle.md) | Vollständige Befehlsreferenz für Installer, Monitor, iptables-Helper und systemd |
| [Benachrichtigungen](doc/benachrichtigungen.md) | Setup für Discord, Slack, Gotify, Ntfy |
| [E-Mail Report](doc/report.md) | Periodische Statistik-Reports per E-Mail (HTML/TXT) |
| [Tipps & Troubleshooting](doc/tipps-und-troubleshooting.md) | Best Practices, häufige Probleme, Deinstallation |
## Lizenz
+33
View File
@@ -70,6 +70,11 @@ LOG_MAX_SIZE_MB=50
# Ban-History Datei (protokolliert alle Sperren & Entsperrungen dauerhaft)
BAN_HISTORY_FILE="/var/log/adguard-shield-bans.log"
# 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
NOTIFY_ENABLED=false
@@ -95,6 +100,34 @@ NTFY_TOKEN=""
# 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/
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_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_MAIL_CMD="msmtp"
# --- Externe Blocklist (optional) ---
# Aktiviert den externen Blocklist-Worker
EXTERNAL_BLOCKLIST_ENABLED=false
+5 -4
View File
@@ -9,7 +9,7 @@
# Lizenz: MIT
###############################################################################
VERSION="0.4.0"
VERSION="v0.5.0"
set -euo pipefail
@@ -238,6 +238,7 @@ report_to_abuseipdb() {
local domain="$2"
local count="$3"
local reason="${4:-rate-limit}"
local window="${5:-$RATE_LIMIT_WINDOW}"
if [[ "${ABUSEIPDB_ENABLED:-false}" != "true" ]]; then
return 0
@@ -251,9 +252,9 @@ report_to_abuseipdb() {
# Kommentar für AbuseIPDB erstellen (englisch)
local comment
if [[ "$reason" == "subdomain-flood" ]]; then
comment="DNS flooding on our DNS server: ${count}x ${domain} (random subdomain attack). Permanently banned by AdGuard Shield."
comment="DNS flooding on our DNS server: ${count}x ${domain} in ${window}s (random subdomain attack). Banned by Adguard Shield 🔗 https://tnvs.de/as"
else
comment="DNS flooding on our DNS server: ${count}x ${domain}. Permanently banned by AdGuard Shield."
comment="DNS flooding on our DNS server: ${count}x ${domain} in ${window}s. Banned by Adguard Shield 🔗 https://tnvs.de/as"
fi
local categories="${ABUSEIPDB_CATEGORIES:-4}"
@@ -456,7 +457,7 @@ EOF
# AbuseIPDB Report (nur bei permanenter Sperre)
if [[ "$is_permanent" == "true" ]]; then
report_to_abuseipdb "$client_ip" "$domain" "$count" "$reason" &
report_to_abuseipdb "$client_ip" "$domain" "$count" "$reason" "$window" &
fi
}
+7 -7
View File
@@ -9,14 +9,14 @@
└──────────┬──────────┘
┌─────────────────────┐ ┌──────────────────────┐
│ AdGuard Home │────▶│ Query Log (API)
│ DNS Server │ └──────────┬───────────┘
┌─────────────────────┐ ┌──────────────────────┐
│ AdGuard Home │────▶ │ Query Log (API) │
│ DNS Server │ └──────────┬───────────┘
└─────────────────────┘ │
┌──────────────────────┐
│ adguard-shield.sh │
│ (Monitor Script)
│ adguard-shield.sh
│ (Monitor Script) │
└──────────┬───────────┘
┌──────────────┼──────────────┐
@@ -166,7 +166,7 @@ Der Installer (`install.sh`) bietet ein interaktives Menü und folgende Funktion
```
┌─────────────────────────┐ ┌─────────────────────────┐
│ Bestehende Konfig │ │ Neue Konfig (Repo) │
│ (Benutzer-Settings) │ │ (mit neuen Parametern) │
│ (Benutzer-Settings) │ │ (mit neuen Parametern) │
└───────────┬─────────────┘ └───────────┬─────────────┘
│ │
▼ ▼
@@ -180,7 +180,7 @@ Der Installer (`install.sh`) bietet ein interaktives Menü und folgende Funktion
┌──────────────────────────┐
│ Aktualisierte Konfig │
(alte Werte + neue Keys) │
│ (alte Werte + neue Keys) │
└──────────────────────────┘
```
+30
View File
@@ -168,6 +168,36 @@ sudo /opt/adguard-shield/external-blocklist-worker.sh status
sudo /opt/adguard-shield/external-blocklist-worker.sh flush
```
## E-Mail Report
```bash
# Report sofort generieren und per E-Mail versenden
sudo /opt/adguard-shield/report-generator.sh send
# Test-E-Mail senden (prüft alle Voraussetzungen + Mailversand)
sudo /opt/adguard-shield/report-generator.sh test
# Report als Datei generieren (Ausgabe auf stdout)
sudo /opt/adguard-shield/report-generator.sh generate
# Report im HTML-Format in Datei speichern
sudo /opt/adguard-shield/report-generator.sh generate html > report.html
# Report im TXT-Format in Datei speichern
sudo /opt/adguard-shield/report-generator.sh generate txt > report.txt
# Cron-Job für automatischen Versand einrichten
sudo /opt/adguard-shield/report-generator.sh install
# Cron-Job entfernen
sudo /opt/adguard-shield/report-generator.sh remove
# Report-Konfiguration und Cron-Status anzeigen
sudo /opt/adguard-shield/report-generator.sh status
```
> Voraussetzung: Ein funktionierender Mail-Transport (z.B. msmtp). Anleitung: [Linux: Einfach E-Mails versenden mit msmtp](https://www.cleveradmin.de/blog/2024/12/linux-einfach-emails-versenden-mit-msmtp/)
## Logs
```bash
+63 -6
View File
@@ -110,6 +110,7 @@ Wiederholungstäter werden wie bei fail2ban stufenweise länger gesperrt. Wird e
| `LOG_LEVEL` | `INFO` | Log-Level: `DEBUG`, `INFO`, `WARN`, `ERROR` |
| `LOG_MAX_SIZE_MB` | `50` | Max. Log-Größe bevor rotiert wird |
| `BAN_HISTORY_FILE` | `/var/log/adguard-shield-bans.log` | Datei für die Ban-History (alle Sperren/Entsperrungen) |
| `BAN_HISTORY_RETENTION_DAYS` | `0` | Aufbewahrungsdauer der Ban-History in Tagen. `0` = unbegrenzt (niemals löschen). Alte Einträge werden beim nächsten Report automatisch entfernt. |
### Benachrichtigungen
@@ -123,6 +124,24 @@ Wiederholungstäter werden wie bei fail2ban stufenweise länger gesperrt. Wird e
| `NTFY_TOKEN` | *(leer)* | Optionaler Ntfy Access-Token |
| `NTFY_PRIORITY` | `4` | Ntfy Priorität (15) |
### E-Mail Report
Regelmäßige Statistik-Reports per E-Mail. Voraussetzung ist ein funktionierender Mail-Transport (z.B. msmtp).
> **Anleitung für msmtp:** [Linux: Einfach E-Mails versenden mit msmtp](https://www.cleveradmin.de/blog/2024/12/linux-einfach-emails-versenden-mit-msmtp/)
| Parameter | Standard | Beschreibung |
|-----------|----------|--------------|
| `REPORT_ENABLED` | `false` | Report-Funktion aktivieren |
| `REPORT_INTERVAL` | `weekly` | Intervall: `daily`, `weekly`, `biweekly`, `monthly` |
| `REPORT_TIME` | `08:00` | Versanduhrzeit (HH:MM, 24h) |
| `REPORT_EMAIL_TO` | *(leer)* | E-Mail-Empfänger |
| `REPORT_EMAIL_FROM` | `adguard-shield@hostname` | E-Mail-Absender |
| `REPORT_FORMAT` | `html` | Format: `html` oder `txt` |
| `REPORT_MAIL_CMD` | `msmtp` | Mail-Befehl (`msmtp`, `sendmail`, `mail`) |
> Siehe [E-Mail Report Dokumentation](report.md) für Details zu Inhalten, Templates und Befehlen.
### Erweitert
| Parameter | Standard | Beschreibung |
@@ -132,12 +151,12 @@ Wiederholungstäter werden wie bei fail2ban stufenweise länger gesperrt. Wird e
| `DRY_RUN` | `false` | Testmodus — nur loggen, nicht sperren |
### Externe Blocklist
Ermöglicht das Einbinden externer IP-Blocklisten (z.B. gehostete Textdateien mit einer IP pro Zeile). Der Worker läuft als Hintergrundprozess und prüft periodisch auf Änderungen.
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.
| Parameter | Standard | Beschreibung |
|-----------|----------|--------------|
| `EXTERNAL_BLOCKLIST_ENABLED` | `false` | Aktiviert den externen Blocklist-Worker |
| `EXTERNAL_BLOCKLIST_URLS` | *(leer)* | URL(s) zu Textdateien mit IPs (kommagetrennt) |
| `EXTERNAL_BLOCKLIST_URLS` | *(leer)* | URL(s) zu Blocklist-Textdateien (kommagetrennt). Unterstützt IPv4, IPv6, CIDR und Hostnamen |
| `EXTERNAL_BLOCKLIST_INTERVAL` | `300` | Prüfintervall in Sekunden (300 = 5 Min.) |
| `EXTERNAL_BLOCKLIST_BAN_DURATION` | `0` | Sperrdauer in Sekunden (0 = permanent bis IP aus Liste entfernt) |
| `EXTERNAL_BLOCKLIST_AUTO_UNBAN` | `true` | IPs automatisch entsperren wenn aus Liste entfernt |
@@ -176,21 +195,38 @@ sudo systemctl restart adguard-shield
Der Report an AbuseIPDB enthält (auf Englisch):
- **Bei Rate-Limit:** `DNS flooding on our DNS server: 100x microsoft.com. Permanently banned by AdGuard Shield.`
- **Bei Subdomain-Flood:** `DNS flooding on our DNS server: 85x *.microsoft.com (random subdomain attack). Permanently banned by AdGuard Shield.`
- **Bei Rate-Limit:** `DNS flooding on our DNS server: 100x microsoft.com in 60s. Banned by Adguard Shield 🔗 https://tnvs.de/as`
- **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"`).
#### Externe Blocklist einrichten
1. Erstelle eine Textdatei auf einem Webserver mit einer IP pro Zeile:
1. Erstelle eine Textdatei auf einem Webserver. Pro Zeile ein Eintrag — IPv4, IPv6, CIDR oder Hostname:
```text
# Kommentare werden ignoriert
# Inline-Kommentare ebenfalls: 1.2.3.4 # dieser Kommentar wird entfernt
# IPv4
192.168.100.50
10.0.0.99
10.0.0.0/8
# IPv6
2001:db8::dead:beef
2001:db8::/32
# Hostnamen (werden über den lokalen DNS-Resolver aufgelöst)
# Liefert ein Hostname mehrere IPs, werden alle gesperrt
bad-actor.example.com
malware.example.net
# Hosts-Datei-Format wird ebenfalls erkannt (Routing-IP wird ignoriert, Hostname aufgelöst)
0.0.0.0 bad-actor.example.com
127.0.0.1 malware.example.net
```
> **Hinweis zur Hostname-Auflösung:** Da AdGuard Shield idealerweise auf demselben Host wie der DNS-Resolver läuft, verwendet der Worker automatisch den lokalen Resolver. Hostnamen die bereits von AdGuard geblockt werden (Antwort `0.0.0.0`) werden übersprungen und nicht importiert.
2. Aktiviere die Blocklist in der Konfiguration:
```bash
@@ -210,6 +246,27 @@ EXTERNAL_BLOCKLIST_URLS="https://example.com/list1.txt,https://other.com/list2.t
```bash
sudo systemctl restart adguard-shield
```
#### Unterstützte Eintragsformate
| Format | Beispiel | Verhalten |
|--------|----------|----------|
| IPv4 | `1.2.3.4` | direkt gesperrt |
| IPv4-CIDR | `10.0.0.0/8` | direkt gesperrt |
| IPv6 | `2001:db8::1` | direkt gesperrt |
| IPv6-CIDR | `2001:db8::/32` | direkt gesperrt |
| Hostname | `bad.example.com` | per lokalem DNS aufgelöst, alle IPs (IPv4 + IPv6) gesperrt |
| Hosts-Format | `0.0.0.0 bad.example.com` | Hostname extrahiert und aufgelöst |
| Kommentar | `# Text` | übersprungen |
| Inline-Kommentar | `1.2.3.4 # Text` | Kommentar entfernt, IP gesperrt |
Folgende Einträge werden mit einer Warnung im Log übersprungen:
- URLs (`https://...`, `http://...`)
- IP:Port-Kombinationen (`1.2.3.4:8080`)
- Hostnamen mit ungültigen Zeichen oder ohne Punkt
- Einträge mit nicht auflösbarem Hostnamen
- `0.0.0.0` und `::` (AdGuard-Blocking-Antwort)
## Gesperrte Ports im Detail
Bei einem Rate-Limit-Verstoß werden **alle** DNS-Protokoll-Ports für den Client gesperrt (IPv4 via `iptables` und IPv6 via `ip6tables`):
+255
View File
@@ -0,0 +1,255 @@
# E-Mail Report
AdGuard Shield kann regelmäßig einen Statistik-Report per E-Mail versenden. Der Report enthält eine Übersicht über alle Sperren, die auffälligsten IPs, meistbetroffene Domains und weitere Statistiken.
## Voraussetzungen
Der Server muss E-Mails versenden können. Empfohlen wird **msmtp** als leichtgewichtiger SMTP-Client.
**Anleitung zur Einrichtung von msmtp:**
👉 [Linux: Einfach E-Mails versenden mit msmtp](https://www.cleveradmin.de/blog/2024/12/linux-einfach-emails-versenden-mit-msmtp/)
Alternativ funktioniert auch `sendmail`, `mail` oder ein anderer Befehl, der E-Mails über stdin entgegennimmt.
## Aktivierung
In der Konfiguration (`adguard-shield.conf`):
```bash
REPORT_ENABLED=true
REPORT_INTERVAL="weekly"
REPORT_TIME="08:00"
REPORT_EMAIL_TO="admin@example.com"
REPORT_EMAIL_FROM="adguard-shield@example.com"
REPORT_FORMAT="html"
REPORT_MAIL_CMD="msmtp"
```
Anschließend den Cron-Job einrichten:
```bash
sudo /opt/adguard-shield/report-generator.sh install
```
## Konfigurationsparameter
| Parameter | Standard | Beschreibung |
|-----------|----------|--------------|
| `REPORT_ENABLED` | `false` | Report-Funktion aktivieren |
| `REPORT_INTERVAL` | `weekly` | Versandintervall (siehe unten) |
| `REPORT_TIME` | `08:00` | Uhrzeit für den Versand (HH:MM, 24h) |
| `REPORT_EMAIL_TO` | *(leer)* | E-Mail-Empfänger |
| `REPORT_EMAIL_FROM` | `adguard-shield@hostname` | E-Mail-Absender |
| `REPORT_FORMAT` | `html` | Format: `html` oder `txt` |
| `REPORT_MAIL_CMD` | `msmtp` | Mail-Befehl (`msmtp`, `sendmail`, `mail`) |
### Versandintervalle
| Wert | Beschreibung |
|------|-------------|
| `daily` | Täglich zur konfigurierten Uhrzeit |
| `weekly` | Wöchentlich am Montag |
| `biweekly` | Alle zwei Wochen am Montag |
| `monthly` | Monatlich am 1. des Monats |
## Report-Inhalte
Der Report enthält folgende Statistiken:
### Übersicht
- Gesamtzahl der Sperren und Entsperrungen
- Anzahl eindeutiger gesperrter IPs
- Permanente Sperren
- Aktuell aktive Sperren
- AbuseIPDB-Reports
### Angriffsarten
- Rate-Limit Sperren
- Subdomain-Flood Sperren
- Externe Blocklist Sperren
- Aktivster Tag im Berichtszeitraum
### Top 10 Listen
- **Auffälligste IPs** — Die 10 IPs mit den meisten Sperren (mit Balkendiagramm im HTML-Format)
- **Meistbetroffene Domains** — Die 10 am häufigsten betroffenen Domains
### Weitere Details
- **Protokoll-Verteilung** — Aufschlüsselung nach DNS, DoH, DoT, DoQ
- **Letzte 10 Sperren** — Die aktuellsten Sperrereignisse mit Zeitstempel, IP, Domain und Grund
## Befehle
```bash
# Report sofort generieren und versenden
sudo /opt/adguard-shield/report-generator.sh send
# Test-E-Mail senden (prüft alle Voraussetzungen + Mailversand)
sudo /opt/adguard-shield/report-generator.sh test
# Report als Datei generieren (auf stdout ausgeben)
sudo /opt/adguard-shield/report-generator.sh generate
# Report im spezifischen Format generieren
sudo /opt/adguard-shield/report-generator.sh generate html > report.html
sudo /opt/adguard-shield/report-generator.sh generate txt > report.txt
# Cron-Job für automatischen Versand einrichten
sudo /opt/adguard-shield/report-generator.sh install
# Cron-Job entfernen
sudo /opt/adguard-shield/report-generator.sh remove
# Report-Konfiguration und Cron-Status anzeigen
sudo /opt/adguard-shield/report-generator.sh status
```
## Report-Intervall ändern
Um das Intervall, die Uhrzeit oder andere Einstellungen zu ändern:
```bash
# 1. Konfiguration bearbeiten
sudo nano /opt/adguard-shield/adguard-shield.conf
# → z.B. REPORT_INTERVAL="weekly" auf "daily" ändern
# → z.B. REPORT_TIME="09:00"
# 2. Cron-Job neu einrichten (überschreibt den alten automatisch)
sudo /opt/adguard-shield/report-generator.sh install
```
> **Hinweis:** Der `install`-Befehl überschreibt den bestehenden Cron-Job mit den aktuellen Werten aus der Konfiguration. Ein vorheriges `remove` ist nicht nötig, schadet aber auch nicht.
Alternativ in zwei Schritten:
```bash
# Alten Cron-Job erst entfernen, dann neu anlegen
sudo /opt/adguard-shield/report-generator.sh remove
sudo nano /opt/adguard-shield/adguard-shield.conf
sudo /opt/adguard-shield/report-generator.sh install
```
## Templates
Die Report-Templates liegen unter:
```
/opt/adguard-shield/templates/report.html # HTML-Template
/opt/adguard-shield/templates/report.txt # TXT-Template
```
Die Templates verwenden Platzhalter (z.B. `{{TOTAL_BANS}}`, `{{TOP10_IPS_TABLE}}`), die beim Generieren durch die tatsächlichen Werte ersetzt werden. Die Templates können nach Bedarf angepasst werden.
### Verfügbare Platzhalter
| Platzhalter | Beschreibung |
|-------------|-------------|
| `{{REPORT_PERIOD}}` | Berichtszeitraum mit Label |
| `{{REPORT_DATE}}` | Erstellungsdatum des Reports |
| `{{HOSTNAME}}` | Server-Hostname |
| `{{VERSION}}` | AdGuard Shield Version |
| `{{TOTAL_BANS}}` | Gesamtzahl Sperren |
| `{{TOTAL_UNBANS}}` | Gesamtzahl Entsperrungen |
| `{{UNIQUE_IPS}}` | Anzahl eindeutiger IPs |
| `{{PERMANENT_BANS}}` | Permanente Sperren |
| `{{ACTIVE_BANS}}` | Aktuell aktive Sperren |
| `{{ABUSEIPDB_REPORTS}}` | Anzahl AbuseIPDB-Reports |
| `{{RATELIMIT_BANS}}` | Rate-Limit Sperren |
| `{{SUBDOMAIN_FLOOD_BANS}}` | Subdomain-Flood Sperren |
| `{{EXTERNAL_BLOCKLIST_BANS}}` | Externe Blocklist Sperren |
| `{{BUSIEST_DAY}}` | Aktivster Tag |
| `{{TOP10_IPS_TABLE}}` | Top 10 IPs (HTML-Tabelle) |
| `{{TOP10_IPS_TEXT}}` | Top 10 IPs (Text-Tabelle) |
| `{{TOP10_DOMAINS_TABLE}}` | Top 10 Domains (HTML-Tabelle) |
| `{{TOP10_DOMAINS_TEXT}}` | Top 10 Domains (Text-Tabelle) |
| `{{PROTOCOL_TABLE}}` | Protokoll-Verteilung (HTML) |
| `{{PROTOCOL_TEXT}}` | Protokoll-Verteilung (Text) |
| `{{RECENT_BANS_TABLE}}` | Letzte Sperren (HTML) |
| `{{RECENT_BANS_TEXT}}` | Letzte Sperren (Text) |
## Beispiel: Schnellstart
```bash
# 1. msmtp installieren und konfigurieren
sudo apt install msmtp msmtp-mta
# Anleitung: https://www.cleveradmin.de/blog/2024/12/linux-einfach-emails-versenden-mit-msmtp/
# 2. Report-Konfiguration anpassen
sudo nano /opt/adguard-shield/adguard-shield.conf
# → REPORT_ENABLED=true
# → REPORT_EMAIL_TO="deine@email.de"
# 3. Test-Mail senden (prüft alle Voraussetzungen)
sudo /opt/adguard-shield/report-generator.sh test
# 4. Wenn die Test-Mail angekommen ist: echten Report testen
sudo /opt/adguard-shield/report-generator.sh send
# 5. Automatischen Versand einrichten
sudo /opt/adguard-shield/report-generator.sh install
# 6. Status prüfen
sudo /opt/adguard-shield/report-generator.sh status
```
## Test-Mail
Bevor du den automatischen Versand einrichtest, kannst du mit dem `test`-Befehl prüfen, ob alles funktioniert:
```bash
sudo /opt/adguard-shield/report-generator.sh test
```
Der Test prüft Schritt für Schritt:
1. **E-Mail-Empfänger** — Ist `REPORT_EMAIL_TO` konfiguriert?
2. **E-Mail-Absender** — Zeigt den konfigurierten Absender an
3. **Mail-Befehl** — Ist `msmtp` (oder der konfigurierte Befehl) installiert?
4. **Report-Template** — Existiert das HTML/TXT-Template?
5. **Ban-History** — Gibt es vorhandene Daten?
6. **Test-Versand** — Sendet eine Test-E-Mail und prüft den Exit-Code
Die Test-Mail enthält eine Übersicht der aktuellen Konfiguration und bestätigt, dass der Mailversand funktioniert.
## Troubleshooting
### E-Mail wird nicht versendet
1. Prüfe ob der Mail-Befehl installiert ist:
```bash
which msmtp
```
2. Teste den Mailversand manuell:
```bash
echo "Test" | msmtp -t deine@email.de
```
3. Prüfe die msmtp-Konfiguration:
```bash
cat ~/.msmtprc
# oder
cat /etc/msmtprc
```
4. Prüfe die Report-Konfiguration:
```bash
sudo /opt/adguard-shield/report-generator.sh status
```
### Report enthält keine Daten
Der Report basiert auf der Ban-History-Datei (`/var/log/adguard-shield-bans.log`). Wenn keine Sperren im Berichtszeitraum vorhanden sind, zeigt der Report „Keine Daten" an.
### Cron-Job wird nicht ausgeführt
1. Prüfe ob der Cron-Job angelegt wurde:
```bash
cat /etc/cron.d/adguard-shield-report
```
2. Prüfe die Cron-Logs:
```bash
grep adguard-shield /var/log/syslog
# oder
journalctl -u cron
```
+132 -9
View File
@@ -318,24 +318,147 @@ download_blocklist() {
return 0
}
# ─── Eintrag-Validierung ─────────────────────────────────────────────────────
# Prüft IPv4-Adresse mit optionalem CIDR (z.B. 1.2.3.4 oder 1.2.3.0/24)
_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 (z.B. ::1 oder 2001:db8::/32)
# Fängt auch IPv4:Port-Kombinationen ab (z.B. 1.2.3.4:8080)
_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
# IPv4:Port abfangen — enthält Punkt(e) vor dem ersten Doppelpunkt
[[ "$addr" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9] ]] && return 1
# Muss mindestens einen Doppelpunkt haben und nur gültige Zeichen (Hex, Doppelpunkt, Punkt für IPv4-mapped)
[[ "$addr" == *:* ]] || return 1
[[ "$addr" =~ ^[0-9a-fA-F:\.]+$ ]] || return 1
return 0
}
# Prüft ob ein Hostname syntaktisch plausibel ist
# Akzeptiert: example.com, sub.example.com, example.com. (trailing dot)
# Lehnt ab: einzelne Wörter ohne Punkt, Sonderzeichen, überlange Einträge
_is_valid_hostname() {
local host="$1"
host="${host%.}" # trailing dot (FQDN) entfernen
[[ -z "$host" ]] && return 1
[[ ${#host} -gt 253 ]] && return 1
[[ "$host" =~ ^[a-zA-Z0-9._-]+$ ]] || return 1
[[ "$host" =~ ^[.\-] ]] && return 1 # darf nicht mit . oder - beginnen
[[ "$host" == *.* ]] || return 1 # muss mindestens einen Punkt enthalten
return 0
}
# ─── IPs aus Blocklist-Datei parsen ──────────────────────────────────────────
# Unterstützt IPv4, IPv6, CIDR-Notation und Hostnamen (werden aufgelöst).
# Unterstützt außerdem das Hosts-Datei-Format: "0.0.0.0 hostname" oder "127.0.0.1 hostname".
# Ungültige Einträge (URLs, IP:Port, fehlerhafte IPs, einzelne Wörter usw.) werden
# mit WARN geloggt und übersprungen.
# 0.0.0.0 / :: wird nie importiert (AdGuard-typische Blocking-Antwort).
parse_blocklist_ips() {
local cache_file="$1"
[[ -f "$cache_file" ]] || return
# Zeilen lesen, Leerzeilen und Kommentare ignorieren, IPs extrahieren
while IFS= read -r line; do
# Leerzeilen überspringen
[[ -z "$line" ]] && continue
# Kommentare überspringen (# am Anfang)
[[ "$line" =~ ^[[:space:]]*# ]] && continue
# Whitespace trimmen
# Leerzeilen und Kommentarzeilen überspringen
[[ -z "$line" ]] && continue
[[ "$line" =~ ^[[:space:]]*# ]] && continue
# Whitespace trimmen, dann Inline-Kommentare entfernen (# oder ;)
line=$(echo "$line" | xargs)
# Leere Zeilen nach Trim überspringen
line=$(echo "$line" | sed 's/[[:space:]]*[#;].*$//' | xargs)
[[ -z "$line" ]] && continue
# CIDR-Notation oder reine IP ausgeben
echo "$line"
# ── URLs ablehnen (http://, https://, ftp:// …) ──────────────────────
if [[ "$line" =~ ^[a-zA-Z][a-zA-Z0-9+.-]*:// ]]; then
log "WARN" "Eintrag übersprungen (URL nicht erlaubt): $line"
continue
fi
# ── Hosts-Datei-Format erkennen: "<routing-IP> <ziel>" ───────────────
# z.B. "0.0.0.0 bad.com" oder "127.0.0.1 malware.net"
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" "Hosts-Format erkannt, extrahiere Ziel: $_second"
line="$_second"
else
log "WARN" "Eintrag übersprungen (Leerzeichen im Eintrag, unbekanntes Format): $line"
continue
fi
fi
# ── Klassifizieren und validieren ─────────────────────────────────────
if [[ "$line" == *:* ]]; then
# ── IPv6 ──────────────────────────────────────────────────────────
if _is_valid_ipv6 "$line"; then
echo "$line"
else
log "WARN" "Eintrag übersprungen (ungültige IPv6-Adresse oder IP:Port): $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" "Eintrag übersprungen (ungültige IPv4-Adresse oder ungültiges CIDR): $line"
fi
else
# ── Hostname → DNS-Auflösung ──────────────────────────────────────
if ! _is_valid_hostname "$line"; then
log "WARN" "Eintrag übersprungen (kein gültiger Hostname): $line"
continue
fi
local resolved
resolved=$(getent ahosts "$line" 2>/dev/null | awk '{print $1}' | sort -u)
if [[ -z "$resolved" ]]; then
log "WARN" "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 # AdGuard-Blocking-Antwort
[[ "$resolved_ip" == "::" ]] && continue # IPv6 unspecified
[[ "$resolved_ip" == "::0" ]] && continue
echo "$resolved_ip"
resolved_count=$((resolved_count + 1))
done <<< "$resolved"
if [[ $resolved_count -gt 0 ]]; then
log "DEBUG" "Hostname aufgelöst: $line$resolved_count IP(s)"
else
log "WARN" "Hostname lieferte nur ungültige Adressen (z.B. 0.0.0.0): $line wird übersprungen"
fi
fi
done < "$cache_file"
}
+23 -3
View File
@@ -6,7 +6,7 @@
# Lizenz: MIT
###############################################################################
VERSION="0.4.0"
VERSION="v0.5.0"
set -euo pipefail
@@ -39,11 +39,12 @@ print_header() {
echo -e "${NC}"
echo -e "${GREEN} Version: ${VERSION}${NC}"
echo -e "${BLUE} Autor: Patrick Asmus${NC}"
echo -e
echo -e "${BLUE} E-Mail: support@techniverse.net${NC}"
echo -e "${BLUE}───────────────────────────────────────────────────────────────────────────────────────────────────────────────${NC}"
echo -e "${BLUE} Web: https://www.patrick-asmus.de${NC}"
echo -e "${BLUE} Blog: https://www.cleveradmin.de${NC}"
echo ""
echo -e "${BLUE}───────────────────────────────────────────────────────────────────────────────────────────────────────────────${NC}"
echo ""
echo -e "${BLUE} Repo: https://git.techniverse.net/scriptos/adguard-shield${NC}"
echo ""
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════════════════════════════════════════════════${NC}"
@@ -109,6 +110,13 @@ print_help() {
echo -e " ${CYAN}sudo /opt/adguard-shield/iptables-helper.sh save${NC} # Regeln speichern"
echo -e " ${CYAN}sudo /opt/adguard-shield/iptables-helper.sh restore${NC} # Regeln wiederherstellen"
echo ""
echo -e "${BOLD}Report-Befehle:${NC}"
echo -e " ${CYAN}sudo /opt/adguard-shield/report-generator.sh status${NC} # Report-Konfiguration anzeigen"
echo -e " ${CYAN}sudo /opt/adguard-shield/report-generator.sh send${NC} # Report sofort senden"
echo -e " ${CYAN}sudo /opt/adguard-shield/report-generator.sh generate${NC} # Report als Datei generieren"
echo -e " ${CYAN}sudo /opt/adguard-shield/report-generator.sh install${NC} # Cron-Job einrichten"
echo -e " ${CYAN}sudo /opt/adguard-shield/report-generator.sh remove${NC} # Cron-Job entfernen"
echo ""
echo -e "${BOLD}Voraussetzungen:${NC}"
echo " - Linux Server (Debian/Ubuntu empfohlen)"
echo " - Root-Zugriff (sudo)"
@@ -230,12 +238,19 @@ 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/report-generator.sh" "$INSTALL_DIR/"
# Templates kopieren
mkdir -p "$INSTALL_DIR/templates"
cp "$SCRIPT_DIR/templates/report.html" "$INSTALL_DIR/templates/"
cp "$SCRIPT_DIR/templates/report.txt" "$INSTALL_DIR/templates/"
# Ausführbar machen
chmod +x "$INSTALL_DIR/adguard-shield.sh"
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/report-generator.sh"
echo -e " ✅ Dateien installiert"
echo ""
@@ -496,6 +511,11 @@ print_summary() {
echo " sudo $INSTALL_DIR/adguard-shield.sh flush"
echo " sudo $INSTALL_DIR/adguard-shield.sh unban <IP>"
echo ""
echo " E-Mail Report:"
echo " sudo $INSTALL_DIR/report-generator.sh status"
echo " sudo $INSTALL_DIR/report-generator.sh install"
echo " sudo $INSTALL_DIR/report-generator.sh send"
echo ""
echo " Hilfe anzeigen:"
echo " sudo bash install.sh --help"
echo ""
+978
View File
@@ -0,0 +1,978 @@
#!/bin/bash
###############################################################################
# AdGuard Shield - Report Generator
# Erstellt und versendet periodische Statistik-Reports per E-Mail.
#
# Nutzung:
# report-generator.sh send Report sofort generieren und versenden
# report-generator.sh test Test-E-Mail senden (Konfiguration prüfen)
# report-generator.sh generate Report als Datei generieren (ohne Versand)
# report-generator.sh install Cron-Job einrichten
# report-generator.sh remove Cron-Job entfernen
# report-generator.sh status Cron-Status anzeigen
#
# Crontab-Eintrag (wird automatisch verwaltet):
# Wird je nach REPORT_INTERVAL als Cron-Job unter /etc/cron.d/ angelegt.
#
# 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"
CRON_FILE="/etc/cron.d/adguard-shield-report"
# ─── 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 ────────────────────────────────────────────────────────────
REPORT_ENABLED="${REPORT_ENABLED:-false}"
REPORT_INTERVAL="${REPORT_INTERVAL:-weekly}"
REPORT_TIME="${REPORT_TIME:-08:00}"
REPORT_EMAIL_TO="${REPORT_EMAIL_TO:-}"
REPORT_EMAIL_FROM="${REPORT_EMAIL_FROM:-adguard-shield@$(hostname -f 2>/dev/null || hostname)}"
REPORT_FORMAT="${REPORT_FORMAT:-html}"
REPORT_MAIL_CMD="${REPORT_MAIL_CMD:-msmtp}"
BAN_HISTORY_FILE="${BAN_HISTORY_FILE:-/var/log/adguard-shield-bans.log}"
BAN_HISTORY_RETENTION_DAYS="${BAN_HISTORY_RETENTION_DAYS:-0}"
STATE_DIR="${STATE_DIR:-/var/lib/adguard-shield}"
TEMPLATE_DIR="${SCRIPT_DIR}/templates"
# Version aus dem Hauptscript auslesen
VERSION="unknown"
if [[ -f "${SCRIPT_DIR}/adguard-shield.sh" ]]; then
VERSION=$(grep -m1 '^VERSION=' "${SCRIPT_DIR}/adguard-shield.sh" 2>/dev/null | cut -d'"' -f2)
VERSION="${VERSION:-unknown}"
fi
# ─── Logging ──────────────────────────────────────────────────────────────────
LOG_FILE="${LOG_FILE:-/var/log/adguard-shield.log}"
log() {
local level="$1"
shift
local message="$*"
local timestamp
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
echo "[$timestamp] [$level] [REPORT] $message" | tee -a "$LOG_FILE"
}
# ─── Berichtszeitraum berechnen ───────────────────────────────────────────────
get_report_period() {
local now_epoch
now_epoch=$(date '+%s')
local start_epoch
local period_label
case "$REPORT_INTERVAL" in
daily)
start_epoch=$((now_epoch - 86400))
period_label="Tagesbericht"
;;
weekly)
start_epoch=$((now_epoch - 604800))
period_label="Wochenbericht"
;;
biweekly)
start_epoch=$((now_epoch - 1209600))
period_label="Zweiwochenbericht"
;;
monthly)
start_epoch=$((now_epoch - 2592000))
period_label="Monatsbericht"
;;
*)
start_epoch=$((now_epoch - 604800))
period_label="Bericht"
;;
esac
local start_date
start_date=$(date -d "@$start_epoch" '+%d.%m.%Y' 2>/dev/null || date -r "$start_epoch" '+%d.%m.%Y')
local end_date
end_date=$(date '+%d.%m.%Y')
echo "${period_label}: ${start_date} ${end_date}"
}
get_period_start_epoch() {
local now_epoch
now_epoch=$(date '+%s')
case "$REPORT_INTERVAL" in
daily) echo $((now_epoch - 86400)) ;;
weekly) echo $((now_epoch - 604800)) ;;
biweekly) echo $((now_epoch - 1209600)) ;;
monthly) echo $((now_epoch - 2592000)) ;;
*) echo $((now_epoch - 604800)) ;;
esac
}
# ─── Ban-History filtern nach Zeitraum ────────────────────────────────────────
# Gibt nur Zeilen zurück, deren Zeitstempel im Berichtszeitraum liegen
filter_history_by_period() {
local start_epoch="$1"
if [[ ! -f "$BAN_HISTORY_FILE" ]]; then
return
fi
while IFS= read -r line; do
# Kommentare und leere Zeilen überspringen
[[ "$line" =~ ^#.*$ || -z "$line" ]] && continue
# Zeitstempel extrahieren (erstes Feld, Format: YYYY-MM-DD HH:MM:SS)
local timestamp
timestamp=$(echo "$line" | awk -F'|' '{print $1}' | xargs)
if [[ -z "$timestamp" ]]; then
continue
fi
# Timestamp in Epoch umwandeln
local line_epoch
line_epoch=$(date -d "$timestamp" '+%s' 2>/dev/null || date -j -f '%Y-%m-%d %H:%M:%S' "$timestamp" '+%s' 2>/dev/null || echo "0")
if [[ "$line_epoch" -ge "$start_epoch" ]]; then
echo "$line"
fi
done < "$BAN_HISTORY_FILE"
}
# ─── Ban-History bereinigen ────────────────────────────────────────────────────
# Entfernt Einträge älter als BAN_HISTORY_RETENTION_DAYS (0 = deaktiviert)
cleanup_ban_history() {
[[ ! -f "$BAN_HISTORY_FILE" ]] && return
[[ "$BAN_HISTORY_RETENTION_DAYS" == "0" || -z "$BAN_HISTORY_RETENTION_DAYS" ]] && return
local cutoff_epoch
cutoff_epoch=$(date -d "-${BAN_HISTORY_RETENTION_DAYS} days" '+%s' 2>/dev/null)
[[ -z "$cutoff_epoch" ]] && return
local tmp_file="${BAN_HISTORY_FILE}.tmp"
local removed=0
while IFS= read -r line; do
# Header-Zeilen immer beibehalten
if [[ "$line" =~ ^#.*$ || -z "$line" ]]; then
echo "$line"
continue
fi
local timestamp
timestamp=$(echo "$line" | awk -F'|' '{print $1}' | xargs)
local line_epoch
line_epoch=$(date -d "$timestamp" '+%s' 2>/dev/null || echo "0")
if [[ "$line_epoch" -ge "$cutoff_epoch" ]]; then
echo "$line"
else
((removed++)) || true
fi
done < "$BAN_HISTORY_FILE" > "$tmp_file"
if [[ $removed -gt 0 ]]; then
mv "$tmp_file" "$BAN_HISTORY_FILE"
log "INFO" "Ban-History bereinigt: $removed Einträge älter als ${BAN_HISTORY_RETENTION_DAYS} Tage entfernt"
else
rm -f "$tmp_file"
fi
}
# ─── Statistiken berechnen ────────────────────────────────────────────────────
calculate_stats() {
# Ban-History bereinigen (falls Retention konfiguriert)
cleanup_ban_history
local start_epoch
start_epoch=$(get_period_start_epoch)
local filtered_data
filtered_data=$(filter_history_by_period "$start_epoch")
# Wenn keine Daten vorhanden, Standardwerte
if [[ -z "$filtered_data" ]]; then
TOTAL_BANS=0
TOTAL_UNBANS=0
UNIQUE_IPS=0
PERMANENT_BANS=0
ACTIVE_BANS=0
ABUSEIPDB_REPORTS=0
RATELIMIT_BANS=0
SUBDOMAIN_FLOOD_BANS=0
EXTERNAL_BLOCKLIST_BANS=0
BUSIEST_DAY=""
TOP10_IPS=""
TOP10_DOMAINS=""
PROTOCOL_STATS=""
RECENT_BANS=""
return
fi
# Gesamtzahl Sperren
TOTAL_BANS=$(echo "$filtered_data" | grep -c '| BAN ' || echo "0")
# Gesamtzahl Entsperrungen
TOTAL_UNBANS=$(echo "$filtered_data" | grep -c '| UNBAN ' || echo "0")
# Eindeutige IPs (nur BAN-Einträge)
UNIQUE_IPS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $3}' | xargs -I{} echo {} | sort -u | wc -l | xargs)
# Permanente Sperren (Dauer enthält "PERMANENT" oder "permanent")
PERMANENT_BANS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $6}' | grep -ic 'permanent' || echo "0")
# Aktuell aktive Sperren (aus State-Dateien)
ACTIVE_BANS=0
if [[ -d "$STATE_DIR" ]]; then
for f in "${STATE_DIR}"/*.ban; do
[[ -f "$f" ]] && ACTIVE_BANS=$((ACTIVE_BANS + 1))
done
fi
# AbuseIPDB Reports (suche in Log-Datei)
ABUSEIPDB_REPORTS=0
if [[ -f "$LOG_FILE" ]]; then
local abuseipdb_start_date
abuseipdb_start_date=$(date -d "@$start_epoch" '+%Y-%m-%d' 2>/dev/null || date -r "$start_epoch" '+%Y-%m-%d')
ABUSEIPDB_REPORTS=$(grep -c "AbuseIPDB: IP .* erfolgreich gemeldet" "$LOG_FILE" 2>/dev/null | head -1 || echo "0")
fi
# Angriffsarten
RATELIMIT_BANS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $8}' | grep -ic 'rate-limit' || echo "0")
SUBDOMAIN_FLOOD_BANS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $8}' | grep -ic 'subdomain-flood' || echo "0")
EXTERNAL_BLOCKLIST_BANS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $8}' | grep -ic 'external-blocklist' || echo "0")
# Aktivster Tag
BUSIEST_DAY=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $1}' | xargs -I{} echo {} | awk '{print $1}' | sort | uniq -c | sort -rn | head -1 | awk '{print $2}' || echo "")
if [[ -z "$BUSIEST_DAY" ]]; then
BUSIEST_DAY=""
else
# Datum in DE-Format umwandeln
local busiest_formatted
busiest_formatted=$(date -d "$BUSIEST_DAY" '+%d.%m.%Y' 2>/dev/null || echo "$BUSIEST_DAY")
BUSIEST_DAY="$busiest_formatted"
fi
# Top 10 IPs (nach Häufigkeit der Sperren)
TOP10_IPS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $3}' | xargs -I{} echo {} | sort | uniq -c | sort -rn | head -10)
# Top 10 Domains (nach Häufigkeit)
TOP10_DOMAINS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $4}' | xargs -I{} echo {} | sed 's/^-$//' | grep -v '^$' | sort | uniq -c | sort -rn | head -10)
# Protokoll-Verteilung
PROTOCOL_STATS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $7}' | xargs -I{} echo {} | sed 's/^-$/unbekannt/' | grep -v '^$' | sort | uniq -c | sort -rn)
# Letzte 10 Sperren
RECENT_BANS=$(echo "$filtered_data" | grep '| BAN ' | tail -10)
}
# ─── HTML-Tabellen generieren ─────────────────────────────────────────────────
generate_top10_ips_html() {
if [[ -z "$TOP10_IPS" ]]; then
echo '<div class="no-data">Keine Daten im Berichtszeitraum</div>'
return
fi
local max_count
max_count=$(echo "$TOP10_IPS" | head -1 | awk '{print $1}')
local html='<table><tr><th>#</th><th>IP-Adresse</th><th>Sperren</th></tr>'
local rank=0
while read -r count ip; do
[[ -z "$count" || -z "$ip" ]] && continue
rank=$((rank + 1))
local rank_class=""
[[ $rank -le 3 ]] && rank_class=" top3"
local bar_width=100
if [[ "$max_count" -gt 0 ]]; then
bar_width=$((count * 100 / max_count))
fi
html+="<tr><td><span class=\"rank${rank_class}\">${rank}</span></td>"
html+="<td class=\"ip-cell\">${ip}</td>"
html+="<td><div class=\"bar-container\"><div class=\"bar\" style=\"width:${bar_width}%\"></div><span class=\"bar-value\">${count}</span></div></td>"
html+="</tr>"
done <<< "$TOP10_IPS"
html+='</table>'
echo "$html"
}
generate_top10_domains_html() {
if [[ -z "$TOP10_DOMAINS" ]]; then
echo '<div class="no-data">Keine Daten im Berichtszeitraum</div>'
return
fi
local max_count
max_count=$(echo "$TOP10_DOMAINS" | head -1 | awk '{print $1}')
local html='<table><tr><th>#</th><th>Domain</th><th>Sperren</th></tr>'
local rank=0
while read -r count domain; do
[[ -z "$count" || -z "$domain" ]] && continue
rank=$((rank + 1))
local rank_class=""
[[ $rank -le 3 ]] && rank_class=" top3"
local bar_width=100
if [[ "$max_count" -gt 0 ]]; then
bar_width=$((count * 100 / max_count))
fi
html+="<tr><td><span class=\"rank${rank_class}\">${rank}</span></td>"
html+="<td>${domain}</td>"
html+="<td><div class=\"bar-container\"><div class=\"bar\" style=\"width:${bar_width}%\"></div><span class=\"bar-value\">${count}</span></div></td>"
html+="</tr>"
done <<< "$TOP10_DOMAINS"
html+='</table>'
echo "$html"
}
generate_protocol_html() {
if [[ -z "$PROTOCOL_STATS" ]]; then
echo '<div class="no-data">Keine Daten im Berichtszeitraum</div>'
return
fi
local html='<table><tr><th>Protokoll</th><th>Anzahl Sperren</th></tr>'
while read -r count proto; do
[[ -z "$count" || -z "$proto" ]] && continue
local badge_class=""
case "${proto,,}" in
dns*) badge_class="dns" ;;
doh*) badge_class="doh" ;;
dot*) badge_class="dot" ;;
doq*) badge_class="doq" ;;
esac
html+="<tr><td><span class=\"protocol-badge ${badge_class}\">${proto}</span></td>"
html+="<td>${count}</td></tr>"
done <<< "$PROTOCOL_STATS"
html+='</table>'
echo "$html"
}
generate_recent_bans_html() {
if [[ -z "$RECENT_BANS" ]]; then
echo '<div class="no-data">Keine Sperren im Berichtszeitraum</div>'
return
fi
local html='<table><tr><th>Zeitpunkt</th><th>IP</th><th>Domain</th><th>Grund</th></tr>'
while IFS='|' read -r timestamp action ip domain count duration protocol reason; do
timestamp=$(echo "$timestamp" | xargs)
ip=$(echo "$ip" | xargs)
domain=$(echo "$domain" | xargs)
reason=$(echo "$reason" | xargs)
[[ "$domain" == "-" ]] && domain=""
[[ -z "$reason" ]] && reason="rate-limit"
local reason_class="rate-limit"
[[ "$reason" == *"subdomain"* ]] && reason_class="subdomain-flood"
[[ "$reason" == *"external"* ]] && reason_class="external"
# Datum kürzen für Anzeige
local short_time
short_time=$(echo "$timestamp" | awk '{print $1" "$2}' | cut -c6-)
html+="<tr><td>${short_time}</td>"
html+="<td class=\"ip-cell\">${ip}</td>"
html+="<td>${domain}</td>"
html+="<td><span class=\"reason-badge ${reason_class}\">${reason}</span></td>"
html+="</tr>"
done <<< "$RECENT_BANS"
html+='</table>'
echo "$html"
}
# ─── TXT-Tabellen generieren ──────────────────────────────────────────────────
generate_top10_ips_txt() {
if [[ -z "$TOP10_IPS" ]]; then
echo " Keine Daten im Berichtszeitraum"
return
fi
local rank=0
printf " %-4s %-42s %s\n" "#" "IP-Adresse" "Sperren"
printf " %-4s %-42s %s\n" "──" "──────────────────────────────────────────" "───────"
while read -r count ip; do
[[ -z "$count" || -z "$ip" ]] && continue
rank=$((rank + 1))
printf " %-4s %-42s %s\n" "${rank}." "$ip" "$count"
done <<< "$TOP10_IPS"
}
generate_top10_domains_txt() {
if [[ -z "$TOP10_DOMAINS" ]]; then
echo " Keine Daten im Berichtszeitraum"
return
fi
local rank=0
printf " %-4s %-42s %s\n" "#" "Domain" "Sperren"
printf " %-4s %-42s %s\n" "──" "──────────────────────────────────────────" "───────"
while read -r count domain; do
[[ -z "$count" || -z "$domain" ]] && continue
rank=$((rank + 1))
printf " %-4s %-42s %s\n" "${rank}." "$domain" "$count"
done <<< "$TOP10_DOMAINS"
}
generate_protocol_txt() {
if [[ -z "$PROTOCOL_STATS" ]]; then
echo " Keine Daten im Berichtszeitraum"
return
fi
printf " %-20s %s\n" "Protokoll" "Anzahl"
printf " %-20s %s\n" "────────────────────" "──────"
while read -r count proto; do
[[ -z "$count" || -z "$proto" ]] && continue
printf " %-20s %s\n" "$proto" "$count"
done <<< "$PROTOCOL_STATS"
}
generate_recent_bans_txt() {
if [[ -z "$RECENT_BANS" ]]; then
echo " Keine Sperren im Berichtszeitraum"
return
fi
printf " %-17s %-42s %-30s %s\n" "Zeitpunkt" "IP" "Domain" "Grund"
printf " %-17s %-42s %-30s %s\n" "─────────────────" "──────────────────────────────────────────" "──────────────────────────────" "──────────"
while IFS='|' read -r timestamp action ip domain count duration protocol reason; do
timestamp=$(echo "$timestamp" | xargs)
ip=$(echo "$ip" | xargs)
domain=$(echo "$domain" | xargs)
reason=$(echo "$reason" | xargs)
[[ "$domain" == "-" ]] && domain=""
[[ -z "$reason" ]] && reason="rate-limit"
local short_time
short_time=$(echo "$timestamp" | awk '{print $1" "$2}' | cut -c6-)
printf " %-17s %-42s %-30s %s\n" "$short_time" "$ip" "$domain" "$reason"
done <<< "$RECENT_BANS"
}
# ─── Report generieren ────────────────────────────────────────────────────────
generate_report() {
local format="${1:-$REPORT_FORMAT}"
log "INFO" "Generiere ${format^^}-Report..."
# Statistiken berechnen
calculate_stats
local report_period
report_period=$(get_report_period)
local report_date
report_date=$(date '+%d.%m.%Y %H:%M:%S')
local hostname
hostname=$(hostname -f 2>/dev/null || hostname)
if [[ "$format" == "html" ]]; then
local template_file="${TEMPLATE_DIR}/report.html"
if [[ ! -f "$template_file" ]]; then
log "ERROR" "HTML-Template nicht gefunden: $template_file"
return 1
fi
local report
report=$(cat "$template_file")
# Tabellen generieren
local top10_ips_table
top10_ips_table=$(generate_top10_ips_html)
local top10_domains_table
top10_domains_table=$(generate_top10_domains_html)
local protocol_table
protocol_table=$(generate_protocol_html)
local recent_bans_table
recent_bans_table=$(generate_recent_bans_html)
# Platzhalter ersetzen
report="${report//\{\{REPORT_PERIOD\}\}/$report_period}"
report="${report//\{\{REPORT_DATE\}\}/$report_date}"
report="${report//\{\{HOSTNAME\}\}/$hostname}"
report="${report//\{\{VERSION\}\}/$VERSION}"
report="${report//\{\{TOTAL_BANS\}\}/$TOTAL_BANS}"
report="${report//\{\{TOTAL_UNBANS\}\}/$TOTAL_UNBANS}"
report="${report//\{\{UNIQUE_IPS\}\}/$UNIQUE_IPS}"
report="${report//\{\{PERMANENT_BANS\}\}/$PERMANENT_BANS}"
report="${report//\{\{ACTIVE_BANS\}\}/$ACTIVE_BANS}"
report="${report//\{\{ABUSEIPDB_REPORTS\}\}/$ABUSEIPDB_REPORTS}"
report="${report//\{\{RATELIMIT_BANS\}\}/$RATELIMIT_BANS}"
report="${report//\{\{SUBDOMAIN_FLOOD_BANS\}\}/$SUBDOMAIN_FLOOD_BANS}"
report="${report//\{\{EXTERNAL_BLOCKLIST_BANS\}\}/$EXTERNAL_BLOCKLIST_BANS}"
report="${report//\{\{BUSIEST_DAY\}\}/$BUSIEST_DAY}"
report="${report//\{\{TOP10_IPS_TABLE\}\}/$top10_ips_table}"
report="${report//\{\{TOP10_DOMAINS_TABLE\}\}/$top10_domains_table}"
report="${report//\{\{PROTOCOL_TABLE\}\}/$protocol_table}"
report="${report//\{\{RECENT_BANS_TABLE\}\}/$recent_bans_table}"
echo "$report"
elif [[ "$format" == "txt" ]]; then
local template_file="${TEMPLATE_DIR}/report.txt"
if [[ ! -f "$template_file" ]]; then
log "ERROR" "TXT-Template nicht gefunden: $template_file"
return 1
fi
local report
report=$(cat "$template_file")
# Text-Tabellen generieren
local top10_ips_txt
top10_ips_txt=$(generate_top10_ips_txt)
local top10_domains_txt
top10_domains_txt=$(generate_top10_domains_txt)
local protocol_txt
protocol_txt=$(generate_protocol_txt)
local recent_bans_txt
recent_bans_txt=$(generate_recent_bans_txt)
# Platzhalter ersetzen
report="${report//\{\{REPORT_PERIOD\}\}/$report_period}"
report="${report//\{\{REPORT_DATE\}\}/$report_date}"
report="${report//\{\{HOSTNAME\}\}/$hostname}"
report="${report//\{\{VERSION\}\}/$VERSION}"
report="${report//\{\{TOTAL_BANS\}\}/$TOTAL_BANS}"
report="${report//\{\{TOTAL_UNBANS\}\}/$TOTAL_UNBANS}"
report="${report//\{\{UNIQUE_IPS\}\}/$UNIQUE_IPS}"
report="${report//\{\{PERMANENT_BANS\}\}/$PERMANENT_BANS}"
report="${report//\{\{ACTIVE_BANS\}\}/$ACTIVE_BANS}"
report="${report//\{\{ABUSEIPDB_REPORTS\}\}/$ABUSEIPDB_REPORTS}"
report="${report//\{\{RATELIMIT_BANS\}\}/$RATELIMIT_BANS}"
report="${report//\{\{SUBDOMAIN_FLOOD_BANS\}\}/$SUBDOMAIN_FLOOD_BANS}"
report="${report//\{\{EXTERNAL_BLOCKLIST_BANS\}\}/$EXTERNAL_BLOCKLIST_BANS}"
report="${report//\{\{BUSIEST_DAY\}\}/$BUSIEST_DAY}"
report="${report//\{\{TOP10_IPS_TEXT\}\}/$top10_ips_txt}"
report="${report//\{\{TOP10_DOMAINS_TEXT\}\}/$top10_domains_txt}"
report="${report//\{\{PROTOCOL_TEXT\}\}/$protocol_txt}"
report="${report//\{\{RECENT_BANS_TEXT\}\}/$recent_bans_txt}"
echo "$report"
else
log "ERROR" "Unbekanntes Report-Format: $format"
return 1
fi
}
# ─── E-Mail senden ────────────────────────────────────────────────────────────
send_report_email() {
if [[ -z "$REPORT_EMAIL_TO" ]]; then
log "ERROR" "Kein E-Mail-Empfänger konfiguriert (REPORT_EMAIL_TO)"
return 1
fi
# Prüfen ob Mail-Befehl verfügbar
if ! command -v "$REPORT_MAIL_CMD" &>/dev/null; then
log "ERROR" "Mail-Befehl nicht gefunden: $REPORT_MAIL_CMD"
log "ERROR" "Bitte installieren, z.B.: sudo apt install msmtp msmtp-mta"
log "ERROR" "Anleitung: https://www.cleveradmin.de/blog/2024/12/linux-einfach-emails-versenden-mit-msmtp/"
return 1
fi
local report_content
report_content=$(generate_report "$REPORT_FORMAT")
if [[ -z "$report_content" ]]; then
log "ERROR" "Report-Generierung fehlgeschlagen keine Daten"
return 1
fi
local subject="🛡️ AdGuard Shield $(get_report_period) $(hostname)"
local content_type="text/plain"
[[ "$REPORT_FORMAT" == "html" ]] && content_type="text/html"
log "INFO" "Sende Report an ${REPORT_EMAIL_TO} via ${REPORT_MAIL_CMD}..."
# E-Mail zusammenbauen und senden
{
echo "From: ${REPORT_EMAIL_FROM}"
echo "To: ${REPORT_EMAIL_TO}"
echo "Subject: ${subject}"
echo "MIME-Version: 1.0"
echo "Content-Type: ${content_type}; charset=UTF-8"
echo "Content-Transfer-Encoding: 8bit"
echo "X-Mailer: AdGuard Shield Report Generator"
echo ""
echo "$report_content"
} | "$REPORT_MAIL_CMD" -t 2>&1 || {
log "ERROR" "E-Mail-Versand fehlgeschlagen (Exit-Code: $?)"
log "ERROR" "Prüfe die ${REPORT_MAIL_CMD}-Konfiguration"
return 1
}
log "INFO" "Report erfolgreich an ${REPORT_EMAIL_TO} gesendet"
}
# ─── Cron-Job verwalten ───────────────────────────────────────────────────────
install_cron() {
if [[ "$REPORT_ENABLED" != "true" ]]; then
log "WARN" "Report ist deaktiviert (REPORT_ENABLED=false)"
echo "Report ist deaktiviert. Bitte REPORT_ENABLED=true setzen."
return 1
fi
local hour minute
hour=$(echo "$REPORT_TIME" | cut -d: -f1 | sed 's/^0//')
minute=$(echo "$REPORT_TIME" | cut -d: -f2 | sed 's/^0//')
local cron_schedule
case "$REPORT_INTERVAL" in
daily)
cron_schedule="${minute} ${hour} * * *"
;;
weekly)
# Montag
cron_schedule="${minute} ${hour} * * 1"
;;
biweekly)
# Alle zwei Wochen am Montag (ungerade Kalenderwochen)
# Nutzt einen Test im Befehl selbst, da Cron keine 2-Wochen-Logik kann
cron_schedule="${minute} ${hour} * * 1"
;;
monthly)
# 1. des Monats
cron_schedule="${minute} ${hour} 1 * *"
;;
*)
log "ERROR" "Unbekanntes Intervall: $REPORT_INTERVAL"
return 1
;;
esac
local cron_cmd="${SCRIPT_DIR}/report-generator.sh send"
# Bei biweekly: Prüfung auf ungerade Kalenderwoche einbauen
if [[ "$REPORT_INTERVAL" == "biweekly" ]]; then
cron_cmd="[ \$(( \$(date +\\%V) \\% 2 )) -eq 1 ] && ${SCRIPT_DIR}/report-generator.sh send"
fi
# Cron-Datei schreiben
cat > "$CRON_FILE" << EOF
# AdGuard Shield - Automatischer Report
# Generiert von: report-generator.sh install
# Intervall: ${REPORT_INTERVAL}
# Uhrzeit: ${REPORT_TIME}
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
${cron_schedule} root ${cron_cmd} >> /var/log/adguard-shield.log 2>&1
EOF
chmod 644 "$CRON_FILE"
log "INFO" "Cron-Job installiert: $CRON_FILE"
echo "✅ Cron-Job installiert: $CRON_FILE"
echo " Intervall: $REPORT_INTERVAL"
echo " Uhrzeit: $REPORT_TIME"
echo " Schedule: $cron_schedule"
echo " Empfänger: $REPORT_EMAIL_TO"
}
remove_cron() {
if [[ -f "$CRON_FILE" ]]; then
rm -f "$CRON_FILE"
log "INFO" "Cron-Job entfernt: $CRON_FILE"
echo "✅ Cron-Job entfernt"
else
echo "️ Kein Cron-Job vorhanden"
fi
}
show_cron_status() {
echo "═══════════════════════════════════════════════════════════════"
echo " AdGuard Shield Report Status"
echo "═══════════════════════════════════════════════════════════════"
echo ""
echo " Report aktiviert: ${REPORT_ENABLED}"
echo " Intervall: ${REPORT_INTERVAL}"
echo " Uhrzeit: ${REPORT_TIME}"
echo " Format: ${REPORT_FORMAT}"
echo " Empfänger: ${REPORT_EMAIL_TO:-nicht konfiguriert}"
echo " Absender: ${REPORT_EMAIL_FROM}"
echo " Mail-Befehl: ${REPORT_MAIL_CMD}"
echo ""
if command -v "$REPORT_MAIL_CMD" &>/dev/null; then
echo "${REPORT_MAIL_CMD} ist installiert"
else
echo "${REPORT_MAIL_CMD} ist NICHT installiert"
echo " → Anleitung: https://www.cleveradmin.de/blog/2024/12/linux-einfach-emails-versenden-mit-msmtp/"
fi
echo ""
if [[ -f "$CRON_FILE" ]]; then
echo " ✅ Cron-Job aktiv:"
grep -v '^#' "$CRON_FILE" | grep -v '^$' | grep -v '^SHELL\|^PATH' | sed 's/^/ /'
else
echo " ❌ Kein Cron-Job installiert"
echo " → Einrichten mit: sudo $(basename "$0") install"
fi
echo ""
echo "═══════════════════════════════════════════════════════════════"
}
# ─── Test-E-Mail senden ───────────────────────────────────────────────────────
send_test_email() {
echo "═══════════════════════════════════════════════════════════════"
echo " AdGuard Shield E-Mail Test"
echo "═══════════════════════════════════════════════════════════════"
echo ""
local errors=0
# 1. Empfänger prüfen
echo -n " 1) E-Mail-Empfänger ... "
if [[ -z "$REPORT_EMAIL_TO" ]]; then
echo "❌ nicht konfiguriert (REPORT_EMAIL_TO ist leer)"
errors=$((errors + 1))
else
echo "$REPORT_EMAIL_TO"
fi
# 2. Absender prüfen
echo -n " 2) E-Mail-Absender ... "
echo "$REPORT_EMAIL_FROM"
# 3. Mail-Befehl prüfen
echo -n " 3) Mail-Befehl ($REPORT_MAIL_CMD) ... "
if command -v "$REPORT_MAIL_CMD" &>/dev/null; then
local mail_path
mail_path=$(command -v "$REPORT_MAIL_CMD")
echo "✅ gefunden ($mail_path)"
else
echo "❌ NICHT gefunden"
echo " → Installieren: sudo apt install msmtp msmtp-mta"
echo " → Anleitung: https://www.cleveradmin.de/blog/2024/12/linux-einfach-emails-versenden-mit-msmtp/"
errors=$((errors + 1))
fi
# 4. Templates prüfen
echo -n " 4) Report-Template ($REPORT_FORMAT) ... "
local tpl="${TEMPLATE_DIR}/report.${REPORT_FORMAT}"
if [[ -f "$tpl" ]]; then
echo "✅ vorhanden"
else
echo "❌ nicht gefunden: $tpl"
errors=$((errors + 1))
fi
# 5. Ban-History prüfen
echo -n " 5) Ban-History ... "
if [[ -f "$BAN_HISTORY_FILE" ]]; then
local lines
lines=$(grep -vc '^#' "$BAN_HISTORY_FILE" 2>/dev/null || echo "0")
echo "✅ vorhanden ($lines Einträge)"
else
echo "⚠️ nicht vorhanden (Report wird leer sein das ist OK für einen Test)"
fi
echo ""
if [[ $errors -gt 0 ]]; then
echo "$errors Fehler gefunden bitte zuerst beheben."
echo ""
echo "═══════════════════════════════════════════════════════════════"
return 1
fi
# 6. Test-Mail senden
echo " 6) Sende Test-E-Mail an ${REPORT_EMAIL_TO} ..."
echo ""
local hostname
hostname=$(hostname -f 2>/dev/null || hostname)
local test_date
test_date=$(date '+%d.%m.%Y %H:%M:%S')
local subject="🧪 AdGuard Shield Test-Mail von ${hostname}"
local content_type="text/plain"
[[ "$REPORT_FORMAT" == "html" ]] && content_type="text/html"
local test_body
if [[ "$REPORT_FORMAT" == "html" ]]; then
test_body=$(cat <<TESTHTML
<!DOCTYPE html>
<html lang="de"><head><meta charset="UTF-8"></head>
<body style="font-family:sans-serif;background:#f0f2f5;padding:30px;">
<div style="max-width:600px;margin:0 auto;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 2px 12px rgba(0,0,0,0.08);">
<div style="background:linear-gradient(135deg,#1a1a2e 0%,#16213e 50%,#0f3460 100%);color:#fff;padding:30px;text-align:center;">
<h1 style="margin:0;">🧪 Test-Mail</h1>
<p style="color:#a8b2d1;margin:6px 0 0;">AdGuard Shield Report</p>
</div>
<div style="padding:30px;">
<h2 style="color:#27ae60;">✅ E-Mail-Versand funktioniert!</h2>
<p>Diese Test-Mail bestätigt, dass der E-Mail-Versand für AdGuard Shield korrekt konfiguriert ist.</p>
<table style="width:100%;border-collapse:collapse;margin:20px 0;">
<tr><td style="padding:8px 14px;border-bottom:1px solid #f0f2f5;color:#6c757d;">Hostname</td><td style="padding:8px 14px;border-bottom:1px solid #f0f2f5;font-weight:600;">${hostname}</td></tr>
<tr><td style="padding:8px 14px;border-bottom:1px solid #f0f2f5;color:#6c757d;">Zeitpunkt</td><td style="padding:8px 14px;border-bottom:1px solid #f0f2f5;font-weight:600;">${test_date}</td></tr>
<tr><td style="padding:8px 14px;border-bottom:1px solid #f0f2f5;color:#6c757d;">Empfänger</td><td style="padding:8px 14px;border-bottom:1px solid #f0f2f5;font-weight:600;">${REPORT_EMAIL_TO}</td></tr>
<tr><td style="padding:8px 14px;border-bottom:1px solid #f0f2f5;color:#6c757d;">Absender</td><td style="padding:8px 14px;border-bottom:1px solid #f0f2f5;font-weight:600;">${REPORT_EMAIL_FROM}</td></tr>
<tr><td style="padding:8px 14px;border-bottom:1px solid #f0f2f5;color:#6c757d;">Mail-Befehl</td><td style="padding:8px 14px;border-bottom:1px solid #f0f2f5;font-weight:600;">${REPORT_MAIL_CMD}</td></tr>
<tr><td style="padding:8px 14px;border-bottom:1px solid #f0f2f5;color:#6c757d;">Format</td><td style="padding:8px 14px;border-bottom:1px solid #f0f2f5;font-weight:600;">${REPORT_FORMAT}</td></tr>
<tr><td style="padding:8px 14px;color:#6c757d;">Intervall</td><td style="padding:8px 14px;font-weight:600;">${REPORT_INTERVAL}</td></tr>
</table>
<p style="color:#6c757d;font-size:13px;">Ab jetzt kannst du den automatischen Versand aktivieren mit:<br><code>sudo $(basename "$0") install</code></p>
</div>
<div style="background:#f8f9fc;padding:20px;text-align:center;font-size:12px;color:#6c757d;border-top:1px solid #e8ecf1;">
<a href="https://www.patrick-asmus.de" style="color:#0f3460;text-decoration:none;font-weight:600;">Patrick-Asmus.DE</a>
<span style="margin:0 8px;color:#ced4da;">|</span>
<a href="https://www.cleveradmin.de" style="color:#0f3460;text-decoration:none;font-weight:600;">CleverAdmin.DE</a>
<span style="margin:0 8px;color:#ced4da;">|</span>
<a href="https://git.techniverse.net/scriptos/adguard-shield.git" style="color:#0f3460;text-decoration:none;font-weight:600;">AdGuard Shield auf Gitea</a>
<div style="margin-top:8px;font-size:11px;color:#adb5bd;">AdGuard Shield v${VERSION} &middot; ${hostname}</div>
</div>
</div>
</body></html>
TESTHTML
)
else
test_body=$(cat <<TESTTXT
═══════════════════════════════════════════════════════════════
🧪 AdGuard Shield Test-Mail
═══════════════════════════════════════════════════════════════
✅ E-Mail-Versand funktioniert!
Diese Test-Mail bestätigt, dass der E-Mail-Versand
für AdGuard Shield korrekt konfiguriert ist.
Hostname: ${hostname}
Zeitpunkt: ${test_date}
Empfänger: ${REPORT_EMAIL_TO}
Absender: ${REPORT_EMAIL_FROM}
Mail-Befehl: ${REPORT_MAIL_CMD}
Format: ${REPORT_FORMAT}
Intervall: ${REPORT_INTERVAL}
Ab jetzt kannst du den automatischen Versand aktivieren mit:
sudo $(basename "$0") install
═══════════════════════════════════════════════════════════════
AdGuard Shield v${VERSION} · ${hostname}
Web: https://www.patrick-asmus.de
Blog: https://www.cleveradmin.de
Repo: https://git.techniverse.net/scriptos/adguard-shield.git
═══════════════════════════════════════════════════════════════
TESTTXT
)
fi
local send_output
send_output=$(
{
echo "From: ${REPORT_EMAIL_FROM}"
echo "To: ${REPORT_EMAIL_TO}"
echo "Subject: ${subject}"
echo "MIME-Version: 1.0"
echo "Content-Type: ${content_type}; charset=UTF-8"
echo "Content-Transfer-Encoding: 8bit"
echo "X-Mailer: AdGuard Shield Report Generator (Test)"
echo ""
echo "$test_body"
} | "$REPORT_MAIL_CMD" -t 2>&1
)
local exit_code=$?
if [[ $exit_code -eq 0 ]]; then
echo " ✅ Test-E-Mail erfolgreich gesendet!"
echo ""
echo " Prüfe dein Postfach: ${REPORT_EMAIL_TO}"
echo " (Evtl. auch im Spam-Ordner nachschauen)"
log "INFO" "Test-E-Mail erfolgreich an ${REPORT_EMAIL_TO} gesendet"
else
echo " ❌ Versand fehlgeschlagen (Exit-Code: $exit_code)"
if [[ -n "$send_output" ]]; then
echo ""
echo " Fehlermeldung:"
echo "$send_output" | sed 's/^/ /'
fi
echo ""
echo " Troubleshooting:"
echo " 1) ${REPORT_MAIL_CMD}-Konfiguration prüfen"
echo " 2) Manuell testen: echo 'Test' | ${REPORT_MAIL_CMD} -t ${REPORT_EMAIL_TO}"
echo " 3) Anleitung: https://www.cleveradmin.de/blog/2024/12/linux-einfach-emails-versenden-mit-msmtp/"
log "ERROR" "Test-E-Mail fehlgeschlagen (Exit-Code: $exit_code): $send_output"
fi
echo ""
echo "═══════════════════════════════════════════════════════════════"
}
# ─── Hilfe ────────────────────────────────────────────────────────────────────
print_help() {
echo "AdGuard Shield Report Generator"
echo ""
echo "Nutzung: $(basename "$0") <Befehl>"
echo ""
echo "Befehle:"
echo " send Report generieren und per E-Mail versenden"
echo " test Test-E-Mail senden (prüft Konfiguration + Mailversand)"
echo " generate Report als Datei generieren (Ausgabe auf stdout)"
echo " install Cron-Job für automatischen Versand einrichten"
echo " remove Cron-Job entfernen"
echo " status Report-Konfiguration und Cron-Status anzeigen"
echo " --help Diese Hilfe anzeigen"
echo ""
echo "Beispiele:"
echo " sudo $(basename "$0") send # Report jetzt senden"
echo " sudo $(basename "$0") test # Test-Mail senden"
echo " sudo $(basename "$0") generate > r.html # Report in Datei speichern"
echo " sudo $(basename "$0") install # Automatischen Versand einrichten"
echo " sudo $(basename "$0") status # Status anzeigen"
echo ""
}
# ─── Kommandozeilen-Argumente ─────────────────────────────────────────────────
case "${1:---help}" in
send)
send_report_email
;;
test)
send_test_email
;;
generate)
generate_report "${2:-$REPORT_FORMAT}"
;;
install)
install_cron
;;
remove)
remove_cron
;;
status)
show_cron_status
;;
--help|-h)
print_help
;;
*)
echo "Unbekannter Befehl: $1"
print_help
exit 1
;;
esac
+318
View File
@@ -0,0 +1,318 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AdGuard Shield Report</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background-color: #f0f2f5;
margin: 0;
padding: 0;
color: #1a1a2e;
}
.container {
max-width: 700px;
margin: 30px auto;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
color: #ffffff;
padding: 30px 35px;
text-align: center;
}
.header h1 {
margin: 0 0 6px 0;
font-size: 26px;
font-weight: 700;
letter-spacing: 0.5px;
}
.header .subtitle {
font-size: 14px;
color: #a8b2d1;
margin: 0;
}
.header .period {
display: inline-block;
margin-top: 14px;
padding: 6px 18px;
background: rgba(255,255,255,0.12);
border-radius: 20px;
font-size: 13px;
color: #ccd6f6;
}
.content {
padding: 30px 35px;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 28px;
}
.stat-card {
background: #f8f9fc;
border-radius: 10px;
padding: 18px 20px;
border-left: 4px solid #0f3460;
}
.stat-card.danger {
border-left-color: #e74c3c;
}
.stat-card.warning {
border-left-color: #f39c12;
}
.stat-card.success {
border-left-color: #27ae60;
}
.stat-card.info {
border-left-color: #3498db;
}
.stat-card .stat-value {
font-size: 28px;
font-weight: 700;
color: #1a1a2e;
line-height: 1.2;
}
.stat-card .stat-label {
font-size: 12px;
color: #6c757d;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 4px;
}
h2 {
font-size: 18px;
color: #1a1a2e;
margin: 28px 0 14px 0;
padding-bottom: 8px;
border-bottom: 2px solid #f0f2f5;
}
h2:first-child {
margin-top: 0;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
font-size: 14px;
}
th {
background: #f8f9fc;
color: #1a1a2e;
font-weight: 600;
text-align: left;
padding: 10px 14px;
border-bottom: 2px solid #e8ecf1;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.3px;
}
td {
padding: 10px 14px;
border-bottom: 1px solid #f0f2f5;
color: #495057;
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background: #fafbfd;
}
.rank {
display: inline-block;
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
background: #e8ecf1;
border-radius: 50%;
font-size: 12px;
font-weight: 600;
color: #495057;
}
.rank.top3 {
background: #0f3460;
color: #ffffff;
}
.ip-cell {
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
font-size: 13px;
color: #1a1a2e;
}
.bar-container {
display: flex;
align-items: center;
gap: 8px;
}
.bar {
height: 8px;
background: linear-gradient(90deg, #0f3460, #3498db);
border-radius: 4px;
min-width: 4px;
}
.bar-value {
font-size: 13px;
font-weight: 600;
color: #1a1a2e;
white-space: nowrap;
}
.protocol-badge {
display: inline-block;
padding: 3px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
background: #e8ecf1;
color: #495057;
margin: 2px;
}
.protocol-badge.dns { background: #dff0d8; color: #3c763d; }
.protocol-badge.doh { background: #d9edf7; color: #31708f; }
.protocol-badge.dot { background: #fcf8e3; color: #8a6d3b; }
.protocol-badge.doq { background: #f2dede; color: #a94442; }
.reason-badge {
display: inline-block;
padding: 3px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
.reason-badge.rate-limit { background: #fcf8e3; color: #8a6d3b; }
.reason-badge.subdomain-flood { background: #f2dede; color: #a94442; }
.reason-badge.external { background: #d9edf7; color: #31708f; }
.no-data {
text-align: center;
padding: 30px;
color: #adb5bd;
font-style: italic;
}
.footer {
background: #f8f9fc;
padding: 24px 35px;
text-align: center;
font-size: 12px;
color: #6c757d;
border-top: 1px solid #e8ecf1;
}
.footer a {
color: #0f3460;
text-decoration: none;
font-weight: 600;
}
.footer a:hover {
text-decoration: underline;
}
.footer .links {
margin-top: 10px;
}
.footer .separator {
margin: 0 8px;
color: #ced4da;
}
.version-tag {
display: inline-block;
margin-top: 8px;
font-size: 11px;
color: #adb5bd;
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="header">
<h1>🛡️ AdGuard Shield</h1>
<p class="subtitle">Sicherheits-Report</p>
<div class="period">{{REPORT_PERIOD}}</div>
</div>
<!-- Statistik-Übersicht -->
<div class="content">
<h2>📊 Übersicht</h2>
<div class="stats-grid">
<div class="stat-card danger">
<div class="stat-value">{{TOTAL_BANS}}</div>
<div class="stat-label">Sperren gesamt</div>
</div>
<div class="stat-card success">
<div class="stat-value">{{TOTAL_UNBANS}}</div>
<div class="stat-label">Entsperrungen</div>
</div>
<div class="stat-card warning">
<div class="stat-value">{{UNIQUE_IPS}}</div>
<div class="stat-label">Eindeutige IPs</div>
</div>
<div class="stat-card info">
<div class="stat-value">{{PERMANENT_BANS}}</div>
<div class="stat-label">Permanente Sperren</div>
</div>
<div class="stat-card">
<div class="stat-value">{{ACTIVE_BANS}}</div>
<div class="stat-label">Aktuell aktive Sperren</div>
</div>
<div class="stat-card info">
<div class="stat-value">{{ABUSEIPDB_REPORTS}}</div>
<div class="stat-label">AbuseIPDB Reports</div>
</div>
</div>
<!-- Angriffsarten -->
<h2>⚔️ Angriffsarten</h2>
<div class="stats-grid">
<div class="stat-card warning">
<div class="stat-value">{{RATELIMIT_BANS}}</div>
<div class="stat-label">Rate-Limit Sperren</div>
</div>
<div class="stat-card danger">
<div class="stat-value">{{SUBDOMAIN_FLOOD_BANS}}</div>
<div class="stat-label">Subdomain-Flood Sperren</div>
</div>
<div class="stat-card">
<div class="stat-value">{{EXTERNAL_BLOCKLIST_BANS}}</div>
<div class="stat-label">Externe Blocklist</div>
</div>
<div class="stat-card success">
<div class="stat-value">{{BUSIEST_DAY}}</div>
<div class="stat-label">Aktivster Tag</div>
</div>
</div>
<!-- Top 10 IPs -->
<h2>🏴‍☠️ Top 10 Auffälligste IPs</h2>
{{TOP10_IPS_TABLE}}
<!-- Top 10 Domains -->
<h2>🌐 Top 10 Meistbetroffene Domains</h2>
{{TOP10_DOMAINS_TABLE}}
<!-- Protokoll-Verteilung -->
<h2>📡 Protokoll-Verteilung</h2>
{{PROTOCOL_TABLE}}
<!-- Letzte Sperren -->
<h2>🕐 Letzte 10 Sperren</h2>
{{RECENT_BANS_TABLE}}
</div>
<!-- Footer -->
<div class="footer">
Dieser Report wurde automatisch von <strong>AdGuard Shield</strong> generiert.<br>
Generiert am: {{REPORT_DATE}}
<div class="links">
<a href="https://www.patrick-asmus.de">Patrick-Asmus.DE</a>
<span class="separator">|</span>
<a href="https://www.cleveradmin.de">CleverAdmin.DE</a>
<span class="separator">|</span>
<a href="https://git.techniverse.net/scriptos/adguard-shield.git">AdGuard Shield auf Gitea</a>
</div>
<div class="version-tag">AdGuard Shield v{{VERSION}} · {{HOSTNAME}}</div>
</div>
</div>
</body>
</html>
+60
View File
@@ -0,0 +1,60 @@
═══════════════════════════════════════════════════════════════
🛡️ AdGuard Shield Sicherheits-Report
═══════════════════════════════════════════════════════════════
Zeitraum: {{REPORT_PERIOD}}
Erstellt: {{REPORT_DATE}}
Host: {{HOSTNAME}}
───────────────────────────────────────────────────────────────
📊 ÜBERSICHT
───────────────────────────────────────────────────────────────
Sperren gesamt: {{TOTAL_BANS}}
Entsperrungen: {{TOTAL_UNBANS}}
Eindeutige IPs: {{UNIQUE_IPS}}
Permanente Sperren: {{PERMANENT_BANS}}
Aktuell aktive Sperren: {{ACTIVE_BANS}}
AbuseIPDB Reports: {{ABUSEIPDB_REPORTS}}
───────────────────────────────────────────────────────────────
⚔️ ANGRIFFSARTEN
───────────────────────────────────────────────────────────────
Rate-Limit Sperren: {{RATELIMIT_BANS}}
Subdomain-Flood Sperren: {{SUBDOMAIN_FLOOD_BANS}}
Externe Blocklist: {{EXTERNAL_BLOCKLIST_BANS}}
Aktivster Tag: {{BUSIEST_DAY}}
───────────────────────────────────────────────────────────────
🏴‍☠️ TOP 10 AUFFÄLLIGSTE IPs
───────────────────────────────────────────────────────────────
{{TOP10_IPS_TEXT}}
───────────────────────────────────────────────────────────────
🌐 TOP 10 MEISTBETROFFENE DOMAINS
───────────────────────────────────────────────────────────────
{{TOP10_DOMAINS_TEXT}}
───────────────────────────────────────────────────────────────
📡 PROTOKOLL-VERTEILUNG
───────────────────────────────────────────────────────────────
{{PROTOCOL_TEXT}}
───────────────────────────────────────────────────────────────
🕐 LETZTE 10 SPERREN
───────────────────────────────────────────────────────────────
{{RECENT_BANS_TEXT}}
═══════════════════════════════════════════════════════════════
Dieser Report wurde automatisch von AdGuard Shield generiert.
AdGuard Shield v{{VERSION}}
Web: https://www.patrick-asmus.de
Blog: https://www.cleveradmin.de
Repo: https://git.techniverse.net/scriptos/adguard-shield.git
═══════════════════════════════════════════════════════════════