1package gomail
2
3import (
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.
14type 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	// TSLConfig represents the TLS configuration used for the TLS (when the
31	// STARTTLS extension is used) or SSL connection.
32	TLSConfig *tls.Config
33	// LocalName is the hostname sent to the SMTP server with the HELO command.
34	// By default, "localhost" is sent.
35	LocalName string
36}
37
38// NewDialer returns a new SMTP Dialer. The given parameters are used to connect
39// to the SMTP server.
40func NewDialer(host string, port int, username, password string) *Dialer {
41	return &Dialer{
42		Host:     host,
43		Port:     port,
44		Username: username,
45		Password: password,
46		SSL:      port == 465,
47	}
48}
49
50// NewPlainDialer returns a new SMTP Dialer. The given parameters are used to
51// connect to the SMTP server.
52//
53// Deprecated: Use NewDialer instead.
54func NewPlainDialer(host string, port int, username, password string) *Dialer {
55	return NewDialer(host, port, username, password)
56}
57
58// Dial dials and authenticates to an SMTP server. The returned SendCloser
59// should be closed when done using it.
60func (d *Dialer) Dial() (SendCloser, error) {
61	conn, err := netDialTimeout("tcp", addr(d.Host, d.Port), 10*time.Second)
62	if err != nil {
63		return nil, err
64	}
65
66	if d.SSL {
67		conn = tlsClient(conn, d.tlsConfig())
68	}
69
70	c, err := smtpNewClient(conn, d.Host)
71	if err != nil {
72		return nil, err
73	}
74
75	if d.LocalName != "" {
76		if err := c.Hello(d.LocalName); err != nil {
77			return nil, err
78		}
79	}
80
81	if !d.SSL {
82		if ok, _ := c.Extension("STARTTLS"); ok {
83			if err := c.StartTLS(d.tlsConfig()); err != nil {
84				c.Close()
85				return nil, err
86			}
87		}
88	}
89
90	if d.Auth == nil && d.Username != "" {
91		if ok, auths := c.Extension("AUTH"); ok {
92			if strings.Contains(auths, "CRAM-MD5") {
93				d.Auth = smtp.CRAMMD5Auth(d.Username, d.Password)
94			} else if strings.Contains(auths, "LOGIN") &&
95				!strings.Contains(auths, "PLAIN") {
96				d.Auth = &loginAuth{
97					username: d.Username,
98					password: d.Password,
99					host:     d.Host,
100				}
101			} else {
102				d.Auth = smtp.PlainAuth("", d.Username, d.Password, d.Host)
103			}
104		}
105	}
106
107	if d.Auth != nil {
108		if err = c.Auth(d.Auth); err != nil {
109			c.Close()
110			return nil, err
111		}
112	}
113
114	return &smtpSender{c, d}, nil
115}
116
117func (d *Dialer) tlsConfig() *tls.Config {
118	if d.TLSConfig == nil {
119		return &tls.Config{ServerName: d.Host}
120	}
121	return d.TLSConfig
122}
123
124func addr(host string, port int) string {
125	return fmt.Sprintf("%s:%d", host, port)
126}
127
128// DialAndSend opens a connection to the SMTP server, sends the given emails and
129// closes the connection.
130func (d *Dialer) DialAndSend(m ...*Message) error {
131	s, err := d.Dial()
132	if err != nil {
133		return err
134	}
135	defer s.Close()
136
137	return Send(s, m...)
138}
139
140type smtpSender struct {
141	smtpClient
142	d *Dialer
143}
144
145func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
146	if err := c.Mail(from); err != nil {
147		if err == io.EOF {
148			// This is probably due to a timeout, so reconnect and try again.
149			sc, derr := c.d.Dial()
150			if derr == nil {
151				if s, ok := sc.(*smtpSender); ok {
152					*c = *s
153					return c.Send(from, to, msg)
154				}
155			}
156		}
157		return err
158	}
159
160	for _, addr := range to {
161		if err := c.Rcpt(addr); err != nil {
162			return err
163		}
164	}
165
166	w, err := c.Data()
167	if err != nil {
168		return err
169	}
170
171	if _, err = msg.WriteTo(w); err != nil {
172		w.Close()
173		return err
174	}
175
176	return w.Close()
177}
178
179func (c *smtpSender) Close() error {
180	return c.Quit()
181}
182
183// Stubbed out for tests.
184var (
185	netDialTimeout = net.DialTimeout
186	tlsClient      = tls.Client
187	smtpNewClient  = func(conn net.Conn, host string) (smtpClient, error) {
188		return smtp.NewClient(conn, host)
189	}
190)
191
192type smtpClient interface {
193	Hello(string) error
194	Extension(string) (bool, string)
195	StartTLS(*tls.Config) error
196	Auth(smtp.Auth) error
197	Mail(string) error
198	Rcpt(string) error
199	Data() (io.WriteCloser, error)
200	Quit() error
201	Close() error
202}
203