2 // Copyright 2020 Blue Static <https://www.bluestatic.org>
3 // This program is free software licensed under the GNU General Public License,
4 // version 3.0. The full text of the license can be found in LICENSE.txt.
5 // SPDX-License-Identifier: GPL-3.0-only
25 stateNew state
= iota // Before EHLO.
32 type connection
struct {
41 tls
*tls
.ConnectionState
49 mailFrom
*mail
.Address
53 func AcceptConnection(netConn net
.Conn
, server Server
, log
*zap
.Logger
) {
56 tp
: textproto
.NewConn(netConn
),
58 remoteAddr
: netConn
.RemoteAddr(),
59 log
: log
.With(zap
.Stringer("client", netConn
.RemoteAddr())),
63 conn
.log
.Info("accepted connection")
64 conn
.writeReply(220, fmt
.Sprintf("%s ESMTP [%s] (mailpopbox)",
65 server
.Name(), netConn
.LocalAddr()))
69 conn
.line
, err
= conn
.tp
.ReadLine()
71 conn
.log
.Error("ReadLine()", zap
.Error(err
))
76 conn
.log
.Info("ReadLine()", zap
.String("line", conn
.line
))
79 if _
, err
= fmt
.Sscanf(conn
.line
, "%s", &cmd
); err
!= nil {
80 conn
.reply(ReplyBadSyntax
)
84 switch strings
.ToUpper(cmd
) {
86 conn
.writeReply(221, "Goodbye")
106 conn
.writeReply(252, "I'll do my best")
108 conn
.writeReply(550, "access denied")
112 conn
.writeReply(250, "https://tools.ietf.org/html/rfc5321")
114 conn
.writeReply(500, "unrecognized command")
119 func (conn
*connection
) reply(reply ReplyLine
) error
{
120 return conn
.writeReply(reply
.Code
, reply
.Message
)
123 func (conn
*connection
) writeReply(code
int, msg
string) error
{
124 conn
.log
.Info("writeReply", zap
.Int("code", code
))
127 err
= conn
.tp
.PrintfLine("%d %s", code
, msg
)
129 err
= conn
.tp
.PrintfLine("%d", code
)
132 conn
.log
.Error("writeReply",
133 zap
.Int("code", code
),
139 // parsePath parses out either a forward-, reverse-, or return-path from the
140 // current connection line. Returns a (valid-path, ReplyOK) if it was
141 // successfully parsed.
142 func (conn
*connection
) parsePath(command
string) (string, ReplyLine
) {
143 if len(conn
.line
) < len(command
) {
144 return "", ReplyBadSyntax
146 if strings
.ToUpper(command
) != strings
.ToUpper(conn
.line
[:len(command
)]) {
147 return "", ReplyLine
{500, "unrecognized command"}
149 params
:= conn
.line
[len(command
):]
150 idx
:= strings
.Index(params
, ">")
152 return "", ReplyBadSyntax
154 return strings
.ToLower(params
[:idx
+1]), ReplyOK
157 func (conn
*connection
) doEHLO() {
161 _
, err
:= fmt
.Sscanf(conn
.line
, "%s %s", &cmd
, &conn
.ehlo
)
163 conn
.reply(ReplyBadSyntax
)
168 conn
.writeReply(250, fmt
.Sprintf("Hello %s [%s]", conn
.ehlo
, conn
.remoteAddr
))
170 conn
.tp
.PrintfLine("250-Hello %s [%s]", conn
.ehlo
, conn
.remoteAddr
)
171 if conn
.server
.TLSConfig() != nil && conn
.tls
== nil {
172 conn
.tp
.PrintfLine("250-STARTTLS")
174 conn
.tp
.PrintfLine("250 SIZE %d", 40960000)
177 conn
.log
.Info("doEHLO()", zap
.String("ehlo", conn
.ehlo
))
179 conn
.state
= stateInitial
182 func (conn
*connection
) doSTARTTLS() {
183 if conn
.state
!= stateInitial
{
184 conn
.reply(ReplyBadSequence
)
188 tlsConfig
:= conn
.server
.TLSConfig()
189 if !conn
.esmtp || tlsConfig
== nil {
190 conn
.writeReply(500, "unrecognized command")
194 conn
.log
.Info("doSTARTTLS()")
195 conn
.writeReply(220, "initiate TLS connection")
197 tlsConn
:= tls
.Server(conn
.nc
, tlsConfig
)
198 if err
:= tlsConn
.Handshake(); err
!= nil {
199 conn
.log
.Error("failed to do TLS handshake", zap
.Error(err
))
204 conn
.tp
= textproto
.NewConn(tlsConn
)
205 conn
.state
= stateNew
207 connState
:= tlsConn
.ConnectionState()
208 conn
.tls
= &connState
210 conn
.log
.Info("TLS connection done", zap
.String("state", conn
.getTransportString()))
213 func (conn
*connection
) doMAIL() {
214 if conn
.state
!= stateInitial
{
215 conn
.reply(ReplyBadSequence
)
219 mailFrom
, reply
:= conn
.parsePath("MAIL FROM:")
220 if reply
!= ReplyOK
{
226 conn
.mailFrom
, err
= mail
.ParseAddress(mailFrom
)
227 if err
!= nil || conn
.mailFrom
== nil {
228 conn
.reply(ReplyBadSyntax
)
232 conn
.log
.Info("doMAIL()", zap
.String("address", conn
.mailFrom
.Address
))
234 conn
.state
= stateMail
238 func (conn
*connection
) doRCPT() {
239 if conn
.state
!= stateMail
&& conn
.state
!= stateRecipient
{
240 conn
.reply(ReplyBadSequence
)
244 rcptTo
, reply
:= conn
.parsePath("RCPT TO:")
245 if reply
!= ReplyOK
{
250 address
, err
:= mail
.ParseAddress(rcptTo
)
252 conn
.reply(ReplyBadSyntax
)
256 if reply
:= conn
.server
.VerifyAddress(*address
); reply
!= ReplyOK
{
257 conn
.log
.Warn("invalid address",
258 zap
.String("address", address
.Address
),
259 zap
.Stringer("reply", reply
))
264 conn
.log
.Info("doRCPT()", zap
.String("address", address
.Address
))
266 conn
.rcptTo
= append(conn
.rcptTo
, *address
)
268 conn
.state
= stateRecipient
272 func (conn
*connection
) doDATA() {
273 if conn
.state
!= stateRecipient
{
274 conn
.reply(ReplyBadSequence
)
278 conn
.writeReply(354, "Start mail input; end with <CRLF>.<CRLF>")
279 conn
.log
.Info("doDATA()")
281 data
, err
:= conn
.tp
.ReadDotBytes()
283 conn
.log
.Error("failed to ReadDotBytes()",
285 zap
.String("bytes", fmt
.Sprintf("%x", data
)))
286 conn
.writeReply(552, "transaction failed")
290 received
:= time
.Now()
292 RemoteAddr
: conn
.remoteAddr
,
294 MailFrom
: *conn
.mailFrom
,
297 ID
: conn
.envelopeID(received
),
300 conn
.log
.Info("received message",
301 zap
.Int("bytes", len(data
)),
302 zap
.Time("date", received
),
303 zap
.String("id", env
.ID
))
305 trace
:= conn
.getReceivedInfo(env
)
307 env
.Data
= append(trace
, data
...)
309 if reply
:= conn
.server
.OnMessageDelivered(env
); reply
!= nil {
310 conn
.log
.Warn("message was rejected", zap
.String("id", env
.ID
))
315 conn
.state
= stateInitial
319 func (conn
*connection
) envelopeID(t time
.Time
) string {
321 rand
.Read(idBytes
[:])
322 return fmt
.Sprintf("m.%d.%x", t
.UnixNano(), idBytes
)
325 func (conn
*connection
) getReceivedInfo(envelope Envelope
) []byte {
326 rhost
, _
, err
:= net
.SplitHostPort(conn
.remoteAddr
.String())
328 rhost
= conn
.remoteAddr
.String()
331 rhosts
, err
:= net
.LookupAddr(rhost
)
333 rhost
= fmt
.Sprintf("%s [%s]", rhosts
[0], rhost
)
336 base
:= fmt
.Sprintf("Received: from %s (%s)\r\n ", conn
.ehlo
, rhost
)
345 base
+= fmt
.Sprintf("by %s (mailpopbox) with %s id %s\r\n ", conn
.server
.Name(), with
, envelope
.ID
)
347 base
+= fmt
.Sprintf("for <%s>\r\n ", envelope
.RcptTo
[0].Address
)
349 transport
:= conn
.getTransportString()
350 date
:= envelope
.Received
.Format(time
.RFC1123Z
) // Same as RFC 5322 ยง 3.3
351 base
+= fmt
.Sprintf("(using %s);\r\n %s\r\n", transport
, date
)
356 func (conn
*connection
) getTransportString() string {
361 ciphers
:= map[uint16]string{
362 tls
.TLS_RSA_WITH_RC4_128_SHA
: "TLS_RSA_WITH_RC4_128_SHA",
363 tls
.TLS_RSA_WITH_3DES_EDE_CBC_SHA
: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
364 tls
.TLS_RSA_WITH_AES_128_CBC_SHA
: "TLS_RSA_WITH_AES_128_CBC_SHA",
365 tls
.TLS_RSA_WITH_AES_256_CBC_SHA
: "TLS_RSA_WITH_AES_256_CBC_SHA",
366 tls
.TLS_RSA_WITH_AES_128_CBC_SHA256
: "TLS_RSA_WITH_AES_128_CBC_SHA256",
367 tls
.TLS_RSA_WITH_AES_128_GCM_SHA256
: "TLS_RSA_WITH_AES_128_GCM_SHA256",
368 tls
.TLS_RSA_WITH_AES_256_GCM_SHA384
: "TLS_RSA_WITH_AES_256_GCM_SHA384",
369 tls
.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
370 tls
.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
371 tls
.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
372 tls
.TLS_ECDHE_RSA_WITH_RC4_128_SHA
: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
373 tls
.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
374 tls
.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
375 tls
.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
376 tls
.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
377 tls
.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
378 tls
.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
379 tls
.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
380 tls
.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
381 tls
.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
382 tls
.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
383 tls
.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
384 tls
.TLS_AES_128_GCM_SHA256
: "TLS_AES_128_GCM_SHA256",
385 tls
.TLS_AES_256_GCM_SHA384
: "TLS_AES_256_GCM_SHA384",
386 tls
.TLS_CHACHA20_POLY1305_SHA256
: "TLS_CHACHA20_POLY1305_SHA256",
388 versions
:= map[uint16]string{
389 tls
.VersionSSL30
: "SSLv3.0",
390 tls
.VersionTLS10
: "TLSv1.0",
391 tls
.VersionTLS11
: "TLSv1.1",
392 tls
.VersionTLS12
: "TLSv1.2",
393 tls
.VersionTLS13
: "TLSv1.3",
398 version
:= versions
[state
.Version
]
399 cipher
:= ciphers
[state
.CipherSuite
]
402 version
= fmt
.Sprintf("%x", state
.Version
)
405 cipher
= fmt
.Sprintf("%x", state
.CipherSuite
)
409 if state
.ServerName
!= "" {
410 name
= fmt
.Sprintf(" name=%s", state
.ServerName
)
413 return fmt
.Sprintf("%s cipher=%s%s", version
, cipher
, name
)
416 func (conn
*connection
) doRSET() {
417 conn
.log
.Info("doRSET()")
418 conn
.state
= stateInitial
423 func (conn
*connection
) resetBuffers() {
425 conn
.rcptTo
= make([]mail
.Address
, 0)