github.com/roboticscm/goman@v0.0.0-20210203095141-87c07b4a0a55/src/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  // The addr must include a port number.
    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  // Verify checks the validity of an email address on the server.
   161  // If Verify returns nil, the address is valid. A non-nil return
   162  // does not necessarily indicate an invalid address. Many servers
   163  // will not verify addresses for security reasons.
   164  func (c *Client) Verify(addr string) error {
   165  	if err := c.hello(); err != nil {
   166  		return err
   167  	}
   168  	_, _, err := c.cmd(250, "VRFY %s", addr)
   169  	return err
   170  }
   171  
   172  // Auth authenticates a client using the provided authentication mechanism.
   173  // A failed authentication closes the connection.
   174  // Only servers that advertise the AUTH extension support this function.
   175  func (c *Client) Auth(a Auth) error {
   176  	if err := c.hello(); err != nil {
   177  		return err
   178  	}
   179  	encoding := base64.StdEncoding
   180  	mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth})
   181  	if err != nil {
   182  		c.Quit()
   183  		return err
   184  	}
   185  	resp64 := make([]byte, encoding.EncodedLen(len(resp)))
   186  	encoding.Encode(resp64, resp)
   187  	code, msg64, err := c.cmd(0, "AUTH %s %s", mech, resp64)
   188  	for err == nil {
   189  		var msg []byte
   190  		switch code {
   191  		case 334:
   192  			msg, err = encoding.DecodeString(msg64)
   193  		case 235:
   194  			// the last message isn't base64 because it isn't a challenge
   195  			msg = []byte(msg64)
   196  		default:
   197  			err = &textproto.Error{Code: code, Msg: msg64}
   198  		}
   199  		if err == nil {
   200  			resp, err = a.Next(msg, code == 334)
   201  		}
   202  		if err != nil {
   203  			// abort the AUTH
   204  			c.cmd(501, "*")
   205  			c.Quit()
   206  			break
   207  		}
   208  		if resp == nil {
   209  			break
   210  		}
   211  		resp64 = make([]byte, encoding.EncodedLen(len(resp)))
   212  		encoding.Encode(resp64, resp)
   213  		code, msg64, err = c.cmd(0, string(resp64))
   214  	}
   215  	return err
   216  }
   217  
   218  // Mail issues a MAIL command to the server using the provided email address.
   219  // If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME
   220  // parameter.
   221  // This initiates a mail transaction and is followed by one or more Rcpt calls.
   222  func (c *Client) Mail(from string) error {
   223  	if err := c.hello(); err != nil {
   224  		return err
   225  	}
   226  	cmdStr := "MAIL FROM:<%s>"
   227  	if c.ext != nil {
   228  		if _, ok := c.ext["8BITMIME"]; ok {
   229  			cmdStr += " BODY=8BITMIME"
   230  		}
   231  	}
   232  	_, _, err := c.cmd(250, cmdStr, from)
   233  	return err
   234  }
   235  
   236  // Rcpt issues a RCPT command to the server using the provided email address.
   237  // A call to Rcpt must be preceded by a call to Mail and may be followed by
   238  // a Data call or another Rcpt call.
   239  func (c *Client) Rcpt(to string) error {
   240  	_, _, err := c.cmd(25, "RCPT TO:<%s>", to)
   241  	return err
   242  }
   243  
   244  type dataCloser struct {
   245  	c *Client
   246  	io.WriteCloser
   247  }
   248  
   249  func (d *dataCloser) Close() error {
   250  	d.WriteCloser.Close()
   251  	_, _, err := d.c.Text.ReadResponse(250)
   252  	return err
   253  }
   254  
   255  // Data issues a DATA command to the server and returns a writer that
   256  // can be used to write the data. The caller should close the writer
   257  // before calling any more methods on c.
   258  // A call to Data must be preceded by one or more calls to Rcpt.
   259  func (c *Client) Data() (io.WriteCloser, error) {
   260  	_, _, err := c.cmd(354, "DATA")
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  	return &dataCloser{c, c.Text.DotWriter()}, nil
   265  }
   266  
   267  var testHookStartTLS func(*tls.Config) // nil, except for tests
   268  
   269  // SendMail connects to the server at addr, switches to TLS if
   270  // possible, authenticates with the optional mechanism a if possible,
   271  // and then sends an email from address from, to addresses to, with
   272  // message msg.
   273  func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
   274  	c, err := Dial(addr)
   275  	if err != nil {
   276  		return err
   277  	}
   278  	defer c.Close()
   279  	if err = c.hello(); err != nil {
   280  		return err
   281  	}
   282  	if ok, _ := c.Extension("STARTTLS"); ok {
   283  		config := &tls.Config{ServerName: c.serverName}
   284  		if testHookStartTLS != nil {
   285  			testHookStartTLS(config)
   286  		}
   287  		if err = c.StartTLS(config); err != nil {
   288  			return err
   289  		}
   290  	}
   291  	if a != nil && c.ext != nil {
   292  		if _, ok := c.ext["AUTH"]; ok {
   293  			if err = c.Auth(a); err != nil {
   294  				return err
   295  			}
   296  		}
   297  	}
   298  	if err = c.Mail(from); err != nil {
   299  		return err
   300  	}
   301  	for _, addr := range to {
   302  		if err = c.Rcpt(addr); err != nil {
   303  			return err
   304  		}
   305  	}
   306  	w, err := c.Data()
   307  	if err != nil {
   308  		return err
   309  	}
   310  	_, err = w.Write(msg)
   311  	if err != nil {
   312  		return err
   313  	}
   314  	err = w.Close()
   315  	if err != nil {
   316  		return err
   317  	}
   318  	return c.Quit()
   319  }
   320  
   321  // Extension reports whether an extension is support by the server.
   322  // The extension name is case-insensitive. If the extension is supported,
   323  // Extension also returns a string that contains any parameters the
   324  // server specifies for the extension.
   325  func (c *Client) Extension(ext string) (bool, string) {
   326  	if err := c.hello(); err != nil {
   327  		return false, ""
   328  	}
   329  	if c.ext == nil {
   330  		return false, ""
   331  	}
   332  	ext = strings.ToUpper(ext)
   333  	param, ok := c.ext[ext]
   334  	return ok, param
   335  }
   336  
   337  // Reset sends the RSET command to the server, aborting the current mail
   338  // transaction.
   339  func (c *Client) Reset() error {
   340  	if err := c.hello(); err != nil {
   341  		return err
   342  	}
   343  	_, _, err := c.cmd(250, "RSET")
   344  	return err
   345  }
   346  
   347  // Quit sends the QUIT command and closes the connection to the server.
   348  func (c *Client) Quit() error {
   349  	if err := c.hello(); err != nil {
   350  		return err
   351  	}
   352  	_, _, err := c.cmd(221, "QUIT")
   353  	if err != nil {
   354  		return err
   355  	}
   356  	return c.Text.Close()
   357  }