341 lines
12 KiB
PHP
341 lines
12 KiB
PHP
<?php
|
||
|
||
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) !== '';
|
||
// }
|
||
//}
|