alle Jails * 'recidive' => nur dieses Jail * 'mailwolt-blacklist' etc. */ public ?string $jail = null; /** * Struktur für Blade (reine Ausgabe, keine Logik im Blade): * [ * [ * 'ip' => '1.2.3.4', * 'jail' => 'recidive', * 'permanent' => false, * 'label' => 'Temporär', // oder 'Permanent' * 'box' => 'border-amber-400/20 bg-white/3', // Kartenstil * 'badge' => 'border-amber-400/30 bg-amber-500/10 text-amber-200', * 'btn' => 'border-rose-400/30 bg-rose-500/10 text-rose-200 hover:border-rose-400/50', * ], * ... * ] * * @var array */ public array $rows = []; #[On('f2b:refresh')] public function refreshList(): void { $this->loadBanned(); } public function mount(?string $jail = null): void { $this->jail = $jail; $this->loadBanned(); } public function render() { return view('livewire.ui.security.fail2ban-banlist'); } /* ================= core ================= */ private function loadBanned(): void { $jails = $this->jailList(); // ggf. nur ein bestimmtes Jail if (is_string($this->jail) && $this->jail !== '' && $this->jail !== '*') { $jails = in_array($this->jail, $jails, true) ? [$this->jail] : []; } $rows = []; foreach ($jails as $j) { $out = $this->f2b("status " . escapeshellarg($j)); if (!preg_match('/IP list:\s*(.+)$/mi', $out, $m)) { continue; } $ips = preg_split('/\s+/', trim($m[1])) ?: []; foreach ($ips as $ip) { if (!filter_var($ip, FILTER_VALIDATE_IP)) { continue; } $permanent = $this->isPermanent($j, $ip); if ($permanent) { $box = 'border-rose-400/30 bg-rose-500/5'; $badge = 'border-rose-400/30 bg-rose-500/10 text-rose-200'; $label = 'Permanent'; $style = 'permanent'; $dot = 'bg-rose-500'; } else { $box = 'border-amber-400/20 bg-white/3'; $badge = 'border-amber-400/30 bg-amber-500/10 text-amber-200'; $label = 'Temporär'; $style = 'temporary'; $dot = 'bg-amber-400'; } $rows[] = [ 'ip' => $ip, 'jail' => $j, 'permanent' => $permanent, 'style' => $style, 'label' => $label, 'box' => $box, 'badge' => $badge, 'dot' => $dot, 'btn' => 'border-rose-400/30 bg-rose-500/10 text-rose-200 hover:border-rose-400/50', ]; } } // Sortierung: permanent oben, dann nach Jail, dann IP usort($rows, function ($a, $b) { if ($a['permanent'] !== $b['permanent']) return $a['permanent'] ? -1 : 1; if ($a['jail'] !== $b['jail']) return strcmp($a['jail'], $b['jail']); return strcmp($a['ip'], $b['ip']); }); $this->rows = $rows; } /** Entbannt eine IP **im angegebenen Jail** (Button gibt Jail mit) */ public function unban(string $ip, string $jail): void { if (!filter_var($ip, FILTER_VALIDATE_IP)) return; $cmd = sprintf( 'sudo -n /usr/bin/fail2ban-client set %s unbanip %s 2>&1', escapeshellarg($jail), escapeshellarg($ip) ); @shell_exec($cmd); $this->loadBanned(); $this->dispatch('toast', type: 'done', badge: 'Fail2Ban', title: 'IP entbannt', text: "IP {$ip} in Jail „{$jail}“ entbannt.", duration: 5000, ); } /* ================= helpers ================= */ /** Prüft via SQLite, ob der **letzte** Ban für (jail, ip) permanent ist (bantime < 0). */ private function isPermanent(string $jail, string $ip): bool { $db = $this->getDbFile(); if ($db === '' || !is_readable($db)) { // Fallback: Blacklist-Jail ist per Design permanent return $jail === 'mailwolt-blacklist'; } $q = <<&1', escapeshellarg($db), escapeshellarg($q) ); $out = trim((string)@shell_exec($cmd)); if ($out === '') return ($jail === 'mailwolt-blacklist'); // Fallback return ((int)$out) < 0; } /** Liste aller Jails */ private function jailList(): array { $out = $this->f2b('status'); if (preg_match('/Jail list:\s*(.+)$/mi', $out, $m)) { $jails = array_map('trim', preg_split('/\s*,\s*/', trim($m[1]))); return array_values(array_filter($jails, fn($v) => $v !== '')); } return []; } /** fail2ban-client über sudo aufrufen */ private function f2b(string $args): string { return (string) @shell_exec('sudo -n /usr/bin/fail2ban-client '.$args.' 2>&1'); } /** Pfad zur Fail2Ban-SQLite-DB holen */ private function getDbFile(): string { $out = $this->f2b('get dbfile'); $lines = array_values(array_filter(array_map('trim', preg_split('/\r?\n/', $out)))); $path = end($lines) ?: ''; $path = preg_replace('/^`?-?\s*/', '', $path); return $path ?: '/var/lib/fail2ban/fail2ban.sqlite3'; } }