...
Run Format

Source file src/net/smtp/smtp_test.go

Documentation: net/smtp

     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: %v", err)
   389  		}
   390  	}
   391  
   392  	go func() {
   393  		conn, err := ln.Accept()
   394  		if err != nil {
   395  			t.Errorf("server: accept: %v", err)
   396  			return
   397  		}
   398  		defer conn.Close()
   399  
   400  		_, err = conn.Write([]byte("220 SIGNS\r\n"))
   401  		if err != nil {
   402  			t.Errorf("server: write: %v", 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: %v", err)
   411  	}
   412  	defer conn.Close()
   413  
   414  	client, err := NewClient(conn, ln.Addr().String())
   415  	if err != nil {
   416  		t.Fatalf("smtp: newclient: %v", 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  		case 9:
   482  			err = c.Noop()
   483  		default:
   484  			t.Fatalf("Unhandled command")
   485  		}
   486  
   487  		if err != nil {
   488  			t.Errorf("Command %d failed: %v", i, err)
   489  		}
   490  
   491  		bcmdbuf.Flush()
   492  		actualcmds := cmdbuf.String()
   493  		if client != actualcmds {
   494  			t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   495  		}
   496  	}
   497  }
   498  
   499  var baseHelloServer = `220 hello world
   500  502 EH?
   501  250-mx.google.com at your service
   502  250 FEATURE
   503  `
   504  
   505  var helloServer = []string{
   506  	"",
   507  	"502 Not implemented\n",
   508  	"250 User is valid\n",
   509  	"235 Accepted\n",
   510  	"250 Sender ok\n",
   511  	"",
   512  	"250 Reset ok\n",
   513  	"221 Goodbye\n",
   514  	"250 Sender ok\n",
   515  	"250 ok\n",
   516  }
   517  
   518  var baseHelloClient = `EHLO customhost
   519  HELO customhost
   520  `
   521  
   522  var helloClient = []string{
   523  	"",
   524  	"STARTTLS\n",
   525  	"VRFY test@example.com\n",
   526  	"AUTH PLAIN AHVzZXIAcGFzcw==\n",
   527  	"MAIL FROM:<test@example.com>\n",
   528  	"",
   529  	"RSET\n",
   530  	"QUIT\n",
   531  	"VRFY test@example.com\n",
   532  	"NOOP\n",
   533  }
   534  
   535  func TestSendMail(t *testing.T) {
   536  	server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n")
   537  	client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n")
   538  	var cmdbuf bytes.Buffer
   539  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   540  	l, err := net.Listen("tcp", "127.0.0.1:0")
   541  	if err != nil {
   542  		t.Fatalf("Unable to to create listener: %v", err)
   543  	}
   544  	defer l.Close()
   545  
   546  	// prevent data race on bcmdbuf
   547  	var done = make(chan struct{})
   548  	go func(data []string) {
   549  
   550  		defer close(done)
   551  
   552  		conn, err := l.Accept()
   553  		if err != nil {
   554  			t.Errorf("Accept error: %v", err)
   555  			return
   556  		}
   557  		defer conn.Close()
   558  
   559  		tc := textproto.NewConn(conn)
   560  		for i := 0; i < len(data) && data[i] != ""; i++ {
   561  			tc.PrintfLine(data[i])
   562  			for len(data[i]) >= 4 && data[i][3] == '-' {
   563  				i++
   564  				tc.PrintfLine(data[i])
   565  			}
   566  			if data[i] == "221 Goodbye" {
   567  				return
   568  			}
   569  			read := false
   570  			for !read || data[i] == "354 Go ahead" {
   571  				msg, err := tc.ReadLine()
   572  				bcmdbuf.Write([]byte(msg + "\r\n"))
   573  				read = true
   574  				if err != nil {
   575  					t.Errorf("Read error: %v", err)
   576  					return
   577  				}
   578  				if data[i] == "354 Go ahead" && msg == "." {
   579  					break
   580  				}
   581  			}
   582  		}
   583  	}(strings.Split(server, "\r\n"))
   584  
   585  	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
   586  To: other@example.com
   587  Subject: SendMail test
   588  
   589  SendMail is working for me.
   590  `, "\n", "\r\n", -1)))
   591  	if err == nil {
   592  		t.Errorf("Expected SendMail to be rejected due to a message injection attempt")
   593  	}
   594  
   595  	err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com
   596  To: other@example.com
   597  Subject: SendMail test
   598  
   599  SendMail is working for me.
   600  `, "\n", "\r\n", -1)))
   601  
   602  	if err != nil {
   603  		t.Errorf("%v", err)
   604  	}
   605  
   606  	<-done
   607  	bcmdbuf.Flush()
   608  	actualcmds := cmdbuf.String()
   609  	if client != actualcmds {
   610  		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   611  	}
   612  }
   613  
   614  var sendMailServer = `220 hello world
   615  502 EH?
   616  250 mx.google.com at your service
   617  250 Sender ok
   618  250 Receiver ok
   619  354 Go ahead
   620  250 Data ok
   621  221 Goodbye
   622  `
   623  
   624  var sendMailClient = `EHLO localhost
   625  HELO localhost
   626  MAIL FROM:<test@example.com>
   627  RCPT TO:<other@example.com>
   628  DATA
   629  From: test@example.com
   630  To: other@example.com
   631  Subject: SendMail test
   632  
   633  SendMail is working for me.
   634  .
   635  QUIT
   636  `
   637  
   638  func TestAuthFailed(t *testing.T) {
   639  	server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n")
   640  	client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n")
   641  	var cmdbuf bytes.Buffer
   642  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   643  	var fake faker
   644  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   645  	c, err := NewClient(fake, "fake.host")
   646  	if err != nil {
   647  		t.Fatalf("NewClient: %v", err)
   648  	}
   649  	defer c.Close()
   650  
   651  	c.tls = true
   652  	c.serverName = "smtp.google.com"
   653  	err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
   654  
   655  	if err == nil {
   656  		t.Error("Auth: expected error; got none")
   657  	} else if err.Error() != "535 Invalid credentials\nplease see www.example.com" {
   658  		t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com")
   659  	}
   660  
   661  	bcmdbuf.Flush()
   662  	actualcmds := cmdbuf.String()
   663  	if client != actualcmds {
   664  		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   665  	}
   666  }
   667  
   668  var authFailedServer = `220 hello world
   669  250-mx.google.com at your service
   670  250 AUTH LOGIN PLAIN
   671  535-Invalid credentials
   672  535 please see www.example.com
   673  221 Goodbye
   674  `
   675  
   676  var authFailedClient = `EHLO localhost
   677  AUTH PLAIN AHVzZXIAcGFzcw==
   678  *
   679  QUIT
   680  `
   681  
   682  func TestTLSClient(t *testing.T) {
   683  	if runtime.GOOS == "freebsd" && runtime.GOARCH == "amd64" {
   684  		testenv.SkipFlaky(t, 19229)
   685  	}
   686  	ln := newLocalListener(t)
   687  	defer ln.Close()
   688  	errc := make(chan error)
   689  	go func() {
   690  		errc <- sendMail(ln.Addr().String())
   691  	}()
   692  	conn, err := ln.Accept()
   693  	if err != nil {
   694  		t.Fatalf("failed to accept connection: %v", err)
   695  	}
   696  	defer conn.Close()
   697  	if err := serverHandle(conn, t); err != nil {
   698  		t.Fatalf("failed to handle connection: %v", err)
   699  	}
   700  	if err := <-errc; err != nil {
   701  		t.Fatalf("client error: %v", err)
   702  	}
   703  }
   704  
   705  func TestTLSConnState(t *testing.T) {
   706  	ln := newLocalListener(t)
   707  	defer ln.Close()
   708  	clientDone := make(chan bool)
   709  	serverDone := make(chan bool)
   710  	go func() {
   711  		defer close(serverDone)
   712  		c, err := ln.Accept()
   713  		if err != nil {
   714  			t.Errorf("Server accept: %v", err)
   715  			return
   716  		}
   717  		defer c.Close()
   718  		if err := serverHandle(c, t); err != nil {
   719  			t.Errorf("server error: %v", err)
   720  		}
   721  	}()
   722  	go func() {
   723  		defer close(clientDone)
   724  		c, err := Dial(ln.Addr().String())
   725  		if err != nil {
   726  			t.Errorf("Client dial: %v", err)
   727  			return
   728  		}
   729  		defer c.Quit()
   730  		cfg := &tls.Config{ServerName: "example.com"}
   731  		testHookStartTLS(cfg) // set the RootCAs
   732  		if err := c.StartTLS(cfg); err != nil {
   733  			t.Errorf("StartTLS: %v", err)
   734  			return
   735  		}
   736  		cs, ok := c.TLSConnectionState()
   737  		if !ok {
   738  			t.Errorf("TLSConnectionState returned ok == false; want true")
   739  			return
   740  		}
   741  		if cs.Version == 0 || !cs.HandshakeComplete {
   742  			t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs)
   743  		}
   744  	}()
   745  	<-clientDone
   746  	<-serverDone
   747  }
   748  
   749  func newLocalListener(t *testing.T) net.Listener {
   750  	ln, err := net.Listen("tcp", "127.0.0.1:0")
   751  	if err != nil {
   752  		ln, err = net.Listen("tcp6", "[::1]:0")
   753  	}
   754  	if err != nil {
   755  		t.Fatal(err)
   756  	}
   757  	return ln
   758  }
   759  
   760  type smtpSender struct {
   761  	w io.Writer
   762  }
   763  
   764  func (s smtpSender) send(f string) {
   765  	s.w.Write([]byte(f + "\r\n"))
   766  }
   767  
   768  // smtp server, finely tailored to deal with our own client only!
   769  func serverHandle(c net.Conn, t *testing.T) error {
   770  	send := smtpSender{c}.send
   771  	send("220 127.0.0.1 ESMTP service ready")
   772  	s := bufio.NewScanner(c)
   773  	for s.Scan() {
   774  		switch s.Text() {
   775  		case "EHLO localhost":
   776  			send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
   777  			send("250-STARTTLS")
   778  			send("250 Ok")
   779  		case "STARTTLS":
   780  			send("220 Go ahead")
   781  			keypair, err := tls.X509KeyPair(localhostCert, localhostKey)
   782  			if err != nil {
   783  				return err
   784  			}
   785  			config := &tls.Config{Certificates: []tls.Certificate{keypair}}
   786  			c = tls.Server(c, config)
   787  			defer c.Close()
   788  			return serverHandleTLS(c, t)
   789  		default:
   790  			t.Fatalf("unrecognized command: %q", s.Text())
   791  		}
   792  	}
   793  	return s.Err()
   794  }
   795  
   796  func serverHandleTLS(c net.Conn, t *testing.T) error {
   797  	send := smtpSender{c}.send
   798  	s := bufio.NewScanner(c)
   799  	for s.Scan() {
   800  		switch s.Text() {
   801  		case "EHLO localhost":
   802  			send("250 Ok")
   803  		case "MAIL FROM:<joe1@example.com>":
   804  			send("250 Ok")
   805  		case "RCPT TO:<joe2@example.com>":
   806  			send("250 Ok")
   807  		case "DATA":
   808  			send("354 send the mail data, end with .")
   809  			send("250 Ok")
   810  		case "Subject: test":
   811  		case "":
   812  		case "howdy!":
   813  		case ".":
   814  		case "QUIT":
   815  			send("221 127.0.0.1 Service closing transmission channel")
   816  			return nil
   817  		default:
   818  			t.Fatalf("unrecognized command during TLS: %q", s.Text())
   819  		}
   820  	}
   821  	return s.Err()
   822  }
   823  
   824  func init() {
   825  	testRootCAs := x509.NewCertPool()
   826  	testRootCAs.AppendCertsFromPEM(localhostCert)
   827  	testHookStartTLS = func(config *tls.Config) {
   828  		config.RootCAs = testRootCAs
   829  	}
   830  }
   831  
   832  func sendMail(hostPort string) error {
   833  	host, _, err := net.SplitHostPort(hostPort)
   834  	if err != nil {
   835  		return err
   836  	}
   837  	auth := PlainAuth("", "", "", host)
   838  	from := "joe1@example.com"
   839  	to := []string{"joe2@example.com"}
   840  	return SendMail(hostPort, auth, from, to, []byte("Subject: test\n\nhowdy!"))
   841  }
   842  
   843  // (copied from net/http/httptest)
   844  // localhostCert is a PEM-encoded TLS cert with SAN IPs
   845  // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
   846  // of ASN.1 time).
   847  // generated from src/crypto/tls:
   848  // 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
   849  var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
   850  MIIBjjCCATigAwIBAgIQMon9v0s3pDFXvAMnPgelpzANBgkqhkiG9w0BAQsFADAS
   851  MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
   852  MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
   853  AM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnlqaUfVKVmz7FF
   854  wLP9csX6vGtvkZg1uWAtvfkCAwEAAaNoMGYwDgYDVR0PAQH/BAQDAgKkMBMGA1Ud
   855  JQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhh
   856  bXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQAD
   857  QQBOZsFVC7IwX+qibmSbt2IPHkUgXhfbq0a9MYhD6tHcj4gbDcTXh4kZCbgHCz22
   858  gfSj2/G2wxzopoISVDucuncj
   859  -----END CERTIFICATE-----`)
   860  
   861  // localhostKey is the private key for localhostCert.
   862  var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
   863  MIIBOwIBAAJBAM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnl
   864  qaUfVKVmz7FFwLP9csX6vGtvkZg1uWAtvfkCAwEAAQJART2qkxODLUbQ2siSx7m2
   865  rmBLyR/7X+nLe8aPDrMOxj3heDNl4YlaAYLexbcY8d7VDfCRBKYoAOP0UCP1Vhuf
   866  UQIhAO6PEI55K3SpNIdc2k5f0xz+9rodJCYzu51EwWX7r8ufAiEA3C9EkLiU2NuK
   867  3L3DHCN5IlUSN1Nr/lw8NIt50Yorj2cCIQCDw1VbvCV6bDLtSSXzAA51B4ZzScE7
   868  sHtB5EYF9Dwm9QIhAJuCquuH4mDzVjUntXjXOQPdj7sRqVGCNWdrJwOukat7AiAy
   869  LXLEwb77DIPoI5ZuaXQC+MnyyJj1ExC9RFcGz+bexA==
   870  -----END RSA PRIVATE KEY-----`)
   871  

View as plain text