package dovecot import ( "bytes" "context" "encoding/json" "errors" "log" "git.autistici.org/ai3/go-common/unix" ) var ( failResponse = []byte{'F', '\n'} noMatchResponse = []byte{'N', '\n'} ) // DictDatabase is an interface to a key/value store by way of the Lookup // method. type DictDatabase interface { // Lookup a key. The result boolean is the presence flag, to // avoid having to check the object for nullness. Errors // result in failure being propagated upstream to Dovecot. Lookup(context.Context, string) (interface{}, bool, error) } // DictProxyServer exposes a Database using the Dovecot dict proxy // protocol (see https://wiki2.dovecot.org/AuthDatabase/Dict). // // It implements the unix.LineHandler interface from the // ai3/go-common/unix package. type DictProxyServer struct { db DictDatabase } // NewDictProxyServer creates a new DictProxyServer. func NewDictProxyServer(db DictDatabase) *DictProxyServer { return &DictProxyServer{db: db} } // ServeLine handles a single command. func (p *DictProxyServer) ServeLine(ctx context.Context, lw unix.LineResponseWriter, line []byte) error { if len(line) < 1 { return errors.New("line too short") } switch line[0] { case 'H': return p.handleHello(ctx, lw, line[1:]) case 'L': return p.handleLookup(ctx, lw, line[1:]) default: return lw.WriteLine(failResponse) } } func (p *DictProxyServer) handleHello(ctx context.Context, lw unix.LineResponseWriter, arg []byte) error { // TODO: parse the hello line and extract useful information. return nil } func (p *DictProxyServer) handleLookup(ctx context.Context, lw unix.LineResponseWriter, arg []byte) error { obj, ok, err := p.db.Lookup(ctx, string(arg)) if err != nil { log.Printf("error: %v", err) return lw.WriteLine(failResponse) } if !ok { return lw.WriteLine(noMatchResponse) } var buf bytes.Buffer buf.Write([]byte{'O'}) if err := json.NewEncoder(&buf).Encode(obj); err != nil { return err } // json Encode will already add a newline. //buf.Write([]byte{'\n'}) return lw.WriteLine(buf.Bytes()) }