diff --git a/config/config.go b/config/config.go index 684c49c..6613175 100644 --- a/config/config.go +++ b/config/config.go @@ -273,6 +273,11 @@ type Domain struct { Domain 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 { diff --git a/http/web.go b/http/web.go index c9309ae..def777f 100644 --- a/http/web.go +++ b/http/web.go @@ -631,8 +631,8 @@ func Listen() { if dom == mox.Conf.Static.HostnameDomain || dom == mox.Conf.Static.Listeners["public"].HostnameDomain { return true } - _, ok := mox.Conf.Domain(dom) - return ok + dc, ok := mox.Conf.Domain(dom) + return ok && !dc.ReportsOnly } srv.Handle("autoconfig", autoconfigMatch, "/mail/config-v1.1.xml", safeHeaders(http.HandlerFunc(autoconfHandle))) srv.Handle("autodiscover", autoconfigMatch, "/autodiscover/autodiscover.xml", safeHeaders(http.HandlerFunc(autodiscoverHandle))) @@ -690,9 +690,8 @@ func Listen() { for _, name := range mox.Conf.Domains() { if dom, err := dns.ParseDomain(name); err != nil { pkglog.Errorx("parsing domain from config", err) - } else if d, _ := mox.Conf.Domain(dom); d.DMARC != nil && d.DMARC.Domain != "" && d.DMARC.DNSDomain != dom { - // Do not gather autoconfig name if this domain is configured to process reports - // for domains hosted elsewhere. + } else if d, _ := mox.Conf.Domain(dom); d.ReportsOnly { + // Do not gather autoconfig name if we aren't accepting email for this domain. continue } diff --git a/mox-/config.go b/mox-/config.go index fb62fcc..f184ae3 100644 --- a/mox-/config.go +++ b/mox-/config.go @@ -257,9 +257,9 @@ func (c *Config) allowACMEHosts(log mlog.Log, checkACMEHosts bool) { } 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 reports - // as external party. + // Do not allow TLS certificates for domains for which we only accept DMARC/TLS + // reports as external party. + if dom.ReportsOnly { continue } @@ -1210,6 +1210,9 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string, c.Domains[d] = domain } + // To determine ReportsOnly. + domainHasAddress := map[string]bool{} + // Validate email addresses. for accName, acc := range c.Accounts { 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) continue } + domainHasAddress[d.Name()] = true addrFull := "@" + d.Name() if _, ok := accDests[addrFull]; ok { 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 dc := c.Domains[address.Domain.Name()] + domainHasAddress[address.Domain.Name()] = true if lp, err := CanonicalLocalpart(address.Localpart, dc); err != nil { addErrorf("canonicalizing localpart %s: %v", address.Localpart, err) } 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 { addErrorf("DMARC domain %q: %s", dmarc.Domain, err) } 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.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) } } + if addrdom == domain.Domain { + domainHasAddress[addrdom.Name()] = true + } domain.TLSRPT.ParsedLocalpart = lp 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} } + // 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. if (len(c.WebDomainRedirects) > 0 || len(c.WebHandlers) > 0) && !haveWebserverListener { addErrorf("WebDomainRedirects or WebHandlers configured but no listener with WebserverHTTP or WebserverHTTPS enabled") diff --git a/mox-/lookup.go b/mox-/lookup.go index de15e41..89c9980 100644 --- a/mox-/lookup.go +++ b/mox-/lookup.go @@ -54,7 +54,9 @@ func FindAccount(localpart smtp.Localpart, domain dns.Domain, allowPostmaster bo } 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 }