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