package ldap import ( "errors" "io" "net" "testing" "time" "github.com/vjeantet/goldap/message" ) // TestUnroutedSearchTerminatesOverTheWire is the end-to-end // regression test for the hang reported in the field: a search whose // base DN matches no route (for example the empty base an LDAP client // uses when no -b flag is given) must still be answered with a // searchResDone, otherwise the client waits for one forever. func TestUnroutedSearchTerminatesOverTheWire(t *testing.T) { s := New(WithLogger(testLogger()), WithNetAuth(&fakeClient{})) if err := s.SetDomain("example.com"); err != nil { t.Fatalf("SetDomain: %s", err) } addr := freeAddr(t) if err := s.Serve(addr); err != nil { t.Fatalf("Serve: %s", err) } t.Cleanup(func() { s.Listener.Close() }) conn, err := net.DialTimeout("tcp", addr, 2*time.Second) if err != nil { t.Fatalf("dial: %s", err) } t.Cleanup(func() { conn.Close() }) // A search with an empty base DN and subtree scope, exactly what // an LDAP client sends when no -b flag is given. It matches no // route and must be answered, not silently dropped. conn.SetWriteDeadline(time.Now().Add(2 * time.Second)) if _, err := conn.Write(berSearchRequest("")); err != nil { t.Fatalf("sending search: %s", err) } // Read responses until a searchResDone arrives or the deadline // passes; a client would block here forever if none is sent. deadline := time.Now().Add(5 * time.Second) for time.Now().Before(deadline) { conn.SetReadDeadline(deadline) raw, err := readLDAPPacket(conn) if err != nil { t.Fatalf("reading response: %s", err) } m, err := message.ReadLDAPMessage(message.NewBytes(0, raw)) if err != nil { t.Fatalf("decoding response: %s", err) } if _, ok := m.ProtocolOp().(message.SearchResultDone); ok { return } } t.Error("no searchResDone received; an unrouted search hangs the client") } // readLDAPPacket reads one BER TLV (the top-level LDAPMessage // SEQUENCE) and returns its full encoding. func readLDAPPacket(r io.Reader) ([]byte, error) { head := make([]byte, 2) if _, err := io.ReadFull(r, head); err != nil { return nil, err } packet := head length := int(head[1]) if head[1]&0x80 != 0 { nb := int(head[1] & 0x7f) if nb == 0 || nb > 4 { return nil, errors.New("unsupported length encoding") } ext := make([]byte, nb) if _, err := io.ReadFull(r, ext); err != nil { return nil, err } length = 0 for _, b := range ext { length = length<<8 | int(b) } packet = append(packet, ext...) } body := make([]byte, length) if _, err := io.ReadFull(r, body); err != nil { return nil, err } return append(packet, body...), nil }