'sshd','banned'=>2,'ips'=>['1.2.3.4',...]], ...] public array $topIps = []; // [['ip'=>'x.x.x.x','count'=>N], ...] public function mount(): void { $this->load(); } public function render() { return view('livewire.ui.security.fail2-ban-card'); } // Wird vom Button "Neu prüfen" genutzt public function refresh(): void { $this->load(true); } /* --------------------- intern --------------------- */ protected function load(bool $force = false): void { // existiert fail2ban-client? $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 = []; $this->topIps = []; return; } // ping → prüft zugleich Rechte (bei Permission-Fehler kommt Klartext) [$ok, $raw] = $this->f2b('ping'); // ok == "pong" erkannt if (!$ok && stripos($raw, 'permission denied') !== false) { $this->available = true; $this->permDenied = true; $this->activeBans = 0; $this->jails = []; $this->topIps = $this->collectTopIps(); return; } // Jails auflisten [, $status] = $this->f2b('status'); $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] = $this->f2b('status '.escapeshellarg($j)); $banned = (int) ($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0); $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,'ips'=>array_slice($ips, 0, 8)]; $total += $banned; } $this->available = true; $this->permDenied = false; $this->activeBans = $total; $this->jails = $rows; $this->topIps = $this->collectTopIps(); } /** führt fail2ban-client via sudo aus; gibt [ok, output] zurück */ private function f2b(string $args): array { $sudo = '/usr/bin/sudo'; $f2b = '/usr/bin/fail2ban-client'; $cmd = "timeout 2 $sudo -n $f2b $args 2>&1"; $out = (string) @shell_exec($cmd); $ok = stripos($out, 'Status') !== false || stripos($out, 'Jail list') !== false || stripos($out, 'pong') !== false; return [$ok, $out]; } private function firstMatch(string $pattern, string $haystack): ?string { return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null; } /** Zählt die häufigsten IPs aus den letzten Fail2Ban-Logs (ban/unban Events) */ private function collectTopIps(): array { // Zieh nur fail2ban.log, nicht auth/mail – präziser & schneller $cmd = 'tail -n 2000 /var/log/fail2ban.log 2>/dev/null' . ' | grep -Eo "([0-9]{1,3}\.){3}[0-9]{1,3}"' . ' | sort | uniq -c | sort -nr | head -5'; $log = (string) @shell_exec($cmd); $rows = []; if ($log !== '') { foreach (preg_split('/\R+/', trim($log)) as $l) { if (preg_match('/^\s*(\d+)\s+(\d+\.\d+\.\d+\.\d+)/', $l, $m)) { $rows[] = ['ip'=>$m[2], 'count'=>(int)$m[1]]; } } } return $rows; } }