mirror of
https://github.com/mjl-/mox.git
synced 2025-07-12 18:24:35 +03:00
accept incoming DMARC and TLS reports with reporting addresses containing catchall separator(s)
Such as "-" when addresses are dmarc-reports@ and tls-reports@. Existing configuration files can have these combinations. We don't allow them to be created through the webadmin interface, as this is a likely source of confusion about how addresses will be matched. We already didn't allow regular addresses containing catchall separators.
This commit is contained in:
@ -2535,6 +2535,23 @@ func (Admin) DomainClientSettingsDomainSave(ctx context.Context, domainName, cli
|
||||
// settings for a domain.
|
||||
func (Admin) DomainLocalpartConfigSave(ctx context.Context, domainName string, localpartCatchallSeparators []string, localpartCaseSensitive bool) {
|
||||
err := admin.DomainSave(ctx, domainName, func(domain *config.Domain) error {
|
||||
// We don't allow introducing new catchall separators that are used in DMARC/TLS
|
||||
// reporting. Can occur in existing configs for backwards compatibility.
|
||||
containsSep := func(seps []string) bool {
|
||||
for _, sep := range seps {
|
||||
if domain.DMARC != nil && strings.Contains(domain.DMARC.Localpart, sep) {
|
||||
return true
|
||||
}
|
||||
if domain.TLSRPT != nil && strings.Contains(domain.TLSRPT.Localpart, sep) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if !containsSep(domain.LocalpartCatchallSeparatorsEffective) && containsSep(localpartCatchallSeparators) {
|
||||
xusererrorf(ctx, "cannot add localpart catchall separators that are used in dmarc and/or tls reporting addresses, change reporting addresses first")
|
||||
}
|
||||
|
||||
domain.LocalpartCatchallSeparatorsEffective = localpartCatchallSeparators
|
||||
// If there is a single separator, we prefer the non-list form, it's easier to
|
||||
// read/edit and should suffice for most setups.
|
||||
@ -2557,6 +2574,17 @@ func (Admin) DomainLocalpartConfigSave(ctx context.Context, domainName string, l
|
||||
// disabled.
|
||||
func (Admin) DomainDMARCAddressSave(ctx context.Context, domainName, localpart, domain, account, mailbox string) {
|
||||
err := admin.DomainSave(ctx, domainName, func(d *config.Domain) error {
|
||||
// DMARC reporting addresses can contain the localpart catchall separator(s) for
|
||||
// backwards compability (hence not enforced when parsing the config files), but we
|
||||
// don't allow creating them.
|
||||
if d.DMARC == nil || d.DMARC.Localpart != localpart {
|
||||
for _, sep := range d.LocalpartCatchallSeparatorsEffective {
|
||||
if strings.Contains(localpart, sep) {
|
||||
xusererrorf(ctx, "dmarc reporting address cannot contain catchall separator %q in localpart (%q)", sep, localpart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if localpart == "" {
|
||||
d.DMARC = nil
|
||||
} else {
|
||||
@ -2577,6 +2605,17 @@ func (Admin) DomainDMARCAddressSave(ctx context.Context, domainName, localpart,
|
||||
// disabled.
|
||||
func (Admin) DomainTLSRPTAddressSave(ctx context.Context, domainName, localpart, domain, account, mailbox string) {
|
||||
err := admin.DomainSave(ctx, domainName, func(d *config.Domain) error {
|
||||
// TLS reporting addresses can contain the localpart catchall separator(s) for
|
||||
// backwards compability (hence not enforced when parsing the config files), but we
|
||||
// don't allow creating them.
|
||||
if d.TLSRPT == nil || d.TLSRPT.Localpart != localpart {
|
||||
for _, sep := range d.LocalpartCatchallSeparatorsEffective {
|
||||
if strings.Contains(localpart, sep) {
|
||||
xusererrorf(ctx, "tls reporting address cannot contain catchall separator %q in localpart (%q)", sep, localpart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if localpart == "" {
|
||||
d.TLSRPT = nil
|
||||
} else {
|
||||
|
@ -279,17 +279,25 @@ func TestAdmin(t *testing.T) {
|
||||
|
||||
api.DomainLocalpartConfigSave(ctxbg, "mox.example", []string{"-"}, true)
|
||||
tneedErrorCode(t, "user:error", func() { api.DomainLocalpartConfigSave(ctxbg, "bogus.example", nil, false) })
|
||||
api.DomainLocalpartConfigSave(ctxbg, "mox.example", nil, false) // Restore.
|
||||
|
||||
api.DomainDMARCAddressSave(ctxbg, "mox.example", "dmarcreports", "", "mjl", "DMARC")
|
||||
api.DomainDMARCAddressSave(ctxbg, "mox.example", "dmarc+reports", "", "mjl", "DMARC")
|
||||
// Catchall separator, bad domain, bad account.
|
||||
tneedErrorCode(t, "user:error", func() { api.DomainDMARCAddressSave(ctxbg, "mox.example", "dmarc-reports", "", "mjl", "DMARC") })
|
||||
tneedErrorCode(t, "user:error", func() { api.DomainDMARCAddressSave(ctxbg, "bogus.example", "dmarcreports", "", "mjl", "DMARC") })
|
||||
tneedErrorCode(t, "user:error", func() { api.DomainDMARCAddressSave(ctxbg, "mox.example", "dmarcreports", "", "bogus", "DMARC") })
|
||||
api.DomainDMARCAddressSave(ctxbg, "mox.example", "", "", "", "") // Restore.
|
||||
|
||||
api.DomainTLSRPTAddressSave(ctxbg, "mox.example", "tlsreports", "", "mjl", "TLSRPT")
|
||||
api.DomainTLSRPTAddressSave(ctxbg, "mox.example", "tls+reports", "", "mjl", "TLSRPT")
|
||||
// Catchall separator, bad domain, bad account.
|
||||
tneedErrorCode(t, "user:error", func() { api.DomainTLSRPTAddressSave(ctxbg, "mox.example", "tls-reports", "", "mjl", "TLSRPT") })
|
||||
tneedErrorCode(t, "user:error", func() { api.DomainTLSRPTAddressSave(ctxbg, "bogus.example", "tlsreports", "", "mjl", "TLSRPT") })
|
||||
tneedErrorCode(t, "user:error", func() { api.DomainTLSRPTAddressSave(ctxbg, "mox.example", "tlsreports", "", "bogus", "TLSRPT") })
|
||||
|
||||
// DMARC/TLS reporting addresses contain separator.
|
||||
tneedErrorCode(t, "user:error", func() { api.DomainLocalpartConfigSave(ctxbg, "mox.example", []string{"+"}, true) })
|
||||
|
||||
api.DomainDMARCAddressSave(ctxbg, "mox.example", "", "", "", "") // Restore.
|
||||
api.DomainTLSRPTAddressSave(ctxbg, "mox.example", "", "", "", "") // Restore.
|
||||
api.DomainLocalpartConfigSave(ctxbg, "mox.example", nil, false) // Restore.
|
||||
|
||||
// todo: cannot enable mta-sts because we have no listener, which would require a tls cert for the domain.
|
||||
// api.DomainMTASTSSave(ctxbg, "mox.example", "id0", mtasts.ModeEnforce, time.Hour, []string{"mail.mox.example"})
|
||||
|
@ -3693,7 +3693,7 @@
|
||||
},
|
||||
{
|
||||
"Name": "ParsedLocalpart",
|
||||
"Docs": "",
|
||||
"Docs": "Lower-case if case-sensitivity is not configured for domain. Not \"canonical\" for catchall separators for backwards compatibility.",
|
||||
"Typewords": [
|
||||
"Localpart"
|
||||
]
|
||||
@ -3776,7 +3776,7 @@
|
||||
},
|
||||
{
|
||||
"Name": "ParsedLocalpart",
|
||||
"Docs": "",
|
||||
"Docs": "Lower-case if case-sensitivity is not configured for domain. Not \"canonical\" for catchall separators for backwards compatibility.",
|
||||
"Typewords": [
|
||||
"Localpart"
|
||||
]
|
||||
|
@ -309,7 +309,7 @@ export interface DMARC {
|
||||
Domain: string
|
||||
Account: string
|
||||
Mailbox: string
|
||||
ParsedLocalpart: Localpart
|
||||
ParsedLocalpart: Localpart // Lower-case if case-sensitivity is not configured for domain. Not "canonical" for catchall separators for backwards compatibility.
|
||||
DNSDomain: Domain // Effective domain, always set based on Domain field or Domain where this is configured.
|
||||
}
|
||||
|
||||
@ -325,7 +325,7 @@ export interface TLSRPT {
|
||||
Domain: string
|
||||
Account: string
|
||||
Mailbox: string
|
||||
ParsedLocalpart: Localpart
|
||||
ParsedLocalpart: Localpart // Lower-case if case-sensitivity is not configured for domain. Not "canonical" for catchall separators for backwards compatibility.
|
||||
DNSDomain: Domain // Effective domain, always set based on Domain field or Domain where this is configured.
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user