mirror of
https://github.com/mjl-/mox.git
synced 2025-07-14 16:14:36 +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:
@ -1747,6 +1747,8 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
|
||||
if _, ok := c.Accounts[dmarc.Account]; !ok {
|
||||
addDomainErrorf("DMARC account %q does not exist", dmarc.Account)
|
||||
}
|
||||
|
||||
// Note: For backwards compabilitiy, DMARC reporting localparts can contain catchall separators.
|
||||
lp, err := smtp.ParseLocalpart(dmarc.Localpart)
|
||||
if err != nil {
|
||||
addDomainErrorf("invalid DMARC localpart %q: %s", dmarc.Localpart, err)
|
||||
@ -1760,9 +1762,13 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
|
||||
addrdom, err = dns.ParseDomain(dmarc.Domain)
|
||||
if err != nil {
|
||||
addDomainErrorf("DMARC domain %q: %s", dmarc.Domain, err)
|
||||
} else if _, ok := c.Domains[addrdom.Name()]; !ok {
|
||||
} else if adomain, ok := c.Domains[addrdom.Name()]; !ok {
|
||||
addDomainErrorf("unknown domain %q for DMARC address", addrdom)
|
||||
} else if !adomain.LocalpartCaseSensitive {
|
||||
lp = smtp.Localpart(strings.ToLower(string(lp)))
|
||||
}
|
||||
} else if !domain.LocalpartCaseSensitive {
|
||||
lp = smtp.Localpart(strings.ToLower(string(lp)))
|
||||
}
|
||||
if addrdom == domain.Domain {
|
||||
domainHasAddress[addrdom.Name()] = true
|
||||
@ -1793,6 +1799,8 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
|
||||
if _, ok := c.Accounts[tlsrpt.Account]; !ok {
|
||||
addDomainErrorf("TLSRPT account %q does not exist", tlsrpt.Account)
|
||||
}
|
||||
|
||||
// Note: For backwards compabilitiy, TLS reporting localparts can contain catchall separators.
|
||||
lp, err := smtp.ParseLocalpart(tlsrpt.Localpart)
|
||||
if err != nil {
|
||||
addDomainErrorf("invalid TLSRPT localpart %q: %s", tlsrpt.Localpart, err)
|
||||
@ -1807,9 +1815,13 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
|
||||
addrdom, err = dns.ParseDomain(tlsrpt.Domain)
|
||||
if err != nil {
|
||||
addDomainErrorf("TLSRPT domain %q: %s", tlsrpt.Domain, err)
|
||||
} else if _, ok := c.Domains[addrdom.Name()]; !ok {
|
||||
} else if adomain, ok := c.Domains[addrdom.Name()]; !ok {
|
||||
addDomainErrorf("unknown domain %q for TLSRPT address", tlsrpt.Domain)
|
||||
} else if !adomain.LocalpartCaseSensitive {
|
||||
lp = smtp.Localpart(strings.ToLower(string(lp)))
|
||||
}
|
||||
} else if !domain.LocalpartCaseSensitive {
|
||||
lp = smtp.Localpart(strings.ToLower(string(lp)))
|
||||
}
|
||||
if addrdom == domain.Domain {
|
||||
domainHasAddress[addrdom.Name()] = true
|
||||
|
@ -85,17 +85,49 @@ func LookupAddress(localpart smtp.Localpart, domain dns.Domain, allowPostmaster,
|
||||
return accAddr.Account, nil, canonical, accAddr.Destination, nil
|
||||
}
|
||||
|
||||
// lp and rlp are both lower-case when domain localparts aren't case sensitive.
|
||||
func matchReportingSeparators(lp, rlp smtp.Localpart, d config.Domain) bool {
|
||||
lps := string(lp)
|
||||
rlps := string(rlp)
|
||||
|
||||
if !strings.HasPrefix(lps, rlps) {
|
||||
return false
|
||||
}
|
||||
if len(lps) == len(rlps) {
|
||||
return true
|
||||
}
|
||||
rem := lps[len(rlps):]
|
||||
for _, sep := range d.LocalpartCatchallSeparatorsEffective {
|
||||
if strings.HasPrefix(rem, sep) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CanonicalLocalpart returns the canonical localpart, removing optional catchall
|
||||
// separators, and optionally lower-casing the string.
|
||||
// The DMARC and TLS reporting addresses are treated specially, they may contain a
|
||||
// localpart catchall separator for historic configurations (not for new
|
||||
// configurations). We try to match them first, still taking additional localpart
|
||||
// catchall separators into account.
|
||||
func CanonicalLocalpart(localpart smtp.Localpart, d config.Domain) smtp.Localpart {
|
||||
if !d.LocalpartCaseSensitive {
|
||||
localpart = smtp.Localpart(strings.ToLower(string(localpart)))
|
||||
}
|
||||
|
||||
if d.DMARC != nil && matchReportingSeparators(localpart, d.DMARC.ParsedLocalpart, d) {
|
||||
return d.DMARC.ParsedLocalpart
|
||||
}
|
||||
if d.TLSRPT != nil && matchReportingSeparators(localpart, d.TLSRPT.ParsedLocalpart, d) {
|
||||
return d.TLSRPT.ParsedLocalpart
|
||||
}
|
||||
|
||||
for _, sep := range d.LocalpartCatchallSeparatorsEffective {
|
||||
t := strings.SplitN(string(localpart), sep, 2)
|
||||
localpart = smtp.Localpart(t[0])
|
||||
}
|
||||
|
||||
if !d.LocalpartCaseSensitive {
|
||||
localpart = smtp.Localpart(strings.ToLower(string(localpart)))
|
||||
}
|
||||
return localpart
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user