mailwolt/app/Livewire/Ui/Mail/DnsHealthCard.php

341 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?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) !== '';
// }
//}