13 "github.com/uber-go/zap"
19 stateNew state
= iota // Before EHLO.
26 type connection
struct {
43 mailFrom
*mail
.Address
47 func AcceptConnection(netConn net
.Conn
, server Server
, log zap
.Logger
) error
{
50 tp
: textproto
.NewConn(netConn
),
52 remoteAddr
: netConn
.RemoteAddr(),
53 log
: log
.With(zap
.Stringer("client", netConn
.RemoteAddr())),
59 conn
.writeReply(220, fmt
.Sprintf("%s ESMTP [%s] (mailpopbox)", server
.Name(), netConn
.LocalAddr()))
62 conn
.line
, err
= conn
.tp
.ReadLine()
64 conn
.writeReply(500, "line too long")
69 if _
, err
= fmt
.Sscanf(conn
.line
, "%s", &cmd
); err
!= nil {
70 conn
.reply(ReplyBadSyntax
)
74 switch strings
.ToUpper(cmd
) {
76 conn
.writeReply(221, "Goodbye")
96 conn
.writeReply(252, "I'll do my best")
98 conn
.writeReply(550, "access denied")
102 conn
.writeReply(250, "https://tools.ietf.org/html/rfc5321")
104 conn
.writeReply(500, "unrecognized command")
111 func (conn
*connection
) reply(reply ReplyLine
) error
{
112 return conn
.writeReply(reply
.Code
, reply
.Message
)
115 func (conn
*connection
) writeReply(code
int, msg
string) error
{
117 return conn
.tp
.PrintfLine("%d %s", code
, msg
)
119 return conn
.tp
.PrintfLine("%d", code
)
123 // parsePath parses out either a forward-, reverse-, or return-path from the
124 // current connection line. Returns a (valid-path, ReplyOK) if it was
125 // successfully parsed.
126 func (conn
*connection
) parsePath(command
string) (string, ReplyLine
) {
127 if len(conn
.line
) < len(command
) {
128 return "", ReplyBadSyntax
130 if strings
.ToUpper(command
) != strings
.ToUpper(conn
.line
[:len(command
)]) {
131 return "", ReplyLine
{500, "unrecognized command"}
133 return conn
.line
[len(command
):], ReplyOK
136 func (conn
*connection
) doEHLO() {
140 _
, err
:= fmt
.Sscanf(conn
.line
, "%s %s", &cmd
, &conn
.ehlo
)
142 conn
.reply(ReplyBadSyntax
)
147 conn
.writeReply(250, fmt
.Sprintf("Hello %s [%s]", conn
.ehlo
, conn
.remoteAddr
))
149 conn
.tp
.PrintfLine("250-Hello %s [%s]", conn
.ehlo
, conn
.remoteAddr
)
150 if conn
.server
.TLSConfig() != nil && conn
.tlsNc
== nil {
151 conn
.tp
.PrintfLine("250-STARTTLS")
153 conn
.tp
.PrintfLine("250 SIZE %d", 40960000)
156 conn
.log
.Info("doEHLO()", zap
.String("ehlo", conn
.ehlo
))
158 conn
.state
= stateInitial
161 func (conn
*connection
) doSTARTTLS() {
162 if conn
.state
!= stateInitial
{
163 conn
.reply(ReplyBadSequence
)
167 tlsConfig
:= conn
.server
.TLSConfig()
168 if !conn
.esmtp || tlsConfig
== nil {
169 conn
.writeReply(500, "unrecognized command")
173 conn
.log
.Info("doSTARTTLS()")
174 conn
.writeReply(220, "initiate TLS connection")
176 newConn
:= tls
.Server(conn
.nc
, tlsConfig
)
177 if err
:= newConn
.Handshake(); err
!= nil {
182 conn
.tp
= textproto
.NewConn(conn
.tlsNc
)
183 conn
.state
= stateInitial
185 conn
.log
.Info("HELO again")
187 conn
.writeReply(220, fmt
.Sprintf("%s ESMTPS [%s] (mailpopbox)",
188 conn
.server
.Name(), newConn
.LocalAddr()))
191 func (conn
*connection
) doMAIL() {
192 if conn
.state
!= stateInitial
{
193 conn
.reply(ReplyBadSequence
)
197 mailFrom
, reply
:= conn
.parsePath("MAIL FROM:")
198 if reply
!= ReplyOK
{
204 conn
.mailFrom
, err
= mail
.ParseAddress(mailFrom
)
206 conn
.reply(ReplyBadSyntax
)
210 conn
.log
.Info("doMAIL()", zap
.String("address", conn
.mailFrom
.Address
))
212 conn
.state
= stateMail
216 func (conn
*connection
) doRCPT() {
217 if conn
.state
!= stateMail
&& conn
.state
!= stateRecipient
{
218 conn
.reply(ReplyBadSequence
)
222 rcptTo
, reply
:= conn
.parsePath("RCPT TO:")
223 if reply
!= ReplyOK
{
228 address
, err
:= mail
.ParseAddress(rcptTo
)
230 conn
.reply(ReplyBadSyntax
)
233 if reply
:= conn
.server
.VerifyAddress(*address
); reply
!= ReplyOK
{
234 conn
.log
.Warn("invalid address",
235 zap
.String("address", address
.Address
),
236 zap
.Stringer("reply", reply
))
241 conn
.log
.Info("doRCPT()", zap
.String("address", address
.Address
))
243 conn
.rcptTo
= append(conn
.rcptTo
, *address
)
245 conn
.state
= stateRecipient
249 func (conn
*connection
) doDATA() {
250 if conn
.state
!= stateRecipient
{
251 conn
.reply(ReplyBadSequence
)
255 conn
.writeReply(354, "Start mail input; end with <CRLF>.<CRLF>")
256 conn
.log
.Info("doDATA()")
258 data
, err
:= conn
.tp
.ReadDotBytes()
260 conn
.log
.Error("failed to ReadDotBytes()", zap
.Error(err
))
261 conn
.writeReply(552, "transaction failed")
265 received
:= time
.Now()
267 RemoteAddr
: conn
.remoteAddr
,
269 MailFrom
: *conn
.mailFrom
,
272 ID
: conn
.envelopeID(received
),
275 conn
.log
.Info("received message",
276 zap
.Int("bytes", len(data
)),
277 zap
.Time("date", received
),
278 zap
.String("id", env
.ID
))
280 trace
:= conn
.getReceivedInfo(env
)
282 env
.Data
= append(trace
, data
...)
284 if reply
:= conn
.server
.OnMessageDelivered(env
); reply
!= nil {
285 conn
.log
.Warn("message was rejected", zap
.String("id", env
.ID
))
290 conn
.state
= stateInitial
294 func (conn
*connection
) envelopeID(t time
.Time
) string {
296 rand
.Read(idBytes
[:])
297 return fmt
.Sprintf("m.%d.%x", t
.UnixNano(), idBytes
)
300 func (conn
*connection
) getReceivedInfo(envelope Envelope
) []byte {
301 rhost
, _
, err
:= net
.SplitHostPort(conn
.remoteAddr
.String())
303 rhost
= conn
.remoteAddr
.String()
306 rhosts
, err
:= net
.LookupAddr(rhost
)
308 rhost
= fmt
.Sprintf("%s [%s]", rhosts
[0], rhost
)
311 base
:= fmt
.Sprintf("Received: from %s (%s)\r\n ", conn
.ehlo
, rhost
)
317 if conn
.tlsNc
!= nil {
320 base
+= fmt
.Sprintf("by %s (mailpopbox) with %s id %s\r\n ", conn
.server
.Name(), with
, envelope
.ID
)
322 base
+= fmt
.Sprintf("for <%s>\r\n ", envelope
.RcptTo
[0].Address
)
324 transport
:= conn
.getTransportString()
325 date
:= envelope
.Received
.Format(time
.RFC1123Z
) // Same as RFC 5322 ยง 3.3
326 base
+= fmt
.Sprintf("(using %s);\r\n %s\r\n", transport
, date
)
331 func (conn
*connection
) getTransportString() string {
332 if conn
.tlsNc
== nil {
336 ciphers
:= map[uint16]string{
337 tls
.TLS_RSA_WITH_RC4_128_SHA
: "TLS_RSA_WITH_RC4_128_SHA",
338 tls
.TLS_RSA_WITH_3DES_EDE_CBC_SHA
: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
339 tls
.TLS_RSA_WITH_AES_128_CBC_SHA
: "TLS_RSA_WITH_AES_128_CBC_SHA",
340 tls
.TLS_RSA_WITH_AES_256_CBC_SHA
: "TLS_RSA_WITH_AES_256_CBC_SHA",
341 tls
.TLS_RSA_WITH_AES_128_GCM_SHA256
: "TLS_RSA_WITH_AES_128_GCM_SHA256",
342 tls
.TLS_RSA_WITH_AES_256_GCM_SHA384
: "TLS_RSA_WITH_AES_256_GCM_SHA384",
343 tls
.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
344 tls
.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
345 tls
.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
346 tls
.TLS_ECDHE_RSA_WITH_RC4_128_SHA
: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
347 tls
.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
348 tls
.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
349 tls
.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
350 tls
.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
351 tls
.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
352 tls
.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
353 tls
.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
355 versions
:= map[uint16]string{
356 tls
.VersionSSL30
: "SSLv3.0",
357 tls
.VersionTLS10
: "TLSv1.0",
358 tls
.VersionTLS11
: "TLSv1.1",
359 tls
.VersionTLS12
: "TLSv1.2",
362 state
:= conn
.tlsNc
.ConnectionState()
364 version
:= versions
[state
.Version
]
365 cipher
:= ciphers
[state
.CipherSuite
]
368 version
= fmt
.Sprintf("%x", state
.Version
)
371 cipher
= fmt
.Sprintf("%x", state
.CipherSuite
)
375 if state
.ServerName
!= "" {
376 name
= fmt
.Sprintf(" name=%s", state
.ServerName
)
379 return fmt
.Sprintf("%s cipher=%s%s", version
, cipher
, name
)
382 func (conn
*connection
) doRSET() {
383 conn
.log
.Info("doRSET()")
384 conn
.state
= stateInitial
389 func (conn
*connection
) resetBuffers() {
391 conn
.rcptTo
= make([]mail
.Address
, 0)