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