github.com/bgentry/go@v0.0.0-20150121062915-6cf5a733d54d/src/net/smtp/smtp.go (about)

     1  // Copyright 2010 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package smtp implements the Simple Mail Transfer Protocol as defined in RFC 5321.
     6  // It also implements the following extensions:
     7  //	8BITMIME  RFC 1652
     8  //	AUTH      RFC 2554
     9  //	STARTTLS  RFC 3207
    10  // Additional extensions may be handled by clients.
    11  package smtp
    12  
    13  import (
    14  	"crypto/tls"
    15  	"encoding/base64"
    16  	"errors"
    17  	"io"
    18  	"net"
    19  	"net/textproto"
    20  	"strings"
    21  )
    22  
    23  // A Client represents a client connection to an SMTP server.
    24  type Client struct {
    25  	// Text is the textproto.Conn used by the Client. It is exported to allow for
    26  	// clients to add extensions.
    27  	Text *textproto.Conn
    28  	// keep a reference to the connection so it can be used to create a TLS
    29  	// connection later
    30  	conn net.Conn
    31  	// whether the Client is using TLS
    32  	tls        bool
    33  	serverName string
    34  	// map of supported extensions
    35  	ext map[string]string
    36  	// supported auth mechanisms
    37  	auth       []string
    38  	localName  string // the name to use in HELO/EHLO
    39  	didHello   bool   // whether we've said HELO/EHLO
    40  	helloError error  // the error from the hello
    41  }
    42  
    43  // Dial returns a new Client connected to an SMTP server at addr.
    44  // The addr must include a port number.
    45  func Dial(addr string) (*Client, error) {
    46  	conn, err := net.Dial("tcp", addr)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	host, _, _ := net.SplitHostPort(addr)
    51  	return NewClient(conn, host)
    52  }
    53  
    54  // NewClient returns a new Client using an existing connection and host as a
    55  // server name to be used when authenticating.
    56  func NewClient(conn net.Conn, host string) (*Client, error) {
    57  	text := textproto.NewConn(conn)
    58  	_, _, err := text.ReadResponse(220)
    59  	if err != nil {
    60  		text.Close()
    61  		return nil, err
    62  	}
    63  	c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
    64  	return c, nil
    65  }
    66  
    67  // Close closes the connection.
    68  func (c *Client) Close() error {
    69  	return c.Text.Close()
    70  }
    71  
    72  // hello runs a hello exchange if needed.
    73  func (c *Client) hello() error {
    74  	if !c.didHello {
    75  		c.didHello = true
    76  		err := c.ehlo()
    77  		if err != nil {
    78  			c.helloError = c.helo()
    79  		}
    80  	}
    81  	return c.helloError
    82  }
    83  
    84  // Hello sends a HELO or EHLO to the server as the given host name.
    85  // Calling this method is only necessary if the client needs control
    86  // over the host name used.  The client will introduce itself as "localhost"
    87  // automatically otherwise.  If Hello is called, it must be called before
    88  // any of the other methods.
    89  func (c *Client) Hello(localName string) error {
    90  	if c.didHello {
    91  		return errors.New("smtp: Hello called after other methods")
    92  	}
    93  	c.localName = localName
    94  	return c.hello()
    95  }
    96  
    97  // cmd is a convenience function that sends a command and returns the response
    98  func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
    99  	id, err := c.Text.Cmd(format, args...)
   100  	if err != nil {
   101  		return 0, "", err
   102  	}
   103  	c.Text.StartResponse(id)
   104  	defer c.Text.EndResponse(id)
   105  	code, msg, err := c.Text.ReadResponse(expectCode)
   106  	return code, msg, err
   107  }
   108  
   109  // helo sends the HELO greeting to the server. It should be used only when the
   110  // server does not support ehlo.
   111  func (c *Client) helo() error {
   112  	c.ext = nil
   113  	_, _, err := c.cmd(250, "HELO %s", c.localName)
   114  	return err
   115  }
   116  
   117  // ehlo sends the EHLO (extended hello) greeting to the server. It
   118  // should be the preferred greeting for servers that support it.
   119  func (c *Client) ehlo() error {
   120  	_, msg, err := c.cmd(250, "EHLO %s", c.localName)
   121  	if err != nil {
   122  		return err
   123  	}
   124  	ext := make(map[string]string)
   125  	extList := strings.Split(msg, "\n")
   126  	if len(extList) > 1 {
   127  		extList = extList[1:]
   128  		for _, line := range extList {
   129  			args := strings.SplitN(line, " ", 2)
   130  			if len(args) > 1 {
   131  				ext[args[0]] = args[1]
   132  			} else {
   133  				ext[args[0]] = ""
   134  			}
   135  		}
   136  	}
   137  	if mechs, ok := ext["AUTH"]; ok {
   138  		c.auth = strings.Split(mechs, " ")
   139  	}
   140  	c.ext = ext
   141  	return err
   142  }
   143  
   144  // StartTLS sends the STARTTLS command and encrypts all further communication.
   145  // Only servers that advertise the STARTTLS extension support this function.
   146  func (c *Client) StartTLS(config *tls.Config) error {
   147  	if err := c.hello(); err != nil {
   148  		return err
   149  	}
   150  	_, _, err := c.cmd(220, "STARTTLS")
   151  	if err != nil {
   152  		return err
   153  	}
   154  	c.conn = tls.Client(c.conn, config)
   155  	c.Text = textproto.NewConn(c.conn)
   156  	c.tls = true
   157  	return c.ehlo()
   158  }
   159  
   160  // TLSConnectionState returns the client's TLS connection state.
   161  // The return values are their zero values if StartTLS did
   162  // not succeed.
   163  func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool) {
   164  	tc, ok := c.conn.(*tls.Conn)
   165  	if !ok {
   166  		return
   167  	}
   168  	return tc.ConnectionState(), true
   169  }
   170  
   171  // Verify checks the validity of an email address on the server.
   172  // If Verify returns nil, the address is valid. A non-nil return
   173  // does not necessarily indicate an invalid address. Many servers
   174  // will not verify addresses for security reasons.
   175  func (c *Client) Verify(addr string) error {
   176  	if err := c.hello(); err != nil {
   177  		return err
   178  	}
   179  	_, _, err := c.cmd(250, "VRFY %s", addr)
   180  	return err
   181  }
   182  
   183  // Auth authenticates a client using the provided authentication mechanism.
   184  // A failed authentication closes the connection.
   185  // Only servers that advertise the AUTH extension support this function.
   186  func (c *Client) Auth(a Auth) error {
   187  	if err := c.hello(); err != nil {
   188  		return err
   189  	}
   190  	encoding := base64.StdEncoding
   191  	mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth})
   192  	if err != nil {
   193  		c.Quit()
   194  		return err
   195  	}
   196  	resp64 := make([]byte, encoding.EncodedLen(len(resp)))
   197  	encoding.Encode(resp64, resp)
   198  	code, msg64, err := c.cmd(0, "AUTH %s %s", mech, resp64)
   199  	for err == nil {
   200  		var msg []byte
   201  		switch code {
   202  		case 334:
   203  			msg, err = encoding.DecodeString(msg64)
   204  		case 235:
   205  			// the last message isn't base64 because it isn't a challenge
   206  			msg = []byte(msg64)
   207  		default:
   208  			err = &textproto.Error{Code: code, Msg: msg64}
   209  		}
   210  		if err == nil {
   211  			resp, err = a.Next(msg, code == 334)
   212  		}
   213  		if err != nil {
   214  			// abort the AUTH
   215  			c.cmd(501, "*")
   216  			c.Quit()
   217  			break
   218  		}
   219  		if resp == nil {
   220  			break
   221  		}
   222  		resp64 = make([]byte, encoding.EncodedLen(len(resp)))
   223  		encoding.Encode(resp64, resp)
   224  		code, msg64, err = c.cmd(0, string(resp64))
   225  	}
   226  	return err
   227  }
   228  
   229  // Mail issues a MAIL command to the server using the provided email address.
   230  // If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME
   231  // parameter.
   232  // This initiates a mail transaction and is followed by one or more Rcpt calls.
   233  func (c *Client) Mail(from string) error {
   234  	if err := c.hello(); err != nil {
   235  		return err
   236  	}
   237  	cmdStr := "MAIL FROM:<%s>"
   238  	if c.ext != nil {
   239  		if _, ok := c.ext["8BITMIME"]; ok {
   240  			cmdStr += " BODY=8BITMIME"
   241  		}
   242  	}
   243  	_, _, err := c.cmd(250, cmdStr, from)
   244  	return err
   245  }
   246  
   247  // Rcpt issues a RCPT command to the server using the provided email address.
   248  // A call to Rcpt must be preceded by a call to Mail and may be followed by
   249  // a Data call or another Rcpt call.
   250  func (c *Client) Rcpt(to string) error {
   251  	_, _, err := c.cmd(25, "RCPT TO:<%s>", to)
   252  	return err
   253  }
   254  
   255  type dataCloser struct {
   256  	c *Client
   257  	io.WriteCloser
   258  }
   259  
   260  func (d *dataCloser) Close() error {
   261  	d.WriteCloser.Close()
   262  	_, _, err := d.c.Text.ReadResponse(250)
   263  	return err
   264  }
   265  
   266  // Data issues a DATA command to the server and returns a writer that
   267  // can be used to write the data. The caller should close the writer
   268  // before calling any more methods on c.
   269  // A call to Data must be preceded by one or more calls to Rcpt.
   270  func (c *Client) Data() (io.WriteCloser, error) {
   271  	_, _, err := c.cmd(354, "DATA")
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  	return &dataCloser{c, c.Text.DotWriter()}, nil
   276  }
   277  
   278  var testHookStartTLS func(*tls.Config) // nil, except for tests
   279  
   280  // SendMail connects to the server at addr, switches to TLS if
   281  // possible, authenticates with the optional mechanism a if possible,
   282  // and then sends an email from address from, to addresses to, with
   283  // message msg.
   284  func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
   285  	c, err := Dial(addr)
   286  	if err != nil {
   287  		return err
   288  	}
   289  	defer c.Close()
   290  	if err = c.hello(); err != nil {
   291  		return err
   292  	}
   293  	if ok, _ := c.Extension("STARTTLS"); ok {
   294  		config := &tls.Config{ServerName: c.serverName}
   295  		if testHookStartTLS != nil {
   296  			testHookStartTLS(config)
   297  		}
   298  		if err = c.StartTLS(config); err != nil {
   299  			return err
   300  		}
   301  	}
   302  	if a != nil && c.ext != nil {
   303  		if _, ok := c.ext["AUTH"]; ok {
   304  			if err = c.Auth(a); err != nil {
   305  				return err
   306  			}
   307  		}
   308  	}
   309  	if err = c.Mail(from); err != nil {
   310  		return err
   311  	}
   312  	for _, addr := range to {
   313  		if err = c.Rcpt(addr); err != nil {
   314  			return err
   315  		}
   316  	}
   317  	w, err := c.Data()
   318  	if err != nil {
   319  		return err
   320  	}
   321  	_, err = w.Write(msg)
   322  	if err != nil {
   323  		return err
   324  	}
   325  	err = w.Close()
   326  	if err != nil {
   327  		return err
   328  	}
   329  	return c.Quit()
   330  }
   331  
   332  // Extension reports whether an extension is support by the server.
   333  // The extension name is case-insensitive. If the extension is supported,
   334  // Extension also returns a string that contains any parameters the
   335  // server specifies for the extension.
   336  func (c *Client) Extension(ext string) (bool, string) {
   337  	if err := c.hello(); err != nil {
   338  		return false, ""
   339  	}
   340  	if c.ext == nil {
   341  		return false, ""
   342  	}
   343  	ext = strings.ToUpper(ext)
   344  	param, ok := c.ext[ext]
   345  	return ok, param
   346  }
   347  
   348  // Reset sends the RSET command to the server, aborting the current mail
   349  // transaction.
   350  func (c *Client) Reset() error {
   351  	if err := c.hello(); err != nil {
   352  		return err
   353  	}
   354  	_, _, err := c.cmd(250, "RSET")
   355  	return err
   356  }
   357  
   358  // Quit sends the QUIT command and closes the connection to the server.
   359  func (c *Client) Quit() error {
   360  	if err := c.hello(); err != nil {
   361  		return err
   362  	}
   363  	_, _, err := c.cmd(221, "QUIT")
   364  	if err != nil {
   365  		return err
   366  	}
   367  	return c.Text.Close()
   368  }