Home | History | Annotate | Download | only in smtp
      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.
      4 
      5 package smtp
      6 
      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 	"testing"
     19 	"time"
     20 )
     21 
     22 type authTest struct {
     23 	auth       Auth
     24 	challenges []string
     25 	name       string
     26 	responses  []string
     27 }
     28 
     29 var authTests = []authTest{
     30 	{PlainAuth("", "user", "pass", "testserver"), []string{}, "PLAIN", []string{"\x00user\x00pass"}},
     31 	{PlainAuth("foo", "bar", "baz", "testserver"), []string{}, "PLAIN", []string{"foo\x00bar\x00baz"}},
     32 	{CRAMMD5Auth("user", "pass"), []string{"<123456.1322876914@testserver>"}, "CRAM-MD5", []string{"", "user 287eb355114cf5c471c26a875f1ca4ae"}},
     33 }
     34 
     35 func TestAuth(t *testing.T) {
     36 testLoop:
     37 	for i, test := range authTests {
     38 		name, resp, err := test.auth.Start(&ServerInfo{"testserver", true, nil})
     39 		if name != test.name {
     40 			t.Errorf("#%d got name %s, expected %s", i, name, test.name)
     41 		}
     42 		if !bytes.Equal(resp, []byte(test.responses[0])) {
     43 			t.Errorf("#%d got response %s, expected %s", i, resp, test.responses[0])
     44 		}
     45 		if err != nil {
     46 			t.Errorf("#%d error: %s", i, err)
     47 		}
     48 		for j := range test.challenges {
     49 			challenge := []byte(test.challenges[j])
     50 			expected := []byte(test.responses[j+1])
     51 			resp, err := test.auth.Next(challenge, true)
     52 			if err != nil {
     53 				t.Errorf("#%d error: %s", i, err)
     54 				continue testLoop
     55 			}
     56 			if !bytes.Equal(resp, expected) {
     57 				t.Errorf("#%d got %s, expected %s", i, resp, expected)
     58 				continue testLoop
     59 			}
     60 		}
     61 	}
     62 }
     63 
     64 func TestAuthPlain(t *testing.T) {
     65 
     66 	tests := []struct {
     67 		authName string
     68 		server   *ServerInfo
     69 		err      string
     70 	}{
     71 		{
     72 			authName: "servername",
     73 			server:   &ServerInfo{Name: "servername", TLS: true},
     74 		},
     75 		{
     76 			// OK to use PlainAuth on localhost without TLS
     77 			authName: "localhost",
     78 			server:   &ServerInfo{Name: "localhost", TLS: false},
     79 		},
     80 		{
     81 			// NOT OK on non-localhost, even if server says PLAIN is OK.
     82 			// (We don't know that the server is the real server.)
     83 			authName: "servername",
     84 			server:   &ServerInfo{Name: "servername", Auth: []string{"PLAIN"}},
     85 			err:      "unencrypted connection",
     86 		},
     87 		{
     88 			authName: "servername",
     89 			server:   &ServerInfo{Name: "servername", Auth: []string{"CRAM-MD5"}},
     90 			err:      "unencrypted connection",
     91 		},
     92 		{
     93 			authName: "servername",
     94 			server:   &ServerInfo{Name: "attacker", TLS: true},
     95 			err:      "wrong host name",
     96 		},
     97 	}
     98 	for i, tt := range tests {
     99 		auth := PlainAuth("foo", "bar", "baz", tt.authName)
    100 		_, _, err := auth.Start(tt.server)
    101 		got := ""
    102 		if err != nil {
    103 			got = err.Error()
    104 		}
    105 		if got != tt.err {
    106 			t.Errorf("%d. got error = %q; want %q", i, got, tt.err)
    107 		}
    108 	}
    109 }
    110 
    111 // Issue 17794: don't send a trailing space on AUTH command when there's no password.
    112 func TestClientAuthTrimSpace(t *testing.T) {
    113 	server := "220 hello world\r\n" +
    114 		"200 some more"
    115 	var wrote bytes.Buffer
    116 	var fake faker
    117 	fake.ReadWriter = struct {
    118 		io.Reader
    119 		io.Writer
    120 	}{
    121 		strings.NewReader(server),
    122 		&wrote,
    123 	}
    124 	c, err := NewClient(fake, "fake.host")
    125 	if err != nil {
    126 		t.Fatalf("NewClient: %v", err)
    127 	}
    128 	c.tls = true
    129 	c.didHello = true
    130 	c.Auth(toServerEmptyAuth{})
    131 	c.Close()
    132 	if got, want := wrote.String(), "AUTH FOOAUTH\r\n*\r\nQUIT\r\n"; got != want {
    133 		t.Errorf("wrote %q; want %q", got, want)
    134 	}
    135 }
    136 
    137 // toServerEmptyAuth is an implementation of Auth that only implements
    138 // the Start method, and returns "FOOAUTH", nil, nil. Notably, it returns
    139 // zero bytes for "toServer" so we can test that we don't send spaces at
    140 // the end of the line. See TestClientAuthTrimSpace.
    141 type toServerEmptyAuth struct{}
    142 
    143 func (toServerEmptyAuth) Start(server *ServerInfo) (proto string, toServer []byte, err error) {
    144 	return "FOOAUTH", nil, nil
    145 }
    146 
    147 func (toServerEmptyAuth) Next(fromServer []byte, more bool) (toServer []byte, err error) {
    148 	panic("unexpected call")
    149 }
    150 
    151 type faker struct {
    152 	io.ReadWriter
    153 }
    154 
    155 func (f faker) Close() error                     { return nil }
    156 func (f faker) LocalAddr() net.Addr              { return nil }
    157 func (f faker) RemoteAddr() net.Addr             { return nil }
    158 func (f faker) SetDeadline(time.Time) error      { return nil }
    159 func (f faker) SetReadDeadline(time.Time) error  { return nil }
    160 func (f faker) SetWriteDeadline(time.Time) error { return nil }
    161 
    162 func TestBasic(t *testing.T) {
    163 	server := strings.Join(strings.Split(basicServer, "\n"), "\r\n")
    164 	client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
    165 
    166 	var cmdbuf bytes.Buffer
    167 	bcmdbuf := bufio.NewWriter(&cmdbuf)
    168 	var fake faker
    169 	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
    170 	c := &Client{Text: textproto.NewConn(fake), localName: "localhost"}
    171 
    172 	if err := c.helo(); err != nil {
    173 		t.Fatalf("HELO failed: %s", err)
    174 	}
    175 	if err := c.ehlo(); err == nil {
    176 		t.Fatalf("Expected first EHLO to fail")
    177 	}
    178 	if err := c.ehlo(); err != nil {
    179 		t.Fatalf("Second EHLO failed: %s", err)
    180 	}
    181 
    182 	c.didHello = true
    183 	if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
    184 		t.Fatalf("Expected AUTH supported")
    185 	}
    186 	if ok, _ := c.Extension("DSN"); ok {
    187 		t.Fatalf("Shouldn't support DSN")
    188 	}
    189 
    190 	if err := c.Mail("user (a] gmail.com"); err == nil {
    191 		t.Fatalf("MAIL should require authentication")
    192 	}
    193 
    194 	if err := c.Verify("user1 (a] gmail.com"); err == nil {
    195 		t.Fatalf("First VRFY: expected no verification")
    196 	}
    197 	if err := c.Verify("user2 (a] gmail.com>\r\nDATA\r\nAnother injected message body\r\n.\r\nQUIT\r\n"); err == nil {
    198 		t.Fatalf("VRFY should have failed due to a message injection attempt")
    199 	}
    200 	if err := c.Verify("user2 (a] gmail.com"); err != nil {
    201 		t.Fatalf("Second VRFY: expected verification, got %s", err)
    202 	}
    203 
    204 	// fake TLS so authentication won't complain
    205 	c.tls = true
    206 	c.serverName = "smtp.google.com"
    207 	if err := c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")); err != nil {
    208 		t.Fatalf("AUTH failed: %s", err)
    209 	}
    210 
    211 	if err := c.Rcpt("golang-nuts (a] googlegroups.com>\r\nDATA\r\nInjected message body\r\n.\r\nQUIT\r\n"); err == nil {
    212 		t.Fatalf("RCPT should have failed due to a message injection attempt")
    213 	}
    214 	if err := c.Mail("user (a] gmail.com>\r\nDATA\r\nAnother injected message body\r\n.\r\nQUIT\r\n"); err == nil {
    215 		t.Fatalf("MAIL should have failed due to a message injection attempt")
    216 	}
    217 	if err := c.Mail("user (a] gmail.com"); err != nil {
    218 		t.Fatalf("MAIL failed: %s", err)
    219 	}
    220 	if err := c.Rcpt("golang-nuts (a] googlegroups.com"); err != nil {
    221 		t.Fatalf("RCPT failed: %s", err)
    222 	}
    223 	msg := `From: user (a] gmail.com
    224 To: golang-nuts (a] googlegroups.com
    225 Subject: Hooray for Go
    226 
    227 Line 1
    228 .Leading dot line .
    229 Goodbye.`
    230 	w, err := c.Data()
    231 	if err != nil {
    232 		t.Fatalf("DATA failed: %s", err)
    233 	}
    234 	if _, err := w.Write([]byte(msg)); err != nil {
    235 		t.Fatalf("Data write failed: %s", err)
    236 	}
    237 	if err := w.Close(); err != nil {
    238 		t.Fatalf("Bad data response: %s", err)
    239 	}
    240 
    241 	if err := c.Quit(); err != nil {
    242 		t.Fatalf("QUIT failed: %s", err)
    243 	}
    244 
    245 	bcmdbuf.Flush()
    246 	actualcmds := cmdbuf.String()
    247 	if client != actualcmds {
    248 		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
    249 	}
    250 }
    251 
    252 var basicServer = `250 mx.google.com at your service
    253 502 Unrecognized command.
    254 250-mx.google.com at your service
    255 250-SIZE 35651584
    256 250-AUTH LOGIN PLAIN
    257 250 8BITMIME
    258 530 Authentication required
    259 252 Send some mail, I'll try my best
    260 250 User is valid
    261 235 Accepted
    262 250 Sender OK
    263 250 Receiver OK
    264 354 Go ahead
    265 250 Data OK
    266 221 OK
    267 `
    268 
    269 var basicClient = `HELO localhost
    270 EHLO localhost
    271 EHLO localhost
    272 MAIL FROM:<user (a] gmail.com> BODY=8BITMIME
    273 VRFY user1 (a] gmail.com
    274 VRFY user2 (a] gmail.com
    275 AUTH PLAIN AHVzZXIAcGFzcw==
    276 MAIL FROM:<user (a] gmail.com> BODY=8BITMIME
    277 RCPT TO:<golang-nuts (a] googlegroups.com>
    278 DATA
    279 From: user (a] gmail.com
    280 To: golang-nuts (a] googlegroups.com
    281 Subject: Hooray for Go
    282 
    283 Line 1
    284 ..Leading dot line .
    285 Goodbye.
    286 .
    287 QUIT
    288 `
    289 
    290 func TestNewClient(t *testing.T) {
    291 	server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n")
    292 	client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n")
    293 
    294 	var cmdbuf bytes.Buffer
    295 	bcmdbuf := bufio.NewWriter(&cmdbuf)
    296 	out := func() string {
    297 		bcmdbuf.Flush()
    298 		return cmdbuf.String()
    299 	}
    300 	var fake faker
    301 	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
    302 	c, err := NewClient(fake, "fake.host")
    303 	if err != nil {
    304 		t.Fatalf("NewClient: %v\n(after %v)", err, out())
    305 	}
    306 	defer c.Close()
    307 	if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
    308 		t.Fatalf("Expected AUTH supported")
    309 	}
    310 	if ok, _ := c.Extension("DSN"); ok {
    311 		t.Fatalf("Shouldn't support DSN")
    312 	}
    313 	if err := c.Quit(); err != nil {
    314 		t.Fatalf("QUIT failed: %s", err)
    315 	}
    316 
    317 	actualcmds := out()
    318 	if client != actualcmds {
    319 		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
    320 	}
    321 }
    322 
    323 var newClientServer = `220 hello world
    324 250-mx.google.com at your service
    325 250-SIZE 35651584
    326 250-AUTH LOGIN PLAIN
    327 250 8BITMIME
    328 221 OK
    329 `
    330 
    331 var newClientClient = `EHLO localhost
    332 QUIT
    333 `
    334 
    335 func TestNewClient2(t *testing.T) {
    336 	server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n")
    337 	client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n")
    338 
    339 	var cmdbuf bytes.Buffer
    340 	bcmdbuf := bufio.NewWriter(&cmdbuf)
    341 	var fake faker
    342 	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
    343 	c, err := NewClient(fake, "fake.host")
    344 	if err != nil {
    345 		t.Fatalf("NewClient: %v", err)
    346 	}
    347 	defer c.Close()
    348 	if ok, _ := c.Extension("DSN"); ok {
    349 		t.Fatalf("Shouldn't support DSN")
    350 	}
    351 	if err := c.Quit(); err != nil {
    352 		t.Fatalf("QUIT failed: %s", err)
    353 	}
    354 
    355 	bcmdbuf.Flush()
    356 	actualcmds := cmdbuf.String()
    357 	if client != actualcmds {
    358 		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
    359 	}
    360 }
    361 
    362 var newClient2Server = `220 hello world
    363 502 EH?
    364 250-mx.google.com at your service
    365 250-SIZE 35651584
    366 250-AUTH LOGIN PLAIN
    367 250 8BITMIME
    368 221 OK
    369 `
    370 
    371 var newClient2Client = `EHLO localhost
    372 HELO localhost
    373 QUIT
    374 `
    375 
    376 func TestNewClientWithTLS(t *testing.T) {
    377 	cert, err := tls.X509KeyPair(localhostCert, localhostKey)
    378 	if err != nil {
    379 		t.Fatalf("loadcert: %v", err)
    380 	}
    381 
    382 	config := tls.Config{Certificates: []tls.Certificate{cert}}
    383 
    384 	ln, err := tls.Listen("tcp", "127.0.0.1:0", &config)
    385 	if err != nil {
    386 		ln, err = tls.Listen("tcp", "[::1]:0", &config)
    387 		if err != nil {
    388 			t.Fatalf("server: listen: %v", err)
    389 		}
    390 	}
    391 
    392 	go func() {
    393 		conn, err := ln.Accept()
    394 		if err != nil {
    395 			t.Errorf("server: accept: %v", err)
    396 			return
    397 		}
    398 		defer conn.Close()
    399 
    400 		_, err = conn.Write([]byte("220 SIGNS\r\n"))
    401 		if err != nil {
    402 			t.Errorf("server: write: %v", err)
    403 			return
    404 		}
    405 	}()
    406 
    407 	config.InsecureSkipVerify = true
    408 	conn, err := tls.Dial("tcp", ln.Addr().String(), &config)
    409 	if err != nil {
    410 		t.Fatalf("client: dial: %v", err)
    411 	}
    412 	defer conn.Close()
    413 
    414 	client, err := NewClient(conn, ln.Addr().String())
    415 	if err != nil {
    416 		t.Fatalf("smtp: newclient: %v", err)
    417 	}
    418 	if !client.tls {
    419 		t.Errorf("client.tls Got: %t Expected: %t", client.tls, true)
    420 	}
    421 }
    422 
    423 func TestHello(t *testing.T) {
    424 
    425 	if len(helloServer) != len(helloClient) {
    426 		t.Fatalf("Hello server and client size mismatch")
    427 	}
    428 
    429 	for i := 0; i < len(helloServer); i++ {
    430 		server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n")
    431 		client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n")
    432 		var cmdbuf bytes.Buffer
    433 		bcmdbuf := bufio.NewWriter(&cmdbuf)
    434 		var fake faker
    435 		fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
    436 		c, err := NewClient(fake, "fake.host")
    437 		if err != nil {
    438 			t.Fatalf("NewClient: %v", err)
    439 		}
    440 		defer c.Close()
    441 		c.localName = "customhost"
    442 		err = nil
    443 
    444 		switch i {
    445 		case 0:
    446 			err = c.Hello("hostinjection>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n")
    447 			if err == nil {
    448 				t.Errorf("Expected Hello to be rejected due to a message injection attempt")
    449 			}
    450 			err = c.Hello("customhost")
    451 		case 1:
    452 			err = c.StartTLS(nil)
    453 			if err.Error() == "502 Not implemented" {
    454 				err = nil
    455 			}
    456 		case 2:
    457 			err = c.Verify("test (a] example.com")
    458 		case 3:
    459 			c.tls = true
    460 			c.serverName = "smtp.google.com"
    461 			err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
    462 		case 4:
    463 			err = c.Mail("test (a] example.com")
    464 		case 5:
    465 			ok, _ := c.Extension("feature")
    466 			if ok {
    467 				t.Errorf("Expected FEATURE not to be supported")
    468 			}
    469 		case 6:
    470 			err = c.Reset()
    471 		case 7:
    472 			err = c.Quit()
    473 		case 8:
    474 			err = c.Verify("test (a] example.com")
    475 			if err != nil {
    476 				err = c.Hello("customhost")
    477 				if err != nil {
    478 					t.Errorf("Want error, got none")
    479 				}
    480 			}
    481 		case 9:
    482 			err = c.Noop()
    483 		default:
    484 			t.Fatalf("Unhandled command")
    485 		}
    486 
    487 		if err != nil {
    488 			t.Errorf("Command %d failed: %v", i, err)
    489 		}
    490 
    491 		bcmdbuf.Flush()
    492 		actualcmds := cmdbuf.String()
    493 		if client != actualcmds {
    494 			t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
    495 		}
    496 	}
    497 }
    498 
    499 var baseHelloServer = `220 hello world
    500 502 EH?
    501 250-mx.google.com at your service
    502 250 FEATURE
    503 `
    504 
    505 var helloServer = []string{
    506 	"",
    507 	"502 Not implemented\n",
    508 	"250 User is valid\n",
    509 	"235 Accepted\n",
    510 	"250 Sender ok\n",
    511 	"",
    512 	"250 Reset ok\n",
    513 	"221 Goodbye\n",
    514 	"250 Sender ok\n",
    515 	"250 ok\n",
    516 }
    517 
    518 var baseHelloClient = `EHLO customhost
    519 HELO customhost
    520 `
    521 
    522 var helloClient = []string{
    523 	"",
    524 	"STARTTLS\n",
    525 	"VRFY test (a] example.com\n",
    526 	"AUTH PLAIN AHVzZXIAcGFzcw==\n",
    527 	"MAIL FROM:<test (a] example.com>\n",
    528 	"",
    529 	"RSET\n",
    530 	"QUIT\n",
    531 	"VRFY test (a] example.com\n",
    532 	"NOOP\n",
    533 }
    534 
    535 func TestSendMail(t *testing.T) {
    536 	server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n")
    537 	client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n")
    538 	var cmdbuf bytes.Buffer
    539 	bcmdbuf := bufio.NewWriter(&cmdbuf)
    540 	l, err := net.Listen("tcp", "127.0.0.1:0")
    541 	if err != nil {
    542 		t.Fatalf("Unable to to create listener: %v", err)
    543 	}
    544 	defer l.Close()
    545 
    546 	// prevent data race on bcmdbuf
    547 	var done = make(chan struct{})
    548 	go func(data []string) {
    549 
    550 		defer close(done)
    551 
    552 		conn, err := l.Accept()
    553 		if err != nil {
    554 			t.Errorf("Accept error: %v", err)
    555 			return
    556 		}
    557 		defer conn.Close()
    558 
    559 		tc := textproto.NewConn(conn)
    560 		for i := 0; i < len(data) && data[i] != ""; i++ {
    561 			tc.PrintfLine(data[i])
    562 			for len(data[i]) >= 4 && data[i][3] == '-' {
    563 				i++
    564 				tc.PrintfLine(data[i])
    565 			}
    566 			if data[i] == "221 Goodbye" {
    567 				return
    568 			}
    569 			read := false
    570 			for !read || data[i] == "354 Go ahead" {
    571 				msg, err := tc.ReadLine()
    572 				bcmdbuf.Write([]byte(msg + "\r\n"))
    573 				read = true
    574 				if err != nil {
    575 					t.Errorf("Read error: %v", err)
    576 					return
    577 				}
    578 				if data[i] == "354 Go ahead" && msg == "." {
    579 					break
    580 				}
    581 			}
    582 		}
    583 	}(strings.Split(server, "\r\n"))
    584 
    585 	err = SendMail(l.Addr().String(), nil, "test (a] example.com", []string{"other (a] example.com>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n"}, []byte(strings.Replace(`From: test (a] example.com
    586 To: other (a] example.com
    587 Subject: SendMail test
    588 
    589 SendMail is working for me.
    590 `, "\n", "\r\n", -1)))
    591 	if err == nil {
    592 		t.Errorf("Expected SendMail to be rejected due to a message injection attempt")
    593 	}
    594 
    595 	err = SendMail(l.Addr().String(), nil, "test (a] example.com", []string{"other (a] example.com"}, []byte(strings.Replace(`From: test (a] example.com
    596 To: other (a] example.com
    597 Subject: SendMail test
    598 
    599 SendMail is working for me.
    600 `, "\n", "\r\n", -1)))
    601 
    602 	if err != nil {
    603 		t.Errorf("%v", err)
    604 	}
    605 
    606 	<-done
    607 	bcmdbuf.Flush()
    608 	actualcmds := cmdbuf.String()
    609 	if client != actualcmds {
    610 		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
    611 	}
    612 }
    613 
    614 var sendMailServer = `220 hello world
    615 502 EH?
    616 250 mx.google.com at your service
    617 250 Sender ok
    618 250 Receiver ok
    619 354 Go ahead
    620 250 Data ok
    621 221 Goodbye
    622 `
    623 
    624 var sendMailClient = `EHLO localhost
    625 HELO localhost
    626 MAIL FROM:<test (a] example.com>
    627 RCPT TO:<other (a] example.com>
    628 DATA
    629 From: test (a] example.com
    630 To: other (a] example.com
    631 Subject: SendMail test
    632 
    633 SendMail is working for me.
    634 .
    635 QUIT
    636 `
    637 
    638 func TestAuthFailed(t *testing.T) {
    639 	server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n")
    640 	client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n")
    641 	var cmdbuf bytes.Buffer
    642 	bcmdbuf := bufio.NewWriter(&cmdbuf)
    643 	var fake faker
    644 	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
    645 	c, err := NewClient(fake, "fake.host")
    646 	if err != nil {
    647 		t.Fatalf("NewClient: %v", err)
    648 	}
    649 	defer c.Close()
    650 
    651 	c.tls = true
    652 	c.serverName = "smtp.google.com"
    653 	err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
    654 
    655 	if err == nil {
    656 		t.Error("Auth: expected error; got none")
    657 	} else if err.Error() != "535 Invalid credentials\nplease see www.example.com" {
    658 		t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com")
    659 	}
    660 
    661 	bcmdbuf.Flush()
    662 	actualcmds := cmdbuf.String()
    663 	if client != actualcmds {
    664 		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
    665 	}
    666 }
    667 
    668 var authFailedServer = `220 hello world
    669 250-mx.google.com at your service
    670 250 AUTH LOGIN PLAIN
    671 535-Invalid credentials
    672 535 please see www.example.com
    673 221 Goodbye
    674 `
    675 
    676 var authFailedClient = `EHLO localhost
    677 AUTH PLAIN AHVzZXIAcGFzcw==
    678 *
    679 QUIT
    680 `
    681 
    682 func TestTLSClient(t *testing.T) {
    683 	if runtime.GOOS == "freebsd" && runtime.GOARCH == "amd64" {
    684 		testenv.SkipFlaky(t, 19229)
    685 	}
    686 	ln := newLocalListener(t)
    687 	defer ln.Close()
    688 	errc := make(chan error)
    689 	go func() {
    690 		errc <- sendMail(ln.Addr().String())
    691 	}()
    692 	conn, err := ln.Accept()
    693 	if err != nil {
    694 		t.Fatalf("failed to accept connection: %v", err)
    695 	}
    696 	defer conn.Close()
    697 	if err := serverHandle(conn, t); err != nil {
    698 		t.Fatalf("failed to handle connection: %v", err)
    699 	}
    700 	if err := <-errc; err != nil {
    701 		t.Fatalf("client error: %v", err)
    702 	}
    703 }
    704 
    705 func TestTLSConnState(t *testing.T) {
    706 	ln := newLocalListener(t)
    707 	defer ln.Close()
    708 	clientDone := make(chan bool)
    709 	serverDone := make(chan bool)
    710 	go func() {
    711 		defer close(serverDone)
    712 		c, err := ln.Accept()
    713 		if err != nil {
    714 			t.Errorf("Server accept: %v", err)
    715 			return
    716 		}
    717 		defer c.Close()
    718 		if err := serverHandle(c, t); err != nil {
    719 			t.Errorf("server error: %v", err)
    720 		}
    721 	}()
    722 	go func() {
    723 		defer close(clientDone)
    724 		c, err := Dial(ln.Addr().String())
    725 		if err != nil {
    726 			t.Errorf("Client dial: %v", err)
    727 			return
    728 		}
    729 		defer c.Quit()
    730 		cfg := &tls.Config{ServerName: "example.com"}
    731 		testHookStartTLS(cfg) // set the RootCAs
    732 		if err := c.StartTLS(cfg); err != nil {
    733 			t.Errorf("StartTLS: %v", err)
    734 			return
    735 		}
    736 		cs, ok := c.TLSConnectionState()
    737 		if !ok {
    738 			t.Errorf("TLSConnectionState returned ok == false; want true")
    739 			return
    740 		}
    741 		if cs.Version == 0 || !cs.HandshakeComplete {
    742 			t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs)
    743 		}
    744 	}()
    745 	<-clientDone
    746 	<-serverDone
    747 }
    748 
    749 func newLocalListener(t *testing.T) net.Listener {
    750 	ln, err := net.Listen("tcp", "127.0.0.1:0")
    751 	if err != nil {
    752 		ln, err = net.Listen("tcp6", "[::1]:0")
    753 	}
    754 	if err != nil {
    755 		t.Fatal(err)
    756 	}
    757 	return ln
    758 }
    759 
    760 type smtpSender struct {
    761 	w io.Writer
    762 }
    763 
    764 func (s smtpSender) send(f string) {
    765 	s.w.Write([]byte(f + "\r\n"))
    766 }
    767 
    768 // smtp server, finely tailored to deal with our own client only!
    769 func serverHandle(c net.Conn, t *testing.T) error {
    770 	send := smtpSender{c}.send
    771 	send("220 127.0.0.1 ESMTP service ready")
    772 	s := bufio.NewScanner(c)
    773 	for s.Scan() {
    774 		switch s.Text() {
    775 		case "EHLO localhost":
    776 			send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
    777 			send("250-STARTTLS")
    778 			send("250 Ok")
    779 		case "STARTTLS":
    780 			send("220 Go ahead")
    781 			keypair, err := tls.X509KeyPair(localhostCert, localhostKey)
    782 			if err != nil {
    783 				return err
    784 			}
    785 			config := &tls.Config{Certificates: []tls.Certificate{keypair}}
    786 			c = tls.Server(c, config)
    787 			defer c.Close()
    788 			return serverHandleTLS(c, t)
    789 		default:
    790 			t.Fatalf("unrecognized command: %q", s.Text())
    791 		}
    792 	}
    793 	return s.Err()
    794 }
    795 
    796 func serverHandleTLS(c net.Conn, t *testing.T) error {
    797 	send := smtpSender{c}.send
    798 	s := bufio.NewScanner(c)
    799 	for s.Scan() {
    800 		switch s.Text() {
    801 		case "EHLO localhost":
    802 			send("250 Ok")
    803 		case "MAIL FROM:<joe1 (a] example.com>":
    804 			send("250 Ok")
    805 		case "RCPT TO:<joe2 (a] example.com>":
    806 			send("250 Ok")
    807 		case "DATA":
    808 			send("354 send the mail data, end with .")
    809 			send("250 Ok")
    810 		case "Subject: test":
    811 		case "":
    812 		case "howdy!":
    813 		case ".":
    814 		case "QUIT":
    815 			send("221 127.0.0.1 Service closing transmission channel")
    816 			return nil
    817 		default:
    818 			t.Fatalf("unrecognized command during TLS: %q", s.Text())
    819 		}
    820 	}
    821 	return s.Err()
    822 }
    823 
    824 func init() {
    825 	testRootCAs := x509.NewCertPool()
    826 	testRootCAs.AppendCertsFromPEM(localhostCert)
    827 	testHookStartTLS = func(config *tls.Config) {
    828 		config.RootCAs = testRootCAs
    829 	}
    830 }
    831 
    832 func sendMail(hostPort string) error {
    833 	host, _, err := net.SplitHostPort(hostPort)
    834 	if err != nil {
    835 		return err
    836 	}
    837 	auth := PlainAuth("", "", "", host)
    838 	from := "joe1 (a] example.com"
    839 	to := []string{"joe2 (a] example.com"}
    840 	return SendMail(hostPort, auth, from, to, []byte("Subject: test\n\nhowdy!"))
    841 }
    842 
    843 // (copied from net/http/httptest)
    844 // localhostCert is a PEM-encoded TLS cert with SAN IPs
    845 // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
    846 // of ASN.1 time).
    847 // generated from src/crypto/tls:
    848 // 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
    849 var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
    850 MIIBjjCCATigAwIBAgIQMon9v0s3pDFXvAMnPgelpzANBgkqhkiG9w0BAQsFADAS
    851 MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
    852 MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
    853 AM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnlqaUfVKVmz7FF
    854 wLP9csX6vGtvkZg1uWAtvfkCAwEAAaNoMGYwDgYDVR0PAQH/BAQDAgKkMBMGA1Ud
    855 JQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhh
    856 bXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQAD
    857 QQBOZsFVC7IwX+qibmSbt2IPHkUgXhfbq0a9MYhD6tHcj4gbDcTXh4kZCbgHCz22
    858 gfSj2/G2wxzopoISVDucuncj
    859 -----END CERTIFICATE-----`)
    860 
    861 // localhostKey is the private key for localhostCert.
    862 var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
    863 MIIBOwIBAAJBAM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnl
    864 qaUfVKVmz7FFwLP9csX6vGtvkZg1uWAtvfkCAwEAAQJART2qkxODLUbQ2siSx7m2
    865 rmBLyR/7X+nLe8aPDrMOxj3heDNl4YlaAYLexbcY8d7VDfCRBKYoAOP0UCP1Vhuf
    866 UQIhAO6PEI55K3SpNIdc2k5f0xz+9rodJCYzu51EwWX7r8ufAiEA3C9EkLiU2NuK
    867 3L3DHCN5IlUSN1Nr/lw8NIt50Yorj2cCIQCDw1VbvCV6bDLtSSXzAA51B4ZzScE7
    868 sHtB5EYF9Dwm9QIhAJuCquuH4mDzVjUntXjXOQPdj7sRqVGCNWdrJwOukat7AiAy
    869 LXLEwb77DIPoI5ZuaXQC+MnyyJj1ExC9RFcGz+bexA==
    870 -----END RSA PRIVATE KEY-----`)
    871