}> */ public array $jails = []; // je Jail: Name, Anzahl, IPs (gekürzt) /** @var array */ public array $topIps = []; // Top IPs aus Log/Journal (Ban-Events) public function mount(): void { $this->load(); } public function render() { return view('livewire.ui.security.fail2-ban-card'); } public function refresh(): void { $this->load(true); } protected function load(bool $force = false): void { // 0) vorhanden? $bin = trim((string) @shell_exec('command -v fail2ban-client 2>/dev/null')) ?: ''; if ($bin === '') { $this->available = false; $this->activeBans = 0; $this->jails = []; $this->topIps = []; return; } // 1) Jails ermitteln $status = (string) (@shell_exec("timeout 2 $bin status 2>/dev/null") ?? ''); $jailsLn = $this->firstMatch('/Jail list:\s*(.+)$/mi', $status); $jails = $jailsLn ? array_filter(array_map('trim', preg_split('/\s*,\s*/', $jailsLn))) : []; $total = 0; $rows = []; foreach ($jails as $j) { $s = (string) (@shell_exec("timeout 2 $bin status ".escapeshellarg($j)." 2>/dev/null") ?? ''); $banned = (int) ($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0); // „Banned IP list:“ kann fehlen → leeres Array $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: ''; $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : []; $rows[] = [ 'name' => $j, 'banned' => $banned, // nur die ersten 8 zur UI-Anzeige 'ips' => array_slice($ips, 0, 8), ]; $total += $banned; } $this->available = true; $this->activeBans = $total; $this->jails = $rows; // 2) Top-IPs aus den letzten Ban-Events $this->topIps = $this->collectTopIps(); } /** Extrahiert erste Regex-Gruppe oder null */ private function firstMatch(string $pattern, string $haystack): ?string { return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null; } /** Zählt „Ban “ aus fail2ban.log (Fallback: journalctl) */ private function collectTopIps(): array { $lines = ''; if (is_readable('/var/log/fail2ban.log')) { // letzte 500 Zeilen reichen völlig $lines = (string) (@shell_exec('tail -n 500 /var/log/fail2ban.log 2>/dev/null') ?? ''); } if ($lines === '') { // Fallback: Journal (falls rsyslog die Datei nicht schreibt) $lines = (string) (@shell_exec('timeout 2 journalctl -u fail2ban -n 500 --no-pager 2>/dev/null') ?? ''); } if ($lines === '') { return []; } // Nur Ban-Events, IP extrahieren $ips = []; foreach (preg_split('/\R+/', $lines) as $ln) { if (stripos($ln, 'Ban ') === false) continue; if (preg_match('/\b(\d{1,3}(?:\.\d{1,3}){3})\b/', $ln, $m)) { $ip = $m[1]; $ips[$ip] = ($ips[$ip] ?? 0) + 1; } } if (!$ips) return []; arsort($ips); $top = array_slice($ips, 0, 5, true); $out = []; foreach ($top as $ip => $cnt) { $out[] = ['ip' => $ip, 'count' => (int)$cnt]; } return $out; } }