Add reverse lookup and forward confirmation

This commit is contained in:
nemunaire 2025-10-23 15:59:57 +07:00
commit 84a504d668
8 changed files with 324 additions and 5 deletions

View file

@ -1,5 +1,5 @@
<script lang="ts">
import type { DNSResults } from "$lib/api/types.gen";
import type { DNSResults, ReceivedHop } from "$lib/api/types.gen";
import { getScoreColorClass } from "$lib/score";
import GradeDisplay from "./GradeDisplay.svelte";
import MxRecordsDisplay from "./MxRecordsDisplay.svelte";
@ -7,14 +7,22 @@
import DkimRecordsDisplay from "./DkimRecordsDisplay.svelte";
import DmarcRecordDisplay from "./DmarcRecordDisplay.svelte";
import BimiRecordDisplay from "./BimiRecordDisplay.svelte";
import PtrRecordsDisplay from "./PtrRecordsDisplay.svelte";
import PtrForwardRecordsDisplay from "./PtrForwardRecordsDisplay.svelte";
interface Props {
dnsResults?: DNSResults;
dnsGrade?: string;
dnsScore?: number;
receivedChain?: ReceivedHop[];
}
let { dnsResults, dnsGrade, dnsScore }: Props = $props();
let { dnsResults, dnsGrade, dnsScore, receivedChain }: Props = $props();
// Extract sender IP from first hop
const senderIp = $derived(
receivedChain && receivedChain.length > 0 ? receivedChain[0].ip : undefined,
);
</script>
<div class="card shadow-sm">
@ -51,6 +59,27 @@
</div>
{/if}
<!-- Reverse IP Section -->
{#if receivedChain && receivedChain.length > 0}
<div class="mb-3 d-flex align-items-center gap-2">
<h4 class="mb-0">
Received by: <code>{receivedChain[0].from} ({receivedChain[0].reverse || "Unknown"} [{receivedChain[0].ip}])</code>
</h4>
</div>
{/if}
<!-- PTR Records Section -->
<PtrRecordsDisplay ptrRecords={dnsResults.ptr_records} {senderIp} />
<!-- Forward-Confirmed Reverse DNS -->
<PtrForwardRecordsDisplay
ptrRecords={dnsResults.ptr_records}
ptrForwardRecords={dnsResults.ptr_forward_records}
{senderIp}
/>
<hr class="my-4" />
<!-- Return-Path Domain Section -->
<div class="mb-3 d-flex align-items-center gap-2">
<h4 class="mb-0">

View file

@ -0,0 +1,103 @@
<script lang="ts">
interface Props {
ptrRecords?: string[];
ptrForwardRecords?: string[];
senderIp?: string;
}
let { ptrRecords, ptrForwardRecords, senderIp }: Props = $props();
// Forward-confirmed reverse DNS is valid if:
// 1. PTR records exist
// 2. Forward records exist
// 3. At least one forward record matches the original sender IP
const fcrDnsIsValid = $derived(
ptrRecords &&
ptrRecords.length > 0 &&
ptrForwardRecords &&
ptrForwardRecords.length > 0 &&
senderIp &&
ptrForwardRecords.includes(senderIp),
);
const hasForwardRecords = $derived(ptrForwardRecords && ptrForwardRecords.length > 0);
</script>
{#if ptrRecords && ptrRecords.length > 0}
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="text-muted mb-0">
<i
class="bi"
class:bi-check-circle-fill={fcrDnsIsValid}
class:text-success={fcrDnsIsValid}
class:bi-x-circle-fill={!fcrDnsIsValid}
class:text-danger={!fcrDnsIsValid}
></i>
Forward-Confirmed Reverse DNS
</h5>
<span class="badge bg-secondary">FCrDNS</span>
</div>
<div class="card-body">
<p class="card-text small text-muted mb-0">
Forward-confirmed reverse DNS (FCrDNS) verifies that the PTR hostname resolves back
to the original sender IP. This double-check helps establish sender legitimacy.
</p>
{#if senderIp}
<div class="mt-2">
<strong>Original Sender IP:</strong> <code>{senderIp}</code>
</div>
{/if}
</div>
{#if hasForwardRecords}
<div class="list-group list-group-flush">
<div class="list-group-item">
<div class="mb-2">
<strong>PTR Hostname(s):</strong>
{#each ptrRecords as ptr}
<div class="mt-1">
<code>{ptr}</code>
</div>
{/each}
</div>
<div class="mb-2">
<strong>Forward Resolution (A/AAAA):</strong>
{#each ptrForwardRecords as ip}
<div class="d-flex gap-2 align-items-center mt-1">
{#if senderIp && ip === senderIp}
<span class="badge bg-success">Match</span>
{:else}
<span class="badge bg-warning">Different</span>
{/if}
<code>{ip}</code>
</div>
{/each}
</div>
{#if fcrDnsIsValid}
<div class="alert alert-success mb-0 mt-2">
<i class="bi bi-check-circle me-1"></i>
<strong>Success:</strong> Forward-confirmed reverse DNS is properly configured.
The PTR hostname resolves back to the sender IP.
</div>
{:else}
<div class="alert alert-warning mb-0 mt-2">
<i class="bi bi-exclamation-triangle me-1"></i>
<strong>Warning:</strong> The PTR hostname does not resolve back to the sender
IP. This may impact deliverability.
</div>
{/if}
</div>
</div>
{:else}
<div class="list-group list-group-flush">
<div class="list-group-item">
<div class="alert alert-danger mb-0">
<i class="bi bi-x-circle me-1"></i>
<strong>Error:</strong> PTR hostname(s) found but could not resolve to any IP
addresses. Check your DNS configuration.
</div>
</div>
</div>
{/if}
</div>
{/if}

View file

@ -0,0 +1,85 @@
<script lang="ts">
interface Props {
ptrRecords?: string[];
senderIp?: string;
}
let { ptrRecords, senderIp }: Props = $props();
// PTR records are valid if at least one exists
const ptrIsValid = $derived(ptrRecords && ptrRecords.length > 0);
</script>
{#if ptrRecords && ptrRecords.length > 0}
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="text-muted mb-0">
<i
class="bi"
class:bi-check-circle-fill={ptrIsValid}
class:text-success={ptrIsValid}
class:bi-x-circle-fill={!ptrIsValid}
class:text-danger={!ptrIsValid}
></i>
Reverse DNS
</h5>
<span class="badge bg-secondary">PTR</span>
</div>
<div class="card-body">
<p class="card-text small text-muted mb-0">
PTR records (reverse DNS) map IP addresses back to hostnames. Having proper PTR
records is important as many mail servers verify that the sending IP has a valid
reverse DNS entry.
</p>
{#if senderIp}
<div class="mt-2">
<strong>Sender IP:</strong> <code>{senderIp}</code>
</div>
{/if}
</div>
<div class="list-group list-group-flush">
{#each ptrRecords as ptr}
<div class="list-group-item">
<div class="d-flex gap-2 align-items-center">
<span class="badge bg-success">Found</span>
<code>{ptr}</code>
</div>
</div>
{/each}
{#if ptrRecords.length > 1}
<div class="list-group-item">
<div class="alert alert-warning mb-0">
<i class="bi bi-exclamation-triangle me-1"></i>
<strong>Warning:</strong> Multiple PTR records found. While not strictly an error,
having multiple PTR records can cause issues with some mail servers. It's recommended
to have exactly one PTR record per IP address.
</div>
</div>
{/if}
</div>
</div>
{:else if senderIp}
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="text-muted mb-2">
<i class="bi bi-x-circle-fill text-danger"></i>
Reverse DNS (PTR)
</h5>
<span class="badge bg-secondary">PTR</span>
</div>
<div class="card-body">
<p class="card-text small text-muted mb-0">
PTR records (reverse DNS) map IP addresses back to hostnames. Having proper PTR
records is important for email deliverability.
</p>
<div class="mt-2">
<strong>Sender IP:</strong> <code>{senderIp}</code>
</div>
<div class="alert alert-danger mb-0 mt-2">
<i class="bi bi-x-circle me-1"></i>
<strong>Error:</strong> No PTR records found for the sender IP. Contact your email service
provider to configure reverse DNS.
</div>
</div>
</div>
{/if}

View file

@ -10,3 +10,5 @@ export { default as DnsRecordsCard } from "./DnsRecordsCard.svelte";
export { default as BlacklistCard } from "./BlacklistCard.svelte";
export { default as ContentAnalysisCard } from "./ContentAnalysisCard.svelte";
export { default as HeaderAnalysisCard } from "./HeaderAnalysisCard.svelte";
export { default as PtrRecordsDisplay } from "./PtrRecordsDisplay.svelte";
export { default as PtrForwardRecordsDisplay } from "./PtrForwardRecordsDisplay.svelte";

View file

@ -146,6 +146,7 @@
dnsResults={report.dns_results}
dnsGrade={report.summary?.dns_grade}
dnsScore={report.summary?.dns_score}
receivedChain={report.header_analysis?.received_chain}
/>
</div>
</div>