type = $type; $this->mode = $mode; $this->ip = $ip ?? ''; $this->prefill = $ip; } public function render() { return view('livewire.ui.security.modal.fail2ban-ip-modal'); } /* ---------------- actions ---------------- */ public function save(): void { $this->assertAddMode(); $ip = trim($this->ip); if (!$this->isValidIpOrCidr($ip)) { throw ValidationException::withMessages(['ip' => 'Ungültige IP oder CIDR.']); } // DB schreiben Fail2banIpList::firstOrCreate(['ip' => $ip, 'type' => $this->type]); if ($this->type === 'whitelist') { $this->writeWhitelistConfig(); $this->reloadFail2ban(); } else { // Blacklist = sofort bannen im dedizierten Jail $this->banIp($ip); } $this->dispatch('f2b:refresh'); $this->dispatch('notify', message: ucfirst($this->type).' aktualisiert.'); $this->closeModal(); $this->dispatch('f2b:refresh'); // falls du eine Liste neu laden willst } public function remove(): void { $this->assertRemoveMode(); $ip = trim($this->prefill ?? $this->ip); if ($ip === '') return; Fail2banIpList::where('type', $this->type)->where('ip', $ip)->delete(); if ($this->type === 'whitelist') { $this->writeWhitelistConfig(); $this->reloadFail2ban(); } else { // aus Blacklist-Jail entbannen, falls noch aktiv $this->unbanIp($ip); } $this->dispatch('f2b:refresh'); $this->dispatch('notify', message: ucfirst($this->type).' Eintrag entfernt.'); $this->closeModal(); $this->dispatch('f2b:refresh'); } /* ---------------- helper ---------------- */ private function assertAddMode(): void { if ($this->mode !== 'add') throw new \LogicException('Wrong mode'); } private function assertRemoveMode(): void { if ($this->mode !== 'remove') throw new \LogicException('Wrong mode'); } private function isValidIpOrCidr(string $s): bool { // IP if (filter_var($s, FILTER_VALIDATE_IP)) return true; // CIDR if (strpos($s, '/') !== false) { [$ip, $mask] = explode('/', $s, 2); if (!filter_var($ip, FILTER_VALIDATE_IP)) return false; if (strpos($ip, ':') !== false) { // IPv6 return ctype_digit($mask) && (int)$mask >= 8 && (int)$mask <= 128; } // IPv4 return ctype_digit($mask) && (int)$mask >= 8 && (int)$mask <= 32; } return false; } private function writeWhitelistConfig(): void { $ips = Fail2banIpList::where('type', 'whitelist')->pluck('ip')->toArray(); $ignore = implode(' ', array_unique(array_filter($ips))); $content = "[DEFAULT]\nignoreip = {$ignore}\n"; $file = '/etc/fail2ban/jail.d/mailwolt-whitelist.local'; $tmp = $file.'.tmp'; @file_put_contents($tmp, $content, LOCK_EX); @chmod($tmp, 0644); @rename($tmp, $file); } private function reloadFail2ban(): void { @shell_exec('sudo fail2ban-client reload 2>&1'); } private function banIp(string $ip): void { $ipEsc = escapeshellarg($ip); @shell_exec("sudo fail2ban-client set mailwolt-blacklist banip {$ipEsc} 2>&1"); // optional: in DB zusätzlich behalten, damit UI konsistent ist (bereits oben getan) } private function unbanIp(string $ip): void { $ipEsc = escapeshellarg($ip); @shell_exec("sudo fail2ban-client set mailwolt-blacklist unbanip {$ipEsc} 2>&1"); } }