> */ public array $static = []; /** @var array> */ public array $dynamic = []; /** @var array> */ public array $optional = []; /** Farbklassen für den Status */ public array $stateColors = [ 'ok' => 'border-emerald-400/30 bg-emerald-500/10', 'missing' => 'border-rose-400/30 bg-rose-500/10', 'syntax' => 'border-amber-400/30 bg-amber-500/10', 'neutral' => 'border-white/10 bg-white/5', ]; /** erst nach Klick färben */ public bool $checked = false; public static function modalMaxWidth(): string { return '6xl'; } public function mount(int $domainId): void { $this->recordColors = [ 'A' => 'bg-cyan-500/20 text-cyan-300', 'AAAA' => 'bg-blue-500/20 text-blue-300', 'MX' => 'bg-emerald-500/20 text-emerald-300', 'CNAME' => 'bg-indigo-500/20 text-indigo-300', 'PTR' => 'bg-amber-500/20 text-amber-300', 'TXT' => 'bg-violet-500/20 text-violet-300', 'SRV' => 'bg-rose-500/20 text-rose-300', 'TLSA' => 'bg-red-500/20 text-red-300', 'OPTIONAL' => 'bg-gray-500/20 text-gray-300', ]; $d = Domain::findOrFail($domainId); $this->domainId = $domainId; $this->domainName = $d->domain; $tlsa = TlsaRecord::forServer()->where('service', '_25._tcp')->latest('id')->first(); $ips = NetProbe::resolve(); $ipv4 = $ips['ipv4']; $ipv6 = $ips['ipv6']; $this->zone = $this->extractZone($d->domain); $mta_sub = env('MTA_SUB'); $base = env('BASE_DOMAIN'); $mailServerFqdn = "{$mta_sub}.{$base}"; // --- Infrastruktur (global) --- $this->static = [ ['type' => 'A', 'name' => $mailServerFqdn, 'value' => $ipv4], ['type' => 'PTR', 'name' => $this->ptrFromIPv4($ipv4), 'value' => $mailServerFqdn], ]; if ($ipv6) { $this->static[] = ['type' => 'AAAA','name' => $mailServerFqdn, 'value' => $ipv6]; $this->static[] = ['type' => 'PTR', 'name' => $this->ptrFromIPv6($ipv6), 'value' => $mailServerFqdn]; } if ($tlsa?->dns_string) { $this->static[] = [ 'type' => 'TLSA', 'name' => "{$tlsa->service}.{$tlsa->host}", 'value' => "{$tlsa->usage} {$tlsa->selector} {$tlsa->matching} {$tlsa->hash}", ]; } // --- Domain-spezifisch --- $spf = "v=spf1 ip4:{$ipv4}" . ($ipv6 ? " ip6:{$ipv6}" : '') . " mx -all"; $dmarc = "v=DMARC1; p=none; rua=mailto:dmarc@{$this->domainName}; pct=100"; $dkim = DB::table('dkim_keys')->where('domain_id', $d->id)->where('is_active', 1)->orderByDesc('id')->first(); $selector = $dkim ? $dkim->selector : 'mwl1'; $dkimHost = "{$selector}._domainkey.{$this->domainName}"; $dkimTxt = $dkim && !str_starts_with(trim($dkim->public_key_txt), 'v=') ? 'v=DKIM1; k=rsa; p=' . trim($dkim->public_key_txt) : (trim($dkim->public_key_txt ?? '') ?: 'v=DKIM1; k=rsa; p='); $this->dynamic = [ ['type' => 'MX', 'name' => $this->domainName, 'value' => "10 {$mailServerFqdn}."], ['type' => 'CNAME', 'name' => "autoconfig.{$this->domainName}", 'value' => "{$mailServerFqdn}."], ['type' => 'CNAME', 'name' => "autodiscover.{$this->domainName}", 'value' => "{$mailServerFqdn}."], ['type' => 'TXT', 'name' => $this->domainName, 'value' => $spf], ['type' => 'TXT', 'name' => "_dmarc.{$this->domainName}", 'value' => $dmarc], ['type' => 'TXT', 'name' => $dkimHost, 'value' => $dkimTxt], ]; $this->optional = [ // --- Service-Erkennung --- [ 'type' => 'SRV', 'name' => "_autodiscover._tcp.{$this->domainName}", 'value' => "0 1 443 {$mailServerFqdn}.", 'helpLabel' => 'Autodiscover SRV', 'helpUrl' => 'https://learn.microsoft.com/en-us/exchange/architecture/client-access/autodiscover', 'info' => 'Ermöglicht automatische Mailkonfiguration (v. a. für Outlook und Apple Mail).', ], [ 'type' => 'SRV', 'name' => "_imaps._tcp.{$this->domainName}", 'value' => "0 1 993 {$mailServerFqdn}.", 'helpLabel' => 'IMAPS SRV', 'helpUrl' => 'https://datatracker.ietf.org/doc/html/rfc6186', 'info' => 'Definiert den IMAP-Dienst (Port 993, SSL/TLS). Empfohlen für Clients.', ], [ 'type' => 'SRV', 'name' => "_pop3s._tcp.{$this->domainName}", 'value' => "0 1 995 {$mailServerFqdn}.", 'helpLabel' => 'POP3S SRV', 'helpUrl' => 'https://datatracker.ietf.org/doc/html/rfc6186', 'info' => 'Optional für POP3 (Port 995, SSL/TLS). Nur falls POP3 unterstützt wird.', ], [ 'type' => 'SRV', 'name' => "_submission._tcp.{$this->domainName}", 'value' => "0 1 587 {$mailServerFqdn}.", 'helpLabel' => 'Submission SRV', 'helpUrl' => 'https://datatracker.ietf.org/doc/html/rfc6409', 'info' => 'Definiert den SMTP-Submission-Dienst (Port 587, STARTTLS).', ], // --- Sicherheit & Policies --- [ 'type' => 'TXT', 'name' => "_mta-sts.{$this->domainName}", 'value' => "v=STSv1; id=20250101T000000Z;", 'helpLabel' => 'MTA-STS Policy', 'helpUrl' => 'https://datatracker.ietf.org/doc/html/rfc8461', 'info' => 'Sichert SMTP-Verbindungen durch verpflichtendes TLS (Mail Transport Security).', ], [ 'type' => 'CNAME', 'name' => "mta-sts.{$this->domainName}", 'value' => "{$mailServerFqdn}.", 'helpLabel' => 'MTA-STS Host', 'helpUrl' => 'https://datatracker.ietf.org/doc/html/rfc8461', 'info' => 'Zeigt auf den Server, der die MTA-STS-Policy unter /.well-known/mta-sts.txt bereitstellt.', ], [ 'type' => 'TXT', 'name' => "_smtp._tls.{$this->domainName}", 'value' => "v=TLSRPTv1; rua=mailto:tlsrpt@{$this->domainName}", 'helpLabel' => 'TLS-RPT (Reporting)', 'helpUrl' => 'https://datatracker.ietf.org/doc/html/rfc8460', 'info' => 'Ermöglicht Berichte zu fehlerhaften TLS-Verbindungen beim Mailversand (TLS Reporting).', ], // --- Komfort / Web --- [ 'type' => 'CNAME', 'name' => env('WEBMAIL_SUB') . ".{$this->domainName}", 'value' => "{$mailServerFqdn}.", 'helpLabel' => 'Webmail Alias', 'helpUrl' => 'https://en.wikipedia.org/wiki/CNAME_record', 'info' => 'Erlaubt Zugriff auf Webmail unter webmail.domain.tld.', ], ]; // wichtig: initial neutral einfärben (kein Check gelaufen) $this->applyNeutralColors(); } private function applyNeutralColors(): void { foreach (['static', 'dynamic', 'optional'] as $group) { $this->{$group} = array_map(fn($r) => array_merge($r, [ 'state' => 'neutral', 'boxClass' => $this->stateColors['neutral'], 'actual' => '', ]), $this->{$group}); } } /** Klick im Footer löst diesen Check aus */ #[On('domain:check-dns')] public function checkDns(): void { // dynamische (Pflicht) Records prüfen foreach ($this->dynamic as $i => $r) { $actual = $this->dig($r['type'], $r['name']); $state = $this->stateFor($r['type'], $r['value'], $actual, optional:false); $this->dynamic[$i]['actual'] = $actual; $this->dynamic[$i]['state'] = $state; $this->dynamic[$i]['boxClass'] = $this->stateColors[$state] ?? $this->stateColors['neutral']; $this->dynamic[$i]['display_actual'] = $this->normActual($r['type'], $actual); } // statische (Pflicht) Records prüfen foreach ($this->static as $i => $r) { $actual = $this->dig($r['type'], $r['name']); $state = $this->stateFor($r['type'], $r['value'], $actual, optional:false); $this->static[$i]['actual'] = $actual; $this->static[$i]['state'] = $state; $this->static[$i]['boxClass'] = $this->stateColors[$state] ?? $this->stateColors['neutral']; $this->static[$i]['display_actual'] = $this->normActual($r['type'], $actual); } // optionale Records: nie „missing“, nur neutral|syntax|ok foreach ($this->optional as $i => $r) { $actual = $this->dig($r['type'], $r['name']); $state = $this->stateFor($r['type'], $r['value'], $actual, optional:true); $this->optional[$i]['actual'] = $actual; $this->optional[$i]['state'] = $state; $this->optional[$i]['boxClass'] = $this->stateColors[$state] ?? $this->stateColors['neutral']; $this->optional[$i]['display_actual'] = $this->normActual($r['type'], $actual); } $this->checked = true; } /** Prüft eine Record-Liste und gibt die Liste mit 'state' zurück */ private function checkGroup(array $group, bool $optional = false): array { foreach ($group as &$r) { $type = (string)$r['type']; $name = (string)$r['name']; $exp = (string)$r['value']; $act = $this->dig($type, $name); if ($act === '') { $r['state'] = $optional ? 'missing' : 'missing'; continue; } $ok = $this->compareDns($type, $exp, $act); $r['state'] = $ok ? 'ok' : 'mismatch'; } return $group; } /* ---------- DNS & Bewertung ---------- */ // private function dig(string $type, string $name): string // { // $type = strtoupper($type); // $name = rtrim($name, '.'); // intern ohne trailing dot // $out = @shell_exec('dig +timeout=2 +tries=1 +short ' // . escapeshellarg($name) . ' ' . escapeshellarg($type) . ' 2>/dev/null') ?? ''; // $out = trim($out); // // // Mehrzeiliges TXT zu einer Zeile squashen, Quotes weg // if ($type === 'TXT' && $out !== '') { // $lines = array_filter(array_map('trim', explode("\n", $out))); // $joined = implode('', array_map(fn($l)=>trim($l,'"'), $lines)); // return $joined; // } // // // Nur erste Zeile vergleichen // if ($out !== '') { // $out = trim(explode("\n", $out)[0]); // // MX/SRV/CNAME Ziele ohne trailing dot vergleichen // if (in_array($type, ['MX','CNAME','SRV'])) { // $out = rtrim($out, '.'); // } // } // return $out; // } private function dig(string $type, string $name): string { $type = strtoupper($type); $name = rtrim($name, '.'); $out = @shell_exec( 'dig +timeout=2 +tries=1 +short ' . escapeshellarg($name) . ' ' . escapeshellarg($type) . ' 2>/dev/null' ) ?? ''; $out = trim($out); if ($out === '') return ''; // TXT: mehrere Zeilen / Quotes zu einer Zeile squashen if ($type === 'TXT') { $lines = array_filter(array_map('trim', explode("\n", $out))); $joined = implode('', array_map(fn($l) => trim($l, '"'), $lines)); return $joined; } // TLSA: Kanonisieren (Leerzeichen/Zeilenumbrüche im Hash, Großbuchstaben, …) if ($type === 'TLSA') { // nimm die ganze Ausgabe (kann mehrzeilig sein) return $this->canonicalizeTlsa(preg_replace('/\s+/', ' ', $out)) ?? ''; } // Für alles andere: erste Zeile reicht, trailing dots weg wo sinnvoll $line = trim(strtok($out, "\n")); if (in_array($type, ['MX','CNAME','SRV'])) $line = rtrim($line, '.'); 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); // Syntaxcheck auf dem **actual** if (!$this->validateSyntax($type, $act)) return 'syntax'; if ($type === 'TXT') { $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'; } 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'; } // 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; $v = trim($v); // tokenisiere: u s m [hash...] $parts = preg_split('/\s+/', $v); if (count($parts) < 4) return null; $u = $parts[0]; $s = $parts[1]; $m = $parts[2]; // restliche Teile gehören zum Hash – zusammenfügen, Non-hex entfernen, kleinschreiben $hash = strtolower(preg_replace('/[^0-9a-f]/i', '', implode('', array_slice($parts, 3)))); if ($hash === '') return null; return sprintf('%s %s %s %s', $u, $s, $m, $hash); } 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, '.')); // 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 { $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, '.')); // 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); // 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('/^[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), // 'TLSA' => (bool)preg_match('/^[0-3]\s+[01]\s+[123]\s+[0-9a-f\s]{32,}$/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 { $type = strtoupper($type); $norm = function (string $s): string { $s = trim($s); $s = trim($s, "\"'"); $s = preg_replace('/\s+/', ' ', $s); $s = rtrim($s, '.'); return $s; }; if ($type === 'TXT') { $parts = array_map( fn($line) => $norm($line), preg_split('/\R+/', $actual) ); $actualFlat = implode('', array_map(fn($p) => str_replace(['"',' '], '', $p), $parts)); $expectedFlat = str_replace(['"',' '], '', $norm($expected)); return stripos($actualFlat, $expectedFlat) !== false; } if ($type === 'MX') { $ax = array_filter(array_map('trim', preg_split('/\R+/', $actual))); foreach ($ax as $line) { $line = $norm($line); if (stripos($line, $norm($expected)) !== false) return true; } return false; } if (in_array($type, ['A','AAAA','CNAME','PTR','SRV','TLSA'], true)) { $ax = array_filter(array_map('trim', preg_split('/\R+/', $actual))); $exp = $norm($expected); foreach ($ax as $line) { if ($norm($line) === $exp) return true; } if (in_array($type, ['CNAME','SRV','TLSA'], true)) { foreach ($ax as $line) { if (stripos($norm($line), rtrim($exp, '.')) !== false) return true; } } return false; } return stripos($actual, $expected) !== false; } private function extractZone(string $fqdn): string { $fqdn = strtolower(trim($fqdn, ".")); $parts = explode('.', $fqdn); $n = count($parts); return $n >= 2 ? $parts[$n - 2] . '.' . $parts[$n - 1] : $fqdn; } private function ptrFromIPv4(string $ip): string { $p = array_reverse(explode('.', $ip)); return implode('.', $p) . '.in-addr.arpa'; } private function ptrFromIPv6(string $ip): string { $bin = @inet_pton($ip); if ($bin === false) return ''; $hex = bin2hex($bin); // exakt 32 Hex-Zeichen, lowercase $nibbles = str_split($hex, 1); // 32 Nibbles return implode('.', array_reverse($nibbles)) . '.ip6.arpa'; } public function render() { return view('livewire.ui.domain.modal.domain-dns-modal'); } } //namespace App\Livewire\Ui\Domain\Modal; // //use App\Models\Domain; //use App\Models\TlsaRecord; //use App\Support\NetProbe; //use Illuminate\Support\Facades\DB; //use Livewire\Attributes\On; //use LivewireUI\Modal\ModalComponent; // //class DomainDnsModal extends ModalComponent //{ // public int $domainId; // public string $domainName = ''; // public string $zone = ''; // public string $ttl = '3600'; // public array $recordColors = []; // // /** @var array> */ // public array $static = []; // /** @var array> */ // public array $dynamic = []; // /** @var array> */ // public array $optional = []; // // /** Farbklassen für den Status */ // public array $stateColors = [ // 'ok' => 'border-emerald-400/30 bg-emerald-500/10', // 'missing' => 'border-rose-400/30 bg-rose-500/10', // 'syntax' => 'border-amber-400/30 bg-amber-500/10', // 'neutral' => 'border-white/10 bg-white/5', // ]; // // /** erst nach Klick färben */ // public bool $checked = false; // // public static function modalMaxWidth(): string { return '6xl'; } // // public function mount(int $domainId): void // { // $this->recordColors = [ // 'A' => 'bg-cyan-500/20 text-cyan-300', // 'AAAA' => 'bg-blue-500/20 text-blue-300', // 'MX' => 'bg-emerald-500/20 text-emerald-300', // 'CNAME' => 'bg-indigo-500/20 text-indigo-300', // 'PTR' => 'bg-amber-500/20 text-amber-300', // 'TXT' => 'bg-violet-500/20 text-violet-300', // 'SRV' => 'bg-rose-500/20 text-rose-300', // 'TLSA' => 'bg-red-500/20 text-red-300', // 'OPTIONAL' => 'bg-gray-500/20 text-gray-300', // ]; // // $d = Domain::findOrFail($domainId); // $this->domainId = $domainId; // $this->domainName = $d->domain; // // $tlsa = TlsaRecord::forServer()->where('service', '_25._tcp')->latest('id')->first(); // // $ips = NetProbe::resolve(); // $ipv4 = $ips['ipv4']; // $ipv6 = $ips['ipv6']; // // $this->zone = $this->extractZone($d->domain); // $mta_sub = env('MTA_SUB'); // $base = env('BASE_DOMAIN'); // $mailServerFqdn = "{$mta_sub}.{$base}"; // // // --- Infrastruktur (global) --- // $this->static = [ // ['type' => 'A', 'name' => $mailServerFqdn, 'value' => $ipv4], // ['type' => 'PTR', 'name' => $this->ptrFromIPv4($ipv4), 'value' => $mailServerFqdn], // ]; // if ($ipv6) { // $this->static[] = ['type' => 'AAAA','name' => $mailServerFqdn, 'value' => $ipv6]; // $this->static[] = ['type' => 'PTR', 'name' => $this->ptrFromIPv6($ipv6), 'value' => $mailServerFqdn]; // } // if ($tlsa?->dns_string) { // $this->static[] = [ // 'type' => 'TLSA', // 'name' => "{$tlsa->service}.{$tlsa->host}", // 'value' => "{$tlsa->usage} {$tlsa->selector} {$tlsa->matching} {$tlsa->hash}", // ]; // } // // // --- Domain-spezifisch --- // $spf = "v=spf1 ip4:{$ipv4}" . ($ipv6 ? " ip6:{$ipv6}" : '') . " mx -all"; // $dmarc = "v=DMARC1; p=none; rua=mailto:dmarc@{$this->domainName}; pct=100"; // // $dkim = DB::table('dkim_keys')->where('domain_id', $d->id)->where('is_active', 1)->orderByDesc('id')->first(); // $selector = $dkim ? $dkim->selector : 'mwl1'; // $dkimHost = "{$selector}._domainkey.{$this->domainName}"; // $dkimTxt = $dkim && !str_starts_with(trim($dkim->public_key_txt), 'v=') // ? 'v=DKIM1; k=rsa; p=' . trim($dkim->public_key_txt) // : (trim($dkim->public_key_txt ?? '') ?: 'v=DKIM1; k=rsa; p='); // // $this->dynamic = [ // ['type' => 'MX', 'name' => $this->domainName, 'value' => "10 {$mailServerFqdn}."], // ['type' => 'CNAME', 'name' => "autoconfig.{$this->domainName}", 'value' => "{$mailServerFqdn}."], // ['type' => 'CNAME', 'name' => "autodiscover.{$this->domainName}",'value' => "{$mailServerFqdn}."], // ['type' => 'TXT', 'name' => $this->domainName, 'value' => $spf], // ['type' => 'TXT', 'name' => "_dmarc.{$this->domainName}", 'value' => $dmarc], // ['type' => 'TXT', 'name' => $dkimHost, 'value' => $dkimTxt], // ]; // // $this->optional = [ // ['type' => 'SRV', 'name' => "_autodiscover._tcp.{$this->domainName}", 'value' => "0 0 443 {$mailServerFqdn}."], // ['type' => 'SRV', 'name' => "_imaps._tcp.{$this->domainName}", 'value' => "0 0 993 {$mailServerFqdn}."], // ['type' => 'SRV', 'name' => "_pop3s._tcp.{$this->domainName}", 'value' => "0 0 995 {$mailServerFqdn}."], // ['type' => 'SRV', 'name' => "_submission._tcp.{$this->domainName}", 'value' => "0 0 587 {$mailServerFqdn}."], // ]; // } // // private function applyNeutralColors(): void // { // foreach (['static', 'dynamic', 'optional'] as $group) { // $this->{$group} = array_map(fn($r) => array_merge($r, [ // 'state' => 'neutral', // 'boxClass' => $this->stateColors['neutral'], // 'actual' => '', // ]), $this->{$group}); // } // } // // /** Klick im Footer löst diesen Check aus */ // #[On('domain:check-dns')] // public function checkDns(): void // { // // dynamische (pflicht) Records prüfen // foreach ($this->dynamic as $i => $r) { // $actual = $this->dig($r['type'], $r['name']); // $state = $this->stateFor($r['type'], $r['value'], $actual, optional:false); // $this->dynamic[$i]['actual'] = $actual; // $this->dynamic[$i]['state'] = $state; // } // // // statische (pflicht) Records prüfen (TLSA, PTR, A/AAAA …) // foreach ($this->static as $i => $r) { // $actual = $this->dig($r['type'], $r['name']); // $state = $this->stateFor($r['type'], $r['value'], $actual, optional:false); // $this->static[$i]['actual'] = $actual; // $this->static[$i]['state'] = $state; // } // // // optionale Records: nie „missing“, nur neutral|syntax|ok // foreach ($this->optional as $i => $r) { // $actual = $this->dig($r['type'], $r['name']); // $state = $this->stateFor($r['type'], $r['value'], $actual, optional:true); // $this->optional[$i]['actual'] = $actual; // $this->optional[$i]['state'] = $state; // } // // $this->checked = true; // } // // /** Prüft eine Record-Liste und gibt die Liste mit 'state' zurück */ // private function checkGroup(array $group, bool $optional = false): array // { // foreach ($group as &$r) { // $type = (string)$r['type']; // $name = (string)$r['name']; // $exp = (string)$r['value']; // // $act = $this->dig($type, $name); // // if ($act === '') { // $r['state'] = $optional ? 'missing' : 'missing'; // continue; // } // // $ok = $this->compareDns($type, $exp, $act); // $r['state'] = $ok ? 'ok' : 'mismatch'; // } // return $group; // } // // /** dig wrapper */ // /* ---------- DNS & Bewertung ---------- */ // // private function dig(string $type, string $name): string // { // $type = strtoupper($type); // $name = rtrim($name, '.'); // wir arbeiten intern ohne trailing dot // $out = @shell_exec('dig +timeout=2 +tries=1 +short ' // . escapeshellarg($name) . ' ' . escapeshellarg($type) . ' 2>/dev/null') ?? ''; // $out = trim($out); // // // Mehrzeiliges TXT zu einer Zeile squashen, Quotes weg // if ($type === 'TXT' && $out !== '') { // $lines = array_filter(array_map('trim', explode("\n", $out))); // $joined = implode('', array_map(fn($l)=>trim($l,'"'), $lines)); // return $joined; // } // // // Nur erste Zeile vergleichen (mehrere Antworten -> erste reicht) // if ($out !== '') { // $out = trim(explode("\n", $out)[0]); // // MX/SRV/CNAME Ziele ohne trailing dot vergleichen // if (in_array($type, ['MX','CNAME','SRV'])) { // $out = rtrim($out, '.'); // } // } // return $out; // } // // 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'; // // // Bei TXT/Policies: inhaltlich locker matchen (Beginn mit v=…) // if ($type === 'TXT') { // // SPF // if (str_starts_with(strtoupper($exp), 'V=SPF1')) { // return str_starts_with(strtoupper($act), 'V=SPF1') ? 'ok' : 'syntax'; // } // // DMARC // if (str_starts_with(strtoupper($exp), 'V=DMARC1')) { // return str_starts_with(strtoupper($act), 'V=DMARC1') ? 'ok' : 'syntax'; // } // // DKIM // if (str_starts_with(strtoupper($exp), 'V=DKIM1')) { // return str_starts_with(strtoupper($act), 'V=DKIM1') ? 'ok' : 'syntax'; // } // return ($act !== '') ? 'ok' : ($optional ? 'neutral' : 'missing'); // } // // // MX: wir erwarten „prio host“ – wir prüfen nur, ob host passt // if ($type === 'MX') { // // $act z.B. "10 mx.example.tld" // $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“ – prüfen 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: einfacher Gleichheitsvergleich (normalisiert) // 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); // 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('/^[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), // 'TLSA' => (bool)preg_match('/^[0-3]\s+[01]\s+[123]\s+[0-9a-f]{32,}$/i', $val), // 'TXT' => strlen($val) > 0, // SPF/DMARC/DKIM oben extra geprüft // default => true, // }; // } // // /** Vergleich je Typ robust normalisieren */ // private function compareDns(string $type, string $expected, string $actual): bool // { // $type = strtoupper($type); // // // Normalisierer // $norm = function (string $s): string { // $s = trim($s); // $s = trim($s, "\"'"); // TXT quotes // $s = preg_replace('/\s+/', ' ', $s); // unify spaces // $s = rtrim($s, '.'); // trailing dot // return $s; // }; // // // TXT kann aus mehreren quoted Strings bestehen (mehrere Zeilen) // if ($type === 'TXT') { // // dig kann mehrere Zeilen liefern, jede in Quotes – zusammenfügen // $parts = array_map( // fn($line) => $norm($line), // preg_split('/\R+/', $actual) // ); // $actualFlat = implode('', array_map(fn($p) => str_replace(['"',' '], '', $p), $parts)); // $expectedFlat = str_replace(['"',' '], '', $norm($expected)); // return stripos($actualFlat, $expectedFlat) !== false; // } // // if ($type === 'MX') { // // Erwartet i.d.R. "10 host." – wir normalisieren beide Seiten // $ax = array_filter(array_map('trim', preg_split('/\R+/', $actual))); // foreach ($ax as $line) { // $line = $norm($line); // if (stripos($line, $norm($expected)) !== false) return true; // } // return false; // } // // if (in_array($type, ['A','AAAA','CNAME','PTR','SRV','TLSA'], true)) { // $ax = array_filter(array_map('trim', preg_split('/\R+/', $actual))); // $exp = $norm($expected); // foreach ($ax as $line) { // if ($norm($line) === $exp) return true; // } // // bei CNAME/SRV/TLSA reicht oft „enthält denselben Zielhost“ // if (in_array($type, ['CNAME','SRV','TLSA'], true)) { // foreach ($ax as $line) { // if (stripos($norm($line), rtrim($exp, '.')) !== false) return true; // } // } // return false; // } // // // Fallback: substring // return stripos($actual, $expected) !== false; // } // // private function extractZone(string $fqdn): string // { // $fqdn = strtolower(trim($fqdn, ".")); // $parts = explode('.', $fqdn); // $n = count($parts); // return $n >= 2 ? $parts[$n - 2] . '.' . $parts[$n - 1] : $fqdn; // } // // private function ptrFromIPv4(string $ip): string // { // $p = array_reverse(explode('.', $ip)); // return implode('.', $p) . '.in-addr.arpa'; // } // // private function ptrFromIPv6(string $ip): string // { // $expanded = strtolower(inet_ntop(inet_pton($ip))); // $hex = str_replace(':', '', $expanded); // return implode('.', array_reverse(str_split($hex))) . '.ip6.arpa'; // } // // public function render() // { // return view('livewire.ui.domain.modal.domain-dns-modal'); // } //} //namespace App\Livewire\Ui\Domain\Modal; // //use App\Models\Domain; //use App\Models\TlsaRecord; //use App\Support\NetProbe; //use Illuminate\Support\Facades\DB; //use Livewire\Attributes\On; //use LivewireUI\Modal\ModalComponent; // //class DomainDnsModal extends ModalComponent //{ // public int $domainId; // public string $domainName = ''; // public string $zone = ''; // public string $ttl = '3600'; // public array $recordColors = []; // // /** @var array> */ // public array $static = []; // /** @var array> */ // public array $dynamic = []; // /** @var array> */ // public array $optional = []; // // public array $stateColors = [ // 'ok' => 'text-emerald-300 bg-emerald-500/10 border-emerald-400/30', // 'missing' => 'text-rose-300 bg-rose-500/10 border-rose-400/30', // 'mismatch' => 'text-amber-200 bg-amber-500/10 border-amber-400/30', // ]; // // public static function modalMaxWidth(): string // { // return '6xl'; // } // // public function mount(int $domainId): void // { // $this->recordColors = [ // 'A' => 'bg-cyan-500/20 text-cyan-300', // 'AAAA' => 'bg-blue-500/20 text-blue-300', // 'MX' => 'bg-emerald-500/20 text-emerald-300', // 'CNAME' => 'bg-indigo-500/20 text-indigo-300', // 'PTR' => 'bg-amber-500/20 text-amber-300', // 'TXT' => 'bg-violet-500/20 text-violet-300', // 'SRV' => 'bg-rose-500/20 text-rose-300', // 'TLSA' => 'bg-red-500/20 text-red-300', // 'OPTIONAL' => 'bg-gray-500/20 text-gray-300', // ]; // // $d = Domain::findOrFail($domainId); // $this->domainName = $d->domain; // // $tlsa = TlsaRecord::forServer() // ->where('service', '_25._tcp') // ->latest('id') // ->first(); // // $ips = NetProbe::resolve(); // $ipv4 = $ips['ipv4']; // $ipv6 = $ips['ipv6']; // // $this->zone = $this->extractZone($d->domain); // $mta_sub = env('MTA_SUB'); // $base = env('BASE_DOMAIN'); // $mailServerFqdn = $mta_sub . '.' . $base; // $mta = $mta_sub . '.' . $this->zone; // // // --- Statische Infrastruktur (für alle Domains gleich) --- // $this->static = [ // ['type' => 'A', 'name' => $mailServerFqdn, 'value' => $ipv4], // ['type' => 'PTR', 'name' => $this->ptrFromIPv4($ipv4), 'value' => $tlsa->host], // ]; // if ($ipv6) { // $this->static[] = ['type' => 'AAAA', 'name' => $mailServerFqdn, 'value' => $ipv6]; // $this->static[] = ['type' => 'PTR', 'name' => $this->ptrFromIPv6($ipv6), 'value' => $mailServerFqdn]; // } // // // if ($tlsa?->dns_string) { // $this->static[] = [ // 'type' => 'TLSA', // 'name' => "{$tlsa->service}.{$tlsa->host}", // 'value' => "{$tlsa->usage} {$tlsa->selector} {$tlsa->matching} {$tlsa->hash}", // ]; // } // // // --- Domain-spezifisch --- // $spf = "v=spf1 ip4:{$ipv4} ip6:{$ipv6} mx -all"; // $dmarc = "v=DMARC1; p=none; rua=mailto:dmarc@{$this->domainName}; pct=100"; // // $dkim = DB::table('dkim_keys') // ->where('domain_id', $d->id)->where('is_active', 1)->orderByDesc('id')->first(); // $selector = $dkim ? $dkim->selector : 'mwl1'; // $dkimHost = "{$selector}._domainkey.{$this->domainName}"; // $dkimTxt = $dkim && !str_starts_with(trim($dkim->public_key_txt), 'v=') // ? 'v=DKIM1; k=rsa; p=' . $dkim->public_key_txt // : ($dkim->public_key_txt ?? 'v=DKIM1; k=rsa; p='); // // $this->dynamic = [ // ['type' => 'MX', 'name' => $this->domainName, 'value' => "10 {$mailServerFqdn}."], // // ['type' => 'CNAME', 'name' => "autoconfig.$this->domainName", 'value' => "$mailServerFqdn."], // ['type' => 'CNAME', 'name' => "autodiscover.$this->domainName", 'value' => "$mailServerFqdn."], // // // TXT Records // ['type' => 'TXT', 'name' => $this->domainName, 'value' => $spf, 'helpLabel' => 'SPF Record Syntax', 'helpUrl' => 'http://www.open-spf.org/SPF_Record_Syntax/'], // ['type' => 'TXT', 'name' => "_dmarc.{$this->domainName}", 'value' => $dmarc, 'helpLabel' => 'DMARC Assistant', 'helpUrl' => 'https://www.kitterman.com/dmarc/assistant.html'], // ['type' => 'TXT', 'name' => $dkimHost, 'value' => $dkimTxt, 'helpLabel' => 'DKIM Inspector', 'helpUrl' => 'https://dkimvalidator.com/'], // // ]; // // $this->optional = [ // // SRV Records für Autodiscover und Maildienste // ['type' => 'SRV', 'name' => "_autodiscover._tcp.$this->domainName", 'value' => "0 0 443 {$mailServerFqdn}."], // ['type' => 'SRV', 'name' => "_imaps._tcp.$this->domainName", 'value' => "0 0 993 {$mailServerFqdn}."], // ['type' => 'SRV', 'name' => "_pop3s._tcp.$this->domainName", 'value' => "0 0 995 {$mailServerFqdn}."], // ['type' => 'SRV', 'name' => "_submission._tcp.$this->domainName", 'value' => "0 0 587 {$mailServerFqdn}."], // ]; // // } // // #[On('domain:check-dns')] // public function checkDns(): void // { // $this->validateDnsRecords(); // } // // // private function validateDnsRecords(): void // { // // alle Listen zusammenführen // $records = array_merge($this->static, $this->dynamic); // // foreach ($records as &$record) { // $type = $record['type']; // $name = $record['name']; // $expected = trim((string) $record['value']); // // $result = @shell_exec("dig +short {$type} {$name} 2>/dev/null"); // $result = trim((string) $result); // // if ($result === '') { // $record['state'] = 'missing'; // } elseif (stripos($result, trim($expected, '.')) !== false) { // $record['state'] = 'ok'; // } else { // $record['state'] = 'mismatch'; // } // } // // $this->static = array_values(array_filter($records, fn($r) => in_array($r['type'], ['A','AAAA','PTR','TLSA']))); // $this->dynamic = array_values(array_filter($records, fn($r) => !in_array($r['type'], ['A','AAAA','PTR','TLSA']))); // } // // private function extractZone(string $fqdn): string // { // $fqdn = strtolower(trim($fqdn, ".")); // $parts = explode('.', $fqdn); // $n = count($parts); // return $n >= 2 ? $parts[$n - 2] . '.' . $parts[$n - 1] : $fqdn; // nimmt die letzten 2 Labels // } // // private function detectIPv4(): string // { // // robust & ohne env // $out = @shell_exec("ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if(\$i==\"src\"){print \$(i+1); exit}}'"); // $ip = trim((string)$out); // if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) return $ip; // $ip = trim($_SERVER['SERVER_ADDR'] ?? ''); // return $ip ?: '203.0.113.10'; // Fallback Demo // } // // private function detectIPv6(): ?string // { // $out = @shell_exec("ip -6 route get 2001:4860:4860::8888 2>/dev/null | awk '{for(i=1;i<=NF;i++) if(\$i==\"src\"){print \$(i+1); exit}}'"); // $ip = trim((string)$out); // return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? $ip : null; // } // // private function ptrFromIPv4(string $ip): string // { // $p = array_reverse(explode('.', $ip)); // return implode('.', $p) . '.in-addr.arpa'; // } // // private function ptrFromIPv6(string $ip): string // { // $expanded = strtolower(inet_ntop(inet_pton($ip))); // $hex = str_replace(':', '', $expanded); // return implode('.', array_reverse(str_split($hex))) . '.ip6.arpa'; // } // // public function render() // { // return view('livewire.ui.domain.modal.domain-dns-modal'); // } // //// public int $domainId; //// public Domain $domain; //// //// public array $records = []; //// //// public function mount(int $domainId): void //// { //// $this->domainId = $domainId; //// $this->domain = Domain::findOrFail($domainId); //// //// // Placeholder-Werte, sofern du sie anderswo speicherst gern ersetzen: //// $serverIp = config('app.server_ip', 'DEINE.SERVER.IP'); //// $mxHost = config('mailwolt.mx_fqdn', 'mx.' . $this->domain->domain); //// $selector = optional( //// DkimKey::where('domain_id', $this->domain->id)->where('is_active', true)->first() //// )->selector ?? 'mwl1'; //// //// $dkimTxt = optional( //// DkimKey::where('domain_id', $this->domain->id)->where('is_active', true)->first() //// )->public_key_txt ?? 'DKIM_PUBLIC_KEY'; //// //// $this->records = [ //// [ //// 'type' => 'A', //// 'host' => $this->domain->domain, //// 'value' => $serverIp, //// 'ttl' => 3600, //// ], //// [ //// 'type' => 'MX', //// 'host' => $this->domain->domain, //// 'value' => "10 {$mxHost}.", //// 'ttl' => 3600, //// ], //// [ //// 'type' => 'TXT', //// 'host' => $this->domain->domain, //// 'value' => 'v=spf1 mx a -all', //// 'ttl' => 3600, //// ], //// [ //// 'type' => 'TXT', //// 'host' => "{$selector}._domainkey." . $this->domain->domain, //// // komplette, fertige TXT-Payload (aus deiner dkim_keys.public_key_txt Spalte) //// 'value' => "v=DKIM1; k=rsa; p={$dkimTxt}", //// 'ttl' => 3600, //// ], //// [ //// 'type' => 'TXT', //// 'host' => "_dmarc." . $this->domain->domain, //// 'value' => "v=DMARC1; p=none; rua=mailto:dmarc@" . $this->domain->domain . "; pct=100", //// 'ttl' => 3600, //// ], //// ]; //// } //// //// public static function modalMaxWidth(): string //// { //// return '4xl'; // schön breit //// } //// //// public function render() //// { //// return view('livewire.ui.domain.modal.domain-dns-modal'); //// } // //// public Domain $domain; //// //// public static function modalMaxWidth(): string //// { //// return '3xl'; //// } //// //// public function mount(int $domainId): void //// { //// $this->domain = Domain::with(['dkimKeys' /* falls Relationen existieren */])->findOrFail($domainId); //// } //// //// public function render() //// { //// // Hier kannst du die Records vorbereiten (SPF/DMARC/DKIM) //// $records = [ //// [ //// 'type' => 'TXT', //// 'host' => $this->domain->domain, //// 'value'=> $this->domain->spf_record ?? 'v=spf1 mx a -all', //// 'ttl' => 3600, //// ], //// [ //// 'type' => 'TXT', //// 'host' => '_dmarc.' . $this->domain->domain, //// 'value'=> $this->domain->dmarc_record ?? 'v=DMARC1; p=none; rua=mailto:dmarc@' . $this->domain->domain . '; pct=100', //// 'ttl' => 3600, //// ], //// // DKIM (falls vorhanden) //// // Beispiel: nimm den aktiven Key oder den ersten //// ]; //// //// $dkim = optional($this->domain->dkimKeys()->where('is_active', true)->first() ?? $this->domain->dkimKeys()->first()); //// if ($dkim && $dkim->selector) { //// $records[] = [ //// 'type' => 'TXT', //// 'host' => "{$dkim->selector}._domainkey." . $this->domain->domain, //// 'value'=> "v=DKIM1; k=rsa; p={$dkim->public_key_txt}", //// 'ttl' => 3600, //// ]; //// } //// //// return view('livewire.ui.domain.modal.domain-dns-modal', [ //// 'records' => $records, //// ]); //// } // //// public function render() //// { //// return view('livewire.ui.domain.modal.domain-dns-modal'); //// } //}