2015-05-18 Steven G. Kargl <kargl@gcc.gnu.org>
[official-gcc.git] / libgo / go / net / smtp / smtp_test.go
blob5c659e8a0950085133230241fd66c354dcbbe894
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 "io"
13 "net"
14 "net/textproto"
15 "strings"
16 "testing"
17 "time"
20 type authTest struct {
21 auth Auth
22 challenges []string
23 name string
24 responses []string
27 var authTests = []authTest{
28 {PlainAuth("", "user", "pass", "testserver"), []string{}, "PLAIN", []string{"\x00user\x00pass"}},
29 {PlainAuth("foo", "bar", "baz", "testserver"), []string{}, "PLAIN", []string{"foo\x00bar\x00baz"}},
30 {CRAMMD5Auth("user", "pass"), []string{"<123456.1322876914@testserver>"}, "CRAM-MD5", []string{"", "user 287eb355114cf5c471c26a875f1ca4ae"}},
33 func TestAuth(t *testing.T) {
34 testLoop:
35 for i, test := range authTests {
36 name, resp, err := test.auth.Start(&ServerInfo{"testserver", true, nil})
37 if name != test.name {
38 t.Errorf("#%d got name %s, expected %s", i, name, test.name)
40 if !bytes.Equal(resp, []byte(test.responses[0])) {
41 t.Errorf("#%d got response %s, expected %s", i, resp, test.responses[0])
43 if err != nil {
44 t.Errorf("#%d error: %s", i, err)
46 for j := range test.challenges {
47 challenge := []byte(test.challenges[j])
48 expected := []byte(test.responses[j+1])
49 resp, err := test.auth.Next(challenge, true)
50 if err != nil {
51 t.Errorf("#%d error: %s", i, err)
52 continue testLoop
54 if !bytes.Equal(resp, expected) {
55 t.Errorf("#%d got %s, expected %s", i, resp, expected)
56 continue testLoop
62 func TestAuthPlain(t *testing.T) {
63 auth := PlainAuth("foo", "bar", "baz", "servername")
65 tests := []struct {
66 server *ServerInfo
67 err string
70 server: &ServerInfo{Name: "servername", TLS: true},
73 // Okay; explicitly advertised by server.
74 server: &ServerInfo{Name: "servername", Auth: []string{"PLAIN"}},
77 server: &ServerInfo{Name: "servername", Auth: []string{"CRAM-MD5"}},
78 err: "unencrypted connection",
81 server: &ServerInfo{Name: "attacker", TLS: true},
82 err: "wrong host name",
85 for i, tt := range tests {
86 _, _, err := auth.Start(tt.server)
87 got := ""
88 if err != nil {
89 got = err.Error()
91 if got != tt.err {
92 t.Errorf("%d. got error = %q; want %q", i, got, tt.err)
97 type faker struct {
98 io.ReadWriter
101 func (f faker) Close() error { return nil }
102 func (f faker) LocalAddr() net.Addr { return nil }
103 func (f faker) RemoteAddr() net.Addr { return nil }
104 func (f faker) SetDeadline(time.Time) error { return nil }
105 func (f faker) SetReadDeadline(time.Time) error { return nil }
106 func (f faker) SetWriteDeadline(time.Time) error { return nil }
108 func TestBasic(t *testing.T) {
109 server := strings.Join(strings.Split(basicServer, "\n"), "\r\n")
110 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
112 var cmdbuf bytes.Buffer
113 bcmdbuf := bufio.NewWriter(&cmdbuf)
114 var fake faker
115 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
116 c := &Client{Text: textproto.NewConn(fake), localName: "localhost"}
118 if err := c.helo(); err != nil {
119 t.Fatalf("HELO failed: %s", err)
121 if err := c.ehlo(); err == nil {
122 t.Fatalf("Expected first EHLO to fail")
124 if err := c.ehlo(); err != nil {
125 t.Fatalf("Second EHLO failed: %s", err)
128 c.didHello = true
129 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
130 t.Fatalf("Expected AUTH supported")
132 if ok, _ := c.Extension("DSN"); ok {
133 t.Fatalf("Shouldn't support DSN")
136 if err := c.Mail("user@gmail.com"); err == nil {
137 t.Fatalf("MAIL should require authentication")
140 if err := c.Verify("user1@gmail.com"); err == nil {
141 t.Fatalf("First VRFY: expected no verification")
143 if err := c.Verify("user2@gmail.com"); err != nil {
144 t.Fatalf("Second VRFY: expected verification, got %s", err)
147 // fake TLS so authentication won't complain
148 c.tls = true
149 c.serverName = "smtp.google.com"
150 if err := c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")); err != nil {
151 t.Fatalf("AUTH failed: %s", err)
154 if err := c.Mail("user@gmail.com"); err != nil {
155 t.Fatalf("MAIL failed: %s", err)
157 if err := c.Rcpt("golang-nuts@googlegroups.com"); err != nil {
158 t.Fatalf("RCPT failed: %s", err)
160 msg := `From: user@gmail.com
161 To: golang-nuts@googlegroups.com
162 Subject: Hooray for Go
164 Line 1
165 .Leading dot line .
166 Goodbye.`
167 w, err := c.Data()
168 if err != nil {
169 t.Fatalf("DATA failed: %s", err)
171 if _, err := w.Write([]byte(msg)); err != nil {
172 t.Fatalf("Data write failed: %s", err)
174 if err := w.Close(); err != nil {
175 t.Fatalf("Bad data response: %s", err)
178 if err := c.Quit(); err != nil {
179 t.Fatalf("QUIT failed: %s", err)
182 bcmdbuf.Flush()
183 actualcmds := cmdbuf.String()
184 if client != actualcmds {
185 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
189 var basicServer = `250 mx.google.com at your service
190 502 Unrecognized command.
191 250-mx.google.com at your service
192 250-SIZE 35651584
193 250-AUTH LOGIN PLAIN
194 250 8BITMIME
195 530 Authentication required
196 252 Send some mail, I'll try my best
197 250 User is valid
198 235 Accepted
199 250 Sender OK
200 250 Receiver OK
201 354 Go ahead
202 250 Data OK
203 221 OK
206 var basicClient = `HELO localhost
207 EHLO localhost
208 EHLO localhost
209 MAIL FROM:<user@gmail.com> BODY=8BITMIME
210 VRFY user1@gmail.com
211 VRFY user2@gmail.com
212 AUTH PLAIN AHVzZXIAcGFzcw==
213 MAIL FROM:<user@gmail.com> BODY=8BITMIME
214 RCPT TO:<golang-nuts@googlegroups.com>
215 DATA
216 From: user@gmail.com
217 To: golang-nuts@googlegroups.com
218 Subject: Hooray for Go
220 Line 1
221 ..Leading dot line .
222 Goodbye.
224 QUIT
227 func TestNewClient(t *testing.T) {
228 server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n")
229 client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n")
231 var cmdbuf bytes.Buffer
232 bcmdbuf := bufio.NewWriter(&cmdbuf)
233 out := func() string {
234 bcmdbuf.Flush()
235 return cmdbuf.String()
237 var fake faker
238 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
239 c, err := NewClient(fake, "fake.host")
240 if err != nil {
241 t.Fatalf("NewClient: %v\n(after %v)", err, out())
243 defer c.Close()
244 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
245 t.Fatalf("Expected AUTH supported")
247 if ok, _ := c.Extension("DSN"); ok {
248 t.Fatalf("Shouldn't support DSN")
250 if err := c.Quit(); err != nil {
251 t.Fatalf("QUIT failed: %s", err)
254 actualcmds := out()
255 if client != actualcmds {
256 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
260 var newClientServer = `220 hello world
261 250-mx.google.com at your service
262 250-SIZE 35651584
263 250-AUTH LOGIN PLAIN
264 250 8BITMIME
265 221 OK
268 var newClientClient = `EHLO localhost
269 QUIT
272 func TestNewClient2(t *testing.T) {
273 server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n")
274 client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n")
276 var cmdbuf bytes.Buffer
277 bcmdbuf := bufio.NewWriter(&cmdbuf)
278 var fake faker
279 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
280 c, err := NewClient(fake, "fake.host")
281 if err != nil {
282 t.Fatalf("NewClient: %v", err)
284 defer c.Close()
285 if ok, _ := c.Extension("DSN"); ok {
286 t.Fatalf("Shouldn't support DSN")
288 if err := c.Quit(); err != nil {
289 t.Fatalf("QUIT failed: %s", err)
292 bcmdbuf.Flush()
293 actualcmds := cmdbuf.String()
294 if client != actualcmds {
295 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
299 var newClient2Server = `220 hello world
300 502 EH?
301 250-mx.google.com at your service
302 250-SIZE 35651584
303 250-AUTH LOGIN PLAIN
304 250 8BITMIME
305 221 OK
308 var newClient2Client = `EHLO localhost
309 HELO localhost
310 QUIT
313 func TestHello(t *testing.T) {
315 if len(helloServer) != len(helloClient) {
316 t.Fatalf("Hello server and client size mismatch")
319 for i := 0; i < len(helloServer); i++ {
320 server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n")
321 client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n")
322 var cmdbuf bytes.Buffer
323 bcmdbuf := bufio.NewWriter(&cmdbuf)
324 var fake faker
325 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
326 c, err := NewClient(fake, "fake.host")
327 if err != nil {
328 t.Fatalf("NewClient: %v", err)
330 defer c.Close()
331 c.localName = "customhost"
332 err = nil
334 switch i {
335 case 0:
336 err = c.Hello("customhost")
337 case 1:
338 err = c.StartTLS(nil)
339 if err.Error() == "502 Not implemented" {
340 err = nil
342 case 2:
343 err = c.Verify("test@example.com")
344 case 3:
345 c.tls = true
346 c.serverName = "smtp.google.com"
347 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
348 case 4:
349 err = c.Mail("test@example.com")
350 case 5:
351 ok, _ := c.Extension("feature")
352 if ok {
353 t.Errorf("Expected FEATURE not to be supported")
355 case 6:
356 err = c.Reset()
357 case 7:
358 err = c.Quit()
359 case 8:
360 err = c.Verify("test@example.com")
361 if err != nil {
362 err = c.Hello("customhost")
363 if err != nil {
364 t.Errorf("Want error, got none")
367 default:
368 t.Fatalf("Unhandled command")
371 if err != nil {
372 t.Errorf("Command %d failed: %v", i, err)
375 bcmdbuf.Flush()
376 actualcmds := cmdbuf.String()
377 if client != actualcmds {
378 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
383 var baseHelloServer = `220 hello world
384 502 EH?
385 250-mx.google.com at your service
386 250 FEATURE
389 var helloServer = []string{
391 "502 Not implemented\n",
392 "250 User is valid\n",
393 "235 Accepted\n",
394 "250 Sender ok\n",
396 "250 Reset ok\n",
397 "221 Goodbye\n",
398 "250 Sender ok\n",
401 var baseHelloClient = `EHLO customhost
402 HELO customhost
405 var helloClient = []string{
407 "STARTTLS\n",
408 "VRFY test@example.com\n",
409 "AUTH PLAIN AHVzZXIAcGFzcw==\n",
410 "MAIL FROM:<test@example.com>\n",
412 "RSET\n",
413 "QUIT\n",
414 "VRFY test@example.com\n",
417 func TestSendMail(t *testing.T) {
418 server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n")
419 client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n")
420 var cmdbuf bytes.Buffer
421 bcmdbuf := bufio.NewWriter(&cmdbuf)
422 l, err := net.Listen("tcp", "127.0.0.1:0")
423 if err != nil {
424 t.Fatalf("Unable to to create listener: %v", err)
426 defer l.Close()
428 // prevent data race on bcmdbuf
429 var done = make(chan struct{})
430 go func(data []string) {
432 defer close(done)
434 conn, err := l.Accept()
435 if err != nil {
436 t.Errorf("Accept error: %v", err)
437 return
439 defer conn.Close()
441 tc := textproto.NewConn(conn)
442 for i := 0; i < len(data) && data[i] != ""; i++ {
443 tc.PrintfLine(data[i])
444 for len(data[i]) >= 4 && data[i][3] == '-' {
446 tc.PrintfLine(data[i])
448 if data[i] == "221 Goodbye" {
449 return
451 read := false
452 for !read || data[i] == "354 Go ahead" {
453 msg, err := tc.ReadLine()
454 bcmdbuf.Write([]byte(msg + "\r\n"))
455 read = true
456 if err != nil {
457 t.Errorf("Read error: %v", err)
458 return
460 if data[i] == "354 Go ahead" && msg == "." {
461 break
465 }(strings.Split(server, "\r\n"))
467 err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com
468 To: other@example.com
469 Subject: SendMail test
471 SendMail is working for me.
472 `, "\n", "\r\n", -1)))
474 if err != nil {
475 t.Errorf("%v", err)
478 <-done
479 bcmdbuf.Flush()
480 actualcmds := cmdbuf.String()
481 if client != actualcmds {
482 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
486 var sendMailServer = `220 hello world
487 502 EH?
488 250 mx.google.com at your service
489 250 Sender ok
490 250 Receiver ok
491 354 Go ahead
492 250 Data ok
493 221 Goodbye
496 var sendMailClient = `EHLO localhost
497 HELO localhost
498 MAIL FROM:<test@example.com>
499 RCPT TO:<other@example.com>
500 DATA
501 From: test@example.com
502 To: other@example.com
503 Subject: SendMail test
505 SendMail is working for me.
507 QUIT
510 func TestAuthFailed(t *testing.T) {
511 server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n")
512 client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n")
513 var cmdbuf bytes.Buffer
514 bcmdbuf := bufio.NewWriter(&cmdbuf)
515 var fake faker
516 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
517 c, err := NewClient(fake, "fake.host")
518 if err != nil {
519 t.Fatalf("NewClient: %v", err)
521 defer c.Close()
523 c.tls = true
524 c.serverName = "smtp.google.com"
525 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
527 if err == nil {
528 t.Error("Auth: expected error; got none")
529 } else if err.Error() != "535 Invalid credentials\nplease see www.example.com" {
530 t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com")
533 bcmdbuf.Flush()
534 actualcmds := cmdbuf.String()
535 if client != actualcmds {
536 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
540 var authFailedServer = `220 hello world
541 250-mx.google.com at your service
542 250 AUTH LOGIN PLAIN
543 535-Invalid credentials
544 535 please see www.example.com
545 221 Goodbye
548 var authFailedClient = `EHLO localhost
549 AUTH PLAIN AHVzZXIAcGFzcw==
551 QUIT
554 func TestTLSClient(t *testing.T) {
555 ln := newLocalListener(t)
556 defer ln.Close()
557 errc := make(chan error)
558 go func() {
559 errc <- sendMail(ln.Addr().String())
561 conn, err := ln.Accept()
562 if err != nil {
563 t.Fatalf("failed to accept connection: %v", err)
565 defer conn.Close()
566 if err := serverHandle(conn, t); err != nil {
567 t.Fatalf("failed to handle connection: %v", err)
569 if err := <-errc; err != nil {
570 t.Fatalf("client error: %v", err)
574 func newLocalListener(t *testing.T) net.Listener {
575 ln, err := net.Listen("tcp", "127.0.0.1:0")
576 if err != nil {
577 ln, err = net.Listen("tcp6", "[::1]:0")
579 if err != nil {
580 t.Fatal(err)
582 return ln
585 type smtpSender struct {
586 w io.Writer
589 func (s smtpSender) send(f string) {
590 s.w.Write([]byte(f + "\r\n"))
593 // smtp server, finely tailored to deal with our own client only!
594 func serverHandle(c net.Conn, t *testing.T) error {
595 send := smtpSender{c}.send
596 send("220 127.0.0.1 ESMTP service ready")
597 s := bufio.NewScanner(c)
598 for s.Scan() {
599 switch s.Text() {
600 case "EHLO localhost":
601 send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
602 send("250-STARTTLS")
603 send("250 Ok")
604 case "STARTTLS":
605 send("220 Go ahead")
606 keypair, err := tls.X509KeyPair(localhostCert, localhostKey)
607 if err != nil {
608 return err
610 config := &tls.Config{Certificates: []tls.Certificate{keypair}}
611 c = tls.Server(c, config)
612 defer c.Close()
613 return serverHandleTLS(c, t)
614 default:
615 t.Fatalf("unrecognized command: %q", s.Text())
618 return s.Err()
621 func serverHandleTLS(c net.Conn, t *testing.T) error {
622 send := smtpSender{c}.send
623 s := bufio.NewScanner(c)
624 for s.Scan() {
625 switch s.Text() {
626 case "EHLO localhost":
627 send("250 Ok")
628 case "MAIL FROM:<joe1@example.com>":
629 send("250 Ok")
630 case "RCPT TO:<joe2@example.com>":
631 send("250 Ok")
632 case "DATA":
633 send("354 send the mail data, end with .")
634 send("250 Ok")
635 case "Subject: test":
636 case "":
637 case "howdy!":
638 case ".":
639 case "QUIT":
640 send("221 127.0.0.1 Service closing transmission channel")
641 return nil
642 default:
643 t.Fatalf("unrecognized command during TLS: %q", s.Text())
646 return s.Err()
649 func init() {
650 testRootCAs := x509.NewCertPool()
651 testRootCAs.AppendCertsFromPEM(localhostCert)
652 testHookStartTLS = func(config *tls.Config) {
653 config.RootCAs = testRootCAs
657 func sendMail(hostPort string) error {
658 host, _, err := net.SplitHostPort(hostPort)
659 if err != nil {
660 return err
662 auth := PlainAuth("", "", "", host)
663 from := "joe1@example.com"
664 to := []string{"joe2@example.com"}
665 return SendMail(hostPort, auth, from, to, []byte("Subject: test\n\nhowdy!"))
668 // (copied from net/http/httptest)
669 // localhostCert is a PEM-encoded TLS cert with SAN IPs
670 // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
671 // of ASN.1 time).
672 // generated from src/crypto/tls:
673 // 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
674 var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
675 MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
676 bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj
677 bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa
678 IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA
679 AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud
680 EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA
681 AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk
682 Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA==
683 -----END CERTIFICATE-----`)
685 // localhostKey is the private key for localhostCert.
686 var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
687 MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0
688 0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV
689 NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d
690 AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW
691 MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD
692 EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA
693 1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE=
694 -----END RSA PRIVATE KEY-----`)