mirror of
https://github.com/mjl-/mox.git
synced 2024-12-27 08:53:48 +03:00
for domains configured only for reporting, don't reject messages to that domain during smtp submission
you can configure a domain only to accept dmarc/tls reports. those domains won't have addresses for that domain configured (the reporting destination address is for another domain). we already handled such domains specially in a few places. but we were considering ourselves authoritative for such domains if an smtp client would send a message to the domain during submit. and we would reject all recipient addresses. but we should be trying to deliver those messages to the actual mx hosts for the domain, which we will now do.
This commit is contained in:
parent
a524c3a50b
commit
1d9e80fd70
4 changed files with 34 additions and 10 deletions
|
@ -273,6 +273,11 @@ type Domain struct {
|
||||||
|
|
||||||
Domain dns.Domain `sconf:"-" json:"-"`
|
Domain dns.Domain `sconf:"-" json:"-"`
|
||||||
ClientSettingsDNSDomain dns.Domain `sconf:"-" json:"-"`
|
ClientSettingsDNSDomain dns.Domain `sconf:"-" json:"-"`
|
||||||
|
|
||||||
|
// Set when DMARC and TLSRPT (when set) has an address with different domain (we're
|
||||||
|
// hosting the reporting), and there are no destination addresses configured for
|
||||||
|
// the domain. Disables some functionality related to hosting a domain.
|
||||||
|
ReportsOnly bool `sconf:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DMARC struct {
|
type DMARC struct {
|
||||||
|
|
|
@ -631,8 +631,8 @@ func Listen() {
|
||||||
if dom == mox.Conf.Static.HostnameDomain || dom == mox.Conf.Static.Listeners["public"].HostnameDomain {
|
if dom == mox.Conf.Static.HostnameDomain || dom == mox.Conf.Static.Listeners["public"].HostnameDomain {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
_, ok := mox.Conf.Domain(dom)
|
dc, ok := mox.Conf.Domain(dom)
|
||||||
return ok
|
return ok && !dc.ReportsOnly
|
||||||
}
|
}
|
||||||
srv.Handle("autoconfig", autoconfigMatch, "/mail/config-v1.1.xml", safeHeaders(http.HandlerFunc(autoconfHandle)))
|
srv.Handle("autoconfig", autoconfigMatch, "/mail/config-v1.1.xml", safeHeaders(http.HandlerFunc(autoconfHandle)))
|
||||||
srv.Handle("autodiscover", autoconfigMatch, "/autodiscover/autodiscover.xml", safeHeaders(http.HandlerFunc(autodiscoverHandle)))
|
srv.Handle("autodiscover", autoconfigMatch, "/autodiscover/autodiscover.xml", safeHeaders(http.HandlerFunc(autodiscoverHandle)))
|
||||||
|
@ -690,9 +690,8 @@ func Listen() {
|
||||||
for _, name := range mox.Conf.Domains() {
|
for _, name := range mox.Conf.Domains() {
|
||||||
if dom, err := dns.ParseDomain(name); err != nil {
|
if dom, err := dns.ParseDomain(name); err != nil {
|
||||||
pkglog.Errorx("parsing domain from config", err)
|
pkglog.Errorx("parsing domain from config", err)
|
||||||
} else if d, _ := mox.Conf.Domain(dom); d.DMARC != nil && d.DMARC.Domain != "" && d.DMARC.DNSDomain != dom {
|
} else if d, _ := mox.Conf.Domain(dom); d.ReportsOnly {
|
||||||
// Do not gather autoconfig name if this domain is configured to process reports
|
// Do not gather autoconfig name if we aren't accepting email for this domain.
|
||||||
// for domains hosted elsewhere.
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -257,9 +257,9 @@ func (c *Config) allowACMEHosts(log mlog.Log, checkACMEHosts bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dom := range c.Dynamic.Domains {
|
for _, dom := range c.Dynamic.Domains {
|
||||||
if dom.DMARC != nil && dom.DMARC.Domain != "" && dom.DMARC.DNSDomain != dom.Domain {
|
// Do not allow TLS certificates for domains for which we only accept DMARC/TLS
|
||||||
// Do not allow TLS certificates for domains for which we only accept DMARC reports
|
// reports as external party.
|
||||||
// as external party.
|
if dom.ReportsOnly {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1210,6 +1210,9 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
|
||||||
c.Domains[d] = domain
|
c.Domains[d] = domain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// To determine ReportsOnly.
|
||||||
|
domainHasAddress := map[string]bool{}
|
||||||
|
|
||||||
// Validate email addresses.
|
// Validate email addresses.
|
||||||
for accName, acc := range c.Accounts {
|
for accName, acc := range c.Accounts {
|
||||||
var err error
|
var err error
|
||||||
|
@ -1331,6 +1334,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
|
||||||
addErrorf("unknown domain for address %q in account %q", addrName, accName)
|
addErrorf("unknown domain for address %q in account %q", addrName, accName)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
domainHasAddress[d.Name()] = true
|
||||||
addrFull := "@" + d.Name()
|
addrFull := "@" + d.Name()
|
||||||
if _, ok := accDests[addrFull]; ok {
|
if _, ok := accDests[addrFull]; ok {
|
||||||
addErrorf("duplicate canonicalized catchall destination address %s", addrFull)
|
addErrorf("duplicate canonicalized catchall destination address %s", addrFull)
|
||||||
|
@ -1365,6 +1369,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
|
||||||
|
|
||||||
origLP := address.Localpart
|
origLP := address.Localpart
|
||||||
dc := c.Domains[address.Domain.Name()]
|
dc := c.Domains[address.Domain.Name()]
|
||||||
|
domainHasAddress[address.Domain.Name()] = true
|
||||||
if lp, err := CanonicalLocalpart(address.Localpart, dc); err != nil {
|
if lp, err := CanonicalLocalpart(address.Localpart, dc); err != nil {
|
||||||
addErrorf("canonicalizing localpart %s: %v", address.Localpart, err)
|
addErrorf("canonicalizing localpart %s: %v", address.Localpart, err)
|
||||||
} else if dc.LocalpartCatchallSeparator != "" && strings.Contains(string(address.Localpart), dc.LocalpartCatchallSeparator) {
|
} else if dc.LocalpartCatchallSeparator != "" && strings.Contains(string(address.Localpart), dc.LocalpartCatchallSeparator) {
|
||||||
|
@ -1419,9 +1424,12 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
addErrorf("DMARC domain %q: %s", dmarc.Domain, err)
|
addErrorf("DMARC domain %q: %s", dmarc.Domain, err)
|
||||||
} else if _, ok := c.Domains[addrdom.Name()]; !ok {
|
} else if _, ok := c.Domains[addrdom.Name()]; !ok {
|
||||||
addErrorf("unknown domain %q for DMARC address in domain %q", dmarc.Domain, d)
|
addErrorf("unknown domain %q for DMARC address in domain %q", addrdom, d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if addrdom == domain.Domain {
|
||||||
|
domainHasAddress[addrdom.Name()] = true
|
||||||
|
}
|
||||||
|
|
||||||
domain.DMARC.ParsedLocalpart = lp
|
domain.DMARC.ParsedLocalpart = lp
|
||||||
domain.DMARC.DNSDomain = addrdom
|
domain.DMARC.DNSDomain = addrdom
|
||||||
|
@ -1462,6 +1470,9 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
|
||||||
addErrorf("unknown domain %q for TLSRPT address in domain %q", tlsrpt.Domain, d)
|
addErrorf("unknown domain %q for TLSRPT address in domain %q", tlsrpt.Domain, d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if addrdom == domain.Domain {
|
||||||
|
domainHasAddress[addrdom.Name()] = true
|
||||||
|
}
|
||||||
|
|
||||||
domain.TLSRPT.ParsedLocalpart = lp
|
domain.TLSRPT.ParsedLocalpart = lp
|
||||||
domain.TLSRPT.DNSDomain = addrdom
|
domain.TLSRPT.DNSDomain = addrdom
|
||||||
|
@ -1475,6 +1486,13 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
|
||||||
accDests[addrFull] = AccountDestination{false, lp, tlsrpt.Account, dest}
|
accDests[addrFull] = AccountDestination{false, lp, tlsrpt.Account, dest}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set ReportsOnly for domains, based on whether we have seen addresses (possibly
|
||||||
|
// from DMARC or TLS reporting).
|
||||||
|
for d, domain := range c.Domains {
|
||||||
|
domain.ReportsOnly = !domainHasAddress[domain.Domain.Name()]
|
||||||
|
c.Domains[d] = domain
|
||||||
|
}
|
||||||
|
|
||||||
// Check webserver configs.
|
// Check webserver configs.
|
||||||
if (len(c.WebDomainRedirects) > 0 || len(c.WebHandlers) > 0) && !haveWebserverListener {
|
if (len(c.WebDomainRedirects) > 0 || len(c.WebHandlers) > 0) && !haveWebserverListener {
|
||||||
addErrorf("WebDomainRedirects or WebHandlers configured but no listener with WebserverHTTP or WebserverHTTPS enabled")
|
addErrorf("WebDomainRedirects or WebHandlers configured but no listener with WebserverHTTP or WebserverHTTPS enabled")
|
||||||
|
|
|
@ -54,7 +54,9 @@ func FindAccount(localpart smtp.Localpart, domain dns.Domain, allowPostmaster bo
|
||||||
}
|
}
|
||||||
|
|
||||||
d, ok := Conf.Domain(domain)
|
d, ok := Conf.Domain(domain)
|
||||||
if !ok {
|
if !ok || d.ReportsOnly {
|
||||||
|
// For ReportsOnly, we also return ErrDomainNotFound, so this domain isn't
|
||||||
|
// considered local/authoritative during delivery.
|
||||||
return "", "", config.Destination{}, ErrDomainNotFound
|
return "", "", config.Destination{}, ErrDomainNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue