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