github.com/varialus/godfly@v0.0.0-20130904042352-1934f9f095ab/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  	defer c.Close()
   242  	if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
   243  		t.Fatalf("Expected AUTH supported")
   244  	}
   245  	if ok, _ := c.Extension("DSN"); ok {
   246  		t.Fatalf("Shouldn't support DSN")
   247  	}
   248  	if err := c.Quit(); err != nil {
   249  		t.Fatalf("QUIT failed: %s", err)
   250  	}
   251  
   252  	actualcmds := out()
   253  	if client != actualcmds {
   254  		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   255  	}
   256  }
   257  
   258  var newClientServer = `220 hello world
   259  250-mx.google.com at your service
   260  250-SIZE 35651584
   261  250-AUTH LOGIN PLAIN
   262  250 8BITMIME
   263  221 OK
   264  `
   265  
   266  var newClientClient = `EHLO localhost
   267  QUIT
   268  `
   269  
   270  func TestNewClient2(t *testing.T) {
   271  	server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n")
   272  	client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n")
   273  
   274  	var cmdbuf bytes.Buffer
   275  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   276  	var fake faker
   277  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   278  	c, err := NewClient(fake, "fake.host")
   279  	if err != nil {
   280  		t.Fatalf("NewClient: %v", err)
   281  	}
   282  	defer c.Close()
   283  	if ok, _ := c.Extension("DSN"); ok {
   284  		t.Fatalf("Shouldn't support DSN")
   285  	}
   286  	if err := c.Quit(); err != nil {
   287  		t.Fatalf("QUIT failed: %s", err)
   288  	}
   289  
   290  	bcmdbuf.Flush()
   291  	actualcmds := cmdbuf.String()
   292  	if client != actualcmds {
   293  		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   294  	}
   295  }
   296  
   297  var newClient2Server = `220 hello world
   298  502 EH?
   299  250-mx.google.com at your service
   300  250-SIZE 35651584
   301  250-AUTH LOGIN PLAIN
   302  250 8BITMIME
   303  221 OK
   304  `
   305  
   306  var newClient2Client = `EHLO localhost
   307  HELO localhost
   308  QUIT
   309  `
   310  
   311  func TestHello(t *testing.T) {
   312  
   313  	if len(helloServer) != len(helloClient) {
   314  		t.Fatalf("Hello server and client size mismatch")
   315  	}
   316  
   317  	for i := 0; i < len(helloServer); i++ {
   318  		server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n")
   319  		client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n")
   320  		var cmdbuf bytes.Buffer
   321  		bcmdbuf := bufio.NewWriter(&cmdbuf)
   322  		var fake faker
   323  		fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   324  		c, err := NewClient(fake, "fake.host")
   325  		if err != nil {
   326  			t.Fatalf("NewClient: %v", err)
   327  		}
   328  		defer c.Close()
   329  		c.localName = "customhost"
   330  		err = nil
   331  
   332  		switch i {
   333  		case 0:
   334  			err = c.Hello("customhost")
   335  		case 1:
   336  			err = c.StartTLS(nil)
   337  			if err.Error() == "502 Not implemented" {
   338  				err = nil
   339  			}
   340  		case 2:
   341  			err = c.Verify("test@example.com")
   342  		case 3:
   343  			c.tls = true
   344  			c.serverName = "smtp.google.com"
   345  			err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
   346  		case 4:
   347  			err = c.Mail("test@example.com")
   348  		case 5:
   349  			ok, _ := c.Extension("feature")
   350  			if ok {
   351  				t.Errorf("Expected FEATURE not to be supported")
   352  			}
   353  		case 6:
   354  			err = c.Reset()
   355  		case 7:
   356  			err = c.Quit()
   357  		case 8:
   358  			err = c.Verify("test@example.com")
   359  			if err != nil {
   360  				err = c.Hello("customhost")
   361  				if err != nil {
   362  					t.Errorf("Want error, got none")
   363  				}
   364  			}
   365  		default:
   366  			t.Fatalf("Unhandled command")
   367  		}
   368  
   369  		if err != nil {
   370  			t.Errorf("Command %d failed: %v", i, err)
   371  		}
   372  
   373  		bcmdbuf.Flush()
   374  		actualcmds := cmdbuf.String()
   375  		if client != actualcmds {
   376  			t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   377  		}
   378  	}
   379  }
   380  
   381  var baseHelloServer = `220 hello world
   382  502 EH?
   383  250-mx.google.com at your service
   384  250 FEATURE
   385  `
   386  
   387  var helloServer = []string{
   388  	"",
   389  	"502 Not implemented\n",
   390  	"250 User is valid\n",
   391  	"235 Accepted\n",
   392  	"250 Sender ok\n",
   393  	"",
   394  	"250 Reset ok\n",
   395  	"221 Goodbye\n",
   396  	"250 Sender ok\n",
   397  }
   398  
   399  var baseHelloClient = `EHLO customhost
   400  HELO customhost
   401  `
   402  
   403  var helloClient = []string{
   404  	"",
   405  	"STARTTLS\n",
   406  	"VRFY test@example.com\n",
   407  	"AUTH PLAIN AHVzZXIAcGFzcw==\n",
   408  	"MAIL FROM:<test@example.com>\n",
   409  	"",
   410  	"RSET\n",
   411  	"QUIT\n",
   412  	"VRFY test@example.com\n",
   413  }
   414  
   415  func TestSendMail(t *testing.T) {
   416  	server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n")
   417  	client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n")
   418  	var cmdbuf bytes.Buffer
   419  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   420  	l, err := net.Listen("tcp", "127.0.0.1:0")
   421  	if err != nil {
   422  		t.Fatalf("Unable to to create listener: %v", err)
   423  	}
   424  	defer l.Close()
   425  
   426  	// prevent data race on bcmdbuf
   427  	var done = make(chan struct{})
   428  	go func(data []string) {
   429  
   430  		defer close(done)
   431  
   432  		conn, err := l.Accept()
   433  		if err != nil {
   434  			t.Errorf("Accept error: %v", err)
   435  			return
   436  		}
   437  		defer conn.Close()
   438  
   439  		tc := textproto.NewConn(conn)
   440  		for i := 0; i < len(data) && data[i] != ""; i++ {
   441  			tc.PrintfLine(data[i])
   442  			for len(data[i]) >= 4 && data[i][3] == '-' {
   443  				i++
   444  				tc.PrintfLine(data[i])
   445  			}
   446  			if data[i] == "221 Goodbye" {
   447  				return
   448  			}
   449  			read := false
   450  			for !read || data[i] == "354 Go ahead" {
   451  				msg, err := tc.ReadLine()
   452  				bcmdbuf.Write([]byte(msg + "\r\n"))
   453  				read = true
   454  				if err != nil {
   455  					t.Errorf("Read error: %v", err)
   456  					return
   457  				}
   458  				if data[i] == "354 Go ahead" && msg == "." {
   459  					break
   460  				}
   461  			}
   462  		}
   463  	}(strings.Split(server, "\r\n"))
   464  
   465  	err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com
   466  To: other@example.com
   467  Subject: SendMail test
   468  
   469  SendMail is working for me.
   470  `, "\n", "\r\n", -1)))
   471  
   472  	if err != nil {
   473  		t.Errorf("%v", err)
   474  	}
   475  
   476  	<-done
   477  	bcmdbuf.Flush()
   478  	actualcmds := cmdbuf.String()
   479  	if client != actualcmds {
   480  		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   481  	}
   482  }
   483  
   484  var sendMailServer = `220 hello world
   485  502 EH?
   486  250 mx.google.com at your service
   487  250 Sender ok
   488  250 Receiver ok
   489  354 Go ahead
   490  250 Data ok
   491  221 Goodbye
   492  `
   493  
   494  var sendMailClient = `EHLO localhost
   495  HELO localhost
   496  MAIL FROM:<test@example.com>
   497  RCPT TO:<other@example.com>
   498  DATA
   499  From: test@example.com
   500  To: other@example.com
   501  Subject: SendMail test
   502  
   503  SendMail is working for me.
   504  .
   505  QUIT
   506  `
   507  
   508  func TestAuthFailed(t *testing.T) {
   509  	server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n")
   510  	client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n")
   511  	var cmdbuf bytes.Buffer
   512  	bcmdbuf := bufio.NewWriter(&cmdbuf)
   513  	var fake faker
   514  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
   515  	c, err := NewClient(fake, "fake.host")
   516  	if err != nil {
   517  		t.Fatalf("NewClient: %v", err)
   518  	}
   519  	defer c.Close()
   520  
   521  	c.tls = true
   522  	c.serverName = "smtp.google.com"
   523  	err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
   524  
   525  	if err == nil {
   526  		t.Error("Auth: expected error; got none")
   527  	} else if err.Error() != "535 Invalid credentials\nplease see www.example.com" {
   528  		t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com")
   529  	}
   530  
   531  	bcmdbuf.Flush()
   532  	actualcmds := cmdbuf.String()
   533  	if client != actualcmds {
   534  		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
   535  	}
   536  }
   537  
   538  var authFailedServer = `220 hello world
   539  250-mx.google.com at your service
   540  250 AUTH LOGIN PLAIN
   541  535-Invalid credentials
   542  535 please see www.example.com
   543  221 Goodbye
   544  `
   545  
   546  var authFailedClient = `EHLO localhost
   547  AUTH PLAIN AHVzZXIAcGFzcw==
   548  *
   549  QUIT
   550  `