8 cryptorand "crypto/rand"
20 "github.com/mjl-/mox/config"
21 "github.com/mjl-/mox/dmarcdb"
22 "github.com/mjl-/mox/dns"
23 "github.com/mjl-/mox/imapclient"
24 "github.com/mjl-/mox/mlog"
25 "github.com/mjl-/mox/mox-"
26 "github.com/mjl-/mox/mtastsdb"
27 "github.com/mjl-/mox/queue"
28 "github.com/mjl-/mox/smtp"
29 "github.com/mjl-/mox/store"
30 "github.com/mjl-/mox/tlsrptdb"
33var ctxbg = context.Background()
34var pkglog = mlog.New("ctl", nil)
36func tcheck(t *testing.T, err error, errmsg string) {
39 t.Fatalf("%s: %v", errmsg, err)
43// TestCtl executes commands through ctl. This tests at least the protocols (who
44// sends when/what) is tested. We often don't check the actual results, but
45// unhandled errors would cause a panic.
46func TestCtl(t *testing.T) {
47 os.RemoveAll("testdata/ctl/data")
48 mox.ConfigStaticPath = filepath.FromSlash("testdata/ctl/config/mox.conf")
49 mox.ConfigDynamicPath = filepath.FromSlash("testdata/ctl/config/domains.conf")
50 if errs := mox.LoadConfig(ctxbg, pkglog, true, false); len(errs) > 0 {
51 t.Fatalf("loading mox config: %v", errs)
53 err := store.Init(ctxbg)
54 tcheck(t, err, "store init")
56 defer store.Switchboard()()
59 tcheck(t, err, "queue init")
60 defer queue.Shutdown()
64 testctl := func(fn func(clientxctl *ctl)) {
67 cconn, sconn := net.Pipe()
68 clientxctl := ctl{conn: cconn, log: pkglog}
69 serverxctl := ctl{conn: sconn, log: pkglog}
70 done := make(chan struct{})
73 servectlcmd(ctxbg, &serverxctl, cid, func() {})
83 testctl(func(xctl *ctl) {
84 ctlcmdDeliver(xctl, "mjl@mox.example")
87 // "setaccountpassword"
88 testctl(func(xctl *ctl) {
89 ctlcmdSetaccountpassword(xctl, "mjl", "test4321")
92 testctl(func(xctl *ctl) {
93 ctlcmdQueueHoldrulesList(xctl)
97 testctl(func(xctl *ctl) {
98 ctlcmdQueueHoldrulesAdd(xctl, "", "", "")
100 testctl(func(xctl *ctl) {
101 ctlcmdQueueHoldrulesAdd(xctl, "mjl", "", "")
103 testctl(func(xctl *ctl) {
104 ctlcmdQueueHoldrulesAdd(xctl, "", "☺.mox.example", "")
106 testctl(func(xctl *ctl) {
107 ctlcmdQueueHoldrulesAdd(xctl, "mox", "☺.mox.example", "example.com")
110 testctl(func(xctl *ctl) {
111 ctlcmdQueueHoldrulesRemove(xctl, 1)
114 // Queue a message to list/change/dump.
115 msg := "Subject: subject\r\n\r\nbody\r\n"
116 msgFile, err := store.CreateMessageTemp(pkglog, "queuedump-test")
117 tcheck(t, err, "temp file")
118 _, err = msgFile.Write([]byte(msg))
119 tcheck(t, err, "write message")
120 _, err = msgFile.Seek(0, 0)
121 tcheck(t, err, "rewind message")
122 defer os.Remove(msgFile.Name())
123 defer msgFile.Close()
124 addr, err := smtp.ParseAddress("mjl@mox.example")
125 tcheck(t, err, "parse address")
126 qml := []queue.Msg{queue.MakeMsg(addr.Path(), addr.Path(), false, false, int64(len(msg)), "<random@localhost>", nil, nil, time.Now(), "subject")}
127 queue.Add(ctxbg, pkglog, "mjl", msgFile, qml...)
131 testctl(func(xctl *ctl) {
132 ctlcmdQueueHoldrulesList(xctl)
136 testctl(func(xctl *ctl) {
137 ctlcmdQueueList(xctl, queue.Filter{}, queue.Sort{})
141 testctl(func(xctl *ctl) {
142 ctlcmdQueueHoldSet(xctl, queue.Filter{}, true)
144 testctl(func(xctl *ctl) {
145 ctlcmdQueueHoldSet(xctl, queue.Filter{}, false)
149 testctl(func(xctl *ctl) {
150 ctlcmdQueueSchedule(xctl, queue.Filter{}, true, time.Minute)
154 testctl(func(xctl *ctl) {
155 ctlcmdQueueTransport(xctl, queue.Filter{}, "socks")
159 testctl(func(xctl *ctl) {
160 ctlcmdQueueRequireTLS(xctl, queue.Filter{}, nil)
164 testctl(func(xctl *ctl) {
165 ctlcmdQueueDump(xctl, fmt.Sprintf("%d", qmid))
169 testctl(func(xctl *ctl) {
170 ctlcmdQueueFail(xctl, queue.Filter{})
174 testctl(func(xctl *ctl) {
175 ctlcmdQueueDrop(xctl, queue.Filter{})
178 // "queueholdruleslist"
179 testctl(func(xctl *ctl) {
180 ctlcmdQueueHoldrulesList(xctl)
183 // "queueholdrulesadd"
184 testctl(func(xctl *ctl) {
185 ctlcmdQueueHoldrulesAdd(xctl, "mjl", "", "")
187 testctl(func(xctl *ctl) {
188 ctlcmdQueueHoldrulesAdd(xctl, "mjl", "localhost", "")
191 // "queueholdrulesremove"
192 testctl(func(xctl *ctl) {
193 ctlcmdQueueHoldrulesRemove(xctl, 2)
195 testctl(func(xctl *ctl) {
196 ctlcmdQueueHoldrulesList(xctl)
199 // "queuesuppresslist"
200 testctl(func(xctl *ctl) {
201 ctlcmdQueueSuppressList(xctl, "mjl")
204 // "queuesuppressadd"
205 testctl(func(xctl *ctl) {
206 ctlcmdQueueSuppressAdd(xctl, "mjl", "base@localhost")
208 testctl(func(xctl *ctl) {
209 ctlcmdQueueSuppressAdd(xctl, "mjl", "other@localhost")
212 // "queuesuppresslookup"
213 testctl(func(xctl *ctl) {
214 ctlcmdQueueSuppressLookup(xctl, "mjl", "base@localhost")
217 // "queuesuppressremove"
218 testctl(func(xctl *ctl) {
219 ctlcmdQueueSuppressRemove(xctl, "mjl", "base@localhost")
221 testctl(func(xctl *ctl) {
222 ctlcmdQueueSuppressList(xctl, "mjl")
225 // "queueretiredlist"
226 testctl(func(xctl *ctl) {
227 ctlcmdQueueRetiredList(xctl, queue.RetiredFilter{}, queue.RetiredSort{})
230 // "queueretiredprint"
231 testctl(func(xctl *ctl) {
232 ctlcmdQueueRetiredPrint(xctl, "1")
236 testctl(func(xctl *ctl) {
237 ctlcmdQueueHookList(xctl, queue.HookFilter{}, queue.HookSort{})
240 // "queuehookschedule"
241 testctl(func(xctl *ctl) {
242 ctlcmdQueueHookSchedule(xctl, queue.HookFilter{}, true, time.Minute)
246 testctl(func(xctl *ctl) {
247 ctlcmdQueueHookPrint(xctl, "1")
251 testctl(func(xctl *ctl) {
252 ctlcmdQueueHookCancel(xctl, queue.HookFilter{})
255 // "queuehookretiredlist"
256 testctl(func(xctl *ctl) {
257 ctlcmdQueueHookRetiredList(xctl, queue.HookRetiredFilter{}, queue.HookRetiredSort{})
260 // "queuehookretiredprint"
261 testctl(func(xctl *ctl) {
262 ctlcmdQueueHookRetiredPrint(xctl, "1")
266 testctl(func(xctl *ctl) {
267 ctlcmdImport(xctl, true, "mjl", "inbox", "testdata/importtest.mbox")
271 testctl(func(xctl *ctl) {
272 ctlcmdImport(xctl, false, "mjl", "inbox", "testdata/importtest.maildir")
276 testctl(func(xctl *ctl) {
277 ctlcmdConfigDomainAdd(xctl, false, dns.Domain{ASCII: "mox2.example"}, "mjl", "")
281 testctl(func(xctl *ctl) {
282 ctlcmdConfigAccountAdd(xctl, "mjl2", "mjl2@mox2.example")
286 testctl(func(xctl *ctl) {
287 ctlcmdConfigAddressAdd(xctl, "mjl3@mox2.example", "mjl2")
291 testctl(func(xctl *ctl) {
292 ctlcmdDeliver(xctl, "mjl3@mox2.example")
294 // "retrain", retrain junk filter.
295 testctl(func(xctl *ctl) {
296 ctlcmdRetrain(xctl, "mjl2")
300 testctl(func(xctl *ctl) {
301 ctlcmdConfigAddressRemove(xctl, "mjl3@mox2.example")
305 testctl(func(xctl *ctl) {
306 ctlcmdConfigAccountDisabled(xctl, "mjl2", "testing")
310 testctl(func(xctl *ctl) {
311 ctlcmdConfigAccountList(xctl)
314 testctl(func(xctl *ctl) {
315 ctlcmdConfigAccountDisabled(xctl, "mjl2", "")
319 testctl(func(xctl *ctl) {
320 ctlcmdConfigAccountRemove(xctl, "mjl2")
324 testctl(func(xctl *ctl) {
325 ctlcmdConfigDomainDisabled(xctl, dns.Domain{ASCII: "mox2.example"}, true)
327 testctl(func(xctl *ctl) {
328 ctlcmdConfigDomainDisabled(xctl, dns.Domain{ASCII: "mox2.example"}, false)
332 testctl(func(xctl *ctl) {
333 ctlcmdConfigDomainRemove(xctl, dns.Domain{ASCII: "mox2.example"})
337 testctl(func(xctl *ctl) {
338 ctlcmdConfigAliasAdd(xctl, "support@mox.example", config.Alias{Addresses: []string{"mjl@mox.example"}})
342 testctl(func(xctl *ctl) {
343 ctlcmdConfigAliasList(xctl, "mox.example")
347 testctl(func(xctl *ctl) {
348 ctlcmdConfigAliasPrint(xctl, "support@mox.example")
352 testctl(func(xctl *ctl) {
353 ctlcmdConfigAliasUpdate(xctl, "support@mox.example", "true", "true", "true")
357 testctl(func(xctl *ctl) {
358 ctlcmdConfigAliasAddaddr(xctl, "support@mox.example", []string{"mjl2@mox.example"})
362 testctl(func(xctl *ctl) {
363 ctlcmdConfigAliasRmaddr(xctl, "support@mox.example", []string{"mjl2@mox.example"})
367 testctl(func(xctl *ctl) {
368 ctlcmdConfigAliasRemove(xctl, "support@mox.example")
371 // accounttlspubkeyadd
372 certDER := fakeCert(t)
373 testctl(func(xctl *ctl) {
374 ctlcmdConfigTlspubkeyAdd(xctl, "mjl@mox.example", "testkey", false, certDER)
377 // "accounttlspubkeylist"
378 testctl(func(xctl *ctl) {
379 ctlcmdConfigTlspubkeyList(xctl, "")
381 testctl(func(xctl *ctl) {
382 ctlcmdConfigTlspubkeyList(xctl, "mjl")
385 tpkl, err := store.TLSPublicKeyList(ctxbg, "")
386 tcheck(t, err, "list tls public keys")
388 t.Fatalf("got %d tls public keys, expected 1", len(tpkl))
390 fingerprint := tpkl[0].Fingerprint
392 // "accounttlspubkeyget"
393 testctl(func(xctl *ctl) {
394 ctlcmdConfigTlspubkeyGet(xctl, fingerprint)
397 // "accounttlspubkeyrm"
398 testctl(func(xctl *ctl) {
399 ctlcmdConfigTlspubkeyRemove(xctl, fingerprint)
402 tpkl, err = store.TLSPublicKeyList(ctxbg, "")
403 tcheck(t, err, "list tls public keys")
405 t.Fatalf("got %d tls public keys, expected 0", len(tpkl))
409 testctl(func(xctl *ctl) {
410 ctlcmdLoglevels(xctl)
414 testctl(func(xctl *ctl) {
415 ctlcmdSetLoglevels(xctl, "", "debug")
417 testctl(func(xctl *ctl) {
418 ctlcmdSetLoglevels(xctl, "smtpserver", "debug")
421 // Export data, import it again
422 xcmdExport(true, false, []string{filepath.FromSlash("testdata/ctl/data/tmp/export/mbox/"), filepath.FromSlash("testdata/ctl/data/accounts/mjl")}, &cmd{log: pkglog})
423 xcmdExport(false, false, []string{filepath.FromSlash("testdata/ctl/data/tmp/export/maildir/"), filepath.FromSlash("testdata/ctl/data/accounts/mjl")}, &cmd{log: pkglog})
424 testctl(func(xctl *ctl) {
425 ctlcmdImport(xctl, true, "mjl", "inbox", filepath.FromSlash("testdata/ctl/data/tmp/export/mbox/Inbox.mbox"))
427 testctl(func(xctl *ctl) {
428 ctlcmdImport(xctl, false, "mjl", "inbox", filepath.FromSlash("testdata/ctl/data/tmp/export/maildir/Inbox"))
431 // "recalculatemailboxcounts"
432 testctl(func(xctl *ctl) {
433 ctlcmdRecalculateMailboxCounts(xctl, "mjl")
437 testctl(func(xctl *ctl) {
438 ctlcmdFixmsgsize(xctl, "mjl")
440 testctl(func(xctl *ctl) {
441 acc, err := store.OpenAccount(xctl.log, "mjl", false)
442 tcheck(t, err, "open account")
448 content := []byte("Subject: hi\r\n\r\nbody\r\n")
450 deliver := func(m *store.Message) {
452 m.Size = int64(len(content))
453 msgf, err := store.CreateMessageTemp(xctl.log, "ctltest")
454 tcheck(t, err, "create temp file")
455 defer os.Remove(msgf.Name())
457 _, err = msgf.Write(content)
458 tcheck(t, err, "write message file")
460 acc.WithWLock(func() {
461 err = acc.DeliverMailbox(xctl.log, "Inbox", m, msgf)
462 tcheck(t, err, "deliver message")
466 var msgBadSize store.Message
470 err = acc.DB.Update(ctxbg, &msgBadSize)
471 tcheck(t, err, "update message to bad size")
472 mb := store.Mailbox{ID: msgBadSize.MailboxID}
473 err = acc.DB.Get(ctxbg, &mb)
474 tcheck(t, err, "get db")
475 mb.Size -= int64(len(content))
477 err = acc.DB.Update(ctxbg, &mb)
478 tcheck(t, err, "update mailbox size")
481 ctlcmdFixmsgsize(xctl, "")
483 err = acc.DB.Get(ctxbg, &msgBadSize)
484 tcheck(t, err, "get message")
485 if msgBadSize.Size != int64(len(content)) {
486 t.Fatalf("after fixing, message size is %d, should be %d", msgBadSize.Size, len(content))
491 testctl(func(xctl *ctl) {
492 ctlcmdReparse(xctl, "mjl")
494 testctl(func(xctl *ctl) {
495 ctlcmdReparse(xctl, "")
499 testctl(func(xctl *ctl) {
500 ctlcmdReassignthreads(xctl, "mjl")
502 testctl(func(xctl *ctl) {
503 ctlcmdReassignthreads(xctl, "")
506 // "backup", backup account.
508 tcheck(t, err, "dmarcdb init")
509 defer dmarcdb.Close()
510 err = mtastsdb.Init(false)
511 tcheck(t, err, "mtastsdb init")
512 defer mtastsdb.Close()
513 err = tlsrptdb.Init()
514 tcheck(t, err, "tlsrptdb init")
515 defer tlsrptdb.Close()
516 testctl(func(xctl *ctl) {
517 os.RemoveAll("testdata/ctl/data/tmp/backup")
518 err := os.WriteFile("testdata/ctl/data/receivedid.key", make([]byte, 16), 0600)
519 tcheck(t, err, "writing receivedid.key")
520 ctlcmdBackup(xctl, filepath.FromSlash("testdata/ctl/data/tmp/backup"), false)
523 // Verify the backup.
525 flag: flag.NewFlagSet("", flag.ExitOnError),
526 flagArgs: []string{filepath.FromSlash("testdata/ctl/data/tmp/backup/data")},
531 testctl(func(xctl *ctl) {
534 opts := imapclient.Opts{
535 Logger: slog.Default().With("cid", mox.Cid()),
536 Error: func(err error) { panic(err) },
538 client, err := imapclient.New(a, &opts)
539 tcheck(t, err, "new imapclient")
540 client.Select("inbox")
544 ctlcmdIMAPServe(xctl, "mjl@mox.example", b, b)
548func fakeCert(t *testing.T) []byte {
550 seed := make([]byte, ed25519.SeedSize)
551 privKey := ed25519.NewKeyFromSeed(seed) // Fake key, don't use this for real!
552 template := &x509.Certificate{
553 SerialNumber: big.NewInt(1), // Required field...
555 localCertBuf, err := x509.CreateCertificate(cryptorand.Reader, template, template, privKey.Public(), privKey)
556 tcheck(t, err, "making certificate")