Mail reverse DNS check, what Jarl adds to named.conf, lists one per line
jarld checks the reverse DNS of the public zones' MX and SPF addresses as the internet sees it, every 6 hours (Exposure page, jarl exposure mail, jarl_mail_reverse_dns_ok). The Server options page, jarl option generated and GET /options/generated list what Jarl writes into named.conf by itself; stats.rate_limit_log turns off the query-errors logging. Option lists of three or more elements are shown one per line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d33f573782
commit
c27a6c10bd
30 files changed
+1095
-16
No files matched your search
@@ -0,0 +1,108 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user