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 	"io"
     13 	"net"
     14 	"net/textproto"
     15 	"strings"
     16 	"testing"
     17 	"time"
     18 )
     19 
     20 type authTest struct {
     21 	auth       Auth
     22 	challenges []string
     23 	name       string
     24 	responses  []string
     25 }
     26 
     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"}},
     31 }
     32 
     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)
     39 		}
     40 		if !bytes.Equal(resp, []byte(test.responses[0])) {
     41 			t.Errorf("#%d got response %s, expected %s", i, resp, test.responses[0])
     42 		}
     43 		if err != nil {
     44 			t.Errorf("#%d error: %s", i, err)
     45 		}
     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
     53 			}
     54 			if !bytes.Equal(resp, expected) {
     55 				t.Errorf("#%d got %s, expected %s", i, resp, expected)
     56 				continue testLoop
     57 			}
     58 		}
     59 	}
     60 }
     61 
     62 func TestAuthPlain(t *testing.T) {
     63 	auth := PlainAuth("foo", "bar", "baz", "servername")
     64 
     65 	tests := []struct {
     66 		server *ServerInfo
     67 		err    string
     68 	}{
     69 		{
     70 			server: &ServerInfo{Name: "servername", TLS: true},
     71 		},
     72 		{
     73 			// Okay; explicitly advertised by server.
     74 			server: &ServerInfo{Name: "servername", Auth: []string{"PLAIN"}},
     75 		},
     76 		{
     77 			server: &ServerInfo{Name: "servername", Auth: []string{"CRAM-MD5"}},
     78 			err:    "unencrypted connection",
     79 		},
     80 		{
     81 			server: &ServerInfo{Name: "attacker", TLS: true},
     82 			err:    "wrong host name",
     83 		},
     84 	}
     85 	for i, tt := range tests {
     86 		_, _, err := auth.Start(tt.server)
     87 		got := ""
     88 		if err != nil {
     89 			got = err.Error()
     90 		}
     91 		if got != tt.err {
     92 			t.Errorf("%d. got error = %q; want %q", i, got, tt.err)
     93 		}
     94 	}
     95 }
     96 
     97 type faker struct {
     98 	io.ReadWriter
     99 }
    100 
    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 }
    107 
    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")
    111 
    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"}
    117 
    118 	if err := c.helo(); err != nil {
    119 		t.Fatalf("HELO failed: %s", err)
    120 	}
    121 	if err := c.ehlo(); err == nil {
    122 		t.Fatalf("Expected first EHLO to fail")
    123 	}
    124 	if err := c.ehlo(); err != nil {
    125 		t.Fatalf("Second EHLO failed: %s", err)
    126 	}
    127 
    128 	c.didHello = true
    129 	if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
    130 		t.Fatalf("Expected AUTH supported")
    131 	}
    132 	if ok, _ := c.Extension("DSN"); ok {
    133 		t.Fatalf("Shouldn't support DSN")
    134 	}
    135 
    136 	if err := c.Mail("user (a] gmail.com"); err == nil {
    137 		t.Fatalf("MAIL should require authentication")
    138 	}
    139 
    140 	if err := c.Verify("user1 (a] gmail.com"); err == nil {
    141 		t.Fatalf("First VRFY: expected no verification")
    142 	}
    143 	if err := c.Verify("user2 (a] gmail.com"); err != nil {
    144 		t.Fatalf("Second VRFY: expected verification, got %s", err)
    145 	}
    146 
    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)
    152 	}
    153 
    154 	if err := c.Mail("user (a] gmail.com"); err != nil {
    155 		t.Fatalf("MAIL failed: %s", err)
    156 	}
    157 	if err := c.Rcpt("golang-nuts (a] googlegroups.com"); err != nil {
    158 		t.Fatalf("RCPT failed: %s", err)
    159 	}
    160 	msg := `From: user (a] gmail.com
    161 To: golang-nuts (a] googlegroups.com
    162 Subject: Hooray for Go
    163 
    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)
    170 	}
    171 	if _, err := w.Write([]byte(msg)); err != nil {
    172 		t.Fatalf("Data write failed: %s", err)
    173 	}
    174 	if err := w.Close(); err != nil {
    175 		t.Fatalf("Bad data response: %s", err)
    176 	}
    177 
    178 	if err := c.Quit(); err != nil {
    179 		t.Fatalf("QUIT failed: %s", err)
    180 	}
    181 
    182 	bcmdbuf.Flush()
    183 	actualcmds := cmdbuf.String()
    184 	if client != actualcmds {
    185 		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
    186 	}
    187 }
    188 
    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
    204 `
    205 
    206 var basicClient = `HELO localhost
    207 EHLO localhost
    208 EHLO localhost
    209 MAIL FROM:<user (a] gmail.com> BODY=8BITMIME
    210 VRFY user1 (a] gmail.com
    211 VRFY user2 (a] gmail.com
    212 AUTH PLAIN AHVzZXIAcGFzcw==
    213 MAIL FROM:<user (a] gmail.com> BODY=8BITMIME
    214 RCPT TO:<golang-nuts (a] googlegroups.com>
    215 DATA
    216 From: user (a] gmail.com
    217 To: golang-nuts (a] googlegroups.com
    218 Subject: Hooray for Go
    219 
    220 Line 1
    221 ..Leading dot line .
    222 Goodbye.
    223 .
    224 QUIT
    225 `
    226 
    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")
    230 
    231 	var cmdbuf bytes.Buffer
    232 	bcmdbuf := bufio.NewWriter(&cmdbuf)
    233 	out := func() string {
    234 		bcmdbuf.Flush()
    235 		return cmdbuf.String()
    236 	}
    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())
    242 	}
    243 	defer c.Close()
    244 	if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
    245 		t.Fatalf("Expected AUTH supported")
    246 	}
    247 	if ok, _ := c.Extension("DSN"); ok {
    248 		t.Fatalf("Shouldn't support DSN")
    249 	}
    250 	if err := c.Quit(); err != nil {
    251 		t.Fatalf("QUIT failed: %s", err)
    252 	}
    253 
    254 	actualcmds := out()
    255 	if client != actualcmds {
    256 		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
    257 	}
    258 }
    259 
    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
    266 `
    267 
    268 var newClientClient = `EHLO localhost
    269 QUIT
    270 `
    271 
    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")
    275 
    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)
    283 	}
    284 	defer c.Close()
    285 	if ok, _ := c.Extension("DSN"); ok {
    286 		t.Fatalf("Shouldn't support DSN")
    287 	}
    288 	if err := c.Quit(); err != nil {
    289 		t.Fatalf("QUIT failed: %s", err)
    290 	}
    291 
    292 	bcmdbuf.Flush()
    293 	actualcmds := cmdbuf.String()
    294 	if client != actualcmds {
    295 		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
    296 	}
    297 }
    298 
    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
    306 `
    307 
    308 var newClient2Client = `EHLO localhost
    309 HELO localhost
    310 QUIT
    311 `
    312 
    313 func TestHello(t *testing.T) {
    314 
    315 	if len(helloServer) != len(helloClient) {
    316 		t.Fatalf("Hello server and client size mismatch")
    317 	}
    318 
    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)
    329 		}
    330 		defer c.Close()
    331 		c.localName = "customhost"
    332 		err = nil
    333 
    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
    341 			}
    342 		case 2:
    343 			err = c.Verify("test (a] 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 (a] example.com")
    350 		case 5:
    351 			ok, _ := c.Extension("feature")
    352 			if ok {
    353 				t.Errorf("Expected FEATURE not to be supported")
    354 			}
    355 		case 6:
    356 			err = c.Reset()
    357 		case 7:
    358 			err = c.Quit()
    359 		case 8:
    360 			err = c.Verify("test (a] example.com")
    361 			if err != nil {
    362 				err = c.Hello("customhost")
    363 				if err != nil {
    364 					t.Errorf("Want error, got none")
    365 				}
    366 			}
    367 		default:
    368 			t.Fatalf("Unhandled command")
    369 		}
    370 
    371 		if err != nil {
    372 			t.Errorf("Command %d failed: %v", i, err)
    373 		}
    374 
    375 		bcmdbuf.Flush()
    376 		actualcmds := cmdbuf.String()
    377 		if client != actualcmds {
    378 			t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
    379 		}
    380 	}
    381 }
    382 
    383 var baseHelloServer = `220 hello world
    384 502 EH?
    385 250-mx.google.com at your service
    386 250 FEATURE
    387 `
    388 
    389 var helloServer = []string{
    390 	"",
    391 	"502 Not implemented\n",
    392 	"250 User is valid\n",
    393 	"235 Accepted\n",
    394 	"250 Sender ok\n",
    395 	"",
    396 	"250 Reset ok\n",
    397 	"221 Goodbye\n",
    398 	"250 Sender ok\n",
    399 }
    400 
    401 var baseHelloClient = `EHLO customhost
    402 HELO customhost
    403 `
    404 
    405 var helloClient = []string{
    406 	"",
    407 	"STARTTLS\n",
    408 	"VRFY test (a] example.com\n",
    409 	"AUTH PLAIN AHVzZXIAcGFzcw==\n",
    410 	"MAIL FROM:<test (a] example.com>\n",
    411 	"",
    412 	"RSET\n",
    413 	"QUIT\n",
    414 	"VRFY test (a] example.com\n",
    415 }
    416 
    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)
    425 	}
    426 	defer l.Close()
    427 
    428 	// prevent data race on bcmdbuf
    429 	var done = make(chan struct{})
    430 	go func(data []string) {
    431 
    432 		defer close(done)
    433 
    434 		conn, err := l.Accept()
    435 		if err != nil {
    436 			t.Errorf("Accept error: %v", err)
    437 			return
    438 		}
    439 		defer conn.Close()
    440 
    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] == '-' {
    445 				i++
    446 				tc.PrintfLine(data[i])
    447 			}
    448 			if data[i] == "221 Goodbye" {
    449 				return
    450 			}
    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
    459 				}
    460 				if data[i] == "354 Go ahead" && msg == "." {
    461 					break
    462 				}
    463 			}
    464 		}
    465 	}(strings.Split(server, "\r\n"))
    466 
    467 	err = SendMail(l.Addr().String(), nil, "test (a] example.com", []string{"other (a] example.com"}, []byte(strings.Replace(`From: test (a] example.com
    468 To: other (a] example.com
    469 Subject: SendMail test
    470 
    471 SendMail is working for me.
    472 `, "\n", "\r\n", -1)))
    473 
    474 	if err != nil {
    475 		t.Errorf("%v", err)
    476 	}
    477 
    478 	<-done
    479 	bcmdbuf.Flush()
    480 	actualcmds := cmdbuf.String()
    481 	if client != actualcmds {
    482 		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
    483 	}
    484 }
    485 
    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
    494 `
    495 
    496 var sendMailClient = `EHLO localhost
    497 HELO localhost
    498 MAIL FROM:<test (a] example.com>
    499 RCPT TO:<other (a] example.com>
    500 DATA
    501 From: test (a] example.com
    502 To: other (a] example.com
    503 Subject: SendMail test
    504 
    505 SendMail is working for me.
    506 .
    507 QUIT
    508 `
    509 
    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)
    520 	}
    521 	defer c.Close()
    522 
    523 	c.tls = true
    524 	c.serverName = "smtp.google.com"
    525 	err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
    526 
    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")
    531 	}
    532 
    533 	bcmdbuf.Flush()
    534 	actualcmds := cmdbuf.String()
    535 	if client != actualcmds {
    536 		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
    537 	}
    538 }
    539 
    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
    546 `
    547 
    548 var authFailedClient = `EHLO localhost
    549 AUTH PLAIN AHVzZXIAcGFzcw==
    550 *
    551 QUIT
    552 `
    553 
    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())
    560 	}()
    561 	conn, err := ln.Accept()
    562 	if err != nil {
    563 		t.Fatalf("failed to accept connection: %v", err)
    564 	}
    565 	defer conn.Close()
    566 	if err := serverHandle(conn, t); err != nil {
    567 		t.Fatalf("failed to handle connection: %v", err)
    568 	}
    569 	if err := <-errc; err != nil {
    570 		t.Fatalf("client error: %v", err)
    571 	}
    572 }
    573 
    574 func TestTLSConnState(t *testing.T) {
    575 	ln := newLocalListener(t)
    576 	defer ln.Close()
    577 	clientDone := make(chan bool)
    578 	serverDone := make(chan bool)
    579 	go func() {
    580 		defer close(serverDone)
    581 		c, err := ln.Accept()
    582 		if err != nil {
    583 			t.Errorf("Server accept: %v", err)
    584 			return
    585 		}
    586 		defer c.Close()
    587 		if err := serverHandle(c, t); err != nil {
    588 			t.Errorf("server error: %v", err)
    589 		}
    590 	}()
    591 	go func() {
    592 		defer close(clientDone)
    593 		c, err := Dial(ln.Addr().String())
    594 		if err != nil {
    595 			t.Errorf("Client dial: %v", err)
    596 			return
    597 		}
    598 		defer c.Quit()
    599 		cfg := &tls.Config{ServerName: "example.com"}
    600 		testHookStartTLS(cfg) // set the RootCAs
    601 		if err := c.StartTLS(cfg); err != nil {
    602 			t.Errorf("StartTLS: %v", err)
    603 			return
    604 		}
    605 		cs, ok := c.TLSConnectionState()
    606 		if !ok {
    607 			t.Errorf("TLSConnectionState returned ok == false; want true")
    608 			return
    609 		}
    610 		if cs.Version == 0 || !cs.HandshakeComplete {
    611 			t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs)
    612 		}
    613 	}()
    614 	<-clientDone
    615 	<-serverDone
    616 }
    617 
    618 func newLocalListener(t *testing.T) net.Listener {
    619 	ln, err := net.Listen("tcp", "127.0.0.1:0")
    620 	if err != nil {
    621 		ln, err = net.Listen("tcp6", "[::1]:0")
    622 	}
    623 	if err != nil {
    624 		t.Fatal(err)
    625 	}
    626 	return ln
    627 }
    628 
    629 type smtpSender struct {
    630 	w io.Writer
    631 }
    632 
    633 func (s smtpSender) send(f string) {
    634 	s.w.Write([]byte(f + "\r\n"))
    635 }
    636 
    637 // smtp server, finely tailored to deal with our own client only!
    638 func serverHandle(c net.Conn, t *testing.T) error {
    639 	send := smtpSender{c}.send
    640 	send("220 127.0.0.1 ESMTP service ready")
    641 	s := bufio.NewScanner(c)
    642 	for s.Scan() {
    643 		switch s.Text() {
    644 		case "EHLO localhost":
    645 			send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
    646 			send("250-STARTTLS")
    647 			send("250 Ok")
    648 		case "STARTTLS":
    649 			send("220 Go ahead")
    650 			keypair, err := tls.X509KeyPair(localhostCert, localhostKey)
    651 			if err != nil {
    652 				return err
    653 			}
    654 			config := &tls.Config{Certificates: []tls.Certificate{keypair}}
    655 			c = tls.Server(c, config)
    656 			defer c.Close()
    657 			return serverHandleTLS(c, t)
    658 		default:
    659 			t.Fatalf("unrecognized command: %q", s.Text())
    660 		}
    661 	}
    662 	return s.Err()
    663 }
    664 
    665 func serverHandleTLS(c net.Conn, t *testing.T) error {
    666 	send := smtpSender{c}.send
    667 	s := bufio.NewScanner(c)
    668 	for s.Scan() {
    669 		switch s.Text() {
    670 		case "EHLO localhost":
    671 			send("250 Ok")
    672 		case "MAIL FROM:<joe1 (a] example.com>":
    673 			send("250 Ok")
    674 		case "RCPT TO:<joe2 (a] example.com>":
    675 			send("250 Ok")
    676 		case "DATA":
    677 			send("354 send the mail data, end with .")
    678 			send("250 Ok")
    679 		case "Subject: test":
    680 		case "":
    681 		case "howdy!":
    682 		case ".":
    683 		case "QUIT":
    684 			send("221 127.0.0.1 Service closing transmission channel")
    685 			return nil
    686 		default:
    687 			t.Fatalf("unrecognized command during TLS: %q", s.Text())
    688 		}
    689 	}
    690 	return s.Err()
    691 }
    692 
    693 func init() {
    694 	testRootCAs := x509.NewCertPool()
    695 	testRootCAs.AppendCertsFromPEM(localhostCert)
    696 	testHookStartTLS = func(config *tls.Config) {
    697 		config.RootCAs = testRootCAs
    698 	}
    699 }
    700 
    701 func sendMail(hostPort string) error {
    702 	host, _, err := net.SplitHostPort(hostPort)
    703 	if err != nil {
    704 		return err
    705 	}
    706 	auth := PlainAuth("", "", "", host)
    707 	from := "joe1 (a] example.com"
    708 	to := []string{"joe2 (a] example.com"}
    709 	return SendMail(hostPort, auth, from, to, []byte("Subject: test\n\nhowdy!"))
    710 }
    711 
    712 // (copied from net/http/httptest)
    713 // localhostCert is a PEM-encoded TLS cert with SAN IPs
    714 // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
    715 // of ASN.1 time).
    716 // generated from src/crypto/tls:
    717 // 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
    718 var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
    719 MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
    720 bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj
    721 bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa
    722 IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA
    723 AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud
    724 EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA
    725 AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk
    726 Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA==
    727 -----END CERTIFICATE-----`)
    728 
    729 // localhostKey is the private key for localhostCert.
    730 var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
    731 MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0
    732 0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV
    733 NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d
    734 AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW
    735 MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD
    736 EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA
    737 1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE=
    738 -----END RSA PRIVATE KEY-----`)
    739