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