github.com/emersion/go-smtp@v0.20.2/client.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
     6  
     7  import (
     8  	"crypto/tls"
     9  	"encoding/base64"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"net"
    14  	"net/textproto"
    15  	"strconv"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/emersion/go-sasl"
    20  )
    21  
    22  // A Client represents a client connection to an SMTP server.
    23  type Client struct {
    24  	// keep a reference to the connection so it can be used to create a TLS
    25  	// connection later
    26  	conn       net.Conn
    27  	text       *textproto.Conn
    28  	serverName string
    29  	lmtp       bool
    30  	// map of supported extensions
    31  	ext map[string]string
    32  	// supported auth mechanisms
    33  	auth       []string
    34  	localName  string   // the name to use in HELO/EHLO/LHLO
    35  	didHello   bool     // whether we've said HELO/EHLO/LHLO
    36  	helloError error    // the error from the hello
    37  	rcpts      []string // recipients accumulated for the current session
    38  
    39  	// Time to wait for command responses (this includes 3xx reply to DATA).
    40  	CommandTimeout time.Duration
    41  	// Time to wait for responses after final dot.
    42  	SubmissionTimeout time.Duration
    43  
    44  	// Logger for all network activity.
    45  	DebugWriter io.Writer
    46  }
    47  
    48  // 30 seconds was chosen as it's the same duration as http.DefaultTransport's
    49  // timeout.
    50  const defaultTimeout = 30 * time.Second
    51  
    52  var defaultDialer = net.Dialer{Timeout: defaultTimeout}
    53  
    54  // Dial returns a new Client connected to an SMTP server at addr. The addr must
    55  // include a port, as in "mail.example.com:smtp".
    56  //
    57  // This function returns a plaintext connection. To enable TLS, use StartTLS.
    58  func Dial(addr string) (*Client, error) {
    59  	conn, err := defaultDialer.Dial("tcp", addr)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	client := NewClient(conn)
    64  	client.serverName, _, _ = net.SplitHostPort(addr)
    65  	return client, nil
    66  }
    67  
    68  // DialTLS returns a new Client connected to an SMTP server via TLS at addr.
    69  // The addr must include a port, as in "mail.example.com:smtps".
    70  //
    71  // A nil tlsConfig is equivalent to a zero tls.Config.
    72  func DialTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
    73  	tlsDialer := tls.Dialer{
    74  		NetDialer: &defaultDialer,
    75  		Config:    tlsConfig,
    76  	}
    77  	conn, err := tlsDialer.Dial("tcp", addr)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	client := NewClient(conn)
    82  	client.serverName, _, _ = net.SplitHostPort(addr)
    83  	return client, nil
    84  }
    85  
    86  // NewClient returns a new Client using an existing connection and host as a
    87  // server name to be used when authenticating.
    88  func NewClient(conn net.Conn) *Client {
    89  	c := &Client{
    90  		localName: "localhost",
    91  		// As recommended by RFC 5321. For DATA command reply (3xx one) RFC
    92  		// recommends a slightly shorter timeout but we do not bother
    93  		// differentiating these.
    94  		CommandTimeout: 5 * time.Minute,
    95  		// 10 minutes + 2 minute buffer in case the server is doing transparent
    96  		// forwarding and also follows recommended timeouts.
    97  		SubmissionTimeout: 12 * time.Minute,
    98  	}
    99  
   100  	c.setConn(conn)
   101  
   102  	return c
   103  }
   104  
   105  // NewClientLMTP returns a new LMTP Client (as defined in RFC 2033) using an
   106  // existing connection and host as a server name to be used when authenticating.
   107  func NewClientLMTP(conn net.Conn) *Client {
   108  	c := NewClient(conn)
   109  	c.lmtp = true
   110  	return c
   111  }
   112  
   113  // setConn sets the underlying network connection for the client.
   114  func (c *Client) setConn(conn net.Conn) {
   115  	c.conn = conn
   116  
   117  	var r io.Reader = conn
   118  	var w io.Writer = conn
   119  
   120  	r = &lineLimitReader{
   121  		R: conn,
   122  		// Doubled maximum line length per RFC 5321 (Section 4.5.3.1.6)
   123  		LineLimit: 2000,
   124  	}
   125  
   126  	r = io.TeeReader(r, clientDebugWriter{c})
   127  	w = io.MultiWriter(w, clientDebugWriter{c})
   128  
   129  	rwc := struct {
   130  		io.Reader
   131  		io.Writer
   132  		io.Closer
   133  	}{
   134  		Reader: r,
   135  		Writer: w,
   136  		Closer: conn,
   137  	}
   138  	c.text = textproto.NewConn(rwc)
   139  }
   140  
   141  // Close closes the connection.
   142  func (c *Client) Close() error {
   143  	return c.text.Close()
   144  }
   145  
   146  func (c *Client) greet() error {
   147  	// Initial greeting timeout. RFC 5321 recommends 5 minutes.
   148  	c.conn.SetDeadline(time.Now().Add(c.CommandTimeout))
   149  	defer c.conn.SetDeadline(time.Time{})
   150  
   151  	_, _, err := c.text.ReadResponse(220)
   152  	if err != nil {
   153  		c.text.Close()
   154  		if protoErr, ok := err.(*textproto.Error); ok {
   155  			return toSMTPErr(protoErr)
   156  		}
   157  		return err
   158  	}
   159  
   160  	return nil
   161  }
   162  
   163  // hello runs a hello exchange if needed.
   164  func (c *Client) hello() error {
   165  	if !c.didHello {
   166  		c.didHello = true
   167  		if err := c.greet(); err != nil {
   168  			c.helloError = err
   169  		} else if err := c.ehlo(); err != nil {
   170  			c.helloError = c.helo()
   171  		}
   172  	}
   173  	return c.helloError
   174  }
   175  
   176  // Hello sends a HELO or EHLO to the server as the given host name.
   177  // Calling this method is only necessary if the client needs control
   178  // over the host name used. The client will introduce itself as "localhost"
   179  // automatically otherwise. If Hello is called, it must be called before
   180  // any of the other methods.
   181  //
   182  // If server returns an error, it will be of type *SMTPError.
   183  func (c *Client) Hello(localName string) error {
   184  	if err := validateLine(localName); err != nil {
   185  		return err
   186  	}
   187  	if c.didHello {
   188  		return errors.New("smtp: Hello called after other methods")
   189  	}
   190  	c.localName = localName
   191  	return c.hello()
   192  }
   193  
   194  // cmd is a convenience function that sends a command and returns the response
   195  // textproto.Error returned by c.text.ReadResponse is converted into SMTPError.
   196  func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
   197  	c.conn.SetDeadline(time.Now().Add(c.CommandTimeout))
   198  	defer c.conn.SetDeadline(time.Time{})
   199  
   200  	id, err := c.text.Cmd(format, args...)
   201  	if err != nil {
   202  		return 0, "", err
   203  	}
   204  	c.text.StartResponse(id)
   205  	defer c.text.EndResponse(id)
   206  	code, msg, err := c.text.ReadResponse(expectCode)
   207  	if err != nil {
   208  		if protoErr, ok := err.(*textproto.Error); ok {
   209  			smtpErr := toSMTPErr(protoErr)
   210  			return code, smtpErr.Message, smtpErr
   211  		}
   212  		return code, msg, err
   213  	}
   214  	return code, msg, nil
   215  }
   216  
   217  // helo sends the HELO greeting to the server. It should be used only when the
   218  // server does not support ehlo.
   219  func (c *Client) helo() error {
   220  	c.ext = nil
   221  	_, _, err := c.cmd(250, "HELO %s", c.localName)
   222  	return err
   223  }
   224  
   225  // ehlo sends the EHLO (extended hello) greeting to the server. It
   226  // should be the preferred greeting for servers that support it.
   227  func (c *Client) ehlo() error {
   228  	cmd := "EHLO"
   229  	if c.lmtp {
   230  		cmd = "LHLO"
   231  	}
   232  
   233  	_, msg, err := c.cmd(250, "%s %s", cmd, c.localName)
   234  	if err != nil {
   235  		return err
   236  	}
   237  	ext := make(map[string]string)
   238  	extList := strings.Split(msg, "\n")
   239  	if len(extList) > 1 {
   240  		extList = extList[1:]
   241  		for _, line := range extList {
   242  			args := strings.SplitN(line, " ", 2)
   243  			if len(args) > 1 {
   244  				ext[args[0]] = args[1]
   245  			} else {
   246  				ext[args[0]] = ""
   247  			}
   248  		}
   249  	}
   250  	if mechs, ok := ext["AUTH"]; ok {
   251  		c.auth = strings.Split(mechs, " ")
   252  	}
   253  	c.ext = ext
   254  	return err
   255  }
   256  
   257  // StartTLS sends the STARTTLS command and encrypts all further communication.
   258  // Only servers that advertise the STARTTLS extension support this function.
   259  //
   260  // A nil config is equivalent to a zero tls.Config.
   261  //
   262  // If server returns an error, it will be of type *SMTPError.
   263  func (c *Client) StartTLS(config *tls.Config) error {
   264  	if err := c.hello(); err != nil {
   265  		return err
   266  	}
   267  	_, _, err := c.cmd(220, "STARTTLS")
   268  	if err != nil {
   269  		return err
   270  	}
   271  	if config == nil {
   272  		config = &tls.Config{}
   273  	}
   274  	if config.ServerName == "" && c.serverName != "" {
   275  		// Make a copy to avoid polluting argument
   276  		config = config.Clone()
   277  		config.ServerName = c.serverName
   278  	}
   279  	if testHookStartTLS != nil {
   280  		testHookStartTLS(config)
   281  	}
   282  	c.setConn(tls.Client(c.conn, config))
   283  	return c.ehlo()
   284  }
   285  
   286  // TLSConnectionState returns the client's TLS connection state.
   287  // The return values are their zero values if StartTLS did
   288  // not succeed.
   289  func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool) {
   290  	tc, ok := c.conn.(*tls.Conn)
   291  	if !ok {
   292  		return
   293  	}
   294  	return tc.ConnectionState(), true
   295  }
   296  
   297  // Verify checks the validity of an email address on the server.
   298  // If Verify returns nil, the address is valid. A non-nil return
   299  // does not necessarily indicate an invalid address. Many servers
   300  // will not verify addresses for security reasons.
   301  //
   302  // If server returns an error, it will be of type *SMTPError.
   303  func (c *Client) Verify(addr string) error {
   304  	if err := validateLine(addr); err != nil {
   305  		return err
   306  	}
   307  	if err := c.hello(); err != nil {
   308  		return err
   309  	}
   310  	_, _, err := c.cmd(250, "VRFY %s", addr)
   311  	return err
   312  }
   313  
   314  // Auth authenticates a client using the provided authentication mechanism.
   315  // Only servers that advertise the AUTH extension support this function.
   316  //
   317  // If server returns an error, it will be of type *SMTPError.
   318  func (c *Client) Auth(a sasl.Client) error {
   319  	if err := c.hello(); err != nil {
   320  		return err
   321  	}
   322  	encoding := base64.StdEncoding
   323  	mech, resp, err := a.Start()
   324  	if err != nil {
   325  		return err
   326  	}
   327  	resp64 := make([]byte, encoding.EncodedLen(len(resp)))
   328  	encoding.Encode(resp64, resp)
   329  	code, msg64, err := c.cmd(0, strings.TrimSpace(fmt.Sprintf("AUTH %s %s", mech, resp64)))
   330  	for err == nil {
   331  		var msg []byte
   332  		switch code {
   333  		case 334:
   334  			msg, err = encoding.DecodeString(msg64)
   335  		case 235:
   336  			// the last message isn't base64 because it isn't a challenge
   337  			msg = []byte(msg64)
   338  		default:
   339  			err = toSMTPErr(&textproto.Error{Code: code, Msg: msg64})
   340  		}
   341  		if err == nil {
   342  			if code == 334 {
   343  				resp, err = a.Next(msg)
   344  			} else {
   345  				resp = nil
   346  			}
   347  		}
   348  		if err != nil {
   349  			// abort the AUTH
   350  			c.cmd(501, "*")
   351  			break
   352  		}
   353  		if resp == nil {
   354  			break
   355  		}
   356  		resp64 = make([]byte, encoding.EncodedLen(len(resp)))
   357  		encoding.Encode(resp64, resp)
   358  		code, msg64, err = c.cmd(0, string(resp64))
   359  	}
   360  	return err
   361  }
   362  
   363  // Mail issues a MAIL command to the server using the provided email address.
   364  // If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME
   365  // parameter.
   366  // This initiates a mail transaction and is followed by one or more Rcpt calls.
   367  //
   368  // If opts is not nil, MAIL arguments provided in the structure will be added
   369  // to the command. Handling of unsupported options depends on the extension.
   370  //
   371  // If server returns an error, it will be of type *SMTPError.
   372  func (c *Client) Mail(from string, opts *MailOptions) error {
   373  	if err := validateLine(from); err != nil {
   374  		return err
   375  	}
   376  	if err := c.hello(); err != nil {
   377  		return err
   378  	}
   379  
   380  	var sb strings.Builder
   381  	// A high enough power of 2 than 510+14+26+11+9+9+39+500
   382  	sb.Grow(2048)
   383  	fmt.Fprintf(&sb, "MAIL FROM:<%s>", from)
   384  	if _, ok := c.ext["8BITMIME"]; ok {
   385  		sb.WriteString(" BODY=8BITMIME")
   386  	}
   387  	if _, ok := c.ext["SIZE"]; ok && opts != nil && opts.Size != 0 {
   388  		fmt.Fprintf(&sb, " SIZE=%v", opts.Size)
   389  	}
   390  	if opts != nil && opts.RequireTLS {
   391  		if _, ok := c.ext["REQUIRETLS"]; ok {
   392  			sb.WriteString(" REQUIRETLS")
   393  		} else {
   394  			return errors.New("smtp: server does not support REQUIRETLS")
   395  		}
   396  	}
   397  	if opts != nil && opts.UTF8 {
   398  		if _, ok := c.ext["SMTPUTF8"]; ok {
   399  			sb.WriteString(" SMTPUTF8")
   400  		} else {
   401  			return errors.New("smtp: server does not support SMTPUTF8")
   402  		}
   403  	}
   404  	if _, ok := c.ext["DSN"]; ok && opts != nil {
   405  		switch opts.Return {
   406  		case DSNReturnFull, DSNReturnHeaders:
   407  			fmt.Fprintf(&sb, " RET=%s", string(opts.Return))
   408  		case "":
   409  			// This space is intentionally left blank
   410  		default:
   411  			return errors.New("smtp: Unknown RET parameter value")
   412  		}
   413  		if opts.EnvelopeID != "" {
   414  			if !isPrintableASCII(opts.EnvelopeID) {
   415  				return errors.New("smtp: Malformed ENVID parameter value")
   416  			}
   417  			fmt.Fprintf(&sb, " ENVID=%s", encodeXtext(opts.EnvelopeID))
   418  		}
   419  	}
   420  	if opts != nil && opts.Auth != nil {
   421  		if _, ok := c.ext["AUTH"]; ok {
   422  			fmt.Fprintf(&sb, " AUTH=%s", encodeXtext(*opts.Auth))
   423  		}
   424  		// We can safely discard parameter if server does not support AUTH.
   425  	}
   426  	_, _, err := c.cmd(250, "%s", sb.String())
   427  	return err
   428  }
   429  
   430  // Rcpt issues a RCPT command to the server using the provided email address.
   431  // A call to Rcpt must be preceded by a call to Mail and may be followed by
   432  // a Data call or another Rcpt call.
   433  //
   434  // If opts is not nil, RCPT arguments provided in the structure will be added
   435  // to the command. Handling of unsupported options depends on the extension.
   436  //
   437  // If server returns an error, it will be of type *SMTPError.
   438  func (c *Client) Rcpt(to string, opts *RcptOptions) error {
   439  	if err := validateLine(to); err != nil {
   440  		return err
   441  	}
   442  
   443  	var sb strings.Builder
   444  	// A high enough power of 2 than 510+29+501
   445  	sb.Grow(2048)
   446  	fmt.Fprintf(&sb, "RCPT TO:<%s>", to)
   447  	if _, ok := c.ext["DSN"]; ok && opts != nil {
   448  		if opts.Notify != nil && len(opts.Notify) != 0 {
   449  			sb.WriteString(" NOTIFY=")
   450  			if err := checkNotifySet(opts.Notify); err != nil {
   451  				return errors.New("smtp: Malformed NOTIFY parameter value")
   452  			}
   453  			for i, v := range opts.Notify {
   454  				if i != 0 {
   455  					sb.WriteString(",")
   456  				}
   457  				sb.WriteString(string(v))
   458  			}
   459  		}
   460  		if opts.OriginalRecipient != "" {
   461  			var enc string
   462  			switch opts.OriginalRecipientType {
   463  			case DSNAddressTypeRFC822:
   464  				if !isPrintableASCII(opts.OriginalRecipient) {
   465  					return errors.New("smtp: Illegal address")
   466  				}
   467  				enc = encodeXtext(opts.OriginalRecipient)
   468  			case DSNAddressTypeUTF8:
   469  				if _, ok := c.ext["SMTPUTF8"]; ok {
   470  					enc = encodeUTF8AddrUnitext(opts.OriginalRecipient)
   471  				} else {
   472  					enc = encodeUTF8AddrXtext(opts.OriginalRecipient)
   473  				}
   474  			default:
   475  				return errors.New("smtp: Unknown address type")
   476  			}
   477  			fmt.Fprintf(&sb, " ORCPT=%s;%s", string(opts.OriginalRecipientType), enc)
   478  		}
   479  	}
   480  	if _, _, err := c.cmd(25, "%s", sb.String()); err != nil {
   481  		return err
   482  	}
   483  	c.rcpts = append(c.rcpts, to)
   484  	return nil
   485  }
   486  
   487  type dataCloser struct {
   488  	c *Client
   489  	io.WriteCloser
   490  	statusCb func(rcpt string, status *SMTPError)
   491  	closed   bool
   492  }
   493  
   494  func (d *dataCloser) Close() error {
   495  	if d.closed {
   496  		return fmt.Errorf("smtp: data writer closed twice")
   497  	}
   498  
   499  	if err := d.WriteCloser.Close(); err != nil {
   500  		return err
   501  	}
   502  
   503  	d.c.conn.SetDeadline(time.Now().Add(d.c.SubmissionTimeout))
   504  	defer d.c.conn.SetDeadline(time.Time{})
   505  
   506  	expectedResponses := len(d.c.rcpts)
   507  	if d.c.lmtp {
   508  		for expectedResponses > 0 {
   509  			rcpt := d.c.rcpts[len(d.c.rcpts)-expectedResponses]
   510  			if _, _, err := d.c.text.ReadResponse(250); err != nil {
   511  				if protoErr, ok := err.(*textproto.Error); ok {
   512  					if d.statusCb != nil {
   513  						d.statusCb(rcpt, toSMTPErr(protoErr))
   514  					}
   515  				} else {
   516  					return err
   517  				}
   518  			} else if d.statusCb != nil {
   519  				d.statusCb(rcpt, nil)
   520  			}
   521  			expectedResponses--
   522  		}
   523  	} else {
   524  		_, _, err := d.c.text.ReadResponse(250)
   525  		if err != nil {
   526  			if protoErr, ok := err.(*textproto.Error); ok {
   527  				return toSMTPErr(protoErr)
   528  			}
   529  			return err
   530  		}
   531  	}
   532  
   533  	d.closed = true
   534  	return nil
   535  }
   536  
   537  // Data issues a DATA command to the server and returns a writer that
   538  // can be used to write the mail headers and body. The caller should
   539  // close the writer before calling any more methods on c. A call to
   540  // Data must be preceded by one or more calls to Rcpt.
   541  //
   542  // If server returns an error, it will be of type *SMTPError.
   543  func (c *Client) Data() (io.WriteCloser, error) {
   544  	_, _, err := c.cmd(354, "DATA")
   545  	if err != nil {
   546  		return nil, err
   547  	}
   548  	return &dataCloser{c: c, WriteCloser: c.text.DotWriter()}, nil
   549  }
   550  
   551  // LMTPData is the LMTP-specific version of the Data method. It accepts a callback
   552  // that will be called for each status response received from the server.
   553  //
   554  // Status callback will receive a SMTPError argument for each negative server
   555  // reply and nil for each positive reply. I/O errors will not be reported using
   556  // callback and instead will be returned by the Close method of io.WriteCloser.
   557  // Callback will be called for each successfull Rcpt call done before in the
   558  // same order.
   559  func (c *Client) LMTPData(statusCb func(rcpt string, status *SMTPError)) (io.WriteCloser, error) {
   560  	if !c.lmtp {
   561  		return nil, errors.New("smtp: not a LMTP client")
   562  	}
   563  
   564  	_, _, err := c.cmd(354, "DATA")
   565  	if err != nil {
   566  		return nil, err
   567  	}
   568  	return &dataCloser{c: c, WriteCloser: c.text.DotWriter(), statusCb: statusCb}, nil
   569  }
   570  
   571  // SendMail will use an existing connection to send an email from
   572  // address from, to addresses to, with message r.
   573  //
   574  // This function does not start TLS, nor does it perform authentication. Use
   575  // StartTLS and Auth before-hand if desirable.
   576  //
   577  // The addresses in the to parameter are the SMTP RCPT addresses.
   578  //
   579  // The r parameter should be an RFC 822-style email with headers
   580  // first, a blank line, and then the message body. The lines of r
   581  // should be CRLF terminated. The r headers should usually include
   582  // fields such as "From", "To", "Subject", and "Cc".  Sending "Bcc"
   583  // messages is accomplished by including an email address in the to
   584  // parameter but not including it in the r headers.
   585  func (c *Client) SendMail(from string, to []string, r io.Reader) error {
   586  	var err error
   587  
   588  	if err = c.Mail(from, nil); err != nil {
   589  		return err
   590  	}
   591  	for _, addr := range to {
   592  		if err = c.Rcpt(addr, nil); err != nil {
   593  			return err
   594  		}
   595  	}
   596  	w, err := c.Data()
   597  	if err != nil {
   598  		return err
   599  	}
   600  	_, err = io.Copy(w, r)
   601  	if err != nil {
   602  		return err
   603  	}
   604  	return w.Close()
   605  }
   606  
   607  var testHookStartTLS func(*tls.Config) // nil, except for tests
   608  
   609  // SendMail connects to the server at addr, switches to TLS, authenticates with
   610  // the optional SASL client, and then sends an email from address from, to
   611  // addresses to, with message r. The addr must include a port, as in
   612  // "mail.example.com:smtp".
   613  //
   614  // The addresses in the to parameter are the SMTP RCPT addresses.
   615  //
   616  // The r parameter should be an RFC 822-style email with headers
   617  // first, a blank line, and then the message body. The lines of r
   618  // should be CRLF terminated. The r headers should usually include
   619  // fields such as "From", "To", "Subject", and "Cc".  Sending "Bcc"
   620  // messages is accomplished by including an email address in the to
   621  // parameter but not including it in the r headers.
   622  //
   623  // SendMail is intended to be used for very simple use-cases. If you want to
   624  // customize SendMail's behavior, use a Client instead.
   625  //
   626  // The SendMail function and the go-smtp package are low-level
   627  // mechanisms and provide no support for DKIM signing (see go-msgauth), MIME
   628  // attachments (see the mime/multipart package or the go-message package), or
   629  // other mail functionality.
   630  func SendMail(addr string, a sasl.Client, from string, to []string, r io.Reader) error {
   631  	if err := validateLine(from); err != nil {
   632  		return err
   633  	}
   634  	for _, recp := range to {
   635  		if err := validateLine(recp); err != nil {
   636  			return err
   637  		}
   638  	}
   639  
   640  	c, err := Dial(addr)
   641  	if err != nil {
   642  		return err
   643  	}
   644  	defer c.Close()
   645  
   646  	if err = c.hello(); err != nil {
   647  		return err
   648  	}
   649  	if ok, _ := c.Extension("STARTTLS"); !ok {
   650  		return errors.New("smtp: server doesn't support STARTTLS")
   651  	}
   652  	if err = c.StartTLS(nil); err != nil {
   653  		return err
   654  	}
   655  	if a != nil {
   656  		if ok, _ := c.Extension("AUTH"); !ok {
   657  			return errors.New("smtp: server doesn't support AUTH")
   658  		}
   659  		if err = c.Auth(a); err != nil {
   660  			return err
   661  		}
   662  	}
   663  	if err := c.SendMail(from, to, r); err != nil {
   664  		return err
   665  	}
   666  	return c.Quit()
   667  }
   668  
   669  // SendMailTLS works like SendMail, but with implicit TLS.
   670  func SendMailTLS(addr string, a sasl.Client, from string, to []string, r io.Reader) error {
   671  	if err := validateLine(from); err != nil {
   672  		return err
   673  	}
   674  	for _, recp := range to {
   675  		if err := validateLine(recp); err != nil {
   676  			return err
   677  		}
   678  	}
   679  
   680  	c, err := DialTLS(addr, nil)
   681  	if err != nil {
   682  		return err
   683  	}
   684  	defer c.Close()
   685  
   686  	if err = c.hello(); err != nil {
   687  		return err
   688  	}
   689  	if a != nil {
   690  		if ok, _ := c.Extension("AUTH"); !ok {
   691  			return errors.New("smtp: server doesn't support AUTH")
   692  		}
   693  		if err = c.Auth(a); err != nil {
   694  			return err
   695  		}
   696  	}
   697  	if err := c.SendMail(from, to, r); err != nil {
   698  		return err
   699  	}
   700  	return c.Quit()
   701  }
   702  
   703  // Extension reports whether an extension is support by the server.
   704  // The extension name is case-insensitive. If the extension is supported,
   705  // Extension also returns a string that contains any parameters the
   706  // server specifies for the extension.
   707  func (c *Client) Extension(ext string) (bool, string) {
   708  	if err := c.hello(); err != nil {
   709  		return false, ""
   710  	}
   711  	if c.ext == nil {
   712  		return false, ""
   713  	}
   714  	ext = strings.ToUpper(ext)
   715  	param, ok := c.ext[ext]
   716  	return ok, param
   717  }
   718  
   719  // Reset sends the RSET command to the server, aborting the current mail
   720  // transaction.
   721  func (c *Client) Reset() error {
   722  	if err := c.hello(); err != nil {
   723  		return err
   724  	}
   725  	if _, _, err := c.cmd(250, "RSET"); err != nil {
   726  		return err
   727  	}
   728  	c.rcpts = nil
   729  	return nil
   730  }
   731  
   732  // Noop sends the NOOP command to the server. It does nothing but check
   733  // that the connection to the server is okay.
   734  func (c *Client) Noop() error {
   735  	if err := c.hello(); err != nil {
   736  		return err
   737  	}
   738  	_, _, err := c.cmd(250, "NOOP")
   739  	return err
   740  }
   741  
   742  // Quit sends the QUIT command and closes the connection to the server.
   743  //
   744  // If Quit fails the connection is not closed, Close should be used
   745  // in this case.
   746  func (c *Client) Quit() error {
   747  	if err := c.hello(); err != nil {
   748  		return err
   749  	}
   750  	_, _, err := c.cmd(221, "QUIT")
   751  	if err != nil {
   752  		return err
   753  	}
   754  	return c.Close()
   755  }
   756  
   757  func parseEnhancedCode(s string) (EnhancedCode, error) {
   758  	parts := strings.Split(s, ".")
   759  	if len(parts) != 3 {
   760  		return EnhancedCode{}, fmt.Errorf("wrong amount of enhanced code parts")
   761  	}
   762  
   763  	code := EnhancedCode{}
   764  	for i, part := range parts {
   765  		num, err := strconv.Atoi(part)
   766  		if err != nil {
   767  			return code, err
   768  		}
   769  		code[i] = num
   770  	}
   771  	return code, nil
   772  }
   773  
   774  // toSMTPErr converts textproto.Error into SMTPError, parsing
   775  // enhanced status code if it is present.
   776  func toSMTPErr(protoErr *textproto.Error) *SMTPError {
   777  	smtpErr := &SMTPError{
   778  		Code:    protoErr.Code,
   779  		Message: protoErr.Msg,
   780  	}
   781  
   782  	parts := strings.SplitN(protoErr.Msg, " ", 2)
   783  	if len(parts) != 2 {
   784  		return smtpErr
   785  	}
   786  
   787  	enchCode, err := parseEnhancedCode(parts[0])
   788  	if err != nil {
   789  		return smtpErr
   790  	}
   791  
   792  	msg := parts[1]
   793  
   794  	// Per RFC 2034, enhanced code should be prepended to each line.
   795  	msg = strings.ReplaceAll(msg, "\n"+parts[0]+" ", "\n")
   796  
   797  	smtpErr.EnhancedCode = enchCode
   798  	smtpErr.Message = msg
   799  	return smtpErr
   800  }
   801  
   802  type clientDebugWriter struct {
   803  	c *Client
   804  }
   805  
   806  func (cdw clientDebugWriter) Write(b []byte) (int, error) {
   807  	if cdw.c.DebugWriter == nil {
   808  		return len(b), nil
   809  	}
   810  	return cdw.c.DebugWriter.Write(b)
   811  }
   812  
   813  // validateLine checks to see if a line has CR or LF.
   814  func validateLine(line string) error {
   815  	if strings.ContainsAny(line, "\n\r") {
   816  		return errors.New("smtp: a line must not contain CR or LF")
   817  	}
   818  	return nil
   819  }