-
Notifications
You must be signed in to change notification settings - Fork 21
Expand file tree
/
Copy pathclient.go
More file actions
166 lines (142 loc) · 3.83 KB
/
client.go
File metadata and controls
166 lines (142 loc) · 3.83 KB
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package main
import (
"bytes"
"crypto/tls"
"fmt"
"io"
"net"
"net/smtp"
"net/textproto"
"strconv"
"github.com/phires/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 := net.JoinHostPort(config.Server, strconv.Itoa(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)
Logger.Infof("Client Remote IP: %s", e.RemoteIP)
var err error
var conn net.Conn
var client *smtp.Client
var writer io.WriteCloser
if AllowedSendersFilter.Blocked(e.RemoteIP) {
Logger.Info("Remote IP of " + e.RemoteIP + " not allowed to send email.")
return errors.New("Remote IP of " + e.RemoteIP + " not allowed to send email.")
}
tlsconfig := &tls.Config{
// InsecureSkipVerify is configurable to support legacy SMTP servers with
// self-signed certificates or hostname mismatches. This should only be
// enabled in trusted network environments.
InsecureSkipVerify: config.SkipVerify,
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 {
closeConn(conn, "conn")
return errors.Wrap(err, "newclient error")
}
shouldCloseClient := true
defer func(shouldClose *bool) {
if *shouldClose {
closeConn(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())
closeConn(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.HeloHost != "" {
if err := client.Hello(config.HeloHost); err != nil {
return errors.Wrap(err, "HELO 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 if config.Username != "" {
auth = smtp.PlainAuth("", config.Username, config.Password, config.Server)
}
if auth != nil {
if err := client.Auth(auth); err != nil {
return errors.Wrap(err, "auth error")
}
}
return nil
}
func closeConn(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
}
var e *textproto.Error
ok := errors.As(err, &e)
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 {
if len(e.RcptTo) == 0 {
return nil
}
ret := make([]string, 0, len(e.RcptTo))
for i := range e.RcptTo {
ret = append(ret, e.RcptTo[i].String())
}
return ret
}