Skip to content
Snippets Groups Projects
Commit 0ee05344 authored by ale's avatar ale
Browse files

Add support for version 3 of the Dovecot dict protocol

parent 35135745
No related branches found
No related tags found
1 merge request!43Add support for version 3 of the Dovecot dict protocol
Pipeline #57971 passed
......@@ -17,7 +17,10 @@ var (
noMatchResponse = []byte{'N', '\n'}
)
const supportedDictProtocolVersion = 2
const (
supportedDictProtocolVersionMin = 2
supportedDictProtocolVersionMax = 3
)
// DictDatabase is an interface to a key/value store by way of the Lookup
// method.
......@@ -29,7 +32,7 @@ type DictDatabase interface {
}
// DictProxyServer exposes a Database using the Dovecot dict proxy
// protocol (see https://wiki2.dovecot.org/AuthDatabase/Dict).
// protocol (see https://doc.dovecot.org/developer_manual/design/dict_protocol/).
//
// It implements the unix.LineHandler interface from the
// ai3/go-common/unix package.
......@@ -59,13 +62,13 @@ func (p *DictProxyServer) ServeLine(ctx context.Context, lw unix.LineResponseWri
}
func (p *DictProxyServer) handleHello(ctx context.Context, lw unix.LineResponseWriter, arg []byte) error {
fields := bytes.Split(arg, []byte{'\t'})
fields := splitFields(arg)
if len(fields) < 1 {
return errors.New("could not parse HELLO")
}
majorVersion, _ := strconv.Atoi(string(fields[0]))
if majorVersion != supportedDictProtocolVersion {
if majorVersion < supportedDictProtocolVersionMin || majorVersion > supportedDictProtocolVersionMax {
return fmt.Errorf("unsupported protocol version %d", majorVersion)
}
......@@ -73,7 +76,15 @@ func (p *DictProxyServer) handleHello(ctx context.Context, lw unix.LineResponseW
}
func (p *DictProxyServer) handleLookup(ctx context.Context, lw unix.LineResponseWriter, arg []byte) error {
obj, ok, err := p.db.Lookup(ctx, string(arg))
// Support protocol versions 2 and 3 by looking for the \t
// field separator, which should not appear in the key in
// version 2 anyway.
fields := splitFields(arg)
if len(fields) < 1 {
return errors.New("could not parse LOOKUP")
}
obj, ok, err := p.db.Lookup(ctx, string(fields[0]))
if err != nil {
log.Printf("error: %v", err)
return lw.WriteLine(failResponse)
......@@ -91,3 +102,41 @@ func (p *DictProxyServer) handleLookup(ctx context.Context, lw unix.LineResponse
//buf.Write([]byte{'\n'})
return lw.WriteLine(buf.Bytes())
}
var dovecotEscapeChars = map[byte]byte{
'0': 0,
'1': 1,
't': '\t',
'r': '\r',
'l': '\n',
}
var fieldSeparator = []byte{'\t'}
func unescapeInplace(b []byte) []byte {
var esc bool
var j int
for i := 0; i < len(b); i++ {
c := b[i]
if esc {
if escC, ok := dovecotEscapeChars[c]; ok {
c = escC
}
esc = false
} else if c == '\001' {
esc = true
continue
}
b[j] = c
j++
}
return b[:j]
}
func splitFields(b []byte) [][]byte {
fields := bytes.Split(b, fieldSeparator)
for i := 0; i < len(fields); i++ {
fields[i] = unescapeInplace(fields[i])
}
return fields
}
package dovecot
import (
"testing"
)
func TestUnescape(t *testing.T) {
for _, td := range []struct {
input, exp string
}{
{"boo", "boo"},
{"bo\001t", "bo\t"},
{"bo\001t\001l", "bo\t\n"},
{"bo\001t\0011", "bo\t\001"},
} {
out := make([]byte, len(td.input))
copy(out, td.input)
out = unescapeInplace(out)
if string(out) != td.exp {
t.Errorf("unescape('%s') returned '%s', expected '%s'", td.input, out, td.exp)
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment