package mailcheck import ( "context" "net" "strings" "testing" "github.com/miekg/dns" "jarl/internal/model" ) // fakeResolver answers from a fixed set of records, as a recursive // resolver would, with the reverse zone's SOA in negative answers. func fakeResolver(t *testing.T, records ...string) string { t.Helper() var rrs []dns.RR for _, s := range records { rr, err := dns.NewRR(s) if err != nil { t.Fatal(err) } rrs = append(rrs, rr) } soa, _ := dns.NewRR("2.0.192.in-addr.arpa. 3600 IN SOA ns1.linode.com. hostmaster.linode.com. 1 3600 900 1209600 300") pc, err := net.ListenPacket("udp", "127.0.0.1:0") if err != nil { t.Fatal(err) } srv := &dns.Server{PacketConn: pc, Handler: dns.HandlerFunc(func(w dns.ResponseWriter, q *dns.Msg) { m := new(dns.Msg) m.SetReply(q) qn := strings.ToLower(q.Question[0].Name) for _, rr := range rrs { if strings.ToLower(rr.Header().Name) == qn && (rr.Header().Rrtype == q.Question[0].Qtype || rr.Header().Rrtype == dns.TypeCNAME) { m.Answer = append(m.Answer, rr) } } if len(m.Answer) == 0 && dns.IsSubDomain(soa.Header().Name, qn) { if q.Question[0].Qtype == dns.TypeSOA && qn == soa.Header().Name { m.Answer = append(m.Answer, soa) } else { m.Ns = append(m.Ns, soa) } } w.WriteMsg(m) })} go srv.ActivateAndServe() t.Cleanup(func() { srv.Shutdown() }) return pc.LocalAddr().String() } func TestCheck(t *testing.T) { st := model.NewState() ext := &model.View{ID: 1, Name: "external", MatchClients: []string{"any"}} in := &model.View{ID: 2, Name: "internal", MatchClients: []string{"lan"}} st.Views[1], st.Views[2] = ext, in z := &model.Zone{ID: 10, Name: "example.com.", ViewID: 1, Type: model.ZonePrimary} zi := &model.Zone{ID: 11, Name: "example.com.", ViewID: 2, Type: model.ZonePrimary} rev := &model.Zone{ID: 12, Name: "27.2.0.192.in-addr.arpa.", ViewID: 2, Type: model.ZonePrimary} st.Zones[10], st.Zones[11], st.Zones[12] = z, zi, rev rec := func(id, zone int64, name, typ, data string) { st.Records[id] = &model.Record{ID: id, ZoneID: zone, Name: name, Type: typ, Data: data} } rec(1, 10, "example.com.", "MX", "10 mail.example.com.") rec(2, 10, "example.com.", "TXT", `"v=spf1 mx ip4:192.0.2.26 ip4:192.0.2.27" " ip4:198.51.100.0/24 -all"`) rec(3, 10, "www.example.com.", "MX", "10 ignored.example.com.") // not the apex rec(4, 11, "example.com.", "MX", "10 internal.example.com.") // private view rec(5, 10, "example.com.", "TXT", `"google-site-verification=x"`) rec(6, 10, "example.com.", "MX", "20 backup.example.com.") // not on the internet res := Resolver{Servers: []string{fakeResolver(t, "mail.example.com. 300 IN A 192.0.2.25", "25.2.0.192.in-addr.arpa. 300 IN PTR mail.example.com.", "27.2.0.192.in-addr.arpa. 300 IN PTR li-27.members.linode.com.", "li-27.members.linode.com. 300 IN A 192.0.2.99", )}} public := func(id int64) bool { return id == 1 } rep := Check(context.Background(), st, public, res) if len(rep.Errors) != 1 || !strings.Contains(rep.Errors[0], "backup.example.com (example.com, MX backup.example.com) has no address") { t.Fatalf("errors %v", rep.Errors) } got := map[string]Result{} for _, r := range rep.Results { got[r.Address] = r } if len(got) != 3 { t.Fatalf("results %+v", rep.Results) } if r := got["192.0.2.25"]; !r.OK || r.Level != "ok" || r.Roles[0] != "MX mail.example.com" || r.ManagedBy != "" && r.ManagedBy != "linode.com" { t.Errorf("mail: %+v", r) } if r := got["192.0.2.26"]; r.OK || r.Level != "warning" || !strings.Contains(r.Message, "no PTR record") || r.ManagedBy != "linode.com" || r.ReverseZone != "2.0.192.in-addr.arpa" { t.Errorf("no PTR: %+v", r) } if r := got["192.0.2.27"]; r.OK || !strings.Contains(r.Message, "doesn't resolve back") || r.LocalZone != "internal/27.2.0.192.in-addr.arpa" { t.Errorf("not forward-confirmed: %+v", r) } } func TestManagedBy(t *testing.T) { for in, want := range map[string]string{"ns1.linode.com.": "linode.com", "dns0.zen.co.uk.": "zen.co.uk", "ns1.members.linode.com.": "linode.com", "localhost.": "localhost"} { if got := managedBy(in); got != want { t.Errorf("managedBy(%q) = %q, want %q", in, got, want) } } }