implement accepting dmarc & tls reports for other domains

to accept reports for another domain, first add that domain to the config,
leaving all options empty except DMARC/TLSRPT in which you configure a Domain.

the suggested DNS DMARC/TLSRPT records will show the email address with
configured domain. for DMARC, the dnscheck functionality will verify that the
destination domain has opted in to receiving reports.

there is a new command-line subcommand "mox dmarc checkreportaddrs" that
verifies if dmarc reporting destination addresses have opted in to received
reports.

this also changes the suggested dns records (in quickstart, and through admin
pages and cli subcommand) to take into account whether DMARC and TLSRPT is
configured, and with which localpart/domain (previously it always printed
records as if reporting was enabled for the domain). and when generating the
suggested DNS records, the dmarc.Record and tlsrpt.Record code is used, with
proper uri-escaping.
This commit is contained in:
Mechiel Lukkien
2023-08-23 14:27:21 +02:00
parent 9e248860ee
commit aebfd78a9f
13 changed files with 332 additions and 48 deletions

View File

@ -10,6 +10,7 @@ import (
"encoding/pem"
"fmt"
"net"
"net/url"
"os"
"path/filepath"
"sort"
@ -18,11 +19,13 @@ import (
"github.com/mjl-/mox/config"
"github.com/mjl-/mox/dkim"
"github.com/mjl-/mox/dmarc"
"github.com/mjl-/mox/dns"
"github.com/mjl-/mox/junk"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/mtasts"
"github.com/mjl-/mox/smtp"
"github.com/mjl-/mox/tlsrpt"
)
// TXTStrings returns a TXT record value as one or more quoted strings, taking the max
@ -496,6 +499,17 @@ func DomainRecords(domConf config.Domain, domain dns.Domain) ([]string, error) {
records = append(records, s)
}
dmarcr := dmarc.DefaultRecord
dmarcr.Policy = "reject"
if domConf.DMARC != nil {
uri := url.URL{
Scheme: "mailto",
Opaque: smtp.NewAddress(domConf.DMARC.ParsedLocalpart, domConf.DMARC.DNSDomain).Pack(false),
}
dmarcr.AggregateReportAddresses = []dmarc.URI{
{Address: uri.String(), MaxSize: 10, Unit: "m"},
}
}
records = append(records,
"",
@ -505,10 +519,11 @@ func DomainRecords(domConf config.Domain, domain dns.Domain) ([]string, error) {
fmt.Sprintf(`%s. IN TXT "v=spf1 mx ~all"`, d),
"",
"; Emails that fail the DMARC check (without DKIM and without SPF) should be rejected, and request reports.",
"; If you email through mailing lists that strip DKIM-Signature headers and don't",
"; rewrite the From header, you may want to set the policy to p=none.",
fmt.Sprintf(`_dmarc.%s. IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@%s!10m"`, d, d),
"; Emails that fail the DMARC check (without aligned DKIM and without aligned SPF)",
"; should be rejected, and request reports. If you email through mailing lists that",
"; strip DKIM-Signature headers and don't rewrite the From header, you may want to",
"; set the policy to p=none.",
fmt.Sprintf(`_dmarc.%s. IN TXT "%s"`, d, dmarcr.String()),
"",
)
@ -527,11 +542,20 @@ func DomainRecords(domConf config.Domain, domain dns.Domain) ([]string, error) {
)
}
records = append(records,
"; Request reporting about TLS failures.",
fmt.Sprintf(`_smtp._tls.%s. IN TXT "v=TLSRPTv1; rua=mailto:tls-reports@%s"`, d, d),
"",
if domConf.TLSRPT != nil {
uri := url.URL{
Scheme: "mailto",
Opaque: smtp.NewAddress(domConf.TLSRPT.ParsedLocalpart, domConf.TLSRPT.DNSDomain).Pack(false),
}
tlsrptr := tlsrpt.Record{Version: "TLSRPTv1", RUAs: [][]string{{uri.String()}}}
records = append(records,
"; Request reporting about TLS failures.",
fmt.Sprintf(`_smtp._tls.%s. IN TXT "%s"`, d, tlsrptr.String()),
"",
)
}
records = append(records,
"; Autoconfig is used by Thunderbird. Autodiscover is (in theory) used by Microsoft.",
fmt.Sprintf(`autoconfig.%s. IN CNAME %s.`, d, h),
fmt.Sprintf(`_autodiscover._tcp.%s. IN SRV 0 1 443 autoconfig.%s.`, d, d),