14 "github.com/uber-go/zap"
20 stateNew state
= iota // Before EHLO.
27 type connection
struct {
44 mailFrom
*mail
.Address
48 func AcceptConnection(netConn net
.Conn
, server Server
, log zap
.Logger
) error
{
51 tp
: textproto
.NewConn(netConn
),
53 remoteAddr
: netConn
.RemoteAddr(),
54 log
: log
.With(zap
.Stringer("client", netConn
.RemoteAddr())),
60 conn
.writeReply(220, fmt
.Sprintf("%s ESMTP [%s] (mailpopbox)", server
.Name(), netConn
.LocalAddr()))
63 conn
.line
, err
= conn
.tp
.ReadLine()
68 conn
.log
.Error("ReadLine()", zap
.Error(err
))
69 conn
.writeReply(500, "line too long")
74 if _
, err
= fmt
.Sscanf(conn
.line
, "%s", &cmd
); err
!= nil {
75 conn
.reply(ReplyBadSyntax
)
79 switch strings
.ToUpper(cmd
) {
81 conn
.writeReply(221, "Goodbye")
101 conn
.writeReply(252, "I'll do my best")
103 conn
.writeReply(550, "access denied")
107 conn
.writeReply(250, "https://tools.ietf.org/html/rfc5321")
109 conn
.writeReply(500, "unrecognized command")
116 func (conn
*connection
) reply(reply ReplyLine
) error
{
117 return conn
.writeReply(reply
.Code
, reply
.Message
)
120 func (conn
*connection
) writeReply(code
int, msg
string) error
{
122 return conn
.tp
.PrintfLine("%d %s", code
, msg
)
124 return conn
.tp
.PrintfLine("%d", code
)
128 // parsePath parses out either a forward-, reverse-, or return-path from the
129 // current connection line. Returns a (valid-path, ReplyOK) if it was
130 // successfully parsed.
131 func (conn
*connection
) parsePath(command
string) (string, ReplyLine
) {
132 if len(conn
.line
) < len(command
) {
133 return "", ReplyBadSyntax
135 if strings
.ToUpper(command
) != strings
.ToUpper(conn
.line
[:len(command
)]) {
136 return "", ReplyLine
{500, "unrecognized command"}
138 return conn
.line
[len(command
):], ReplyOK
141 func (conn
*connection
) doEHLO() {
145 _
, err
:= fmt
.Sscanf(conn
.line
, "%s %s", &cmd
, &conn
.ehlo
)
147 conn
.reply(ReplyBadSyntax
)
152 conn
.writeReply(250, fmt
.Sprintf("Hello %s [%s]", conn
.ehlo
, conn
.remoteAddr
))
154 conn
.tp
.PrintfLine("250-Hello %s [%s]", conn
.ehlo
, conn
.remoteAddr
)
155 if conn
.server
.TLSConfig() != nil && conn
.tlsNc
== nil {
156 conn
.tp
.PrintfLine("250-STARTTLS")
158 conn
.tp
.PrintfLine("250 SIZE %d", 40960000)
161 conn
.log
.Info("doEHLO()", zap
.String("ehlo", conn
.ehlo
))
163 conn
.state
= stateInitial
166 func (conn
*connection
) doSTARTTLS() {
167 if conn
.state
!= stateInitial
{
168 conn
.reply(ReplyBadSequence
)
172 tlsConfig
:= conn
.server
.TLSConfig()
173 if !conn
.esmtp || tlsConfig
== nil {
174 conn
.writeReply(500, "unrecognized command")
178 conn
.log
.Info("doSTARTTLS()")
179 conn
.writeReply(220, "initiate TLS connection")
181 newConn
:= tls
.Server(conn
.nc
, tlsConfig
)
182 if err
:= newConn
.Handshake(); err
!= nil {
187 conn
.tp
= textproto
.NewConn(conn
.tlsNc
)
188 conn
.state
= stateInitial
190 conn
.log
.Info("HELO again")
192 conn
.writeReply(220, fmt
.Sprintf("%s ESMTPS [%s] (mailpopbox)",
193 conn
.server
.Name(), newConn
.LocalAddr()))
196 func (conn
*connection
) doMAIL() {
197 if conn
.state
!= stateInitial
{
198 conn
.reply(ReplyBadSequence
)
202 mailFrom
, reply
:= conn
.parsePath("MAIL FROM:")
203 if reply
!= ReplyOK
{
209 conn
.mailFrom
, err
= mail
.ParseAddress(mailFrom
)
211 conn
.reply(ReplyBadSyntax
)
215 conn
.log
.Info("doMAIL()", zap
.String("address", conn
.mailFrom
.Address
))
217 conn
.state
= stateMail
221 func (conn
*connection
) doRCPT() {
222 if conn
.state
!= stateMail
&& conn
.state
!= stateRecipient
{
223 conn
.reply(ReplyBadSequence
)
227 rcptTo
, reply
:= conn
.parsePath("RCPT TO:")
228 if reply
!= ReplyOK
{
233 address
, err
:= mail
.ParseAddress(rcptTo
)
235 conn
.reply(ReplyBadSyntax
)
238 if reply
:= conn
.server
.VerifyAddress(*address
); reply
!= ReplyOK
{
239 conn
.log
.Warn("invalid address",
240 zap
.String("address", address
.Address
),
241 zap
.Stringer("reply", reply
))
246 conn
.log
.Info("doRCPT()", zap
.String("address", address
.Address
))
248 conn
.rcptTo
= append(conn
.rcptTo
, *address
)
250 conn
.state
= stateRecipient
254 func (conn
*connection
) doDATA() {
255 if conn
.state
!= stateRecipient
{
256 conn
.reply(ReplyBadSequence
)
260 conn
.writeReply(354, "Start mail input; end with <CRLF>.<CRLF>")
261 conn
.log
.Info("doDATA()")
263 data
, err
:= conn
.tp
.ReadDotBytes()
265 conn
.log
.Error("failed to ReadDotBytes()", zap
.Error(err
))
266 conn
.writeReply(552, "transaction failed")
270 received
:= time
.Now()
272 RemoteAddr
: conn
.remoteAddr
,
274 MailFrom
: *conn
.mailFrom
,
277 ID
: conn
.envelopeID(received
),
280 conn
.log
.Info("received message",
281 zap
.Int("bytes", len(data
)),
282 zap
.Time("date", received
),
283 zap
.String("id", env
.ID
))
285 trace
:= conn
.getReceivedInfo(env
)
287 env
.Data
= append(trace
, data
...)
289 if reply
:= conn
.server
.OnMessageDelivered(env
); reply
!= nil {
290 conn
.log
.Warn("message was rejected", zap
.String("id", env
.ID
))
295 conn
.state
= stateInitial
299 func (conn
*connection
) envelopeID(t time
.Time
) string {
301 rand
.Read(idBytes
[:])
302 return fmt
.Sprintf("m.%d.%x", t
.UnixNano(), idBytes
)
305 func (conn
*connection
) getReceivedInfo(envelope Envelope
) []byte {
306 rhost
, _
, err
:= net
.SplitHostPort(conn
.remoteAddr
.String())
308 rhost
= conn
.remoteAddr
.String()
311 rhosts
, err
:= net
.LookupAddr(rhost
)
313 rhost
= fmt
.Sprintf("%s [%s]", rhosts
[0], rhost
)
316 base
:= fmt
.Sprintf("Received: from %s (%s)\r\n ", conn
.ehlo
, rhost
)
322 if conn
.tlsNc
!= nil {
325 base
+= fmt
.Sprintf("by %s (mailpopbox) with %s id %s\r\n ", conn
.server
.Name(), with
, envelope
.ID
)
327 base
+= fmt
.Sprintf("for <%s>\r\n ", envelope
.RcptTo
[0].Address
)
329 transport
:= conn
.getTransportString()
330 date
:= envelope
.Received
.Format(time
.RFC1123Z
) // Same as RFC 5322 ยง 3.3
331 base
+= fmt
.Sprintf("(using %s);\r\n %s\r\n", transport
, date
)
336 func (conn
*connection
) getTransportString() string {
337 if conn
.tlsNc
== nil {
341 ciphers
:= map[uint16]string{
342 tls
.TLS_RSA_WITH_RC4_128_SHA
: "TLS_RSA_WITH_RC4_128_SHA",
343 tls
.TLS_RSA_WITH_3DES_EDE_CBC_SHA
: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
344 tls
.TLS_RSA_WITH_AES_128_CBC_SHA
: "TLS_RSA_WITH_AES_128_CBC_SHA",
345 tls
.TLS_RSA_WITH_AES_256_CBC_SHA
: "TLS_RSA_WITH_AES_256_CBC_SHA",
346 tls
.TLS_RSA_WITH_AES_128_GCM_SHA256
: "TLS_RSA_WITH_AES_128_GCM_SHA256",
347 tls
.TLS_RSA_WITH_AES_256_GCM_SHA384
: "TLS_RSA_WITH_AES_256_GCM_SHA384",
348 tls
.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
349 tls
.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
350 tls
.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
351 tls
.TLS_ECDHE_RSA_WITH_RC4_128_SHA
: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
352 tls
.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
353 tls
.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
354 tls
.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
355 tls
.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
356 tls
.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
357 tls
.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
358 tls
.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
360 versions
:= map[uint16]string{
361 tls
.VersionSSL30
: "SSLv3.0",
362 tls
.VersionTLS10
: "TLSv1.0",
363 tls
.VersionTLS11
: "TLSv1.1",
364 tls
.VersionTLS12
: "TLSv1.2",
367 state
:= conn
.tlsNc
.ConnectionState()
369 version
:= versions
[state
.Version
]
370 cipher
:= ciphers
[state
.CipherSuite
]
373 version
= fmt
.Sprintf("%x", state
.Version
)
376 cipher
= fmt
.Sprintf("%x", state
.CipherSuite
)
380 if state
.ServerName
!= "" {
381 name
= fmt
.Sprintf(" name=%s", state
.ServerName
)
384 return fmt
.Sprintf("%s cipher=%s%s", version
, cipher
, name
)
387 func (conn
*connection
) doRSET() {
388 conn
.log
.Info("doRSET()")
389 conn
.state
= stateInitial
394 func (conn
*connection
) resetBuffers() {
396 conn
.rcptTo
= make([]mail
.Address
, 0)