github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/pkg/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  	"io"
    11  	"net"
    12  	"net/textproto"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  )
    17  
    18  type authTest struct {
    19  	auth       Auth
    20  	challenges []string
    21  	name       string
    22  	responses  []string
    23  }
    24  
    25  var authTests = []authTest{
    26  	{PlainAuth("", "user", "pass", "testserver"), []string{}, "PLAIN", []string{"\x00user\x00pass"}},
    27  	{PlainAuth("foo", "bar", "baz", "testserver"), []string{}, "PLAIN", []string{"foo\x00bar\x00baz"}},
    28  	{CRAMMD5Auth("user", "pass"), []string{"<123456.1322876914@testserver>"}, "CRAM-MD5", []string{"", "user 287eb355114cf5c471c26a875f1ca4ae"}},
    29  }
    30  
    31  func TestAuth(t *testing.T) {
    32  testLoop:
    33  	for i, test := range authTests {
    34  		name, resp, err := test.auth.Start(&ServerInfo{"testserver", true, nil})
    35  		if name != test.name {
    36  			t.Errorf("#%d got name %s, expected %s", i, name, test.name)
    37  		}
    38  		if !bytes.Equal(resp, []byte(test.responses[0])) {
    39  			t.Errorf("#%d got response %s, expected %s", i, resp, test.responses[0])
    40  		}
    41  		if err != nil {
    42  			t.Errorf("#%d error: %s", i, err)
    43  		}
    44  		for j := range test.challenges {
    45  			challenge := []byte(test.challenges[j])
    46  			expected := []byte(test.responses[j+1])
    47  			resp, err := test.auth.Next(challenge, true)
    48  			if err != nil {
    49  				t.Errorf("#%d error: %s", i, err)
    50  				continue testLoop
    51  			}
    52  			if !bytes.Equal(resp, expected) {
    53  				t.Errorf("#%d got %s, expected %s", i, resp, expected)
    54  				continue testLoop
    55  			}
    56  		}
    57  	}
    58  }
    59  
    60  func TestAuthPlain(t *testing.T) {
    61  	auth := PlainAuth("foo", "bar", "baz", "servername")
    62  
    63  	tests := []struct {
    64  		server *ServerInfo
    65  		err    string
    66  	}{
    67  		{
    68  			server: &ServerInfo{Name: "servername", TLS: true},
    69  		},
    70  		{
    71  			// Okay; explicitly advertised by server.
    72  			server: &ServerInfo{Name: "servername", Auth: []string{"PLAIN"}},
    73  		},
    74  		{
    75  			server: &ServerInfo{Name: "servername", Auth: []string{"CRAM-MD5"}},
    76  			err:    "unencrypted connection",
    77  		},
    78  		{
    79  			server: &ServerInfo{Name: "attacker", TLS: true},
    80  			err:    "wrong host name",
    81  		},
    82  	}
    83  	for i, tt := range tests {
    84  		_, _, err := auth.Start(tt.server)
    85  		got := ""
    86  		if err != nil {
    87  			got = err.Error()
    88  		}
    89  		if got != tt.err {
    90  			t.Errorf("%d. got error = %q; want %q", i, got, tt.err)
    91  		}
    92  	}
    93  }
    94  
    95  type faker struct {
    96  	io.ReadWriter
    97  }
    98  
    99  func (f faker) Close() error                     { return nil }
   100  func (f faker) LocalAddr() net.Addr              { return nil }
   101  func (f faker) RemoteAddr() net.Addr             { return nil }
   102  func (f faker) SetDeadline(time.Time) error      { return nil }
   103  func (f faker) SetReadDeadline(time.Time) error  { return nil }
   104  func (f faker) SetWriteDeadline(time.Time) error { return nil }
   105  
   106  func TestBasic(t *testing.T) {
   107  	server := strings.Join(strings.Split(basicServer, "\n"), "\r\n")
   108  	client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
   109  
   110  	var cmdbuf bytes.Buffer
   111  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   112  	var fake faker
   113  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   114  	c := &Client{Text: textproto.NewConn(fake), localName: "localhost"}
   115  
   116  	if err := c.helo(); err != nil {
   117  		t.Fatalf("HELO failed: %s", err)
   118  	}
   119  	if err := c.ehlo(); err == nil {
   120  		t.Fatalf("Expected first EHLO to fail")
   121  	}
   122  	if err := c.ehlo(); err != nil {
   123  		t.Fatalf("Second EHLO failed: %s", err)
   124  	}
   125  
   126  	c.didHello = true
   127  	if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
   128  		t.Fatalf("Expected AUTH supported")
   129  	}
   130  	if ok, _ := c.Extension("DSN"); ok {
   131  		t.Fatalf("Shouldn't support DSN")
   132  	}
   133  
   134  	if err := c.Mail("user@gmail.com"); err == nil {
   135  		t.Fatalf("MAIL should require authentication")
   136  	}
   137  
   138  	if err := c.Verify("user1@gmail.com"); err == nil {
   139  		t.Fatalf("First VRFY: expected no verification")
   140  	}
   141  	if err := c.Verify("user2@gmail.com"); err != nil {
   142  		t.Fatalf("Second VRFY: expected verification, got %s", err)
   143  	}
   144  
   145  	// fake TLS so authentication won't complain
   146  	c.tls = true
   147  	c.serverName = "smtp.google.com"
   148  	if err := c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")); err != nil {
   149  		t.Fatalf("AUTH failed: %s", err)
   150  	}
   151  
   152  	if err := c.Mail("user@gmail.com"); err != nil {
   153  		t.Fatalf("MAIL failed: %s", err)
   154  	}
   155  	if err := c.Rcpt("golang-nuts@googlegroups.com"); err != nil {
   156  		t.Fatalf("RCPT failed: %s", err)
   157  	}
   158  	msg := `From: user@gmail.com
   159  To: golang-nuts@googlegroups.com
   160  Subject: Hooray for Go
   161  
   162  Line 1
   163  .Leading dot line .
   164  Goodbye.`
   165  	w, err := c.Data()
   166  	if err != nil {
   167  		t.Fatalf("DATA failed: %s", err)
   168  	}
   169  	if _, err := w.Write([]byte(msg)); err != nil {
   170  		t.Fatalf("Data write failed: %s", err)
   171  	}
   172  	if err := w.Close(); err != nil {
   173  		t.Fatalf("Bad data response: %s", err)
   174  	}
   175  
   176  	if err := c.Quit(); err != nil {
   177  		t.Fatalf("QUIT failed: %s", err)
   178  	}
   179  
   180  	bcmdbuf.Flush()
   181  	actualcmds := cmdbuf.String()
   182  	if client != actualcmds {
   183  		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   184  	}
   185  }
   186  
   187  var basicServer = `250 mx.google.com at your service
   188  502 Unrecognized command.
   189  250-mx.google.com at your service
   190  250-SIZE 35651584
   191  250-AUTH LOGIN PLAIN
   192  250 8BITMIME
   193  530 Authentication required
   194  252 Send some mail, I'll try my best
   195  250 User is valid
   196  235 Accepted
   197  250 Sender OK
   198  250 Receiver OK
   199  354 Go ahead
   200  250 Data OK
   201  221 OK
   202  `
   203  
   204  var basicClient = `HELO localhost
   205  EHLO localhost
   206  EHLO localhost
   207  MAIL FROM:<user@gmail.com> BODY=8BITMIME
   208  VRFY user1@gmail.com
   209  VRFY user2@gmail.com
   210  AUTH PLAIN AHVzZXIAcGFzcw==
   211  MAIL FROM:<user@gmail.com> BODY=8BITMIME
   212  RCPT TO:<golang-nuts@googlegroups.com>
   213  DATA
   214  From: user@gmail.com
   215  To: golang-nuts@googlegroups.com
   216  Subject: Hooray for Go
   217  
   218  Line 1
   219  ..Leading dot line .
   220  Goodbye.
   221  .
   222  QUIT
   223  `
   224  
   225  func TestNewClient(t *testing.T) {
   226  	server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n")
   227  	client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n")
   228  
   229  	var cmdbuf bytes.Buffer
   230  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   231  	out := func() string {
   232  		bcmdbuf.Flush()
   233  		return cmdbuf.String()
   234  	}
   235  	var fake faker
   236  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   237  	c, err := NewClient(fake, "fake.host")
   238  	if err != nil {
   239  		t.Fatalf("NewClient: %v\n(after %v)", err, out())
   240  	}
   241  	if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
   242  		t.Fatalf("Expected AUTH supported")
   243  	}
   244  	if ok, _ := c.Extension("DSN"); ok {
   245  		t.Fatalf("Shouldn't support DSN")
   246  	}
   247  	if err := c.Quit(); err != nil {
   248  		t.Fatalf("QUIT failed: %s", err)
   249  	}
   250  
   251  	actualcmds := out()
   252  	if client != actualcmds {
   253  		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   254  	}
   255  }
   256  
   257  var newClientServer = `220 hello world
   258  250-mx.google.com at your service
   259  250-SIZE 35651584
   260  250-AUTH LOGIN PLAIN
   261  250 8BITMIME
   262  221 OK
   263  `
   264  
   265  var newClientClient = `EHLO localhost
   266  QUIT
   267  `
   268  
   269  func TestNewClient2(t *testing.T) {
   270  	server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n")
   271  	client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n")
   272  
   273  	var cmdbuf bytes.Buffer
   274  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   275  	var fake faker
   276  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   277  	c, err := NewClient(fake, "fake.host")
   278  	if err != nil {
   279  		t.Fatalf("NewClient: %v", err)
   280  	}
   281  	if ok, _ := c.Extension("DSN"); ok {
   282  		t.Fatalf("Shouldn't support DSN")
   283  	}
   284  	if err := c.Quit(); err != nil {
   285  		t.Fatalf("QUIT failed: %s", err)
   286  	}
   287  
   288  	bcmdbuf.Flush()
   289  	actualcmds := cmdbuf.String()
   290  	if client != actualcmds {
   291  		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   292  	}
   293  }
   294  
   295  var newClient2Server = `220 hello world
   296  502 EH?
   297  250-mx.google.com at your service
   298  250-SIZE 35651584
   299  250-AUTH LOGIN PLAIN
   300  250 8BITMIME
   301  221 OK
   302  `
   303  
   304  var newClient2Client = `EHLO localhost
   305  HELO localhost
   306  QUIT
   307  `
   308  
   309  func TestHello(t *testing.T) {
   310  
   311  	if len(helloServer) != len(helloClient) {
   312  		t.Fatalf("Hello server and client size mismatch")
   313  	}
   314  
   315  	for i := 0; i < len(helloServer); i++ {
   316  		server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n")
   317  		client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n")
   318  		var cmdbuf bytes.Buffer
   319  		bcmdbuf := bufio.NewWriter(&cmdbuf)
   320  		var fake faker
   321  		fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   322  		c, err := NewClient(fake, "fake.host")
   323  		if err != nil {
   324  			t.Fatalf("NewClient: %v", err)
   325  		}
   326  		c.localName = "customhost"
   327  		err = nil
   328  
   329  		switch i {
   330  		case 0:
   331  			err = c.Hello("customhost")
   332  		case 1:
   333  			err = c.StartTLS(nil)
   334  			if err.Error() == "502 Not implemented" {
   335  				err = nil
   336  			}
   337  		case 2:
   338  			err = c.Verify("test@example.com")
   339  		case 3:
   340  			c.tls = true
   341  			c.serverName = "smtp.google.com"
   342  			err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
   343  		case 4:
   344  			err = c.Mail("test@example.com")
   345  		case 5:
   346  			ok, _ := c.Extension("feature")
   347  			if ok {
   348  				t.Errorf("Expected FEATURE not to be supported")
   349  			}
   350  		case 6:
   351  			err = c.Reset()
   352  		case 7:
   353  			err = c.Quit()
   354  		case 8:
   355  			err = c.Verify("test@example.com")
   356  			if err != nil {
   357  				err = c.Hello("customhost")
   358  				if err != nil {
   359  					t.Errorf("Want error, got none")
   360  				}
   361  			}
   362  		default:
   363  			t.Fatalf("Unhandled command")
   364  		}
   365  
   366  		if err != nil {
   367  			t.Errorf("Command %d failed: %v", i, err)
   368  		}
   369  
   370  		bcmdbuf.Flush()
   371  		actualcmds := cmdbuf.String()
   372  		if client != actualcmds {
   373  			t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   374  		}
   375  	}
   376  }
   377  
   378  var baseHelloServer = `220 hello world
   379  502 EH?
   380  250-mx.google.com at your service
   381  250 FEATURE
   382  `
   383  
   384  var helloServer = []string{
   385  	"",
   386  	"502 Not implemented\n",
   387  	"250 User is valid\n",
   388  	"235 Accepted\n",
   389  	"250 Sender ok\n",
   390  	"",
   391  	"250 Reset ok\n",
   392  	"221 Goodbye\n",
   393  	"250 Sender ok\n",
   394  }
   395  
   396  var baseHelloClient = `EHLO customhost
   397  HELO customhost
   398  `
   399  
   400  var helloClient = []string{
   401  	"",
   402  	"STARTTLS\n",
   403  	"VRFY test@example.com\n",
   404  	"AUTH PLAIN AHVzZXIAcGFzcw==\n",
   405  	"MAIL FROM:<test@example.com>\n",
   406  	"",
   407  	"RSET\n",
   408  	"QUIT\n",
   409  	"VRFY test@example.com\n",
   410  }
   411  
   412  func TestSendMail(t *testing.T) {
   413  	server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n")
   414  	client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n")
   415  	var cmdbuf bytes.Buffer
   416  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   417  	l, err := net.Listen("tcp", "127.0.0.1:0")
   418  	if err != nil {
   419  		t.Fatalf("Unable to to create listener: %v", err)
   420  	}
   421  	defer l.Close()
   422  
   423  	// prevent data race on bcmdbuf
   424  	var done = make(chan struct{})
   425  	go func(data []string) {
   426  
   427  		defer close(done)
   428  
   429  		conn, err := l.Accept()
   430  		if err != nil {
   431  			t.Errorf("Accept error: %v", err)
   432  			return
   433  		}
   434  		defer conn.Close()
   435  
   436  		tc := textproto.NewConn(conn)
   437  		for i := 0; i < len(data) && data[i] != ""; i++ {
   438  			tc.PrintfLine(data[i])
   439  			for len(data[i]) >= 4 && data[i][3] == '-' {
   440  				i++
   441  				tc.PrintfLine(data[i])
   442  			}
   443  			if data[i] == "221 Goodbye" {
   444  				return
   445  			}
   446  			read := false
   447  			for !read || data[i] == "354 Go ahead" {
   448  				msg, err := tc.ReadLine()
   449  				bcmdbuf.Write([]byte(msg + "\r\n"))
   450  				read = true
   451  				if err != nil {
   452  					t.Errorf("Read error: %v", err)
   453  					return
   454  				}
   455  				if data[i] == "354 Go ahead" && msg == "." {
   456  					break
   457  				}
   458  			}
   459  		}
   460  	}(strings.Split(server, "\r\n"))
   461  
   462  	err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com
   463  To: other@example.com
   464  Subject: SendMail test
   465  
   466  SendMail is working for me.
   467  `, "\n", "\r\n", -1)))
   468  
   469  	if err != nil {
   470  		t.Errorf("%v", err)
   471  	}
   472  
   473  	<-done
   474  	bcmdbuf.Flush()
   475  	actualcmds := cmdbuf.String()
   476  	if client != actualcmds {
   477  		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   478  	}
   479  }
   480  
   481  var sendMailServer = `220 hello world
   482  502 EH?
   483  250 mx.google.com at your service
   484  250 Sender ok
   485  250 Receiver ok
   486  354 Go ahead
   487  250 Data ok
   488  221 Goodbye
   489  `
   490  
   491  var sendMailClient = `EHLO localhost
   492  HELO localhost
   493  MAIL FROM:<test@example.com>
   494  RCPT TO:<other@example.com>
   495  DATA
   496  From: test@example.com
   497  To: other@example.com
   498  Subject: SendMail test
   499  
   500  SendMail is working for me.
   501  .
   502  QUIT
   503  `