Fix: Mailbox Stats über Dovecot mit config/mailpool.php

main v1.0.95
boban 2025-10-29 03:54:39 +01:00
parent ab13bab984
commit 0cb7212d4b
2 changed files with 120 additions and 113 deletions

View File

@ -312,98 +312,22 @@ class DomainDnsModal extends ModalComponent
return $line;
}
// private function stateFor(string $type, string $expected, string $actual, bool $optional): string
// {
// if ($actual === '') {
// return $optional ? 'neutral' : 'missing';
// }
//
// $type = strtoupper($type);
// $exp = $this->normExpected($type, $expected);
// $act = $this->normActual($type, $actual);
//
// // Syntax plausibilisieren
// $syntaxOk = $this->validateSyntax($type, $act);
// if (!$syntaxOk) return 'syntax';
//
// // TXT-Policies: nur „Startet mit v=…“ prüfen → OK,
// // selbst wenn Inhalt nicht 1:1 dem Vorschlag entspricht.
// if ($type === 'TXT') {
// $upperExp = strtoupper($exp);
// $upperAct = strtoupper($act);
// if (str_starts_with($upperExp, 'V=SPF1')) return str_starts_with($upperAct, 'V=SPF1') ? 'ok' : 'syntax';
// if (str_starts_with($upperExp, 'V=DMARC1')) return str_starts_with($upperAct, 'V=DMARC1') ? 'ok' : 'syntax';
// if (str_starts_with($upperExp, 'V=DKIM1')) return str_starts_with($upperAct, 'V=DKIM1') ? 'ok' : 'syntax';
// return ($act !== '') ? 'ok' : ($optional ? 'neutral' : 'missing');
// }
//
// // MX: „prio host“ wir prüfen Host grob
// if ($type === 'MX') {
// $parts = preg_split('/\s+/', $act);
// $host = strtolower($parts[1] ?? $act);
// $expHost = strtolower(preg_replace('/^\d+\s+/', '', $exp));
// return ($host === $expHost) ? 'ok' : 'syntax';
// }
//
// // SRV: „prio weight port host“ Port + Host grob
// if ($type === 'SRV') {
// $ap = preg_split('/\s+/', $act);
// $ep = preg_split('/\s+/', $exp);
// if (count($ap) >= 4 && count($ep) >= 4) {
// $aport = (int)$ap[2];
// $eport = (int)$ep[2];
// $ahost = strtolower(rtrim(end($ap), '.'));
// $ehost = strtolower(rtrim(end($ep), '.'));
// return ($aport === $eport && $ahost === $ehost) ? 'ok' : 'syntax';
// }
// return 'syntax';
// }
//
// // CNAME/A/AAAA/PTR/TLSA: Gleichheit nach Normalisierung
// return ($act === $exp) ? 'ok' : 'syntax';
// }
// private function normExpected(string $type, string $v): string
// {
// $v = trim($v);
// $t = strtoupper($type);
// if (in_array($t, ['MX','CNAME','SRV'])) $v = rtrim($v, '.');
// if ($t === 'PTR') $v = strtolower(rtrim($v, '.'));
// if ($t === 'TLSA') $v = preg_replace('/\s+/', ' ', $v);
// return $v;
// }
// private function normActual(string $type, string $v): string
// {
// $v = trim($v);
// $t = strtoupper($type);
// if (in_array($t, ['MX','CNAME','SRV'])) $v = rtrim($v, '.');
// if ($t === 'PTR') $v = strtolower(rtrim($v, '.'));
// if ($t === 'TLSA') {
// $v = preg_replace('/\s+/', '', $v); // Hash-Zeilen zusammenfügen
// $v = preg_replace('/^([0-3][\s]+[01][\s]+[123])/', '$1 ', $v); // spacing nach Header erzwingen
// }
// return $v;
// }
private function stateFor(string $type, string $expected, string $actual, bool $optional): string
{
private function stateFor(string $type, string $expected, string $actual, bool $optional): string {
if ($actual === '') return $optional ? 'neutral' : 'missing';
$type = strtoupper($type);
$exp = $this->normExpected($type, $expected);
$act = $this->normActual($type, $actual);
// Syntaxcheck nach Normalisierung
// Syntaxcheck auf dem **actual**
if (!$this->validateSyntax($type, $act)) return 'syntax';
// TXT: nur „v=…“-Präfix grob prüfen
if ($type === 'TXT') {
$E = strtoupper($exp);
$A = strtoupper($act);
if (str_starts_with($E, 'V=SPF1')) return str_starts_with($A, 'V=SPF1') ? 'ok' : 'syntax';
if (str_starts_with($E, 'V=DMARC1')) return str_starts_with($A, 'V=DMARC1') ? 'ok' : 'syntax';
if (str_starts_with($E, 'V=DKIM1')) return str_starts_with($A, 'V=DKIM1') ? 'ok' : 'syntax';
$ue = strtoupper($exp);
$ua = strtoupper($act);
if (str_starts_with($ue,'V=SPF1')) return str_starts_with($ua,'V=SPF1') ? 'ok' : 'syntax';
if (str_starts_with($ue,'V=DMARC1')) return str_starts_with($ua,'V=DMARC1') ? 'ok' : 'syntax';
if (str_starts_with($ue,'V=DKIM1')) return str_starts_with($ua,'V=DKIM1') ? 'ok' : 'syntax';
return 'ok';
}
@ -423,10 +347,51 @@ class DomainDnsModal extends ModalComponent
return 'syntax';
}
// TLSA / A / AAAA / CNAME / PTR: exakter Vergleich nach Norm.
// A/AAAA/CNAME/PTR/TLSA
return ($act === $exp) ? 'ok' : 'syntax';
}
// private function stateFor(string $type, string $expected, string $actual, bool $optional): string
// {
// if ($actual === '') return $optional ? 'neutral' : 'missing';
//
// $type = strtoupper($type);
// $exp = $this->normExpected($type, $expected);
// $act = $this->normActual($type, $actual);
//
// // Syntaxcheck nach Normalisierung
// if (!$this->validateSyntax($type, $act)) return 'syntax';
//
// // TXT: nur „v=…“-Präfix grob prüfen
// if ($type === 'TXT') {
// $E = strtoupper($exp);
// $A = strtoupper($act);
// if (str_starts_with($E, 'V=SPF1')) return str_starts_with($A, 'V=SPF1') ? 'ok' : 'syntax';
// if (str_starts_with($E, 'V=DMARC1')) return str_starts_with($A, 'V=DMARC1') ? 'ok' : 'syntax';
// if (str_starts_with($E, 'V=DKIM1')) return str_starts_with($A, 'V=DKIM1') ? 'ok' : 'syntax';
// return 'ok';
// }
//
// if ($type === 'MX') {
// $parts = preg_split('/\s+/', $act);
// $host = strtolower($parts[1] ?? $act);
// $expHost = strtolower(preg_replace('/^\d+\s+/', '', $exp));
// return ($host === $expHost) ? 'ok' : 'syntax';
// }
//
// if ($type === 'SRV') {
// $ap = preg_split('/\s+/', $act);
// $ep = preg_split('/\s+/', $exp);
// if (count($ap) >= 4 && count($ep) >= 4) {
// return ((int)$ap[2] === (int)$ep[2] && strtolower(end($ap)) === strtolower(end($ep))) ? 'ok' : 'syntax';
// }
// return 'syntax';
// }
//
// // TLSA / A / AAAA / CNAME / PTR: exakter Vergleich nach Norm.
// return ($act === $exp) ? 'ok' : 'syntax';
// }
private function canonicalizeTlsa(?string $v): ?string
{
if (!$v) return null;
@ -444,26 +409,67 @@ class DomainDnsModal extends ModalComponent
return sprintf('%s %s %s %s', $u, $s, $m, $hash);
}
private function normExpected(string $type, string $v): string
{
private function normExpected(string $type, string $v): string {
$v = trim($v);
$t = strtoupper($type);
if (in_array($t, ['MX','CNAME','SRV'])) $v = rtrim($v, '.');
if ($t === 'PTR') $v = strtolower(rtrim($v, '.'));
if ($t === 'TLSA') $v = $this->canonicalizeTlsa($v) ?? $v;
if ($t === 'PTR') $v = strtolower(rtrim($v, '.')); // Hostname als Value
if ($t === 'TLSA') $v = strtolower(preg_replace('/\s+/', '', $v)); // hex compact, lc
if ($t === 'TXT') $v = trim($v, "\"'");
return $v;
}
private function normActual(string $type, string $v): string
{
private function normActual(string $type, string $v): string {
$v = trim($v);
$t = strtoupper($type);
// nur 1. Antwortzeile
if ($v !== '') $v = trim(explode("\n", $v)[0]);
if (in_array($t, ['MX','CNAME','SRV'])) $v = rtrim($v, '.');
if ($t === 'PTR') $v = strtolower(rtrim($v, '.'));
if ($t === 'TLSA') $v = $this->canonicalizeTlsa($v) ?? $v;
if ($t === 'PTR') $v = strtolower(rtrim($v, '.')); // Hostname als Value
if ($t === 'TLSA') $v = strtolower(preg_replace('/\s+/', '', $v)); // hex compact, lc
if ($t === 'TXT') $v = trim($v, "\"'");
return $v;
}
private function validateSyntax(string $type, string $val): bool {
$t = strtoupper($type);
if ($val === '') return false;
return match ($t) {
'A' => (bool)filter_var($val, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4),
'AAAA' => (bool)filter_var($val, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6),
'CNAME' => (bool)preg_match('/^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i', $val),
// PTR: Value ist ein Hostname (rdns zeigt auf FQDN)
'PTR' => (bool)preg_match('/^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i', $val),
'MX' => (bool)preg_match('/^\d+\s+[a-z0-9._-]+$/i', $val),
'SRV' => (bool)preg_match('/^\d+\s+\d+\s+\d+\s+[a-z0-9._-]+$/i', $val),
// TLSA: „usage selector matching hex…“ (hex jetzt schon lc/kompakt)
'TLSA' => (bool)preg_match('/^[0-3][01][123][0-9a-f]{32,}$/', str_replace(' ','',$val)),
'TXT' => strlen($val) > 0,
default => true,
};
}
// private function normExpected(string $type, string $v): string
// {
// $v = trim($v);
// $t = strtoupper($type);
// if (in_array($t, ['MX','CNAME','SRV'])) $v = rtrim($v, '.');
// if ($t === 'PTR') $v = strtolower(rtrim($v, '.'));
// if ($t === 'TLSA') $v = $this->canonicalizeTlsa($v) ?? $v;
// return $v;
// }
//
// private function normActual(string $type, string $v): string
// {
// $v = trim($v);
// $t = strtoupper($type);
// if (in_array($t, ['MX','CNAME','SRV'])) $v = rtrim($v, '.');
// if ($t === 'PTR') $v = strtolower(rtrim($v, '.'));
// if ($t === 'TLSA') $v = $this->canonicalizeTlsa($v) ?? $v;
// return $v;
// }
// private function validateSyntax(string $type, string $val): bool
// {
// $t = strtoupper($type);
@ -482,27 +488,27 @@ class DomainDnsModal extends ModalComponent
// };
// }
private function validateSyntax(string $type, string $val): bool
{
$t = strtoupper($type);
if ($val === '') return false;
if ($t === 'TLSA') {
$canon = $this->canonicalizeTlsa($val);
return is_string($canon) && (bool)preg_match('/^[0-3]\s+[01]\s+[123]\s+[0-9a-f]{32,}$/', $canon);
}
return match ($t) {
'A' => (bool)filter_var($val, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4),
'AAAA' => (bool)filter_var($val, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6),
'CNAME' => (bool)preg_match('/^[a-z0-9._-]+$/i', $val),
'PTR' => (bool)preg_match('/\.(in-addr|ip6)\.arpa$/i', $val),
'MX' => (bool)preg_match('/^\d+\s+[a-z0-9._-]+$/i', $val),
'SRV' => (bool)preg_match('/^\d+\s+\d+\s+\d+\s+[a-z0-9._-]+$/i', $val),
'TXT' => strlen($val) > 0,
default => true,
};
}
// private function validateSyntax(string $type, string $val): bool
// {
// $t = strtoupper($type);
// if ($val === '') return false;
//
// if ($t === 'TLSA') {
// $canon = $this->canonicalizeTlsa($val);
// return is_string($canon) && (bool)preg_match('/^[0-3]\s+[01]\s+[123]\s+[0-9a-f]{32,}$/', $canon);
// }
//
// return match ($t) {
// 'A' => (bool)filter_var($val, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4),
// 'AAAA' => (bool)filter_var($val, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6),
// 'CNAME' => (bool)preg_match('/^[a-z0-9._-]+$/i', $val),
// 'PTR' => (bool)preg_match('/\.(in-addr|ip6)\.arpa$/i', $val),
// 'MX' => (bool)preg_match('/^\d+\s+[a-z0-9._-]+$/i', $val),
// 'SRV' => (bool)preg_match('/^\d+\s+\d+\s+\d+\s+[a-z0-9._-]+$/i', $val),
// 'TXT' => strlen($val) > 0,
// default => true,
// };
// }
/** Vergleich je Typ robust normalisieren (nicht genutzt, bleibt aber da) */
private function compareDns(string $type, string $expected, string $actual): bool

View File

@ -120,6 +120,7 @@ class UpdateCard extends Component
$ver = $this->displayCurrent ?? 'aktuelle Version';
$this->progressLine = 'Update abgeschlossen: ' . $ver;
@shell_exec('nohup php /var/www/mailwolt/artisan optimize:clear >/dev/null 2>&1 &');
// Optional: NICHT sofort reloaden Nutzer entscheidet
// $this->dispatch('reload-page', delay: 6000);
} elseif ($this->rc !== null && $this->rc !== 0 && !$this->postActionsDone) {