-
Notifications
You must be signed in to change notification settings - Fork 20
/
client.go
142 lines (122 loc) · 3.11 KB
/
client.go
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
137
138
139
140
141
142
package main
import (
"bytes"
"crypto/tls"
"fmt"
"io"
"net"
"net/smtp"
"net/textproto"
"github.com/flashmob/go-guerrilla/mail"
"github.com/pkg/errors"
)
type closeable interface {
Close() error
}
// sendMail sends the contents of the envelope to a SMTP server.
func sendMail(e *mail.Envelope, config *relayConfig) error {
server := fmt.Sprintf("%s:%d", config.Server, config.Port)
to := getTo(e)
var msg bytes.Buffer
msg.Write(e.Data.Bytes())
msg.WriteString("\r\n")
Logger.Infof("starting email send -- from:%s, starttls:%t", e.MailFrom.String(), config.STARTTLS)
var err error
var conn net.Conn
var client *smtp.Client
var writer io.WriteCloser
tlsconfig := &tls.Config{
InsecureSkipVerify: config.SkipVerify, //nolint:gosec
ServerName: config.Server,
}
if config.STARTTLS {
if conn, err = net.Dial("tcp", server); err != nil {
return errors.Wrap(err, "dial error")
}
} else {
if conn, err = tls.Dial("tcp", server, tlsconfig); err != nil {
return errors.Wrap(err, "TLS dial error")
}
}
if client, err = smtp.NewClient(conn, config.Server); err != nil {
close(conn, "conn")
return errors.Wrap(err, "newclient error")
}
shouldCloseClient := true
defer func(shouldClose *bool) {
if *shouldClose {
close(client, "client")
}
}(&shouldCloseClient)
if err = handshake(client, config, tlsconfig); err != nil {
return err
}
if err = client.Mail(e.MailFrom.String()); err != nil {
return errors.Wrap(err, "mail error")
}
for _, addy := range to {
if err = client.Rcpt(addy); err != nil {
return errors.Wrap(err, "rcpt error")
}
}
if writer, err = client.Data(); err != nil {
return errors.Wrap(err, "data error")
}
_, err = writer.Write(msg.Bytes())
close(writer, "writer")
if err != nil {
return errors.Wrap(err, "write error")
}
if err = client.Quit(); isQuitError(err) {
return errors.Wrap(err, "quit error")
}
// We only need to close client if some other error prevented us
// from getting to `client.Quit`
shouldCloseClient = false
Logger.Info("email sent with no errors.")
return nil
}
func handshake(client *smtp.Client, config *relayConfig, tlsConfig *tls.Config) error {
if config.STARTTLS {
if err := client.StartTLS(tlsConfig); err != nil {
return errors.Wrap(err, "starttls error")
}
}
var auth smtp.Auth
if config.LoginAuthType {
auth = LoginAuth(config.Username, config.Password)
} else {
auth = smtp.PlainAuth("", config.Username, config.Password, config.Server)
}
if err := client.Auth(auth); err != nil {
return errors.Wrap(err, "auth error")
}
return nil
}
func close(c closeable, what string) {
err := c.Close()
if err != nil {
fmt.Printf("Error closing %s: %v\n", what, err)
}
}
func isQuitError(err error) bool {
if err == nil {
return false
}
e, ok := err.(*textproto.Error)
if ok {
// SMTP codes 221 or 250 are acceptable here
if e.Code == 221 || e.Code == 250 {
return false
}
}
return true
}
// getTo returns the array of email addresses in the envelope.
func getTo(e *mail.Envelope) []string {
var ret []string
for i := range e.RcptTo {
ret = append(ret, e.RcptTo[i].String())
}
return ret
}