start more function names/calls with x when they handle errors through panics

mostly the imapserver and smtpserver connection write and read methods.
This commit is contained in:
Mechiel Lukkien
2025-04-02 13:59:46 +02:00
parent deb57462a4
commit 00c8db98e6
10 changed files with 195 additions and 196 deletions

View File

@ -779,10 +779,10 @@ func (c *conn) Read(buf []byte) (int, error) {
// Filled on demand.
var bufpool = moxio.NewBufpool(8, 2*1024)
func (c *conn) readline() string {
func (c *conn) xreadline() string {
line, err := bufpool.Readline(c.log, c.xbr)
if err != nil && errors.Is(err, moxio.ErrLineTooLong) {
c.writecodeline(smtp.C500BadSyntax, smtp.SeProto5Other0, "line too long, smtp max is 512, we reached 2048", nil)
c.xwritecodeline(smtp.C500BadSyntax, smtp.SeProto5Other0, "line too long, smtp max is 512, we reached 2048", nil)
panic(fmt.Errorf("%s (%w)", err, errIO))
} else if err != nil {
panic(fmt.Errorf("%s (%w)", err, errIO))
@ -792,7 +792,7 @@ func (c *conn) readline() string {
// Buffered-write command response line to connection with codes and msg.
// Err is not sent to remote but is used for logging and can be empty.
func (c *conn) bwritecodeline(code int, secode string, msg string, err error) {
func (c *conn) xbwritecodeline(code int, secode string, msg string, err error) {
var ecode string
if secode != "" {
ecode = fmt.Sprintf("%d.%s", code/100, secode)
@ -820,19 +820,19 @@ func (c *conn) bwritecodeline(code int, secode string, msg string, err error) {
for ; e > 400 && line[e] != ' '; e-- {
}
// todo future: understand if ecode should be on each line. won't hurt. at least as long as we don't do expn or vrfy.
c.bwritelinef("%d-%s%s%s", code, ecode, sep, line[:e])
c.xbwritelinef("%d-%s%s%s", code, ecode, sep, line[:e])
line = line[e:]
}
spdash := " "
if i < len(lines)-1 {
spdash = "-"
}
c.bwritelinef("%d%s%s%s%s", code, spdash, ecode, sep, line)
c.xbwritelinef("%d%s%s%s%s", code, spdash, ecode, sep, line)
}
}
// Buffered-write a formatted response line to connection.
func (c *conn) bwritelinef(format string, args ...any) {
func (c *conn) xbwritelinef(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
fmt.Fprint(c.xbw, msg+"\r\n")
}
@ -843,14 +843,14 @@ func (c *conn) xflush() {
}
// Write (with flush) a response line with codes and message. err is not written, used for logging and can be nil.
func (c *conn) writecodeline(code int, secode string, msg string, err error) {
c.bwritecodeline(code, secode, msg, err)
func (c *conn) xwritecodeline(code int, secode string, msg string, err error) {
c.xbwritecodeline(code, secode, msg, err)
c.xflush()
}
// Write (with flush) a formatted response line to connection.
func (c *conn) writelinef(format string, args ...any) {
c.bwritelinef(format, args...)
func (c *conn) xwritelinef(format string, args ...any) {
c.xbwritelinef(format, args...)
c.xflush()
}
@ -965,13 +965,13 @@ func serve(listenerName string, cid int64, hostname dns.Domain, tlsConfig *tls.C
select {
case <-mox.Shutdown.Done():
// ../rfc/5321:2811 ../rfc/5321:1666 ../rfc/3463:420
c.writecodeline(smtp.C421ServiceUnavail, smtp.SeSys3NotAccepting2, "shutting down", nil)
c.xwritecodeline(smtp.C421ServiceUnavail, smtp.SeSys3NotAccepting2, "shutting down", nil)
return
default:
}
if !limiterConnectionRate.Add(c.remoteIP, time.Now(), 1) {
c.writecodeline(smtp.C421ServiceUnavail, smtp.SePol7Other0, "connection rate from your ip or network too high, slow down please", nil)
c.xwritecodeline(smtp.C421ServiceUnavail, smtp.SePol7Other0, "connection rate from your ip or network too high, slow down please", nil)
return
}
@ -979,13 +979,13 @@ func serve(listenerName string, cid int64, hostname dns.Domain, tlsConfig *tls.C
if submission && !mox.LimiterFailedAuth.CanAdd(c.remoteIP, time.Now(), 1) {
metrics.AuthenticationRatelimitedInc("submission")
c.log.Debug("refusing connection due to many auth failures", slog.Any("remoteip", c.remoteIP))
c.writecodeline(smtp.C421ServiceUnavail, smtp.SePol7Other0, "too many auth failures", nil)
c.xwritecodeline(smtp.C421ServiceUnavail, smtp.SePol7Other0, "too many auth failures", nil)
return
}
if !limiterConnections.Add(c.remoteIP, time.Now(), 1) {
c.log.Debug("refusing connection due to many open connections", slog.Any("remoteip", c.remoteIP))
c.writecodeline(smtp.C421ServiceUnavail, smtp.SePol7Other0, "too many open connections from your ip or network", nil)
c.xwritecodeline(smtp.C421ServiceUnavail, smtp.SePol7Other0, "too many open connections from your ip or network", nil)
return
}
defer limiterConnections.Add(c.remoteIP, time.Now(), -1)
@ -1000,7 +1000,7 @@ func serve(listenerName string, cid int64, hostname dns.Domain, tlsConfig *tls.C
// We include the string ESMTP. https://cr.yp.to/smtp/greeting.html recommends it.
// Should not be too relevant nowadays, but does not hurt and default blackbox
// exporter SMTP health check expects it.
c.writelinef("%d %s ESMTP mox", smtp.C220ServiceReady, c.hostname.ASCII)
c.xwritelinef("%d %s ESMTP mox", smtp.C220ServiceReady, c.hostname.ASCII)
for {
command(c)
@ -1051,7 +1051,7 @@ func command(c *conn) {
var serr smtpError
if errors.As(err, &serr) {
c.writecodeline(serr.code, serr.secode, fmt.Sprintf("%s (%s)", serr.errmsg, mox.ReceivedID(c.cid)), serr.err)
c.xwritecodeline(serr.code, serr.secode, fmt.Sprintf("%s (%s)", serr.errmsg, mox.ReceivedID(c.cid)), serr.err)
if serr.printStack {
c.log.Errorx("smtp error", serr.err, slog.Int("code", serr.code), slog.String("secode", serr.secode))
debug.PrintStack()
@ -1065,7 +1065,7 @@ func command(c *conn) {
// todo future: we could wait for either a line or shutdown, and just close the connection on shutdown.
line := c.readline()
line := c.xreadline()
t := strings.SplitN(line, " ", 2)
var args string
if len(t) == 2 {
@ -1079,7 +1079,7 @@ func command(c *conn) {
select {
case <-mox.Shutdown.Done():
// ../rfc/5321:2811 ../rfc/5321:1666 ../rfc/3463:420
c.writecodeline(smtp.C421ServiceUnavail, smtp.SeSys3NotAccepting2, "shutting down", nil)
c.xwritecodeline(smtp.C421ServiceUnavail, smtp.SeSys3NotAccepting2, "shutting down", nil)
panic(errIO)
default:
}
@ -1095,7 +1095,7 @@ func command(c *conn) {
// Other side is likely speaking something else than SMTP, send error message and
// stop processing because there is a good chance whatever they sent has multiple
// lines.
c.writecodeline(smtp.C500BadSyntax, smtp.SeProto5Syntax2, "please try again speaking smtp", nil)
c.xwritecodeline(smtp.C500BadSyntax, smtp.SeProto5Syntax2, "please try again speaking smtp", nil)
panic(errIO)
}
// note: not "command not implemented", see ../rfc/5321:2934 ../rfc/5321:2539
@ -1186,17 +1186,17 @@ func (c *conn) cmdHello(p *parser, ehlo bool) {
// https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml
c.bwritelinef("250-%s", c.hostname.ASCII)
c.bwritelinef("250-PIPELINING") // ../rfc/2920:108
c.bwritelinef("250-SIZE %d", c.maxMessageSize) // ../rfc/1870:70
c.xbwritelinef("250-%s", c.hostname.ASCII)
c.xbwritelinef("250-PIPELINING") // ../rfc/2920:108
c.xbwritelinef("250-SIZE %d", c.maxMessageSize) // ../rfc/1870:70
// ../rfc/3207:237
if !c.tls && c.baseTLSConfig != nil {
// ../rfc/3207:90
c.bwritelinef("250-STARTTLS")
c.xbwritelinef("250-STARTTLS")
} else if c.extRequireTLS {
// ../rfc/8689:202
// ../rfc/8689:143
c.bwritelinef("250-REQUIRETLS")
c.xbwritelinef("250-REQUIRETLS")
}
if c.submission {
var mechs string
@ -1212,16 +1212,16 @@ func (c *conn) cmdHello(p *parser, ehlo bool) {
if c.tls && len(c.conn.(*tls.Conn).ConnectionState().PeerCertificates) > 0 && !c.viaHTTPS {
mechs = "EXTERNAL " + mechs
}
c.bwritelinef("250-AUTH %s", mechs)
c.xbwritelinef("250-AUTH %s", mechs)
// ../rfc/4865:127
t := time.Now().Add(queue.FutureReleaseIntervalMax).UTC() // ../rfc/4865:98
c.bwritelinef("250-FUTURERELEASE %d %s", queue.FutureReleaseIntervalMax/time.Second, t.Format(time.RFC3339))
c.xbwritelinef("250-FUTURERELEASE %d %s", queue.FutureReleaseIntervalMax/time.Second, t.Format(time.RFC3339))
}
c.bwritelinef("250-ENHANCEDSTATUSCODES") // ../rfc/2034:71
c.xbwritelinef("250-ENHANCEDSTATUSCODES") // ../rfc/2034:71
// todo future? c.writelinef("250-DSN")
c.bwritelinef("250-8BITMIME") // ../rfc/6152:86
c.bwritelinef("250-LIMITS RCPTMAX=%d", rcptToLimit) // ../rfc/9422:301
c.bwritecodeline(250, "", "SMTPUTF8", nil) // ../rfc/6531:201
c.xbwritelinef("250-8BITMIME") // ../rfc/6152:86
c.xbwritelinef("250-LIMITS RCPTMAX=%d", rcptToLimit) // ../rfc/9422:301
c.xbwritecodeline(250, "", "SMTPUTF8", nil) // ../rfc/6531:201
c.xflush()
}
@ -1254,7 +1254,7 @@ func (c *conn) cmdStarttls(p *parser) {
}
// We add the cid to the output, to help debugging in case of a failing TLS connection.
c.writecodeline(smtp.C220ServiceReady, smtp.SeOther00, "go! ("+mox.ReceivedID(c.cid)+")", nil)
c.xwritecodeline(smtp.C220ServiceReady, smtp.SeOther00, "go! ("+mox.ReceivedID(c.cid)+")", nil)
c.xtlsHandshakeAndAuthenticate(conn)
@ -1321,9 +1321,9 @@ func (c *conn) cmdAuth(p *parser) {
xreadInitial := func(encChal string) []byte {
var auth string
if p.empty() {
c.writelinef("%d %s", smtp.C334ContinueAuth, encChal) // ../rfc/4954:205
c.xwritelinef("%d %s", smtp.C334ContinueAuth, encChal) // ../rfc/4954:205
// todo future: handle max length of 12288 octets and return proper responde codes otherwise ../rfc/4954:253
auth = c.readline()
auth = c.xreadline()
if auth == "*" {
// ../rfc/4954:193
la.Result = store.AuthAborted
@ -1355,7 +1355,7 @@ func (c *conn) cmdAuth(p *parser) {
}
xreadContinuation := func() []byte {
line := c.readline()
line := c.xreadline()
if line == "*" {
la.Result = store.AuthAborted
xsmtpUserErrorf(smtp.C501BadParamSyntax, smtp.SeProto5Other0, "authentication aborted")
@ -1444,7 +1444,7 @@ func (c *conn) cmdAuth(p *parser) {
// Again, client should ignore the challenge, we send the same as the example in
// the I-D.
c.writelinef("%d %s", smtp.C334ContinueAuth, base64.StdEncoding.EncodeToString([]byte("Password:")))
c.xwritelinef("%d %s", smtp.C334ContinueAuth, base64.StdEncoding.EncodeToString([]byte("Password:")))
// Password is in line in plain text, so hide it.
defer c.xtrace(mlog.LevelTraceauth)()
@ -1468,7 +1468,7 @@ func (c *conn) cmdAuth(p *parser) {
// ../rfc/2195:82
chal := fmt.Sprintf("<%d.%d@%s>", uint64(mox.CryptoRandInt()), time.Now().UnixNano(), mox.Conf.Static.HostnameDomain.ASCII)
c.writelinef("%d %s", smtp.C334ContinueAuth, base64.StdEncoding.EncodeToString([]byte(chal)))
c.xwritelinef("%d %s", smtp.C334ContinueAuth, base64.StdEncoding.EncodeToString([]byte(chal)))
resp := xreadContinuation()
t := strings.Split(string(resp), " ")
@ -1597,14 +1597,14 @@ func (c *conn) cmdAuth(p *parser) {
})
s1, err := ss.ServerFirst(xscram.Iterations, xscram.Salt)
xcheckf(err, "scram first server step")
c.writelinef("%d %s", smtp.C334ContinueAuth, base64.StdEncoding.EncodeToString([]byte(s1))) // ../rfc/4954:187
c.xwritelinef("%d %s", smtp.C334ContinueAuth, base64.StdEncoding.EncodeToString([]byte(s1))) // ../rfc/4954:187
c2 := xreadContinuation()
s3, err := ss.Finish(c2, xscram.SaltedPassword)
if len(s3) > 0 {
c.writelinef("%d %s", smtp.C334ContinueAuth, base64.StdEncoding.EncodeToString([]byte(s3))) // ../rfc/4954:187
c.xwritelinef("%d %s", smtp.C334ContinueAuth, base64.StdEncoding.EncodeToString([]byte(s3))) // ../rfc/4954:187
}
if err != nil {
c.readline() // Should be "*" for cancellation.
c.xreadline() // Should be "*" for cancellation.
if errors.Is(err, scram.ErrInvalidProof) {
la.Result = store.AuthBadCredentials
c.log.Info("failed authentication attempt", slog.String("username", username), slog.Any("remote", c.remoteIP))
@ -1696,7 +1696,7 @@ func (c *conn) cmdAuth(p *parser) {
c.authFailed = 0
c.setSlow(false)
// ../rfc/4954:276
c.writecodeline(smtp.C235AuthSuccess, smtp.SePol7Other0, "nice", nil)
c.xwritecodeline(smtp.C235AuthSuccess, smtp.SePol7Other0, "nice", nil)
}
// ../rfc/5321:1879 ../rfc/5321:1025
@ -1709,7 +1709,7 @@ func (c *conn) cmdMail(p *parser) {
// If we get many bad transactions, it's probably a spammer that is guessing user names.
// Useful in combination with rate limiting.
// ../rfc/5321:4349
c.writecodeline(smtp.C550MailboxUnavail, smtp.SeAddr1Other0, "too many failures", nil)
c.xwritecodeline(smtp.C550MailboxUnavail, smtp.SeAddr1Other0, "too many failures", nil)
panic(errIO)
}
@ -1905,7 +1905,7 @@ func (c *conn) cmdMail(p *parser) {
c.mailFrom = &rpath
c.bwritecodeline(smtp.C250Completed, smtp.SeAddr1Other0, "looking good", nil)
c.xbwritecodeline(smtp.C250Completed, smtp.SeAddr1Other0, "looking good", nil)
}
// ../rfc/5321:1916 ../rfc/5321:1054
@ -2049,7 +2049,7 @@ func (c *conn) cmdRcpt(p *parser) {
c.log.Errorx("looking up account for delivery", err, slog.Any("rcptto", fpath))
xsmtpServerErrorf(codes{smtp.C451LocalErr, smtp.SeSys3Other0}, "error processing")
}
c.bwritecodeline(smtp.C250Completed, smtp.SeAddr1Other0, "now on the list", nil)
c.xbwritecodeline(smtp.C250Completed, smtp.SeAddr1Other0, "now on the list", nil)
}
func hasNonASCII(s string) bool {
@ -2109,7 +2109,7 @@ func (c *conn) cmdData(p *parser) {
}()
// ../rfc/5321:1994
c.writelinef("354 see you at the bare dot")
c.xwritelinef("354 see you at the bare dot")
// Mark as tracedata.
defer c.xtrace(mlog.LevelTracedata)()
@ -2131,12 +2131,12 @@ func (c *conn) cmdData(p *parser) {
if n < config.DefaultMaxMsgSize {
ecode = smtp.SeMailbox2MsgLimitExceeded3
}
c.writecodeline(smtp.C451LocalErr, ecode, fmt.Sprintf("error copying data to file (%s)", mox.ReceivedID(c.cid)), err)
c.xwritecodeline(smtp.C451LocalErr, ecode, fmt.Sprintf("error copying data to file (%s)", mox.ReceivedID(c.cid)), err)
panic(fmt.Errorf("remote sent too much DATA: %w", errIO))
}
if errors.Is(err, smtp.ErrCRLF) {
c.writecodeline(smtp.C500BadSyntax, smtp.SeProto5Syntax2, fmt.Sprintf("invalid bare \\r or \\n, may be smtp smuggling (%s)", mox.ReceivedID(c.cid)), err)
c.xwritecodeline(smtp.C500BadSyntax, smtp.SeProto5Syntax2, fmt.Sprintf("invalid bare \\r or \\n, may be smtp smuggling (%s)", mox.ReceivedID(c.cid)), err)
return
}
@ -2146,7 +2146,7 @@ func (c *conn) cmdData(p *parser) {
// available and our write blocks us from reading remaining data, leading to
// deadlock. We have a timeout on our connection writes though, so worst case we'll
// abort the connection due to expiration.
c.writecodeline(smtp.C451LocalErr, smtp.SeSys3Other0, fmt.Sprintf("error copying data to file (%s)", mox.ReceivedID(c.cid)), err)
c.xwritecodeline(smtp.C451LocalErr, smtp.SeSys3Other0, fmt.Sprintf("error copying data to file (%s)", mox.ReceivedID(c.cid)), err)
io.Copy(io.Discard, dr)
return
}
@ -2560,7 +2560,7 @@ func (c *conn) submit(ctx context.Context, recvHdrFor func(string) string, msgWr
c.transactionBad-- // Compensate for early earlier pessimistic increase.
c.rset()
c.writecodeline(smtp.C250Completed, smtp.SeMailbox2Other0, "it is done", nil)
c.xwritecodeline(smtp.C250Completed, smtp.SeMailbox2Other0, "it is done", nil)
}
func xrandomID(n int) string {
@ -3689,7 +3689,7 @@ func (c *conn) deliver(ctx context.Context, recvHdrFor func(string) string, msgW
c.transactionGood++
c.transactionBad-- // Compensate for early earlier pessimistic increase.
c.rset()
c.writecodeline(smtp.C250Completed, smtp.SeMailbox2Other0, "it is done", nil)
c.xwritecodeline(smtp.C250Completed, smtp.SeMailbox2Other0, "it is done", nil)
}
// Return whether msgFrom address is allowed to send a message to alias.
@ -3735,7 +3735,7 @@ func (c *conn) cmdRset(p *parser) {
p.xend()
c.rset()
c.bwritecodeline(smtp.C250Completed, smtp.SeOther00, "all clear", nil)
c.xbwritecodeline(smtp.C250Completed, smtp.SeOther00, "all clear", nil)
}
// ../rfc/5321:2108 ../rfc/5321:1222
@ -3781,7 +3781,7 @@ func (c *conn) cmdHelp(p *parser) {
// Let's not strictly parse the request for help. We are ignoring the text anyway.
// ../rfc/5321:2166
c.bwritecodeline(smtp.C214Help, smtp.SeOther00, "see rfc 5321 (smtp)", nil)
c.xbwritecodeline(smtp.C214Help, smtp.SeOther00, "see rfc 5321 (smtp)", nil)
}
// ../rfc/5321:2191
@ -3793,7 +3793,7 @@ func (c *conn) cmdNoop(p *parser) {
}
p.xend()
c.bwritecodeline(smtp.C250Completed, smtp.SeOther00, "alrighty", nil)
c.xbwritecodeline(smtp.C250Completed, smtp.SeOther00, "alrighty", nil)
}
// ../rfc/5321:2205
@ -3801,6 +3801,6 @@ func (c *conn) cmdQuit(p *parser) {
// ../rfc/5321:2226
p.xend()
c.writecodeline(smtp.C221Closing, smtp.SeOther00, "okay thanks bye", nil)
c.xwritecodeline(smtp.C221Closing, smtp.SeOther00, "okay thanks bye", nil)
panic(cleanClose)
}