panel: show the SPF and DMARC records the domain page expects
The domain page generated and displayed the DKIM record but said only "also configure SPF and DMARC for the domain (see the documentation)". The concrete example existed — buried in the check's remediation text, and only visible once the check had already failed. Show both records up front, host and value with a Copy button, the way the DKIM record is shown, plus the two things that actually bite: a domain may carry only one SPF record (add the mechanism to the existing one rather than publishing a second), and p=none is safe to publish immediately. The SPF value names the addresses SELFPOST_HOSTNAME resolves to, taken from the hostname check the page already runs, and falls back to an "a:" mechanism when it does not resolve. New dnscheck.SPFExample/DMARCExample are the single source for both the page and the checks' advice, so the two cannot drift into recommending different records; dnscheck.Query gains Hostname for the fallback. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
package dnscheck
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SPF and DMARC are the two records SelfPost cannot generate for the operator:
|
||||
// unlike the DKIM record they are policy, not a key, and a domain may already
|
||||
// publish one for other senders. The panel still has to say what "correct"
|
||||
// looks like, and the checks below have to suggest the same thing when a record
|
||||
// is missing — so both take their example from here rather than each spelling
|
||||
// out its own.
|
||||
|
||||
// DMARCRecordName is the name a DMARC record is published at. (SPF has no such
|
||||
// helper: it is published at the domain itself.)
|
||||
func DMARCRecordName(domainName string) string { return "_dmarc." + domainName }
|
||||
|
||||
// SPFExample is the SPF record this server expects for a sending domain: the
|
||||
// addresses its mail actually leaves from, and "-all" to say that nothing else
|
||||
// is authorised. When the server's own addresses are not known (its hostname
|
||||
// does not resolve) it falls back to an "a:" mechanism naming the host, so the
|
||||
// panel always has something concrete to show.
|
||||
func SPFExample(hostname string, serverIPs []string) string {
|
||||
var mechanisms []string
|
||||
for _, s := range serverIPs {
|
||||
ip := net.ParseIP(strings.TrimSpace(s))
|
||||
switch {
|
||||
case ip == nil:
|
||||
continue
|
||||
case ip.To4() != nil:
|
||||
mechanisms = append(mechanisms, "ip4:"+ip.String())
|
||||
default:
|
||||
mechanisms = append(mechanisms, "ip6:"+ip.String())
|
||||
}
|
||||
}
|
||||
if len(mechanisms) == 0 {
|
||||
mechanisms = []string{"a:" + hostname}
|
||||
}
|
||||
return "v=spf1 " + strings.Join(mechanisms, " ") + " -all"
|
||||
}
|
||||
|
||||
// DMARCExample is the least a domain should publish: monitoring only, with an
|
||||
// address the aggregate reports go to. p=none is deliberate — it changes
|
||||
// nothing about delivery, so it is safe to publish before the reports have
|
||||
// shown that DKIM and SPF pass everywhere.
|
||||
func DMARCExample(domainName string) string {
|
||||
return "v=DMARC1; p=none; rua=mailto:dmarc@" + domainName
|
||||
}
|
||||
Reference in New Issue
Block a user