1package smtpserver
2
3import (
4 "errors"
5 "path/filepath"
6 "strings"
7 "testing"
8
9 "github.com/mjl-/bstore"
10
11 "github.com/mjl-/mox/dns"
12 "github.com/mjl-/mox/smtp"
13 "github.com/mjl-/mox/smtpclient"
14 "github.com/mjl-/mox/store"
15)
16
17// Check user can submit message with message From address they are member of.
18func TestAliasSubmitMsgFrom(t *testing.T) {
19 ts := newTestServer(t, filepath.FromSlash("../testdata/smtp/mox.conf"), dns.MockResolver{})
20 defer ts.close()
21
22 ts.submission = true
23 ts.user = "mjl@mox.example"
24 ts.pass = password0
25
26 var msg = strings.ReplaceAll(`From: <public@mox.example>
27To: <public@mox.example>
28Subject: test
29
30test email
31`, "\n", "\r\n")
32
33 ts.run(func(err error, client *smtpclient.Client) {
34 t.Helper()
35 mailFrom := "mjl@mox.example"
36 rcptTo := "public@mox.example"
37 if err == nil {
38 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
39 }
40 ts.smtpErr(err, nil)
41 })
42
43 ts.run(func(err error, client *smtpclient.Client) {
44 t.Helper()
45 mailFrom := "public@mox.example" // List address as smtp mail from.
46 rcptTo := "public@mox.example"
47 if err == nil {
48 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
49 }
50 ts.smtpErr(err, nil)
51 })
52
53 msg = strings.ReplaceAll(`From: <private@mox.example>
54To: <private@mox.example>
55Subject: test
56
57test email
58`, "\n", "\r\n")
59
60 ts.run(func(err error, client *smtpclient.Client) {
61 t.Helper()
62 mailFrom := "mjl@mox.example"
63 rcptTo := "private@mox.example"
64 if err == nil {
65 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
66 }
67 ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SePol7DeliveryUnauth1})
68 })
69
70 ts.run(func(err error, client *smtpclient.Client) {
71 t.Helper()
72 mailFrom := "private@mox.example" // List address as smtp mail from.
73 rcptTo := "private@mox.example"
74 if err == nil {
75 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
76 }
77 ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SePol7DeliveryUnauth1})
78 })
79}
80
81// Non-member cannot submit as alias that allows it for members.
82func TestAliasSubmitMsgFromDenied(t *testing.T) {
83 ts := newTestServer(t, filepath.FromSlash("../testdata/smtp/mox.conf"), dns.MockResolver{})
84 defer ts.close()
85
86 // Trying to open account by alias should result in proper error.
87 _, _, err := store.OpenEmail(pkglog, "public@mox.example")
88 if err == nil || !errors.Is(err, store.ErrUnknownCredentials) {
89 t.Fatalf("opening alias, got err %v, expected store.ErrUnknownCredentials", err)
90 }
91
92 acc, err := store.OpenAccount(pkglog, "☺")
93 tcheck(t, err, "open account")
94 err = acc.SetPassword(pkglog, password0)
95 tcheck(t, err, "set password")
96 err = acc.Close()
97 tcheck(t, err, "close account")
98 acc.CheckClosed()
99
100 ts.submission = true
101 ts.user = "☺@mox.example"
102 ts.pass = password0
103
104 var msg = strings.ReplaceAll(`From: <public@mox.example>
105To: <public@mox.example>
106Subject: test
107
108test email
109`, "\n", "\r\n")
110
111 ts.run(func(err error, client *smtpclient.Client) {
112 t.Helper()
113 mailFrom := "☺@mox.example"
114 rcptTo := "public@mox.example"
115 if err == nil {
116 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), true, true, false)
117 }
118 ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SePol7DeliveryUnauth1})
119 })
120
121 ts.run(func(err error, client *smtpclient.Client) {
122 t.Helper()
123 mailFrom := "public@mox.example" // List address as message from.
124 rcptTo := "public@mox.example"
125 if err == nil {
126 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), true, true, false)
127 }
128 ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SePol7DeliveryUnauth1})
129 })
130}
131
132// Non-member can deliver to public list, not to private list.
133func TestAliasDeliverNonMember(t *testing.T) {
134 resolver := dns.MockResolver{
135 A: map[string][]string{
136 "example.org.": {"127.0.0.10"}, // For mx check.
137 },
138 PTR: map[string][]string{
139 "127.0.0.10": {"example.org."}, // To get passed junk filter.
140 },
141 }
142 ts := newTestServer(t, filepath.FromSlash("../testdata/smtp/mox.conf"), resolver)
143 defer ts.close()
144
145 var msg = strings.ReplaceAll(`From: <other@example.org>
146To: <private@mox.example>
147
148test email
149`, "\n", "\r\n")
150
151 ts.run(func(err error, client *smtpclient.Client) {
152 t.Helper()
153 mailFrom := "other@example.org"
154 rcptTo := "private@mox.example"
155 if err == nil {
156 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
157 }
158 ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SePol7ExpnProhibited2})
159 })
160
161 msg = strings.ReplaceAll(`From: <private@mox.example>
162To: <private@mox.example>
163
164test email
165`, "\n", "\r\n")
166
167 ts.run(func(err error, client *smtpclient.Client) {
168 t.Helper()
169 mailFrom := "private@example.org"
170 rcptTo := "private@mox.example"
171 if err == nil {
172 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
173 }
174 ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SePol7ExpnProhibited2})
175 })
176
177 msg = strings.ReplaceAll(`From: <other@example.org>
178To: <public@mox.example>
179Subject: test
180
181test email
182`, "\n", "\r\n")
183
184 ts.run(func(err error, client *smtpclient.Client) {
185 t.Helper()
186 mailFrom := "other@example.org"
187 rcptTo := "public@mox.example"
188 if err == nil {
189 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
190 }
191 ts.smtpErr(err, nil)
192
193 ts.checkCount("Inbox", 2) // Receiving for both mjl@ and móx@.
194 })
195
196 ts.run(func(err error, client *smtpclient.Client) {
197 t.Helper()
198 mailFrom := "public@example.org" // List address as message from.
199 rcptTo := "public@mox.example"
200 if err == nil {
201 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
202 }
203 ts.smtpErr(err, nil)
204
205 ts.checkCount("Inbox", 4) // Receiving for both mjl@ and móx@.
206 })
207}
208
209// Member can deliver to private list, but still not with alias address as message
210// from. Message with alias from address as message from is allowed.
211func TestAliasDeliverMember(t *testing.T) {
212 resolver := dns.MockResolver{
213 A: map[string][]string{
214 "mox.example.": {"127.0.0.10"}, // For mx check.
215 },
216 PTR: map[string][]string{
217 "127.0.0.10": {"mox.example."}, // To get passed junk filter.
218 },
219 TXT: map[string][]string{
220 "mox.example.": {"v=spf1 ip4:127.0.0.10 -all"}, // To allow multiple recipients in transaction.
221 },
222 }
223 ts := newTestServer(t, filepath.FromSlash("../testdata/smtp/mox.conf"), resolver)
224 defer ts.close()
225
226 var msg = strings.ReplaceAll(`From: <mjl@mox.example>
227To: <private@mox.example>
228
229test email
230`, "\n", "\r\n")
231
232 ts.run(func(err error, client *smtpclient.Client) {
233 t.Helper()
234 mailFrom := "mjl@mox.example"
235 rcptTo := []string{"private@mox.example", "móx@mox.example"}
236 if err == nil {
237 _, err = client.DeliverMultiple(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), true, true, false)
238 // assuming there wasn't a per-recipient error
239 }
240 ts.smtpErr(err, nil)
241
242 ts.checkCount("Inbox", 0) // Not receiving for mjl@ due to msgfrom, and not móx@ due to rcpt to.
243 })
244
245 ts.run(func(err error, client *smtpclient.Client) {
246 t.Helper()
247 mailFrom := "mjl@mox.example"
248 rcptTo := "private@mox.example"
249 if err == nil {
250 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
251 }
252 ts.smtpErr(err, nil)
253
254 ts.checkCount("Inbox", 1) // Only receiving for móx@mox.example, not mjl@.
255 })
256
257 msg = strings.ReplaceAll(`From: <private@mox.example>
258To: <private@mox.example>
259Subject: test
260
261test email
262`, "\n", "\r\n")
263
264 ts.run(func(err error, client *smtpclient.Client) {
265 t.Helper()
266 mailFrom := "other@mox.example"
267 rcptTo := "private@mox.example"
268 if err == nil {
269 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
270 }
271 ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SePol7ExpnProhibited2})
272 })
273
274 msg = strings.ReplaceAll(`From: <public@mox.example>
275To: <public@mox.example>
276Subject: test
277
278test email
279`, "\n", "\r\n")
280
281 ts.run(func(err error, client *smtpclient.Client) {
282 t.Helper()
283 mailFrom := "mjl@mox.example"
284 rcptTo := "public@mox.example"
285 if err == nil {
286 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
287 }
288 ts.smtpErr(err, nil)
289 })
290}
291
292// Message is rejected if no member accepts it.
293func TestAliasDeliverReject(t *testing.T) {
294 resolver := dns.MockResolver{
295 A: map[string][]string{
296 "mox.example.": {"127.0.0.10"}, // For mx check.
297 },
298 PTR: map[string][]string{
299 "127.0.0.10": {"mox.example."}, // To get passed junk filter.
300 },
301 TXT: map[string][]string{
302 "mox.example.": {"v=spf1 ip4:127.0.0.10 -all"}, // To allow multiple recipients in transaction.
303 },
304 }
305 ts := newTestServer(t, filepath.FromSlash("../testdata/smtp/mox.conf"), resolver)
306 defer ts.close()
307
308 var msg = strings.ReplaceAll(`From: <mjl@mox.example>
309To: <private@mox.example>
310
311test email
312`, "\n", "\r\n")
313
314 ts.run(func(err error, client *smtpclient.Client) {
315 t.Helper()
316 mailFrom := "mjl@mox.example"
317 rcptTo := "private@mox.example"
318 if err == nil {
319 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
320 }
321 ts.smtpErr(err, nil)
322
323 ts.checkCount("Inbox", 1) // Only receiving for móx@mox.example, not mjl@.
324 })
325
326 // Mark message as junk.
327 q := bstore.QueryDB[store.Message](ctxbg, ts.acc.DB)
328 n, err := q.UpdateFields(map[string]any{"Junk": true})
329 tcheck(t, err, "mark as junk")
330 tcompare(t, n, 1)
331
332 ts.run(func(err error, client *smtpclient.Client) {
333 t.Helper()
334 mailFrom := "mjl@mox.example"
335 rcptTo := "private@mox.example"
336 if err == nil {
337 err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
338 }
339 ts.smtpErr(err, &smtpclient.Error{Code: smtp.C451LocalErr, Secode: smtp.SeSys3Other0})
340 })
341}
342