14 "github.com/uber-go/zap"
20 stateNew state
= iota // Before EHLO.
27 type connection
struct {
36 tls
*tls
.ConnectionState
38 // The authcid from a PLAIN SASL login. Non-empty iff tls is non-nil and
39 // doAUTH() succeeded.
48 mailFrom
*mail
.Address
52 func AcceptConnection(netConn net
.Conn
, server Server
, log zap
.Logger
) {
55 tp
: textproto
.NewConn(netConn
),
57 remoteAddr
: netConn
.RemoteAddr(),
58 log
: log
.With(zap
.Stringer("client", netConn
.RemoteAddr())),
62 conn
.log
.Info("accepted connection")
63 conn
.writeReply(220, fmt
.Sprintf("%s ESMTP [%s] (mailpopbox)",
64 server
.Name(), netConn
.LocalAddr()))
68 conn
.line
, err
= conn
.tp
.ReadLine()
70 conn
.log
.Error("ReadLine()", zap
.Error(err
))
75 conn
.log
.Info("ReadLine()", zap
.String("line", conn
.line
))
78 if _
, err
= fmt
.Sscanf(conn
.line
, "%s", &cmd
); err
!= nil {
79 conn
.reply(ReplyBadSyntax
)
83 switch strings
.ToUpper(cmd
) {
85 conn
.writeReply(221, "Goodbye")
107 conn
.writeReply(252, "I'll do my best")
109 conn
.writeReply(550, "access denied")
113 conn
.writeReply(250, "https://tools.ietf.org/html/rfc5321")
115 conn
.writeReply(500, "unrecognized command")
120 func (conn
*connection
) reply(reply ReplyLine
) error
{
121 return conn
.writeReply(reply
.Code
, reply
.Message
)
124 func (conn
*connection
) writeReply(code
int, msg
string) error
{
125 conn
.log
.Info("writeReply", zap
.Int("code", code
))
128 err
= conn
.tp
.PrintfLine("%d %s", code
, msg
)
130 err
= conn
.tp
.PrintfLine("%d", code
)
133 conn
.log
.Error("writeReply",
134 zap
.Int("code", code
),
140 // parsePath parses out either a forward-, reverse-, or return-path from the
141 // current connection line. Returns a (valid-path, ReplyOK) if it was
142 // successfully parsed.
143 func (conn
*connection
) parsePath(command
string) (string, ReplyLine
) {
144 if len(conn
.line
) < len(command
) {
145 return "", ReplyBadSyntax
147 if strings
.ToUpper(command
) != strings
.ToUpper(conn
.line
[:len(command
)]) {
148 return "", ReplyLine
{500, "unrecognized command"}
150 params
:= conn
.line
[len(command
):]
151 idx
:= strings
.Index(params
, ">")
153 return "", ReplyBadSyntax
155 return strings
.ToLower(params
[:idx
+1]), ReplyOK
158 func (conn
*connection
) doEHLO() {
162 _
, err
:= fmt
.Sscanf(conn
.line
, "%s %s", &cmd
, &conn
.ehlo
)
164 conn
.reply(ReplyBadSyntax
)
169 conn
.writeReply(250, fmt
.Sprintf("Hello %s [%s]", conn
.ehlo
, conn
.remoteAddr
))
171 conn
.tp
.PrintfLine("250-Hello %s [%s]", conn
.ehlo
, conn
.remoteAddr
)
172 if conn
.server
.TLSConfig() != nil && conn
.tls
== nil {
173 conn
.tp
.PrintfLine("250-STARTTLS")
176 conn
.tp
.PrintfLine("250-AUTH PLAIN")
178 conn
.tp
.PrintfLine("250 SIZE %d", 40960000)
181 conn
.log
.Info("doEHLO()", zap
.String("ehlo", conn
.ehlo
))
183 conn
.state
= stateInitial
186 func (conn
*connection
) doSTARTTLS() {
187 if conn
.state
!= stateInitial
{
188 conn
.reply(ReplyBadSequence
)
192 tlsConfig
:= conn
.server
.TLSConfig()
193 if !conn
.esmtp || tlsConfig
== nil {
194 conn
.writeReply(500, "unrecognized command")
198 conn
.log
.Info("doSTARTTLS()")
199 conn
.writeReply(220, "initiate TLS connection")
201 tlsConn
:= tls
.Server(conn
.nc
, tlsConfig
)
202 if err
:= tlsConn
.Handshake(); err
!= nil {
203 conn
.log
.Error("failed to do TLS handshake", zap
.Error(err
))
208 conn
.tp
= textproto
.NewConn(tlsConn
)
209 conn
.state
= stateNew
211 connState
:= tlsConn
.ConnectionState()
212 conn
.tls
= &connState
214 conn
.log
.Info("TLS connection done", zap
.String("state", conn
.getTransportString()))
217 func (conn
*connection
) doAUTH() {
218 if conn
.state
!= stateInitial || conn
.tls
== nil {
219 conn
.reply(ReplyBadSequence
)
223 if conn
.authc
!= "" {
224 conn
.writeReply(503, "already authenticated")
228 var cmd
, authType
string
229 _
, err
:= fmt
.Sscanf(conn
.line
, "%s %s", &cmd
, &authType
)
231 conn
.reply(ReplyBadSyntax
)
235 if authType
!= "PLAIN" {
236 conn
.writeReply(504, "unrecognized auth type")
240 conn
.log
.Info("doAUTH()")
242 conn
.writeReply(334, " ")
244 authLine
, err
:= conn
.tp
.ReadLine()
246 conn
.log
.Error("failed to read auth line", zap
.Error(err
))
247 conn
.reply(ReplyBadSyntax
)
251 authBytes
, err
:= base64
.StdEncoding
.DecodeString(authLine
)
253 conn
.reply(ReplyBadSyntax
)
257 authParts
:= strings
.Split(string(authBytes
), "\x00")
258 if len(authParts
) != 3 {
259 conn
.log
.Error("bad auth line syntax")
260 conn
.reply(ReplyBadSyntax
)
264 if !conn
.server
.Authenticate(authParts
[0], authParts
[1], authParts
[2]) {
265 conn
.log
.Error("failed to authenticate", zap
.String("authc", authParts
[1]))
266 conn
.writeReply(535, "invalid credentials")
270 conn
.log
.Info("authenticated", zap
.String("authz", authParts
[0]), zap
.String("authc", authParts
[1]))
271 conn
.authc
= authParts
[1]
275 func (conn
*connection
) doMAIL() {
276 if conn
.state
!= stateInitial
{
277 conn
.reply(ReplyBadSequence
)
281 mailFrom
, reply
:= conn
.parsePath("MAIL FROM:")
282 if reply
!= ReplyOK
{
288 conn
.mailFrom
, err
= mail
.ParseAddress(mailFrom
)
289 if err
!= nil || conn
.mailFrom
== nil {
290 conn
.reply(ReplyBadSyntax
)
294 conn
.log
.Info("doMAIL()", zap
.String("address", conn
.mailFrom
.Address
))
296 conn
.state
= stateMail
300 func (conn
*connection
) doRCPT() {
301 if conn
.state
!= stateMail
&& conn
.state
!= stateRecipient
{
302 conn
.reply(ReplyBadSequence
)
306 rcptTo
, reply
:= conn
.parsePath("RCPT TO:")
307 if reply
!= ReplyOK
{
312 address
, err
:= mail
.ParseAddress(rcptTo
)
314 conn
.reply(ReplyBadSyntax
)
318 if reply
:= conn
.server
.VerifyAddress(*address
); reply
!= ReplyOK
{
319 conn
.log
.Warn("invalid address",
320 zap
.String("address", address
.Address
),
321 zap
.Stringer("reply", reply
))
326 conn
.log
.Info("doRCPT()", zap
.String("address", address
.Address
))
328 conn
.rcptTo
= append(conn
.rcptTo
, *address
)
330 conn
.state
= stateRecipient
334 func (conn
*connection
) doDATA() {
335 if conn
.state
!= stateRecipient
{
336 conn
.reply(ReplyBadSequence
)
340 conn
.writeReply(354, "Start mail input; end with <CRLF>.<CRLF>")
341 conn
.log
.Info("doDATA()")
343 data
, err
:= conn
.tp
.ReadDotBytes()
345 conn
.log
.Error("failed to ReadDotBytes()",
347 zap
.String("bytes", fmt
.Sprintf("%x", data
)))
348 conn
.writeReply(552, "transaction failed")
352 received
:= time
.Now()
354 RemoteAddr
: conn
.remoteAddr
,
356 MailFrom
: *conn
.mailFrom
,
359 ID
: conn
.envelopeID(received
),
362 conn
.log
.Info("received message",
363 zap
.Int("bytes", len(data
)),
364 zap
.Time("date", received
),
365 zap
.String("id", env
.ID
))
367 trace
:= conn
.getReceivedInfo(env
)
369 env
.Data
= append(trace
, data
...)
371 if reply
:= conn
.server
.OnMessageDelivered(env
); reply
!= nil {
372 conn
.log
.Warn("message was rejected", zap
.String("id", env
.ID
))
377 conn
.state
= stateInitial
381 func (conn
*connection
) envelopeID(t time
.Time
) string {
383 rand
.Read(idBytes
[:])
384 return fmt
.Sprintf("m.%d.%x", t
.UnixNano(), idBytes
)
387 func (conn
*connection
) getReceivedInfo(envelope Envelope
) []byte {
388 rhost
, _
, err
:= net
.SplitHostPort(conn
.remoteAddr
.String())
390 rhost
= conn
.remoteAddr
.String()
393 rhosts
, err
:= net
.LookupAddr(rhost
)
395 rhost
= fmt
.Sprintf("%s [%s]", rhosts
[0], rhost
)
398 base
:= fmt
.Sprintf("Received: from %s (%s)\r\n ", conn
.ehlo
, rhost
)
407 base
+= fmt
.Sprintf("by %s (mailpopbox) with %s id %s\r\n ", conn
.server
.Name(), with
, envelope
.ID
)
409 base
+= fmt
.Sprintf("for <%s>\r\n ", envelope
.RcptTo
[0].Address
)
411 transport
:= conn
.getTransportString()
412 date
:= envelope
.Received
.Format(time
.RFC1123Z
) // Same as RFC 5322 ยง 3.3
413 base
+= fmt
.Sprintf("(using %s);\r\n %s\r\n", transport
, date
)
418 func (conn
*connection
) getTransportString() string {
423 ciphers
:= map[uint16]string{
424 tls
.TLS_RSA_WITH_RC4_128_SHA
: "TLS_RSA_WITH_RC4_128_SHA",
425 tls
.TLS_RSA_WITH_3DES_EDE_CBC_SHA
: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
426 tls
.TLS_RSA_WITH_AES_128_CBC_SHA
: "TLS_RSA_WITH_AES_128_CBC_SHA",
427 tls
.TLS_RSA_WITH_AES_256_CBC_SHA
: "TLS_RSA_WITH_AES_256_CBC_SHA",
428 tls
.TLS_RSA_WITH_AES_128_GCM_SHA256
: "TLS_RSA_WITH_AES_128_GCM_SHA256",
429 tls
.TLS_RSA_WITH_AES_256_GCM_SHA384
: "TLS_RSA_WITH_AES_256_GCM_SHA384",
430 tls
.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
431 tls
.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
432 tls
.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
433 tls
.TLS_ECDHE_RSA_WITH_RC4_128_SHA
: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
434 tls
.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
435 tls
.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
436 tls
.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
437 tls
.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
438 tls
.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
439 tls
.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
440 tls
.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
442 versions
:= map[uint16]string{
443 tls
.VersionSSL30
: "SSLv3.0",
444 tls
.VersionTLS10
: "TLSv1.0",
445 tls
.VersionTLS11
: "TLSv1.1",
446 tls
.VersionTLS12
: "TLSv1.2",
451 version
:= versions
[state
.Version
]
452 cipher
:= ciphers
[state
.CipherSuite
]
455 version
= fmt
.Sprintf("%x", state
.Version
)
458 cipher
= fmt
.Sprintf("%x", state
.CipherSuite
)
462 if state
.ServerName
!= "" {
463 name
= fmt
.Sprintf(" name=%s", state
.ServerName
)
466 return fmt
.Sprintf("%s cipher=%s%s", version
, cipher
, name
)
469 func (conn
*connection
) doRSET() {
470 conn
.log
.Info("doRSET()")
471 conn
.state
= stateInitial
476 func (conn
*connection
) resetBuffers() {
478 conn
.rcptTo
= make([]mail
.Address
, 0)