Source file src/net/smtp/smtp.go

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

View as plain text