1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
package send
import (
"crypto/tls"
"fmt"
"io"
"net/url"
"strings"
"github.com/emersion/go-message/mail"
"github.com/emersion/go-smtp"
"github.com/pkg/errors"
)
func connectSmtp(starttls bool, host string, domain string) (*smtp.Client, error) {
serverName := host
if !strings.ContainsRune(host, ':') {
host += ":587" // Default to submission port
} else {
serverName = host[:strings.IndexRune(host, ':')]
}
conn, err := smtp.Dial(host)
if err != nil {
return nil, errors.Wrap(err, "smtp.Dial")
}
if domain != "" {
err := conn.Hello(domain)
if err != nil {
return nil, errors.Wrap(err, "Hello")
}
}
if starttls {
if sup, _ := conn.Extension("STARTTLS"); !sup {
err := errors.New("STARTTLS requested, but not supported " +
"by this SMTP server. Is someone tampering with your " +
"connection?")
conn.Close()
return nil, err
}
if err = conn.StartTLS(&tls.Config{
ServerName: serverName,
}); err != nil {
conn.Close()
return nil, errors.Wrap(err, "StartTLS")
}
}
return conn, nil
}
func connectSmtps(host string) (*smtp.Client, error) {
serverName := host
if !strings.ContainsRune(host, ':') {
host += ":465" // Default to smtps port
} else {
serverName = host[:strings.IndexRune(host, ':')]
}
conn, err := smtp.DialTLS(host, &tls.Config{
ServerName: serverName,
})
if err != nil {
return nil, errors.Wrap(err, "smtp.DialTLS")
}
return conn, nil
}
type smtpSender struct {
conn *smtp.Client
w io.WriteCloser
}
func (s *smtpSender) Write(p []byte) (int, error) {
return s.w.Write(p)
}
func (s *smtpSender) Close() error {
we := s.w.Close()
ce := s.conn.Close()
if we != nil {
return we
}
return ce
}
func newSmtpSender(
protocol string, auth string, uri *url.URL, domain string,
from *mail.Address, rcpts []*mail.Address,
) (io.WriteCloser, error) {
var err error
var conn *smtp.Client
switch protocol {
case "smtp":
conn, err = connectSmtp(true, uri.Host, domain)
case "smtp+insecure":
conn, err = connectSmtp(false, uri.Host, domain)
case "smtps":
conn, err = connectSmtps(uri.Host)
default:
return nil, fmt.Errorf("not a smtp protocol %s", protocol)
}
if err != nil {
return nil, errors.Wrap(err, "Connection failed")
}
saslclient, err := newSaslClient(auth, uri)
if err != nil {
conn.Close()
return nil, err
}
if saslclient != nil {
if err := conn.Auth(saslclient); err != nil {
conn.Close()
return nil, errors.Wrap(err, "conn.Auth")
}
}
s := &smtpSender{
conn: conn,
}
if err := s.conn.Mail(from.Address, nil); err != nil {
conn.Close()
return nil, errors.Wrap(err, "conn.Mail")
}
for _, rcpt := range rcpts {
if err := s.conn.Rcpt(rcpt.Address); err != nil {
conn.Close()
return nil, errors.Wrap(err, "conn.Rcpt")
}
}
s.w, err = s.conn.Data()
if err != nil {
conn.Close()
return nil, errors.Wrap(err, "conn.Data")
}
return s.w, nil
}
|