...
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  	"sync"
    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 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 to create listener: %v", err)
   643  	}
   644  	defer l.Close()
   645  	wg := sync.WaitGroup{}
   646  	var done = make(chan struct{})
   647  	go func() {
   648  		defer wg.Done()
   649  		conn, err := l.Accept()
   650  		if err != nil {
   651  			t.Errorf("Accept error: %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 msg == "EHLO localhost" {
   660  			tc.PrintfLine("250 mx.google.com at your service")
   661  		}
   662  		// for this test case, there should have no more traffic
   663  		<-done
   664  	}()
   665  	wg.Add(1)
   666  
   667  	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
   668  To: other@example.com
   669  Subject: SendMail test
   670  
   671  SendMail is working for me.
   672  `, "\n", "\r\n", -1)))
   673  	if err == nil {
   674  		t.Error("SendMail: Server doesn't support AUTH, expected to get an error, but got none ")
   675  	}
   676  	if err.Error() != "smtp: server doesn't support AUTH" {
   677  		t.Errorf("Expected: smtp: server doesn't support AUTH, got: %s", err)
   678  	}
   679  	close(done)
   680  	wg.Wait()
   681  }
   682  
   683  func TestAuthFailed(t *testing.T) {
   684  	server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n")
   685  	client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n")
   686  	var cmdbuf bytes.Buffer
   687  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   688  	var fake faker
   689  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   690  	c, err := NewClient(fake, "fake.host")
   691  	if err != nil {
   692  		t.Fatalf("NewClient: %v", err)
   693  	}
   694  	defer c.Close()
   695  
   696  	c.tls = true
   697  	c.serverName = "smtp.google.com"
   698  	err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
   699  
   700  	if err == nil {
   701  		t.Error("Auth: expected error; got none")
   702  	} else if err.Error() != "535 Invalid credentials\nplease see www.example.com" {
   703  		t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com")
   704  	}
   705  
   706  	bcmdbuf.Flush()
   707  	actualcmds := cmdbuf.String()
   708  	if client != actualcmds {
   709  		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   710  	}
   711  }
   712  
   713  var authFailedServer = `220 hello world
   714  250-mx.google.com at your service
   715  250 AUTH LOGIN PLAIN
   716  535-Invalid credentials
   717  535 please see www.example.com
   718  221 Goodbye
   719  `
   720  
   721  var authFailedClient = `EHLO localhost
   722  AUTH PLAIN AHVzZXIAcGFzcw==
   723  *
   724  QUIT
   725  `
   726  
   727  func TestTLSClient(t *testing.T) {
   728  	if (runtime.GOOS == "freebsd" && runtime.GOARCH == "amd64") || runtime.GOOS == "js" {
   729  		testenv.SkipFlaky(t, 19229)
   730  	}
   731  	ln := newLocalListener(t)
   732  	defer ln.Close()
   733  	errc := make(chan error)
   734  	go func() {
   735  		errc <- sendMail(ln.Addr().String())
   736  	}()
   737  	conn, err := ln.Accept()
   738  	if err != nil {
   739  		t.Fatalf("failed to accept connection: %v", err)
   740  	}
   741  	defer conn.Close()
   742  	if err := serverHandle(conn, t); err != nil {
   743  		t.Fatalf("failed to handle connection: %v", err)
   744  	}
   745  	if err := <-errc; err != nil {
   746  		t.Fatalf("client error: %v", err)
   747  	}
   748  }
   749  
   750  func TestTLSConnState(t *testing.T) {
   751  	ln := newLocalListener(t)
   752  	defer ln.Close()
   753  	clientDone := make(chan bool)
   754  	serverDone := make(chan bool)
   755  	go func() {
   756  		defer close(serverDone)
   757  		c, err := ln.Accept()
   758  		if err != nil {
   759  			t.Errorf("Server accept: %v", err)
   760  			return
   761  		}
   762  		defer c.Close()
   763  		if err := serverHandle(c, t); err != nil {
   764  			t.Errorf("server error: %v", err)
   765  		}
   766  	}()
   767  	go func() {
   768  		defer close(clientDone)
   769  		c, err := Dial(ln.Addr().String())
   770  		if err != nil {
   771  			t.Errorf("Client dial: %v", err)
   772  			return
   773  		}
   774  		defer c.Quit()
   775  		cfg := &tls.Config{ServerName: "example.com"}
   776  		testHookStartTLS(cfg) // set the RootCAs
   777  		if err := c.StartTLS(cfg); err != nil {
   778  			t.Errorf("StartTLS: %v", err)
   779  			return
   780  		}
   781  		cs, ok := c.TLSConnectionState()
   782  		if !ok {
   783  			t.Errorf("TLSConnectionState returned ok == false; want true")
   784  			return
   785  		}
   786  		if cs.Version == 0 || !cs.HandshakeComplete {
   787  			t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs)
   788  		}
   789  	}()
   790  	<-clientDone
   791  	<-serverDone
   792  }
   793  
   794  func newLocalListener(t *testing.T) net.Listener {
   795  	ln, err := net.Listen("tcp", "127.0.0.1:0")
   796  	if err != nil {
   797  		ln, err = net.Listen("tcp6", "[::1]:0")
   798  	}
   799  	if err != nil {
   800  		t.Fatal(err)
   801  	}
   802  	return ln
   803  }
   804  
   805  type smtpSender struct {
   806  	w io.Writer
   807  }
   808  
   809  func (s smtpSender) send(f string) {
   810  	s.w.Write([]byte(f + "\r\n"))
   811  }
   812  
   813  // smtp server, finely tailored to deal with our own client only!
   814  func serverHandle(c net.Conn, t *testing.T) error {
   815  	send := smtpSender{c}.send
   816  	send("220 127.0.0.1 ESMTP service ready")
   817  	s := bufio.NewScanner(c)
   818  	for s.Scan() {
   819  		switch s.Text() {
   820  		case "EHLO localhost":
   821  			send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
   822  			send("250-STARTTLS")
   823  			send("250 Ok")
   824  		case "STARTTLS":
   825  			send("220 Go ahead")
   826  			keypair, err := tls.X509KeyPair(localhostCert, localhostKey)
   827  			if err != nil {
   828  				return err
   829  			}
   830  			config := &tls.Config{Certificates: []tls.Certificate{keypair}}
   831  			c = tls.Server(c, config)
   832  			defer c.Close()
   833  			return serverHandleTLS(c, t)
   834  		default:
   835  			t.Fatalf("unrecognized command: %q", s.Text())
   836  		}
   837  	}
   838  	return s.Err()
   839  }
   840  
   841  func serverHandleTLS(c net.Conn, t *testing.T) error {
   842  	send := smtpSender{c}.send
   843  	s := bufio.NewScanner(c)
   844  	for s.Scan() {
   845  		switch s.Text() {
   846  		case "EHLO localhost":
   847  			send("250 Ok")
   848  		case "MAIL FROM:<joe1@example.com>":
   849  			send("250 Ok")
   850  		case "RCPT TO:<joe2@example.com>":
   851  			send("250 Ok")
   852  		case "DATA":
   853  			send("354 send the mail data, end with .")
   854  			send("250 Ok")
   855  		case "Subject: test":
   856  		case "":
   857  		case "howdy!":
   858  		case ".":
   859  		case "QUIT":
   860  			send("221 127.0.0.1 Service closing transmission channel")
   861  			return nil
   862  		default:
   863  			t.Fatalf("unrecognized command during TLS: %q", s.Text())
   864  		}
   865  	}
   866  	return s.Err()
   867  }
   868  
   869  func init() {
   870  	testRootCAs := x509.NewCertPool()
   871  	testRootCAs.AppendCertsFromPEM(localhostCert)
   872  	testHookStartTLS = func(config *tls.Config) {
   873  		config.RootCAs = testRootCAs
   874  	}
   875  }
   876  
   877  func sendMail(hostPort string) error {
   878  	from := "joe1@example.com"
   879  	to := []string{"joe2@example.com"}
   880  	return SendMail(hostPort, nil, from, to, []byte("Subject: test\n\nhowdy!"))
   881  }
   882  
   883  // (copied from net/http/httptest)
   884  // localhostCert is a PEM-encoded TLS cert with SAN IPs
   885  // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
   886  // of ASN.1 time).
   887  // generated from src/crypto/tls:
   888  // 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
   889  var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
   890  MIIBjjCCATigAwIBAgIQMon9v0s3pDFXvAMnPgelpzANBgkqhkiG9w0BAQsFADAS
   891  MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
   892  MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
   893  AM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnlqaUfVKVmz7FF
   894  wLP9csX6vGtvkZg1uWAtvfkCAwEAAaNoMGYwDgYDVR0PAQH/BAQDAgKkMBMGA1Ud
   895  JQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhh
   896  bXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQAD
   897  QQBOZsFVC7IwX+qibmSbt2IPHkUgXhfbq0a9MYhD6tHcj4gbDcTXh4kZCbgHCz22
   898  gfSj2/G2wxzopoISVDucuncj
   899  -----END CERTIFICATE-----`)
   900  
   901  // localhostKey is the private key for localhostCert.
   902  var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
   903  MIIBOwIBAAJBAM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnl
   904  qaUfVKVmz7FFwLP9csX6vGtvkZg1uWAtvfkCAwEAAQJART2qkxODLUbQ2siSx7m2
   905  rmBLyR/7X+nLe8aPDrMOxj3heDNl4YlaAYLexbcY8d7VDfCRBKYoAOP0UCP1Vhuf
   906  UQIhAO6PEI55K3SpNIdc2k5f0xz+9rodJCYzu51EwWX7r8ufAiEA3C9EkLiU2NuK
   907  3L3DHCN5IlUSN1Nr/lw8NIt50Yorj2cCIQCDw1VbvCV6bDLtSSXzAA51B4ZzScE7
   908  sHtB5EYF9Dwm9QIhAJuCquuH4mDzVjUntXjXOQPdj7sRqVGCNWdrJwOukat7AiAy
   909  LXLEwb77DIPoI5ZuaXQC+MnyyJj1ExC9RFcGz+bexA==
   910  -----END RSA PRIVATE KEY-----`)
   911  

View as plain text