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
No known key found for this signature in database
10 changed files with 195 additions and 196 deletions

View file

@ -39,7 +39,7 @@ func (c *Conn) readbyte() (byte, error) {
return b, err
}
func (c *Conn) unreadbyte() {
func (c *Conn) xunreadbyte() {
if c.record {
c.recordBuf = c.recordBuf[:len(c.recordBuf)-1]
}
@ -70,7 +70,7 @@ func (c *Conn) xcrlf() {
func (c *Conn) peek(exp byte) bool {
b, err := c.readbyte()
if err == nil {
c.unreadbyte()
c.xunreadbyte()
}
return err == nil && strings.EqualFold(string(rune(b)), string(rune(exp)))
}
@ -264,7 +264,7 @@ func (c *Conn) xtakeuntil(b byte) string {
x, err := c.readbyte()
c.xcheckf(err, "read byte")
if x == b {
c.unreadbyte()
c.xunreadbyte()
return s
}
s += string(rune(x))
@ -279,14 +279,14 @@ func (c *Conn) xdigits() string {
s += string(rune(b))
continue
}
c.unreadbyte()
c.xunreadbyte()
return s
}
}
func (c *Conn) peekdigit() bool {
if b, err := c.readbyte(); err == nil {
c.unreadbyte()
c.xunreadbyte()
return b >= '0' && b <= '9'
}
return false
@ -692,7 +692,7 @@ func (c *Conn) xmsgatt1() FetchAttr {
f += string(rune(b))
continue
}
c.unreadbyte()
c.xunreadbyte()
break
}
@ -851,8 +851,7 @@ func (c *Conn) xatom() string {
b, err := c.readbyte()
c.xcheckf(err, "read byte for atom")
if b <= ' ' || strings.IndexByte("(){%*\"\\]", b) >= 0 {
err := c.br.UnreadByte()
c.xcheckf(err, "unreadbyte")
c.xunreadbyte()
if s == "" {
c.xerrorf("expected atom")
}
@ -1288,7 +1287,7 @@ func (c *Conn) xtaggedExtVal() TaggedExtVal {
b, err := c.readbyte()
c.xcheckf(err, "read byte for tagged-ext-val")
if b < '0' || b > '9' {
c.unreadbyte()
c.xunreadbyte()
ss := c.xsequenceSet()
return TaggedExtVal{SeqSet: &ss}
}

View file

@ -242,7 +242,7 @@ func (c *conn) cmdxFetch(isUID bool, tag, cmdstr string, p *parser) {
// No hard limit on response sizes, but clients are recommended to not send more
// than 8k. We send a more conservative max 4k.
for _, s := range compactUIDSet(vanishedUIDs).Strings(4*1024 - 32) {
c.bwritelinef("* VANISHED (EARLIER) %s", s)
c.xbwritelinef("* VANISHED (EARLIER) %s", s)
}
}
@ -338,7 +338,7 @@ func (c *conn) cmdxFetch(isUID bool, tag, cmdstr string, p *parser) {
if cmd.expungeIssued {
// ../rfc/2180:343
// ../rfc/9051:5102
c.writeresultf("%s OK [EXPUNGEISSUED] at least one message was expunged", tag)
c.xwriteresultf("%s OK [EXPUNGEISSUED] at least one message was expunged", tag)
} else {
c.ok(tag, cmdstr)
}
@ -447,7 +447,7 @@ func (cmd *fetchCmd) process(atts []fetchAtt) {
// Write errors are turned into panics because we write through c.
fmt.Fprintf(cmd.conn.xbw, "* %d FETCH ", cmd.conn.xsequence(cmd.uid))
data.writeTo(cmd.conn, cmd.conn.xbw)
data.xwriteTo(cmd.conn, cmd.conn.xbw)
cmd.conn.xbw.Write([]byte("\r\n"))
}

View file

@ -115,7 +115,7 @@ func (c *conn) cmdList(tag, cmd string, p *parser) {
if !isExtended && reference == "" && patterns[0] == "" {
// ../rfc/9051:2277 ../rfc/3501:2221
c.bwritelinef(`* LIST () "/" ""`)
c.xbwritelinef(`* LIST () "/" ""`)
c.ok(tag, cmd)
return
}
@ -261,11 +261,11 @@ func (c *conn) cmdList(tag, cmd string, p *parser) {
})
for _, line := range responseLines {
c.bwritelinef("%s", line)
c.xbwritelinef("%s", line)
}
for _, meta := range respMetadata {
meta.writeTo(c, c.xbw)
c.bwritelinef("")
meta.xwriteTo(c, c.xbw)
c.xbwritelinef("")
}
c.ok(tag, cmd)
}

View file

@ -160,20 +160,20 @@ func (c *conn) cmdGetmetadata(tag, cmd string, p *parser) {
if i > 0 {
fmt.Fprint(c.xbw, " ")
}
astring(a.Key).writeTo(c, c.xbw)
astring(a.Key).xwriteTo(c, c.xbw)
fmt.Fprint(c.xbw, " ")
if a.IsString {
string0(string(a.Value)).writeTo(c, c.xbw)
string0(string(a.Value)).xwriteTo(c, c.xbw)
} else {
v := readerSizeSyncliteral{bytes.NewReader(a.Value), int64(len(a.Value)), true}
v.writeTo(c, c.xbw)
v.xwriteTo(c, c.xbw)
}
}
c.bwritelinef(")")
c.xbwritelinef(")")
}
if longentries >= 0 {
c.bwritelinef("%s OK [METADATA LONGENTRIES %d] getmetadata done", tag, longentries)
c.xbwritelinef("%s OK [METADATA LONGENTRIES %d] getmetadata done", tag, longentries)
} else {
c.ok(tag, cmd)
}

View file

@ -9,7 +9,7 @@ import (
type token interface {
pack(c *conn) string
writeTo(c *conn, xw io.Writer) // Writes to xw panic on error.
xwriteTo(c *conn, xw io.Writer) // Writes to xw panic on error.
}
type bare string
@ -18,7 +18,7 @@ func (t bare) pack(c *conn) string {
return string(t)
}
func (t bare) writeTo(c *conn, xw io.Writer) {
func (t bare) xwriteTo(c *conn, xw io.Writer) {
xw.Write([]byte(t.pack(c)))
}
@ -30,7 +30,7 @@ func (t niltoken) pack(c *conn) string {
return "NIL"
}
func (t niltoken) writeTo(c *conn, xw io.Writer) {
func (t niltoken) xwriteTo(c *conn, xw io.Writer) {
xw.Write([]byte(t.pack(c)))
}
@ -60,7 +60,7 @@ func (t string0) pack(c *conn) string {
return r
}
func (t string0) writeTo(c *conn, xw io.Writer) {
func (t string0) xwriteTo(c *conn, xw io.Writer) {
xw.Write([]byte(t.pack(c)))
}
@ -78,7 +78,7 @@ func (t dquote) pack(c *conn) string {
return r
}
func (t dquote) writeTo(c *conn, xw io.Writer) {
func (t dquote) xwriteTo(c *conn, xw io.Writer) {
xw.Write([]byte(t.pack(c)))
}
@ -88,7 +88,7 @@ func (t syncliteral) pack(c *conn) string {
return fmt.Sprintf("{%d}\r\n", len(t)) + string(t)
}
func (t syncliteral) writeTo(c *conn, xw io.Writer) {
func (t syncliteral) xwriteTo(c *conn, xw io.Writer) {
fmt.Fprintf(xw, "{%d}\r\n", len(t))
xw.Write([]byte(t))
}
@ -112,7 +112,7 @@ func (t readerSizeSyncliteral) pack(c *conn) string {
return fmt.Sprintf("%s{%d}\r\n", lit, t.size) + string(buf)
}
func (t readerSizeSyncliteral) writeTo(c *conn, xw io.Writer) {
func (t readerSizeSyncliteral) xwriteTo(c *conn, xw io.Writer) {
var lit string
if t.lit8 {
lit = "~"
@ -137,7 +137,7 @@ func (t readerSyncliteral) pack(c *conn) string {
return fmt.Sprintf("{%d}\r\n", len(buf)) + string(buf)
}
func (t readerSyncliteral) writeTo(c *conn, xw io.Writer) {
func (t readerSyncliteral) xwriteTo(c *conn, xw io.Writer) {
buf, err := io.ReadAll(t.r)
if err != nil {
panic(err)
@ -162,13 +162,13 @@ func (t listspace) pack(c *conn) string {
return s
}
func (t listspace) writeTo(c *conn, xw io.Writer) {
func (t listspace) xwriteTo(c *conn, xw io.Writer) {
fmt.Fprint(xw, "(")
for i, e := range t {
if i > 0 {
fmt.Fprint(xw, " ")
}
e.writeTo(c, xw)
e.xwriteTo(c, xw)
}
fmt.Fprint(xw, ")")
}
@ -187,12 +187,12 @@ func (t concatspace) pack(c *conn) string {
return s
}
func (t concatspace) writeTo(c *conn, xw io.Writer) {
func (t concatspace) xwriteTo(c *conn, xw io.Writer) {
for i, e := range t {
if i > 0 {
fmt.Fprint(xw, " ")
}
e.writeTo(c, xw)
e.xwriteTo(c, xw)
}
}
@ -207,9 +207,9 @@ func (t concat) pack(c *conn) string {
return s
}
func (t concat) writeTo(c *conn, xw io.Writer) {
func (t concat) xwriteTo(c *conn, xw io.Writer) {
for _, e := range t {
e.writeTo(c, xw)
e.xwriteTo(c, xw)
}
}
@ -231,7 +231,7 @@ next:
return string(t)
}
func (t astring) writeTo(c *conn, xw io.Writer) {
func (t astring) xwriteTo(c *conn, xw io.Writer) {
xw.Write([]byte(t.pack(c)))
}
@ -246,7 +246,7 @@ func (t mailboxt) pack(c *conn) string {
return astring(s).pack(c)
}
func (t mailboxt) writeTo(c *conn, xw io.Writer) {
func (t mailboxt) xwriteTo(c *conn, xw io.Writer) {
xw.Write([]byte(t.pack(c)))
}
@ -256,6 +256,6 @@ func (t number) pack(c *conn) string {
return fmt.Sprintf("%d", t)
}
func (t number) writeTo(c *conn, xw io.Writer) {
func (t number) xwriteTo(c *conn, xw io.Writer) {
xw.Write([]byte(t.pack(c)))
}

View file

@ -306,7 +306,7 @@ func (p *parser) xstring() (r string) {
}
size, sync := p.xliteralSize(false, true)
buf := p.conn.xreadliteral(size, sync)
line := p.conn.readline(false)
line := p.conn.xreadline(false)
p.orig, p.upper, p.o = line, toUpper(line), 0
return string(buf)
}
@ -1041,7 +1041,7 @@ func (p *parser) xmetadataKeyValue() (key string, isString bool, value []byte) {
if p.hasPrefix("~{") {
size, sync := p.xliteralSize(true, true)
value = p.conn.xreadliteral(size, sync)
line := p.conn.readline(false)
line := p.conn.xreadline(false)
p.orig, p.upper, p.o = line, toUpper(line), 0
} else if p.hasPrefix(`"`) {
value = []byte(p.xstring())

View file

@ -150,7 +150,7 @@ func (c *conn) cmdxReplace(isUID bool, tag, cmd string, p *parser) {
})
})
c.writelinef("+ ")
c.xwritelinef("+ ")
} else {
var err error
name, _, err = store.CheckMailboxName(name, true)
@ -212,7 +212,7 @@ func (c *conn) cmdxReplace(isUID bool, tag, cmd string, p *parser) {
}
// Finish reading the command.
line := c.readline(false)
line := c.xreadline(false)
p = newParser(line, c)
if utf8 {
p.xtake(")")
@ -328,8 +328,8 @@ func (c *conn) cmdxReplace(isUID bool, tag, cmd string, p *parser) {
if mbDst.ID == c.mailboxID {
c.uidAppend(nm.UID)
// We send an untagged OK with APPENDUID, for sane bookkeeping in clients. ../rfc/8508:401
c.bwritelinef("* OK [APPENDUID %d %d] ", mbDst.UIDValidity, nm.UID)
c.bwritelinef("* %d EXISTS", len(c.uids))
c.xbwritelinef("* OK [APPENDUID %d %d] ", mbDst.UIDValidity, nm.UID)
c.xbwritelinef("* %d EXISTS", len(c.uids))
}
// We must return vanished instead of expunge, and also highestmodseq, when qresync
@ -341,10 +341,10 @@ func (c *conn) cmdxReplace(isUID bool, tag, cmd string, p *parser) {
omsgseq := c.xsequence(om.UID)
c.sequenceRemove(omsgseq, om.UID)
if qresync {
c.bwritelinef("* VANISHED %d", om.UID)
c.xbwritelinef("* VANISHED %d", om.UID)
// ../rfc/7162:1916
} else {
c.bwritelinef("* %d EXPUNGE", omsgseq)
c.xbwritelinef("* %d EXPUNGE", omsgseq)
}
c.writeresultf("%s OK [HIGHESTMODSEQ %d] replaced", tag, nm.ModSeq.Client())
c.xwriteresultf("%s OK [HIGHESTMODSEQ %d] replaced", tag, nm.ModSeq.Client())
}

View file

@ -431,7 +431,7 @@ func (c *conn) cmdxSearch(isUID, isE bool, tag, cmd string, p *parser) {
lastUID = m.UID
if time.Since(inProgressLast) > inProgressPeriod {
c.writelinef("* OK [INPROGRESS (%s %d %s)] still searching", inProgressTag, progress, goal)
c.xwritelinef("* OK [INPROGRESS (%s %d %s)] still searching", inProgressTag, progress, goal)
inProgressLast = time.Now()
}
progress++
@ -468,7 +468,7 @@ func (c *conn) cmdxSearch(isUID, isE bool, tag, cmd string, p *parser) {
xcheckf(err, "list messages in mailbox")
if time.Since(inProgressLast) > inProgressPeriod {
c.writelinef("* OK [INPROGRESS (%s %d %s)] still searching", inProgressTag, progress, goal)
c.xwritelinef("* OK [INPROGRESS (%s %d %s)] still searching", inProgressTag, progress, goal)
inProgressLast = time.Now()
}
progress++
@ -497,7 +497,7 @@ func (c *conn) cmdxSearch(isUID, isE bool, tag, cmd string, p *parser) {
// In IMAP4rev1, an untagged SEARCH response is required. ../rfc/3501:2728
if len(result.UIDs) == 0 {
c.bwritelinef("* SEARCH")
c.xbwritelinef("* SEARCH")
}
// Old-style SEARCH response. We must spell out each number. So we may be splitting
@ -527,7 +527,7 @@ func (c *conn) cmdxSearch(isUID, isE bool, tag, cmd string, p *parser) {
modseq = fmt.Sprintf(" (MODSEQ %d)", result.MaxModSeq.Client())
}
c.bwritelinef("* SEARCH%s%s", s, modseq)
c.xbwritelinef("* SEARCH%s%s", s, modseq)
result.UIDs = result.UIDs[n:]
}
} else {
@ -595,7 +595,7 @@ func (c *conn) cmdxSearch(isUID, isE bool, tag, cmd string, p *parser) {
fmt.Fprintf(c.xbw, " MODSEQ %d", result.MaxModSeq.Client())
}
c.bwritelinef("")
c.xbwritelinef("")
}
}
}

View file

@ -579,7 +579,7 @@ func (c *conn) lineChan() chan lineErr {
}
// readline from either the c.line channel, or otherwise read from connection.
func (c *conn) readline(readCmd bool) string {
func (c *conn) xreadline(readCmd bool) string {
var line string
var err error
if c.line != nil {
@ -593,7 +593,7 @@ func (c *conn) readline(readCmd bool) string {
if readCmd && errors.Is(err, os.ErrDeadlineExceeded) {
err := c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
c.log.Check(err, "setting write deadline")
c.writelinef("* BYE inactive")
c.xwritelinef("* BYE inactive")
}
if !errors.Is(err, errIO) && !errors.Is(err, errProtocol) {
c.xbrokenf("%s (%w)", err, errIO)
@ -618,13 +618,13 @@ func (c *conn) readline(readCmd bool) string {
}
// write tagged command response, but first write pending changes.
func (c *conn) writeresultf(format string, args ...any) {
c.bwriteresultf(format, args...)
func (c *conn) xwriteresultf(format string, args ...any) {
c.xbwriteresultf(format, args...)
c.xflush()
}
// write buffered tagged command response, but first write pending changes.
func (c *conn) bwriteresultf(format string, args ...any) {
func (c *conn) xbwriteresultf(format string, args ...any) {
switch c.cmd {
case "fetch", "store", "search":
// ../rfc/9051:5862 ../rfc/7162:2033
@ -633,16 +633,16 @@ func (c *conn) bwriteresultf(format string, args ...any) {
c.applyChanges(c.comm.Get(), false)
}
}
c.bwritelinef(format, args...)
c.xbwritelinef(format, args...)
}
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()
}
// Buffer line for write.
func (c *conn) bwritelinef(format string, args ...any) {
func (c *conn) xbwritelinef(format string, args ...any) {
format += "\r\n"
fmt.Fprintf(c.xbw, format, args...)
}
@ -671,7 +671,7 @@ func (c *conn) xflush() {
}
func (c *conn) readCommand(tag *string) (cmd string, p *parser) {
line := c.readline(true)
line := c.xreadline(true)
p = newParser(line, c)
p.context("tag")
*tag = p.xtag()
@ -683,7 +683,7 @@ func (c *conn) readCommand(tag *string) (cmd string, p *parser) {
func (c *conn) xreadliteral(size int64, sync bool) []byte {
if sync {
c.writelinef("+ ")
c.xwritelinef("+ ")
}
buf := make([]byte, size)
if size > 0 {
@ -817,13 +817,13 @@ func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, x
select {
case <-mox.Shutdown.Done():
// ../rfc/9051:5381
c.writelinef("* BYE mox shutting down")
c.xwritelinef("* BYE mox shutting down")
return
default:
}
if !limiterConnectionrate.Add(c.remoteIP, time.Now(), 1) {
c.writelinef("* BYE connection rate from your ip or network too high, slow down please")
c.xwritelinef("* BYE connection rate from your ip or network too high, slow down please")
return
}
@ -831,13 +831,13 @@ func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, x
if !mox.LimiterFailedAuth.CanAdd(c.remoteIP, time.Now(), 1) {
metrics.AuthenticationRatelimitedInc("imap")
c.log.Debug("refusing connection due to many auth failures", slog.Any("remoteip", c.remoteIP))
c.writelinef("* BYE too many auth failures")
c.xwritelinef("* BYE too many auth failures")
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.writelinef("* BYE too many open connections from your ip or network")
c.xwritelinef("* BYE too many open connections from your ip or network")
return
}
defer limiterConnections.Add(c.remoteIP, time.Now(), -1)
@ -851,7 +851,7 @@ func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, x
acc, _, _, err := store.OpenEmail(c.log, preauthAddress, false)
if err != nil {
c.log.Debugx("open account for preauth address", err, slog.String("address", preauthAddress))
c.writelinef("* BYE open account for address: %s", err)
c.xwritelinef("* BYE open account for address: %s", err)
return
}
c.username = preauthAddress
@ -861,9 +861,9 @@ func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, x
if c.account != nil && !c.noPreauth {
c.state = stateAuthenticated
c.writelinef("* PREAUTH [CAPABILITY %s] mox imap welcomes %s", c.capabilities(), c.username)
c.xwritelinef("* PREAUTH [CAPABILITY %s] mox imap welcomes %s", c.capabilities(), c.username)
} else {
c.writelinef("* OK [CAPABILITY %s] mox imap", c.capabilities())
c.xwritelinef("* OK [CAPABILITY %s] mox imap", c.capabilities())
}
// Ensure any pending loginAttempt is written before we stop.
@ -1101,7 +1101,7 @@ func (c *conn) xtlsHandshakeAndAuthenticate(conn net.Conn) {
// Verify client after session resumption.
err := c.tlsClientAuthVerifyPeerCertParsed(cs.PeerCertificates[0])
if err != nil {
c.writelinef("* BYE [ALERT] Error verifying client certificate after TLS session resumption: %s", err)
c.xwritelinef("* BYE [ALERT] Error verifying client certificate after TLS session resumption: %s", err)
c.xbrokenf("tls verify client certificate after resumption: %s (%w)", err, errIO)
}
}
@ -1181,7 +1181,7 @@ func (c *conn) command() {
// Other side is likely speaking something else than IMAP, send error message and
// stop processing because there is a good chance whatever they sent has multiple
// lines.
c.writelinef("* BYE please try again speaking imap")
c.xwritelinef("* BYE please try again speaking imap")
c.xbrokenf("not speaking imap (%w)", errIO)
}
c.log.Debugx("imap command syntax error", sxerr.err, logFields...)
@ -1192,13 +1192,13 @@ func (c *conn) command() {
c.log.Check(err, "setting write deadline")
}
if sxerr.line != "" {
c.bwritelinef("%s", sxerr.line)
c.xbwritelinef("%s", sxerr.line)
}
code := ""
if sxerr.code != "" {
code = "[" + sxerr.code + "] "
}
c.bwriteresultf("%s BAD %s%s unrecognized syntax/command: %v", tag, code, cmd, sxerr.errmsg)
c.xbwriteresultf("%s BAD %s%s unrecognized syntax/command: %v", tag, code, cmd, sxerr.errmsg)
if fatal {
c.xflush()
panic(fmt.Errorf("aborting connection after syntax error for command with non-sync literal: %w", errProtocol))
@ -1207,14 +1207,14 @@ func (c *conn) command() {
result = "servererror"
c.log.Errorx("imap command server error", err, logFields...)
debug.PrintStack()
c.bwriteresultf("%s NO %s %v", tag, cmd, err)
c.xbwriteresultf("%s NO %s %v", tag, cmd, err)
} else if errors.As(err, &uerr) {
result = "usererror"
c.log.Debugx("imap command user error", err, logFields...)
if uerr.code != "" {
c.bwriteresultf("%s NO [%s] %s %v", tag, uerr.code, cmd, err)
c.xbwriteresultf("%s NO [%s] %s %v", tag, uerr.code, cmd, err)
} else {
c.bwriteresultf("%s NO %s %v", tag, cmd, err)
c.xbwriteresultf("%s NO %s %v", tag, cmd, err)
}
} else {
// Other type of panic, we pass it on, aborting the connection.
@ -1234,7 +1234,7 @@ func (c *conn) command() {
select {
case <-mox.Shutdown.Done():
// ../rfc/9051:5375
c.writelinef("* BYE shutting down")
c.xwritelinef("* BYE shutting down")
c.xbrokenf("shutting down (%w)", errIO)
default:
}
@ -1527,7 +1527,7 @@ func (c *conn) xnumSetConditionUIDs(forDB, returnUIDs bool, isUID bool, nums num
}
func (c *conn) ok(tag, cmd string) {
c.bwriteresultf("%s OK %s done", tag, cmd)
c.xbwriteresultf("%s OK %s done", tag, cmd)
c.xflush()
}
@ -1646,14 +1646,14 @@ func (c *conn) applyChanges(changes []store.Change, initial bool) {
// Write the exists, and the UID and flags as well. Hopefully the client waits for
// long enough after the EXISTS to see these messages, and doesn't request them
// again with a FETCH.
c.bwritelinef("* %d EXISTS", len(c.uids))
c.xbwritelinef("* %d EXISTS", len(c.uids))
for _, add := range adds {
seq := c.xsequence(add.UID)
var modseqStr string
if condstore {
modseqStr = fmt.Sprintf(" MODSEQ (%d)", add.ModSeq.Client())
}
c.bwritelinef("* %d FETCH (UID %d FLAGS %s%s)", seq, add.UID, flaglist(add.Flags, add.Keywords).pack(c), modseqStr)
c.xbwritelinef("* %d FETCH (UID %d FLAGS %s%s)", seq, add.UID, flaglist(add.Flags, add.Keywords).pack(c), modseqStr)
}
continue
}
@ -1679,14 +1679,14 @@ func (c *conn) applyChanges(changes []store.Change, initial bool) {
if qresync {
vanishedUIDs.append(uint32(uid))
} else {
c.bwritelinef("* %d EXPUNGE", seq)
c.xbwritelinef("* %d EXPUNGE", seq)
}
}
}
if qresync {
// VANISHED without EARLIER. ../rfc/7162:2004
for _, s := range vanishedUIDs.Strings(4*1024 - 32) {
c.bwritelinef("* VANISHED %s", s)
c.xbwritelinef("* VANISHED %s", s)
}
}
case store.ChangeFlags:
@ -1700,29 +1700,29 @@ func (c *conn) applyChanges(changes []store.Change, initial bool) {
if condstore {
modseqStr = fmt.Sprintf(" MODSEQ (%d)", ch.ModSeq.Client())
}
c.bwritelinef("* %d FETCH (UID %d FLAGS %s%s)", seq, ch.UID, flaglist(ch.Flags, ch.Keywords).pack(c), modseqStr)
c.xbwritelinef("* %d FETCH (UID %d FLAGS %s%s)", seq, ch.UID, flaglist(ch.Flags, ch.Keywords).pack(c), modseqStr)
}
case store.ChangeRemoveMailbox:
// Only announce \NonExistent to modern clients, otherwise they may ignore the
// unrecognized \NonExistent and interpret this as a newly created mailbox, while
// the goal was to remove it...
if c.enabled[capIMAP4rev2] {
c.bwritelinef(`* LIST (\NonExistent) "/" %s`, mailboxt(ch.Name).pack(c))
c.xbwritelinef(`* LIST (\NonExistent) "/" %s`, mailboxt(ch.Name).pack(c))
}
case store.ChangeAddMailbox:
c.bwritelinef(`* LIST (%s) "/" %s`, strings.Join(ch.Flags, " "), mailboxt(ch.Mailbox.Name).pack(c))
c.xbwritelinef(`* LIST (%s) "/" %s`, strings.Join(ch.Flags, " "), mailboxt(ch.Mailbox.Name).pack(c))
case store.ChangeRenameMailbox:
// OLDNAME only with IMAP4rev2 or NOTIFY ../rfc/9051:2726 ../rfc/5465:628
var oldname string
if c.enabled[capIMAP4rev2] {
oldname = fmt.Sprintf(` ("OLDNAME" (%s))`, mailboxt(ch.OldName).pack(c))
}
c.bwritelinef(`* LIST (%s) "/" %s%s`, strings.Join(ch.Flags, " "), mailboxt(ch.NewName).pack(c), oldname)
c.xbwritelinef(`* LIST (%s) "/" %s%s`, strings.Join(ch.Flags, " "), mailboxt(ch.NewName).pack(c), oldname)
case store.ChangeAddSubscription:
c.bwritelinef(`* LIST (%s) "/" %s`, strings.Join(append([]string{`\Subscribed`}, ch.Flags...), " "), mailboxt(ch.Name).pack(c))
c.xbwritelinef(`* LIST (%s) "/" %s`, strings.Join(append([]string{`\Subscribed`}, ch.Flags...), " "), mailboxt(ch.Name).pack(c))
case store.ChangeAnnotation:
// ../rfc/5464:807 ../rfc/5464:788
c.bwritelinef(`* METADATA %s %s`, mailboxt(ch.MailboxName).pack(c), astring(ch.Key).pack(c))
c.xbwritelinef(`* METADATA %s %s`, mailboxt(ch.MailboxName).pack(c), astring(ch.Key).pack(c))
default:
panic(fmt.Sprintf("internal error, missing case for %#v", change))
}
@ -1742,7 +1742,7 @@ func (c *conn) cmdCapability(tag, cmd string, p *parser) {
caps := c.capabilities()
// Response syntax: ../rfc/9051:6427 ../rfc/3501:4655
c.bwritelinef("* CAPABILITY %s", caps)
c.xbwritelinef("* CAPABILITY %s", caps)
c.ok(tag, cmd)
}
@ -1790,7 +1790,7 @@ func (c *conn) cmdLogout(tag, cmd string, p *parser) {
c.unselect()
c.state = stateNotAuthenticated
// Response syntax: ../rfc/9051:6886 ../rfc/3501:4935
c.bwritelinef("* BYE thanks")
c.xbwritelinef("* BYE thanks")
c.ok(tag, cmd)
panic(cleanClose)
}
@ -1843,9 +1843,9 @@ func (c *conn) cmdID(tag, cmd string, p *parser) {
// Response syntax: ../rfc/2971:243
// We send our name, and only the version for authenticated users. ../rfc/2971:193
if c.state == stateAuthenticated || c.state == stateSelected {
c.bwritelinef(`* ID ("name" "mox" "version" %s)`, string0(moxvar.Version).pack(c))
c.xbwritelinef(`* ID ("name" "mox" "version" %s)`, string0(moxvar.Version).pack(c))
} else {
c.bwritelinef(`* ID ("name" "mox")`)
c.xbwritelinef(`* ID ("name" "mox")`)
}
c.ok(tag, cmd)
}
@ -1981,8 +1981,8 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
xreadInitial := func() []byte {
var line string
if p.empty() {
c.writelinef("+ ")
line = c.readline(false)
c.xwritelinef("+ ")
line = c.xreadline(false)
} else {
// ../rfc/9051:1407 ../rfc/4959:84
p.xspace()
@ -2005,7 +2005,7 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
}
xreadContinuation := func() []byte {
line := c.readline(false)
line := c.xreadline(false)
if line == "*" {
c.loginAttempt.Result = store.AuthAborted
xsyntaxErrorf("authenticate aborted by client")
@ -2074,7 +2074,7 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
// ../rfc/2195:82
chal := fmt.Sprintf("<%d.%d@%s>", uint64(mox.CryptoRandInt()), time.Now().UnixNano(), mox.Conf.Static.HostnameDomain.ASCII)
c.writelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(chal)))
c.xwritelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(chal)))
resp := xreadContinuation()
t := strings.Split(string(resp), " ")
@ -2202,14 +2202,14 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
})
s1, err := ss.ServerFirst(xscram.Iterations, xscram.Salt)
xcheckf(err, "scram first server step")
c.writelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(s1)))
c.xwritelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(s1)))
c2 := xreadContinuation()
s3, err := ss.Finish(c2, xscram.SaltedPassword)
if len(s3) > 0 {
c.writelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(s3)))
c.xwritelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(s3)))
}
if err != nil {
c.readline(false) // Should be "*" for cancellation.
c.xreadline(false) // Should be "*" for cancellation.
if errors.Is(err, scram.ErrInvalidProof) {
c.loginAttempt.Result = store.AuthBadCredentials
c.log.Info("failed authentication attempt", slog.String("username", username), slog.Any("remote", c.remoteIP))
@ -2304,7 +2304,7 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
c.loginAttempt.Result = store.AuthSuccess
c.authFailed = 0
c.state = stateAuthenticated
c.writeresultf("%s OK [CAPABILITY %s] authenticate done", tag, c.capabilities())
c.xwriteresultf("%s OK [CAPABILITY %s] authenticate done", tag, c.capabilities())
}
// Login logs in with username and password.
@ -2412,7 +2412,7 @@ func (c *conn) cmdLogin(tag, cmd string, p *parser) {
c.authFailed = 0
c.setSlow(false)
c.state = stateAuthenticated
c.writeresultf("%s OK [CAPABILITY %s] login done", tag, c.capabilities())
c.xwriteresultf("%s OK [CAPABILITY %s] login done", tag, c.capabilities())
}
// Enable explicitly opts in to an extension. A server can typically send new kinds
@ -2461,7 +2461,7 @@ func (c *conn) cmdEnable(tag, cmd string, p *parser) {
}
// Response syntax: ../rfc/9051:6520 ../rfc/5161:211
c.bwritelinef("* ENABLED%s", enabled)
c.xbwritelinef("* ENABLED%s", enabled)
c.ok(tag, cmd)
}
@ -2486,7 +2486,7 @@ func (c *conn) xensureCondstore(tx *bstore.Tx) {
} else {
mb = c.xmailboxID(tx, c.mailboxID)
}
c.bwritelinef("* OK [HIGHESTMODSEQ %d] after condstore-enabling command", mb.ModSeq.Client())
c.xbwritelinef("* OK [HIGHESTMODSEQ %d] after condstore-enabling command", mb.ModSeq.Client())
}
}
@ -2573,7 +2573,7 @@ func (c *conn) cmdSelectExamine(isselect bool, tag, cmd string, p *parser) {
// ../rfc/9051:1809
if c.state == stateSelected {
// ../rfc/9051:1812 ../rfc/7162:2111
c.bwritelinef("* OK [CLOSED] x")
c.xbwritelinef("* OK [CLOSED] x")
c.unselect()
}
@ -2624,23 +2624,23 @@ func (c *conn) cmdSelectExamine(isselect bool, tag, cmd string, p *parser) {
if len(mb.Keywords) > 0 {
flags = " " + strings.Join(mb.Keywords, " ")
}
c.bwritelinef(`* FLAGS (\Seen \Answered \Flagged \Deleted \Draft $Forwarded $Junk $NotJunk $Phishing $MDNSent%s)`, flags)
c.bwritelinef(`* OK [PERMANENTFLAGS (\Seen \Answered \Flagged \Deleted \Draft $Forwarded $Junk $NotJunk $Phishing $MDNSent \*)] x`)
c.xbwritelinef(`* FLAGS (\Seen \Answered \Flagged \Deleted \Draft $Forwarded $Junk $NotJunk $Phishing $MDNSent%s)`, flags)
c.xbwritelinef(`* OK [PERMANENTFLAGS (\Seen \Answered \Flagged \Deleted \Draft $Forwarded $Junk $NotJunk $Phishing $MDNSent \*)] x`)
if !c.enabled[capIMAP4rev2] {
c.bwritelinef(`* 0 RECENT`)
c.xbwritelinef(`* 0 RECENT`)
}
c.bwritelinef(`* %d EXISTS`, len(c.uids))
c.xbwritelinef(`* %d EXISTS`, len(c.uids))
if !c.enabled[capIMAP4rev2] && firstUnseen > 0 {
// ../rfc/9051:8051 ../rfc/3501:1774
c.bwritelinef(`* OK [UNSEEN %d] x`, firstUnseen)
c.xbwritelinef(`* OK [UNSEEN %d] x`, firstUnseen)
}
c.bwritelinef(`* OK [UIDVALIDITY %d] x`, mb.UIDValidity)
c.bwritelinef(`* OK [UIDNEXT %d] x`, mb.UIDNext)
c.bwritelinef(`* LIST () "/" %s`, mailboxt(mb.Name).pack(c))
c.xbwritelinef(`* OK [UIDVALIDITY %d] x`, mb.UIDValidity)
c.xbwritelinef(`* OK [UIDNEXT %d] x`, mb.UIDNext)
c.xbwritelinef(`* LIST () "/" %s`, mailboxt(mb.Name).pack(c))
if c.enabled[capCondstore] {
// ../rfc/7162:417
// ../rfc/7162-eid5055 ../rfc/7162:484 ../rfc/7162:1167
c.bwritelinef(`* OK [HIGHESTMODSEQ %d] x`, highestModSeq.Client())
c.xbwritelinef(`* OK [HIGHESTMODSEQ %d] x`, highestModSeq.Client())
}
// If QRESYNC uidvalidity matches, we send any changes. ../rfc/7162:1509
@ -2711,7 +2711,7 @@ func (c *conn) cmdSelectExamine(isselect bool, tag, cmd string, p *parser) {
qrmodseq = m.ModSeq.Client() - 1
preVanished = 0
qrknownUIDs = nil
c.bwritelinef("* OK [ALERT] Synchronization inconsistency in client detected. Client tried to sync with a UID that was removed at or after the MODSEQ it sent in the request. Sending all historic message removals for selected mailbox. Full synchronization recommended.")
c.xbwritelinef("* OK [ALERT] Synchronization inconsistency in client detected. Client tried to sync with a UID that was removed at or after the MODSEQ it sent in the request. Sending all historic message removals for selected mailbox. Full synchronization recommended.")
}
} else if err != bstore.ErrAbsent {
xcheckf(err, "checking old client uid")
@ -2738,7 +2738,7 @@ func (c *conn) cmdSelectExamine(isselect bool, tag, cmd string, p *parser) {
}
msgseq := c.sequence(m.UID)
if msgseq > 0 {
c.bwritelinef("* %d FETCH (UID %d FLAGS %s MODSEQ (%d))", msgseq, m.UID, flaglist(m.Flags, m.Keywords).pack(c), m.ModSeq.Client())
c.xbwritelinef("* %d FETCH (UID %d FLAGS %s MODSEQ (%d))", msgseq, m.UID, flaglist(m.Flags, m.Keywords).pack(c), m.ModSeq.Client())
}
return nil
})
@ -2774,16 +2774,16 @@ func (c *conn) cmdSelectExamine(isselect bool, tag, cmd string, p *parser) {
l := slices.Sorted(maps.Keys(vanishedUIDs))
// ../rfc/7162:1985
for _, s := range compactUIDSet(l).Strings(4*1024 - 32) {
c.bwritelinef("* VANISHED (EARLIER) %s", s)
c.xbwritelinef("* VANISHED (EARLIER) %s", s)
}
}
}
if isselect {
c.bwriteresultf("%s OK [READ-WRITE] x", tag)
c.xbwriteresultf("%s OK [READ-WRITE] x", tag)
c.readonly = false
} else {
c.bwriteresultf("%s OK [READ-ONLY] x", tag)
c.xbwriteresultf("%s OK [READ-ONLY] x", tag)
c.readonly = true
}
c.mailboxID = mb.ID
@ -2870,7 +2870,7 @@ func (c *conn) cmdCreate(tag, cmd string, p *parser) {
if c.enabled[capIMAP4rev2] && n == name && name != origName && !(name == "Inbox" || strings.HasPrefix(name, "Inbox/")) {
oldname = fmt.Sprintf(` ("OLDNAME" (%s))`, mailboxt(origName).pack(c))
}
c.bwritelinef(`* LIST (\Subscribed) "/" %s%s`, mailboxt(n).pack(c), oldname)
c.xbwritelinef(`* LIST (\Subscribed) "/" %s%s`, mailboxt(n).pack(c), oldname)
}
c.ok(tag, cmd)
}
@ -3145,7 +3145,7 @@ func (c *conn) cmdLsub(tag, cmd string, p *parser) {
// Response syntax: ../rfc/3501:4833 ../rfc/3501:4837
for _, line := range lines {
c.bwritelinef("%s", line)
c.xbwritelinef("%s", line)
}
c.ok(tag, cmd)
}
@ -3163,7 +3163,7 @@ func (c *conn) cmdNamespace(tag, cmd string, p *parser) {
p.xempty()
// Response syntax: ../rfc/9051:6778 ../rfc/2342:415
c.bwritelinef(`* NAMESPACE (("" "/")) NIL NIL`)
c.xbwritelinef(`* NAMESPACE (("" "/")) NIL NIL`)
c.ok(tag, cmd)
}
@ -3200,7 +3200,7 @@ func (c *conn) cmdStatus(tag, cmd string, p *parser) {
})
})
c.bwritelinef("%s", responseLine)
c.xbwritelinef("%s", responseLine)
c.ok(tag, cmd)
}
@ -3394,7 +3394,7 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
defer store.CloseRemoveTempFile(c.log, a.file, "temporary message file")
f = a.file
c.writelinef("+ ")
c.xwritelinef("+ ")
} else {
// We'll discard the message and return an error as soon as we can (possible
// synchronizing literal of next message, or after we've seen all messages).
@ -3422,7 +3422,7 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
}
totalSize += msize
line := c.readline(false)
line := c.xreadline(false)
p = newParser(line, c)
if utf8 {
p.xtake(")")
@ -3524,7 +3524,7 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
c.uidAppend(a.m.UID)
}
// todo spec: with condstore/qresync, is there a mechanism to let the client know the modseq for the appended uid? in theory an untagged fetch with the modseq after the OK APPENDUID could make sense, but this probably isn't allowed.
c.bwritelinef("* %d EXISTS", len(c.uids))
c.xbwritelinef("* %d EXISTS", len(c.uids))
}
// ../rfc/4315:289 ../rfc/3502:236 APPENDUID
@ -3535,7 +3535,7 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
} else {
uidset = fmt.Sprintf("%d:%d", appends[0].m.UID, appends[len(appends)-1].m.UID)
}
c.writeresultf("%s OK [APPENDUID %d %s] appended", tag, mb.UIDValidity, uidset)
c.xwriteresultf("%s OK [APPENDUID %d %s] appended", tag, mb.UIDValidity, uidset)
}
// Idle makes a client wait until the server sends untagged updates, e.g. about
@ -3550,7 +3550,7 @@ func (c *conn) cmdIdle(tag, cmd string, p *parser) {
// Request syntax: ../rfc/9051:6594 ../rfc/2177:163
p.xempty()
c.writelinef("+ waiting")
c.xwritelinef("+ waiting")
var line string
wait:
@ -3566,7 +3566,7 @@ wait:
c.xflush()
case <-mox.Shutdown.Done():
// ../rfc/9051:5375
c.writelinef("* BYE shutting down")
c.xwritelinef("* BYE shutting down")
c.xbrokenf("shutting down (%w)", errIO)
}
}
@ -3616,13 +3616,13 @@ func (c *conn) cmdGetquotaroot(tag, cmd string, p *parser) {
// We only have one per account quota, we name it "" like the examples in the RFC.
// Response syntax: ../rfc/9208:668 ../rfc/2087:242
c.bwritelinef(`* QUOTAROOT %s ""`, astring(name).pack(c))
c.xbwritelinef(`* QUOTAROOT %s ""`, astring(name).pack(c))
// We only write the quota response if there is a limit. The syntax doesn't allow
// an empty list, so we cannot send the current disk usage if there is no limit.
if quota > 0 {
// Response syntax: ../rfc/9208:666 ../rfc/2087:239
c.bwritelinef(`* QUOTA "" (STORAGE %d %d)`, (size+1024-1)/1024, (quota+1024-1)/1024)
c.xbwritelinef(`* QUOTA "" (STORAGE %d %d)`, (size+1024-1)/1024, (quota+1024-1)/1024)
}
c.ok(tag, cmd)
}
@ -3660,7 +3660,7 @@ func (c *conn) cmdGetquota(tag, cmd string, p *parser) {
// an empty list, so we cannot send the current disk usage if there is no limit.
if quota > 0 {
// Response syntax: ../rfc/9208:666 ../rfc/2087:239
c.bwritelinef(`* QUOTA "" (STORAGE %d %d)`, (size+1024-1)/1024, (quota+1024-1)/1024)
c.xbwritelinef(`* QUOTA "" (STORAGE %d %d)`, (size+1024-1)/1024, (quota+1024-1)/1024)
}
c.ok(tag, cmd)
}
@ -3830,18 +3830,18 @@ func (c *conn) cmdxExpunge(tag, cmd string, uidSet *numSet) {
if qresync {
vanishedUIDs.append(uint32(m.UID))
} else {
c.bwritelinef("* %d EXPUNGE", seq)
c.xbwritelinef("* %d EXPUNGE", seq)
}
}
if !vanishedUIDs.empty() {
// VANISHED without EARLIER. ../rfc/7162:2004
for _, s := range vanishedUIDs.Strings(4*1024 - 32) {
c.bwritelinef("* VANISHED %s", s)
c.xbwritelinef("* VANISHED %s", s)
}
}
if c.enabled[capCondstore] {
c.writeresultf("%s OK [HIGHESTMODSEQ %d] expunged", tag, highestModSeq.Client())
c.xwriteresultf("%s OK [HIGHESTMODSEQ %d] expunged", tag, highestModSeq.Client())
} else {
c.ok(tag, cmd)
}
@ -4119,7 +4119,7 @@ func (c *conn) cmdxCopy(isUID bool, tag, cmd string, p *parser) {
})
// ../rfc/9051:6881 ../rfc/4315:183
c.writeresultf("%s OK [COPYUID %d %s %s] copied", tag, mbDst.UIDValidity, compactUIDSet(origUIDs).String(), compactUIDSet(newUIDs).String())
c.xwriteresultf("%s OK [COPYUID %d %s %s] copied", tag, mbDst.UIDValidity, compactUIDSet(origUIDs).String(), compactUIDSet(newUIDs).String())
}
// Move moves messages from the currently selected/active mailbox to a named mailbox.
@ -4197,7 +4197,7 @@ func (c *conn) cmdxMove(isUID bool, tag, cmd string, p *parser) {
// ../rfc/9051:4708 ../rfc/6851:254
// ../rfc/9051:4713
newUIDs := numSet{ranges: []numRange{{setNumber{number: uint32(uidFirst)}, &setNumber{number: uint32(mbDst.UIDNext - 1)}}}}
c.bwritelinef("* OK [COPYUID %d %s %s] moved", mbDst.UIDValidity, compactUIDSet(uids).String(), newUIDs.String())
c.xbwritelinef("* OK [COPYUID %d %s %s] moved", mbDst.UIDValidity, compactUIDSet(uids).String(), newUIDs.String())
qresync := c.enabled[capQresync]
var vanishedUIDs numSet
for i := range uids {
@ -4206,19 +4206,19 @@ func (c *conn) cmdxMove(isUID bool, tag, cmd string, p *parser) {
if qresync {
vanishedUIDs.append(uint32(uids[i]))
} else {
c.bwritelinef("* %d EXPUNGE", seq)
c.xbwritelinef("* %d EXPUNGE", seq)
}
}
if !vanishedUIDs.empty() {
// VANISHED without EARLIER. ../rfc/7162:2004
for _, s := range vanishedUIDs.Strings(4*1024 - 32) {
c.bwritelinef("* VANISHED %s", s)
c.xbwritelinef("* VANISHED %s", s)
}
}
if qresync {
// ../rfc/9051:6744 ../rfc/7162:1334
c.writeresultf("%s OK [HIGHESTMODSEQ %d] move", tag, modseq.Client())
c.xwriteresultf("%s OK [HIGHESTMODSEQ %d] move", tag, modseq.Client())
} else {
c.ok(tag, cmd)
}
@ -4570,7 +4570,7 @@ func (c *conn) cmdxStore(isUID bool, tag, cmd string, p *parser) {
modseqStr = fmt.Sprintf(" MODSEQ (%d)", m.ModSeq.Client())
}
// ../rfc/9051:6749 ../rfc/3501:4869 ../rfc/7162:2490
c.bwritelinef("* %d FETCH (UID %d%s%s)", c.xsequence(m.UID), m.UID, flags, modseqStr)
c.xbwritelinef("* %d FETCH (UID %d%s%s)", c.xsequence(m.UID), m.UID, flags, modseqStr)
}
}
@ -4588,7 +4588,7 @@ func (c *conn) cmdxStore(isUID bool, tag, cmd string, p *parser) {
// Also gather UIDs or sequences for the MODIFIED response below. ../rfc/7162:571
var mnums []store.UID
for _, m := range changed {
c.bwritelinef("* %d FETCH (UID %d FLAGS %s MODSEQ (%d))", c.xsequence(m.UID), m.UID, flaglist(m.Flags, m.Keywords).pack(c), m.ModSeq.Client())
c.xbwritelinef("* %d FETCH (UID %d FLAGS %s MODSEQ (%d))", c.xsequence(m.UID), m.UID, flaglist(m.Flags, m.Keywords).pack(c), m.ModSeq.Client())
if isUID {
mnums = append(mnums, m.UID)
} else {
@ -4599,5 +4599,5 @@ func (c *conn) cmdxStore(isUID bool, tag, cmd string, p *parser) {
slices.Sort(mnums)
set := compactUIDSet(mnums)
// ../rfc/7162:2506
c.writeresultf("%s OK [MODIFIED %s] conditional store did not modify all", tag, set.String())
c.xwriteresultf("%s OK [MODIFIED %s] conditional store did not modify all", tag, set.String())
}

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)
}