summaryrefslogtreecommitdiff
path: root/src/pkg/net/smtp
diff options
context:
space:
mode:
authorTianon Gravi <admwiggin@gmail.com>2015-01-15 11:54:00 -0700
committerTianon Gravi <admwiggin@gmail.com>2015-01-15 11:54:00 -0700
commitf154da9e12608589e8d5f0508f908a0c3e88a1bb (patch)
treef8255d51e10c6f1e0ed69702200b966c9556a431 /src/pkg/net/smtp
parent8d8329ed5dfb9622c82a9fbec6fd99a580f9c9f6 (diff)
downloadgolang-upstream/1.4.tar.gz
Imported Upstream version 1.4upstream/1.4
Diffstat (limited to 'src/pkg/net/smtp')
-rw-r--r--src/pkg/net/smtp/auth.go107
-rw-r--r--src/pkg/net/smtp/example_test.go61
-rw-r--r--src/pkg/net/smtp/smtp.go357
-rw-r--r--src/pkg/net/smtp/smtp_test.go694
4 files changed, 0 insertions, 1219 deletions
diff --git a/src/pkg/net/smtp/auth.go b/src/pkg/net/smtp/auth.go
deleted file mode 100644
index 3f1339ebc..000000000
--- a/src/pkg/net/smtp/auth.go
+++ /dev/null
@@ -1,107 +0,0 @@
-// 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 (
- "crypto/hmac"
- "crypto/md5"
- "errors"
- "fmt"
-)
-
-// Auth is implemented by an SMTP authentication mechanism.
-type Auth interface {
- // Start begins an authentication with a server.
- // It returns the name of the authentication protocol
- // and optionally data to include in the initial AUTH message
- // sent to the server. It can return proto == "" to indicate
- // that the authentication should be skipped.
- // If it returns a non-nil error, the SMTP client aborts
- // the authentication attempt and closes the connection.
- Start(server *ServerInfo) (proto string, toServer []byte, err error)
-
- // Next continues the authentication. The server has just sent
- // the fromServer data. If more is true, the server expects a
- // response, which Next should return as toServer; otherwise
- // Next should return toServer == nil.
- // If Next returns a non-nil error, the SMTP client aborts
- // the authentication attempt and closes the connection.
- Next(fromServer []byte, more bool) (toServer []byte, err error)
-}
-
-// ServerInfo records information about an SMTP server.
-type ServerInfo struct {
- Name string // SMTP server name
- TLS bool // using TLS, with valid certificate for Name
- Auth []string // advertised authentication mechanisms
-}
-
-type plainAuth struct {
- identity, username, password string
- host string
-}
-
-// PlainAuth returns an Auth that implements the PLAIN authentication
-// mechanism as defined in RFC 4616.
-// The returned Auth uses the given username and password to authenticate
-// on TLS connections to host and act as identity. Usually identity will be
-// left blank to act as username.
-func PlainAuth(identity, username, password, host string) Auth {
- return &plainAuth{identity, username, password, host}
-}
-
-func (a *plainAuth) Start(server *ServerInfo) (string, []byte, error) {
- if !server.TLS {
- advertised := false
- for _, mechanism := range server.Auth {
- if mechanism == "PLAIN" {
- advertised = true
- break
- }
- }
- if !advertised {
- return "", nil, errors.New("unencrypted connection")
- }
- }
- if server.Name != a.host {
- return "", nil, errors.New("wrong host name")
- }
- resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password)
- return "PLAIN", resp, nil
-}
-
-func (a *plainAuth) Next(fromServer []byte, more bool) ([]byte, error) {
- if more {
- // We've already sent everything.
- return nil, errors.New("unexpected server challenge")
- }
- return nil, nil
-}
-
-type cramMD5Auth struct {
- username, secret string
-}
-
-// CRAMMD5Auth returns an Auth that implements the CRAM-MD5 authentication
-// mechanism as defined in RFC 2195.
-// The returned Auth uses the given username and secret to authenticate
-// to the server using the challenge-response mechanism.
-func CRAMMD5Auth(username, secret string) Auth {
- return &cramMD5Auth{username, secret}
-}
-
-func (a *cramMD5Auth) Start(server *ServerInfo) (string, []byte, error) {
- return "CRAM-MD5", nil, nil
-}
-
-func (a *cramMD5Auth) Next(fromServer []byte, more bool) ([]byte, error) {
- if more {
- d := hmac.New(md5.New, []byte(a.secret))
- d.Write(fromServer)
- s := make([]byte, 0, d.Size())
- return []byte(fmt.Sprintf("%s %x", a.username, d.Sum(s))), nil
- }
- return nil, nil
-}
diff --git a/src/pkg/net/smtp/example_test.go b/src/pkg/net/smtp/example_test.go
deleted file mode 100644
index d551e365a..000000000
--- a/src/pkg/net/smtp/example_test.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2013 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_test
-
-import (
- "fmt"
- "log"
- "net/smtp"
-)
-
-func Example() {
- // Connect to the remote SMTP server.
- c, err := smtp.Dial("mail.example.com:25")
- if err != nil {
- log.Fatal(err)
- }
-
- // Set the sender and recipient first
- if err := c.Mail("sender@example.org"); err != nil {
- log.Fatal(err)
- }
- if err := c.Rcpt("recipient@example.net"); err != nil {
- log.Fatal(err)
- }
-
- // Send the email body.
- wc, err := c.Data()
- if err != nil {
- log.Fatal(err)
- }
- _, err = fmt.Fprintf(wc, "This is the email body")
- if err != nil {
- log.Fatal(err)
- }
- err = wc.Close()
- if err != nil {
- log.Fatal(err)
- }
-
- // Send the QUIT command and close the connection.
- err = c.Quit()
- if err != nil {
- log.Fatal(err)
- }
-}
-
-func ExamplePlainAuth() {
- // Set up authentication information.
- auth := smtp.PlainAuth("", "user@example.com", "password", "mail.example.com")
-
- // Connect to the server, authenticate, set the sender and recipient,
- // and send the email all in one step.
- to := []string{"recipient@example.net"}
- msg := []byte("This is the email body.")
- err := smtp.SendMail("mail.example.com:25", auth, "sender@example.org", to, msg)
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/src/pkg/net/smtp/smtp.go b/src/pkg/net/smtp/smtp.go
deleted file mode 100644
index 87dea442c..000000000
--- a/src/pkg/net/smtp/smtp.go
+++ /dev/null
@@ -1,357 +0,0 @@
-// 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 implements the Simple Mail Transfer Protocol as defined in RFC 5321.
-// It also implements the following extensions:
-// 8BITMIME RFC 1652
-// AUTH RFC 2554
-// STARTTLS RFC 3207
-// Additional extensions may be handled by clients.
-package smtp
-
-import (
- "crypto/tls"
- "encoding/base64"
- "errors"
- "io"
- "net"
- "net/textproto"
- "strings"
-)
-
-// A Client represents a client connection to an SMTP server.
-type Client struct {
- // Text is the textproto.Conn used by the Client. It is exported to allow for
- // clients to add extensions.
- Text *textproto.Conn
- // keep a reference to the connection so it can be used to create a TLS
- // connection later
- conn net.Conn
- // whether the Client is using TLS
- tls bool
- serverName string
- // map of supported extensions
- ext map[string]string
- // supported auth mechanisms
- auth []string
- localName string // the name to use in HELO/EHLO
- didHello bool // whether we've said HELO/EHLO
- helloError error // the error from the hello
-}
-
-// Dial returns a new Client connected to an SMTP server at addr.
-// The addr must include a port number.
-func Dial(addr string) (*Client, error) {
- conn, err := net.Dial("tcp", addr)
- if err != nil {
- return nil, err
- }
- host, _, _ := net.SplitHostPort(addr)
- return NewClient(conn, host)
-}
-
-// NewClient returns a new Client using an existing connection and host as a
-// server name to be used when authenticating.
-func NewClient(conn net.Conn, host string) (*Client, error) {
- text := textproto.NewConn(conn)
- _, _, err := text.ReadResponse(220)
- if err != nil {
- text.Close()
- return nil, err
- }
- c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
- return c, nil
-}
-
-// Close closes the connection.
-func (c *Client) Close() error {
- return c.Text.Close()
-}
-
-// hello runs a hello exchange if needed.
-func (c *Client) hello() error {
- if !c.didHello {
- c.didHello = true
- err := c.ehlo()
- if err != nil {
- c.helloError = c.helo()
- }
- }
- return c.helloError
-}
-
-// Hello sends a HELO or EHLO to the server as the given host name.
-// Calling this method is only necessary if the client needs control
-// over the host name used. The client will introduce itself as "localhost"
-// automatically otherwise. If Hello is called, it must be called before
-// any of the other methods.
-func (c *Client) Hello(localName string) error {
- if c.didHello {
- return errors.New("smtp: Hello called after other methods")
- }
- c.localName = localName
- return c.hello()
-}
-
-// cmd is a convenience function that sends a command and returns the response
-func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
- id, err := c.Text.Cmd(format, args...)
- if err != nil {
- return 0, "", err
- }
- c.Text.StartResponse(id)
- defer c.Text.EndResponse(id)
- code, msg, err := c.Text.ReadResponse(expectCode)
- return code, msg, err
-}
-
-// helo sends the HELO greeting to the server. It should be used only when the
-// server does not support ehlo.
-func (c *Client) helo() error {
- c.ext = nil
- _, _, err := c.cmd(250, "HELO %s", c.localName)
- return err
-}
-
-// ehlo sends the EHLO (extended hello) greeting to the server. It
-// should be the preferred greeting for servers that support it.
-func (c *Client) ehlo() error {
- _, msg, err := c.cmd(250, "EHLO %s", c.localName)
- if err != nil {
- return err
- }
- ext := make(map[string]string)
- extList := strings.Split(msg, "\n")
- if len(extList) > 1 {
- extList = extList[1:]
- for _, line := range extList {
- args := strings.SplitN(line, " ", 2)
- if len(args) > 1 {
- ext[args[0]] = args[1]
- } else {
- ext[args[0]] = ""
- }
- }
- }
- if mechs, ok := ext["AUTH"]; ok {
- c.auth = strings.Split(mechs, " ")
- }
- c.ext = ext
- return err
-}
-
-// StartTLS sends the STARTTLS command and encrypts all further communication.
-// Only servers that advertise the STARTTLS extension support this function.
-func (c *Client) StartTLS(config *tls.Config) error {
- if err := c.hello(); err != nil {
- return err
- }
- _, _, err := c.cmd(220, "STARTTLS")
- if err != nil {
- return err
- }
- c.conn = tls.Client(c.conn, config)
- c.Text = textproto.NewConn(c.conn)
- c.tls = true
- return c.ehlo()
-}
-
-// Verify checks the validity of an email address on the server.
-// If Verify returns nil, the address is valid. A non-nil return
-// does not necessarily indicate an invalid address. Many servers
-// will not verify addresses for security reasons.
-func (c *Client) Verify(addr string) error {
- if err := c.hello(); err != nil {
- return err
- }
- _, _, err := c.cmd(250, "VRFY %s", addr)
- return err
-}
-
-// Auth authenticates a client using the provided authentication mechanism.
-// A failed authentication closes the connection.
-// Only servers that advertise the AUTH extension support this function.
-func (c *Client) Auth(a Auth) error {
- if err := c.hello(); err != nil {
- return err
- }
- encoding := base64.StdEncoding
- mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth})
- if err != nil {
- c.Quit()
- return err
- }
- resp64 := make([]byte, encoding.EncodedLen(len(resp)))
- encoding.Encode(resp64, resp)
- code, msg64, err := c.cmd(0, "AUTH %s %s", mech, resp64)
- for err == nil {
- var msg []byte
- switch code {
- case 334:
- msg, err = encoding.DecodeString(msg64)
- case 235:
- // the last message isn't base64 because it isn't a challenge
- msg = []byte(msg64)
- default:
- err = &textproto.Error{Code: code, Msg: msg64}
- }
- if err == nil {
- resp, err = a.Next(msg, code == 334)
- }
- if err != nil {
- // abort the AUTH
- c.cmd(501, "*")
- c.Quit()
- break
- }
- if resp == nil {
- break
- }
- resp64 = make([]byte, encoding.EncodedLen(len(resp)))
- encoding.Encode(resp64, resp)
- code, msg64, err = c.cmd(0, string(resp64))
- }
- return err
-}
-
-// Mail issues a MAIL command to the server using the provided email address.
-// If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME
-// parameter.
-// This initiates a mail transaction and is followed by one or more Rcpt calls.
-func (c *Client) Mail(from string) error {
- if err := c.hello(); err != nil {
- return err
- }
- cmdStr := "MAIL FROM:<%s>"
- if c.ext != nil {
- if _, ok := c.ext["8BITMIME"]; ok {
- cmdStr += " BODY=8BITMIME"
- }
- }
- _, _, err := c.cmd(250, cmdStr, from)
- return err
-}
-
-// Rcpt issues a RCPT command to the server using the provided email address.
-// A call to Rcpt must be preceded by a call to Mail and may be followed by
-// a Data call or another Rcpt call.
-func (c *Client) Rcpt(to string) error {
- _, _, err := c.cmd(25, "RCPT TO:<%s>", to)
- return err
-}
-
-type dataCloser struct {
- c *Client
- io.WriteCloser
-}
-
-func (d *dataCloser) Close() error {
- d.WriteCloser.Close()
- _, _, err := d.c.Text.ReadResponse(250)
- return err
-}
-
-// Data issues a DATA command to the server and returns a writer that
-// can be used to write the data. The caller should close the writer
-// before calling any more methods on c.
-// A call to Data must be preceded by one or more calls to Rcpt.
-func (c *Client) Data() (io.WriteCloser, error) {
- _, _, err := c.cmd(354, "DATA")
- if err != nil {
- return nil, err
- }
- return &dataCloser{c, c.Text.DotWriter()}, nil
-}
-
-var testHookStartTLS func(*tls.Config) // nil, except for tests
-
-// SendMail connects to the server at addr, switches to TLS if
-// possible, authenticates with the optional mechanism a if possible,
-// and then sends an email from address from, to addresses to, with
-// message msg.
-func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
- c, err := Dial(addr)
- if err != nil {
- return err
- }
- defer c.Close()
- if err = c.hello(); err != nil {
- return err
- }
- if ok, _ := c.Extension("STARTTLS"); ok {
- config := &tls.Config{ServerName: c.serverName}
- if testHookStartTLS != nil {
- testHookStartTLS(config)
- }
- if err = c.StartTLS(config); err != nil {
- return err
- }
- }
- if a != nil && c.ext != nil {
- if _, ok := c.ext["AUTH"]; ok {
- if err = c.Auth(a); err != nil {
- return err
- }
- }
- }
- if err = c.Mail(from); err != nil {
- return err
- }
- for _, addr := range to {
- if err = c.Rcpt(addr); err != nil {
- return err
- }
- }
- w, err := c.Data()
- if err != nil {
- return err
- }
- _, err = w.Write(msg)
- if err != nil {
- return err
- }
- err = w.Close()
- if err != nil {
- return err
- }
- return c.Quit()
-}
-
-// Extension reports whether an extension is support by the server.
-// The extension name is case-insensitive. If the extension is supported,
-// Extension also returns a string that contains any parameters the
-// server specifies for the extension.
-func (c *Client) Extension(ext string) (bool, string) {
- if err := c.hello(); err != nil {
- return false, ""
- }
- if c.ext == nil {
- return false, ""
- }
- ext = strings.ToUpper(ext)
- param, ok := c.ext[ext]
- return ok, param
-}
-
-// Reset sends the RSET command to the server, aborting the current mail
-// transaction.
-func (c *Client) Reset() error {
- if err := c.hello(); err != nil {
- return err
- }
- _, _, err := c.cmd(250, "RSET")
- return err
-}
-
-// Quit sends the QUIT command and closes the connection to the server.
-func (c *Client) Quit() error {
- if err := c.hello(); err != nil {
- return err
- }
- _, _, err := c.cmd(221, "QUIT")
- if err != nil {
- return err
- }
- return c.Text.Close()
-}
diff --git a/src/pkg/net/smtp/smtp_test.go b/src/pkg/net/smtp/smtp_test.go
deleted file mode 100644
index 3fba1ea5a..000000000
--- a/src/pkg/net/smtp/smtp_test.go
+++ /dev/null
@@ -1,694 +0,0 @@
-// 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 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/pkg/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-----`)