...

Source file src/github.com/go-mail/mail/smtp.go

Documentation: github.com/go-mail/mail

     1  package mail
     2  
     3  import (
     4  	"crypto/tls"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"net/smtp"
     9  	"strings"
    10  	"time"
    11  )
    12  
    13  // A Dialer is a dialer to an SMTP server.
    14  type Dialer struct {
    15  	// Host represents the host of the SMTP server.
    16  	Host string
    17  	// Port represents the port of the SMTP server.
    18  	Port int
    19  	// Username is the username to use to authenticate to the SMTP server.
    20  	Username string
    21  	// Password is the password to use to authenticate to the SMTP server.
    22  	Password string
    23  	// Auth represents the authentication mechanism used to authenticate to the
    24  	// SMTP server.
    25  	Auth smtp.Auth
    26  	// SSL defines whether an SSL connection is used. It should be false in
    27  	// most cases since the authentication mechanism should use the STARTTLS
    28  	// extension instead.
    29  	SSL bool
    30  	// TLSConfig represents the TLS configuration used for the TLS (when the
    31  	// STARTTLS extension is used) or SSL connection.
    32  	TLSConfig *tls.Config
    33  	// StartTLSPolicy represents the TLS security level required to
    34  	// communicate with the SMTP server.
    35  	//
    36  	// This defaults to OpportunisticStartTLS for backwards compatibility,
    37  	// but we recommend MandatoryStartTLS for all modern SMTP servers.
    38  	//
    39  	// This option has no effect if SSL is set to true.
    40  	StartTLSPolicy StartTLSPolicy
    41  	// LocalName is the hostname sent to the SMTP server with the HELO command.
    42  	// By default, "localhost" is sent.
    43  	LocalName string
    44  	// Timeout to use for read/write operations. Defaults to 10 seconds, can
    45  	// be set to 0 to disable timeouts.
    46  	Timeout time.Duration
    47  	// Whether we should retry mailing if the connection returned an error,
    48  	// defaults to true.
    49  	RetryFailure bool
    50  }
    51  
    52  // NewDialer returns a new SMTP Dialer. The given parameters are used to connect
    53  // to the SMTP server.
    54  func NewDialer(host string, port int, username, password string) *Dialer {
    55  	return &Dialer{
    56  		Host:         host,
    57  		Port:         port,
    58  		Username:     username,
    59  		Password:     password,
    60  		SSL:          port == 465,
    61  		Timeout:      10 * time.Second,
    62  		RetryFailure: true,
    63  	}
    64  }
    65  
    66  // NewPlainDialer returns a new SMTP Dialer. The given parameters are used to
    67  // connect to the SMTP server.
    68  //
    69  // Deprecated: Use NewDialer instead.
    70  func NewPlainDialer(host string, port int, username, password string) *Dialer {
    71  	return NewDialer(host, port, username, password)
    72  }
    73  
    74  // NetDialTimeout specifies the DialTimeout function to establish a connection
    75  // to the SMTP server. This can be used to override dialing in the case that a
    76  // proxy or other special behavior is needed.
    77  var NetDialTimeout = net.DialTimeout
    78  
    79  // Dial dials and authenticates to an SMTP server. The returned SendCloser
    80  // should be closed when done using it.
    81  func (d *Dialer) Dial() (SendCloser, error) {
    82  	conn, err := NetDialTimeout("tcp", addr(d.Host, d.Port), d.Timeout)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	if d.SSL {
    88  		conn = tlsClient(conn, d.tlsConfig())
    89  	}
    90  
    91  	c, err := smtpNewClient(conn, d.Host)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	if d.Timeout > 0 {
    97  		conn.SetDeadline(time.Now().Add(d.Timeout))
    98  	}
    99  
   100  	if d.LocalName != "" {
   101  		if err := c.Hello(d.LocalName); err != nil {
   102  			return nil, err
   103  		}
   104  	}
   105  
   106  	if !d.SSL && d.StartTLSPolicy != NoStartTLS {
   107  		ok, _ := c.Extension("STARTTLS")
   108  		if !ok && d.StartTLSPolicy == MandatoryStartTLS {
   109  			err := StartTLSUnsupportedError{
   110  				Policy: d.StartTLSPolicy}
   111  			return nil, err
   112  		}
   113  
   114  		if ok {
   115  			if err := c.StartTLS(d.tlsConfig()); err != nil {
   116  				c.Close()
   117  				return nil, err
   118  			}
   119  		}
   120  	}
   121  
   122  	if d.Auth == nil && d.Username != "" {
   123  		if ok, auths := c.Extension("AUTH"); ok {
   124  			if strings.Contains(auths, "CRAM-MD5") {
   125  				d.Auth = smtp.CRAMMD5Auth(d.Username, d.Password)
   126  			} else if strings.Contains(auths, "LOGIN") &&
   127  				!strings.Contains(auths, "PLAIN") {
   128  				d.Auth = &loginAuth{
   129  					username: d.Username,
   130  					password: d.Password,
   131  					host:     d.Host,
   132  				}
   133  			} else {
   134  				d.Auth = smtp.PlainAuth("", d.Username, d.Password, d.Host)
   135  			}
   136  		}
   137  	}
   138  
   139  	if d.Auth != nil {
   140  		if err = c.Auth(d.Auth); err != nil {
   141  			c.Close()
   142  			return nil, err
   143  		}
   144  	}
   145  
   146  	return &smtpSender{c, conn, d}, nil
   147  }
   148  
   149  func (d *Dialer) tlsConfig() *tls.Config {
   150  	if d.TLSConfig == nil {
   151  		return &tls.Config{ServerName: d.Host}
   152  	}
   153  	return d.TLSConfig
   154  }
   155  
   156  // StartTLSPolicy constants are valid values for Dialer.StartTLSPolicy.
   157  type StartTLSPolicy int
   158  
   159  const (
   160  	// OpportunisticStartTLS means that SMTP transactions are encrypted if
   161  	// STARTTLS is supported by the SMTP server. Otherwise, messages are
   162  	// sent in the clear. This is the default setting.
   163  	OpportunisticStartTLS StartTLSPolicy = iota
   164  	// MandatoryStartTLS means that SMTP transactions must be encrypted.
   165  	// SMTP transactions are aborted unless STARTTLS is supported by the
   166  	// SMTP server.
   167  	MandatoryStartTLS
   168  	// NoStartTLS means encryption is disabled and messages are sent in the
   169  	// clear.
   170  	NoStartTLS = -1
   171  )
   172  
   173  func (policy *StartTLSPolicy) String() string {
   174  	switch *policy {
   175  	case OpportunisticStartTLS:
   176  		return "OpportunisticStartTLS"
   177  	case MandatoryStartTLS:
   178  		return "MandatoryStartTLS"
   179  	case NoStartTLS:
   180  		return "NoStartTLS"
   181  	default:
   182  		return fmt.Sprintf("StartTLSPolicy:%v", *policy)
   183  	}
   184  }
   185  
   186  // StartTLSUnsupportedError is returned by Dial when connecting to an SMTP
   187  // server that does not support STARTTLS.
   188  type StartTLSUnsupportedError struct {
   189  	Policy StartTLSPolicy
   190  }
   191  
   192  func (e StartTLSUnsupportedError) Error() string {
   193  	return "gomail: " + e.Policy.String() + " required, but " +
   194  		"SMTP server does not support STARTTLS"
   195  }
   196  
   197  func addr(host string, port int) string {
   198  	return fmt.Sprintf("%s:%d", host, port)
   199  }
   200  
   201  // DialAndSend opens a connection to the SMTP server, sends the given emails and
   202  // closes the connection.
   203  func (d *Dialer) DialAndSend(m ...*Message) error {
   204  	s, err := d.Dial()
   205  	if err != nil {
   206  		return err
   207  	}
   208  	defer s.Close()
   209  
   210  	return Send(s, m...)
   211  }
   212  
   213  type smtpSender struct {
   214  	smtpClient
   215  	conn net.Conn
   216  	d    *Dialer
   217  }
   218  
   219  func (c *smtpSender) retryError(err error) bool {
   220  	if !c.d.RetryFailure {
   221  		return false
   222  	}
   223  
   224  	if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
   225  		return true
   226  	}
   227  
   228  	return err == io.EOF
   229  }
   230  
   231  func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
   232  	if c.d.Timeout > 0 {
   233  		c.conn.SetDeadline(time.Now().Add(c.d.Timeout))
   234  	}
   235  
   236  	if err := c.Mail(from); err != nil {
   237  		if c.retryError(err) {
   238  			// This is probably due to a timeout, so reconnect and try again.
   239  			sc, derr := c.d.Dial()
   240  			if derr == nil {
   241  				if s, ok := sc.(*smtpSender); ok {
   242  					*c = *s
   243  					return c.Send(from, to, msg)
   244  				}
   245  			}
   246  		}
   247  
   248  		return err
   249  	}
   250  
   251  	for _, addr := range to {
   252  		if err := c.Rcpt(addr); err != nil {
   253  			return err
   254  		}
   255  	}
   256  
   257  	w, err := c.Data()
   258  	if err != nil {
   259  		return err
   260  	}
   261  
   262  	if _, err = msg.WriteTo(w); err != nil {
   263  		w.Close()
   264  		return err
   265  	}
   266  
   267  	return w.Close()
   268  }
   269  
   270  func (c *smtpSender) Close() error {
   271  	return c.Quit()
   272  }
   273  
   274  // Stubbed out for tests.
   275  var (
   276  	tlsClient     = tls.Client
   277  	smtpNewClient = func(conn net.Conn, host string) (smtpClient, error) {
   278  		return smtp.NewClient(conn, host)
   279  	}
   280  )
   281  
   282  type smtpClient interface {
   283  	Hello(string) error
   284  	Extension(string) (bool, string)
   285  	StartTLS(*tls.Config) error
   286  	Auth(smtp.Auth) error
   287  	Mail(string) error
   288  	Rcpt(string) error
   289  	Data() (io.WriteCloser, error)
   290  	Quit() error
   291  	Close() error
   292  }
   293  

View as plain text