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