pax_global_header00006660000000000000000000000064141065416020014511gustar00rootroot0000000000000052 comment=f61ea45f3b2b60217901f6110d5fef3193812062 ldap-3.4.1/000077500000000000000000000000001410654160200124365ustar00rootroot00000000000000ldap-3.4.1/.githooks/000077500000000000000000000000001410654160200143435ustar00rootroot00000000000000ldap-3.4.1/.githooks/pre-push000077500000000000000000000002101410654160200160250ustar00rootroot00000000000000#!/usr/bin/env bash # install from the root of the repo with: # ln -s ../../.githooks/pre-push .git/hooks/pre-push make vet fmt lintldap-3.4.1/.github/000077500000000000000000000000001410654160200137765ustar00rootroot00000000000000ldap-3.4.1/.github/workflows/000077500000000000000000000000001410654160200160335ustar00rootroot00000000000000ldap-3.4.1/.github/workflows/pr.yml000066400000000000000000000034731410654160200172060ustar00rootroot00000000000000name: PR on: pull_request: branches: [ master ] jobs: gomod: runs-on: ubuntu-latest strategy: matrix: go: [ '1.16', '1.15', '1.14', '1.13', ] branch: [ '.', './v3' ] name: Go ${{ matrix.go }}.x PR Validate ${{ matrix.branch }} (Modules) steps: - name: Checkout code uses: actions/checkout@v2 - name: Setup Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} - name: Version run: go version - name: Build and Validate run: | cd ${{ matrix.branch }} go vet . go test . go test -cover -race -cpu 1,2,4 . go build . golegacy: runs-on: ubuntu-latest strategy: matrix: go: [ '1.10', '1.9', '1.8', '1.7', ] name: Go ${{ matrix.go }}.x PR Validate (Legacy) env: GOPATH: /home/runner/go LDAP_PACKAGE: github.com/go-ldap/ldap steps: - name: Checkout code uses: actions/checkout@v2 with: fetch-depth: 1 path: go/src/github.com/go-ldap/ldap - name: Setup Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} - name: Version run: go version - name: Build and Validate run: | go get -v -u ${LDAP_PACKAGE}/... go vet ${LDAP_PACKAGE} go test ${LDAP_PACKAGE} go test -cover -race -cpu 1,2,4 ${LDAP_PACKAGE} go build ${LDAP_PACKAGE} lint: name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: version: v1.33 only-new-issues: true ldap-3.4.1/.gitignore000066400000000000000000000000001410654160200144140ustar00rootroot00000000000000ldap-3.4.1/CONTRIBUTING.md000066400000000000000000000007461410654160200146760ustar00rootroot00000000000000# Contribution Guidelines We welcome contribution and improvements. ## Guiding Principles To begin with here is a draft from an email exchange: * take compatibility seriously (our semvers, compatibility with older go versions, etc) * don't tag untested code for release * beware of baking in implicit behavior based on other libraries/tools choices * be as high-fidelity as possible in plumbing through LDAP data (don't mask errors or reduce power of someone using the library) ldap-3.4.1/LICENSE000066400000000000000000000022031410654160200134400ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com) Portions copyright (c) 2015-2016 go-ldap Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ldap-3.4.1/Makefile000066400000000000000000000034501410654160200141000ustar00rootroot00000000000000.PHONY: default install build test quicktest fmt vet lint # List of all release tags "supported" by our current Go version # E.g. ":go1.1:go1.2:go1.3:go1.4:go1.5:go1.6:go1.7:go1.8:go1.9:go1.10:go1.11:go1.12:" GO_RELEASE_TAGS := $(shell go list -f ':{{join (context.ReleaseTags) ":"}}:' runtime) # Only use the `-race` flag on newer versions of Go (version 1.3 and newer) ifeq (,$(findstring :go1.3:,$(GO_RELEASE_TAGS))) RACE_FLAG := else RACE_FLAG := -race -cpu 1,2,4 endif # Run `go vet` on Go 1.12 and newer. For Go 1.5-1.11, use `go tool vet` ifneq (,$(findstring :go1.12:,$(GO_RELEASE_TAGS))) GO_VET := go vet \ -atomic \ -bool \ -copylocks \ -nilfunc \ -printf \ -rangeloops \ -unreachable \ -unsafeptr \ -unusedresult \ . else ifneq (,$(findstring :go1.5:,$(GO_RELEASE_TAGS))) GO_VET := go tool vet \ -atomic \ -bool \ -copylocks \ -nilfunc \ -printf \ -shadow \ -rangeloops \ -unreachable \ -unsafeptr \ -unusedresult \ . else GO_VET := @echo "go vet skipped -- not supported on this version of Go" endif default: fmt vet lint build quicktest install: go get -t -v ./... build: go build -v ./... test: go test -v $(RACE_FLAG) -cover ./... quicktest: go test ./... # Capture output and force failure when there is non-empty output fmt: @echo gofmt -l . @OUTPUT=`gofmt -l . 2>&1`; \ if [ "$$OUTPUT" ]; then \ echo "gofmt must be run on the following files:"; \ echo "$$OUTPUT"; \ exit 1; \ fi vet: $(GO_VET) # https://github.com/golang/lint # go get github.com/golang/lint/golint # Capture output and force failure when there is non-empty output # Only run on go1.5+ lint: @echo golint ./... @OUTPUT=`command -v golint >/dev/null 2>&1 && golint ./... 2>&1`; \ if [ "$$OUTPUT" ]; then \ echo "golint errors:"; \ echo "$$OUTPUT"; \ exit 1; \ fi ldap-3.4.1/README.md000066400000000000000000000043321410654160200137170ustar00rootroot00000000000000[![GoDoc](https://godoc.org/github.com/go-ldap/ldap?status.svg)](https://godoc.org/github.com/go-ldap/ldap) [![Build Status](https://travis-ci.org/go-ldap/ldap.svg)](https://travis-ci.org/go-ldap/ldap) # Basic LDAP v3 functionality for the GO programming language. The library implements the following specifications: - https://tools.ietf.org/html/rfc4511 for basic operations - https://tools.ietf.org/html/rfc3062 for password modify operation - https://tools.ietf.org/html/rfc4514 for distinguished names parsing ## Features: - Connecting to LDAP server (non-TLS, TLS, STARTTLS) - Binding to LDAP server - Searching for entries - Filter Compile / Decompile - Paging Search Results - Modify Requests / Responses - Add Requests / Responses - Delete Requests / Responses - Modify DN Requests / Responses ## Go Modules: `go get github.com/go-ldap/ldap/v3` As go-ldap was v2+ when Go Modules came out, updating to Go Modules would be considered a breaking change. To maintain backwards compatability, we ultimately decided to use subfolders (as v3 was already a branch). Whilst this duplicates the code, we can move toward implementing a backwards-compatible versioning system that allows for code reuse. The alternative would be to increment the version number, however we believe that this would confuse users as v3 is in line with LDAPv3 (RFC-4511) https://tools.ietf.org/html/rfc4511 For more info, please visit the pull request that updated to modules. https://github.com/go-ldap/ldap/pull/247 To install with `GOMODULE111=off`, use `go get github.com/go-ldap/ldap` https://golang.org/cmd/go/#hdr-Legacy_GOPATH_go_get As always, we are looking for contributors with great ideas on how to best move forward. ## Contributing: Bug reports and pull requests are welcome! Before submitting a pull request, please make sure tests and verification scripts pass: ``` make all ``` To set up a pre-push hook to run the tests and verify scripts before pushing: ``` ln -s ../../.githooks/pre-push .git/hooks/pre-push ``` --- The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/) The design is licensed under the Creative Commons 3.0 Attributions license. Read this article for more details: http://blog.golang.org/gopher ldap-3.4.1/add.go000066400000000000000000000050331410654160200135160ustar00rootroot00000000000000package ldap import ( "log" ber "github.com/go-asn1-ber/asn1-ber" ) // Attribute represents an LDAP attribute type Attribute struct { // Type is the name of the LDAP attribute Type string // Vals are the LDAP attribute values Vals []string } func (a *Attribute) encode() *ber.Packet { seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attribute") seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, a.Type, "Type")) set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue") for _, value := range a.Vals { set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals")) } seq.AppendChild(set) return seq } // AddRequest represents an LDAP AddRequest operation type AddRequest struct { // DN identifies the entry being added DN string // Attributes list the attributes of the new entry Attributes []Attribute // Controls hold optional controls to send with the request Controls []Control } func (req *AddRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationAddRequest, nil, "Add Request") pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN")) attributes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes") for _, attribute := range req.Attributes { attributes.AppendChild(attribute.encode()) } pkt.AppendChild(attributes) envelope.AppendChild(pkt) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // Attribute adds an attribute with the given type and values func (req *AddRequest) Attribute(attrType string, attrVals []string) { req.Attributes = append(req.Attributes, Attribute{Type: attrType, Vals: attrVals}) } // NewAddRequest returns an AddRequest for the given DN, with no attributes func NewAddRequest(dn string, controls []Control) *AddRequest { return &AddRequest{ DN: dn, Controls: controls, } } // Add performs the given AddRequest func (l *Conn) Add(addRequest *AddRequest) error { msgCtx, err := l.doRequest(addRequest) if err != nil { return err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return err } if packet.Children[1].Tag == ApplicationAddResponse { err := GetLDAPError(packet) if err != nil { return err } } else { log.Printf("Unexpected Response: %d", packet.Children[1].Tag) } return nil } ldap-3.4.1/bind.go000066400000000000000000000415511410654160200137070ustar00rootroot00000000000000package ldap import ( "bytes" "crypto/md5" enchex "encoding/hex" "errors" "fmt" "io/ioutil" "math/rand" "strings" "github.com/Azure/go-ntlmssp" ber "github.com/go-asn1-ber/asn1-ber" ) // SimpleBindRequest represents a username/password bind operation type SimpleBindRequest struct { // Username is the name of the Directory object that the client wishes to bind as Username string // Password is the credentials to bind with Password string // Controls are optional controls to send with the bind request Controls []Control // AllowEmptyPassword sets whether the client allows binding with an empty password // (normally used for unauthenticated bind). AllowEmptyPassword bool } // SimpleBindResult contains the response from the server type SimpleBindResult struct { Controls []Control } // NewSimpleBindRequest returns a bind request func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest { return &SimpleBindRequest{ Username: username, Password: password, Controls: controls, AllowEmptyPassword: false, } } func (req *SimpleBindRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.Username, "User Name")) pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, req.Password, "Password")) envelope.AppendChild(pkt) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // SimpleBind performs the simple bind operation defined in the given request func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) { if simpleBindRequest.Password == "" && !simpleBindRequest.AllowEmptyPassword { return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client")) } msgCtx, err := l.doRequest(simpleBindRequest) if err != nil { return nil, err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return nil, err } result := &SimpleBindResult{ Controls: make([]Control, 0), } if len(packet.Children) == 3 { for _, child := range packet.Children[2].Children { decodedChild, decodeErr := DecodeControl(child) if decodeErr != nil { return nil, fmt.Errorf("failed to decode child control: %s", decodeErr) } result.Controls = append(result.Controls, decodedChild) } } err = GetLDAPError(packet) return result, err } // Bind performs a bind with the given username and password. // // It does not allow unauthenticated bind (i.e. empty password). Use the UnauthenticatedBind method // for that. func (l *Conn) Bind(username, password string) error { req := &SimpleBindRequest{ Username: username, Password: password, AllowEmptyPassword: false, } _, err := l.SimpleBind(req) return err } // UnauthenticatedBind performs an unauthenticated bind. // // A username may be provided for trace (e.g. logging) purpose only, but it is normally not // authenticated or otherwise validated by the LDAP server. // // See https://tools.ietf.org/html/rfc4513#section-5.1.2 . // See https://tools.ietf.org/html/rfc4513#section-6.3.1 . func (l *Conn) UnauthenticatedBind(username string) error { req := &SimpleBindRequest{ Username: username, Password: "", AllowEmptyPassword: true, } _, err := l.SimpleBind(req) return err } // DigestMD5BindRequest represents a digest-md5 bind operation type DigestMD5BindRequest struct { Host string // Username is the name of the Directory object that the client wishes to bind as Username string // Password is the credentials to bind with Password string // Controls are optional controls to send with the bind request Controls []Control } func (req *DigestMD5BindRequest) appendTo(envelope *ber.Packet) error { request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication") auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "DIGEST-MD5", "SASL Mech")) request.AppendChild(auth) envelope.AppendChild(request) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // DigestMD5BindResult contains the response from the server type DigestMD5BindResult struct { Controls []Control } // MD5Bind performs a digest-md5 bind with the given host, username and password. func (l *Conn) MD5Bind(host, username, password string) error { req := &DigestMD5BindRequest{ Host: host, Username: username, Password: password, } _, err := l.DigestMD5Bind(req) return err } // DigestMD5Bind performs the digest-md5 bind operation defined in the given request func (l *Conn) DigestMD5Bind(digestMD5BindRequest *DigestMD5BindRequest) (*DigestMD5BindResult, error) { if digestMD5BindRequest.Password == "" { return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client")) } msgCtx, err := l.doRequest(digestMD5BindRequest) if err != nil { return nil, err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return nil, err } l.Debug.Printf("%d: got response %p", msgCtx.id, packet) if l.Debug { if err = addLDAPDescriptions(packet); err != nil { return nil, err } ber.PrintPacket(packet) } result := &DigestMD5BindResult{ Controls: make([]Control, 0), } var params map[string]string if len(packet.Children) == 2 { if len(packet.Children[1].Children) == 4 { child := packet.Children[1].Children[0] if child.Tag != ber.TagEnumerated { return result, GetLDAPError(packet) } if child.Value.(int64) != 14 { return result, GetLDAPError(packet) } child = packet.Children[1].Children[3] if child.Tag != ber.TagObjectDescriptor { return result, GetLDAPError(packet) } if child.Data == nil { return result, GetLDAPError(packet) } data, _ := ioutil.ReadAll(child.Data) params, err = parseParams(string(data)) if err != nil { return result, fmt.Errorf("parsing digest-challenge: %s", err) } } } if params != nil { resp := computeResponse( params, "ldap/"+strings.ToLower(digestMD5BindRequest.Host), digestMD5BindRequest.Username, digestMD5BindRequest.Password, ) 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")) request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication") auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "DIGEST-MD5", "SASL Mech")) auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, resp, "Credentials")) request.AppendChild(auth) packet.AppendChild(request) msgCtx, err = l.sendMessage(packet) if err != nil { return nil, fmt.Errorf("send message: %s", err) } defer l.finishMessage(msgCtx) packetResponse, ok := <-msgCtx.responses if !ok { return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed")) } packet, err = packetResponse.ReadPacket() l.Debug.Printf("%d: got response %p", msgCtx.id, packet) if err != nil { return nil, fmt.Errorf("read packet: %s", err) } } err = GetLDAPError(packet) return result, err } func parseParams(str string) (map[string]string, error) { m := make(map[string]string) var key, value string var state int 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) } if str[i] != '=' { key += string(str[i]) continue } state = 1 case 1: //reading value if i == len(str) { m[key] = value break } switch str[i] { case ',': m[key] = value state = 0 key = "" value = "" case '"': if value != "" { return nil, fmt.Errorf("syntax error on %d", i) } state = 2 default: value += string(str[i]) } case 2: //inside quotes if i == len(str) { return nil, fmt.Errorf("syntax error on %d", i) } if str[i] != '"' { value += string(str[i]) } else { state = 1 } } } return m, nil } func computeResponse(params map[string]string, uri, username, password string) string { nc := "00000001" qop := "auth" cnonce := enchex.EncodeToString(randomBytes(16)) x := username + ":" + params["realm"] + ":" + password y := md5Hash([]byte(x)) a1 := bytes.NewBuffer(y) a1.WriteString(":" + params["nonce"] + ":" + cnonce) if len(params["authzid"]) > 0 { a1.WriteString(":" + params["authzid"]) } a2 := bytes.NewBuffer([]byte("AUTHENTICATE")) a2.WriteString(":" + uri) ha1 := enchex.EncodeToString(md5Hash(a1.Bytes())) ha2 := enchex.EncodeToString(md5Hash(a2.Bytes())) kd := ha1 kd += ":" + params["nonce"] kd += ":" + nc kd += ":" + cnonce kd += ":" + qop kd += ":" + ha2 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"], cnonce, qop, uri, resp, ) } func md5Hash(b []byte) []byte { hasher := md5.New() hasher.Write(b) 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)) } return b } var externalBindRequest = requestFunc(func(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) saslAuth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication") saslAuth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "EXTERNAL", "SASL Mech")) saslAuth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "SASL Cred")) pkt.AppendChild(saslAuth) envelope.AppendChild(pkt) return nil }) // ExternalBind performs SASL/EXTERNAL authentication. // // Use ldap.DialURL("ldapi://") to connect to the Unix socket before ExternalBind. // // See https://tools.ietf.org/html/rfc4422#appendix-A func (l *Conn) ExternalBind() error { msgCtx, err := l.doRequest(externalBindRequest) if err != nil { return err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return err } return GetLDAPError(packet) } // NTLMBind performs an NTLMSSP bind leveraging https://github.com/Azure/go-ntlmssp // NTLMBindRequest represents an NTLMSSP bind operation type NTLMBindRequest struct { // Domain is the AD Domain to authenticate too. If not specified, it will be grabbed from the NTLMSSP Challenge Domain string // Username is the name of the Directory object that the client wishes to bind as Username string // Password is the credentials to bind with Password string // Hash is the hex NTLM hash to bind with. Password or hash must be provided Hash string // Controls are optional controls to send with the bind request Controls []Control } func (req *NTLMBindRequest) appendTo(envelope *ber.Packet) error { request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) // generate an NTLMSSP Negotiation message for the specified domain (it can be blank) negMessage, err := ntlmssp.NewNegotiateMessage(req.Domain, "") if err != nil { return fmt.Errorf("err creating negmessage: %s", err) } // append the generated NTLMSSP message as a TagEnumerated BER value auth := ber.Encode(ber.ClassContext, ber.TypePrimitive, ber.TagEnumerated, negMessage, "authentication") request.AppendChild(auth) envelope.AppendChild(request) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // NTLMBindResult contains the response from the server type NTLMBindResult struct { Controls []Control } // NTLMBind performs an NTLMSSP Bind with the given domain, username and password func (l *Conn) NTLMBind(domain, username, password string) error { req := &NTLMBindRequest{ Domain: domain, Username: username, Password: password, } _, err := l.NTLMChallengeBind(req) return err } // NTLMBindWithHash performs an NTLM Bind with an NTLM hash instead of plaintext password (pass-the-hash) func (l *Conn) NTLMBindWithHash(domain, username, hash string) error { req := &NTLMBindRequest{ Domain: domain, Username: username, Hash: hash, } _, err := l.NTLMChallengeBind(req) return err } // NTLMChallengeBind performs the NTLMSSP bind operation defined in the given request func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindResult, error) { if ntlmBindRequest.Password == "" && ntlmBindRequest.Hash == "" { return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client")) } msgCtx, err := l.doRequest(ntlmBindRequest) if err != nil { return nil, err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return nil, err } l.Debug.Printf("%d: got response %p", msgCtx.id, packet) if l.Debug { if err = addLDAPDescriptions(packet); err != nil { return nil, err } ber.PrintPacket(packet) } result := &NTLMBindResult{ Controls: make([]Control, 0), } var ntlmsspChallenge []byte // now find the NTLM Response Message if len(packet.Children) == 2 { if len(packet.Children[1].Children) == 3 { child := packet.Children[1].Children[1] ntlmsspChallenge = child.ByteValue // Check to make sure we got the right message. It will always start with NTLMSSP if len(ntlmsspChallenge) < 7 || !bytes.Equal(ntlmsspChallenge[:7], []byte("NTLMSSP")) { return result, GetLDAPError(packet) } l.Debug.Printf("%d: found ntlmssp challenge", msgCtx.id) } } if ntlmsspChallenge != nil { var err error var responseMessage []byte // generate a response message to the challenge with the given Username/Password if password is provided if ntlmBindRequest.Password != "" { responseMessage, err = ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password) } else if ntlmBindRequest.Hash != "" { responseMessage, err = ntlmssp.ProcessChallengeWithHash(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Hash) } else { err = fmt.Errorf("need a password or hash to generate reply") } if err != nil { return result, fmt.Errorf("parsing ntlm-challenge: %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")) request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) // append the challenge response message as a TagEmbeddedPDV BER value auth := ber.Encode(ber.ClassContext, ber.TypePrimitive, ber.TagEmbeddedPDV, responseMessage, "authentication") request.AppendChild(auth) packet.AppendChild(request) msgCtx, err = l.sendMessage(packet) if err != nil { return nil, fmt.Errorf("send message: %s", err) } defer l.finishMessage(msgCtx) packetResponse, ok := <-msgCtx.responses if !ok { return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed")) } packet, err = packetResponse.ReadPacket() l.Debug.Printf("%d: got response %p", msgCtx.id, packet) if err != nil { return nil, fmt.Errorf("read packet: %s", err) } } err = GetLDAPError(packet) return result, err } ldap-3.4.1/client.go000066400000000000000000000014571410654160200142520ustar00rootroot00000000000000package ldap import ( "crypto/tls" "time" ) // Client knows how to interact with an LDAP server type Client interface { Start() StartTLS(*tls.Config) error Close() IsClosing() bool SetTimeout(time.Duration) Bind(username, password string) error UnauthenticatedBind(username string) error SimpleBind(*SimpleBindRequest) (*SimpleBindResult, error) ExternalBind() error Add(*AddRequest) error Del(*DelRequest) error Modify(*ModifyRequest) error ModifyDN(*ModifyDNRequest) error ModifyWithResult(*ModifyRequest) (*ModifyResult, error) Compare(dn, attribute, value string) (bool, error) PasswordModify(*PasswordModifyRequest) (*PasswordModifyResult, error) Search(*SearchRequest) (*SearchResult, error) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) } ldap-3.4.1/compare.go000066400000000000000000000033361410654160200144200ustar00rootroot00000000000000package ldap import ( "fmt" ber "github.com/go-asn1-ber/asn1-ber" ) // CompareRequest represents an LDAP CompareRequest operation. type CompareRequest struct { DN string Attribute string Value string } func (req *CompareRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationCompareRequest, nil, "Compare Request") pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN")) ava := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "AttributeValueAssertion") ava.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.Attribute, "AttributeDesc")) ava.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.Value, "AssertionValue")) pkt.AppendChild(ava) envelope.AppendChild(pkt) return nil } // Compare checks to see if the attribute of the dn matches value. Returns true if it does otherwise // false with any error that occurs if any. func (l *Conn) Compare(dn, attribute, value string) (bool, error) { msgCtx, err := l.doRequest(&CompareRequest{ DN: dn, Attribute: attribute, Value: value}) if err != nil { return false, err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return false, err } if packet.Children[1].Tag == ApplicationCompareResponse { err := GetLDAPError(packet) switch { case IsErrorWithCode(err, LDAPResultCompareTrue): return true, nil case IsErrorWithCode(err, LDAPResultCompareFalse): return false, nil default: return false, err } } return false, fmt.Errorf("unexpected Response: %d", packet.Children[1].Tag) } ldap-3.4.1/conn.go000066400000000000000000000366501410654160200137340ustar00rootroot00000000000000package ldap import ( "bufio" "crypto/tls" "errors" "fmt" "log" "net" "net/url" "sync" "sync/atomic" "time" ber "github.com/go-asn1-ber/asn1-ber" ) const ( // MessageQuit causes the processMessages loop to exit MessageQuit = 0 // MessageRequest sends a request to the server MessageRequest = 1 // MessageResponse receives a response from the server MessageResponse = 2 // MessageFinish indicates the client considers a particular message ID to be finished MessageFinish = 3 // MessageTimeout indicates the client-specified timeout for a particular message ID has been reached MessageTimeout = 4 ) const ( // DefaultLdapPort default ldap port for pure TCP connection DefaultLdapPort = "389" // DefaultLdapsPort default ldap port for SSL connection DefaultLdapsPort = "636" ) // PacketResponse contains the packet or error encountered reading a response type PacketResponse struct { // Packet is the packet read from the server Packet *ber.Packet // Error is an error encountered while reading Error error } // ReadPacket returns the packet or an error func (pr *PacketResponse) ReadPacket() (*ber.Packet, error) { if (pr == nil) || (pr.Packet == nil && pr.Error == nil) { return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve response")) } return pr.Packet, pr.Error } type messageContext struct { id int64 // close(done) should only be called from finishMessage() done chan struct{} // close(responses) should only be called from processMessages(), and only sent to from sendResponse() responses chan *PacketResponse } // sendResponse should only be called within the processMessages() loop which // is also responsible for closing the responses channel. func (msgCtx *messageContext) sendResponse(packet *PacketResponse) { select { case msgCtx.responses <- packet: // Successfully sent packet to message handler. case <-msgCtx.done: // The request handler is done and will not receive more // packets. } } type messagePacket struct { Op int MessageID int64 Packet *ber.Packet Context *messageContext } type sendMessageFlags uint const ( startTLS sendMessageFlags = 1 << iota ) // Conn represents an LDAP Connection type Conn struct { // requestTimeout is loaded atomically // so we need to ensure 64-bit alignment on 32-bit platforms. requestTimeout int64 conn net.Conn isTLS bool closing uint32 closeErr atomic.Value isStartingTLS bool Debug debugging chanConfirm chan struct{} messageContexts map[int64]*messageContext chanMessage chan *messagePacket chanMessageID chan int64 wgClose sync.WaitGroup outstandingRequests uint messageMutex sync.Mutex } var _ Client = &Conn{} // DefaultTimeout is a package-level variable that sets the timeout value // used for the Dial and DialTLS methods. // // WARNING: since this is a package-level variable, setting this value from // multiple places will probably result in undesired behaviour. var DefaultTimeout = 60 * time.Second // DialOpt configures DialContext. type DialOpt func(*DialContext) // DialWithDialer updates net.Dialer in DialContext. func DialWithDialer(d *net.Dialer) DialOpt { return func(dc *DialContext) { dc.d = d } } // DialWithTLSConfig updates tls.Config in DialContext. func DialWithTLSConfig(tc *tls.Config) DialOpt { return func(dc *DialContext) { dc.tc = tc } } // DialWithTLSDialer is a wrapper for DialWithTLSConfig with the option to // specify a net.Dialer to for example define a timeout or a custom resolver. func DialWithTLSDialer(tlsConfig *tls.Config, dialer *net.Dialer) DialOpt { return func(dc *DialContext) { dc.tc = tlsConfig dc.d = dialer } } // DialContext contains necessary parameters to dial the given ldap URL. type DialContext struct { d *net.Dialer tc *tls.Config } 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" } return dc.d.Dial("unix", u.Path) } host, port, err := net.SplitHostPort(u.Host) if err != nil { // we assume that error is due to missing port host = u.Host port = "" } switch u.Scheme { case "ldap": if port == "" { port = DefaultLdapPort } return dc.d.Dial("tcp", net.JoinHostPort(host, port)) case "ldaps": if port == "" { port = DefaultLdapsPort } return tls.DialWithDialer(dc.d, "tcp", net.JoinHostPort(host, port), dc.tc) } return nil, fmt.Errorf("Unknown scheme '%s'", u.Scheme) } // Dial connects to the given address on the given network using net.Dial // and then returns a new Conn for the connection. // @deprecated Use DialURL instead. func Dial(network, addr string) (*Conn, error) { c, err := net.DialTimeout(network, addr, DefaultTimeout) if err != nil { return nil, NewError(ErrorNetwork, err) } conn := NewConn(c, false) conn.Start() return conn, nil } // DialTLS connects to the given address on the given network using tls.Dial // and then returns a new Conn for the connection. // @deprecated Use DialURL instead. func DialTLS(network, addr string, config *tls.Config) (*Conn, error) { c, err := tls.DialWithDialer(&net.Dialer{Timeout: DefaultTimeout}, network, addr, config) if err != nil { return nil, NewError(ErrorNetwork, err) } conn := NewConn(c, true) conn.Start() return conn, nil } // DialURL connects to the given ldap URL. // The following schemas are supported: ldap://, ldaps://, ldapi://. // On success a new Conn for the connection is returned. func DialURL(addr string, opts ...DialOpt) (*Conn, error) { u, err := url.Parse(addr) if err != nil { return nil, NewError(ErrorNetwork, err) } var dc DialContext for _, opt := range opts { opt(&dc) } if dc.d == nil { dc.d = &net.Dialer{Timeout: DefaultTimeout} } c, err := dc.dial(u) if err != nil { return nil, NewError(ErrorNetwork, err) } conn := NewConn(c, u.Scheme == "ldaps") conn.Start() return conn, nil } // NewConn returns a new Conn using conn for network I/O. func NewConn(conn net.Conn, isTLS bool) *Conn { return &Conn{ conn: conn, chanConfirm: make(chan struct{}), chanMessageID: make(chan int64), chanMessage: make(chan *messagePacket, 10), messageContexts: map[int64]*messageContext{}, requestTimeout: 0, isTLS: isTLS, } } // Start initializes goroutines to read responses and process messages func (l *Conn) Start() { l.wgClose.Add(1) go l.reader() go l.processMessages() } // IsClosing returns whether or not we're currently closing. func (l *Conn) IsClosing() bool { return atomic.LoadUint32(&l.closing) == 1 } // setClosing sets the closing value to true func (l *Conn) setClosing() bool { return atomic.CompareAndSwapUint32(&l.closing, 0, 1) } // Close closes the connection. func (l *Conn) Close() { l.messageMutex.Lock() defer l.messageMutex.Unlock() if l.setClosing() { l.Debug.Printf("Sending quit message and waiting for confirmation") l.chanMessage <- &messagePacket{Op: MessageQuit} <-l.chanConfirm close(l.chanMessage) l.Debug.Printf("Closing network connection") if err := l.conn.Close(); err != nil { log.Println(err) } l.wgClose.Done() } l.wgClose.Wait() } // SetTimeout sets the time after a request is sent that a MessageTimeout triggers func (l *Conn) SetTimeout(timeout time.Duration) { if timeout > 0 { atomic.StoreInt64(&l.requestTimeout, int64(timeout)) } } // Returns the next available messageID func (l *Conn) nextMessageID() int64 { if messageID, ok := <-l.chanMessageID; ok { return messageID } return 0 } // 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 { return NewError(ErrorNetwork, errors.New("ldap: already encrypted")) } 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")) request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Start TLS") request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, "1.3.6.1.4.1.1466.20037", "TLS Extended Command")) packet.AppendChild(request) l.Debug.PrintPacket(packet) msgCtx, err := l.sendMessageWithFlags(packet, startTLS) if err != nil { return err } defer l.finishMessage(msgCtx) l.Debug.Printf("%d: waiting for response", msgCtx.id) packetResponse, ok := <-msgCtx.responses if !ok { return NewError(ErrorNetwork, errors.New("ldap: response channel closed")) } packet, err = packetResponse.ReadPacket() l.Debug.Printf("%d: got response %p", msgCtx.id, packet) if err != nil { return err } if l.Debug { if err := addLDAPDescriptions(packet); err != nil { l.Close() return err } l.Debug.PrintPacket(packet) } if err := GetLDAPError(packet); err == nil { conn := tls.Client(l.conn, config) if connErr := conn.Handshake(); connErr != nil { l.Close() return NewError(ErrorNetwork, fmt.Errorf("TLS handshake failed (%v)", connErr)) } l.isTLS = true l.conn = conn } else { return err } go l.reader() return nil } // TLSConnectionState returns the client's TLS connection state. // The return values are their zero values if StartTLS did // not succeed. func (l *Conn) TLSConnectionState() (state tls.ConnectionState, ok bool) { tc, ok := l.conn.(*tls.Conn) if !ok { return } return tc.ConnectionState(), true } func (l *Conn) sendMessage(packet *ber.Packet) (*messageContext, error) { return l.sendMessageWithFlags(packet, 0) } func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) (*messageContext, error) { if l.IsClosing() { return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed")) } l.messageMutex.Lock() l.Debug.Printf("flags&startTLS = %d", flags&startTLS) if l.isStartingTLS { l.messageMutex.Unlock() return nil, NewError(ErrorNetwork, errors.New("ldap: connection is in startls phase")) } if flags&startTLS != 0 { if l.outstandingRequests != 0 { l.messageMutex.Unlock() return nil, NewError(ErrorNetwork, errors.New("ldap: cannot StartTLS with outstanding requests")) } l.isStartingTLS = true } l.outstandingRequests++ l.messageMutex.Unlock() responses := make(chan *PacketResponse) messageID := packet.Children[0].Value.(int64) message := &messagePacket{ Op: MessageRequest, MessageID: messageID, Packet: packet, Context: &messageContext{ id: messageID, done: make(chan struct{}), responses: responses, }, } if !l.sendProcessMessage(message) { if l.IsClosing() { return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed")) } return nil, NewError(ErrorNetwork, errors.New("ldap: could not send message for unknown reason")) } return message.Context, nil } func (l *Conn) finishMessage(msgCtx *messageContext) { close(msgCtx.done) if l.IsClosing() { return } l.messageMutex.Lock() l.outstandingRequests-- if l.isStartingTLS { l.isStartingTLS = false } l.messageMutex.Unlock() message := &messagePacket{ Op: MessageFinish, MessageID: msgCtx.id, } l.sendProcessMessage(message) } func (l *Conn) sendProcessMessage(message *messagePacket) bool { l.messageMutex.Lock() defer l.messageMutex.Unlock() if l.IsClosing() { return false } l.chanMessage <- message return true } func (l *Conn) processMessages() { defer func() { if err := recover(); err != nil { log.Printf("ldap: recovered panic in processMessages: %v", err) } for messageID, msgCtx := range l.messageContexts { // If we are closing due to an error, inform anyone who // is waiting about the error. if l.IsClosing() && l.closeErr.Load() != nil { msgCtx.sendResponse(&PacketResponse{Error: l.closeErr.Load().(error)}) } l.Debug.Printf("Closing channel for MessageID %d", messageID) close(msgCtx.responses) delete(l.messageContexts, messageID) } close(l.chanMessageID) close(l.chanConfirm) }() var messageID int64 = 1 for { select { case l.chanMessageID <- messageID: messageID++ case message := <-l.chanMessage: switch message.Op { case MessageQuit: l.Debug.Printf("Shutting down - quit message received") return case MessageRequest: // Add to message list and write to network l.Debug.Printf("Sending message %d", message.MessageID) buf := message.Packet.Bytes() _, err := l.conn.Write(buf) if err != nil { l.Debug.Printf("Error Sending Message: %s", err.Error()) message.Context.sendResponse(&PacketResponse{Error: fmt.Errorf("unable to send request: %s", err)}) close(message.Context.responses) break } // Only add to messageContexts if we were able to // successfully write the message. l.messageContexts[message.MessageID] = message.Context // Add timeout if defined requestTimeout := time.Duration(atomic.LoadInt64(&l.requestTimeout)) if requestTimeout > 0 { go func() { defer func() { if err := recover(); err != nil { log.Printf("ldap: recovered panic in RequestTimeout: %v", err) } }() time.Sleep(requestTimeout) timeoutMessage := &messagePacket{ Op: MessageTimeout, MessageID: message.MessageID, } l.sendProcessMessage(timeoutMessage) }() } case MessageResponse: l.Debug.Printf("Receiving message %d", message.MessageID) if msgCtx, ok := l.messageContexts[message.MessageID]; ok { msgCtx.sendResponse(&PacketResponse{message.Packet, nil}) } else { log.Printf("Received unexpected message %d, %v", message.MessageID, l.IsClosing()) l.Debug.PrintPacket(message.Packet) } case MessageTimeout: // Handle the timeout by closing the channel // All reads will return immediately if msgCtx, ok := l.messageContexts[message.MessageID]; ok { l.Debug.Printf("Receiving message timeout for %d", message.MessageID) msgCtx.sendResponse(&PacketResponse{message.Packet, NewError(ErrorNetwork, errors.New("ldap: connection timed out"))}) delete(l.messageContexts, message.MessageID) close(msgCtx.responses) } case MessageFinish: l.Debug.Printf("Finished message %d", message.MessageID) if msgCtx, ok := l.messageContexts[message.MessageID]; ok { delete(l.messageContexts, message.MessageID) close(msgCtx.responses) } } } } } func (l *Conn) reader() { cleanstop := false defer func() { if err := recover(); err != nil { log.Printf("ldap: recovered panic in reader: %v", err) } if !cleanstop { l.Close() } }() bufConn := bufio.NewReader(l.conn) for { if cleanstop { l.Debug.Printf("reader clean stopping (without closing the connection)") return } packet, err := ber.ReadPacket(bufConn) if err != nil { // A read error is expected here if we are closing the connection... if !l.IsClosing() { l.closeErr.Store(fmt.Errorf("unable to read LDAP response packet: %s", err)) l.Debug.Printf("reader error: %s", err) } return } if err := addLDAPDescriptions(packet); err != nil { l.Debug.Printf("descriptions error: %s", err) } if len(packet.Children) == 0 { l.Debug.Printf("Received bad ldap packet") continue } l.messageMutex.Lock() if l.isStartingTLS { cleanstop = true } l.messageMutex.Unlock() message := &messagePacket{ Op: MessageResponse, MessageID: packet.Children[0].Value.(int64), Packet: packet, } if !l.sendProcessMessage(message) { return } } } ldap-3.4.1/conn_test.go000066400000000000000000000240471410654160200147700ustar00rootroot00000000000000package ldap import ( "bytes" "errors" "io" "net" "net/http" "net/http/httptest" "runtime" "sync" "testing" "time" ber "github.com/go-asn1-ber/asn1-ber" ) func TestUnresponsiveConnection(t *testing.T) { // The do-nothing server that accepts requests and does nothing ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { })) defer ts.Close() c, err := net.Dial(ts.Listener.Addr().Network(), ts.Listener.Addr().String()) if err != nil { t.Fatalf("error connecting to localhost tcp: %v", err) } // Create an Ldap connection conn := NewConn(c, false) conn.SetTimeout(time.Millisecond) conn.Start() defer conn.Close() // Mock a packet packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, conn.nextMessageID(), "MessageID")) bindRequest := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") bindRequest.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) packet.AppendChild(bindRequest) // Send packet and test response msgCtx, err := conn.sendMessage(packet) if err != nil { t.Fatalf("error sending message: %v", err) } defer conn.finishMessage(msgCtx) packetResponse, ok := <-msgCtx.responses if !ok { t.Fatalf("no PacketResponse in response channel") } packet, err = packetResponse.ReadPacket() if err == nil { t.Fatalf("expected timeout error") } if !IsErrorWithCode(err, ErrorNetwork) || err.(*Error).Err.Error() != "ldap: connection timed out" { t.Fatalf("unexpected error: %v", err) } } // TestFinishMessage tests that we do not enter deadlock when a goroutine makes // a request but does not handle all responses from the server. func TestFinishMessage(t *testing.T) { ptc := newPacketTranslatorConn() defer ptc.Close() conn := NewConn(ptc, false) conn.Start() // Test sending 5 different requests in series. Ensure that we can // get a response packet from the underlying connection and also // ensure that we can gracefully ignore unhandled responses. for i := 0; i < 5; i++ { t.Logf("serial request %d", i) // Create a message and make sure we can receive responses. msgCtx := testSendRequest(t, ptc, conn) testReceiveResponse(t, ptc, msgCtx) // Send a few unhandled responses and finish the message. testSendUnhandledResponsesAndFinish(t, ptc, conn, msgCtx, 5) t.Logf("serial request %d done", i) } // Test sending 5 different requests in parallel. var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go func(i int) { defer wg.Done() t.Logf("parallel request %d", i) // Create a message and make sure we can receive responses. msgCtx := testSendRequest(t, ptc, conn) testReceiveResponse(t, ptc, msgCtx) // Send a few unhandled responses and finish the message. testSendUnhandledResponsesAndFinish(t, ptc, conn, msgCtx, 5) t.Logf("parallel request %d done", i) }(i) } wg.Wait() // We cannot run Close() in a defer because t.FailNow() will run it and // it will block if the processMessage Loop is in a deadlock. conn.Close() } // See: https://github.com/go-ldap/ldap/issues/332 func TestNilConnection(t *testing.T) { var conn *Conn _, err := conn.Search(&SearchRequest{}) if err != ErrNilConnection { t.Fatalf("expected error to be ErrNilConnection, got %v", err) } } func testSendRequest(t *testing.T, ptc *packetTranslatorConn, conn *Conn) (msgCtx *messageContext) { var msgID int64 runWithTimeout(t, time.Second, func() { msgID = conn.nextMessageID() }) requestPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") requestPacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, msgID, "MessageID")) var err error runWithTimeout(t, time.Second, func() { msgCtx, err = conn.sendMessage(requestPacket) if err != nil { t.Fatalf("unable to send request message: %s", err) } }) // We should now be able to get this request packet out from the other // side. runWithTimeout(t, time.Second, func() { if _, err = ptc.ReceiveRequest(); err != nil { t.Fatalf("unable to receive request packet: %s", err) } }) return msgCtx } func testReceiveResponse(t *testing.T, ptc *packetTranslatorConn, msgCtx *messageContext) { // Send a mock response packet. responsePacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Response") responsePacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, msgCtx.id, "MessageID")) runWithTimeout(t, time.Second, func() { if err := ptc.SendResponse(responsePacket); err != nil { t.Fatalf("unable to send response packet: %s", err) } }) // We should be able to receive the packet from the connection. runWithTimeout(t, time.Second, func() { if _, ok := <-msgCtx.responses; !ok { t.Fatal("response channel closed") } }) } func testSendUnhandledResponsesAndFinish(t *testing.T, ptc *packetTranslatorConn, conn *Conn, msgCtx *messageContext, numResponses int) { // Send a mock response packet. responsePacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Response") responsePacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, msgCtx.id, "MessageID")) // Send extra responses but do not attempt to receive them on the // client side. for i := 0; i < numResponses; i++ { runWithTimeout(t, time.Second, func() { if err := ptc.SendResponse(responsePacket); err != nil { t.Fatalf("unable to send response packet: %s", err) } }) } // Finally, attempt to finish this message. runWithTimeout(t, time.Second, func() { conn.finishMessage(msgCtx) }) } func runWithTimeout(t *testing.T, timeout time.Duration, f func()) { done := make(chan struct{}) go func() { f() close(done) }() select { case <-done: // Success! case <-time.After(timeout): _, file, line, _ := runtime.Caller(1) t.Fatalf("%s:%d timed out", file, line) } } // packetTranslatorConn is a helpful type which can be used with various tests // in this package. It implements the net.Conn interface to be used as an // underlying connection for a *ldap.Conn. Most methods are no-ops but the // Read() and Write() methods are able to translate ber-encoded packets for // testing LDAP requests and responses. // // Test cases can simulate an LDAP server sending a response by calling the // SendResponse() method with a ber-encoded LDAP response packet. Test cases // can simulate an LDAP server receiving a request from a client by calling the // ReceiveRequest() method which returns a ber-encoded LDAP request packet. type packetTranslatorConn struct { lock sync.Mutex isClosed bool responseCond sync.Cond requestCond sync.Cond responseBuf bytes.Buffer requestBuf bytes.Buffer } var errPacketTranslatorConnClosed = errors.New("connection closed") func newPacketTranslatorConn() *packetTranslatorConn { conn := &packetTranslatorConn{} conn.responseCond = sync.Cond{L: &conn.lock} conn.requestCond = sync.Cond{L: &conn.lock} return conn } // Read is called by the reader() loop to receive response packets. It will // block until there are more packet bytes available or this connection is // closed. func (c *packetTranslatorConn) Read(b []byte) (n int, err error) { c.lock.Lock() defer c.lock.Unlock() for !c.isClosed { // Attempt to read data from the response buffer. If it fails // with an EOF, wait and try again. n, err = c.responseBuf.Read(b) if err != io.EOF { return n, err } c.responseCond.Wait() } return 0, errPacketTranslatorConnClosed } // SendResponse writes the given response packet to the response buffer for // this connection, signalling any goroutine waiting to read a response. func (c *packetTranslatorConn) SendResponse(packet *ber.Packet) error { c.lock.Lock() defer c.lock.Unlock() if c.isClosed { return errPacketTranslatorConnClosed } // Signal any goroutine waiting to read a response. defer c.responseCond.Broadcast() // Writes to the buffer should always succeed. c.responseBuf.Write(packet.Bytes()) return nil } // Write is called by the processMessages() loop to send request packets. func (c *packetTranslatorConn) Write(b []byte) (n int, err error) { c.lock.Lock() defer c.lock.Unlock() if c.isClosed { return 0, errPacketTranslatorConnClosed } // Signal any goroutine waiting to read a request. defer c.requestCond.Broadcast() // Writes to the buffer should always succeed. return c.requestBuf.Write(b) } // ReceiveRequest attempts to read a request packet from this connection. It // will block until it is able to read a full request packet or until this // connection is closed. func (c *packetTranslatorConn) ReceiveRequest() (*ber.Packet, error) { c.lock.Lock() defer c.lock.Unlock() for !c.isClosed { // Attempt to parse a request packet from the request buffer. // If it fails with an unexpected EOF, wait and try again. requestReader := bytes.NewReader(c.requestBuf.Bytes()) packet, err := ber.ReadPacket(requestReader) switch err { case io.EOF, io.ErrUnexpectedEOF: c.requestCond.Wait() case nil: // Advance the request buffer by the number of bytes // read to decode the request packet. c.requestBuf.Next(c.requestBuf.Len() - requestReader.Len()) return packet, nil default: return nil, err } } return nil, errPacketTranslatorConnClosed } // Close closes this connection causing Read() and Write() calls to fail. func (c *packetTranslatorConn) Close() error { c.lock.Lock() defer c.lock.Unlock() c.isClosed = true c.responseCond.Broadcast() c.requestCond.Broadcast() return nil } func (c *packetTranslatorConn) LocalAddr() net.Addr { return (*net.TCPAddr)(nil) } func (c *packetTranslatorConn) RemoteAddr() net.Addr { return (*net.TCPAddr)(nil) } func (c *packetTranslatorConn) SetDeadline(t time.Time) error { return nil } func (c *packetTranslatorConn) SetReadDeadline(t time.Time) error { return nil } func (c *packetTranslatorConn) SetWriteDeadline(t time.Time) error { return nil } ldap-3.4.1/control.go000066400000000000000000000447111410654160200144540ustar00rootroot00000000000000package ldap import ( "fmt" "strconv" ber "github.com/go-asn1-ber/asn1-ber" ) const ( // ControlTypePaging - https://www.ietf.org/rfc/rfc2696.txt ControlTypePaging = "1.2.840.113556.1.4.319" // ControlTypeBeheraPasswordPolicy - https://tools.ietf.org/html/draft-behera-ldap-password-policy-10 ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1" // ControlTypeVChuPasswordMustChange - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00 ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4" // ControlTypeVChuPasswordWarning - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00 ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5" // ControlTypeManageDsaIT - https://tools.ietf.org/html/rfc3296 ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2" // ControlTypeWhoAmI - https://tools.ietf.org/html/rfc4532 ControlTypeWhoAmI = "1.3.6.1.4.1.4203.1.11.3" // ControlTypeMicrosoftNotification - https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx ControlTypeMicrosoftNotification = "1.2.840.113556.1.4.528" // ControlTypeMicrosoftShowDeleted - https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx ControlTypeMicrosoftShowDeleted = "1.2.840.113556.1.4.417" // ControlTypeMicrosoftServerLinkTTL - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f4f523a8-abc0-4b3a-a471-6b2fef135481?redirectedfrom=MSDN ControlTypeMicrosoftServerLinkTTL = "1.2.840.113556.1.4.2309" ) // ControlTypeMap maps controls to text descriptions var ControlTypeMap = map[string]string{ ControlTypePaging: "Paging", ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft", ControlTypeManageDsaIT: "Manage DSA IT", ControlTypeMicrosoftNotification: "Change Notification - Microsoft", ControlTypeMicrosoftShowDeleted: "Show Deleted Objects - Microsoft", ControlTypeMicrosoftServerLinkTTL: "Return TTL-DNs for link values with associated expiry times - Microsoft", } // Control defines an interface controls provide to encode and describe themselves type Control interface { // GetControlType returns the OID GetControlType() string // Encode returns the ber packet representation Encode() *ber.Packet // String returns a human-readable description String() string } // ControlString implements the Control interface for simple controls type ControlString struct { ControlType string Criticality bool ControlValue string } // GetControlType returns the OID func (c *ControlString) GetControlType() string { return c.ControlType } // Encode returns the ber packet representation func (c *ControlString) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.ControlType, "Control Type ("+ControlTypeMap[c.ControlType]+")")) if c.Criticality { packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) } if c.ControlValue != "" { packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, string(c.ControlValue), "Control Value")) } return packet } // String returns a human-readable description func (c *ControlString) String() string { return fmt.Sprintf("Control Type: %s (%q) Criticality: %t Control Value: %s", ControlTypeMap[c.ControlType], c.ControlType, c.Criticality, c.ControlValue) } // ControlPaging implements the paging control described in https://www.ietf.org/rfc/rfc2696.txt type ControlPaging struct { // PagingSize indicates the page size PagingSize uint32 // Cookie is an opaque value returned by the server to track a paging cursor Cookie []byte } // GetControlType returns the OID func (c *ControlPaging) GetControlType() string { return ControlTypePaging } // Encode returns the ber packet representation func (c *ControlPaging) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypePaging, "Control Type ("+ControlTypeMap[ControlTypePaging]+")")) p2 := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (Paging)") seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Search Control Value") seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.PagingSize), "Paging Size")) cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie") cookie.Value = c.Cookie cookie.Data.Write(c.Cookie) seq.AppendChild(cookie) p2.AppendChild(seq) packet.AppendChild(p2) return packet } // String returns a human-readable description func (c *ControlPaging) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t PagingSize: %d Cookie: %q", ControlTypeMap[ControlTypePaging], ControlTypePaging, false, c.PagingSize, c.Cookie) } // SetCookie stores the given cookie in the paging control func (c *ControlPaging) SetCookie(cookie []byte) { c.Cookie = cookie } // ControlBeheraPasswordPolicy implements the control described in https://tools.ietf.org/html/draft-behera-ldap-password-policy-10 type ControlBeheraPasswordPolicy struct { // Expire contains the number of seconds before a password will expire Expire int64 // Grace indicates the remaining number of times a user will be allowed to authenticate with an expired password Grace int64 // Error indicates the error code Error int8 // ErrorString is a human readable error ErrorString string } // GetControlType returns the OID func (c *ControlBeheraPasswordPolicy) GetControlType() string { return ControlTypeBeheraPasswordPolicy } // Encode returns the ber packet representation func (c *ControlBeheraPasswordPolicy) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeBeheraPasswordPolicy, "Control Type ("+ControlTypeMap[ControlTypeBeheraPasswordPolicy]+")")) return packet } // String returns a human-readable description func (c *ControlBeheraPasswordPolicy) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t Expire: %d Grace: %d Error: %d, ErrorString: %s", ControlTypeMap[ControlTypeBeheraPasswordPolicy], ControlTypeBeheraPasswordPolicy, false, c.Expire, c.Grace, c.Error, c.ErrorString) } // ControlVChuPasswordMustChange implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00 type ControlVChuPasswordMustChange struct { // MustChange indicates if the password is required to be changed MustChange bool } // GetControlType returns the OID func (c *ControlVChuPasswordMustChange) GetControlType() string { return ControlTypeVChuPasswordMustChange } // Encode returns the ber packet representation func (c *ControlVChuPasswordMustChange) Encode() *ber.Packet { return nil } // String returns a human-readable description func (c *ControlVChuPasswordMustChange) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t MustChange: %v", ControlTypeMap[ControlTypeVChuPasswordMustChange], ControlTypeVChuPasswordMustChange, false, c.MustChange) } // ControlVChuPasswordWarning implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00 type ControlVChuPasswordWarning struct { // Expire indicates the time in seconds until the password expires Expire int64 } // GetControlType returns the OID func (c *ControlVChuPasswordWarning) GetControlType() string { return ControlTypeVChuPasswordWarning } // Encode returns the ber packet representation func (c *ControlVChuPasswordWarning) Encode() *ber.Packet { return nil } // String returns a human-readable description func (c *ControlVChuPasswordWarning) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t Expire: %b", ControlTypeMap[ControlTypeVChuPasswordWarning], ControlTypeVChuPasswordWarning, false, c.Expire) } // ControlManageDsaIT implements the control described in https://tools.ietf.org/html/rfc3296 type ControlManageDsaIT struct { // Criticality indicates if this control is required Criticality bool } // GetControlType returns the OID func (c *ControlManageDsaIT) GetControlType() string { return ControlTypeManageDsaIT } // Encode returns the ber packet representation func (c *ControlManageDsaIT) Encode() *ber.Packet { //FIXME packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeManageDsaIT, "Control Type ("+ControlTypeMap[ControlTypeManageDsaIT]+")")) if c.Criticality { packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) } return packet } // String returns a human-readable description func (c *ControlManageDsaIT) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t", ControlTypeMap[ControlTypeManageDsaIT], ControlTypeManageDsaIT, c.Criticality) } // NewControlManageDsaIT returns a ControlManageDsaIT control func NewControlManageDsaIT(Criticality bool) *ControlManageDsaIT { return &ControlManageDsaIT{Criticality: Criticality} } // ControlMicrosoftNotification implements the control described in https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx type ControlMicrosoftNotification struct{} // GetControlType returns the OID func (c *ControlMicrosoftNotification) GetControlType() string { return ControlTypeMicrosoftNotification } // Encode returns the ber packet representation func (c *ControlMicrosoftNotification) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftNotification, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftNotification]+")")) return packet } // String returns a human-readable description func (c *ControlMicrosoftNotification) String() string { return fmt.Sprintf( "Control Type: %s (%q)", ControlTypeMap[ControlTypeMicrosoftNotification], ControlTypeMicrosoftNotification) } // NewControlMicrosoftNotification returns a ControlMicrosoftNotification control func NewControlMicrosoftNotification() *ControlMicrosoftNotification { return &ControlMicrosoftNotification{} } // ControlMicrosoftShowDeleted implements the control described in https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx type ControlMicrosoftShowDeleted struct{} // GetControlType returns the OID func (c *ControlMicrosoftShowDeleted) GetControlType() string { return ControlTypeMicrosoftShowDeleted } // Encode returns the ber packet representation func (c *ControlMicrosoftShowDeleted) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftShowDeleted, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftShowDeleted]+")")) return packet } // String returns a human-readable description func (c *ControlMicrosoftShowDeleted) String() string { return fmt.Sprintf( "Control Type: %s (%q)", ControlTypeMap[ControlTypeMicrosoftShowDeleted], ControlTypeMicrosoftShowDeleted) } // NewControlMicrosoftShowDeleted returns a ControlMicrosoftShowDeleted control func NewControlMicrosoftShowDeleted() *ControlMicrosoftShowDeleted { return &ControlMicrosoftShowDeleted{} } // ControlMicrosoftServerLinkTTL implements the control described in https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f4f523a8-abc0-4b3a-a471-6b2fef135481?redirectedfrom=MSDN type ControlMicrosoftServerLinkTTL struct{} // GetControlType returns the OID func (c *ControlMicrosoftServerLinkTTL) GetControlType() string { return ControlTypeMicrosoftServerLinkTTL } // Encode returns the ber packet representation func (c *ControlMicrosoftServerLinkTTL) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftServerLinkTTL, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftServerLinkTTL]+")")) return packet } // String returns a human-readable description func (c *ControlMicrosoftServerLinkTTL) String() string { return fmt.Sprintf( "Control Type: %s (%q)", ControlTypeMap[ControlTypeMicrosoftServerLinkTTL], ControlTypeMicrosoftServerLinkTTL) } // NewControlMicrosoftServerLinkTTL returns a ControlMicrosoftServerLinkTTL control func NewControlMicrosoftServerLinkTTL() *ControlMicrosoftServerLinkTTL { return &ControlMicrosoftServerLinkTTL{} } // FindControl returns the first control of the given type in the list, or nil func FindControl(controls []Control, controlType string) Control { for _, c := range controls { if c.GetControlType() == controlType { return c } } return nil } // DecodeControl returns a control read from the given packet, or nil if no recognized control can be made func DecodeControl(packet *ber.Packet) (Control, error) { var ( ControlType = "" Criticality = false value *ber.Packet ) switch len(packet.Children) { case 0: // at least one child is required for control type return nil, fmt.Errorf("at least one child is required for control type") case 1: // just type, no criticality or value packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")" ControlType = packet.Children[0].Value.(string) case 2: packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")" ControlType = packet.Children[0].Value.(string) // 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 { packet.Children[1].Description = "Criticality" Criticality = packet.Children[1].Value.(bool) } else { packet.Children[1].Description = "Control Value" value = packet.Children[1] } case 3: packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")" ControlType = packet.Children[0].Value.(string) packet.Children[1].Description = "Criticality" Criticality = packet.Children[1].Value.(bool) packet.Children[2].Description = "Control Value" value = packet.Children[2] default: // more than 3 children is invalid return nil, fmt.Errorf("more than 3 children is invalid for controls") } switch ControlType { case ControlTypeManageDsaIT: return NewControlManageDsaIT(Criticality), nil case ControlTypePaging: value.Description += " (Paging)" c := new(ControlPaging) if value.Value != nil { valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) if err != nil { return nil, fmt.Errorf("failed to decode data bytes: %s", err) } value.Data.Truncate(0) value.Value = nil value.AppendChild(valueChildren) } value = value.Children[0] value.Description = "Search Control Value" value.Children[0].Description = "Paging Size" value.Children[1].Description = "Cookie" c.PagingSize = uint32(value.Children[0].Value.(int64)) c.Cookie = value.Children[1].Data.Bytes() value.Children[1].Value = c.Cookie return c, nil case ControlTypeBeheraPasswordPolicy: value.Description += " (Password Policy - Behera)" c := NewControlBeheraPasswordPolicy() if value.Value != nil { valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) if err != nil { return nil, fmt.Errorf("failed to decode data bytes: %s", err) } value.Data.Truncate(0) value.Value = nil value.AppendChild(valueChildren) } sequence := value.Children[0] for _, child := range sequence.Children { if child.Tag == 0 { //Warning warningPacket := child.Children[0] val, err := ber.ParseInt64(warningPacket.Data.Bytes()) if err != nil { return nil, fmt.Errorf("failed to decode data bytes: %s", err) } if warningPacket.Tag == 0 { //timeBeforeExpiration c.Expire = val warningPacket.Value = c.Expire } else if warningPacket.Tag == 1 { //graceAuthNsRemaining c.Grace = val warningPacket.Value = c.Grace } } else if child.Tag == 1 { // Error bs := child.Data.Bytes() if len(bs) != 1 || bs[0] > 8 { return nil, fmt.Errorf("failed to decode data bytes: %s", "invalid PasswordPolicyResponse enum value") } val := int8(bs[0]) c.Error = val child.Value = c.Error c.ErrorString = BeheraPasswordPolicyErrorMap[c.Error] } } return c, nil case ControlTypeVChuPasswordMustChange: c := &ControlVChuPasswordMustChange{MustChange: true} return c, nil case ControlTypeVChuPasswordWarning: c := &ControlVChuPasswordWarning{Expire: -1} expireStr := ber.DecodeString(value.Data.Bytes()) expire, err := strconv.ParseInt(expireStr, 10, 64) if err != nil { return nil, fmt.Errorf("failed to parse value as int: %s", err) } c.Expire = expire value.Value = c.Expire return c, nil case ControlTypeMicrosoftNotification: return NewControlMicrosoftNotification(), nil case ControlTypeMicrosoftShowDeleted: return NewControlMicrosoftShowDeleted(), nil case ControlTypeMicrosoftServerLinkTTL: return NewControlMicrosoftServerLinkTTL(), nil default: c := new(ControlString) c.ControlType = ControlType c.Criticality = Criticality if value != nil { c.ControlValue = value.Value.(string) } return c, nil } } // NewControlString returns a generic control func NewControlString(controlType string, criticality bool, controlValue string) *ControlString { return &ControlString{ ControlType: controlType, Criticality: criticality, ControlValue: controlValue, } } // NewControlPaging returns a paging control func NewControlPaging(pagingSize uint32) *ControlPaging { return &ControlPaging{PagingSize: pagingSize} } // NewControlBeheraPasswordPolicy returns a ControlBeheraPasswordPolicy func NewControlBeheraPasswordPolicy() *ControlBeheraPasswordPolicy { return &ControlBeheraPasswordPolicy{ Expire: -1, Grace: -1, Error: -1, } } func encodeControls(controls []Control) *ber.Packet { packet := ber.Encode(ber.ClassContext, ber.TypeConstructed, 0, nil, "Controls") for _, control := range controls { packet.AppendChild(control.Encode()) } return packet } ldap-3.4.1/control_test.go000066400000000000000000000232431410654160200155100ustar00rootroot00000000000000package ldap import ( "bytes" "fmt" "reflect" "runtime" "testing" ber "github.com/go-asn1-ber/asn1-ber" ) func TestControlPaging(t *testing.T) { runControlTest(t, NewControlPaging(0)) runControlTest(t, NewControlPaging(100)) } func TestControlManageDsaIT(t *testing.T) { runControlTest(t, NewControlManageDsaIT(true)) runControlTest(t, NewControlManageDsaIT(false)) } func TestControlMicrosoftNotification(t *testing.T) { runControlTest(t, NewControlMicrosoftNotification()) } func TestControlMicrosoftShowDeleted(t *testing.T) { runControlTest(t, NewControlMicrosoftShowDeleted()) } func TestControlMicrosoftServerLinkTTL(t *testing.T) { runControlTest(t, NewControlMicrosoftServerLinkTTL()) } func TestControlString(t *testing.T) { runControlTest(t, NewControlString("x", true, "y")) runControlTest(t, NewControlString("x", true, "")) runControlTest(t, NewControlString("x", false, "y")) runControlTest(t, NewControlString("x", false, "")) } func runControlTest(t *testing.T, originalControl Control) { header := "" if callerpc, _, line, ok := runtime.Caller(1); ok { if caller := runtime.FuncForPC(callerpc); caller != nil { header = fmt.Sprintf("%s:%d: ", caller.Name(), line) } } encodedPacket := originalControl.Encode() encodedBytes := encodedPacket.Bytes() // Decode directly from the encoded packet (ensures Value is correct) fromPacket, err := DecodeControl(encodedPacket) if err != nil { t.Errorf("%sdecoding encoded bytes control failed: %s", header, err) } if !bytes.Equal(encodedBytes, fromPacket.Encode().Bytes()) { t.Errorf("%sround-trip from encoded packet failed", header) } if reflect.TypeOf(originalControl) != reflect.TypeOf(fromPacket) { t.Errorf("%sgot different type decoding from encoded packet: %T vs %T", header, fromPacket, originalControl) } // Decode from the wire bytes (ensures ber-encoding is correct) pkt, err := ber.DecodePacketErr(encodedBytes) if err != nil { t.Errorf("%sdecoding encoded bytes failed: %s", header, err) } fromBytes, err := DecodeControl(pkt) if err != nil { t.Errorf("%sdecoding control failed: %s", header, err) } if !bytes.Equal(encodedBytes, fromBytes.Encode().Bytes()) { t.Errorf("%sround-trip from encoded bytes failed", header) } if reflect.TypeOf(originalControl) != reflect.TypeOf(fromPacket) { t.Errorf("%sgot different type decoding from encoded bytes: %T vs %T", header, fromBytes, originalControl) } } func TestDescribeControlManageDsaIT(t *testing.T) { runAddControlDescriptions(t, NewControlManageDsaIT(false), "Control Type (Manage DSA IT)") runAddControlDescriptions(t, NewControlManageDsaIT(true), "Control Type (Manage DSA IT)", "Criticality") } func TestDescribeControlPaging(t *testing.T) { runAddControlDescriptions(t, NewControlPaging(100), "Control Type (Paging)", "Control Value (Paging)") runAddControlDescriptions(t, NewControlPaging(0), "Control Type (Paging)", "Control Value (Paging)") } func TestDescribeControlMicrosoftNotification(t *testing.T) { runAddControlDescriptions(t, NewControlMicrosoftNotification(), "Control Type (Change Notification - Microsoft)") } func TestDescribeControlMicrosoftShowDeleted(t *testing.T) { runAddControlDescriptions(t, NewControlMicrosoftShowDeleted(), "Control Type (Show Deleted Objects - Microsoft)") } func TestDescribeControlMicrosoftServerLinkTTL(t *testing.T) { runAddControlDescriptions(t, NewControlMicrosoftServerLinkTTL(), "Control Type (Return TTL-DNs for link values with associated expiry times - Microsoft)") } func TestDescribeControlString(t *testing.T) { runAddControlDescriptions(t, NewControlString("x", true, "y"), "Control Type ()", "Criticality", "Control Value") runAddControlDescriptions(t, NewControlString("x", true, ""), "Control Type ()", "Criticality") runAddControlDescriptions(t, NewControlString("x", false, "y"), "Control Type ()", "Control Value") runAddControlDescriptions(t, NewControlString("x", false, ""), "Control Type ()") } func runAddControlDescriptions(t *testing.T, originalControl Control, childDescriptions ...string) { header := "" if callerpc, _, line, ok := runtime.Caller(1); ok { if caller := runtime.FuncForPC(callerpc); caller != nil { header = fmt.Sprintf("%s:%d: ", caller.Name(), line) } } encodedControls := encodeControls([]Control{originalControl}) addControlDescriptions(encodedControls) encodedPacket := encodedControls.Children[0] if len(encodedPacket.Children) != len(childDescriptions) { t.Errorf("%sinvalid number of children: %d != %d", header, len(encodedPacket.Children), len(childDescriptions)) } for i, desc := range childDescriptions { if encodedPacket.Children[i].Description != desc { t.Errorf("%sdescription not as expected: %s != %s", header, encodedPacket.Children[i].Description, desc) } } } func TestDecodeControl(t *testing.T) { type args struct { packet *ber.Packet } tests := []struct { name string args args want Control wantErr bool }{ {name: "timeBeforeExpiration", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x29, 0x30, 0x27, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0xa, 0x30, 0x8, 0xa0, 0x6, 0x80, 0x4, 0x7f, 0xff, 0xf6, 0x5c})}, want: &ControlBeheraPasswordPolicy{Expire: 2147481180, Grace: -1, Error: -1, ErrorString: ""}, wantErr: false}, {name: "graceAuthNsRemaining", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x26, 0x30, 0x24, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x7, 0x30, 0x5, 0xa0, 0x3, 0x81, 0x1, 0x11})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: 17, Error: -1, ErrorString: ""}, wantErr: false}, {name: "passwordExpired", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x0})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 0, ErrorString: "Password expired"}, wantErr: false}, {name: "accountLocked", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x1})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 1, ErrorString: "Account locked"}, wantErr: false}, {name: "passwordModNotAllowed", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x3})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 3, ErrorString: "Policy prevents password modification"}, wantErr: false}, {name: "mustSupplyOldPassword", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x4})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 4, ErrorString: "Policy requires old password in order to change password"}, wantErr: false}, {name: "insufficientPasswordQuality", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x5})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 5, ErrorString: "Password fails quality checks"}, wantErr: false}, {name: "passwordTooShort", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x6})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 6, ErrorString: "Password is too short for policy"}, wantErr: false}, {name: "passwordTooYoung", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x7})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 7, ErrorString: "Password has been changed too recently"}, wantErr: false}, {name: "passwordInHistory", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x8})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 8, ErrorString: "New password is in list of old passwords"}, wantErr: false}, } for i := range tests { err := addControlDescriptions(tests[i].args.packet) if err != nil { t.Fatal(err) } tests[i].args.packet = tests[i].args.packet.Children[0] } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := DecodeControl(tt.args.packet) if (err != nil) != tt.wantErr { t.Errorf("DecodeControl() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("DecodeControl() got = %v, want %v", got, tt.want) } }) } } ldap-3.4.1/debug.go000066400000000000000000000010611410654160200140510ustar00rootroot00000000000000package ldap import ( "log" ber "github.com/go-asn1-ber/asn1-ber" ) // debugging type // - has a Printf method to write the debug output type debugging bool // Enable controls debugging mode. func (debug *debugging) Enable(b bool) { *debug = debugging(b) } // Printf writes debug output. func (debug debugging) Printf(format string, args ...interface{}) { if debug { log.Printf(format, args...) } } // PrintPacket dumps a packet. func (debug debugging) PrintPacket(packet *ber.Packet) { if debug { ber.WritePacket(log.Writer(), packet) } } ldap-3.4.1/del.go000066400000000000000000000024301410654160200135300ustar00rootroot00000000000000package ldap import ( "log" ber "github.com/go-asn1-ber/asn1-ber" ) // DelRequest implements an LDAP deletion request type DelRequest struct { // DN is the name of the directory entry to delete DN string // Controls hold optional controls to send with the request Controls []Control } func (req *DelRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypePrimitive, ApplicationDelRequest, req.DN, "Del Request") pkt.Data.Write([]byte(req.DN)) envelope.AppendChild(pkt) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // NewDelRequest creates a delete request for the given DN and controls func NewDelRequest(DN string, Controls []Control) *DelRequest { return &DelRequest{ DN: DN, Controls: Controls, } } // Del executes the given delete request func (l *Conn) Del(delRequest *DelRequest) error { msgCtx, err := l.doRequest(delRequest) if err != nil { return err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return err } if packet.Children[1].Tag == ApplicationDelResponse { err := GetLDAPError(packet) if err != nil { return err } } else { log.Printf("Unexpected Response: %d", packet.Children[1].Tag) } return nil } ldap-3.4.1/dn.go000066400000000000000000000200241410654160200133640ustar00rootroot00000000000000package ldap import ( "bytes" enchex "encoding/hex" "errors" "fmt" "strings" ber "github.com/go-asn1-ber/asn1-ber" ) // AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514 type AttributeTypeAndValue struct { // Type is the attribute type Type string // Value is the attribute value Value string } // RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514 type RelativeDN struct { Attributes []*AttributeTypeAndValue } // DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514 type DN struct { RDNs []*RelativeDN } // ParseDN returns a distinguishedName or an error. // The function respects https://tools.ietf.org/html/rfc4514 func ParseDN(str string) (*DN, error) { dn := new(DN) dn.RDNs = make([]*RelativeDN, 0) rdn := new(RelativeDN) rdn.Attributes = make([]*AttributeTypeAndValue, 0) buffer := bytes.Buffer{} attribute := new(AttributeTypeAndValue) escaping := false unescapedTrailingSpaces := 0 stringFromBuffer := func() string { s := buffer.String() s = s[0 : len(s)-unescapedTrailingSpaces] buffer.Reset() unescapedTrailingSpaces = 0 return s } for i := 0; i < len(str); i++ { char := str[i] switch { case escaping: unescapedTrailingSpaces = 0 escaping = false switch char { case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\': buffer.WriteByte(char) continue } // Not a special character, assume hex encoded octet if len(str) == i+1 { return nil, errors.New("got corrupted escaped character") } dst := []byte{0} n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2])) if err != nil { return nil, fmt.Errorf("failed to decode escaped character: %s", err) } else if n != 1 { return nil, fmt.Errorf("expected 1 byte when un-escaping, got %d", n) } buffer.WriteByte(dst[0]) i++ case char == '\\': unescapedTrailingSpaces = 0 escaping = true case char == '=': attribute.Type = stringFromBuffer() // Special case: If the first character in the value is # the // following data is BER encoded so we can just fast forward // and decode. if len(str) > i+1 && str[i+1] == '#' { i += 2 index := strings.IndexAny(str[i:], ",+") var data string if index > 0 { data = str[i : i+index] } else { data = str[i:] } rawBER, err := enchex.DecodeString(data) if err != nil { return nil, fmt.Errorf("failed to decode BER encoding: %s", err) } packet, err := ber.DecodePacketErr(rawBER) if err != nil { return nil, fmt.Errorf("failed to decode BER packet: %s", err) } buffer.WriteString(packet.Data.String()) i += len(data) - 1 } case char == ',' || char == '+': // We're done with this RDN or value, push it if len(attribute.Type) == 0 { return nil, errors.New("incomplete type, value pair") } attribute.Value = stringFromBuffer() rdn.Attributes = append(rdn.Attributes, attribute) attribute = new(AttributeTypeAndValue) if char == ',' { dn.RDNs = append(dn.RDNs, rdn) rdn = new(RelativeDN) rdn.Attributes = make([]*AttributeTypeAndValue, 0) } case char == ' ' && buffer.Len() == 0: // ignore unescaped leading spaces continue default: if char == ' ' { // Track unescaped spaces in case they are trailing and we need to remove them unescapedTrailingSpaces++ } else { // Reset if we see a non-space char unescapedTrailingSpaces = 0 } buffer.WriteByte(char) } } if buffer.Len() > 0 { if len(attribute.Type) == 0 { return nil, errors.New("DN ended with incomplete type, value pair") } attribute.Value = stringFromBuffer() rdn.Attributes = append(rdn.Attributes, attribute) dn.RDNs = append(dn.RDNs, rdn) } return dn, nil } // Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch). // Returns true if they have the same number of relative distinguished names // and corresponding relative distinguished names (by position) are the same. func (d *DN) Equal(other *DN) bool { if len(d.RDNs) != len(other.RDNs) { return false } for i := range d.RDNs { if !d.RDNs[i].Equal(other.RDNs[i]) { return false } } return true } // AncestorOf returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN. // "ou=widgets,o=acme.com" is an ancestor of "ou=sprockets,ou=widgets,o=acme.com" // "ou=widgets,o=acme.com" is not an ancestor of "ou=sprockets,ou=widgets,o=foo.com" // "ou=widgets,o=acme.com" is not an ancestor of "ou=widgets,o=acme.com" func (d *DN) AncestorOf(other *DN) bool { if len(d.RDNs) >= len(other.RDNs) { return false } // Take the last `len(d.RDNs)` RDNs from the other DN to compare against otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):] for i := range d.RDNs { if !d.RDNs[i].Equal(otherRDNs[i]) { return false } } return true } // Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch). // Relative distinguished names are the same if and only if they have the same number of AttributeTypeAndValues // and each attribute of the first RDN is the same as the attribute of the second RDN with the same attribute type. // The order of attributes is not significant. // Case of attribute types is not significant. func (r *RelativeDN) Equal(other *RelativeDN) bool { if len(r.Attributes) != len(other.Attributes) { return false } return r.hasAllAttributes(other.Attributes) && other.hasAllAttributes(r.Attributes) } func (r *RelativeDN) hasAllAttributes(attrs []*AttributeTypeAndValue) bool { for _, attr := range attrs { found := false for _, myattr := range r.Attributes { if myattr.Equal(attr) { found = true break } } if !found { return false } } return true } // Equal returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue // Case of the attribute type is not significant func (a *AttributeTypeAndValue) Equal(other *AttributeTypeAndValue) bool { return strings.EqualFold(a.Type, other.Type) && a.Value == other.Value } // Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch). // Returns true if they have the same number of relative distinguished names // and corresponding relative distinguished names (by position) are the same. // Case of the attribute type and value is not significant func (d *DN) EqualFold(other *DN) bool { if len(d.RDNs) != len(other.RDNs) { return false } for i := range d.RDNs { if !d.RDNs[i].EqualFold(other.RDNs[i]) { return false } } return true } // AncestorOfFold returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN. // Case of the attribute type and value is not significant func (d *DN) AncestorOfFold(other *DN) bool { if len(d.RDNs) >= len(other.RDNs) { return false } // Take the last `len(d.RDNs)` RDNs from the other DN to compare against otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):] for i := range d.RDNs { if !d.RDNs[i].EqualFold(otherRDNs[i]) { return false } } return true } // Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch). // Case of the attribute type is not significant func (r *RelativeDN) EqualFold(other *RelativeDN) bool { if len(r.Attributes) != len(other.Attributes) { return false } return r.hasAllAttributesFold(other.Attributes) && other.hasAllAttributesFold(r.Attributes) } func (r *RelativeDN) hasAllAttributesFold(attrs []*AttributeTypeAndValue) bool { for _, attr := range attrs { found := false for _, myattr := range r.Attributes { if myattr.EqualFold(attr) { found = true break } } if !found { return false } } return true } // EqualFold returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue // Case of the attribute type and value is not significant func (a *AttributeTypeAndValue) EqualFold(other *AttributeTypeAndValue) bool { return strings.EqualFold(a.Type, other.Type) && strings.EqualFold(a.Value, other.Value) } ldap-3.4.1/dn_test.go000066400000000000000000000157361410654160200144410ustar00rootroot00000000000000package ldap import ( "reflect" "testing" ) func TestSuccessfulDNParsing(t *testing.T) { testcases := map[string]DN{ "": {[]*RelativeDN{}}, "cn=Jim\\2C \\22Hasse Hö\\22 Hansson!,dc=dummy,dc=com": {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"cn", "Jim, \"Hasse Hö\" Hansson!"}}}, {[]*AttributeTypeAndValue{{"dc", "dummy"}}}, {[]*AttributeTypeAndValue{{"dc", "com"}}}}}, "UID=jsmith,DC=example,DC=net": {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"UID", "jsmith"}}}, {[]*AttributeTypeAndValue{{"DC", "example"}}}, {[]*AttributeTypeAndValue{{"DC", "net"}}}}}, "OU=Sales+CN=J. Smith,DC=example,DC=net": {[]*RelativeDN{ {[]*AttributeTypeAndValue{ {"OU", "Sales"}, {"CN", "J. Smith"}}}, {[]*AttributeTypeAndValue{{"DC", "example"}}}, {[]*AttributeTypeAndValue{{"DC", "net"}}}}}, "1.3.6.1.4.1.1466.0=#04024869": {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"1.3.6.1.4.1.1466.0", "Hi"}}}}}, "1.3.6.1.4.1.1466.0=#04024869,DC=net": {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"1.3.6.1.4.1.1466.0", "Hi"}}}, {[]*AttributeTypeAndValue{{"DC", "net"}}}}}, "CN=Lu\\C4\\8Di\\C4\\87": {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"CN", "Lučić"}}}}}, " CN = Lu\\C4\\8Di\\C4\\87 ": {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"CN", "Lučić"}}}}}, ` A = 1 , B = 2 `: {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"A", "1"}}}, {[]*AttributeTypeAndValue{{"B", "2"}}}}}, ` A = 1 + B = 2 `: {[]*RelativeDN{ {[]*AttributeTypeAndValue{ {"A", "1"}, {"B", "2"}}}}}, ` \ \ A\ \ = \ \ 1\ \ , \ \ B\ \ = \ \ 2\ \ `: {[]*RelativeDN{ {[]*AttributeTypeAndValue{{" A ", " 1 "}}}, {[]*AttributeTypeAndValue{{" B ", " 2 "}}}}}, ` \ \ A\ \ = \ \ 1\ \ + \ \ B\ \ = \ \ 2\ \ `: {[]*RelativeDN{ {[]*AttributeTypeAndValue{ {" A ", " 1 "}, {" B ", " 2 "}}}}}, } for test, answer := range testcases { dn, err := ParseDN(test) if err != nil { t.Errorf(err.Error()) continue } if !reflect.DeepEqual(dn, &answer) { t.Errorf("Parsed DN %s is not equal to the expected structure", test) t.Logf("Expected:") for _, rdn := range answer.RDNs { for _, attribs := range rdn.Attributes { t.Logf("#%v\n", attribs) } } t.Logf("Actual:") for _, rdn := range dn.RDNs { for _, attribs := range rdn.Attributes { t.Logf("#%v\n", attribs) } } } } } func TestErrorDNParsing(t *testing.T) { testcases := map[string]string{ "*": "DN ended with incomplete type, value pair", "cn=Jim\\0Test": "failed to decode escaped character: encoding/hex: invalid byte: U+0054 'T'", "cn=Jim\\0": "got corrupted escaped character", "DC=example,=net": "DN ended with incomplete type, value pair", "1=#0402486": "failed to decode BER encoding: encoding/hex: odd length hex string", "test,DC=example,DC=com": "incomplete type, value pair", "=test,DC=example,DC=com": "incomplete type, value pair", } for test, answer := range testcases { _, err := ParseDN(test) if err == nil { t.Errorf("Expected %s to fail parsing but succeeded\n", test) } else if err.Error() != answer { t.Errorf("Unexpected error on %s:\n%s\nvs.\n%s\n", test, answer, err.Error()) } } } func TestDNEqual(t *testing.T) { testcases := []struct { A string B string Equal bool }{ // Exact match {"", "", true}, {"o=A", "o=A", true}, {"o=A", "o=B", false}, {"o=A,o=B", "o=A,o=B", true}, {"o=A,o=B", "o=A,o=C", false}, {"o=A+o=B", "o=A+o=B", true}, {"o=A+o=B", "o=A+o=C", false}, // Case mismatch in type is ignored {"o=A", "O=A", true}, {"o=A,o=B", "o=A,O=B", true}, {"o=A+o=B", "o=A+O=B", true}, // Case mismatch in value is significant {"o=a", "O=A", false}, {"o=a,o=B", "o=A,O=B", false}, {"o=a+o=B", "o=A+O=B", false}, // Multi-valued RDN order mismatch is ignored {"o=A+o=B", "O=B+o=A", true}, // Number of RDN attributes is significant {"o=A+o=B", "O=B+o=A+O=B", false}, // Missing values are significant {"o=A+o=B", "O=B+o=A+O=C", false}, // missing values matter {"o=A+o=B+o=C", "O=B+o=A", false}, // missing values matter // Whitespace tests // Matching { "cn=John Doe, ou=People, dc=sun.com", "cn=John Doe, ou=People, dc=sun.com", true, }, // Difference in leading/trailing chars is ignored { "cn=John Doe, ou=People, dc=sun.com", "cn=John Doe,ou=People,dc=sun.com", true, }, // Difference in values is significant { "cn=John Doe, ou=People, dc=sun.com", "cn=John Doe, ou=People, dc=sun.com", false, }, } for i, tc := range testcases { a, err := ParseDN(tc.A) if err != nil { t.Errorf("%d: %v", i, err) continue } b, err := ParseDN(tc.B) if err != nil { t.Errorf("%d: %v", i, err) continue } if expected, actual := tc.Equal, a.Equal(b); expected != actual { t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual) continue } if expected, actual := tc.Equal, b.Equal(a); expected != actual { t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual) continue } } } func TestDNEqualFold(t *testing.T) { testcases := []struct { A string B string Equal bool }{ // Match on case insensitive {"o=A", "o=a", true}, {"o=A,o=b", "o=a,o=B", true}, {"o=a+o=B", "o=A+o=b", true}, { "cn=users,ou=example,dc=com", "cn=Users,ou=example,dc=com", true, }, // Match on case insensitive and case mismatch in type {"o=A", "O=a", true}, {"o=A,o=b", "o=a,O=B", true}, {"o=a+o=B", "o=A+O=b", true}, } for i, tc := range testcases { a, err := ParseDN(tc.A) if err != nil { t.Errorf("%d: %v", i, err) continue } b, err := ParseDN(tc.B) if err != nil { t.Errorf("%d: %v", i, err) continue } if expected, actual := tc.Equal, a.EqualFold(b); expected != actual { t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual) continue } if expected, actual := tc.Equal, b.EqualFold(a); expected != actual { t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual) continue } } } func TestDNAncestor(t *testing.T) { testcases := []struct { A string B string Ancestor bool }{ // Exact match returns false {"", "", false}, {"o=A", "o=A", false}, {"o=A,o=B", "o=A,o=B", false}, {"o=A+o=B", "o=A+o=B", false}, // Mismatch {"ou=C,ou=B,o=A", "ou=E,ou=D,ou=B,o=A", false}, // Descendant {"ou=C,ou=B,o=A", "ou=E,ou=C,ou=B,o=A", true}, } for i, tc := range testcases { a, err := ParseDN(tc.A) if err != nil { t.Errorf("%d: %v", i, err) continue } b, err := ParseDN(tc.B) if err != nil { t.Errorf("%d: %v", i, err) continue } if expected, actual := tc.Ancestor, a.AncestorOf(b); expected != actual { t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual) continue } } } ldap-3.4.1/doc.go000066400000000000000000000001061410654160200135270ustar00rootroot00000000000000/* Package ldap provides basic LDAP v3 functionality. */ package ldap ldap-3.4.1/error.go000066400000000000000000000305401410654160200141200ustar00rootroot00000000000000package ldap import ( "fmt" ber "github.com/go-asn1-ber/asn1-ber" ) // LDAP Result Codes const ( LDAPResultSuccess = 0 LDAPResultOperationsError = 1 LDAPResultProtocolError = 2 LDAPResultTimeLimitExceeded = 3 LDAPResultSizeLimitExceeded = 4 LDAPResultCompareFalse = 5 LDAPResultCompareTrue = 6 LDAPResultAuthMethodNotSupported = 7 LDAPResultStrongAuthRequired = 8 LDAPResultReferral = 10 LDAPResultAdminLimitExceeded = 11 LDAPResultUnavailableCriticalExtension = 12 LDAPResultConfidentialityRequired = 13 LDAPResultSaslBindInProgress = 14 LDAPResultNoSuchAttribute = 16 LDAPResultUndefinedAttributeType = 17 LDAPResultInappropriateMatching = 18 LDAPResultConstraintViolation = 19 LDAPResultAttributeOrValueExists = 20 LDAPResultInvalidAttributeSyntax = 21 LDAPResultNoSuchObject = 32 LDAPResultAliasProblem = 33 LDAPResultInvalidDNSyntax = 34 LDAPResultIsLeaf = 35 LDAPResultAliasDereferencingProblem = 36 LDAPResultInappropriateAuthentication = 48 LDAPResultInvalidCredentials = 49 LDAPResultInsufficientAccessRights = 50 LDAPResultBusy = 51 LDAPResultUnavailable = 52 LDAPResultUnwillingToPerform = 53 LDAPResultLoopDetect = 54 LDAPResultSortControlMissing = 60 LDAPResultOffsetRangeError = 61 LDAPResultNamingViolation = 64 LDAPResultObjectClassViolation = 65 LDAPResultNotAllowedOnNonLeaf = 66 LDAPResultNotAllowedOnRDN = 67 LDAPResultEntryAlreadyExists = 68 LDAPResultObjectClassModsProhibited = 69 LDAPResultResultsTooLarge = 70 LDAPResultAffectsMultipleDSAs = 71 LDAPResultVirtualListViewErrorOrControlError = 76 LDAPResultOther = 80 LDAPResultServerDown = 81 LDAPResultLocalError = 82 LDAPResultEncodingError = 83 LDAPResultDecodingError = 84 LDAPResultTimeout = 85 LDAPResultAuthUnknown = 86 LDAPResultFilterError = 87 LDAPResultUserCanceled = 88 LDAPResultParamError = 89 LDAPResultNoMemory = 90 LDAPResultConnectError = 91 LDAPResultNotSupported = 92 LDAPResultControlNotFound = 93 LDAPResultNoResultsReturned = 94 LDAPResultMoreResultsToReturn = 95 LDAPResultClientLoop = 96 LDAPResultReferralLimitExceeded = 97 LDAPResultInvalidResponse = 100 LDAPResultAmbiguousResponse = 101 LDAPResultTLSNotSupported = 112 LDAPResultIntermediateResponse = 113 LDAPResultUnknownType = 114 LDAPResultCanceled = 118 LDAPResultNoSuchOperation = 119 LDAPResultTooLate = 120 LDAPResultCannotCancel = 121 LDAPResultAssertionFailed = 122 LDAPResultAuthorizationDenied = 123 LDAPResultSyncRefreshRequired = 4096 ErrorNetwork = 200 ErrorFilterCompile = 201 ErrorFilterDecompile = 202 ErrorDebugging = 203 ErrorUnexpectedMessage = 204 ErrorUnexpectedResponse = 205 ErrorEmptyPassword = 206 ) // LDAPResultCodeMap contains string descriptions for LDAP error codes var LDAPResultCodeMap = map[uint16]string{ LDAPResultSuccess: "Success", LDAPResultOperationsError: "Operations Error", LDAPResultProtocolError: "Protocol Error", LDAPResultTimeLimitExceeded: "Time Limit Exceeded", LDAPResultSizeLimitExceeded: "Size Limit Exceeded", LDAPResultCompareFalse: "Compare False", LDAPResultCompareTrue: "Compare True", LDAPResultAuthMethodNotSupported: "Auth Method Not Supported", LDAPResultStrongAuthRequired: "Strong Auth Required", LDAPResultReferral: "Referral", LDAPResultAdminLimitExceeded: "Admin Limit Exceeded", LDAPResultUnavailableCriticalExtension: "Unavailable Critical Extension", LDAPResultConfidentialityRequired: "Confidentiality Required", LDAPResultSaslBindInProgress: "Sasl Bind In Progress", LDAPResultNoSuchAttribute: "No Such Attribute", LDAPResultUndefinedAttributeType: "Undefined Attribute Type", LDAPResultInappropriateMatching: "Inappropriate Matching", LDAPResultConstraintViolation: "Constraint Violation", LDAPResultAttributeOrValueExists: "Attribute Or Value Exists", LDAPResultInvalidAttributeSyntax: "Invalid Attribute Syntax", LDAPResultNoSuchObject: "No Such Object", LDAPResultAliasProblem: "Alias Problem", LDAPResultInvalidDNSyntax: "Invalid DN Syntax", LDAPResultIsLeaf: "Is Leaf", LDAPResultAliasDereferencingProblem: "Alias Dereferencing Problem", LDAPResultInappropriateAuthentication: "Inappropriate Authentication", LDAPResultInvalidCredentials: "Invalid Credentials", LDAPResultInsufficientAccessRights: "Insufficient Access Rights", LDAPResultBusy: "Busy", LDAPResultUnavailable: "Unavailable", LDAPResultUnwillingToPerform: "Unwilling To Perform", LDAPResultLoopDetect: "Loop Detect", LDAPResultSortControlMissing: "Sort Control Missing", LDAPResultOffsetRangeError: "Result Offset Range Error", LDAPResultNamingViolation: "Naming Violation", LDAPResultObjectClassViolation: "Object Class Violation", LDAPResultResultsTooLarge: "Results Too Large", LDAPResultNotAllowedOnNonLeaf: "Not Allowed On Non Leaf", LDAPResultNotAllowedOnRDN: "Not Allowed On RDN", LDAPResultEntryAlreadyExists: "Entry Already Exists", LDAPResultObjectClassModsProhibited: "Object Class Mods Prohibited", LDAPResultAffectsMultipleDSAs: "Affects Multiple DSAs", LDAPResultVirtualListViewErrorOrControlError: "Failed because of a problem related to the virtual list view", LDAPResultOther: "Other", LDAPResultServerDown: "Cannot establish a connection", LDAPResultLocalError: "An error occurred", LDAPResultEncodingError: "LDAP encountered an error while encoding", LDAPResultDecodingError: "LDAP encountered an error while decoding", LDAPResultTimeout: "LDAP timeout while waiting for a response from the server", LDAPResultAuthUnknown: "The auth method requested in a bind request is unknown", LDAPResultFilterError: "An error occurred while encoding the given search filter", LDAPResultUserCanceled: "The user canceled the operation", LDAPResultParamError: "An invalid parameter was specified", LDAPResultNoMemory: "Out of memory error", LDAPResultConnectError: "A connection to the server could not be established", LDAPResultNotSupported: "An attempt has been made to use a feature not supported LDAP", LDAPResultControlNotFound: "The controls required to perform the requested operation were not found", LDAPResultNoResultsReturned: "No results were returned from the server", LDAPResultMoreResultsToReturn: "There are more results in the chain of results", LDAPResultClientLoop: "A loop has been detected. For example when following referrals", LDAPResultReferralLimitExceeded: "The referral hop limit has been exceeded", LDAPResultCanceled: "Operation was canceled", LDAPResultNoSuchOperation: "Server has no knowledge of the operation requested for cancellation", LDAPResultTooLate: "Too late to cancel the outstanding operation", LDAPResultCannotCancel: "The identified operation does not support cancellation or the cancel operation cannot be performed", LDAPResultAssertionFailed: "An assertion control given in the LDAP operation evaluated to false causing the operation to not be performed", LDAPResultSyncRefreshRequired: "Refresh Required", LDAPResultInvalidResponse: "Invalid Response", LDAPResultAmbiguousResponse: "Ambiguous Response", LDAPResultTLSNotSupported: "Tls Not Supported", LDAPResultIntermediateResponse: "Intermediate Response", LDAPResultUnknownType: "Unknown Type", LDAPResultAuthorizationDenied: "Authorization Denied", ErrorNetwork: "Network Error", ErrorFilterCompile: "Filter Compile Error", ErrorFilterDecompile: "Filter Decompile Error", ErrorDebugging: "Debugging Error", ErrorUnexpectedMessage: "Unexpected Message", ErrorUnexpectedResponse: "Unexpected Response", ErrorEmptyPassword: "Empty password not allowed by the client", } // Error holds LDAP error information type Error struct { // Err is the underlying error Err error // ResultCode is the LDAP error code ResultCode uint16 // MatchedDN is the matchedDN returned if any MatchedDN string // Packet is the returned packet if any Packet *ber.Packet } func (e *Error) Error() string { return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error()) } // GetLDAPError creates an Error out of a BER packet representing a LDAPResult // The return is an error object. It can be casted to a Error structure. // This function returns nil if resultCode in the LDAPResult sequence is success(0). func GetLDAPError(packet *ber.Packet) error { if packet == nil { return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("Empty packet")} } if len(packet.Children) >= 2 { response := packet.Children[1] if response == nil { return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("Empty response in packet"), Packet: packet} } if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 { resultCode := uint16(response.Children[0].Value.(int64)) if resultCode == 0 { // No error return nil } return &Error{ ResultCode: resultCode, MatchedDN: response.Children[1].Value.(string), Err: fmt.Errorf("%s", response.Children[2].Value.(string)), Packet: packet, } } } return &Error{ResultCode: ErrorNetwork, Err: fmt.Errorf("Invalid packet format"), Packet: packet} } // NewError creates an LDAP error with the given code and underlying error func NewError(resultCode uint16, err error) error { return &Error{ResultCode: resultCode, Err: err} } // IsErrorAnyOf returns true if the given error is an LDAP error with any one of the given result codes func IsErrorAnyOf(err error, codes ...uint16) bool { if err == nil { return false } serverError, ok := err.(*Error) if !ok { return false } for _, code := range codes { if serverError.ResultCode == code { return true } } return false } // IsErrorWithCode returns true if the given error is an LDAP error with the given result code func IsErrorWithCode(err error, desiredResultCode uint16) bool { return IsErrorAnyOf(err, desiredResultCode) } ldap-3.4.1/error_test.go000066400000000000000000000116021410654160200151550ustar00rootroot00000000000000package ldap import ( "errors" "net" "strings" "testing" "time" ber "github.com/go-asn1-ber/asn1-ber" ) // TestNilPacket tests that nil packets don't cause a panic. func TestNilPacket(t *testing.T) { // Test for nil packet err := GetLDAPError(nil) if !IsErrorWithCode(err, ErrorUnexpectedResponse) { t.Errorf("Should have an 'ErrorUnexpectedResponse' error in nil packets, got: %v", err) } // Test for nil result kids := []*ber.Packet{ {}, // Unused nil, // Can't be nil } pack := &ber.Packet{Children: kids} err = GetLDAPError(pack) if !IsErrorWithCode(err, ErrorUnexpectedResponse) { t.Errorf("Should have an 'ErrorUnexpectedResponse' error in nil packets, got: %v", err) } } // TestConnReadErr tests that an unexpected error reading from underlying // connection bubbles up to the goroutine which makes a request. func TestConnReadErr(t *testing.T) { conn := &signalErrConn{ signals: make(chan error), } ldapConn := NewConn(conn, false) ldapConn.Start() // Make a dummy search request. searchReq := NewSearchRequest("dc=example,dc=com", ScopeWholeSubtree, DerefAlways, 0, 0, false, "(objectClass=*)", nil, nil) expectedError := errors.New("this is the error you are looking for") // Send the signal after a short amount of time. time.AfterFunc(10*time.Millisecond, func() { conn.signals <- expectedError }) // This should block until the underlying conn gets the error signal // which should bubble up through the reader() goroutine, close the // connection, and _, err := ldapConn.Search(searchReq) if err == nil || !strings.Contains(err.Error(), expectedError.Error()) { t.Errorf("not the expected error: %s", err) } } // TestGetLDAPError tests parsing of result with a error response. func TestGetLDAPError(t *testing.T) { diagnosticMessage := "Detailed error message" bindResponse := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindResponse, nil, "Bind Response") bindResponse.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(LDAPResultInvalidCredentials), "resultCode")) bindResponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "dc=example,dc=org", "matchedDN")) bindResponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, diagnosticMessage, "diagnosticMessage")) packet := ber.NewSequence("LDAPMessage") packet.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(0), "messageID")) packet.AppendChild(bindResponse) err := GetLDAPError(packet) if err == nil { t.Errorf("Did not get error response") } ldapError := err.(*Error) if ldapError.ResultCode != LDAPResultInvalidCredentials { t.Errorf("Got incorrect error code in LDAP error; got %v, expected %v", ldapError.ResultCode, LDAPResultInvalidCredentials) } if ldapError.Err.Error() != diagnosticMessage { t.Errorf("Got incorrect error message in LDAP error; got %v, expected %v", ldapError.Err.Error(), diagnosticMessage) } } // TestGetLDAPErrorSuccess tests parsing of a result with no error (resultCode == 0). func TestGetLDAPErrorSuccess(t *testing.T) { bindResponse := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindResponse, nil, "Bind Response") bindResponse.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(0), "resultCode")) bindResponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "matchedDN")) bindResponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "diagnosticMessage")) packet := ber.NewSequence("LDAPMessage") packet.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(0), "messageID")) packet.AppendChild(bindResponse) err := GetLDAPError(packet) if err != nil { t.Errorf("Successful responses should not produce an error, but got: %v", err) } } // signalErrConn is a helpful type used with TestConnReadErr. It implements the // net.Conn interface to be used as a connection for the test. Most methods are // no-ops but the Read() method blocks until it receives a signal which it // returns as an error. type signalErrConn struct { signals chan error } // Read blocks until an error is sent on the internal signals channel. That // error is returned. func (c *signalErrConn) Read(b []byte) (n int, err error) { return 0, <-c.signals } func (c *signalErrConn) Write(b []byte) (n int, err error) { return len(b), nil } func (c *signalErrConn) Close() error { close(c.signals) return nil } func (c *signalErrConn) LocalAddr() net.Addr { return (*net.TCPAddr)(nil) } func (c *signalErrConn) RemoteAddr() net.Addr { return (*net.TCPAddr)(nil) } func (c *signalErrConn) SetDeadline(t time.Time) error { return nil } func (c *signalErrConn) SetReadDeadline(t time.Time) error { return nil } func (c *signalErrConn) SetWriteDeadline(t time.Time) error { return nil } ldap-3.4.1/examples_moddn_test.go000066400000000000000000000043331410654160200170260ustar00rootroot00000000000000package ldap import ( "log" ) // This example shows how to rename an entry without moving it func ExampleConn_ModifyDN_renameNoMove() { conn, err := DialURL("ldap://ldap.example.org:389") if err != nil { log.Fatalf("Failed to connect: %s\n", err) } defer conn.Close() _, err = conn.SimpleBind(&SimpleBindRequest{ Username: "uid=someone,ou=people,dc=example,dc=org", Password: "MySecretPass", }) if err != nil { log.Fatalf("Failed to bind: %s\n", err) } // just rename to uid=new,ou=people,dc=example,dc=org: req := NewModifyDNRequest("uid=user,ou=people,dc=example,dc=org", "uid=new", true, "") if err = conn.ModifyDN(req); err != nil { log.Fatalf("Failed to call ModifyDN(): %s\n", err) } } // This example shows how to rename an entry and moving it to a new base func ExampleConn_ModifyDN_renameAndMove() { conn, err := DialURL("ldap://ldap.example.org:389") if err != nil { log.Fatalf("Failed to connect: %s\n", err) } defer conn.Close() _, err = conn.SimpleBind(&SimpleBindRequest{ Username: "uid=someone,ou=people,dc=example,dc=org", Password: "MySecretPass", }) if err != nil { log.Fatalf("Failed to bind: %s\n", err) } // rename to uid=new,ou=people,dc=example,dc=org and move to ou=users,dc=example,dc=org -> // uid=new,ou=users,dc=example,dc=org req := NewModifyDNRequest("uid=user,ou=people,dc=example,dc=org", "uid=new", true, "ou=users,dc=example,dc=org") if err = conn.ModifyDN(req); err != nil { log.Fatalf("Failed to call ModifyDN(): %s\n", err) } } // This example shows how to move an entry to a new base without renaming the RDN func ExampleConn_ModifyDN_moveOnly() { conn, err := DialURL("ldap://ldap.example.org:389") if err != nil { log.Fatalf("Failed to connect: %s\n", err) } defer conn.Close() _, err = conn.SimpleBind(&SimpleBindRequest{ Username: "uid=someone,ou=people,dc=example,dc=org", Password: "MySecretPass", }) if err != nil { log.Fatalf("Failed to bind: %s\n", err) } // move to ou=users,dc=example,dc=org -> uid=user,ou=users,dc=example,dc=org req := NewModifyDNRequest("uid=user,ou=people,dc=example,dc=org", "uid=user", true, "ou=users,dc=example,dc=org") if err = conn.ModifyDN(req); err != nil { log.Fatalf("Failed to call ModifyDN(): %s\n", err) } } ldap-3.4.1/examples_test.go000066400000000000000000000245601410654160200156510ustar00rootroot00000000000000package ldap import ( "crypto/tls" "crypto/x509" "fmt" "io/ioutil" "log" ) // This example demonstrates how to bind a connection to an ldap user // allowing access to restricted attributes that user has access to func ExampleConn_Bind() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() err = l.Bind("cn=read-only-admin,dc=example,dc=com", "password") if err != nil { log.Fatal(err) } } // This example demonstrates how to use the search interface func ExampleConn_Search() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() searchRequest := NewSearchRequest( "dc=example,dc=com", // The base dn to search ScopeWholeSubtree, NeverDerefAliases, 0, 0, false, "(&(objectClass=organizationalPerson))", // The filter to apply []string{"dn", "cn"}, // A list attributes to retrieve nil, ) sr, err := l.Search(searchRequest) if err != nil { log.Fatal(err) } for _, entry := range sr.Entries { fmt.Printf("%s: %v\n", entry.DN, entry.GetAttributeValue("cn")) } } // This example demonstrates how to start a TLS connection func ExampleConn_StartTLS() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() // Reconnect with TLS err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { log.Fatal(err) } // Operations via l are now encrypted } // This example demonstrates how to compare an attribute with a value func ExampleConn_Compare() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() matched, err := l.Compare("cn=user,dc=example,dc=com", "uid", "someuserid") if err != nil { log.Fatal(err) } fmt.Println(matched) } func ExampleConn_PasswordModify_admin() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() err = l.Bind("cn=admin,dc=example,dc=com", "password") if err != nil { log.Fatal(err) } passwordModifyRequest := NewPasswordModifyRequest("cn=user,dc=example,dc=com", "", "NewPassword") _, err = l.PasswordModify(passwordModifyRequest) if err != nil { log.Fatalf("Password could not be changed: %s", err.Error()) } } func ExampleConn_PasswordModify_generatedPassword() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() err = l.Bind("cn=user,dc=example,dc=com", "password") if err != nil { log.Fatal(err) } passwordModifyRequest := NewPasswordModifyRequest("", "OldPassword", "") passwordModifyResponse, err := l.PasswordModify(passwordModifyRequest) if err != nil { log.Fatalf("Password could not be changed: %s", err.Error()) } generatedPassword := passwordModifyResponse.GeneratedPassword log.Printf("Generated password: %s\n", generatedPassword) } func ExampleConn_PasswordModify_setNewPassword() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() err = l.Bind("cn=user,dc=example,dc=com", "password") if err != nil { log.Fatal(err) } passwordModifyRequest := NewPasswordModifyRequest("", "OldPassword", "NewPassword") _, err = l.PasswordModify(passwordModifyRequest) if err != nil { log.Fatalf("Password could not be changed: %s", err.Error()) } } func ExampleConn_Modify() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() // Add a description, and replace the mail attributes modify := NewModifyRequest("cn=user,dc=example,dc=com", nil) modify.Add("description", []string{"An example user"}) modify.Replace("mail", []string{"user@example.org"}) err = l.Modify(modify) if err != nil { log.Fatal(err) } } // Example_userAuthentication shows how a typical application can verify a login attempt // Refer to https://github.com/go-ldap/ldap/issues/93 for issues revolving around unauthenticated binds, with zero length passwords func Example_userAuthentication() { // The username and password we want to check username := "someuser" password := "userpassword" bindusername := "readonly" bindpassword := "password" l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() // Reconnect with TLS err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { log.Fatal(err) } // First bind with a read only user err = l.Bind(bindusername, bindpassword) if err != nil { log.Fatal(err) } // Search for the given username searchRequest := NewSearchRequest( "dc=example,dc=com", ScopeWholeSubtree, NeverDerefAliases, 0, 0, false, fmt.Sprintf("(&(objectClass=organizationalPerson)(uid=%s))", EscapeFilter(username)), []string{"dn"}, nil, ) sr, err := l.Search(searchRequest) if err != nil { log.Fatal(err) } if len(sr.Entries) != 1 { log.Fatal("User does not exist or too many entries returned") } userdn := sr.Entries[0].DN // Bind as the user to verify their password err = l.Bind(userdn, password) if err != nil { log.Fatal(err) } // Rebind as the read only user for any further queries err = l.Bind(bindusername, bindpassword) if err != nil { log.Fatal(err) } } func Example_beherappolicy() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() controls := []Control{} controls = append(controls, NewControlBeheraPasswordPolicy()) bindRequest := NewSimpleBindRequest("cn=admin,dc=example,dc=com", "password", controls) r, err := l.SimpleBind(bindRequest) ppolicyControl := FindControl(r.Controls, ControlTypeBeheraPasswordPolicy) var ppolicy *ControlBeheraPasswordPolicy if ppolicyControl != nil { ppolicy = ppolicyControl.(*ControlBeheraPasswordPolicy) } else { log.Printf("ppolicyControl response not available.\n") } if err != nil { errStr := "ERROR: Cannot bind: " + err.Error() if ppolicy != nil && ppolicy.Error >= 0 { errStr += ":" + ppolicy.ErrorString } log.Print(errStr) } else { logStr := "Login Ok" if ppolicy != nil { if ppolicy.Expire >= 0 { logStr += fmt.Sprintf(". Password expires in %d seconds\n", ppolicy.Expire) } else if ppolicy.Grace >= 0 { logStr += fmt.Sprintf(". Password expired, %d grace logins remain\n", ppolicy.Grace) } } log.Print(logStr) } } func Example_vchuppolicy() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() l.Debug = true bindRequest := NewSimpleBindRequest("cn=admin,dc=example,dc=com", "password", nil) r, err := l.SimpleBind(bindRequest) passwordMustChangeControl := FindControl(r.Controls, ControlTypeVChuPasswordMustChange) var passwordMustChange *ControlVChuPasswordMustChange if passwordMustChangeControl != nil { passwordMustChange = passwordMustChangeControl.(*ControlVChuPasswordMustChange) } if passwordMustChange != nil && passwordMustChange.MustChange { log.Printf("Password Must be changed.\n") } passwordWarningControl := FindControl(r.Controls, ControlTypeVChuPasswordWarning) var passwordWarning *ControlVChuPasswordWarning if passwordWarningControl != nil { passwordWarning = passwordWarningControl.(*ControlVChuPasswordWarning) } else { log.Printf("ppolicyControl response not available.\n") } if err != nil { log.Print("ERROR: Cannot bind: " + err.Error()) } else { logStr := "Login Ok" if passwordWarning != nil { if passwordWarning.Expire >= 0 { logStr += fmt.Sprintf(". Password expires in %d seconds\n", passwordWarning.Expire) } } log.Print(logStr) } } // This example demonstrates how to use ControlPaging to manually execute a // paginated search request instead of using SearchWithPaging. func ExampleControlPaging_manualPaging() { conn, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer conn.Close() var pageSize uint32 = 32 searchBase := "dc=example,dc=com" filter := "(objectClass=group)" pagingControl := NewControlPaging(pageSize) attributes := []string{} controls := []Control{pagingControl} for { request := NewSearchRequest(searchBase, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter, attributes, controls) response, err := conn.Search(request) if err != nil { log.Fatalf("Failed to execute search request: %s", err.Error()) } // [do something with the response entries] // In order to prepare the next request, we check if the response // contains another ControlPaging object and a not-empty cookie and // copy that cookie into our pagingControl object: updatedControl := FindControl(response.Controls, ControlTypePaging) if ctrl, ok := updatedControl.(*ControlPaging); ctrl != nil && ok && len(ctrl.Cookie) != 0 { pagingControl.SetCookie(ctrl.Cookie) continue } // If no new paging information is available or the cookie is empty, we // are done with the pagination. break } } // This example demonstrates how to use EXTERNAL SASL with TLS client certificates. func ExampleConn_ExternalBind() { var ldapCert = "/path/to/cert.pem" var ldapKey = "/path/to/key.pem" var ldapCAchain = "/path/to/ca_chain.pem" // Load client cert and key cert, err := tls.LoadX509KeyPair(ldapCert, ldapKey) if err != nil { log.Fatal(err) } // Load CA chain caCert, err := ioutil.ReadFile(ldapCAchain) if err != nil { log.Fatal(err) } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) // Setup TLS with ldap client cert tlsConfig := &tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: caCertPool, InsecureSkipVerify: true, } // connect to ldap server l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() // reconnect using tls err = l.StartTLS(tlsConfig) if err != nil { log.Fatal(err) } // sasl external bind err = l.ExternalBind() if err != nil { log.Fatal(err) } // Conduct ldap queries } // ExampleConn_WhoAmI demonstrates how to run a whoami request according to https://tools.ietf.org/html/rfc4532 func ExampleConn_WhoAmI() { conn, err := DialURL("ldap.example.org:389") if err != nil { log.Fatalf("Failed to connect: %s\n", err) } _, err = conn.SimpleBind(&SimpleBindRequest{ Username: "uid=someone,ou=people,dc=example,dc=org", Password: "MySecretPass", }) if err != nil { log.Fatalf("Failed to bind: %s\n", err) } res, err := conn.WhoAmI(nil) if err != nil { log.Fatalf("Failed to call WhoAmI(): %s\n", err) } fmt.Printf("I am: %s\n", res.AuthzID) } ldap-3.4.1/filter.go000066400000000000000000000374231410654160200142630ustar00rootroot00000000000000package ldap import ( "bytes" hexpac "encoding/hex" "errors" "fmt" "io" "strings" "unicode" "unicode/utf8" ber "github.com/go-asn1-ber/asn1-ber" ) // Filter choices const ( FilterAnd = 0 FilterOr = 1 FilterNot = 2 FilterEqualityMatch = 3 FilterSubstrings = 4 FilterGreaterOrEqual = 5 FilterLessOrEqual = 6 FilterPresent = 7 FilterApproxMatch = 8 FilterExtensibleMatch = 9 ) // FilterMap contains human readable descriptions of Filter choices var FilterMap = map[uint64]string{ FilterAnd: "And", FilterOr: "Or", FilterNot: "Not", FilterEqualityMatch: "Equality Match", FilterSubstrings: "Substrings", FilterGreaterOrEqual: "Greater Or Equal", FilterLessOrEqual: "Less Or Equal", FilterPresent: "Present", FilterApproxMatch: "Approx Match", FilterExtensibleMatch: "Extensible Match", } // SubstringFilter options const ( FilterSubstringsInitial = 0 FilterSubstringsAny = 1 FilterSubstringsFinal = 2 ) // FilterSubstringsMap contains human readable descriptions of SubstringFilter choices var FilterSubstringsMap = map[uint64]string{ FilterSubstringsInitial: "Substrings Initial", FilterSubstringsAny: "Substrings Any", FilterSubstringsFinal: "Substrings Final", } // MatchingRuleAssertion choices const ( MatchingRuleAssertionMatchingRule = 1 MatchingRuleAssertionType = 2 MatchingRuleAssertionMatchValue = 3 MatchingRuleAssertionDNAttributes = 4 ) // MatchingRuleAssertionMap contains human readable descriptions of MatchingRuleAssertion choices var MatchingRuleAssertionMap = map[uint64]string{ MatchingRuleAssertionMatchingRule: "Matching Rule Assertion Matching Rule", MatchingRuleAssertionType: "Matching Rule Assertion Type", MatchingRuleAssertionMatchValue: "Matching Rule Assertion Match Value", MatchingRuleAssertionDNAttributes: "Matching Rule Assertion DN Attributes", } var _SymbolAny = []byte{'*'} // CompileFilter converts a string representation of a filter into a BER-encoded packet func CompileFilter(filter string) (*ber.Packet, error) { if len(filter) == 0 || filter[0] != '(' { return nil, NewError(ErrorFilterCompile, errors.New("ldap: filter does not start with an '('")) } packet, pos, err := compileFilter(filter, 1) if err != nil { return nil, err } switch { case pos > len(filter): return nil, NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter")) case pos < len(filter): return nil, NewError(ErrorFilterCompile, errors.New("ldap: finished compiling filter with extra at end: "+fmt.Sprint(filter[pos:]))) } return packet, nil } // DecompileFilter converts a packet representation of a filter into a string representation func DecompileFilter(packet *ber.Packet) (_ string, err error) { defer func() { if r := recover(); r != nil { err = NewError(ErrorFilterDecompile, errors.New("ldap: error decompiling filter")) } }() buf := bytes.NewBuffer(nil) buf.WriteByte('(') childStr := "" switch packet.Tag { case FilterAnd: buf.WriteByte('&') for _, child := range packet.Children { childStr, err = DecompileFilter(child) if err != nil { return } buf.WriteString(childStr) } case FilterOr: buf.WriteByte('|') for _, child := range packet.Children { childStr, err = DecompileFilter(child) if err != nil { return } buf.WriteString(childStr) } case FilterNot: buf.WriteByte('!') childStr, err = DecompileFilter(packet.Children[0]) if err != nil { return } buf.WriteString(childStr) case FilterSubstrings: buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes())) buf.WriteByte('=') for i, child := range packet.Children[1].Children { if i == 0 && child.Tag != FilterSubstringsInitial { buf.Write(_SymbolAny) } buf.WriteString(EscapeFilter(ber.DecodeString(child.Data.Bytes()))) if child.Tag != FilterSubstringsFinal { buf.Write(_SymbolAny) } } case FilterEqualityMatch: buf.WriteString(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(">=") buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))) case FilterLessOrEqual: buf.WriteString(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("=*") case FilterApproxMatch: buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes())) buf.WriteString("~=") buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))) case FilterExtensibleMatch: attr := "" dnAttributes := false matchingRule := "" value := "" for _, child := range packet.Children { switch child.Tag { case MatchingRuleAssertionMatchingRule: matchingRule = ber.DecodeString(child.Data.Bytes()) case MatchingRuleAssertionType: attr = ber.DecodeString(child.Data.Bytes()) case MatchingRuleAssertionMatchValue: value = ber.DecodeString(child.Data.Bytes()) case MatchingRuleAssertionDNAttributes: dnAttributes = child.Value.(bool) } } if len(attr) > 0 { buf.WriteString(attr) } if dnAttributes { buf.WriteString(":dn") } if len(matchingRule) > 0 { buf.WriteString(":") buf.WriteString(matchingRule) } buf.WriteString(":=") buf.WriteString(EscapeFilter(value)) } buf.WriteByte(')') return buf.String(), nil } func compileFilterSet(filter string, pos int, parent *ber.Packet) (int, error) { for pos < len(filter) && filter[pos] == '(' { child, newPos, err := compileFilter(filter, pos+1) if err != nil { return pos, err } pos = newPos parent.AppendChild(child) } if pos == len(filter) { return pos, NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter")) } return pos + 1, nil } func compileFilter(filter string, pos int) (*ber.Packet, int, error) { var ( packet *ber.Packet err error ) defer func() { if r := recover(); r != nil { err = NewError(ErrorFilterCompile, errors.New("ldap: error compiling filter")) } }() newPos := pos currentRune, currentWidth := utf8.DecodeRuneInString(filter[newPos:]) switch currentRune { case utf8.RuneError: return nil, 0, NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", newPos)) case '(': packet, newPos, err = compileFilter(filter, pos+currentWidth) newPos++ return packet, newPos, err case '&': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterAnd, nil, FilterMap[FilterAnd]) newPos, err = compileFilterSet(filter, pos+currentWidth, packet) return packet, newPos, err case '|': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterOr, nil, FilterMap[FilterOr]) newPos, err = compileFilterSet(filter, pos+currentWidth, packet) return packet, newPos, err case '!': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterNot, nil, FilterMap[FilterNot]) var child *ber.Packet child, newPos, err = compileFilter(filter, pos+currentWidth) packet.AppendChild(child) return packet, newPos, err default: const ( stateReadingAttr = 0 stateReadingExtensibleMatchingRule = 1 stateReadingCondition = 2 ) state := stateReadingAttr attribute := bytes.NewBuffer(nil) extensibleDNAttributes := false extensibleMatchingRule := bytes.NewBuffer(nil) condition := bytes.NewBuffer(nil) for newPos < len(filter) { remainingFilter := filter[newPos:] currentRune, currentWidth = utf8.DecodeRuneInString(remainingFilter) if currentRune == ')' { break } if currentRune == utf8.RuneError { return packet, newPos, NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", newPos)) } switch state { case stateReadingAttr: switch { // Extensible rule, with only DN-matching case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) extensibleDNAttributes = true state = stateReadingCondition newPos += 5 // Extensible rule, with DN-matching and a matching OID case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:"): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) extensibleDNAttributes = true state = stateReadingExtensibleMatchingRule newPos += 4 // Extensible rule, with attr only case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) state = stateReadingCondition newPos += 2 // Extensible rule, with no DN attribute matching case currentRune == ':': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) state = stateReadingExtensibleMatchingRule newPos++ // Equality condition case currentRune == '=': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch]) state = stateReadingCondition newPos++ // Greater-than or equal case currentRune == '>' && strings.HasPrefix(remainingFilter, ">="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual]) state = stateReadingCondition newPos += 2 // Less-than or equal case currentRune == '<' && strings.HasPrefix(remainingFilter, "<="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual]) state = stateReadingCondition newPos += 2 // Approx case currentRune == '~' && strings.HasPrefix(remainingFilter, "~="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterApproxMatch]) state = stateReadingCondition newPos += 2 // Still reading the attribute name default: attribute.WriteRune(currentRune) newPos += currentWidth } case stateReadingExtensibleMatchingRule: switch { // Matching rule OID is done case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="): state = stateReadingCondition newPos += 2 // Still reading the matching rule oid default: extensibleMatchingRule.WriteRune(currentRune) newPos += currentWidth } case stateReadingCondition: // append to the condition condition.WriteRune(currentRune) newPos += currentWidth } } if newPos == len(filter) { err = NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter")) return packet, newPos, err } if packet == nil { err = NewError(ErrorFilterCompile, errors.New("ldap: error parsing filter")) return packet, newPos, err } switch { case packet.Tag == FilterExtensibleMatch: // MatchingRuleAssertion ::= SEQUENCE { // matchingRule [1] MatchingRuleID OPTIONAL, // type [2] AttributeDescription OPTIONAL, // matchValue [3] AssertionValue, // dnAttributes [4] BOOLEAN DEFAULT FALSE // } // Include the matching rule oid, if specified if extensibleMatchingRule.Len() > 0 { packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchingRule, extensibleMatchingRule.String(), MatchingRuleAssertionMap[MatchingRuleAssertionMatchingRule])) } // Include the attribute, if specified if attribute.Len() > 0 { packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionType, attribute.String(), MatchingRuleAssertionMap[MatchingRuleAssertionType])) } // Add the value (only required child) encodedString, encodeErr := decodeEscapedSymbols(condition.Bytes()) if encodeErr != nil { return packet, newPos, encodeErr } packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchValue, encodedString, MatchingRuleAssertionMap[MatchingRuleAssertionMatchValue])) // Defaults to false, so only include in the sequence if true if extensibleDNAttributes { packet.AppendChild(ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionDNAttributes, extensibleDNAttributes, MatchingRuleAssertionMap[MatchingRuleAssertionDNAttributes])) } case packet.Tag == FilterEqualityMatch && bytes.Equal(condition.Bytes(), _SymbolAny): packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute.String(), FilterMap[FilterPresent]) case packet.Tag == FilterEqualityMatch && bytes.Index(condition.Bytes(), _SymbolAny) > -1: packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute.String(), "Attribute")) packet.Tag = FilterSubstrings packet.Description = FilterMap[uint64(packet.Tag)] seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings") parts := bytes.Split(condition.Bytes(), _SymbolAny) for i, part := range parts { if len(part) == 0 { continue } var tag ber.Tag switch i { case 0: tag = FilterSubstringsInitial case len(parts) - 1: tag = FilterSubstringsFinal default: tag = FilterSubstringsAny } encodedString, encodeErr := decodeEscapedSymbols(part) if encodeErr != nil { return packet, newPos, encodeErr } seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, tag, encodedString, FilterSubstringsMap[uint64(tag)])) } packet.AppendChild(seq) default: encodedString, encodeErr := decodeEscapedSymbols(condition.Bytes()) if encodeErr != nil { return packet, newPos, encodeErr } packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute.String(), "Attribute")) packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, encodedString, "Condition")) } newPos += currentWidth return packet, newPos, err } } // Convert from "ABC\xx\xx\xx" form to literal bytes for transport func decodeEscapedSymbols(src []byte) (string, error) { var ( buffer bytes.Buffer offset int reader = bytes.NewReader(src) byteHex []byte byteVal []byte ) for { runeVal, runeSize, err := reader.ReadRune() if err == io.EOF { return buffer.String(), nil } else if err != nil { return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: failed to read filter: %v", err)) } else if runeVal == unicode.ReplacementChar { return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", offset)) } if runeVal == '\\' { // http://tools.ietf.org/search/rfc4515 // \ (%x5C) is not a valid character unless it is followed by two HEX characters due to not // being a member of UTF1SUBSET. if byteHex == nil { byteHex = make([]byte, 2) byteVal = make([]byte, 1) } if _, err := io.ReadFull(reader, byteHex); err != nil { if err == io.ErrUnexpectedEOF { return "", NewError(ErrorFilterCompile, errors.New("ldap: missing characters for escape in filter")) } return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: invalid characters for escape in filter: %v", err)) } if _, err := hexpac.Decode(byteVal, byteHex); err != nil { return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: invalid characters for escape in filter: %v", err)) } buffer.Write(byteVal) } else { buffer.WriteRune(runeVal) } offset += runeSize } } ldap-3.4.1/filter_test.go000066400000000000000000000170231410654160200153140ustar00rootroot00000000000000package ldap import ( "strings" "testing" ber "github.com/go-asn1-ber/asn1-ber" ) type compileTest struct { filterStr string expectedFilter string expectedType int expectedErr string } var testFilters = []compileTest{ { filterStr: "(&(sn=Miller)(givenName=Bob))", expectedFilter: "(&(sn=Miller)(givenName=Bob))", expectedType: FilterAnd, }, { filterStr: "(|(sn=Miller)(givenName=Bob))", expectedFilter: "(|(sn=Miller)(givenName=Bob))", expectedType: FilterOr, }, { filterStr: "(!(sn=Miller))", expectedFilter: "(!(sn=Miller))", expectedType: FilterNot, }, { filterStr: "(sn=Miller)", expectedFilter: "(sn=Miller)", expectedType: FilterEqualityMatch, }, { filterStr: "(sn=Mill*)", expectedFilter: "(sn=Mill*)", expectedType: FilterSubstrings, }, { filterStr: "(sn=*Mill)", expectedFilter: "(sn=*Mill)", expectedType: FilterSubstrings, }, { filterStr: "(sn=*Mill*)", expectedFilter: "(sn=*Mill*)", expectedType: FilterSubstrings, }, { filterStr: "(sn=*i*le*)", expectedFilter: "(sn=*i*le*)", expectedType: FilterSubstrings, }, { filterStr: "(sn=Mi*l*r)", expectedFilter: "(sn=Mi*l*r)", expectedType: FilterSubstrings, }, // substring filters escape properly { filterStr: `(sn=Mi*함*r)`, expectedFilter: `(sn=Mi*\ed\95\a8*r)`, expectedType: FilterSubstrings, }, // already escaped substring filters don't get double-escaped { filterStr: `(sn=Mi*\ed\95\a8*r)`, expectedFilter: `(sn=Mi*\ed\95\a8*r)`, expectedType: FilterSubstrings, }, { filterStr: "(sn=Mi*le*)", expectedFilter: "(sn=Mi*le*)", expectedType: FilterSubstrings, }, { filterStr: "(sn=*i*ler)", expectedFilter: "(sn=*i*ler)", expectedType: FilterSubstrings, }, { filterStr: "(sn>=Miller)", expectedFilter: "(sn>=Miller)", expectedType: FilterGreaterOrEqual, }, { filterStr: "(sn<=Miller)", expectedFilter: "(sn<=Miller)", expectedType: FilterLessOrEqual, }, { filterStr: "(sn=*)", expectedFilter: "(sn=*)", expectedType: FilterPresent, }, { filterStr: "(sn~=Miller)", expectedFilter: "(sn~=Miller)", expectedType: FilterApproxMatch, }, { filterStr: `(objectGUID='\fc\fe\a3\ab\f9\90N\aaGm\d5I~\d12)`, expectedFilter: `(objectGUID='\fc\fe\a3\ab\f9\90N\aaGm\d5I~\d12)`, expectedType: FilterEqualityMatch, }, { filterStr: `(objectGUID=абвгдеёжзийклмнопрстуфхцчшщъыьэюя)`, expectedFilter: `(objectGUID=\d0\b0\d0\b1\d0\b2\d0\b3\d0\b4\d0\b5\d1\91\d0\b6\d0\b7\d0\b8\d0\b9\d0\ba\d0\bb\d0\bc\d0\bd\d0\be\d0\bf\d1\80\d1\81\d1\82\d1\83\d1\84\d1\85\d1\86\d1\87\d1\88\d1\89\d1\8a\d1\8b\d1\8c\d1\8d\d1\8e\d1\8f)`, expectedType: FilterEqualityMatch, }, { filterStr: `(objectGUID=함수목록)`, expectedFilter: `(objectGUID=\ed\95\a8\ec\88\98\eb\aa\a9\eb\a1\9d)`, expectedType: FilterEqualityMatch, }, { filterStr: `(objectGUID=`, expectedFilter: ``, expectedType: 0, expectedErr: "unexpected end of filter", }, { filterStr: `(objectGUID=함수목록`, expectedFilter: ``, expectedType: 0, expectedErr: "unexpected end of filter", }, { filterStr: `((cn=)`, expectedFilter: ``, expectedType: 0, expectedErr: "unexpected end of filter", }, { filterStr: `(&(objectclass=inetorgperson)(cn=中文))`, expectedFilter: `(&(objectclass=inetorgperson)(cn=\e4\b8\ad\e6\96\87))`, expectedType: 0, }, // attr extension { filterStr: `(memberOf:=foo)`, expectedFilter: `(memberOf:=foo)`, expectedType: FilterExtensibleMatch, }, // attr+named matching rule extension { filterStr: `(memberOf:test:=foo)`, expectedFilter: `(memberOf:test:=foo)`, expectedType: FilterExtensibleMatch, }, // attr+oid matching rule extension { filterStr: `(cn:1.2.3.4.5:=Fred Flintstone)`, expectedFilter: `(cn:1.2.3.4.5:=Fred Flintstone)`, expectedType: FilterExtensibleMatch, }, // attr+dn+oid matching rule extension { filterStr: `(sn:dn:2.4.6.8.10:=Barney Rubble)`, expectedFilter: `(sn:dn:2.4.6.8.10:=Barney Rubble)`, expectedType: FilterExtensibleMatch, }, // attr+dn extension { filterStr: `(o:dn:=Ace Industry)`, expectedFilter: `(o:dn:=Ace Industry)`, expectedType: FilterExtensibleMatch, }, // dn extension { filterStr: `(:dn:2.4.6.8.10:=Dino)`, expectedFilter: `(:dn:2.4.6.8.10:=Dino)`, expectedType: FilterExtensibleMatch, }, { filterStr: `(memberOf:1.2.840.113556.1.4.1941:=CN=User1,OU=blah,DC=mydomain,DC=net)`, expectedFilter: `(memberOf:1.2.840.113556.1.4.1941:=CN=User1,OU=blah,DC=mydomain,DC=net)`, expectedType: FilterExtensibleMatch, }, // compileTest{ filterStr: "()", filterType: FilterExtensibleMatch }, } var testInvalidFilters = []string{ `(objectGUID=\zz)`, `(objectGUID=\a)`, } func TestFilter(t *testing.T) { // Test Compiler and Decompiler for _, i := range testFilters { filter, err := CompileFilter(i.filterStr) switch { case err != nil: if i.expectedErr == "" || !strings.Contains(err.Error(), i.expectedErr) { t.Errorf("Problem compiling '%s' - '%v' (expected error to contain '%v')", i.filterStr, err, i.expectedErr) } case filter.Tag != ber.Tag(i.expectedType): t.Errorf("%q Expected %q got %q", i.filterStr, FilterMap[uint64(i.expectedType)], FilterMap[uint64(filter.Tag)]) default: o, err := DecompileFilter(filter) if err != nil { t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error()) } else if i.expectedFilter != o { t.Errorf("%q expected, got %q", i.expectedFilter, o) } } } } func TestDecodeEscapedSymbols(t *testing.T) { for _, testInfo := range []struct { Src string Err string }{ { Src: "a\u0100\x80", Err: `LDAP Result Code 201 "Filter Compile Error": ldap: error reading rune at position 3`, }, { Src: `start\d`, Err: `LDAP Result Code 201 "Filter Compile Error": ldap: missing characters for escape in filter`, }, { Src: `\`, Err: `LDAP Result Code 201 "Filter Compile Error": ldap: invalid characters for escape in filter: EOF`, }, { Src: `start\--end`, Err: `LDAP Result Code 201 "Filter Compile Error": ldap: invalid characters for escape in filter: encoding/hex: invalid byte: U+002D '-'`, }, { Src: `start\d0\hh`, Err: `LDAP Result Code 201 "Filter Compile Error": ldap: invalid characters for escape in filter: encoding/hex: invalid byte: U+0068 'h'`, }, } { res, err := decodeEscapedSymbols([]byte(testInfo.Src)) if err == nil || err.Error() != testInfo.Err { t.Fatal(testInfo.Src, "=> ", err, "!=", testInfo.Err) } if res != "" { t.Fatal(testInfo.Src, "=> ", "invalid result", res) } } } func TestInvalidFilter(t *testing.T) { for _, filterStr := range testInvalidFilters { if _, err := CompileFilter(filterStr); err == nil { t.Errorf("Problem compiling %s - expected err", filterStr) } } } func BenchmarkFilterCompile(b *testing.B) { b.StopTimer() filters := make([]string, len(testFilters)) // Test Compiler and Decompiler for idx, i := range testFilters { filters[idx] = i.filterStr } maxIdx := len(filters) b.StartTimer() for i := 0; i < b.N; i++ { CompileFilter(filters[i%maxIdx]) } } func BenchmarkFilterDecompile(b *testing.B) { b.StopTimer() filters := make([]*ber.Packet, len(testFilters)) // Test Compiler and Decompiler for idx, i := range testFilters { filters[idx], _ = CompileFilter(i.filterStr) } maxIdx := len(filters) b.StartTimer() for i := 0; i < b.N; i++ { DecompileFilter(filters[i%maxIdx]) } } ldap-3.4.1/go.mod000066400000000000000000000003411410654160200135420ustar00rootroot00000000000000module github.com/go-ldap/ldap go 1.13 require ( github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c github.com/go-asn1-ber/asn1-ber v1.5.1 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect ) ldap-3.4.1/go.sum000066400000000000000000000021561410654160200135750ustar00rootroot00000000000000github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= ldap-3.4.1/ldap.go000066400000000000000000000260751410654160200137170ustar00rootroot00000000000000package ldap import ( "fmt" "io/ioutil" "os" ber "github.com/go-asn1-ber/asn1-ber" ) // LDAP Application Codes const ( ApplicationBindRequest = 0 ApplicationBindResponse = 1 ApplicationUnbindRequest = 2 ApplicationSearchRequest = 3 ApplicationSearchResultEntry = 4 ApplicationSearchResultDone = 5 ApplicationModifyRequest = 6 ApplicationModifyResponse = 7 ApplicationAddRequest = 8 ApplicationAddResponse = 9 ApplicationDelRequest = 10 ApplicationDelResponse = 11 ApplicationModifyDNRequest = 12 ApplicationModifyDNResponse = 13 ApplicationCompareRequest = 14 ApplicationCompareResponse = 15 ApplicationAbandonRequest = 16 ApplicationSearchResultReference = 19 ApplicationExtendedRequest = 23 ApplicationExtendedResponse = 24 ) // ApplicationMap contains human readable descriptions of LDAP Application Codes var ApplicationMap = map[uint8]string{ ApplicationBindRequest: "Bind Request", ApplicationBindResponse: "Bind Response", ApplicationUnbindRequest: "Unbind Request", ApplicationSearchRequest: "Search Request", ApplicationSearchResultEntry: "Search Result Entry", ApplicationSearchResultDone: "Search Result Done", ApplicationModifyRequest: "Modify Request", ApplicationModifyResponse: "Modify Response", ApplicationAddRequest: "Add Request", ApplicationAddResponse: "Add Response", ApplicationDelRequest: "Del Request", ApplicationDelResponse: "Del Response", ApplicationModifyDNRequest: "Modify DN Request", ApplicationModifyDNResponse: "Modify DN Response", ApplicationCompareRequest: "Compare Request", ApplicationCompareResponse: "Compare Response", ApplicationAbandonRequest: "Abandon Request", ApplicationSearchResultReference: "Search Result Reference", ApplicationExtendedRequest: "Extended Request", ApplicationExtendedResponse: "Extended Response", } // Ldap Behera Password Policy Draft 10 (https://tools.ietf.org/html/draft-behera-ldap-password-policy-10) const ( BeheraPasswordExpired = 0 BeheraAccountLocked = 1 BeheraChangeAfterReset = 2 BeheraPasswordModNotAllowed = 3 BeheraMustSupplyOldPassword = 4 BeheraInsufficientPasswordQuality = 5 BeheraPasswordTooShort = 6 BeheraPasswordTooYoung = 7 BeheraPasswordInHistory = 8 ) // BeheraPasswordPolicyErrorMap contains human readable descriptions of Behera Password Policy error codes var BeheraPasswordPolicyErrorMap = map[int8]string{ BeheraPasswordExpired: "Password expired", BeheraAccountLocked: "Account locked", BeheraChangeAfterReset: "Password must be changed", BeheraPasswordModNotAllowed: "Policy prevents password modification", BeheraMustSupplyOldPassword: "Policy requires old password in order to change password", BeheraInsufficientPasswordQuality: "Password fails quality checks", BeheraPasswordTooShort: "Password is too short for policy", BeheraPasswordTooYoung: "Password has been changed too recently", BeheraPasswordInHistory: "New password is in list of old passwords", } // Adds descriptions to an LDAP Response packet for debugging func addLDAPDescriptions(packet *ber.Packet) (err error) { defer func() { if r := recover(); r != nil { err = NewError(ErrorDebugging, fmt.Errorf("ldap: cannot process packet to add descriptions: %s", r)) } }() packet.Description = "LDAP Response" packet.Children[0].Description = "Message ID" application := uint8(packet.Children[1].Tag) packet.Children[1].Description = ApplicationMap[application] switch application { case ApplicationBindRequest: err = addRequestDescriptions(packet) case ApplicationBindResponse: err = addDefaultLDAPResponseDescriptions(packet) case ApplicationUnbindRequest: err = addRequestDescriptions(packet) case ApplicationSearchRequest: err = addRequestDescriptions(packet) case ApplicationSearchResultEntry: packet.Children[1].Children[0].Description = "Object Name" packet.Children[1].Children[1].Description = "Attributes" for _, child := range packet.Children[1].Children[1].Children { child.Description = "Attribute" child.Children[0].Description = "Attribute Name" child.Children[1].Description = "Attribute Values" for _, grandchild := range child.Children[1].Children { grandchild.Description = "Attribute Value" } } if len(packet.Children) == 3 { err = addControlDescriptions(packet.Children[2]) } case ApplicationSearchResultDone: err = addDefaultLDAPResponseDescriptions(packet) case ApplicationModifyRequest: err = addRequestDescriptions(packet) case ApplicationModifyResponse: case ApplicationAddRequest: err = addRequestDescriptions(packet) case ApplicationAddResponse: case ApplicationDelRequest: err = addRequestDescriptions(packet) case ApplicationDelResponse: case ApplicationModifyDNRequest: err = addRequestDescriptions(packet) case ApplicationModifyDNResponse: case ApplicationCompareRequest: err = addRequestDescriptions(packet) case ApplicationCompareResponse: case ApplicationAbandonRequest: err = addRequestDescriptions(packet) case ApplicationSearchResultReference: case ApplicationExtendedRequest: err = addRequestDescriptions(packet) case ApplicationExtendedResponse: } return err } func addControlDescriptions(packet *ber.Packet) error { packet.Description = "Controls" for _, child := range packet.Children { var value *ber.Packet controlType := "" child.Description = "Control" switch len(child.Children) { case 0: // at least one child is required for control type return fmt.Errorf("at least one child is required for control type") case 1: // just type, no criticality or value controlType = child.Children[0].Value.(string) child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")" case 2: controlType = child.Children[0].Value.(string) child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")" // Children[1] could be criticality or value (both are optional) // duck-type on whether this is a boolean if _, ok := child.Children[1].Value.(bool); ok { child.Children[1].Description = "Criticality" } else { child.Children[1].Description = "Control Value" value = child.Children[1] } case 3: // criticality and value present controlType = child.Children[0].Value.(string) child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")" child.Children[1].Description = "Criticality" child.Children[2].Description = "Control Value" value = child.Children[2] default: // more than 3 children is invalid return fmt.Errorf("more than 3 children for control packet found") } if value == nil { continue } switch controlType { case ControlTypePaging: value.Description += " (Paging)" if value.Value != nil { valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) if err != nil { return fmt.Errorf("failed to decode data bytes: %s", err) } value.Data.Truncate(0) value.Value = nil valueChildren.Children[1].Value = valueChildren.Children[1].Data.Bytes() value.AppendChild(valueChildren) } value.Children[0].Description = "Real Search Control Value" value.Children[0].Children[0].Description = "Paging Size" value.Children[0].Children[1].Description = "Cookie" case ControlTypeBeheraPasswordPolicy: value.Description += " (Password Policy - Behera Draft)" if value.Value != nil { valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) if err != nil { return fmt.Errorf("failed to decode data bytes: %s", err) } value.Data.Truncate(0) value.Value = nil value.AppendChild(valueChildren) } sequence := value.Children[0] for _, child := range sequence.Children { if child.Tag == 0 { //Warning warningPacket := child.Children[0] val, err := ber.ParseInt64(warningPacket.Data.Bytes()) if err != nil { return fmt.Errorf("failed to decode data bytes: %s", err) } if warningPacket.Tag == 0 { //timeBeforeExpiration value.Description += " (TimeBeforeExpiration)" warningPacket.Value = val } else if warningPacket.Tag == 1 { //graceAuthNsRemaining value.Description += " (GraceAuthNsRemaining)" warningPacket.Value = val } } else if child.Tag == 1 { // Error bs := child.Data.Bytes() if len(bs) != 1 || bs[0] > 8 { return fmt.Errorf("failed to decode data bytes: %s", "invalid PasswordPolicyResponse enum value") } val := int8(bs[0]) child.Description = "Error" child.Value = val } } } } return nil } func addRequestDescriptions(packet *ber.Packet) error { packet.Description = "LDAP Request" packet.Children[0].Description = "Message ID" packet.Children[1].Description = ApplicationMap[uint8(packet.Children[1].Tag)] if len(packet.Children) == 3 { return addControlDescriptions(packet.Children[2]) } return nil } func addDefaultLDAPResponseDescriptions(packet *ber.Packet) error { resultCode := uint16(LDAPResultSuccess) matchedDN := "" description := "Success" if err := GetLDAPError(packet); err != nil { resultCode = err.(*Error).ResultCode matchedDN = err.(*Error).MatchedDN description = "Error Message" } packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[resultCode] + ")" packet.Children[1].Children[1].Description = "Matched DN (" + matchedDN + ")" packet.Children[1].Children[2].Description = description if len(packet.Children[1].Children) > 3 { packet.Children[1].Children[3].Description = "Referral" } if len(packet.Children) == 3 { return addControlDescriptions(packet.Children[2]) } return nil } // DebugBinaryFile reads and prints packets from the given filename func DebugBinaryFile(fileName string) error { file, err := ioutil.ReadFile(fileName) if err != nil { return NewError(ErrorDebugging, err) } ber.PrintBytes(os.Stdout, file, "") packet, err := ber.DecodePacketErr(file) if err != nil { return fmt.Errorf("failed to decode packet: %s", err) } if err := addLDAPDescriptions(packet); err != nil { return err } ber.PrintPacket(packet) return nil } var hex = "0123456789abcdef" func mustEscape(c byte) bool { return c > 0x7f || c == '(' || c == ')' || c == '\\' || c == '*' || c == 0 } // EscapeFilter escapes from the provided LDAP filter string the special // characters in the set `()*\` and those out of the range 0 < c < 0x80, // as defined in RFC4515. func EscapeFilter(filter string) string { escape := 0 for i := 0; i < len(filter); i++ { if mustEscape(filter[i]) { escape++ } } if escape == 0 { return filter } buf := make([]byte, len(filter)+escape*2) for i, j := 0, 0; i < len(filter); i++ { c := filter[i] if mustEscape(c) { buf[j+0] = '\\' buf[j+1] = hex[c>>4] buf[j+2] = hex[c&0xf] j += 3 } else { buf[j] = c j++ } } return string(buf) } ldap-3.4.1/ldap_test.go000066400000000000000000000233201410654160200147440ustar00rootroot00000000000000package ldap import ( "crypto/tls" "testing" ber "github.com/go-asn1-ber/asn1-ber" ) const ldapServer = "ldap://ldap.itd.umich.edu:389" const ldapsServer = "ldaps://ldap.itd.umich.edu:636" const baseDN = "dc=umich,dc=edu" var filter = []string{ "(cn=cis-fac)", "(&(owner=*)(cn=cis-fac))", "(&(objectclass=rfc822mailgroup)(cn=*Computer*))", "(&(objectclass=rfc822mailgroup)(cn=*Mathematics*))"} var attributes = []string{ "cn", "description"} func TestUnsecureDialURL(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() } func TestSecureDialURL(t *testing.T) { l, err := DialURL(ldapsServer, DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true})) if err != nil { t.Fatal(err) } defer l.Close() } func TestStartTLS(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { t.Fatal(err) } } func TestTLSConnectionState(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { t.Fatal(err) } cs, ok := l.TLSConnectionState() if !ok { t.Errorf("TLSConnectionState returned ok == false; want true") } if cs.Version == 0 || !cs.HandshakeComplete { t.Errorf("ConnectionState = %#v; expected Version != 0 and HandshakeComplete = true", cs) } } func TestSearch(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() searchRequest := NewSearchRequest( baseDN, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter[0], attributes, nil) sr, err := l.Search(searchRequest) if err != nil { t.Fatal(err) } t.Logf("TestSearch: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries)) } func TestSearchStartTLS(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() searchRequest := NewSearchRequest( baseDN, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter[0], attributes, nil) sr, err := l.Search(searchRequest) if err != nil { t.Fatal(err) } t.Logf("TestSearchStartTLS: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries)) t.Log("TestSearchStartTLS: upgrading with startTLS") err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { t.Fatal(err) } sr, err = l.Search(searchRequest) if err != nil { t.Fatal(err) } t.Logf("TestSearchStartTLS: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries)) } func TestSearchWithPaging(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() err = l.UnauthenticatedBind("") if err != nil { t.Fatal(err) } searchRequest := NewSearchRequest( baseDN, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter[2], attributes, nil) sr, err := l.SearchWithPaging(searchRequest, 5) if err != nil { t.Fatal(err) } t.Logf("TestSearchWithPaging: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries)) searchRequest = NewSearchRequest( baseDN, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter[2], attributes, []Control{NewControlPaging(5)}) sr, err = l.SearchWithPaging(searchRequest, 5) if err != nil { t.Fatal(err) } t.Logf("TestSearchWithPaging: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries)) searchRequest = NewSearchRequest( baseDN, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter[2], attributes, []Control{NewControlPaging(500)}) sr, err = l.SearchWithPaging(searchRequest, 5) if err == nil { t.Fatal("expected an error when paging size in control in search request doesn't match size given in call, got none") } } func searchGoroutine(t *testing.T, l *Conn, results chan *SearchResult, i int) { searchRequest := NewSearchRequest( baseDN, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter[i], attributes, nil) sr, err := l.Search(searchRequest) if err != nil { t.Error(err) results <- nil return } results <- sr } func testMultiGoroutineSearch(t *testing.T, TLS bool, startTLS bool) { var l *Conn var err error if TLS { l, err = DialURL(ldapsServer, DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true})) if err != nil { t.Fatal(err) } defer l.Close() } else { l, err = DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() if startTLS { t.Log("TestMultiGoroutineSearch: using StartTLS...") err := l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { t.Fatal(err) } } } results := make([]chan *SearchResult, len(filter)) for i := range filter { results[i] = make(chan *SearchResult) go searchGoroutine(t, l, results[i], i) } for i := range filter { sr := <-results[i] if sr == nil { t.Errorf("Did not receive results from goroutine for %q", filter[i]) } else { t.Logf("TestMultiGoroutineSearch(%d): %s -> num of entries = %d", i, filter[i], len(sr.Entries)) } } } func TestMultiGoroutineSearch(t *testing.T) { testMultiGoroutineSearch(t, false, false) testMultiGoroutineSearch(t, true, true) testMultiGoroutineSearch(t, false, true) } func TestEscapeFilter(t *testing.T) { if got, want := EscapeFilter("a\x00b(c)d*e\\f"), `a\00b\28c\29d\2ae\5cf`; got != want { t.Errorf("Got %s, expected %s", want, got) } if got, want := EscapeFilter("Lučić"), `Lu\c4\8di\c4\87`; got != want { t.Errorf("Got %s, expected %s", want, got) } } func TestCompare(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() const dn = "cn=math mich,ou=User Groups,ou=Groups,dc=umich,dc=edu" const attribute = "cn" const value = "math mich" sr, err := l.Compare(dn, attribute, value) if err != nil { t.Fatal(err) } t.Log("Compare result:", sr) } func TestMatchDNError(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() const wrongBase = "ou=roups,dc=umich,dc=edu" searchRequest := NewSearchRequest( wrongBase, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter[0], attributes, nil) _, err = l.Search(searchRequest) if err == nil { t.Fatal("Expected Error, got nil") } t.Log("TestMatchDNError:", err) } func Test_addControlDescriptions(t *testing.T) { type args struct { packet *ber.Packet } tests := []struct { name string args args wantErr bool }{ {name: "timeBeforeExpiration", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x29, 0x30, 0x27, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0xa, 0x30, 0x8, 0xa0, 0x6, 0x80, 0x4, 0x7f, 0xff, 0xf6, 0x5c})}, wantErr: false}, {name: "graceAuthNsRemaining", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x26, 0x30, 0x24, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x7, 0x30, 0x5, 0xa0, 0x3, 0x81, 0x1, 0x11})}, wantErr: false}, {name: "passwordExpired", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x0})}, wantErr: false}, {name: "accountLocked", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x1})}, wantErr: false}, {name: "passwordModNotAllowed", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x3})}, wantErr: false}, {name: "mustSupplyOldPassword", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x4})}, wantErr: false}, {name: "insufficientPasswordQuality", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x5})}, wantErr: false}, {name: "passwordTooShort", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x6})}, wantErr: false}, {name: "passwordTooYoung", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x7})}, wantErr: false}, {name: "passwordInHistory", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x8})}, wantErr: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := addControlDescriptions(tt.args.packet); (err != nil) != tt.wantErr { t.Errorf("addControlDescriptions() error = %v, wantErr %v", err, tt.wantErr) } }) } } ldap-3.4.1/moddn.go000066400000000000000000000060071410654160200140710ustar00rootroot00000000000000package ldap import ( "log" ber "github.com/go-asn1-ber/asn1-ber" ) // ModifyDNRequest holds the request to modify a DN type ModifyDNRequest struct { DN string NewRDN string DeleteOldRDN bool NewSuperior string // Controls hold optional controls to send with the request Controls []Control } // NewModifyDNRequest creates a new request which can be passed to ModifyDN(). // // To move an object in the tree, set the "newSup" to the new parent entry DN. Use an // empty string for just changing the object's RDN. // // For moving the object without renaming, the "rdn" must be the first // RDN of the given DN. // // A call like // mdnReq := NewModifyDNRequest("uid=someone,dc=example,dc=org", "uid=newname", true, "") // will setup the request to just rename uid=someone,dc=example,dc=org to // uid=newname,dc=example,dc=org. func NewModifyDNRequest(dn string, rdn string, delOld bool, newSup string) *ModifyDNRequest { return &ModifyDNRequest{ DN: dn, NewRDN: rdn, DeleteOldRDN: delOld, NewSuperior: newSup, } } // NewModifyDNWithControlsRequest creates a new request which can be passed to ModifyDN() // and also allows setting LDAP request controls. // // Refer NewModifyDNRequest for other parameters func NewModifyDNWithControlsRequest(dn string, rdn string, delOld bool, newSup string, controls []Control) *ModifyDNRequest { return &ModifyDNRequest{ DN: dn, NewRDN: rdn, DeleteOldRDN: delOld, NewSuperior: newSup, Controls: controls, } } func (req *ModifyDNRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyDNRequest, nil, "Modify DN Request") pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN")) pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.NewRDN, "New RDN")) if req.DeleteOldRDN { buf := []byte{0xff} pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, string(buf), "Delete old RDN")) } else { pkt.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, req.DeleteOldRDN, "Delete old RDN")) } if req.NewSuperior != "" { pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, req.NewSuperior, "New Superior")) } envelope.AppendChild(pkt) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // ModifyDN renames the given DN and optionally move to another base (when the "newSup" argument // to NewModifyDNRequest() is not ""). func (l *Conn) ModifyDN(m *ModifyDNRequest) error { msgCtx, err := l.doRequest(m) if err != nil { return err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return err } if packet.Children[1].Tag == ApplicationModifyDNResponse { err := GetLDAPError(packet) if err != nil { return err } } else { log.Printf("Unexpected Response: %d", packet.Children[1].Tag) } return nil } ldap-3.4.1/modify.go000066400000000000000000000124001410654160200142510ustar00rootroot00000000000000package ldap import ( "errors" "log" ber "github.com/go-asn1-ber/asn1-ber" ) // Change operation choices const ( AddAttribute = 0 DeleteAttribute = 1 ReplaceAttribute = 2 IncrementAttribute = 3 // (https://tools.ietf.org/html/rfc4525) ) // PartialAttribute for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511 type PartialAttribute struct { // Type is the type of the partial attribute Type string // Vals are the values of the partial attribute Vals []string } func (p *PartialAttribute) encode() *ber.Packet { seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "PartialAttribute") seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, p.Type, "Type")) set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue") for _, value := range p.Vals { set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals")) } seq.AppendChild(set) return seq } // Change for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511 type Change struct { // Operation is the type of change to be made Operation uint // Modification is the attribute to be modified Modification PartialAttribute } func (c *Change) encode() *ber.Packet { change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change") change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(c.Operation), "Operation")) change.AppendChild(c.Modification.encode()) return change } // ModifyRequest as defined in https://tools.ietf.org/html/rfc4511 type ModifyRequest struct { // DN is the distinguishedName of the directory entry to modify DN string // Changes contain the attributes to modify Changes []Change // Controls hold optional controls to send with the request Controls []Control } // Add appends the given attribute to the list of changes to be made func (req *ModifyRequest) Add(attrType string, attrVals []string) { req.appendChange(AddAttribute, attrType, attrVals) } // Delete appends the given attribute to the list of changes to be made func (req *ModifyRequest) Delete(attrType string, attrVals []string) { req.appendChange(DeleteAttribute, attrType, attrVals) } // Replace appends the given attribute to the list of changes to be made func (req *ModifyRequest) Replace(attrType string, attrVals []string) { req.appendChange(ReplaceAttribute, attrType, attrVals) } // Increment appends the given attribute to the list of changes to be made func (req *ModifyRequest) Increment(attrType string, attrVal string) { req.appendChange(IncrementAttribute, attrType, []string{attrVal}) } func (req *ModifyRequest) appendChange(operation uint, attrType string, attrVals []string) { req.Changes = append(req.Changes, Change{operation, PartialAttribute{Type: attrType, Vals: attrVals}}) } func (req *ModifyRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request") pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN")) changes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Changes") for _, change := range req.Changes { changes.AppendChild(change.encode()) } pkt.AppendChild(changes) envelope.AppendChild(pkt) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // NewModifyRequest creates a modify request for the given DN func NewModifyRequest(dn string, controls []Control) *ModifyRequest { return &ModifyRequest{ DN: dn, Controls: controls, } } // Modify performs the ModifyRequest func (l *Conn) Modify(modifyRequest *ModifyRequest) error { msgCtx, err := l.doRequest(modifyRequest) if err != nil { return err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return err } if packet.Children[1].Tag == ApplicationModifyResponse { err := GetLDAPError(packet) if err != nil { return err } } else { log.Printf("Unexpected Response: %d", packet.Children[1].Tag) } return nil } // ModifyResult holds the server's response to a modify request type ModifyResult struct { // Controls are the returned controls Controls []Control } // ModifyWithResult performs the ModifyRequest and returns the result func (l *Conn) ModifyWithResult(modifyRequest *ModifyRequest) (*ModifyResult, error) { msgCtx, err := l.doRequest(modifyRequest) if err != nil { return nil, err } defer l.finishMessage(msgCtx) result := &ModifyResult{ Controls: make([]Control, 0), } l.Debug.Printf("%d: waiting for response", msgCtx.id) packet, err := l.readPacket(msgCtx) if err != nil { return nil, err } switch packet.Children[1].Tag { case ApplicationModifyResponse: err := GetLDAPError(packet) if err != nil { return nil, err } if len(packet.Children) == 3 { for _, child := range packet.Children[2].Children { decodedChild, err := DecodeControl(child) if err != nil { return nil, errors.New("failed to decode child control: " + err.Error()) } result.Controls = append(result.Controls, decodedChild) } } } l.Debug.Printf("%d: returning", msgCtx.id) return result, nil } ldap-3.4.1/passwdmodify.go000066400000000000000000000111261410654160200154770ustar00rootroot00000000000000package ldap import ( "fmt" ber "github.com/go-asn1-ber/asn1-ber" ) const ( passwordModifyOID = "1.3.6.1.4.1.4203.1.11.1" ) // PasswordModifyRequest implements the Password Modify Extended Operation as defined in https://www.ietf.org/rfc/rfc3062.txt type PasswordModifyRequest struct { // UserIdentity is an optional string representation of the user associated with the request. // This string may or may not be an LDAPDN [RFC2253]. // If no UserIdentity field is present, the request acts up upon the password of the user currently associated with the LDAP session UserIdentity string // OldPassword, if present, contains the user's current password OldPassword string // NewPassword, if present, contains the desired password for this user NewPassword string } // PasswordModifyResult holds the server response to a PasswordModifyRequest type PasswordModifyResult struct { // GeneratedPassword holds a password generated by the server, if present GeneratedPassword string // Referral are the returned referral Referral string } func (req *PasswordModifyRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Password Modify Extended Operation") pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, passwordModifyOID, "Extended Request Name: Password Modify OID")) extendedRequestValue := ber.Encode(ber.ClassContext, ber.TypePrimitive, 1, nil, "Extended Request Value: Password Modify Request") passwordModifyRequestValue := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Password Modify Request") if req.UserIdentity != "" { passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, req.UserIdentity, "User Identity")) } if req.OldPassword != "" { passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 1, req.OldPassword, "Old Password")) } if req.NewPassword != "" { passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 2, req.NewPassword, "New Password")) } extendedRequestValue.AppendChild(passwordModifyRequestValue) pkt.AppendChild(extendedRequestValue) envelope.AppendChild(pkt) return nil } // NewPasswordModifyRequest creates a new PasswordModifyRequest // // According to the RFC 3602 (https://tools.ietf.org/html/rfc3062): // userIdentity is a string representing the user associated with the request. // This string may or may not be an LDAPDN (RFC 2253). // If userIdentity is empty then the operation will act on the user associated // with the session. // // oldPassword is the current user's password, it can be empty or it can be // needed depending on the session user access rights (usually an administrator // can change a user's password without knowing the current one) and the // password policy (see pwdSafeModify password policy's attribute) // // newPassword is the desired user's password. If empty the server can return // an error or generate a new password that will be available in the // PasswordModifyResult.GeneratedPassword // func NewPasswordModifyRequest(userIdentity string, oldPassword string, newPassword string) *PasswordModifyRequest { return &PasswordModifyRequest{ UserIdentity: userIdentity, OldPassword: oldPassword, NewPassword: newPassword, } } // PasswordModify performs the modification request func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error) { msgCtx, err := l.doRequest(passwordModifyRequest) if err != nil { return nil, err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return nil, err } result := &PasswordModifyResult{} if packet.Children[1].Tag == ApplicationExtendedResponse { err := GetLDAPError(packet) if err != nil { if IsErrorWithCode(err, LDAPResultReferral) { for _, child := range packet.Children[1].Children { if child.Tag == 3 { result.Referral = child.Children[0].Value.(string) } } } return result, err } } else { return nil, NewError(ErrorUnexpectedResponse, fmt.Errorf("unexpected Response: %d", packet.Children[1].Tag)) } extendedResponse := packet.Children[1] for _, child := range extendedResponse.Children { if child.Tag == 11 { passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes()) if len(passwordModifyResponseValue.Children) == 1 { if passwordModifyResponseValue.Children[0].Tag == 0 { result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes()) } } } } return result, nil } ldap-3.4.1/request.go000066400000000000000000000032161410654160200144570ustar00rootroot00000000000000package ldap import ( "errors" ber "github.com/go-asn1-ber/asn1-ber" ) var ( errRespChanClosed = errors.New("ldap: response channel closed") errCouldNotRetMsg = errors.New("ldap: could not retrieve message") ErrNilConnection = errors.New("ldap: conn is nil, expected net.Conn") ) type request interface { appendTo(*ber.Packet) error } type requestFunc func(*ber.Packet) error func (f requestFunc) appendTo(p *ber.Packet) error { return f(p) } func (l *Conn) doRequest(req request) (*messageContext, error) { if l == nil || l.conn == nil { return nil, ErrNilConnection } 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")) if err := req.appendTo(packet); err != nil { return nil, err } if l.Debug { l.Debug.PrintPacket(packet) } msgCtx, err := l.sendMessage(packet) if err != nil { return nil, err } l.Debug.Printf("%d: returning", msgCtx.id) return msgCtx, nil } func (l *Conn) readPacket(msgCtx *messageContext) (*ber.Packet, error) { l.Debug.Printf("%d: waiting for response", msgCtx.id) packetResponse, ok := <-msgCtx.responses if !ok { return nil, NewError(ErrorNetwork, errRespChanClosed) } packet, err := packetResponse.ReadPacket() l.Debug.Printf("%d: got response %p", msgCtx.id, packet) if err != nil { return nil, err } if packet == nil { return nil, NewError(ErrorNetwork, errCouldNotRetMsg) } if l.Debug { if err = addLDAPDescriptions(packet); err != nil { return nil, err } l.Debug.PrintPacket(packet) } return packet, nil } ldap-3.4.1/search.go000066400000000000000000000317341410654160200142420ustar00rootroot00000000000000package ldap import ( "errors" "fmt" "sort" "strings" ber "github.com/go-asn1-ber/asn1-ber" ) // scope choices const ( ScopeBaseObject = 0 ScopeSingleLevel = 1 ScopeWholeSubtree = 2 ) // ScopeMap contains human readable descriptions of scope choices var ScopeMap = map[int]string{ ScopeBaseObject: "Base Object", ScopeSingleLevel: "Single Level", ScopeWholeSubtree: "Whole Subtree", } // derefAliases const ( NeverDerefAliases = 0 DerefInSearching = 1 DerefFindingBaseObj = 2 DerefAlways = 3 ) // DerefMap contains human readable descriptions of derefAliases choices var DerefMap = map[int]string{ NeverDerefAliases: "NeverDerefAliases", DerefInSearching: "DerefInSearching", DerefFindingBaseObj: "DerefFindingBaseObj", DerefAlways: "DerefAlways", } // NewEntry returns an Entry object with the specified distinguished name and attribute key-value pairs. // The map of attributes is accessed in alphabetical order of the keys in order to ensure that, for the // same input map of attributes, the output entry will contain the same order of attributes func NewEntry(dn string, attributes map[string][]string) *Entry { var attributeNames []string for attributeName := range attributes { attributeNames = append(attributeNames, attributeName) } sort.Strings(attributeNames) var encodedAttributes []*EntryAttribute for _, attributeName := range attributeNames { encodedAttributes = append(encodedAttributes, NewEntryAttribute(attributeName, attributes[attributeName])) } return &Entry{ DN: dn, Attributes: encodedAttributes, } } // Entry represents a single search result entry type Entry struct { // DN is the distinguished name of the entry DN string // Attributes are the returned attributes for the entry Attributes []*EntryAttribute } // GetAttributeValues returns the values for the named attribute, or an empty list func (e *Entry) GetAttributeValues(attribute string) []string { for _, attr := range e.Attributes { if attr.Name == attribute { return attr.Values } } return []string{} } // GetEqualFoldAttributeValues returns the values for the named attribute, or an // empty list. Attribute matching is done with strings.EqualFold. func (e *Entry) GetEqualFoldAttributeValues(attribute string) []string { for _, attr := range e.Attributes { if strings.EqualFold(attribute, attr.Name) { return attr.Values } } return []string{} } // GetRawAttributeValues returns the byte values for the named attribute, or an empty list func (e *Entry) GetRawAttributeValues(attribute string) [][]byte { for _, attr := range e.Attributes { if attr.Name == attribute { return attr.ByteValues } } return [][]byte{} } // GetEqualFoldRawAttributeValues returns the byte values for the named attribute, or an empty list func (e *Entry) GetEqualFoldRawAttributeValues(attribute string) [][]byte { for _, attr := range e.Attributes { if strings.EqualFold(attr.Name, attribute) { return attr.ByteValues } } return [][]byte{} } // GetAttributeValue returns the first value for the named attribute, or "" func (e *Entry) GetAttributeValue(attribute string) string { values := e.GetAttributeValues(attribute) if len(values) == 0 { return "" } return values[0] } // GetEqualFoldAttributeValue returns the first value for the named attribute, or "". // Attribute comparison is done with strings.EqualFold. func (e *Entry) GetEqualFoldAttributeValue(attribute string) string { values := e.GetEqualFoldAttributeValues(attribute) if len(values) == 0 { return "" } return values[0] } // GetRawAttributeValue returns the first value for the named attribute, or an empty slice func (e *Entry) GetRawAttributeValue(attribute string) []byte { values := e.GetRawAttributeValues(attribute) if len(values) == 0 { return []byte{} } return values[0] } // GetEqualFoldRawAttributeValue returns the first value for the named attribute, or an empty slice func (e *Entry) GetEqualFoldRawAttributeValue(attribute string) []byte { values := e.GetEqualFoldRawAttributeValues(attribute) if len(values) == 0 { return []byte{} } return values[0] } // Print outputs a human-readable description func (e *Entry) Print() { fmt.Printf("DN: %s\n", e.DN) for _, attr := range e.Attributes { attr.Print() } } // PrettyPrint outputs a human-readable description indenting func (e *Entry) PrettyPrint(indent int) { fmt.Printf("%sDN: %s\n", strings.Repeat(" ", indent), e.DN) for _, attr := range e.Attributes { attr.PrettyPrint(indent + 2) } } // NewEntryAttribute returns a new EntryAttribute with the desired key-value pair func NewEntryAttribute(name string, values []string) *EntryAttribute { var bytes [][]byte for _, value := range values { bytes = append(bytes, []byte(value)) } return &EntryAttribute{ Name: name, Values: values, ByteValues: bytes, } } // EntryAttribute holds a single attribute type EntryAttribute struct { // Name is the name of the attribute Name string // Values contain the string values of the attribute Values []string // ByteValues contain the raw values of the attribute ByteValues [][]byte } // Print outputs a human-readable description func (e *EntryAttribute) Print() { fmt.Printf("%s: %s\n", e.Name, e.Values) } // PrettyPrint outputs a human-readable description with indenting func (e *EntryAttribute) PrettyPrint(indent int) { fmt.Printf("%s%s: %s\n", strings.Repeat(" ", indent), e.Name, e.Values) } // SearchResult holds the server's response to a search request type SearchResult struct { // Entries are the returned entries Entries []*Entry // Referrals are the returned referrals Referrals []string // Controls are the returned controls Controls []Control } // Print outputs a human-readable description func (s *SearchResult) Print() { for _, entry := range s.Entries { entry.Print() } } // PrettyPrint outputs a human-readable description with indenting func (s *SearchResult) PrettyPrint(indent int) { for _, entry := range s.Entries { entry.PrettyPrint(indent) } } // SearchRequest represents a search request to send to the server type SearchRequest struct { BaseDN string Scope int DerefAliases int SizeLimit int TimeLimit int TypesOnly bool Filter string Attributes []string Controls []Control } func (req *SearchRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationSearchRequest, nil, "Search Request") pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.BaseDN, "Base DN")) pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(req.Scope), "Scope")) pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(req.DerefAliases), "Deref Aliases")) pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(req.SizeLimit), "Size Limit")) pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(req.TimeLimit), "Time Limit")) pkt.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, req.TypesOnly, "Types Only")) // compile and encode filter filterPacket, err := CompileFilter(req.Filter) if err != nil { return err } pkt.AppendChild(filterPacket) // encode attributes attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes") for _, attribute := range req.Attributes { attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute")) } pkt.AppendChild(attributesPacket) envelope.AppendChild(pkt) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // NewSearchRequest creates a new search request func NewSearchRequest( BaseDN string, Scope, DerefAliases, SizeLimit, TimeLimit int, TypesOnly bool, Filter string, Attributes []string, Controls []Control, ) *SearchRequest { return &SearchRequest{ BaseDN: BaseDN, Scope: Scope, DerefAliases: DerefAliases, SizeLimit: SizeLimit, TimeLimit: TimeLimit, TypesOnly: TypesOnly, Filter: Filter, Attributes: Attributes, Controls: Controls, } } // SearchWithPaging accepts a search request and desired page size in order to execute LDAP queries to fulfill the // search request. All paged LDAP query responses will be buffered and the final result will be returned atomically. // The following four cases are possible given the arguments: // - given SearchRequest missing a control of type ControlTypePaging: we will add one with the desired paging size // - given SearchRequest contains a control of type ControlTypePaging that isn't actually a ControlPaging: fail without issuing any queries // - given SearchRequest contains a control of type ControlTypePaging with pagingSize equal to the size requested: no change to the search request // - given SearchRequest contains a control of type ControlTypePaging with pagingSize not equal to the size requested: fail without issuing any queries // A requested pagingSize of 0 is interpreted as no limit by LDAP servers. func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) { var pagingControl *ControlPaging control := FindControl(searchRequest.Controls, ControlTypePaging) if control == nil { pagingControl = NewControlPaging(pagingSize) searchRequest.Controls = append(searchRequest.Controls, pagingControl) } else { castControl, ok := control.(*ControlPaging) if !ok { return nil, fmt.Errorf("expected paging control to be of type *ControlPaging, got %v", control) } if castControl.PagingSize != pagingSize { return nil, fmt.Errorf("paging size given in search request (%d) conflicts with size given in search call (%d)", castControl.PagingSize, pagingSize) } pagingControl = castControl } searchResult := new(SearchResult) for { result, err := l.Search(searchRequest) l.Debug.Printf("Looking for Paging Control...") if err != nil { return searchResult, err } if result == nil { return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received")) } for _, entry := range result.Entries { searchResult.Entries = append(searchResult.Entries, entry) } for _, referral := range result.Referrals { searchResult.Referrals = append(searchResult.Referrals, referral) } for _, control := range result.Controls { searchResult.Controls = append(searchResult.Controls, control) } l.Debug.Printf("Looking for Paging Control...") pagingResult := FindControl(result.Controls, ControlTypePaging) if pagingResult == nil { pagingControl = nil l.Debug.Printf("Could not find paging control. Breaking...") break } cookie := pagingResult.(*ControlPaging).Cookie if len(cookie) == 0 { pagingControl = nil l.Debug.Printf("Could not find cookie. Breaking...") break } pagingControl.SetCookie(cookie) } if pagingControl != nil { l.Debug.Printf("Abandoning Paging...") pagingControl.PagingSize = 0 l.Search(searchRequest) } return searchResult, nil } // Search performs the given search request func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) { msgCtx, err := l.doRequest(searchRequest) if err != nil { return nil, err } defer l.finishMessage(msgCtx) result := &SearchResult{ Entries: make([]*Entry, 0), Referrals: make([]string, 0), Controls: make([]Control, 0)} for { packet, err := l.readPacket(msgCtx) if err != nil { return result, err } switch packet.Children[1].Tag { case 4: entry := &Entry{ DN: packet.Children[1].Children[0].Value.(string), Attributes: unpackAttributes(packet.Children[1].Children[1].Children), } result.Entries = append(result.Entries, entry) case 5: err := GetLDAPError(packet) if err != nil { return result, err } if len(packet.Children) == 3 { for _, child := range packet.Children[2].Children { decodedChild, err := DecodeControl(child) if err != nil { return result, fmt.Errorf("failed to decode child control: %s", err) } result.Controls = append(result.Controls, decodedChild) } } return result, nil case 19: result.Referrals = append(result.Referrals, packet.Children[1].Children[0].Value.(string)) } } } // unpackAttributes will extract all given LDAP attributes and it's values // from the ber.Packet func unpackAttributes(children []*ber.Packet) []*EntryAttribute { entries := make([]*EntryAttribute, len(children)) for i, child := range children { length := len(child.Children[1].Children) entry := &EntryAttribute{ Name: child.Children[0].Value.(string), // pre-allocate the slice since we can determine // the number of attributes at this point Values: make([]string, length), ByteValues: make([][]byte, length), } for i, value := range child.Children[1].Children { entry.ByteValues[i] = value.ByteValue entry.Values[i] = value.Value.(string) } entries[i] = entry } return entries } ldap-3.4.1/search_test.go000066400000000000000000000022631410654160200152740ustar00rootroot00000000000000package ldap import ( "reflect" "testing" ) // TestNewEntry tests that repeated calls to NewEntry return the same value with the same input func TestNewEntry(t *testing.T) { dn := "testDN" attributes := map[string][]string{ "alpha": {"value"}, "beta": {"value"}, "gamma": {"value"}, "delta": {"value"}, "epsilon": {"value"}, } executedEntry := NewEntry(dn, attributes) iteration := 0 for { if iteration == 100 { break } testEntry := NewEntry(dn, attributes) if !reflect.DeepEqual(executedEntry, testEntry) { t.Fatalf("subsequent calls to NewEntry did not yield the same result:\n\texpected:\n\t%v\n\tgot:\n\t%v\n", executedEntry, testEntry) } iteration = iteration + 1 } } func TestGetAttributeValue(t *testing.T) { dn := "testDN" attributes := map[string][]string{ "Alpha": {"value"}, "bEta": {"value"}, "gaMma": {"value"}, "delTa": {"value"}, "epsiLon": {"value"}, } entry := NewEntry(dn, attributes) if entry.GetAttributeValue("Alpha") != "value" { t.Errorf("failed to get attribute in original case") } if entry.GetEqualFoldAttributeValue("alpha") != "value" { t.Errorf("failed to get attribute in changed case") } } ldap-3.4.1/unbind.go000066400000000000000000000016611410654160200142500ustar00rootroot00000000000000package ldap import ( "errors" ber "github.com/go-asn1-ber/asn1-ber" ) var ErrConnUnbound = NewError(ErrorNetwork, errors.New("ldap: connection is closed")) type unbindRequest struct{} func (unbindRequest) appendTo(envelope *ber.Packet) error { envelope.AppendChild(ber.Encode(ber.ClassApplication, ber.TypePrimitive, ApplicationUnbindRequest, nil, ApplicationMap[ApplicationUnbindRequest])) return nil } // Unbind will perform an unbind request. The Unbind operation // should be thought of as the "quit" operation. // See https://datatracker.ietf.org/doc/html/rfc4511#section-4.3 func (l *Conn) Unbind() error { if l.IsClosing() { return ErrConnUnbound } _, err := l.doRequest(unbindRequest{}) if err != nil { return err } // Sending an unbindRequest will make the connection unusable. // Pending requests will fail with: // LDAP Result Code 200 "Network Error": ldap: response channel closed l.Close() return nil } ldap-3.4.1/v3/000077500000000000000000000000001410654160200127665ustar00rootroot00000000000000ldap-3.4.1/v3/add.go000066400000000000000000000050331410654160200140460ustar00rootroot00000000000000package ldap import ( "log" ber "github.com/go-asn1-ber/asn1-ber" ) // Attribute represents an LDAP attribute type Attribute struct { // Type is the name of the LDAP attribute Type string // Vals are the LDAP attribute values Vals []string } func (a *Attribute) encode() *ber.Packet { seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attribute") seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, a.Type, "Type")) set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue") for _, value := range a.Vals { set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals")) } seq.AppendChild(set) return seq } // AddRequest represents an LDAP AddRequest operation type AddRequest struct { // DN identifies the entry being added DN string // Attributes list the attributes of the new entry Attributes []Attribute // Controls hold optional controls to send with the request Controls []Control } func (req *AddRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationAddRequest, nil, "Add Request") pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN")) attributes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes") for _, attribute := range req.Attributes { attributes.AppendChild(attribute.encode()) } pkt.AppendChild(attributes) envelope.AppendChild(pkt) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // Attribute adds an attribute with the given type and values func (req *AddRequest) Attribute(attrType string, attrVals []string) { req.Attributes = append(req.Attributes, Attribute{Type: attrType, Vals: attrVals}) } // NewAddRequest returns an AddRequest for the given DN, with no attributes func NewAddRequest(dn string, controls []Control) *AddRequest { return &AddRequest{ DN: dn, Controls: controls, } } // Add performs the given AddRequest func (l *Conn) Add(addRequest *AddRequest) error { msgCtx, err := l.doRequest(addRequest) if err != nil { return err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return err } if packet.Children[1].Tag == ApplicationAddResponse { err := GetLDAPError(packet) if err != nil { return err } } else { log.Printf("Unexpected Response: %d", packet.Children[1].Tag) } return nil } ldap-3.4.1/v3/bind.go000066400000000000000000000415511410654160200142370ustar00rootroot00000000000000package ldap import ( "bytes" "crypto/md5" enchex "encoding/hex" "errors" "fmt" "io/ioutil" "math/rand" "strings" "github.com/Azure/go-ntlmssp" ber "github.com/go-asn1-ber/asn1-ber" ) // SimpleBindRequest represents a username/password bind operation type SimpleBindRequest struct { // Username is the name of the Directory object that the client wishes to bind as Username string // Password is the credentials to bind with Password string // Controls are optional controls to send with the bind request Controls []Control // AllowEmptyPassword sets whether the client allows binding with an empty password // (normally used for unauthenticated bind). AllowEmptyPassword bool } // SimpleBindResult contains the response from the server type SimpleBindResult struct { Controls []Control } // NewSimpleBindRequest returns a bind request func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest { return &SimpleBindRequest{ Username: username, Password: password, Controls: controls, AllowEmptyPassword: false, } } func (req *SimpleBindRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.Username, "User Name")) pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, req.Password, "Password")) envelope.AppendChild(pkt) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // SimpleBind performs the simple bind operation defined in the given request func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) { if simpleBindRequest.Password == "" && !simpleBindRequest.AllowEmptyPassword { return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client")) } msgCtx, err := l.doRequest(simpleBindRequest) if err != nil { return nil, err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return nil, err } result := &SimpleBindResult{ Controls: make([]Control, 0), } if len(packet.Children) == 3 { for _, child := range packet.Children[2].Children { decodedChild, decodeErr := DecodeControl(child) if decodeErr != nil { return nil, fmt.Errorf("failed to decode child control: %s", decodeErr) } result.Controls = append(result.Controls, decodedChild) } } err = GetLDAPError(packet) return result, err } // Bind performs a bind with the given username and password. // // It does not allow unauthenticated bind (i.e. empty password). Use the UnauthenticatedBind method // for that. func (l *Conn) Bind(username, password string) error { req := &SimpleBindRequest{ Username: username, Password: password, AllowEmptyPassword: false, } _, err := l.SimpleBind(req) return err } // UnauthenticatedBind performs an unauthenticated bind. // // A username may be provided for trace (e.g. logging) purpose only, but it is normally not // authenticated or otherwise validated by the LDAP server. // // See https://tools.ietf.org/html/rfc4513#section-5.1.2 . // See https://tools.ietf.org/html/rfc4513#section-6.3.1 . func (l *Conn) UnauthenticatedBind(username string) error { req := &SimpleBindRequest{ Username: username, Password: "", AllowEmptyPassword: true, } _, err := l.SimpleBind(req) return err } // DigestMD5BindRequest represents a digest-md5 bind operation type DigestMD5BindRequest struct { Host string // Username is the name of the Directory object that the client wishes to bind as Username string // Password is the credentials to bind with Password string // Controls are optional controls to send with the bind request Controls []Control } func (req *DigestMD5BindRequest) appendTo(envelope *ber.Packet) error { request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication") auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "DIGEST-MD5", "SASL Mech")) request.AppendChild(auth) envelope.AppendChild(request) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // DigestMD5BindResult contains the response from the server type DigestMD5BindResult struct { Controls []Control } // MD5Bind performs a digest-md5 bind with the given host, username and password. func (l *Conn) MD5Bind(host, username, password string) error { req := &DigestMD5BindRequest{ Host: host, Username: username, Password: password, } _, err := l.DigestMD5Bind(req) return err } // DigestMD5Bind performs the digest-md5 bind operation defined in the given request func (l *Conn) DigestMD5Bind(digestMD5BindRequest *DigestMD5BindRequest) (*DigestMD5BindResult, error) { if digestMD5BindRequest.Password == "" { return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client")) } msgCtx, err := l.doRequest(digestMD5BindRequest) if err != nil { return nil, err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return nil, err } l.Debug.Printf("%d: got response %p", msgCtx.id, packet) if l.Debug { if err = addLDAPDescriptions(packet); err != nil { return nil, err } ber.PrintPacket(packet) } result := &DigestMD5BindResult{ Controls: make([]Control, 0), } var params map[string]string if len(packet.Children) == 2 { if len(packet.Children[1].Children) == 4 { child := packet.Children[1].Children[0] if child.Tag != ber.TagEnumerated { return result, GetLDAPError(packet) } if child.Value.(int64) != 14 { return result, GetLDAPError(packet) } child = packet.Children[1].Children[3] if child.Tag != ber.TagObjectDescriptor { return result, GetLDAPError(packet) } if child.Data == nil { return result, GetLDAPError(packet) } data, _ := ioutil.ReadAll(child.Data) params, err = parseParams(string(data)) if err != nil { return result, fmt.Errorf("parsing digest-challenge: %s", err) } } } if params != nil { resp := computeResponse( params, "ldap/"+strings.ToLower(digestMD5BindRequest.Host), digestMD5BindRequest.Username, digestMD5BindRequest.Password, ) 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")) request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication") auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "DIGEST-MD5", "SASL Mech")) auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, resp, "Credentials")) request.AppendChild(auth) packet.AppendChild(request) msgCtx, err = l.sendMessage(packet) if err != nil { return nil, fmt.Errorf("send message: %s", err) } defer l.finishMessage(msgCtx) packetResponse, ok := <-msgCtx.responses if !ok { return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed")) } packet, err = packetResponse.ReadPacket() l.Debug.Printf("%d: got response %p", msgCtx.id, packet) if err != nil { return nil, fmt.Errorf("read packet: %s", err) } } err = GetLDAPError(packet) return result, err } func parseParams(str string) (map[string]string, error) { m := make(map[string]string) var key, value string var state int 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) } if str[i] != '=' { key += string(str[i]) continue } state = 1 case 1: //reading value if i == len(str) { m[key] = value break } switch str[i] { case ',': m[key] = value state = 0 key = "" value = "" case '"': if value != "" { return nil, fmt.Errorf("syntax error on %d", i) } state = 2 default: value += string(str[i]) } case 2: //inside quotes if i == len(str) { return nil, fmt.Errorf("syntax error on %d", i) } if str[i] != '"' { value += string(str[i]) } else { state = 1 } } } return m, nil } func computeResponse(params map[string]string, uri, username, password string) string { nc := "00000001" qop := "auth" cnonce := enchex.EncodeToString(randomBytes(16)) x := username + ":" + params["realm"] + ":" + password y := md5Hash([]byte(x)) a1 := bytes.NewBuffer(y) a1.WriteString(":" + params["nonce"] + ":" + cnonce) if len(params["authzid"]) > 0 { a1.WriteString(":" + params["authzid"]) } a2 := bytes.NewBuffer([]byte("AUTHENTICATE")) a2.WriteString(":" + uri) ha1 := enchex.EncodeToString(md5Hash(a1.Bytes())) ha2 := enchex.EncodeToString(md5Hash(a2.Bytes())) kd := ha1 kd += ":" + params["nonce"] kd += ":" + nc kd += ":" + cnonce kd += ":" + qop kd += ":" + ha2 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"], cnonce, qop, uri, resp, ) } func md5Hash(b []byte) []byte { hasher := md5.New() hasher.Write(b) 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)) } return b } var externalBindRequest = requestFunc(func(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) saslAuth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication") saslAuth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "EXTERNAL", "SASL Mech")) saslAuth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "SASL Cred")) pkt.AppendChild(saslAuth) envelope.AppendChild(pkt) return nil }) // ExternalBind performs SASL/EXTERNAL authentication. // // Use ldap.DialURL("ldapi://") to connect to the Unix socket before ExternalBind. // // See https://tools.ietf.org/html/rfc4422#appendix-A func (l *Conn) ExternalBind() error { msgCtx, err := l.doRequest(externalBindRequest) if err != nil { return err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return err } return GetLDAPError(packet) } // NTLMBind performs an NTLMSSP bind leveraging https://github.com/Azure/go-ntlmssp // NTLMBindRequest represents an NTLMSSP bind operation type NTLMBindRequest struct { // Domain is the AD Domain to authenticate too. If not specified, it will be grabbed from the NTLMSSP Challenge Domain string // Username is the name of the Directory object that the client wishes to bind as Username string // Password is the credentials to bind with Password string // Hash is the hex NTLM hash to bind with. Password or hash must be provided Hash string // Controls are optional controls to send with the bind request Controls []Control } func (req *NTLMBindRequest) appendTo(envelope *ber.Packet) error { request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) // generate an NTLMSSP Negotiation message for the specified domain (it can be blank) negMessage, err := ntlmssp.NewNegotiateMessage(req.Domain, "") if err != nil { return fmt.Errorf("err creating negmessage: %s", err) } // append the generated NTLMSSP message as a TagEnumerated BER value auth := ber.Encode(ber.ClassContext, ber.TypePrimitive, ber.TagEnumerated, negMessage, "authentication") request.AppendChild(auth) envelope.AppendChild(request) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // NTLMBindResult contains the response from the server type NTLMBindResult struct { Controls []Control } // NTLMBind performs an NTLMSSP Bind with the given domain, username and password func (l *Conn) NTLMBind(domain, username, password string) error { req := &NTLMBindRequest{ Domain: domain, Username: username, Password: password, } _, err := l.NTLMChallengeBind(req) return err } // NTLMBindWithHash performs an NTLM Bind with an NTLM hash instead of plaintext password (pass-the-hash) func (l *Conn) NTLMBindWithHash(domain, username, hash string) error { req := &NTLMBindRequest{ Domain: domain, Username: username, Hash: hash, } _, err := l.NTLMChallengeBind(req) return err } // NTLMChallengeBind performs the NTLMSSP bind operation defined in the given request func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindResult, error) { if ntlmBindRequest.Password == "" && ntlmBindRequest.Hash == "" { return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client")) } msgCtx, err := l.doRequest(ntlmBindRequest) if err != nil { return nil, err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return nil, err } l.Debug.Printf("%d: got response %p", msgCtx.id, packet) if l.Debug { if err = addLDAPDescriptions(packet); err != nil { return nil, err } ber.PrintPacket(packet) } result := &NTLMBindResult{ Controls: make([]Control, 0), } var ntlmsspChallenge []byte // now find the NTLM Response Message if len(packet.Children) == 2 { if len(packet.Children[1].Children) == 3 { child := packet.Children[1].Children[1] ntlmsspChallenge = child.ByteValue // Check to make sure we got the right message. It will always start with NTLMSSP if len(ntlmsspChallenge) < 7 || !bytes.Equal(ntlmsspChallenge[:7], []byte("NTLMSSP")) { return result, GetLDAPError(packet) } l.Debug.Printf("%d: found ntlmssp challenge", msgCtx.id) } } if ntlmsspChallenge != nil { var err error var responseMessage []byte // generate a response message to the challenge with the given Username/Password if password is provided if ntlmBindRequest.Password != "" { responseMessage, err = ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password) } else if ntlmBindRequest.Hash != "" { responseMessage, err = ntlmssp.ProcessChallengeWithHash(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Hash) } else { err = fmt.Errorf("need a password or hash to generate reply") } if err != nil { return result, fmt.Errorf("parsing ntlm-challenge: %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")) request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) // append the challenge response message as a TagEmbeddedPDV BER value auth := ber.Encode(ber.ClassContext, ber.TypePrimitive, ber.TagEmbeddedPDV, responseMessage, "authentication") request.AppendChild(auth) packet.AppendChild(request) msgCtx, err = l.sendMessage(packet) if err != nil { return nil, fmt.Errorf("send message: %s", err) } defer l.finishMessage(msgCtx) packetResponse, ok := <-msgCtx.responses if !ok { return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed")) } packet, err = packetResponse.ReadPacket() l.Debug.Printf("%d: got response %p", msgCtx.id, packet) if err != nil { return nil, fmt.Errorf("read packet: %s", err) } } err = GetLDAPError(packet) return result, err } ldap-3.4.1/v3/client.go000066400000000000000000000014571410654160200146020ustar00rootroot00000000000000package ldap import ( "crypto/tls" "time" ) // Client knows how to interact with an LDAP server type Client interface { Start() StartTLS(*tls.Config) error Close() IsClosing() bool SetTimeout(time.Duration) Bind(username, password string) error UnauthenticatedBind(username string) error SimpleBind(*SimpleBindRequest) (*SimpleBindResult, error) ExternalBind() error Add(*AddRequest) error Del(*DelRequest) error Modify(*ModifyRequest) error ModifyDN(*ModifyDNRequest) error ModifyWithResult(*ModifyRequest) (*ModifyResult, error) Compare(dn, attribute, value string) (bool, error) PasswordModify(*PasswordModifyRequest) (*PasswordModifyResult, error) Search(*SearchRequest) (*SearchResult, error) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) } ldap-3.4.1/v3/compare.go000066400000000000000000000033361410654160200147500ustar00rootroot00000000000000package ldap import ( "fmt" ber "github.com/go-asn1-ber/asn1-ber" ) // CompareRequest represents an LDAP CompareRequest operation. type CompareRequest struct { DN string Attribute string Value string } func (req *CompareRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationCompareRequest, nil, "Compare Request") pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN")) ava := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "AttributeValueAssertion") ava.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.Attribute, "AttributeDesc")) ava.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.Value, "AssertionValue")) pkt.AppendChild(ava) envelope.AppendChild(pkt) return nil } // Compare checks to see if the attribute of the dn matches value. Returns true if it does otherwise // false with any error that occurs if any. func (l *Conn) Compare(dn, attribute, value string) (bool, error) { msgCtx, err := l.doRequest(&CompareRequest{ DN: dn, Attribute: attribute, Value: value}) if err != nil { return false, err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return false, err } if packet.Children[1].Tag == ApplicationCompareResponse { err := GetLDAPError(packet) switch { case IsErrorWithCode(err, LDAPResultCompareTrue): return true, nil case IsErrorWithCode(err, LDAPResultCompareFalse): return false, nil default: return false, err } } return false, fmt.Errorf("unexpected Response: %d", packet.Children[1].Tag) } ldap-3.4.1/v3/conn.go000066400000000000000000000366501410654160200142640ustar00rootroot00000000000000package ldap import ( "bufio" "crypto/tls" "errors" "fmt" "log" "net" "net/url" "sync" "sync/atomic" "time" ber "github.com/go-asn1-ber/asn1-ber" ) const ( // MessageQuit causes the processMessages loop to exit MessageQuit = 0 // MessageRequest sends a request to the server MessageRequest = 1 // MessageResponse receives a response from the server MessageResponse = 2 // MessageFinish indicates the client considers a particular message ID to be finished MessageFinish = 3 // MessageTimeout indicates the client-specified timeout for a particular message ID has been reached MessageTimeout = 4 ) const ( // DefaultLdapPort default ldap port for pure TCP connection DefaultLdapPort = "389" // DefaultLdapsPort default ldap port for SSL connection DefaultLdapsPort = "636" ) // PacketResponse contains the packet or error encountered reading a response type PacketResponse struct { // Packet is the packet read from the server Packet *ber.Packet // Error is an error encountered while reading Error error } // ReadPacket returns the packet or an error func (pr *PacketResponse) ReadPacket() (*ber.Packet, error) { if (pr == nil) || (pr.Packet == nil && pr.Error == nil) { return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve response")) } return pr.Packet, pr.Error } type messageContext struct { id int64 // close(done) should only be called from finishMessage() done chan struct{} // close(responses) should only be called from processMessages(), and only sent to from sendResponse() responses chan *PacketResponse } // sendResponse should only be called within the processMessages() loop which // is also responsible for closing the responses channel. func (msgCtx *messageContext) sendResponse(packet *PacketResponse) { select { case msgCtx.responses <- packet: // Successfully sent packet to message handler. case <-msgCtx.done: // The request handler is done and will not receive more // packets. } } type messagePacket struct { Op int MessageID int64 Packet *ber.Packet Context *messageContext } type sendMessageFlags uint const ( startTLS sendMessageFlags = 1 << iota ) // Conn represents an LDAP Connection type Conn struct { // requestTimeout is loaded atomically // so we need to ensure 64-bit alignment on 32-bit platforms. requestTimeout int64 conn net.Conn isTLS bool closing uint32 closeErr atomic.Value isStartingTLS bool Debug debugging chanConfirm chan struct{} messageContexts map[int64]*messageContext chanMessage chan *messagePacket chanMessageID chan int64 wgClose sync.WaitGroup outstandingRequests uint messageMutex sync.Mutex } var _ Client = &Conn{} // DefaultTimeout is a package-level variable that sets the timeout value // used for the Dial and DialTLS methods. // // WARNING: since this is a package-level variable, setting this value from // multiple places will probably result in undesired behaviour. var DefaultTimeout = 60 * time.Second // DialOpt configures DialContext. type DialOpt func(*DialContext) // DialWithDialer updates net.Dialer in DialContext. func DialWithDialer(d *net.Dialer) DialOpt { return func(dc *DialContext) { dc.d = d } } // DialWithTLSConfig updates tls.Config in DialContext. func DialWithTLSConfig(tc *tls.Config) DialOpt { return func(dc *DialContext) { dc.tc = tc } } // DialWithTLSDialer is a wrapper for DialWithTLSConfig with the option to // specify a net.Dialer to for example define a timeout or a custom resolver. func DialWithTLSDialer(tlsConfig *tls.Config, dialer *net.Dialer) DialOpt { return func(dc *DialContext) { dc.tc = tlsConfig dc.d = dialer } } // DialContext contains necessary parameters to dial the given ldap URL. type DialContext struct { d *net.Dialer tc *tls.Config } 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" } return dc.d.Dial("unix", u.Path) } host, port, err := net.SplitHostPort(u.Host) if err != nil { // we assume that error is due to missing port host = u.Host port = "" } switch u.Scheme { case "ldap": if port == "" { port = DefaultLdapPort } return dc.d.Dial("tcp", net.JoinHostPort(host, port)) case "ldaps": if port == "" { port = DefaultLdapsPort } return tls.DialWithDialer(dc.d, "tcp", net.JoinHostPort(host, port), dc.tc) } return nil, fmt.Errorf("Unknown scheme '%s'", u.Scheme) } // Dial connects to the given address on the given network using net.Dial // and then returns a new Conn for the connection. // @deprecated Use DialURL instead. func Dial(network, addr string) (*Conn, error) { c, err := net.DialTimeout(network, addr, DefaultTimeout) if err != nil { return nil, NewError(ErrorNetwork, err) } conn := NewConn(c, false) conn.Start() return conn, nil } // DialTLS connects to the given address on the given network using tls.Dial // and then returns a new Conn for the connection. // @deprecated Use DialURL instead. func DialTLS(network, addr string, config *tls.Config) (*Conn, error) { c, err := tls.DialWithDialer(&net.Dialer{Timeout: DefaultTimeout}, network, addr, config) if err != nil { return nil, NewError(ErrorNetwork, err) } conn := NewConn(c, true) conn.Start() return conn, nil } // DialURL connects to the given ldap URL. // The following schemas are supported: ldap://, ldaps://, ldapi://. // On success a new Conn for the connection is returned. func DialURL(addr string, opts ...DialOpt) (*Conn, error) { u, err := url.Parse(addr) if err != nil { return nil, NewError(ErrorNetwork, err) } var dc DialContext for _, opt := range opts { opt(&dc) } if dc.d == nil { dc.d = &net.Dialer{Timeout: DefaultTimeout} } c, err := dc.dial(u) if err != nil { return nil, NewError(ErrorNetwork, err) } conn := NewConn(c, u.Scheme == "ldaps") conn.Start() return conn, nil } // NewConn returns a new Conn using conn for network I/O. func NewConn(conn net.Conn, isTLS bool) *Conn { return &Conn{ conn: conn, chanConfirm: make(chan struct{}), chanMessageID: make(chan int64), chanMessage: make(chan *messagePacket, 10), messageContexts: map[int64]*messageContext{}, requestTimeout: 0, isTLS: isTLS, } } // Start initializes goroutines to read responses and process messages func (l *Conn) Start() { l.wgClose.Add(1) go l.reader() go l.processMessages() } // IsClosing returns whether or not we're currently closing. func (l *Conn) IsClosing() bool { return atomic.LoadUint32(&l.closing) == 1 } // setClosing sets the closing value to true func (l *Conn) setClosing() bool { return atomic.CompareAndSwapUint32(&l.closing, 0, 1) } // Close closes the connection. func (l *Conn) Close() { l.messageMutex.Lock() defer l.messageMutex.Unlock() if l.setClosing() { l.Debug.Printf("Sending quit message and waiting for confirmation") l.chanMessage <- &messagePacket{Op: MessageQuit} <-l.chanConfirm close(l.chanMessage) l.Debug.Printf("Closing network connection") if err := l.conn.Close(); err != nil { log.Println(err) } l.wgClose.Done() } l.wgClose.Wait() } // SetTimeout sets the time after a request is sent that a MessageTimeout triggers func (l *Conn) SetTimeout(timeout time.Duration) { if timeout > 0 { atomic.StoreInt64(&l.requestTimeout, int64(timeout)) } } // Returns the next available messageID func (l *Conn) nextMessageID() int64 { if messageID, ok := <-l.chanMessageID; ok { return messageID } return 0 } // 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 { return NewError(ErrorNetwork, errors.New("ldap: already encrypted")) } 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")) request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Start TLS") request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, "1.3.6.1.4.1.1466.20037", "TLS Extended Command")) packet.AppendChild(request) l.Debug.PrintPacket(packet) msgCtx, err := l.sendMessageWithFlags(packet, startTLS) if err != nil { return err } defer l.finishMessage(msgCtx) l.Debug.Printf("%d: waiting for response", msgCtx.id) packetResponse, ok := <-msgCtx.responses if !ok { return NewError(ErrorNetwork, errors.New("ldap: response channel closed")) } packet, err = packetResponse.ReadPacket() l.Debug.Printf("%d: got response %p", msgCtx.id, packet) if err != nil { return err } if l.Debug { if err := addLDAPDescriptions(packet); err != nil { l.Close() return err } l.Debug.PrintPacket(packet) } if err := GetLDAPError(packet); err == nil { conn := tls.Client(l.conn, config) if connErr := conn.Handshake(); connErr != nil { l.Close() return NewError(ErrorNetwork, fmt.Errorf("TLS handshake failed (%v)", connErr)) } l.isTLS = true l.conn = conn } else { return err } go l.reader() return nil } // TLSConnectionState returns the client's TLS connection state. // The return values are their zero values if StartTLS did // not succeed. func (l *Conn) TLSConnectionState() (state tls.ConnectionState, ok bool) { tc, ok := l.conn.(*tls.Conn) if !ok { return } return tc.ConnectionState(), true } func (l *Conn) sendMessage(packet *ber.Packet) (*messageContext, error) { return l.sendMessageWithFlags(packet, 0) } func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) (*messageContext, error) { if l.IsClosing() { return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed")) } l.messageMutex.Lock() l.Debug.Printf("flags&startTLS = %d", flags&startTLS) if l.isStartingTLS { l.messageMutex.Unlock() return nil, NewError(ErrorNetwork, errors.New("ldap: connection is in startls phase")) } if flags&startTLS != 0 { if l.outstandingRequests != 0 { l.messageMutex.Unlock() return nil, NewError(ErrorNetwork, errors.New("ldap: cannot StartTLS with outstanding requests")) } l.isStartingTLS = true } l.outstandingRequests++ l.messageMutex.Unlock() responses := make(chan *PacketResponse) messageID := packet.Children[0].Value.(int64) message := &messagePacket{ Op: MessageRequest, MessageID: messageID, Packet: packet, Context: &messageContext{ id: messageID, done: make(chan struct{}), responses: responses, }, } if !l.sendProcessMessage(message) { if l.IsClosing() { return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed")) } return nil, NewError(ErrorNetwork, errors.New("ldap: could not send message for unknown reason")) } return message.Context, nil } func (l *Conn) finishMessage(msgCtx *messageContext) { close(msgCtx.done) if l.IsClosing() { return } l.messageMutex.Lock() l.outstandingRequests-- if l.isStartingTLS { l.isStartingTLS = false } l.messageMutex.Unlock() message := &messagePacket{ Op: MessageFinish, MessageID: msgCtx.id, } l.sendProcessMessage(message) } func (l *Conn) sendProcessMessage(message *messagePacket) bool { l.messageMutex.Lock() defer l.messageMutex.Unlock() if l.IsClosing() { return false } l.chanMessage <- message return true } func (l *Conn) processMessages() { defer func() { if err := recover(); err != nil { log.Printf("ldap: recovered panic in processMessages: %v", err) } for messageID, msgCtx := range l.messageContexts { // If we are closing due to an error, inform anyone who // is waiting about the error. if l.IsClosing() && l.closeErr.Load() != nil { msgCtx.sendResponse(&PacketResponse{Error: l.closeErr.Load().(error)}) } l.Debug.Printf("Closing channel for MessageID %d", messageID) close(msgCtx.responses) delete(l.messageContexts, messageID) } close(l.chanMessageID) close(l.chanConfirm) }() var messageID int64 = 1 for { select { case l.chanMessageID <- messageID: messageID++ case message := <-l.chanMessage: switch message.Op { case MessageQuit: l.Debug.Printf("Shutting down - quit message received") return case MessageRequest: // Add to message list and write to network l.Debug.Printf("Sending message %d", message.MessageID) buf := message.Packet.Bytes() _, err := l.conn.Write(buf) if err != nil { l.Debug.Printf("Error Sending Message: %s", err.Error()) message.Context.sendResponse(&PacketResponse{Error: fmt.Errorf("unable to send request: %s", err)}) close(message.Context.responses) break } // Only add to messageContexts if we were able to // successfully write the message. l.messageContexts[message.MessageID] = message.Context // Add timeout if defined requestTimeout := time.Duration(atomic.LoadInt64(&l.requestTimeout)) if requestTimeout > 0 { go func() { defer func() { if err := recover(); err != nil { log.Printf("ldap: recovered panic in RequestTimeout: %v", err) } }() time.Sleep(requestTimeout) timeoutMessage := &messagePacket{ Op: MessageTimeout, MessageID: message.MessageID, } l.sendProcessMessage(timeoutMessage) }() } case MessageResponse: l.Debug.Printf("Receiving message %d", message.MessageID) if msgCtx, ok := l.messageContexts[message.MessageID]; ok { msgCtx.sendResponse(&PacketResponse{message.Packet, nil}) } else { log.Printf("Received unexpected message %d, %v", message.MessageID, l.IsClosing()) l.Debug.PrintPacket(message.Packet) } case MessageTimeout: // Handle the timeout by closing the channel // All reads will return immediately if msgCtx, ok := l.messageContexts[message.MessageID]; ok { l.Debug.Printf("Receiving message timeout for %d", message.MessageID) msgCtx.sendResponse(&PacketResponse{message.Packet, NewError(ErrorNetwork, errors.New("ldap: connection timed out"))}) delete(l.messageContexts, message.MessageID) close(msgCtx.responses) } case MessageFinish: l.Debug.Printf("Finished message %d", message.MessageID) if msgCtx, ok := l.messageContexts[message.MessageID]; ok { delete(l.messageContexts, message.MessageID) close(msgCtx.responses) } } } } } func (l *Conn) reader() { cleanstop := false defer func() { if err := recover(); err != nil { log.Printf("ldap: recovered panic in reader: %v", err) } if !cleanstop { l.Close() } }() bufConn := bufio.NewReader(l.conn) for { if cleanstop { l.Debug.Printf("reader clean stopping (without closing the connection)") return } packet, err := ber.ReadPacket(bufConn) if err != nil { // A read error is expected here if we are closing the connection... if !l.IsClosing() { l.closeErr.Store(fmt.Errorf("unable to read LDAP response packet: %s", err)) l.Debug.Printf("reader error: %s", err) } return } if err := addLDAPDescriptions(packet); err != nil { l.Debug.Printf("descriptions error: %s", err) } if len(packet.Children) == 0 { l.Debug.Printf("Received bad ldap packet") continue } l.messageMutex.Lock() if l.isStartingTLS { cleanstop = true } l.messageMutex.Unlock() message := &messagePacket{ Op: MessageResponse, MessageID: packet.Children[0].Value.(int64), Packet: packet, } if !l.sendProcessMessage(message) { return } } } ldap-3.4.1/v3/conn_test.go000066400000000000000000000240471410654160200153200ustar00rootroot00000000000000package ldap import ( "bytes" "errors" "io" "net" "net/http" "net/http/httptest" "runtime" "sync" "testing" "time" ber "github.com/go-asn1-ber/asn1-ber" ) func TestUnresponsiveConnection(t *testing.T) { // The do-nothing server that accepts requests and does nothing ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { })) defer ts.Close() c, err := net.Dial(ts.Listener.Addr().Network(), ts.Listener.Addr().String()) if err != nil { t.Fatalf("error connecting to localhost tcp: %v", err) } // Create an Ldap connection conn := NewConn(c, false) conn.SetTimeout(time.Millisecond) conn.Start() defer conn.Close() // Mock a packet packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, conn.nextMessageID(), "MessageID")) bindRequest := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") bindRequest.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) packet.AppendChild(bindRequest) // Send packet and test response msgCtx, err := conn.sendMessage(packet) if err != nil { t.Fatalf("error sending message: %v", err) } defer conn.finishMessage(msgCtx) packetResponse, ok := <-msgCtx.responses if !ok { t.Fatalf("no PacketResponse in response channel") } packet, err = packetResponse.ReadPacket() if err == nil { t.Fatalf("expected timeout error") } if !IsErrorWithCode(err, ErrorNetwork) || err.(*Error).Err.Error() != "ldap: connection timed out" { t.Fatalf("unexpected error: %v", err) } } // TestFinishMessage tests that we do not enter deadlock when a goroutine makes // a request but does not handle all responses from the server. func TestFinishMessage(t *testing.T) { ptc := newPacketTranslatorConn() defer ptc.Close() conn := NewConn(ptc, false) conn.Start() // Test sending 5 different requests in series. Ensure that we can // get a response packet from the underlying connection and also // ensure that we can gracefully ignore unhandled responses. for i := 0; i < 5; i++ { t.Logf("serial request %d", i) // Create a message and make sure we can receive responses. msgCtx := testSendRequest(t, ptc, conn) testReceiveResponse(t, ptc, msgCtx) // Send a few unhandled responses and finish the message. testSendUnhandledResponsesAndFinish(t, ptc, conn, msgCtx, 5) t.Logf("serial request %d done", i) } // Test sending 5 different requests in parallel. var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go func(i int) { defer wg.Done() t.Logf("parallel request %d", i) // Create a message and make sure we can receive responses. msgCtx := testSendRequest(t, ptc, conn) testReceiveResponse(t, ptc, msgCtx) // Send a few unhandled responses and finish the message. testSendUnhandledResponsesAndFinish(t, ptc, conn, msgCtx, 5) t.Logf("parallel request %d done", i) }(i) } wg.Wait() // We cannot run Close() in a defer because t.FailNow() will run it and // it will block if the processMessage Loop is in a deadlock. conn.Close() } // See: https://github.com/go-ldap/ldap/issues/332 func TestNilConnection(t *testing.T) { var conn *Conn _, err := conn.Search(&SearchRequest{}) if err != ErrNilConnection { t.Fatalf("expected error to be ErrNilConnection, got %v", err) } } func testSendRequest(t *testing.T, ptc *packetTranslatorConn, conn *Conn) (msgCtx *messageContext) { var msgID int64 runWithTimeout(t, time.Second, func() { msgID = conn.nextMessageID() }) requestPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") requestPacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, msgID, "MessageID")) var err error runWithTimeout(t, time.Second, func() { msgCtx, err = conn.sendMessage(requestPacket) if err != nil { t.Fatalf("unable to send request message: %s", err) } }) // We should now be able to get this request packet out from the other // side. runWithTimeout(t, time.Second, func() { if _, err = ptc.ReceiveRequest(); err != nil { t.Fatalf("unable to receive request packet: %s", err) } }) return msgCtx } func testReceiveResponse(t *testing.T, ptc *packetTranslatorConn, msgCtx *messageContext) { // Send a mock response packet. responsePacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Response") responsePacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, msgCtx.id, "MessageID")) runWithTimeout(t, time.Second, func() { if err := ptc.SendResponse(responsePacket); err != nil { t.Fatalf("unable to send response packet: %s", err) } }) // We should be able to receive the packet from the connection. runWithTimeout(t, time.Second, func() { if _, ok := <-msgCtx.responses; !ok { t.Fatal("response channel closed") } }) } func testSendUnhandledResponsesAndFinish(t *testing.T, ptc *packetTranslatorConn, conn *Conn, msgCtx *messageContext, numResponses int) { // Send a mock response packet. responsePacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Response") responsePacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, msgCtx.id, "MessageID")) // Send extra responses but do not attempt to receive them on the // client side. for i := 0; i < numResponses; i++ { runWithTimeout(t, time.Second, func() { if err := ptc.SendResponse(responsePacket); err != nil { t.Fatalf("unable to send response packet: %s", err) } }) } // Finally, attempt to finish this message. runWithTimeout(t, time.Second, func() { conn.finishMessage(msgCtx) }) } func runWithTimeout(t *testing.T, timeout time.Duration, f func()) { done := make(chan struct{}) go func() { f() close(done) }() select { case <-done: // Success! case <-time.After(timeout): _, file, line, _ := runtime.Caller(1) t.Fatalf("%s:%d timed out", file, line) } } // packetTranslatorConn is a helpful type which can be used with various tests // in this package. It implements the net.Conn interface to be used as an // underlying connection for a *ldap.Conn. Most methods are no-ops but the // Read() and Write() methods are able to translate ber-encoded packets for // testing LDAP requests and responses. // // Test cases can simulate an LDAP server sending a response by calling the // SendResponse() method with a ber-encoded LDAP response packet. Test cases // can simulate an LDAP server receiving a request from a client by calling the // ReceiveRequest() method which returns a ber-encoded LDAP request packet. type packetTranslatorConn struct { lock sync.Mutex isClosed bool responseCond sync.Cond requestCond sync.Cond responseBuf bytes.Buffer requestBuf bytes.Buffer } var errPacketTranslatorConnClosed = errors.New("connection closed") func newPacketTranslatorConn() *packetTranslatorConn { conn := &packetTranslatorConn{} conn.responseCond = sync.Cond{L: &conn.lock} conn.requestCond = sync.Cond{L: &conn.lock} return conn } // Read is called by the reader() loop to receive response packets. It will // block until there are more packet bytes available or this connection is // closed. func (c *packetTranslatorConn) Read(b []byte) (n int, err error) { c.lock.Lock() defer c.lock.Unlock() for !c.isClosed { // Attempt to read data from the response buffer. If it fails // with an EOF, wait and try again. n, err = c.responseBuf.Read(b) if err != io.EOF { return n, err } c.responseCond.Wait() } return 0, errPacketTranslatorConnClosed } // SendResponse writes the given response packet to the response buffer for // this connection, signalling any goroutine waiting to read a response. func (c *packetTranslatorConn) SendResponse(packet *ber.Packet) error { c.lock.Lock() defer c.lock.Unlock() if c.isClosed { return errPacketTranslatorConnClosed } // Signal any goroutine waiting to read a response. defer c.responseCond.Broadcast() // Writes to the buffer should always succeed. c.responseBuf.Write(packet.Bytes()) return nil } // Write is called by the processMessages() loop to send request packets. func (c *packetTranslatorConn) Write(b []byte) (n int, err error) { c.lock.Lock() defer c.lock.Unlock() if c.isClosed { return 0, errPacketTranslatorConnClosed } // Signal any goroutine waiting to read a request. defer c.requestCond.Broadcast() // Writes to the buffer should always succeed. return c.requestBuf.Write(b) } // ReceiveRequest attempts to read a request packet from this connection. It // will block until it is able to read a full request packet or until this // connection is closed. func (c *packetTranslatorConn) ReceiveRequest() (*ber.Packet, error) { c.lock.Lock() defer c.lock.Unlock() for !c.isClosed { // Attempt to parse a request packet from the request buffer. // If it fails with an unexpected EOF, wait and try again. requestReader := bytes.NewReader(c.requestBuf.Bytes()) packet, err := ber.ReadPacket(requestReader) switch err { case io.EOF, io.ErrUnexpectedEOF: c.requestCond.Wait() case nil: // Advance the request buffer by the number of bytes // read to decode the request packet. c.requestBuf.Next(c.requestBuf.Len() - requestReader.Len()) return packet, nil default: return nil, err } } return nil, errPacketTranslatorConnClosed } // Close closes this connection causing Read() and Write() calls to fail. func (c *packetTranslatorConn) Close() error { c.lock.Lock() defer c.lock.Unlock() c.isClosed = true c.responseCond.Broadcast() c.requestCond.Broadcast() return nil } func (c *packetTranslatorConn) LocalAddr() net.Addr { return (*net.TCPAddr)(nil) } func (c *packetTranslatorConn) RemoteAddr() net.Addr { return (*net.TCPAddr)(nil) } func (c *packetTranslatorConn) SetDeadline(t time.Time) error { return nil } func (c *packetTranslatorConn) SetReadDeadline(t time.Time) error { return nil } func (c *packetTranslatorConn) SetWriteDeadline(t time.Time) error { return nil } ldap-3.4.1/v3/control.go000066400000000000000000000447111410654160200150040ustar00rootroot00000000000000package ldap import ( "fmt" "strconv" ber "github.com/go-asn1-ber/asn1-ber" ) const ( // ControlTypePaging - https://www.ietf.org/rfc/rfc2696.txt ControlTypePaging = "1.2.840.113556.1.4.319" // ControlTypeBeheraPasswordPolicy - https://tools.ietf.org/html/draft-behera-ldap-password-policy-10 ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1" // ControlTypeVChuPasswordMustChange - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00 ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4" // ControlTypeVChuPasswordWarning - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00 ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5" // ControlTypeManageDsaIT - https://tools.ietf.org/html/rfc3296 ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2" // ControlTypeWhoAmI - https://tools.ietf.org/html/rfc4532 ControlTypeWhoAmI = "1.3.6.1.4.1.4203.1.11.3" // ControlTypeMicrosoftNotification - https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx ControlTypeMicrosoftNotification = "1.2.840.113556.1.4.528" // ControlTypeMicrosoftShowDeleted - https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx ControlTypeMicrosoftShowDeleted = "1.2.840.113556.1.4.417" // ControlTypeMicrosoftServerLinkTTL - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f4f523a8-abc0-4b3a-a471-6b2fef135481?redirectedfrom=MSDN ControlTypeMicrosoftServerLinkTTL = "1.2.840.113556.1.4.2309" ) // ControlTypeMap maps controls to text descriptions var ControlTypeMap = map[string]string{ ControlTypePaging: "Paging", ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft", ControlTypeManageDsaIT: "Manage DSA IT", ControlTypeMicrosoftNotification: "Change Notification - Microsoft", ControlTypeMicrosoftShowDeleted: "Show Deleted Objects - Microsoft", ControlTypeMicrosoftServerLinkTTL: "Return TTL-DNs for link values with associated expiry times - Microsoft", } // Control defines an interface controls provide to encode and describe themselves type Control interface { // GetControlType returns the OID GetControlType() string // Encode returns the ber packet representation Encode() *ber.Packet // String returns a human-readable description String() string } // ControlString implements the Control interface for simple controls type ControlString struct { ControlType string Criticality bool ControlValue string } // GetControlType returns the OID func (c *ControlString) GetControlType() string { return c.ControlType } // Encode returns the ber packet representation func (c *ControlString) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.ControlType, "Control Type ("+ControlTypeMap[c.ControlType]+")")) if c.Criticality { packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) } if c.ControlValue != "" { packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, string(c.ControlValue), "Control Value")) } return packet } // String returns a human-readable description func (c *ControlString) String() string { return fmt.Sprintf("Control Type: %s (%q) Criticality: %t Control Value: %s", ControlTypeMap[c.ControlType], c.ControlType, c.Criticality, c.ControlValue) } // ControlPaging implements the paging control described in https://www.ietf.org/rfc/rfc2696.txt type ControlPaging struct { // PagingSize indicates the page size PagingSize uint32 // Cookie is an opaque value returned by the server to track a paging cursor Cookie []byte } // GetControlType returns the OID func (c *ControlPaging) GetControlType() string { return ControlTypePaging } // Encode returns the ber packet representation func (c *ControlPaging) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypePaging, "Control Type ("+ControlTypeMap[ControlTypePaging]+")")) p2 := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (Paging)") seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Search Control Value") seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.PagingSize), "Paging Size")) cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie") cookie.Value = c.Cookie cookie.Data.Write(c.Cookie) seq.AppendChild(cookie) p2.AppendChild(seq) packet.AppendChild(p2) return packet } // String returns a human-readable description func (c *ControlPaging) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t PagingSize: %d Cookie: %q", ControlTypeMap[ControlTypePaging], ControlTypePaging, false, c.PagingSize, c.Cookie) } // SetCookie stores the given cookie in the paging control func (c *ControlPaging) SetCookie(cookie []byte) { c.Cookie = cookie } // ControlBeheraPasswordPolicy implements the control described in https://tools.ietf.org/html/draft-behera-ldap-password-policy-10 type ControlBeheraPasswordPolicy struct { // Expire contains the number of seconds before a password will expire Expire int64 // Grace indicates the remaining number of times a user will be allowed to authenticate with an expired password Grace int64 // Error indicates the error code Error int8 // ErrorString is a human readable error ErrorString string } // GetControlType returns the OID func (c *ControlBeheraPasswordPolicy) GetControlType() string { return ControlTypeBeheraPasswordPolicy } // Encode returns the ber packet representation func (c *ControlBeheraPasswordPolicy) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeBeheraPasswordPolicy, "Control Type ("+ControlTypeMap[ControlTypeBeheraPasswordPolicy]+")")) return packet } // String returns a human-readable description func (c *ControlBeheraPasswordPolicy) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t Expire: %d Grace: %d Error: %d, ErrorString: %s", ControlTypeMap[ControlTypeBeheraPasswordPolicy], ControlTypeBeheraPasswordPolicy, false, c.Expire, c.Grace, c.Error, c.ErrorString) } // ControlVChuPasswordMustChange implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00 type ControlVChuPasswordMustChange struct { // MustChange indicates if the password is required to be changed MustChange bool } // GetControlType returns the OID func (c *ControlVChuPasswordMustChange) GetControlType() string { return ControlTypeVChuPasswordMustChange } // Encode returns the ber packet representation func (c *ControlVChuPasswordMustChange) Encode() *ber.Packet { return nil } // String returns a human-readable description func (c *ControlVChuPasswordMustChange) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t MustChange: %v", ControlTypeMap[ControlTypeVChuPasswordMustChange], ControlTypeVChuPasswordMustChange, false, c.MustChange) } // ControlVChuPasswordWarning implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00 type ControlVChuPasswordWarning struct { // Expire indicates the time in seconds until the password expires Expire int64 } // GetControlType returns the OID func (c *ControlVChuPasswordWarning) GetControlType() string { return ControlTypeVChuPasswordWarning } // Encode returns the ber packet representation func (c *ControlVChuPasswordWarning) Encode() *ber.Packet { return nil } // String returns a human-readable description func (c *ControlVChuPasswordWarning) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t Expire: %b", ControlTypeMap[ControlTypeVChuPasswordWarning], ControlTypeVChuPasswordWarning, false, c.Expire) } // ControlManageDsaIT implements the control described in https://tools.ietf.org/html/rfc3296 type ControlManageDsaIT struct { // Criticality indicates if this control is required Criticality bool } // GetControlType returns the OID func (c *ControlManageDsaIT) GetControlType() string { return ControlTypeManageDsaIT } // Encode returns the ber packet representation func (c *ControlManageDsaIT) Encode() *ber.Packet { //FIXME packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeManageDsaIT, "Control Type ("+ControlTypeMap[ControlTypeManageDsaIT]+")")) if c.Criticality { packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) } return packet } // String returns a human-readable description func (c *ControlManageDsaIT) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t", ControlTypeMap[ControlTypeManageDsaIT], ControlTypeManageDsaIT, c.Criticality) } // NewControlManageDsaIT returns a ControlManageDsaIT control func NewControlManageDsaIT(Criticality bool) *ControlManageDsaIT { return &ControlManageDsaIT{Criticality: Criticality} } // ControlMicrosoftNotification implements the control described in https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx type ControlMicrosoftNotification struct{} // GetControlType returns the OID func (c *ControlMicrosoftNotification) GetControlType() string { return ControlTypeMicrosoftNotification } // Encode returns the ber packet representation func (c *ControlMicrosoftNotification) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftNotification, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftNotification]+")")) return packet } // String returns a human-readable description func (c *ControlMicrosoftNotification) String() string { return fmt.Sprintf( "Control Type: %s (%q)", ControlTypeMap[ControlTypeMicrosoftNotification], ControlTypeMicrosoftNotification) } // NewControlMicrosoftNotification returns a ControlMicrosoftNotification control func NewControlMicrosoftNotification() *ControlMicrosoftNotification { return &ControlMicrosoftNotification{} } // ControlMicrosoftShowDeleted implements the control described in https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx type ControlMicrosoftShowDeleted struct{} // GetControlType returns the OID func (c *ControlMicrosoftShowDeleted) GetControlType() string { return ControlTypeMicrosoftShowDeleted } // Encode returns the ber packet representation func (c *ControlMicrosoftShowDeleted) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftShowDeleted, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftShowDeleted]+")")) return packet } // String returns a human-readable description func (c *ControlMicrosoftShowDeleted) String() string { return fmt.Sprintf( "Control Type: %s (%q)", ControlTypeMap[ControlTypeMicrosoftShowDeleted], ControlTypeMicrosoftShowDeleted) } // NewControlMicrosoftShowDeleted returns a ControlMicrosoftShowDeleted control func NewControlMicrosoftShowDeleted() *ControlMicrosoftShowDeleted { return &ControlMicrosoftShowDeleted{} } // ControlMicrosoftServerLinkTTL implements the control described in https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f4f523a8-abc0-4b3a-a471-6b2fef135481?redirectedfrom=MSDN type ControlMicrosoftServerLinkTTL struct{} // GetControlType returns the OID func (c *ControlMicrosoftServerLinkTTL) GetControlType() string { return ControlTypeMicrosoftServerLinkTTL } // Encode returns the ber packet representation func (c *ControlMicrosoftServerLinkTTL) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftServerLinkTTL, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftServerLinkTTL]+")")) return packet } // String returns a human-readable description func (c *ControlMicrosoftServerLinkTTL) String() string { return fmt.Sprintf( "Control Type: %s (%q)", ControlTypeMap[ControlTypeMicrosoftServerLinkTTL], ControlTypeMicrosoftServerLinkTTL) } // NewControlMicrosoftServerLinkTTL returns a ControlMicrosoftServerLinkTTL control func NewControlMicrosoftServerLinkTTL() *ControlMicrosoftServerLinkTTL { return &ControlMicrosoftServerLinkTTL{} } // FindControl returns the first control of the given type in the list, or nil func FindControl(controls []Control, controlType string) Control { for _, c := range controls { if c.GetControlType() == controlType { return c } } return nil } // DecodeControl returns a control read from the given packet, or nil if no recognized control can be made func DecodeControl(packet *ber.Packet) (Control, error) { var ( ControlType = "" Criticality = false value *ber.Packet ) switch len(packet.Children) { case 0: // at least one child is required for control type return nil, fmt.Errorf("at least one child is required for control type") case 1: // just type, no criticality or value packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")" ControlType = packet.Children[0].Value.(string) case 2: packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")" ControlType = packet.Children[0].Value.(string) // 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 { packet.Children[1].Description = "Criticality" Criticality = packet.Children[1].Value.(bool) } else { packet.Children[1].Description = "Control Value" value = packet.Children[1] } case 3: packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")" ControlType = packet.Children[0].Value.(string) packet.Children[1].Description = "Criticality" Criticality = packet.Children[1].Value.(bool) packet.Children[2].Description = "Control Value" value = packet.Children[2] default: // more than 3 children is invalid return nil, fmt.Errorf("more than 3 children is invalid for controls") } switch ControlType { case ControlTypeManageDsaIT: return NewControlManageDsaIT(Criticality), nil case ControlTypePaging: value.Description += " (Paging)" c := new(ControlPaging) if value.Value != nil { valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) if err != nil { return nil, fmt.Errorf("failed to decode data bytes: %s", err) } value.Data.Truncate(0) value.Value = nil value.AppendChild(valueChildren) } value = value.Children[0] value.Description = "Search Control Value" value.Children[0].Description = "Paging Size" value.Children[1].Description = "Cookie" c.PagingSize = uint32(value.Children[0].Value.(int64)) c.Cookie = value.Children[1].Data.Bytes() value.Children[1].Value = c.Cookie return c, nil case ControlTypeBeheraPasswordPolicy: value.Description += " (Password Policy - Behera)" c := NewControlBeheraPasswordPolicy() if value.Value != nil { valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) if err != nil { return nil, fmt.Errorf("failed to decode data bytes: %s", err) } value.Data.Truncate(0) value.Value = nil value.AppendChild(valueChildren) } sequence := value.Children[0] for _, child := range sequence.Children { if child.Tag == 0 { //Warning warningPacket := child.Children[0] val, err := ber.ParseInt64(warningPacket.Data.Bytes()) if err != nil { return nil, fmt.Errorf("failed to decode data bytes: %s", err) } if warningPacket.Tag == 0 { //timeBeforeExpiration c.Expire = val warningPacket.Value = c.Expire } else if warningPacket.Tag == 1 { //graceAuthNsRemaining c.Grace = val warningPacket.Value = c.Grace } } else if child.Tag == 1 { // Error bs := child.Data.Bytes() if len(bs) != 1 || bs[0] > 8 { return nil, fmt.Errorf("failed to decode data bytes: %s", "invalid PasswordPolicyResponse enum value") } val := int8(bs[0]) c.Error = val child.Value = c.Error c.ErrorString = BeheraPasswordPolicyErrorMap[c.Error] } } return c, nil case ControlTypeVChuPasswordMustChange: c := &ControlVChuPasswordMustChange{MustChange: true} return c, nil case ControlTypeVChuPasswordWarning: c := &ControlVChuPasswordWarning{Expire: -1} expireStr := ber.DecodeString(value.Data.Bytes()) expire, err := strconv.ParseInt(expireStr, 10, 64) if err != nil { return nil, fmt.Errorf("failed to parse value as int: %s", err) } c.Expire = expire value.Value = c.Expire return c, nil case ControlTypeMicrosoftNotification: return NewControlMicrosoftNotification(), nil case ControlTypeMicrosoftShowDeleted: return NewControlMicrosoftShowDeleted(), nil case ControlTypeMicrosoftServerLinkTTL: return NewControlMicrosoftServerLinkTTL(), nil default: c := new(ControlString) c.ControlType = ControlType c.Criticality = Criticality if value != nil { c.ControlValue = value.Value.(string) } return c, nil } } // NewControlString returns a generic control func NewControlString(controlType string, criticality bool, controlValue string) *ControlString { return &ControlString{ ControlType: controlType, Criticality: criticality, ControlValue: controlValue, } } // NewControlPaging returns a paging control func NewControlPaging(pagingSize uint32) *ControlPaging { return &ControlPaging{PagingSize: pagingSize} } // NewControlBeheraPasswordPolicy returns a ControlBeheraPasswordPolicy func NewControlBeheraPasswordPolicy() *ControlBeheraPasswordPolicy { return &ControlBeheraPasswordPolicy{ Expire: -1, Grace: -1, Error: -1, } } func encodeControls(controls []Control) *ber.Packet { packet := ber.Encode(ber.ClassContext, ber.TypeConstructed, 0, nil, "Controls") for _, control := range controls { packet.AppendChild(control.Encode()) } return packet } ldap-3.4.1/v3/control_test.go000066400000000000000000000232431410654160200160400ustar00rootroot00000000000000package ldap import ( "bytes" "fmt" "reflect" "runtime" "testing" ber "github.com/go-asn1-ber/asn1-ber" ) func TestControlPaging(t *testing.T) { runControlTest(t, NewControlPaging(0)) runControlTest(t, NewControlPaging(100)) } func TestControlManageDsaIT(t *testing.T) { runControlTest(t, NewControlManageDsaIT(true)) runControlTest(t, NewControlManageDsaIT(false)) } func TestControlMicrosoftNotification(t *testing.T) { runControlTest(t, NewControlMicrosoftNotification()) } func TestControlMicrosoftShowDeleted(t *testing.T) { runControlTest(t, NewControlMicrosoftShowDeleted()) } func TestControlMicrosoftServerLinkTTL(t *testing.T) { runControlTest(t, NewControlMicrosoftServerLinkTTL()) } func TestControlString(t *testing.T) { runControlTest(t, NewControlString("x", true, "y")) runControlTest(t, NewControlString("x", true, "")) runControlTest(t, NewControlString("x", false, "y")) runControlTest(t, NewControlString("x", false, "")) } func runControlTest(t *testing.T, originalControl Control) { header := "" if callerpc, _, line, ok := runtime.Caller(1); ok { if caller := runtime.FuncForPC(callerpc); caller != nil { header = fmt.Sprintf("%s:%d: ", caller.Name(), line) } } encodedPacket := originalControl.Encode() encodedBytes := encodedPacket.Bytes() // Decode directly from the encoded packet (ensures Value is correct) fromPacket, err := DecodeControl(encodedPacket) if err != nil { t.Errorf("%sdecoding encoded bytes control failed: %s", header, err) } if !bytes.Equal(encodedBytes, fromPacket.Encode().Bytes()) { t.Errorf("%sround-trip from encoded packet failed", header) } if reflect.TypeOf(originalControl) != reflect.TypeOf(fromPacket) { t.Errorf("%sgot different type decoding from encoded packet: %T vs %T", header, fromPacket, originalControl) } // Decode from the wire bytes (ensures ber-encoding is correct) pkt, err := ber.DecodePacketErr(encodedBytes) if err != nil { t.Errorf("%sdecoding encoded bytes failed: %s", header, err) } fromBytes, err := DecodeControl(pkt) if err != nil { t.Errorf("%sdecoding control failed: %s", header, err) } if !bytes.Equal(encodedBytes, fromBytes.Encode().Bytes()) { t.Errorf("%sround-trip from encoded bytes failed", header) } if reflect.TypeOf(originalControl) != reflect.TypeOf(fromPacket) { t.Errorf("%sgot different type decoding from encoded bytes: %T vs %T", header, fromBytes, originalControl) } } func TestDescribeControlManageDsaIT(t *testing.T) { runAddControlDescriptions(t, NewControlManageDsaIT(false), "Control Type (Manage DSA IT)") runAddControlDescriptions(t, NewControlManageDsaIT(true), "Control Type (Manage DSA IT)", "Criticality") } func TestDescribeControlPaging(t *testing.T) { runAddControlDescriptions(t, NewControlPaging(100), "Control Type (Paging)", "Control Value (Paging)") runAddControlDescriptions(t, NewControlPaging(0), "Control Type (Paging)", "Control Value (Paging)") } func TestDescribeControlMicrosoftNotification(t *testing.T) { runAddControlDescriptions(t, NewControlMicrosoftNotification(), "Control Type (Change Notification - Microsoft)") } func TestDescribeControlMicrosoftShowDeleted(t *testing.T) { runAddControlDescriptions(t, NewControlMicrosoftShowDeleted(), "Control Type (Show Deleted Objects - Microsoft)") } func TestDescribeControlMicrosoftServerLinkTTL(t *testing.T) { runAddControlDescriptions(t, NewControlMicrosoftServerLinkTTL(), "Control Type (Return TTL-DNs for link values with associated expiry times - Microsoft)") } func TestDescribeControlString(t *testing.T) { runAddControlDescriptions(t, NewControlString("x", true, "y"), "Control Type ()", "Criticality", "Control Value") runAddControlDescriptions(t, NewControlString("x", true, ""), "Control Type ()", "Criticality") runAddControlDescriptions(t, NewControlString("x", false, "y"), "Control Type ()", "Control Value") runAddControlDescriptions(t, NewControlString("x", false, ""), "Control Type ()") } func runAddControlDescriptions(t *testing.T, originalControl Control, childDescriptions ...string) { header := "" if callerpc, _, line, ok := runtime.Caller(1); ok { if caller := runtime.FuncForPC(callerpc); caller != nil { header = fmt.Sprintf("%s:%d: ", caller.Name(), line) } } encodedControls := encodeControls([]Control{originalControl}) addControlDescriptions(encodedControls) encodedPacket := encodedControls.Children[0] if len(encodedPacket.Children) != len(childDescriptions) { t.Errorf("%sinvalid number of children: %d != %d", header, len(encodedPacket.Children), len(childDescriptions)) } for i, desc := range childDescriptions { if encodedPacket.Children[i].Description != desc { t.Errorf("%sdescription not as expected: %s != %s", header, encodedPacket.Children[i].Description, desc) } } } func TestDecodeControl(t *testing.T) { type args struct { packet *ber.Packet } tests := []struct { name string args args want Control wantErr bool }{ {name: "timeBeforeExpiration", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x29, 0x30, 0x27, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0xa, 0x30, 0x8, 0xa0, 0x6, 0x80, 0x4, 0x7f, 0xff, 0xf6, 0x5c})}, want: &ControlBeheraPasswordPolicy{Expire: 2147481180, Grace: -1, Error: -1, ErrorString: ""}, wantErr: false}, {name: "graceAuthNsRemaining", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x26, 0x30, 0x24, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x7, 0x30, 0x5, 0xa0, 0x3, 0x81, 0x1, 0x11})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: 17, Error: -1, ErrorString: ""}, wantErr: false}, {name: "passwordExpired", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x0})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 0, ErrorString: "Password expired"}, wantErr: false}, {name: "accountLocked", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x1})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 1, ErrorString: "Account locked"}, wantErr: false}, {name: "passwordModNotAllowed", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x3})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 3, ErrorString: "Policy prevents password modification"}, wantErr: false}, {name: "mustSupplyOldPassword", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x4})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 4, ErrorString: "Policy requires old password in order to change password"}, wantErr: false}, {name: "insufficientPasswordQuality", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x5})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 5, ErrorString: "Password fails quality checks"}, wantErr: false}, {name: "passwordTooShort", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x6})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 6, ErrorString: "Password is too short for policy"}, wantErr: false}, {name: "passwordTooYoung", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x7})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 7, ErrorString: "Password has been changed too recently"}, wantErr: false}, {name: "passwordInHistory", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x8})}, want: &ControlBeheraPasswordPolicy{Expire: -1, Grace: -1, Error: 8, ErrorString: "New password is in list of old passwords"}, wantErr: false}, } for i := range tests { err := addControlDescriptions(tests[i].args.packet) if err != nil { t.Fatal(err) } tests[i].args.packet = tests[i].args.packet.Children[0] } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := DecodeControl(tt.args.packet) if (err != nil) != tt.wantErr { t.Errorf("DecodeControl() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("DecodeControl() got = %v, want %v", got, tt.want) } }) } } ldap-3.4.1/v3/debug.go000066400000000000000000000010611410654160200144010ustar00rootroot00000000000000package ldap import ( "log" ber "github.com/go-asn1-ber/asn1-ber" ) // debugging type // - has a Printf method to write the debug output type debugging bool // Enable controls debugging mode. func (debug *debugging) Enable(b bool) { *debug = debugging(b) } // Printf writes debug output. func (debug debugging) Printf(format string, args ...interface{}) { if debug { log.Printf(format, args...) } } // PrintPacket dumps a packet. func (debug debugging) PrintPacket(packet *ber.Packet) { if debug { ber.WritePacket(log.Writer(), packet) } } ldap-3.4.1/v3/del.go000066400000000000000000000024301410654160200140600ustar00rootroot00000000000000package ldap import ( "log" ber "github.com/go-asn1-ber/asn1-ber" ) // DelRequest implements an LDAP deletion request type DelRequest struct { // DN is the name of the directory entry to delete DN string // Controls hold optional controls to send with the request Controls []Control } func (req *DelRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypePrimitive, ApplicationDelRequest, req.DN, "Del Request") pkt.Data.Write([]byte(req.DN)) envelope.AppendChild(pkt) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // NewDelRequest creates a delete request for the given DN and controls func NewDelRequest(DN string, Controls []Control) *DelRequest { return &DelRequest{ DN: DN, Controls: Controls, } } // Del executes the given delete request func (l *Conn) Del(delRequest *DelRequest) error { msgCtx, err := l.doRequest(delRequest) if err != nil { return err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return err } if packet.Children[1].Tag == ApplicationDelResponse { err := GetLDAPError(packet) if err != nil { return err } } else { log.Printf("Unexpected Response: %d", packet.Children[1].Tag) } return nil } ldap-3.4.1/v3/dn.go000066400000000000000000000200201410654160200137100ustar00rootroot00000000000000package ldap import ( "bytes" enchex "encoding/hex" "errors" "fmt" "strings" ber "github.com/go-asn1-ber/asn1-ber" ) // AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514 type AttributeTypeAndValue struct { // Type is the attribute type Type string // Value is the attribute value Value string } // RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514 type RelativeDN struct { Attributes []*AttributeTypeAndValue } // DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514 type DN struct { RDNs []*RelativeDN } // ParseDN returns a distinguishedName or an error. // The function respects https://tools.ietf.org/html/rfc4514 func ParseDN(str string) (*DN, error) { dn := new(DN) dn.RDNs = make([]*RelativeDN, 0) rdn := new(RelativeDN) rdn.Attributes = make([]*AttributeTypeAndValue, 0) buffer := bytes.Buffer{} attribute := new(AttributeTypeAndValue) escaping := false unescapedTrailingSpaces := 0 stringFromBuffer := func() string { s := buffer.String() s = s[0 : len(s)-unescapedTrailingSpaces] buffer.Reset() unescapedTrailingSpaces = 0 return s } for i := 0; i < len(str); i++ { char := str[i] switch { case escaping: unescapedTrailingSpaces = 0 escaping = false switch char { case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\': buffer.WriteByte(char) continue } // Not a special character, assume hex encoded octet if len(str) == i+1 { return nil, errors.New("got corrupted escaped character") } dst := []byte{0} n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2])) if err != nil { return nil, fmt.Errorf("failed to decode escaped character: %s", err) } else if n != 1 { return nil, fmt.Errorf("expected 1 byte when un-escaping, got %d", n) } buffer.WriteByte(dst[0]) i++ case char == '\\': unescapedTrailingSpaces = 0 escaping = true case char == '=': attribute.Type = stringFromBuffer() // Special case: If the first character in the value is # the // following data is BER encoded so we can just fast forward // and decode. if len(str) > i+1 && str[i+1] == '#' { i += 2 index := strings.IndexAny(str[i:], ",+") data := str if index > 0 { data = str[i : i+index] } else { data = str[i:] } rawBER, err := enchex.DecodeString(data) if err != nil { return nil, fmt.Errorf("failed to decode BER encoding: %s", err) } packet, err := ber.DecodePacketErr(rawBER) if err != nil { return nil, fmt.Errorf("failed to decode BER packet: %s", err) } buffer.WriteString(packet.Data.String()) i += len(data) - 1 } case char == ',' || char == '+': // We're done with this RDN or value, push it if len(attribute.Type) == 0 { return nil, errors.New("incomplete type, value pair") } attribute.Value = stringFromBuffer() rdn.Attributes = append(rdn.Attributes, attribute) attribute = new(AttributeTypeAndValue) if char == ',' { dn.RDNs = append(dn.RDNs, rdn) rdn = new(RelativeDN) rdn.Attributes = make([]*AttributeTypeAndValue, 0) } case char == ' ' && buffer.Len() == 0: // ignore unescaped leading spaces continue default: if char == ' ' { // Track unescaped spaces in case they are trailing and we need to remove them unescapedTrailingSpaces++ } else { // Reset if we see a non-space char unescapedTrailingSpaces = 0 } buffer.WriteByte(char) } } if buffer.Len() > 0 { if len(attribute.Type) == 0 { return nil, errors.New("DN ended with incomplete type, value pair") } attribute.Value = stringFromBuffer() rdn.Attributes = append(rdn.Attributes, attribute) dn.RDNs = append(dn.RDNs, rdn) } return dn, nil } // Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch). // Returns true if they have the same number of relative distinguished names // and corresponding relative distinguished names (by position) are the same. func (d *DN) Equal(other *DN) bool { if len(d.RDNs) != len(other.RDNs) { return false } for i := range d.RDNs { if !d.RDNs[i].Equal(other.RDNs[i]) { return false } } return true } // AncestorOf returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN. // "ou=widgets,o=acme.com" is an ancestor of "ou=sprockets,ou=widgets,o=acme.com" // "ou=widgets,o=acme.com" is not an ancestor of "ou=sprockets,ou=widgets,o=foo.com" // "ou=widgets,o=acme.com" is not an ancestor of "ou=widgets,o=acme.com" func (d *DN) AncestorOf(other *DN) bool { if len(d.RDNs) >= len(other.RDNs) { return false } // Take the last `len(d.RDNs)` RDNs from the other DN to compare against otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):] for i := range d.RDNs { if !d.RDNs[i].Equal(otherRDNs[i]) { return false } } return true } // Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch). // Relative distinguished names are the same if and only if they have the same number of AttributeTypeAndValues // and each attribute of the first RDN is the same as the attribute of the second RDN with the same attribute type. // The order of attributes is not significant. // Case of attribute types is not significant. func (r *RelativeDN) Equal(other *RelativeDN) bool { if len(r.Attributes) != len(other.Attributes) { return false } return r.hasAllAttributes(other.Attributes) && other.hasAllAttributes(r.Attributes) } func (r *RelativeDN) hasAllAttributes(attrs []*AttributeTypeAndValue) bool { for _, attr := range attrs { found := false for _, myattr := range r.Attributes { if myattr.Equal(attr) { found = true break } } if !found { return false } } return true } // Equal returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue // Case of the attribute type is not significant func (a *AttributeTypeAndValue) Equal(other *AttributeTypeAndValue) bool { return strings.EqualFold(a.Type, other.Type) && a.Value == other.Value } // Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch). // Returns true if they have the same number of relative distinguished names // and corresponding relative distinguished names (by position) are the same. // Case of the attribute type and value is not significant func (d *DN) EqualFold(other *DN) bool { if len(d.RDNs) != len(other.RDNs) { return false } for i := range d.RDNs { if !d.RDNs[i].EqualFold(other.RDNs[i]) { return false } } return true } // AncestorOfFold returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN. // Case of the attribute type and value is not significant func (d *DN) AncestorOfFold(other *DN) bool { if len(d.RDNs) >= len(other.RDNs) { return false } // Take the last `len(d.RDNs)` RDNs from the other DN to compare against otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):] for i := range d.RDNs { if !d.RDNs[i].EqualFold(otherRDNs[i]) { return false } } return true } // Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch). // Case of the attribute type is not significant func (r *RelativeDN) EqualFold(other *RelativeDN) bool { if len(r.Attributes) != len(other.Attributes) { return false } return r.hasAllAttributesFold(other.Attributes) && other.hasAllAttributesFold(r.Attributes) } func (r *RelativeDN) hasAllAttributesFold(attrs []*AttributeTypeAndValue) bool { for _, attr := range attrs { found := false for _, myattr := range r.Attributes { if myattr.EqualFold(attr) { found = true break } } if !found { return false } } return true } // EqualFold returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue // Case of the attribute type and value is not significant func (a *AttributeTypeAndValue) EqualFold(other *AttributeTypeAndValue) bool { return strings.EqualFold(a.Type, other.Type) && strings.EqualFold(a.Value, other.Value) } ldap-3.4.1/v3/dn_test.go000066400000000000000000000157361410654160200147710ustar00rootroot00000000000000package ldap import ( "reflect" "testing" ) func TestSuccessfulDNParsing(t *testing.T) { testcases := map[string]DN{ "": {[]*RelativeDN{}}, "cn=Jim\\2C \\22Hasse Hö\\22 Hansson!,dc=dummy,dc=com": {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"cn", "Jim, \"Hasse Hö\" Hansson!"}}}, {[]*AttributeTypeAndValue{{"dc", "dummy"}}}, {[]*AttributeTypeAndValue{{"dc", "com"}}}}}, "UID=jsmith,DC=example,DC=net": {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"UID", "jsmith"}}}, {[]*AttributeTypeAndValue{{"DC", "example"}}}, {[]*AttributeTypeAndValue{{"DC", "net"}}}}}, "OU=Sales+CN=J. Smith,DC=example,DC=net": {[]*RelativeDN{ {[]*AttributeTypeAndValue{ {"OU", "Sales"}, {"CN", "J. Smith"}}}, {[]*AttributeTypeAndValue{{"DC", "example"}}}, {[]*AttributeTypeAndValue{{"DC", "net"}}}}}, "1.3.6.1.4.1.1466.0=#04024869": {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"1.3.6.1.4.1.1466.0", "Hi"}}}}}, "1.3.6.1.4.1.1466.0=#04024869,DC=net": {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"1.3.6.1.4.1.1466.0", "Hi"}}}, {[]*AttributeTypeAndValue{{"DC", "net"}}}}}, "CN=Lu\\C4\\8Di\\C4\\87": {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"CN", "Lučić"}}}}}, " CN = Lu\\C4\\8Di\\C4\\87 ": {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"CN", "Lučić"}}}}}, ` A = 1 , B = 2 `: {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"A", "1"}}}, {[]*AttributeTypeAndValue{{"B", "2"}}}}}, ` A = 1 + B = 2 `: {[]*RelativeDN{ {[]*AttributeTypeAndValue{ {"A", "1"}, {"B", "2"}}}}}, ` \ \ A\ \ = \ \ 1\ \ , \ \ B\ \ = \ \ 2\ \ `: {[]*RelativeDN{ {[]*AttributeTypeAndValue{{" A ", " 1 "}}}, {[]*AttributeTypeAndValue{{" B ", " 2 "}}}}}, ` \ \ A\ \ = \ \ 1\ \ + \ \ B\ \ = \ \ 2\ \ `: {[]*RelativeDN{ {[]*AttributeTypeAndValue{ {" A ", " 1 "}, {" B ", " 2 "}}}}}, } for test, answer := range testcases { dn, err := ParseDN(test) if err != nil { t.Errorf(err.Error()) continue } if !reflect.DeepEqual(dn, &answer) { t.Errorf("Parsed DN %s is not equal to the expected structure", test) t.Logf("Expected:") for _, rdn := range answer.RDNs { for _, attribs := range rdn.Attributes { t.Logf("#%v\n", attribs) } } t.Logf("Actual:") for _, rdn := range dn.RDNs { for _, attribs := range rdn.Attributes { t.Logf("#%v\n", attribs) } } } } } func TestErrorDNParsing(t *testing.T) { testcases := map[string]string{ "*": "DN ended with incomplete type, value pair", "cn=Jim\\0Test": "failed to decode escaped character: encoding/hex: invalid byte: U+0054 'T'", "cn=Jim\\0": "got corrupted escaped character", "DC=example,=net": "DN ended with incomplete type, value pair", "1=#0402486": "failed to decode BER encoding: encoding/hex: odd length hex string", "test,DC=example,DC=com": "incomplete type, value pair", "=test,DC=example,DC=com": "incomplete type, value pair", } for test, answer := range testcases { _, err := ParseDN(test) if err == nil { t.Errorf("Expected %s to fail parsing but succeeded\n", test) } else if err.Error() != answer { t.Errorf("Unexpected error on %s:\n%s\nvs.\n%s\n", test, answer, err.Error()) } } } func TestDNEqual(t *testing.T) { testcases := []struct { A string B string Equal bool }{ // Exact match {"", "", true}, {"o=A", "o=A", true}, {"o=A", "o=B", false}, {"o=A,o=B", "o=A,o=B", true}, {"o=A,o=B", "o=A,o=C", false}, {"o=A+o=B", "o=A+o=B", true}, {"o=A+o=B", "o=A+o=C", false}, // Case mismatch in type is ignored {"o=A", "O=A", true}, {"o=A,o=B", "o=A,O=B", true}, {"o=A+o=B", "o=A+O=B", true}, // Case mismatch in value is significant {"o=a", "O=A", false}, {"o=a,o=B", "o=A,O=B", false}, {"o=a+o=B", "o=A+O=B", false}, // Multi-valued RDN order mismatch is ignored {"o=A+o=B", "O=B+o=A", true}, // Number of RDN attributes is significant {"o=A+o=B", "O=B+o=A+O=B", false}, // Missing values are significant {"o=A+o=B", "O=B+o=A+O=C", false}, // missing values matter {"o=A+o=B+o=C", "O=B+o=A", false}, // missing values matter // Whitespace tests // Matching { "cn=John Doe, ou=People, dc=sun.com", "cn=John Doe, ou=People, dc=sun.com", true, }, // Difference in leading/trailing chars is ignored { "cn=John Doe, ou=People, dc=sun.com", "cn=John Doe,ou=People,dc=sun.com", true, }, // Difference in values is significant { "cn=John Doe, ou=People, dc=sun.com", "cn=John Doe, ou=People, dc=sun.com", false, }, } for i, tc := range testcases { a, err := ParseDN(tc.A) if err != nil { t.Errorf("%d: %v", i, err) continue } b, err := ParseDN(tc.B) if err != nil { t.Errorf("%d: %v", i, err) continue } if expected, actual := tc.Equal, a.Equal(b); expected != actual { t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual) continue } if expected, actual := tc.Equal, b.Equal(a); expected != actual { t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual) continue } } } func TestDNEqualFold(t *testing.T) { testcases := []struct { A string B string Equal bool }{ // Match on case insensitive {"o=A", "o=a", true}, {"o=A,o=b", "o=a,o=B", true}, {"o=a+o=B", "o=A+o=b", true}, { "cn=users,ou=example,dc=com", "cn=Users,ou=example,dc=com", true, }, // Match on case insensitive and case mismatch in type {"o=A", "O=a", true}, {"o=A,o=b", "o=a,O=B", true}, {"o=a+o=B", "o=A+O=b", true}, } for i, tc := range testcases { a, err := ParseDN(tc.A) if err != nil { t.Errorf("%d: %v", i, err) continue } b, err := ParseDN(tc.B) if err != nil { t.Errorf("%d: %v", i, err) continue } if expected, actual := tc.Equal, a.EqualFold(b); expected != actual { t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual) continue } if expected, actual := tc.Equal, b.EqualFold(a); expected != actual { t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual) continue } } } func TestDNAncestor(t *testing.T) { testcases := []struct { A string B string Ancestor bool }{ // Exact match returns false {"", "", false}, {"o=A", "o=A", false}, {"o=A,o=B", "o=A,o=B", false}, {"o=A+o=B", "o=A+o=B", false}, // Mismatch {"ou=C,ou=B,o=A", "ou=E,ou=D,ou=B,o=A", false}, // Descendant {"ou=C,ou=B,o=A", "ou=E,ou=C,ou=B,o=A", true}, } for i, tc := range testcases { a, err := ParseDN(tc.A) if err != nil { t.Errorf("%d: %v", i, err) continue } b, err := ParseDN(tc.B) if err != nil { t.Errorf("%d: %v", i, err) continue } if expected, actual := tc.Ancestor, a.AncestorOf(b); expected != actual { t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual) continue } } } ldap-3.4.1/v3/doc.go000066400000000000000000000001061410654160200140570ustar00rootroot00000000000000/* Package ldap provides basic LDAP v3 functionality. */ package ldap ldap-3.4.1/v3/error.go000066400000000000000000000305401410654160200144500ustar00rootroot00000000000000package ldap import ( "fmt" ber "github.com/go-asn1-ber/asn1-ber" ) // LDAP Result Codes const ( LDAPResultSuccess = 0 LDAPResultOperationsError = 1 LDAPResultProtocolError = 2 LDAPResultTimeLimitExceeded = 3 LDAPResultSizeLimitExceeded = 4 LDAPResultCompareFalse = 5 LDAPResultCompareTrue = 6 LDAPResultAuthMethodNotSupported = 7 LDAPResultStrongAuthRequired = 8 LDAPResultReferral = 10 LDAPResultAdminLimitExceeded = 11 LDAPResultUnavailableCriticalExtension = 12 LDAPResultConfidentialityRequired = 13 LDAPResultSaslBindInProgress = 14 LDAPResultNoSuchAttribute = 16 LDAPResultUndefinedAttributeType = 17 LDAPResultInappropriateMatching = 18 LDAPResultConstraintViolation = 19 LDAPResultAttributeOrValueExists = 20 LDAPResultInvalidAttributeSyntax = 21 LDAPResultNoSuchObject = 32 LDAPResultAliasProblem = 33 LDAPResultInvalidDNSyntax = 34 LDAPResultIsLeaf = 35 LDAPResultAliasDereferencingProblem = 36 LDAPResultInappropriateAuthentication = 48 LDAPResultInvalidCredentials = 49 LDAPResultInsufficientAccessRights = 50 LDAPResultBusy = 51 LDAPResultUnavailable = 52 LDAPResultUnwillingToPerform = 53 LDAPResultLoopDetect = 54 LDAPResultSortControlMissing = 60 LDAPResultOffsetRangeError = 61 LDAPResultNamingViolation = 64 LDAPResultObjectClassViolation = 65 LDAPResultNotAllowedOnNonLeaf = 66 LDAPResultNotAllowedOnRDN = 67 LDAPResultEntryAlreadyExists = 68 LDAPResultObjectClassModsProhibited = 69 LDAPResultResultsTooLarge = 70 LDAPResultAffectsMultipleDSAs = 71 LDAPResultVirtualListViewErrorOrControlError = 76 LDAPResultOther = 80 LDAPResultServerDown = 81 LDAPResultLocalError = 82 LDAPResultEncodingError = 83 LDAPResultDecodingError = 84 LDAPResultTimeout = 85 LDAPResultAuthUnknown = 86 LDAPResultFilterError = 87 LDAPResultUserCanceled = 88 LDAPResultParamError = 89 LDAPResultNoMemory = 90 LDAPResultConnectError = 91 LDAPResultNotSupported = 92 LDAPResultControlNotFound = 93 LDAPResultNoResultsReturned = 94 LDAPResultMoreResultsToReturn = 95 LDAPResultClientLoop = 96 LDAPResultReferralLimitExceeded = 97 LDAPResultInvalidResponse = 100 LDAPResultAmbiguousResponse = 101 LDAPResultTLSNotSupported = 112 LDAPResultIntermediateResponse = 113 LDAPResultUnknownType = 114 LDAPResultCanceled = 118 LDAPResultNoSuchOperation = 119 LDAPResultTooLate = 120 LDAPResultCannotCancel = 121 LDAPResultAssertionFailed = 122 LDAPResultAuthorizationDenied = 123 LDAPResultSyncRefreshRequired = 4096 ErrorNetwork = 200 ErrorFilterCompile = 201 ErrorFilterDecompile = 202 ErrorDebugging = 203 ErrorUnexpectedMessage = 204 ErrorUnexpectedResponse = 205 ErrorEmptyPassword = 206 ) // LDAPResultCodeMap contains string descriptions for LDAP error codes var LDAPResultCodeMap = map[uint16]string{ LDAPResultSuccess: "Success", LDAPResultOperationsError: "Operations Error", LDAPResultProtocolError: "Protocol Error", LDAPResultTimeLimitExceeded: "Time Limit Exceeded", LDAPResultSizeLimitExceeded: "Size Limit Exceeded", LDAPResultCompareFalse: "Compare False", LDAPResultCompareTrue: "Compare True", LDAPResultAuthMethodNotSupported: "Auth Method Not Supported", LDAPResultStrongAuthRequired: "Strong Auth Required", LDAPResultReferral: "Referral", LDAPResultAdminLimitExceeded: "Admin Limit Exceeded", LDAPResultUnavailableCriticalExtension: "Unavailable Critical Extension", LDAPResultConfidentialityRequired: "Confidentiality Required", LDAPResultSaslBindInProgress: "Sasl Bind In Progress", LDAPResultNoSuchAttribute: "No Such Attribute", LDAPResultUndefinedAttributeType: "Undefined Attribute Type", LDAPResultInappropriateMatching: "Inappropriate Matching", LDAPResultConstraintViolation: "Constraint Violation", LDAPResultAttributeOrValueExists: "Attribute Or Value Exists", LDAPResultInvalidAttributeSyntax: "Invalid Attribute Syntax", LDAPResultNoSuchObject: "No Such Object", LDAPResultAliasProblem: "Alias Problem", LDAPResultInvalidDNSyntax: "Invalid DN Syntax", LDAPResultIsLeaf: "Is Leaf", LDAPResultAliasDereferencingProblem: "Alias Dereferencing Problem", LDAPResultInappropriateAuthentication: "Inappropriate Authentication", LDAPResultInvalidCredentials: "Invalid Credentials", LDAPResultInsufficientAccessRights: "Insufficient Access Rights", LDAPResultBusy: "Busy", LDAPResultUnavailable: "Unavailable", LDAPResultUnwillingToPerform: "Unwilling To Perform", LDAPResultLoopDetect: "Loop Detect", LDAPResultSortControlMissing: "Sort Control Missing", LDAPResultOffsetRangeError: "Result Offset Range Error", LDAPResultNamingViolation: "Naming Violation", LDAPResultObjectClassViolation: "Object Class Violation", LDAPResultResultsTooLarge: "Results Too Large", LDAPResultNotAllowedOnNonLeaf: "Not Allowed On Non Leaf", LDAPResultNotAllowedOnRDN: "Not Allowed On RDN", LDAPResultEntryAlreadyExists: "Entry Already Exists", LDAPResultObjectClassModsProhibited: "Object Class Mods Prohibited", LDAPResultAffectsMultipleDSAs: "Affects Multiple DSAs", LDAPResultVirtualListViewErrorOrControlError: "Failed because of a problem related to the virtual list view", LDAPResultOther: "Other", LDAPResultServerDown: "Cannot establish a connection", LDAPResultLocalError: "An error occurred", LDAPResultEncodingError: "LDAP encountered an error while encoding", LDAPResultDecodingError: "LDAP encountered an error while decoding", LDAPResultTimeout: "LDAP timeout while waiting for a response from the server", LDAPResultAuthUnknown: "The auth method requested in a bind request is unknown", LDAPResultFilterError: "An error occurred while encoding the given search filter", LDAPResultUserCanceled: "The user canceled the operation", LDAPResultParamError: "An invalid parameter was specified", LDAPResultNoMemory: "Out of memory error", LDAPResultConnectError: "A connection to the server could not be established", LDAPResultNotSupported: "An attempt has been made to use a feature not supported LDAP", LDAPResultControlNotFound: "The controls required to perform the requested operation were not found", LDAPResultNoResultsReturned: "No results were returned from the server", LDAPResultMoreResultsToReturn: "There are more results in the chain of results", LDAPResultClientLoop: "A loop has been detected. For example when following referrals", LDAPResultReferralLimitExceeded: "The referral hop limit has been exceeded", LDAPResultCanceled: "Operation was canceled", LDAPResultNoSuchOperation: "Server has no knowledge of the operation requested for cancellation", LDAPResultTooLate: "Too late to cancel the outstanding operation", LDAPResultCannotCancel: "The identified operation does not support cancellation or the cancel operation cannot be performed", LDAPResultAssertionFailed: "An assertion control given in the LDAP operation evaluated to false causing the operation to not be performed", LDAPResultSyncRefreshRequired: "Refresh Required", LDAPResultInvalidResponse: "Invalid Response", LDAPResultAmbiguousResponse: "Ambiguous Response", LDAPResultTLSNotSupported: "Tls Not Supported", LDAPResultIntermediateResponse: "Intermediate Response", LDAPResultUnknownType: "Unknown Type", LDAPResultAuthorizationDenied: "Authorization Denied", ErrorNetwork: "Network Error", ErrorFilterCompile: "Filter Compile Error", ErrorFilterDecompile: "Filter Decompile Error", ErrorDebugging: "Debugging Error", ErrorUnexpectedMessage: "Unexpected Message", ErrorUnexpectedResponse: "Unexpected Response", ErrorEmptyPassword: "Empty password not allowed by the client", } // Error holds LDAP error information type Error struct { // Err is the underlying error Err error // ResultCode is the LDAP error code ResultCode uint16 // MatchedDN is the matchedDN returned if any MatchedDN string // Packet is the returned packet if any Packet *ber.Packet } func (e *Error) Error() string { return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error()) } // GetLDAPError creates an Error out of a BER packet representing a LDAPResult // The return is an error object. It can be casted to a Error structure. // This function returns nil if resultCode in the LDAPResult sequence is success(0). func GetLDAPError(packet *ber.Packet) error { if packet == nil { return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("Empty packet")} } if len(packet.Children) >= 2 { response := packet.Children[1] if response == nil { return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("Empty response in packet"), Packet: packet} } if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 { resultCode := uint16(response.Children[0].Value.(int64)) if resultCode == 0 { // No error return nil } return &Error{ ResultCode: resultCode, MatchedDN: response.Children[1].Value.(string), Err: fmt.Errorf("%s", response.Children[2].Value.(string)), Packet: packet, } } } return &Error{ResultCode: ErrorNetwork, Err: fmt.Errorf("Invalid packet format"), Packet: packet} } // NewError creates an LDAP error with the given code and underlying error func NewError(resultCode uint16, err error) error { return &Error{ResultCode: resultCode, Err: err} } // IsErrorAnyOf returns true if the given error is an LDAP error with any one of the given result codes func IsErrorAnyOf(err error, codes ...uint16) bool { if err == nil { return false } serverError, ok := err.(*Error) if !ok { return false } for _, code := range codes { if serverError.ResultCode == code { return true } } return false } // IsErrorWithCode returns true if the given error is an LDAP error with the given result code func IsErrorWithCode(err error, desiredResultCode uint16) bool { return IsErrorAnyOf(err, desiredResultCode) } ldap-3.4.1/v3/error_test.go000066400000000000000000000116021410654160200155050ustar00rootroot00000000000000package ldap import ( "errors" "net" "strings" "testing" "time" ber "github.com/go-asn1-ber/asn1-ber" ) // TestNilPacket tests that nil packets don't cause a panic. func TestNilPacket(t *testing.T) { // Test for nil packet err := GetLDAPError(nil) if !IsErrorWithCode(err, ErrorUnexpectedResponse) { t.Errorf("Should have an 'ErrorUnexpectedResponse' error in nil packets, got: %v", err) } // Test for nil result kids := []*ber.Packet{ {}, // Unused nil, // Can't be nil } pack := &ber.Packet{Children: kids} err = GetLDAPError(pack) if !IsErrorWithCode(err, ErrorUnexpectedResponse) { t.Errorf("Should have an 'ErrorUnexpectedResponse' error in nil packets, got: %v", err) } } // TestConnReadErr tests that an unexpected error reading from underlying // connection bubbles up to the goroutine which makes a request. func TestConnReadErr(t *testing.T) { conn := &signalErrConn{ signals: make(chan error), } ldapConn := NewConn(conn, false) ldapConn.Start() // Make a dummy search request. searchReq := NewSearchRequest("dc=example,dc=com", ScopeWholeSubtree, DerefAlways, 0, 0, false, "(objectClass=*)", nil, nil) expectedError := errors.New("this is the error you are looking for") // Send the signal after a short amount of time. time.AfterFunc(10*time.Millisecond, func() { conn.signals <- expectedError }) // This should block until the underlying conn gets the error signal // which should bubble up through the reader() goroutine, close the // connection, and _, err := ldapConn.Search(searchReq) if err == nil || !strings.Contains(err.Error(), expectedError.Error()) { t.Errorf("not the expected error: %s", err) } } // TestGetLDAPError tests parsing of result with a error response. func TestGetLDAPError(t *testing.T) { diagnosticMessage := "Detailed error message" bindResponse := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindResponse, nil, "Bind Response") bindResponse.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(LDAPResultInvalidCredentials), "resultCode")) bindResponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "dc=example,dc=org", "matchedDN")) bindResponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, diagnosticMessage, "diagnosticMessage")) packet := ber.NewSequence("LDAPMessage") packet.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(0), "messageID")) packet.AppendChild(bindResponse) err := GetLDAPError(packet) if err == nil { t.Errorf("Did not get error response") } ldapError := err.(*Error) if ldapError.ResultCode != LDAPResultInvalidCredentials { t.Errorf("Got incorrect error code in LDAP error; got %v, expected %v", ldapError.ResultCode, LDAPResultInvalidCredentials) } if ldapError.Err.Error() != diagnosticMessage { t.Errorf("Got incorrect error message in LDAP error; got %v, expected %v", ldapError.Err.Error(), diagnosticMessage) } } // TestGetLDAPErrorSuccess tests parsing of a result with no error (resultCode == 0). func TestGetLDAPErrorSuccess(t *testing.T) { bindResponse := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindResponse, nil, "Bind Response") bindResponse.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(0), "resultCode")) bindResponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "matchedDN")) bindResponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "diagnosticMessage")) packet := ber.NewSequence("LDAPMessage") packet.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(0), "messageID")) packet.AppendChild(bindResponse) err := GetLDAPError(packet) if err != nil { t.Errorf("Successful responses should not produce an error, but got: %v", err) } } // signalErrConn is a helpful type used with TestConnReadErr. It implements the // net.Conn interface to be used as a connection for the test. Most methods are // no-ops but the Read() method blocks until it receives a signal which it // returns as an error. type signalErrConn struct { signals chan error } // Read blocks until an error is sent on the internal signals channel. That // error is returned. func (c *signalErrConn) Read(b []byte) (n int, err error) { return 0, <-c.signals } func (c *signalErrConn) Write(b []byte) (n int, err error) { return len(b), nil } func (c *signalErrConn) Close() error { close(c.signals) return nil } func (c *signalErrConn) LocalAddr() net.Addr { return (*net.TCPAddr)(nil) } func (c *signalErrConn) RemoteAddr() net.Addr { return (*net.TCPAddr)(nil) } func (c *signalErrConn) SetDeadline(t time.Time) error { return nil } func (c *signalErrConn) SetReadDeadline(t time.Time) error { return nil } func (c *signalErrConn) SetWriteDeadline(t time.Time) error { return nil } ldap-3.4.1/v3/examples_moddn_test.go000066400000000000000000000043331410654160200173560ustar00rootroot00000000000000package ldap import ( "log" ) // This example shows how to rename an entry without moving it func ExampleConn_ModifyDN_renameNoMove() { conn, err := DialURL("ldap://ldap.example.org:389") if err != nil { log.Fatalf("Failed to connect: %s\n", err) } defer conn.Close() _, err = conn.SimpleBind(&SimpleBindRequest{ Username: "uid=someone,ou=people,dc=example,dc=org", Password: "MySecretPass", }) if err != nil { log.Fatalf("Failed to bind: %s\n", err) } // just rename to uid=new,ou=people,dc=example,dc=org: req := NewModifyDNRequest("uid=user,ou=people,dc=example,dc=org", "uid=new", true, "") if err = conn.ModifyDN(req); err != nil { log.Fatalf("Failed to call ModifyDN(): %s\n", err) } } // This example shows how to rename an entry and moving it to a new base func ExampleConn_ModifyDN_renameAndMove() { conn, err := DialURL("ldap://ldap.example.org:389") if err != nil { log.Fatalf("Failed to connect: %s\n", err) } defer conn.Close() _, err = conn.SimpleBind(&SimpleBindRequest{ Username: "uid=someone,ou=people,dc=example,dc=org", Password: "MySecretPass", }) if err != nil { log.Fatalf("Failed to bind: %s\n", err) } // rename to uid=new,ou=people,dc=example,dc=org and move to ou=users,dc=example,dc=org -> // uid=new,ou=users,dc=example,dc=org req := NewModifyDNRequest("uid=user,ou=people,dc=example,dc=org", "uid=new", true, "ou=users,dc=example,dc=org") if err = conn.ModifyDN(req); err != nil { log.Fatalf("Failed to call ModifyDN(): %s\n", err) } } // This example shows how to move an entry to a new base without renaming the RDN func ExampleConn_ModifyDN_moveOnly() { conn, err := DialURL("ldap://ldap.example.org:389") if err != nil { log.Fatalf("Failed to connect: %s\n", err) } defer conn.Close() _, err = conn.SimpleBind(&SimpleBindRequest{ Username: "uid=someone,ou=people,dc=example,dc=org", Password: "MySecretPass", }) if err != nil { log.Fatalf("Failed to bind: %s\n", err) } // move to ou=users,dc=example,dc=org -> uid=user,ou=users,dc=example,dc=org req := NewModifyDNRequest("uid=user,ou=people,dc=example,dc=org", "uid=user", true, "ou=users,dc=example,dc=org") if err = conn.ModifyDN(req); err != nil { log.Fatalf("Failed to call ModifyDN(): %s\n", err) } } ldap-3.4.1/v3/examples_test.go000066400000000000000000000245601410654160200162010ustar00rootroot00000000000000package ldap import ( "crypto/tls" "crypto/x509" "fmt" "io/ioutil" "log" ) // This example demonstrates how to bind a connection to an ldap user // allowing access to restricted attributes that user has access to func ExampleConn_Bind() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() err = l.Bind("cn=read-only-admin,dc=example,dc=com", "password") if err != nil { log.Fatal(err) } } // This example demonstrates how to use the search interface func ExampleConn_Search() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() searchRequest := NewSearchRequest( "dc=example,dc=com", // The base dn to search ScopeWholeSubtree, NeverDerefAliases, 0, 0, false, "(&(objectClass=organizationalPerson))", // The filter to apply []string{"dn", "cn"}, // A list attributes to retrieve nil, ) sr, err := l.Search(searchRequest) if err != nil { log.Fatal(err) } for _, entry := range sr.Entries { fmt.Printf("%s: %v\n", entry.DN, entry.GetAttributeValue("cn")) } } // This example demonstrates how to start a TLS connection func ExampleConn_StartTLS() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() // Reconnect with TLS err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { log.Fatal(err) } // Operations via l are now encrypted } // This example demonstrates how to compare an attribute with a value func ExampleConn_Compare() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() matched, err := l.Compare("cn=user,dc=example,dc=com", "uid", "someuserid") if err != nil { log.Fatal(err) } fmt.Println(matched) } func ExampleConn_PasswordModify_admin() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() err = l.Bind("cn=admin,dc=example,dc=com", "password") if err != nil { log.Fatal(err) } passwordModifyRequest := NewPasswordModifyRequest("cn=user,dc=example,dc=com", "", "NewPassword") _, err = l.PasswordModify(passwordModifyRequest) if err != nil { log.Fatalf("Password could not be changed: %s", err.Error()) } } func ExampleConn_PasswordModify_generatedPassword() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() err = l.Bind("cn=user,dc=example,dc=com", "password") if err != nil { log.Fatal(err) } passwordModifyRequest := NewPasswordModifyRequest("", "OldPassword", "") passwordModifyResponse, err := l.PasswordModify(passwordModifyRequest) if err != nil { log.Fatalf("Password could not be changed: %s", err.Error()) } generatedPassword := passwordModifyResponse.GeneratedPassword log.Printf("Generated password: %s\n", generatedPassword) } func ExampleConn_PasswordModify_setNewPassword() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() err = l.Bind("cn=user,dc=example,dc=com", "password") if err != nil { log.Fatal(err) } passwordModifyRequest := NewPasswordModifyRequest("", "OldPassword", "NewPassword") _, err = l.PasswordModify(passwordModifyRequest) if err != nil { log.Fatalf("Password could not be changed: %s", err.Error()) } } func ExampleConn_Modify() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() // Add a description, and replace the mail attributes modify := NewModifyRequest("cn=user,dc=example,dc=com", nil) modify.Add("description", []string{"An example user"}) modify.Replace("mail", []string{"user@example.org"}) err = l.Modify(modify) if err != nil { log.Fatal(err) } } // Example_userAuthentication shows how a typical application can verify a login attempt // Refer to https://github.com/go-ldap/ldap/issues/93 for issues revolving around unauthenticated binds, with zero length passwords func Example_userAuthentication() { // The username and password we want to check username := "someuser" password := "userpassword" bindusername := "readonly" bindpassword := "password" l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() // Reconnect with TLS err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { log.Fatal(err) } // First bind with a read only user err = l.Bind(bindusername, bindpassword) if err != nil { log.Fatal(err) } // Search for the given username searchRequest := NewSearchRequest( "dc=example,dc=com", ScopeWholeSubtree, NeverDerefAliases, 0, 0, false, fmt.Sprintf("(&(objectClass=organizationalPerson)(uid=%s))", EscapeFilter(username)), []string{"dn"}, nil, ) sr, err := l.Search(searchRequest) if err != nil { log.Fatal(err) } if len(sr.Entries) != 1 { log.Fatal("User does not exist or too many entries returned") } userdn := sr.Entries[0].DN // Bind as the user to verify their password err = l.Bind(userdn, password) if err != nil { log.Fatal(err) } // Rebind as the read only user for any further queries err = l.Bind(bindusername, bindpassword) if err != nil { log.Fatal(err) } } func Example_beherappolicy() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() controls := []Control{} controls = append(controls, NewControlBeheraPasswordPolicy()) bindRequest := NewSimpleBindRequest("cn=admin,dc=example,dc=com", "password", controls) r, err := l.SimpleBind(bindRequest) ppolicyControl := FindControl(r.Controls, ControlTypeBeheraPasswordPolicy) var ppolicy *ControlBeheraPasswordPolicy if ppolicyControl != nil { ppolicy = ppolicyControl.(*ControlBeheraPasswordPolicy) } else { log.Printf("ppolicyControl response not available.\n") } if err != nil { errStr := "ERROR: Cannot bind: " + err.Error() if ppolicy != nil && ppolicy.Error >= 0 { errStr += ":" + ppolicy.ErrorString } log.Print(errStr) } else { logStr := "Login Ok" if ppolicy != nil { if ppolicy.Expire >= 0 { logStr += fmt.Sprintf(". Password expires in %d seconds\n", ppolicy.Expire) } else if ppolicy.Grace >= 0 { logStr += fmt.Sprintf(". Password expired, %d grace logins remain\n", ppolicy.Grace) } } log.Print(logStr) } } func Example_vchuppolicy() { l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() l.Debug = true bindRequest := NewSimpleBindRequest("cn=admin,dc=example,dc=com", "password", nil) r, err := l.SimpleBind(bindRequest) passwordMustChangeControl := FindControl(r.Controls, ControlTypeVChuPasswordMustChange) var passwordMustChange *ControlVChuPasswordMustChange if passwordMustChangeControl != nil { passwordMustChange = passwordMustChangeControl.(*ControlVChuPasswordMustChange) } if passwordMustChange != nil && passwordMustChange.MustChange { log.Printf("Password Must be changed.\n") } passwordWarningControl := FindControl(r.Controls, ControlTypeVChuPasswordWarning) var passwordWarning *ControlVChuPasswordWarning if passwordWarningControl != nil { passwordWarning = passwordWarningControl.(*ControlVChuPasswordWarning) } else { log.Printf("ppolicyControl response not available.\n") } if err != nil { log.Print("ERROR: Cannot bind: " + err.Error()) } else { logStr := "Login Ok" if passwordWarning != nil { if passwordWarning.Expire >= 0 { logStr += fmt.Sprintf(". Password expires in %d seconds\n", passwordWarning.Expire) } } log.Print(logStr) } } // This example demonstrates how to use ControlPaging to manually execute a // paginated search request instead of using SearchWithPaging. func ExampleControlPaging_manualPaging() { conn, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer conn.Close() var pageSize uint32 = 32 searchBase := "dc=example,dc=com" filter := "(objectClass=group)" pagingControl := NewControlPaging(pageSize) attributes := []string{} controls := []Control{pagingControl} for { request := NewSearchRequest(searchBase, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter, attributes, controls) response, err := conn.Search(request) if err != nil { log.Fatalf("Failed to execute search request: %s", err.Error()) } // [do something with the response entries] // In order to prepare the next request, we check if the response // contains another ControlPaging object and a not-empty cookie and // copy that cookie into our pagingControl object: updatedControl := FindControl(response.Controls, ControlTypePaging) if ctrl, ok := updatedControl.(*ControlPaging); ctrl != nil && ok && len(ctrl.Cookie) != 0 { pagingControl.SetCookie(ctrl.Cookie) continue } // If no new paging information is available or the cookie is empty, we // are done with the pagination. break } } // This example demonstrates how to use EXTERNAL SASL with TLS client certificates. func ExampleConn_ExternalBind() { var ldapCert = "/path/to/cert.pem" var ldapKey = "/path/to/key.pem" var ldapCAchain = "/path/to/ca_chain.pem" // Load client cert and key cert, err := tls.LoadX509KeyPair(ldapCert, ldapKey) if err != nil { log.Fatal(err) } // Load CA chain caCert, err := ioutil.ReadFile(ldapCAchain) if err != nil { log.Fatal(err) } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) // Setup TLS with ldap client cert tlsConfig := &tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: caCertPool, InsecureSkipVerify: true, } // connect to ldap server l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() // reconnect using tls err = l.StartTLS(tlsConfig) if err != nil { log.Fatal(err) } // sasl external bind err = l.ExternalBind() if err != nil { log.Fatal(err) } // Conduct ldap queries } // ExampleConn_WhoAmI demonstrates how to run a whoami request according to https://tools.ietf.org/html/rfc4532 func ExampleConn_WhoAmI() { conn, err := DialURL("ldap.example.org:389") if err != nil { log.Fatalf("Failed to connect: %s\n", err) } _, err = conn.SimpleBind(&SimpleBindRequest{ Username: "uid=someone,ou=people,dc=example,dc=org", Password: "MySecretPass", }) if err != nil { log.Fatalf("Failed to bind: %s\n", err) } res, err := conn.WhoAmI(nil) if err != nil { log.Fatalf("Failed to call WhoAmI(): %s\n", err) } fmt.Printf("I am: %s\n", res.AuthzID) } ldap-3.4.1/v3/filter.go000066400000000000000000000374231410654160200146130ustar00rootroot00000000000000package ldap import ( "bytes" hexpac "encoding/hex" "errors" "fmt" "io" "strings" "unicode" "unicode/utf8" ber "github.com/go-asn1-ber/asn1-ber" ) // Filter choices const ( FilterAnd = 0 FilterOr = 1 FilterNot = 2 FilterEqualityMatch = 3 FilterSubstrings = 4 FilterGreaterOrEqual = 5 FilterLessOrEqual = 6 FilterPresent = 7 FilterApproxMatch = 8 FilterExtensibleMatch = 9 ) // FilterMap contains human readable descriptions of Filter choices var FilterMap = map[uint64]string{ FilterAnd: "And", FilterOr: "Or", FilterNot: "Not", FilterEqualityMatch: "Equality Match", FilterSubstrings: "Substrings", FilterGreaterOrEqual: "Greater Or Equal", FilterLessOrEqual: "Less Or Equal", FilterPresent: "Present", FilterApproxMatch: "Approx Match", FilterExtensibleMatch: "Extensible Match", } // SubstringFilter options const ( FilterSubstringsInitial = 0 FilterSubstringsAny = 1 FilterSubstringsFinal = 2 ) // FilterSubstringsMap contains human readable descriptions of SubstringFilter choices var FilterSubstringsMap = map[uint64]string{ FilterSubstringsInitial: "Substrings Initial", FilterSubstringsAny: "Substrings Any", FilterSubstringsFinal: "Substrings Final", } // MatchingRuleAssertion choices const ( MatchingRuleAssertionMatchingRule = 1 MatchingRuleAssertionType = 2 MatchingRuleAssertionMatchValue = 3 MatchingRuleAssertionDNAttributes = 4 ) // MatchingRuleAssertionMap contains human readable descriptions of MatchingRuleAssertion choices var MatchingRuleAssertionMap = map[uint64]string{ MatchingRuleAssertionMatchingRule: "Matching Rule Assertion Matching Rule", MatchingRuleAssertionType: "Matching Rule Assertion Type", MatchingRuleAssertionMatchValue: "Matching Rule Assertion Match Value", MatchingRuleAssertionDNAttributes: "Matching Rule Assertion DN Attributes", } var _SymbolAny = []byte{'*'} // CompileFilter converts a string representation of a filter into a BER-encoded packet func CompileFilter(filter string) (*ber.Packet, error) { if len(filter) == 0 || filter[0] != '(' { return nil, NewError(ErrorFilterCompile, errors.New("ldap: filter does not start with an '('")) } packet, pos, err := compileFilter(filter, 1) if err != nil { return nil, err } switch { case pos > len(filter): return nil, NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter")) case pos < len(filter): return nil, NewError(ErrorFilterCompile, errors.New("ldap: finished compiling filter with extra at end: "+fmt.Sprint(filter[pos:]))) } return packet, nil } // DecompileFilter converts a packet representation of a filter into a string representation func DecompileFilter(packet *ber.Packet) (_ string, err error) { defer func() { if r := recover(); r != nil { err = NewError(ErrorFilterDecompile, errors.New("ldap: error decompiling filter")) } }() buf := bytes.NewBuffer(nil) buf.WriteByte('(') childStr := "" switch packet.Tag { case FilterAnd: buf.WriteByte('&') for _, child := range packet.Children { childStr, err = DecompileFilter(child) if err != nil { return } buf.WriteString(childStr) } case FilterOr: buf.WriteByte('|') for _, child := range packet.Children { childStr, err = DecompileFilter(child) if err != nil { return } buf.WriteString(childStr) } case FilterNot: buf.WriteByte('!') childStr, err = DecompileFilter(packet.Children[0]) if err != nil { return } buf.WriteString(childStr) case FilterSubstrings: buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes())) buf.WriteByte('=') for i, child := range packet.Children[1].Children { if i == 0 && child.Tag != FilterSubstringsInitial { buf.Write(_SymbolAny) } buf.WriteString(EscapeFilter(ber.DecodeString(child.Data.Bytes()))) if child.Tag != FilterSubstringsFinal { buf.Write(_SymbolAny) } } case FilterEqualityMatch: buf.WriteString(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(">=") buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))) case FilterLessOrEqual: buf.WriteString(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("=*") case FilterApproxMatch: buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes())) buf.WriteString("~=") buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))) case FilterExtensibleMatch: attr := "" dnAttributes := false matchingRule := "" value := "" for _, child := range packet.Children { switch child.Tag { case MatchingRuleAssertionMatchingRule: matchingRule = ber.DecodeString(child.Data.Bytes()) case MatchingRuleAssertionType: attr = ber.DecodeString(child.Data.Bytes()) case MatchingRuleAssertionMatchValue: value = ber.DecodeString(child.Data.Bytes()) case MatchingRuleAssertionDNAttributes: dnAttributes = child.Value.(bool) } } if len(attr) > 0 { buf.WriteString(attr) } if dnAttributes { buf.WriteString(":dn") } if len(matchingRule) > 0 { buf.WriteString(":") buf.WriteString(matchingRule) } buf.WriteString(":=") buf.WriteString(EscapeFilter(value)) } buf.WriteByte(')') return buf.String(), nil } func compileFilterSet(filter string, pos int, parent *ber.Packet) (int, error) { for pos < len(filter) && filter[pos] == '(' { child, newPos, err := compileFilter(filter, pos+1) if err != nil { return pos, err } pos = newPos parent.AppendChild(child) } if pos == len(filter) { return pos, NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter")) } return pos + 1, nil } func compileFilter(filter string, pos int) (*ber.Packet, int, error) { var ( packet *ber.Packet err error ) defer func() { if r := recover(); r != nil { err = NewError(ErrorFilterCompile, errors.New("ldap: error compiling filter")) } }() newPos := pos currentRune, currentWidth := utf8.DecodeRuneInString(filter[newPos:]) switch currentRune { case utf8.RuneError: return nil, 0, NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", newPos)) case '(': packet, newPos, err = compileFilter(filter, pos+currentWidth) newPos++ return packet, newPos, err case '&': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterAnd, nil, FilterMap[FilterAnd]) newPos, err = compileFilterSet(filter, pos+currentWidth, packet) return packet, newPos, err case '|': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterOr, nil, FilterMap[FilterOr]) newPos, err = compileFilterSet(filter, pos+currentWidth, packet) return packet, newPos, err case '!': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterNot, nil, FilterMap[FilterNot]) var child *ber.Packet child, newPos, err = compileFilter(filter, pos+currentWidth) packet.AppendChild(child) return packet, newPos, err default: const ( stateReadingAttr = 0 stateReadingExtensibleMatchingRule = 1 stateReadingCondition = 2 ) state := stateReadingAttr attribute := bytes.NewBuffer(nil) extensibleDNAttributes := false extensibleMatchingRule := bytes.NewBuffer(nil) condition := bytes.NewBuffer(nil) for newPos < len(filter) { remainingFilter := filter[newPos:] currentRune, currentWidth = utf8.DecodeRuneInString(remainingFilter) if currentRune == ')' { break } if currentRune == utf8.RuneError { return packet, newPos, NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", newPos)) } switch state { case stateReadingAttr: switch { // Extensible rule, with only DN-matching case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) extensibleDNAttributes = true state = stateReadingCondition newPos += 5 // Extensible rule, with DN-matching and a matching OID case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:"): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) extensibleDNAttributes = true state = stateReadingExtensibleMatchingRule newPos += 4 // Extensible rule, with attr only case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) state = stateReadingCondition newPos += 2 // Extensible rule, with no DN attribute matching case currentRune == ':': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) state = stateReadingExtensibleMatchingRule newPos++ // Equality condition case currentRune == '=': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch]) state = stateReadingCondition newPos++ // Greater-than or equal case currentRune == '>' && strings.HasPrefix(remainingFilter, ">="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual]) state = stateReadingCondition newPos += 2 // Less-than or equal case currentRune == '<' && strings.HasPrefix(remainingFilter, "<="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual]) state = stateReadingCondition newPos += 2 // Approx case currentRune == '~' && strings.HasPrefix(remainingFilter, "~="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterApproxMatch]) state = stateReadingCondition newPos += 2 // Still reading the attribute name default: attribute.WriteRune(currentRune) newPos += currentWidth } case stateReadingExtensibleMatchingRule: switch { // Matching rule OID is done case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="): state = stateReadingCondition newPos += 2 // Still reading the matching rule oid default: extensibleMatchingRule.WriteRune(currentRune) newPos += currentWidth } case stateReadingCondition: // append to the condition condition.WriteRune(currentRune) newPos += currentWidth } } if newPos == len(filter) { err = NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter")) return packet, newPos, err } if packet == nil { err = NewError(ErrorFilterCompile, errors.New("ldap: error parsing filter")) return packet, newPos, err } switch { case packet.Tag == FilterExtensibleMatch: // MatchingRuleAssertion ::= SEQUENCE { // matchingRule [1] MatchingRuleID OPTIONAL, // type [2] AttributeDescription OPTIONAL, // matchValue [3] AssertionValue, // dnAttributes [4] BOOLEAN DEFAULT FALSE // } // Include the matching rule oid, if specified if extensibleMatchingRule.Len() > 0 { packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchingRule, extensibleMatchingRule.String(), MatchingRuleAssertionMap[MatchingRuleAssertionMatchingRule])) } // Include the attribute, if specified if attribute.Len() > 0 { packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionType, attribute.String(), MatchingRuleAssertionMap[MatchingRuleAssertionType])) } // Add the value (only required child) encodedString, encodeErr := decodeEscapedSymbols(condition.Bytes()) if encodeErr != nil { return packet, newPos, encodeErr } packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchValue, encodedString, MatchingRuleAssertionMap[MatchingRuleAssertionMatchValue])) // Defaults to false, so only include in the sequence if true if extensibleDNAttributes { packet.AppendChild(ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionDNAttributes, extensibleDNAttributes, MatchingRuleAssertionMap[MatchingRuleAssertionDNAttributes])) } case packet.Tag == FilterEqualityMatch && bytes.Equal(condition.Bytes(), _SymbolAny): packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute.String(), FilterMap[FilterPresent]) case packet.Tag == FilterEqualityMatch && bytes.Index(condition.Bytes(), _SymbolAny) > -1: packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute.String(), "Attribute")) packet.Tag = FilterSubstrings packet.Description = FilterMap[uint64(packet.Tag)] seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings") parts := bytes.Split(condition.Bytes(), _SymbolAny) for i, part := range parts { if len(part) == 0 { continue } var tag ber.Tag switch i { case 0: tag = FilterSubstringsInitial case len(parts) - 1: tag = FilterSubstringsFinal default: tag = FilterSubstringsAny } encodedString, encodeErr := decodeEscapedSymbols(part) if encodeErr != nil { return packet, newPos, encodeErr } seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, tag, encodedString, FilterSubstringsMap[uint64(tag)])) } packet.AppendChild(seq) default: encodedString, encodeErr := decodeEscapedSymbols(condition.Bytes()) if encodeErr != nil { return packet, newPos, encodeErr } packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute.String(), "Attribute")) packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, encodedString, "Condition")) } newPos += currentWidth return packet, newPos, err } } // Convert from "ABC\xx\xx\xx" form to literal bytes for transport func decodeEscapedSymbols(src []byte) (string, error) { var ( buffer bytes.Buffer offset int reader = bytes.NewReader(src) byteHex []byte byteVal []byte ) for { runeVal, runeSize, err := reader.ReadRune() if err == io.EOF { return buffer.String(), nil } else if err != nil { return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: failed to read filter: %v", err)) } else if runeVal == unicode.ReplacementChar { return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", offset)) } if runeVal == '\\' { // http://tools.ietf.org/search/rfc4515 // \ (%x5C) is not a valid character unless it is followed by two HEX characters due to not // being a member of UTF1SUBSET. if byteHex == nil { byteHex = make([]byte, 2) byteVal = make([]byte, 1) } if _, err := io.ReadFull(reader, byteHex); err != nil { if err == io.ErrUnexpectedEOF { return "", NewError(ErrorFilterCompile, errors.New("ldap: missing characters for escape in filter")) } return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: invalid characters for escape in filter: %v", err)) } if _, err := hexpac.Decode(byteVal, byteHex); err != nil { return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: invalid characters for escape in filter: %v", err)) } buffer.Write(byteVal) } else { buffer.WriteRune(runeVal) } offset += runeSize } } ldap-3.4.1/v3/filter_test.go000066400000000000000000000170231410654160200156440ustar00rootroot00000000000000package ldap import ( "strings" "testing" ber "github.com/go-asn1-ber/asn1-ber" ) type compileTest struct { filterStr string expectedFilter string expectedType int expectedErr string } var testFilters = []compileTest{ { filterStr: "(&(sn=Miller)(givenName=Bob))", expectedFilter: "(&(sn=Miller)(givenName=Bob))", expectedType: FilterAnd, }, { filterStr: "(|(sn=Miller)(givenName=Bob))", expectedFilter: "(|(sn=Miller)(givenName=Bob))", expectedType: FilterOr, }, { filterStr: "(!(sn=Miller))", expectedFilter: "(!(sn=Miller))", expectedType: FilterNot, }, { filterStr: "(sn=Miller)", expectedFilter: "(sn=Miller)", expectedType: FilterEqualityMatch, }, { filterStr: "(sn=Mill*)", expectedFilter: "(sn=Mill*)", expectedType: FilterSubstrings, }, { filterStr: "(sn=*Mill)", expectedFilter: "(sn=*Mill)", expectedType: FilterSubstrings, }, { filterStr: "(sn=*Mill*)", expectedFilter: "(sn=*Mill*)", expectedType: FilterSubstrings, }, { filterStr: "(sn=*i*le*)", expectedFilter: "(sn=*i*le*)", expectedType: FilterSubstrings, }, { filterStr: "(sn=Mi*l*r)", expectedFilter: "(sn=Mi*l*r)", expectedType: FilterSubstrings, }, // substring filters escape properly { filterStr: `(sn=Mi*함*r)`, expectedFilter: `(sn=Mi*\ed\95\a8*r)`, expectedType: FilterSubstrings, }, // already escaped substring filters don't get double-escaped { filterStr: `(sn=Mi*\ed\95\a8*r)`, expectedFilter: `(sn=Mi*\ed\95\a8*r)`, expectedType: FilterSubstrings, }, { filterStr: "(sn=Mi*le*)", expectedFilter: "(sn=Mi*le*)", expectedType: FilterSubstrings, }, { filterStr: "(sn=*i*ler)", expectedFilter: "(sn=*i*ler)", expectedType: FilterSubstrings, }, { filterStr: "(sn>=Miller)", expectedFilter: "(sn>=Miller)", expectedType: FilterGreaterOrEqual, }, { filterStr: "(sn<=Miller)", expectedFilter: "(sn<=Miller)", expectedType: FilterLessOrEqual, }, { filterStr: "(sn=*)", expectedFilter: "(sn=*)", expectedType: FilterPresent, }, { filterStr: "(sn~=Miller)", expectedFilter: "(sn~=Miller)", expectedType: FilterApproxMatch, }, { filterStr: `(objectGUID='\fc\fe\a3\ab\f9\90N\aaGm\d5I~\d12)`, expectedFilter: `(objectGUID='\fc\fe\a3\ab\f9\90N\aaGm\d5I~\d12)`, expectedType: FilterEqualityMatch, }, { filterStr: `(objectGUID=абвгдеёжзийклмнопрстуфхцчшщъыьэюя)`, expectedFilter: `(objectGUID=\d0\b0\d0\b1\d0\b2\d0\b3\d0\b4\d0\b5\d1\91\d0\b6\d0\b7\d0\b8\d0\b9\d0\ba\d0\bb\d0\bc\d0\bd\d0\be\d0\bf\d1\80\d1\81\d1\82\d1\83\d1\84\d1\85\d1\86\d1\87\d1\88\d1\89\d1\8a\d1\8b\d1\8c\d1\8d\d1\8e\d1\8f)`, expectedType: FilterEqualityMatch, }, { filterStr: `(objectGUID=함수목록)`, expectedFilter: `(objectGUID=\ed\95\a8\ec\88\98\eb\aa\a9\eb\a1\9d)`, expectedType: FilterEqualityMatch, }, { filterStr: `(objectGUID=`, expectedFilter: ``, expectedType: 0, expectedErr: "unexpected end of filter", }, { filterStr: `(objectGUID=함수목록`, expectedFilter: ``, expectedType: 0, expectedErr: "unexpected end of filter", }, { filterStr: `((cn=)`, expectedFilter: ``, expectedType: 0, expectedErr: "unexpected end of filter", }, { filterStr: `(&(objectclass=inetorgperson)(cn=中文))`, expectedFilter: `(&(objectclass=inetorgperson)(cn=\e4\b8\ad\e6\96\87))`, expectedType: 0, }, // attr extension { filterStr: `(memberOf:=foo)`, expectedFilter: `(memberOf:=foo)`, expectedType: FilterExtensibleMatch, }, // attr+named matching rule extension { filterStr: `(memberOf:test:=foo)`, expectedFilter: `(memberOf:test:=foo)`, expectedType: FilterExtensibleMatch, }, // attr+oid matching rule extension { filterStr: `(cn:1.2.3.4.5:=Fred Flintstone)`, expectedFilter: `(cn:1.2.3.4.5:=Fred Flintstone)`, expectedType: FilterExtensibleMatch, }, // attr+dn+oid matching rule extension { filterStr: `(sn:dn:2.4.6.8.10:=Barney Rubble)`, expectedFilter: `(sn:dn:2.4.6.8.10:=Barney Rubble)`, expectedType: FilterExtensibleMatch, }, // attr+dn extension { filterStr: `(o:dn:=Ace Industry)`, expectedFilter: `(o:dn:=Ace Industry)`, expectedType: FilterExtensibleMatch, }, // dn extension { filterStr: `(:dn:2.4.6.8.10:=Dino)`, expectedFilter: `(:dn:2.4.6.8.10:=Dino)`, expectedType: FilterExtensibleMatch, }, { filterStr: `(memberOf:1.2.840.113556.1.4.1941:=CN=User1,OU=blah,DC=mydomain,DC=net)`, expectedFilter: `(memberOf:1.2.840.113556.1.4.1941:=CN=User1,OU=blah,DC=mydomain,DC=net)`, expectedType: FilterExtensibleMatch, }, // compileTest{ filterStr: "()", filterType: FilterExtensibleMatch }, } var testInvalidFilters = []string{ `(objectGUID=\zz)`, `(objectGUID=\a)`, } func TestFilter(t *testing.T) { // Test Compiler and Decompiler for _, i := range testFilters { filter, err := CompileFilter(i.filterStr) switch { case err != nil: if i.expectedErr == "" || !strings.Contains(err.Error(), i.expectedErr) { t.Errorf("Problem compiling '%s' - '%v' (expected error to contain '%v')", i.filterStr, err, i.expectedErr) } case filter.Tag != ber.Tag(i.expectedType): t.Errorf("%q Expected %q got %q", i.filterStr, FilterMap[uint64(i.expectedType)], FilterMap[uint64(filter.Tag)]) default: o, err := DecompileFilter(filter) if err != nil { t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error()) } else if i.expectedFilter != o { t.Errorf("%q expected, got %q", i.expectedFilter, o) } } } } func TestDecodeEscapedSymbols(t *testing.T) { for _, testInfo := range []struct { Src string Err string }{ { Src: "a\u0100\x80", Err: `LDAP Result Code 201 "Filter Compile Error": ldap: error reading rune at position 3`, }, { Src: `start\d`, Err: `LDAP Result Code 201 "Filter Compile Error": ldap: missing characters for escape in filter`, }, { Src: `\`, Err: `LDAP Result Code 201 "Filter Compile Error": ldap: invalid characters for escape in filter: EOF`, }, { Src: `start\--end`, Err: `LDAP Result Code 201 "Filter Compile Error": ldap: invalid characters for escape in filter: encoding/hex: invalid byte: U+002D '-'`, }, { Src: `start\d0\hh`, Err: `LDAP Result Code 201 "Filter Compile Error": ldap: invalid characters for escape in filter: encoding/hex: invalid byte: U+0068 'h'`, }, } { res, err := decodeEscapedSymbols([]byte(testInfo.Src)) if err == nil || err.Error() != testInfo.Err { t.Fatal(testInfo.Src, "=> ", err, "!=", testInfo.Err) } if res != "" { t.Fatal(testInfo.Src, "=> ", "invalid result", res) } } } func TestInvalidFilter(t *testing.T) { for _, filterStr := range testInvalidFilters { if _, err := CompileFilter(filterStr); err == nil { t.Errorf("Problem compiling %s - expected err", filterStr) } } } func BenchmarkFilterCompile(b *testing.B) { b.StopTimer() filters := make([]string, len(testFilters)) // Test Compiler and Decompiler for idx, i := range testFilters { filters[idx] = i.filterStr } maxIdx := len(filters) b.StartTimer() for i := 0; i < b.N; i++ { CompileFilter(filters[i%maxIdx]) } } func BenchmarkFilterDecompile(b *testing.B) { b.StopTimer() filters := make([]*ber.Packet, len(testFilters)) // Test Compiler and Decompiler for idx, i := range testFilters { filters[idx], _ = CompileFilter(i.filterStr) } maxIdx := len(filters) b.StartTimer() for i := 0; i < b.N; i++ { DecompileFilter(filters[i%maxIdx]) } } ldap-3.4.1/v3/go.mod000066400000000000000000000003441410654160200140750ustar00rootroot00000000000000module github.com/go-ldap/ldap/v3 go 1.13 require ( github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c github.com/go-asn1-ber/asn1-ber v1.5.1 golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect ) ldap-3.4.1/v3/go.sum000066400000000000000000000021561410654160200141250ustar00rootroot00000000000000github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= ldap-3.4.1/v3/ldap.go000066400000000000000000000260751410654160200142470ustar00rootroot00000000000000package ldap import ( "fmt" "io/ioutil" "os" ber "github.com/go-asn1-ber/asn1-ber" ) // LDAP Application Codes const ( ApplicationBindRequest = 0 ApplicationBindResponse = 1 ApplicationUnbindRequest = 2 ApplicationSearchRequest = 3 ApplicationSearchResultEntry = 4 ApplicationSearchResultDone = 5 ApplicationModifyRequest = 6 ApplicationModifyResponse = 7 ApplicationAddRequest = 8 ApplicationAddResponse = 9 ApplicationDelRequest = 10 ApplicationDelResponse = 11 ApplicationModifyDNRequest = 12 ApplicationModifyDNResponse = 13 ApplicationCompareRequest = 14 ApplicationCompareResponse = 15 ApplicationAbandonRequest = 16 ApplicationSearchResultReference = 19 ApplicationExtendedRequest = 23 ApplicationExtendedResponse = 24 ) // ApplicationMap contains human readable descriptions of LDAP Application Codes var ApplicationMap = map[uint8]string{ ApplicationBindRequest: "Bind Request", ApplicationBindResponse: "Bind Response", ApplicationUnbindRequest: "Unbind Request", ApplicationSearchRequest: "Search Request", ApplicationSearchResultEntry: "Search Result Entry", ApplicationSearchResultDone: "Search Result Done", ApplicationModifyRequest: "Modify Request", ApplicationModifyResponse: "Modify Response", ApplicationAddRequest: "Add Request", ApplicationAddResponse: "Add Response", ApplicationDelRequest: "Del Request", ApplicationDelResponse: "Del Response", ApplicationModifyDNRequest: "Modify DN Request", ApplicationModifyDNResponse: "Modify DN Response", ApplicationCompareRequest: "Compare Request", ApplicationCompareResponse: "Compare Response", ApplicationAbandonRequest: "Abandon Request", ApplicationSearchResultReference: "Search Result Reference", ApplicationExtendedRequest: "Extended Request", ApplicationExtendedResponse: "Extended Response", } // Ldap Behera Password Policy Draft 10 (https://tools.ietf.org/html/draft-behera-ldap-password-policy-10) const ( BeheraPasswordExpired = 0 BeheraAccountLocked = 1 BeheraChangeAfterReset = 2 BeheraPasswordModNotAllowed = 3 BeheraMustSupplyOldPassword = 4 BeheraInsufficientPasswordQuality = 5 BeheraPasswordTooShort = 6 BeheraPasswordTooYoung = 7 BeheraPasswordInHistory = 8 ) // BeheraPasswordPolicyErrorMap contains human readable descriptions of Behera Password Policy error codes var BeheraPasswordPolicyErrorMap = map[int8]string{ BeheraPasswordExpired: "Password expired", BeheraAccountLocked: "Account locked", BeheraChangeAfterReset: "Password must be changed", BeheraPasswordModNotAllowed: "Policy prevents password modification", BeheraMustSupplyOldPassword: "Policy requires old password in order to change password", BeheraInsufficientPasswordQuality: "Password fails quality checks", BeheraPasswordTooShort: "Password is too short for policy", BeheraPasswordTooYoung: "Password has been changed too recently", BeheraPasswordInHistory: "New password is in list of old passwords", } // Adds descriptions to an LDAP Response packet for debugging func addLDAPDescriptions(packet *ber.Packet) (err error) { defer func() { if r := recover(); r != nil { err = NewError(ErrorDebugging, fmt.Errorf("ldap: cannot process packet to add descriptions: %s", r)) } }() packet.Description = "LDAP Response" packet.Children[0].Description = "Message ID" application := uint8(packet.Children[1].Tag) packet.Children[1].Description = ApplicationMap[application] switch application { case ApplicationBindRequest: err = addRequestDescriptions(packet) case ApplicationBindResponse: err = addDefaultLDAPResponseDescriptions(packet) case ApplicationUnbindRequest: err = addRequestDescriptions(packet) case ApplicationSearchRequest: err = addRequestDescriptions(packet) case ApplicationSearchResultEntry: packet.Children[1].Children[0].Description = "Object Name" packet.Children[1].Children[1].Description = "Attributes" for _, child := range packet.Children[1].Children[1].Children { child.Description = "Attribute" child.Children[0].Description = "Attribute Name" child.Children[1].Description = "Attribute Values" for _, grandchild := range child.Children[1].Children { grandchild.Description = "Attribute Value" } } if len(packet.Children) == 3 { err = addControlDescriptions(packet.Children[2]) } case ApplicationSearchResultDone: err = addDefaultLDAPResponseDescriptions(packet) case ApplicationModifyRequest: err = addRequestDescriptions(packet) case ApplicationModifyResponse: case ApplicationAddRequest: err = addRequestDescriptions(packet) case ApplicationAddResponse: case ApplicationDelRequest: err = addRequestDescriptions(packet) case ApplicationDelResponse: case ApplicationModifyDNRequest: err = addRequestDescriptions(packet) case ApplicationModifyDNResponse: case ApplicationCompareRequest: err = addRequestDescriptions(packet) case ApplicationCompareResponse: case ApplicationAbandonRequest: err = addRequestDescriptions(packet) case ApplicationSearchResultReference: case ApplicationExtendedRequest: err = addRequestDescriptions(packet) case ApplicationExtendedResponse: } return err } func addControlDescriptions(packet *ber.Packet) error { packet.Description = "Controls" for _, child := range packet.Children { var value *ber.Packet controlType := "" child.Description = "Control" switch len(child.Children) { case 0: // at least one child is required for control type return fmt.Errorf("at least one child is required for control type") case 1: // just type, no criticality or value controlType = child.Children[0].Value.(string) child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")" case 2: controlType = child.Children[0].Value.(string) child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")" // Children[1] could be criticality or value (both are optional) // duck-type on whether this is a boolean if _, ok := child.Children[1].Value.(bool); ok { child.Children[1].Description = "Criticality" } else { child.Children[1].Description = "Control Value" value = child.Children[1] } case 3: // criticality and value present controlType = child.Children[0].Value.(string) child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")" child.Children[1].Description = "Criticality" child.Children[2].Description = "Control Value" value = child.Children[2] default: // more than 3 children is invalid return fmt.Errorf("more than 3 children for control packet found") } if value == nil { continue } switch controlType { case ControlTypePaging: value.Description += " (Paging)" if value.Value != nil { valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) if err != nil { return fmt.Errorf("failed to decode data bytes: %s", err) } value.Data.Truncate(0) value.Value = nil valueChildren.Children[1].Value = valueChildren.Children[1].Data.Bytes() value.AppendChild(valueChildren) } value.Children[0].Description = "Real Search Control Value" value.Children[0].Children[0].Description = "Paging Size" value.Children[0].Children[1].Description = "Cookie" case ControlTypeBeheraPasswordPolicy: value.Description += " (Password Policy - Behera Draft)" if value.Value != nil { valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) if err != nil { return fmt.Errorf("failed to decode data bytes: %s", err) } value.Data.Truncate(0) value.Value = nil value.AppendChild(valueChildren) } sequence := value.Children[0] for _, child := range sequence.Children { if child.Tag == 0 { //Warning warningPacket := child.Children[0] val, err := ber.ParseInt64(warningPacket.Data.Bytes()) if err != nil { return fmt.Errorf("failed to decode data bytes: %s", err) } if warningPacket.Tag == 0 { //timeBeforeExpiration value.Description += " (TimeBeforeExpiration)" warningPacket.Value = val } else if warningPacket.Tag == 1 { //graceAuthNsRemaining value.Description += " (GraceAuthNsRemaining)" warningPacket.Value = val } } else if child.Tag == 1 { // Error bs := child.Data.Bytes() if len(bs) != 1 || bs[0] > 8 { return fmt.Errorf("failed to decode data bytes: %s", "invalid PasswordPolicyResponse enum value") } val := int8(bs[0]) child.Description = "Error" child.Value = val } } } } return nil } func addRequestDescriptions(packet *ber.Packet) error { packet.Description = "LDAP Request" packet.Children[0].Description = "Message ID" packet.Children[1].Description = ApplicationMap[uint8(packet.Children[1].Tag)] if len(packet.Children) == 3 { return addControlDescriptions(packet.Children[2]) } return nil } func addDefaultLDAPResponseDescriptions(packet *ber.Packet) error { resultCode := uint16(LDAPResultSuccess) matchedDN := "" description := "Success" if err := GetLDAPError(packet); err != nil { resultCode = err.(*Error).ResultCode matchedDN = err.(*Error).MatchedDN description = "Error Message" } packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[resultCode] + ")" packet.Children[1].Children[1].Description = "Matched DN (" + matchedDN + ")" packet.Children[1].Children[2].Description = description if len(packet.Children[1].Children) > 3 { packet.Children[1].Children[3].Description = "Referral" } if len(packet.Children) == 3 { return addControlDescriptions(packet.Children[2]) } return nil } // DebugBinaryFile reads and prints packets from the given filename func DebugBinaryFile(fileName string) error { file, err := ioutil.ReadFile(fileName) if err != nil { return NewError(ErrorDebugging, err) } ber.PrintBytes(os.Stdout, file, "") packet, err := ber.DecodePacketErr(file) if err != nil { return fmt.Errorf("failed to decode packet: %s", err) } if err := addLDAPDescriptions(packet); err != nil { return err } ber.PrintPacket(packet) return nil } var hex = "0123456789abcdef" func mustEscape(c byte) bool { return c > 0x7f || c == '(' || c == ')' || c == '\\' || c == '*' || c == 0 } // EscapeFilter escapes from the provided LDAP filter string the special // characters in the set `()*\` and those out of the range 0 < c < 0x80, // as defined in RFC4515. func EscapeFilter(filter string) string { escape := 0 for i := 0; i < len(filter); i++ { if mustEscape(filter[i]) { escape++ } } if escape == 0 { return filter } buf := make([]byte, len(filter)+escape*2) for i, j := 0, 0; i < len(filter); i++ { c := filter[i] if mustEscape(c) { buf[j+0] = '\\' buf[j+1] = hex[c>>4] buf[j+2] = hex[c&0xf] j += 3 } else { buf[j] = c j++ } } return string(buf) } ldap-3.4.1/v3/ldap_test.go000066400000000000000000000233201410654160200152740ustar00rootroot00000000000000package ldap import ( "crypto/tls" "testing" ber "github.com/go-asn1-ber/asn1-ber" ) const ldapServer = "ldap://ldap.itd.umich.edu:389" const ldapsServer = "ldaps://ldap.itd.umich.edu:636" const baseDN = "dc=umich,dc=edu" var filter = []string{ "(cn=cis-fac)", "(&(owner=*)(cn=cis-fac))", "(&(objectclass=rfc822mailgroup)(cn=*Computer*))", "(&(objectclass=rfc822mailgroup)(cn=*Mathematics*))"} var attributes = []string{ "cn", "description"} func TestUnsecureDialURL(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() } func TestSecureDialURL(t *testing.T) { l, err := DialURL(ldapsServer, DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true})) if err != nil { t.Fatal(err) } defer l.Close() } func TestStartTLS(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { t.Fatal(err) } } func TestTLSConnectionState(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { t.Fatal(err) } cs, ok := l.TLSConnectionState() if !ok { t.Errorf("TLSConnectionState returned ok == false; want true") } if cs.Version == 0 || !cs.HandshakeComplete { t.Errorf("ConnectionState = %#v; expected Version != 0 and HandshakeComplete = true", cs) } } func TestSearch(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() searchRequest := NewSearchRequest( baseDN, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter[0], attributes, nil) sr, err := l.Search(searchRequest) if err != nil { t.Fatal(err) } t.Logf("TestSearch: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries)) } func TestSearchStartTLS(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() searchRequest := NewSearchRequest( baseDN, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter[0], attributes, nil) sr, err := l.Search(searchRequest) if err != nil { t.Fatal(err) } t.Logf("TestSearchStartTLS: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries)) t.Log("TestSearchStartTLS: upgrading with startTLS") err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { t.Fatal(err) } sr, err = l.Search(searchRequest) if err != nil { t.Fatal(err) } t.Logf("TestSearchStartTLS: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries)) } func TestSearchWithPaging(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() err = l.UnauthenticatedBind("") if err != nil { t.Fatal(err) } searchRequest := NewSearchRequest( baseDN, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter[2], attributes, nil) sr, err := l.SearchWithPaging(searchRequest, 5) if err != nil { t.Fatal(err) } t.Logf("TestSearchWithPaging: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries)) searchRequest = NewSearchRequest( baseDN, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter[2], attributes, []Control{NewControlPaging(5)}) sr, err = l.SearchWithPaging(searchRequest, 5) if err != nil { t.Fatal(err) } t.Logf("TestSearchWithPaging: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries)) searchRequest = NewSearchRequest( baseDN, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter[2], attributes, []Control{NewControlPaging(500)}) sr, err = l.SearchWithPaging(searchRequest, 5) if err == nil { t.Fatal("expected an error when paging size in control in search request doesn't match size given in call, got none") } } func searchGoroutine(t *testing.T, l *Conn, results chan *SearchResult, i int) { searchRequest := NewSearchRequest( baseDN, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter[i], attributes, nil) sr, err := l.Search(searchRequest) if err != nil { t.Error(err) results <- nil return } results <- sr } func testMultiGoroutineSearch(t *testing.T, TLS bool, startTLS bool) { var l *Conn var err error if TLS { l, err = DialURL(ldapsServer, DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true})) if err != nil { t.Fatal(err) } defer l.Close() } else { l, err = DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() if startTLS { t.Log("TestMultiGoroutineSearch: using StartTLS...") err := l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { t.Fatal(err) } } } results := make([]chan *SearchResult, len(filter)) for i := range filter { results[i] = make(chan *SearchResult) go searchGoroutine(t, l, results[i], i) } for i := range filter { sr := <-results[i] if sr == nil { t.Errorf("Did not receive results from goroutine for %q", filter[i]) } else { t.Logf("TestMultiGoroutineSearch(%d): %s -> num of entries = %d", i, filter[i], len(sr.Entries)) } } } func TestMultiGoroutineSearch(t *testing.T) { testMultiGoroutineSearch(t, false, false) testMultiGoroutineSearch(t, true, true) testMultiGoroutineSearch(t, false, true) } func TestEscapeFilter(t *testing.T) { if got, want := EscapeFilter("a\x00b(c)d*e\\f"), `a\00b\28c\29d\2ae\5cf`; got != want { t.Errorf("Got %s, expected %s", want, got) } if got, want := EscapeFilter("Lučić"), `Lu\c4\8di\c4\87`; got != want { t.Errorf("Got %s, expected %s", want, got) } } func TestCompare(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() const dn = "cn=math mich,ou=User Groups,ou=Groups,dc=umich,dc=edu" const attribute = "cn" const value = "math mich" sr, err := l.Compare(dn, attribute, value) if err != nil { t.Fatal(err) } t.Log("Compare result:", sr) } func TestMatchDNError(t *testing.T) { l, err := DialURL(ldapServer) if err != nil { t.Fatal(err) } defer l.Close() const wrongBase = "ou=roups,dc=umich,dc=edu" searchRequest := NewSearchRequest( wrongBase, ScopeWholeSubtree, DerefAlways, 0, 0, false, filter[0], attributes, nil) _, err = l.Search(searchRequest) if err == nil { t.Fatal("Expected Error, got nil") } t.Log("TestMatchDNError:", err) } func Test_addControlDescriptions(t *testing.T) { type args struct { packet *ber.Packet } tests := []struct { name string args args wantErr bool }{ {name: "timeBeforeExpiration", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x29, 0x30, 0x27, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0xa, 0x30, 0x8, 0xa0, 0x6, 0x80, 0x4, 0x7f, 0xff, 0xf6, 0x5c})}, wantErr: false}, {name: "graceAuthNsRemaining", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x26, 0x30, 0x24, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x7, 0x30, 0x5, 0xa0, 0x3, 0x81, 0x1, 0x11})}, wantErr: false}, {name: "passwordExpired", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x0})}, wantErr: false}, {name: "accountLocked", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x1})}, wantErr: false}, {name: "passwordModNotAllowed", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x3})}, wantErr: false}, {name: "mustSupplyOldPassword", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x4})}, wantErr: false}, {name: "insufficientPasswordQuality", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x5})}, wantErr: false}, {name: "passwordTooShort", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x6})}, wantErr: false}, {name: "passwordTooYoung", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x7})}, wantErr: false}, {name: "passwordInHistory", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x8})}, wantErr: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := addControlDescriptions(tt.args.packet); (err != nil) != tt.wantErr { t.Errorf("addControlDescriptions() error = %v, wantErr %v", err, tt.wantErr) } }) } } ldap-3.4.1/v3/moddn.go000066400000000000000000000060071410654160200144210ustar00rootroot00000000000000package ldap import ( "log" ber "github.com/go-asn1-ber/asn1-ber" ) // ModifyDNRequest holds the request to modify a DN type ModifyDNRequest struct { DN string NewRDN string DeleteOldRDN bool NewSuperior string // Controls hold optional controls to send with the request Controls []Control } // NewModifyDNRequest creates a new request which can be passed to ModifyDN(). // // To move an object in the tree, set the "newSup" to the new parent entry DN. Use an // empty string for just changing the object's RDN. // // For moving the object without renaming, the "rdn" must be the first // RDN of the given DN. // // A call like // mdnReq := NewModifyDNRequest("uid=someone,dc=example,dc=org", "uid=newname", true, "") // will setup the request to just rename uid=someone,dc=example,dc=org to // uid=newname,dc=example,dc=org. func NewModifyDNRequest(dn string, rdn string, delOld bool, newSup string) *ModifyDNRequest { return &ModifyDNRequest{ DN: dn, NewRDN: rdn, DeleteOldRDN: delOld, NewSuperior: newSup, } } // NewModifyDNWithControlsRequest creates a new request which can be passed to ModifyDN() // and also allows setting LDAP request controls. // // Refer NewModifyDNRequest for other parameters func NewModifyDNWithControlsRequest(dn string, rdn string, delOld bool, newSup string, controls []Control) *ModifyDNRequest { return &ModifyDNRequest{ DN: dn, NewRDN: rdn, DeleteOldRDN: delOld, NewSuperior: newSup, Controls: controls, } } func (req *ModifyDNRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyDNRequest, nil, "Modify DN Request") pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN")) pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.NewRDN, "New RDN")) if req.DeleteOldRDN { buf := []byte{0xff} pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, string(buf), "Delete old RDN")) } else { pkt.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, req.DeleteOldRDN, "Delete old RDN")) } if req.NewSuperior != "" { pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, req.NewSuperior, "New Superior")) } envelope.AppendChild(pkt) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // ModifyDN renames the given DN and optionally move to another base (when the "newSup" argument // to NewModifyDNRequest() is not ""). func (l *Conn) ModifyDN(m *ModifyDNRequest) error { msgCtx, err := l.doRequest(m) if err != nil { return err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return err } if packet.Children[1].Tag == ApplicationModifyDNResponse { err := GetLDAPError(packet) if err != nil { return err } } else { log.Printf("Unexpected Response: %d", packet.Children[1].Tag) } return nil } ldap-3.4.1/v3/modify.go000066400000000000000000000124001410654160200146010ustar00rootroot00000000000000package ldap import ( "errors" "log" ber "github.com/go-asn1-ber/asn1-ber" ) // Change operation choices const ( AddAttribute = 0 DeleteAttribute = 1 ReplaceAttribute = 2 IncrementAttribute = 3 // (https://tools.ietf.org/html/rfc4525) ) // PartialAttribute for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511 type PartialAttribute struct { // Type is the type of the partial attribute Type string // Vals are the values of the partial attribute Vals []string } func (p *PartialAttribute) encode() *ber.Packet { seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "PartialAttribute") seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, p.Type, "Type")) set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue") for _, value := range p.Vals { set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals")) } seq.AppendChild(set) return seq } // Change for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511 type Change struct { // Operation is the type of change to be made Operation uint // Modification is the attribute to be modified Modification PartialAttribute } func (c *Change) encode() *ber.Packet { change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change") change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(c.Operation), "Operation")) change.AppendChild(c.Modification.encode()) return change } // ModifyRequest as defined in https://tools.ietf.org/html/rfc4511 type ModifyRequest struct { // DN is the distinguishedName of the directory entry to modify DN string // Changes contain the attributes to modify Changes []Change // Controls hold optional controls to send with the request Controls []Control } // Add appends the given attribute to the list of changes to be made func (req *ModifyRequest) Add(attrType string, attrVals []string) { req.appendChange(AddAttribute, attrType, attrVals) } // Delete appends the given attribute to the list of changes to be made func (req *ModifyRequest) Delete(attrType string, attrVals []string) { req.appendChange(DeleteAttribute, attrType, attrVals) } // Replace appends the given attribute to the list of changes to be made func (req *ModifyRequest) Replace(attrType string, attrVals []string) { req.appendChange(ReplaceAttribute, attrType, attrVals) } // Increment appends the given attribute to the list of changes to be made func (req *ModifyRequest) Increment(attrType string, attrVal string) { req.appendChange(IncrementAttribute, attrType, []string{attrVal}) } func (req *ModifyRequest) appendChange(operation uint, attrType string, attrVals []string) { req.Changes = append(req.Changes, Change{operation, PartialAttribute{Type: attrType, Vals: attrVals}}) } func (req *ModifyRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request") pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN")) changes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Changes") for _, change := range req.Changes { changes.AppendChild(change.encode()) } pkt.AppendChild(changes) envelope.AppendChild(pkt) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // NewModifyRequest creates a modify request for the given DN func NewModifyRequest(dn string, controls []Control) *ModifyRequest { return &ModifyRequest{ DN: dn, Controls: controls, } } // Modify performs the ModifyRequest func (l *Conn) Modify(modifyRequest *ModifyRequest) error { msgCtx, err := l.doRequest(modifyRequest) if err != nil { return err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return err } if packet.Children[1].Tag == ApplicationModifyResponse { err := GetLDAPError(packet) if err != nil { return err } } else { log.Printf("Unexpected Response: %d", packet.Children[1].Tag) } return nil } // ModifyResult holds the server's response to a modify request type ModifyResult struct { // Controls are the returned controls Controls []Control } // ModifyWithResult performs the ModifyRequest and returns the result func (l *Conn) ModifyWithResult(modifyRequest *ModifyRequest) (*ModifyResult, error) { msgCtx, err := l.doRequest(modifyRequest) if err != nil { return nil, err } defer l.finishMessage(msgCtx) result := &ModifyResult{ Controls: make([]Control, 0), } l.Debug.Printf("%d: waiting for response", msgCtx.id) packet, err := l.readPacket(msgCtx) if err != nil { return nil, err } switch packet.Children[1].Tag { case ApplicationModifyResponse: err := GetLDAPError(packet) if err != nil { return nil, err } if len(packet.Children) == 3 { for _, child := range packet.Children[2].Children { decodedChild, err := DecodeControl(child) if err != nil { return nil, errors.New("failed to decode child control: " + err.Error()) } result.Controls = append(result.Controls, decodedChild) } } } l.Debug.Printf("%d: returning", msgCtx.id) return result, nil } ldap-3.4.1/v3/passwdmodify.go000066400000000000000000000111261410654160200160270ustar00rootroot00000000000000package ldap import ( "fmt" ber "github.com/go-asn1-ber/asn1-ber" ) const ( passwordModifyOID = "1.3.6.1.4.1.4203.1.11.1" ) // PasswordModifyRequest implements the Password Modify Extended Operation as defined in https://www.ietf.org/rfc/rfc3062.txt type PasswordModifyRequest struct { // UserIdentity is an optional string representation of the user associated with the request. // This string may or may not be an LDAPDN [RFC2253]. // If no UserIdentity field is present, the request acts up upon the password of the user currently associated with the LDAP session UserIdentity string // OldPassword, if present, contains the user's current password OldPassword string // NewPassword, if present, contains the desired password for this user NewPassword string } // PasswordModifyResult holds the server response to a PasswordModifyRequest type PasswordModifyResult struct { // GeneratedPassword holds a password generated by the server, if present GeneratedPassword string // Referral are the returned referral Referral string } func (req *PasswordModifyRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Password Modify Extended Operation") pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, passwordModifyOID, "Extended Request Name: Password Modify OID")) extendedRequestValue := ber.Encode(ber.ClassContext, ber.TypePrimitive, 1, nil, "Extended Request Value: Password Modify Request") passwordModifyRequestValue := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Password Modify Request") if req.UserIdentity != "" { passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, req.UserIdentity, "User Identity")) } if req.OldPassword != "" { passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 1, req.OldPassword, "Old Password")) } if req.NewPassword != "" { passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 2, req.NewPassword, "New Password")) } extendedRequestValue.AppendChild(passwordModifyRequestValue) pkt.AppendChild(extendedRequestValue) envelope.AppendChild(pkt) return nil } // NewPasswordModifyRequest creates a new PasswordModifyRequest // // According to the RFC 3602 (https://tools.ietf.org/html/rfc3062): // userIdentity is a string representing the user associated with the request. // This string may or may not be an LDAPDN (RFC 2253). // If userIdentity is empty then the operation will act on the user associated // with the session. // // oldPassword is the current user's password, it can be empty or it can be // needed depending on the session user access rights (usually an administrator // can change a user's password without knowing the current one) and the // password policy (see pwdSafeModify password policy's attribute) // // newPassword is the desired user's password. If empty the server can return // an error or generate a new password that will be available in the // PasswordModifyResult.GeneratedPassword // func NewPasswordModifyRequest(userIdentity string, oldPassword string, newPassword string) *PasswordModifyRequest { return &PasswordModifyRequest{ UserIdentity: userIdentity, OldPassword: oldPassword, NewPassword: newPassword, } } // PasswordModify performs the modification request func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error) { msgCtx, err := l.doRequest(passwordModifyRequest) if err != nil { return nil, err } defer l.finishMessage(msgCtx) packet, err := l.readPacket(msgCtx) if err != nil { return nil, err } result := &PasswordModifyResult{} if packet.Children[1].Tag == ApplicationExtendedResponse { err := GetLDAPError(packet) if err != nil { if IsErrorWithCode(err, LDAPResultReferral) { for _, child := range packet.Children[1].Children { if child.Tag == 3 { result.Referral = child.Children[0].Value.(string) } } } return result, err } } else { return nil, NewError(ErrorUnexpectedResponse, fmt.Errorf("unexpected Response: %d", packet.Children[1].Tag)) } extendedResponse := packet.Children[1] for _, child := range extendedResponse.Children { if child.Tag == 11 { passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes()) if len(passwordModifyResponseValue.Children) == 1 { if passwordModifyResponseValue.Children[0].Tag == 0 { result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes()) } } } } return result, nil } ldap-3.4.1/v3/request.go000066400000000000000000000032161410654160200150070ustar00rootroot00000000000000package ldap import ( "errors" ber "github.com/go-asn1-ber/asn1-ber" ) var ( errRespChanClosed = errors.New("ldap: response channel closed") errCouldNotRetMsg = errors.New("ldap: could not retrieve message") ErrNilConnection = errors.New("ldap: conn is nil, expected net.Conn") ) type request interface { appendTo(*ber.Packet) error } type requestFunc func(*ber.Packet) error func (f requestFunc) appendTo(p *ber.Packet) error { return f(p) } func (l *Conn) doRequest(req request) (*messageContext, error) { if l == nil || l.conn == nil { return nil, ErrNilConnection } 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")) if err := req.appendTo(packet); err != nil { return nil, err } if l.Debug { l.Debug.PrintPacket(packet) } msgCtx, err := l.sendMessage(packet) if err != nil { return nil, err } l.Debug.Printf("%d: returning", msgCtx.id) return msgCtx, nil } func (l *Conn) readPacket(msgCtx *messageContext) (*ber.Packet, error) { l.Debug.Printf("%d: waiting for response", msgCtx.id) packetResponse, ok := <-msgCtx.responses if !ok { return nil, NewError(ErrorNetwork, errRespChanClosed) } packet, err := packetResponse.ReadPacket() l.Debug.Printf("%d: got response %p", msgCtx.id, packet) if err != nil { return nil, err } if packet == nil { return nil, NewError(ErrorNetwork, errCouldNotRetMsg) } if l.Debug { if err = addLDAPDescriptions(packet); err != nil { return nil, err } l.Debug.PrintPacket(packet) } return packet, nil } ldap-3.4.1/v3/search.go000066400000000000000000000317341410654160200145720ustar00rootroot00000000000000package ldap import ( "errors" "fmt" "sort" "strings" ber "github.com/go-asn1-ber/asn1-ber" ) // scope choices const ( ScopeBaseObject = 0 ScopeSingleLevel = 1 ScopeWholeSubtree = 2 ) // ScopeMap contains human readable descriptions of scope choices var ScopeMap = map[int]string{ ScopeBaseObject: "Base Object", ScopeSingleLevel: "Single Level", ScopeWholeSubtree: "Whole Subtree", } // derefAliases const ( NeverDerefAliases = 0 DerefInSearching = 1 DerefFindingBaseObj = 2 DerefAlways = 3 ) // DerefMap contains human readable descriptions of derefAliases choices var DerefMap = map[int]string{ NeverDerefAliases: "NeverDerefAliases", DerefInSearching: "DerefInSearching", DerefFindingBaseObj: "DerefFindingBaseObj", DerefAlways: "DerefAlways", } // NewEntry returns an Entry object with the specified distinguished name and attribute key-value pairs. // The map of attributes is accessed in alphabetical order of the keys in order to ensure that, for the // same input map of attributes, the output entry will contain the same order of attributes func NewEntry(dn string, attributes map[string][]string) *Entry { var attributeNames []string for attributeName := range attributes { attributeNames = append(attributeNames, attributeName) } sort.Strings(attributeNames) var encodedAttributes []*EntryAttribute for _, attributeName := range attributeNames { encodedAttributes = append(encodedAttributes, NewEntryAttribute(attributeName, attributes[attributeName])) } return &Entry{ DN: dn, Attributes: encodedAttributes, } } // Entry represents a single search result entry type Entry struct { // DN is the distinguished name of the entry DN string // Attributes are the returned attributes for the entry Attributes []*EntryAttribute } // GetAttributeValues returns the values for the named attribute, or an empty list func (e *Entry) GetAttributeValues(attribute string) []string { for _, attr := range e.Attributes { if attr.Name == attribute { return attr.Values } } return []string{} } // GetEqualFoldAttributeValues returns the values for the named attribute, or an // empty list. Attribute matching is done with strings.EqualFold. func (e *Entry) GetEqualFoldAttributeValues(attribute string) []string { for _, attr := range e.Attributes { if strings.EqualFold(attribute, attr.Name) { return attr.Values } } return []string{} } // GetRawAttributeValues returns the byte values for the named attribute, or an empty list func (e *Entry) GetRawAttributeValues(attribute string) [][]byte { for _, attr := range e.Attributes { if attr.Name == attribute { return attr.ByteValues } } return [][]byte{} } // GetEqualFoldRawAttributeValues returns the byte values for the named attribute, or an empty list func (e *Entry) GetEqualFoldRawAttributeValues(attribute string) [][]byte { for _, attr := range e.Attributes { if strings.EqualFold(attr.Name, attribute) { return attr.ByteValues } } return [][]byte{} } // GetAttributeValue returns the first value for the named attribute, or "" func (e *Entry) GetAttributeValue(attribute string) string { values := e.GetAttributeValues(attribute) if len(values) == 0 { return "" } return values[0] } // GetEqualFoldAttributeValue returns the first value for the named attribute, or "". // Attribute comparison is done with strings.EqualFold. func (e *Entry) GetEqualFoldAttributeValue(attribute string) string { values := e.GetEqualFoldAttributeValues(attribute) if len(values) == 0 { return "" } return values[0] } // GetRawAttributeValue returns the first value for the named attribute, or an empty slice func (e *Entry) GetRawAttributeValue(attribute string) []byte { values := e.GetRawAttributeValues(attribute) if len(values) == 0 { return []byte{} } return values[0] } // GetEqualFoldRawAttributeValue returns the first value for the named attribute, or an empty slice func (e *Entry) GetEqualFoldRawAttributeValue(attribute string) []byte { values := e.GetEqualFoldRawAttributeValues(attribute) if len(values) == 0 { return []byte{} } return values[0] } // Print outputs a human-readable description func (e *Entry) Print() { fmt.Printf("DN: %s\n", e.DN) for _, attr := range e.Attributes { attr.Print() } } // PrettyPrint outputs a human-readable description indenting func (e *Entry) PrettyPrint(indent int) { fmt.Printf("%sDN: %s\n", strings.Repeat(" ", indent), e.DN) for _, attr := range e.Attributes { attr.PrettyPrint(indent + 2) } } // NewEntryAttribute returns a new EntryAttribute with the desired key-value pair func NewEntryAttribute(name string, values []string) *EntryAttribute { var bytes [][]byte for _, value := range values { bytes = append(bytes, []byte(value)) } return &EntryAttribute{ Name: name, Values: values, ByteValues: bytes, } } // EntryAttribute holds a single attribute type EntryAttribute struct { // Name is the name of the attribute Name string // Values contain the string values of the attribute Values []string // ByteValues contain the raw values of the attribute ByteValues [][]byte } // Print outputs a human-readable description func (e *EntryAttribute) Print() { fmt.Printf("%s: %s\n", e.Name, e.Values) } // PrettyPrint outputs a human-readable description with indenting func (e *EntryAttribute) PrettyPrint(indent int) { fmt.Printf("%s%s: %s\n", strings.Repeat(" ", indent), e.Name, e.Values) } // SearchResult holds the server's response to a search request type SearchResult struct { // Entries are the returned entries Entries []*Entry // Referrals are the returned referrals Referrals []string // Controls are the returned controls Controls []Control } // Print outputs a human-readable description func (s *SearchResult) Print() { for _, entry := range s.Entries { entry.Print() } } // PrettyPrint outputs a human-readable description with indenting func (s *SearchResult) PrettyPrint(indent int) { for _, entry := range s.Entries { entry.PrettyPrint(indent) } } // SearchRequest represents a search request to send to the server type SearchRequest struct { BaseDN string Scope int DerefAliases int SizeLimit int TimeLimit int TypesOnly bool Filter string Attributes []string Controls []Control } func (req *SearchRequest) appendTo(envelope *ber.Packet) error { pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationSearchRequest, nil, "Search Request") pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.BaseDN, "Base DN")) pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(req.Scope), "Scope")) pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(req.DerefAliases), "Deref Aliases")) pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(req.SizeLimit), "Size Limit")) pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(req.TimeLimit), "Time Limit")) pkt.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, req.TypesOnly, "Types Only")) // compile and encode filter filterPacket, err := CompileFilter(req.Filter) if err != nil { return err } pkt.AppendChild(filterPacket) // encode attributes attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes") for _, attribute := range req.Attributes { attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute")) } pkt.AppendChild(attributesPacket) envelope.AppendChild(pkt) if len(req.Controls) > 0 { envelope.AppendChild(encodeControls(req.Controls)) } return nil } // NewSearchRequest creates a new search request func NewSearchRequest( BaseDN string, Scope, DerefAliases, SizeLimit, TimeLimit int, TypesOnly bool, Filter string, Attributes []string, Controls []Control, ) *SearchRequest { return &SearchRequest{ BaseDN: BaseDN, Scope: Scope, DerefAliases: DerefAliases, SizeLimit: SizeLimit, TimeLimit: TimeLimit, TypesOnly: TypesOnly, Filter: Filter, Attributes: Attributes, Controls: Controls, } } // SearchWithPaging accepts a search request and desired page size in order to execute LDAP queries to fulfill the // search request. All paged LDAP query responses will be buffered and the final result will be returned atomically. // The following four cases are possible given the arguments: // - given SearchRequest missing a control of type ControlTypePaging: we will add one with the desired paging size // - given SearchRequest contains a control of type ControlTypePaging that isn't actually a ControlPaging: fail without issuing any queries // - given SearchRequest contains a control of type ControlTypePaging with pagingSize equal to the size requested: no change to the search request // - given SearchRequest contains a control of type ControlTypePaging with pagingSize not equal to the size requested: fail without issuing any queries // A requested pagingSize of 0 is interpreted as no limit by LDAP servers. func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) { var pagingControl *ControlPaging control := FindControl(searchRequest.Controls, ControlTypePaging) if control == nil { pagingControl = NewControlPaging(pagingSize) searchRequest.Controls = append(searchRequest.Controls, pagingControl) } else { castControl, ok := control.(*ControlPaging) if !ok { return nil, fmt.Errorf("expected paging control to be of type *ControlPaging, got %v", control) } if castControl.PagingSize != pagingSize { return nil, fmt.Errorf("paging size given in search request (%d) conflicts with size given in search call (%d)", castControl.PagingSize, pagingSize) } pagingControl = castControl } searchResult := new(SearchResult) for { result, err := l.Search(searchRequest) l.Debug.Printf("Looking for Paging Control...") if err != nil { return searchResult, err } if result == nil { return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received")) } for _, entry := range result.Entries { searchResult.Entries = append(searchResult.Entries, entry) } for _, referral := range result.Referrals { searchResult.Referrals = append(searchResult.Referrals, referral) } for _, control := range result.Controls { searchResult.Controls = append(searchResult.Controls, control) } l.Debug.Printf("Looking for Paging Control...") pagingResult := FindControl(result.Controls, ControlTypePaging) if pagingResult == nil { pagingControl = nil l.Debug.Printf("Could not find paging control. Breaking...") break } cookie := pagingResult.(*ControlPaging).Cookie if len(cookie) == 0 { pagingControl = nil l.Debug.Printf("Could not find cookie. Breaking...") break } pagingControl.SetCookie(cookie) } if pagingControl != nil { l.Debug.Printf("Abandoning Paging...") pagingControl.PagingSize = 0 l.Search(searchRequest) } return searchResult, nil } // Search performs the given search request func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) { msgCtx, err := l.doRequest(searchRequest) if err != nil { return nil, err } defer l.finishMessage(msgCtx) result := &SearchResult{ Entries: make([]*Entry, 0), Referrals: make([]string, 0), Controls: make([]Control, 0)} for { packet, err := l.readPacket(msgCtx) if err != nil { return result, err } switch packet.Children[1].Tag { case 4: entry := &Entry{ DN: packet.Children[1].Children[0].Value.(string), Attributes: unpackAttributes(packet.Children[1].Children[1].Children), } result.Entries = append(result.Entries, entry) case 5: err := GetLDAPError(packet) if err != nil { return result, err } if len(packet.Children) == 3 { for _, child := range packet.Children[2].Children { decodedChild, err := DecodeControl(child) if err != nil { return result, fmt.Errorf("failed to decode child control: %s", err) } result.Controls = append(result.Controls, decodedChild) } } return result, nil case 19: result.Referrals = append(result.Referrals, packet.Children[1].Children[0].Value.(string)) } } } // unpackAttributes will extract all given LDAP attributes and it's values // from the ber.Packet func unpackAttributes(children []*ber.Packet) []*EntryAttribute { entries := make([]*EntryAttribute, len(children)) for i, child := range children { length := len(child.Children[1].Children) entry := &EntryAttribute{ Name: child.Children[0].Value.(string), // pre-allocate the slice since we can determine // the number of attributes at this point Values: make([]string, length), ByteValues: make([][]byte, length), } for i, value := range child.Children[1].Children { entry.ByteValues[i] = value.ByteValue entry.Values[i] = value.Value.(string) } entries[i] = entry } return entries } ldap-3.4.1/v3/search_test.go000066400000000000000000000022631410654160200156240ustar00rootroot00000000000000package ldap import ( "reflect" "testing" ) // TestNewEntry tests that repeated calls to NewEntry return the same value with the same input func TestNewEntry(t *testing.T) { dn := "testDN" attributes := map[string][]string{ "alpha": {"value"}, "beta": {"value"}, "gamma": {"value"}, "delta": {"value"}, "epsilon": {"value"}, } executedEntry := NewEntry(dn, attributes) iteration := 0 for { if iteration == 100 { break } testEntry := NewEntry(dn, attributes) if !reflect.DeepEqual(executedEntry, testEntry) { t.Fatalf("subsequent calls to NewEntry did not yield the same result:\n\texpected:\n\t%v\n\tgot:\n\t%v\n", executedEntry, testEntry) } iteration = iteration + 1 } } func TestGetAttributeValue(t *testing.T) { dn := "testDN" attributes := map[string][]string{ "Alpha": {"value"}, "bEta": {"value"}, "gaMma": {"value"}, "delTa": {"value"}, "epsiLon": {"value"}, } entry := NewEntry(dn, attributes) if entry.GetAttributeValue("Alpha") != "value" { t.Errorf("failed to get attribute in original case") } if entry.GetEqualFoldAttributeValue("alpha") != "value" { t.Errorf("failed to get attribute in changed case") } } ldap-3.4.1/v3/unbind.go000066400000000000000000000016611410654160200146000ustar00rootroot00000000000000package ldap import ( "errors" ber "github.com/go-asn1-ber/asn1-ber" ) var ErrConnUnbound = NewError(ErrorNetwork, errors.New("ldap: connection is closed")) type unbindRequest struct{} func (unbindRequest) appendTo(envelope *ber.Packet) error { envelope.AppendChild(ber.Encode(ber.ClassApplication, ber.TypePrimitive, ApplicationUnbindRequest, nil, ApplicationMap[ApplicationUnbindRequest])) return nil } // Unbind will perform an unbind request. The Unbind operation // should be thought of as the "quit" operation. // See https://datatracker.ietf.org/doc/html/rfc4511#section-4.3 func (l *Conn) Unbind() error { if l.IsClosing() { return ErrConnUnbound } _, err := l.doRequest(unbindRequest{}) if err != nil { return err } // Sending an unbindRequest will make the connection unusable. // Pending requests will fail with: // LDAP Result Code 200 "Network Error": ldap: response channel closed l.Close() return nil } ldap-3.4.1/v3/whoami.go000066400000000000000000000046761410654160200146160ustar00rootroot00000000000000package ldap // This file contains the "Who Am I?" extended operation as specified in rfc 4532 // // https://tools.ietf.org/html/rfc4532 import ( "errors" "fmt" ber "github.com/go-asn1-ber/asn1-ber" ) type whoAmIRequest bool // WhoAmIResult is returned by the WhoAmI() call type WhoAmIResult struct { AuthzID string } func (r whoAmIRequest) encode() (*ber.Packet, error) { request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Who Am I? Extended Operation") request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, ControlTypeWhoAmI, "Extended Request Name: Who Am I? OID")) return request, nil } // WhoAmI returns the authzId the server thinks we are, you may pass controls // like a Proxied Authorization control func (l *Conn) WhoAmI(controls []Control) (*WhoAmIResult, error) { 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")) req := whoAmIRequest(true) encodedWhoAmIRequest, err := req.encode() if err != nil { return nil, err } packet.AppendChild(encodedWhoAmIRequest) if len(controls) != 0 { packet.AppendChild(encodeControls(controls)) } l.Debug.PrintPacket(packet) msgCtx, err := l.sendMessage(packet) if err != nil { return nil, err } defer l.finishMessage(msgCtx) result := &WhoAmIResult{} l.Debug.Printf("%d: waiting for response", msgCtx.id) packetResponse, ok := <-msgCtx.responses if !ok { return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed")) } packet, err = packetResponse.ReadPacket() l.Debug.Printf("%d: got response %p", msgCtx.id, packet) if err != nil { return nil, err } if packet == nil { return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve message")) } if l.Debug { if err := addLDAPDescriptions(packet); err != nil { return nil, err } ber.PrintPacket(packet) } if packet.Children[1].Tag == ApplicationExtendedResponse { if err := GetLDAPError(packet); err != nil { return nil, err } } else { return nil, NewError(ErrorUnexpectedResponse, fmt.Errorf("Unexpected Response: %d", packet.Children[1].Tag)) } extendedResponse := packet.Children[1] for _, child := range extendedResponse.Children { if child.Tag == 11 { result.AuthzID = ber.DecodeString(child.Data.Bytes()) } } return result, nil } ldap-3.4.1/whoami.go000066400000000000000000000046761410654160200142660ustar00rootroot00000000000000package ldap // This file contains the "Who Am I?" extended operation as specified in rfc 4532 // // https://tools.ietf.org/html/rfc4532 import ( "errors" "fmt" ber "github.com/go-asn1-ber/asn1-ber" ) type whoAmIRequest bool // WhoAmIResult is returned by the WhoAmI() call type WhoAmIResult struct { AuthzID string } func (r whoAmIRequest) encode() (*ber.Packet, error) { request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Who Am I? Extended Operation") request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, ControlTypeWhoAmI, "Extended Request Name: Who Am I? OID")) return request, nil } // WhoAmI returns the authzId the server thinks we are, you may pass controls // like a Proxied Authorization control func (l *Conn) WhoAmI(controls []Control) (*WhoAmIResult, error) { 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")) req := whoAmIRequest(true) encodedWhoAmIRequest, err := req.encode() if err != nil { return nil, err } packet.AppendChild(encodedWhoAmIRequest) if len(controls) != 0 { packet.AppendChild(encodeControls(controls)) } l.Debug.PrintPacket(packet) msgCtx, err := l.sendMessage(packet) if err != nil { return nil, err } defer l.finishMessage(msgCtx) result := &WhoAmIResult{} l.Debug.Printf("%d: waiting for response", msgCtx.id) packetResponse, ok := <-msgCtx.responses if !ok { return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed")) } packet, err = packetResponse.ReadPacket() l.Debug.Printf("%d: got response %p", msgCtx.id, packet) if err != nil { return nil, err } if packet == nil { return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve message")) } if l.Debug { if err := addLDAPDescriptions(packet); err != nil { return nil, err } ber.PrintPacket(packet) } if packet.Children[1].Tag == ApplicationExtendedResponse { if err := GetLDAPError(packet); err != nil { return nil, err } } else { return nil, NewError(ErrorUnexpectedResponse, fmt.Errorf("Unexpected Response: %d", packet.Children[1].Tag)) } extendedResponse := packet.Children[1] for _, child := range extendedResponse.Children { if child.Tag == 11 { result.AuthzID = ber.DecodeString(child.Data.Bytes()) } } return result, nil }