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 TestHELOFailed(t *testing.T) {
292 serverLines := `502 EH?
293 502 EH?
294 221 OK
295 `
296 clientLines := `EHLO localhost
297 HELO localhost
298 QUIT
299 `
300
301 server := strings.Join(strings.Split(serverLines, "\n"), "\r\n")
302 client := strings.Join(strings.Split(clientLines, "\n"), "\r\n")
303 var cmdbuf strings.Builder
304 bcmdbuf := bufio.NewWriter(&cmdbuf)
305 var fake faker
306 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
307 c := &Client{Text: textproto.NewConn(fake), localName: "localhost"}
308
309 if err := c.Hello("localhost"); err == nil {
310 t.Fatal("expected EHLO to fail")
311 }
312 if err := c.Quit(); err != nil {
313 t.Errorf("QUIT failed: %s", err)
314 }
315 bcmdbuf.Flush()
316 actual := cmdbuf.String()
317 if client != actual {
318 t.Errorf("Got:\n%s\nWant:\n%s", actual, client)
319 }
320 }
321
322 func TestExtensions(t *testing.T) {
323 fake := func(server string) (c *Client, bcmdbuf *bufio.Writer, cmdbuf *strings.Builder) {
324 server = strings.Join(strings.Split(server, "\n"), "\r\n")
325
326 cmdbuf = &strings.Builder{}
327 bcmdbuf = bufio.NewWriter(cmdbuf)
328 var fake faker
329 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
330 c = &Client{Text: textproto.NewConn(fake), localName: "localhost"}
331
332 return c, bcmdbuf, cmdbuf
333 }
334
335 t.Run("helo", func(t *testing.T) {
336 const (
337 basicServer = `250 mx.google.com at your service
338 250 Sender OK
339 221 Goodbye
340 `
341
342 basicClient = `HELO localhost
343 MAIL FROM:<user@gmail.com>
344 QUIT
345 `
346 )
347
348 c, bcmdbuf, cmdbuf := fake(basicServer)
349
350 if err := c.helo(); err != nil {
351 t.Fatalf("HELO failed: %s", err)
352 }
353 c.didHello = true
354 if err := c.Mail("user@gmail.com"); err != nil {
355 t.Fatalf("MAIL FROM failed: %s", err)
356 }
357 if err := c.Quit(); err != nil {
358 t.Fatalf("QUIT failed: %s", err)
359 }
360
361 bcmdbuf.Flush()
362 actualcmds := cmdbuf.String()
363 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
364 if client != actualcmds {
365 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
366 }
367 })
368
369 t.Run("ehlo", func(t *testing.T) {
370 const (
371 basicServer = `250-mx.google.com at your service
372 250 SIZE 35651584
373 250 Sender OK
374 221 Goodbye
375 `
376
377 basicClient = `EHLO localhost
378 MAIL FROM:<user@gmail.com>
379 QUIT
380 `
381 )
382
383 c, bcmdbuf, cmdbuf := fake(basicServer)
384
385 if err := c.Hello("localhost"); err != nil {
386 t.Fatalf("EHLO failed: %s", err)
387 }
388 if ok, _ := c.Extension("8BITMIME"); ok {
389 t.Fatalf("Shouldn't support 8BITMIME")
390 }
391 if ok, _ := c.Extension("SMTPUTF8"); ok {
392 t.Fatalf("Shouldn't support SMTPUTF8")
393 }
394 if err := c.Mail("user@gmail.com"); err != nil {
395 t.Fatalf("MAIL FROM failed: %s", err)
396 }
397 if err := c.Quit(); err != nil {
398 t.Fatalf("QUIT failed: %s", err)
399 }
400
401 bcmdbuf.Flush()
402 actualcmds := cmdbuf.String()
403 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
404 if client != actualcmds {
405 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
406 }
407 })
408
409 t.Run("ehlo 8bitmime", func(t *testing.T) {
410 const (
411 basicServer = `250-mx.google.com at your service
412 250-SIZE 35651584
413 250 8BITMIME
414 250 Sender OK
415 221 Goodbye
416 `
417
418 basicClient = `EHLO localhost
419 MAIL FROM:<user@gmail.com> BODY=8BITMIME
420 QUIT
421 `
422 )
423
424 c, bcmdbuf, cmdbuf := fake(basicServer)
425
426 if err := c.Hello("localhost"); err != nil {
427 t.Fatalf("EHLO failed: %s", err)
428 }
429 if ok, _ := c.Extension("8BITMIME"); !ok {
430 t.Fatalf("Should support 8BITMIME")
431 }
432 if ok, _ := c.Extension("SMTPUTF8"); ok {
433 t.Fatalf("Shouldn't support SMTPUTF8")
434 }
435 if err := c.Mail("user@gmail.com"); err != nil {
436 t.Fatalf("MAIL FROM failed: %s", err)
437 }
438 if err := c.Quit(); err != nil {
439 t.Fatalf("QUIT failed: %s", err)
440 }
441
442 bcmdbuf.Flush()
443 actualcmds := cmdbuf.String()
444 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
445 if client != actualcmds {
446 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
447 }
448 })
449
450 t.Run("ehlo smtputf8", func(t *testing.T) {
451 const (
452 basicServer = `250-mx.google.com at your service
453 250-SIZE 35651584
454 250 SMTPUTF8
455 250 Sender OK
456 221 Goodbye
457 `
458
459 basicClient = `EHLO localhost
460 MAIL FROM:<user+📧@gmail.com> SMTPUTF8
461 QUIT
462 `
463 )
464
465 c, bcmdbuf, cmdbuf := fake(basicServer)
466
467 if err := c.Hello("localhost"); err != nil {
468 t.Fatalf("EHLO failed: %s", err)
469 }
470 if ok, _ := c.Extension("8BITMIME"); ok {
471 t.Fatalf("Shouldn't support 8BITMIME")
472 }
473 if ok, _ := c.Extension("SMTPUTF8"); !ok {
474 t.Fatalf("Should support SMTPUTF8")
475 }
476 if err := c.Mail("user+📧@gmail.com"); err != nil {
477 t.Fatalf("MAIL FROM failed: %s", err)
478 }
479 if err := c.Quit(); err != nil {
480 t.Fatalf("QUIT failed: %s", err)
481 }
482
483 bcmdbuf.Flush()
484 actualcmds := cmdbuf.String()
485 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
486 if client != actualcmds {
487 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
488 }
489 })
490
491 t.Run("ehlo 8bitmime smtputf8", func(t *testing.T) {
492 const (
493 basicServer = `250-mx.google.com at your service
494 250-SIZE 35651584
495 250-8BITMIME
496 250 SMTPUTF8
497 250 Sender OK
498 221 Goodbye
499 `
500
501 basicClient = `EHLO localhost
502 MAIL FROM:<user+📧@gmail.com> BODY=8BITMIME SMTPUTF8
503 QUIT
504 `
505 )
506
507 c, bcmdbuf, cmdbuf := fake(basicServer)
508
509 if err := c.Hello("localhost"); err != nil {
510 t.Fatalf("EHLO failed: %s", err)
511 }
512 c.didHello = true
513 if ok, _ := c.Extension("8BITMIME"); !ok {
514 t.Fatalf("Should support 8BITMIME")
515 }
516 if ok, _ := c.Extension("SMTPUTF8"); !ok {
517 t.Fatalf("Should support SMTPUTF8")
518 }
519 if err := c.Mail("user+📧@gmail.com"); err != nil {
520 t.Fatalf("MAIL FROM failed: %s", err)
521 }
522 if err := c.Quit(); err != nil {
523 t.Fatalf("QUIT failed: %s", err)
524 }
525
526 bcmdbuf.Flush()
527 actualcmds := cmdbuf.String()
528 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
529 if client != actualcmds {
530 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
531 }
532 })
533 }
534
535 func TestNewClient(t *testing.T) {
536 server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n")
537 client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n")
538
539 var cmdbuf strings.Builder
540 bcmdbuf := bufio.NewWriter(&cmdbuf)
541 out := func() string {
542 bcmdbuf.Flush()
543 return cmdbuf.String()
544 }
545 var fake faker
546 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
547 c, err := NewClient(fake, "fake.host")
548 if err != nil {
549 t.Fatalf("NewClient: %v\n(after %v)", err, out())
550 }
551 defer c.Close()
552 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
553 t.Fatalf("Expected AUTH supported")
554 }
555 if ok, _ := c.Extension("DSN"); ok {
556 t.Fatalf("Shouldn't support DSN")
557 }
558 if err := c.Quit(); err != nil {
559 t.Fatalf("QUIT failed: %s", err)
560 }
561
562 actualcmds := out()
563 if client != actualcmds {
564 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
565 }
566 }
567
568 var newClientServer = `220 hello world
569 250-mx.google.com at your service
570 250-SIZE 35651584
571 250-AUTH LOGIN PLAIN
572 250 8BITMIME
573 221 OK
574 `
575
576 var newClientClient = `EHLO localhost
577 QUIT
578 `
579
580 func TestNewClient2(t *testing.T) {
581 server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n")
582 client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n")
583
584 var cmdbuf strings.Builder
585 bcmdbuf := bufio.NewWriter(&cmdbuf)
586 var fake faker
587 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
588 c, err := NewClient(fake, "fake.host")
589 if err != nil {
590 t.Fatalf("NewClient: %v", err)
591 }
592 defer c.Close()
593 if ok, _ := c.Extension("DSN"); ok {
594 t.Fatalf("Shouldn't support DSN")
595 }
596 if err := c.Quit(); err != nil {
597 t.Fatalf("QUIT failed: %s", err)
598 }
599
600 bcmdbuf.Flush()
601 actualcmds := cmdbuf.String()
602 if client != actualcmds {
603 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
604 }
605 }
606
607 var newClient2Server = `220 hello world
608 502 EH?
609 250-mx.google.com at your service
610 250-SIZE 35651584
611 250-AUTH LOGIN PLAIN
612 250 8BITMIME
613 221 OK
614 `
615
616 var newClient2Client = `EHLO localhost
617 HELO localhost
618 QUIT
619 `
620
621 func TestNewClientWithTLS(t *testing.T) {
622 cert, err := tls.X509KeyPair(localhostCert, localhostKey)
623 if err != nil {
624 t.Fatalf("loadcert: %v", err)
625 }
626
627 config := tls.Config{Certificates: []tls.Certificate{cert}}
628
629 ln, err := tls.Listen("tcp", "127.0.0.1:0", &config)
630 if err != nil {
631 ln, err = tls.Listen("tcp", "[::1]:0", &config)
632 if err != nil {
633 t.Fatalf("server: listen: %v", err)
634 }
635 }
636
637 go func() {
638 conn, err := ln.Accept()
639 if err != nil {
640 t.Errorf("server: accept: %v", err)
641 return
642 }
643 defer conn.Close()
644
645 _, err = conn.Write([]byte("220 SIGNS\r\n"))
646 if err != nil {
647 t.Errorf("server: write: %v", err)
648 return
649 }
650 }()
651
652 config.InsecureSkipVerify = true
653 conn, err := tls.Dial("tcp", ln.Addr().String(), &config)
654 if err != nil {
655 t.Fatalf("client: dial: %v", err)
656 }
657 defer conn.Close()
658
659 client, err := NewClient(conn, ln.Addr().String())
660 if err != nil {
661 t.Fatalf("smtp: newclient: %v", err)
662 }
663 if !client.tls {
664 t.Errorf("client.tls Got: %t Expected: %t", client.tls, true)
665 }
666 }
667
668 func TestHello(t *testing.T) {
669
670 if len(helloServer) != len(helloClient) {
671 t.Fatalf("Hello server and client size mismatch")
672 }
673
674 for i := 0; i < len(helloServer); i++ {
675 server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n")
676 client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n")
677 var cmdbuf strings.Builder
678 bcmdbuf := bufio.NewWriter(&cmdbuf)
679 var fake faker
680 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
681 c, err := NewClient(fake, "fake.host")
682 if err != nil {
683 t.Fatalf("NewClient: %v", err)
684 }
685 defer c.Close()
686 c.localName = "customhost"
687 err = nil
688
689 switch i {
690 case 0:
691 err = c.Hello("hostinjection>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n")
692 if err == nil {
693 t.Errorf("Expected Hello to be rejected due to a message injection attempt")
694 }
695 err = c.Hello("customhost")
696 case 1:
697 err = c.StartTLS(nil)
698 if err.Error() == "502 Not implemented" {
699 err = nil
700 }
701 case 2:
702 err = c.Verify("test@example.com")
703 case 3:
704 c.tls = true
705 c.serverName = "smtp.google.com"
706 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
707 case 4:
708 err = c.Mail("test@example.com")
709 case 5:
710 ok, _ := c.Extension("feature")
711 if ok {
712 t.Errorf("Expected FEATURE not to be supported")
713 }
714 case 6:
715 err = c.Reset()
716 case 7:
717 err = c.Quit()
718 case 8:
719 err = c.Verify("test@example.com")
720 if err != nil {
721 err = c.Hello("customhost")
722 if err != nil {
723 t.Errorf("Want error, got none")
724 }
725 }
726 case 9:
727 err = c.Noop()
728 default:
729 t.Fatalf("Unhandled command")
730 }
731
732 if err != nil {
733 t.Errorf("Command %d failed: %v", i, err)
734 }
735
736 bcmdbuf.Flush()
737 actualcmds := cmdbuf.String()
738 if client != actualcmds {
739 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
740 }
741 }
742 }
743
744 var baseHelloServer = `220 hello world
745 502 EH?
746 250-mx.google.com at your service
747 250 FEATURE
748 `
749
750 var helloServer = []string{
751 "",
752 "502 Not implemented\n",
753 "250 User is valid\n",
754 "235 Accepted\n",
755 "250 Sender ok\n",
756 "",
757 "250 Reset ok\n",
758 "221 Goodbye\n",
759 "250 Sender ok\n",
760 "250 ok\n",
761 }
762
763 var baseHelloClient = `EHLO customhost
764 HELO customhost
765 `
766
767 var helloClient = []string{
768 "",
769 "STARTTLS\n",
770 "VRFY test@example.com\n",
771 "AUTH PLAIN AHVzZXIAcGFzcw==\n",
772 "MAIL FROM:<test@example.com>\n",
773 "",
774 "RSET\n",
775 "QUIT\n",
776 "VRFY test@example.com\n",
777 "NOOP\n",
778 }
779
780 func TestSendMail(t *testing.T) {
781 server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n")
782 client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n")
783 var cmdbuf strings.Builder
784 bcmdbuf := bufio.NewWriter(&cmdbuf)
785 l, err := net.Listen("tcp", "127.0.0.1:0")
786 if err != nil {
787 t.Fatalf("Unable to create listener: %v", err)
788 }
789 defer l.Close()
790
791
792 var done = make(chan struct{})
793 go func(data []string) {
794
795 defer close(done)
796
797 conn, err := l.Accept()
798 if err != nil {
799 t.Errorf("Accept error: %v", err)
800 return
801 }
802 defer conn.Close()
803
804 tc := textproto.NewConn(conn)
805 for i := 0; i < len(data) && data[i] != ""; i++ {
806 tc.PrintfLine("%s", data[i])
807 for len(data[i]) >= 4 && data[i][3] == '-' {
808 i++
809 tc.PrintfLine("%s", data[i])
810 }
811 if data[i] == "221 Goodbye" {
812 return
813 }
814 read := false
815 for !read || data[i] == "354 Go ahead" {
816 msg, err := tc.ReadLine()
817 bcmdbuf.Write([]byte(msg + "\r\n"))
818 read = true
819 if err != nil {
820 t.Errorf("Read error: %v", err)
821 return
822 }
823 if data[i] == "354 Go ahead" && msg == "." {
824 break
825 }
826 }
827 }
828 }(strings.Split(server, "\r\n"))
829
830 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
831 To: other@example.com
832 Subject: SendMail test
833
834 SendMail is working for me.
835 `, "\n", "\r\n", -1)))
836 if err == nil {
837 t.Errorf("Expected SendMail to be rejected due to a message injection attempt")
838 }
839
840 err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com
841 To: other@example.com
842 Subject: SendMail test
843
844 SendMail is working for me.
845 `, "\n", "\r\n", -1)))
846
847 if err != nil {
848 t.Errorf("%v", err)
849 }
850
851 <-done
852 bcmdbuf.Flush()
853 actualcmds := cmdbuf.String()
854 if client != actualcmds {
855 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
856 }
857 }
858
859 var sendMailServer = `220 hello world
860 502 EH?
861 250 mx.google.com at your service
862 250 Sender ok
863 250 Receiver ok
864 354 Go ahead
865 250 Data ok
866 221 Goodbye
867 `
868
869 var sendMailClient = `EHLO localhost
870 HELO localhost
871 MAIL FROM:<test@example.com>
872 RCPT TO:<other@example.com>
873 DATA
874 From: test@example.com
875 To: other@example.com
876 Subject: SendMail test
877
878 SendMail is working for me.
879 .
880 QUIT
881 `
882
883 func TestSendMailWithAuth(t *testing.T) {
884 l, err := net.Listen("tcp", "127.0.0.1:0")
885 if err != nil {
886 t.Fatalf("Unable to create listener: %v", err)
887 }
888 defer l.Close()
889
890 errCh := make(chan error)
891 go func() {
892 defer close(errCh)
893 conn, err := l.Accept()
894 if err != nil {
895 errCh <- fmt.Errorf("Accept: %v", err)
896 return
897 }
898 defer conn.Close()
899
900 tc := textproto.NewConn(conn)
901 tc.PrintfLine("220 hello world")
902 msg, err := tc.ReadLine()
903 if err != nil {
904 errCh <- fmt.Errorf("ReadLine error: %v", err)
905 return
906 }
907 const wantMsg = "EHLO localhost"
908 if msg != wantMsg {
909 errCh <- fmt.Errorf("unexpected response %q; want %q", msg, wantMsg)
910 return
911 }
912 err = tc.PrintfLine("250 mx.google.com at your service")
913 if err != nil {
914 errCh <- fmt.Errorf("PrintfLine: %v", err)
915 return
916 }
917 }()
918
919 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
920 To: other@example.com
921 Subject: SendMail test
922
923 SendMail is working for me.
924 `, "\n", "\r\n", -1)))
925 if err == nil {
926 t.Error("SendMail: Server doesn't support AUTH, expected to get an error, but got none ")
927 }
928 if err.Error() != "smtp: server doesn't support AUTH" {
929 t.Errorf("Expected: smtp: server doesn't support AUTH, got: %s", err)
930 }
931 err = <-errCh
932 if err != nil {
933 t.Fatalf("server error: %v", err)
934 }
935 }
936
937 func TestAuthFailed(t *testing.T) {
938 server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n")
939 client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n")
940 var cmdbuf strings.Builder
941 bcmdbuf := bufio.NewWriter(&cmdbuf)
942 var fake faker
943 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
944 c, err := NewClient(fake, "fake.host")
945 if err != nil {
946 t.Fatalf("NewClient: %v", err)
947 }
948 defer c.Close()
949
950 c.tls = true
951 c.serverName = "smtp.google.com"
952 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
953
954 if err == nil {
955 t.Error("Auth: expected error; got none")
956 } else if err.Error() != "535 Invalid credentials\nplease see www.example.com" {
957 t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com")
958 }
959
960 bcmdbuf.Flush()
961 actualcmds := cmdbuf.String()
962 if client != actualcmds {
963 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
964 }
965 }
966
967 var authFailedServer = `220 hello world
968 250-mx.google.com at your service
969 250 AUTH LOGIN PLAIN
970 535-Invalid credentials
971 535 please see www.example.com
972 221 Goodbye
973 `
974
975 var authFailedClient = `EHLO localhost
976 AUTH PLAIN AHVzZXIAcGFzcw==
977 *
978 QUIT
979 `
980
981 func TestTLSClient(t *testing.T) {
982 if runtime.GOOS == "freebsd" || runtime.GOOS == "js" || runtime.GOOS == "wasip1" {
983 testenv.SkipFlaky(t, 19229)
984 }
985 ln := newLocalListener(t)
986 defer ln.Close()
987 errc := make(chan error)
988 go func() {
989 errc <- sendMail(ln.Addr().String())
990 }()
991 conn, err := ln.Accept()
992 if err != nil {
993 t.Fatalf("failed to accept connection: %v", err)
994 }
995 defer conn.Close()
996 if err := serverHandle(conn, t); err != nil {
997 t.Fatalf("failed to handle connection: %v", err)
998 }
999 if err := <-errc; err != nil {
1000 t.Fatalf("client error: %v", err)
1001 }
1002 }
1003
1004 func TestTLSConnState(t *testing.T) {
1005 ln := newLocalListener(t)
1006 defer ln.Close()
1007 clientDone := make(chan bool)
1008 serverDone := make(chan bool)
1009 go func() {
1010 defer close(serverDone)
1011 c, err := ln.Accept()
1012 if err != nil {
1013 t.Errorf("Server accept: %v", err)
1014 return
1015 }
1016 defer c.Close()
1017 if err := serverHandle(c, t); err != nil {
1018 t.Errorf("server error: %v", err)
1019 }
1020 }()
1021 go func() {
1022 defer close(clientDone)
1023 c, err := Dial(ln.Addr().String())
1024 if err != nil {
1025 t.Errorf("Client dial: %v", err)
1026 return
1027 }
1028 defer c.Quit()
1029 cfg := &tls.Config{ServerName: "example.com"}
1030 testHookStartTLS(cfg)
1031 if err := c.StartTLS(cfg); err != nil {
1032 t.Errorf("StartTLS: %v", err)
1033 return
1034 }
1035 cs, ok := c.TLSConnectionState()
1036 if !ok {
1037 t.Errorf("TLSConnectionState returned ok == false; want true")
1038 return
1039 }
1040 if cs.Version == 0 || !cs.HandshakeComplete {
1041 t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs)
1042 }
1043 }()
1044 <-clientDone
1045 <-serverDone
1046 }
1047
1048 func newLocalListener(t *testing.T) net.Listener {
1049 ln, err := net.Listen("tcp", "127.0.0.1:0")
1050 if err != nil {
1051 ln, err = net.Listen("tcp6", "[::1]:0")
1052 }
1053 if err != nil {
1054 t.Fatal(err)
1055 }
1056 return ln
1057 }
1058
1059 type smtpSender struct {
1060 w io.Writer
1061 }
1062
1063 func (s smtpSender) send(f string) {
1064 s.w.Write([]byte(f + "\r\n"))
1065 }
1066
1067
1068 func serverHandle(c net.Conn, t *testing.T) error {
1069 send := smtpSender{c}.send
1070 send("220 127.0.0.1 ESMTP service ready")
1071 s := bufio.NewScanner(c)
1072 for s.Scan() {
1073 switch s.Text() {
1074 case "EHLO localhost":
1075 send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
1076 send("250-STARTTLS")
1077 send("250 Ok")
1078 case "STARTTLS":
1079 send("220 Go ahead")
1080 keypair, err := tls.X509KeyPair(localhostCert, localhostKey)
1081 if err != nil {
1082 return err
1083 }
1084 config := &tls.Config{Certificates: []tls.Certificate{keypair}}
1085 c = tls.Server(c, config)
1086 defer c.Close()
1087 return serverHandleTLS(c, t)
1088 default:
1089 t.Fatalf("unrecognized command: %q", s.Text())
1090 }
1091 }
1092 return s.Err()
1093 }
1094
1095 func serverHandleTLS(c net.Conn, t *testing.T) error {
1096 send := smtpSender{c}.send
1097 s := bufio.NewScanner(c)
1098 for s.Scan() {
1099 switch s.Text() {
1100 case "EHLO localhost":
1101 send("250 Ok")
1102 case "MAIL FROM:<joe1@example.com>":
1103 send("250 Ok")
1104 case "RCPT TO:<joe2@example.com>":
1105 send("250 Ok")
1106 case "DATA":
1107 send("354 send the mail data, end with .")
1108 send("250 Ok")
1109 case "Subject: test":
1110 case "":
1111 case "howdy!":
1112 case ".":
1113 case "QUIT":
1114 send("221 127.0.0.1 Service closing transmission channel")
1115 return nil
1116 default:
1117 t.Fatalf("unrecognized command during TLS: %q", s.Text())
1118 }
1119 }
1120 return s.Err()
1121 }
1122
1123 func init() {
1124 testRootCAs := x509.NewCertPool()
1125 testRootCAs.AppendCertsFromPEM(localhostCert)
1126 testHookStartTLS = func(config *tls.Config) {
1127 config.RootCAs = testRootCAs
1128 }
1129 }
1130
1131 func sendMail(hostPort string) error {
1132 from := "joe1@example.com"
1133 to := []string{"joe2@example.com"}
1134 return SendMail(hostPort, nil, from, to, []byte("Subject: test\n\nhowdy!"))
1135 }
1136
1137
1138
1139
1140
1141 var localhostCert = []byte(`
1142 -----BEGIN CERTIFICATE-----
1143 MIICFDCCAX2gAwIBAgIRAK0xjnaPuNDSreeXb+z+0u4wDQYJKoZIhvcNAQELBQAw
1144 EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
1145 MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
1146 gYkCgYEA0nFbQQuOWsjbGtejcpWz153OlziZM4bVjJ9jYruNw5n2Ry6uYQAffhqa
1147 JOInCmmcVe2siJglsyH9aRh6vKiobBbIUXXUU1ABd56ebAzlt0LobLlx7pZEMy30
1148 LqIi9E6zmL3YvdGzpYlkFRnRrqwEtWYbGBf3znO250S56CCWH2UCAwEAAaNoMGYw
1149 DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF
1150 MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA
1151 AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAbZtDS2dVuBYvb+MnolWnCNqvw1w5Gtgi
1152 NmvQQPOMgM3m+oQSCPRTNGSg25e1Qbo7bgQDv8ZTnq8FgOJ/rbkyERw2JckkHpD4
1153 n4qcK27WkEDBtQFlPihIM8hLIuzWoi/9wygiElTy/tVL3y7fGCvY2/k1KBthtZGF
1154 tN8URjVmyEo=
1155 -----END CERTIFICATE-----`)
1156
1157
1158 var localhostKey = []byte(testingKey(`
1159 -----BEGIN RSA TESTING KEY-----
1160 MIICXgIBAAKBgQDScVtBC45ayNsa16NylbPXnc6XOJkzhtWMn2Niu43DmfZHLq5h
1161 AB9+Gpok4icKaZxV7ayImCWzIf1pGHq8qKhsFshRddRTUAF3np5sDOW3QuhsuXHu
1162 lkQzLfQuoiL0TrOYvdi90bOliWQVGdGurAS1ZhsYF/fOc7bnRLnoIJYfZQIDAQAB
1163 AoGBAMst7OgpKyFV6c3JwyI/jWqxDySL3caU+RuTTBaodKAUx2ZEmNJIlx9eudLA
1164 kucHvoxsM/eRxlxkhdFxdBcwU6J+zqooTnhu/FE3jhrT1lPrbhfGhyKnUrB0KKMM
1165 VY3IQZyiehpxaeXAwoAou6TbWoTpl9t8ImAqAMY8hlULCUqlAkEA+9+Ry5FSYK/m
1166 542LujIcCaIGoG1/Te6Sxr3hsPagKC2rH20rDLqXwEedSFOpSS0vpzlPAzy/6Rbb
1167 PHTJUhNdwwJBANXkA+TkMdbJI5do9/mn//U0LfrCR9NkcoYohxfKz8JuhgRQxzF2
1168 6jpo3q7CdTuuRixLWVfeJzcrAyNrVcBq87cCQFkTCtOMNC7fZnCTPUv+9q1tcJyB
1169 vNjJu3yvoEZeIeuzouX9TJE21/33FaeDdsXbRhQEj23cqR38qFHsF1qAYNMCQQDP
1170 QXLEiJoClkR2orAmqjPLVhR3t2oB3INcnEjLNSq8LHyQEfXyaFfu4U9l5+fRPL2i
1171 jiC0k/9L5dHUsF0XZothAkEA23ddgRs+Id/HxtojqqUT27B8MT/IGNrYsp4DvS/c
1172 qgkeluku4GjxRlDMBuXk94xOBEinUs+p/hwP1Alll80Tpg==
1173 -----END RSA TESTING KEY-----`))
1174
1175 func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
1176
View as plain text