1 package mail
2
3 import (
4 "bufio"
5 "crypto/tls"
6 "crypto/x509"
7 "fmt"
8 "math/big"
9 "net"
10 "net/mail"
11 "net/textproto"
12 "os"
13 "strings"
14 "testing"
15 "time"
16
17 "github.com/jmhodges/clock"
18
19 blog "github.com/letsencrypt/boulder/log"
20 "github.com/letsencrypt/boulder/metrics"
21 "github.com/letsencrypt/boulder/test"
22 )
23
24 type fakeSource struct{}
25
26 func (f fakeSource) generate() *big.Int {
27 return big.NewInt(1991)
28 }
29
30 func TestGenerateMessage(t *testing.T) {
31 fc := clock.NewFake()
32 fromAddress, _ := mail.ParseAddress("happy sender <send@email.com>")
33 log := blog.UseMock()
34 m := New("", "", "", "", nil, *fromAddress, log, metrics.NoopRegisterer, 0, 0)
35 m.clk = fc
36 m.csprgSource = fakeSource{}
37 messageBytes, err := m.generateMessage([]string{"recv@email.com"}, "test subject", "this is the body\n")
38 test.AssertNotError(t, err, "Failed to generate email body")
39 message := string(messageBytes)
40 fields := strings.Split(message, "\r\n")
41 test.AssertEquals(t, len(fields), 12)
42 fmt.Println(message)
43 test.AssertEquals(t, fields[0], "To: \"recv@email.com\"")
44 test.AssertEquals(t, fields[1], "From: \"happy sender\" <send@email.com>")
45 test.AssertEquals(t, fields[2], "Subject: test subject")
46 test.AssertEquals(t, fields[3], "Date: 01 Jan 70 00:00 UTC")
47 test.AssertEquals(t, fields[4], "Message-Id: <19700101T000000.1991.send@email.com>")
48 test.AssertEquals(t, fields[5], "MIME-Version: 1.0")
49 test.AssertEquals(t, fields[6], "Content-Type: text/plain; charset=UTF-8")
50 test.AssertEquals(t, fields[7], "Content-Transfer-Encoding: quoted-printable")
51 test.AssertEquals(t, fields[8], "")
52 test.AssertEquals(t, fields[9], "this is the body")
53 }
54
55 func TestFailNonASCIIAddress(t *testing.T) {
56 log := blog.UseMock()
57 fromAddress, _ := mail.ParseAddress("send@email.com")
58 m := New("", "", "", "", nil, *fromAddress, log, metrics.NoopRegisterer, 0, 0)
59 _, err := m.generateMessage([]string{"遗憾@email.com"}, "test subject", "this is the body\n")
60 test.AssertError(t, err, "Allowed a non-ASCII to address incorrectly")
61 }
62
63 func expect(t *testing.T, buf *bufio.Reader, expected string) error {
64 line, _, err := buf.ReadLine()
65 if err != nil {
66 t.Errorf("readline: %s expected: %s\n", err, expected)
67 return err
68 }
69 if string(line) != expected {
70 t.Errorf("Expected %s, got %s", expected, line)
71 return fmt.Errorf("Expected %s, got %s", expected, line)
72 }
73 return nil
74 }
75
76 type connHandler func(int, *testing.T, net.Conn, *net.TCPConn)
77
78 func listenForever(l *net.TCPListener, t *testing.T, handler connHandler) {
79 keyPair, err := tls.LoadX509KeyPair("../test/mail-test-srv/localhost/cert.pem", "../test/mail-test-srv/localhost/key.pem")
80 if err != nil {
81 t.Errorf("loading keypair: %s", err)
82
83 }
84 tlsConf := &tls.Config{
85 Certificates: []tls.Certificate{keyPair},
86 }
87 connID := 0
88 for {
89 tcpConn, err := l.AcceptTCP()
90 if err != nil {
91 return
92 }
93
94 tlsConn := tls.Server(tcpConn, tlsConf)
95 connID++
96 go handler(connID, t, tlsConn, tcpConn)
97 }
98 }
99
100 func authenticateClient(t *testing.T, conn net.Conn) {
101 buf := bufio.NewReader(conn)
102
103
104
105 _, _ = conn.Write([]byte("220 smtp.example.com ESMTP\n"))
106 err := expect(t, buf, "EHLO localhost")
107 if err != nil {
108 return
109 }
110
111 _, _ = conn.Write([]byte("250-PIPELINING\n"))
112 _, _ = conn.Write([]byte("250-AUTH PLAIN LOGIN\n"))
113 _, _ = conn.Write([]byte("250 8BITMIME\n"))
114
115 err = expect(t, buf, "AUTH PLAIN AHVzZXJAZXhhbXBsZS5jb20AcGFzc3dk")
116 if err != nil {
117 return
118 }
119 _, _ = conn.Write([]byte("235 2.7.0 Authentication successful\n"))
120 }
121
122
123
124 func normalHandler(connID int, t *testing.T, tlsConn net.Conn, tcpConn *net.TCPConn) {
125 defer func() {
126 err := tlsConn.Close()
127 if err != nil {
128 t.Errorf("conn.Close: %s", err)
129 }
130 }()
131 authenticateClient(t, tlsConn)
132 }
133
134
135
136
137
138
139
140
141 func disconnectHandler(closeFirst int, goodbyeMsg string) connHandler {
142 return func(connID int, t *testing.T, conn net.Conn, _ *net.TCPConn) {
143 defer func() {
144 err := conn.Close()
145 if err != nil {
146 t.Errorf("conn.Close: %s", err)
147 }
148 }()
149 authenticateClient(t, conn)
150
151 buf := bufio.NewReader(conn)
152 err := expect(t, buf, "MAIL FROM:<<you-are-a-winner@example.com>> BODY=8BITMIME")
153 if err != nil {
154 return
155 }
156
157 if connID <= closeFirst {
158
159
160
161 if goodbyeMsg != "" {
162 _, _ = fmt.Fprintf(conn, "%s\r\n", goodbyeMsg)
163 t.Logf("Wrote goodbye msg: %s", goodbyeMsg)
164 }
165 t.Log("Cutting off client early")
166 return
167 }
168 _, _ = conn.Write([]byte("250 Sure. Go on. \r\n"))
169
170 err = expect(t, buf, "RCPT TO:<hi@bye.com>")
171 if err != nil {
172 return
173 }
174 _, _ = conn.Write([]byte("250 Tell Me More \r\n"))
175
176 err = expect(t, buf, "DATA")
177 if err != nil {
178 return
179 }
180 _, _ = conn.Write([]byte("354 Cool Data\r\n"))
181 _, _ = conn.Write([]byte("250 Peace Out\r\n"))
182 }
183 }
184
185 func badEmailHandler(messagesToProcess int) connHandler {
186 return func(_ int, t *testing.T, conn net.Conn, _ *net.TCPConn) {
187 defer func() {
188 err := conn.Close()
189 if err != nil {
190 t.Errorf("conn.Close: %s", err)
191 }
192 }()
193 authenticateClient(t, conn)
194
195 buf := bufio.NewReader(conn)
196 err := expect(t, buf, "MAIL FROM:<<you-are-a-winner@example.com>> BODY=8BITMIME")
197 if err != nil {
198 return
199 }
200
201 _, _ = conn.Write([]byte("250 Sure. Go on. \r\n"))
202
203 err = expect(t, buf, "RCPT TO:<hi@bye.com>")
204 if err != nil {
205 return
206 }
207 _, _ = conn.Write([]byte("401 4.1.3 Bad recipient address syntax\r\n"))
208 err = expect(t, buf, "RSET")
209 if err != nil {
210 return
211 }
212 _, _ = conn.Write([]byte("250 Ok yr rset now\r\n"))
213 }
214 }
215
216
217
218
219
220
221
222
223
224 func rstHandler(rstFirst int) connHandler {
225 return func(connID int, t *testing.T, tlsConn net.Conn, tcpConn *net.TCPConn) {
226 defer func() {
227 err := tcpConn.Close()
228 if err != nil {
229 t.Errorf("conn.Close: %s", err)
230 }
231 }()
232 authenticateClient(t, tlsConn)
233
234 buf := bufio.NewReader(tlsConn)
235 err := expect(t, buf, "MAIL FROM:<<you-are-a-winner@example.com>> BODY=8BITMIME")
236 if err != nil {
237 return
238 }
239
240
241 if connID <= rstFirst {
242 err := tcpConn.SetLinger(0)
243 if err != nil {
244 t.Error(err)
245 return
246 }
247 t.Log("Socket set for abruptive close. Cutting off client early")
248 return
249 }
250 _, _ = tlsConn.Write([]byte("250 Sure. Go on. \r\n"))
251
252 err = expect(t, buf, "RCPT TO:<hi@bye.com>")
253 if err != nil {
254 return
255 }
256 _, _ = tlsConn.Write([]byte("250 Tell Me More \r\n"))
257
258 err = expect(t, buf, "DATA")
259 if err != nil {
260 return
261 }
262 _, _ = tlsConn.Write([]byte("354 Cool Data\r\n"))
263 _, _ = tlsConn.Write([]byte("250 Peace Out\r\n"))
264 }
265 }
266
267 func setup(t *testing.T) (*mailerImpl, *net.TCPListener, func()) {
268 fromAddress, _ := mail.ParseAddress("you-are-a-winner@example.com")
269 log := blog.UseMock()
270
271
272 tcpAddr, err := net.ResolveTCPAddr("tcp", ":0")
273 if err != nil {
274 t.Fatalf("resolving tcp addr: %s", err)
275 }
276 tcpl, err := net.ListenTCP("tcp", tcpAddr)
277 if err != nil {
278 t.Fatalf("listen: %s", err)
279 }
280
281 cleanUp := func() {
282 err := tcpl.Close()
283 if err != nil {
284 t.Errorf("listen.Close: %s", err)
285 }
286 }
287
288 pem, err := os.ReadFile("../test/mail-test-srv/minica.pem")
289 if err != nil {
290 t.Fatalf("loading smtp root: %s", err)
291 }
292 smtpRoots := x509.NewCertPool()
293 ok := smtpRoots.AppendCertsFromPEM(pem)
294 if !ok {
295 t.Fatal("failed parsing SMTP root")
296 }
297
298
299
300
301 _, port, err := net.SplitHostPort(tcpl.Addr().String())
302 if err != nil {
303 t.Fatal("failed parsing port from tcp listen")
304 }
305
306 m := New(
307 "localhost",
308 port,
309 "user@example.com",
310 "passwd",
311 smtpRoots,
312 *fromAddress,
313 log,
314 metrics.NoopRegisterer,
315 time.Second*2, time.Second*10)
316
317 return m, tcpl, cleanUp
318 }
319
320 func TestConnect(t *testing.T) {
321 m, l, cleanUp := setup(t)
322 defer cleanUp()
323
324 go listenForever(l, t, normalHandler)
325 conn, err := m.Connect()
326 if err != nil {
327 t.Errorf("Failed to connect: %s", err)
328 }
329 err = conn.Close()
330 if err != nil {
331 t.Errorf("Failed to clean up: %s", err)
332 }
333 }
334
335 func TestReconnectSuccess(t *testing.T) {
336 m, l, cleanUp := setup(t)
337 defer cleanUp()
338 const closedConns = 5
339
340
341
342 go listenForever(l, t, disconnectHandler(closedConns, ""))
343
344
345
346
347 conn, err := m.Connect()
348 if err != nil {
349 t.Errorf("Failed to connect: %s", err)
350 }
351 err = conn.SendMail([]string{"hi@bye.com"}, "You are already a winner!", "Just kidding")
352 if err != nil {
353 t.Errorf("Expected SendMail() to not fail. Got err: %s", err)
354 }
355 }
356
357 func TestBadEmailError(t *testing.T) {
358 m, l, cleanUp := setup(t)
359 defer cleanUp()
360 const messages = 3
361
362 go listenForever(l, t, badEmailHandler(messages))
363
364 conn, err := m.Connect()
365 if err != nil {
366 t.Errorf("Failed to connect: %s", err)
367 }
368
369 err = conn.SendMail([]string{"hi@bye.com"}, "You are already a winner!", "Just kidding")
370
371 if err == nil {
372 t.Errorf("Expected SendMail() to return an BadAddressSMTPError, got nil")
373 }
374 expected := "401: 4.1.3 Bad recipient address syntax"
375 var badAddrErr BadAddressSMTPError
376 test.AssertErrorWraps(t, err, &badAddrErr)
377 test.AssertEquals(t, badAddrErr.Message, expected)
378 }
379
380 func TestReconnectSMTP421(t *testing.T) {
381 m, l, cleanUp := setup(t)
382 defer cleanUp()
383 const closedConns = 5
384
385
386
387 smtp421 := "421 1.2.3 green.eggs.and.spam Error: timeout exceeded"
388
389
390
391 go listenForever(l, t, disconnectHandler(closedConns, smtp421))
392
393
394
395
396 conn, err := m.Connect()
397 if err != nil {
398 t.Errorf("Failed to connect: %s", err)
399 }
400 err = conn.SendMail([]string{"hi@bye.com"}, "You are already a winner!", "Just kidding")
401 if err != nil {
402 t.Errorf("Expected SendMail() to not fail. Got err: %s", err)
403 }
404 }
405
406 func TestOtherError(t *testing.T) {
407 m, l, cleanUp := setup(t)
408 defer cleanUp()
409
410 go listenForever(l, t, func(_ int, t *testing.T, conn net.Conn, _ *net.TCPConn) {
411 defer func() {
412 err := conn.Close()
413 if err != nil {
414 t.Errorf("conn.Close: %s", err)
415 }
416 }()
417 authenticateClient(t, conn)
418
419 buf := bufio.NewReader(conn)
420 err := expect(t, buf, "MAIL FROM:<<you-are-a-winner@example.com>> BODY=8BITMIME")
421 if err != nil {
422 return
423 }
424
425 _, _ = conn.Write([]byte("250 Sure. Go on. \r\n"))
426
427 err = expect(t, buf, "RCPT TO:<hi@bye.com>")
428 if err != nil {
429 return
430 }
431
432 _, _ = conn.Write([]byte("999 1.1.1 This would probably be bad?\r\n"))
433
434 err = expect(t, buf, "RSET")
435 if err != nil {
436 return
437 }
438
439 _, _ = conn.Write([]byte("250 Ok yr rset now\r\n"))
440 })
441
442 conn, err := m.Connect()
443 if err != nil {
444 t.Errorf("Failed to connect: %s", err)
445 }
446
447 err = conn.SendMail([]string{"hi@bye.com"}, "You are already a winner!", "Just kidding")
448
449 if err == nil {
450 t.Errorf("Expected SendMail() to return an error, got nil")
451 }
452 expected := "999 1.1.1 This would probably be bad?"
453 var rcptErr *textproto.Error
454 test.AssertErrorWraps(t, err, &rcptErr)
455 test.AssertEquals(t, rcptErr.Error(), expected)
456
457 m, l, cleanUp = setup(t)
458 defer cleanUp()
459
460 go listenForever(l, t, func(_ int, t *testing.T, conn net.Conn, _ *net.TCPConn) {
461 defer func() {
462 err := conn.Close()
463 if err != nil {
464 t.Errorf("conn.Close: %s", err)
465 }
466 }()
467 authenticateClient(t, conn)
468
469 buf := bufio.NewReader(conn)
470 err := expect(t, buf, "MAIL FROM:<<you-are-a-winner@example.com>> BODY=8BITMIME")
471 if err != nil {
472 return
473 }
474
475 _, _ = conn.Write([]byte("250 Sure. Go on. \r\n"))
476
477 err = expect(t, buf, "RCPT TO:<hi@bye.com>")
478 if err != nil {
479 return
480 }
481
482 _, _ = conn.Write([]byte("999 1.1.1 This would probably be bad?\r\n"))
483
484 err = expect(t, buf, "RSET")
485 if err != nil {
486 return
487 }
488
489 _, _ = conn.Write([]byte("nop\r\n"))
490 })
491 conn, err = m.Connect()
492 if err != nil {
493 t.Errorf("Failed to connect: %s", err)
494 }
495
496 err = conn.SendMail([]string{"hi@bye.com"}, "You are already a winner!", "Just kidding")
497
498 test.AssertError(t, err, "SendMail didn't fail as expected")
499 test.AssertEquals(t, err.Error(), "999 1.1.1 This would probably be bad? (also, on sending RSET: short response: nop)")
500 }
501
502 func TestReconnectAfterRST(t *testing.T) {
503 m, l, cleanUp := setup(t)
504 defer cleanUp()
505 const rstConns = 5
506
507
508
509 go listenForever(l, t, rstHandler(rstConns))
510
511
512
513
514 conn, err := m.Connect()
515 if err != nil {
516 t.Errorf("Failed to connect: %s", err)
517 }
518 err = conn.SendMail([]string{"hi@bye.com"}, "You are already a winner!", "Just kidding")
519 if err != nil {
520 t.Errorf("Expected SendMail() to not fail. Got err: %s", err)
521 }
522 }
523
View as plain text