...

Source file src/pkg/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	//	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.
   245	// This initiates a mail transaction and is followed by one or more Rcpt calls.
   246	func (c *Client) Mail(from string) error {
   247		if err := validateLine(from); err != nil {
   248			return err
   249		}
   250		if err := c.hello(); err != nil {
   251			return err
   252		}
   253		cmdStr := "MAIL FROM:<%s>"
   254		if c.ext != nil {
   255			if _, ok := c.ext["8BITMIME"]; ok {
   256				cmdStr += " BODY=8BITMIME"
   257			}
   258		}
   259		_, _, err := c.cmd(250, cmdStr, from)
   260		return err
   261	}
   262	
   263	// Rcpt issues a RCPT command to the server using the provided email address.
   264	// A call to Rcpt must be preceded by a call to Mail and may be followed by
   265	// a Data call or another Rcpt call.
   266	func (c *Client) Rcpt(to string) error {
   267		if err := validateLine(to); err != nil {
   268			return err
   269		}
   270		_, _, err := c.cmd(25, "RCPT TO:<%s>", to)
   271		return err
   272	}
   273	
   274	type dataCloser struct {
   275		c *Client
   276		io.WriteCloser
   277	}
   278	
   279	func (d *dataCloser) Close() error {
   280		d.WriteCloser.Close()
   281		_, _, err := d.c.Text.ReadResponse(250)
   282		return err
   283	}
   284	
   285	// Data issues a DATA command to the server and returns a writer that
   286	// can be used to write the mail headers and body. The caller should
   287	// close the writer before calling any more methods on c. A call to
   288	// Data must be preceded by one or more calls to Rcpt.
   289	func (c *Client) Data() (io.WriteCloser, error) {
   290		_, _, err := c.cmd(354, "DATA")
   291		if err != nil {
   292			return nil, err
   293		}
   294		return &dataCloser{c, c.Text.DotWriter()}, nil
   295	}
   296	
   297	var testHookStartTLS func(*tls.Config) // nil, except for tests
   298	
   299	// SendMail connects to the server at addr, switches to TLS if
   300	// possible, authenticates with the optional mechanism a if possible,
   301	// and then sends an email from address from, to addresses to, with
   302	// message msg.
   303	// The addr must include a port, as in "mail.example.com:smtp".
   304	//
   305	// The addresses in the to parameter are the SMTP RCPT addresses.
   306	//
   307	// The msg parameter should be an RFC 822-style email with headers
   308	// first, a blank line, and then the message body. The lines of msg
   309	// should be CRLF terminated. The msg headers should usually include
   310	// fields such as "From", "To", "Subject", and "Cc".  Sending "Bcc"
   311	// messages is accomplished by including an email address in the to
   312	// parameter but not including it in the msg headers.
   313	//
   314	// The SendMail function and the net/smtp package are low-level
   315	// mechanisms and provide no support for DKIM signing, MIME
   316	// attachments (see the mime/multipart package), or other mail
   317	// functionality. Higher-level packages exist outside of the standard
   318	// library.
   319	func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
   320		if err := validateLine(from); err != nil {
   321			return err
   322		}
   323		for _, recp := range to {
   324			if err := validateLine(recp); err != nil {
   325				return err
   326			}
   327		}
   328		c, err := Dial(addr)
   329		if err != nil {
   330			return err
   331		}
   332		defer c.Close()
   333		if err = c.hello(); err != nil {
   334			return err
   335		}
   336		if ok, _ := c.Extension("STARTTLS"); ok {
   337			config := &tls.Config{ServerName: c.serverName}
   338			if testHookStartTLS != nil {
   339				testHookStartTLS(config)
   340			}
   341			if err = c.StartTLS(config); err != nil {
   342				return err
   343			}
   344		}
   345		if a != nil && c.ext != nil {
   346			if _, ok := c.ext["AUTH"]; !ok {
   347				return errors.New("smtp: server doesn't support AUTH")
   348			}
   349			if err = c.Auth(a); err != nil {
   350				return err
   351			}
   352		}
   353		if err = c.Mail(from); err != nil {
   354			return err
   355		}
   356		for _, addr := range to {
   357			if err = c.Rcpt(addr); err != nil {
   358				return err
   359			}
   360		}
   361		w, err := c.Data()
   362		if err != nil {
   363			return err
   364		}
   365		_, err = w.Write(msg)
   366		if err != nil {
   367			return err
   368		}
   369		err = w.Close()
   370		if err != nil {
   371			return err
   372		}
   373		return c.Quit()
   374	}
   375	
   376	// Extension reports whether an extension is support by the server.
   377	// The extension name is case-insensitive. If the extension is supported,
   378	// Extension also returns a string that contains any parameters the
   379	// server specifies for the extension.
   380	func (c *Client) Extension(ext string) (bool, string) {
   381		if err := c.hello(); err != nil {
   382			return false, ""
   383		}
   384		if c.ext == nil {
   385			return false, ""
   386		}
   387		ext = strings.ToUpper(ext)
   388		param, ok := c.ext[ext]
   389		return ok, param
   390	}
   391	
   392	// Reset sends the RSET command to the server, aborting the current mail
   393	// transaction.
   394	func (c *Client) Reset() error {
   395		if err := c.hello(); err != nil {
   396			return err
   397		}
   398		_, _, err := c.cmd(250, "RSET")
   399		return err
   400	}
   401	
   402	// Noop sends the NOOP command to the server. It does nothing but check
   403	// that the connection to the server is okay.
   404	func (c *Client) Noop() error {
   405		if err := c.hello(); err != nil {
   406			return err
   407		}
   408		_, _, err := c.cmd(250, "NOOP")
   409		return err
   410	}
   411	
   412	// Quit sends the QUIT command and closes the connection to the server.
   413	func (c *Client) Quit() error {
   414		if err := c.hello(); err != nil {
   415			return err
   416		}
   417		_, _, err := c.cmd(221, "QUIT")
   418		if err != nil {
   419			return err
   420		}
   421		return c.Text.Close()
   422	}
   423	
   424	// validateLine checks to see if a line has CR or LF as per RFC 5321
   425	func validateLine(line string) error {
   426		if strings.ContainsAny(line, "\n\r") {
   427			return errors.New("smtp: A line must not contain CR or LF")
   428		}
   429		return nil
   430	}
   431	

View as plain text