github.com/guyezi/gofrontend@v0.0.0-20200228202240-7a62a49e62c0/libgo/go/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  	"fmt"
    13  	"internal/testenv"
    14  	"io"
    15  	"net"
    16  	"net/textproto"
    17  	"runtime"
    18  	"strings"
    19  	"testing"
    20  	"time"
    21  )
    22  
    23  type authTest struct {
    24  	auth       Auth
    25  	challenges []string
    26  	name       string
    27  	responses  []string
    28  }
    29  
    30  var authTests = []authTest{
    31  	{PlainAuth("", "user", "pass", "testserver"), []string{}, "PLAIN", []string{"\x00user\x00pass"}},
    32  	{PlainAuth("foo", "bar", "baz", "testserver"), []string{}, "PLAIN", []string{"foo\x00bar\x00baz"}},
    33  	{CRAMMD5Auth("user", "pass"), []string{"<123456.1322876914@testserver>"}, "CRAM-MD5", []string{"", "user 287eb355114cf5c471c26a875f1ca4ae"}},
    34  }
    35  
    36  func TestAuth(t *testing.T) {
    37  testLoop:
    38  	for i, test := range authTests {
    39  		name, resp, err := test.auth.Start(&ServerInfo{"testserver", true, nil})
    40  		if name != test.name {
    41  			t.Errorf("#%d got name %s, expected %s", i, name, test.name)
    42  		}
    43  		if !bytes.Equal(resp, []byte(test.responses[0])) {
    44  			t.Errorf("#%d got response %s, expected %s", i, resp, test.responses[0])
    45  		}
    46  		if err != nil {
    47  			t.Errorf("#%d error: %s", i, err)
    48  		}
    49  		for j := range test.challenges {
    50  			challenge := []byte(test.challenges[j])
    51  			expected := []byte(test.responses[j+1])
    52  			resp, err := test.auth.Next(challenge, true)
    53  			if err != nil {
    54  				t.Errorf("#%d error: %s", i, err)
    55  				continue testLoop
    56  			}
    57  			if !bytes.Equal(resp, expected) {
    58  				t.Errorf("#%d got %s, expected %s", i, resp, expected)
    59  				continue testLoop
    60  			}
    61  		}
    62  	}
    63  }
    64  
    65  func TestAuthPlain(t *testing.T) {
    66  
    67  	tests := []struct {
    68  		authName string
    69  		server   *ServerInfo
    70  		err      string
    71  	}{
    72  		{
    73  			authName: "servername",
    74  			server:   &ServerInfo{Name: "servername", TLS: true},
    75  		},
    76  		{
    77  			// OK to use PlainAuth on localhost without TLS
    78  			authName: "localhost",
    79  			server:   &ServerInfo{Name: "localhost", TLS: false},
    80  		},
    81  		{
    82  			// NOT OK on non-localhost, even if server says PLAIN is OK.
    83  			// (We don't know that the server is the real server.)
    84  			authName: "servername",
    85  			server:   &ServerInfo{Name: "servername", Auth: []string{"PLAIN"}},
    86  			err:      "unencrypted connection",
    87  		},
    88  		{
    89  			authName: "servername",
    90  			server:   &ServerInfo{Name: "servername", Auth: []string{"CRAM-MD5"}},
    91  			err:      "unencrypted connection",
    92  		},
    93  		{
    94  			authName: "servername",
    95  			server:   &ServerInfo{Name: "attacker", TLS: true},
    96  			err:      "wrong host name",
    97  		},
    98  	}
    99  	for i, tt := range tests {
   100  		auth := PlainAuth("foo", "bar", "baz", tt.authName)
   101  		_, _, err := auth.Start(tt.server)
   102  		got := ""
   103  		if err != nil {
   104  			got = err.Error()
   105  		}
   106  		if got != tt.err {
   107  			t.Errorf("%d. got error = %q; want %q", i, got, tt.err)
   108  		}
   109  	}
   110  }
   111  
   112  // Issue 17794: don't send a trailing space on AUTH command when there's no password.
   113  func TestClientAuthTrimSpace(t *testing.T) {
   114  	server := "220 hello world\r\n" +
   115  		"200 some more"
   116  	var wrote bytes.Buffer
   117  	var fake faker
   118  	fake.ReadWriter = struct {
   119  		io.Reader
   120  		io.Writer
   121  	}{
   122  		strings.NewReader(server),
   123  		&wrote,
   124  	}
   125  	c, err := NewClient(fake, "fake.host")
   126  	if err != nil {
   127  		t.Fatalf("NewClient: %v", err)
   128  	}
   129  	c.tls = true
   130  	c.didHello = true
   131  	c.Auth(toServerEmptyAuth{})
   132  	c.Close()
   133  	if got, want := wrote.String(), "AUTH FOOAUTH\r\n*\r\nQUIT\r\n"; got != want {
   134  		t.Errorf("wrote %q; want %q", got, want)
   135  	}
   136  }
   137  
   138  // toServerEmptyAuth is an implementation of Auth that only implements
   139  // the Start method, and returns "FOOAUTH", nil, nil. Notably, it returns
   140  // zero bytes for "toServer" so we can test that we don't send spaces at
   141  // the end of the line. See TestClientAuthTrimSpace.
   142  type toServerEmptyAuth struct{}
   143  
   144  func (toServerEmptyAuth) Start(server *ServerInfo) (proto string, toServer []byte, err error) {
   145  	return "FOOAUTH", nil, nil
   146  }
   147  
   148  func (toServerEmptyAuth) Next(fromServer []byte, more bool) (toServer []byte, err error) {
   149  	panic("unexpected call")
   150  }
   151  
   152  type faker struct {
   153  	io.ReadWriter
   154  }
   155  
   156  func (f faker) Close() error                     { return nil }
   157  func (f faker) LocalAddr() net.Addr              { return nil }
   158  func (f faker) RemoteAddr() net.Addr             { return nil }
   159  func (f faker) SetDeadline(time.Time) error      { return nil }
   160  func (f faker) SetReadDeadline(time.Time) error  { return nil }
   161  func (f faker) SetWriteDeadline(time.Time) error { return nil }
   162  
   163  func TestBasic(t *testing.T) {
   164  	server := strings.Join(strings.Split(basicServer, "\n"), "\r\n")
   165  	client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
   166  
   167  	var cmdbuf bytes.Buffer
   168  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   169  	var fake faker
   170  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   171  	c := &Client{Text: textproto.NewConn(fake), localName: "localhost"}
   172  
   173  	if err := c.helo(); err != nil {
   174  		t.Fatalf("HELO failed: %s", err)
   175  	}
   176  	if err := c.ehlo(); err == nil {
   177  		t.Fatalf("Expected first EHLO to fail")
   178  	}
   179  	if err := c.ehlo(); err != nil {
   180  		t.Fatalf("Second EHLO failed: %s", err)
   181  	}
   182  
   183  	c.didHello = true
   184  	if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
   185  		t.Fatalf("Expected AUTH supported")
   186  	}
   187  	if ok, _ := c.Extension("DSN"); ok {
   188  		t.Fatalf("Shouldn't support DSN")
   189  	}
   190  
   191  	if err := c.Mail("user@gmail.com"); err == nil {
   192  		t.Fatalf("MAIL should require authentication")
   193  	}
   194  
   195  	if err := c.Verify("user1@gmail.com"); err == nil {
   196  		t.Fatalf("First VRFY: expected no verification")
   197  	}
   198  	if err := c.Verify("user2@gmail.com>\r\nDATA\r\nAnother injected message body\r\n.\r\nQUIT\r\n"); err == nil {
   199  		t.Fatalf("VRFY should have failed due to a message injection attempt")
   200  	}
   201  	if err := c.Verify("user2@gmail.com"); err != nil {
   202  		t.Fatalf("Second VRFY: expected verification, got %s", err)
   203  	}
   204  
   205  	// fake TLS so authentication won't complain
   206  	c.tls = true
   207  	c.serverName = "smtp.google.com"
   208  	if err := c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")); err != nil {
   209  		t.Fatalf("AUTH failed: %s", err)
   210  	}
   211  
   212  	if err := c.Rcpt("golang-nuts@googlegroups.com>\r\nDATA\r\nInjected message body\r\n.\r\nQUIT\r\n"); err == nil {
   213  		t.Fatalf("RCPT should have failed due to a message injection attempt")
   214  	}
   215  	if err := c.Mail("user@gmail.com>\r\nDATA\r\nAnother injected message body\r\n.\r\nQUIT\r\n"); err == nil {
   216  		t.Fatalf("MAIL should have failed due to a message injection attempt")
   217  	}
   218  	if err := c.Mail("user@gmail.com"); err != nil {
   219  		t.Fatalf("MAIL failed: %s", err)
   220  	}
   221  	if err := c.Rcpt("golang-nuts@googlegroups.com"); err != nil {
   222  		t.Fatalf("RCPT failed: %s", err)
   223  	}
   224  	msg := `From: user@gmail.com
   225  To: golang-nuts@googlegroups.com
   226  Subject: Hooray for Go
   227  
   228  Line 1
   229  .Leading dot line .
   230  Goodbye.`
   231  	w, err := c.Data()
   232  	if err != nil {
   233  		t.Fatalf("DATA failed: %s", err)
   234  	}
   235  	if _, err := w.Write([]byte(msg)); err != nil {
   236  		t.Fatalf("Data write failed: %s", err)
   237  	}
   238  	if err := w.Close(); err != nil {
   239  		t.Fatalf("Bad data response: %s", err)
   240  	}
   241  
   242  	if err := c.Quit(); err != nil {
   243  		t.Fatalf("QUIT failed: %s", err)
   244  	}
   245  
   246  	bcmdbuf.Flush()
   247  	actualcmds := cmdbuf.String()
   248  	if client != actualcmds {
   249  		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   250  	}
   251  }
   252  
   253  var basicServer = `250 mx.google.com at your service
   254  502 Unrecognized command.
   255  250-mx.google.com at your service
   256  250-SIZE 35651584
   257  250-AUTH LOGIN PLAIN
   258  250 8BITMIME
   259  530 Authentication required
   260  252 Send some mail, I'll try my best
   261  250 User is valid
   262  235 Accepted
   263  250 Sender OK
   264  250 Receiver OK
   265  354 Go ahead
   266  250 Data OK
   267  221 OK
   268  `
   269  
   270  var basicClient = `HELO localhost
   271  EHLO localhost
   272  EHLO localhost
   273  MAIL FROM:<user@gmail.com> BODY=8BITMIME
   274  VRFY user1@gmail.com
   275  VRFY user2@gmail.com
   276  AUTH PLAIN AHVzZXIAcGFzcw==
   277  MAIL FROM:<user@gmail.com> BODY=8BITMIME
   278  RCPT TO:<golang-nuts@googlegroups.com>
   279  DATA
   280  From: user@gmail.com
   281  To: golang-nuts@googlegroups.com
   282  Subject: Hooray for Go
   283  
   284  Line 1
   285  ..Leading dot line .
   286  Goodbye.
   287  .
   288  QUIT
   289  `
   290  
   291  func TestNewClient(t *testing.T) {
   292  	server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n")
   293  	client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n")
   294  
   295  	var cmdbuf bytes.Buffer
   296  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   297  	out := func() string {
   298  		bcmdbuf.Flush()
   299  		return cmdbuf.String()
   300  	}
   301  	var fake faker
   302  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   303  	c, err := NewClient(fake, "fake.host")
   304  	if err != nil {
   305  		t.Fatalf("NewClient: %v\n(after %v)", err, out())
   306  	}
   307  	defer c.Close()
   308  	if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
   309  		t.Fatalf("Expected AUTH supported")
   310  	}
   311  	if ok, _ := c.Extension("DSN"); ok {
   312  		t.Fatalf("Shouldn't support DSN")
   313  	}
   314  	if err := c.Quit(); err != nil {
   315  		t.Fatalf("QUIT failed: %s", err)
   316  	}
   317  
   318  	actualcmds := out()
   319  	if client != actualcmds {
   320  		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   321  	}
   322  }
   323  
   324  var newClientServer = `220 hello world
   325  250-mx.google.com at your service
   326  250-SIZE 35651584
   327  250-AUTH LOGIN PLAIN
   328  250 8BITMIME
   329  221 OK
   330  `
   331  
   332  var newClientClient = `EHLO localhost
   333  QUIT
   334  `
   335  
   336  func TestNewClient2(t *testing.T) {
   337  	server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n")
   338  	client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n")
   339  
   340  	var cmdbuf bytes.Buffer
   341  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   342  	var fake faker
   343  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   344  	c, err := NewClient(fake, "fake.host")
   345  	if err != nil {
   346  		t.Fatalf("NewClient: %v", err)
   347  	}
   348  	defer c.Close()
   349  	if ok, _ := c.Extension("DSN"); ok {
   350  		t.Fatalf("Shouldn't support DSN")
   351  	}
   352  	if err := c.Quit(); err != nil {
   353  		t.Fatalf("QUIT failed: %s", err)
   354  	}
   355  
   356  	bcmdbuf.Flush()
   357  	actualcmds := cmdbuf.String()
   358  	if client != actualcmds {
   359  		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   360  	}
   361  }
   362  
   363  var newClient2Server = `220 hello world
   364  502 EH?
   365  250-mx.google.com at your service
   366  250-SIZE 35651584
   367  250-AUTH LOGIN PLAIN
   368  250 8BITMIME
   369  221 OK
   370  `
   371  
   372  var newClient2Client = `EHLO localhost
   373  HELO localhost
   374  QUIT
   375  `
   376  
   377  func TestNewClientWithTLS(t *testing.T) {
   378  	cert, err := tls.X509KeyPair(localhostCert, localhostKey)
   379  	if err != nil {
   380  		t.Fatalf("loadcert: %v", err)
   381  	}
   382  
   383  	config := tls.Config{Certificates: []tls.Certificate{cert}}
   384  
   385  	ln, err := tls.Listen("tcp", "127.0.0.1:0", &config)
   386  	if err != nil {
   387  		ln, err = tls.Listen("tcp", "[::1]:0", &config)
   388  		if err != nil {
   389  			t.Fatalf("server: listen: %v", err)
   390  		}
   391  	}
   392  
   393  	go func() {
   394  		conn, err := ln.Accept()
   395  		if err != nil {
   396  			t.Errorf("server: accept: %v", err)
   397  			return
   398  		}
   399  		defer conn.Close()
   400  
   401  		_, err = conn.Write([]byte("220 SIGNS\r\n"))
   402  		if err != nil {
   403  			t.Errorf("server: write: %v", err)
   404  			return
   405  		}
   406  	}()
   407  
   408  	config.InsecureSkipVerify = true
   409  	conn, err := tls.Dial("tcp", ln.Addr().String(), &config)
   410  	if err != nil {
   411  		t.Fatalf("client: dial: %v", err)
   412  	}
   413  	defer conn.Close()
   414  
   415  	client, err := NewClient(conn, ln.Addr().String())
   416  	if err != nil {
   417  		t.Fatalf("smtp: newclient: %v", err)
   418  	}
   419  	if !client.tls {
   420  		t.Errorf("client.tls Got: %t Expected: %t", client.tls, true)
   421  	}
   422  }
   423  
   424  func TestHello(t *testing.T) {
   425  
   426  	if len(helloServer) != len(helloClient) {
   427  		t.Fatalf("Hello server and client size mismatch")
   428  	}
   429  
   430  	for i := 0; i < len(helloServer); i++ {
   431  		server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n")
   432  		client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n")
   433  		var cmdbuf bytes.Buffer
   434  		bcmdbuf := bufio.NewWriter(&cmdbuf)
   435  		var fake faker
   436  		fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   437  		c, err := NewClient(fake, "fake.host")
   438  		if err != nil {
   439  			t.Fatalf("NewClient: %v", err)
   440  		}
   441  		defer c.Close()
   442  		c.localName = "customhost"
   443  		err = nil
   444  
   445  		switch i {
   446  		case 0:
   447  			err = c.Hello("hostinjection>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n")
   448  			if err == nil {
   449  				t.Errorf("Expected Hello to be rejected due to a message injection attempt")
   450  			}
   451  			err = c.Hello("customhost")
   452  		case 1:
   453  			err = c.StartTLS(nil)
   454  			if err.Error() == "502 Not implemented" {
   455  				err = nil
   456  			}
   457  		case 2:
   458  			err = c.Verify("test@example.com")
   459  		case 3:
   460  			c.tls = true
   461  			c.serverName = "smtp.google.com"
   462  			err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
   463  		case 4:
   464  			err = c.Mail("test@example.com")
   465  		case 5:
   466  			ok, _ := c.Extension("feature")
   467  			if ok {
   468  				t.Errorf("Expected FEATURE not to be supported")
   469  			}
   470  		case 6:
   471  			err = c.Reset()
   472  		case 7:
   473  			err = c.Quit()
   474  		case 8:
   475  			err = c.Verify("test@example.com")
   476  			if err != nil {
   477  				err = c.Hello("customhost")
   478  				if err != nil {
   479  					t.Errorf("Want error, got none")
   480  				}
   481  			}
   482  		case 9:
   483  			err = c.Noop()
   484  		default:
   485  			t.Fatalf("Unhandled command")
   486  		}
   487  
   488  		if err != nil {
   489  			t.Errorf("Command %d failed: %v", i, err)
   490  		}
   491  
   492  		bcmdbuf.Flush()
   493  		actualcmds := cmdbuf.String()
   494  		if client != actualcmds {
   495  			t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   496  		}
   497  	}
   498  }
   499  
   500  var baseHelloServer = `220 hello world
   501  502 EH?
   502  250-mx.google.com at your service
   503  250 FEATURE
   504  `
   505  
   506  var helloServer = []string{
   507  	"",
   508  	"502 Not implemented\n",
   509  	"250 User is valid\n",
   510  	"235 Accepted\n",
   511  	"250 Sender ok\n",
   512  	"",
   513  	"250 Reset ok\n",
   514  	"221 Goodbye\n",
   515  	"250 Sender ok\n",
   516  	"250 ok\n",
   517  }
   518  
   519  var baseHelloClient = `EHLO customhost
   520  HELO customhost
   521  `
   522  
   523  var helloClient = []string{
   524  	"",
   525  	"STARTTLS\n",
   526  	"VRFY test@example.com\n",
   527  	"AUTH PLAIN AHVzZXIAcGFzcw==\n",
   528  	"MAIL FROM:<test@example.com>\n",
   529  	"",
   530  	"RSET\n",
   531  	"QUIT\n",
   532  	"VRFY test@example.com\n",
   533  	"NOOP\n",
   534  }
   535  
   536  func TestSendMail(t *testing.T) {
   537  	server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n")
   538  	client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n")
   539  	var cmdbuf bytes.Buffer
   540  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   541  	l, err := net.Listen("tcp", "127.0.0.1:0")
   542  	if err != nil {
   543  		t.Fatalf("Unable to create listener: %v", err)
   544  	}
   545  	defer l.Close()
   546  
   547  	// prevent data race on bcmdbuf
   548  	var done = make(chan struct{})
   549  	go func(data []string) {
   550  
   551  		defer close(done)
   552  
   553  		conn, err := l.Accept()
   554  		if err != nil {
   555  			t.Errorf("Accept error: %v", err)
   556  			return
   557  		}
   558  		defer conn.Close()
   559  
   560  		tc := textproto.NewConn(conn)
   561  		for i := 0; i < len(data) && data[i] != ""; i++ {
   562  			tc.PrintfLine(data[i])
   563  			for len(data[i]) >= 4 && data[i][3] == '-' {
   564  				i++
   565  				tc.PrintfLine(data[i])
   566  			}
   567  			if data[i] == "221 Goodbye" {
   568  				return
   569  			}
   570  			read := false
   571  			for !read || data[i] == "354 Go ahead" {
   572  				msg, err := tc.ReadLine()
   573  				bcmdbuf.Write([]byte(msg + "\r\n"))
   574  				read = true
   575  				if err != nil {
   576  					t.Errorf("Read error: %v", err)
   577  					return
   578  				}
   579  				if data[i] == "354 Go ahead" && msg == "." {
   580  					break
   581  				}
   582  			}
   583  		}
   584  	}(strings.Split(server, "\r\n"))
   585  
   586  	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
   587  To: other@example.com
   588  Subject: SendMail test
   589  
   590  SendMail is working for me.
   591  `, "\n", "\r\n", -1)))
   592  	if err == nil {
   593  		t.Errorf("Expected SendMail to be rejected due to a message injection attempt")
   594  	}
   595  
   596  	err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com
   597  To: other@example.com
   598  Subject: SendMail test
   599  
   600  SendMail is working for me.
   601  `, "\n", "\r\n", -1)))
   602  
   603  	if err != nil {
   604  		t.Errorf("%v", err)
   605  	}
   606  
   607  	<-done
   608  	bcmdbuf.Flush()
   609  	actualcmds := cmdbuf.String()
   610  	if client != actualcmds {
   611  		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   612  	}
   613  }
   614  
   615  var sendMailServer = `220 hello world
   616  502 EH?
   617  250 mx.google.com at your service
   618  250 Sender ok
   619  250 Receiver ok
   620  354 Go ahead
   621  250 Data ok
   622  221 Goodbye
   623  `
   624  
   625  var sendMailClient = `EHLO localhost
   626  HELO localhost
   627  MAIL FROM:<test@example.com>
   628  RCPT TO:<other@example.com>
   629  DATA
   630  From: test@example.com
   631  To: other@example.com
   632  Subject: SendMail test
   633  
   634  SendMail is working for me.
   635  .
   636  QUIT
   637  `
   638  
   639  func TestSendMailWithAuth(t *testing.T) {
   640  	l, err := net.Listen("tcp", "127.0.0.1:0")
   641  	if err != nil {
   642  		t.Fatalf("Unable to create listener: %v", err)
   643  	}
   644  	defer l.Close()
   645  
   646  	errCh := make(chan error)
   647  	go func() {
   648  		defer close(errCh)
   649  		conn, err := l.Accept()
   650  		if err != nil {
   651  			errCh <- fmt.Errorf("Accept: %v", err)
   652  			return
   653  		}
   654  		defer conn.Close()
   655  
   656  		tc := textproto.NewConn(conn)
   657  		tc.PrintfLine("220 hello world")
   658  		msg, err := tc.ReadLine()
   659  		if err != nil {
   660  			errCh <- fmt.Errorf("ReadLine error: %v", err)
   661  			return
   662  		}
   663  		const wantMsg = "EHLO localhost"
   664  		if msg != wantMsg {
   665  			errCh <- fmt.Errorf("unexpected response %q; want %q", msg, wantMsg)
   666  			return
   667  		}
   668  		err = tc.PrintfLine("250 mx.google.com at your service")
   669  		if err != nil {
   670  			errCh <- fmt.Errorf("PrintfLine: %v", err)
   671  			return
   672  		}
   673  	}()
   674  
   675  	err = SendMail(l.Addr().String(), PlainAuth("", "user", "pass", "smtp.google.com"), "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com
   676  To: other@example.com
   677  Subject: SendMail test
   678  
   679  SendMail is working for me.
   680  `, "\n", "\r\n", -1)))
   681  	if err == nil {
   682  		t.Error("SendMail: Server doesn't support AUTH, expected to get an error, but got none ")
   683  	}
   684  	if err.Error() != "smtp: server doesn't support AUTH" {
   685  		t.Errorf("Expected: smtp: server doesn't support AUTH, got: %s", err)
   686  	}
   687  	err = <-errCh
   688  	if err != nil {
   689  		t.Fatalf("server error: %v", err)
   690  	}
   691  }
   692  
   693  func TestAuthFailed(t *testing.T) {
   694  	server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n")
   695  	client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n")
   696  	var cmdbuf bytes.Buffer
   697  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   698  	var fake faker
   699  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   700  	c, err := NewClient(fake, "fake.host")
   701  	if err != nil {
   702  		t.Fatalf("NewClient: %v", err)
   703  	}
   704  	defer c.Close()
   705  
   706  	c.tls = true
   707  	c.serverName = "smtp.google.com"
   708  	err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
   709  
   710  	if err == nil {
   711  		t.Error("Auth: expected error; got none")
   712  	} else if err.Error() != "535 Invalid credentials\nplease see www.example.com" {
   713  		t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com")
   714  	}
   715  
   716  	bcmdbuf.Flush()
   717  	actualcmds := cmdbuf.String()
   718  	if client != actualcmds {
   719  		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   720  	}
   721  }
   722  
   723  var authFailedServer = `220 hello world
   724  250-mx.google.com at your service
   725  250 AUTH LOGIN PLAIN
   726  535-Invalid credentials
   727  535 please see www.example.com
   728  221 Goodbye
   729  `
   730  
   731  var authFailedClient = `EHLO localhost
   732  AUTH PLAIN AHVzZXIAcGFzcw==
   733  *
   734  QUIT
   735  `
   736  
   737  func TestTLSClient(t *testing.T) {
   738  	if (runtime.GOOS == "freebsd" && runtime.GOARCH == "amd64") || runtime.GOOS == "js" {
   739  		testenv.SkipFlaky(t, 19229)
   740  	}
   741  	ln := newLocalListener(t)
   742  	defer ln.Close()
   743  	errc := make(chan error)
   744  	go func() {
   745  		errc <- sendMail(ln.Addr().String())
   746  	}()
   747  	conn, err := ln.Accept()
   748  	if err != nil {
   749  		t.Fatalf("failed to accept connection: %v", err)
   750  	}
   751  	defer conn.Close()
   752  	if err := serverHandle(conn, t); err != nil {
   753  		t.Fatalf("failed to handle connection: %v", err)
   754  	}
   755  	if err := <-errc; err != nil {
   756  		t.Fatalf("client error: %v", err)
   757  	}
   758  }
   759  
   760  func TestTLSConnState(t *testing.T) {
   761  	ln := newLocalListener(t)
   762  	defer ln.Close()
   763  	clientDone := make(chan bool)
   764  	serverDone := make(chan bool)
   765  	go func() {
   766  		defer close(serverDone)
   767  		c, err := ln.Accept()
   768  		if err != nil {
   769  			t.Errorf("Server accept: %v", err)
   770  			return
   771  		}
   772  		defer c.Close()
   773  		if err := serverHandle(c, t); err != nil {
   774  			t.Errorf("server error: %v", err)
   775  		}
   776  	}()
   777  	go func() {
   778  		defer close(clientDone)
   779  		c, err := Dial(ln.Addr().String())
   780  		if err != nil {
   781  			t.Errorf("Client dial: %v", err)
   782  			return
   783  		}
   784  		defer c.Quit()
   785  		cfg := &tls.Config{ServerName: "example.com"}
   786  		testHookStartTLS(cfg) // set the RootCAs
   787  		if err := c.StartTLS(cfg); err != nil {
   788  			t.Errorf("StartTLS: %v", err)
   789  			return
   790  		}
   791  		cs, ok := c.TLSConnectionState()
   792  		if !ok {
   793  			t.Errorf("TLSConnectionState returned ok == false; want true")
   794  			return
   795  		}
   796  		if cs.Version == 0 || !cs.HandshakeComplete {
   797  			t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs)
   798  		}
   799  	}()
   800  	<-clientDone
   801  	<-serverDone
   802  }
   803  
   804  func newLocalListener(t *testing.T) net.Listener {
   805  	ln, err := net.Listen("tcp", "127.0.0.1:0")
   806  	if err != nil {
   807  		ln, err = net.Listen("tcp6", "[::1]:0")
   808  	}
   809  	if err != nil {
   810  		t.Fatal(err)
   811  	}
   812  	return ln
   813  }
   814  
   815  type smtpSender struct {
   816  	w io.Writer
   817  }
   818  
   819  func (s smtpSender) send(f string) {
   820  	s.w.Write([]byte(f + "\r\n"))
   821  }
   822  
   823  // smtp server, finely tailored to deal with our own client only!
   824  func serverHandle(c net.Conn, t *testing.T) error {
   825  	send := smtpSender{c}.send
   826  	send("220 127.0.0.1 ESMTP service ready")
   827  	s := bufio.NewScanner(c)
   828  	for s.Scan() {
   829  		switch s.Text() {
   830  		case "EHLO localhost":
   831  			send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
   832  			send("250-STARTTLS")
   833  			send("250 Ok")
   834  		case "STARTTLS":
   835  			send("220 Go ahead")
   836  			keypair, err := tls.X509KeyPair(localhostCert, localhostKey)
   837  			if err != nil {
   838  				return err
   839  			}
   840  			config := &tls.Config{Certificates: []tls.Certificate{keypair}}
   841  			c = tls.Server(c, config)
   842  			defer c.Close()
   843  			return serverHandleTLS(c, t)
   844  		default:
   845  			t.Fatalf("unrecognized command: %q", s.Text())
   846  		}
   847  	}
   848  	return s.Err()
   849  }
   850  
   851  func serverHandleTLS(c net.Conn, t *testing.T) error {
   852  	send := smtpSender{c}.send
   853  	s := bufio.NewScanner(c)
   854  	for s.Scan() {
   855  		switch s.Text() {
   856  		case "EHLO localhost":
   857  			send("250 Ok")
   858  		case "MAIL FROM:<joe1@example.com>":
   859  			send("250 Ok")
   860  		case "RCPT TO:<joe2@example.com>":
   861  			send("250 Ok")
   862  		case "DATA":
   863  			send("354 send the mail data, end with .")
   864  			send("250 Ok")
   865  		case "Subject: test":
   866  		case "":
   867  		case "howdy!":
   868  		case ".":
   869  		case "QUIT":
   870  			send("221 127.0.0.1 Service closing transmission channel")
   871  			return nil
   872  		default:
   873  			t.Fatalf("unrecognized command during TLS: %q", s.Text())
   874  		}
   875  	}
   876  	return s.Err()
   877  }
   878  
   879  func init() {
   880  	testRootCAs := x509.NewCertPool()
   881  	testRootCAs.AppendCertsFromPEM(localhostCert)
   882  	testHookStartTLS = func(config *tls.Config) {
   883  		config.RootCAs = testRootCAs
   884  	}
   885  }
   886  
   887  func sendMail(hostPort string) error {
   888  	from := "joe1@example.com"
   889  	to := []string{"joe2@example.com"}
   890  	return SendMail(hostPort, nil, from, to, []byte("Subject: test\n\nhowdy!"))
   891  }
   892  
   893  // localhostCert is a PEM-encoded TLS cert generated from src/crypto/tls:
   894  // go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com \
   895  // 		--ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
   896  var localhostCert = []byte(`
   897  -----BEGIN CERTIFICATE-----
   898  MIICFDCCAX2gAwIBAgIRAK0xjnaPuNDSreeXb+z+0u4wDQYJKoZIhvcNAQELBQAw
   899  EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
   900  MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
   901  gYkCgYEA0nFbQQuOWsjbGtejcpWz153OlziZM4bVjJ9jYruNw5n2Ry6uYQAffhqa
   902  JOInCmmcVe2siJglsyH9aRh6vKiobBbIUXXUU1ABd56ebAzlt0LobLlx7pZEMy30
   903  LqIi9E6zmL3YvdGzpYlkFRnRrqwEtWYbGBf3znO250S56CCWH2UCAwEAAaNoMGYw
   904  DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF
   905  MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA
   906  AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAbZtDS2dVuBYvb+MnolWnCNqvw1w5Gtgi
   907  NmvQQPOMgM3m+oQSCPRTNGSg25e1Qbo7bgQDv8ZTnq8FgOJ/rbkyERw2JckkHpD4
   908  n4qcK27WkEDBtQFlPihIM8hLIuzWoi/9wygiElTy/tVL3y7fGCvY2/k1KBthtZGF
   909  tN8URjVmyEo=
   910  -----END CERTIFICATE-----`)
   911  
   912  // localhostKey is the private key for localhostCert.
   913  var localhostKey = []byte(testingKey(`
   914  -----BEGIN RSA TESTING KEY-----
   915  MIICXgIBAAKBgQDScVtBC45ayNsa16NylbPXnc6XOJkzhtWMn2Niu43DmfZHLq5h
   916  AB9+Gpok4icKaZxV7ayImCWzIf1pGHq8qKhsFshRddRTUAF3np5sDOW3QuhsuXHu
   917  lkQzLfQuoiL0TrOYvdi90bOliWQVGdGurAS1ZhsYF/fOc7bnRLnoIJYfZQIDAQAB
   918  AoGBAMst7OgpKyFV6c3JwyI/jWqxDySL3caU+RuTTBaodKAUx2ZEmNJIlx9eudLA
   919  kucHvoxsM/eRxlxkhdFxdBcwU6J+zqooTnhu/FE3jhrT1lPrbhfGhyKnUrB0KKMM
   920  VY3IQZyiehpxaeXAwoAou6TbWoTpl9t8ImAqAMY8hlULCUqlAkEA+9+Ry5FSYK/m
   921  542LujIcCaIGoG1/Te6Sxr3hsPagKC2rH20rDLqXwEedSFOpSS0vpzlPAzy/6Rbb
   922  PHTJUhNdwwJBANXkA+TkMdbJI5do9/mn//U0LfrCR9NkcoYohxfKz8JuhgRQxzF2
   923  6jpo3q7CdTuuRixLWVfeJzcrAyNrVcBq87cCQFkTCtOMNC7fZnCTPUv+9q1tcJyB
   924  vNjJu3yvoEZeIeuzouX9TJE21/33FaeDdsXbRhQEj23cqR38qFHsF1qAYNMCQQDP
   925  QXLEiJoClkR2orAmqjPLVhR3t2oB3INcnEjLNSq8LHyQEfXyaFfu4U9l5+fRPL2i
   926  jiC0k/9L5dHUsF0XZothAkEA23ddgRs+Id/HxtojqqUT27B8MT/IGNrYsp4DvS/c
   927  qgkeluku4GjxRlDMBuXk94xOBEinUs+p/hwP1Alll80Tpg==
   928  -----END RSA TESTING KEY-----`))
   929  
   930  func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }