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:
@@ -105,6 +105,32 @@ func (c *Checker) checkDMARC(ctx context.Context, q Query) Result {
|
||||
return Result{Status: health.StatusOK, Detail: detail, Records: records}
|
||||
}
|
||||
|
||||
// checkServerDMARC checks the DMARC record of the server's own name. It is a
|
||||
// sending domain's check held to more: a record missing is an error, as a
|
||||
// missing DKIM or SPF record of this name is, and p=none, which refuses
|
||||
// nothing, a warning - either leaves forged mail in the server's name
|
||||
// deliverable, and that name sends nothing a stricter policy could hurt
|
||||
// (ServerMailDMARC). quarantine is as good as reject here.
|
||||
func (c *Checker) checkServerDMARC(ctx context.Context, name string) Result {
|
||||
r := c.checkDMARC(ctx, Query{Name: name})
|
||||
recordName := DMARCRecordName(name)
|
||||
switch {
|
||||
case r.Status == health.StatusWarn && len(r.Records) == 0:
|
||||
// Missing, as a missing DKIM or SPF record of this name is: the page
|
||||
// draws it as a warning all the same (notifications still go).
|
||||
r.Status = health.StatusError
|
||||
r.Detail = fmt.Sprintf("No DMARC record at %s. Publish the TXT record %q - without it a receiver does not refuse mail forged in this server's name.", recordName, ServerMailDMARC)
|
||||
case r.Status == health.StatusOK:
|
||||
if policy, _ := tagValue(r.Records[0], "p"); strings.EqualFold(policy, "none") {
|
||||
r.Status = health.StatusWarn
|
||||
r.Detail = fmt.Sprintf("Published with p=none, which refuses nothing: mail forged in this server's name is still delivered. Publish %q - only the panel sends as this name, signed.", ServerMailDMARC)
|
||||
} else {
|
||||
r.Detail = fmt.Sprintf("Published with policy p=%s.", policy)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// checkReportAuth verifies the hub domain publishes _report._dmarc for external
|
||||
// aggregate-report destinations. Missing authorisation does not affect outbound
|
||||
// delivery, only whether reports reach the rua= mailbox.
|
||||
|
||||
Reference in New Issue
Block a user