src/pkg/net/smtp/smtp.go - The Go Programming Language

Golang

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	package smtp
    12	
    13	import (
    14		"crypto/tls"
    15		"encoding/base64"
    16		"io"
    17		"net"
    18		"net/textproto"
    19		"strings"
    20	)
    21	
    22	// A Client represents a client connection to an SMTP server.
    23	type Client struct {
    24		// Text is the textproto.Conn used by the Client. It is exported to allow for
    25		// clients to add extensions.
    26		Text *textproto.Conn
    27		// keep a reference to the connection so it can be used to create a TLS
    28		// connection later
    29		conn net.Conn
    30		// whether the Client is using TLS
    31		tls        bool
    32		serverName string
    33		// map of supported extensions
    34		ext map[string]string
    35		// supported auth mechanisms
    36		auth []string
    37	}
    38	
    39	// Dial returns a new Client connected to an SMTP server at addr.
    40	func Dial(addr string) (*Client, error) {
    41		conn, err := net.Dial("tcp", addr)
    42		if err != nil {
    43			return nil, err
    44		}
    45		host := addr[:strings.Index(addr, ":")]
    46		return NewClient(conn, host)
    47	}
    48	
    49	// NewClient returns a new Client using an existing connection and host as a
    50	// server name to be used when authenticating.
    51	func NewClient(conn net.Conn, host string) (*Client, error) {
    52		text := textproto.NewConn(conn)
    53		_, _, err := text.ReadResponse(220)
    54		if err != nil {
    55			text.Close()
    56			return nil, err
    57		}
    58		c := &Client{Text: text, conn: conn, serverName: host}
    59		err = c.ehlo()
    60		if err != nil {
    61			err = c.helo()
    62		}
    63		return c, err
    64	}
    65	
    66	// cmd is a convenience function that sends a command and returns the response
    67	func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
    68		id, err := c.Text.Cmd(format, args...)
    69		if err != nil {
    70			return 0, "", err
    71		}
    72		c.Text.StartResponse(id)
    73		defer c.Text.EndResponse(id)
    74		code, msg, err := c.Text.ReadResponse(expectCode)
    75		return code, msg, err
    76	}
    77	
    78	// helo sends the HELO greeting to the server. It should be used only when the
    79	// server does not support ehlo.
    80	func (c *Client) helo() error {
    81		c.ext = nil
    82		_, _, err := c.cmd(250, "HELO localhost")
    83		return err
    84	}
    85	
    86	// ehlo sends the EHLO (extended hello) greeting to the server. It
    87	// should be the preferred greeting for servers that support it.
    88	func (c *Client) ehlo() error {
    89		_, msg, err := c.cmd(250, "EHLO localhost")
    90		if err != nil {
    91			return err
    92		}
    93		ext := make(map[string]string)
    94		extList := strings.Split(msg, "\n")
    95		if len(extList) > 1 {
    96			extList = extList[1:]
    97			for _, line := range extList {
    98				args := strings.SplitN(line, " ", 2)
    99				if len(args) > 1 {
   100					ext[args[0]] = args[1]
   101				} else {
   102					ext[args[0]] = ""
   103				}
   104			}
   105		}
   106		if mechs, ok := ext["AUTH"]; ok {
   107			c.auth = strings.Split(mechs, " ")
   108		}
   109		c.ext = ext
   110		return err
   111	}
   112	
   113	// StartTLS sends the STARTTLS command and encrypts all further communication.
   114	// Only servers that advertise the STARTTLS extension support this function.
   115	func (c *Client) StartTLS(config *tls.Config) error {
   116		_, _, err := c.cmd(220, "STARTTLS")
   117		if err != nil {
   118			return err
   119		}
   120		c.conn = tls.Client(c.conn, config)
   121		c.Text = textproto.NewConn(c.conn)
   122		c.tls = true
   123		return c.ehlo()
   124	}
   125	
   126	// Verify checks the validity of an email address on the server.
   127	// If Verify returns nil, the address is valid. A non-nil return
   128	// does not necessarily indicate an invalid address. Many servers
   129	// will not verify addresses for security reasons.
   130	func (c *Client) Verify(addr string) error {
   131		_, _, err := c.cmd(250, "VRFY %s", addr)
   132		return err
   133	}
   134	
   135	// Auth authenticates a client using the provided authentication mechanism.
   136	// A failed authentication closes the connection.
   137	// Only servers that advertise the AUTH extension support this function.
   138	func (c *Client) Auth(a Auth) error {
   139		encoding := base64.StdEncoding
   140		mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth})
   141		if err != nil {
   142			c.Quit()
   143			return err
   144		}
   145		resp64 := make([]byte, encoding.EncodedLen(len(resp)))
   146		encoding.Encode(resp64, resp)
   147		code, msg64, err := c.cmd(0, "AUTH %s %s", mech, resp64)
   148		for err == nil {
   149			var msg []byte
   150			switch code {
   151			case 334:
   152				msg, err = encoding.DecodeString(msg64)
   153			case 235:
   154				// the last message isn't base64 because it isn't a challenge
   155				msg = []byte(msg64)
   156			default:
   157				err = &textproto.Error{Code: code, Msg: msg64}
   158			}
   159			resp, err = a.Next(msg, code == 334)
   160			if err != nil {
   161				// abort the AUTH
   162				c.cmd(501, "*")
   163				c.Quit()
   164				break
   165			}
   166			if resp == nil {
   167				break
   168			}
   169			resp64 = make([]byte, encoding.EncodedLen(len(resp)))
   170			encoding.Encode(resp64, resp)
   171			code, msg64, err = c.cmd(0, string(resp64))
   172		}
   173		return err
   174	}
   175	
   176	// Mail issues a MAIL command to the server using the provided email address.
   177	// If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME
   178	// parameter.
   179	// This initiates a mail transaction and is followed by one or more Rcpt calls.
   180	func (c *Client) Mail(from string) error {
   181		cmdStr := "MAIL FROM:<%s>"
   182		if c.ext != nil {
   183			if _, ok := c.ext["8BITMIME"]; ok {
   184				cmdStr += " BODY=8BITMIME"
   185			}
   186		}
   187		_, _, err := c.cmd(250, cmdStr, from)
   188		return err
   189	}
   190	
   191	// Rcpt issues a RCPT command to the server using the provided email address.
   192	// A call to Rcpt must be preceded by a call to Mail and may be followed by
   193	// a Data call or another Rcpt call.
   194	func (c *Client) Rcpt(to string) error {
   195		_, _, err := c.cmd(25, "RCPT TO:<%s>", to)
   196		return err
   197	}
   198	
   199	type dataCloser struct {
   200		c *Client
   201		io.WriteCloser
   202	}
   203	
   204	func (d *dataCloser) Close() error {
   205		d.WriteCloser.Close()
   206		_, _, err := d.c.Text.ReadResponse(250)
   207		return err
   208	}
   209	
   210	// Data issues a DATA command to the server and returns a writer that
   211	// can be used to write the data. The caller should close the writer
   212	// before calling any more methods on c.
   213	// A call to Data must be preceded by one or more calls to Rcpt.
   214	func (c *Client) Data() (io.WriteCloser, error) {
   215		_, _, err := c.cmd(354, "DATA")
   216		if err != nil {
   217			return nil, err
   218		}
   219		return &dataCloser{c, c.Text.DotWriter()}, nil
   220	}
   221	
   222	// SendMail connects to the server at addr, switches to TLS if possible,
   223	// authenticates with mechanism a if possible, and then sends an email from
   224	// address from, to addresses to, with message msg.
   225	func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
   226		c, err := Dial(addr)
   227		if err != nil {
   228			return err
   229		}
   230		if ok, _ := c.Extension("STARTTLS"); ok {
   231			if err = c.StartTLS(nil); err != nil {
   232				return err
   233			}
   234		}
   235		if a != nil && c.ext != nil {
   236			if _, ok := c.ext["AUTH"]; ok {
   237				if err = c.Auth(a); err != nil {
   238					return err
   239				}
   240			}
   241		}
   242		if err = c.Mail(from); err != nil {
   243			return err
   244		}
   245		for _, addr := range to {
   246			if err = c.Rcpt(addr); err != nil {
   247				return err
   248			}
   249		}
   250		w, err := c.Data()
   251		if err != nil {
   252			return err
   253		}
   254		_, err = w.Write(msg)
   255		if err != nil {
   256			return err
   257		}
   258		err = w.Close()
   259		if err != nil {
   260			return err
   261		}
   262		return c.Quit()
   263	}
   264	
   265	// Extension reports whether an extension is support by the server.
   266	// The extension name is case-insensitive. If the extension is supported,
   267	// Extension also returns a string that contains any parameters the
   268	// server specifies for the extension.
   269	func (c *Client) Extension(ext string) (bool, string) {
   270		if c.ext == nil {
   271			return false, ""
   272		}
   273		ext = strings.ToUpper(ext)
   274		param, ok := c.ext[ext]
   275		return ok, param
   276	}
   277	
   278	// Reset sends the RSET command to the server, aborting the current mail
   279	// transaction.
   280	func (c *Client) Reset() error {
   281		_, _, err := c.cmd(250, "RSET")
   282		return err
   283	}
   284	
   285	// Quit sends the QUIT command and closes the connection to the server.
   286	func (c *Client) Quit() error {
   287		_, _, err := c.cmd(221, "QUIT")
   288		if err != nil {
   289			return err
   290		}
   291		return c.Text.Close()
   292	}