15 "github.com/uber-go/zap"
21 stateNew state
= iota // Before EHLO.
30 func (d delivery
) String() string {
39 panic("Unknown delivery")
43 deliverUnknown delivery
= iota
44 deliverInbound
// Mail is not from one of this server's domains.
45 deliverOutbound
// Mail IS from one of this server's domains.
48 type connection
struct {
57 tls
*tls
.ConnectionState
59 // The authcid from a PLAIN SASL login. Non-empty iff tls is non-nil and
60 // doAUTH() succeeded.
69 // For deliverOutbound, replaces the From and Reply-To values.
73 mailFrom
*mail
.Address
77 func AcceptConnection(netConn net
.Conn
, server Server
, log zap
.Logger
) {
80 tp
: textproto
.NewConn(netConn
),
82 remoteAddr
: netConn
.RemoteAddr(),
83 log
: log
.With(zap
.Stringer("client", netConn
.RemoteAddr())),
87 conn
.log
.Info("accepted connection")
88 conn
.writeReply(220, fmt
.Sprintf("%s ESMTP [%s] (mailpopbox)",
89 server
.Name(), netConn
.LocalAddr()))
93 conn
.line
, err
= conn
.tp
.ReadLine()
95 conn
.log
.Error("ReadLine()", zap
.Error(err
))
100 conn
.log
.Info("ReadLine()", zap
.String("line", conn
.line
))
103 if _
, err
= fmt
.Sscanf(conn
.line
, "%s", &cmd
); err
!= nil {
104 conn
.reply(ReplyBadSyntax
)
108 switch strings
.ToUpper(cmd
) {
110 conn
.writeReply(221, "Goodbye")
132 conn
.writeReply(252, "I'll do my best")
134 conn
.writeReply(550, "access denied")
138 conn
.writeReply(250, "https://tools.ietf.org/html/rfc5321")
140 conn
.writeReply(500, "unrecognized command")
145 func (conn
*connection
) reply(reply ReplyLine
) error
{
146 return conn
.writeReply(reply
.Code
, reply
.Message
)
149 func (conn
*connection
) writeReply(code
int, msg
string) error
{
150 conn
.log
.Info("writeReply", zap
.Int("code", code
))
153 err
= conn
.tp
.PrintfLine("%d %s", code
, msg
)
155 err
= conn
.tp
.PrintfLine("%d", code
)
158 conn
.log
.Error("writeReply",
159 zap
.Int("code", code
),
165 // parsePath parses out either a forward-, reverse-, or return-path from the
166 // current connection line. Returns a (valid-path, ReplyOK) if it was
167 // successfully parsed.
168 func (conn
*connection
) parsePath(command
string) (string, ReplyLine
) {
169 if len(conn
.line
) < len(command
) {
170 return "", ReplyBadSyntax
172 if strings
.ToUpper(command
) != strings
.ToUpper(conn
.line
[:len(command
)]) {
173 return "", ReplyLine
{500, "unrecognized command"}
175 params
:= conn
.line
[len(command
):]
176 idx
:= strings
.Index(params
, ">")
178 return "", ReplyBadSyntax
180 return strings
.ToLower(params
[:idx
+1]), ReplyOK
183 func (conn
*connection
) doEHLO() {
187 _
, err
:= fmt
.Sscanf(conn
.line
, "%s %s", &cmd
, &conn
.ehlo
)
189 conn
.reply(ReplyBadSyntax
)
194 conn
.writeReply(250, fmt
.Sprintf("Hello %s [%s]", conn
.ehlo
, conn
.remoteAddr
))
196 conn
.tp
.PrintfLine("250-Hello %s [%s]", conn
.ehlo
, conn
.remoteAddr
)
197 if conn
.server
.TLSConfig() != nil && conn
.tls
== nil {
198 conn
.tp
.PrintfLine("250-STARTTLS")
201 conn
.tp
.PrintfLine("250-AUTH PLAIN")
203 conn
.tp
.PrintfLine("250 SIZE %d", 40960000)
206 conn
.log
.Info("doEHLO()", zap
.String("ehlo", conn
.ehlo
))
208 conn
.state
= stateInitial
211 func (conn
*connection
) doSTARTTLS() {
212 if conn
.state
!= stateInitial
{
213 conn
.reply(ReplyBadSequence
)
217 tlsConfig
:= conn
.server
.TLSConfig()
218 if !conn
.esmtp || tlsConfig
== nil {
219 conn
.writeReply(500, "unrecognized command")
223 conn
.log
.Info("doSTARTTLS()")
224 conn
.writeReply(220, "initiate TLS connection")
226 tlsConn
:= tls
.Server(conn
.nc
, tlsConfig
)
227 if err
:= tlsConn
.Handshake(); err
!= nil {
228 conn
.log
.Error("failed to do TLS handshake", zap
.Error(err
))
233 conn
.tp
= textproto
.NewConn(tlsConn
)
234 conn
.state
= stateNew
236 connState
:= tlsConn
.ConnectionState()
237 conn
.tls
= &connState
239 conn
.log
.Info("TLS connection done", zap
.String("state", conn
.getTransportString()))
242 func (conn
*connection
) doAUTH() {
243 if conn
.state
!= stateInitial || conn
.tls
== nil {
244 conn
.reply(ReplyBadSequence
)
248 if conn
.authc
!= "" {
249 conn
.writeReply(503, "already authenticated")
253 var cmd
, authType
string
254 _
, err
:= fmt
.Sscanf(conn
.line
, "%s %s", &cmd
, &authType
)
256 conn
.reply(ReplyBadSyntax
)
260 if authType
!= "PLAIN" {
261 conn
.writeReply(504, "unrecognized auth type")
265 conn
.log
.Info("doAUTH()")
267 conn
.writeReply(334, " ")
269 authLine
, err
:= conn
.tp
.ReadLine()
271 conn
.log
.Error("failed to read auth line", zap
.Error(err
))
272 conn
.reply(ReplyBadSyntax
)
276 authBytes
, err
:= base64
.StdEncoding
.DecodeString(authLine
)
278 conn
.reply(ReplyBadSyntax
)
282 authParts
:= strings
.Split(string(authBytes
), "\x00")
283 if len(authParts
) != 3 {
284 conn
.log
.Error("bad auth line syntax")
285 conn
.reply(ReplyBadSyntax
)
289 if !conn
.server
.Authenticate(authParts
[0], authParts
[1], authParts
[2]) {
290 conn
.log
.Error("failed to authenticate", zap
.String("authc", authParts
[1]))
291 conn
.writeReply(535, "invalid credentials")
295 conn
.log
.Info("authenticated", zap
.String("authz", authParts
[0]), zap
.String("authc", authParts
[1]))
296 conn
.authc
= authParts
[1]
300 func (conn
*connection
) doMAIL() {
301 if conn
.state
!= stateInitial
{
302 conn
.reply(ReplyBadSequence
)
306 mailFrom
, reply
:= conn
.parsePath("MAIL FROM:")
307 if reply
!= ReplyOK
{
313 conn
.mailFrom
, err
= mail
.ParseAddress(mailFrom
)
314 if err
!= nil || conn
.mailFrom
== nil {
315 conn
.reply(ReplyBadSyntax
)
319 if conn
.server
.VerifyAddress(*conn
.mailFrom
) == ReplyOK
{
320 // Message is being sent from a domain that this is an MTA for. Ultimate
321 // handling of the outbound message requires knowing the recipient.
322 domain
:= DomainForAddress(*conn
.mailFrom
)
323 // TODO: better way to authenticate this?
324 if !strings
.HasSuffix(conn
.authc
, "@"+domain
) {
325 conn
.writeReply(550, "not authenticated")
328 conn
.delivery
= deliverOutbound
330 conn
.delivery
= deliverInbound
333 conn
.log
.Info("doMAIL()", zap
.String("address", conn
.mailFrom
.Address
))
335 conn
.state
= stateMail
339 func (conn
*connection
) doRCPT() {
340 if conn
.state
!= stateMail
&& conn
.state
!= stateRecipient
{
341 conn
.reply(ReplyBadSequence
)
345 rcptTo
, reply
:= conn
.parsePath("RCPT TO:")
346 if reply
!= ReplyOK
{
351 address
, err
:= mail
.ParseAddress(rcptTo
)
353 conn
.reply(ReplyBadSyntax
)
357 if reply
:= conn
.server
.VerifyAddress(*address
); reply
== ReplyOK
{
358 // Message is addressed to this server. If it's outbound, only support
359 // the special send-as addressing.
360 if conn
.delivery
== deliverOutbound
{
361 if !strings
.HasPrefix(address
.Address
, SendAsAddress
) {
362 conn
.log
.Error("internal relay addressing not supported",
363 zap
.String("address", address
.Address
))
364 conn
.reply(ReplyBadMailbox
)
367 address
.Address
= strings
.TrimPrefix(address
.Address
, SendAsAddress
)
368 if DomainForAddress(*address
) != DomainForAddressString(conn
.authc
) {
369 conn
.log
.Error("not authenticated for send-as",
370 zap
.String("address", address
.Address
),
371 zap
.String("authc", conn
.authc
))
372 conn
.reply(ReplyBadMailbox
)
375 if conn
.sendAs
!= nil {
376 conn
.log
.Error("sendAs already specified",
377 zap
.String("address", address
.Address
),
378 zap
.String("sendAs", conn
.sendAs
.Address
))
379 conn
.reply(ReplyMailboxUnallowed
)
382 conn
.log
.Info("doRCPT()",
383 zap
.String("sendAs", address
.Address
))
384 conn
.sendAs
= address
385 conn
.state
= stateRecipient
390 // Message is not addressed to this server, so the delivery must be outbound.
391 if conn
.delivery
== deliverInbound
{
392 conn
.log
.Warn("invalid address",
393 zap
.String("address", address
.Address
),
394 zap
.Stringer("reply", reply
))
400 conn
.log
.Info("doRCPT()",
401 zap
.String("address", address
.Address
),
402 zap
.String("delivery", conn
.delivery
.String()))
404 conn
.rcptTo
= append(conn
.rcptTo
, *address
)
406 conn
.state
= stateRecipient
410 func (conn
*connection
) doDATA() {
411 if conn
.state
!= stateRecipient
{
412 conn
.reply(ReplyBadSequence
)
416 conn
.writeReply(354, "Start mail input; end with <CRLF>.<CRLF>")
417 conn
.log
.Info("doDATA()")
419 data
, err
:= conn
.tp
.ReadDotBytes()
421 conn
.log
.Error("failed to ReadDotBytes()",
423 zap
.String("bytes", fmt
.Sprintf("%x", data
)))
424 conn
.writeReply(552, "transaction failed")
428 conn
.handleSendAs(&data
)
430 received
:= time
.Now()
432 RemoteAddr
: conn
.remoteAddr
,
434 MailFrom
: *conn
.mailFrom
,
437 ID
: conn
.envelopeID(received
),
440 conn
.log
.Info("received message",
441 zap
.Int("bytes", len(data
)),
442 zap
.Time("date", received
),
443 zap
.String("id", env
.ID
),
444 zap
.String("delivery", conn
.delivery
.String()))
446 trace
:= conn
.getReceivedInfo(env
)
448 env
.Data
= append(trace
, data
...)
450 if conn
.delivery
== deliverInbound
{
451 if reply
:= conn
.server
.OnMessageDelivered(env
); reply
!= nil {
452 conn
.log
.Warn("message was rejected", zap
.String("id", env
.ID
))
456 } else if conn
.delivery
== deliverOutbound
{
457 conn
.server
.RelayMessage(env
)
460 conn
.state
= stateInitial
465 func (conn
*connection
) handleSendAs(data
*[]byte) {
466 if conn
.delivery
!= deliverOutbound || conn
.sendAs
== nil {
470 conn
.mailFrom
= conn
.sendAs
472 // Find the separator between the message header and body.
473 headerIdx
:= bytes
.Index(*data
, []byte("\n\n"))
475 conn
.log
.Error("send-as: could not find headers index")
479 fromPrefix
:= []byte("From: ")
480 fromIdx
:= bytes
.Index(*data
, fromPrefix
)
481 if fromIdx
== -1 || fromIdx
>= headerIdx
{
482 conn
.log
.Error("send-as: could not find From header")
486 if (*data
)[fromIdx
-1] != '\n' {
487 conn
.log
.Error("send-as: could not find From header")
492 fromEndIdx
:= bytes
.IndexByte((*data
)[fromIdx
:], '\n')
494 conn
.log
.Error("send-as: could not find end of From header")
497 fromEndIdx
+= fromIdx
499 newData
:= (*data
)[:fromIdx
]
500 newData
= append(newData
, fromPrefix
...)
501 newData
= append(newData
, []byte(conn
.sendAs
.String())...)
502 newData
= append(newData
, (*data
)[fromEndIdx
:]...)
507 func (conn
*connection
) envelopeID(t time
.Time
) string {
509 rand
.Read(idBytes
[:])
510 return fmt
.Sprintf("m.%d.%x", t
.UnixNano(), idBytes
)
513 func (conn
*connection
) getReceivedInfo(envelope Envelope
) []byte {
514 rhost
, _
, err
:= net
.SplitHostPort(conn
.remoteAddr
.String())
516 rhost
= conn
.remoteAddr
.String()
519 rhosts
, err
:= net
.LookupAddr(rhost
)
521 rhost
= fmt
.Sprintf("%s [%s]", rhosts
[0], rhost
)
524 base
:= fmt
.Sprintf("Received: from %s (%s)\r\n ", conn
.ehlo
, rhost
)
533 base
+= fmt
.Sprintf("by %s (mailpopbox) with %s id %s\r\n ", conn
.server
.Name(), with
, envelope
.ID
)
535 base
+= fmt
.Sprintf("for <%s>\r\n ", envelope
.RcptTo
[0].Address
)
537 transport
:= conn
.getTransportString()
538 date
:= envelope
.Received
.Format(time
.RFC1123Z
) // Same as RFC 5322 ยง 3.3
539 base
+= fmt
.Sprintf("(using %s);\r\n %s\r\n", transport
, date
)
544 func (conn
*connection
) getTransportString() string {
549 ciphers
:= map[uint16]string{
550 tls
.TLS_RSA_WITH_RC4_128_SHA
: "TLS_RSA_WITH_RC4_128_SHA",
551 tls
.TLS_RSA_WITH_3DES_EDE_CBC_SHA
: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
552 tls
.TLS_RSA_WITH_AES_128_CBC_SHA
: "TLS_RSA_WITH_AES_128_CBC_SHA",
553 tls
.TLS_RSA_WITH_AES_256_CBC_SHA
: "TLS_RSA_WITH_AES_256_CBC_SHA",
554 tls
.TLS_RSA_WITH_AES_128_GCM_SHA256
: "TLS_RSA_WITH_AES_128_GCM_SHA256",
555 tls
.TLS_RSA_WITH_AES_256_GCM_SHA384
: "TLS_RSA_WITH_AES_256_GCM_SHA384",
556 tls
.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
557 tls
.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
558 tls
.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
559 tls
.TLS_ECDHE_RSA_WITH_RC4_128_SHA
: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
560 tls
.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
561 tls
.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
562 tls
.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
563 tls
.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
564 tls
.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
565 tls
.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
566 tls
.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
568 versions
:= map[uint16]string{
569 tls
.VersionSSL30
: "SSLv3.0",
570 tls
.VersionTLS10
: "TLSv1.0",
571 tls
.VersionTLS11
: "TLSv1.1",
572 tls
.VersionTLS12
: "TLSv1.2",
577 version
:= versions
[state
.Version
]
578 cipher
:= ciphers
[state
.CipherSuite
]
581 version
= fmt
.Sprintf("%x", state
.Version
)
584 cipher
= fmt
.Sprintf("%x", state
.CipherSuite
)
588 if state
.ServerName
!= "" {
589 name
= fmt
.Sprintf(" name=%s", state
.ServerName
)
592 return fmt
.Sprintf("%s cipher=%s%s", version
, cipher
, name
)
595 func (conn
*connection
) doRSET() {
596 conn
.log
.Info("doRSET()")
597 conn
.state
= stateInitial
602 func (conn
*connection
) resetBuffers() {
603 conn
.delivery
= deliverUnknown
606 conn
.rcptTo
= make([]mail
.Address
, 0)