libgo: update to Go 1.11
[official-gcc.git] / libgo / go / net / smtp / smtp_test.go
blob000cac4fcbbfee07b8017d4259743ace92f78e96
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 "internal/testenv"
13 "io"
14 "net"
15 "net/textproto"
16 "runtime"
17 "strings"
18 "sync"
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 TestNewClient(t *testing.T) {
292 server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n")
293 client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n")
295 var cmdbuf bytes.Buffer
296 bcmdbuf := bufio.NewWriter(&cmdbuf)
297 out := func() string {
298 bcmdbuf.Flush()
299 return cmdbuf.String()
301 var fake faker
302 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
303 c, err := NewClient(fake, "fake.host")
304 if err != nil {
305 t.Fatalf("NewClient: %v\n(after %v)", err, out())
307 defer c.Close()
308 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
309 t.Fatalf("Expected AUTH supported")
311 if ok, _ := c.Extension("DSN"); ok {
312 t.Fatalf("Shouldn't support DSN")
314 if err := c.Quit(); err != nil {
315 t.Fatalf("QUIT failed: %s", err)
318 actualcmds := out()
319 if client != actualcmds {
320 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
324 var newClientServer = `220 hello world
325 250-mx.google.com at your service
326 250-SIZE 35651584
327 250-AUTH LOGIN PLAIN
328 250 8BITMIME
329 221 OK
332 var newClientClient = `EHLO localhost
333 QUIT
336 func TestNewClient2(t *testing.T) {
337 server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n")
338 client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n")
340 var cmdbuf bytes.Buffer
341 bcmdbuf := bufio.NewWriter(&cmdbuf)
342 var fake faker
343 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
344 c, err := NewClient(fake, "fake.host")
345 if err != nil {
346 t.Fatalf("NewClient: %v", err)
348 defer c.Close()
349 if ok, _ := c.Extension("DSN"); ok {
350 t.Fatalf("Shouldn't support DSN")
352 if err := c.Quit(); err != nil {
353 t.Fatalf("QUIT failed: %s", err)
356 bcmdbuf.Flush()
357 actualcmds := cmdbuf.String()
358 if client != actualcmds {
359 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
363 var newClient2Server = `220 hello world
364 502 EH?
365 250-mx.google.com at your service
366 250-SIZE 35651584
367 250-AUTH LOGIN PLAIN
368 250 8BITMIME
369 221 OK
372 var newClient2Client = `EHLO localhost
373 HELO localhost
374 QUIT
377 func TestNewClientWithTLS(t *testing.T) {
378 cert, err := tls.X509KeyPair(localhostCert, localhostKey)
379 if err != nil {
380 t.Fatalf("loadcert: %v", err)
383 config := tls.Config{Certificates: []tls.Certificate{cert}}
385 ln, err := tls.Listen("tcp", "127.0.0.1:0", &config)
386 if err != nil {
387 ln, err = tls.Listen("tcp", "[::1]:0", &config)
388 if err != nil {
389 t.Fatalf("server: listen: %v", err)
393 go func() {
394 conn, err := ln.Accept()
395 if err != nil {
396 t.Errorf("server: accept: %v", err)
397 return
399 defer conn.Close()
401 _, err = conn.Write([]byte("220 SIGNS\r\n"))
402 if err != nil {
403 t.Errorf("server: write: %v", err)
404 return
408 config.InsecureSkipVerify = true
409 conn, err := tls.Dial("tcp", ln.Addr().String(), &config)
410 if err != nil {
411 t.Fatalf("client: dial: %v", err)
413 defer conn.Close()
415 client, err := NewClient(conn, ln.Addr().String())
416 if err != nil {
417 t.Fatalf("smtp: newclient: %v", err)
419 if !client.tls {
420 t.Errorf("client.tls Got: %t Expected: %t", client.tls, true)
424 func TestHello(t *testing.T) {
426 if len(helloServer) != len(helloClient) {
427 t.Fatalf("Hello server and client size mismatch")
430 for i := 0; i < len(helloServer); i++ {
431 server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n")
432 client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n")
433 var cmdbuf bytes.Buffer
434 bcmdbuf := bufio.NewWriter(&cmdbuf)
435 var fake faker
436 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
437 c, err := NewClient(fake, "fake.host")
438 if err != nil {
439 t.Fatalf("NewClient: %v", err)
441 defer c.Close()
442 c.localName = "customhost"
443 err = nil
445 switch i {
446 case 0:
447 err = c.Hello("hostinjection>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n")
448 if err == nil {
449 t.Errorf("Expected Hello to be rejected due to a message injection attempt")
451 err = c.Hello("customhost")
452 case 1:
453 err = c.StartTLS(nil)
454 if err.Error() == "502 Not implemented" {
455 err = nil
457 case 2:
458 err = c.Verify("test@example.com")
459 case 3:
460 c.tls = true
461 c.serverName = "smtp.google.com"
462 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
463 case 4:
464 err = c.Mail("test@example.com")
465 case 5:
466 ok, _ := c.Extension("feature")
467 if ok {
468 t.Errorf("Expected FEATURE not to be supported")
470 case 6:
471 err = c.Reset()
472 case 7:
473 err = c.Quit()
474 case 8:
475 err = c.Verify("test@example.com")
476 if err != nil {
477 err = c.Hello("customhost")
478 if err != nil {
479 t.Errorf("Want error, got none")
482 case 9:
483 err = c.Noop()
484 default:
485 t.Fatalf("Unhandled command")
488 if err != nil {
489 t.Errorf("Command %d failed: %v", i, err)
492 bcmdbuf.Flush()
493 actualcmds := cmdbuf.String()
494 if client != actualcmds {
495 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
500 var baseHelloServer = `220 hello world
501 502 EH?
502 250-mx.google.com at your service
503 250 FEATURE
506 var helloServer = []string{
508 "502 Not implemented\n",
509 "250 User is valid\n",
510 "235 Accepted\n",
511 "250 Sender ok\n",
513 "250 Reset ok\n",
514 "221 Goodbye\n",
515 "250 Sender ok\n",
516 "250 ok\n",
519 var baseHelloClient = `EHLO customhost
520 HELO customhost
523 var helloClient = []string{
525 "STARTTLS\n",
526 "VRFY test@example.com\n",
527 "AUTH PLAIN AHVzZXIAcGFzcw==\n",
528 "MAIL FROM:<test@example.com>\n",
530 "RSET\n",
531 "QUIT\n",
532 "VRFY test@example.com\n",
533 "NOOP\n",
536 func TestSendMail(t *testing.T) {
537 server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n")
538 client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n")
539 var cmdbuf bytes.Buffer
540 bcmdbuf := bufio.NewWriter(&cmdbuf)
541 l, err := net.Listen("tcp", "127.0.0.1:0")
542 if err != nil {
543 t.Fatalf("Unable to to create listener: %v", err)
545 defer l.Close()
547 // prevent data race on bcmdbuf
548 var done = make(chan struct{})
549 go func(data []string) {
551 defer close(done)
553 conn, err := l.Accept()
554 if err != nil {
555 t.Errorf("Accept error: %v", err)
556 return
558 defer conn.Close()
560 tc := textproto.NewConn(conn)
561 for i := 0; i < len(data) && data[i] != ""; i++ {
562 tc.PrintfLine(data[i])
563 for len(data[i]) >= 4 && data[i][3] == '-' {
565 tc.PrintfLine(data[i])
567 if data[i] == "221 Goodbye" {
568 return
570 read := false
571 for !read || data[i] == "354 Go ahead" {
572 msg, err := tc.ReadLine()
573 bcmdbuf.Write([]byte(msg + "\r\n"))
574 read = true
575 if err != nil {
576 t.Errorf("Read error: %v", err)
577 return
579 if data[i] == "354 Go ahead" && msg == "." {
580 break
584 }(strings.Split(server, "\r\n"))
586 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
587 To: other@example.com
588 Subject: SendMail test
590 SendMail is working for me.
591 `, "\n", "\r\n", -1)))
592 if err == nil {
593 t.Errorf("Expected SendMail to be rejected due to a message injection attempt")
596 err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com
597 To: other@example.com
598 Subject: SendMail test
600 SendMail is working for me.
601 `, "\n", "\r\n", -1)))
603 if err != nil {
604 t.Errorf("%v", err)
607 <-done
608 bcmdbuf.Flush()
609 actualcmds := cmdbuf.String()
610 if client != actualcmds {
611 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
615 var sendMailServer = `220 hello world
616 502 EH?
617 250 mx.google.com at your service
618 250 Sender ok
619 250 Receiver ok
620 354 Go ahead
621 250 Data ok
622 221 Goodbye
625 var sendMailClient = `EHLO localhost
626 HELO localhost
627 MAIL FROM:<test@example.com>
628 RCPT TO:<other@example.com>
629 DATA
630 From: test@example.com
631 To: other@example.com
632 Subject: SendMail test
634 SendMail is working for me.
636 QUIT
639 func TestSendMailWithAuth(t *testing.T) {
640 l, err := net.Listen("tcp", "127.0.0.1:0")
641 if err != nil {
642 t.Fatalf("Unable to to create listener: %v", err)
644 defer l.Close()
645 wg := sync.WaitGroup{}
646 var done = make(chan struct{})
647 go func() {
648 defer wg.Done()
649 conn, err := l.Accept()
650 if err != nil {
651 t.Errorf("Accept error: %v", err)
652 return
654 defer conn.Close()
656 tc := textproto.NewConn(conn)
657 tc.PrintfLine("220 hello world")
658 msg, err := tc.ReadLine()
659 if msg == "EHLO localhost" {
660 tc.PrintfLine("250 mx.google.com at your service")
662 // for this test case, there should have no more traffic
663 <-done
665 wg.Add(1)
667 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
668 To: other@example.com
669 Subject: SendMail test
671 SendMail is working for me.
672 `, "\n", "\r\n", -1)))
673 if err == nil {
674 t.Error("SendMail: Server doesn't support AUTH, expected to get an error, but got none ")
676 if err.Error() != "smtp: server doesn't support AUTH" {
677 t.Errorf("Expected: smtp: server doesn't support AUTH, got: %s", err)
679 close(done)
680 wg.Wait()
683 func TestAuthFailed(t *testing.T) {
684 server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n")
685 client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n")
686 var cmdbuf bytes.Buffer
687 bcmdbuf := bufio.NewWriter(&cmdbuf)
688 var fake faker
689 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
690 c, err := NewClient(fake, "fake.host")
691 if err != nil {
692 t.Fatalf("NewClient: %v", err)
694 defer c.Close()
696 c.tls = true
697 c.serverName = "smtp.google.com"
698 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
700 if err == nil {
701 t.Error("Auth: expected error; got none")
702 } else if err.Error() != "535 Invalid credentials\nplease see www.example.com" {
703 t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com")
706 bcmdbuf.Flush()
707 actualcmds := cmdbuf.String()
708 if client != actualcmds {
709 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
713 var authFailedServer = `220 hello world
714 250-mx.google.com at your service
715 250 AUTH LOGIN PLAIN
716 535-Invalid credentials
717 535 please see www.example.com
718 221 Goodbye
721 var authFailedClient = `EHLO localhost
722 AUTH PLAIN AHVzZXIAcGFzcw==
724 QUIT
727 func TestTLSClient(t *testing.T) {
728 if (runtime.GOOS == "freebsd" && runtime.GOARCH == "amd64") || runtime.GOOS == "js" {
729 testenv.SkipFlaky(t, 19229)
731 ln := newLocalListener(t)
732 defer ln.Close()
733 errc := make(chan error)
734 go func() {
735 errc <- sendMail(ln.Addr().String())
737 conn, err := ln.Accept()
738 if err != nil {
739 t.Fatalf("failed to accept connection: %v", err)
741 defer conn.Close()
742 if err := serverHandle(conn, t); err != nil {
743 t.Fatalf("failed to handle connection: %v", err)
745 if err := <-errc; err != nil {
746 t.Fatalf("client error: %v", err)
750 func TestTLSConnState(t *testing.T) {
751 ln := newLocalListener(t)
752 defer ln.Close()
753 clientDone := make(chan bool)
754 serverDone := make(chan bool)
755 go func() {
756 defer close(serverDone)
757 c, err := ln.Accept()
758 if err != nil {
759 t.Errorf("Server accept: %v", err)
760 return
762 defer c.Close()
763 if err := serverHandle(c, t); err != nil {
764 t.Errorf("server error: %v", err)
767 go func() {
768 defer close(clientDone)
769 c, err := Dial(ln.Addr().String())
770 if err != nil {
771 t.Errorf("Client dial: %v", err)
772 return
774 defer c.Quit()
775 cfg := &tls.Config{ServerName: "example.com"}
776 testHookStartTLS(cfg) // set the RootCAs
777 if err := c.StartTLS(cfg); err != nil {
778 t.Errorf("StartTLS: %v", err)
779 return
781 cs, ok := c.TLSConnectionState()
782 if !ok {
783 t.Errorf("TLSConnectionState returned ok == false; want true")
784 return
786 if cs.Version == 0 || !cs.HandshakeComplete {
787 t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs)
790 <-clientDone
791 <-serverDone
794 func newLocalListener(t *testing.T) net.Listener {
795 ln, err := net.Listen("tcp", "127.0.0.1:0")
796 if err != nil {
797 ln, err = net.Listen("tcp6", "[::1]:0")
799 if err != nil {
800 t.Fatal(err)
802 return ln
805 type smtpSender struct {
806 w io.Writer
809 func (s smtpSender) send(f string) {
810 s.w.Write([]byte(f + "\r\n"))
813 // smtp server, finely tailored to deal with our own client only!
814 func serverHandle(c net.Conn, t *testing.T) error {
815 send := smtpSender{c}.send
816 send("220 127.0.0.1 ESMTP service ready")
817 s := bufio.NewScanner(c)
818 for s.Scan() {
819 switch s.Text() {
820 case "EHLO localhost":
821 send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
822 send("250-STARTTLS")
823 send("250 Ok")
824 case "STARTTLS":
825 send("220 Go ahead")
826 keypair, err := tls.X509KeyPair(localhostCert, localhostKey)
827 if err != nil {
828 return err
830 config := &tls.Config{Certificates: []tls.Certificate{keypair}}
831 c = tls.Server(c, config)
832 defer c.Close()
833 return serverHandleTLS(c, t)
834 default:
835 t.Fatalf("unrecognized command: %q", s.Text())
838 return s.Err()
841 func serverHandleTLS(c net.Conn, t *testing.T) error {
842 send := smtpSender{c}.send
843 s := bufio.NewScanner(c)
844 for s.Scan() {
845 switch s.Text() {
846 case "EHLO localhost":
847 send("250 Ok")
848 case "MAIL FROM:<joe1@example.com>":
849 send("250 Ok")
850 case "RCPT TO:<joe2@example.com>":
851 send("250 Ok")
852 case "DATA":
853 send("354 send the mail data, end with .")
854 send("250 Ok")
855 case "Subject: test":
856 case "":
857 case "howdy!":
858 case ".":
859 case "QUIT":
860 send("221 127.0.0.1 Service closing transmission channel")
861 return nil
862 default:
863 t.Fatalf("unrecognized command during TLS: %q", s.Text())
866 return s.Err()
869 func init() {
870 testRootCAs := x509.NewCertPool()
871 testRootCAs.AppendCertsFromPEM(localhostCert)
872 testHookStartTLS = func(config *tls.Config) {
873 config.RootCAs = testRootCAs
877 func sendMail(hostPort string) error {
878 from := "joe1@example.com"
879 to := []string{"joe2@example.com"}
880 return SendMail(hostPort, nil, from, to, []byte("Subject: test\n\nhowdy!"))
883 // (copied from net/http/httptest)
884 // localhostCert is a PEM-encoded TLS cert with SAN IPs
885 // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
886 // of ASN.1 time).
887 // generated from src/crypto/tls:
888 // go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
889 var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
890 MIIBjjCCATigAwIBAgIQMon9v0s3pDFXvAMnPgelpzANBgkqhkiG9w0BAQsFADAS
891 MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
892 MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
893 AM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnlqaUfVKVmz7FF
894 wLP9csX6vGtvkZg1uWAtvfkCAwEAAaNoMGYwDgYDVR0PAQH/BAQDAgKkMBMGA1Ud
895 JQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhh
896 bXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQAD
897 QQBOZsFVC7IwX+qibmSbt2IPHkUgXhfbq0a9MYhD6tHcj4gbDcTXh4kZCbgHCz22
898 gfSj2/G2wxzopoISVDucuncj
899 -----END CERTIFICATE-----`)
901 // localhostKey is the private key for localhostCert.
902 var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
903 MIIBOwIBAAJBAM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnl
904 qaUfVKVmz7FFwLP9csX6vGtvkZg1uWAtvfkCAwEAAQJART2qkxODLUbQ2siSx7m2
905 rmBLyR/7X+nLe8aPDrMOxj3heDNl4YlaAYLexbcY8d7VDfCRBKYoAOP0UCP1Vhuf
906 UQIhAO6PEI55K3SpNIdc2k5f0xz+9rodJCYzu51EwWX7r8ufAiEA3C9EkLiU2NuK
907 3L3DHCN5IlUSN1Nr/lw8NIt50Yorj2cCIQCDw1VbvCV6bDLtSSXzAA51B4ZzScE7
908 sHtB5EYF9Dwm9QIhAJuCquuH4mDzVjUntXjXOQPdj7sRqVGCNWdrJwOukat7AiAy
909 LXLEwb77DIPoI5ZuaXQC+MnyyJj1ExC9RFcGz+bexA==
910 -----END RSA PRIVATE KEY-----`)