libbacktrace: it's OK if zstd decompressor sees no backward bits
[official-gcc.git] / libgo / go / net / smtp / smtp_test.go
blob0f758f4a339414720dd23278a23920bc1e48cba2
1 // Copyright 2010 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 package smtp
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"
23 type authTest struct {
24 auth Auth
25 challenges []string
26 name string
27 responses []string
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"}},
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)
43 if !bytes.Equal(resp, []byte(test.responses[0])) {
44 t.Errorf("#%d got response %s, expected %s", i, resp, test.responses[0])
46 if err != nil {
47 t.Errorf("#%d error: %s", i, err)
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
57 if !bytes.Equal(resp, expected) {
58 t.Errorf("#%d got %s, expected %s", i, resp, expected)
59 continue testLoop
65 func TestAuthPlain(t *testing.T) {
67 tests := []struct {
68 authName string
69 server *ServerInfo
70 err string
73 authName: "servername",
74 server: &ServerInfo{Name: "servername", TLS: true},
77 // OK to use PlainAuth on localhost without TLS
78 authName: "localhost",
79 server: &ServerInfo{Name: "localhost", TLS: false},
82 // NOT OK on non-localhost, even if server says PLAIN is OK.
83 // (We don't know that the server is the real server.)
84 authName: "servername",
85 server: &ServerInfo{Name: "servername", Auth: []string{"PLAIN"}},
86 err: "unencrypted connection",
89 authName: "servername",
90 server: &ServerInfo{Name: "servername", Auth: []string{"CRAM-MD5"}},
91 err: "unencrypted connection",
94 authName: "servername",
95 server: &ServerInfo{Name: "attacker", TLS: true},
96 err: "wrong host name",
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()
106 if got != tt.err {
107 t.Errorf("%d. got error = %q; want %q", i, got, tt.err)
112 // Issue 17794: don't send a trailing space on AUTH command when there's no password.
113 func TestClientAuthTrimSpace(t *testing.T) {
114 server := "220 hello world\r\n" +
115 "200 some more"
116 var wrote bytes.Buffer
117 var fake faker
118 fake.ReadWriter = struct {
119 io.Reader
120 io.Writer
122 strings.NewReader(server),
123 &wrote,
125 c, err := NewClient(fake, "fake.host")
126 if err != nil {
127 t.Fatalf("NewClient: %v", err)
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)
138 // toServerEmptyAuth is an implementation of Auth that only implements
139 // the Start method, and returns "FOOAUTH", nil, nil. Notably, it returns
140 // zero bytes for "toServer" so we can test that we don't send spaces at
141 // the end of the line. See TestClientAuthTrimSpace.
142 type toServerEmptyAuth struct{}
144 func (toServerEmptyAuth) Start(server *ServerInfo) (proto string, toServer []byte, err error) {
145 return "FOOAUTH", nil, nil
148 func (toServerEmptyAuth) Next(fromServer []byte, more bool) (toServer []byte, err error) {
149 panic("unexpected call")
152 type faker struct {
153 io.ReadWriter
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 }
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")
167 var cmdbuf bytes.Buffer
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"}
173 if err := c.helo(); err != nil {
174 t.Fatalf("HELO failed: %s", err)
176 if err := c.ehlo(); err == nil {
177 t.Fatalf("Expected first EHLO to fail")
179 if err := c.ehlo(); err != nil {
180 t.Fatalf("Second EHLO failed: %s", err)
183 c.didHello = true
184 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
185 t.Fatalf("Expected AUTH supported")
187 if ok, _ := c.Extension("DSN"); ok {
188 t.Fatalf("Shouldn't support DSN")
191 if err := c.Mail("user@gmail.com"); err == nil {
192 t.Fatalf("MAIL should require authentication")
195 if err := c.Verify("user1@gmail.com"); err == nil {
196 t.Fatalf("First VRFY: expected no verification")
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")
201 if err := c.Verify("user2@gmail.com"); err != nil {
202 t.Fatalf("Second VRFY: expected verification, got %s", err)
205 // fake TLS so authentication won't complain
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)
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")
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")
218 if err := c.Mail("user@gmail.com"); err != nil {
219 t.Fatalf("MAIL failed: %s", err)
221 if err := c.Rcpt("golang-nuts@googlegroups.com"); err != nil {
222 t.Fatalf("RCPT failed: %s", err)
224 msg := `From: user@gmail.com
225 To: golang-nuts@googlegroups.com
226 Subject: Hooray for Go
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)
235 if _, err := w.Write([]byte(msg)); err != nil {
236 t.Fatalf("Data write failed: %s", err)
238 if err := w.Close(); err != nil {
239 t.Fatalf("Bad data response: %s", err)
242 if err := c.Quit(); err != nil {
243 t.Fatalf("QUIT failed: %s", err)
246 bcmdbuf.Flush()
247 actualcmds := cmdbuf.String()
248 if client != actualcmds {
249 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
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
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
284 Line 1
285 ..Leading dot line .
286 Goodbye.
288 QUIT
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")
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"}
301 return c, bcmdbuf, cmdbuf
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
311 basicClient = `HELO localhost
312 MAIL FROM:<user@gmail.com>
313 QUIT
317 c, bcmdbuf, cmdbuf := fake(basicServer)
319 if err := c.helo(); err != nil {
320 t.Fatalf("HELO failed: %s", err)
322 c.didHello = true
323 if err := c.Mail("user@gmail.com"); err != nil {
324 t.Fatalf("MAIL FROM failed: %s", err)
326 if err := c.Quit(); err != nil {
327 t.Fatalf("QUIT failed: %s", err)
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)
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
346 basicClient = `EHLO localhost
347 MAIL FROM:<user@gmail.com>
348 QUIT
352 c, bcmdbuf, cmdbuf := fake(basicServer)
354 if err := c.Hello("localhost"); err != nil {
355 t.Fatalf("EHLO failed: %s", err)
357 if ok, _ := c.Extension("8BITMIME"); ok {
358 t.Fatalf("Shouldn't support 8BITMIME")
360 if ok, _ := c.Extension("SMTPUTF8"); ok {
361 t.Fatalf("Shouldn't support SMTPUTF8")
363 if err := c.Mail("user@gmail.com"); err != nil {
364 t.Fatalf("MAIL FROM failed: %s", err)
366 if err := c.Quit(); err != nil {
367 t.Fatalf("QUIT failed: %s", err)
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)
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
387 basicClient = `EHLO localhost
388 MAIL FROM:<user@gmail.com> BODY=8BITMIME
389 QUIT
393 c, bcmdbuf, cmdbuf := fake(basicServer)
395 if err := c.Hello("localhost"); err != nil {
396 t.Fatalf("EHLO failed: %s", err)
398 if ok, _ := c.Extension("8BITMIME"); !ok {
399 t.Fatalf("Should support 8BITMIME")
401 if ok, _ := c.Extension("SMTPUTF8"); ok {
402 t.Fatalf("Shouldn't support SMTPUTF8")
404 if err := c.Mail("user@gmail.com"); err != nil {
405 t.Fatalf("MAIL FROM failed: %s", err)
407 if err := c.Quit(); err != nil {
408 t.Fatalf("QUIT failed: %s", err)
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)
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
428 basicClient = `EHLO localhost
429 MAIL FROM:<user+📧@gmail.com> SMTPUTF8
430 QUIT
434 c, bcmdbuf, cmdbuf := fake(basicServer)
436 if err := c.Hello("localhost"); err != nil {
437 t.Fatalf("EHLO failed: %s", err)
439 if ok, _ := c.Extension("8BITMIME"); ok {
440 t.Fatalf("Shouldn't support 8BITMIME")
442 if ok, _ := c.Extension("SMTPUTF8"); !ok {
443 t.Fatalf("Should support SMTPUTF8")
445 if err := c.Mail("user+📧@gmail.com"); err != nil {
446 t.Fatalf("MAIL FROM failed: %s", err)
448 if err := c.Quit(); err != nil {
449 t.Fatalf("QUIT failed: %s", err)
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)
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
470 basicClient = `EHLO localhost
471 MAIL FROM:<user+📧@gmail.com> BODY=8BITMIME SMTPUTF8
472 QUIT
476 c, bcmdbuf, cmdbuf := fake(basicServer)
478 if err := c.Hello("localhost"); err != nil {
479 t.Fatalf("EHLO failed: %s", err)
481 c.didHello = true
482 if ok, _ := c.Extension("8BITMIME"); !ok {
483 t.Fatalf("Should support 8BITMIME")
485 if ok, _ := c.Extension("SMTPUTF8"); !ok {
486 t.Fatalf("Should support SMTPUTF8")
488 if err := c.Mail("user+📧@gmail.com"); err != nil {
489 t.Fatalf("MAIL FROM failed: %s", err)
491 if err := c.Quit(); err != nil {
492 t.Fatalf("QUIT failed: %s", err)
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)
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")
508 var cmdbuf bytes.Buffer
509 bcmdbuf := bufio.NewWriter(&cmdbuf)
510 out := func() string {
511 bcmdbuf.Flush()
512 return cmdbuf.String()
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())
520 defer c.Close()
521 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
522 t.Fatalf("Expected AUTH supported")
524 if ok, _ := c.Extension("DSN"); ok {
525 t.Fatalf("Shouldn't support DSN")
527 if err := c.Quit(); err != nil {
528 t.Fatalf("QUIT failed: %s", err)
531 actualcmds := out()
532 if client != actualcmds {
533 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
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
545 var newClientClient = `EHLO localhost
546 QUIT
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")
553 var cmdbuf bytes.Buffer
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)
561 defer c.Close()
562 if ok, _ := c.Extension("DSN"); ok {
563 t.Fatalf("Shouldn't support DSN")
565 if err := c.Quit(); err != nil {
566 t.Fatalf("QUIT failed: %s", err)
569 bcmdbuf.Flush()
570 actualcmds := cmdbuf.String()
571 if client != actualcmds {
572 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
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
585 var newClient2Client = `EHLO localhost
586 HELO localhost
587 QUIT
590 func TestNewClientWithTLS(t *testing.T) {
591 cert, err := tls.X509KeyPair(localhostCert, localhostKey)
592 if err != nil {
593 t.Fatalf("loadcert: %v", err)
596 config := tls.Config{Certificates: []tls.Certificate{cert}}
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)
606 go func() {
607 conn, err := ln.Accept()
608 if err != nil {
609 t.Errorf("server: accept: %v", err)
610 return
612 defer conn.Close()
614 _, err = conn.Write([]byte("220 SIGNS\r\n"))
615 if err != nil {
616 t.Errorf("server: write: %v", err)
617 return
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)
626 defer conn.Close()
628 client, err := NewClient(conn, ln.Addr().String())
629 if err != nil {
630 t.Fatalf("smtp: newclient: %v", err)
632 if !client.tls {
633 t.Errorf("client.tls Got: %t Expected: %t", client.tls, true)
637 func TestHello(t *testing.T) {
639 if len(helloServer) != len(helloClient) {
640 t.Fatalf("Hello server and client size mismatch")
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 bytes.Buffer
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)
654 defer c.Close()
655 c.localName = "customhost"
656 err = nil
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")
664 err = c.Hello("customhost")
665 case 1:
666 err = c.StartTLS(nil)
667 if err.Error() == "502 Not implemented" {
668 err = nil
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")
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")
695 case 9:
696 err = c.Noop()
697 default:
698 t.Fatalf("Unhandled command")
701 if err != nil {
702 t.Errorf("Command %d failed: %v", i, err)
705 bcmdbuf.Flush()
706 actualcmds := cmdbuf.String()
707 if client != actualcmds {
708 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
713 var baseHelloServer = `220 hello world
714 502 EH?
715 250-mx.google.com at your service
716 250 FEATURE
719 var helloServer = []string{
721 "502 Not implemented\n",
722 "250 User is valid\n",
723 "235 Accepted\n",
724 "250 Sender ok\n",
726 "250 Reset ok\n",
727 "221 Goodbye\n",
728 "250 Sender ok\n",
729 "250 ok\n",
732 var baseHelloClient = `EHLO customhost
733 HELO customhost
736 var helloClient = []string{
738 "STARTTLS\n",
739 "VRFY test@example.com\n",
740 "AUTH PLAIN AHVzZXIAcGFzcw==\n",
741 "MAIL FROM:<test@example.com>\n",
743 "RSET\n",
744 "QUIT\n",
745 "VRFY test@example.com\n",
746 "NOOP\n",
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 bytes.Buffer
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)
758 defer l.Close()
760 // prevent data race on bcmdbuf
761 var done = make(chan struct{})
762 go func(data []string) {
764 defer close(done)
766 conn, err := l.Accept()
767 if err != nil {
768 t.Errorf("Accept error: %v", err)
769 return
771 defer conn.Close()
773 tc := textproto.NewConn(conn)
774 for i := 0; i < len(data) && data[i] != ""; i++ {
775 tc.PrintfLine(data[i])
776 for len(data[i]) >= 4 && data[i][3] == '-' {
778 tc.PrintfLine(data[i])
780 if data[i] == "221 Goodbye" {
781 return
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
792 if data[i] == "354 Go ahead" && msg == "." {
793 break
797 }(strings.Split(server, "\r\n"))
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
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")
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
813 SendMail is working for me.
814 `, "\n", "\r\n", -1)))
816 if err != nil {
817 t.Errorf("%v", err)
820 <-done
821 bcmdbuf.Flush()
822 actualcmds := cmdbuf.String()
823 if client != actualcmds {
824 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
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
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
847 SendMail is working for me.
849 QUIT
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)
857 defer l.Close()
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
867 defer conn.Close()
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
876 const wantMsg = "EHLO localhost"
877 if msg != wantMsg {
878 errCh <- fmt.Errorf("unexpected response %q; want %q", msg, wantMsg)
879 return
881 err = tc.PrintfLine("250 mx.google.com at your service")
882 if err != nil {
883 errCh <- fmt.Errorf("PrintfLine: %v", err)
884 return
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
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 ")
897 if err.Error() != "smtp: server doesn't support AUTH" {
898 t.Errorf("Expected: smtp: server doesn't support AUTH, got: %s", err)
900 err = <-errCh
901 if err != nil {
902 t.Fatalf("server error: %v", err)
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 bytes.Buffer
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)
917 defer c.Close()
919 c.tls = true
920 c.serverName = "smtp.google.com"
921 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
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")
929 bcmdbuf.Flush()
930 actualcmds := cmdbuf.String()
931 if client != actualcmds {
932 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
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
944 var authFailedClient = `EHLO localhost
945 AUTH PLAIN AHVzZXIAcGFzcw==
947 QUIT
950 func TestTLSClient(t *testing.T) {
951 if runtime.GOOS == "freebsd" || runtime.GOOS == "js" {
952 testenv.SkipFlaky(t, 19229)
954 ln := newLocalListener(t)
955 defer ln.Close()
956 errc := make(chan error)
957 go func() {
958 errc <- sendMail(ln.Addr().String())
960 conn, err := ln.Accept()
961 if err != nil {
962 t.Fatalf("failed to accept connection: %v", err)
964 defer conn.Close()
965 if err := serverHandle(conn, t); err != nil {
966 t.Fatalf("failed to handle connection: %v", err)
968 if err := <-errc; err != nil {
969 t.Fatalf("client error: %v", err)
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
985 defer c.Close()
986 if err := serverHandle(c, t); err != nil {
987 t.Errorf("server error: %v", err)
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
997 defer c.Quit()
998 cfg := &tls.Config{ServerName: "example.com"}
999 testHookStartTLS(cfg) // set the RootCAs
1000 if err := c.StartTLS(cfg); err != nil {
1001 t.Errorf("StartTLS: %v", err)
1002 return
1004 cs, ok := c.TLSConnectionState()
1005 if !ok {
1006 t.Errorf("TLSConnectionState returned ok == false; want true")
1007 return
1009 if cs.Version == 0 || !cs.HandshakeComplete {
1010 t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs)
1013 <-clientDone
1014 <-serverDone
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")
1022 if err != nil {
1023 t.Fatal(err)
1025 return ln
1028 type smtpSender struct {
1029 w io.Writer
1032 func (s smtpSender) send(f string) {
1033 s.w.Write([]byte(f + "\r\n"))
1036 // smtp server, finely tailored to deal with our own client only!
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
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())
1061 return s.Err()
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())
1089 return s.Err()
1092 func init() {
1093 testRootCAs := x509.NewCertPool()
1094 testRootCAs.AppendCertsFromPEM(localhostCert)
1095 testHookStartTLS = func(config *tls.Config) {
1096 config.RootCAs = testRootCAs
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!"))
1106 // localhostCert is a PEM-encoded TLS cert generated from src/crypto/tls:
1107 // go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com \
1108 // --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
1109 var localhostCert = []byte(`
1110 -----BEGIN CERTIFICATE-----
1111 MIICFDCCAX2gAwIBAgIRAK0xjnaPuNDSreeXb+z+0u4wDQYJKoZIhvcNAQELBQAw
1112 EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
1113 MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
1114 gYkCgYEA0nFbQQuOWsjbGtejcpWz153OlziZM4bVjJ9jYruNw5n2Ry6uYQAffhqa
1115 JOInCmmcVe2siJglsyH9aRh6vKiobBbIUXXUU1ABd56ebAzlt0LobLlx7pZEMy30
1116 LqIi9E6zmL3YvdGzpYlkFRnRrqwEtWYbGBf3znO250S56CCWH2UCAwEAAaNoMGYw
1117 DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF
1118 MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA
1119 AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAbZtDS2dVuBYvb+MnolWnCNqvw1w5Gtgi
1120 NmvQQPOMgM3m+oQSCPRTNGSg25e1Qbo7bgQDv8ZTnq8FgOJ/rbkyERw2JckkHpD4
1121 n4qcK27WkEDBtQFlPihIM8hLIuzWoi/9wygiElTy/tVL3y7fGCvY2/k1KBthtZGF
1122 tN8URjVmyEo=
1123 -----END CERTIFICATE-----`)
1125 // localhostKey is the private key for localhostCert.
1126 var localhostKey = []byte(testingKey(`
1127 -----BEGIN RSA TESTING KEY-----
1128 MIICXgIBAAKBgQDScVtBC45ayNsa16NylbPXnc6XOJkzhtWMn2Niu43DmfZHLq5h
1129 AB9+Gpok4icKaZxV7ayImCWzIf1pGHq8qKhsFshRddRTUAF3np5sDOW3QuhsuXHu
1130 lkQzLfQuoiL0TrOYvdi90bOliWQVGdGurAS1ZhsYF/fOc7bnRLnoIJYfZQIDAQAB
1131 AoGBAMst7OgpKyFV6c3JwyI/jWqxDySL3caU+RuTTBaodKAUx2ZEmNJIlx9eudLA
1132 kucHvoxsM/eRxlxkhdFxdBcwU6J+zqooTnhu/FE3jhrT1lPrbhfGhyKnUrB0KKMM
1133 VY3IQZyiehpxaeXAwoAou6TbWoTpl9t8ImAqAMY8hlULCUqlAkEA+9+Ry5FSYK/m
1134 542LujIcCaIGoG1/Te6Sxr3hsPagKC2rH20rDLqXwEedSFOpSS0vpzlPAzy/6Rbb
1135 PHTJUhNdwwJBANXkA+TkMdbJI5do9/mn//U0LfrCR9NkcoYohxfKz8JuhgRQxzF2
1136 6jpo3q7CdTuuRixLWVfeJzcrAyNrVcBq87cCQFkTCtOMNC7fZnCTPUv+9q1tcJyB
1137 vNjJu3yvoEZeIeuzouX9TJE21/33FaeDdsXbRhQEj23cqR38qFHsF1qAYNMCQQDP
1138 QXLEiJoClkR2orAmqjPLVhR3t2oB3INcnEjLNSq8LHyQEfXyaFfu4U9l5+fRPL2i
1139 jiC0k/9L5dHUsF0XZothAkEA23ddgRs+Id/HxtojqqUT27B8MT/IGNrYsp4DvS/c
1140 qgkeluku4GjxRlDMBuXk94xOBEinUs+p/hwP1Alll80Tpg==
1141 -----END RSA TESTING KEY-----`))
1143 func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }