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