package webadmin import ( "context" "crypto/ed25519" "net" "net/http/httptest" "os" "testing" "time" "golang.org/x/crypto/bcrypt" "github.com/mjl-/mox/config" "github.com/mjl-/mox/dns" "github.com/mjl-/mox/mox-" ) var ctxbg = context.Background() func init() { mox.LimitersInit() } func TestAdminAuth(t *testing.T) { test := func(passwordfile, authHdr string, expect bool) { t.Helper() w := httptest.NewRecorder() r := httptest.NewRequest("GET", "/ignored", nil) if authHdr != "" { r.Header.Add("Authorization", authHdr) } ok := checkAdminAuth(ctxbg, passwordfile, w, r) if ok != expect { t.Fatalf("got %v, expected %v", ok, expect) } } const authOK = "Basic YWRtaW46bW94dGVzdDEyMw==" // admin:moxtest123 const authBad = "Basic YWRtaW46YmFkcGFzc3dvcmQ=" // admin:badpassword const path = "../testdata/http-passwordfile" os.Remove(path) defer os.Remove(path) test(path, authOK, false) // Password file does not exist. adminpwhash, err := bcrypt.GenerateFromPassword([]byte("moxtest123"), bcrypt.DefaultCost) if err != nil { t.Fatalf("generate bcrypt hash: %v", err) } if err := os.WriteFile(path, adminpwhash, 0660); err != nil { t.Fatalf("write password file: %v", err) } // We loop to also exercise the auth cache. for i := 0; i < 2; i++ { test(path, "", false) // Empty/missing header. test(path, "Malformed ", false) // Not "Basic" test(path, "Basic malformed ", false) // Bad base64. test(path, "Basic dGVzdA== ", false) // base64 is ok, but wrong tokens inside. test(path, authBad, false) // Wrong password. test(path, authOK, true) } } func TestCheckDomain(t *testing.T) { // NOTE: we aren't currently looking at the results, having the code paths executed is better than nothing. resolver := dns.MockResolver{ MX: map[string][]*net.MX{ "mox.example.": {{Host: "mail.mox.example.", Pref: 10}}, }, A: map[string][]string{ "mail.mox.example.": {"127.0.0.2"}, }, AAAA: map[string][]string{ "mail.mox.example.": {"127.0.0.2"}, }, TXT: map[string][]string{ "mox.example.": {"v=spf1 mx -all"}, "test._domainkey.mox.example.": {"v=DKIM1;h=sha256;k=ed25519;p=ln5zd/JEX4Jy60WAhUOv33IYm2YZMyTQAdr9stML504="}, "_dmarc.mox.example.": {"v=DMARC1; p=reject; rua=mailto:mjl@mox.example"}, "_smtp._tls.mox.example": {"v=TLSRPTv1; rua=mailto:tlsrpt@mox.example;"}, "_mta-sts.mox.example": {"v=STSv1; id=20160831085700Z"}, }, CNAME: map[string]string{}, } listener := config.Listener{ IPs: []string{"127.0.0.2"}, Hostname: "mox.example", HostnameDomain: dns.Domain{ASCII: "mox.example"}, } listener.SMTP.Enabled = true listener.AutoconfigHTTPS.Enabled = true listener.MTASTSHTTPS.Enabled = true mox.Conf.Static.Listeners = map[string]config.Listener{ "public": listener, } domain := config.Domain{ DKIM: config.DKIM{ Selectors: map[string]config.Selector{ "test": { HashEffective: "sha256", HeadersEffective: []string{"From", "Date", "Subject"}, Key: ed25519.NewKeyFromSeed(make([]byte, 32)), // warning: fake zero key, do not copy this code. Domain: dns.Domain{ASCII: "test"}, }, "missing": { HashEffective: "sha256", HeadersEffective: []string{"From", "Date", "Subject"}, Key: ed25519.NewKeyFromSeed(make([]byte, 32)), // warning: fake zero key, do not copy this code. Domain: dns.Domain{ASCII: "missing"}, }, }, Sign: []string{"test", "test2"}, }, } mox.Conf.Dynamic.Domains = map[string]config.Domain{ "mox.example": domain, } // Make a dialer that fails immediately before actually connecting. done := make(chan struct{}) close(done) dialer := &net.Dialer{Deadline: time.Now().Add(-time.Second), Cancel: done} checkDomain(ctxbg, resolver, dialer, "mox.example") // todo: check returned data Admin{}.Domains(ctxbg) // todo: check results dnsblsStatus(ctxbg, resolver) // todo: check results }