diff --git a/app/Livewire/Ui/Security/Fail2BanCard.php b/app/Livewire/Ui/Security/Fail2BanCard.php index 2f06d70..87d9f06 100644 --- a/app/Livewire/Ui/Security/Fail2BanCard.php +++ b/app/Livewire/Ui/Security/Fail2BanCard.php @@ -32,58 +32,11 @@ class Fail2BanCard extends Component public function openDetails(string $jail): void { // KORREKTER DISPATCH für wire-elements/modal - $this->dispatch('openModal', 'ui.security.modal.fail2-ban-jail-modal', ['jail' => $jail]); + $this->dispatch('openModal', component: 'ui.security.modal.fail2-ban-jail-modal', arguments: ['jail' => $jail]); } /* ------------------- intern ------------------- */ -// protected function load(bool $force = false): void -// { -// $bin = trim((string)@shell_exec('command -v fail2ban-client 2>/dev/null')) ?: ''; -// if ($bin === '') { -// $this->available = false; -// $this->permDenied = false; -// $this->activeBans = 0; -// $this->jails = []; -// return; -// } -// -// [$ok, $raw] = $this->f2b('ping'); -// if (!$ok && stripos($raw, 'permission denied') !== false) { -// $this->available = true; -// $this->permDenied = true; -// $this->activeBans = 0; -// $this->jails = []; -// return; -// } -// -// [, $status] = $this->f2b('status'); -// $jailsLn = $this->firstMatch('/Jail list:\s*(.+)$/mi', $status); -// $jails = $jailsLn ? array_filter(array_map('trim', preg_split('/\s*,\s*/', $jailsLn))) : []; -// -// $rows = []; -// $sum = 0; -// foreach ($jails as $j) { -// [, $s] = $this->f2b('status '.escapeshellarg($j)); -// $bantime = $this->getBantime($j); -// $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: ''; -// $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : []; -// -// $ipDetails = $this->buildIpDetails($j, $ips, $bantime); -// -// [, $s] = $this->f2b('status ' . escapeshellarg($j)); -// $banned = (int)($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0); -// $bantime = $this->getBantime($j); -// $rows[] = ['name' => $j, 'banned' => $banned, 'bantime' => $bantime]; -// $sum += $banned; -// } -// -// $this->available = true; -// $this->permDenied = false; -// $this->activeBans = $sum; -// $this->jails = $rows; -// } - protected function load(bool $force = false): void { $bin = trim((string)@shell_exec('command -v fail2ban-client 2>/dev/null')) ?: ''; @@ -139,15 +92,6 @@ class Fail2BanCard extends Component $this->jails = $rows; } -// private function f2b(string $args): array -// { -// $sudo = '/usr/bin/sudo'; -// $f2b = '/usr/bin/fail2ban-client'; -// $out = (string)@shell_exec("timeout 2 $sudo -n $f2b $args 2>&1"); -// $ok = str_contains($out, 'Status') || str_contains($out, 'Jail list') || str_contains($out, 'pong'); -// return [$ok, $out]; -// } - private function f2b(string $args): array { $sudo = '/usr/bin/sudo'; @@ -171,14 +115,6 @@ class Fail2BanCard extends Component return 600; // konservativer Fallback } -// private function getBantime(string $jail): int -// { -// [, $out] = $this->f2b('get ' . escapeshellarg($jail) . ' bantime'); -// $val = trim($out); -// if (preg_match('/-?\d+/', $val, $m)) return (int)$m[0]; -// return 600; -// } - /** Letzten Ban-Zeitpunkt (Unix-Timestamp) aus /var/log/fail2ban.log ermitteln. */ private function lastBanTimestamp(string $jail, string $ip): ?int { @@ -237,18 +173,12 @@ class Fail2BanCard extends Component } return $out; } - + private function firstMatch(string $pattern, string $haystack): ?string { return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null; } - - -// private function firstMatch(string $pattern, string $haystack): ?string -// { -// return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null; -// } } diff --git a/app/Livewire/Ui/Security/Modal/Fail2BanJailModal.php b/app/Livewire/Ui/Security/Modal/Fail2BanJailModal.php index dcb256c..1d55acd 100644 --- a/app/Livewire/Ui/Security/Modal/Fail2BanJailModal.php +++ b/app/Livewire/Ui/Security/Modal/Fail2BanJailModal.php @@ -144,42 +144,34 @@ class Fail2BanJailModal extends ModalComponent } /** letzte "Ban "-Zeile parsen -> Unix-Timestamp */ + /** letzte "Ban "-Zeile -> Unix-Timestamp aus /var/log/fail2ban.log */ private function lastBanTimestamp(string $jail, string $ip): ?int { - $jailQ = escapeshellarg($jail); - $ipQ = escapeshellarg($ip); + $file = '/var/log/fail2ban.log'; + if (!is_readable($file)) return null; - // 1) Durchsuche fail2ban.log + rotierte + .gz - $cmdFiles = "sh -c 'ls -1 /var/log/fail2ban.log* 2>/dev/null | wc -l'"; - $haveFiles = ((int) @shell_exec($cmdFiles)) > 0; + // nur das Dateiende lesen (performant, auch bei großen Logs) + $tailBytes = 400000; // 400 KB + $size = @filesize($file) ?: 0; + $seek = max(0, $size - $tailBytes); - $line = ''; - if ($haveFiles) { - // zgrep -h: ohne Dateinamen; -E für Regex; tail -n1: letzte Ban-Zeile - $pattern = escapeshellarg(sprintf('\\[%s\\] Ban %s', $jail, $ip)); - $cmd = "zgrep -h -E \"\\[${jail}\\]\\s+Ban\\s+${ip}\" /var/log/fail2ban.log* 2>/dev/null | tail -n 1"; - $line = (string) @shell_exec($cmd); - } + $fh = @fopen($file, 'rb'); + if (!$fh) return null; + if ($seek > 0) fseek($fh, $seek); + $data = stream_get_contents($fh) ?: ''; + fclose($fh); - // 2) Fallback: journald (wenn kein Dateilog) - if (trim($line) === '') { - // seit 14 Tagen scannen, Format wie im file-log - $cmdJ = "journalctl -u fail2ban --since '14 days ago' 2>/dev/null | grep -F \"[{$jail}] Ban {$ip}\" | tail -n 1"; - $line = (string) @shell_exec($cmdJ); - } + // Beispiel: 2025-10-30 22:34:20,797 ... [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'; - $line = trim($line); - if ($line === '') return null; - - // Datum am Anfang: 2025-10-29 18:07:11,436 ... - if (preg_match('/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})/', $line, $m)) { - $ts = strtotime($m[1].' '.$m[2]); - return $ts ?: null; - } - // journald kann anderes Präfix haben; versuche generischen ISO-Zeitstempel irgendwo in der Zeile - if (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; + if (preg_match_all($pattern, $data, $m) && !empty($m[1])) { + $date = end($m[1]); + $time = end($m[2]); + $dt = \DateTime::createFromFormat('Y-m-d H:i:s', "$date $time", + new \DateTimeZone(date_default_timezone_get())); + return $dt ? $dt->getTimestamp() : null; } return null; }