From 02e558bf4b4707400b43db2609c4cbe137f11671 Mon Sep 17 00:00:00 2001 From: boban Date: Fri, 31 Oct 2025 00:59:51 +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/Fail2BanCard.php | 75 +++++++++++++---------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/app/Livewire/Ui/Security/Fail2BanCard.php b/app/Livewire/Ui/Security/Fail2BanCard.php index b4f4ccd..2f06d70 100644 --- a/app/Livewire/Ui/Security/Fail2BanCard.php +++ b/app/Livewire/Ui/Security/Fail2BanCard.php @@ -179,13 +179,48 @@ class Fail2BanCard extends Component // return 600; // } - /** baut Detail-Liste inkl. Restzeit */ + /** Letzten Ban-Zeitpunkt (Unix-Timestamp) aus /var/log/fail2ban.log ermitteln. */ + private function lastBanTimestamp(string $jail, string $ip): ?int + { + $file = '/var/log/fail2ban.log'; + if (!is_readable($file)) return null; + + // nur das Ende der Datei lesen (Performance, auch bei Rotation groß genug wählen) + $tailBytes = 400000; // 400 KB + $size = @filesize($file) ?: 0; + $seek = max(0, $size - $tailBytes); + + $fh = @fopen($file, 'rb'); + if (!$fh) return null; + if ($seek > 0) fseek($fh, $seek); + $data = stream_get_contents($fh) ?: ''; + fclose($fh); + + // Beispielzeile: + // 2025-10-30 22:34:20,797 fail2ban.actions [...] NOTICE [sshd] Ban 193.46.255.244 + $j = preg_quote($jail, '/'); + $p = preg_quote($ip, '/'); + $pattern = '/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2}),\d+.*\['.$j.'\]\s+Ban\s+'.$p.'\s*$/m'; + + if (preg_match_all($pattern, $data, $m) && !empty($m[1])) { + $date = end($m[1]); // YYYY-MM-DD + $time = end($m[2]); // HH:MM:SS + $dt = \DateTime::createFromFormat('Y-m-d H:i:s', "$date $time", new \DateTimeZone(date_default_timezone_get())); + return $dt ? $dt->getTimestamp() : null; + } + return null; + } + + /** Baut Details inkl. Restzeit (Sekunden; -1 = permanent). */ private function buildIpDetails(string $jail, array $ips, int $bantime): array { - $now = time(); $out = []; + $now = time(); + $out = []; + foreach ($ips as $ip) { $banAt = $this->lastBanTimestamp($jail, $ip); - $remaining = null; $until = null; + $remaining = null; + $until = null; if ($bantime === -1) { $remaining = -1; // permanent @@ -194,37 +229,15 @@ class Fail2BanCard extends Component $until = $remaining > 0 ? ($banAt + $bantime) : null; } - $out[] = ['ip' => $ip, 'remaining' => $remaining, 'until' => $until]; + $out[] = [ + 'ip' => $ip, + 'remaining' => $remaining, // -1 = permanent, null = Ban-Zeitpunkt nicht gefunden, >=0 = Sekunden + 'until' => $until, // Unix-Timestamp oder null + ]; } return $out; } - - /** letzte "Ban "-Zeile finden (Log + Rotation + journald), liefert Unix-Timestamp oder null */ - private function lastBanTimestamp(string $jail, string $ip): ?int - { - // 1) zgrep in /var/log/fail2ban.log* - $line = (string)@shell_exec( - "zgrep -h -E '\\[{$jail}\\]\\s+Ban\\s+{$ip}' /var/log/fail2ban.log* 2>/dev/null | tail -n 1" - ); - $line = trim($line); - - // 2) Fallback: journald - if ($line === '') { - $line = (string)@shell_exec( - "journalctl -u fail2ban --since '14 days ago' 2>/dev/null | grep -F '[{$jail}] Ban {$ip}' | tail -n 1" - ); - $line = trim($line); - } - if ($line === '') return null; - - // "YYYY-MM-DD HH:MM:SS,mmm ..." oder ISO im Text - if (preg_match('/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})/', $line, $m) - || preg_match('/(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}:\d{2})/', $line, $m)) { - $ts = strtotime($m[1].' '.$m[2]); - return $ts ?: null; - } - return null; - } + private function firstMatch(string $pattern, string $haystack): ?string {