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
14 type Dialer struct {
15
16 Host string
17
18 Port int
19
20 Username string
21
22 Password string
23
24
25 Auth smtp.Auth
26
27
28
29 SSL bool
30
31
32 TLSConfig *tls.Config
33
34
35
36
37
38
39
40 StartTLSPolicy StartTLSPolicy
41
42
43 LocalName string
44
45
46 Timeout time.Duration
47
48
49 RetryFailure bool
50 }
51
52
53
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
67
68
69
70 func NewPlainDialer(host string, port int, username, password string) *Dialer {
71 return NewDialer(host, port, username, password)
72 }
73
74
75
76
77 var NetDialTimeout = net.DialTimeout
78
79
80
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
157 type StartTLSPolicy int
158
159 const (
160
161
162
163 OpportunisticStartTLS StartTLSPolicy = iota
164
165
166
167 MandatoryStartTLS
168
169
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
187
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
202
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
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
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