github.com/euank/go@v0.0.0-20160829210321-495514729181/src/net/smtp/smtp_test.go (about)

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