server-dmarc-hint: the server's own name gets a DMARC record
Owner, 2026-10-18: "Оба предложения делаем"; the mockup is 8b2dc8f. The
security audit's S3 left a product side: the panel showed the DKIM and SPF
records of the server's own name, not a DMARC one, and without it a
receiver does not refuse mail forged in that name - a false password-reset
message among it.
dnscheck.ServerMail checks a third record (Mail.DMARC, checkServerDMARC):
the sending-domain DMARC check held to more - missing is an error, as a
missing DKIM or SPF record of this name is (the page draws it as a
warning: notifications still go), p=none a warning ("weak": it refuses
nothing), quarantine or reject ok - and Overall includes it. The record to
publish is ServerMailDMARC, v=DMARC1; p=reject: only the panel sends as
the name, signed with its key, from the address its SPF names, so reject
costs nothing. Settings › Notification mail draws it after SPF
(view.ServerMailDMARC, folded when published), Health's card has the row,
Re-check DNS re-reads it with the others.
Tests: TestServerMailDMARC (missing, p=none, quarantine, reject); the
Settings and Health tests publish and expect three records. Help word for
word with the mockup; guide § Notifications; roadmap; CHANGELOG.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -139,6 +139,7 @@ func healthFixture(refresh bool) *Health {
|
||||
h.WithNotificationMail("postmaster@mail.example.org", []MailRow{
|
||||
NewMailRow("DKIM", mailFixtureDKIMHost, DNSCheck{Status: "ok"}),
|
||||
NewMailRow("SPF", "mail.example.org", DNSCheck{Status: "error"}),
|
||||
NewMailRow("DMARC", "_dmarc.mail.example.org", DNSCheck{Status: "warn"}),
|
||||
}, "warn")
|
||||
return h
|
||||
}
|
||||
@@ -171,6 +172,7 @@ func settingsFixture() *Settings {
|
||||
WithMail([]Record{
|
||||
ServerMailDKIM("sp2026", mailFixtureDKIMHost, key, DNSCheck{Status: "ok"}),
|
||||
ServerMailSPF("mail.example.org", "v=spf1 a -all", DNSCheck{Status: "error", Detail: "No SPF record is published for mail.example.org."}),
|
||||
ServerMailDMARC("_dmarc.mail.example.org", "v=DMARC1; p=reject", DNSCheck{Status: "warn", Detail: "No DMARC record at _dmarc.mail.example.org."}),
|
||||
}, "3 min ago").
|
||||
WithTimeZone("Europe/Moscow", msk, time.Date(2026, 10, 17, 12, 0, 0, 0, time.UTC))
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ var serverTopics = []HelpTopic{
|
||||
Br(), Br(),
|
||||
"Each user turns notifications on or off under Account and needs an e-mail there; Server › Settings turns them off for everyone. They are the panel's own mail: not rows of the Outbound log, and not counted against any limit.",
|
||||
Br(), Br(),
|
||||
"For that mail to arrive, the server's own name needs two DNS records of its own, as each of your domains does: a DKIM key, which the panel generates and signs notifications with, and an SPF record, ", Code("v=spf1 a -all"), ". Server › Settings gives both with the values to publish and whether they are in place, and its Re-check DNS checks them again once they are published; Server › Health shows them too. Without them notifications are still sent, and a receiver may file them as spam. Mail of your domains does not depend on either."),
|
||||
"For that mail to arrive, the server's own name needs DNS records of its own, as each of your domains does: a DKIM key, which the panel generates and signs notifications with, an SPF record, ", Code("v=spf1 a -all"), ", and a DMARC record, ", Code("v=DMARC1; p=reject"), ", which has receivers refuse mail forged in the server's name - a false password-reset message among it; only the panel sends as this name, signed, so reject is safe. Server › Settings gives all three with the values to publish and whether they are in place, and its Re-check DNS checks them again once they are published; Server › Health shows them too. Without them notifications are still sent, and a receiver may file them as spam. Mail of your domains depends on none of them."),
|
||||
}},
|
||||
}
|
||||
|
||||
|
||||
@@ -213,6 +213,21 @@ func ServerMailSPF(host, value string, c DNSCheck) Record {
|
||||
return r
|
||||
}
|
||||
|
||||
// ServerMailDMARC is the DMARC record of the server's own name
|
||||
// (server-dmarc-hint). While it is not in place the note under the value says
|
||||
// what it is for and why its strict policy is safe.
|
||||
func ServerMailDMARC(host, value string, c DNSCheck) Record {
|
||||
r := Record{Name: "DMARC", Note: Plain("For the server's own name, not for your domains"),
|
||||
Host: host, Type: "TXT", Value: value}
|
||||
var standing Text
|
||||
if c.Status != "ok" {
|
||||
standing = Plain("Without it a receiver does not refuse mail forged in the server's name - a false password-reset message included. Only the panel sends as this name, signed, so reject is safe.")
|
||||
}
|
||||
applyCheck(&r, c, standing, nil)
|
||||
r.Status = mailTag(c)
|
||||
return r
|
||||
}
|
||||
|
||||
// DMARCRecordInput is what a DMARC record needs besides its check: where the
|
||||
// reports go in words (Source: the address, or "no reports") and the page that changes it,
|
||||
// the page of the reports themselves ("" when the panel has none), and whether
|
||||
|
||||
Reference in New Issue
Block a user