545 lines
19 KiB
PHP
545 lines
19 KiB
PHP
<?php
|
||
|
||
namespace App\Livewire\Ui\Mail;
|
||
|
||
use Livewire\Component;
|
||
use App\Models\Domain;
|
||
use Illuminate\Support\Facades\Cache;
|
||
use Illuminate\Support\Facades\DB;
|
||
|
||
class DnsHealthCard extends Component
|
||
{
|
||
public array $rows = []; // [{id,name,ok,missing:[...]}]
|
||
public string $mtaHost = '';
|
||
public bool $tlsa = false;
|
||
|
||
public function mount(): void
|
||
{
|
||
$this->load();
|
||
}
|
||
|
||
public function render()
|
||
{
|
||
return view('livewire.ui.mail.dns-health-card');
|
||
}
|
||
|
||
public function refresh(): void
|
||
{
|
||
$this->load();
|
||
}
|
||
|
||
public function openDnsModal(int $domainId): void
|
||
{
|
||
$this->dispatch('openModal', component: 'ui.domain.modal.domain-dns-modal', arguments: ['domainId' => $domainId]);
|
||
}
|
||
|
||
// protected function load(bool $force = false): void
|
||
// {
|
||
// [$this->mtaHost, $this->tlsa, $this->rows] = Cache::remember('dash.dnshealth.v2', $force ? 1 : 600, function () {
|
||
//
|
||
// $base = trim((string) env('BASE_DOMAIN', ''));
|
||
// $mtaSub = trim((string) env('MTA_SUB', 'mx'));
|
||
// $mtaHost = $base !== '' ? "{$mtaSub}.{$base}" : $mtaSub; // z.B. mx.nexlab.at
|
||
//
|
||
// // ▼ gewünschter Filter:
|
||
// $domains = Domain::query()
|
||
// ->where('is_active', true)
|
||
// ->where('is_server', false) // <<< Server-Domain sauber ausschließen
|
||
// ->orderBy('domain')
|
||
// ->get(['id', 'domain']);
|
||
//
|
||
// $rows = [];
|
||
// foreach ($domains as $d) {
|
||
// $dom = $d->domain;
|
||
//
|
||
// // DKIM-Selector ermitteln: .env > DB > Fallback null
|
||
// $selector = trim((string) env('DKIM_SELECTOR', ''));
|
||
// if ($selector === '') {
|
||
// $selector = (string) DB::table('dkim_keys')
|
||
// ->where('domain_id', $d->id)
|
||
// ->where('is_active', 1)
|
||
// ->orderByDesc('id')
|
||
// ->value('selector') ?? '';
|
||
// }
|
||
//
|
||
// $missing = [];
|
||
//
|
||
// if (!$this->mxPointsTo($dom, [$mtaHost])) $missing[] = 'MX';
|
||
// if (!$this->hasSpf($dom)) $missing[] = 'SPF';
|
||
// if (!$this->hasDkim($dom, $selector)) $missing[] = 'DKIM';
|
||
// if (!$this->hasTxt("_dmarc.$dom")) $missing[] = 'DMARC';
|
||
//
|
||
// $rows[] = [
|
||
// 'id' => (int) $d->id,
|
||
// 'name' => $dom,
|
||
// 'ok' => empty($missing),
|
||
// 'missing' => $missing,
|
||
// ];
|
||
// }
|
||
//
|
||
// // Hostweites TLSA (nur Hinweis)
|
||
// $tlsa = $this->hasTlsa("_25._tcp.$mtaHost") || $this->hasTlsa("_465._tcp.$mtaHost") || $this->hasTlsa("_587._tcp.$mtaHost");
|
||
//
|
||
// return [$mtaHost, $tlsa, $rows];
|
||
// });
|
||
// }
|
||
|
||
protected function load(): void
|
||
{
|
||
$base = trim((string) env('BASE_DOMAIN', ''));
|
||
$mtaSub = trim((string) env('MTA_SUB', 'mx'));
|
||
$mtaHost = $base !== '' ? "{$mtaSub}.{$base}" : $mtaSub; // z.B. mx.nexlab.at
|
||
|
||
// nur aktive, NICHT-Server-Domains (System + Custom, solange is_server = false)
|
||
$domains = Domain::query()
|
||
->where('is_active', true)
|
||
->where('is_server', false)
|
||
->orderBy('domain')
|
||
->get(['id','domain']);
|
||
|
||
$rows = [];
|
||
|
||
foreach ($domains as $d) {
|
||
$dom = $d->domain;
|
||
|
||
// DKIM-Selector: .env > DB > leer
|
||
$selector = trim((string) env('DKIM_SELECTOR', ''));
|
||
if ($selector === '') {
|
||
$selector = (string) DB::table('dkim_keys')
|
||
->where('domain_id', $d->id)
|
||
->where('is_active', 1)
|
||
->orderByDesc('id')
|
||
->value('selector') ?? '';
|
||
}
|
||
|
||
$missing = [];
|
||
if (!$this->mxPointsTo($dom, [$mtaHost])) $missing[] = 'MX';
|
||
if (!$this->hasSpf($dom)) $missing[] = 'SPF';
|
||
if (!$this->hasDkim($dom, $selector)) $missing[] = 'DKIM';
|
||
if (!$this->hasTxt("_dmarc.$dom")) $missing[] = 'DMARC';
|
||
|
||
$rows[] = [
|
||
'id' => (int) $d->id,
|
||
'name' => $dom,
|
||
'ok' => empty($missing),
|
||
'missing' => $missing,
|
||
];
|
||
}
|
||
|
||
// Hostweites TLSA (nur Info)
|
||
$tlsa = $this->hasTlsa("_25._tcp.$mtaHost")
|
||
|| $this->hasTlsa("_465._tcp.$mtaHost")
|
||
|| $this->hasTlsa("_587._tcp.$mtaHost");
|
||
|
||
$this->mtaHost = $mtaHost;
|
||
$this->tlsa = $tlsa;
|
||
$this->rows = $rows;
|
||
}
|
||
/* ── DNS Helpers ───────────────────────────────────────────────────── */
|
||
|
||
protected function digShort(string $type, string $name): string
|
||
{
|
||
$cmd = "timeout 2 dig +short " . escapeshellarg($name) . ' ' . escapeshellarg(strtoupper($type)) . " 2>/dev/null";
|
||
return (string) @shell_exec($cmd) ?: '';
|
||
}
|
||
|
||
protected function hasTxt(string $name): bool
|
||
{
|
||
return trim($this->digShort('TXT', $name)) !== '';
|
||
}
|
||
|
||
protected function hasTlsa(string $name): bool
|
||
{
|
||
return trim($this->digShort('TLSA', $name)) !== '';
|
||
}
|
||
|
||
protected function hasSpf(string $domain): bool
|
||
{
|
||
$out = $this->digShort('TXT', $domain);
|
||
foreach (preg_split('/\R+/', trim($out)) as $line) {
|
||
if (stripos($line, 'v=spf1') !== false) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// ▼ DKIM: bevorzugt konkreten Selector prüfen; wenn leer, versuche Policy (_domainkey)
|
||
protected function hasDkim(string $domain, string $selector = ''): bool
|
||
{
|
||
if ($selector !== '' && $this->hasTxt("{$selector}._domainkey.$domain")) {
|
||
return true;
|
||
}
|
||
// Fallback: irgendein _domainkey-TXT vorhanden
|
||
return $this->hasTxt("_domainkey.$domain");
|
||
}
|
||
|
||
// protected function hasDkim(string $domain, string $selector = ''): bool
|
||
// {
|
||
// if ($selector !== '') {
|
||
// return $this->hasTxt("{$selector}._domainkey.$domain");
|
||
// }
|
||
// // Manche Betreiber veröffentlichen eine Policy auf _domainkey.<dom>
|
||
// return $this->hasTxt("_domainkey.$domain");
|
||
// }
|
||
|
||
protected function mxPointsTo(string $domain, array $allowedHosts): bool
|
||
{
|
||
$out = $this->digShort('MX', $domain);
|
||
if ($out === '') return false;
|
||
|
||
$targets = [];
|
||
foreach (preg_split('/\R+/', trim($out)) as $line) {
|
||
if (preg_match('~\s+([A-Za-z0-9\.\-]+)\.?$~', trim($line), $m)) {
|
||
$targets[] = strtolower($m[1]);
|
||
}
|
||
}
|
||
if (!$targets) return false;
|
||
|
||
$allowed = array_map(fn ($h) => strtolower(rtrim($h, '.')), $allowedHosts);
|
||
foreach ($targets as $t) {
|
||
$t = rtrim($t, '.');
|
||
if (in_array($t, $allowed, true)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
}
|
||
|
||
//namespace App\Livewire\Ui\Mail;
|
||
//
|
||
//use Livewire\Attributes\On;
|
||
//use Livewire\Component;
|
||
//use App\Models\Domain;
|
||
//use Illuminate\Support\Facades\Cache;
|
||
//
|
||
//class DnsHealthCard extends Component
|
||
//{
|
||
// public array $rows = []; // [{id,name,ok,missing:[...]}]
|
||
// public string $mtaHost = ''; // z.B. mx.nexlab.at
|
||
// public bool $tlsa = false; // hostweit
|
||
//
|
||
// public function mount(): void
|
||
// {
|
||
// $this->load();
|
||
// }
|
||
//
|
||
// public function render()
|
||
// {
|
||
// return view('livewire.ui.mail.dns-health-card');
|
||
// }
|
||
//
|
||
// public function refresh(): void
|
||
// {
|
||
// $this->load(true);
|
||
// }
|
||
//
|
||
// public function openDnsModal(int $domainId): void
|
||
// {
|
||
// $this->dispatch('openModal', component: 'ui.domain.modal.domain-dns-modal', arguments: ['domainId' => $domainId]);
|
||
// }
|
||
//
|
||
// protected function load(bool $force = false): void
|
||
// {
|
||
// [$this->mtaHost, $this->tlsa, $this->rows] = Cache::remember('dash.dnshealth.v1', $force ? 1 : 600, function () {
|
||
//
|
||
// $base = trim((string)env('BASE_DOMAIN', ''));
|
||
// $mtaSub = trim((string)env('MTA_SUB', 'mx'));
|
||
// $mtaHost = $base !== '' ? "{$mtaSub}.{$base}" : $mtaSub;
|
||
//
|
||
// $rows = [];
|
||
// $domains = Domain::query()
|
||
// ->where('is_system', false)
|
||
// ->where('is_active', true)
|
||
// ->orderBy('domain')
|
||
// ->get(['id', 'domain']);
|
||
//
|
||
// foreach ($domains as $d) {
|
||
// $dom = $d->domain;
|
||
//
|
||
// $missing = [];
|
||
//
|
||
// // Pflicht-Checks
|
||
// if (!$this->mxPointsTo($dom, [$mtaHost])) $missing[] = 'MX';
|
||
// if (!$this->hasSpf($dom)) $missing[] = 'SPF';
|
||
// if (!$this->hasDkim($dom)) $missing[] = 'DKIM';
|
||
// if (!$this->hasTxt("_dmarc.$dom")) $missing[] = 'DMARC';
|
||
//
|
||
// $rows[] = [
|
||
// 'id' => (int)$d->id,
|
||
// 'name' => $dom,
|
||
// 'ok' => empty($missing),
|
||
// 'missing' => $missing,
|
||
// ];
|
||
// }
|
||
//
|
||
// // TLSA (hostweit, nur Info)
|
||
// $tlsa = $this->hasTlsa("_25._tcp.$mtaHost") || $this->hasTlsa("_465._tcp.$mtaHost") || $this->hasTlsa("_587._tcp.$mtaHost");
|
||
//
|
||
// return [$mtaHost, $tlsa, $rows];
|
||
// });
|
||
// }
|
||
//
|
||
// /* ── DNS Helpers (mit Timeout, damit UI nicht hängt) ───────────────── */
|
||
//
|
||
// protected function digShort(string $type, string $name): string
|
||
// {
|
||
// $cmd = "timeout 2 dig +short " . escapeshellarg($name) . " " . escapeshellarg(strtoupper($type)) . " 2>/dev/null";
|
||
// return (string)@shell_exec($cmd) ?: '';
|
||
// }
|
||
//
|
||
// protected function hasTxt(string $name): bool
|
||
// {
|
||
// return trim($this->digShort('TXT', $name)) !== '';
|
||
// }
|
||
//
|
||
// protected function hasTlsa(string $name): bool
|
||
// {
|
||
// return trim($this->digShort('TLSA', $name)) !== '';
|
||
// }
|
||
//
|
||
// protected function hasSpf(string $domain): bool
|
||
// {
|
||
// $out = $this->digShort('TXT', $domain);
|
||
// foreach (preg_split('/\R+/', trim($out)) as $line) {
|
||
// if (stripos($line, 'v=spf1') !== false) return true;
|
||
// }
|
||
// return false;
|
||
// }
|
||
//
|
||
// // DKIM: wenn spezifischer Selector vorhanden → prüfe den, sonst akzeptiere _domainkey-Policy als “vorhanden”
|
||
// protected function hasDkim(string $domain): bool
|
||
// {
|
||
// $sel = trim((string)env('DKIM_SELECTOR', ''));
|
||
// if ($sel !== '' && $this->hasTxt("{$sel}._domainkey.$domain")) return true;
|
||
// return $this->hasTxt("_domainkey.$domain");
|
||
// }
|
||
//
|
||
// protected function mxPointsTo(string $domain, array $allowedHosts): bool
|
||
// {
|
||
// $out = $this->digShort('MX', $domain);
|
||
// if ($out === '') return false;
|
||
//
|
||
// $targets = [];
|
||
// foreach (preg_split('/\R+/', trim($out)) as $line) {
|
||
// // Format: "10 mx.example.com."
|
||
// if (preg_match('~\s+([A-Za-z0-9\.\-]+)\.?$~', trim($line), $m)) {
|
||
// $targets[] = strtolower($m[1]);
|
||
// }
|
||
// }
|
||
// if (!$targets) return false;
|
||
//
|
||
// $allowed = array_map('strtolower', $allowedHosts);
|
||
// foreach ($targets as $t) {
|
||
// if (in_array($t, $allowed, true)) return true;
|
||
// }
|
||
// return false;
|
||
// }
|
||
//}
|
||
|
||
|
||
//namespace App\Livewire\Ui\Mail;
|
||
//
|
||
//use Livewire\Component;
|
||
//use App\Models\Domain;
|
||
//use Illuminate\Support\Facades\Cache;
|
||
//
|
||
//class DnsHealthCard extends Component
|
||
//{
|
||
// public array $domains = []; // [['name'=>..., 'dkim'=>bool, 'dmarc'=>bool], ...]
|
||
// public string $host = ''; // z.B. mx.nexlab.at
|
||
// public bool $tlsa = false; // hostbasiert (einmalig)
|
||
// public ?string $ipv4 = null;
|
||
// public ?string $ipv6 = null;
|
||
//
|
||
// public function mount(): void
|
||
// {
|
||
// $this->load();
|
||
// }
|
||
//
|
||
// public function render()
|
||
// {
|
||
// return view('livewire.ui.mail.dns-health-card');
|
||
// }
|
||
//
|
||
// public function refresh(): void
|
||
// {
|
||
// $this->load(true);
|
||
// }
|
||
//
|
||
// protected function load(bool $force = false): void
|
||
// {
|
||
// [$this->host, $this->tlsa, $this->domains, $this->ipv4, $this->ipv6] =
|
||
// Cache::remember('dash.dnshealth', $force ? 1 : 900, function () {
|
||
//
|
||
// // ── ENV lesen ────────────────────────────────────────────────
|
||
// $base = trim((string)env('BASE_DOMAIN', ''));
|
||
// $mtaSub = trim((string)env('MTA_SUB', 'mx'));
|
||
// $host = $base !== '' ? "{$mtaSub}.{$base}" : $mtaSub;
|
||
//
|
||
// $ipv4 = trim((string)env('SERVER_PUBLIC_IPV4', '')) ?: null;
|
||
// $ipv6 = trim((string)env('SERVER_PUBLIC_IPV6', '')) ?: null;
|
||
//
|
||
// // ── Domains laden (nur aktive, nicht-system) ────────────────
|
||
// $rows = [];
|
||
// $domains = Domain::query()
|
||
// ->where('is_system', false)
|
||
// ->where('is_active', true)
|
||
// ->orderBy('domain')
|
||
// ->get(['domain']);
|
||
//
|
||
// foreach ($domains as $d) {
|
||
// $dom = $d->domain;
|
||
// $rows[] = [
|
||
// 'name' => $dom,
|
||
// 'dkim' => $this->hasTxt("_domainkey.$dom"),
|
||
// 'dmarc' => $this->hasTxt("_dmarc.$dom"),
|
||
// ];
|
||
// }
|
||
//
|
||
// // ── TLSA nur hostbasiert prüfen (25/465/587) ────────────────
|
||
// $tlsa = $this->hasTlsa("_25._tcp.$host")
|
||
// || $this->hasTlsa("_465._tcp.$host")
|
||
// || $this->hasTlsa("_587._tcp.$host");
|
||
//
|
||
// return [$host, $tlsa, $rows, $ipv4, $ipv6];
|
||
// });
|
||
// }
|
||
//
|
||
// /* ───────────────────────── DNS Helpers ───────────────────────── */
|
||
//
|
||
// protected function hasTxt(string $name): bool
|
||
// {
|
||
// $out = @shell_exec("timeout 2 dig +short TXT " . escapeshellarg($name) . " 2>/dev/null");
|
||
// return is_string($out) && trim($out) !== '';
|
||
// }
|
||
//
|
||
// protected function hasTlsa(string $name): bool
|
||
// {
|
||
// $out = @shell_exec("timeout 2 dig +short TLSA " . escapeshellarg($name) . " 2>/dev/null");
|
||
// return is_string($out) && trim($out) !== '';
|
||
// }
|
||
//}
|
||
|
||
//namespace App\Livewire\Ui\Mail;
|
||
//
|
||
//use Livewire\Component;
|
||
//use App\Models\Domain;
|
||
//use Illuminate\Support\Facades\Cache;
|
||
//
|
||
//class DnsHealthCard extends Component
|
||
//{
|
||
// public array $rows = []; // pro Domain: ['dom','dkim','dmarc']
|
||
// public string $host = ''; // z.B. mx.nexlab.at
|
||
// public bool $tlsa = false; // TLSA-Status für den Host
|
||
// public ?string $ipv4 = null;
|
||
// public ?string $ipv6 = null;
|
||
//
|
||
// public function mount(): void { $this->load(); }
|
||
// public function render() { return view('livewire.ui.mail.dns-health-card'); }
|
||
// public function refresh(): void { $this->load(true); }
|
||
//
|
||
// protected function load(bool $force = false): void
|
||
// {
|
||
// // Werte aus .env
|
||
// $this->ipv4 = trim((string) env('SERVER_PUBLIC_IPV4', '')) ?: null;
|
||
// $this->ipv6 = trim((string) env('SERVER_PUBLIC_IPV6', '')) ?: null;
|
||
//
|
||
// $base = trim((string) env('BASE_DOMAIN', ''));
|
||
// $mta = trim((string) env('MTA_SUB', 'mx'));
|
||
// $host = $base ? "{$mta}.{$base}" : $mta; // z.B. mx.nexlab.at
|
||
// $this->host = $host;
|
||
//
|
||
// // Neuer Cache-Key, damit altes Format keinen Crash verursacht
|
||
// [$calcHost, $calcTlsa, $calcRows] = Cache::remember(
|
||
// 'dash.dnshealth.v2',
|
||
// $force ? 1 : 900,
|
||
// function () use ($host) {
|
||
// // TLSA: nur 1× pro Host prüfen
|
||
// $tlsa = $this->hasTlsa("_25._tcp.{$host}")
|
||
// || $this->hasTlsa("_465._tcp.{$host}")
|
||
// || $this->hasTlsa("_587._tcp.{$host}");
|
||
//
|
||
// // Domains: nur DKIM/DMARC
|
||
// $rows = [];
|
||
// $domains = Domain::query()
|
||
// ->where('is_system', false)
|
||
// ->where('is_active', true)
|
||
// ->get(['domain']);
|
||
//
|
||
// foreach ($domains as $d) {
|
||
// $dom = $d->domain;
|
||
// $dkim = $this->hasTxt("_domainkey.{$dom}");
|
||
// $dmarc = $this->hasTxt("_dmarc.{$dom}");
|
||
// $rows[] = compact('dom','dkim','dmarc');
|
||
// }
|
||
//
|
||
// return [$host, $tlsa, $rows];
|
||
// }
|
||
// );
|
||
//
|
||
// // Defensive: falls mal falsches Datenformat im Cache war
|
||
// if (!is_string($calcHost) || !is_bool($calcTlsa) || !is_array($calcRows)) {
|
||
// Cache::forget('dash.dnshealth.v2');
|
||
// $this->load(true);
|
||
// return;
|
||
// }
|
||
//
|
||
// $this->host = $calcHost;
|
||
// $this->tlsa = $calcTlsa;
|
||
// $this->rows = $calcRows;
|
||
// }
|
||
//
|
||
// protected function hasTxt(string $name): bool
|
||
// {
|
||
// $out = @shell_exec("dig +short TXT " . escapeshellarg($name) . " 2>/dev/null");
|
||
// return is_string($out) && trim($out) !== '';
|
||
// }
|
||
//
|
||
// protected function hasTlsa(string $name): bool
|
||
// {
|
||
// $out = @shell_exec("dig +short TLSA " . escapeshellarg($name) . " 2>/dev/null");
|
||
// return is_string($out) && trim($out) !== '';
|
||
// }
|
||
//}
|
||
|
||
//
|
||
//namespace App\Livewire\Ui\Mail;
|
||
//
|
||
//use Livewire\Component;
|
||
//use App\Models\Domain;
|
||
//use Illuminate\Support\Facades\Cache;
|
||
//
|
||
//class DnsHealthCard extends Component
|
||
//{
|
||
// public array $rows = []; // [ ['domain'=>..., 'dkim'=>bool, 'dmarc'=>bool, 'tlsa'=>bool], ... ]
|
||
//
|
||
// public function mount(): void { $this->load(); }
|
||
// public function render() { return view('livewire.ui.mail.dns-health-card'); }
|
||
// public function refresh(): void { $this->load(true); }
|
||
//
|
||
// protected function load(bool $force=false): void
|
||
// {
|
||
// $this->rows = Cache::remember('dash.dnshealth', $force ? 1 : 600, function () {
|
||
// $rows = [];
|
||
// $domains = Domain::query()->where('is_system', false)->where('is_active', true)->get(['domain']);
|
||
// foreach ($domains as $d) {
|
||
// $dom = $d->domain;
|
||
// $dkim = $this->hasTxt("_domainkey.$dom"); // rough: just any dkim TXT exists
|
||
// $dmarc = $this->hasTxt("_dmarc.$dom");
|
||
// $tlsa = $this->hasTlsa("_25._tcp.$dom") || $this->hasTlsa("_465._tcp.$dom") || $this->hasTlsa("_587._tcp.$dom");
|
||
// $rows[] = compact('dom','dkim','dmarc','tlsa');
|
||
// }
|
||
// return $rows;
|
||
// });
|
||
// }
|
||
//
|
||
// protected function hasTxt(string $name): bool
|
||
// {
|
||
// $out = @shell_exec("dig +short TXT ".escapeshellarg($name)." 2>/dev/null");
|
||
// return is_string($out) && trim($out) !== '';
|
||
// }
|
||
// protected function hasTlsa(string $name): bool
|
||
// {
|
||
// $out = @shell_exec("dig +short TLSA ".escapeshellarg($name)." 2>/dev/null");
|
||
// return is_string($out) && trim($out) !== '';
|
||
// }
|
||
//}
|