Skip to content

Commit

Permalink
for domains configured only for reporting, don't reject messages to t…
Browse files Browse the repository at this point in the history
…hat 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.
  • Loading branch information
mjl- committed Jan 26, 2024
1 parent a524c3a commit 1d9e80f
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 10 deletions.
5 changes: 5 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 4 additions & 5 deletions http/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down Expand Up @@ -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
}

Expand Down
26 changes: 22 additions & 4 deletions mox-/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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")
Expand Down
4 changes: 3 additions & 1 deletion mox-/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down

0 comments on commit 1d9e80f

Please sign in to comment.