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