[ 'postfix_protocols' => '!SSLv2, !SSLv3, !TLSv1, !TLSv1.1, !TLSv1.2', 'postfix_ciphers' => 'high', 'dovecot_min_proto' => 'TLSv1.3', 'dovecot_ciphers' => 'ECDH+AESGCM:ECDH+CHACHA20:!aNULL:!MD5', ], 'intermediate' => [ 'postfix_protocols' => '!SSLv2, !SSLv3, !TLSv1, !TLSv1.1', 'postfix_ciphers' => 'medium', 'dovecot_min_proto' => 'TLSv1.2', 'dovecot_ciphers' => 'ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:DH+AES256:!aNULL:!MD5:!DSS', ], 'old' => [ 'postfix_protocols' => '!SSLv2, !SSLv3', 'postfix_ciphers' => 'low', 'dovecot_min_proto' => 'TLSv1', 'dovecot_ciphers' => 'HIGH:MEDIUM:!aNULL:!MD5', ], ]; public function mount(): void { $this->preset = Setting::get('tls.preset', 'intermediate'); $this->postfix_protocols = Setting::get('tls.postfix_protocols', self::PRESETS['intermediate']['postfix_protocols']); $this->postfix_ciphers = Setting::get('tls.postfix_ciphers', self::PRESETS['intermediate']['postfix_ciphers']); $this->dovecot_min_proto = Setting::get('tls.dovecot_min_proto', self::PRESETS['intermediate']['dovecot_min_proto']); $this->dovecot_ciphers = Setting::get('tls.dovecot_ciphers', self::PRESETS['intermediate']['dovecot_ciphers']); } public function applyPreset(string $preset): void { if (!isset(self::PRESETS[$preset])) return; $p = self::PRESETS[$preset]; $this->preset = $preset; $this->postfix_protocols = $p['postfix_protocols']; $this->postfix_ciphers = $p['postfix_ciphers']; $this->dovecot_min_proto = $p['dovecot_min_proto']; $this->dovecot_ciphers = $p['dovecot_ciphers']; } public function save(): void { $this->validate([ 'postfix_protocols' => 'required|string|max:200', 'postfix_ciphers' => 'required|string|max:500', 'dovecot_min_proto' => 'required|string|max:50', 'dovecot_ciphers' => 'required|string|max:500', ]); Setting::setMany([ 'tls.preset' => $this->preset, 'tls.postfix_protocols' => $this->postfix_protocols, 'tls.postfix_ciphers' => $this->postfix_ciphers, 'tls.dovecot_min_proto' => $this->dovecot_min_proto, 'tls.dovecot_ciphers' => $this->dovecot_ciphers, ]); try { $this->writePostfixConfig(); $this->writeDovecotConfig(); Process::run(['sudo', '-n', '/usr/bin/systemctl', 'reload', 'postfix']); Process::run(['sudo', '-n', '/usr/bin/systemctl', 'reload', 'dovecot']); $this->dispatch('toast', type: 'done', badge: 'TLS', title: 'TLS-Konfiguration übernommen', text: 'Postfix und Dovecot wurden neu geladen.', duration: 5000); } catch (\Throwable $e) { $this->dispatch('toast', type: 'error', badge: 'TLS', title: 'Fehler', text: $e->getMessage(), duration: 0); } } private function writePostfixConfig(): void { $target = '/etc/postfix/mailwolt-tls.cf'; $content = "smtpd_tls_protocols = {$this->postfix_protocols}\n" . "smtp_tls_protocols = {$this->postfix_protocols}\n" . "smtpd_tls_ciphers = {$this->postfix_ciphers}\n" . "smtp_tls_ciphers = {$this->postfix_ciphers}\n"; $this->tee($target, $content); } private function writeDovecotConfig(): void { $target = '/etc/dovecot/conf.d/99-mailwolt-tls.conf'; $content = "ssl_min_protocol = {$this->dovecot_min_proto}\n" . "ssl_cipher_list = {$this->dovecot_ciphers}\n"; $this->tee($target, $content); } private function tee(string $target, string $content): void { $proc = proc_open( sprintf('sudo -n /usr/bin/tee %s >/dev/null', escapeshellarg($target)), [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes ); if (!is_resource($proc)) throw new \RuntimeException('tee start fehlgeschlagen'); fwrite($pipes[0], $content); fclose($pipes[0]); stream_get_contents($pipes[1]); stream_get_contents($pipes[2]); if (proc_close($proc) !== 0) throw new \RuntimeException("tee failed: {$target}"); } public function render() { return view('livewire.ui.security.tls-ciphers-form', ['presets' => array_keys(self::PRESETS)]); } }