6 cryptorand "crypto/rand"
21 "github.com/mjl-/bstore"
23 "github.com/mjl-/mox/dns"
24 "github.com/mjl-/mox/mlog"
25 "github.com/mjl-/mox/mox-"
26 "github.com/mjl-/mox/mtasts"
29var ctxbg = context.Background()
31func TestRefresh(t *testing.T) {
33 mox.ConfigStaticPath = filepath.FromSlash("../testdata/mtasts/fake.conf")
34 mox.Conf.Static.DataDir = "."
36 dbpath := mox.DataDirPath("mtasts.db")
37 os.MkdirAll(filepath.Dir(dbpath), 0770)
39 defer os.Remove(dbpath)
41 log := mlog.New("mtastsdb", nil)
44 tcheckf(t, err, "init database")
47 tcheckf(t, err, "close database")
50 cert := fakeCert(t, false)
52 insert := func(domain string, validEnd, lastUpdate, lastUse time.Time, backoff bool, recordID string, mode mtasts.Mode, maxAge int, mx string) {
55 mxd, err := dns.ParseDomain(mx)
57 t.Fatalf("parsing mx domain %q: %s", mx, err)
59 policy := mtasts.Policy{
62 MX: []mtasts.MX{{Wildcard: false, Domain: mxd}},
63 MaxAgeSeconds: maxAge,
67 pr := PolicyRecord{domain, time.Time{}, validEnd, lastUpdate, lastUse, backoff, recordID, policy, policy.String()}
68 if err := DB.Insert(ctxbg, &pr); err != nil {
69 t.Fatalf("insert policy: %s", err)
73 resolver := dns.MockResolver{
74 TXT: map[string][]string{
75 "_mta-sts.refresh.mox.example.": {"v=STSv1; id=1"},
76 "_mta-sts.policyok.mox.example.": {"v=STSv1; id=2"},
77 "_mta-sts.policybad.mox.example.": {"v=STSv1; id=2"},
81 pool := x509.NewCertPool()
82 pool.AddCert(cert.Leaf)
84 l, err := net.Listen("tcp", "127.0.0.1:0")
85 tcheckf(t, err, "listen")
88 mux := &http.ServeMux{}
89 mux.HandleFunc("/.well-known/mta-sts.txt", func(w http.ResponseWriter, r *http.Request) {
90 if r.Host == "mta-sts.policybad.mox.example" {
94 fmt.Fprintf(w, "version: STSv1\nmode: enforce\nmx: mx.mox.example.com\nmax_age: 3600\n")
98 TLSConfig: &tls.Config{
99 Certificates: []tls.Certificate{cert},
101 ErrorLog: golog.New(io.Discard, "", 0),
103 s.ServeTLS(l, "", "")
106 mtasts.HTTPClient.Transport = &http.Transport{
107 DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
108 var dialer net.Dialer
109 return dialer.DialContext(ctx, "tcp", l.Addr().String())
111 TLSClientConfig: &tls.Config{
116 mtasts.HTTPClient.CloseIdleConnections()
117 mtasts.HTTPClient.Transport = nil
121 sleep := func(d time.Duration) {
123 interval := 3 * time.Hour / 2
124 if d < time.Duration(slept)*interval-interval/2 || d > time.Duration(slept)*interval+interval/2 {
125 t.Fatalf("bad sleep duration %v", d)
129 // Run with synctest, to ensure all goroutines that could write results are
130 // finished before we check again if all work is finished.
131 synctest.Test(t, func(t *testing.T) {
134 insert("mox.example", now.Add(24*time.Hour), now, now, false, "1", mtasts.ModeEnforce, 3600, "mx.mox.example.com")
136 insert("stale.mox.example", now.Add(-time.Hour), now, now.Add(-181*24*time.Hour), false, "1", mtasts.ModeEnforce, 3600, "mx.mox.example.com")
137 // To be refreshed, same id.
138 insert("refresh.mox.example", now.Add(7*24*time.Hour), now.Add(-24*time.Hour), now.Add(-179*24*time.Hour), false, "1", mtasts.ModeEnforce, 3600, "mx.mox.example.com")
139 // To be refreshed and succeed.
140 insert("policyok.mox.example", now.Add(7*24*time.Hour), now.Add(-24*time.Hour), now.Add(-179*24*time.Hour), false, "1", mtasts.ModeEnforce, 3600, "mx.mox.example.com")
141 // To be refreshed and fail to fetch.
142 insert("policybad.mox.example", now.Add(7*24*time.Hour), now.Add(-24*time.Hour), now.Add(-179*24*time.Hour), false, "1", mtasts.ModeEnforce, 3600, "mx.mox.example.com")
144 if n, err := refresh1(ctxbg, log, resolver, sleep); err != nil || n != 3 {
145 t.Fatalf("refresh1: err %s, n %d, expected no error, 3", err, n)
149 t.Fatalf("bad sleeps, %d instead of 2", slept)
153 // Should not do any more refreshes and return immediately.
154 q := bstore.QueryDB[PolicyRecord](ctxbg, DB)
155 q.FilterNonzero(PolicyRecord{Domain: "policybad.mox.example"})
156 if _, err := q.Delete(); err != nil {
157 t.Fatalf("delete record that would be refreshed: %v", err)
160 mox.Shutdown, mox.ShutdownCancel = context.WithCancel(ctxbg)
164 t.Fatalf("refresh found unexpected work, n %d", n)
166 mox.Shutdown, mox.ShutdownCancel = context.WithCancel(ctxbg)
169func fakeCert(t *testing.T, expired bool) tls.Certificate {
170 notAfter := time.Now()
172 notAfter = notAfter.Add(-time.Hour)
174 notAfter = notAfter.Add(time.Hour)
177 privKey := ed25519.NewKeyFromSeed(make([]byte, ed25519.SeedSize)) // Fake key, don't use this for real!
179 template := &x509.Certificate{
180 SerialNumber: big.NewInt(1), // Required field...
181 DNSNames: []string{"mta-sts.policybad.mox.example", "mta-sts.policyok.mox.example"},
182 NotBefore: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC), // synctest time
185 localCertBuf, err := x509.CreateCertificate(cryptorand.Reader, template, template, privKey.Public(), privKey)
187 t.Fatalf("making certificate: %s", err)
189 cert, err := x509.ParseCertificate(localCertBuf)
191 t.Fatalf("parsing generated certificate: %s", err)
193 c := tls.Certificate{
194 Certificate: [][]byte{localCertBuf},