mirror of
https://github.com/mjl-/mox.git
synced 2025-07-18 23:26:36 +03:00
add mx preference to smtpclient.GatherDestinations
mostly so moxtools can show the mx preferences in its output
This commit is contained in:
@ -26,6 +26,12 @@ var (
|
||||
errNoMail = errors.New("domain does not accept email as indicated with single dot for mx record")
|
||||
)
|
||||
|
||||
// HostPref is a host for delivery, with preference for MX records.
|
||||
type HostPref struct {
|
||||
Host dns.IPDomain
|
||||
Pref int // -1 when not an MX record.
|
||||
}
|
||||
|
||||
// GatherDestinations looks up the hosts to deliver email to a domain ("next-hop").
|
||||
// If it is an IP address, it is the only destination to try. Otherwise CNAMEs of
|
||||
// the domain are followed. Then MX records for the expanded CNAME are looked up.
|
||||
@ -46,14 +52,14 @@ var (
|
||||
// were found, both the original and expanded next-hops must be authentic for DANE
|
||||
// to be option. For a non-IP with no MX records found, the authentic result can
|
||||
// be used to decide which of the names to use as TLSA base domain.
|
||||
func GatherDestinations(ctx context.Context, elog *slog.Logger, resolver dns.Resolver, origNextHop dns.IPDomain) (haveMX, origNextHopAuthentic, expandedNextHopAuthentic bool, expandedNextHop dns.Domain, hosts []dns.IPDomain, permanent bool, err error) {
|
||||
func GatherDestinations(ctx context.Context, elog *slog.Logger, resolver dns.Resolver, origNextHop dns.IPDomain) (haveMX, origNextHopAuthentic, expandedNextHopAuthentic bool, expandedNextHop dns.Domain, hostPrefs []HostPref, permanent bool, err error) {
|
||||
// ../rfc/5321:3824
|
||||
|
||||
log := mlog.New("smtpclient", elog)
|
||||
|
||||
// IP addresses are dialed directly, and don't have TLSA records.
|
||||
if len(origNextHop.IP) > 0 {
|
||||
return false, false, false, expandedNextHop, []dns.IPDomain{origNextHop}, false, nil
|
||||
return false, false, false, expandedNextHop, []HostPref{{origNextHop, -1}}, false, nil
|
||||
}
|
||||
|
||||
// We start out assuming the result is authentic. Updated with each lookup.
|
||||
@ -133,8 +139,8 @@ func GatherDestinations(ctx context.Context, elog *slog.Logger, resolver dns.Res
|
||||
}
|
||||
|
||||
// No MX record, attempt delivery directly to host. ../rfc/5321:3842
|
||||
hosts = []dns.IPDomain{{Domain: expandedNextHop}}
|
||||
return false, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hosts, false, nil
|
||||
hostPrefs = []HostPref{{dns.IPDomain{Domain: expandedNextHop}, -1}}
|
||||
return false, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hostPrefs, false, nil
|
||||
} else if err != nil {
|
||||
log.Infox("mx record has some invalid records, keeping only the valid mx records", err)
|
||||
}
|
||||
@ -158,12 +164,12 @@ func GatherDestinations(ctx context.Context, elog *slog.Logger, resolver dns.Res
|
||||
err = fmt.Errorf("%w: invalid host name in mx record %q: %v", errDNS, mx.Host, err)
|
||||
return true, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, nil, true, err
|
||||
}
|
||||
hosts = append(hosts, dns.IPDomain{Domain: host})
|
||||
hostPrefs = append(hostPrefs, HostPref{dns.IPDomain{Domain: host}, int(mx.Pref)})
|
||||
}
|
||||
if len(hosts) > 0 {
|
||||
if len(hostPrefs) > 0 {
|
||||
err = nil
|
||||
}
|
||||
return true, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hosts, false, err
|
||||
return true, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hostPrefs, false, err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,11 +35,11 @@ func ipdomain(s string) dns.IPDomain {
|
||||
return dns.IPDomain{Domain: d}
|
||||
}
|
||||
|
||||
func ipdomains(s ...string) (l []dns.IPDomain) {
|
||||
for _, e := range s {
|
||||
l = append(l, ipdomain(e))
|
||||
func hostprefs(pref int, names ...string) (l []HostPref) {
|
||||
for _, s := range names {
|
||||
l = append(l, HostPref{Host: ipdomain(s), Pref: pref})
|
||||
}
|
||||
return
|
||||
return l
|
||||
}
|
||||
|
||||
// Test basic MX lookup case, but also following CNAME, detecting CNAME loops and
|
||||
@ -86,10 +86,10 @@ func TestGatherDestinations(t *testing.T) {
|
||||
resolver.CNAME[s] = next
|
||||
}
|
||||
|
||||
test := func(ipd dns.IPDomain, expHosts []dns.IPDomain, expDomain dns.Domain, expPerm, expAuthic, expExpAuthic bool, expErr error) {
|
||||
test := func(ipd dns.IPDomain, expHostPrefs []HostPref, expDomain dns.Domain, expPerm, expAuthic, expExpAuthic bool, expErr error) {
|
||||
t.Helper()
|
||||
|
||||
_, authic, authicExp, ed, hosts, perm, err := GatherDestinations(ctxbg, log.Logger, resolver, ipd)
|
||||
_, authic, authicExp, ed, hostPrefs, perm, err := GatherDestinations(ctxbg, log.Logger, resolver, ipd)
|
||||
if (err == nil) != (expErr == nil) || err != nil && !errors.Is(err, expErr) {
|
||||
// todo: could also check the individual errors? code currently does not have structured errors.
|
||||
t.Fatalf("gather hosts: %v, expected %v", err, expErr)
|
||||
@ -97,8 +97,8 @@ func TestGatherDestinations(t *testing.T) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(hosts, expHosts) || ed != expDomain || perm != expPerm || authic != expAuthic || authicExp != expExpAuthic {
|
||||
t.Fatalf("got hosts %#v, effectiveDomain %#v, permanent %#v, authic %v %v, expected %#v %#v %#v %v %v", hosts, ed, perm, authic, authicExp, expHosts, expDomain, expPerm, expAuthic, expExpAuthic)
|
||||
if !reflect.DeepEqual(hostPrefs, expHostPrefs) || ed != expDomain || perm != expPerm || authic != expAuthic || authicExp != expExpAuthic {
|
||||
t.Fatalf("got hosts %#v, effectiveDomain %#v, permanent %#v, authic %v %v, expected %#v %#v %#v %v %v", hostPrefs, ed, perm, authic, authicExp, expHostPrefs, expDomain, expPerm, expAuthic, expExpAuthic)
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,18 +108,18 @@ func TestGatherDestinations(t *testing.T) {
|
||||
authic := i == 1
|
||||
resolver.AllAuthentic = authic
|
||||
// Basic with simple MX.
|
||||
test(ipdomain("basic.example"), ipdomains("mail.basic.example"), domain("basic.example"), false, authic, authic, nil)
|
||||
test(ipdomain("multimx.example"), ipdomains("mail1.multimx.example", "mail2.multimx.example"), domain("multimx.example"), false, authic, authic, nil)
|
||||
test(ipdomain("basic.example"), hostprefs(10, "mail.basic.example"), domain("basic.example"), false, authic, authic, nil)
|
||||
test(ipdomain("multimx.example"), hostprefs(10, "mail1.multimx.example", "mail2.multimx.example"), domain("multimx.example"), false, authic, authic, nil)
|
||||
// Only an A record.
|
||||
test(ipdomain("justhost.example"), ipdomains("justhost.example"), domain("justhost.example"), false, authic, authic, nil)
|
||||
test(ipdomain("justhost.example"), hostprefs(-1, "justhost.example"), domain("justhost.example"), false, authic, authic, nil)
|
||||
// Only an AAAA record.
|
||||
test(ipdomain("justhost6.example"), ipdomains("justhost6.example"), domain("justhost6.example"), false, authic, authic, nil)
|
||||
test(ipdomain("justhost6.example"), hostprefs(-1, "justhost6.example"), domain("justhost6.example"), false, authic, authic, nil)
|
||||
// Follow CNAME.
|
||||
test(ipdomain("cname.example"), ipdomains("mail.basic.example"), domain("basic.example"), false, authic, authic, nil)
|
||||
test(ipdomain("cname.example"), hostprefs(10, "mail.basic.example"), domain("basic.example"), false, authic, authic, nil)
|
||||
// No MX/CNAME, non-existence of host will be found out later.
|
||||
test(ipdomain("absent.example"), ipdomains("absent.example"), domain("absent.example"), false, authic, authic, nil)
|
||||
test(ipdomain("absent.example"), hostprefs(-1, "absent.example"), domain("absent.example"), false, authic, authic, nil)
|
||||
// Followed CNAME, has no MX, non-existence of host will be found out later.
|
||||
test(ipdomain("danglingcname.example"), ipdomains("absent.example"), domain("absent.example"), false, authic, authic, nil)
|
||||
test(ipdomain("danglingcname.example"), hostprefs(-1, "absent.example"), domain("absent.example"), false, authic, authic, nil)
|
||||
test(ipdomain("cnamelimit1.example"), nil, zerodom, true, authic, authic, errCNAMELimit)
|
||||
test(ipdomain("cnameloop.example"), nil, zerodom, true, authic, authic, errCNAMELoop)
|
||||
test(ipdomain("nullmx.example"), nil, zerodom, true, authic, authic, errNoMail)
|
||||
@ -127,9 +127,9 @@ func TestGatherDestinations(t *testing.T) {
|
||||
test(ipdomain("temperror-cname.example"), nil, zerodom, false, authic, authic, errDNS)
|
||||
}
|
||||
|
||||
test(ipdomain("10.0.0.1"), ipdomains("10.0.0.1"), zerodom, false, false, false, nil)
|
||||
test(ipdomain("cnameinauthentic.example"), ipdomains("mail.basic.example"), domain("basic.example"), false, false, false, nil)
|
||||
test(ipdomain("cname-to-inauthentic.example"), ipdomains("mail.basic.example"), domain("basic.example"), false, true, false, nil)
|
||||
test(ipdomain("10.0.0.1"), hostprefs(-1, "10.0.0.1"), zerodom, false, false, false, nil)
|
||||
test(ipdomain("cnameinauthentic.example"), hostprefs(10, "mail.basic.example"), domain("basic.example"), false, false, false, nil)
|
||||
test(ipdomain("cname-to-inauthentic.example"), hostprefs(10, "mail.basic.example"), domain("basic.example"), false, true, false, nil)
|
||||
}
|
||||
|
||||
func TestGatherIPs(t *testing.T) {
|
||||
|
Reference in New Issue
Block a user