github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/pkg/net/smtp/smtp.go (about)

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