From 77f22518c84894102f90f775be02c128ff716bc2 Mon Sep 17 00:00:00 2001 From: boban Date: Sat, 1 Nov 2025 22:05:14 +0100 Subject: [PATCH] =?UTF-8?q?Fix:=20Mailbox=20Stats=20=C3=BCber=20Dovecot=20?= =?UTF-8?q?mit=20config/mailpool.php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Livewire/Ui/Security/Fail2banBanlist.php | 77 +++++++++++ .../Ui/Security/Modal/Fail2BanJailModal.php | 125 +++++++++++++----- .../ui/security/fail2ban-banlist.blade.php | 39 ++++++ .../ui/security/fail2ban-settings.blade.php | 23 +++- 4 files changed, 226 insertions(+), 38 deletions(-) create mode 100644 app/Livewire/Ui/Security/Fail2banBanlist.php create mode 100644 resources/views/livewire/ui/security/fail2ban-banlist.blade.php diff --git a/app/Livewire/Ui/Security/Fail2banBanlist.php b/app/Livewire/Ui/Security/Fail2banBanlist.php new file mode 100644 index 0000000..27ecffd --- /dev/null +++ b/app/Livewire/Ui/Security/Fail2banBanlist.php @@ -0,0 +1,77 @@ +loadBannedIps(); + } + + public function mount(): void + { + $this->loadBannedIps(); + } + + private function loadBannedIps(): void + { + $output = @shell_exec(sprintf('sudo -n /usr/bin/fail2ban-client status %s 2>&1', escapeshellarg($this->jail))); + + if (!$output) { + $this->banned = []; + return; + } + + // Beispielausgabe: + // Status for the jail: mailwolt-blacklist + // |- Filter + // | |- Currently failed: 0 + // | `- Total failed: 0 + // `- Actions + // |- Currently banned: 2 + // | `- IP list: 203.0.113.45 198.51.100.22 + // `- Total banned: 2 + + if (preg_match('/IP list:\s*(.+)$/mi', $output, $m)) { + $ips = preg_split('/\s+/', trim($m[1])); + $this->banned = array_values(array_filter($ips, fn($ip) => filter_var($ip, FILTER_VALIDATE_IP))); + } else { + $this->banned = []; + } + } + + public function unban(string $ip): void + { + if (!filter_var($ip, FILTER_VALIDATE_IP)) { + return; + } + + $ipEsc = escapeshellarg($ip); + $cmd = sprintf('sudo -n /usr/bin/fail2ban-client set %s unbanip %s 2>&1', escapeshellarg($this->jail), $ipEsc); + @shell_exec($cmd); + + $this->dispatch('toast', + type: 'info', + badge: 'Fail2Ban', + title: 'IP entbannt', + text: "Die IP {$ip} wurde erfolgreich entbannt.", + duration: 6000, + ); + + $this->refreshList(); + } + + public function render() + { + return view('livewire.ui.security.fail2ban-banlist'); + } +} diff --git a/app/Livewire/Ui/Security/Modal/Fail2BanJailModal.php b/app/Livewire/Ui/Security/Modal/Fail2BanJailModal.php index b42b0bf..52142a3 100644 --- a/app/Livewire/Ui/Security/Modal/Fail2BanJailModal.php +++ b/app/Livewire/Ui/Security/Modal/Fail2BanJailModal.php @@ -74,50 +74,111 @@ class Fail2BanJailModal extends ModalComponent // // $this->rows = $rows; // logger()->debug('rows='.json_encode($rows, JSON_UNESCAPED_SLASHES)); +// } + +// protected function load(bool $force = false): void +// { +// $jail = $this->jail; +// +// // 1) Primär: DB +// $rows = $this->activeBansFromDb($jail); +// +// // 2) Fallback: status parsen, wenn DB leer (z. B. sehr alte F2B-Setups) +// if (empty($rows)) { +// [, $s] = $this->f2b('status '.escapeshellarg($jail)); +// $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: ''; +// $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : []; +// $defaultBantime = $this->getBantime($jail); +// +// foreach ($ips as $ip) { +// $banAt = $this->lastBanTimestamp($jail, $ip); +// $remaining = $banAt !== null ? max(0, $defaultBantime - (time() - $banAt)) : -2; +// $until = ($remaining > 0 && $banAt) ? $banAt + $defaultBantime : null; +// $rows[] = [ +// 'ip' => $ip, +// 'bantime' => $defaultBantime, +// 'banned_at' => $banAt, +// 'remaining' => $remaining, +// 'until' => $until, +// ]; +// } +// } +// +// // Präsentationswerte bauen (einheitlich) +// $presented = []; +// foreach ($rows as $r) { +// [$timeText, $metaText, $boxClass] = $this->present($r['remaining'], $r['banned_at'], $r['until']); +// $presented[] = $r + [ +// 'time_text' => $timeText, +// 'meta_text' => $metaText, +// 'box_class' => $boxClass, +// ]; +// } +// +// $this->rows = $presented; +// logger()->debug('rows='.json_encode($presented, JSON_UNESCAPED_SLASHES)); // } protected function load(bool $force = false): void { $jail = $this->jail; - // 1) Primär: DB - $rows = $this->activeBansFromDb($jail); + // 1) IP-Liste IMMER aus "status " holen (Quelle der Wahrheit) + [, $s] = $this->f2b('status '.escapeshellarg($jail)); + $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: ''; + $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : []; - // 2) Fallback: status parsen, wenn DB leer (z. B. sehr alte F2B-Setups) - if (empty($rows)) { - [, $s] = $this->f2b('status '.escapeshellarg($jail)); - $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: ''; - $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : []; - $defaultBantime = $this->getBantime($jail); + $defaultBantime = $this->getBantime($jail); - foreach ($ips as $ip) { - $banAt = $this->lastBanTimestamp($jail, $ip); - $remaining = $banAt !== null ? max(0, $defaultBantime - (time() - $banAt)) : -2; - $until = ($remaining > 0 && $banAt) ? $banAt + $defaultBantime : null; - $rows[] = [ - 'ip' => $ip, - 'bantime' => $defaultBantime, - 'banned_at' => $banAt, - 'remaining' => $remaining, - 'until' => $until, - ]; + $rows = []; + foreach ($ips as $ip) { + $banAt = null; $until = null; $remaining = null; + + // 2) DB-Info pro IP (optional, je nach Schema belegt) + if ($info = $this->banInfoFromDb($jail, $ip)) { + $banAt = $info['banned_at']; + if ((int)$info['expire'] === -1) { + $remaining = -1; + } elseif ((int)$info['expire'] > 0) { + $until = (int)$info['expire']; + $remaining = max(0, $until - time()); + } } + + // 3) Fallback: Log + Default-Bantime + if ($remaining === null) { + $banAt = $banAt ?? $this->lastBanTimestamp($jail, $ip); + if ($banAt !== null) { + $remaining = max(0, $defaultBantime - (time() - $banAt)); + $until = $remaining > 0 ? $banAt + $defaultBantime : null; + } else { + $remaining = -2; // ~unbekannt/approx + } + } + + // 4) Safety: falls 0s, aber F2B hält sie noch → als „~unbekannt“ kennzeichnen + if ($remaining === 0 && $this->isStillBanned($jail, $ip)) { + $remaining = -2; + } + + [$timeText, $metaText, $boxClass] = $this->present($remaining, $banAt, $until); + + $rows[] = [ + 'ip' => $ip, + 'bantime' => $defaultBantime, + 'banned_at' => $banAt, + 'remaining' => $remaining, + 'until' => $until, + 'time_text' => $timeText, + 'meta_text' => $metaText, + 'box_class' => $boxClass, + ]; } - // Präsentationswerte bauen (einheitlich) - $presented = []; - foreach ($rows as $r) { - [$timeText, $metaText, $boxClass] = $this->present($r['remaining'], $r['banned_at'], $r['until']); - $presented[] = $r + [ - 'time_text' => $timeText, - 'meta_text' => $metaText, - 'box_class' => $boxClass, - ]; - } - - $this->rows = $presented; - logger()->debug('rows='.json_encode($presented, JSON_UNESCAPED_SLASHES)); + $this->rows = $rows; + logger()->debug('rows='.json_encode($rows, JSON_UNESCAPED_SLASHES)); } + /** ROBUST: findet Binaries automatisch */ private function bin(string $name): string { diff --git a/resources/views/livewire/ui/security/fail2ban-banlist.blade.php b/resources/views/livewire/ui/security/fail2ban-banlist.blade.php new file mode 100644 index 0000000..65cf654 --- /dev/null +++ b/resources/views/livewire/ui/security/fail2ban-banlist.blade.php @@ -0,0 +1,39 @@ +
+
+

Aktuell gebannte IPs

+ +
+ + @if (empty($banned)) +
+ Keine aktiven Banns vorhanden. +
+ @else +
+ + + + + + + + + @foreach ($banned as $ip) + + + + + @endforeach + +
IP-AdresseAktion
{{ $ip }} + +
+
+ @endif +
diff --git a/resources/views/livewire/ui/security/fail2ban-settings.blade.php b/resources/views/livewire/ui/security/fail2ban-settings.blade.php index 0c74f66..c95736e 100644 --- a/resources/views/livewire/ui/security/fail2ban-settings.blade.php +++ b/resources/views/livewire/ui/security/fail2ban-settings.blade.php @@ -45,7 +45,8 @@
+
+ +
+ +
@@ -75,6 +81,7 @@
  • Alle Änderungen hier werden nach Klick auf „Speichern & Reload“ sofort aktiv.
  • +
    {{-- RIGHT 1/3 --}} @@ -86,7 +93,8 @@ @forelse($whitelist as $ip) -
    +
    {{ $ip }}
    -
    +
    Blacklist
    @forelse($blacklist as $ip) -
    +
    {{ $ip }}