diff --git a/app/Console/Commands/MigrateDovecot24.php b/app/Console/Commands/MigrateDovecot24.php new file mode 100644 index 0000000..2f1780c --- /dev/null +++ b/app/Console/Commands/MigrateDovecot24.php @@ -0,0 +1,145 @@ +option('dry-run'); + + // 1) dovecot.conf: dovecot_config_version als erste Zeile + $this->fixDovecotConf($dry); + + // 2) 10-auth.conf: disable_plaintext_auth → auth_allow_cleartext + $this->fixAuthConf($dry); + + // 3) auth-sql.conf.ext: neue passdb/userdb-Syntax + $this->fixAuthSqlConf($dry); + + if (!$dry) { + $this->info('Fertig. Starte dovecot neu…'); + @exec('systemctl restart dovecot 2>&1', $out, $rc); + if ($rc === 0) { + $this->info('Dovecot erfolgreich gestartet.'); + } else { + $this->error('Dovecot-Neustart fehlgeschlagen: ' . implode("\n", $out)); + $this->line('Prüfen mit: doveconf -n 2>&1'); + } + } + + return self::SUCCESS; + } + + private function fixDovecotConf(bool $dry): void + { + $file = '/etc/dovecot/dovecot.conf'; + $content = @file_get_contents($file); + if ($content === false) { $this->warn("Nicht gefunden: {$file}"); return; } + + if (str_starts_with(trim($content), 'dovecot_config_version')) { + // Falsche Version ersetzen + $new = preg_replace('/^dovecot_config_version\s*=\s*\S+/m', 'dovecot_config_version = 2.4.1', $content); + } else { + $new = "dovecot_config_version = 2.4.1\n" . $content; + } + + if ($new === $content) { $this->line(" ok (unverändert): {$file}"); return; } + + if ($dry) { $this->line(" [dry-run] würde dovecot_config_version ergänzen in {$file}"); return; } + file_put_contents($file, $new); + $this->info(" aktualisiert: {$file}"); + } + + private function fixAuthConf(bool $dry): void + { + $file = '/etc/dovecot/conf.d/10-auth.conf'; + $content = @file_get_contents($file); + if ($content === false) { $this->warn("Nicht gefunden: {$file}"); return; } + + $new = str_replace('disable_plaintext_auth = yes', 'auth_allow_cleartext = no', $content); + $new = str_replace('disable_plaintext_auth = no', 'auth_allow_cleartext = yes', $new); + + if ($new === $content) { $this->line(" ok (unverändert): {$file}"); return; } + + if ($dry) { $this->line(" [dry-run] würde disable_plaintext_auth ersetzen in {$file}"); return; } + file_put_contents($file, $new); + $this->info(" aktualisiert: {$file}"); + } + + private function fixAuthSqlConf(bool $dry): void + { + $file = '/etc/dovecot/conf.d/auth-sql.conf.ext'; + $sqlConf = '/etc/dovecot/dovecot-sql.conf.ext'; + + // Alte Werte aus dovecot-sql.conf.ext lesen + $sqlRaw = (string) @file_get_contents($sqlConf); + $host = $this->parseSqlValue($sqlRaw, 'host') ?: '127.0.0.1'; + $dbname = $this->parseSqlValue($sqlRaw, 'dbname') ?: 'mailwolt'; + $user = $this->parseSqlValue($sqlRaw, 'user') ?: 'mailwolt'; + $pass = $this->parseSqlValue($sqlRaw, 'password') ?: ''; + $scheme = $this->parseSqlValue($sqlRaw, 'default_pass_scheme') ?: 'BLF-CRYPT'; + + // password_query extrahieren (auch aus Kommentaren) + $query = $this->parsePasswordQuery($sqlRaw); + + $new = "mysql {$host} {\n" + . " dbname = {$dbname}\n" + . " user = {$user}\n" + . " password = {$pass}\n" + . "}\n\n" + . "passdb sql {\n" + . " default_password_scheme = {$scheme}\n" + . " query = {$query}\n" + . "}\n\n" + . "userdb static {\n" + . " fields {\n" + . " uid = vmail\n" + . " gid = vmail\n" + . " home = /var/mail/vhosts/%{user|domain}/%{user|username}\n" + . " }\n" + . "}\n"; + + if ($dry) { + $this->line(" [dry-run] würde {$file} neu schreiben:"); + $this->line($new); + return; + } + + file_put_contents($file, $new); + $this->info(" neu geschrieben: {$file}"); + } + + private function parseSqlValue(string $content, string $key): ?string + { + // Aus connect-Zeile: connect = host=x dbname=y user=z password=w + if (preg_match('/^connect\s*=\s*(.+)/m', $content, $m)) { + $connect = $m[1]; + if (preg_match('/' . preg_quote($key, '/') . '\s*=\s*(\S+)/i', $connect, $m2)) { + return trim($m2[1]); + } + } + // Direkte Zeile: key = value + if (preg_match('/^' . preg_quote($key, '/') . '\s*=\s*(.+)/m', $content, $m)) { + return trim($m[1]); + } + return null; + } + + private function parsePasswordQuery(string $content): string + { + // Auskommentierte oder aktive password_query / query Zeile + if (preg_match('/^#?\s*password_query\s*=\s*(.+)/m', $content, $m)) { + $q = trim($m[1]); + // %u → %{user} + $q = str_replace("'%u'", "'%{user}'", $q); + return rtrim($q, ';'); + } + return "SELECT email AS user, password_hash AS password FROM mail_users WHERE email = '%{user}' AND is_active = 1 LIMIT 1"; + } +}