1package imapserver
2
3import (
4 "encoding/base64"
5 "errors"
6 "fmt"
7 "io"
8 "log/slog"
9 "net"
10 "os"
11 "path/filepath"
12 "testing"
13 "time"
14
15 "github.com/mjl-/mox/imapclient"
16 "github.com/mjl-/mox/mlog"
17 "github.com/mjl-/mox/mox-"
18 "github.com/mjl-/mox/store"
19)
20
21// Fuzz the server. For each fuzz string, we set up servers in various connection states, and write the string as command.
22func FuzzServer(f *testing.F) {
23 seed := []string{
24 fmt.Sprintf("authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl@mox.example\u0000testtest"))),
25 "*",
26 "capability",
27 "noop",
28 "logout",
29 "select inbox",
30 "examine inbox",
31 "unselect",
32 "close",
33 "expunge",
34 "subscribe inbox",
35 "unsubscribe inbox",
36 `lsub "" "*"`,
37 `list "" ""`,
38 `namespace`,
39 "enable utf8=accept",
40 "create inbox",
41 "create tmpbox",
42 "rename tmpbox ntmpbox",
43 "delete ntmpbox",
44 "status inbox (uidnext messages uidvalidity deleted size unseen recent)",
45 "append inbox (\\seen) {2+}\r\nhi",
46 "fetch 1 all",
47 "fetch 1 body",
48 "fetch 1 (bodystructure)",
49 `store 1 flags (\seen \answered)`,
50 `store 1 +flags ($junk)`,
51 `store 1 -flags ($junk)`,
52 "noop",
53 "copy 1Trash",
54 "copy 1 Trash",
55 "move 1 Trash",
56 "search 1 all",
57 }
58 for _, cmd := range seed {
59 const tag = "x "
60 f.Add(tag + cmd)
61 }
62
63 var cid int64 = 1
64
65 var fl *os.File
66 if false {
67 var err error
68 fl, err = os.OpenFile("fuzz.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
69 if err != nil {
70 f.Fatalf("fuzz log")
71 }
72 defer fl.Close()
73 }
74 flog := func(err error, msg string) {
75 if fl != nil && err != nil {
76 fmt.Fprintf(fl, "%s: %v\n", msg, err)
77 }
78 }
79
80 f.Fuzz(func(t *testing.T, s string) {
81 log := mlog.New("imapserver", nil)
82 mox.ConfigStaticPath = filepath.FromSlash("../testdata/imapserverfuzz/mox.conf")
83 mox.MustLoadConfig(true, false)
84 store.Close() // May not be open, we ignore error.
85 dataDir := mox.ConfigDirPath(mox.Conf.Static.DataDir)
86 os.RemoveAll(dataDir)
87 err := store.Init(ctxbg)
88 if err != nil {
89 t.Fatalf("store init: %v", err)
90 }
91 defer store.Switchboard()()
92
93 acc, err := store.OpenAccount(log, "mjl", false)
94 if err != nil {
95 t.Fatalf("open account: %v", err)
96 }
97 defer func() {
98 acc.Close()
99 acc.WaitClosed()
100 }()
101 err = acc.SetPassword(log, password0)
102 if err != nil {
103 t.Fatalf("set password: %v", err)
104 }
105
106 comm := store.RegisterComm(acc)
107 defer comm.Unregister()
108
109 run := func(cmds []string) {
110 limitersInit() // Reset rate limiters.
111 serverConn, clientConn := net.Pipe()
112 defer serverConn.Close()
113
114 go func() {
115 defer func() {
116 x := recover()
117 // Protocol can become botched, when fuzzer sends literals.
118 if x == nil {
119 return
120 }
121 err, ok := x.(error)
122 if !ok || (!errors.Is(err, os.ErrDeadlineExceeded) && !errors.Is(err, io.EOF)) {
123 panic(x)
124 }
125 }()
126
127 defer clientConn.Close()
128
129 err := clientConn.SetDeadline(time.Now().Add(time.Second))
130 flog(err, "set client deadline")
131 opts := imapclient.Opts{
132 Logger: slog.Default().With("cid", mox.Cid()),
133 Error: func(err error) { panic(err) },
134 }
135 client, _ := imapclient.New(clientConn, &opts)
136
137 for _, cmd := range cmds {
138 client.WriteCommandf("", "%s", cmd)
139 client.ReadResponse()
140 }
141 client.WriteCommandf("", "%s", s)
142 client.ReadResponse()
143 }()
144
145 err = serverConn.SetDeadline(time.Now().Add(time.Second))
146 flog(err, "set server deadline")
147 serve("test", cid, nil, serverConn, false, true, false, "")
148 cid++
149 }
150
151 // Each command brings the connection state one step further. We try the fuzzing
152 // input for each state.
153 run([]string{})
154 run([]string{`login mjl@mox.example "` + password0 + `"`})
155 run([]string{`login mjl@mox.example "` + password0 + `"`, "select inbox"})
156 xappend := fmt.Sprintf("append inbox () {%d+}\r\n%s", len(exampleMsg), exampleMsg)
157 run([]string{`login mjl@mox.example "` + password0 + `"`, "select inbox", xappend})
158 })
159}
160