/third_party/gofrontend/libgo/go/net/smtp/smtp_test.go
Go | 738 lines | 656 code | 68 blank | 14 comment | 164 complexity | 5513dac1021ddcb2baebaa1b8566d4f8 MD5 | raw file
Possible License(s): BSD-3-Clause, MIT
- // Copyright 2010 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package smtp
- import (
- "bufio"
- "bytes"
- "crypto/tls"
- "crypto/x509"
- "io"
- "net"
- "net/textproto"
- "strings"
- "testing"
- "time"
- )
- type authTest struct {
- auth Auth
- challenges []string
- name string
- responses []string
- }
- var authTests = []authTest{
- {PlainAuth("", "user", "pass", "testserver"), []string{}, "PLAIN", []string{"\x00user\x00pass"}},
- {PlainAuth("foo", "bar", "baz", "testserver"), []string{}, "PLAIN", []string{"foo\x00bar\x00baz"}},
- {CRAMMD5Auth("user", "pass"), []string{"<123456.1322876914@testserver>"}, "CRAM-MD5", []string{"", "user 287eb355114cf5c471c26a875f1ca4ae"}},
- }
- func TestAuth(t *testing.T) {
- testLoop:
- for i, test := range authTests {
- name, resp, err := test.auth.Start(&ServerInfo{"testserver", true, nil})
- if name != test.name {
- t.Errorf("#%d got name %s, expected %s", i, name, test.name)
- }
- if !bytes.Equal(resp, []byte(test.responses[0])) {
- t.Errorf("#%d got response %s, expected %s", i, resp, test.responses[0])
- }
- if err != nil {
- t.Errorf("#%d error: %s", i, err)
- }
- for j := range test.challenges {
- challenge := []byte(test.challenges[j])
- expected := []byte(test.responses[j+1])
- resp, err := test.auth.Next(challenge, true)
- if err != nil {
- t.Errorf("#%d error: %s", i, err)
- continue testLoop
- }
- if !bytes.Equal(resp, expected) {
- t.Errorf("#%d got %s, expected %s", i, resp, expected)
- continue testLoop
- }
- }
- }
- }
- func TestAuthPlain(t *testing.T) {
- auth := PlainAuth("foo", "bar", "baz", "servername")
- tests := []struct {
- server *ServerInfo
- err string
- }{
- {
- server: &ServerInfo{Name: "servername", TLS: true},
- },
- {
- // Okay; explicitly advertised by server.
- server: &ServerInfo{Name: "servername", Auth: []string{"PLAIN"}},
- },
- {
- server: &ServerInfo{Name: "servername", Auth: []string{"CRAM-MD5"}},
- err: "unencrypted connection",
- },
- {
- server: &ServerInfo{Name: "attacker", TLS: true},
- err: "wrong host name",
- },
- }
- for i, tt := range tests {
- _, _, err := auth.Start(tt.server)
- got := ""
- if err != nil {
- got = err.Error()
- }
- if got != tt.err {
- t.Errorf("%d. got error = %q; want %q", i, got, tt.err)
- }
- }
- }
- type faker struct {
- io.ReadWriter
- }
- func (f faker) Close() error { return nil }
- func (f faker) LocalAddr() net.Addr { return nil }
- func (f faker) RemoteAddr() net.Addr { return nil }
- func (f faker) SetDeadline(time.Time) error { return nil }
- func (f faker) SetReadDeadline(time.Time) error { return nil }
- func (f faker) SetWriteDeadline(time.Time) error { return nil }
- func TestBasic(t *testing.T) {
- server := strings.Join(strings.Split(basicServer, "\n"), "\r\n")
- client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
- var cmdbuf bytes.Buffer
- bcmdbuf := bufio.NewWriter(&cmdbuf)
- var fake faker
- fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
- c := &Client{Text: textproto.NewConn(fake), localName: "localhost"}
- if err := c.helo(); err != nil {
- t.Fatalf("HELO failed: %s", err)
- }
- if err := c.ehlo(); err == nil {
- t.Fatalf("Expected first EHLO to fail")
- }
- if err := c.ehlo(); err != nil {
- t.Fatalf("Second EHLO failed: %s", err)
- }
- c.didHello = true
- if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
- t.Fatalf("Expected AUTH supported")
- }
- if ok, _ := c.Extension("DSN"); ok {
- t.Fatalf("Shouldn't support DSN")
- }
- if err := c.Mail("user@gmail.com"); err == nil {
- t.Fatalf("MAIL should require authentication")
- }
- if err := c.Verify("user1@gmail.com"); err == nil {
- t.Fatalf("First VRFY: expected no verification")
- }
- if err := c.Verify("user2@gmail.com"); err != nil {
- t.Fatalf("Second VRFY: expected verification, got %s", err)
- }
- // fake TLS so authentication won't complain
- c.tls = true
- c.serverName = "smtp.google.com"
- if err := c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")); err != nil {
- t.Fatalf("AUTH failed: %s", err)
- }
- if err := c.Mail("user@gmail.com"); err != nil {
- t.Fatalf("MAIL failed: %s", err)
- }
- if err := c.Rcpt("golang-nuts@googlegroups.com"); err != nil {
- t.Fatalf("RCPT failed: %s", err)
- }
- msg := `From: user@gmail.com
- To: golang-nuts@googlegroups.com
- Subject: Hooray for Go
- Line 1
- .Leading dot line .
- Goodbye.`
- w, err := c.Data()
- if err != nil {
- t.Fatalf("DATA failed: %s", err)
- }
- if _, err := w.Write([]byte(msg)); err != nil {
- t.Fatalf("Data write failed: %s", err)
- }
- if err := w.Close(); err != nil {
- t.Fatalf("Bad data response: %s", err)
- }
- if err := c.Quit(); err != nil {
- t.Fatalf("QUIT failed: %s", err)
- }
- bcmdbuf.Flush()
- actualcmds := cmdbuf.String()
- if client != actualcmds {
- t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
- }
- }
- var basicServer = `250 mx.google.com at your service
- 502 Unrecognized command.
- 250-mx.google.com at your service
- 250-SIZE 35651584
- 250-AUTH LOGIN PLAIN
- 250 8BITMIME
- 530 Authentication required
- 252 Send some mail, I'll try my best
- 250 User is valid
- 235 Accepted
- 250 Sender OK
- 250 Receiver OK
- 354 Go ahead
- 250 Data OK
- 221 OK
- `
- var basicClient = `HELO localhost
- EHLO localhost
- EHLO localhost
- MAIL FROM:<user@gmail.com> BODY=8BITMIME
- VRFY user1@gmail.com
- VRFY user2@gmail.com
- AUTH PLAIN AHVzZXIAcGFzcw==
- MAIL FROM:<user@gmail.com> BODY=8BITMIME
- RCPT TO:<golang-nuts@googlegroups.com>
- DATA
- From: user@gmail.com
- To: golang-nuts@googlegroups.com
- Subject: Hooray for Go
- Line 1
- ..Leading dot line .
- Goodbye.
- .
- QUIT
- `
- func TestNewClient(t *testing.T) {
- server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n")
- client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n")
- var cmdbuf bytes.Buffer
- bcmdbuf := bufio.NewWriter(&cmdbuf)
- out := func() string {
- bcmdbuf.Flush()
- return cmdbuf.String()
- }
- var fake faker
- fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
- c, err := NewClient(fake, "fake.host")
- if err != nil {
- t.Fatalf("NewClient: %v\n(after %v)", err, out())
- }
- defer c.Close()
- if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
- t.Fatalf("Expected AUTH supported")
- }
- if ok, _ := c.Extension("DSN"); ok {
- t.Fatalf("Shouldn't support DSN")
- }
- if err := c.Quit(); err != nil {
- t.Fatalf("QUIT failed: %s", err)
- }
- actualcmds := out()
- if client != actualcmds {
- t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
- }
- }
- var newClientServer = `220 hello world
- 250-mx.google.com at your service
- 250-SIZE 35651584
- 250-AUTH LOGIN PLAIN
- 250 8BITMIME
- 221 OK
- `
- var newClientClient = `EHLO localhost
- QUIT
- `
- func TestNewClient2(t *testing.T) {
- server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n")
- client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n")
- var cmdbuf bytes.Buffer
- bcmdbuf := bufio.NewWriter(&cmdbuf)
- var fake faker
- fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
- c, err := NewClient(fake, "fake.host")
- if err != nil {
- t.Fatalf("NewClient: %v", err)
- }
- defer c.Close()
- if ok, _ := c.Extension("DSN"); ok {
- t.Fatalf("Shouldn't support DSN")
- }
- if err := c.Quit(); err != nil {
- t.Fatalf("QUIT failed: %s", err)
- }
- bcmdbuf.Flush()
- actualcmds := cmdbuf.String()
- if client != actualcmds {
- t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
- }
- }
- var newClient2Server = `220 hello world
- 502 EH?
- 250-mx.google.com at your service
- 250-SIZE 35651584
- 250-AUTH LOGIN PLAIN
- 250 8BITMIME
- 221 OK
- `
- var newClient2Client = `EHLO localhost
- HELO localhost
- QUIT
- `
- func TestHello(t *testing.T) {
- if len(helloServer) != len(helloClient) {
- t.Fatalf("Hello server and client size mismatch")
- }
- for i := 0; i < len(helloServer); i++ {
- server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n")
- client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n")
- var cmdbuf bytes.Buffer
- bcmdbuf := bufio.NewWriter(&cmdbuf)
- var fake faker
- fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
- c, err := NewClient(fake, "fake.host")
- if err != nil {
- t.Fatalf("NewClient: %v", err)
- }
- defer c.Close()
- c.localName = "customhost"
- err = nil
- switch i {
- case 0:
- err = c.Hello("customhost")
- case 1:
- err = c.StartTLS(nil)
- if err.Error() == "502 Not implemented" {
- err = nil
- }
- case 2:
- err = c.Verify("test@example.com")
- case 3:
- c.tls = true
- c.serverName = "smtp.google.com"
- err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
- case 4:
- err = c.Mail("test@example.com")
- case 5:
- ok, _ := c.Extension("feature")
- if ok {
- t.Errorf("Expected FEATURE not to be supported")
- }
- case 6:
- err = c.Reset()
- case 7:
- err = c.Quit()
- case 8:
- err = c.Verify("test@example.com")
- if err != nil {
- err = c.Hello("customhost")
- if err != nil {
- t.Errorf("Want error, got none")
- }
- }
- default:
- t.Fatalf("Unhandled command")
- }
- if err != nil {
- t.Errorf("Command %d failed: %v", i, err)
- }
- bcmdbuf.Flush()
- actualcmds := cmdbuf.String()
- if client != actualcmds {
- t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
- }
- }
- }
- var baseHelloServer = `220 hello world
- 502 EH?
- 250-mx.google.com at your service
- 250 FEATURE
- `
- var helloServer = []string{
- "",
- "502 Not implemented\n",
- "250 User is valid\n",
- "235 Accepted\n",
- "250 Sender ok\n",
- "",
- "250 Reset ok\n",
- "221 Goodbye\n",
- "250 Sender ok\n",
- }
- var baseHelloClient = `EHLO customhost
- HELO customhost
- `
- var helloClient = []string{
- "",
- "STARTTLS\n",
- "VRFY test@example.com\n",
- "AUTH PLAIN AHVzZXIAcGFzcw==\n",
- "MAIL FROM:<test@example.com>\n",
- "",
- "RSET\n",
- "QUIT\n",
- "VRFY test@example.com\n",
- }
- func TestSendMail(t *testing.T) {
- server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n")
- client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n")
- var cmdbuf bytes.Buffer
- bcmdbuf := bufio.NewWriter(&cmdbuf)
- l, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- t.Fatalf("Unable to to create listener: %v", err)
- }
- defer l.Close()
- // prevent data race on bcmdbuf
- var done = make(chan struct{})
- go func(data []string) {
- defer close(done)
- conn, err := l.Accept()
- if err != nil {
- t.Errorf("Accept error: %v", err)
- return
- }
- defer conn.Close()
- tc := textproto.NewConn(conn)
- for i := 0; i < len(data) && data[i] != ""; i++ {
- tc.PrintfLine(data[i])
- for len(data[i]) >= 4 && data[i][3] == '-' {
- i++
- tc.PrintfLine(data[i])
- }
- if data[i] == "221 Goodbye" {
- return
- }
- read := false
- for !read || data[i] == "354 Go ahead" {
- msg, err := tc.ReadLine()
- bcmdbuf.Write([]byte(msg + "\r\n"))
- read = true
- if err != nil {
- t.Errorf("Read error: %v", err)
- return
- }
- if data[i] == "354 Go ahead" && msg == "." {
- break
- }
- }
- }
- }(strings.Split(server, "\r\n"))
- err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com
- To: other@example.com
- Subject: SendMail test
- SendMail is working for me.
- `, "\n", "\r\n", -1)))
- if err != nil {
- t.Errorf("%v", err)
- }
- <-done
- bcmdbuf.Flush()
- actualcmds := cmdbuf.String()
- if client != actualcmds {
- t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
- }
- }
- var sendMailServer = `220 hello world
- 502 EH?
- 250 mx.google.com at your service
- 250 Sender ok
- 250 Receiver ok
- 354 Go ahead
- 250 Data ok
- 221 Goodbye
- `
- var sendMailClient = `EHLO localhost
- HELO localhost
- MAIL FROM:<test@example.com>
- RCPT TO:<other@example.com>
- DATA
- From: test@example.com
- To: other@example.com
- Subject: SendMail test
- SendMail is working for me.
- .
- QUIT
- `
- func TestAuthFailed(t *testing.T) {
- server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n")
- client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n")
- var cmdbuf bytes.Buffer
- bcmdbuf := bufio.NewWriter(&cmdbuf)
- var fake faker
- fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
- c, err := NewClient(fake, "fake.host")
- if err != nil {
- t.Fatalf("NewClient: %v", err)
- }
- defer c.Close()
- c.tls = true
- c.serverName = "smtp.google.com"
- err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
- if err == nil {
- t.Error("Auth: expected error; got none")
- } else if err.Error() != "535 Invalid credentials\nplease see www.example.com" {
- t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com")
- }
- bcmdbuf.Flush()
- actualcmds := cmdbuf.String()
- if client != actualcmds {
- t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
- }
- }
- var authFailedServer = `220 hello world
- 250-mx.google.com at your service
- 250 AUTH LOGIN PLAIN
- 535-Invalid credentials
- 535 please see www.example.com
- 221 Goodbye
- `
- var authFailedClient = `EHLO localhost
- AUTH PLAIN AHVzZXIAcGFzcw==
- *
- QUIT
- `
- func TestTLSClient(t *testing.T) {
- ln := newLocalListener(t)
- defer ln.Close()
- errc := make(chan error)
- go func() {
- errc <- sendMail(ln.Addr().String())
- }()
- conn, err := ln.Accept()
- if err != nil {
- t.Fatalf("failed to accept connection: %v", err)
- }
- defer conn.Close()
- if err := serverHandle(conn, t); err != nil {
- t.Fatalf("failed to handle connection: %v", err)
- }
- if err := <-errc; err != nil {
- t.Fatalf("client error: %v", err)
- }
- }
- func TestTLSConnState(t *testing.T) {
- ln := newLocalListener(t)
- defer ln.Close()
- clientDone := make(chan bool)
- serverDone := make(chan bool)
- go func() {
- defer close(serverDone)
- c, err := ln.Accept()
- if err != nil {
- t.Errorf("Server accept: %v", err)
- return
- }
- defer c.Close()
- if err := serverHandle(c, t); err != nil {
- t.Errorf("server error: %v", err)
- }
- }()
- go func() {
- defer close(clientDone)
- c, err := Dial(ln.Addr().String())
- if err != nil {
- t.Errorf("Client dial: %v", err)
- return
- }
- defer c.Quit()
- cfg := &tls.Config{ServerName: "example.com"}
- testHookStartTLS(cfg) // set the RootCAs
- if err := c.StartTLS(cfg); err != nil {
- t.Errorf("StartTLS: %v", err)
- return
- }
- cs, ok := c.TLSConnectionState()
- if !ok {
- t.Errorf("TLSConnectionState returned ok == false; want true")
- return
- }
- if cs.Version == 0 || !cs.HandshakeComplete {
- t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs)
- }
- }()
- <-clientDone
- <-serverDone
- }
- func newLocalListener(t *testing.T) net.Listener {
- ln, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- ln, err = net.Listen("tcp6", "[::1]:0")
- }
- if err != nil {
- t.Fatal(err)
- }
- return ln
- }
- type smtpSender struct {
- w io.Writer
- }
- func (s smtpSender) send(f string) {
- s.w.Write([]byte(f + "\r\n"))
- }
- // smtp server, finely tailored to deal with our own client only!
- func serverHandle(c net.Conn, t *testing.T) error {
- send := smtpSender{c}.send
- send("220 127.0.0.1 ESMTP service ready")
- s := bufio.NewScanner(c)
- for s.Scan() {
- switch s.Text() {
- case "EHLO localhost":
- send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
- send("250-STARTTLS")
- send("250 Ok")
- case "STARTTLS":
- send("220 Go ahead")
- keypair, err := tls.X509KeyPair(localhostCert, localhostKey)
- if err != nil {
- return err
- }
- config := &tls.Config{Certificates: []tls.Certificate{keypair}}
- c = tls.Server(c, config)
- defer c.Close()
- return serverHandleTLS(c, t)
- default:
- t.Fatalf("unrecognized command: %q", s.Text())
- }
- }
- return s.Err()
- }
- func serverHandleTLS(c net.Conn, t *testing.T) error {
- send := smtpSender{c}.send
- s := bufio.NewScanner(c)
- for s.Scan() {
- switch s.Text() {
- case "EHLO localhost":
- send("250 Ok")
- case "MAIL FROM:<joe1@example.com>":
- send("250 Ok")
- case "RCPT TO:<joe2@example.com>":
- send("250 Ok")
- case "DATA":
- send("354 send the mail data, end with .")
- send("250 Ok")
- case "Subject: test":
- case "":
- case "howdy!":
- case ".":
- case "QUIT":
- send("221 127.0.0.1 Service closing transmission channel")
- return nil
- default:
- t.Fatalf("unrecognized command during TLS: %q", s.Text())
- }
- }
- return s.Err()
- }
- func init() {
- testRootCAs := x509.NewCertPool()
- testRootCAs.AppendCertsFromPEM(localhostCert)
- testHookStartTLS = func(config *tls.Config) {
- config.RootCAs = testRootCAs
- }
- }
- func sendMail(hostPort string) error {
- host, _, err := net.SplitHostPort(hostPort)
- if err != nil {
- return err
- }
- auth := PlainAuth("", "", "", host)
- from := "joe1@example.com"
- to := []string{"joe2@example.com"}
- return SendMail(hostPort, auth, from, to, []byte("Subject: test\n\nhowdy!"))
- }
- // (copied from net/http/httptest)
- // localhostCert is a PEM-encoded TLS cert with SAN IPs
- // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
- // of ASN.1 time).
- // generated from src/crypto/tls:
- // 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
- var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
- MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
- bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj
- bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa
- IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA
- AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud
- EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA
- AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk
- Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA==
- -----END CERTIFICATE-----`)
- // localhostKey is the private key for localhostCert.
- var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
- MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0
- 0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV
- NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d
- AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW
- MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD
- EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA
- 1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE=
- -----END RSA PRIVATE KEY-----`)