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 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, as in "mail.example.com:smtp".
     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 mail headers and body. The caller should
    268 // close the writer before calling any more methods on c.  A call to
    269 // 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 // The addr must include a port, as in "mail.example.com:smtp".
    285 //
    286 // The addresses in the to parameter are the SMTP RCPT addresses.
    287 //
    288 // The msg parameter should be an RFC 822-style email with headers
    289 // first, a blank line, and then the message body. The lines of msg
    290 // should be CRLF terminated.  The msg headers should usually include
    291 // fields such as "From", "To", "Subject", and "Cc".  Sending "Bcc"
    292 // messages is accomplished by including an email address in the to
    293 // parameter but not including it in the msg headers.
    294 //
    295 // The SendMail function and the the net/smtp package are low-level
    296 // mechanisms and provide no support for DKIM signing, MIME
    297 // attachments (see the mime/multipart package), or other mail
    298 // functionality. Higher-level packages exist outside of the standard
    299 // library.
    300 func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
    301 	c, err := Dial(addr)
    302 	if err != nil {
    303 		return err
    304 	}
    305 	defer c.Close()
    306 	if err = c.hello(); err != nil {
    307 		return err
    308 	}
    309 	if ok, _ := c.Extension("STARTTLS"); ok {
    310 		config := &tls.Config{ServerName: c.serverName}
    311 		if testHookStartTLS != nil {
    312 			testHookStartTLS(config)
    313 		}
    314 		if err = c.StartTLS(config); err != nil {
    315 			return err
    316 		}
    317 	}
    318 	if a != nil && c.ext != nil {
    319 		if _, ok := c.ext["AUTH"]; ok {
    320 			if err = c.Auth(a); err != nil {
    321 				return err
    322 			}
    323 		}
    324 	}
    325 	if err = c.Mail(from); err != nil {
    326 		return err
    327 	}
    328 	for _, addr := range to {
    329 		if err = c.Rcpt(addr); err != nil {
    330 			return err
    331 		}
    332 	}
    333 	w, err := c.Data()
    334 	if err != nil {
    335 		return err
    336 	}
    337 	_, err = w.Write(msg)
    338 	if err != nil {
    339 		return err
    340 	}
    341 	err = w.Close()
    342 	if err != nil {
    343 		return err
    344 	}
    345 	return c.Quit()
    346 }
    347 
    348 // Extension reports whether an extension is support by the server.
    349 // The extension name is case-insensitive. If the extension is supported,
    350 // Extension also returns a string that contains any parameters the
    351 // server specifies for the extension.
    352 func (c *Client) Extension(ext string) (bool, string) {
    353 	if err := c.hello(); err != nil {
    354 		return false, ""
    355 	}
    356 	if c.ext == nil {
    357 		return false, ""
    358 	}
    359 	ext = strings.ToUpper(ext)
    360 	param, ok := c.ext[ext]
    361 	return ok, param
    362 }
    363 
    364 // Reset sends the RSET command to the server, aborting the current mail
    365 // transaction.
    366 func (c *Client) Reset() error {
    367 	if err := c.hello(); err != nil {
    368 		return err
    369 	}
    370 	_, _, err := c.cmd(250, "RSET")
    371 	return err
    372 }
    373 
    374 // Quit sends the QUIT command and closes the connection to the server.
    375 func (c *Client) Quit() error {
    376 	if err := c.hello(); err != nil {
    377 		return err
    378 	}
    379 	_, _, err := c.cmd(221, "QUIT")
    380 	if err != nil {
    381 		return err
    382 	}
    383 	return c.Text.Close()
    384 }
    385