mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			739 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			739 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
// 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-----`)
 |