dns: add HELO/PTR consistency check

Compare the HELO/EHLO hostname announced by the sending server (first
Received hop) against the sender IP's PTR records, surfacing the same
signal as x-ptr/policy.ptr in Authentication-Results. Adds helo_hostname
and helo_ptr_match to DNSResults, applies a 15-point PTR sub-score
penalty on mismatch, and displays the result in a new HELO/PTR
Consistency card.
This commit is contained in:
nemunaire 2026-06-06 13:27:35 +09:00
commit e168446b44
10 changed files with 460 additions and 0 deletions

View file

@ -23,6 +23,7 @@ package analyzer
import (
"context"
"strings"
"git.happydns.org/happyDeliver/internal/model"
)
@ -62,6 +63,21 @@ func (d *DNSAnalyzer) checkPTRAndForward(ip string) ([]string, []string) {
return ptrNames, forwardIPs
}
// checkHeloPtrMatch reports whether the announced HELO hostname matches one of
// the sender's PTR records (case-insensitive, trailing dot ignored).
func checkHeloPtrMatch(helo string, ptrRecords []string) bool {
helo = strings.TrimSuffix(strings.ToLower(strings.TrimSpace(helo)), ".")
if helo == "" {
return false
}
for _, ptr := range ptrRecords {
if strings.TrimSuffix(strings.ToLower(ptr), ".") == helo {
return true
}
}
return false
}
// Proper reverse DNS (PTR) and forward-confirmed reverse DNS (FCrDNS) is important for deliverability
func (d *DNSAnalyzer) calculatePTRScore(results *model.DNSResults, senderIP string) (score int) {
if results.PtrRecords != nil && len(*results.PtrRecords) > 0 {
@ -73,6 +89,11 @@ func (d *DNSAnalyzer) calculatePTRScore(results *model.DNSResults, senderIP stri
score -= 15
}
// Penalty when the announced HELO name doesn't match the PTR hostname
if results.HeloPtrMatch != nil && !*results.HeloPtrMatch {
score -= 15
}
// Additional 50 points for forward-confirmed reverse DNS (FCrDNS)
// This means the PTR hostname resolves back to IPs that include the original sender IP
if results.PtrForwardRecords != nil && len(*results.PtrForwardRecords) > 0 && senderIP != "" {