mirror of
https://github.com/mjl-/mox.git
synced 2025-06-28 01:48:15 +03:00
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:
parent
deb57462a4
commit
00c8db98e6
@ -39,7 +39,7 @@ func (c *Conn) readbyte() (byte, error) {
|
|||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) unreadbyte() {
|
func (c *Conn) xunreadbyte() {
|
||||||
if c.record {
|
if c.record {
|
||||||
c.recordBuf = c.recordBuf[:len(c.recordBuf)-1]
|
c.recordBuf = c.recordBuf[:len(c.recordBuf)-1]
|
||||||
}
|
}
|
||||||
@ -70,7 +70,7 @@ func (c *Conn) xcrlf() {
|
|||||||
func (c *Conn) peek(exp byte) bool {
|
func (c *Conn) peek(exp byte) bool {
|
||||||
b, err := c.readbyte()
|
b, err := c.readbyte()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.unreadbyte()
|
c.xunreadbyte()
|
||||||
}
|
}
|
||||||
return err == nil && strings.EqualFold(string(rune(b)), string(rune(exp)))
|
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()
|
x, err := c.readbyte()
|
||||||
c.xcheckf(err, "read byte")
|
c.xcheckf(err, "read byte")
|
||||||
if x == b {
|
if x == b {
|
||||||
c.unreadbyte()
|
c.xunreadbyte()
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
s += string(rune(x))
|
s += string(rune(x))
|
||||||
@ -279,14 +279,14 @@ func (c *Conn) xdigits() string {
|
|||||||
s += string(rune(b))
|
s += string(rune(b))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.unreadbyte()
|
c.xunreadbyte()
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) peekdigit() bool {
|
func (c *Conn) peekdigit() bool {
|
||||||
if b, err := c.readbyte(); err == nil {
|
if b, err := c.readbyte(); err == nil {
|
||||||
c.unreadbyte()
|
c.xunreadbyte()
|
||||||
return b >= '0' && b <= '9'
|
return b >= '0' && b <= '9'
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -692,7 +692,7 @@ func (c *Conn) xmsgatt1() FetchAttr {
|
|||||||
f += string(rune(b))
|
f += string(rune(b))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.unreadbyte()
|
c.xunreadbyte()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -851,8 +851,7 @@ func (c *Conn) xatom() string {
|
|||||||
b, err := c.readbyte()
|
b, err := c.readbyte()
|
||||||
c.xcheckf(err, "read byte for atom")
|
c.xcheckf(err, "read byte for atom")
|
||||||
if b <= ' ' || strings.IndexByte("(){%*\"\\]", b) >= 0 {
|
if b <= ' ' || strings.IndexByte("(){%*\"\\]", b) >= 0 {
|
||||||
err := c.br.UnreadByte()
|
c.xunreadbyte()
|
||||||
c.xcheckf(err, "unreadbyte")
|
|
||||||
if s == "" {
|
if s == "" {
|
||||||
c.xerrorf("expected atom")
|
c.xerrorf("expected atom")
|
||||||
}
|
}
|
||||||
@ -1288,7 +1287,7 @@ func (c *Conn) xtaggedExtVal() TaggedExtVal {
|
|||||||
b, err := c.readbyte()
|
b, err := c.readbyte()
|
||||||
c.xcheckf(err, "read byte for tagged-ext-val")
|
c.xcheckf(err, "read byte for tagged-ext-val")
|
||||||
if b < '0' || b > '9' {
|
if b < '0' || b > '9' {
|
||||||
c.unreadbyte()
|
c.xunreadbyte()
|
||||||
ss := c.xsequenceSet()
|
ss := c.xsequenceSet()
|
||||||
return TaggedExtVal{SeqSet: &ss}
|
return TaggedExtVal{SeqSet: &ss}
|
||||||
}
|
}
|
||||||
|
@ -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
|
// No hard limit on response sizes, but clients are recommended to not send more
|
||||||
// than 8k. We send a more conservative max 4k.
|
// than 8k. We send a more conservative max 4k.
|
||||||
for _, s := range compactUIDSet(vanishedUIDs).Strings(4*1024 - 32) {
|
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 {
|
if cmd.expungeIssued {
|
||||||
// ../rfc/2180:343
|
// ../rfc/2180:343
|
||||||
// ../rfc/9051:5102
|
// ../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 {
|
} else {
|
||||||
c.ok(tag, cmdstr)
|
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.
|
// Write errors are turned into panics because we write through c.
|
||||||
fmt.Fprintf(cmd.conn.xbw, "* %d FETCH ", cmd.conn.xsequence(cmd.uid))
|
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"))
|
cmd.conn.xbw.Write([]byte("\r\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ func (c *conn) cmdList(tag, cmd string, p *parser) {
|
|||||||
|
|
||||||
if !isExtended && reference == "" && patterns[0] == "" {
|
if !isExtended && reference == "" && patterns[0] == "" {
|
||||||
// ../rfc/9051:2277 ../rfc/3501:2221
|
// ../rfc/9051:2277 ../rfc/3501:2221
|
||||||
c.bwritelinef(`* LIST () "/" ""`)
|
c.xbwritelinef(`* LIST () "/" ""`)
|
||||||
c.ok(tag, cmd)
|
c.ok(tag, cmd)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -261,11 +261,11 @@ func (c *conn) cmdList(tag, cmd string, p *parser) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
for _, line := range responseLines {
|
for _, line := range responseLines {
|
||||||
c.bwritelinef("%s", line)
|
c.xbwritelinef("%s", line)
|
||||||
}
|
}
|
||||||
for _, meta := range respMetadata {
|
for _, meta := range respMetadata {
|
||||||
meta.writeTo(c, c.xbw)
|
meta.xwriteTo(c, c.xbw)
|
||||||
c.bwritelinef("")
|
c.xbwritelinef("")
|
||||||
}
|
}
|
||||||
c.ok(tag, cmd)
|
c.ok(tag, cmd)
|
||||||
}
|
}
|
||||||
|
@ -160,20 +160,20 @@ func (c *conn) cmdGetmetadata(tag, cmd string, p *parser) {
|
|||||||
if i > 0 {
|
if i > 0 {
|
||||||
fmt.Fprint(c.xbw, " ")
|
fmt.Fprint(c.xbw, " ")
|
||||||
}
|
}
|
||||||
astring(a.Key).writeTo(c, c.xbw)
|
astring(a.Key).xwriteTo(c, c.xbw)
|
||||||
fmt.Fprint(c.xbw, " ")
|
fmt.Fprint(c.xbw, " ")
|
||||||
if a.IsString {
|
if a.IsString {
|
||||||
string0(string(a.Value)).writeTo(c, c.xbw)
|
string0(string(a.Value)).xwriteTo(c, c.xbw)
|
||||||
} else {
|
} else {
|
||||||
v := readerSizeSyncliteral{bytes.NewReader(a.Value), int64(len(a.Value)), true}
|
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 {
|
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 {
|
} else {
|
||||||
c.ok(tag, cmd)
|
c.ok(tag, cmd)
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
type token interface {
|
type token interface {
|
||||||
pack(c *conn) string
|
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
|
type bare string
|
||||||
@ -18,7 +18,7 @@ func (t bare) pack(c *conn) string {
|
|||||||
return string(t)
|
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)))
|
xw.Write([]byte(t.pack(c)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ func (t niltoken) pack(c *conn) string {
|
|||||||
return "NIL"
|
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)))
|
xw.Write([]byte(t.pack(c)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ func (t string0) pack(c *conn) string {
|
|||||||
return r
|
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)))
|
xw.Write([]byte(t.pack(c)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ func (t dquote) pack(c *conn) string {
|
|||||||
return r
|
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)))
|
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)
|
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))
|
fmt.Fprintf(xw, "{%d}\r\n", len(t))
|
||||||
xw.Write([]byte(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)
|
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
|
var lit string
|
||||||
if t.lit8 {
|
if t.lit8 {
|
||||||
lit = "~"
|
lit = "~"
|
||||||
@ -137,7 +137,7 @@ func (t readerSyncliteral) pack(c *conn) string {
|
|||||||
return fmt.Sprintf("{%d}\r\n", len(buf)) + string(buf)
|
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)
|
buf, err := io.ReadAll(t.r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -162,13 +162,13 @@ func (t listspace) pack(c *conn) string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t listspace) writeTo(c *conn, xw io.Writer) {
|
func (t listspace) xwriteTo(c *conn, xw io.Writer) {
|
||||||
fmt.Fprint(xw, "(")
|
fmt.Fprint(xw, "(")
|
||||||
for i, e := range t {
|
for i, e := range t {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
fmt.Fprint(xw, " ")
|
fmt.Fprint(xw, " ")
|
||||||
}
|
}
|
||||||
e.writeTo(c, xw)
|
e.xwriteTo(c, xw)
|
||||||
}
|
}
|
||||||
fmt.Fprint(xw, ")")
|
fmt.Fprint(xw, ")")
|
||||||
}
|
}
|
||||||
@ -187,12 +187,12 @@ func (t concatspace) pack(c *conn) string {
|
|||||||
return s
|
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 {
|
for i, e := range t {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
fmt.Fprint(xw, " ")
|
fmt.Fprint(xw, " ")
|
||||||
}
|
}
|
||||||
e.writeTo(c, xw)
|
e.xwriteTo(c, xw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,9 +207,9 @@ func (t concat) pack(c *conn) string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t concat) writeTo(c *conn, xw io.Writer) {
|
func (t concat) xwriteTo(c *conn, xw io.Writer) {
|
||||||
for _, e := range t {
|
for _, e := range t {
|
||||||
e.writeTo(c, xw)
|
e.xwriteTo(c, xw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,7 +231,7 @@ next:
|
|||||||
return string(t)
|
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)))
|
xw.Write([]byte(t.pack(c)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +246,7 @@ func (t mailboxt) pack(c *conn) string {
|
|||||||
return astring(s).pack(c)
|
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)))
|
xw.Write([]byte(t.pack(c)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,6 +256,6 @@ func (t number) pack(c *conn) string {
|
|||||||
return fmt.Sprintf("%d", t)
|
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)))
|
xw.Write([]byte(t.pack(c)))
|
||||||
}
|
}
|
||||||
|
@ -306,7 +306,7 @@ func (p *parser) xstring() (r string) {
|
|||||||
}
|
}
|
||||||
size, sync := p.xliteralSize(false, true)
|
size, sync := p.xliteralSize(false, true)
|
||||||
buf := p.conn.xreadliteral(size, sync)
|
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
|
p.orig, p.upper, p.o = line, toUpper(line), 0
|
||||||
return string(buf)
|
return string(buf)
|
||||||
}
|
}
|
||||||
@ -1041,7 +1041,7 @@ func (p *parser) xmetadataKeyValue() (key string, isString bool, value []byte) {
|
|||||||
if p.hasPrefix("~{") {
|
if p.hasPrefix("~{") {
|
||||||
size, sync := p.xliteralSize(true, true)
|
size, sync := p.xliteralSize(true, true)
|
||||||
value = p.conn.xreadliteral(size, sync)
|
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
|
p.orig, p.upper, p.o = line, toUpper(line), 0
|
||||||
} else if p.hasPrefix(`"`) {
|
} else if p.hasPrefix(`"`) {
|
||||||
value = []byte(p.xstring())
|
value = []byte(p.xstring())
|
||||||
|
@ -150,7 +150,7 @@ func (c *conn) cmdxReplace(isUID bool, tag, cmd string, p *parser) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
c.writelinef("+ ")
|
c.xwritelinef("+ ")
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
name, _, err = store.CheckMailboxName(name, true)
|
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.
|
// Finish reading the command.
|
||||||
line := c.readline(false)
|
line := c.xreadline(false)
|
||||||
p = newParser(line, c)
|
p = newParser(line, c)
|
||||||
if utf8 {
|
if utf8 {
|
||||||
p.xtake(")")
|
p.xtake(")")
|
||||||
@ -328,8 +328,8 @@ func (c *conn) cmdxReplace(isUID bool, tag, cmd string, p *parser) {
|
|||||||
if mbDst.ID == c.mailboxID {
|
if mbDst.ID == c.mailboxID {
|
||||||
c.uidAppend(nm.UID)
|
c.uidAppend(nm.UID)
|
||||||
// We send an untagged OK with APPENDUID, for sane bookkeeping in clients. ../rfc/8508:401
|
// 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.xbwritelinef("* OK [APPENDUID %d %d] ", mbDst.UIDValidity, nm.UID)
|
||||||
c.bwritelinef("* %d EXISTS", len(c.uids))
|
c.xbwritelinef("* %d EXISTS", len(c.uids))
|
||||||
}
|
}
|
||||||
|
|
||||||
// We must return vanished instead of expunge, and also highestmodseq, when qresync
|
// 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)
|
omsgseq := c.xsequence(om.UID)
|
||||||
c.sequenceRemove(omsgseq, om.UID)
|
c.sequenceRemove(omsgseq, om.UID)
|
||||||
if qresync {
|
if qresync {
|
||||||
c.bwritelinef("* VANISHED %d", om.UID)
|
c.xbwritelinef("* VANISHED %d", om.UID)
|
||||||
// ../rfc/7162:1916
|
// ../rfc/7162:1916
|
||||||
} else {
|
} 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())
|
||||||
}
|
}
|
||||||
|
@ -431,7 +431,7 @@ func (c *conn) cmdxSearch(isUID, isE bool, tag, cmd string, p *parser) {
|
|||||||
lastUID = m.UID
|
lastUID = m.UID
|
||||||
|
|
||||||
if time.Since(inProgressLast) > inProgressPeriod {
|
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()
|
inProgressLast = time.Now()
|
||||||
}
|
}
|
||||||
progress++
|
progress++
|
||||||
@ -468,7 +468,7 @@ func (c *conn) cmdxSearch(isUID, isE bool, tag, cmd string, p *parser) {
|
|||||||
xcheckf(err, "list messages in mailbox")
|
xcheckf(err, "list messages in mailbox")
|
||||||
|
|
||||||
if time.Since(inProgressLast) > inProgressPeriod {
|
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()
|
inProgressLast = time.Now()
|
||||||
}
|
}
|
||||||
progress++
|
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
|
// In IMAP4rev1, an untagged SEARCH response is required. ../rfc/3501:2728
|
||||||
if len(result.UIDs) == 0 {
|
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
|
// 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())
|
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:]
|
result.UIDs = result.UIDs[n:]
|
||||||
}
|
}
|
||||||
} else {
|
} 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())
|
fmt.Fprintf(c.xbw, " MODSEQ %d", result.MaxModSeq.Client())
|
||||||
}
|
}
|
||||||
|
|
||||||
c.bwritelinef("")
|
c.xbwritelinef("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -579,7 +579,7 @@ func (c *conn) lineChan() chan lineErr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// readline from either the c.line channel, or otherwise read from connection.
|
// 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 line string
|
||||||
var err error
|
var err error
|
||||||
if c.line != nil {
|
if c.line != nil {
|
||||||
@ -593,7 +593,7 @@ func (c *conn) readline(readCmd bool) string {
|
|||||||
if readCmd && errors.Is(err, os.ErrDeadlineExceeded) {
|
if readCmd && errors.Is(err, os.ErrDeadlineExceeded) {
|
||||||
err := c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
err := c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||||
c.log.Check(err, "setting write deadline")
|
c.log.Check(err, "setting write deadline")
|
||||||
c.writelinef("* BYE inactive")
|
c.xwritelinef("* BYE inactive")
|
||||||
}
|
}
|
||||||
if !errors.Is(err, errIO) && !errors.Is(err, errProtocol) {
|
if !errors.Is(err, errIO) && !errors.Is(err, errProtocol) {
|
||||||
c.xbrokenf("%s (%w)", err, errIO)
|
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.
|
// write tagged command response, but first write pending changes.
|
||||||
func (c *conn) writeresultf(format string, args ...any) {
|
func (c *conn) xwriteresultf(format string, args ...any) {
|
||||||
c.bwriteresultf(format, args...)
|
c.xbwriteresultf(format, args...)
|
||||||
c.xflush()
|
c.xflush()
|
||||||
}
|
}
|
||||||
|
|
||||||
// write buffered tagged command response, but first write pending changes.
|
// 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 {
|
switch c.cmd {
|
||||||
case "fetch", "store", "search":
|
case "fetch", "store", "search":
|
||||||
// ../rfc/9051:5862 ../rfc/7162:2033
|
// ../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.applyChanges(c.comm.Get(), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.bwritelinef(format, args...)
|
c.xbwritelinef(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conn) writelinef(format string, args ...any) {
|
func (c *conn) xwritelinef(format string, args ...any) {
|
||||||
c.bwritelinef(format, args...)
|
c.xbwritelinef(format, args...)
|
||||||
c.xflush()
|
c.xflush()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buffer line for write.
|
// Buffer line for write.
|
||||||
func (c *conn) bwritelinef(format string, args ...any) {
|
func (c *conn) xbwritelinef(format string, args ...any) {
|
||||||
format += "\r\n"
|
format += "\r\n"
|
||||||
fmt.Fprintf(c.xbw, format, args...)
|
fmt.Fprintf(c.xbw, format, args...)
|
||||||
}
|
}
|
||||||
@ -671,7 +671,7 @@ func (c *conn) xflush() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *conn) readCommand(tag *string) (cmd string, p *parser) {
|
func (c *conn) readCommand(tag *string) (cmd string, p *parser) {
|
||||||
line := c.readline(true)
|
line := c.xreadline(true)
|
||||||
p = newParser(line, c)
|
p = newParser(line, c)
|
||||||
p.context("tag")
|
p.context("tag")
|
||||||
*tag = p.xtag()
|
*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 {
|
func (c *conn) xreadliteral(size int64, sync bool) []byte {
|
||||||
if sync {
|
if sync {
|
||||||
c.writelinef("+ ")
|
c.xwritelinef("+ ")
|
||||||
}
|
}
|
||||||
buf := make([]byte, size)
|
buf := make([]byte, size)
|
||||||
if size > 0 {
|
if size > 0 {
|
||||||
@ -817,13 +817,13 @@ func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, x
|
|||||||
select {
|
select {
|
||||||
case <-mox.Shutdown.Done():
|
case <-mox.Shutdown.Done():
|
||||||
// ../rfc/9051:5381
|
// ../rfc/9051:5381
|
||||||
c.writelinef("* BYE mox shutting down")
|
c.xwritelinef("* BYE mox shutting down")
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
if !limiterConnectionrate.Add(c.remoteIP, time.Now(), 1) {
|
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
|
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) {
|
if !mox.LimiterFailedAuth.CanAdd(c.remoteIP, time.Now(), 1) {
|
||||||
metrics.AuthenticationRatelimitedInc("imap")
|
metrics.AuthenticationRatelimitedInc("imap")
|
||||||
c.log.Debug("refusing connection due to many auth failures", slog.Any("remoteip", c.remoteIP))
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !limiterConnections.Add(c.remoteIP, time.Now(), 1) {
|
if !limiterConnections.Add(c.remoteIP, time.Now(), 1) {
|
||||||
c.log.Debug("refusing connection due to many open connections", slog.Any("remoteip", c.remoteIP))
|
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
|
return
|
||||||
}
|
}
|
||||||
defer limiterConnections.Add(c.remoteIP, time.Now(), -1)
|
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)
|
acc, _, _, err := store.OpenEmail(c.log, preauthAddress, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.log.Debugx("open account for preauth address", err, slog.String("address", preauthAddress))
|
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
|
return
|
||||||
}
|
}
|
||||||
c.username = preauthAddress
|
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 {
|
if c.account != nil && !c.noPreauth {
|
||||||
c.state = stateAuthenticated
|
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 {
|
} 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.
|
// 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.
|
// Verify client after session resumption.
|
||||||
err := c.tlsClientAuthVerifyPeerCertParsed(cs.PeerCertificates[0])
|
err := c.tlsClientAuthVerifyPeerCertParsed(cs.PeerCertificates[0])
|
||||||
if err != nil {
|
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)
|
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
|
// 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
|
// stop processing because there is a good chance whatever they sent has multiple
|
||||||
// lines.
|
// 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.xbrokenf("not speaking imap (%w)", errIO)
|
||||||
}
|
}
|
||||||
c.log.Debugx("imap command syntax error", sxerr.err, logFields...)
|
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")
|
c.log.Check(err, "setting write deadline")
|
||||||
}
|
}
|
||||||
if sxerr.line != "" {
|
if sxerr.line != "" {
|
||||||
c.bwritelinef("%s", sxerr.line)
|
c.xbwritelinef("%s", sxerr.line)
|
||||||
}
|
}
|
||||||
code := ""
|
code := ""
|
||||||
if sxerr.code != "" {
|
if sxerr.code != "" {
|
||||||
code = "[" + 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 {
|
if fatal {
|
||||||
c.xflush()
|
c.xflush()
|
||||||
panic(fmt.Errorf("aborting connection after syntax error for command with non-sync literal: %w", errProtocol))
|
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"
|
result = "servererror"
|
||||||
c.log.Errorx("imap command server error", err, logFields...)
|
c.log.Errorx("imap command server error", err, logFields...)
|
||||||
debug.PrintStack()
|
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) {
|
} else if errors.As(err, &uerr) {
|
||||||
result = "usererror"
|
result = "usererror"
|
||||||
c.log.Debugx("imap command user error", err, logFields...)
|
c.log.Debugx("imap command user error", err, logFields...)
|
||||||
if uerr.code != "" {
|
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 {
|
} else {
|
||||||
c.bwriteresultf("%s NO %s %v", tag, cmd, err)
|
c.xbwriteresultf("%s NO %s %v", tag, cmd, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Other type of panic, we pass it on, aborting the connection.
|
// Other type of panic, we pass it on, aborting the connection.
|
||||||
@ -1234,7 +1234,7 @@ func (c *conn) command() {
|
|||||||
select {
|
select {
|
||||||
case <-mox.Shutdown.Done():
|
case <-mox.Shutdown.Done():
|
||||||
// ../rfc/9051:5375
|
// ../rfc/9051:5375
|
||||||
c.writelinef("* BYE shutting down")
|
c.xwritelinef("* BYE shutting down")
|
||||||
c.xbrokenf("shutting down (%w)", errIO)
|
c.xbrokenf("shutting down (%w)", errIO)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@ -1527,7 +1527,7 @@ func (c *conn) xnumSetConditionUIDs(forDB, returnUIDs bool, isUID bool, nums num
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *conn) ok(tag, cmd string) {
|
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()
|
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
|
// 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
|
// long enough after the EXISTS to see these messages, and doesn't request them
|
||||||
// again with a FETCH.
|
// again with a FETCH.
|
||||||
c.bwritelinef("* %d EXISTS", len(c.uids))
|
c.xbwritelinef("* %d EXISTS", len(c.uids))
|
||||||
for _, add := range adds {
|
for _, add := range adds {
|
||||||
seq := c.xsequence(add.UID)
|
seq := c.xsequence(add.UID)
|
||||||
var modseqStr string
|
var modseqStr string
|
||||||
if condstore {
|
if condstore {
|
||||||
modseqStr = fmt.Sprintf(" MODSEQ (%d)", add.ModSeq.Client())
|
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
|
continue
|
||||||
}
|
}
|
||||||
@ -1679,14 +1679,14 @@ func (c *conn) applyChanges(changes []store.Change, initial bool) {
|
|||||||
if qresync {
|
if qresync {
|
||||||
vanishedUIDs.append(uint32(uid))
|
vanishedUIDs.append(uint32(uid))
|
||||||
} else {
|
} else {
|
||||||
c.bwritelinef("* %d EXPUNGE", seq)
|
c.xbwritelinef("* %d EXPUNGE", seq)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if qresync {
|
if qresync {
|
||||||
// VANISHED without EARLIER. ../rfc/7162:2004
|
// VANISHED without EARLIER. ../rfc/7162:2004
|
||||||
for _, s := range vanishedUIDs.Strings(4*1024 - 32) {
|
for _, s := range vanishedUIDs.Strings(4*1024 - 32) {
|
||||||
c.bwritelinef("* VANISHED %s", s)
|
c.xbwritelinef("* VANISHED %s", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case store.ChangeFlags:
|
case store.ChangeFlags:
|
||||||
@ -1700,29 +1700,29 @@ func (c *conn) applyChanges(changes []store.Change, initial bool) {
|
|||||||
if condstore {
|
if condstore {
|
||||||
modseqStr = fmt.Sprintf(" MODSEQ (%d)", ch.ModSeq.Client())
|
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:
|
case store.ChangeRemoveMailbox:
|
||||||
// Only announce \NonExistent to modern clients, otherwise they may ignore the
|
// Only announce \NonExistent to modern clients, otherwise they may ignore the
|
||||||
// unrecognized \NonExistent and interpret this as a newly created mailbox, while
|
// unrecognized \NonExistent and interpret this as a newly created mailbox, while
|
||||||
// the goal was to remove it...
|
// the goal was to remove it...
|
||||||
if c.enabled[capIMAP4rev2] {
|
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:
|
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:
|
case store.ChangeRenameMailbox:
|
||||||
// OLDNAME only with IMAP4rev2 or NOTIFY ../rfc/9051:2726 ../rfc/5465:628
|
// OLDNAME only with IMAP4rev2 or NOTIFY ../rfc/9051:2726 ../rfc/5465:628
|
||||||
var oldname string
|
var oldname string
|
||||||
if c.enabled[capIMAP4rev2] {
|
if c.enabled[capIMAP4rev2] {
|
||||||
oldname = fmt.Sprintf(` ("OLDNAME" (%s))`, mailboxt(ch.OldName).pack(c))
|
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:
|
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:
|
case store.ChangeAnnotation:
|
||||||
// ../rfc/5464:807 ../rfc/5464:788
|
// ../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:
|
default:
|
||||||
panic(fmt.Sprintf("internal error, missing case for %#v", change))
|
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()
|
caps := c.capabilities()
|
||||||
|
|
||||||
// Response syntax: ../rfc/9051:6427 ../rfc/3501:4655
|
// Response syntax: ../rfc/9051:6427 ../rfc/3501:4655
|
||||||
c.bwritelinef("* CAPABILITY %s", caps)
|
c.xbwritelinef("* CAPABILITY %s", caps)
|
||||||
c.ok(tag, cmd)
|
c.ok(tag, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1790,7 +1790,7 @@ func (c *conn) cmdLogout(tag, cmd string, p *parser) {
|
|||||||
c.unselect()
|
c.unselect()
|
||||||
c.state = stateNotAuthenticated
|
c.state = stateNotAuthenticated
|
||||||
// Response syntax: ../rfc/9051:6886 ../rfc/3501:4935
|
// Response syntax: ../rfc/9051:6886 ../rfc/3501:4935
|
||||||
c.bwritelinef("* BYE thanks")
|
c.xbwritelinef("* BYE thanks")
|
||||||
c.ok(tag, cmd)
|
c.ok(tag, cmd)
|
||||||
panic(cleanClose)
|
panic(cleanClose)
|
||||||
}
|
}
|
||||||
@ -1843,9 +1843,9 @@ func (c *conn) cmdID(tag, cmd string, p *parser) {
|
|||||||
// Response syntax: ../rfc/2971:243
|
// Response syntax: ../rfc/2971:243
|
||||||
// We send our name, and only the version for authenticated users. ../rfc/2971:193
|
// We send our name, and only the version for authenticated users. ../rfc/2971:193
|
||||||
if c.state == stateAuthenticated || c.state == stateSelected {
|
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 {
|
} else {
|
||||||
c.bwritelinef(`* ID ("name" "mox")`)
|
c.xbwritelinef(`* ID ("name" "mox")`)
|
||||||
}
|
}
|
||||||
c.ok(tag, cmd)
|
c.ok(tag, cmd)
|
||||||
}
|
}
|
||||||
@ -1981,8 +1981,8 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
|
|||||||
xreadInitial := func() []byte {
|
xreadInitial := func() []byte {
|
||||||
var line string
|
var line string
|
||||||
if p.empty() {
|
if p.empty() {
|
||||||
c.writelinef("+ ")
|
c.xwritelinef("+ ")
|
||||||
line = c.readline(false)
|
line = c.xreadline(false)
|
||||||
} else {
|
} else {
|
||||||
// ../rfc/9051:1407 ../rfc/4959:84
|
// ../rfc/9051:1407 ../rfc/4959:84
|
||||||
p.xspace()
|
p.xspace()
|
||||||
@ -2005,7 +2005,7 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
xreadContinuation := func() []byte {
|
xreadContinuation := func() []byte {
|
||||||
line := c.readline(false)
|
line := c.xreadline(false)
|
||||||
if line == "*" {
|
if line == "*" {
|
||||||
c.loginAttempt.Result = store.AuthAborted
|
c.loginAttempt.Result = store.AuthAborted
|
||||||
xsyntaxErrorf("authenticate aborted by client")
|
xsyntaxErrorf("authenticate aborted by client")
|
||||||
@ -2074,7 +2074,7 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
|
|||||||
|
|
||||||
// ../rfc/2195:82
|
// ../rfc/2195:82
|
||||||
chal := fmt.Sprintf("<%d.%d@%s>", uint64(mox.CryptoRandInt()), time.Now().UnixNano(), mox.Conf.Static.HostnameDomain.ASCII)
|
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()
|
resp := xreadContinuation()
|
||||||
t := strings.Split(string(resp), " ")
|
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)
|
s1, err := ss.ServerFirst(xscram.Iterations, xscram.Salt)
|
||||||
xcheckf(err, "scram first server step")
|
xcheckf(err, "scram first server step")
|
||||||
c.writelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(s1)))
|
c.xwritelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(s1)))
|
||||||
c2 := xreadContinuation()
|
c2 := xreadContinuation()
|
||||||
s3, err := ss.Finish(c2, xscram.SaltedPassword)
|
s3, err := ss.Finish(c2, xscram.SaltedPassword)
|
||||||
if len(s3) > 0 {
|
if len(s3) > 0 {
|
||||||
c.writelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(s3)))
|
c.xwritelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(s3)))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.readline(false) // Should be "*" for cancellation.
|
c.xreadline(false) // Should be "*" for cancellation.
|
||||||
if errors.Is(err, scram.ErrInvalidProof) {
|
if errors.Is(err, scram.ErrInvalidProof) {
|
||||||
c.loginAttempt.Result = store.AuthBadCredentials
|
c.loginAttempt.Result = store.AuthBadCredentials
|
||||||
c.log.Info("failed authentication attempt", slog.String("username", username), slog.Any("remote", c.remoteIP))
|
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.loginAttempt.Result = store.AuthSuccess
|
||||||
c.authFailed = 0
|
c.authFailed = 0
|
||||||
c.state = stateAuthenticated
|
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.
|
// Login logs in with username and password.
|
||||||
@ -2412,7 +2412,7 @@ func (c *conn) cmdLogin(tag, cmd string, p *parser) {
|
|||||||
c.authFailed = 0
|
c.authFailed = 0
|
||||||
c.setSlow(false)
|
c.setSlow(false)
|
||||||
c.state = stateAuthenticated
|
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
|
// 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
|
// Response syntax: ../rfc/9051:6520 ../rfc/5161:211
|
||||||
c.bwritelinef("* ENABLED%s", enabled)
|
c.xbwritelinef("* ENABLED%s", enabled)
|
||||||
c.ok(tag, cmd)
|
c.ok(tag, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2486,7 +2486,7 @@ func (c *conn) xensureCondstore(tx *bstore.Tx) {
|
|||||||
} else {
|
} else {
|
||||||
mb = c.xmailboxID(tx, c.mailboxID)
|
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
|
// ../rfc/9051:1809
|
||||||
if c.state == stateSelected {
|
if c.state == stateSelected {
|
||||||
// ../rfc/9051:1812 ../rfc/7162:2111
|
// ../rfc/9051:1812 ../rfc/7162:2111
|
||||||
c.bwritelinef("* OK [CLOSED] x")
|
c.xbwritelinef("* OK [CLOSED] x")
|
||||||
c.unselect()
|
c.unselect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2624,23 +2624,23 @@ func (c *conn) cmdSelectExamine(isselect bool, tag, cmd string, p *parser) {
|
|||||||
if len(mb.Keywords) > 0 {
|
if len(mb.Keywords) > 0 {
|
||||||
flags = " " + strings.Join(mb.Keywords, " ")
|
flags = " " + strings.Join(mb.Keywords, " ")
|
||||||
}
|
}
|
||||||
c.bwritelinef(`* FLAGS (\Seen \Answered \Flagged \Deleted \Draft $Forwarded $Junk $NotJunk $Phishing $MDNSent%s)`, flags)
|
c.xbwritelinef(`* 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(`* OK [PERMANENTFLAGS (\Seen \Answered \Flagged \Deleted \Draft $Forwarded $Junk $NotJunk $Phishing $MDNSent \*)] x`)
|
||||||
if !c.enabled[capIMAP4rev2] {
|
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 {
|
if !c.enabled[capIMAP4rev2] && firstUnseen > 0 {
|
||||||
// ../rfc/9051:8051 ../rfc/3501:1774
|
// ../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.xbwritelinef(`* OK [UIDVALIDITY %d] x`, mb.UIDValidity)
|
||||||
c.bwritelinef(`* OK [UIDNEXT %d] x`, mb.UIDNext)
|
c.xbwritelinef(`* OK [UIDNEXT %d] x`, mb.UIDNext)
|
||||||
c.bwritelinef(`* LIST () "/" %s`, mailboxt(mb.Name).pack(c))
|
c.xbwritelinef(`* LIST () "/" %s`, mailboxt(mb.Name).pack(c))
|
||||||
if c.enabled[capCondstore] {
|
if c.enabled[capCondstore] {
|
||||||
// ../rfc/7162:417
|
// ../rfc/7162:417
|
||||||
// ../rfc/7162-eid5055 ../rfc/7162:484 ../rfc/7162:1167
|
// ../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
|
// 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
|
qrmodseq = m.ModSeq.Client() - 1
|
||||||
preVanished = 0
|
preVanished = 0
|
||||||
qrknownUIDs = nil
|
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 {
|
} else if err != bstore.ErrAbsent {
|
||||||
xcheckf(err, "checking old client uid")
|
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)
|
msgseq := c.sequence(m.UID)
|
||||||
if msgseq > 0 {
|
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
|
return nil
|
||||||
})
|
})
|
||||||
@ -2774,16 +2774,16 @@ func (c *conn) cmdSelectExamine(isselect bool, tag, cmd string, p *parser) {
|
|||||||
l := slices.Sorted(maps.Keys(vanishedUIDs))
|
l := slices.Sorted(maps.Keys(vanishedUIDs))
|
||||||
// ../rfc/7162:1985
|
// ../rfc/7162:1985
|
||||||
for _, s := range compactUIDSet(l).Strings(4*1024 - 32) {
|
for _, s := range compactUIDSet(l).Strings(4*1024 - 32) {
|
||||||
c.bwritelinef("* VANISHED (EARLIER) %s", s)
|
c.xbwritelinef("* VANISHED (EARLIER) %s", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isselect {
|
if isselect {
|
||||||
c.bwriteresultf("%s OK [READ-WRITE] x", tag)
|
c.xbwriteresultf("%s OK [READ-WRITE] x", tag)
|
||||||
c.readonly = false
|
c.readonly = false
|
||||||
} else {
|
} else {
|
||||||
c.bwriteresultf("%s OK [READ-ONLY] x", tag)
|
c.xbwriteresultf("%s OK [READ-ONLY] x", tag)
|
||||||
c.readonly = true
|
c.readonly = true
|
||||||
}
|
}
|
||||||
c.mailboxID = mb.ID
|
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/")) {
|
if c.enabled[capIMAP4rev2] && n == name && name != origName && !(name == "Inbox" || strings.HasPrefix(name, "Inbox/")) {
|
||||||
oldname = fmt.Sprintf(` ("OLDNAME" (%s))`, mailboxt(origName).pack(c))
|
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)
|
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
|
// Response syntax: ../rfc/3501:4833 ../rfc/3501:4837
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
c.bwritelinef("%s", line)
|
c.xbwritelinef("%s", line)
|
||||||
}
|
}
|
||||||
c.ok(tag, cmd)
|
c.ok(tag, cmd)
|
||||||
}
|
}
|
||||||
@ -3163,7 +3163,7 @@ func (c *conn) cmdNamespace(tag, cmd string, p *parser) {
|
|||||||
p.xempty()
|
p.xempty()
|
||||||
|
|
||||||
// Response syntax: ../rfc/9051:6778 ../rfc/2342:415
|
// Response syntax: ../rfc/9051:6778 ../rfc/2342:415
|
||||||
c.bwritelinef(`* NAMESPACE (("" "/")) NIL NIL`)
|
c.xbwritelinef(`* NAMESPACE (("" "/")) NIL NIL`)
|
||||||
c.ok(tag, cmd)
|
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)
|
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")
|
defer store.CloseRemoveTempFile(c.log, a.file, "temporary message file")
|
||||||
f = a.file
|
f = a.file
|
||||||
|
|
||||||
c.writelinef("+ ")
|
c.xwritelinef("+ ")
|
||||||
} else {
|
} else {
|
||||||
// We'll discard the message and return an error as soon as we can (possible
|
// 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).
|
// 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
|
totalSize += msize
|
||||||
|
|
||||||
line := c.readline(false)
|
line := c.xreadline(false)
|
||||||
p = newParser(line, c)
|
p = newParser(line, c)
|
||||||
if utf8 {
|
if utf8 {
|
||||||
p.xtake(")")
|
p.xtake(")")
|
||||||
@ -3524,7 +3524,7 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
|
|||||||
c.uidAppend(a.m.UID)
|
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.
|
// 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
|
// ../rfc/4315:289 ../rfc/3502:236 APPENDUID
|
||||||
@ -3535,7 +3535,7 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
|
|||||||
} else {
|
} else {
|
||||||
uidset = fmt.Sprintf("%d:%d", appends[0].m.UID, appends[len(appends)-1].m.UID)
|
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
|
// 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
|
// Request syntax: ../rfc/9051:6594 ../rfc/2177:163
|
||||||
p.xempty()
|
p.xempty()
|
||||||
|
|
||||||
c.writelinef("+ waiting")
|
c.xwritelinef("+ waiting")
|
||||||
|
|
||||||
var line string
|
var line string
|
||||||
wait:
|
wait:
|
||||||
@ -3566,7 +3566,7 @@ wait:
|
|||||||
c.xflush()
|
c.xflush()
|
||||||
case <-mox.Shutdown.Done():
|
case <-mox.Shutdown.Done():
|
||||||
// ../rfc/9051:5375
|
// ../rfc/9051:5375
|
||||||
c.writelinef("* BYE shutting down")
|
c.xwritelinef("* BYE shutting down")
|
||||||
c.xbrokenf("shutting down (%w)", errIO)
|
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.
|
// We only have one per account quota, we name it "" like the examples in the RFC.
|
||||||
// Response syntax: ../rfc/9208:668 ../rfc/2087:242
|
// 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
|
// 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.
|
// an empty list, so we cannot send the current disk usage if there is no limit.
|
||||||
if quota > 0 {
|
if quota > 0 {
|
||||||
// Response syntax: ../rfc/9208:666 ../rfc/2087:239
|
// 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)
|
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.
|
// an empty list, so we cannot send the current disk usage if there is no limit.
|
||||||
if quota > 0 {
|
if quota > 0 {
|
||||||
// Response syntax: ../rfc/9208:666 ../rfc/2087:239
|
// 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)
|
c.ok(tag, cmd)
|
||||||
}
|
}
|
||||||
@ -3830,18 +3830,18 @@ func (c *conn) cmdxExpunge(tag, cmd string, uidSet *numSet) {
|
|||||||
if qresync {
|
if qresync {
|
||||||
vanishedUIDs.append(uint32(m.UID))
|
vanishedUIDs.append(uint32(m.UID))
|
||||||
} else {
|
} else {
|
||||||
c.bwritelinef("* %d EXPUNGE", seq)
|
c.xbwritelinef("* %d EXPUNGE", seq)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !vanishedUIDs.empty() {
|
if !vanishedUIDs.empty() {
|
||||||
// VANISHED without EARLIER. ../rfc/7162:2004
|
// VANISHED without EARLIER. ../rfc/7162:2004
|
||||||
for _, s := range vanishedUIDs.Strings(4*1024 - 32) {
|
for _, s := range vanishedUIDs.Strings(4*1024 - 32) {
|
||||||
c.bwritelinef("* VANISHED %s", s)
|
c.xbwritelinef("* VANISHED %s", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.enabled[capCondstore] {
|
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 {
|
} else {
|
||||||
c.ok(tag, cmd)
|
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
|
// ../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.
|
// 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:4708 ../rfc/6851:254
|
||||||
// ../rfc/9051:4713
|
// ../rfc/9051:4713
|
||||||
newUIDs := numSet{ranges: []numRange{{setNumber{number: uint32(uidFirst)}, &setNumber{number: uint32(mbDst.UIDNext - 1)}}}}
|
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]
|
qresync := c.enabled[capQresync]
|
||||||
var vanishedUIDs numSet
|
var vanishedUIDs numSet
|
||||||
for i := range uids {
|
for i := range uids {
|
||||||
@ -4206,19 +4206,19 @@ func (c *conn) cmdxMove(isUID bool, tag, cmd string, p *parser) {
|
|||||||
if qresync {
|
if qresync {
|
||||||
vanishedUIDs.append(uint32(uids[i]))
|
vanishedUIDs.append(uint32(uids[i]))
|
||||||
} else {
|
} else {
|
||||||
c.bwritelinef("* %d EXPUNGE", seq)
|
c.xbwritelinef("* %d EXPUNGE", seq)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !vanishedUIDs.empty() {
|
if !vanishedUIDs.empty() {
|
||||||
// VANISHED without EARLIER. ../rfc/7162:2004
|
// VANISHED without EARLIER. ../rfc/7162:2004
|
||||||
for _, s := range vanishedUIDs.Strings(4*1024 - 32) {
|
for _, s := range vanishedUIDs.Strings(4*1024 - 32) {
|
||||||
c.bwritelinef("* VANISHED %s", s)
|
c.xbwritelinef("* VANISHED %s", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if qresync {
|
if qresync {
|
||||||
// ../rfc/9051:6744 ../rfc/7162:1334
|
// ../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 {
|
} else {
|
||||||
c.ok(tag, cmd)
|
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())
|
modseqStr = fmt.Sprintf(" MODSEQ (%d)", m.ModSeq.Client())
|
||||||
}
|
}
|
||||||
// ../rfc/9051:6749 ../rfc/3501:4869 ../rfc/7162:2490
|
// ../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
|
// Also gather UIDs or sequences for the MODIFIED response below. ../rfc/7162:571
|
||||||
var mnums []store.UID
|
var mnums []store.UID
|
||||||
for _, m := range changed {
|
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 {
|
if isUID {
|
||||||
mnums = append(mnums, m.UID)
|
mnums = append(mnums, m.UID)
|
||||||
} else {
|
} else {
|
||||||
@ -4599,5 +4599,5 @@ func (c *conn) cmdxStore(isUID bool, tag, cmd string, p *parser) {
|
|||||||
slices.Sort(mnums)
|
slices.Sort(mnums)
|
||||||
set := compactUIDSet(mnums)
|
set := compactUIDSet(mnums)
|
||||||
// ../rfc/7162:2506
|
// ../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())
|
||||||
}
|
}
|
||||||
|
@ -779,10 +779,10 @@ func (c *conn) Read(buf []byte) (int, error) {
|
|||||||
// Filled on demand.
|
// Filled on demand.
|
||||||
var bufpool = moxio.NewBufpool(8, 2*1024)
|
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)
|
line, err := bufpool.Readline(c.log, c.xbr)
|
||||||
if err != nil && errors.Is(err, moxio.ErrLineTooLong) {
|
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))
|
panic(fmt.Errorf("%s (%w)", err, errIO))
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
panic(fmt.Errorf("%s (%w)", err, errIO))
|
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.
|
// 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.
|
// 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
|
var ecode string
|
||||||
if secode != "" {
|
if secode != "" {
|
||||||
ecode = fmt.Sprintf("%d.%s", code/100, 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-- {
|
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.
|
// 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:]
|
line = line[e:]
|
||||||
}
|
}
|
||||||
spdash := " "
|
spdash := " "
|
||||||
if i < len(lines)-1 {
|
if i < len(lines)-1 {
|
||||||
spdash = "-"
|
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.
|
// 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...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
fmt.Fprint(c.xbw, msg+"\r\n")
|
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.
|
// 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) {
|
func (c *conn) xwritecodeline(code int, secode string, msg string, err error) {
|
||||||
c.bwritecodeline(code, secode, msg, err)
|
c.xbwritecodeline(code, secode, msg, err)
|
||||||
c.xflush()
|
c.xflush()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write (with flush) a formatted response line to connection.
|
// Write (with flush) a formatted response line to connection.
|
||||||
func (c *conn) writelinef(format string, args ...any) {
|
func (c *conn) xwritelinef(format string, args ...any) {
|
||||||
c.bwritelinef(format, args...)
|
c.xbwritelinef(format, args...)
|
||||||
c.xflush()
|
c.xflush()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -965,13 +965,13 @@ func serve(listenerName string, cid int64, hostname dns.Domain, tlsConfig *tls.C
|
|||||||
select {
|
select {
|
||||||
case <-mox.Shutdown.Done():
|
case <-mox.Shutdown.Done():
|
||||||
// ../rfc/5321:2811 ../rfc/5321:1666 ../rfc/3463:420
|
// ../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
|
return
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
if !limiterConnectionRate.Add(c.remoteIP, time.Now(), 1) {
|
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
|
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) {
|
if submission && !mox.LimiterFailedAuth.CanAdd(c.remoteIP, time.Now(), 1) {
|
||||||
metrics.AuthenticationRatelimitedInc("submission")
|
metrics.AuthenticationRatelimitedInc("submission")
|
||||||
c.log.Debug("refusing connection due to many auth failures", slog.Any("remoteip", c.remoteIP))
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !limiterConnections.Add(c.remoteIP, time.Now(), 1) {
|
if !limiterConnections.Add(c.remoteIP, time.Now(), 1) {
|
||||||
c.log.Debug("refusing connection due to many open connections", slog.Any("remoteip", c.remoteIP))
|
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
|
return
|
||||||
}
|
}
|
||||||
defer limiterConnections.Add(c.remoteIP, time.Now(), -1)
|
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.
|
// 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
|
// Should not be too relevant nowadays, but does not hurt and default blackbox
|
||||||
// exporter SMTP health check expects it.
|
// 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 {
|
for {
|
||||||
command(c)
|
command(c)
|
||||||
@ -1051,7 +1051,7 @@ func command(c *conn) {
|
|||||||
|
|
||||||
var serr smtpError
|
var serr smtpError
|
||||||
if errors.As(err, &serr) {
|
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 {
|
if serr.printStack {
|
||||||
c.log.Errorx("smtp error", serr.err, slog.Int("code", serr.code), slog.String("secode", serr.secode))
|
c.log.Errorx("smtp error", serr.err, slog.Int("code", serr.code), slog.String("secode", serr.secode))
|
||||||
debug.PrintStack()
|
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.
|
// 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)
|
t := strings.SplitN(line, " ", 2)
|
||||||
var args string
|
var args string
|
||||||
if len(t) == 2 {
|
if len(t) == 2 {
|
||||||
@ -1079,7 +1079,7 @@ func command(c *conn) {
|
|||||||
select {
|
select {
|
||||||
case <-mox.Shutdown.Done():
|
case <-mox.Shutdown.Done():
|
||||||
// ../rfc/5321:2811 ../rfc/5321:1666 ../rfc/3463:420
|
// ../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)
|
panic(errIO)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@ -1095,7 +1095,7 @@ func command(c *conn) {
|
|||||||
// Other side is likely speaking something else than SMTP, send error message and
|
// 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
|
// stop processing because there is a good chance whatever they sent has multiple
|
||||||
// lines.
|
// 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)
|
panic(errIO)
|
||||||
}
|
}
|
||||||
// note: not "command not implemented", see ../rfc/5321:2934 ../rfc/5321:2539
|
// 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
|
// https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml
|
||||||
|
|
||||||
c.bwritelinef("250-%s", c.hostname.ASCII)
|
c.xbwritelinef("250-%s", c.hostname.ASCII)
|
||||||
c.bwritelinef("250-PIPELINING") // ../rfc/2920:108
|
c.xbwritelinef("250-PIPELINING") // ../rfc/2920:108
|
||||||
c.bwritelinef("250-SIZE %d", c.maxMessageSize) // ../rfc/1870:70
|
c.xbwritelinef("250-SIZE %d", c.maxMessageSize) // ../rfc/1870:70
|
||||||
// ../rfc/3207:237
|
// ../rfc/3207:237
|
||||||
if !c.tls && c.baseTLSConfig != nil {
|
if !c.tls && c.baseTLSConfig != nil {
|
||||||
// ../rfc/3207:90
|
// ../rfc/3207:90
|
||||||
c.bwritelinef("250-STARTTLS")
|
c.xbwritelinef("250-STARTTLS")
|
||||||
} else if c.extRequireTLS {
|
} else if c.extRequireTLS {
|
||||||
// ../rfc/8689:202
|
// ../rfc/8689:202
|
||||||
// ../rfc/8689:143
|
// ../rfc/8689:143
|
||||||
c.bwritelinef("250-REQUIRETLS")
|
c.xbwritelinef("250-REQUIRETLS")
|
||||||
}
|
}
|
||||||
if c.submission {
|
if c.submission {
|
||||||
var mechs string
|
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 {
|
if c.tls && len(c.conn.(*tls.Conn).ConnectionState().PeerCertificates) > 0 && !c.viaHTTPS {
|
||||||
mechs = "EXTERNAL " + mechs
|
mechs = "EXTERNAL " + mechs
|
||||||
}
|
}
|
||||||
c.bwritelinef("250-AUTH %s", mechs)
|
c.xbwritelinef("250-AUTH %s", mechs)
|
||||||
// ../rfc/4865:127
|
// ../rfc/4865:127
|
||||||
t := time.Now().Add(queue.FutureReleaseIntervalMax).UTC() // ../rfc/4865:98
|
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")
|
// todo future? c.writelinef("250-DSN")
|
||||||
c.bwritelinef("250-8BITMIME") // ../rfc/6152:86
|
c.xbwritelinef("250-8BITMIME") // ../rfc/6152:86
|
||||||
c.bwritelinef("250-LIMITS RCPTMAX=%d", rcptToLimit) // ../rfc/9422:301
|
c.xbwritelinef("250-LIMITS RCPTMAX=%d", rcptToLimit) // ../rfc/9422:301
|
||||||
c.bwritecodeline(250, "", "SMTPUTF8", nil) // ../rfc/6531:201
|
c.xbwritecodeline(250, "", "SMTPUTF8", nil) // ../rfc/6531:201
|
||||||
c.xflush()
|
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.
|
// 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)
|
c.xtlsHandshakeAndAuthenticate(conn)
|
||||||
|
|
||||||
@ -1321,9 +1321,9 @@ func (c *conn) cmdAuth(p *parser) {
|
|||||||
xreadInitial := func(encChal string) []byte {
|
xreadInitial := func(encChal string) []byte {
|
||||||
var auth string
|
var auth string
|
||||||
if p.empty() {
|
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
|
// 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 == "*" {
|
if auth == "*" {
|
||||||
// ../rfc/4954:193
|
// ../rfc/4954:193
|
||||||
la.Result = store.AuthAborted
|
la.Result = store.AuthAborted
|
||||||
@ -1355,7 +1355,7 @@ func (c *conn) cmdAuth(p *parser) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
xreadContinuation := func() []byte {
|
xreadContinuation := func() []byte {
|
||||||
line := c.readline()
|
line := c.xreadline()
|
||||||
if line == "*" {
|
if line == "*" {
|
||||||
la.Result = store.AuthAborted
|
la.Result = store.AuthAborted
|
||||||
xsmtpUserErrorf(smtp.C501BadParamSyntax, smtp.SeProto5Other0, "authentication aborted")
|
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
|
// Again, client should ignore the challenge, we send the same as the example in
|
||||||
// the I-D.
|
// 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.
|
// Password is in line in plain text, so hide it.
|
||||||
defer c.xtrace(mlog.LevelTraceauth)()
|
defer c.xtrace(mlog.LevelTraceauth)()
|
||||||
@ -1468,7 +1468,7 @@ func (c *conn) cmdAuth(p *parser) {
|
|||||||
|
|
||||||
// ../rfc/2195:82
|
// ../rfc/2195:82
|
||||||
chal := fmt.Sprintf("<%d.%d@%s>", uint64(mox.CryptoRandInt()), time.Now().UnixNano(), mox.Conf.Static.HostnameDomain.ASCII)
|
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()
|
resp := xreadContinuation()
|
||||||
t := strings.Split(string(resp), " ")
|
t := strings.Split(string(resp), " ")
|
||||||
@ -1597,14 +1597,14 @@ func (c *conn) cmdAuth(p *parser) {
|
|||||||
})
|
})
|
||||||
s1, err := ss.ServerFirst(xscram.Iterations, xscram.Salt)
|
s1, err := ss.ServerFirst(xscram.Iterations, xscram.Salt)
|
||||||
xcheckf(err, "scram first server step")
|
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()
|
c2 := xreadContinuation()
|
||||||
s3, err := ss.Finish(c2, xscram.SaltedPassword)
|
s3, err := ss.Finish(c2, xscram.SaltedPassword)
|
||||||
if len(s3) > 0 {
|
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 {
|
if err != nil {
|
||||||
c.readline() // Should be "*" for cancellation.
|
c.xreadline() // Should be "*" for cancellation.
|
||||||
if errors.Is(err, scram.ErrInvalidProof) {
|
if errors.Is(err, scram.ErrInvalidProof) {
|
||||||
la.Result = store.AuthBadCredentials
|
la.Result = store.AuthBadCredentials
|
||||||
c.log.Info("failed authentication attempt", slog.String("username", username), slog.Any("remote", c.remoteIP))
|
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.authFailed = 0
|
||||||
c.setSlow(false)
|
c.setSlow(false)
|
||||||
// ../rfc/4954:276
|
// ../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
|
// ../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.
|
// If we get many bad transactions, it's probably a spammer that is guessing user names.
|
||||||
// Useful in combination with rate limiting.
|
// Useful in combination with rate limiting.
|
||||||
// ../rfc/5321:4349
|
// ../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)
|
panic(errIO)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1905,7 +1905,7 @@ func (c *conn) cmdMail(p *parser) {
|
|||||||
|
|
||||||
c.mailFrom = &rpath
|
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
|
// ../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))
|
c.log.Errorx("looking up account for delivery", err, slog.Any("rcptto", fpath))
|
||||||
xsmtpServerErrorf(codes{smtp.C451LocalErr, smtp.SeSys3Other0}, "error processing")
|
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 {
|
func hasNonASCII(s string) bool {
|
||||||
@ -2109,7 +2109,7 @@ func (c *conn) cmdData(p *parser) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// ../rfc/5321:1994
|
// ../rfc/5321:1994
|
||||||
c.writelinef("354 see you at the bare dot")
|
c.xwritelinef("354 see you at the bare dot")
|
||||||
|
|
||||||
// Mark as tracedata.
|
// Mark as tracedata.
|
||||||
defer c.xtrace(mlog.LevelTracedata)()
|
defer c.xtrace(mlog.LevelTracedata)()
|
||||||
@ -2131,12 +2131,12 @@ func (c *conn) cmdData(p *parser) {
|
|||||||
if n < config.DefaultMaxMsgSize {
|
if n < config.DefaultMaxMsgSize {
|
||||||
ecode = smtp.SeMailbox2MsgLimitExceeded3
|
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))
|
panic(fmt.Errorf("remote sent too much DATA: %w", errIO))
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.Is(err, smtp.ErrCRLF) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2146,7 +2146,7 @@ func (c *conn) cmdData(p *parser) {
|
|||||||
// available and our write blocks us from reading remaining data, leading to
|
// 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
|
// deadlock. We have a timeout on our connection writes though, so worst case we'll
|
||||||
// abort the connection due to expiration.
|
// 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)
|
io.Copy(io.Discard, dr)
|
||||||
return
|
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.transactionBad-- // Compensate for early earlier pessimistic increase.
|
||||||
|
|
||||||
c.rset()
|
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 {
|
func xrandomID(n int) string {
|
||||||
@ -3689,7 +3689,7 @@ func (c *conn) deliver(ctx context.Context, recvHdrFor func(string) string, msgW
|
|||||||
c.transactionGood++
|
c.transactionGood++
|
||||||
c.transactionBad-- // Compensate for early earlier pessimistic increase.
|
c.transactionBad-- // Compensate for early earlier pessimistic increase.
|
||||||
c.rset()
|
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.
|
// Return whether msgFrom address is allowed to send a message to alias.
|
||||||
@ -3735,7 +3735,7 @@ func (c *conn) cmdRset(p *parser) {
|
|||||||
p.xend()
|
p.xend()
|
||||||
|
|
||||||
c.rset()
|
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
|
// ../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.
|
// Let's not strictly parse the request for help. We are ignoring the text anyway.
|
||||||
// ../rfc/5321:2166
|
// ../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
|
// ../rfc/5321:2191
|
||||||
@ -3793,7 +3793,7 @@ func (c *conn) cmdNoop(p *parser) {
|
|||||||
}
|
}
|
||||||
p.xend()
|
p.xend()
|
||||||
|
|
||||||
c.bwritecodeline(smtp.C250Completed, smtp.SeOther00, "alrighty", nil)
|
c.xbwritecodeline(smtp.C250Completed, smtp.SeOther00, "alrighty", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ../rfc/5321:2205
|
// ../rfc/5321:2205
|
||||||
@ -3801,6 +3801,6 @@ func (c *conn) cmdQuit(p *parser) {
|
|||||||
// ../rfc/5321:2226
|
// ../rfc/5321:2226
|
||||||
p.xend()
|
p.xend()
|
||||||
|
|
||||||
c.writecodeline(smtp.C221Closing, smtp.SeOther00, "okay thanks bye", nil)
|
c.xwritecodeline(smtp.C221Closing, smtp.SeOther00, "okay thanks bye", nil)
|
||||||
panic(cleanClose)
|
panic(cleanClose)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user