Source file
src/net/smtp/smtp_test.go
1
2
3
4
5 package smtp
6
7 import (
8 "bufio"
9 "bytes"
10 "crypto/tls"
11 "crypto/x509"
12 "fmt"
13 "internal/testenv"
14 "io"
15 "net"
16 "net/textproto"
17 "runtime"
18 "strings"
19 "testing"
20 "time"
21 )
22
23 type authTest struct {
24 auth Auth
25 challenges []string
26 name string
27 responses []string
28 }
29
30 var authTests = []authTest{
31 {PlainAuth("", "user", "pass", "testserver"), []string{}, "PLAIN", []string{"\x00user\x00pass"}},
32 {PlainAuth("foo", "bar", "baz", "testserver"), []string{}, "PLAIN", []string{"foo\x00bar\x00baz"}},
33 {CRAMMD5Auth("user", "pass"), []string{"<123456.1322876914@testserver>"}, "CRAM-MD5", []string{"", "user 287eb355114cf5c471c26a875f1ca4ae"}},
34 }
35
36 func TestAuth(t *testing.T) {
37 testLoop:
38 for i, test := range authTests {
39 name, resp, err := test.auth.Start(&ServerInfo{"testserver", true, nil})
40 if name != test.name {
41 t.Errorf("#%d got name %s, expected %s", i, name, test.name)
42 }
43 if !bytes.Equal(resp, []byte(test.responses[0])) {
44 t.Errorf("#%d got response %s, expected %s", i, resp, test.responses[0])
45 }
46 if err != nil {
47 t.Errorf("#%d error: %s", i, err)
48 }
49 for j := range test.challenges {
50 challenge := []byte(test.challenges[j])
51 expected := []byte(test.responses[j+1])
52 resp, err := test.auth.Next(challenge, true)
53 if err != nil {
54 t.Errorf("#%d error: %s", i, err)
55 continue testLoop
56 }
57 if !bytes.Equal(resp, expected) {
58 t.Errorf("#%d got %s, expected %s", i, resp, expected)
59 continue testLoop
60 }
61 }
62 }
63 }
64
65 func TestAuthPlain(t *testing.T) {
66
67 tests := []struct {
68 authName string
69 server *ServerInfo
70 err string
71 }{
72 {
73 authName: "servername",
74 server: &ServerInfo{Name: "servername", TLS: true},
75 },
76 {
77
78 authName: "localhost",
79 server: &ServerInfo{Name: "localhost", TLS: false},
80 },
81 {
82
83
84 authName: "servername",
85 server: &ServerInfo{Name: "servername", Auth: []string{"PLAIN"}},
86 err: "unencrypted connection",
87 },
88 {
89 authName: "servername",
90 server: &ServerInfo{Name: "servername", Auth: []string{"CRAM-MD5"}},
91 err: "unencrypted connection",
92 },
93 {
94 authName: "servername",
95 server: &ServerInfo{Name: "attacker", TLS: true},
96 err: "wrong host name",
97 },
98 }
99 for i, tt := range tests {
100 auth := PlainAuth("foo", "bar", "baz", tt.authName)
101 _, _, err := auth.Start(tt.server)
102 got := ""
103 if err != nil {
104 got = err.Error()
105 }
106 if got != tt.err {
107 t.Errorf("%d. got error = %q; want %q", i, got, tt.err)
108 }
109 }
110 }
111
112
113 func TestClientAuthTrimSpace(t *testing.T) {
114 server := "220 hello world\r\n" +
115 "200 some more"
116 var wrote strings.Builder
117 var fake faker
118 fake.ReadWriter = struct {
119 io.Reader
120 io.Writer
121 }{
122 strings.NewReader(server),
123 &wrote,
124 }
125 c, err := NewClient(fake, "fake.host")
126 if err != nil {
127 t.Fatalf("NewClient: %v", err)
128 }
129 c.tls = true
130 c.didHello = true
131 c.Auth(toServerEmptyAuth{})
132 c.Close()
133 if got, want := wrote.String(), "AUTH FOOAUTH\r\n*\r\nQUIT\r\n"; got != want {
134 t.Errorf("wrote %q; want %q", got, want)
135 }
136 }
137
138
139
140
141
142 type toServerEmptyAuth struct{}
143
144 func (toServerEmptyAuth) Start(server *ServerInfo) (proto string, toServer []byte, err error) {
145 return "FOOAUTH", nil, nil
146 }
147
148 func (toServerEmptyAuth) Next(fromServer []byte, more bool) (toServer []byte, err error) {
149 panic("unexpected call")
150 }
151
152 type faker struct {
153 io.ReadWriter
154 }
155
156 func (f faker) Close() error { return nil }
157 func (f faker) LocalAddr() net.Addr { return nil }
158 func (f faker) RemoteAddr() net.Addr { return nil }
159 func (f faker) SetDeadline(time.Time) error { return nil }
160 func (f faker) SetReadDeadline(time.Time) error { return nil }
161 func (f faker) SetWriteDeadline(time.Time) error { return nil }
162
163 func TestBasic(t *testing.T) {
164 server := strings.Join(strings.Split(basicServer, "\n"), "\r\n")
165 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
166
167 var cmdbuf strings.Builder
168 bcmdbuf := bufio.NewWriter(&cmdbuf)
169 var fake faker
170 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
171 c := &Client{Text: textproto.NewConn(fake), localName: "localhost"}
172
173 if err := c.helo(); err != nil {
174 t.Fatalf("HELO failed: %s", err)
175 }
176 if err := c.ehlo(); err == nil {
177 t.Fatalf("Expected first EHLO to fail")
178 }
179 if err := c.ehlo(); err != nil {
180 t.Fatalf("Second EHLO failed: %s", err)
181 }
182
183 c.didHello = true
184 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
185 t.Fatalf("Expected AUTH supported")
186 }
187 if ok, _ := c.Extension("DSN"); ok {
188 t.Fatalf("Shouldn't support DSN")
189 }
190
191 if err := c.Mail("user@gmail.com"); err == nil {
192 t.Fatalf("MAIL should require authentication")
193 }
194
195 if err := c.Verify("user1@gmail.com"); err == nil {
196 t.Fatalf("First VRFY: expected no verification")
197 }
198 if err := c.Verify("user2@gmail.com>\r\nDATA\r\nAnother injected message body\r\n.\r\nQUIT\r\n"); err == nil {
199 t.Fatalf("VRFY should have failed due to a message injection attempt")
200 }
201 if err := c.Verify("user2@gmail.com"); err != nil {
202 t.Fatalf("Second VRFY: expected verification, got %s", err)
203 }
204
205
206 c.tls = true
207 c.serverName = "smtp.google.com"
208 if err := c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")); err != nil {
209 t.Fatalf("AUTH failed: %s", err)
210 }
211
212 if err := c.Rcpt("golang-nuts@googlegroups.com>\r\nDATA\r\nInjected message body\r\n.\r\nQUIT\r\n"); err == nil {
213 t.Fatalf("RCPT should have failed due to a message injection attempt")
214 }
215 if err := c.Mail("user@gmail.com>\r\nDATA\r\nAnother injected message body\r\n.\r\nQUIT\r\n"); err == nil {
216 t.Fatalf("MAIL should have failed due to a message injection attempt")
217 }
218 if err := c.Mail("user@gmail.com"); err != nil {
219 t.Fatalf("MAIL failed: %s", err)
220 }
221 if err := c.Rcpt("golang-nuts@googlegroups.com"); err != nil {
222 t.Fatalf("RCPT failed: %s", err)
223 }
224 msg := `From: user@gmail.com
225 To: golang-nuts@googlegroups.com
226 Subject: Hooray for Go
227
228 Line 1
229 .Leading dot line .
230 Goodbye.`
231 w, err := c.Data()
232 if err != nil {
233 t.Fatalf("DATA failed: %s", err)
234 }
235 if _, err := w.Write([]byte(msg)); err != nil {
236 t.Fatalf("Data write failed: %s", err)
237 }
238 if err := w.Close(); err != nil {
239 t.Fatalf("Bad data response: %s", err)
240 }
241
242 if err := c.Quit(); err != nil {
243 t.Fatalf("QUIT failed: %s", err)
244 }
245
246 bcmdbuf.Flush()
247 actualcmds := cmdbuf.String()
248 if client != actualcmds {
249 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
250 }
251 }
252
253 var basicServer = `250 mx.google.com at your service
254 502 Unrecognized command.
255 250-mx.google.com at your service
256 250-SIZE 35651584
257 250-AUTH LOGIN PLAIN
258 250 8BITMIME
259 530 Authentication required
260 252 Send some mail, I'll try my best
261 250 User is valid
262 235 Accepted
263 250 Sender OK
264 250 Receiver OK
265 354 Go ahead
266 250 Data OK
267 221 OK
268 `
269
270 var basicClient = `HELO localhost
271 EHLO localhost
272 EHLO localhost
273 MAIL FROM:<user@gmail.com> BODY=8BITMIME
274 VRFY user1@gmail.com
275 VRFY user2@gmail.com
276 AUTH PLAIN AHVzZXIAcGFzcw==
277 MAIL FROM:<user@gmail.com> BODY=8BITMIME
278 RCPT TO:<golang-nuts@googlegroups.com>
279 DATA
280 From: user@gmail.com
281 To: golang-nuts@googlegroups.com
282 Subject: Hooray for Go
283
284 Line 1
285 ..Leading dot line .
286 Goodbye.
287 .
288 QUIT
289 `
290
291 func TestExtensions(t *testing.T) {
292 fake := func(server string) (c *Client, bcmdbuf *bufio.Writer, cmdbuf *strings.Builder) {
293 server = strings.Join(strings.Split(server, "\n"), "\r\n")
294
295 cmdbuf = &strings.Builder{}
296 bcmdbuf = bufio.NewWriter(cmdbuf)
297 var fake faker
298 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
299 c = &Client{Text: textproto.NewConn(fake), localName: "localhost"}
300
301 return c, bcmdbuf, cmdbuf
302 }
303
304 t.Run("helo", func(t *testing.T) {
305 const (
306 basicServer = `250 mx.google.com at your service
307 250 Sender OK
308 221 Goodbye
309 `
310
311 basicClient = `HELO localhost
312 MAIL FROM:<user@gmail.com>
313 QUIT
314 `
315 )
316
317 c, bcmdbuf, cmdbuf := fake(basicServer)
318
319 if err := c.helo(); err != nil {
320 t.Fatalf("HELO failed: %s", err)
321 }
322 c.didHello = true
323 if err := c.Mail("user@gmail.com"); err != nil {
324 t.Fatalf("MAIL FROM failed: %s", err)
325 }
326 if err := c.Quit(); err != nil {
327 t.Fatalf("QUIT failed: %s", err)
328 }
329
330 bcmdbuf.Flush()
331 actualcmds := cmdbuf.String()
332 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
333 if client != actualcmds {
334 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
335 }
336 })
337
338 t.Run("ehlo", func(t *testing.T) {
339 const (
340 basicServer = `250-mx.google.com at your service
341 250 SIZE 35651584
342 250 Sender OK
343 221 Goodbye
344 `
345
346 basicClient = `EHLO localhost
347 MAIL FROM:<user@gmail.com>
348 QUIT
349 `
350 )
351
352 c, bcmdbuf, cmdbuf := fake(basicServer)
353
354 if err := c.Hello("localhost"); err != nil {
355 t.Fatalf("EHLO failed: %s", err)
356 }
357 if ok, _ := c.Extension("8BITMIME"); ok {
358 t.Fatalf("Shouldn't support 8BITMIME")
359 }
360 if ok, _ := c.Extension("SMTPUTF8"); ok {
361 t.Fatalf("Shouldn't support SMTPUTF8")
362 }
363 if err := c.Mail("user@gmail.com"); err != nil {
364 t.Fatalf("MAIL FROM failed: %s", err)
365 }
366 if err := c.Quit(); err != nil {
367 t.Fatalf("QUIT failed: %s", err)
368 }
369
370 bcmdbuf.Flush()
371 actualcmds := cmdbuf.String()
372 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
373 if client != actualcmds {
374 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
375 }
376 })
377
378 t.Run("ehlo 8bitmime", func(t *testing.T) {
379 const (
380 basicServer = `250-mx.google.com at your service
381 250-SIZE 35651584
382 250 8BITMIME
383 250 Sender OK
384 221 Goodbye
385 `
386
387 basicClient = `EHLO localhost
388 MAIL FROM:<user@gmail.com> BODY=8BITMIME
389 QUIT
390 `
391 )
392
393 c, bcmdbuf, cmdbuf := fake(basicServer)
394
395 if err := c.Hello("localhost"); err != nil {
396 t.Fatalf("EHLO failed: %s", err)
397 }
398 if ok, _ := c.Extension("8BITMIME"); !ok {
399 t.Fatalf("Should support 8BITMIME")
400 }
401 if ok, _ := c.Extension("SMTPUTF8"); ok {
402 t.Fatalf("Shouldn't support SMTPUTF8")
403 }
404 if err := c.Mail("user@gmail.com"); err != nil {
405 t.Fatalf("MAIL FROM failed: %s", err)
406 }
407 if err := c.Quit(); err != nil {
408 t.Fatalf("QUIT failed: %s", err)
409 }
410
411 bcmdbuf.Flush()
412 actualcmds := cmdbuf.String()
413 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
414 if client != actualcmds {
415 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
416 }
417 })
418
419 t.Run("ehlo smtputf8", func(t *testing.T) {
420 const (
421 basicServer = `250-mx.google.com at your service
422 250-SIZE 35651584
423 250 SMTPUTF8
424 250 Sender OK
425 221 Goodbye
426 `
427
428 basicClient = `EHLO localhost
429 MAIL FROM:<user+📧@gmail.com> SMTPUTF8
430 QUIT
431 `
432 )
433
434 c, bcmdbuf, cmdbuf := fake(basicServer)
435
436 if err := c.Hello("localhost"); err != nil {
437 t.Fatalf("EHLO failed: %s", err)
438 }
439 if ok, _ := c.Extension("8BITMIME"); ok {
440 t.Fatalf("Shouldn't support 8BITMIME")
441 }
442 if ok, _ := c.Extension("SMTPUTF8"); !ok {
443 t.Fatalf("Should support SMTPUTF8")
444 }
445 if err := c.Mail("user+📧@gmail.com"); err != nil {
446 t.Fatalf("MAIL FROM failed: %s", err)
447 }
448 if err := c.Quit(); err != nil {
449 t.Fatalf("QUIT failed: %s", err)
450 }
451
452 bcmdbuf.Flush()
453 actualcmds := cmdbuf.String()
454 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
455 if client != actualcmds {
456 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
457 }
458 })
459
460 t.Run("ehlo 8bitmime smtputf8", func(t *testing.T) {
461 const (
462 basicServer = `250-mx.google.com at your service
463 250-SIZE 35651584
464 250-8BITMIME
465 250 SMTPUTF8
466 250 Sender OK
467 221 Goodbye
468 `
469
470 basicClient = `EHLO localhost
471 MAIL FROM:<user+📧@gmail.com> BODY=8BITMIME SMTPUTF8
472 QUIT
473 `
474 )
475
476 c, bcmdbuf, cmdbuf := fake(basicServer)
477
478 if err := c.Hello("localhost"); err != nil {
479 t.Fatalf("EHLO failed: %s", err)
480 }
481 c.didHello = true
482 if ok, _ := c.Extension("8BITMIME"); !ok {
483 t.Fatalf("Should support 8BITMIME")
484 }
485 if ok, _ := c.Extension("SMTPUTF8"); !ok {
486 t.Fatalf("Should support SMTPUTF8")
487 }
488 if err := c.Mail("user+📧@gmail.com"); err != nil {
489 t.Fatalf("MAIL FROM failed: %s", err)
490 }
491 if err := c.Quit(); err != nil {
492 t.Fatalf("QUIT failed: %s", err)
493 }
494
495 bcmdbuf.Flush()
496 actualcmds := cmdbuf.String()
497 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
498 if client != actualcmds {
499 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
500 }
501 })
502 }
503
504 func TestNewClient(t *testing.T) {
505 server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n")
506 client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n")
507
508 var cmdbuf strings.Builder
509 bcmdbuf := bufio.NewWriter(&cmdbuf)
510 out := func() string {
511 bcmdbuf.Flush()
512 return cmdbuf.String()
513 }
514 var fake faker
515 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
516 c, err := NewClient(fake, "fake.host")
517 if err != nil {
518 t.Fatalf("NewClient: %v\n(after %v)", err, out())
519 }
520 defer c.Close()
521 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
522 t.Fatalf("Expected AUTH supported")
523 }
524 if ok, _ := c.Extension("DSN"); ok {
525 t.Fatalf("Shouldn't support DSN")
526 }
527 if err := c.Quit(); err != nil {
528 t.Fatalf("QUIT failed: %s", err)
529 }
530
531 actualcmds := out()
532 if client != actualcmds {
533 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
534 }
535 }
536
537 var newClientServer = `220 hello world
538 250-mx.google.com at your service
539 250-SIZE 35651584
540 250-AUTH LOGIN PLAIN
541 250 8BITMIME
542 221 OK
543 `
544
545 var newClientClient = `EHLO localhost
546 QUIT
547 `
548
549 func TestNewClient2(t *testing.T) {
550 server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n")
551 client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n")
552
553 var cmdbuf strings.Builder
554 bcmdbuf := bufio.NewWriter(&cmdbuf)
555 var fake faker
556 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
557 c, err := NewClient(fake, "fake.host")
558 if err != nil {
559 t.Fatalf("NewClient: %v", err)
560 }
561 defer c.Close()
562 if ok, _ := c.Extension("DSN"); ok {
563 t.Fatalf("Shouldn't support DSN")
564 }
565 if err := c.Quit(); err != nil {
566 t.Fatalf("QUIT failed: %s", err)
567 }
568
569 bcmdbuf.Flush()
570 actualcmds := cmdbuf.String()
571 if client != actualcmds {
572 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
573 }
574 }
575
576 var newClient2Server = `220 hello world
577 502 EH?
578 250-mx.google.com at your service
579 250-SIZE 35651584
580 250-AUTH LOGIN PLAIN
581 250 8BITMIME
582 221 OK
583 `
584
585 var newClient2Client = `EHLO localhost
586 HELO localhost
587 QUIT
588 `
589
590 func TestNewClientWithTLS(t *testing.T) {
591 cert, err := tls.X509KeyPair(localhostCert, localhostKey)
592 if err != nil {
593 t.Fatalf("loadcert: %v", err)
594 }
595
596 config := tls.Config{Certificates: []tls.Certificate{cert}}
597
598 ln, err := tls.Listen("tcp", "127.0.0.1:0", &config)
599 if err != nil {
600 ln, err = tls.Listen("tcp", "[::1]:0", &config)
601 if err != nil {
602 t.Fatalf("server: listen: %v", err)
603 }
604 }
605
606 go func() {
607 conn, err := ln.Accept()
608 if err != nil {
609 t.Errorf("server: accept: %v", err)
610 return
611 }
612 defer conn.Close()
613
614 _, err = conn.Write([]byte("220 SIGNS\r\n"))
615 if err != nil {
616 t.Errorf("server: write: %v", err)
617 return
618 }
619 }()
620
621 config.InsecureSkipVerify = true
622 conn, err := tls.Dial("tcp", ln.Addr().String(), &config)
623 if err != nil {
624 t.Fatalf("client: dial: %v", err)
625 }
626 defer conn.Close()
627
628 client, err := NewClient(conn, ln.Addr().String())
629 if err != nil {
630 t.Fatalf("smtp: newclient: %v", err)
631 }
632 if !client.tls {
633 t.Errorf("client.tls Got: %t Expected: %t", client.tls, true)
634 }
635 }
636
637 func TestHello(t *testing.T) {
638
639 if len(helloServer) != len(helloClient) {
640 t.Fatalf("Hello server and client size mismatch")
641 }
642
643 for i := 0; i < len(helloServer); i++ {
644 server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n")
645 client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n")
646 var cmdbuf strings.Builder
647 bcmdbuf := bufio.NewWriter(&cmdbuf)
648 var fake faker
649 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
650 c, err := NewClient(fake, "fake.host")
651 if err != nil {
652 t.Fatalf("NewClient: %v", err)
653 }
654 defer c.Close()
655 c.localName = "customhost"
656 err = nil
657
658 switch i {
659 case 0:
660 err = c.Hello("hostinjection>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n")
661 if err == nil {
662 t.Errorf("Expected Hello to be rejected due to a message injection attempt")
663 }
664 err = c.Hello("customhost")
665 case 1:
666 err = c.StartTLS(nil)
667 if err.Error() == "502 Not implemented" {
668 err = nil
669 }
670 case 2:
671 err = c.Verify("test@example.com")
672 case 3:
673 c.tls = true
674 c.serverName = "smtp.google.com"
675 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
676 case 4:
677 err = c.Mail("test@example.com")
678 case 5:
679 ok, _ := c.Extension("feature")
680 if ok {
681 t.Errorf("Expected FEATURE not to be supported")
682 }
683 case 6:
684 err = c.Reset()
685 case 7:
686 err = c.Quit()
687 case 8:
688 err = c.Verify("test@example.com")
689 if err != nil {
690 err = c.Hello("customhost")
691 if err != nil {
692 t.Errorf("Want error, got none")
693 }
694 }
695 case 9:
696 err = c.Noop()
697 default:
698 t.Fatalf("Unhandled command")
699 }
700
701 if err != nil {
702 t.Errorf("Command %d failed: %v", i, err)
703 }
704
705 bcmdbuf.Flush()
706 actualcmds := cmdbuf.String()
707 if client != actualcmds {
708 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
709 }
710 }
711 }
712
713 var baseHelloServer = `220 hello world
714 502 EH?
715 250-mx.google.com at your service
716 250 FEATURE
717 `
718
719 var helloServer = []string{
720 "",
721 "502 Not implemented\n",
722 "250 User is valid\n",
723 "235 Accepted\n",
724 "250 Sender ok\n",
725 "",
726 "250 Reset ok\n",
727 "221 Goodbye\n",
728 "250 Sender ok\n",
729 "250 ok\n",
730 }
731
732 var baseHelloClient = `EHLO customhost
733 HELO customhost
734 `
735
736 var helloClient = []string{
737 "",
738 "STARTTLS\n",
739 "VRFY test@example.com\n",
740 "AUTH PLAIN AHVzZXIAcGFzcw==\n",
741 "MAIL FROM:<test@example.com>\n",
742 "",
743 "RSET\n",
744 "QUIT\n",
745 "VRFY test@example.com\n",
746 "NOOP\n",
747 }
748
749 func TestSendMail(t *testing.T) {
750 server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n")
751 client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n")
752 var cmdbuf strings.Builder
753 bcmdbuf := bufio.NewWriter(&cmdbuf)
754 l, err := net.Listen("tcp", "127.0.0.1:0")
755 if err != nil {
756 t.Fatalf("Unable to create listener: %v", err)
757 }
758 defer l.Close()
759
760
761 var done = make(chan struct{})
762 go func(data []string) {
763
764 defer close(done)
765
766 conn, err := l.Accept()
767 if err != nil {
768 t.Errorf("Accept error: %v", err)
769 return
770 }
771 defer conn.Close()
772
773 tc := textproto.NewConn(conn)
774 for i := 0; i < len(data) && data[i] != ""; i++ {
775 tc.PrintfLine("%s", data[i])
776 for len(data[i]) >= 4 && data[i][3] == '-' {
777 i++
778 tc.PrintfLine("%s", data[i])
779 }
780 if data[i] == "221 Goodbye" {
781 return
782 }
783 read := false
784 for !read || data[i] == "354 Go ahead" {
785 msg, err := tc.ReadLine()
786 bcmdbuf.Write([]byte(msg + "\r\n"))
787 read = true
788 if err != nil {
789 t.Errorf("Read error: %v", err)
790 return
791 }
792 if data[i] == "354 Go ahead" && msg == "." {
793 break
794 }
795 }
796 }
797 }(strings.Split(server, "\r\n"))
798
799 err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n"}, []byte(strings.Replace(`From: test@example.com
800 To: other@example.com
801 Subject: SendMail test
802
803 SendMail is working for me.
804 `, "\n", "\r\n", -1)))
805 if err == nil {
806 t.Errorf("Expected SendMail to be rejected due to a message injection attempt")
807 }
808
809 err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com
810 To: other@example.com
811 Subject: SendMail test
812
813 SendMail is working for me.
814 `, "\n", "\r\n", -1)))
815
816 if err != nil {
817 t.Errorf("%v", err)
818 }
819
820 <-done
821 bcmdbuf.Flush()
822 actualcmds := cmdbuf.String()
823 if client != actualcmds {
824 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
825 }
826 }
827
828 var sendMailServer = `220 hello world
829 502 EH?
830 250 mx.google.com at your service
831 250 Sender ok
832 250 Receiver ok
833 354 Go ahead
834 250 Data ok
835 221 Goodbye
836 `
837
838 var sendMailClient = `EHLO localhost
839 HELO localhost
840 MAIL FROM:<test@example.com>
841 RCPT TO:<other@example.com>
842 DATA
843 From: test@example.com
844 To: other@example.com
845 Subject: SendMail test
846
847 SendMail is working for me.
848 .
849 QUIT
850 `
851
852 func TestSendMailWithAuth(t *testing.T) {
853 l, err := net.Listen("tcp", "127.0.0.1:0")
854 if err != nil {
855 t.Fatalf("Unable to create listener: %v", err)
856 }
857 defer l.Close()
858
859 errCh := make(chan error)
860 go func() {
861 defer close(errCh)
862 conn, err := l.Accept()
863 if err != nil {
864 errCh <- fmt.Errorf("Accept: %v", err)
865 return
866 }
867 defer conn.Close()
868
869 tc := textproto.NewConn(conn)
870 tc.PrintfLine("220 hello world")
871 msg, err := tc.ReadLine()
872 if err != nil {
873 errCh <- fmt.Errorf("ReadLine error: %v", err)
874 return
875 }
876 const wantMsg = "EHLO localhost"
877 if msg != wantMsg {
878 errCh <- fmt.Errorf("unexpected response %q; want %q", msg, wantMsg)
879 return
880 }
881 err = tc.PrintfLine("250 mx.google.com at your service")
882 if err != nil {
883 errCh <- fmt.Errorf("PrintfLine: %v", err)
884 return
885 }
886 }()
887
888 err = SendMail(l.Addr().String(), PlainAuth("", "user", "pass", "smtp.google.com"), "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com
889 To: other@example.com
890 Subject: SendMail test
891
892 SendMail is working for me.
893 `, "\n", "\r\n", -1)))
894 if err == nil {
895 t.Error("SendMail: Server doesn't support AUTH, expected to get an error, but got none ")
896 }
897 if err.Error() != "smtp: server doesn't support AUTH" {
898 t.Errorf("Expected: smtp: server doesn't support AUTH, got: %s", err)
899 }
900 err = <-errCh
901 if err != nil {
902 t.Fatalf("server error: %v", err)
903 }
904 }
905
906 func TestAuthFailed(t *testing.T) {
907 server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n")
908 client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n")
909 var cmdbuf strings.Builder
910 bcmdbuf := bufio.NewWriter(&cmdbuf)
911 var fake faker
912 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
913 c, err := NewClient(fake, "fake.host")
914 if err != nil {
915 t.Fatalf("NewClient: %v", err)
916 }
917 defer c.Close()
918
919 c.tls = true
920 c.serverName = "smtp.google.com"
921 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
922
923 if err == nil {
924 t.Error("Auth: expected error; got none")
925 } else if err.Error() != "535 Invalid credentials\nplease see www.example.com" {
926 t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com")
927 }
928
929 bcmdbuf.Flush()
930 actualcmds := cmdbuf.String()
931 if client != actualcmds {
932 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
933 }
934 }
935
936 var authFailedServer = `220 hello world
937 250-mx.google.com at your service
938 250 AUTH LOGIN PLAIN
939 535-Invalid credentials
940 535 please see www.example.com
941 221 Goodbye
942 `
943
944 var authFailedClient = `EHLO localhost
945 AUTH PLAIN AHVzZXIAcGFzcw==
946 *
947 QUIT
948 `
949
950 func TestTLSClient(t *testing.T) {
951 if runtime.GOOS == "freebsd" || runtime.GOOS == "js" || runtime.GOOS == "wasip1" {
952 testenv.SkipFlaky(t, 19229)
953 }
954 ln := newLocalListener(t)
955 defer ln.Close()
956 errc := make(chan error)
957 go func() {
958 errc <- sendMail(ln.Addr().String())
959 }()
960 conn, err := ln.Accept()
961 if err != nil {
962 t.Fatalf("failed to accept connection: %v", err)
963 }
964 defer conn.Close()
965 if err := serverHandle(conn, t); err != nil {
966 t.Fatalf("failed to handle connection: %v", err)
967 }
968 if err := <-errc; err != nil {
969 t.Fatalf("client error: %v", err)
970 }
971 }
972
973 func TestTLSConnState(t *testing.T) {
974 ln := newLocalListener(t)
975 defer ln.Close()
976 clientDone := make(chan bool)
977 serverDone := make(chan bool)
978 go func() {
979 defer close(serverDone)
980 c, err := ln.Accept()
981 if err != nil {
982 t.Errorf("Server accept: %v", err)
983 return
984 }
985 defer c.Close()
986 if err := serverHandle(c, t); err != nil {
987 t.Errorf("server error: %v", err)
988 }
989 }()
990 go func() {
991 defer close(clientDone)
992 c, err := Dial(ln.Addr().String())
993 if err != nil {
994 t.Errorf("Client dial: %v", err)
995 return
996 }
997 defer c.Quit()
998 cfg := &tls.Config{ServerName: "example.com"}
999 testHookStartTLS(cfg)
1000 if err := c.StartTLS(cfg); err != nil {
1001 t.Errorf("StartTLS: %v", err)
1002 return
1003 }
1004 cs, ok := c.TLSConnectionState()
1005 if !ok {
1006 t.Errorf("TLSConnectionState returned ok == false; want true")
1007 return
1008 }
1009 if cs.Version == 0 || !cs.HandshakeComplete {
1010 t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs)
1011 }
1012 }()
1013 <-clientDone
1014 <-serverDone
1015 }
1016
1017 func newLocalListener(t *testing.T) net.Listener {
1018 ln, err := net.Listen("tcp", "127.0.0.1:0")
1019 if err != nil {
1020 ln, err = net.Listen("tcp6", "[::1]:0")
1021 }
1022 if err != nil {
1023 t.Fatal(err)
1024 }
1025 return ln
1026 }
1027
1028 type smtpSender struct {
1029 w io.Writer
1030 }
1031
1032 func (s smtpSender) send(f string) {
1033 s.w.Write([]byte(f + "\r\n"))
1034 }
1035
1036
1037 func serverHandle(c net.Conn, t *testing.T) error {
1038 send := smtpSender{c}.send
1039 send("220 127.0.0.1 ESMTP service ready")
1040 s := bufio.NewScanner(c)
1041 for s.Scan() {
1042 switch s.Text() {
1043 case "EHLO localhost":
1044 send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
1045 send("250-STARTTLS")
1046 send("250 Ok")
1047 case "STARTTLS":
1048 send("220 Go ahead")
1049 keypair, err := tls.X509KeyPair(localhostCert, localhostKey)
1050 if err != nil {
1051 return err
1052 }
1053 config := &tls.Config{Certificates: []tls.Certificate{keypair}}
1054 c = tls.Server(c, config)
1055 defer c.Close()
1056 return serverHandleTLS(c, t)
1057 default:
1058 t.Fatalf("unrecognized command: %q", s.Text())
1059 }
1060 }
1061 return s.Err()
1062 }
1063
1064 func serverHandleTLS(c net.Conn, t *testing.T) error {
1065 send := smtpSender{c}.send
1066 s := bufio.NewScanner(c)
1067 for s.Scan() {
1068 switch s.Text() {
1069 case "EHLO localhost":
1070 send("250 Ok")
1071 case "MAIL FROM:<joe1@example.com>":
1072 send("250 Ok")
1073 case "RCPT TO:<joe2@example.com>":
1074 send("250 Ok")
1075 case "DATA":
1076 send("354 send the mail data, end with .")
1077 send("250 Ok")
1078 case "Subject: test":
1079 case "":
1080 case "howdy!":
1081 case ".":
1082 case "QUIT":
1083 send("221 127.0.0.1 Service closing transmission channel")
1084 return nil
1085 default:
1086 t.Fatalf("unrecognized command during TLS: %q", s.Text())
1087 }
1088 }
1089 return s.Err()
1090 }
1091
1092 func init() {
1093 testRootCAs := x509.NewCertPool()
1094 testRootCAs.AppendCertsFromPEM(localhostCert)
1095 testHookStartTLS = func(config *tls.Config) {
1096 config.RootCAs = testRootCAs
1097 }
1098 }
1099
1100 func sendMail(hostPort string) error {
1101 from := "joe1@example.com"
1102 to := []string{"joe2@example.com"}
1103 return SendMail(hostPort, nil, from, to, []byte("Subject: test\n\nhowdy!"))
1104 }
1105
1106
1107
1108
1109
1110 var localhostCert = []byte(`
1111 -----BEGIN CERTIFICATE-----
1112 MIICFDCCAX2gAwIBAgIRAK0xjnaPuNDSreeXb+z+0u4wDQYJKoZIhvcNAQELBQAw
1113 EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
1114 MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
1115 gYkCgYEA0nFbQQuOWsjbGtejcpWz153OlziZM4bVjJ9jYruNw5n2Ry6uYQAffhqa
1116 JOInCmmcVe2siJglsyH9aRh6vKiobBbIUXXUU1ABd56ebAzlt0LobLlx7pZEMy30
1117 LqIi9E6zmL3YvdGzpYlkFRnRrqwEtWYbGBf3znO250S56CCWH2UCAwEAAaNoMGYw
1118 DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF
1119 MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA
1120 AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAbZtDS2dVuBYvb+MnolWnCNqvw1w5Gtgi
1121 NmvQQPOMgM3m+oQSCPRTNGSg25e1Qbo7bgQDv8ZTnq8FgOJ/rbkyERw2JckkHpD4
1122 n4qcK27WkEDBtQFlPihIM8hLIuzWoi/9wygiElTy/tVL3y7fGCvY2/k1KBthtZGF
1123 tN8URjVmyEo=
1124 -----END CERTIFICATE-----`)
1125
1126
1127 var localhostKey = []byte(testingKey(`
1128 -----BEGIN RSA TESTING KEY-----
1129 MIICXgIBAAKBgQDScVtBC45ayNsa16NylbPXnc6XOJkzhtWMn2Niu43DmfZHLq5h
1130 AB9+Gpok4icKaZxV7ayImCWzIf1pGHq8qKhsFshRddRTUAF3np5sDOW3QuhsuXHu
1131 lkQzLfQuoiL0TrOYvdi90bOliWQVGdGurAS1ZhsYF/fOc7bnRLnoIJYfZQIDAQAB
1132 AoGBAMst7OgpKyFV6c3JwyI/jWqxDySL3caU+RuTTBaodKAUx2ZEmNJIlx9eudLA
1133 kucHvoxsM/eRxlxkhdFxdBcwU6J+zqooTnhu/FE3jhrT1lPrbhfGhyKnUrB0KKMM
1134 VY3IQZyiehpxaeXAwoAou6TbWoTpl9t8ImAqAMY8hlULCUqlAkEA+9+Ry5FSYK/m
1135 542LujIcCaIGoG1/Te6Sxr3hsPagKC2rH20rDLqXwEedSFOpSS0vpzlPAzy/6Rbb
1136 PHTJUhNdwwJBANXkA+TkMdbJI5do9/mn//U0LfrCR9NkcoYohxfKz8JuhgRQxzF2
1137 6jpo3q7CdTuuRixLWVfeJzcrAyNrVcBq87cCQFkTCtOMNC7fZnCTPUv+9q1tcJyB
1138 vNjJu3yvoEZeIeuzouX9TJE21/33FaeDdsXbRhQEj23cqR38qFHsF1qAYNMCQQDP
1139 QXLEiJoClkR2orAmqjPLVhR3t2oB3INcnEjLNSq8LHyQEfXyaFfu4U9l5+fRPL2i
1140 jiC0k/9L5dHUsF0XZothAkEA23ddgRs+Id/HxtojqqUT27B8MT/IGNrYsp4DvS/c
1141 qgkeluku4GjxRlDMBuXk94xOBEinUs+p/hwP1Alll80Tpg==
1142 -----END RSA TESTING KEY-----`))
1143
1144 func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
1145
View as plain text