diff --git a/go.mod b/go.mod index 8d249ec43a..3334661b2c 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/go-chi/chi/v5 v5.3.1 github.com/go-chi/render v1.0.3 github.com/go-jose/go-jose/v3 v3.0.5 - github.com/go-ldap/ldap/v3 v3.4.13 + github.com/go-ldap/ldap/v3 v3.4.14 github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3 github.com/go-micro/plugins/v4/client/grpc v1.2.1 github.com/go-micro/plugins/v4/logger/zerolog v1.2.0 @@ -203,7 +203,7 @@ require ( github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/gdexlab/go-render v1.0.1 // indirect github.com/go-acme/lego/v4 v4.4.0 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.9.0 // indirect github.com/go-git/go-git/v5 v5.19.1 // indirect diff --git a/go.sum b/go.sum index e497b4c827..605cf4761b 100644 --- a/go.sum +++ b/go.sum @@ -370,8 +370,8 @@ github.com/go-acme/lego/v4 v4.4.0 h1:uHhU5LpOYQOdp3aDU+XY2bajseu8fuExphTL1Ss6/Fc github.com/go-acme/lego/v4 v4.4.0/go.mod h1:l3+tFUFZb590dWcqhWZegynUthtaHJbG2fevUpoOOE0= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.4.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= -github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-asn1-ber/asn1-ber v1.5.8 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ= +github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8= github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= @@ -402,8 +402,8 @@ github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBj github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-ldap/ldap/v3 v3.1.7/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= -github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ= -github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0= +github.com/go-ldap/ldap/v3 v3.4.14 h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs= +github.com/go-ldap/ldap/v3 v3.4.14/go.mod h1:S4eJUMUNjDkE0ZJtIZdybwyb03sGGLW6gxXT1Hs8VKA= github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3 h1:sfz1YppV05y4sYaW7kXZtrocU/+vimnIWt4cxAYh7+o= github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3/go.mod h1:ZXFhGda43Z2TVbfGZefXyMJzsDHhCh0go3bZUcwTx7o= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= diff --git a/vendor/github.com/go-asn1-ber/asn1-ber/ber.go b/vendor/github.com/go-asn1-ber/asn1-ber/ber.go index f27229ed60..f6e3e0d576 100644 --- a/vendor/github.com/go-asn1-ber/asn1-ber/ber.go +++ b/vendor/github.com/go-asn1-ber/asn1-ber/ber.go @@ -19,6 +19,10 @@ import ( // no limit. var MaxPacketLengthBytes int64 = math.MaxInt32 +// MaxNestingDepth specifies the maximum allowed nesting depth when calling ReadPacket, DecodePacket, or +// DecodePacketErr. Set to 0 for no limit. +var MaxNestingDepth int = 1000 + type Packet struct { Identifier Value interface{} @@ -218,7 +222,7 @@ func printPacket(out io.Writer, p *Packet, indent int, printBytes bool) { // ReadPacket reads a single Packet from the reader. func ReadPacket(reader io.Reader) (*Packet, error) { - p, _, err := readPacket(reader) + p, _, err := readPacket(reader, 0) if err != nil { return nil, err } @@ -278,7 +282,7 @@ func int64Length(i int64) (numBytes int) { // DecodePacket decodes the given bytes into a single Packet // If a decode error is encountered, nil is returned. func DecodePacket(data []byte) *Packet { - p, _, _ := readPacket(bytes.NewBuffer(data)) + p, _, _ := readPacket(bytes.NewBuffer(data), 0) return p } @@ -286,7 +290,7 @@ func DecodePacket(data []byte) *Packet { // DecodePacketErr decodes the given bytes into a single Packet // If a decode error is encountered, nil is returned. func DecodePacketErr(data []byte) (*Packet, error) { - p, _, err := readPacket(bytes.NewBuffer(data)) + p, _, err := readPacket(bytes.NewBuffer(data), 0) if err != nil { return nil, err } @@ -294,12 +298,20 @@ func DecodePacketErr(data []byte) (*Packet, error) { } // readPacket reads a single Packet from the reader, returning the number of bytes read. -func readPacket(reader io.Reader) (*Packet, int, error) { +func readPacket(reader io.Reader, depth int) (*Packet, int, error) { + if MaxNestingDepth > 0 && depth >= MaxNestingDepth { + return nil, 0, fmt.Errorf("nesting depth %d exceeds maximum %d", depth, MaxNestingDepth) + } + identifier, length, read, err := readHeader(reader) if err != nil { return nil, read, err } + if length != LengthIndefinite && MaxPacketLengthBytes > 0 && int64(length) > MaxPacketLengthBytes { + return nil, read, fmt.Errorf("length %d greater than maximum %d", length, MaxPacketLengthBytes) + } + p := &Packet{ Identifier: identifier, } @@ -326,13 +338,19 @@ func readPacket(reader io.Reader) (*Packet, int, error) { } // Read the next packet - child, r, err := readPacket(reader) + child, r, err := readPacket(reader, depth+1) if err != nil { return nil, read, unexpectedEOF(err) } contentRead += r read += r + // Enforce the aggregate size limit for constructed packets. Indefinite length declares + // no bound up front, so the content bytes are only known as they are read. + if MaxPacketLengthBytes > 0 && int64(contentRead) > MaxPacketLengthBytes { + return nil, read, fmt.Errorf("length %d greater than maximum %d", contentRead, MaxPacketLengthBytes) + } + // Test is this is the EOC marker for our packet if isEOCPacket(child) { if length == LengthIndefinite { @@ -351,11 +369,6 @@ func readPacket(reader io.Reader) (*Packet, int, error) { return nil, read, errors.New("indefinite length used with primitive type") } - // Read definite-length content - if MaxPacketLengthBytes > 0 && int64(length) > MaxPacketLengthBytes { - return nil, read, fmt.Errorf("length %d greater than maximum %d", length, MaxPacketLengthBytes) - } - var content []byte if length > 0 { // Read the content and limit it to the parsed length. diff --git a/vendor/github.com/go-asn1-ber/asn1-ber/length.go b/vendor/github.com/go-asn1-ber/asn1-ber/length.go index 2c81cc3fd2..fc533f0f85 100644 --- a/vendor/github.com/go-asn1-ber/asn1-ber/length.go +++ b/vendor/github.com/go-asn1-ber/asn1-ber/length.go @@ -40,7 +40,7 @@ func readLength(reader io.Reader) (length int, read int, err error) { } // Accumulate into a 64-bit variable - var length64 int64 + var length64 uint64 for i := 0; i < lengthBytes; i++ { b, err = readByte(reader) if err != nil { @@ -53,13 +53,14 @@ func readLength(reader io.Reader) (length int, read int, err error) { // x.600, 8.1.3.5 length64 <<= 8 - length64 |= int64(b) + length64 |= uint64(b) } // Cast to a platform-specific integer length = int(length64) - // Ensure we didn't overflow - if int64(length) != length64 { + // Ensure we didn't overflow or wrap negative. Length octets are unsigned + // (x.600, 8.1.3.5), so a negative result is unrepresentable, not indefinite. + if length < 0 || uint64(length) != length64 { return 0, read, errors.New("long-form length overflow") } diff --git a/vendor/github.com/go-ldap/ldap/v3/add.go b/vendor/github.com/go-ldap/ldap/v3/add.go index 6d8854e083..ca4329b3c9 100644 --- a/vendor/github.com/go-ldap/ldap/v3/add.go +++ b/vendor/github.com/go-ldap/ldap/v3/add.go @@ -83,6 +83,9 @@ func (l *Conn) Add(addRequest *AddRequest) error { return err } + if len(packet.Children) < 2 { + return fmt.Errorf("ldap: malformed response: expected at least 2 children, got %d", len(packet.Children)) + } if packet.Children[1].Tag == ApplicationAddResponse { err := GetLDAPError(packet) if err != nil { diff --git a/vendor/github.com/go-ldap/ldap/v3/bind.go b/vendor/github.com/go-ldap/ldap/v3/bind.go index 6cfd37ebeb..c3ee84e316 100644 --- a/vendor/github.com/go-ldap/ldap/v3/bind.go +++ b/vendor/github.com/go-ldap/ldap/v3/bind.go @@ -3,13 +3,13 @@ package ldap import ( "bytes" "crypto/md5" + "crypto/rand" "encoding/binary" "encoding/hex" enchex "encoding/hex" "errors" "fmt" "io/ioutil" - "math/rand" "strings" "unicode/utf16" @@ -221,12 +221,15 @@ func (l *Conn) DigestMD5Bind(digestMD5BindRequest *DigestMD5BindRequest) (*Diges } if len(params) > 0 { - resp := computeResponse( + resp, err := computeResponse( params, "ldap/"+strings.ToLower(digestMD5BindRequest.Host), digestMD5BindRequest.Username, digestMD5BindRequest.Password, ) + if err != nil { + return nil, fmt.Errorf("compute digest-md5 response: %s", err) + } packet = ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID")) @@ -291,12 +294,22 @@ func parseParams(str string) (map[string]string, error) { m := make(map[string]string) var key, value string var state int + var escaped bool for i := 0; i <= len(str); i++ { switch state { case 0: // reading key if i == len(str) { return nil, fmt.Errorf("syntax error on %d", i) } + // The digest-challenge is an RFC 2068 #rule (RFC 2831 section 2.1.1), + // which permits optional linear whitespace around the comma directive + // separators. Directive names are tokens that never contain + // whitespace, so skip it here; otherwise a directive following + // "..., name" is keyed with a leading space and the lookups in + // computeResponse (realm, nonce, authzid) miss it. + if str[i] == ' ' || str[i] == '\t' { + continue + } if str[i] != '=' { key += string(str[i]) continue @@ -307,6 +320,14 @@ func parseParams(str string) (map[string]string, error) { m[key] = value break } + // Linear whitespace outside a quoted string is not part of the + // value: an unquoted value is a token and a quoted value's content + // is read in the quoted state below. Skipping it lets a challenge + // using the whitespace the #rule allows (e.g. `nonce="n" , qop=auth`) + // parse the same as the unspaced form. + if str[i] == ' ' || str[i] == '\t' { + continue + } switch str[i] { case ',': m[key] = value @@ -325,20 +346,34 @@ func parseParams(str string) (map[string]string, error) { if i == len(str) { return nil, fmt.Errorf("syntax error on %d", i) } - if str[i] != '"' { + switch { + case escaped: + // RFC 2831 section 7.1 quoted-pair: a backslash escapes the + // following character, so the next byte is taken literally + // (this is how a server sends a literal " or \ in a realm or + // nonce). value += string(str[i]) - } else { + escaped = false + case str[i] == '\\': + escaped = true + case str[i] == '"': state = 1 + default: + value += string(str[i]) } } } return m, nil } -func computeResponse(params map[string]string, uri, username, password string) string { +func computeResponse(params map[string]string, uri, username, password string) (string, error) { nc := "00000001" qop := "auth" - cnonce := enchex.EncodeToString(randomBytes(16)) + rb, err := randomBytes(16) + if err != nil { + return "", err + } + cnonce := enchex.EncodeToString(rb) x := username + ":" + params["realm"] + ":" + password y := md5Hash([]byte(x)) @@ -361,14 +396,24 @@ func computeResponse(params map[string]string, uri, username, password string) s resp := enchex.EncodeToString(md5Hash([]byte(kd))) return fmt.Sprintf( `username="%s",realm="%s",nonce="%s",cnonce="%s",nc=00000001,qop=%s,digest-uri="%s",response=%s`, - username, - params["realm"], - params["nonce"], + quotedStringEscape(username), + quotedStringEscape(params["realm"]), + quotedStringEscape(params["nonce"]), cnonce, qop, - uri, + quotedStringEscape(uri), resp, - ) + ), nil +} + +// quotedStringEscape escapes the two characters that may not appear unescaped +// inside a DIGEST-MD5 quoted string per RFC 2831 section 7.1: the backslash +// and the double quote. The backslash is replaced first so the quotes escaped +// afterwards are not doubled. +func quotedStringEscape(s string) string { + s = strings.ReplaceAll(s, `\`, `\\`) + s = strings.ReplaceAll(s, `"`, `\"`) + return s } func md5Hash(b []byte) []byte { @@ -377,12 +422,12 @@ func md5Hash(b []byte) []byte { return hasher.Sum(nil) } -func randomBytes(len int) []byte { - b := make([]byte, len) - for i := 0; i < len; i++ { - b[i] = byte(rand.Intn(256)) +func randomBytes(length int) ([]byte, error) { + b := make([]byte, length) + if _, err := rand.Read(b); err != nil { + return nil, err } - return b + return b, nil } var externalBindRequest = requestFunc(func(envelope *ber.Packet) error { diff --git a/vendor/github.com/go-ldap/ldap/v3/compare.go b/vendor/github.com/go-ldap/ldap/v3/compare.go index a1cd760b34..4ce669e059 100644 --- a/vendor/github.com/go-ldap/ldap/v3/compare.go +++ b/vendor/github.com/go-ldap/ldap/v3/compare.go @@ -46,6 +46,9 @@ func (l *Conn) Compare(dn, attribute, value string) (bool, error) { return false, err } + if len(packet.Children) < 2 { + return false, fmt.Errorf("ldap: malformed response: expected at least 2 children, got %d", len(packet.Children)) + } if packet.Children[1].Tag == ApplicationCompareResponse { err := GetLDAPError(packet) diff --git a/vendor/github.com/go-ldap/ldap/v3/conn.go b/vendor/github.com/go-ldap/ldap/v3/conn.go index 05febbcaf7..3c3e67e5da 100644 --- a/vendor/github.com/go-ldap/ldap/v3/conn.go +++ b/vendor/github.com/go-ldap/ldap/v3/conn.go @@ -8,6 +8,7 @@ import ( "fmt" "net" "net/url" + "strings" "sync" "sync/atomic" "time" @@ -112,7 +113,11 @@ type Conn struct { outstandingRequests uint messageMutex sync.Mutex - err error + // errMutex guards err only. It is a leaf lock: processMessages and reader + // record errors while another goroutine may hold messageMutex, so err must + // not share messageMutex or those writers could deadlock. + errMutex sync.Mutex + err error } var _ Client = &Conn{} @@ -160,10 +165,18 @@ type DialContext struct { func (dc *DialContext) dial(u *url.URL) (net.Conn, error) { if u.Scheme == "ldapi" { - if u.Path == "" || u.Path == "/" { - u.Path = "/var/run/slapd/ldapi" + // RFC 4516 (and draft-chu-ldap-ldapi) put the socket path in the + // host component, percent-encoded; the path is an optional DN. + // parseLDAPURL has already decoded the host. Accept the older + // ldapi:///path form too so existing callers keep working. + path := u.Host + if path == "" { + path = u.Path } - return dc.dialer.Dial("unix", u.Path) + if path == "" || path == "/" { + path = "/var/run/slapd/ldapi" + } + return dc.dialer.Dial("unix", path) } host, port, err := net.SplitHostPort(u.Host) @@ -222,12 +235,33 @@ func DialTLS(network, addr string, config *tls.Config) (*Conn, error) { return conn, nil } +// parseLDAPURL parses an LDAP URL. It defers to net/url for the common +// ldap/ldaps/cldap schemes, but handles ldapi specially: the spec puts the +// unix socket path in the host, percent-encoded with %2F, and net/url rejects +// that as invalid. Pull the host out manually and decode it. +func parseLDAPURL(addr string) (*url.URL, error) { + const ldapi = "ldapi://" + if !strings.HasPrefix(addr, ldapi) { + return url.Parse(addr) + } + rest := addr[len(ldapi):] + host, path := rest, "" + if i := strings.IndexByte(rest, '/'); i >= 0 { + host, path = rest[:i], rest[i:] + } + decodedHost, err := url.PathUnescape(host) + if err != nil { + return nil, fmt.Errorf("ldapi: invalid host %q: %w", host, err) + } + return &url.URL{Scheme: "ldapi", Host: decodedHost, Path: path}, nil +} + // DialURL connects to the given ldap URL. // The following schemas are supported: ldap://, ldaps://, ldapi://, // and cldap:// (RFC1798, deprecated but used by Active Directory). // On success a new Conn for the connection is returned. func DialURL(addr string, opts ...DialOpt) (*Conn, error) { - u, err := url.Parse(addr) + u, err := parseLDAPURL(addr) if err != nil { return nil, NewError(ErrorNetwork, err) } @@ -338,11 +372,21 @@ func (l *Conn) nextMessageID() int64 { // GetLastError returns the last recorded error from goroutines like processMessages and reader. // Only the last recorded error will be returned. func (l *Conn) GetLastError() error { - l.messageMutex.Lock() - defer l.messageMutex.Unlock() + l.errMutex.Lock() + defer l.errMutex.Unlock() return l.err } +// setError records the connection's last error. The background goroutines that +// call it (processMessages, reader, the per-request timeout helper and the +// SearchAsync worker) run concurrently with callers of GetLastError, so the +// write must take the mutex the getter reads under. +func (l *Conn) setError(err error) { + l.errMutex.Lock() + defer l.errMutex.Unlock() + l.err = err +} + // StartTLS sends the command to start a TLS session and then creates a new TLS Client func (l *Conn) StartTLS(config *tls.Config) error { if l.isTLS { @@ -491,7 +535,7 @@ func (l *Conn) sendProcessMessage(message *messagePacket) bool { func (l *Conn) processMessages() { defer func() { if err := recover(); err != nil { - l.err = fmt.Errorf("ldap: recovered panic in processMessages: %v", err) + l.setError(fmt.Errorf("ldap: recovered panic in processMessages: %v", err)) } for messageID, msgCtx := range l.messageContexts { // If we are closing due to an error, inform anyone who @@ -541,7 +585,7 @@ func (l *Conn) processMessages() { timer := time.NewTimer(time.Duration(requestTimeout)) defer func() { if err := recover(); err != nil { - l.err = fmt.Errorf("ldap: recovered panic in RequestTimeout: %v", err) + l.setError(fmt.Errorf("ldap: recovered panic in RequestTimeout: %v", err)) } timer.Stop() @@ -563,7 +607,7 @@ func (l *Conn) processMessages() { if msgCtx, ok := l.messageContexts[message.MessageID]; ok { msgCtx.sendResponse(&PacketResponse{message.Packet, nil}, time.Duration(l.getTimeout())) } else { - l.err = fmt.Errorf("ldap: received unexpected message %d, %v", message.MessageID, l.IsClosing()) + l.setError(fmt.Errorf("ldap: received unexpected message %d, %v", message.MessageID, l.IsClosing())) l.Debug.PrintPacket(message.Packet) } case MessageTimeout: @@ -590,7 +634,7 @@ func (l *Conn) reader() { cleanstop := false defer func() { if err := recover(); err != nil { - l.err = fmt.Errorf("ldap: recovered panic in reader: %v", err) + l.setError(fmt.Errorf("ldap: recovered panic in reader: %v", err)) } if !cleanstop { l.Close() diff --git a/vendor/github.com/go-ldap/ldap/v3/control.go b/vendor/github.com/go-ldap/ldap/v3/control.go index 1f93b38025..bfaf67aa6e 100644 --- a/vendor/github.com/go-ldap/ldap/v3/control.go +++ b/vendor/github.com/go-ldap/ldap/v3/control.go @@ -565,12 +565,20 @@ func DecodeControl(packet *ber.Packet) (Control, error) { case 1: // just type, no criticality or value packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")" - ControlType = packet.Children[0].Value.(string) + ct, ok := packet.Children[0].Value.(string) + if !ok { + return nil, fmt.Errorf("control type is not a string: %T", packet.Children[0].Value) + } + ControlType = ct case 2: packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")" if packet.Children[0].Value != nil { - ControlType = packet.Children[0].Value.(string) + ct, ok := packet.Children[0].Value.(string) + if !ok { + return nil, fmt.Errorf("control type is not a string: %T", packet.Children[0].Value) + } + ControlType = ct } else if packet.Children[0].Data != nil { ControlType = packet.Children[0].Data.String() } else { @@ -579,9 +587,9 @@ func DecodeControl(packet *ber.Packet) (Control, error) { // Children[1] could be criticality or value (both are optional) // duck-type on whether this is a boolean - if _, ok := packet.Children[1].Value.(bool); ok { + if crit, ok := packet.Children[1].Value.(bool); ok { packet.Children[1].Description = "Criticality" - Criticality = packet.Children[1].Value.(bool) + Criticality = crit } else { packet.Children[1].Description = "Control Value" value = packet.Children[1] @@ -589,10 +597,18 @@ func DecodeControl(packet *ber.Packet) (Control, error) { case 3: packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")" - ControlType = packet.Children[0].Value.(string) + ct, ok := packet.Children[0].Value.(string) + if !ok { + return nil, fmt.Errorf("control type is not a string: %T", packet.Children[0].Value) + } + ControlType = ct packet.Children[1].Description = "Criticality" - Criticality = packet.Children[1].Value.(bool) + crit, ok := packet.Children[1].Value.(bool) + if !ok { + return nil, fmt.Errorf("criticality is not a bool: %T", packet.Children[1].Value) + } + Criticality = crit packet.Children[2].Description = "Control Value" value = packet.Children[2] @@ -606,6 +622,9 @@ func DecodeControl(packet *ber.Packet) (Control, error) { case ControlTypeManageDsaIT: return NewControlManageDsaIT(Criticality), nil case ControlTypePaging: + if value == nil { + return nil, fmt.Errorf("paging control value is missing") + } value.Description += " (Paging)" c := new(ControlPaging) if value.Value != nil { @@ -617,11 +636,21 @@ func DecodeControl(packet *ber.Packet) (Control, error) { value.Value = nil value.AppendChild(valueChildren) } + if len(value.Children) == 0 { + return nil, fmt.Errorf("paging control value is empty") + } value = value.Children[0] value.Description = "Search Control Value" + if len(value.Children) < 2 { + return nil, fmt.Errorf("paging control value has %d children, expected 2", len(value.Children)) + } value.Children[0].Description = "Paging Size" value.Children[1].Description = "Cookie" - c.PagingSize = uint32(value.Children[0].Value.(int64)) + pagingSize, ok := value.Children[0].Value.(int64) + if !ok { + return nil, fmt.Errorf("paging size is not an integer: %T", value.Children[0].Value) + } + c.PagingSize = uint32(pagingSize) c.Cookie = value.Children[1].Data.Bytes() value.Children[1].Value = c.Cookie return c, nil @@ -729,7 +758,16 @@ func DecodeControl(packet *ber.Packet) (Control, error) { c.ControlType = ControlType c.Criticality = Criticality if value != nil { - c.ControlValue = value.Value.(string) + // A non-conforming or malicious server can send a non-string + // (or nil) value here; the previous unchecked cast panicked + // the calling goroutine, see #561. Fall back to the raw bytes + // when the value isn't a string so we surface an error + // instead of crashing. + if s, ok := value.Value.(string); ok { + c.ControlValue = s + } else if value.Data != nil { + c.ControlValue = value.Data.String() + } } return c, nil } @@ -921,28 +959,44 @@ func (c *ControlServerSideSorting) GetControlType() string { } func NewControlServerSideSorting(value *ber.Packet) (*ControlServerSideSorting, error) { - sortKeys := []*SortKey{} + val, err := ber.DecodePacketErr(value.Data.Bytes()) + if err != nil { + return nil, fmt.Errorf("decode packet err: %s", err) + } - val := value.Children[1].Children - - if len(val) != 1 { + if len(val.Children) == 0 { return nil, fmt.Errorf("no sequence value in packet") } - sequences := val[0].Children + var sortKeys []*SortKey - for i, sequence := range sequences { - sortKey := new(SortKey) - - if len(sequence.Children) < 2 { - return nil, fmt.Errorf("attributeType or matchingRule is missing from sequence %d", i) + for i, sequence := range val.Children { + if len(sequence.Children) < 1 || len(sequence.Children) > 3 { + return nil, fmt.Errorf("attributeType is missing from sequence %d", i) } - sortKey.AttributeType = sequence.Children[0].Value.(string) - sortKey.MatchingRule = sequence.Children[1].Value.(string) + sortKey := new(SortKey) - if len(sequence.Children) == 3 { - sortKey.Reverse = sequence.Children[2].Value.(bool) + for _, child := range sequence.Children { + switch { + case child.ClassType == ber.ClassUniversal && child.Tag == ber.TagOctetString: + // A constructed-form OCTET STRING matches this case but leaves + // Value nil; guard the assertion so a malformed attributeType is + // rejected below rather than panicking. + if attrType, ok := child.Value.(string); ok { + sortKey.AttributeType = attrType + } + + case child.ClassType == ber.ClassContext && child.Tag == 0: + sortKey.MatchingRule = child.Data.String() + + case child.ClassType == ber.ClassContext && child.Tag == 1: + b := child.Data.Bytes() + sortKey.Reverse = len(b) > 0 && b[0] != 0 + } + } + if sortKey.AttributeType == "" { + return nil, fmt.Errorf("attributeType is missing from sequence %d", i) } sortKeys = append(sortKeys, sortKey) @@ -959,7 +1013,6 @@ func (c *ControlServerSideSorting) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") control := ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.GetControlType(), "Control Type") - value := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value") seqs := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "SortKeyList") for _, f := range c.SortKeys { @@ -968,9 +1021,11 @@ func (c *ControlServerSideSorting) Encode() *ber.Packet { seq.AppendChild( ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, f.AttributeType, "attributeType"), ) - seq.AppendChild( - ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, f.MatchingRule, "orderingRule"), - ) + if f.MatchingRule != "" { + seq.AppendChild( + ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, f.MatchingRule, "orderingRule"), + ) + } if f.Reverse { seq.AppendChild( ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, 1, f.Reverse, "reverseOrder"), @@ -980,7 +1035,7 @@ func (c *ControlServerSideSorting) Encode() *ber.Packet { seqs.AppendChild(seq) } - value.AppendChild(seqs) + value := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, string(seqs.Bytes()), "Control Value") packet.AppendChild(control) packet.AppendChild(value) @@ -1060,6 +1115,8 @@ func NewControlServerSideSortingResult(pkt *ber.Packet) (*ControlServerSideSorti return nil, err } + control.Result = ControlServerSideSortingCode(codeInt) + return control, nil } diff --git a/vendor/github.com/go-ldap/ldap/v3/del.go b/vendor/github.com/go-ldap/ldap/v3/del.go index cb7a683f5a..076e6c5195 100644 --- a/vendor/github.com/go-ldap/ldap/v3/del.go +++ b/vendor/github.com/go-ldap/ldap/v3/del.go @@ -52,6 +52,9 @@ func (l *Conn) Del(delRequest *DelRequest) error { return err } + if len(packet.Children) < 2 { + return fmt.Errorf("ldap: malformed response: expected at least 2 children, got %d", len(packet.Children)) + } if packet.Children[1].Tag == ApplicationDelResponse { err := GetLDAPError(packet) if err != nil { diff --git a/vendor/github.com/go-ldap/ldap/v3/dn.go b/vendor/github.com/go-ldap/ldap/v3/dn.go index 6520b8ea90..a124486476 100644 --- a/vendor/github.com/go-ldap/ldap/v3/dn.go +++ b/vendor/github.com/go-ldap/ldap/v3/dn.go @@ -1,14 +1,16 @@ package ldap import ( + "bytes" "encoding/hex" "errors" "fmt" - ber "github.com/go-asn1-ber/asn1-ber" "sort" "strings" "unicode" "unicode/utf8" + + ber "github.com/go-asn1-ber/asn1-ber" ) // AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514 @@ -94,9 +96,21 @@ func (d *DN) String() string { func stripLeadingAndTrailingSpaces(inVal string) string { noSpaces := strings.Trim(inVal, " ") - // Re-add the trailing space if it was an escaped space - if len(noSpaces) > 0 && noSpaces[len(noSpaces)-1] == '\\' && inVal[len(inVal)-1] == ' ' { - noSpaces = noSpaces + " " + // Re-add the trailing space only if it was escaped. A trailing space is + // escaped when it is preceded by an odd number of backslashes; an even + // number leaves the space unescaped (each "\\" is a literal backslash), so + // the space is insignificant and stays stripped. Counting only the final + // backslash treated "\\ " (a literal backslash plus an insignificant + // space) as an escaped space, keeping a spurious trailing space in the + // decoded value. + if len(noSpaces) > 0 && inVal[len(inVal)-1] == ' ' { + backslashes := 0 + for i := len(noSpaces) - 1; i >= 0 && noSpaces[i] == '\\'; i-- { + backslashes++ + } + if backslashes%2 == 1 { + noSpaces = noSpaces + " " + } } return noSpaces @@ -116,6 +130,16 @@ func decodeString(str string) (string, error) { // If the character is not an escape character, just add it to the // builder and continue if char != '\\' { + // RFC 4514 section 2.4: these characters must appear escaped + // (either as "\X" or as "\XX" hex) when present in an AttributeValue. + // Reject the raw form here so that callers don't silently accept + // input that violates the grammar. + switch char { + case '"', ';', '<', '>': + return "", fmt.Errorf("got unescaped character: '%s'", string(char)) + case 0: + return "", fmt.Errorf("got unescaped NULL character") + } builder.WriteRune(char) continue } @@ -233,10 +257,18 @@ func decodeEncodedString(str string) (string, error) { return "", fmt.Errorf("failed to decode BER encoding: %w", err) } - packet, err := ber.DecodePacketErr(decoded) + // RFC 4514 section 2.4: the value following '#' is the hex encoding of the + // BER encoding of a single AttributeValue. Read exactly one element and + // reject any leftover octets, otherwise bytes appended after the value are + // silently dropped and two different DN strings decode to the same value. + reader := bytes.NewBuffer(decoded) + packet, err := ber.ReadPacket(reader) if err != nil { return "", fmt.Errorf("failed to decode BER encoding: %w", err) } + if reader.Len() != 0 { + return "", errors.New("failed to decode BER encoding: trailing bytes after value") + } return packet.Data.String(), nil } @@ -352,10 +384,19 @@ func (r *RelativeDN) Equal(other *RelativeDN) bool { } func (r *RelativeDN) hasAllAttributes(attrs []*AttributeTypeAndValue) bool { + // Each candidate attribute must match a distinct attribute of the receiver. + // Without consuming matches this is a set containment test, so a multi-valued + // RDN that repeats an attributeTypeAndValue would compare equal to one that + // repeats a different pair the same number of times. + matched := make([]bool, len(r.Attributes)) for _, attr := range attrs { found := false - for _, myattr := range r.Attributes { + for i, myattr := range r.Attributes { + if matched[i] { + continue + } if myattr.Equal(attr) { + matched[i] = true found = true break } @@ -415,10 +456,16 @@ func (r *RelativeDN) EqualFold(other *RelativeDN) bool { } func (r *RelativeDN) hasAllAttributesFold(attrs []*AttributeTypeAndValue) bool { + // See hasAllAttributes: matches are consumed so multiplicity is respected. + matched := make([]bool, len(r.Attributes)) for _, attr := range attrs { found := false - for _, myattr := range r.Attributes { + for i, myattr := range r.Attributes { + if matched[i] { + continue + } if myattr.EqualFold(attr) { + matched[i] = true found = true break } diff --git a/vendor/github.com/go-ldap/ldap/v3/extended.go b/vendor/github.com/go-ldap/ldap/v3/extended.go index 84cffbeea0..3533e4fd5e 100644 --- a/vendor/github.com/go-ldap/ldap/v3/extended.go +++ b/vendor/github.com/go-ldap/ldap/v3/extended.go @@ -89,6 +89,14 @@ func (l *Conn) Extended(er *ExtendedRequest) (*ExtendedResponse, error) { } for _, child := range extResp.Children { + // responseName [10] and responseValue [11] are context-class and + // optional. The preceding resultCode is a universal ENUMERATED whose + // tag number (10) is the same as responseName, so a child must be + // matched on its class as well, otherwise the resultCode is read as + // the responseName whenever the server omits the latter. + if child.ClassType != ber.ClassContext { + continue + } switch child.Tag { case ber.TagEnumerated: response.Name = child.Data.String() diff --git a/vendor/github.com/go-ldap/ldap/v3/filter.go b/vendor/github.com/go-ldap/ldap/v3/filter.go index db76210c10..b5e89b1e53 100644 --- a/vendor/github.com/go-ldap/ldap/v3/filter.go +++ b/vendor/github.com/go-ldap/ldap/v3/filter.go @@ -131,7 +131,7 @@ func DecompileFilter(packet *ber.Packet) (_ string, err error) { buf.WriteString(childStr) case FilterSubstrings: - buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes())) + buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[0].Data.Bytes()))) buf.WriteByte('=') for i, child := range packet.Children[1].Children { if i == 0 && child.Tag != FilterSubstringsInitial { @@ -143,22 +143,22 @@ func DecompileFilter(packet *ber.Packet) (_ string, err error) { } } case FilterEqualityMatch: - buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes())) + buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[0].Data.Bytes()))) buf.WriteByte('=') buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))) case FilterGreaterOrEqual: - buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes())) + buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[0].Data.Bytes()))) buf.WriteString(">=") buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))) case FilterLessOrEqual: - buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes())) + buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[0].Data.Bytes()))) buf.WriteString("<=") buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))) case FilterPresent: - buf.WriteString(ber.DecodeString(packet.Data.Bytes())) + buf.WriteString(EscapeFilter(ber.DecodeString(packet.Data.Bytes()))) buf.WriteString("=*") case FilterApproxMatch: - buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes())) + buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[0].Data.Bytes()))) buf.WriteString("~=") buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))) case FilterExtensibleMatch: @@ -181,14 +181,14 @@ func DecompileFilter(packet *ber.Packet) (_ string, err error) { } if len(attr) > 0 { - buf.WriteString(attr) + buf.WriteString(EscapeFilter(attr)) } if dnAttributes { buf.WriteString(":dn") } if len(matchingRule) > 0 { buf.WriteString(":") - buf.WriteString(matchingRule) + buf.WriteString(EscapeFilter(matchingRule)) } buf.WriteString(":=") buf.WriteString(EscapeFilter(value)) diff --git a/vendor/github.com/go-ldap/ldap/v3/moddn.go b/vendor/github.com/go-ldap/ldap/v3/moddn.go index 84a6488e42..92acea0bbe 100644 --- a/vendor/github.com/go-ldap/ldap/v3/moddn.go +++ b/vendor/github.com/go-ldap/ldap/v3/moddn.go @@ -89,6 +89,9 @@ func (l *Conn) ModifyDN(m *ModifyDNRequest) error { return err } + if len(packet.Children) < 2 { + return fmt.Errorf("ldap: malformed response: expected at least 2 children, got %d", len(packet.Children)) + } if packet.Children[1].Tag == ApplicationModifyDNResponse { err := GetLDAPError(packet) if err != nil { diff --git a/vendor/github.com/go-ldap/ldap/v3/modify.go b/vendor/github.com/go-ldap/ldap/v3/modify.go index 0e5013601f..663be5fd6f 100644 --- a/vendor/github.com/go-ldap/ldap/v3/modify.go +++ b/vendor/github.com/go-ldap/ldap/v3/modify.go @@ -121,6 +121,9 @@ func (l *Conn) Modify(modifyRequest *ModifyRequest) error { return err } + if len(packet.Children) < 2 { + return fmt.Errorf("ldap: malformed response: expected at least 2 children, got %d", len(packet.Children)) + } if packet.Children[1].Tag == ApplicationModifyResponse { err := GetLDAPError(packet) if err != nil { @@ -159,6 +162,10 @@ func (l *Conn) ModifyWithResult(modifyRequest *ModifyRequest) (*ModifyResult, er return nil, err } + if len(packet.Children) < 2 { + return nil, fmt.Errorf("ldap: malformed response: expected at least 2 children, got %d", len(packet.Children)) + } + switch packet.Children[1].Tag { case ApplicationModifyResponse: if err = GetLDAPError(packet); err != nil { diff --git a/vendor/github.com/go-ldap/ldap/v3/passwdmodify.go b/vendor/github.com/go-ldap/ldap/v3/passwdmodify.go index 72a2351a24..6ab177e26e 100644 --- a/vendor/github.com/go-ldap/ldap/v3/passwdmodify.go +++ b/vendor/github.com/go-ldap/ldap/v3/passwdmodify.go @@ -93,6 +93,9 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa result := &PasswordModifyResult{} + if len(packet.Children) < 2 { + return nil, fmt.Errorf("ldap: malformed response: expected at least 2 children, got %d", len(packet.Children)) + } if packet.Children[1].Tag == ApplicationExtendedResponse { if err = GetLDAPError(packet); err != nil { result.Referral = getReferral(err, packet) @@ -106,7 +109,10 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa extendedResponse := packet.Children[1] for _, child := range extendedResponse.Children { if child.Tag == ber.TagEmbeddedPDV { - passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes()) + passwordModifyResponseValue, err := ber.DecodePacketErr(child.Data.Bytes()) + if err != nil { + return nil, fmt.Errorf("ldap: failed to decode PasswordModifyResponseValue: %s", err) + } if len(passwordModifyResponseValue.Children) == 1 { if passwordModifyResponseValue.Children[0].Tag == ber.TagEOC { result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes()) diff --git a/vendor/github.com/go-ldap/ldap/v3/request.go b/vendor/github.com/go-ldap/ldap/v3/request.go index b64f232dc3..f6dca73204 100644 --- a/vendor/github.com/go-ldap/ldap/v3/request.go +++ b/vendor/github.com/go-ldap/ldap/v3/request.go @@ -101,6 +101,14 @@ func getReferral(err error, packet *ber.Packet) (referral string) { continue } + // A Referral is a SEQUENCE SIZE (1..MAX) OF uri, but a non-conforming or + // malicious server can send an empty SEQUENCE. Skip it instead of indexing + // child.Children[0], which would panic the goroutine that called Modify or + // PasswordModify. + if len(child.Children) == 0 { + continue + } + if referral, ok = child.Children[0].Value.(string); ok { return referral } diff --git a/vendor/github.com/go-ldap/ldap/v3/response.go b/vendor/github.com/go-ldap/ldap/v3/response.go index 0eae10019b..d795d72693 100644 --- a/vendor/github.com/go-ldap/ldap/v3/response.go +++ b/vendor/github.com/go-ldap/ldap/v3/response.go @@ -66,12 +66,28 @@ func (r *searchResponse) Next() bool { return true } +// send enqueues a result on the result channel, giving up when ctx is +// cancelled so an abandoned consumer cannot block the search goroutine +// forever on a full buffer. It reports whether the result was handed off to +// the channel; a true return does not mean the consumer received it. The +// give-up is best-effort: if ctx is already cancelled but buffer space is +// available, the result may still be enqueued. Callers that terminate the +// stream regardless of the outcome may ignore the return value. +func (r *searchResponse) send(ctx context.Context, res *SearchSingleResult) bool { + select { + case r.ch <- res: + return true + case <-ctx.Done(): + return false + } +} + func (r *searchResponse) start(ctx context.Context, searchRequest *SearchRequest) { go func() { defer func() { close(r.ch) if err := recover(); err != nil { - r.conn.err = fmt.Errorf("ldap: recovered panic in searchResponse: %v", err) + r.conn.setError(fmt.Errorf("ldap: recovered panic in searchResponse: %v", err)) } }() @@ -84,14 +100,14 @@ func (r *searchResponse) start(ctx context.Context, searchRequest *SearchRequest // encode search request err := searchRequest.appendTo(packet) if err != nil { - r.ch <- &SearchSingleResult{Error: err} + r.send(ctx, &SearchSingleResult{Error: err}) return } r.conn.Debug.PrintPacket(packet) msgCtx, err := r.conn.sendMessage(packet) if err != nil { - r.ch <- &SearchSingleResult{Error: err} + r.send(ctx, &SearchSingleResult{Error: err}) return } defer r.conn.finishMessage(msgCtx) @@ -106,19 +122,19 @@ func (r *searchResponse) start(ctx context.Context, searchRequest *SearchRequest case packetResponse, ok := <-msgCtx.responses: if !ok { err := NewError(ErrorNetwork, errors.New("ldap: response channel closed")) - r.ch <- &SearchSingleResult{Error: err} + r.send(ctx, &SearchSingleResult{Error: err}) return } packet, err = packetResponse.ReadPacket() r.conn.Debug.Printf("%d: got response %p", msgCtx.id, packet) if err != nil { - r.ch <- &SearchSingleResult{Error: err} + r.send(ctx, &SearchSingleResult{Error: err}) return } if r.conn.Debug { if err := addLDAPDescriptions(packet); err != nil { - r.ch <- &SearchSingleResult{Error: err} + r.send(ctx, &SearchSingleResult{Error: err}) return } ber.PrintPacket(packet) @@ -133,22 +149,26 @@ func (r *searchResponse) start(ctx context.Context, searchRequest *SearchRequest }, } if len(packet.Children) != 3 { - r.ch <- result + if !r.send(ctx, result) { + return + } continue } decoded, err := DecodeControl(packet.Children[2].Children[0]) if err != nil { werr := fmt.Errorf("failed to decode search result entry: %w", err) result.Error = werr - r.ch <- result + r.send(ctx, result) return } result.Controls = append(result.Controls, decoded) - r.ch <- result + if !r.send(ctx, result) { + return + } case ApplicationSearchResultDone: if err := GetLDAPError(packet); err != nil { - r.ch <- &SearchSingleResult{Error: err} + r.send(ctx, &SearchSingleResult{Error: err}) return } if len(packet.Children) == 3 { @@ -157,33 +177,37 @@ func (r *searchResponse) start(ctx context.Context, searchRequest *SearchRequest decodedChild, err := DecodeControl(child) if err != nil { werr := fmt.Errorf("failed to decode child control: %w", err) - r.ch <- &SearchSingleResult{Error: werr} + r.send(ctx, &SearchSingleResult{Error: werr}) return } result.Controls = append(result.Controls, decodedChild) } - r.ch <- result + r.send(ctx, result) } foundSearchSingleResultDone = true case ApplicationSearchResultReference: ref := packet.Children[1].Children[0].Value.(string) - r.ch <- &SearchSingleResult{Referral: ref} + if !r.send(ctx, &SearchSingleResult{Referral: ref}) { + return + } case ApplicationIntermediateResponse: decoded, err := DecodeControl(packet.Children[1]) if err != nil { werr := fmt.Errorf("failed to decode intermediate response: %w", err) - r.ch <- &SearchSingleResult{Error: werr} + r.send(ctx, &SearchSingleResult{Error: werr}) return } result := &SearchSingleResult{} result.Controls = append(result.Controls, decoded) - r.ch <- result + if !r.send(ctx, result) { + return + } default: err := fmt.Errorf("unknown tag: %d", packet.Children[1].Tag) - r.ch <- &SearchSingleResult{Error: err} + r.send(ctx, &SearchSingleResult{Error: err}) return } } diff --git a/vendor/github.com/go-ldap/ldap/v3/search.go b/vendor/github.com/go-ldap/ldap/v3/search.go index e1c684e12b..c0f19e34fd 100644 --- a/vendor/github.com/go-ldap/ldap/v3/search.go +++ b/vendor/github.com/go-ldap/ldap/v3/search.go @@ -624,7 +624,10 @@ func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) { // SearchAsync performs a search request and returns all search results asynchronously. // This means you get all results until an error happens (or the search successfully finished), // e.g. for size / time limited requests all are received until the limit is reached. -// To stop the search, call cancel function of the context. +// To stop the search, call the cancel function of the context; Next may +// still deliver a few results received before the cancellation took effect. +// Cancellation is not reported as an error: Err returns nil, same as a +// successfully completed search. func (l *Conn) SearchAsync( ctx context.Context, searchRequest *SearchRequest, bufferSize int) Response { r := newSearchResponse(l, bufferSize) @@ -635,7 +638,8 @@ func (l *Conn) SearchAsync( // Syncrepl is a short name for LDAP Sync Replication engine that works on the // consumer-side. This can perform a persistent search and returns an entry // when the entry is updated on the server side. -// To stop the search, call cancel function of the context. +// To stop the search, call the cancel function of the context; cancellation +// is not reported as an error, Err returns nil. func (l *Conn) Syncrepl( ctx context.Context, searchRequest *SearchRequest, bufferSize int, mode ControlSyncRequestMode, cookie []byte, reloadHint bool, diff --git a/vendor/github.com/go-ldap/ldap/v3/unbind.go b/vendor/github.com/go-ldap/ldap/v3/unbind.go index 10cf75c679..6111fb1f39 100644 --- a/vendor/github.com/go-ldap/ldap/v3/unbind.go +++ b/vendor/github.com/go-ldap/ldap/v3/unbind.go @@ -24,11 +24,18 @@ func (l *Conn) Unbind() error { return ErrConnUnbound } - _, err := l.doRequest(unbindRequest{}) + msgCtx, err := l.doRequest(unbindRequest{}) if err != nil { return err } + // Finish the message context so its done channel is closed. Without + // this, a server-initiated disconnect racing with Close can deadlock: + // processMessages cleanup tries to deliver closeErr to the orphaned + // context via sendResponse, which blocks forever on the unclosed done + // channel, preventing chanConfirm from being signalled. + l.finishMessage(msgCtx) + // Sending an unbindRequest will make the connection unusable. // Pending requests will fail with: // LDAP Result Code 200 "Network Error": ldap: response channel closed diff --git a/vendor/github.com/go-ldap/ldap/v3/whoami.go b/vendor/github.com/go-ldap/ldap/v3/whoami.go index 0d743d2243..cecb50f016 100644 --- a/vendor/github.com/go-ldap/ldap/v3/whoami.go +++ b/vendor/github.com/go-ldap/ldap/v3/whoami.go @@ -19,5 +19,11 @@ func (l *Conn) WhoAmI(controls []Control) (*WhoAmIResult, error) { return nil, err } - return &WhoAmIResult{AuthzID: resp.Value.Data.String()}, nil + // responseValue is OPTIONAL (RFC 4532); Extended leaves Value nil when the + // server omits it. Guard the dereference and report an empty authzId. + result := &WhoAmIResult{} + if resp.Value != nil { + result.AuthzID = resp.Value.Data.String() + } + return result, nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index f6c29fa8b9..34862c0e50 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -451,7 +451,7 @@ github.com/ggwhite/go-masker ## explicit; go 1.15 github.com/go-acme/lego/v4/acme github.com/go-acme/lego/v4/challenge -# github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 +# github.com/go-asn1-ber/asn1-ber v1.5.8 ## explicit; go 1.13 github.com/go-asn1-ber/asn1-ber # github.com/go-chi/chi/v5 v5.3.1 @@ -539,8 +539,8 @@ github.com/go-jose/go-jose/v4/json ## explicit; go 1.17 github.com/go-kit/log github.com/go-kit/log/level -# github.com/go-ldap/ldap/v3 v3.4.13 -## explicit; go 1.24.0 +# github.com/go-ldap/ldap/v3 v3.4.14 +## explicit; go 1.25.0 github.com/go-ldap/ldap/v3 # github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3 ## explicit; go 1.14