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