Query MX/TXT for subdomains; add host column

Add MX and TXT queries to DnsService probing flows (initial, special, and deep scan) by removing the previous TXT-only conditional and explicitly querying DNS_MX and DNS_TXT. Extend sortRecords to include A, AAAA, MX, TXT, and CAA and sort by root (@) first, then by host, and by priority when present. Update DNS views to show a Host column for MX, TXT, and CAA tables, display @ as "@ (root)", and surface record source badges (manual/imported) next to the host for clarity.
This commit is contained in:
Hosteroid
2026-03-11 14:57:56 +02:00
parent e3006738a9
commit 27f036eee1
2 changed files with 59 additions and 27 deletions

View File

@@ -106,10 +106,9 @@ class DnsService
$this->queryAndCollect($fqdn, DNS_A, 'A', $domain, $records, $seen);
$this->queryAndCollect($fqdn, DNS_AAAA, 'AAAA', $domain, $records, $seen);
$this->queryAndCollect($fqdn, DNS_CNAME, 'CNAME', $domain, $records, $seen);
if (in_array($sub, ['_dmarc', '_mta-sts', '_domainkey']) || str_starts_with($sub, '_')) {
$this->queryAndCollect($fqdn, DNS_MX, 'MX', $domain, $records, $seen);
$this->queryAndCollect($fqdn, DNS_TXT, 'TXT', $domain, $records, $seen);
}
}
foreach (self::SPECIAL_TXT_SUBDOMAINS as $sub) {
$fqdn = "{$sub}.{$domain}";
@@ -147,6 +146,8 @@ class DnsService
$this->queryAndCollect($fqdn, DNS_A, 'A', $domain, $records, $seen);
$this->queryAndCollect($fqdn, DNS_AAAA, 'AAAA', $domain, $records, $seen);
$this->queryAndCollect($fqdn, DNS_CNAME, 'CNAME', $domain, $records, $seen);
$this->queryAndCollect($fqdn, DNS_MX, 'MX', $domain, $records, $seen);
$this->queryAndCollect($fqdn, DNS_TXT, 'TXT', $domain, $records, $seen);
}
foreach (self::SPECIAL_TXT_SUBDOMAINS as $sub) {
@@ -222,7 +223,7 @@ class DnsService
}
$log("Subdomain probe complete: " . count($discovered) . " found out of {$total}");
// Deep scan discovered subdomains (A, AAAA, CNAME, TXT)
// Deep scan discovered subdomains
if (!empty($discovered)) {
$log("Querying " . count($discovered) . " discovered subdomain(s)...");
}
@@ -231,10 +232,9 @@ class DnsService
$this->queryAndCollect($fqdn, DNS_A, 'A', $domain, $records, $seen);
$this->queryAndCollect($fqdn, DNS_AAAA, 'AAAA', $domain, $records, $seen);
$this->queryAndCollect($fqdn, DNS_CNAME, 'CNAME', $domain, $records, $seen);
if (in_array($sub, ['_dmarc', '_mta-sts', '_domainkey']) || str_starts_with($sub, '_')) {
$this->queryAndCollect($fqdn, DNS_MX, 'MX', $domain, $records, $seen);
$this->queryAndCollect($fqdn, DNS_TXT, 'TXT', $domain, $records, $seen);
}
}
$log("Querying special TXT subdomains...");
foreach (self::SPECIAL_TXT_SUBDOMAINS as $sub) {
@@ -452,15 +452,20 @@ class DnsService
}
/**
* Sort A/AAAA records: root (@) first, then alphabetical by host.
* Sort records: root (@) first, then alphabetical by host.
*/
private function sortRecords(array &$records): void
{
foreach (['A', 'AAAA'] as $type) {
foreach (['A', 'AAAA', 'MX', 'TXT', 'CAA'] as $type) {
if (empty($records[$type])) {
continue;
}
usort($records[$type], function ($a, $b) {
if ($a['host'] === '@') return -1;
if ($b['host'] === '@') return 1;
return strcmp($a['host'], $b['host']);
if ($a['host'] === '@' && $b['host'] !== '@') return -1;
if ($b['host'] === '@' && $a['host'] !== '@') return 1;
$hostCmp = strcmp($a['host'], $b['host']);
if ($hostCmp !== 0) return $hostCmp;
return ($a['priority'] ?? 0) <=> ($b['priority'] ?? 0);
});
}
}

View File

@@ -408,6 +408,7 @@
<thead class="bg-gray-50 dark:bg-slate-900">
<tr>
<th class="w-8 px-2 py-2"><input type="checkbox" class="dns-select-all rounded border-gray-300 dark:border-slate-600" data-type="MX"></th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Host</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Priority</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Mail Server</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">TTL</th>
@@ -418,13 +419,21 @@
{% for record in dnsRecords['MX'] %}
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700">
<td class="w-8 px-2 py-2"><input type="checkbox" class="dns-record-cb rounded border-gray-300 dark:border-slate-600" value="{{ record.id }}"></td>
<td class="px-4 py-2">
<span class="inline-flex items-center justify-center w-6 h-6 bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400 font-bold rounded-full text-xs">{{ record.priority }}</span>{% if record.source|default('discovered') == 'manual' %}
<td class="px-4 py-2 text-xs font-medium text-gray-900 dark:text-white">
{% if record.host == '@' %}
<span class="text-green-600 dark:text-green-400">@ (root)</span>
{% else %}
{{ record.host }}
{% endif %}
{% if record.source|default('discovered') == 'manual' %}
<span class="ml-1 px-1 py-0.5 bg-blue-100 dark:bg-blue-500/10 text-blue-700 dark:text-blue-400 text-xs rounded font-medium" style="font-size: 9px;">manual</span>
{% elseif record.source|default('discovered') == 'imported' %}
<span class="ml-1 px-1 py-0.5 bg-purple-100 dark:bg-purple-500/10 text-purple-700 dark:text-purple-400 text-xs rounded font-medium" style="font-size: 9px;">imported</span>
{% endif %}
</td>
<td class="px-4 py-2">
<span class="inline-flex items-center justify-center w-6 h-6 bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400 font-bold rounded-full text-xs">{{ record.priority }}</span>
</td>
<td class="px-4 py-2 text-xs font-mono text-gray-900 dark:text-white">{{ record.value }}</td>
<td class="px-4 py-2 text-xs text-gray-600 dark:text-slate-400">{{ record.ttl }}s</td>
<td class="w-8 px-2 py-2">
@@ -458,6 +467,7 @@
<thead class="bg-gray-50 dark:bg-slate-900">
<tr>
<th class="w-8 px-2 py-2"><input type="checkbox" class="dns-select-all rounded border-gray-300 dark:border-slate-600" data-type="TXT"></th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Host</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Type</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Value</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">TTL</th>
@@ -484,13 +494,21 @@
{% endif %}
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700">
<td class="w-8 px-2 py-2"><input type="checkbox" class="dns-record-cb rounded border-gray-300 dark:border-slate-600" value="{{ record.id }}"></td>
<td class="px-4 py-2">
<span class="inline-flex items-center px-1.5 py-0.5 bg-purple-100 dark:bg-purple-500/10 text-purple-800 dark:text-purple-400 text-xs font-semibold rounded">{{ txtType }}</span>{% if record.source|default('discovered') == 'manual' %}
<td class="px-4 py-2 text-xs font-medium text-gray-900 dark:text-white">
{% if record.host == '@' %}
<span class="text-purple-600 dark:text-purple-400">@ (root)</span>
{% else %}
{{ record.host }}
{% endif %}
{% if record.source|default('discovered') == 'manual' %}
<span class="ml-1 px-1 py-0.5 bg-blue-100 dark:bg-blue-500/10 text-blue-700 dark:text-blue-400 text-xs rounded font-medium" style="font-size: 9px;">manual</span>
{% elseif record.source|default('discovered') == 'imported' %}
<span class="ml-1 px-1 py-0.5 bg-purple-100 dark:bg-purple-500/10 text-purple-700 dark:text-purple-400 text-xs rounded font-medium" style="font-size: 9px;">imported</span>
{% endif %}
</td>
<td class="px-4 py-2">
<span class="inline-flex items-center px-1.5 py-0.5 bg-purple-100 dark:bg-purple-500/10 text-purple-800 dark:text-purple-400 text-xs font-semibold rounded">{{ txtType }}</span>
</td>
<td class="px-4 py-2 text-xs font-mono text-gray-900 dark:text-white break-all">{{ record.value }}</td>
<td class="px-4 py-2 text-xs text-gray-600 dark:text-slate-400">{{ record.ttl }}s</td>
<td class="w-8 px-2 py-2">
@@ -643,6 +661,7 @@
<thead class="bg-gray-50 dark:bg-slate-900">
<tr>
<th class="w-8 px-2 py-2"><input type="checkbox" class="dns-select-all rounded border-gray-300 dark:border-slate-600" data-type="CAA"></th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Host</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Tag</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Value (CA)</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Flags</th>
@@ -655,13 +674,21 @@
{% set rawData = record.raw_data ? record.raw_data|from_json : {} %}
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700">
<td class="w-8 px-2 py-2"><input type="checkbox" class="dns-record-cb rounded border-gray-300 dark:border-slate-600" value="{{ record.id }}"></td>
<td class="px-4 py-2">
<span class="inline-flex items-center px-1.5 py-0.5 bg-orange-100 dark:bg-orange-500/10 text-orange-800 dark:text-orange-400 text-xs font-semibold rounded">{{ rawData.tag|default('-') }}</span>{% if record.source|default('discovered') == 'manual' %}
<td class="px-4 py-2 text-xs font-medium text-gray-900 dark:text-white">
{% if record.host == '@' %}
<span class="text-orange-600 dark:text-orange-400">@ (root)</span>
{% else %}
{{ record.host }}
{% endif %}
{% if record.source|default('discovered') == 'manual' %}
<span class="ml-1 px-1 py-0.5 bg-blue-100 dark:bg-blue-500/10 text-blue-700 dark:text-blue-400 text-xs rounded font-medium" style="font-size: 9px;">manual</span>
{% elseif record.source|default('discovered') == 'imported' %}
<span class="ml-1 px-1 py-0.5 bg-purple-100 dark:bg-purple-500/10 text-purple-700 dark:text-purple-400 text-xs rounded font-medium" style="font-size: 9px;">imported</span>
{% endif %}
</td>
<td class="px-4 py-2">
<span class="inline-flex items-center px-1.5 py-0.5 bg-orange-100 dark:bg-orange-500/10 text-orange-800 dark:text-orange-400 text-xs font-semibold rounded">{{ rawData.tag|default('-') }}</span>
</td>
<td class="px-4 py-2 text-xs font-mono text-gray-900 dark:text-white">{{ rawData.value|default(record.value) }}</td>
<td class="px-4 py-2 text-xs text-gray-600 dark:text-slate-400">{{ rawData.flags|default('0') }}</td>
<td class="px-4 py-2 text-xs text-gray-600 dark:text-slate-400">{{ record.ttl }}s</td>