Black Lives Matter. Support the Equal Justice Initiative.

Source file src/net/smtp/smtp.go

Documentation: net/smtp

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

View as plain text