235 lines
9.1 KiB
PHP
235 lines
9.1 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
|
|
class MigrateDovecot24 extends Command
|
|
{
|
|
protected $signature = 'clubird:migrate-dovecot24 {--dry-run : Nur anzeigen was geändert würde}';
|
|
protected $description = 'Migriert Dovecot-Config von 2.3 auf 2.4 Syntax';
|
|
|
|
public function handle(): int
|
|
{
|
|
$dry = $this->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) 10-mail.conf: mail_location → mail_driver + mail_path
|
|
$this->fixMailConf($dry);
|
|
|
|
// 4) 10-master.conf: einzeilige Blöcke aufsplitten
|
|
$this->fixMasterConf($dry);
|
|
|
|
// 5) 10-ssl.conf: ssl_cert/ssl_key → ssl_server_cert_file/ssl_server_key_file
|
|
$this->fixSslConf($dry);
|
|
|
|
// 6) 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 fixMailConf(bool $dry): void
|
|
{
|
|
$file = '/etc/dovecot/conf.d/10-mail.conf';
|
|
$content = @file_get_contents($file);
|
|
if ($content === false) { $this->warn("Nicht gefunden: {$file}"); return; }
|
|
|
|
$new = preg_replace_callback(
|
|
'/^mail_location\s*=\s*(\w+):(.+)$/m',
|
|
function ($m) {
|
|
return "mail_driver = {$m[1]}\nmail_path = {$m[2]}";
|
|
},
|
|
$content
|
|
);
|
|
|
|
if ($new === $content) { $this->line(" ok (unverändert): {$file}"); return; }
|
|
|
|
if ($dry) { $this->line(" [dry-run] würde mail_location aufteilen in {$file}"); return; }
|
|
file_put_contents($file, $new);
|
|
$this->info(" aktualisiert: {$file}");
|
|
}
|
|
|
|
private function fixMasterConf(bool $dry): void
|
|
{
|
|
$file = '/etc/dovecot/conf.d/10-master.conf';
|
|
$content = @file_get_contents($file);
|
|
if ($content === false) { $this->warn("Nicht gefunden: {$file}"); return; }
|
|
|
|
// Einzeilige Blöcke "name { key = val }" → mehrzeilig
|
|
$new = preg_replace_callback(
|
|
'/^(\s*)(\S+)\s*\{\s*([^}]+)\s*\}(\s*)$/m',
|
|
function ($m) {
|
|
$indent = $m[1];
|
|
$name = $m[2];
|
|
$inner = trim($m[3]);
|
|
$pairs = preg_split('/\s+(?=\w+=)/', $inner);
|
|
$body = implode("\n{$indent} ", array_map('trim', $pairs));
|
|
return "{$indent}{$name} {\n{$indent} {$body}\n{$indent}}";
|
|
},
|
|
$content
|
|
);
|
|
|
|
if ($new === $content) { $this->line(" ok (unverändert): {$file}"); return; }
|
|
|
|
if ($dry) { $this->line(" [dry-run] würde einzeilige Blöcke aufsplitten in {$file}"); return; }
|
|
file_put_contents($file, $new);
|
|
$this->info(" aktualisiert: {$file}");
|
|
}
|
|
|
|
private function fixSslConf(bool $dry): void
|
|
{
|
|
$file = '/etc/dovecot/conf.d/10-ssl.conf';
|
|
$content = @file_get_contents($file);
|
|
if ($content === false) { $this->warn("Nicht gefunden: {$file}"); return; }
|
|
|
|
// ssl_cert = </path> → ssl_server_cert_file = /path
|
|
$new = preg_replace('/^ssl_cert\s*=\s*<(.+)$/m', 'ssl_server_cert_file = $1', $content);
|
|
$new = preg_replace('/^ssl_key\s*=\s*<(.+)$/m', 'ssl_server_key_file = $1', $new);
|
|
// Direkte Pfade ohne < (falls schon teilweise migriert)
|
|
$new = preg_replace('/^ssl_cert\s*=\s*(?!<)(.+)$/m', 'ssl_server_cert_file = $1', $new);
|
|
$new = preg_replace('/^ssl_key\s*=\s*(?!<)(.+)$/m', 'ssl_server_key_file = $1', $new);
|
|
|
|
// dovecot_storage_version in dovecot.conf (wenn noch nicht vorhanden)
|
|
$mainConf = '/etc/dovecot/dovecot.conf';
|
|
$mainContent = (string) @file_get_contents($mainConf);
|
|
if (!str_contains($mainContent, 'dovecot_storage_version')) {
|
|
if (!$dry) {
|
|
file_put_contents($mainConf, $mainContent . "\ndovecot_storage_version = 2.4.1\n");
|
|
$this->info(" dovecot_storage_version ergänzt in {$mainConf}");
|
|
} else {
|
|
$this->line(" [dry-run] würde dovecot_storage_version ergänzen in {$mainConf}");
|
|
}
|
|
}
|
|
|
|
if ($new === $content) { $this->line(" ok (unverändert): {$file}"); return; }
|
|
|
|
if ($dry) { $this->line(" [dry-run] würde ssl_cert/ssl_key umbenennen 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";
|
|
}
|
|
}
|