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