13 "github.com/uber-go/zap"
19 stateNew state = iota // Before EHLO.
26 type connection struct {
35 tls *tls.ConnectionState
43 mailFrom *mail.Address
47 func AcceptConnection(netConn net.Conn, server Server, log zap.Logger) {
50 tp: textproto.NewConn(netConn),
52 remoteAddr: netConn.RemoteAddr(),
53 log: log.With(zap.Stringer("client", netConn.RemoteAddr())),
57 conn.log.Info("accepted connection")
58 conn.writeReply(220, fmt.Sprintf("%s ESMTP [%s] (mailpopbox)",
59 server.Name(), netConn.LocalAddr()))
63 conn.line, err = conn.tp.ReadLine()
65 conn.log.Error("ReadLine()", zap.Error(err))
70 conn.log.Info("ReadLine()", zap.String("line", conn.line))
73 if _, err = fmt.Sscanf(conn.line, "%s", &cmd); err != nil {
74 conn.reply(ReplyBadSyntax)
78 switch strings.ToUpper(cmd) {
80 conn.writeReply(221, "Goodbye")
100 conn.writeReply(252, "I'll do my best")
102 conn.writeReply(550, "access denied")
106 conn.writeReply(250, "https://tools.ietf.org/html/rfc5321")
108 conn.writeReply(500, "unrecognized command")
113 func (conn *connection) reply(reply ReplyLine) error {
114 return conn.writeReply(reply.Code, reply.Message)
117 func (conn *connection) writeReply(code int, msg string) error {
118 conn.log.Info("writeReply", zap.Int("code", code))
121 err = conn.tp.PrintfLine("%d %s", code, msg)
123 err = conn.tp.PrintfLine("%d", code)
126 conn.log.Error("writeReply",
127 zap.Int("code", code),
133 // parsePath parses out either a forward-, reverse-, or return-path from the
134 // current connection line. Returns a (valid-path, ReplyOK) if it was
135 // successfully parsed.
136 func (conn *connection) parsePath(command string) (string, ReplyLine) {
137 if len(conn.line) < len(command) {
138 return "", ReplyBadSyntax
140 if strings.ToUpper(command) != strings.ToUpper(conn.line[:len(command)]) {
141 return "", ReplyLine{500, "unrecognized command"}
143 params := conn.line[len(command):]
144 idx := strings.Index(params, ">")
146 return "", ReplyBadSyntax
148 return strings.ToLower(params[:idx+1]), ReplyOK
151 func (conn *connection) doEHLO() {
155 _, err := fmt.Sscanf(conn.line, "%s %s", &cmd, &conn.ehlo)
157 conn.reply(ReplyBadSyntax)
162 conn.writeReply(250, fmt.Sprintf("Hello %s [%s]", conn.ehlo, conn.remoteAddr))
164 conn.tp.PrintfLine("250-Hello %s [%s]", conn.ehlo, conn.remoteAddr)
165 if conn.server.TLSConfig() != nil && conn.tls == nil {
166 conn.tp.PrintfLine("250-STARTTLS")
168 conn.tp.PrintfLine("250 SIZE %d", 40960000)
171 conn.log.Info("doEHLO()", zap.String("ehlo", conn.ehlo))
173 conn.state = stateInitial
176 func (conn *connection) doSTARTTLS() {
177 if conn.state != stateInitial {
178 conn.reply(ReplyBadSequence)
182 tlsConfig := conn.server.TLSConfig()
183 if !conn.esmtp || tlsConfig == nil {
184 conn.writeReply(500, "unrecognized command")
188 conn.log.Info("doSTARTTLS()")
189 conn.writeReply(220, "initiate TLS connection")
191 tlsConn := tls.Server(conn.nc, tlsConfig)
192 if err := tlsConn.Handshake(); err != nil {
193 conn.log.Error("failed to do TLS handshake", zap.Error(err))
198 conn.tp = textproto.NewConn(tlsConn)
199 conn.state = stateNew
201 connState := tlsConn.ConnectionState()
202 conn.tls = &connState
204 conn.log.Info("TLS connection done", zap.String("state", conn.getTransportString()))
207 func (conn *connection) doMAIL() {
208 if conn.state != stateInitial {
209 conn.reply(ReplyBadSequence)
213 mailFrom, reply := conn.parsePath("MAIL FROM:")
214 if reply != ReplyOK {
220 conn.mailFrom, err = mail.ParseAddress(mailFrom)
221 if err != nil || conn.mailFrom == nil {
222 conn.reply(ReplyBadSyntax)
226 conn.log.Info("doMAIL()", zap.String("address", conn.mailFrom.Address))
228 conn.state = stateMail
232 func (conn *connection) doRCPT() {
233 if conn.state != stateMail && conn.state != stateRecipient {
234 conn.reply(ReplyBadSequence)
238 rcptTo, reply := conn.parsePath("RCPT TO:")
239 if reply != ReplyOK {
244 address, err := mail.ParseAddress(rcptTo)
246 conn.reply(ReplyBadSyntax)
250 if reply := conn.server.VerifyAddress(*address); reply != ReplyOK {
251 conn.log.Warn("invalid address",
252 zap.String("address", address.Address),
253 zap.Stringer("reply", reply))
258 conn.log.Info("doRCPT()", zap.String("address", address.Address))
260 conn.rcptTo = append(conn.rcptTo, *address)
262 conn.state = stateRecipient
266 func (conn *connection) doDATA() {
267 if conn.state != stateRecipient {
268 conn.reply(ReplyBadSequence)
272 conn.writeReply(354, "Start mail input; end with <CRLF>.<CRLF>")
273 conn.log.Info("doDATA()")
275 data, err := conn.tp.ReadDotBytes()
277 conn.log.Error("failed to ReadDotBytes()",
279 zap.String("bytes", fmt.Sprintf("%x", data)))
280 conn.writeReply(552, "transaction failed")
284 received := time.Now()
286 RemoteAddr: conn.remoteAddr,
288 MailFrom: *conn.mailFrom,
291 ID: conn.envelopeID(received),
294 conn.log.Info("received message",
295 zap.Int("bytes", len(data)),
296 zap.Time("date", received),
297 zap.String("id", env.ID))
299 trace := conn.getReceivedInfo(env)
301 env.Data = append(trace, data...)
303 if reply := conn.server.OnMessageDelivered(env); reply != nil {
304 conn.log.Warn("message was rejected", zap.String("id", env.ID))
309 conn.state = stateInitial
313 func (conn *connection) envelopeID(t time.Time) string {
315 rand.Read(idBytes[:])
316 return fmt.Sprintf("m.%d.%x", t.UnixNano(), idBytes)
319 func (conn *connection) getReceivedInfo(envelope Envelope) []byte {
320 rhost, _, err := net.SplitHostPort(conn.remoteAddr.String())
322 rhost = conn.remoteAddr.String()
325 rhosts, err := net.LookupAddr(rhost)
327 rhost = fmt.Sprintf("%s [%s]", rhosts[0], rhost)
330 base := fmt.Sprintf("Received: from %s (%s)\r\n ", conn.ehlo, rhost)
339 base += fmt.Sprintf("by %s (mailpopbox) with %s id %s\r\n ", conn.server.Name(), with, envelope.ID)
341 base += fmt.Sprintf("for <%s>\r\n ", envelope.RcptTo[0].Address)
343 transport := conn.getTransportString()
344 date := envelope.Received.Format(time.RFC1123Z) // Same as RFC 5322 ยง 3.3
345 base += fmt.Sprintf("(using %s);\r\n %s\r\n", transport, date)
350 func (conn *connection) getTransportString() string {
355 ciphers := map[uint16]string{
356 tls.TLS_RSA_WITH_RC4_128_SHA: "TLS_RSA_WITH_RC4_128_SHA",
357 tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
358 tls.TLS_RSA_WITH_AES_128_CBC_SHA: "TLS_RSA_WITH_AES_128_CBC_SHA",
359 tls.TLS_RSA_WITH_AES_256_CBC_SHA: "TLS_RSA_WITH_AES_256_CBC_SHA",
360 tls.TLS_RSA_WITH_AES_128_GCM_SHA256: "TLS_RSA_WITH_AES_128_GCM_SHA256",
361 tls.TLS_RSA_WITH_AES_256_GCM_SHA384: "TLS_RSA_WITH_AES_256_GCM_SHA384",
362 tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
363 tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
364 tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
365 tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
366 tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
367 tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
368 tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
369 tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
370 tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
371 tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
372 tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
374 versions := map[uint16]string{
375 tls.VersionSSL30: "SSLv3.0",
376 tls.VersionTLS10: "TLSv1.0",
377 tls.VersionTLS11: "TLSv1.1",
378 tls.VersionTLS12: "TLSv1.2",
383 version := versions[state.Version]
384 cipher := ciphers[state.CipherSuite]
387 version = fmt.Sprintf("%x", state.Version)
390 cipher = fmt.Sprintf("%x", state.CipherSuite)
394 if state.ServerName != "" {
395 name = fmt.Sprintf(" name=%s", state.ServerName)
398 return fmt.Sprintf("%s cipher=%s%s", version, cipher, name)
401 func (conn *connection) doRSET() {
402 conn.log.Info("doRSET()")
403 conn.state = stateInitial
408 func (conn *connection) resetBuffers() {
410 conn.rcptTo = make([]mail.Address, 0)