From 94aec78d4cde87ffe21b7f9a13586b7998ce3f45 Mon Sep 17 00:00:00 2001 From: boban Date: Fri, 31 Oct 2025 16:52:41 +0100 Subject: [PATCH] =?UTF-8?q?Fix:=20Mailbox=20Stats=20=C3=BCber=20Dovecot=20?= =?UTF-8?q?mit=20config/mailpool.php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Livewire/Ui/Security/Fail2banSettings.php | 282 ++++++++++++++++-- 1 file changed, 249 insertions(+), 33 deletions(-) diff --git a/app/Livewire/Ui/Security/Fail2banSettings.php b/app/Livewire/Ui/Security/Fail2banSettings.php index 16a36dd..38f9b91 100644 --- a/app/Livewire/Ui/Security/Fail2banSettings.php +++ b/app/Livewire/Ui/Security/Fail2banSettings.php @@ -1,5 +1,6 @@ settings = Fail2banSetting::first() ?? Fail2banSetting::create([ - 'bantime' => 3600, 'max_bantime' => 43200, 'bantime_increment' => true, - 'bantime_factor' => 1.5, 'max_retry' => 3, 'findtime' => 600, - 'cidr_v4' => 32, 'cidr_v6' => 128, 'external_mode' => false, + 'bantime' => 3600, + 'max_bantime' => 43200, + 'bantime_increment' => true, + 'bantime_factor' => 1.5, + 'max_retry' => 3, + 'findtime' => 600, + 'cidr_v4' => 32, + 'cidr_v6' => 128, + 'external_mode' => false, ]); - // Properties füllen (KEINE Mixed-Objekte in Inputs binden) + // Properties befüllen $this->fill([ - 'bantime' => (int)$this->settings->bantime, - 'max_bantime' => (int)$this->settings->max_bantime, + 'bantime' => (int)$this->settings->bantime, + 'max_bantime' => (int)$this->settings->max_bantime, 'bantime_increment' => (bool)$this->settings->bantime_increment, - 'bantime_factor' => (float)$this->settings->bantime_factor, - 'max_retry' => (int)$this->settings->max_retry, - 'findtime' => (int)$this->settings->findtime, - 'cidr_v4' => (int)$this->settings->cidr_v4, - 'cidr_v6' => (int)$this->settings->cidr_v6, - 'external_mode' => (bool)$this->settings->external_mode, + 'bantime_factor' => (float)$this->settings->bantime_factor, + 'max_retry' => (int)$this->settings->max_retry, + 'findtime' => (int)$this->settings->findtime, + 'cidr_v4' => (int)$this->settings->cidr_v4, + 'cidr_v6' => (int)$this->settings->cidr_v6, + 'external_mode' => (bool)$this->settings->external_mode, ]); - $this->whitelist = Fail2banIpList::where('type','whitelist')->pluck('ip')->toArray(); - $this->blacklist = Fail2banIpList::where('type','blacklist')->pluck('ip')->toArray(); + $this->refreshLists(); } public function save(): void { $this->validate([ - 'bantime' => 'required|integer|min:60', - 'max_bantime' => 'required|integer|min:60', + 'bantime' => 'required|integer|min:60', + 'max_bantime' => 'required|integer|min:60', 'bantime_factor' => 'required|numeric|min:1', - 'max_retry' => 'required|integer|min:1', - 'findtime' => 'required|integer|min:60', - 'cidr_v4' => 'required|integer|min:8|max:32', - 'cidr_v6' => 'required|integer|min:8|max:128', + 'max_retry' => 'required|integer|min:1', + 'findtime' => 'required|integer|min:60', + 'cidr_v4' => 'required|integer|min:8|max:32', + 'cidr_v6' => 'required|integer|min:8|max:128', ]); + // Einstellungen speichern $this->settings->update([ - 'bantime' => $this->bantime, - 'max_bantime' => $this->max_bantime, + 'bantime' => $this->bantime, + 'max_bantime' => $this->max_bantime, 'bantime_increment' => $this->bantime_increment, - 'bantime_factor' => $this->bantime_factor, - 'max_retry' => $this->max_retry, - 'findtime' => $this->findtime, - 'cidr_v4' => $this->cidr_v4, - 'cidr_v6' => $this->cidr_v6, - 'external_mode' => $this->external_mode, + 'bantime_factor' => $this->bantime_factor, + 'max_retry' => $this->max_retry, + 'findtime' => $this->findtime, + 'cidr_v4' => $this->cidr_v4, + 'cidr_v6' => $this->cidr_v6, + 'external_mode' => $this->external_mode, ]); + // Config-Dateien schreiben $this->writeDefaultsConfig(); $this->writeWhitelistConfig(); - @shell_exec('sudo fail2ban-client reload'); + // Fail2Ban reload + $this->runCommand('sudo -n /usr/bin/fail2ban-client reload'); + $this->dispatch('notify', message: 'Gespeichert & Fail2Ban neu geladen.'); } protected function writeDefaultsConfig(): void { $s = $this->settings; + $content = <<bantime} @@ -101,15 +112,65 @@ bantime.increment = {$this->boolToStr($s->bantime_increment)} bantime.factor = {$s->bantime_factor} bantime.maxtime = {$s->max_bantime} CONF; - file_put_contents('/etc/fail2ban/jail.d/00-mailwolt-defaults.local', $content); + + $this->writeRootFileViaTee('/etc/fail2ban/jail.d/00-mailwolt-defaults.local', $content); } protected function writeWhitelistConfig(): void { - $ips = Fail2banIpList::where('type','whitelist')->pluck('ip')->toArray(); + $ips = Fail2banIpList::where('type', 'whitelist')->pluck('ip')->toArray(); $ignore = implode(' ', array_unique(array_filter($ips))); + $content = "[DEFAULT]\nignoreip = {$ignore}\n"; - file_put_contents('/etc/fail2ban/jail.d/mailwolt-whitelist.local', $content); + + $this->writeRootFileViaTee('/etc/fail2ban/jail.d/mailwolt-whitelist.local', $content); + } + + /** + * Schreibt Root-Dateien sicher via `sudo tee` + */ + private function writeRootFileViaTee(string $target, string $content): void + { + if (!preg_match('#^/etc/fail2ban/jail\.d/[A-Za-z0-9._-]+\.local$#', $target)) { + throw new \RuntimeException("Illegal path: $target"); + } + + $cmd = sprintf('sudo -n /usr/bin/tee %s >/dev/null', escapeshellarg($target)); + + $descriptorspec = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + + $proc = proc_open($cmd, $descriptorspec, $pipes, null, null); + if (!is_resource($proc)) { + throw new \RuntimeException('Failed to start tee'); + } + + fwrite($pipes[0], $content); + fclose($pipes[0]); + stream_get_contents($pipes[1]); + stream_get_contents($pipes[2]); + $exitCode = proc_close($proc); + + if ($exitCode !== 0) { + throw new \RuntimeException("tee failed writing to {$target}"); + } + } + + /** + * Führt Systembefehle aus und wirft Exception bei Fehlern + */ + private function runCommand(string $cmd): void + { + $output = []; + $return = 0; + exec($cmd . ' 2>&1', $output, $return); + + if ($return !== 0) { + throw new \RuntimeException("Command failed ($return): {$cmd}\n" . implode("\n", $output)); + } } private function boolToStr(bool $v): string @@ -122,3 +183,158 @@ CONF; return view('livewire.ui.security.fail2ban-settings'); } } + +// +//namespace App\Livewire\Ui\Security; +// +//use Livewire\Attributes\On; +//use Livewire\Component; +//use App\Models\Fail2banSetting; +//use App\Models\Fail2banIpList; +// +//class Fail2banSettings extends Component +//{ +// // Formfelder +// public int $bantime; +// public int $max_bantime; +// public bool $bantime_increment; +// public float $bantime_factor; +// public int $max_retry; +// public int $findtime; +// public int $cidr_v4; +// public int $cidr_v6; +// public bool $external_mode; +// +// public array $whitelist = []; +// public array $blacklist = []; +// +// public Fail2banSetting $settings; +// +// #[On('f2b:refresh')] +// public function refreshLists(): void +// { +// $this->whitelist = Fail2banIpList::where('type', 'whitelist')->pluck('ip')->toArray(); +// $this->blacklist = Fail2banIpList::where('type', 'blacklist')->pluck('ip')->toArray(); +// } +// +// public function mount(): void +// { +// // Setting holen oder mit Defaults anlegen +// $this->settings = Fail2banSetting::first() ?? Fail2banSetting::create([ +// 'bantime' => 3600, 'max_bantime' => 43200, 'bantime_increment' => true, +// 'bantime_factor' => 1.5, 'max_retry' => 3, 'findtime' => 600, +// 'cidr_v4' => 32, 'cidr_v6' => 128, 'external_mode' => false, +// ]); +// +// // Properties füllen (KEINE Mixed-Objekte in Inputs binden) +// $this->fill([ +// 'bantime' => (int)$this->settings->bantime, +// 'max_bantime' => (int)$this->settings->max_bantime, +// 'bantime_increment' => (bool)$this->settings->bantime_increment, +// 'bantime_factor' => (float)$this->settings->bantime_factor, +// 'max_retry' => (int)$this->settings->max_retry, +// 'findtime' => (int)$this->settings->findtime, +// 'cidr_v4' => (int)$this->settings->cidr_v4, +// 'cidr_v6' => (int)$this->settings->cidr_v6, +// 'external_mode' => (bool)$this->settings->external_mode, +// ]); +// +// $this->whitelist = Fail2banIpList::where('type','whitelist')->pluck('ip')->toArray(); +// $this->blacklist = Fail2banIpList::where('type','blacklist')->pluck('ip')->toArray(); +// } +// +// public function save(): void +// { +// $this->validate([ +// 'bantime' => 'required|integer|min:60', +// 'max_bantime' => 'required|integer|min:60', +// 'bantime_factor' => 'required|numeric|min:1', +// 'max_retry' => 'required|integer|min:1', +// 'findtime' => 'required|integer|min:60', +// 'cidr_v4' => 'required|integer|min:8|max:32', +// 'cidr_v6' => 'required|integer|min:8|max:128', +// ]); +// +// $this->settings->update([ +// 'bantime' => $this->bantime, +// 'max_bantime' => $this->max_bantime, +// 'bantime_increment' => $this->bantime_increment, +// 'bantime_factor' => $this->bantime_factor, +// 'max_retry' => $this->max_retry, +// 'findtime' => $this->findtime, +// 'cidr_v4' => $this->cidr_v4, +// 'cidr_v6' => $this->cidr_v6, +// 'external_mode' => $this->external_mode, +// ]); +// +// $this->writeDefaultsConfig(); +// $this->writeWhitelistConfig(); +// +// @shell_exec('sudo fail2ban-client reload'); +// $this->dispatch('notify', message: 'Gespeichert & Fail2Ban neu geladen.'); +// } +// +// protected function writeDefaultsConfig(): void +// { +// $s = $this->settings; +// $content = <<bantime} +//findtime = {$s->findtime} +//maxretry = {$s->max_retry} +//bantime.increment = {$this->boolToStr($s->bantime_increment)} +//bantime.factor = {$s->bantime_factor} +//bantime.maxtime = {$s->max_bantime} +//CONF; +// file_put_contents('/etc/fail2ban/jail.d/00-mailwolt-defaults.local', $content); +// } +// +// protected function writeWhitelistConfig(): void +// { +// $ips = Fail2banIpList::where('type','whitelist')->pluck('ip')->toArray(); +// $ignore = implode(' ', array_unique(array_filter($ips))); +// $content = "[DEFAULT]\nignoreip = {$ignore}\n"; +// file_put_contents('/etc/fail2ban/jail.d/mailwolt-whitelist.local', $content); +// } +// +// private function writeRootFileViaTee(string $target, string $content): void +// { +// // Nur erlaubte Pfade (Hardening) +// if (!preg_match('#^/etc/fail2ban/jail\.d/[A-Za-z0-9._-]+\.local$#', $target)) { +// throw new \RuntimeException("Illegal path: $target"); +// } +// +// $cmd = sprintf('sudo -n /usr/bin/tee %s >/dev/null', escapeshellarg($target)); +// +// $descriptorspec = [ +// 0 => ['pipe', 'r'], // stdin -> tee +// 1 => ['pipe', 'w'], // stdout +// 2 => ['pipe', 'w'], // stderr +// ]; +// +// $proc = proc_open($cmd, $descriptorspec, $pipes, null, null); +// if (!is_resource($proc)) { +// throw new \RuntimeException('Failed to start tee'); +// } +// +// fwrite($pipes[0], $content); +// fclose($pipes[0]); +// $stdout = stream_get_contents($pipes[1]); fclose($pipes[1]); +// $stderr = stream_get_contents($pipes[2]); fclose($pipes[2]); +// +// $code = proc_close($proc); +// if ($code !== 0) { +// throw new \RuntimeException("tee failed (code $code): $stderr $stdout"); +// } +// } +// +// private function boolToStr(bool $v): string +// { +// return $v ? 'true' : 'false'; +// } +// +// public function render() +// { +// return view('livewire.ui.security.fail2ban-settings'); +// } +//}