...

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  	"fmt"
		13  	"internal/testenv"
		14  	"io"
		15  	"net"
		16  	"net/textproto"
		17  	"runtime"
		18  	"strings"
		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("[email protected]"); err == nil {
	 192  		t.Fatalf("MAIL should require authentication")
	 193  	}
	 194  
	 195  	if err := c.Verify("[email protected]"); err == nil {
	 196  		t.Fatalf("First VRFY: expected no verification")
	 197  	}
	 198  	if err := c.Verify("[email protected]>\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("[email protected]"); 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("[email protected]>\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("[email protected]>\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("[email protected]"); err != nil {
	 219  		t.Fatalf("MAIL failed: %s", err)
	 220  	}
	 221  	if err := c.Rcpt("[email protected]"); err != nil {
	 222  		t.Fatalf("RCPT failed: %s", err)
	 223  	}
	 224  	msg := `From: [email protected]
	 225  To: [email protected]
	 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:<[email protected]> BODY=8BITMIME
	 274  VRFY [email protected]
	 275  VRFY [email protected]
	 276  AUTH PLAIN AHVzZXIAcGFzcw==
	 277  MAIL FROM:<[email protected]> BODY=8BITMIME
	 278  RCPT TO:<[email protected]>
	 279  DATA
	 280  From: [email protected]
	 281  To: [email protected]
	 282  Subject: Hooray for Go
	 283  
	 284  Line 1
	 285  ..Leading dot line .
	 286  Goodbye.
	 287  .
	 288  QUIT
	 289  `
	 290  
	 291  func TestExtensions(t *testing.T) {
	 292  	fake := func(server string) (c *Client, bcmdbuf *bufio.Writer, cmdbuf *strings.Builder) {
	 293  		server = strings.Join(strings.Split(server, "\n"), "\r\n")
	 294  
	 295  		cmdbuf = &strings.Builder{}
	 296  		bcmdbuf = bufio.NewWriter(cmdbuf)
	 297  		var fake faker
	 298  		fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
	 299  		c = &Client{Text: textproto.NewConn(fake), localName: "localhost"}
	 300  
	 301  		return c, bcmdbuf, cmdbuf
	 302  	}
	 303  
	 304  	t.Run("helo", func(t *testing.T) {
	 305  		const (
	 306  			basicServer = `250 mx.google.com at your service
	 307  250 Sender OK
	 308  221 Goodbye
	 309  `
	 310  
	 311  			basicClient = `HELO localhost
	 312  MAIL FROM:<[email protected]>
	 313  QUIT
	 314  `
	 315  		)
	 316  
	 317  		c, bcmdbuf, cmdbuf := fake(basicServer)
	 318  
	 319  		if err := c.helo(); err != nil {
	 320  			t.Fatalf("HELO failed: %s", err)
	 321  		}
	 322  		c.didHello = true
	 323  		if err := c.Mail("[email protected]"); err != nil {
	 324  			t.Fatalf("MAIL FROM failed: %s", err)
	 325  		}
	 326  		if err := c.Quit(); err != nil {
	 327  			t.Fatalf("QUIT failed: %s", err)
	 328  		}
	 329  
	 330  		bcmdbuf.Flush()
	 331  		actualcmds := cmdbuf.String()
	 332  		client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
	 333  		if client != actualcmds {
	 334  			t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
	 335  		}
	 336  	})
	 337  
	 338  	t.Run("ehlo", func(t *testing.T) {
	 339  		const (
	 340  			basicServer = `250-mx.google.com at your service
	 341  250 SIZE 35651584
	 342  250 Sender OK
	 343  221 Goodbye
	 344  `
	 345  
	 346  			basicClient = `EHLO localhost
	 347  MAIL FROM:<[email protected]>
	 348  QUIT
	 349  `
	 350  		)
	 351  
	 352  		c, bcmdbuf, cmdbuf := fake(basicServer)
	 353  
	 354  		if err := c.Hello("localhost"); err != nil {
	 355  			t.Fatalf("EHLO failed: %s", err)
	 356  		}
	 357  		if ok, _ := c.Extension("8BITMIME"); ok {
	 358  			t.Fatalf("Shouldn't support 8BITMIME")
	 359  		}
	 360  		if ok, _ := c.Extension("SMTPUTF8"); ok {
	 361  			t.Fatalf("Shouldn't support SMTPUTF8")
	 362  		}
	 363  		if err := c.Mail("[email protected]"); err != nil {
	 364  			t.Fatalf("MAIL FROM failed: %s", err)
	 365  		}
	 366  		if err := c.Quit(); err != nil {
	 367  			t.Fatalf("QUIT failed: %s", err)
	 368  		}
	 369  
	 370  		bcmdbuf.Flush()
	 371  		actualcmds := cmdbuf.String()
	 372  		client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
	 373  		if client != actualcmds {
	 374  			t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
	 375  		}
	 376  	})
	 377  
	 378  	t.Run("ehlo 8bitmime", func(t *testing.T) {
	 379  		const (
	 380  			basicServer = `250-mx.google.com at your service
	 381  250-SIZE 35651584
	 382  250 8BITMIME
	 383  250 Sender OK
	 384  221 Goodbye
	 385  `
	 386  
	 387  			basicClient = `EHLO localhost
	 388  MAIL FROM:<[email protected]> BODY=8BITMIME
	 389  QUIT
	 390  `
	 391  		)
	 392  
	 393  		c, bcmdbuf, cmdbuf := fake(basicServer)
	 394  
	 395  		if err := c.Hello("localhost"); err != nil {
	 396  			t.Fatalf("EHLO failed: %s", err)
	 397  		}
	 398  		if ok, _ := c.Extension("8BITMIME"); !ok {
	 399  			t.Fatalf("Should support 8BITMIME")
	 400  		}
	 401  		if ok, _ := c.Extension("SMTPUTF8"); ok {
	 402  			t.Fatalf("Shouldn't support SMTPUTF8")
	 403  		}
	 404  		if err := c.Mail("[email protected]"); err != nil {
	 405  			t.Fatalf("MAIL FROM failed: %s", err)
	 406  		}
	 407  		if err := c.Quit(); err != nil {
	 408  			t.Fatalf("QUIT failed: %s", err)
	 409  		}
	 410  
	 411  		bcmdbuf.Flush()
	 412  		actualcmds := cmdbuf.String()
	 413  		client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
	 414  		if client != actualcmds {
	 415  			t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
	 416  		}
	 417  	})
	 418  
	 419  	t.Run("ehlo smtputf8", func(t *testing.T) {
	 420  		const (
	 421  			basicServer = `250-mx.google.com at your service
	 422  250-SIZE 35651584
	 423  250 SMTPUTF8
	 424  250 Sender OK
	 425  221 Goodbye
	 426  `
	 427  
	 428  			basicClient = `EHLO localhost
	 429  MAIL FROM:<user+📧@gmail.com> SMTPUTF8
	 430  QUIT
	 431  `
	 432  		)
	 433  
	 434  		c, bcmdbuf, cmdbuf := fake(basicServer)
	 435  
	 436  		if err := c.Hello("localhost"); err != nil {
	 437  			t.Fatalf("EHLO failed: %s", err)
	 438  		}
	 439  		if ok, _ := c.Extension("8BITMIME"); ok {
	 440  			t.Fatalf("Shouldn't support 8BITMIME")
	 441  		}
	 442  		if ok, _ := c.Extension("SMTPUTF8"); !ok {
	 443  			t.Fatalf("Should support SMTPUTF8")
	 444  		}
	 445  		if err := c.Mail("user+📧@gmail.com"); err != nil {
	 446  			t.Fatalf("MAIL FROM failed: %s", err)
	 447  		}
	 448  		if err := c.Quit(); err != nil {
	 449  			t.Fatalf("QUIT failed: %s", err)
	 450  		}
	 451  
	 452  		bcmdbuf.Flush()
	 453  		actualcmds := cmdbuf.String()
	 454  		client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
	 455  		if client != actualcmds {
	 456  			t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
	 457  		}
	 458  	})
	 459  
	 460  	t.Run("ehlo 8bitmime smtputf8", func(t *testing.T) {
	 461  		const (
	 462  			basicServer = `250-mx.google.com at your service
	 463  250-SIZE 35651584
	 464  250-8BITMIME
	 465  250 SMTPUTF8
	 466  250 Sender OK
	 467  221 Goodbye
	 468  	`
	 469  
	 470  			basicClient = `EHLO localhost
	 471  MAIL FROM:<user+📧@gmail.com> BODY=8BITMIME SMTPUTF8
	 472  QUIT
	 473  `
	 474  		)
	 475  
	 476  		c, bcmdbuf, cmdbuf := fake(basicServer)
	 477  
	 478  		if err := c.Hello("localhost"); err != nil {
	 479  			t.Fatalf("EHLO failed: %s", err)
	 480  		}
	 481  		c.didHello = true
	 482  		if ok, _ := c.Extension("8BITMIME"); !ok {
	 483  			t.Fatalf("Should support 8BITMIME")
	 484  		}
	 485  		if ok, _ := c.Extension("SMTPUTF8"); !ok {
	 486  			t.Fatalf("Should support SMTPUTF8")
	 487  		}
	 488  		if err := c.Mail("user+📧@gmail.com"); err != nil {
	 489  			t.Fatalf("MAIL FROM failed: %s", err)
	 490  		}
	 491  		if err := c.Quit(); err != nil {
	 492  			t.Fatalf("QUIT failed: %s", err)
	 493  		}
	 494  
	 495  		bcmdbuf.Flush()
	 496  		actualcmds := cmdbuf.String()
	 497  		client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
	 498  		if client != actualcmds {
	 499  			t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
	 500  		}
	 501  	})
	 502  }
	 503  
	 504  func TestNewClient(t *testing.T) {
	 505  	server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n")
	 506  	client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n")
	 507  
	 508  	var cmdbuf bytes.Buffer
	 509  	bcmdbuf := bufio.NewWriter(&cmdbuf)
	 510  	out := func() string {
	 511  		bcmdbuf.Flush()
	 512  		return cmdbuf.String()
	 513  	}
	 514  	var fake faker
	 515  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
	 516  	c, err := NewClient(fake, "fake.host")
	 517  	if err != nil {
	 518  		t.Fatalf("NewClient: %v\n(after %v)", err, out())
	 519  	}
	 520  	defer c.Close()
	 521  	if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
	 522  		t.Fatalf("Expected AUTH supported")
	 523  	}
	 524  	if ok, _ := c.Extension("DSN"); ok {
	 525  		t.Fatalf("Shouldn't support DSN")
	 526  	}
	 527  	if err := c.Quit(); err != nil {
	 528  		t.Fatalf("QUIT failed: %s", err)
	 529  	}
	 530  
	 531  	actualcmds := out()
	 532  	if client != actualcmds {
	 533  		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
	 534  	}
	 535  }
	 536  
	 537  var newClientServer = `220 hello world
	 538  250-mx.google.com at your service
	 539  250-SIZE 35651584
	 540  250-AUTH LOGIN PLAIN
	 541  250 8BITMIME
	 542  221 OK
	 543  `
	 544  
	 545  var newClientClient = `EHLO localhost
	 546  QUIT
	 547  `
	 548  
	 549  func TestNewClient2(t *testing.T) {
	 550  	server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n")
	 551  	client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n")
	 552  
	 553  	var cmdbuf bytes.Buffer
	 554  	bcmdbuf := bufio.NewWriter(&cmdbuf)
	 555  	var fake faker
	 556  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
	 557  	c, err := NewClient(fake, "fake.host")
	 558  	if err != nil {
	 559  		t.Fatalf("NewClient: %v", err)
	 560  	}
	 561  	defer c.Close()
	 562  	if ok, _ := c.Extension("DSN"); ok {
	 563  		t.Fatalf("Shouldn't support DSN")
	 564  	}
	 565  	if err := c.Quit(); err != nil {
	 566  		t.Fatalf("QUIT failed: %s", err)
	 567  	}
	 568  
	 569  	bcmdbuf.Flush()
	 570  	actualcmds := cmdbuf.String()
	 571  	if client != actualcmds {
	 572  		t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
	 573  	}
	 574  }
	 575  
	 576  var newClient2Server = `220 hello world
	 577  502 EH?
	 578  250-mx.google.com at your service
	 579  250-SIZE 35651584
	 580  250-AUTH LOGIN PLAIN
	 581  250 8BITMIME
	 582  221 OK
	 583  `
	 584  
	 585  var newClient2Client = `EHLO localhost
	 586  HELO localhost
	 587  QUIT
	 588  `
	 589  
	 590  func TestNewClientWithTLS(t *testing.T) {
	 591  	cert, err := tls.X509KeyPair(localhostCert, localhostKey)
	 592  	if err != nil {
	 593  		t.Fatalf("loadcert: %v", err)
	 594  	}
	 595  
	 596  	config := tls.Config{Certificates: []tls.Certificate{cert}}
	 597  
	 598  	ln, err := tls.Listen("tcp", "127.0.0.1:0", &config)
	 599  	if err != nil {
	 600  		ln, err = tls.Listen("tcp", "[::1]:0", &config)
	 601  		if err != nil {
	 602  			t.Fatalf("server: listen: %v", err)
	 603  		}
	 604  	}
	 605  
	 606  	go func() {
	 607  		conn, err := ln.Accept()
	 608  		if err != nil {
	 609  			t.Errorf("server: accept: %v", err)
	 610  			return
	 611  		}
	 612  		defer conn.Close()
	 613  
	 614  		_, err = conn.Write([]byte("220 SIGNS\r\n"))
	 615  		if err != nil {
	 616  			t.Errorf("server: write: %v", err)
	 617  			return
	 618  		}
	 619  	}()
	 620  
	 621  	config.InsecureSkipVerify = true
	 622  	conn, err := tls.Dial("tcp", ln.Addr().String(), &config)
	 623  	if err != nil {
	 624  		t.Fatalf("client: dial: %v", err)
	 625  	}
	 626  	defer conn.Close()
	 627  
	 628  	client, err := NewClient(conn, ln.Addr().String())
	 629  	if err != nil {
	 630  		t.Fatalf("smtp: newclient: %v", err)
	 631  	}
	 632  	if !client.tls {
	 633  		t.Errorf("client.tls Got: %t Expected: %t", client.tls, true)
	 634  	}
	 635  }
	 636  
	 637  func TestHello(t *testing.T) {
	 638  
	 639  	if len(helloServer) != len(helloClient) {
	 640  		t.Fatalf("Hello server and client size mismatch")
	 641  	}
	 642  
	 643  	for i := 0; i < len(helloServer); i++ {
	 644  		server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n")
	 645  		client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n")
	 646  		var cmdbuf bytes.Buffer
	 647  		bcmdbuf := bufio.NewWriter(&cmdbuf)
	 648  		var fake faker
	 649  		fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
	 650  		c, err := NewClient(fake, "fake.host")
	 651  		if err != nil {
	 652  			t.Fatalf("NewClient: %v", err)
	 653  		}
	 654  		defer c.Close()
	 655  		c.localName = "customhost"
	 656  		err = nil
	 657  
	 658  		switch i {
	 659  		case 0:
	 660  			err = c.Hello("hostinjection>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n")
	 661  			if err == nil {
	 662  				t.Errorf("Expected Hello to be rejected due to a message injection attempt")
	 663  			}
	 664  			err = c.Hello("customhost")
	 665  		case 1:
	 666  			err = c.StartTLS(nil)
	 667  			if err.Error() == "502 Not implemented" {
	 668  				err = nil
	 669  			}
	 670  		case 2:
	 671  			err = c.Verify("[email protected]")
	 672  		case 3:
	 673  			c.tls = true
	 674  			c.serverName = "smtp.google.com"
	 675  			err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
	 676  		case 4:
	 677  			err = c.Mail("[email protected]")
	 678  		case 5:
	 679  			ok, _ := c.Extension("feature")
	 680  			if ok {
	 681  				t.Errorf("Expected FEATURE not to be supported")
	 682  			}
	 683  		case 6:
	 684  			err = c.Reset()
	 685  		case 7:
	 686  			err = c.Quit()
	 687  		case 8:
	 688  			err = c.Verify("[email protected]")
	 689  			if err != nil {
	 690  				err = c.Hello("customhost")
	 691  				if err != nil {
	 692  					t.Errorf("Want error, got none")
	 693  				}
	 694  			}
	 695  		case 9:
	 696  			err = c.Noop()
	 697  		default:
	 698  			t.Fatalf("Unhandled command")
	 699  		}
	 700  
	 701  		if err != nil {
	 702  			t.Errorf("Command %d failed: %v", i, err)
	 703  		}
	 704  
	 705  		bcmdbuf.Flush()
	 706  		actualcmds := cmdbuf.String()
	 707  		if client != actualcmds {
	 708  			t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
	 709  		}
	 710  	}
	 711  }
	 712  
	 713  var baseHelloServer = `220 hello world
	 714  502 EH?
	 715  250-mx.google.com at your service
	 716  250 FEATURE
	 717  `
	 718  
	 719  var helloServer = []string{
	 720  	"",
	 721  	"502 Not implemented\n",
	 722  	"250 User is valid\n",
	 723  	"235 Accepted\n",
	 724  	"250 Sender ok\n",
	 725  	"",
	 726  	"250 Reset ok\n",
	 727  	"221 Goodbye\n",
	 728  	"250 Sender ok\n",
	 729  	"250 ok\n",
	 730  }
	 731  
	 732  var baseHelloClient = `EHLO customhost
	 733  HELO customhost
	 734  `
	 735  
	 736  var helloClient = []string{
	 737  	"",
	 738  	"STARTTLS\n",
	 739  	"VRFY [email protected]\n",
	 740  	"AUTH PLAIN AHVzZXIAcGFzcw==\n",
	 741  	"MAIL FROM:<[email protected]>\n",
	 742  	"",
	 743  	"RSET\n",
	 744  	"QUIT\n",
	 745  	"VRFY [email protected]\n",
	 746  	"NOOP\n",
	 747  }
	 748  
	 749  func TestSendMail(t *testing.T) {
	 750  	server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n")
	 751  	client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n")
	 752  	var cmdbuf bytes.Buffer
	 753  	bcmdbuf := bufio.NewWriter(&cmdbuf)
	 754  	l, err := net.Listen("tcp", "127.0.0.1:0")
	 755  	if err != nil {
	 756  		t.Fatalf("Unable to create listener: %v", err)
	 757  	}
	 758  	defer l.Close()
	 759  
	 760  	// prevent data race on bcmdbuf
	 761  	var done = make(chan struct{})
	 762  	go func(data []string) {
	 763  
	 764  		defer close(done)
	 765  
	 766  		conn, err := l.Accept()
	 767  		if err != nil {
	 768  			t.Errorf("Accept error: %v", err)
	 769  			return
	 770  		}
	 771  		defer conn.Close()
	 772  
	 773  		tc := textproto.NewConn(conn)
	 774  		for i := 0; i < len(data) && data[i] != ""; i++ {
	 775  			tc.PrintfLine(data[i])
	 776  			for len(data[i]) >= 4 && data[i][3] == '-' {
	 777  				i++
	 778  				tc.PrintfLine(data[i])
	 779  			}
	 780  			if data[i] == "221 Goodbye" {
	 781  				return
	 782  			}
	 783  			read := false
	 784  			for !read || data[i] == "354 Go ahead" {
	 785  				msg, err := tc.ReadLine()
	 786  				bcmdbuf.Write([]byte(msg + "\r\n"))
	 787  				read = true
	 788  				if err != nil {
	 789  					t.Errorf("Read error: %v", err)
	 790  					return
	 791  				}
	 792  				if data[i] == "354 Go ahead" && msg == "." {
	 793  					break
	 794  				}
	 795  			}
	 796  		}
	 797  	}(strings.Split(server, "\r\n"))
	 798  
	 799  	err = SendMail(l.Addr().String(), nil, "[email protected]", []string{"[email protected]>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n"}, []byte(strings.Replace(`From: [email protected]
	 800  To: [email protected]
	 801  Subject: SendMail test
	 802  
	 803  SendMail is working for me.
	 804  `, "\n", "\r\n", -1)))
	 805  	if err == nil {
	 806  		t.Errorf("Expected SendMail to be rejected due to a message injection attempt")
	 807  	}
	 808  
	 809  	err = SendMail(l.Addr().String(), nil, "[email protected]", []string{"[email protected]"}, []byte(strings.Replace(`From: [email protected]
	 810  To: [email protected]
	 811  Subject: SendMail test
	 812  
	 813  SendMail is working for me.
	 814  `, "\n", "\r\n", -1)))
	 815  
	 816  	if err != nil {
	 817  		t.Errorf("%v", err)
	 818  	}
	 819  
	 820  	<-done
	 821  	bcmdbuf.Flush()
	 822  	actualcmds := cmdbuf.String()
	 823  	if client != actualcmds {
	 824  		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
	 825  	}
	 826  }
	 827  
	 828  var sendMailServer = `220 hello world
	 829  502 EH?
	 830  250 mx.google.com at your service
	 831  250 Sender ok
	 832  250 Receiver ok
	 833  354 Go ahead
	 834  250 Data ok
	 835  221 Goodbye
	 836  `
	 837  
	 838  var sendMailClient = `EHLO localhost
	 839  HELO localhost
	 840  MAIL FROM:<[email protected]>
	 841  RCPT TO:<[email protected]>
	 842  DATA
	 843  From: [email protected]
	 844  To: [email protected]
	 845  Subject: SendMail test
	 846  
	 847  SendMail is working for me.
	 848  .
	 849  QUIT
	 850  `
	 851  
	 852  func TestSendMailWithAuth(t *testing.T) {
	 853  	l, err := net.Listen("tcp", "127.0.0.1:0")
	 854  	if err != nil {
	 855  		t.Fatalf("Unable to create listener: %v", err)
	 856  	}
	 857  	defer l.Close()
	 858  
	 859  	errCh := make(chan error)
	 860  	go func() {
	 861  		defer close(errCh)
	 862  		conn, err := l.Accept()
	 863  		if err != nil {
	 864  			errCh <- fmt.Errorf("Accept: %v", err)
	 865  			return
	 866  		}
	 867  		defer conn.Close()
	 868  
	 869  		tc := textproto.NewConn(conn)
	 870  		tc.PrintfLine("220 hello world")
	 871  		msg, err := tc.ReadLine()
	 872  		if err != nil {
	 873  			errCh <- fmt.Errorf("ReadLine error: %v", err)
	 874  			return
	 875  		}
	 876  		const wantMsg = "EHLO localhost"
	 877  		if msg != wantMsg {
	 878  			errCh <- fmt.Errorf("unexpected response %q; want %q", msg, wantMsg)
	 879  			return
	 880  		}
	 881  		err = tc.PrintfLine("250 mx.google.com at your service")
	 882  		if err != nil {
	 883  			errCh <- fmt.Errorf("PrintfLine: %v", err)
	 884  			return
	 885  		}
	 886  	}()
	 887  
	 888  	err = SendMail(l.Addr().String(), PlainAuth("", "user", "pass", "smtp.google.com"), "[email protected]", []string{"[email protected]"}, []byte(strings.Replace(`From: [email protected]
	 889  To: [email protected]
	 890  Subject: SendMail test
	 891  
	 892  SendMail is working for me.
	 893  `, "\n", "\r\n", -1)))
	 894  	if err == nil {
	 895  		t.Error("SendMail: Server doesn't support AUTH, expected to get an error, but got none ")
	 896  	}
	 897  	if err.Error() != "smtp: server doesn't support AUTH" {
	 898  		t.Errorf("Expected: smtp: server doesn't support AUTH, got: %s", err)
	 899  	}
	 900  	err = <-errCh
	 901  	if err != nil {
	 902  		t.Fatalf("server error: %v", err)
	 903  	}
	 904  }
	 905  
	 906  func TestAuthFailed(t *testing.T) {
	 907  	server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n")
	 908  	client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n")
	 909  	var cmdbuf bytes.Buffer
	 910  	bcmdbuf := bufio.NewWriter(&cmdbuf)
	 911  	var fake faker
	 912  	fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
	 913  	c, err := NewClient(fake, "fake.host")
	 914  	if err != nil {
	 915  		t.Fatalf("NewClient: %v", err)
	 916  	}
	 917  	defer c.Close()
	 918  
	 919  	c.tls = true
	 920  	c.serverName = "smtp.google.com"
	 921  	err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
	 922  
	 923  	if err == nil {
	 924  		t.Error("Auth: expected error; got none")
	 925  	} else if err.Error() != "535 Invalid credentials\nplease see www.example.com" {
	 926  		t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com")
	 927  	}
	 928  
	 929  	bcmdbuf.Flush()
	 930  	actualcmds := cmdbuf.String()
	 931  	if client != actualcmds {
	 932  		t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
	 933  	}
	 934  }
	 935  
	 936  var authFailedServer = `220 hello world
	 937  250-mx.google.com at your service
	 938  250 AUTH LOGIN PLAIN
	 939  535-Invalid credentials
	 940  535 please see www.example.com
	 941  221 Goodbye
	 942  `
	 943  
	 944  var authFailedClient = `EHLO localhost
	 945  AUTH PLAIN AHVzZXIAcGFzcw==
	 946  *
	 947  QUIT
	 948  `
	 949  
	 950  func TestTLSClient(t *testing.T) {
	 951  	if (runtime.GOOS == "freebsd" && runtime.GOARCH == "amd64") || runtime.GOOS == "js" {
	 952  		testenv.SkipFlaky(t, 19229)
	 953  	}
	 954  	ln := newLocalListener(t)
	 955  	defer ln.Close()
	 956  	errc := make(chan error)
	 957  	go func() {
	 958  		errc <- sendMail(ln.Addr().String())
	 959  	}()
	 960  	conn, err := ln.Accept()
	 961  	if err != nil {
	 962  		t.Fatalf("failed to accept connection: %v", err)
	 963  	}
	 964  	defer conn.Close()
	 965  	if err := serverHandle(conn, t); err != nil {
	 966  		t.Fatalf("failed to handle connection: %v", err)
	 967  	}
	 968  	if err := <-errc; err != nil {
	 969  		t.Fatalf("client error: %v", err)
	 970  	}
	 971  }
	 972  
	 973  func TestTLSConnState(t *testing.T) {
	 974  	ln := newLocalListener(t)
	 975  	defer ln.Close()
	 976  	clientDone := make(chan bool)
	 977  	serverDone := make(chan bool)
	 978  	go func() {
	 979  		defer close(serverDone)
	 980  		c, err := ln.Accept()
	 981  		if err != nil {
	 982  			t.Errorf("Server accept: %v", err)
	 983  			return
	 984  		}
	 985  		defer c.Close()
	 986  		if err := serverHandle(c, t); err != nil {
	 987  			t.Errorf("server error: %v", err)
	 988  		}
	 989  	}()
	 990  	go func() {
	 991  		defer close(clientDone)
	 992  		c, err := Dial(ln.Addr().String())
	 993  		if err != nil {
	 994  			t.Errorf("Client dial: %v", err)
	 995  			return
	 996  		}
	 997  		defer c.Quit()
	 998  		cfg := &tls.Config{ServerName: "example.com"}
	 999  		testHookStartTLS(cfg) // set the RootCAs
	1000  		if err := c.StartTLS(cfg); err != nil {
	1001  			t.Errorf("StartTLS: %v", err)
	1002  			return
	1003  		}
	1004  		cs, ok := c.TLSConnectionState()
	1005  		if !ok {
	1006  			t.Errorf("TLSConnectionState returned ok == false; want true")
	1007  			return
	1008  		}
	1009  		if cs.Version == 0 || !cs.HandshakeComplete {
	1010  			t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs)
	1011  		}
	1012  	}()
	1013  	<-clientDone
	1014  	<-serverDone
	1015  }
	1016  
	1017  func newLocalListener(t *testing.T) net.Listener {
	1018  	ln, err := net.Listen("tcp", "127.0.0.1:0")
	1019  	if err != nil {
	1020  		ln, err = net.Listen("tcp6", "[::1]:0")
	1021  	}
	1022  	if err != nil {
	1023  		t.Fatal(err)
	1024  	}
	1025  	return ln
	1026  }
	1027  
	1028  type smtpSender struct {
	1029  	w io.Writer
	1030  }
	1031  
	1032  func (s smtpSender) send(f string) {
	1033  	s.w.Write([]byte(f + "\r\n"))
	1034  }
	1035  
	1036  // smtp server, finely tailored to deal with our own client only!
	1037  func serverHandle(c net.Conn, t *testing.T) error {
	1038  	send := smtpSender{c}.send
	1039  	send("220 127.0.0.1 ESMTP service ready")
	1040  	s := bufio.NewScanner(c)
	1041  	for s.Scan() {
	1042  		switch s.Text() {
	1043  		case "EHLO localhost":
	1044  			send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
	1045  			send("250-STARTTLS")
	1046  			send("250 Ok")
	1047  		case "STARTTLS":
	1048  			send("220 Go ahead")
	1049  			keypair, err := tls.X509KeyPair(localhostCert, localhostKey)
	1050  			if err != nil {
	1051  				return err
	1052  			}
	1053  			config := &tls.Config{Certificates: []tls.Certificate{keypair}}
	1054  			c = tls.Server(c, config)
	1055  			defer c.Close()
	1056  			return serverHandleTLS(c, t)
	1057  		default:
	1058  			t.Fatalf("unrecognized command: %q", s.Text())
	1059  		}
	1060  	}
	1061  	return s.Err()
	1062  }
	1063  
	1064  func serverHandleTLS(c net.Conn, t *testing.T) error {
	1065  	send := smtpSender{c}.send
	1066  	s := bufio.NewScanner(c)
	1067  	for s.Scan() {
	1068  		switch s.Text() {
	1069  		case "EHLO localhost":
	1070  			send("250 Ok")
	1071  		case "MAIL FROM:<[email protected]>":
	1072  			send("250 Ok")
	1073  		case "RCPT TO:<[email protected]>":
	1074  			send("250 Ok")
	1075  		case "DATA":
	1076  			send("354 send the mail data, end with .")
	1077  			send("250 Ok")
	1078  		case "Subject: test":
	1079  		case "":
	1080  		case "howdy!":
	1081  		case ".":
	1082  		case "QUIT":
	1083  			send("221 127.0.0.1 Service closing transmission channel")
	1084  			return nil
	1085  		default:
	1086  			t.Fatalf("unrecognized command during TLS: %q", s.Text())
	1087  		}
	1088  	}
	1089  	return s.Err()
	1090  }
	1091  
	1092  func init() {
	1093  	testRootCAs := x509.NewCertPool()
	1094  	testRootCAs.AppendCertsFromPEM(localhostCert)
	1095  	testHookStartTLS = func(config *tls.Config) {
	1096  		config.RootCAs = testRootCAs
	1097  	}
	1098  }
	1099  
	1100  func sendMail(hostPort string) error {
	1101  	from := "[email protected]"
	1102  	to := []string{"[email protected]"}
	1103  	return SendMail(hostPort, nil, from, to, []byte("Subject: test\n\nhowdy!"))
	1104  }
	1105  
	1106  // localhostCert is a PEM-encoded TLS cert generated from src/crypto/tls:
	1107  // go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com \
	1108  // 		--ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
	1109  var localhostCert = []byte(`
	1110  -----BEGIN CERTIFICATE-----
	1111  MIICFDCCAX2gAwIBAgIRAK0xjnaPuNDSreeXb+z+0u4wDQYJKoZIhvcNAQELBQAw
	1112  EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
	1113  MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
	1114  gYkCgYEA0nFbQQuOWsjbGtejcpWz153OlziZM4bVjJ9jYruNw5n2Ry6uYQAffhqa
	1115  JOInCmmcVe2siJglsyH9aRh6vKiobBbIUXXUU1ABd56ebAzlt0LobLlx7pZEMy30
	1116  LqIi9E6zmL3YvdGzpYlkFRnRrqwEtWYbGBf3znO250S56CCWH2UCAwEAAaNoMGYw
	1117  DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF
	1118  MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA
	1119  AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAbZtDS2dVuBYvb+MnolWnCNqvw1w5Gtgi
	1120  NmvQQPOMgM3m+oQSCPRTNGSg25e1Qbo7bgQDv8ZTnq8FgOJ/rbkyERw2JckkHpD4
	1121  n4qcK27WkEDBtQFlPihIM8hLIuzWoi/9wygiElTy/tVL3y7fGCvY2/k1KBthtZGF
	1122  tN8URjVmyEo=
	1123  -----END CERTIFICATE-----`)
	1124  
	1125  // localhostKey is the private key for localhostCert.
	1126  var localhostKey = []byte(testingKey(`
	1127  -----BEGIN RSA TESTING KEY-----
	1128  MIICXgIBAAKBgQDScVtBC45ayNsa16NylbPXnc6XOJkzhtWMn2Niu43DmfZHLq5h
	1129  AB9+Gpok4icKaZxV7ayImCWzIf1pGHq8qKhsFshRddRTUAF3np5sDOW3QuhsuXHu
	1130  lkQzLfQuoiL0TrOYvdi90bOliWQVGdGurAS1ZhsYF/fOc7bnRLnoIJYfZQIDAQAB
	1131  AoGBAMst7OgpKyFV6c3JwyI/jWqxDySL3caU+RuTTBaodKAUx2ZEmNJIlx9eudLA
	1132  kucHvoxsM/eRxlxkhdFxdBcwU6J+zqooTnhu/FE3jhrT1lPrbhfGhyKnUrB0KKMM
	1133  VY3IQZyiehpxaeXAwoAou6TbWoTpl9t8ImAqAMY8hlULCUqlAkEA+9+Ry5FSYK/m
	1134  542LujIcCaIGoG1/Te6Sxr3hsPagKC2rH20rDLqXwEedSFOpSS0vpzlPAzy/6Rbb
	1135  PHTJUhNdwwJBANXkA+TkMdbJI5do9/mn//U0LfrCR9NkcoYohxfKz8JuhgRQxzF2
	1136  6jpo3q7CdTuuRixLWVfeJzcrAyNrVcBq87cCQFkTCtOMNC7fZnCTPUv+9q1tcJyB
	1137  vNjJu3yvoEZeIeuzouX9TJE21/33FaeDdsXbRhQEj23cqR38qFHsF1qAYNMCQQDP
	1138  QXLEiJoClkR2orAmqjPLVhR3t2oB3INcnEjLNSq8LHyQEfXyaFfu4U9l5+fRPL2i
	1139  jiC0k/9L5dHUsF0XZothAkEA23ddgRs+Id/HxtojqqUT27B8MT/IGNrYsp4DvS/c
	1140  qgkeluku4GjxRlDMBuXk94xOBEinUs+p/hwP1Alll80Tpg==
	1141  -----END RSA TESTING KEY-----`))
	1142  
	1143  func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
	1144  

View as plain text