github.com/zebozhuang/go@v0.0.0-20200207033046-f8a98f6f5c5d/src/net/smtp/smtp_test.go (about)

     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@gmail.com"); err == nil {
   191  		t.Fatalf("MAIL should require authentication")
   192  	}
   193  
   194  	if err := c.Verify("user1@gmail.com"); err == nil {
   195  		t.Fatalf("First VRFY: expected no verification")
   196  	}
   197  	if err := c.Verify("user2@gmail.com"); err != nil {
   198  		t.Fatalf("Second VRFY: expected verification, got %s", err)
   199  	}
   200  
   201  	// fake TLS so authentication won't complain
   202  	c.tls = true
   203  	c.serverName = "smtp.google.com"
   204  	if err := c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")); err != nil {
   205  		t.Fatalf("AUTH failed: %s", err)
   206  	}
   207  
   208  	if err := c.Mail("user@gmail.com"); err != nil {
   209  		t.Fatalf("MAIL failed: %s", err)
   210  	}
   211  	if err := c.Rcpt("golang-nuts@googlegroups.com"); err != nil {
   212  		t.Fatalf("RCPT failed: %s", err)
   213  	}
   214  	msg := `From: user@gmail.com
   215  To: golang-nuts@googlegroups.com
   216  Subject: Hooray for Go
   217  
   218  Line 1
   219  .Leading dot line .
   220  Goodbye.`
   221  	w, err := c.Data()
   222  	if err != nil {
   223  		t.Fatalf("DATA failed: %s", err)
   224  	}
   225  	if _, err := w.Write([]byte(msg)); err != nil {
   226  		t.Fatalf("Data write failed: %s", err)
   227  	}
   228  	if err := w.Close(); err != nil {
   229  		t.Fatalf("Bad data response: %s", err)
   230  	}
   231  
   232  	if err := c.Quit(); err != nil {
   233  		t.Fatalf("QUIT failed: %s", err)
   234  	}
   235  
   236  	bcmdbuf.Flush()
   237  	actualcmds := cmdbuf.String()
   238  	if client != actualcmds {
   239  		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   240  	}
   241  }
   242  
   243  var basicServer = `250 mx.google.com at your service
   244  502 Unrecognized command.
   245  250-mx.google.com at your service
   246  250-SIZE 35651584
   247  250-AUTH LOGIN PLAIN
   248  250 8BITMIME
   249  530 Authentication required
   250  252 Send some mail, I'll try my best
   251  250 User is valid
   252  235 Accepted
   253  250 Sender OK
   254  250 Receiver OK
   255  354 Go ahead
   256  250 Data OK
   257  221 OK
   258  `
   259  
   260  var basicClient = `HELO localhost
   261  EHLO localhost
   262  EHLO localhost
   263  MAIL FROM:<user@gmail.com> BODY=8BITMIME
   264  VRFY user1@gmail.com
   265  VRFY user2@gmail.com
   266  AUTH PLAIN AHVzZXIAcGFzcw==
   267  MAIL FROM:<user@gmail.com> BODY=8BITMIME
   268  RCPT TO:<golang-nuts@googlegroups.com>
   269  DATA
   270  From: user@gmail.com
   271  To: golang-nuts@googlegroups.com
   272  Subject: Hooray for Go
   273  
   274  Line 1
   275  ..Leading dot line .
   276  Goodbye.
   277  .
   278  QUIT
   279  `
   280  
   281  func TestNewClient(t *testing.T) {
   282  	server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n")
   283  	client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n")
   284  
   285  	var cmdbuf bytes.Buffer
   286  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   287  	out := func() string {
   288  		bcmdbuf.Flush()
   289  		return cmdbuf.String()
   290  	}
   291  	var fake faker
   292  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   293  	c, err := NewClient(fake, "fake.host")
   294  	if err != nil {
   295  		t.Fatalf("NewClient: %v\n(after %v)", err, out())
   296  	}
   297  	defer c.Close()
   298  	if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
   299  		t.Fatalf("Expected AUTH supported")
   300  	}
   301  	if ok, _ := c.Extension("DSN"); ok {
   302  		t.Fatalf("Shouldn't support DSN")
   303  	}
   304  	if err := c.Quit(); err != nil {
   305  		t.Fatalf("QUIT failed: %s", err)
   306  	}
   307  
   308  	actualcmds := out()
   309  	if client != actualcmds {
   310  		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   311  	}
   312  }
   313  
   314  var newClientServer = `220 hello world
   315  250-mx.google.com at your service
   316  250-SIZE 35651584
   317  250-AUTH LOGIN PLAIN
   318  250 8BITMIME
   319  221 OK
   320  `
   321  
   322  var newClientClient = `EHLO localhost
   323  QUIT
   324  `
   325  
   326  func TestNewClient2(t *testing.T) {
   327  	server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n")
   328  	client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n")
   329  
   330  	var cmdbuf bytes.Buffer
   331  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   332  	var fake faker
   333  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   334  	c, err := NewClient(fake, "fake.host")
   335  	if err != nil {
   336  		t.Fatalf("NewClient: %v", err)
   337  	}
   338  	defer c.Close()
   339  	if ok, _ := c.Extension("DSN"); ok {
   340  		t.Fatalf("Shouldn't support DSN")
   341  	}
   342  	if err := c.Quit(); err != nil {
   343  		t.Fatalf("QUIT failed: %s", err)
   344  	}
   345  
   346  	bcmdbuf.Flush()
   347  	actualcmds := cmdbuf.String()
   348  	if client != actualcmds {
   349  		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   350  	}
   351  }
   352  
   353  var newClient2Server = `220 hello world
   354  502 EH?
   355  250-mx.google.com at your service
   356  250-SIZE 35651584
   357  250-AUTH LOGIN PLAIN
   358  250 8BITMIME
   359  221 OK
   360  `
   361  
   362  var newClient2Client = `EHLO localhost
   363  HELO localhost
   364  QUIT
   365  `
   366  
   367  func TestNewClientWithTLS(t *testing.T) {
   368  	cert, err := tls.X509KeyPair(localhostCert, localhostKey)
   369  	if err != nil {
   370  		t.Fatalf("loadcert: %v", err)
   371  	}
   372  
   373  	config := tls.Config{Certificates: []tls.Certificate{cert}}
   374  
   375  	ln, err := tls.Listen("tcp", "127.0.0.1:0", &config)
   376  	if err != nil {
   377  		ln, err = tls.Listen("tcp", "[::1]:0", &config)
   378  		if err != nil {
   379  			t.Fatalf("server: listen: %s", err)
   380  		}
   381  	}
   382  
   383  	go func() {
   384  		conn, err := ln.Accept()
   385  		if err != nil {
   386  			t.Fatalf("server: accept: %s", err)
   387  			return
   388  		}
   389  		defer conn.Close()
   390  
   391  		_, err = conn.Write([]byte("220 SIGNS\r\n"))
   392  		if err != nil {
   393  			t.Fatalf("server: write: %s", err)
   394  			return
   395  		}
   396  	}()
   397  
   398  	config.InsecureSkipVerify = true
   399  	conn, err := tls.Dial("tcp", ln.Addr().String(), &config)
   400  	if err != nil {
   401  		t.Fatalf("client: dial: %s", err)
   402  	}
   403  	defer conn.Close()
   404  
   405  	client, err := NewClient(conn, ln.Addr().String())
   406  	if err != nil {
   407  		t.Fatalf("smtp: newclient: %s", err)
   408  	}
   409  	if !client.tls {
   410  		t.Errorf("client.tls Got: %t Expected: %t", client.tls, true)
   411  	}
   412  }
   413  
   414  func TestHello(t *testing.T) {
   415  
   416  	if len(helloServer) != len(helloClient) {
   417  		t.Fatalf("Hello server and client size mismatch")
   418  	}
   419  
   420  	for i := 0; i < len(helloServer); i++ {
   421  		server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n")
   422  		client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n")
   423  		var cmdbuf bytes.Buffer
   424  		bcmdbuf := bufio.NewWriter(&cmdbuf)
   425  		var fake faker
   426  		fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   427  		c, err := NewClient(fake, "fake.host")
   428  		if err != nil {
   429  			t.Fatalf("NewClient: %v", err)
   430  		}
   431  		defer c.Close()
   432  		c.localName = "customhost"
   433  		err = nil
   434  
   435  		switch i {
   436  		case 0:
   437  			err = c.Hello("customhost")
   438  		case 1:
   439  			err = c.StartTLS(nil)
   440  			if err.Error() == "502 Not implemented" {
   441  				err = nil
   442  			}
   443  		case 2:
   444  			err = c.Verify("test@example.com")
   445  		case 3:
   446  			c.tls = true
   447  			c.serverName = "smtp.google.com"
   448  			err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
   449  		case 4:
   450  			err = c.Mail("test@example.com")
   451  		case 5:
   452  			ok, _ := c.Extension("feature")
   453  			if ok {
   454  				t.Errorf("Expected FEATURE not to be supported")
   455  			}
   456  		case 6:
   457  			err = c.Reset()
   458  		case 7:
   459  			err = c.Quit()
   460  		case 8:
   461  			err = c.Verify("test@example.com")
   462  			if err != nil {
   463  				err = c.Hello("customhost")
   464  				if err != nil {
   465  					t.Errorf("Want error, got none")
   466  				}
   467  			}
   468  		default:
   469  			t.Fatalf("Unhandled command")
   470  		}
   471  
   472  		if err != nil {
   473  			t.Errorf("Command %d failed: %v", i, err)
   474  		}
   475  
   476  		bcmdbuf.Flush()
   477  		actualcmds := cmdbuf.String()
   478  		if client != actualcmds {
   479  			t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   480  		}
   481  	}
   482  }
   483  
   484  var baseHelloServer = `220 hello world
   485  502 EH?
   486  250-mx.google.com at your service
   487  250 FEATURE
   488  `
   489  
   490  var helloServer = []string{
   491  	"",
   492  	"502 Not implemented\n",
   493  	"250 User is valid\n",
   494  	"235 Accepted\n",
   495  	"250 Sender ok\n",
   496  	"",
   497  	"250 Reset ok\n",
   498  	"221 Goodbye\n",
   499  	"250 Sender ok\n",
   500  }
   501  
   502  var baseHelloClient = `EHLO customhost
   503  HELO customhost
   504  `
   505  
   506  var helloClient = []string{
   507  	"",
   508  	"STARTTLS\n",
   509  	"VRFY test@example.com\n",
   510  	"AUTH PLAIN AHVzZXIAcGFzcw==\n",
   511  	"MAIL FROM:<test@example.com>\n",
   512  	"",
   513  	"RSET\n",
   514  	"QUIT\n",
   515  	"VRFY test@example.com\n",
   516  }
   517  
   518  func TestSendMail(t *testing.T) {
   519  	server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n")
   520  	client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n")
   521  	var cmdbuf bytes.Buffer
   522  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   523  	l, err := net.Listen("tcp", "127.0.0.1:0")
   524  	if err != nil {
   525  		t.Fatalf("Unable to to create listener: %v", err)
   526  	}
   527  	defer l.Close()
   528  
   529  	// prevent data race on bcmdbuf
   530  	var done = make(chan struct{})
   531  	go func(data []string) {
   532  
   533  		defer close(done)
   534  
   535  		conn, err := l.Accept()
   536  		if err != nil {
   537  			t.Errorf("Accept error: %v", err)
   538  			return
   539  		}
   540  		defer conn.Close()
   541  
   542  		tc := textproto.NewConn(conn)
   543  		for i := 0; i < len(data) && data[i] != ""; i++ {
   544  			tc.PrintfLine(data[i])
   545  			for len(data[i]) >= 4 && data[i][3] == '-' {
   546  				i++
   547  				tc.PrintfLine(data[i])
   548  			}
   549  			if data[i] == "221 Goodbye" {
   550  				return
   551  			}
   552  			read := false
   553  			for !read || data[i] == "354 Go ahead" {
   554  				msg, err := tc.ReadLine()
   555  				bcmdbuf.Write([]byte(msg + "\r\n"))
   556  				read = true
   557  				if err != nil {
   558  					t.Errorf("Read error: %v", err)
   559  					return
   560  				}
   561  				if data[i] == "354 Go ahead" && msg == "." {
   562  					break
   563  				}
   564  			}
   565  		}
   566  	}(strings.Split(server, "\r\n"))
   567  
   568  	err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com
   569  To: other@example.com
   570  Subject: SendMail test
   571  
   572  SendMail is working for me.
   573  `, "\n", "\r\n", -1)))
   574  
   575  	if err != nil {
   576  		t.Errorf("%v", err)
   577  	}
   578  
   579  	<-done
   580  	bcmdbuf.Flush()
   581  	actualcmds := cmdbuf.String()
   582  	if client != actualcmds {
   583  		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   584  	}
   585  }
   586  
   587  var sendMailServer = `220 hello world
   588  502 EH?
   589  250 mx.google.com at your service
   590  250 Sender ok
   591  250 Receiver ok
   592  354 Go ahead
   593  250 Data ok
   594  221 Goodbye
   595  `
   596  
   597  var sendMailClient = `EHLO localhost
   598  HELO localhost
   599  MAIL FROM:<test@example.com>
   600  RCPT TO:<other@example.com>
   601  DATA
   602  From: test@example.com
   603  To: other@example.com
   604  Subject: SendMail test
   605  
   606  SendMail is working for me.
   607  .
   608  QUIT
   609  `
   610  
   611  func TestAuthFailed(t *testing.T) {
   612  	server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n")
   613  	client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n")
   614  	var cmdbuf bytes.Buffer
   615  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   616  	var fake faker
   617  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   618  	c, err := NewClient(fake, "fake.host")
   619  	if err != nil {
   620  		t.Fatalf("NewClient: %v", err)
   621  	}
   622  	defer c.Close()
   623  
   624  	c.tls = true
   625  	c.serverName = "smtp.google.com"
   626  	err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
   627  
   628  	if err == nil {
   629  		t.Error("Auth: expected error; got none")
   630  	} else if err.Error() != "535 Invalid credentials\nplease see www.example.com" {
   631  		t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com")
   632  	}
   633  
   634  	bcmdbuf.Flush()
   635  	actualcmds := cmdbuf.String()
   636  	if client != actualcmds {
   637  		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   638  	}
   639  }
   640  
   641  var authFailedServer = `220 hello world
   642  250-mx.google.com at your service
   643  250 AUTH LOGIN PLAIN
   644  535-Invalid credentials
   645  535 please see www.example.com
   646  221 Goodbye
   647  `
   648  
   649  var authFailedClient = `EHLO localhost
   650  AUTH PLAIN AHVzZXIAcGFzcw==
   651  *
   652  QUIT
   653  `
   654  
   655  func TestTLSClient(t *testing.T) {
   656  	if runtime.GOOS == "freebsd" && runtime.GOARCH == "amd64" {
   657  		testenv.SkipFlaky(t, 19229)
   658  	}
   659  	ln := newLocalListener(t)
   660  	defer ln.Close()
   661  	errc := make(chan error)
   662  	go func() {
   663  		errc <- sendMail(ln.Addr().String())
   664  	}()
   665  	conn, err := ln.Accept()
   666  	if err != nil {
   667  		t.Fatalf("failed to accept connection: %v", err)
   668  	}
   669  	defer conn.Close()
   670  	if err := serverHandle(conn, t); err != nil {
   671  		t.Fatalf("failed to handle connection: %v", err)
   672  	}
   673  	if err := <-errc; err != nil {
   674  		t.Fatalf("client error: %v", err)
   675  	}
   676  }
   677  
   678  func TestTLSConnState(t *testing.T) {
   679  	ln := newLocalListener(t)
   680  	defer ln.Close()
   681  	clientDone := make(chan bool)
   682  	serverDone := make(chan bool)
   683  	go func() {
   684  		defer close(serverDone)
   685  		c, err := ln.Accept()
   686  		if err != nil {
   687  			t.Errorf("Server accept: %v", err)
   688  			return
   689  		}
   690  		defer c.Close()
   691  		if err := serverHandle(c, t); err != nil {
   692  			t.Errorf("server error: %v", err)
   693  		}
   694  	}()
   695  	go func() {
   696  		defer close(clientDone)
   697  		c, err := Dial(ln.Addr().String())
   698  		if err != nil {
   699  			t.Errorf("Client dial: %v", err)
   700  			return
   701  		}
   702  		defer c.Quit()
   703  		cfg := &tls.Config{ServerName: "example.com"}
   704  		testHookStartTLS(cfg) // set the RootCAs
   705  		if err := c.StartTLS(cfg); err != nil {
   706  			t.Errorf("StartTLS: %v", err)
   707  			return
   708  		}
   709  		cs, ok := c.TLSConnectionState()
   710  		if !ok {
   711  			t.Errorf("TLSConnectionState returned ok == false; want true")
   712  			return
   713  		}
   714  		if cs.Version == 0 || !cs.HandshakeComplete {
   715  			t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs)
   716  		}
   717  	}()
   718  	<-clientDone
   719  	<-serverDone
   720  }
   721  
   722  func newLocalListener(t *testing.T) net.Listener {
   723  	ln, err := net.Listen("tcp", "127.0.0.1:0")
   724  	if err != nil {
   725  		ln, err = net.Listen("tcp6", "[::1]:0")
   726  	}
   727  	if err != nil {
   728  		t.Fatal(err)
   729  	}
   730  	return ln
   731  }
   732  
   733  type smtpSender struct {
   734  	w io.Writer
   735  }
   736  
   737  func (s smtpSender) send(f string) {
   738  	s.w.Write([]byte(f + "\r\n"))
   739  }
   740  
   741  // smtp server, finely tailored to deal with our own client only!
   742  func serverHandle(c net.Conn, t *testing.T) error {
   743  	send := smtpSender{c}.send
   744  	send("220 127.0.0.1 ESMTP service ready")
   745  	s := bufio.NewScanner(c)
   746  	for s.Scan() {
   747  		switch s.Text() {
   748  		case "EHLO localhost":
   749  			send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
   750  			send("250-STARTTLS")
   751  			send("250 Ok")
   752  		case "STARTTLS":
   753  			send("220 Go ahead")
   754  			keypair, err := tls.X509KeyPair(localhostCert, localhostKey)
   755  			if err != nil {
   756  				return err
   757  			}
   758  			config := &tls.Config{Certificates: []tls.Certificate{keypair}}
   759  			c = tls.Server(c, config)
   760  			defer c.Close()
   761  			return serverHandleTLS(c, t)
   762  		default:
   763  			t.Fatalf("unrecognized command: %q", s.Text())
   764  		}
   765  	}
   766  	return s.Err()
   767  }
   768  
   769  func serverHandleTLS(c net.Conn, t *testing.T) error {
   770  	send := smtpSender{c}.send
   771  	s := bufio.NewScanner(c)
   772  	for s.Scan() {
   773  		switch s.Text() {
   774  		case "EHLO localhost":
   775  			send("250 Ok")
   776  		case "MAIL FROM:<joe1@example.com>":
   777  			send("250 Ok")
   778  		case "RCPT TO:<joe2@example.com>":
   779  			send("250 Ok")
   780  		case "DATA":
   781  			send("354 send the mail data, end with .")
   782  			send("250 Ok")
   783  		case "Subject: test":
   784  		case "":
   785  		case "howdy!":
   786  		case ".":
   787  		case "QUIT":
   788  			send("221 127.0.0.1 Service closing transmission channel")
   789  			return nil
   790  		default:
   791  			t.Fatalf("unrecognized command during TLS: %q", s.Text())
   792  		}
   793  	}
   794  	return s.Err()
   795  }
   796  
   797  func init() {
   798  	testRootCAs := x509.NewCertPool()
   799  	testRootCAs.AppendCertsFromPEM(localhostCert)
   800  	testHookStartTLS = func(config *tls.Config) {
   801  		config.RootCAs = testRootCAs
   802  	}
   803  }
   804  
   805  func sendMail(hostPort string) error {
   806  	host, _, err := net.SplitHostPort(hostPort)
   807  	if err != nil {
   808  		return err
   809  	}
   810  	auth := PlainAuth("", "", "", host)
   811  	from := "joe1@example.com"
   812  	to := []string{"joe2@example.com"}
   813  	return SendMail(hostPort, auth, from, to, []byte("Subject: test\n\nhowdy!"))
   814  }
   815  
   816  // (copied from net/http/httptest)
   817  // localhostCert is a PEM-encoded TLS cert with SAN IPs
   818  // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
   819  // of ASN.1 time).
   820  // generated from src/crypto/tls:
   821  // 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
   822  var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
   823  MIIBjjCCATigAwIBAgIQMon9v0s3pDFXvAMnPgelpzANBgkqhkiG9w0BAQsFADAS
   824  MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
   825  MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
   826  AM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnlqaUfVKVmz7FF
   827  wLP9csX6vGtvkZg1uWAtvfkCAwEAAaNoMGYwDgYDVR0PAQH/BAQDAgKkMBMGA1Ud
   828  JQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhh
   829  bXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQAD
   830  QQBOZsFVC7IwX+qibmSbt2IPHkUgXhfbq0a9MYhD6tHcj4gbDcTXh4kZCbgHCz22
   831  gfSj2/G2wxzopoISVDucuncj
   832  -----END CERTIFICATE-----`)
   833  
   834  // localhostKey is the private key for localhostCert.
   835  var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
   836  MIIBOwIBAAJBAM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnl
   837  qaUfVKVmz7FFwLP9csX6vGtvkZg1uWAtvfkCAwEAAQJART2qkxODLUbQ2siSx7m2
   838  rmBLyR/7X+nLe8aPDrMOxj3heDNl4YlaAYLexbcY8d7VDfCRBKYoAOP0UCP1Vhuf
   839  UQIhAO6PEI55K3SpNIdc2k5f0xz+9rodJCYzu51EwWX7r8ufAiEA3C9EkLiU2NuK
   840  3L3DHCN5IlUSN1Nr/lw8NIt50Yorj2cCIQCDw1VbvCV6bDLtSSXzAA51B4ZzScE7
   841  sHtB5EYF9Dwm9QIhAJuCquuH4mDzVjUntXjXOQPdj7sRqVGCNWdrJwOukat7AiAy
   842  LXLEwb77DIPoI5ZuaXQC+MnyyJj1ExC9RFcGz+bexA==
   843  -----END RSA PRIVATE KEY-----`)