-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsmtp_proxy.go
161 lines (154 loc) · 4.87 KB
/
smtp_proxy.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package deliver
import (
"crypto/tls"
"net"
"net/smtp"
"github.com/vodolaz095/msmtpd"
)
// SMTPProxyOptions used to configure ViaSMTPProxy
type SMTPProxyOptions struct {
// Network defines how we dial SMTP Proxy, can be tcp or unix
Network string
// Address defines where remote SMTP Proxy is available
Address string
// HELO sets greeting for remote server
HELO string
// TLS, if not null, enables STARTTLS with this options
TLS *tls.Config
// Auth sets authentication mechanism for proxy backend
Auth smtp.Auth
// MailFrom defines senders override, if empty, value from Transaction.MailFrom will be used
MailFrom string
// RcptTo defines recipients' override, if empty, values from Transaction.Aliases or Transaction.RcptTo will be used
RcptTo []string
}
func (so *SMTPProxyOptions) String() string {
return so.Network + "+smtp://" + so.Address
}
// ViaSMTPProxy adds DataHandler that performs delivery via 3rd party SMTP server
func ViaSMTPProxy(opts SMTPProxyOptions) msmtpd.DataHandler {
return func(tr *msmtpd.Transaction) error {
if tr.IsFlagSet(DiscardFlag) {
tr.LogInfo("Message was discarded, nothing is send via %s", opts.String())
return nil
}
var i int
var recipientsFound bool
dialer := net.Dialer{
Resolver: tr.Resolver(),
}
conn, err := dialer.DialContext(tr.Context(), opts.Network, opts.Address)
if err != nil {
tr.LogError(err, "error dialing SMTP backend")
return TemporaryError
}
client, err := smtp.NewClient(conn, opts.HELO)
if err != nil {
tr.LogError(err, "error making client to SMTP backend")
return TemporaryError
}
tr.LogDebug("Connection to SMTP backend %s is established via %s", opts.Address, opts.Network)
err = client.Hello(opts.HELO)
if err != nil {
tr.LogError(err, "error making HELO/EHLO to SMTP backend")
return TemporaryError
}
tr.LogDebug("HELO/EHLO %s passed to SMTP backend", opts.HELO)
if opts.TLS != nil {
tr.LogDebug("Starting TLS to SMTP backend...")
err = client.StartTLS(opts.TLS)
if err != nil {
tr.LogError(err, "error making STARTTLS to smtp backend")
return TemporaryError
}
}
if opts.Auth != nil {
tr.LogDebug("Starting Authorization to SMTP backend")
err = client.Auth(opts.Auth)
if err != nil {
tr.LogError(err, "error performing authorization for smtp backend")
return TemporaryError
}
tr.LogDebug("Authorization to SMTP backend is passed")
}
if opts.MailFrom != "" {
tr.LogDebug("Sending `MAIL FROM <%s>` like options says", opts.MailFrom)
err = client.Mail(opts.MailFrom)
} else {
tr.LogDebug("Sending `MAIL FROM <%s>` from transaction", tr.MailFrom.Address)
err = client.Mail(tr.MailFrom.Address)
}
if err != nil {
tr.LogError(err, "error making MAILFROM to smtp backend")
return TemporaryError
}
if opts.RcptTo != nil {
for i = range opts.RcptTo {
tr.LogDebug("Sending `RCPT TO <%s>` from options...", opts.RcptTo[i])
err = client.Rcpt(opts.RcptTo[i])
if err != nil {
tr.LogWarn("%s: proxy recipient override %s is not accepted",
err, opts.RcptTo[i])
} else {
tr.LogDebug("Sending `RCPT TO <%s>` accepted!", opts.RcptTo[i])
recipientsFound = true
}
}
} else {
if tr.Aliases != nil {
for i = range tr.Aliases {
tr.LogDebug("Sending `RCPT TO <%s>` from aliases...", tr.Aliases[i].Address)
err = client.Rcpt(tr.Aliases[i].Address)
if err != nil {
tr.LogWarn("%s: original alias %s is not accepted", err, tr.Aliases[i].Address)
} else {
tr.LogDebug("Sending `RCPT TO <%s>` accepted!", tr.Aliases[i].Address)
recipientsFound = true
}
}
} else {
if tr.RcptTo != nil {
for i = range tr.RcptTo {
tr.LogDebug("Sending `RCPT TO <%s>` from RCPT TO provided by client...", tr.RcptTo[i].Address)
err = client.Rcpt(tr.RcptTo[i].Address)
if err != nil {
tr.LogWarn("%s: original recipient %s is not accepted", err, tr.RcptTo[i].Address)
} else {
tr.LogDebug("Sending `RCPT TO <%s>` accepted!", tr.RcptTo[i].Address)
recipientsFound = true
}
}
}
}
}
if !recipientsFound {
tr.LogWarn("no recipients found")
return UnknownRecipientError
}
wc, err := client.Data()
if err != nil {
tr.LogError(err, "error making DATA to smtp backend")
return TemporaryError
}
tr.LogDebug("DATA started...")
i, err = wc.Write(tr.Body)
if err != nil {
tr.LogError(err, "error writing body to smtp backend")
return TemporaryError
}
tr.LogDebug("%v bytes of DATA is written, closing...", i)
err = wc.Close()
if err != nil {
tr.LogError(err, "error closing data to smtp backend")
return TemporaryError
}
tr.LogDebug("DATA closed...")
err = client.Close()
if err != nil {
tr.LogError(err, "error closing connection to smtp backend")
return TemporaryError
}
tr.LogInfo("Message of %v bytes is proxied to smtp backend %s", i, opts.Address)
return nil
}
}