pax_global_header00006660000000000000000000000064145013133230014505gustar00rootroot0000000000000052 comment=21d1415bcac35ef3e8e35abf1c0a6af46977dc3d ldap-3.4.6/000077500000000000000000000000001450131332300124375ustar00rootroot00000000000000ldap-3.4.6/.githooks/000077500000000000000000000000001450131332300143445ustar00rootroot00000000000000ldap-3.4.6/.githooks/pre-push000077500000000000000000000002101450131332300160260ustar00rootroot00000000000000#!/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.6/.github/000077500000000000000000000000001450131332300137775ustar00rootroot00000000000000ldap-3.4.6/.github/workflows/000077500000000000000000000000001450131332300160345ustar00rootroot00000000000000ldap-3.4.6/.github/workflows/cifuzz.yml000066400000000000000000000017461450131332300201010ustar00rootroot00000000000000name: CIFuzz on: [pull_request] permissions: {} jobs: Fuzzing: runs-on: ubuntu-latest permissions: security-events: write steps: - name: Build Fuzzers id: build uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master with: oss-fuzz-project-name: 'go-ldap' language: go - name: Run Fuzzers uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master with: oss-fuzz-project-name: 'go-ldap' language: go fuzz-seconds: 300 output-sarif: true - name: Upload Crash uses: actions/upload-artifact@v3 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts - name: Upload Sarif if: always() && steps.build.outcome == 'success' uses: github/codeql-action/upload-sarif@v2 with: # Path to SARIF file relative to the root of the repository sarif_file: cifuzz-sarif/results.sarif checkout_path: cifuzz-sarif ldap-3.4.6/.github/workflows/lint.yml000066400000000000000000000007011450131332300175230ustar00rootroot00000000000000name: golangci-lint on: pull_request: branches: [ master ] permissions: contents: read jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/setup-go@v4 with: go-version: '1.21' cache: false - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: version: latest only-new-issues: true ldap-3.4.6/.github/workflows/pr.yml000066400000000000000000000020161450131332300171770ustar00rootroot00000000000000name: PR on: pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: matrix: go: [ '1.21', '1.20', '1.19', '1.18', '1.17', '1.16', '1.15', '1.14', ] branch: [ '.', './v3' ] name: Go ${{ matrix.go }}.x PR Validate ${{ matrix.branch }} (Modules) steps: - name: Checkout code uses: actions/checkout@v3 - name: Set up Go uses: actions/setup-go@v4 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 . lint: name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3.2.0 with: version: v1.50.0 only-new-issues: true ldap-3.4.6/.gitignore000066400000000000000000000000001450131332300144150ustar00rootroot00000000000000ldap-3.4.6/CONTRIBUTING.md000066400000000000000000000007461450131332300146770ustar00rootroot00000000000000# 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.6/LICENSE000066400000000000000000000022031450131332300134410ustar00rootroot00000000000000The 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.6/Makefile000066400000000000000000000037101450131332300141000ustar00rootroot00000000000000.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 ./... fuzz: go test -fuzz=FuzzParseDN -fuzztime=600s . go test -fuzz=FuzzDecodeEscapedSymbols -fuzztime=600s . go test -fuzz=FuzzEscapeDN -fuzztime=600s . # 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.6/README.md000066400000000000000000000043321450131332300137200ustar00rootroot00000000000000[![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.6/add.go000066400000000000000000000050461450131332300135230ustar00rootroot00000000000000package ldap import ( "fmt" 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 { return fmt.Errorf("ldap: unexpected response: %d", packet.Children[1].Tag) } return nil } ldap-3.4.6/bind.go000066400000000000000000000603171450131332300137110ustar00rootroot00000000000000package 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 // AllowEmptyPassword sets whether the client allows binding with an empty password // (normally used for unauthenticated bind). AllowEmptyPassword bool // 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 } // NTLMUnauthenticatedBind performs an bind with an empty password. // // A username is required. The anonymous bind is not (yet) supported by the go-ntlmssp library (https://github.com/Azure/go-ntlmssp/blob/819c794454d067543bc61d29f61fef4b3c3df62c/authenticate_message.go#L87) // // See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4 part 3.2.5.1.2 func (l *Conn) NTLMUnauthenticatedBind(domain, username string) error { req := &NTLMBindRequest{ Domain: domain, Username: username, Password: "", AllowEmptyPassword: true, } _, 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.AllowEmptyPassword && 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.Hash != "" { responseMessage, err = ntlmssp.ProcessChallengeWithHash(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Hash) } else if ntlmBindRequest.Password != "" || ntlmBindRequest.AllowEmptyPassword { _, _, domainNeeded := ntlmssp.GetDomain(ntlmBindRequest.Username) responseMessage, err = ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password, domainNeeded) } 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 } // GSSAPIClient interface is used as the client-side implementation for the // GSSAPI SASL mechanism. // Interface inspired by GSSAPIClient from golang.org/x/crypto/ssh type GSSAPIClient interface { // InitSecContext initiates the establishment of a security context for // GSS-API between the client and server. // Initially the token parameter should be specified as nil. // The routine may return a outputToken which should be transferred to // the server, where the server will present it to AcceptSecContext. // If no token need be sent, InitSecContext will indicate this by setting // needContinue to false. To complete the context // establishment, one or more reply tokens may be required from the server; // if so, InitSecContext will return a needContinue which is true. // In this case, InitSecContext should be called again when the // reply token is received from the server, passing the reply token // to InitSecContext via the token parameters. // See RFC 4752 section 3.1. InitSecContext(target string, token []byte) (outputToken []byte, needContinue bool, err error) // NegotiateSaslAuth performs the last step of the Sasl handshake. // It takes a token, which, when unwrapped, describes the servers supported // security layers (first octet) and maximum receive buffer (remaining // three octets). // If the received token is unacceptable an error must be returned to abort // the handshake. // Outputs a signed token describing the client's selected security layer // and receive buffer size and optionally an authorization identity. // The returned token will be sent to the server and the handshake considered // completed successfully and the server authenticated. // See RFC 4752 section 3.1. NegotiateSaslAuth(token []byte, authzid string) ([]byte, error) // DeleteSecContext destroys any established secure context. DeleteSecContext() error } // GSSAPIBindRequest represents a GSSAPI SASL mechanism bind request. // See rfc4752 and rfc4513 section 5.2.1.2. type GSSAPIBindRequest struct { // Service Principal Name user for the service ticket. Eg. "ldap/" ServicePrincipalName string // (Optional) Authorization entity AuthZID string // (Optional) Controls to send with the bind request Controls []Control } // GSSAPIBind performs the GSSAPI SASL bind using the provided GSSAPI client. func (l *Conn) GSSAPIBind(client GSSAPIClient, servicePrincipal, authzid string) error { return l.GSSAPIBindRequest(client, &GSSAPIBindRequest{ ServicePrincipalName: servicePrincipal, AuthZID: authzid, }) } // GSSAPIBindRequest performs the GSSAPI SASL bind using the provided GSSAPI client. func (l *Conn) GSSAPIBindRequest(client GSSAPIClient, req *GSSAPIBindRequest) error { //nolint:errcheck defer client.DeleteSecContext() var err error var reqToken []byte var recvToken []byte needInit := true for { if needInit { // Establish secure context between client and server. reqToken, needInit, err = client.InitSecContext(req.ServicePrincipalName, recvToken) if err != nil { return err } } else { // Secure context is set up, perform the last step of SASL handshake. reqToken, err = client.NegotiateSaslAuth(recvToken, req.AuthZID) if err != nil { return err } } // Send Bind request containing the current token and extract the // token sent by server. recvToken, err = l.saslBindTokenExchange(req.Controls, reqToken) if err != nil { return err } if !needInit && len(recvToken) == 0 { break } } return nil } func (l *Conn) saslBindTokenExchange(reqControls []Control, reqToken []byte) ([]byte, error) { // Construct LDAP Bind request with GSSAPI SASL mechanism. envelope := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") envelope.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, "GSSAPI", "SASL Mech")) if len(reqToken) > 0 { auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, string(reqToken), "Credentials")) } request.AppendChild(auth) envelope.AppendChild(request) if len(reqControls) > 0 { envelope.AppendChild(encodeControls(reqControls)) } msgCtx, err := l.sendMessage(envelope) 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) } // https://www.rfc-editor.org/rfc/rfc4511#section-4.1.1 // packet is an envelope // child 0 is message id // child 1 is protocolOp if len(packet.Children) != 2 { return nil, fmt.Errorf("bad bind response") } protocolOp := packet.Children[1] RESP: switch protocolOp.Description { case "Bind Response": // Bind Response // Bind Reponse is an LDAP Response (https://www.rfc-editor.org/rfc/rfc4511#section-4.1.9) // with an additional optional serverSaslCreds string (https://www.rfc-editor.org/rfc/rfc4511#section-4.2.2) // child 0 is resultCode resultCode := protocolOp.Children[0] if resultCode.Tag != ber.TagEnumerated { break RESP } switch resultCode.Value.(int64) { case 14: // Sasl bind in progress if len(protocolOp.Children) < 3 { break RESP } referral := protocolOp.Children[3] switch referral.Description { case "Referral": if referral.ClassType != ber.ClassContext || referral.Tag != ber.TagObjectDescriptor { break RESP } return ioutil.ReadAll(referral.Data) } // Optional: //if len(protocolOp.Children) == 4 { // serverSaslCreds := protocolOp.Children[4] //} case 0: // Success - Bind OK. // SASL layer in effect (if any) (See https://www.rfc-editor.org/rfc/rfc4513#section-5.2.1.4) // NOTE: SASL security layers are not supported currently. return nil, nil } } return nil, GetLDAPError(packet) } ldap-3.4.6/client.go000066400000000000000000000026501450131332300142470ustar00rootroot00000000000000package ldap import ( "context" "crypto/tls" "time" ) // Client knows how to interact with an LDAP server type Client interface { Start() StartTLS(*tls.Config) error Close() error GetLastError() error IsClosing() bool SetTimeout(time.Duration) TLSConnectionState() (tls.ConnectionState, bool) Bind(username, password string) error UnauthenticatedBind(username string) error SimpleBind(*SimpleBindRequest) (*SimpleBindResult, error) ExternalBind() error NTLMUnauthenticatedBind(domain, username string) error Unbind() 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) SearchAsync(ctx context.Context, searchRequest *SearchRequest, bufferSize int) Response SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) DirSync(searchRequest *SearchRequest, flags, maxAttrCount int64, cookie []byte) (*SearchResult, error) DirSyncAsync(ctx context.Context, searchRequest *SearchRequest, bufferSize int, flags, maxAttrCount int64, cookie []byte) Response Syncrepl(ctx context.Context, searchRequest *SearchRequest, bufferSize int, mode ControlSyncRequestMode, cookie []byte, reloadHint bool) Response } ldap-3.4.6/compare.go000066400000000000000000000033411450131332300144150ustar00rootroot00000000000000package 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.6/conn.go000066400000000000000000000417251450131332300137340ustar00rootroot00000000000000package ldap import ( "bufio" "context" "crypto/tls" "errors" "fmt" "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, timeout time.Duration) { timeoutCtx := context.Background() if timeout > 0 { var cancelFunc context.CancelFunc timeoutCtx, cancelFunc = context.WithTimeout(context.Background(), timeout) defer cancelFunc() } 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. case <-timeoutCtx.Done(): // The timeout was reached before the packet was sent. } } 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. // https://github.com/go-ldap/ldap/pull/199 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 err error } 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.dialer = d } } // DialWithTLSConfig updates tls.Config in DialContext. func DialWithTLSConfig(tc *tls.Config) DialOpt { return func(dc *DialContext) { dc.tlsConfig = 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. // @deprecated Use DialWithDialer and DialWithTLSConfig instead func DialWithTLSDialer(tlsConfig *tls.Config, dialer *net.Dialer) DialOpt { return func(dc *DialContext) { dc.tlsConfig = tlsConfig dc.dialer = dialer } } // DialContext contains necessary parameters to dial the given ldap URL. type DialContext struct { dialer *net.Dialer tlsConfig *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.dialer.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 "cldap": if port == "" { port = DefaultLdapPort } return dc.dialer.Dial("udp", net.JoinHostPort(host, port)) case "ldap": if port == "" { port = DefaultLdapPort } return dc.dialer.Dial("tcp", net.JoinHostPort(host, port)) case "ldaps": if port == "" { port = DefaultLdapsPort } return tls.DialWithDialer(dc.dialer, "tcp", net.JoinHostPort(host, port), dc.tlsConfig) } 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://, // and cldap:// (RFC1798, deprecated but used by Active Directory). // On success a new Conn for the connection is returned. func DialURL(addr string, opts ...DialOpt) (*Conn, error) { u, err := url.Parse(addr) if err != nil { return nil, NewError(ErrorNetwork, err) } var dc DialContext for _, opt := range opts { opt(&dc) } if dc.dialer == nil { dc.dialer = &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 { l := &Conn{ conn: conn, chanConfirm: make(chan struct{}), chanMessageID: make(chan int64), chanMessage: make(chan *messagePacket, 10), messageContexts: map[int64]*messageContext{}, requestTimeout: 0, isTLS: isTLS, } l.wgClose.Add(1) return l } // Start initializes goroutines to read responses and process messages func (l *Conn) Start() { 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() (err error) { 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} timeoutCtx := context.Background() if l.getTimeout() > 0 { var cancelFunc context.CancelFunc timeoutCtx, cancelFunc = context.WithTimeout(timeoutCtx, time.Duration(l.getTimeout())) defer cancelFunc() } select { case <-l.chanConfirm: // Confirmation was received. case <-timeoutCtx.Done(): // The timeout was reached before confirmation was received. } close(l.chanMessage) l.Debug.Printf("Closing network connection") err = l.conn.Close() l.wgClose.Done() } l.wgClose.Wait() return err } // SetTimeout sets the time after a request is sent that a MessageTimeout triggers func (l *Conn) SetTimeout(timeout time.Duration) { atomic.StoreInt64(&l.requestTimeout, int64(timeout)) } func (l *Conn) getTimeout() int64 { return atomic.LoadInt64(&l.requestTimeout) } // Returns the next available messageID func (l *Conn) nextMessageID() int64 { if messageID, ok := <-l.chanMessageID; ok { return messageID } return 0 } // GetLastError returns the last recorded error from goroutines like processMessages and reader. // Only the last recorded error will be returned. func (l *Conn) GetLastError() error { l.messageMutex.Lock() defer l.messageMutex.Unlock() return l.err } // StartTLS sends the command to start a TLS session and then creates a new TLS Client func (l *Conn) StartTLS(config *tls.Config) error { if l.isTLS { 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 { l.err = fmt.Errorf("ldap: recovered panic in processMessages: %v", err) } for messageID, msgCtx := range l.messageContexts { // If we are closing due to an error, inform anyone who // is waiting about the error. if l.IsClosing() && l.closeErr.Load() != nil { msgCtx.sendResponse(&PacketResponse{Error: l.closeErr.Load().(error)}, time.Duration(l.getTimeout())) } 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)}, time.Duration(l.getTimeout())) 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 if l.getTimeout() > 0 { go func() { timer := time.NewTimer(time.Duration(l.getTimeout())) defer func() { if err := recover(); err != nil { l.err = fmt.Errorf("ldap: recovered panic in RequestTimeout: %v", err) } timer.Stop() }() select { case <-timer.C: timeoutMessage := &messagePacket{ Op: MessageTimeout, MessageID: message.MessageID, } l.sendProcessMessage(timeoutMessage) case <-message.Context.done: } }() } case MessageResponse: l.Debug.Printf("Receiving message %d", message.MessageID) if msgCtx, ok := l.messageContexts[message.MessageID]; ok { msgCtx.sendResponse(&PacketResponse{message.Packet, nil}, time.Duration(l.getTimeout())) } else { l.err = fmt.Errorf("ldap: received unexpected message %d, %v", message.MessageID, l.IsClosing()) l.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"))}, time.Duration(l.getTimeout())) 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 { l.err = fmt.Errorf("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.6/conn_test.go000066400000000000000000000275621450131332300147760ustar00rootroot00000000000000package 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") } _, 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) } } func TestRequestTimeoutDeadlock(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.Start() // trigger a race condition on accessing request timeout n := 3 for i := 0; i < n; i++ { go func() { conn.SetTimeout(time.Millisecond) }() } // Attempt to close the connection when the message handler is // blocked or inactive conn.Close() } // TestInvalidStateCloseDeadlock tests that we do not enter deadlock when the // message handler is blocked or inactive. func TestInvalidStateCloseDeadlock(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) // Attempt to close the connection when the message handler is // blocked or inactive conn.Close() } // TestInvalidStateSendResponseDeadlock tests that we do not enter deadlock when the // message handler is blocked or inactive. func TestInvalidStateSendResponseDeadlock(t *testing.T) { // Attempt to send a response packet when the message handler is blocked or inactive msgCtx := &messageContext{ id: 0, done: make(chan struct{}), responses: make(chan *PacketResponse), } msgCtx.sendResponse(&PacketResponse{}, time.Millisecond) } // 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.6/control.go000066400000000000000000001240551450131332300144550ustar00rootroot00000000000000package ldap import ( "fmt" "strconv" ber "github.com/go-asn1-ber/asn1-ber" "github.com/google/uuid" ) 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" // ControlTypeSubtreeDelete - https://datatracker.ietf.org/doc/html/draft-armijo-ldap-treedelete-02 ControlTypeSubtreeDelete = "1.2.840.113556.1.4.805" // ControlTypeServerSideSorting - https://www.ietf.org/rfc/rfc2891.txt ControlTypeServerSideSorting = "1.2.840.113556.1.4.473" // ControlTypeServerSideSorting - https://www.ietf.org/rfc/rfc2891.txt ControlTypeServerSideSortingResult = "1.2.840.113556.1.4.474" // 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" // ControlTypeDirSync - Active Directory DirSync - https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx ControlTypeDirSync = "1.2.840.113556.1.4.841" // ControlTypeSyncRequest - https://www.ietf.org/rfc/rfc4533.txt ControlTypeSyncRequest = "1.3.6.1.4.1.4203.1.9.1.1" // ControlTypeSyncState - https://www.ietf.org/rfc/rfc4533.txt ControlTypeSyncState = "1.3.6.1.4.1.4203.1.9.1.2" // ControlTypeSyncDone - https://www.ietf.org/rfc/rfc4533.txt ControlTypeSyncDone = "1.3.6.1.4.1.4203.1.9.1.3" // ControlTypeSyncInfo - https://www.ietf.org/rfc/rfc4533.txt ControlTypeSyncInfo = "1.3.6.1.4.1.4203.1.9.1.4" ) // Flags for DirSync control const ( DirSyncIncrementalValues int64 = 2147483648 DirSyncPublicDataOnly int64 = 8192 DirSyncAncestorsFirstOrder int64 = 2048 DirSyncObjectSecurity int64 = 1 ) // ControlTypeMap maps controls to text descriptions var ControlTypeMap = map[string]string{ ControlTypePaging: "Paging", ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft", ControlTypeManageDsaIT: "Manage DSA IT", ControlTypeSubtreeDelete: "Subtree Delete Control", ControlTypeMicrosoftNotification: "Change Notification - Microsoft", ControlTypeMicrosoftShowDeleted: "Show Deleted Objects - Microsoft", ControlTypeMicrosoftServerLinkTTL: "Return TTL-DNs for link values with associated expiry times - Microsoft", ControlTypeServerSideSorting: "Server Side Sorting Request - LDAP Control Extension for Server Side Sorting of Search Results (RFC2891)", ControlTypeServerSideSortingResult: "Server Side Sorting Results - LDAP Control Extension for Server Side Sorting of Search Results (RFC2891)", ControlTypeDirSync: "DirSync", ControlTypeSyncRequest: "Sync Request", ControlTypeSyncState: "Sync State", ControlTypeSyncDone: "Sync Done", ControlTypeSyncInfo: "Sync Info", } // 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] + ")" if packet.Children[0].Value != nil { ControlType = packet.Children[0].Value.(string) } else if packet.Children[0].Data != nil { ControlType = packet.Children[0].Data.String() } else { return nil, fmt.Errorf("not found where to get the control type") } // 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 case ControlTypeSubtreeDelete: return NewControlSubtreeDelete(), nil case ControlTypeServerSideSorting: return NewControlServerSideSorting(value) case ControlTypeServerSideSortingResult: return NewControlServerSideSortingResult(value) case ControlTypeDirSync: value.Description += " (DirSync)" return NewResponseControlDirSync(value) case ControlTypeSyncState: value.Description += " (Sync State)" valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) if err != nil { return nil, fmt.Errorf("failed to decode data bytes: %s", err) } return NewControlSyncState(valueChildren) case ControlTypeSyncDone: value.Description += " (Sync Done)" valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) if err != nil { return nil, fmt.Errorf("failed to decode data bytes: %s", err) } return NewControlSyncDone(valueChildren) case ControlTypeSyncInfo: value.Description += " (Sync Info)" valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) if err != nil { return nil, fmt.Errorf("failed to decode data bytes: %s", err) } return NewControlSyncInfo(valueChildren) 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, } } // ControlSubtreeDelete implements the subtree delete control described in // https://datatracker.ietf.org/doc/html/draft-armijo-ldap-treedelete-02 type ControlSubtreeDelete struct{} // GetControlType returns the OID func (c *ControlSubtreeDelete) GetControlType() string { return ControlTypeSubtreeDelete } // NewControlSubtreeDelete returns a ControlSubtreeDelete control. func NewControlSubtreeDelete() *ControlSubtreeDelete { return &ControlSubtreeDelete{} } // Encode returns the ber packet representation func (c *ControlSubtreeDelete) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeSubtreeDelete, "Control Type ("+ControlTypeMap[ControlTypeSubtreeDelete]+")")) return packet } func (c *ControlSubtreeDelete) String() string { return fmt.Sprintf( "Control Type: %s (%q)", ControlTypeMap[ControlTypeSubtreeDelete], ControlTypeSubtreeDelete) } 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 } // ControlDirSync implements the control described in https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx type ControlDirSync struct { Criticality bool Flags int64 MaxAttrCount int64 Cookie []byte } // @deprecated Use NewRequestControlDirSync instead func NewControlDirSync(flags int64, maxAttrCount int64, cookie []byte) *ControlDirSync { return NewRequestControlDirSync(flags, maxAttrCount, cookie) } // NewRequestControlDirSync returns a dir sync control func NewRequestControlDirSync( flags int64, maxAttrCount int64, cookie []byte, ) *ControlDirSync { return &ControlDirSync{ Criticality: true, Flags: flags, MaxAttrCount: maxAttrCount, Cookie: cookie, } } // NewResponseControlDirSync returns a dir sync control func NewResponseControlDirSync(value *ber.Packet) (*ControlDirSync, error) { 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) } child := value.Children[0] if len(child.Children) != 3 { // also on initial creation, Cookie is an empty string return nil, fmt.Errorf("invalid number of children in dirSync control") } child.Description = "DirSync Control Value" child.Children[0].Description = "Flags" child.Children[1].Description = "MaxAttrCount" child.Children[2].Description = "Cookie" cookie := child.Children[2].Data.Bytes() child.Children[2].Value = cookie return &ControlDirSync{ Criticality: true, Flags: child.Children[0].Value.(int64), MaxAttrCount: child.Children[1].Value.(int64), Cookie: cookie, }, nil } // GetControlType returns the OID func (c *ControlDirSync) GetControlType() string { return ControlTypeDirSync } // String returns a human-readable description func (c *ControlDirSync) String() string { return fmt.Sprintf( "ControlType: %s (%q) Criticality: %t ControlValue: Flags: %d MaxAttrCount: %d", ControlTypeMap[ControlTypeDirSync], ControlTypeDirSync, c.Criticality, c.Flags, c.MaxAttrCount, ) } // Encode returns the ber packet representation func (c *ControlDirSync) Encode() *ber.Packet { cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Cookie") if len(c.Cookie) != 0 { cookie.Value = c.Cookie cookie.Data.Write(c.Cookie) } packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeDirSync, "Control Type ("+ControlTypeMap[ControlTypeDirSync]+")")) packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) // must be true always val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (DirSync)") seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "DirSync Control Value") seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.Flags), "Flags")) seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.MaxAttrCount), "MaxAttrCount")) seq.AppendChild(cookie) val.AppendChild(seq) packet.AppendChild(val) return packet } // SetCookie stores the given cookie in the dirSync control func (c *ControlDirSync) SetCookie(cookie []byte) { c.Cookie = cookie } // ControlServerSideSorting type SortKey struct { Reverse bool AttributeType string MatchingRule string } type ControlServerSideSorting struct { SortKeys []*SortKey } func (c *ControlServerSideSorting) GetControlType() string { return ControlTypeServerSideSorting } func NewControlServerSideSorting(value *ber.Packet) (*ControlServerSideSorting, error) { sortKeys := []*SortKey{} val := value.Children[1].Children if len(val) != 1 { return nil, fmt.Errorf("no sequence value in packet") } sequences := val[0].Children for i, sequence := range sequences { sortKey := &SortKey{} if len(sequence.Children) < 2 { return nil, fmt.Errorf("attributeType or matchingRule is missing from sequence %d", i) } sortKey.AttributeType = sequence.Children[0].Value.(string) sortKey.MatchingRule = sequence.Children[1].Value.(string) if len(sequence.Children) == 3 { sortKey.Reverse = sequence.Children[2].Value.(bool) } sortKeys = append(sortKeys, sortKey) } return &ControlServerSideSorting{SortKeys: sortKeys}, nil } func NewControlServerSideSortingWithSortKeys(sortKeys []*SortKey) *ControlServerSideSorting { return &ControlServerSideSorting{SortKeys: sortKeys} } func (c *ControlServerSideSorting) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") control := ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.GetControlType(), "Control Type") value := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value") seqs := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "SortKeyList") for _, f := range c.SortKeys { seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "") seq.AppendChild( ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, f.AttributeType, "attributeType"), ) seq.AppendChild( ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, f.MatchingRule, "orderingRule"), ) if f.Reverse { seq.AppendChild( ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, 1, f.Reverse, "reverseOrder"), ) } seqs.AppendChild(seq) } value.AppendChild(seqs) packet.AppendChild(control) packet.AppendChild(value) return packet } func (c *ControlServerSideSorting) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality:%t %+v", "Server Side Sorting", c.GetControlType(), false, c.SortKeys, ) } // ControlServerSideSortingResponse const ( ControlServerSideSortingCodeSuccess ControlServerSideSortingCode = 0 ControlServerSideSortingCodeOperationsError ControlServerSideSortingCode = 1 ControlServerSideSortingCodeTimeLimitExceeded ControlServerSideSortingCode = 2 ControlServerSideSortingCodeStrongAuthRequired ControlServerSideSortingCode = 8 ControlServerSideSortingCodeAdminLimitExceeded ControlServerSideSortingCode = 11 ControlServerSideSortingCodeNoSuchAttribute ControlServerSideSortingCode = 16 ControlServerSideSortingCodeInappropriateMatching ControlServerSideSortingCode = 18 ControlServerSideSortingCodeInsufficientAccessRights ControlServerSideSortingCode = 50 ControlServerSideSortingCodeBusy ControlServerSideSortingCode = 51 ControlServerSideSortingCodeUnwillingToPerform ControlServerSideSortingCode = 53 ControlServerSideSortingCodeOther ControlServerSideSortingCode = 80 ) var ControlServerSideSortingCodes = []ControlServerSideSortingCode{ ControlServerSideSortingCodeSuccess, ControlServerSideSortingCodeOperationsError, ControlServerSideSortingCodeTimeLimitExceeded, ControlServerSideSortingCodeStrongAuthRequired, ControlServerSideSortingCodeAdminLimitExceeded, ControlServerSideSortingCodeNoSuchAttribute, ControlServerSideSortingCodeInappropriateMatching, ControlServerSideSortingCodeInsufficientAccessRights, ControlServerSideSortingCodeBusy, ControlServerSideSortingCodeUnwillingToPerform, ControlServerSideSortingCodeOther, } type ControlServerSideSortingCode int64 // Valid test the code contained in the control against the ControlServerSideSortingCodes slice and return an error if the code is unknown. func (c ControlServerSideSortingCode) Valid() error { for _, validRet := range ControlServerSideSortingCodes { if c == validRet { return nil } } return fmt.Errorf("unknown return code : %d", c) } func NewControlServerSideSortingResult(pkt *ber.Packet) (*ControlServerSideSortingResult, error) { control := &ControlServerSideSortingResult{} if pkt == nil || len(pkt.Children) == 0 { return nil, fmt.Errorf("bad packet") } codeInt, err := ber.ParseInt64(pkt.Children[0].Data.Bytes()) if err != nil { return nil, err } code := ControlServerSideSortingCode(codeInt) if err := code.Valid(); err != nil { return nil, err } return control, nil } type ControlServerSideSortingResult struct { Criticality bool Result ControlServerSideSortingCode // Not populated for now. I can't get openldap to send me this value, so I think this is specific to other directory server // AttributeType string } func (control *ControlServerSideSortingResult) GetControlType() string { return ControlTypeServerSideSortingResult } func (c *ControlServerSideSortingResult) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "SortResult sequence") sortResult := ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, int64(c.Result), "SortResult") packet.AppendChild(sortResult) return packet } func (c *ControlServerSideSortingResult) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality:%t ResultCode:%+v", "Server Side Sorting Result", c.GetControlType(), c.Criticality, c.Result, ) } // Mode for ControlTypeSyncRequest type ControlSyncRequestMode int64 const ( SyncRequestModeRefreshOnly ControlSyncRequestMode = 1 SyncRequestModeRefreshAndPersist ControlSyncRequestMode = 3 ) // ControlSyncRequest implements the Sync Request Control described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncRequest struct { Criticality bool Mode ControlSyncRequestMode Cookie []byte ReloadHint bool } func NewControlSyncRequest( mode ControlSyncRequestMode, cookie []byte, reloadHint bool, ) *ControlSyncRequest { return &ControlSyncRequest{ Criticality: true, Mode: mode, Cookie: cookie, ReloadHint: reloadHint, } } // GetControlType returns the OID func (c *ControlSyncRequest) GetControlType() string { return ControlTypeSyncRequest } // Encode encodes the control func (c *ControlSyncRequest) Encode() *ber.Packet { _mode := int64(c.Mode) mode := ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, _mode, "Mode") var cookie *ber.Packet if len(c.Cookie) > 0 { cookie = ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie") cookie.Value = c.Cookie cookie.Data.Write(c.Cookie) } reloadHint := ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.ReloadHint, "Reload Hint") packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeSyncRequest, "Control Type ("+ControlTypeMap[ControlTypeSyncRequest]+")")) packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (Sync Request)") seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Sync Request Value") seq.AppendChild(mode) if cookie != nil { seq.AppendChild(cookie) } seq.AppendChild(reloadHint) val.AppendChild(seq) packet.AppendChild(val) return packet } // String returns a human-readable description func (c *ControlSyncRequest) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t Mode: %d Cookie: %s ReloadHint: %t", ControlTypeMap[ControlTypeSyncRequest], ControlTypeSyncRequest, c.Criticality, c.Mode, string(c.Cookie), c.ReloadHint, ) } // State for ControlSyncState type ControlSyncStateState int64 const ( SyncStatePresent ControlSyncStateState = 0 SyncStateAdd ControlSyncStateState = 1 SyncStateModify ControlSyncStateState = 2 SyncStateDelete ControlSyncStateState = 3 ) // ControlSyncState implements the Sync State Control described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncState struct { Criticality bool State ControlSyncStateState EntryUUID uuid.UUID Cookie []byte } func NewControlSyncState(pkt *ber.Packet) (*ControlSyncState, error) { var ( state ControlSyncStateState entryUUID uuid.UUID cookie []byte err error ) switch len(pkt.Children) { case 0, 1: return nil, fmt.Errorf("at least two children are required: %d", len(pkt.Children)) case 2: state = ControlSyncStateState(pkt.Children[0].Value.(int64)) entryUUID, err = uuid.FromBytes(pkt.Children[1].ByteValue) if err != nil { return nil, fmt.Errorf("failed to decode uuid: %w", err) } case 3: state = ControlSyncStateState(pkt.Children[0].Value.(int64)) entryUUID, err = uuid.FromBytes(pkt.Children[1].ByteValue) if err != nil { return nil, fmt.Errorf("failed to decode uuid: %w", err) } cookie = pkt.Children[2].ByteValue } return &ControlSyncState{ Criticality: false, State: state, EntryUUID: entryUUID, Cookie: cookie, }, nil } // GetControlType returns the OID func (c *ControlSyncState) GetControlType() string { return ControlTypeSyncState } // Encode encodes the control func (c *ControlSyncState) Encode() *ber.Packet { return nil } // String returns a human-readable description func (c *ControlSyncState) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t State: %d EntryUUID: %s Cookie: %s", ControlTypeMap[ControlTypeSyncState], ControlTypeSyncState, c.Criticality, c.State, c.EntryUUID.String(), string(c.Cookie), ) } // ControlSyncDone implements the Sync Done Control described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncDone struct { Criticality bool Cookie []byte RefreshDeletes bool } func NewControlSyncDone(pkt *ber.Packet) (*ControlSyncDone, error) { var ( cookie []byte refreshDeletes bool ) switch len(pkt.Children) { case 0: // have nothing to do case 1: cookie = pkt.Children[0].ByteValue case 2: cookie = pkt.Children[0].ByteValue refreshDeletes = pkt.Children[1].Value.(bool) } return &ControlSyncDone{ Criticality: false, Cookie: cookie, RefreshDeletes: refreshDeletes, }, nil } // GetControlType returns the OID func (c *ControlSyncDone) GetControlType() string { return ControlTypeSyncDone } // Encode encodes the control func (c *ControlSyncDone) Encode() *ber.Packet { return nil } // String returns a human-readable description func (c *ControlSyncDone) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t Cookie: %s RefreshDeletes: %t", ControlTypeMap[ControlTypeSyncDone], ControlTypeSyncDone, c.Criticality, string(c.Cookie), c.RefreshDeletes, ) } // Tag For ControlSyncInfo type ControlSyncInfoValue uint64 const ( SyncInfoNewcookie ControlSyncInfoValue = 0 SyncInfoRefreshDelete ControlSyncInfoValue = 1 SyncInfoRefreshPresent ControlSyncInfoValue = 2 SyncInfoSyncIdSet ControlSyncInfoValue = 3 ) // ControlSyncInfoNewCookie implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncInfoNewCookie struct { Cookie []byte } // String returns a human-readable description func (c *ControlSyncInfoNewCookie) String() string { return fmt.Sprintf( "NewCookie[Cookie: %s]", string(c.Cookie), ) } // ControlSyncInfoRefreshDelete implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncInfoRefreshDelete struct { Cookie []byte RefreshDone bool } // String returns a human-readable description func (c *ControlSyncInfoRefreshDelete) String() string { return fmt.Sprintf( "RefreshDelete[Cookie: %s RefreshDone: %t]", string(c.Cookie), c.RefreshDone, ) } // ControlSyncInfoRefreshPresent implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncInfoRefreshPresent struct { Cookie []byte RefreshDone bool } // String returns a human-readable description func (c *ControlSyncInfoRefreshPresent) String() string { return fmt.Sprintf( "RefreshPresent[Cookie: %s RefreshDone: %t]", string(c.Cookie), c.RefreshDone, ) } // ControlSyncInfoSyncIdSet implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncInfoSyncIdSet struct { Cookie []byte RefreshDeletes bool SyncUUIDs []uuid.UUID } // String returns a human-readable description func (c *ControlSyncInfoSyncIdSet) String() string { return fmt.Sprintf( "SyncIdSet[Cookie: %s RefreshDeletes: %t SyncUUIDs: %v]", string(c.Cookie), c.RefreshDeletes, c.SyncUUIDs, ) } // ControlSyncInfo implements the Sync Info Control described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncInfo struct { Criticality bool Value ControlSyncInfoValue NewCookie *ControlSyncInfoNewCookie RefreshDelete *ControlSyncInfoRefreshDelete RefreshPresent *ControlSyncInfoRefreshPresent SyncIdSet *ControlSyncInfoSyncIdSet } func NewControlSyncInfo(pkt *ber.Packet) (*ControlSyncInfo, error) { var ( cookie []byte refreshDone = true refreshDeletes bool syncUUIDs []uuid.UUID ) c := &ControlSyncInfo{Criticality: false} switch ControlSyncInfoValue(pkt.Identifier.Tag) { case SyncInfoNewcookie: c.Value = SyncInfoNewcookie c.NewCookie = &ControlSyncInfoNewCookie{ Cookie: pkt.ByteValue, } case SyncInfoRefreshDelete: c.Value = SyncInfoRefreshDelete switch len(pkt.Children) { case 0: // have nothing to do case 1: cookie = pkt.Children[0].ByteValue case 2: cookie = pkt.Children[0].ByteValue refreshDone = pkt.Children[1].Value.(bool) } c.RefreshDelete = &ControlSyncInfoRefreshDelete{ Cookie: cookie, RefreshDone: refreshDone, } case SyncInfoRefreshPresent: c.Value = SyncInfoRefreshPresent switch len(pkt.Children) { case 0: // have nothing to do case 1: cookie = pkt.Children[0].ByteValue case 2: cookie = pkt.Children[0].ByteValue refreshDone = pkt.Children[1].Value.(bool) } c.RefreshPresent = &ControlSyncInfoRefreshPresent{ Cookie: cookie, RefreshDone: refreshDone, } case SyncInfoSyncIdSet: c.Value = SyncInfoSyncIdSet switch len(pkt.Children) { case 0: // have nothing to do case 1: cookie = pkt.Children[0].ByteValue case 2: cookie = pkt.Children[0].ByteValue refreshDeletes = pkt.Children[1].Value.(bool) case 3: cookie = pkt.Children[0].ByteValue refreshDeletes = pkt.Children[1].Value.(bool) syncUUIDs = make([]uuid.UUID, 0, len(pkt.Children[2].Children)) for _, child := range pkt.Children[2].Children { u, err := uuid.FromBytes(child.ByteValue) if err != nil { return nil, fmt.Errorf("failed to decode uuid: %w", err) } syncUUIDs = append(syncUUIDs, u) } } c.SyncIdSet = &ControlSyncInfoSyncIdSet{ Cookie: cookie, RefreshDeletes: refreshDeletes, SyncUUIDs: syncUUIDs, } default: return nil, fmt.Errorf("unknown sync info value: %d", pkt.Identifier.Tag) } return c, nil } // GetControlType returns the OID func (c *ControlSyncInfo) GetControlType() string { return ControlTypeSyncInfo } // Encode encodes the control func (c *ControlSyncInfo) Encode() *ber.Packet { return nil } // String returns a human-readable description func (c *ControlSyncInfo) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t Value: %d %s %s %s %s", ControlTypeMap[ControlTypeSyncInfo], ControlTypeSyncInfo, c.Criticality, c.Value, c.NewCookie, c.RefreshDelete, c.RefreshPresent, c.SyncIdSet, ) } ldap-3.4.6/control_test.go000066400000000000000000000273661450131332300155230ustar00rootroot00000000000000package 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 TestControlSubtreeDelete(t *testing.T) { runControlTest(t, NewControlSubtreeDelete()) } 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 TestControlDirSync(t *testing.T) { runControlTest(t, NewRequestControlDirSync(DirSyncObjectSecurity, 1000, nil)) runControlTest(t, NewRequestControlDirSync(DirSyncObjectSecurity, 1000, []byte("I'm a cookie!"))) } 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 TestDescribeControlSubtreeDelete(t *testing.T) { runAddControlDescriptions(t, NewControlSubtreeDelete(), "Control Type (Subtree Delete Control)") } 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 TestDescribeControlDirSync(t *testing.T) { runAddControlDescriptions(t, NewRequestControlDirSync(DirSyncObjectSecurity, 1000, nil), "Control Type (DirSync)", "Criticality", "Control Value") } 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) } }) } } func TestControlServerSideSortingDecoding(t *testing.T) { control := NewControlServerSideSortingWithSortKeys([]*SortKey{{ MatchingRule: "foo", AttributeType: "foobar", Reverse: true, }, { MatchingRule: "foo", AttributeType: "foobar", Reverse: false, }, { MatchingRule: "", AttributeType: "", Reverse: false, }, { MatchingRule: "totoRule", AttributeType: "", Reverse: false, }, { MatchingRule: "", AttributeType: "totoType", Reverse: false, }}) controlDecoded, err := NewControlServerSideSorting(control.Encode()) if err != nil { t.Fatal(err) } if control.GetControlType() != controlDecoded.GetControlType() { t.Fatalf("control type mismatch: control:%s - decoded:%s", control.GetControlType(), controlDecoded.GetControlType()) } if len(control.SortKeys) != len(controlDecoded.SortKeys) { t.Fatalf("sort keys length mismatch (control: %d - decoded: %d)", len(control.SortKeys), len(controlDecoded.SortKeys)) } for i, sk := range control.SortKeys { dsk := controlDecoded.SortKeys[i] if sk.AttributeType != dsk.AttributeType { t.Fatalf("attribute type mismatch for sortkey %d", i) } if sk.MatchingRule != dsk.MatchingRule { t.Fatalf("matching rule mismatch for sortkey %d", i) } if sk.Reverse != dsk.Reverse { t.Fatalf("reverse mismtach for sortkey %d", i) } } } ldap-3.4.6/debug.go000066400000000000000000000010551450131332300140550ustar00rootroot00000000000000package ldap import ( 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 { logger.Printf(format, args...) } } // PrintPacket dumps a packet. func (debug debugging) PrintPacket(packet *ber.Packet) { if debug { ber.WritePacket(logger.Writer(), packet) } } ldap-3.4.6/del.go000066400000000000000000000024451450131332300135370ustar00rootroot00000000000000package ldap import ( "fmt" 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 { return fmt.Errorf("ldap: unexpected response: %d", packet.Children[1].Tag) } return nil } ldap-3.4.6/dn.go000066400000000000000000000242101450131332300133660ustar00rootroot00000000000000package ldap import ( "bytes" enchex "encoding/hex" "errors" "fmt" "sort" "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 } // String returns a normalized string representation of this attribute type and // value pair which is the a lowercased join of the Type and Value with a "=". func (a *AttributeTypeAndValue) String() string { return strings.ToLower(a.Type) + "=" + a.encodeValue() } func (a *AttributeTypeAndValue) encodeValue() string { // Normalize the value first. // value := strings.ToLower(a.Value) value := a.Value encodedBuf := bytes.Buffer{} escapeChar := func(c byte) { encodedBuf.WriteByte('\\') encodedBuf.WriteByte(c) } escapeHex := func(c byte) { encodedBuf.WriteByte('\\') encodedBuf.WriteString(enchex.EncodeToString([]byte{c})) } for i := 0; i < len(value); i++ { char := value[i] if i == 0 && char == ' ' || char == '#' { // Special case leading space or number sign. escapeChar(char) continue } if i == len(value)-1 && char == ' ' { // Special case trailing space. escapeChar(char) continue } switch char { case '"', '+', ',', ';', '<', '>', '\\': // Each of these special characters must be escaped. escapeChar(char) continue } if char < ' ' || char > '~' { // All special character escapes are handled first // above. All bytes less than ASCII SPACE and all bytes // greater than ASCII TILDE must be hex-escaped. escapeHex(char) continue } // Any other character does not require escaping. encodedBuf.WriteByte(char) } return encodedBuf.String() } // RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514 type RelativeDN struct { Attributes []*AttributeTypeAndValue } // String returns a normalized string representation of this relative DN which // is the a join of all attributes (sorted in increasing order) with a "+". func (r *RelativeDN) String() string { attrs := make([]string, len(r.Attributes)) for i := range r.Attributes { attrs[i] = r.Attributes[i].String() } sort.Strings(attrs) return strings.Join(attrs, "+") } // DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514 type DN struct { RDNs []*RelativeDN } // String returns a normalized string representation of this DN which is the // join of all relative DNs with a ",". func (d *DN) String() string { rdns := make([]string, len(d.RDNs)) for i := range d.RDNs { rdns[i] = d.RDNs[i].String() } return strings.Join(rdns, ",") } // 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 == "": 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 == '+' || 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 == ',' || 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 } // EqualFold 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 } // EqualFold 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.6/dn_test.go000066400000000000000000000200531450131332300144260ustar00rootroot00000000000000package 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 "}, }}, }}, `cn=john.doe;dc=example,dc=net`: {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"cn", "john.doe"}}}, {[]*AttributeTypeAndValue{{"dc", "example"}}}, {[]*AttributeTypeAndValue{{"dc", "net"}}}, }}, // Escaped `;` should not be treated as RDN `cn=john.doe\;weird name,dc=example,dc=net`: {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"cn", "john.doe;weird name"}}}, {[]*AttributeTypeAndValue{{"dc", "example"}}}, {[]*AttributeTypeAndValue{{"dc", "net"}}}, }}, `cn=ZXhhbXBsZVRleHQ=,dc=dummy,dc=com`: {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"cn", "ZXhhbXBsZVRleHQ="}}}, {[]*AttributeTypeAndValue{{"dc", "dummy"}}}, {[]*AttributeTypeAndValue{{"dc", "com"}}}, }}, } 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\\20Doe, 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, }, // Test parsing of `;` for separating RDNs {"cn=john;dc=example,dc=com", "cn=john,dc=example,dc=com", true}, // missing values matter } 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 %q and %q expected %v, got %v", i, a, b, expected, actual) continue } if expected, actual := tc.Equal, b.Equal(a); expected != actual { t.Errorf("%d: when comparing %q and %q expected %v, got %v", i, a, b, expected, actual) continue } if expected, actual := a.Equal(b), a.String() == b.String(); expected != actual { t.Errorf("%d: when asserting string comparison of %q and %q expected equal %v, got %v", i, a, 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.6/doc.go000066400000000000000000000001061450131332300135300ustar00rootroot00000000000000/* Package ldap provides basic LDAP v3 functionality. */ package ldap ldap-3.4.6/error.go000066400000000000000000000313201450131332300141160ustar00rootroot00000000000000package 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()) } func (e *Error) Unwrap() error { return e.Err } // 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 { if ber.Type(response.Children[0].Tag) == ber.Type(ber.TagInteger) || ber.Type(response.Children[0].Tag) == ber.Type(ber.TagEnumerated) { resultCode := uint16(response.Children[0].Value.(int64)) if resultCode == 0 { // No error return nil } if ber.Type(response.Children[1].Tag) == ber.Type(ber.TagOctetString) && ber.Type(response.Children[2].Tag) == ber.Type(ber.TagOctetString) { 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.6/error_test.go000066400000000000000000000147001450131332300151600ustar00rootroot00000000000000package ldap import ( "errors" "io" "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) } } // TestGetLDAPErrorInvalidResponse tests that responses with an unexpected ordering or combination of children // don't cause a panic. func TestGetLDAPErrorInvalidResponse(t *testing.T) { bindResponse := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindResponse, nil, "Bind Response") bindResponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "dc=example,dc=org", "matchedDN")) bindResponse.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(LDAPResultInvalidCredentials), "resultCode")) bindResponse.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(LDAPResultInvalidCredentials), "resultCode")) 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 != ErrorNetwork { t.Errorf("Got incorrect error code in LDAP error; got %v, expected %v", ldapError.ResultCode, ErrorNetwork) } } func TestErrorIs(t *testing.T) { err := NewError(ErrorNetwork, io.EOF) if !errors.Is(err, io.EOF) { t.Errorf("Expected an io.EOF error: %v", err) } } func TestErrorAs(t *testing.T) { var netErr net.InvalidAddrError = "invalid addr" err := NewError(ErrorNetwork, netErr) var target net.InvalidAddrError ok := errors.As(err, &target) if !ok { t.Error("Expected an InvalidAddrError") } } // 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.6/examples_moddn_test.go000066400000000000000000000043331450131332300170270ustar00rootroot00000000000000package 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.6/examples_test.go000066400000000000000000000351271450131332300156530ustar00rootroot00000000000000package ldap import ( "context" "crypto/tls" "crypto/x509" "fmt" "io/ioutil" "log" "time" ) // 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 search asynchronously func ExampleConn_SearchAsync() { l, err := DialURL(fmt.Sprintf("%s:%d", "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, ) ctx, cancel := context.WithCancel(context.Background()) defer cancel() r := l.SearchAsync(ctx, searchRequest, 64) for r.Next() { entry := r.Entry() fmt.Printf("%s has DN %s\n", entry.GetAttributeValue("cn"), entry.DN) } if err := r.Err(); err != nil { log.Fatal(err) } } // This example demonstrates how to do syncrepl (persistent search) func ExampleConn_Syncrepl() { l, err := DialURL(fmt.Sprintf("%s:%d", "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, ) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mode := SyncRequestModeRefreshAndPersist var cookie []byte = nil r := l.Syncrepl(ctx, searchRequest, 64, mode, cookie, false) for r.Next() { entry := r.Entry() if entry != nil { fmt.Printf("%s has DN %s\n", entry.GetAttributeValue("cn"), entry.DN) } controls := r.Controls() if len(controls) != 0 { fmt.Printf("%s", controls) } } if err := r.Err(); err != nil { log.Fatal(err) } } // 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 DirSync to manually execute a // DirSync search request func ExampleConn_DirSync() { conn, err := Dial("tcp", "ad.example.org:389") if err != nil { log.Fatalf("Failed to connect: %s\n", err) } defer conn.Close() _, err = conn.SimpleBind(&SimpleBindRequest{ Username: "cn=Some User,ou=people,dc=example,dc=org", Password: "MySecretPass", }) if err != nil { log.Fatalf("failed to bind: %s", err) } req := &SearchRequest{ BaseDN: `DC=example,DC=org`, Filter: `(&(objectClass=person)(!(objectClass=computer)))`, Attributes: []string{"*"}, Scope: ScopeWholeSubtree, } // This is the initial sync with all entries matching the filter doMore := true var cookie []byte for doMore { res, err := conn.DirSync(req, DirSyncObjectSecurity, 1000, cookie) if err != nil { log.Fatalf("failed to search: %s", err) } for _, entry := range res.Entries { entry.Print() } ctrl := FindControl(res.Controls, ControlTypeDirSync) if ctrl == nil || ctrl.(*ControlDirSync).Flags == 0 { doMore = false } cookie = ctrl.(*ControlDirSync).Cookie } // We're done with the initial sync. Now pull every 15 seconds for the // updated entries - note that you get just the changes, not a full entry. for { res, err := conn.DirSync(req, DirSyncObjectSecurity, 1000, cookie) if err != nil { log.Fatalf("failed to search: %s", err) } for _, entry := range res.Entries { entry.Print() } time.Sleep(15 * time.Second) } } // This example demonstrates how to use DirSync search asynchronously func ExampleConn_DirSyncAsync() { conn, err := Dial("tcp", "ad.example.org:389") if err != nil { log.Fatalf("Failed to connect: %s\n", err) } defer conn.Close() _, err = conn.SimpleBind(&SimpleBindRequest{ Username: "cn=Some User,ou=people,dc=example,dc=org", Password: "MySecretPass", }) if err != nil { log.Fatalf("failed to bind: %s", err) } req := &SearchRequest{ BaseDN: `DC=example,DC=org`, Filter: `(&(objectClass=person)(!(objectClass=computer)))`, Attributes: []string{"*"}, Scope: ScopeWholeSubtree, } ctx, cancel := context.WithCancel(context.Background()) defer cancel() var cookie []byte = nil r := conn.DirSyncAsync(ctx, req, 64, DirSyncObjectSecurity, 1000, cookie) for r.Next() { entry := r.Entry() if entry != nil { entry.Print() } controls := r.Controls() if len(controls) != 0 { fmt.Printf("%s", controls) } } if err := r.Err(); err != nil { log.Fatal(err) } } // This example demonstrates how to use EXTERNAL SASL with TLS client certificates. func ExampleConn_ExternalBind() { ldapCert := "/path/to/cert.pem" ldapKey := "/path/to/key.pem" 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.6/examples_windows_test.go000066400000000000000000000014641450131332300174220ustar00rootroot00000000000000//go:build windows // +build windows package ldap import ( "log" "github.com/go-ldap/ldap/gssapi" ) // This example demonstrates passwordless bind using the current process' user // credentials on Windows (SASL GSSAPI mechanism bind with SSPI client). func ExampleConn_SSPIClient_GSSAPIBind() { // Windows only: Create a GSSAPIClient using Windows built-in SSPI lib // (secur32.dll). // This will use the credentials of the current process' user. sspiClient, err := gssapi.NewSSPIClient() if err != nil { log.Fatal(err) } defer sspiClient.Close() l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() // Bind using supplied GSSAPIClient implementation err = l.GSSAPIBind(sspiClient, "ldap/ldap.example.com", "") if err != nil { log.Fatal(err) } } ldap-3.4.6/filter.go000066400000000000000000000374201450131332300142610ustar00rootroot00000000000000package 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.Contains(condition.Bytes(), _SymbolAny): 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.6/filter_test.go000066400000000000000000000170401450131332300153140ustar00rootroot00000000000000package 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.6/fuzz_test.go000066400000000000000000000021351450131332300150240ustar00rootroot00000000000000//go:build go1.18 // +build go1.18 package ldap import ( ber "github.com/go-asn1-ber/asn1-ber" "testing" ) func FuzzParseDN(f *testing.F) { // See https://github.com/go-asn1-ber/asn1-ber/blob/04301b4b1c5ff66221f8f8a394f814a9917d678a/fuzz_test.go#L33-L37 // for why this limitation is necessary ber.MaxPacketLengthBytes = 65536 f.Add("*") f.Add("cn=Jim\\0Test") f.Add("cn=Jim\\0") f.Add("DC=example,=net") f.Add("o=a+o=B") f.Fuzz(func(t *testing.T, input_data string) { _, _ = ParseDN(input_data) }) } func FuzzDecodeEscapedSymbols(f *testing.F) { f.Add([]byte("a\u0100\x80")) f.Add([]byte(`start\d`)) f.Add([]byte(`\`)) f.Add([]byte(`start\--end`)) f.Add([]byte(`start\d0\hh`)) f.Fuzz(func(t *testing.T, input_data []byte) { _, _ = decodeEscapedSymbols(input_data) }) } func FuzzEscapeDN(f *testing.F) { f.Add("test,user") f.Add("#test#user#") f.Add("\\test\\user\\") f.Add(" test user ") f.Add("\u0000te\x00st\x00user" + string(rune(0))) f.Add("test\"+,;<>\\-_user") f.Add("test\u0391user ") f.Fuzz(func(t *testing.T, input_data string) { _ = EscapeDN(input_data) }) } ldap-3.4.6/go.mod000066400000000000000000000005121450131332300135430ustar00rootroot00000000000000module github.com/go-ldap/ldap go 1.14 require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 github.com/go-asn1-ber/asn1-ber v1.5.5 github.com/google/uuid v1.3.1 github.com/stretchr/testify v1.8.0 golang.org/x/crypto v0.13.0 // indirect ) ldap-3.4.6/go.sum000066400000000000000000000132271450131332300135770ustar00rootroot00000000000000github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ldap-3.4.6/gssapi/000077500000000000000000000000001450131332300137255ustar00rootroot00000000000000ldap-3.4.6/gssapi/sspi.go000066400000000000000000000145361450131332300152430ustar00rootroot00000000000000//go:build windows // +build windows package gssapi import ( "bytes" "encoding/binary" "fmt" "github.com/alexbrainman/sspi" "github.com/alexbrainman/sspi/kerberos" ) // SSPIClient implements ldap.GSSAPIClient interface. // Depends on secur32.dll. type SSPIClient struct { creds *sspi.Credentials ctx *kerberos.ClientContext } // NewSSPIClient returns a client with credentials of the current user. func NewSSPIClient() (*SSPIClient, error) { creds, err := kerberos.AcquireCurrentUserCredentials() if err != nil { return nil, err } return NewSSPIClientWithCredentials(creds), nil } // NewSSPIClientWithCredentials returns a client with the provided credentials. func NewSSPIClientWithCredentials(creds *sspi.Credentials) *SSPIClient { return &SSPIClient{ creds: creds, } } // NewSSPIClientWithUserCredentials returns a client using the provided user's // credentials. func NewSSPIClientWithUserCredentials(domain, username, password string) (*SSPIClient, error) { creds, err := kerberos.AcquireUserCredentials(domain, username, password) if err != nil { return nil, err } return &SSPIClient{ creds: creds, }, nil } // Close deletes any established secure context and closes the client. func (c *SSPIClient) Close() error { err1 := c.DeleteSecContext() err2 := c.creds.Release() if err1 != nil { return err1 } if err2 != nil { return err2 } return nil } // DeleteSecContext destroys any established secure context. func (c *SSPIClient) DeleteSecContext() error { return c.ctx.Release() } // InitSecContext initiates the establishment of a security context for // GSS-API between the client and server. // See RFC 4752 section 3.1. func (c *SSPIClient) InitSecContext(target string, token []byte) ([]byte, bool, error) { sspiFlags := uint32(sspi.ISC_REQ_INTEGRITY | sspi.ISC_REQ_CONFIDENTIALITY | sspi.ISC_REQ_MUTUAL_AUTH) switch token { case nil: ctx, completed, output, err := kerberos.NewClientContextWithFlags(c.creds, target, sspiFlags) if err != nil { return nil, false, err } c.ctx = ctx return output, !completed, nil default: completed, output, err := c.ctx.Update(token) if err != nil { return nil, false, err } if err := c.ctx.VerifyFlags(); err != nil { return nil, false, fmt.Errorf("error verifying flags: %v", err) } return output, !completed, nil } } // NegotiateSaslAuth performs the last step of the SASL handshake. // See RFC 4752 section 3.1. func (c *SSPIClient) NegotiateSaslAuth(token []byte, authzid string) ([]byte, error) { // Using SSPI rather than of GSSAPI, relevant documentation of differences here: // https://learn.microsoft.com/en-us/windows/win32/secauthn/sspi-kerberos-interoperability-with-gssapi // KERB_WRAP_NO_ENCRYPT (SECQOP_WRAP_NO_ENCRYPT) flag indicates Wrap and Unwrap // should only sign & verify (not encrypt & decrypt). const KERB_WRAP_NO_ENCRYPT = 0x80000001 // https://learn.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-decryptmessage flags, inputPayload, err := c.ctx.DecryptMessage(token, 0) if err != nil { return nil, fmt.Errorf("error decrypting message: %w", err) } if flags&KERB_WRAP_NO_ENCRYPT == 0 { // Encrypted message, this is unexpected. return nil, fmt.Errorf("message encrypted") } // `bytes` describes available security context: // "the first octet of resulting cleartext as a // bit-mask specifying the security layers supported by the server and // the second through fourth octets as the maximum size output_message // the server is able to receive (in network byte order). If the // resulting cleartext is not 4 octets long, the client fails the // negotiation. The client verifies that the server maximum buffer is 0 // if the server does not advertise support for any security layer." // From https://www.rfc-editor.org/rfc/rfc4752#section-3.1 if len(inputPayload) != 4 { return nil, fmt.Errorf("bad server token") } if inputPayload[0] == 0x0 && !bytes.Equal(inputPayload, []byte{0x0, 0x0, 0x0, 0x0}) { return nil, fmt.Errorf("bad server token") } // Security layers https://www.rfc-editor.org/rfc/rfc4422#section-3.7 // https://www.rfc-editor.org/rfc/rfc4752#section-3.3 // supportNoSecurity := input[0] & 0b00000001 // supportIntegrity := input[0] & 0b00000010 // supportPrivacy := input[0] & 0b00000100 selectedSec := 0 // Disabled var maxSecMsgSize uint32 if selectedSec != 0 { maxSecMsgSize, _, _, _, err = c.ctx.Sizes() if err != nil { return nil, fmt.Errorf("error getting security context max message size: %w", err) } } // https://learn.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-encryptmessage inputPayload, err = c.ctx.EncryptMessage(handshakePayload(byte(selectedSec), maxSecMsgSize, []byte(authzid)), KERB_WRAP_NO_ENCRYPT, 0) if err != nil { return nil, fmt.Errorf("error encrypting message: %w", err) } return inputPayload, nil } func handshakePayload(secLayer byte, maxSize uint32, authzid []byte) []byte { // construct payload and send unencrypted: // "The client then constructs data, with the first octet containing the // bit-mask specifying the selected security layer, the second through // fourth octets containing in network byte order the maximum size // output_message the client is able to receive (which MUST be 0 if the // client does not support any security layer), and the remaining octets // containing the UTF-8 [UTF8] encoded authorization identity. // (Implementation note: The authorization identity is not terminated // with the zero-valued (%x00) octet (e.g., the UTF-8 encoding of the // NUL (U+0000) character)). The client passes the data to GSS_Wrap // with conf_flag set to FALSE and responds with the generated // output_message. The client can then consider the server // authenticated." // From https://www.rfc-editor.org/rfc/rfc4752#section-3.1 // Client picks security layer to use, 0 is disabled. var selectedSecurity byte = secLayer var truncatedSize uint32 // must be 0 if secLayer is 0 if selectedSecurity != 0 { // Only 3 bytes to describe the max size, set the maximum. truncatedSize = 0b00000000_11111111_11111111_11111111 if truncatedSize > maxSize { truncatedSize = maxSize } } payload := make([]byte, 4, 4+len(authzid)) binary.BigEndian.PutUint32(payload, truncatedSize) payload[0] = selectedSecurity // Overwrites most significant byte of `maxSize` payload = append(payload, []byte(authzid)...) return payload } ldap-3.4.6/ldap.go000066400000000000000000000304041450131332300137070ustar00rootroot00000000000000package ldap import ( "fmt" "io/ioutil" "log" "os" "strings" 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 ApplicationIntermediateResponse = 25 ) // 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", ApplicationIntermediateResponse: "Intermediate 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", } var logger = log.New(os.Stderr, "", log.LstdFlags) // Logger allows clients to override the default logger func Logger(l *log.Logger) { logger = l } // 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) } // EscapeDN escapes distinguished names as described in RFC4514. Characters in the // set `"+,;<>\` are escaped by prepending a backslash, which is also done for trailing // spaces or a leading `#`. Null bytes are replaced with `\00`. func EscapeDN(dn string) string { if dn == "" { return "" } builder := strings.Builder{} for i, r := range dn { // Escape leading and trailing spaces if (i == 0 || i == len(dn)-1) && r == ' ' { builder.WriteRune('\\') builder.WriteRune(r) continue } // Escape leading '#' if i == 0 && r == '#' { builder.WriteRune('\\') builder.WriteRune(r) continue } // Escape characters as defined in RFC4514 switch r { case '"', '+', ',', ';', '<', '>', '\\': builder.WriteRune('\\') builder.WriteRune(r) case '\x00': // Null byte may not be escaped by a leading backslash builder.WriteString("\\00") default: builder.WriteRune(r) } } return builder.String() } ldap-3.4.6/ldap_test.go000066400000000000000000000277501450131332300147600ustar00rootroot00000000000000package ldap import ( "context" "crypto/tls" "log" "testing" ber "github.com/go-asn1-ber/asn1-ber" ) const ( ldapServer = "ldap://ldap.itd.umich.edu:389" ldapsServer = "ldaps://ldap.itd.umich.edu:636" 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)}) _, 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) } }) } } func TestEscapeDN(t *testing.T) { tests := []struct { name string dn string want string }{ {name: "emptyString", dn: "", want: ""}, {name: "comma", dn: "test,user", want: "test\\,user"}, {name: "numberSign", dn: "#test#user#", want: "\\#test#user#"}, {name: "backslash", dn: "\\test\\user\\", want: "\\\\test\\\\user\\\\"}, {name: "whitespaces", dn: " test user ", want: "\\ test user \\ "}, {name: "nullByte", dn: "\u0000te\x00st\x00user" + string(rune(0)), want: "\\00te\\00st\\00user\\00"}, {name: "variousCharacters", dn: "test\"+,;<>\\-_user", want: "test\\\"\\+\\,\\;\\<\\>\\\\-_user"}, {name: "multiByteRunes", dn: "test\u0391user ", want: "test\u0391user\\ "}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := EscapeDN(tt.dn); got != tt.want { t.Errorf("EscapeDN(%s) = %s, expected %s", tt.dn, got, tt.want) } }) } } func TestSearchAsync(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[2], attributes, nil) srs := make([]*Entry, 0) ctx := context.Background() r := l.SearchAsync(ctx, searchRequest, 64) for r.Next() { srs = append(srs, r.Entry()) } if err := r.Err(); err != nil { log.Fatal(err) } t.Logf("TestSearcAsync: %s -> num of entries = %d", searchRequest.Filter, len(srs)) } func TestSearchAsyncAndCancel(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[2], attributes, nil) cancelNum := 10 srs := make([]*Entry, 0) ctx, cancel := context.WithCancel(context.Background()) defer cancel() r := l.SearchAsync(ctx, searchRequest, 0) for r.Next() { srs = append(srs, r.Entry()) if len(srs) == cancelNum { cancel() } } if err := r.Err(); err != nil { log.Fatal(err) } if len(srs) > cancelNum+3 { // the cancellation process is asynchronous, // so it might get some entries after calling cancel() t.Errorf("Got entries %d, expected < %d", len(srs), cancelNum+3) } t.Logf("TestSearchAsyncAndCancel: %s -> num of entries = %d", searchRequest.Filter, len(srs)) } ldap-3.4.6/moddn.go000066400000000000000000000060301450131332300140660ustar00rootroot00000000000000package ldap import ( "fmt" 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 { return fmt.Errorf("ldap: unexpected response: %d", packet.Children[1].Tag) } return nil } ldap-3.4.6/modify.go000066400000000000000000000125651450131332300142660ustar00rootroot00000000000000package ldap import ( "errors" "fmt" 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 { return fmt.Errorf("ldap: 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 // Referral is the returned referral Referral string } // 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: if err = GetLDAPError(packet); err != nil { result.Referral = getReferral(err, packet) return result, 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.6/passwdmodify.go000066400000000000000000000107141450131332300155020ustar00rootroot00000000000000package 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 { if err = GetLDAPError(packet); err != nil { result.Referral = getReferral(err, packet) 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 == ber.TagEmbeddedPDV { passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes()) if len(passwordModifyResponseValue.Children) == 1 { if passwordModifyResponseValue.Children[0].Tag == ber.TagEOC { result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes()) } } } } return result, nil } ldap-3.4.6/request.go000066400000000000000000000062031450131332300144570ustar00rootroot00000000000000package 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 is returned if doRequest is called with a nil connection. 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 } func getReferral(err error, packet *ber.Packet) (referral string) { if !IsErrorWithCode(err, LDAPResultReferral) { return "" } if len(packet.Children) < 2 { return "" } // The packet Tag itself (of child 2) is generally a ber.TagObjectDescriptor with referrals however OpenLDAP // seemingly returns a ber.Tag.GeneralizedTime. Every currently tested LDAP server which returns referrals returns // an ASN.1 BER packet with the Type of ber.TypeConstructed and Class of ber.ClassApplication however. Thus this // check expressly checks these fields instead. // // Related Issues: // - https://github.com/authelia/authelia/issues/4199 (downstream) if len(packet.Children[1].Children) == 0 || (packet.Children[1].TagType != ber.TypeConstructed || packet.Children[1].ClassType != ber.ClassApplication) { return "" } var ok bool for _, child := range packet.Children[1].Children { // The referral URI itself should be contained within a child which has a Tag of ber.BitString or // ber.TagPrintableString, and the Type of ber.TypeConstructed and the Class of ClassContext. As soon as any of // these conditions is not true we can skip this child. if (child.Tag != ber.TagBitString && child.Tag != ber.TagPrintableString) || child.TagType != ber.TypeConstructed || child.ClassType != ber.ClassContext { continue } if referral, ok = child.Children[0].Value.(string); ok { return referral } } return "" } ldap-3.4.6/response.go000066400000000000000000000122161450131332300146260ustar00rootroot00000000000000package ldap import ( "context" "errors" "fmt" ber "github.com/go-asn1-ber/asn1-ber" ) // Response defines an interface to get data from an LDAP server type Response interface { Entry() *Entry Referral() string Controls() []Control Err() error Next() bool } type searchResponse struct { conn *Conn ch chan *SearchSingleResult entry *Entry referral string controls []Control err error } // Entry returns an entry from the given search request func (r *searchResponse) Entry() *Entry { return r.entry } // Referral returns a referral from the given search request func (r *searchResponse) Referral() string { return r.referral } // Controls returns controls from the given search request func (r *searchResponse) Controls() []Control { return r.controls } // Err returns an error when the given search request was failed func (r *searchResponse) Err() error { return r.err } // Next returns whether next data exist or not func (r *searchResponse) Next() bool { res, ok := <-r.ch if !ok { return false } if res == nil { return false } r.err = res.Error if r.err != nil { return false } r.entry = res.Entry r.referral = res.Referral r.controls = res.Controls return true } func (r *searchResponse) start(ctx context.Context, searchRequest *SearchRequest) { go func() { defer func() { close(r.ch) if err := recover(); err != nil { r.conn.err = fmt.Errorf("ldap: recovered panic in searchResponse: %v", err) } }() if r.conn.IsClosing() { return } packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, r.conn.nextMessageID(), "MessageID")) // encode search request err := searchRequest.appendTo(packet) if err != nil { r.ch <- &SearchSingleResult{Error: err} return } r.conn.Debug.PrintPacket(packet) msgCtx, err := r.conn.sendMessage(packet) if err != nil { r.ch <- &SearchSingleResult{Error: err} return } defer r.conn.finishMessage(msgCtx) foundSearchSingleResultDone := false for !foundSearchSingleResultDone { select { case <-ctx.Done(): r.conn.Debug.Printf("%d: %s", msgCtx.id, ctx.Err().Error()) return default: r.conn.Debug.Printf("%d: waiting for response", msgCtx.id) packetResponse, ok := <-msgCtx.responses if !ok { err := NewError(ErrorNetwork, errors.New("ldap: response channel closed")) r.ch <- &SearchSingleResult{Error: err} return } packet, err = packetResponse.ReadPacket() r.conn.Debug.Printf("%d: got response %p", msgCtx.id, packet) if err != nil { r.ch <- &SearchSingleResult{Error: err} return } if r.conn.Debug { if err := addLDAPDescriptions(packet); err != nil { r.ch <- &SearchSingleResult{Error: err} return } ber.PrintPacket(packet) } switch packet.Children[1].Tag { case ApplicationSearchResultEntry: result := &SearchSingleResult{ Entry: &Entry{ DN: packet.Children[1].Children[0].Value.(string), Attributes: unpackAttributes(packet.Children[1].Children[1].Children), }, } if len(packet.Children) != 3 { r.ch <- result continue } decoded, err := DecodeControl(packet.Children[2].Children[0]) if err != nil { werr := fmt.Errorf("failed to decode search result entry: %w", err) result.Error = werr r.ch <- result return } result.Controls = append(result.Controls, decoded) r.ch <- result case ApplicationSearchResultDone: if err := GetLDAPError(packet); err != nil { r.ch <- &SearchSingleResult{Error: err} return } if len(packet.Children) == 3 { result := &SearchSingleResult{} for _, child := range packet.Children[2].Children { decodedChild, err := DecodeControl(child) if err != nil { werr := fmt.Errorf("failed to decode child control: %w", err) r.ch <- &SearchSingleResult{Error: werr} return } result.Controls = append(result.Controls, decodedChild) } r.ch <- result } foundSearchSingleResultDone = true case ApplicationSearchResultReference: ref := packet.Children[1].Children[0].Value.(string) r.ch <- &SearchSingleResult{Referral: ref} case ApplicationIntermediateResponse: decoded, err := DecodeControl(packet.Children[1]) if err != nil { werr := fmt.Errorf("failed to decode intermediate response: %w", err) r.ch <- &SearchSingleResult{Error: werr} return } result := &SearchSingleResult{} result.Controls = append(result.Controls, decoded) r.ch <- result default: err := fmt.Errorf("unknown tag: %d", packet.Children[1].Tag) r.ch <- &SearchSingleResult{Error: err} return } } } r.conn.Debug.Printf("%d: returning", msgCtx.id) }() } func newSearchResponse(conn *Conn, bufferSize int) *searchResponse { var ch chan *SearchSingleResult if bufferSize > 0 { ch = make(chan *SearchSingleResult, bufferSize) } else { ch = make(chan *SearchSingleResult) } return &searchResponse{ conn: conn, ch: ch, } } ldap-3.4.6/search.go000066400000000000000000000530001450131332300142310ustar00rootroot00000000000000package ldap import ( "context" "errors" "fmt" "reflect" "sort" "strconv" "strings" "time" 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) } } // Describe the tag to use for struct field tags const decoderTagName = "ldap" // readTag will read the reflect.StructField value for // the key defined in decoderTagName. If omitempty is // specified, the field may not be filled. func readTag(f reflect.StructField) (string, bool) { val, ok := f.Tag.Lookup(decoderTagName) if !ok { return f.Name, false } opts := strings.Split(val, ",") omit := false if len(opts) == 2 { omit = opts[1] == "omitempty" } return opts[0], omit } // Unmarshal parses the Entry in the value pointed to by i // // Currently, this methods only supports struct fields of type // string, []string, int, int64, []byte, *DN, []*DN or time.Time. Other field types // will not be regarded. If the field type is a string or int but multiple // attribute values are returned, the first value will be used to fill the field. // // Example: // // type UserEntry struct { // // Fields with the tag key `dn` are automatically filled with the // // objects distinguishedName. This can be used multiple times. // DN string `ldap:"dn"` // // // This field will be filled with the attribute value for // // userPrincipalName. An attribute can be read into a struct field // // multiple times. Missing attributes will not result in an error. // UserPrincipalName string `ldap:"userPrincipalName"` // // // memberOf may have multiple values. If you don't // // know the amount of attribute values at runtime, use a string array. // MemberOf []string `ldap:"memberOf"` // // // ID is an integer value, it will fail unmarshaling when the given // // attribute value cannot be parsed into an integer. // ID int `ldap:"id"` // // // LongID is similar to ID but uses an int64 instead. // LongID int64 `ldap:"longId"` // // // Data is similar to MemberOf a slice containing all attribute // // values. // Data []byte `ldap:"data"` // // // Time is parsed with the generalizedTime spec into a time.Time // Created time.Time `ldap:"createdTimestamp"` // // // *DN is parsed with the ParseDN // Owner *ldap.DN `ldap:"owner"` // // // []*DN is parsed with the ParseDN // Children []*ldap.DN `ldap:"children"` // // // This won't work, as the field is not of type string. For this // // to work, you'll have to temporarily store the result in string // // (or string array) and convert it to the desired type afterwards. // UserAccountControl uint32 `ldap:"userPrincipalName"` // } // user := UserEntry{} // // if err := result.Unmarshal(&user); err != nil { // // ... // } func (e *Entry) Unmarshal(i interface{}) (err error) { // Make sure it's a ptr if vo := reflect.ValueOf(i).Kind(); vo != reflect.Ptr { return fmt.Errorf("ldap: cannot use %s, expected pointer to a struct", vo) } sv, st := reflect.ValueOf(i).Elem(), reflect.TypeOf(i).Elem() // Make sure it's pointing to a struct if sv.Kind() != reflect.Struct { return fmt.Errorf("ldap: expected pointer to a struct, got %s", sv.Kind()) } for n := 0; n < st.NumField(); n++ { // Holds struct field value and type fv, ft := sv.Field(n), st.Field(n) // skip unexported fields if ft.PkgPath != "" { continue } // omitempty can be safely discarded, as it's not needed when unmarshalling fieldTag, _ := readTag(ft) // Fill the field with the distinguishedName if the tag key is `dn` if fieldTag == "dn" { fv.SetString(e.DN) continue } values := e.GetAttributeValues(fieldTag) if len(values) == 0 { continue } switch fv.Interface().(type) { case []string: for _, item := range values { fv.Set(reflect.Append(fv, reflect.ValueOf(item))) } case string: fv.SetString(values[0]) case []byte: fv.SetBytes([]byte(values[0])) case int, int64: intVal, err := strconv.ParseInt(values[0], 10, 64) if err != nil { return fmt.Errorf("ldap: could not parse value '%s' into int field", values[0]) } fv.SetInt(intVal) case time.Time: t, err := ber.ParseGeneralizedTime([]byte(values[0])) if err != nil { return fmt.Errorf("ldap: could not parse value '%s' into time.Time field", values[0]) } fv.Set(reflect.ValueOf(t)) case *DN: dn, err := ParseDN(values[0]) if err != nil { return fmt.Errorf("ldap: could not parse value '%s' into *ldap.DN field", values[0]) } fv.Set(reflect.ValueOf(dn)) case []*DN: for _, item := range values { dn, err := ParseDN(item) if err != nil { return fmt.Errorf("ldap: could not parse value '%s' into *ldap.DN field", item) } fv.Set(reflect.Append(fv, reflect.ValueOf(dn))) } default: return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64, []byte, *DN, []*DN or time.Time, got %v", ft.Type) } } return } // 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) } } // appendTo appends all entries of `s` to `r` func (s *SearchResult) appendTo(r *SearchResult) { r.Entries = append(r.Entries, s.Entries...) r.Referrals = append(r.Referrals, s.Referrals...) r.Controls = append(r.Controls, s.Controls...) } // SearchSingleResult holds the server's single entry response to a search request type SearchSingleResult struct { // Entry is the returned entry Entry *Entry // Referral is the returned referral Referral string // Controls are the returned controls Controls []Control // Error is set when the search request was failed Error error } // Print outputs a human-readable description func (s *SearchSingleResult) Print() { s.Entry.Print() } // PrettyPrint outputs a human-readable description with indenting func (s *SearchSingleResult) PrettyPrint(indent int) { s.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) if result != nil { result.appendTo(searchResult) } else { if err == nil { // We have to do this beautifulness in case something absolutely strange happens, which // should only occur in case there is no packet, but also no error. return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received")) } } if err != nil { // If an error occurred, all results that have been received so far will be returned return searchResult, err } 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 if _, err := l.Search(searchRequest); err != nil { return searchResult, err } } 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)) } } } // SearchAsync performs a search request and returns all search results asynchronously. // This means you get all results until an error happens (or the search successfully finished), // e.g. for size / time limited requests all are recieved until the limit is reached. // To stop the search, call cancel function of the context. func (l *Conn) SearchAsync( ctx context.Context, searchRequest *SearchRequest, bufferSize int) Response { r := newSearchResponse(l, bufferSize) r.start(ctx, searchRequest) return r } // Syncrepl is a short name for LDAP Sync Replication engine that works on the // consumer-side. This can perform a persistent search and returns an entry // when the entry is updated on the server side. // To stop the search, call cancel function of the context. func (l *Conn) Syncrepl( ctx context.Context, searchRequest *SearchRequest, bufferSize int, mode ControlSyncRequestMode, cookie []byte, reloadHint bool, ) Response { control := NewControlSyncRequest(mode, cookie, reloadHint) searchRequest.Controls = append(searchRequest.Controls, control) r := newSearchResponse(l, bufferSize) r.start(ctx, searchRequest) return r } // 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 } // DirSync does a Search with dirSync Control. func (l *Conn) DirSync( searchRequest *SearchRequest, flags int64, maxAttrCount int64, cookie []byte, ) (*SearchResult, error) { control := FindControl(searchRequest.Controls, ControlTypeDirSync) if control == nil { c := NewRequestControlDirSync(flags, maxAttrCount, cookie) searchRequest.Controls = append(searchRequest.Controls, c) } else { c := control.(*ControlDirSync) if c.Flags != flags { return nil, fmt.Errorf("flags given in search request (%d) conflicts with flags given in search call (%d)", c.Flags, flags) } if c.MaxAttrCount != maxAttrCount { return nil, fmt.Errorf("MaxAttrCnt given in search request (%d) conflicts with maxAttrCount given in search call (%d)", c.MaxAttrCount, maxAttrCount) } } searchResult, err := l.Search(searchRequest) l.Debug.Printf("Looking for result...") if err != nil { return nil, err } if searchResult == nil { return nil, NewError(ErrorNetwork, errors.New("ldap: packet not received")) } l.Debug.Printf("Looking for DirSync Control...") resultControl := FindControl(searchResult.Controls, ControlTypeDirSync) if resultControl == nil { l.Debug.Printf("Could not find dirSyncControl control. Breaking...") return searchResult, nil } cookie = resultControl.(*ControlDirSync).Cookie if len(cookie) == 0 { l.Debug.Printf("Could not find cookie. Breaking...") return searchResult, nil } return searchResult, nil } // DirSyncDirSyncAsync performs a search request and returns all search results // asynchronously. This is efficient when the server returns lots of entries. func (l *Conn) DirSyncAsync( ctx context.Context, searchRequest *SearchRequest, bufferSize int, flags, maxAttrCount int64, cookie []byte, ) Response { control := NewRequestControlDirSync(flags, maxAttrCount, cookie) searchRequest.Controls = append(searchRequest.Controls, control) r := newSearchResponse(l, bufferSize) r.start(ctx, searchRequest) return r } ldap-3.4.6/search_test.go000066400000000000000000000116501450131332300152750ustar00rootroot00000000000000package ldap import ( "reflect" "testing" "time" "github.com/stretchr/testify/assert" ) // 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") } } func TestEntry_Unmarshal(t *testing.T) { t.Run("passing a struct should fail", func(t *testing.T) { entry := &Entry{} type toStruct struct{} result := toStruct{} err := entry.Unmarshal(result) assert.NotNil(t, err) }) t.Run("passing a ptr to string should fail", func(t *testing.T) { entry := &Entry{} str := "foo" err := entry.Unmarshal(&str) assert.NotNil(t, err) }) t.Run("user struct be decoded", func(t *testing.T) { entry := &Entry{ DN: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com", Attributes: []*EntryAttribute{ { Name: "cn", Values: []string{"mario"}, ByteValues: nil, }, { Name: "mail", Values: []string{"mario@go-ldap.com"}, ByteValues: nil, }, // Tests int value. { Name: "id", Values: []string{"2147483647"}, ByteValues: nil, }, // Tests int64 value. { Name: "longId", Values: []string{"9223372036854775807"}, ByteValues: nil, }, // Tests []byte value. { Name: "data", Values: []string{"data"}, ByteValues: [][]byte{ []byte("data"), }, }, // Tests time.Time value. { Name: "createdTimestamp", Values: []string{"202305041930Z"}, ByteValues: nil, }, // Tests *DN value { Name: "owner", Values: []string{"uid=foo,dc=example,dc=org"}, ByteValues: nil, }, // Tests []*DN value { Name: "children", Values: []string{"uid=bar,dc=example,dc=org", "uid=baz,dc=example,dc=org"}, ByteValues: nil, }, }, } type User struct { Dn string `ldap:"dn"` Cn string `ldap:"cn"` Mail string `ldap:"mail"` ID int `ldap:"id"` LongID int64 `ldap:"longId"` Data []byte `ldap:"data"` Created time.Time `ldap:"createdTimestamp"` Owner *DN `ldap:"owner"` Children []*DN `ldap:"children"` } created, err := time.Parse("200601021504Z", "202305041930Z") if err != nil { t.Errorf("failed to parse ref time: %s", err) } owner, err := ParseDN("uid=foo,dc=example,dc=org") if err != nil { t.Errorf("failed to parse ref DN: %s", err) } var children []*DN for _, child := range []string{"uid=bar,dc=example,dc=org", "uid=baz,dc=example,dc=org"} { dn, err := ParseDN(child) if err != nil { t.Errorf("failed to parse child ref DN: %s", err) } children = append(children, dn) } expect := &User{ Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com", Cn: "mario", Mail: "mario@go-ldap.com", ID: 2147483647, LongID: 9223372036854775807, Data: []byte("data"), Created: created, Owner: owner, Children: children, } result := &User{} err = entry.Unmarshal(result) assert.Nil(t, err) assert.Equal(t, expect, result) }) t.Run("group struct be decoded", func(t *testing.T) { entry := &Entry{ DN: "cn=DREAM_TEAM,ou=Groups,dc=go-ldap,dc=github,dc=com", Attributes: []*EntryAttribute{ { Name: "cn", Values: []string{"DREAM_TEAM"}, ByteValues: nil, }, { Name: "member", Values: []string{"mario", "luigi", "browser"}, ByteValues: nil, }, }, } type Group struct { DN string `ldap:"dn" yaml:"dn" json:"dn"` CN string `ldap:"cn" yaml:"cn" json:"cn"` Members []string `ldap:"member"` } expect := &Group{ DN: "cn=DREAM_TEAM,ou=Groups,dc=go-ldap,dc=github,dc=com", CN: "DREAM_TEAM", Members: []string{"mario", "luigi", "browser"}, } result := &Group{} err := entry.Unmarshal(result) assert.Nil(t, err) assert.Equal(t, expect, result) }) } ldap-3.4.6/unbind.go000066400000000000000000000020071450131332300142440ustar00rootroot00000000000000package ldap import ( "errors" ber "github.com/go-asn1-ber/asn1-ber" ) // ErrConnUnbound is returned when Unbind is called on an already closing connection. 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.6/v3/000077500000000000000000000000001450131332300127675ustar00rootroot00000000000000ldap-3.4.6/v3/add.go000066400000000000000000000050461450131332300140530ustar00rootroot00000000000000package ldap import ( "fmt" 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 { return fmt.Errorf("ldap: unexpected response: %d", packet.Children[1].Tag) } return nil } ldap-3.4.6/v3/bind.go000066400000000000000000000603171450131332300142410ustar00rootroot00000000000000package 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 // AllowEmptyPassword sets whether the client allows binding with an empty password // (normally used for unauthenticated bind). AllowEmptyPassword bool // 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 } // NTLMUnauthenticatedBind performs an bind with an empty password. // // A username is required. The anonymous bind is not (yet) supported by the go-ntlmssp library (https://github.com/Azure/go-ntlmssp/blob/819c794454d067543bc61d29f61fef4b3c3df62c/authenticate_message.go#L87) // // See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4 part 3.2.5.1.2 func (l *Conn) NTLMUnauthenticatedBind(domain, username string) error { req := &NTLMBindRequest{ Domain: domain, Username: username, Password: "", AllowEmptyPassword: true, } _, 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.AllowEmptyPassword && 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.Hash != "" { responseMessage, err = ntlmssp.ProcessChallengeWithHash(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Hash) } else if ntlmBindRequest.Password != "" || ntlmBindRequest.AllowEmptyPassword { _, _, domainNeeded := ntlmssp.GetDomain(ntlmBindRequest.Username) responseMessage, err = ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password, domainNeeded) } 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 } // GSSAPIClient interface is used as the client-side implementation for the // GSSAPI SASL mechanism. // Interface inspired by GSSAPIClient from golang.org/x/crypto/ssh type GSSAPIClient interface { // InitSecContext initiates the establishment of a security context for // GSS-API between the client and server. // Initially the token parameter should be specified as nil. // The routine may return a outputToken which should be transferred to // the server, where the server will present it to AcceptSecContext. // If no token need be sent, InitSecContext will indicate this by setting // needContinue to false. To complete the context // establishment, one or more reply tokens may be required from the server; // if so, InitSecContext will return a needContinue which is true. // In this case, InitSecContext should be called again when the // reply token is received from the server, passing the reply token // to InitSecContext via the token parameters. // See RFC 4752 section 3.1. InitSecContext(target string, token []byte) (outputToken []byte, needContinue bool, err error) // NegotiateSaslAuth performs the last step of the Sasl handshake. // It takes a token, which, when unwrapped, describes the servers supported // security layers (first octet) and maximum receive buffer (remaining // three octets). // If the received token is unacceptable an error must be returned to abort // the handshake. // Outputs a signed token describing the client's selected security layer // and receive buffer size and optionally an authorization identity. // The returned token will be sent to the server and the handshake considered // completed successfully and the server authenticated. // See RFC 4752 section 3.1. NegotiateSaslAuth(token []byte, authzid string) ([]byte, error) // DeleteSecContext destroys any established secure context. DeleteSecContext() error } // GSSAPIBindRequest represents a GSSAPI SASL mechanism bind request. // See rfc4752 and rfc4513 section 5.2.1.2. type GSSAPIBindRequest struct { // Service Principal Name user for the service ticket. Eg. "ldap/" ServicePrincipalName string // (Optional) Authorization entity AuthZID string // (Optional) Controls to send with the bind request Controls []Control } // GSSAPIBind performs the GSSAPI SASL bind using the provided GSSAPI client. func (l *Conn) GSSAPIBind(client GSSAPIClient, servicePrincipal, authzid string) error { return l.GSSAPIBindRequest(client, &GSSAPIBindRequest{ ServicePrincipalName: servicePrincipal, AuthZID: authzid, }) } // GSSAPIBindRequest performs the GSSAPI SASL bind using the provided GSSAPI client. func (l *Conn) GSSAPIBindRequest(client GSSAPIClient, req *GSSAPIBindRequest) error { //nolint:errcheck defer client.DeleteSecContext() var err error var reqToken []byte var recvToken []byte needInit := true for { if needInit { // Establish secure context between client and server. reqToken, needInit, err = client.InitSecContext(req.ServicePrincipalName, recvToken) if err != nil { return err } } else { // Secure context is set up, perform the last step of SASL handshake. reqToken, err = client.NegotiateSaslAuth(recvToken, req.AuthZID) if err != nil { return err } } // Send Bind request containing the current token and extract the // token sent by server. recvToken, err = l.saslBindTokenExchange(req.Controls, reqToken) if err != nil { return err } if !needInit && len(recvToken) == 0 { break } } return nil } func (l *Conn) saslBindTokenExchange(reqControls []Control, reqToken []byte) ([]byte, error) { // Construct LDAP Bind request with GSSAPI SASL mechanism. envelope := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") envelope.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, "GSSAPI", "SASL Mech")) if len(reqToken) > 0 { auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, string(reqToken), "Credentials")) } request.AppendChild(auth) envelope.AppendChild(request) if len(reqControls) > 0 { envelope.AppendChild(encodeControls(reqControls)) } msgCtx, err := l.sendMessage(envelope) 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) } // https://www.rfc-editor.org/rfc/rfc4511#section-4.1.1 // packet is an envelope // child 0 is message id // child 1 is protocolOp if len(packet.Children) != 2 { return nil, fmt.Errorf("bad bind response") } protocolOp := packet.Children[1] RESP: switch protocolOp.Description { case "Bind Response": // Bind Response // Bind Reponse is an LDAP Response (https://www.rfc-editor.org/rfc/rfc4511#section-4.1.9) // with an additional optional serverSaslCreds string (https://www.rfc-editor.org/rfc/rfc4511#section-4.2.2) // child 0 is resultCode resultCode := protocolOp.Children[0] if resultCode.Tag != ber.TagEnumerated { break RESP } switch resultCode.Value.(int64) { case 14: // Sasl bind in progress if len(protocolOp.Children) < 3 { break RESP } referral := protocolOp.Children[3] switch referral.Description { case "Referral": if referral.ClassType != ber.ClassContext || referral.Tag != ber.TagObjectDescriptor { break RESP } return ioutil.ReadAll(referral.Data) } // Optional: //if len(protocolOp.Children) == 4 { // serverSaslCreds := protocolOp.Children[4] //} case 0: // Success - Bind OK. // SASL layer in effect (if any) (See https://www.rfc-editor.org/rfc/rfc4513#section-5.2.1.4) // NOTE: SASL security layers are not supported currently. return nil, nil } } return nil, GetLDAPError(packet) } ldap-3.4.6/v3/client.go000066400000000000000000000026501450131332300145770ustar00rootroot00000000000000package ldap import ( "context" "crypto/tls" "time" ) // Client knows how to interact with an LDAP server type Client interface { Start() StartTLS(*tls.Config) error Close() error GetLastError() error IsClosing() bool SetTimeout(time.Duration) TLSConnectionState() (tls.ConnectionState, bool) Bind(username, password string) error UnauthenticatedBind(username string) error SimpleBind(*SimpleBindRequest) (*SimpleBindResult, error) ExternalBind() error NTLMUnauthenticatedBind(domain, username string) error Unbind() 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) SearchAsync(ctx context.Context, searchRequest *SearchRequest, bufferSize int) Response SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) DirSync(searchRequest *SearchRequest, flags, maxAttrCount int64, cookie []byte) (*SearchResult, error) DirSyncAsync(ctx context.Context, searchRequest *SearchRequest, bufferSize int, flags, maxAttrCount int64, cookie []byte) Response Syncrepl(ctx context.Context, searchRequest *SearchRequest, bufferSize int, mode ControlSyncRequestMode, cookie []byte, reloadHint bool) Response } ldap-3.4.6/v3/compare.go000066400000000000000000000033411450131332300147450ustar00rootroot00000000000000package 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.6/v3/conn.go000066400000000000000000000417721450131332300142660ustar00rootroot00000000000000package ldap import ( "bufio" "context" "crypto/tls" "errors" "fmt" "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, timeout time.Duration) { timeoutCtx := context.Background() if timeout > 0 { var cancelFunc context.CancelFunc timeoutCtx, cancelFunc = context.WithTimeout(context.Background(), timeout) defer cancelFunc() } 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. case <-timeoutCtx.Done(): // The timeout was reached before the packet was sent. } } 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. // https://github.com/go-ldap/ldap/pull/199 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 err error } 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.dialer = d } } // DialWithTLSConfig updates tls.Config in DialContext. func DialWithTLSConfig(tc *tls.Config) DialOpt { return func(dc *DialContext) { dc.tlsConfig = 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. // @deprecated Use DialWithDialer and DialWithTLSConfig instead func DialWithTLSDialer(tlsConfig *tls.Config, dialer *net.Dialer) DialOpt { return func(dc *DialContext) { dc.tlsConfig = tlsConfig dc.dialer = dialer } } // DialContext contains necessary parameters to dial the given ldap URL. type DialContext struct { dialer *net.Dialer tlsConfig *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.dialer.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 "cldap": if port == "" { port = DefaultLdapPort } return dc.dialer.Dial("udp", net.JoinHostPort(host, port)) case "ldap": if port == "" { port = DefaultLdapPort } return dc.dialer.Dial("tcp", net.JoinHostPort(host, port)) case "ldaps": if port == "" { port = DefaultLdapsPort } return tls.DialWithDialer(dc.dialer, "tcp", net.JoinHostPort(host, port), dc.tlsConfig) } 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://, // and cldap:// (RFC1798, deprecated but used by Active Directory). // On success a new Conn for the connection is returned. func DialURL(addr string, opts ...DialOpt) (*Conn, error) { u, err := url.Parse(addr) if err != nil { return nil, NewError(ErrorNetwork, err) } var dc DialContext for _, opt := range opts { opt(&dc) } if dc.dialer == nil { dc.dialer = &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 { l := &Conn{ conn: conn, chanConfirm: make(chan struct{}), chanMessageID: make(chan int64), chanMessage: make(chan *messagePacket, 10), messageContexts: map[int64]*messageContext{}, requestTimeout: 0, isTLS: isTLS, } l.wgClose.Add(1) return l } // Start initializes goroutines to read responses and process messages func (l *Conn) Start() { 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() (err error) { 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} timeoutCtx := context.Background() if l.getTimeout() > 0 { var cancelFunc context.CancelFunc timeoutCtx, cancelFunc = context.WithTimeout(timeoutCtx, time.Duration(l.getTimeout())) defer cancelFunc() } select { case <-l.chanConfirm: // Confirmation was received. case <-timeoutCtx.Done(): // The timeout was reached before confirmation was received. } close(l.chanMessage) l.Debug.Printf("Closing network connection") err = l.conn.Close() l.wgClose.Done() } l.wgClose.Wait() return err } // SetTimeout sets the time after a request is sent that a MessageTimeout triggers func (l *Conn) SetTimeout(timeout time.Duration) { atomic.StoreInt64(&l.requestTimeout, int64(timeout)) } func (l *Conn) getTimeout() int64 { return atomic.LoadInt64(&l.requestTimeout) } // Returns the next available messageID func (l *Conn) nextMessageID() int64 { if messageID, ok := <-l.chanMessageID; ok { return messageID } return 0 } // GetLastError returns the last recorded error from goroutines like processMessages and reader. // Only the last recorded error will be returned. func (l *Conn) GetLastError() error { l.messageMutex.Lock() defer l.messageMutex.Unlock() return l.err } // StartTLS sends the command to start a TLS session and then creates a new TLS Client func (l *Conn) StartTLS(config *tls.Config) error { if l.isTLS { 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 { l.err = fmt.Errorf("ldap: recovered panic in processMessages: %v", err) } for messageID, msgCtx := range l.messageContexts { // If we are closing due to an error, inform anyone who // is waiting about the error. if l.IsClosing() && l.closeErr.Load() != nil { msgCtx.sendResponse(&PacketResponse{Error: l.closeErr.Load().(error)}, time.Duration(l.getTimeout())) } 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)}, time.Duration(l.getTimeout())) 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 := l.getTimeout() if requestTimeout > 0 { go func() { timer := time.NewTimer(time.Duration(requestTimeout)) defer func() { if err := recover(); err != nil { l.err = fmt.Errorf("ldap: recovered panic in RequestTimeout: %v", err) } timer.Stop() }() select { case <-timer.C: timeoutMessage := &messagePacket{ Op: MessageTimeout, MessageID: message.MessageID, } l.sendProcessMessage(timeoutMessage) case <-message.Context.done: } }() } case MessageResponse: l.Debug.Printf("Receiving message %d", message.MessageID) if msgCtx, ok := l.messageContexts[message.MessageID]; ok { msgCtx.sendResponse(&PacketResponse{message.Packet, nil}, time.Duration(l.getTimeout())) } else { l.err = fmt.Errorf("ldap: received unexpected message %d, %v", message.MessageID, l.IsClosing()) l.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"))}, time.Duration(l.getTimeout())) 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 { l.err = fmt.Errorf("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.6/v3/conn_test.go000066400000000000000000000275621450131332300153260ustar00rootroot00000000000000package 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") } _, 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) } } func TestRequestTimeoutDeadlock(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.Start() // trigger a race condition on accessing request timeout n := 3 for i := 0; i < n; i++ { go func() { conn.SetTimeout(time.Millisecond) }() } // Attempt to close the connection when the message handler is // blocked or inactive conn.Close() } // TestInvalidStateCloseDeadlock tests that we do not enter deadlock when the // message handler is blocked or inactive. func TestInvalidStateCloseDeadlock(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) // Attempt to close the connection when the message handler is // blocked or inactive conn.Close() } // TestInvalidStateSendResponseDeadlock tests that we do not enter deadlock when the // message handler is blocked or inactive. func TestInvalidStateSendResponseDeadlock(t *testing.T) { // Attempt to send a response packet when the message handler is blocked or inactive msgCtx := &messageContext{ id: 0, done: make(chan struct{}), responses: make(chan *PacketResponse), } msgCtx.sendResponse(&PacketResponse{}, time.Millisecond) } // 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.6/v3/control.go000066400000000000000000001240551450131332300150050ustar00rootroot00000000000000package ldap import ( "fmt" "strconv" ber "github.com/go-asn1-ber/asn1-ber" "github.com/google/uuid" ) 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" // ControlTypeSubtreeDelete - https://datatracker.ietf.org/doc/html/draft-armijo-ldap-treedelete-02 ControlTypeSubtreeDelete = "1.2.840.113556.1.4.805" // ControlTypeServerSideSorting - https://www.ietf.org/rfc/rfc2891.txt ControlTypeServerSideSorting = "1.2.840.113556.1.4.473" // ControlTypeServerSideSorting - https://www.ietf.org/rfc/rfc2891.txt ControlTypeServerSideSortingResult = "1.2.840.113556.1.4.474" // 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" // ControlTypeDirSync - Active Directory DirSync - https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx ControlTypeDirSync = "1.2.840.113556.1.4.841" // ControlTypeSyncRequest - https://www.ietf.org/rfc/rfc4533.txt ControlTypeSyncRequest = "1.3.6.1.4.1.4203.1.9.1.1" // ControlTypeSyncState - https://www.ietf.org/rfc/rfc4533.txt ControlTypeSyncState = "1.3.6.1.4.1.4203.1.9.1.2" // ControlTypeSyncDone - https://www.ietf.org/rfc/rfc4533.txt ControlTypeSyncDone = "1.3.6.1.4.1.4203.1.9.1.3" // ControlTypeSyncInfo - https://www.ietf.org/rfc/rfc4533.txt ControlTypeSyncInfo = "1.3.6.1.4.1.4203.1.9.1.4" ) // Flags for DirSync control const ( DirSyncIncrementalValues int64 = 2147483648 DirSyncPublicDataOnly int64 = 8192 DirSyncAncestorsFirstOrder int64 = 2048 DirSyncObjectSecurity int64 = 1 ) // ControlTypeMap maps controls to text descriptions var ControlTypeMap = map[string]string{ ControlTypePaging: "Paging", ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft", ControlTypeManageDsaIT: "Manage DSA IT", ControlTypeSubtreeDelete: "Subtree Delete Control", ControlTypeMicrosoftNotification: "Change Notification - Microsoft", ControlTypeMicrosoftShowDeleted: "Show Deleted Objects - Microsoft", ControlTypeMicrosoftServerLinkTTL: "Return TTL-DNs for link values with associated expiry times - Microsoft", ControlTypeServerSideSorting: "Server Side Sorting Request - LDAP Control Extension for Server Side Sorting of Search Results (RFC2891)", ControlTypeServerSideSortingResult: "Server Side Sorting Results - LDAP Control Extension for Server Side Sorting of Search Results (RFC2891)", ControlTypeDirSync: "DirSync", ControlTypeSyncRequest: "Sync Request", ControlTypeSyncState: "Sync State", ControlTypeSyncDone: "Sync Done", ControlTypeSyncInfo: "Sync Info", } // 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] + ")" if packet.Children[0].Value != nil { ControlType = packet.Children[0].Value.(string) } else if packet.Children[0].Data != nil { ControlType = packet.Children[0].Data.String() } else { return nil, fmt.Errorf("not found where to get the control type") } // 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 case ControlTypeSubtreeDelete: return NewControlSubtreeDelete(), nil case ControlTypeServerSideSorting: return NewControlServerSideSorting(value) case ControlTypeServerSideSortingResult: return NewControlServerSideSortingResult(value) case ControlTypeDirSync: value.Description += " (DirSync)" return NewResponseControlDirSync(value) case ControlTypeSyncState: value.Description += " (Sync State)" valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) if err != nil { return nil, fmt.Errorf("failed to decode data bytes: %s", err) } return NewControlSyncState(valueChildren) case ControlTypeSyncDone: value.Description += " (Sync Done)" valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) if err != nil { return nil, fmt.Errorf("failed to decode data bytes: %s", err) } return NewControlSyncDone(valueChildren) case ControlTypeSyncInfo: value.Description += " (Sync Info)" valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) if err != nil { return nil, fmt.Errorf("failed to decode data bytes: %s", err) } return NewControlSyncInfo(valueChildren) 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, } } // ControlSubtreeDelete implements the subtree delete control described in // https://datatracker.ietf.org/doc/html/draft-armijo-ldap-treedelete-02 type ControlSubtreeDelete struct{} // GetControlType returns the OID func (c *ControlSubtreeDelete) GetControlType() string { return ControlTypeSubtreeDelete } // NewControlSubtreeDelete returns a ControlSubtreeDelete control. func NewControlSubtreeDelete() *ControlSubtreeDelete { return &ControlSubtreeDelete{} } // Encode returns the ber packet representation func (c *ControlSubtreeDelete) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeSubtreeDelete, "Control Type ("+ControlTypeMap[ControlTypeSubtreeDelete]+")")) return packet } func (c *ControlSubtreeDelete) String() string { return fmt.Sprintf( "Control Type: %s (%q)", ControlTypeMap[ControlTypeSubtreeDelete], ControlTypeSubtreeDelete) } 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 } // ControlDirSync implements the control described in https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx type ControlDirSync struct { Criticality bool Flags int64 MaxAttrCount int64 Cookie []byte } // @deprecated Use NewRequestControlDirSync instead func NewControlDirSync(flags int64, maxAttrCount int64, cookie []byte) *ControlDirSync { return NewRequestControlDirSync(flags, maxAttrCount, cookie) } // NewRequestControlDirSync returns a dir sync control func NewRequestControlDirSync( flags int64, maxAttrCount int64, cookie []byte, ) *ControlDirSync { return &ControlDirSync{ Criticality: true, Flags: flags, MaxAttrCount: maxAttrCount, Cookie: cookie, } } // NewResponseControlDirSync returns a dir sync control func NewResponseControlDirSync(value *ber.Packet) (*ControlDirSync, error) { 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) } child := value.Children[0] if len(child.Children) != 3 { // also on initial creation, Cookie is an empty string return nil, fmt.Errorf("invalid number of children in dirSync control") } child.Description = "DirSync Control Value" child.Children[0].Description = "Flags" child.Children[1].Description = "MaxAttrCount" child.Children[2].Description = "Cookie" cookie := child.Children[2].Data.Bytes() child.Children[2].Value = cookie return &ControlDirSync{ Criticality: true, Flags: child.Children[0].Value.(int64), MaxAttrCount: child.Children[1].Value.(int64), Cookie: cookie, }, nil } // GetControlType returns the OID func (c *ControlDirSync) GetControlType() string { return ControlTypeDirSync } // String returns a human-readable description func (c *ControlDirSync) String() string { return fmt.Sprintf( "ControlType: %s (%q) Criticality: %t ControlValue: Flags: %d MaxAttrCount: %d", ControlTypeMap[ControlTypeDirSync], ControlTypeDirSync, c.Criticality, c.Flags, c.MaxAttrCount, ) } // Encode returns the ber packet representation func (c *ControlDirSync) Encode() *ber.Packet { cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Cookie") if len(c.Cookie) != 0 { cookie.Value = c.Cookie cookie.Data.Write(c.Cookie) } packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeDirSync, "Control Type ("+ControlTypeMap[ControlTypeDirSync]+")")) packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) // must be true always val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (DirSync)") seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "DirSync Control Value") seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.Flags), "Flags")) seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.MaxAttrCount), "MaxAttrCount")) seq.AppendChild(cookie) val.AppendChild(seq) packet.AppendChild(val) return packet } // SetCookie stores the given cookie in the dirSync control func (c *ControlDirSync) SetCookie(cookie []byte) { c.Cookie = cookie } // ControlServerSideSorting type SortKey struct { Reverse bool AttributeType string MatchingRule string } type ControlServerSideSorting struct { SortKeys []*SortKey } func (c *ControlServerSideSorting) GetControlType() string { return ControlTypeServerSideSorting } func NewControlServerSideSorting(value *ber.Packet) (*ControlServerSideSorting, error) { sortKeys := []*SortKey{} val := value.Children[1].Children if len(val) != 1 { return nil, fmt.Errorf("no sequence value in packet") } sequences := val[0].Children for i, sequence := range sequences { sortKey := &SortKey{} if len(sequence.Children) < 2 { return nil, fmt.Errorf("attributeType or matchingRule is missing from sequence %d", i) } sortKey.AttributeType = sequence.Children[0].Value.(string) sortKey.MatchingRule = sequence.Children[1].Value.(string) if len(sequence.Children) == 3 { sortKey.Reverse = sequence.Children[2].Value.(bool) } sortKeys = append(sortKeys, sortKey) } return &ControlServerSideSorting{SortKeys: sortKeys}, nil } func NewControlServerSideSortingWithSortKeys(sortKeys []*SortKey) *ControlServerSideSorting { return &ControlServerSideSorting{SortKeys: sortKeys} } func (c *ControlServerSideSorting) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") control := ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.GetControlType(), "Control Type") value := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value") seqs := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "SortKeyList") for _, f := range c.SortKeys { seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "") seq.AppendChild( ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, f.AttributeType, "attributeType"), ) seq.AppendChild( ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, f.MatchingRule, "orderingRule"), ) if f.Reverse { seq.AppendChild( ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, 1, f.Reverse, "reverseOrder"), ) } seqs.AppendChild(seq) } value.AppendChild(seqs) packet.AppendChild(control) packet.AppendChild(value) return packet } func (c *ControlServerSideSorting) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality:%t %+v", "Server Side Sorting", c.GetControlType(), false, c.SortKeys, ) } // ControlServerSideSortingResponse const ( ControlServerSideSortingCodeSuccess ControlServerSideSortingCode = 0 ControlServerSideSortingCodeOperationsError ControlServerSideSortingCode = 1 ControlServerSideSortingCodeTimeLimitExceeded ControlServerSideSortingCode = 2 ControlServerSideSortingCodeStrongAuthRequired ControlServerSideSortingCode = 8 ControlServerSideSortingCodeAdminLimitExceeded ControlServerSideSortingCode = 11 ControlServerSideSortingCodeNoSuchAttribute ControlServerSideSortingCode = 16 ControlServerSideSortingCodeInappropriateMatching ControlServerSideSortingCode = 18 ControlServerSideSortingCodeInsufficientAccessRights ControlServerSideSortingCode = 50 ControlServerSideSortingCodeBusy ControlServerSideSortingCode = 51 ControlServerSideSortingCodeUnwillingToPerform ControlServerSideSortingCode = 53 ControlServerSideSortingCodeOther ControlServerSideSortingCode = 80 ) var ControlServerSideSortingCodes = []ControlServerSideSortingCode{ ControlServerSideSortingCodeSuccess, ControlServerSideSortingCodeOperationsError, ControlServerSideSortingCodeTimeLimitExceeded, ControlServerSideSortingCodeStrongAuthRequired, ControlServerSideSortingCodeAdminLimitExceeded, ControlServerSideSortingCodeNoSuchAttribute, ControlServerSideSortingCodeInappropriateMatching, ControlServerSideSortingCodeInsufficientAccessRights, ControlServerSideSortingCodeBusy, ControlServerSideSortingCodeUnwillingToPerform, ControlServerSideSortingCodeOther, } type ControlServerSideSortingCode int64 // Valid test the code contained in the control against the ControlServerSideSortingCodes slice and return an error if the code is unknown. func (c ControlServerSideSortingCode) Valid() error { for _, validRet := range ControlServerSideSortingCodes { if c == validRet { return nil } } return fmt.Errorf("unknown return code : %d", c) } func NewControlServerSideSortingResult(pkt *ber.Packet) (*ControlServerSideSortingResult, error) { control := &ControlServerSideSortingResult{} if pkt == nil || len(pkt.Children) == 0 { return nil, fmt.Errorf("bad packet") } codeInt, err := ber.ParseInt64(pkt.Children[0].Data.Bytes()) if err != nil { return nil, err } code := ControlServerSideSortingCode(codeInt) if err := code.Valid(); err != nil { return nil, err } return control, nil } type ControlServerSideSortingResult struct { Criticality bool Result ControlServerSideSortingCode // Not populated for now. I can't get openldap to send me this value, so I think this is specific to other directory server // AttributeType string } func (control *ControlServerSideSortingResult) GetControlType() string { return ControlTypeServerSideSortingResult } func (c *ControlServerSideSortingResult) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "SortResult sequence") sortResult := ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, int64(c.Result), "SortResult") packet.AppendChild(sortResult) return packet } func (c *ControlServerSideSortingResult) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality:%t ResultCode:%+v", "Server Side Sorting Result", c.GetControlType(), c.Criticality, c.Result, ) } // Mode for ControlTypeSyncRequest type ControlSyncRequestMode int64 const ( SyncRequestModeRefreshOnly ControlSyncRequestMode = 1 SyncRequestModeRefreshAndPersist ControlSyncRequestMode = 3 ) // ControlSyncRequest implements the Sync Request Control described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncRequest struct { Criticality bool Mode ControlSyncRequestMode Cookie []byte ReloadHint bool } func NewControlSyncRequest( mode ControlSyncRequestMode, cookie []byte, reloadHint bool, ) *ControlSyncRequest { return &ControlSyncRequest{ Criticality: true, Mode: mode, Cookie: cookie, ReloadHint: reloadHint, } } // GetControlType returns the OID func (c *ControlSyncRequest) GetControlType() string { return ControlTypeSyncRequest } // Encode encodes the control func (c *ControlSyncRequest) Encode() *ber.Packet { _mode := int64(c.Mode) mode := ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, _mode, "Mode") var cookie *ber.Packet if len(c.Cookie) > 0 { cookie = ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie") cookie.Value = c.Cookie cookie.Data.Write(c.Cookie) } reloadHint := ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.ReloadHint, "Reload Hint") packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeSyncRequest, "Control Type ("+ControlTypeMap[ControlTypeSyncRequest]+")")) packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (Sync Request)") seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Sync Request Value") seq.AppendChild(mode) if cookie != nil { seq.AppendChild(cookie) } seq.AppendChild(reloadHint) val.AppendChild(seq) packet.AppendChild(val) return packet } // String returns a human-readable description func (c *ControlSyncRequest) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t Mode: %d Cookie: %s ReloadHint: %t", ControlTypeMap[ControlTypeSyncRequest], ControlTypeSyncRequest, c.Criticality, c.Mode, string(c.Cookie), c.ReloadHint, ) } // State for ControlSyncState type ControlSyncStateState int64 const ( SyncStatePresent ControlSyncStateState = 0 SyncStateAdd ControlSyncStateState = 1 SyncStateModify ControlSyncStateState = 2 SyncStateDelete ControlSyncStateState = 3 ) // ControlSyncState implements the Sync State Control described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncState struct { Criticality bool State ControlSyncStateState EntryUUID uuid.UUID Cookie []byte } func NewControlSyncState(pkt *ber.Packet) (*ControlSyncState, error) { var ( state ControlSyncStateState entryUUID uuid.UUID cookie []byte err error ) switch len(pkt.Children) { case 0, 1: return nil, fmt.Errorf("at least two children are required: %d", len(pkt.Children)) case 2: state = ControlSyncStateState(pkt.Children[0].Value.(int64)) entryUUID, err = uuid.FromBytes(pkt.Children[1].ByteValue) if err != nil { return nil, fmt.Errorf("failed to decode uuid: %w", err) } case 3: state = ControlSyncStateState(pkt.Children[0].Value.(int64)) entryUUID, err = uuid.FromBytes(pkt.Children[1].ByteValue) if err != nil { return nil, fmt.Errorf("failed to decode uuid: %w", err) } cookie = pkt.Children[2].ByteValue } return &ControlSyncState{ Criticality: false, State: state, EntryUUID: entryUUID, Cookie: cookie, }, nil } // GetControlType returns the OID func (c *ControlSyncState) GetControlType() string { return ControlTypeSyncState } // Encode encodes the control func (c *ControlSyncState) Encode() *ber.Packet { return nil } // String returns a human-readable description func (c *ControlSyncState) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t State: %d EntryUUID: %s Cookie: %s", ControlTypeMap[ControlTypeSyncState], ControlTypeSyncState, c.Criticality, c.State, c.EntryUUID.String(), string(c.Cookie), ) } // ControlSyncDone implements the Sync Done Control described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncDone struct { Criticality bool Cookie []byte RefreshDeletes bool } func NewControlSyncDone(pkt *ber.Packet) (*ControlSyncDone, error) { var ( cookie []byte refreshDeletes bool ) switch len(pkt.Children) { case 0: // have nothing to do case 1: cookie = pkt.Children[0].ByteValue case 2: cookie = pkt.Children[0].ByteValue refreshDeletes = pkt.Children[1].Value.(bool) } return &ControlSyncDone{ Criticality: false, Cookie: cookie, RefreshDeletes: refreshDeletes, }, nil } // GetControlType returns the OID func (c *ControlSyncDone) GetControlType() string { return ControlTypeSyncDone } // Encode encodes the control func (c *ControlSyncDone) Encode() *ber.Packet { return nil } // String returns a human-readable description func (c *ControlSyncDone) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t Cookie: %s RefreshDeletes: %t", ControlTypeMap[ControlTypeSyncDone], ControlTypeSyncDone, c.Criticality, string(c.Cookie), c.RefreshDeletes, ) } // Tag For ControlSyncInfo type ControlSyncInfoValue uint64 const ( SyncInfoNewcookie ControlSyncInfoValue = 0 SyncInfoRefreshDelete ControlSyncInfoValue = 1 SyncInfoRefreshPresent ControlSyncInfoValue = 2 SyncInfoSyncIdSet ControlSyncInfoValue = 3 ) // ControlSyncInfoNewCookie implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncInfoNewCookie struct { Cookie []byte } // String returns a human-readable description func (c *ControlSyncInfoNewCookie) String() string { return fmt.Sprintf( "NewCookie[Cookie: %s]", string(c.Cookie), ) } // ControlSyncInfoRefreshDelete implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncInfoRefreshDelete struct { Cookie []byte RefreshDone bool } // String returns a human-readable description func (c *ControlSyncInfoRefreshDelete) String() string { return fmt.Sprintf( "RefreshDelete[Cookie: %s RefreshDone: %t]", string(c.Cookie), c.RefreshDone, ) } // ControlSyncInfoRefreshPresent implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncInfoRefreshPresent struct { Cookie []byte RefreshDone bool } // String returns a human-readable description func (c *ControlSyncInfoRefreshPresent) String() string { return fmt.Sprintf( "RefreshPresent[Cookie: %s RefreshDone: %t]", string(c.Cookie), c.RefreshDone, ) } // ControlSyncInfoSyncIdSet implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncInfoSyncIdSet struct { Cookie []byte RefreshDeletes bool SyncUUIDs []uuid.UUID } // String returns a human-readable description func (c *ControlSyncInfoSyncIdSet) String() string { return fmt.Sprintf( "SyncIdSet[Cookie: %s RefreshDeletes: %t SyncUUIDs: %v]", string(c.Cookie), c.RefreshDeletes, c.SyncUUIDs, ) } // ControlSyncInfo implements the Sync Info Control described in https://www.ietf.org/rfc/rfc4533.txt type ControlSyncInfo struct { Criticality bool Value ControlSyncInfoValue NewCookie *ControlSyncInfoNewCookie RefreshDelete *ControlSyncInfoRefreshDelete RefreshPresent *ControlSyncInfoRefreshPresent SyncIdSet *ControlSyncInfoSyncIdSet } func NewControlSyncInfo(pkt *ber.Packet) (*ControlSyncInfo, error) { var ( cookie []byte refreshDone = true refreshDeletes bool syncUUIDs []uuid.UUID ) c := &ControlSyncInfo{Criticality: false} switch ControlSyncInfoValue(pkt.Identifier.Tag) { case SyncInfoNewcookie: c.Value = SyncInfoNewcookie c.NewCookie = &ControlSyncInfoNewCookie{ Cookie: pkt.ByteValue, } case SyncInfoRefreshDelete: c.Value = SyncInfoRefreshDelete switch len(pkt.Children) { case 0: // have nothing to do case 1: cookie = pkt.Children[0].ByteValue case 2: cookie = pkt.Children[0].ByteValue refreshDone = pkt.Children[1].Value.(bool) } c.RefreshDelete = &ControlSyncInfoRefreshDelete{ Cookie: cookie, RefreshDone: refreshDone, } case SyncInfoRefreshPresent: c.Value = SyncInfoRefreshPresent switch len(pkt.Children) { case 0: // have nothing to do case 1: cookie = pkt.Children[0].ByteValue case 2: cookie = pkt.Children[0].ByteValue refreshDone = pkt.Children[1].Value.(bool) } c.RefreshPresent = &ControlSyncInfoRefreshPresent{ Cookie: cookie, RefreshDone: refreshDone, } case SyncInfoSyncIdSet: c.Value = SyncInfoSyncIdSet switch len(pkt.Children) { case 0: // have nothing to do case 1: cookie = pkt.Children[0].ByteValue case 2: cookie = pkt.Children[0].ByteValue refreshDeletes = pkt.Children[1].Value.(bool) case 3: cookie = pkt.Children[0].ByteValue refreshDeletes = pkt.Children[1].Value.(bool) syncUUIDs = make([]uuid.UUID, 0, len(pkt.Children[2].Children)) for _, child := range pkt.Children[2].Children { u, err := uuid.FromBytes(child.ByteValue) if err != nil { return nil, fmt.Errorf("failed to decode uuid: %w", err) } syncUUIDs = append(syncUUIDs, u) } } c.SyncIdSet = &ControlSyncInfoSyncIdSet{ Cookie: cookie, RefreshDeletes: refreshDeletes, SyncUUIDs: syncUUIDs, } default: return nil, fmt.Errorf("unknown sync info value: %d", pkt.Identifier.Tag) } return c, nil } // GetControlType returns the OID func (c *ControlSyncInfo) GetControlType() string { return ControlTypeSyncInfo } // Encode encodes the control func (c *ControlSyncInfo) Encode() *ber.Packet { return nil } // String returns a human-readable description func (c *ControlSyncInfo) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t Value: %d %s %s %s %s", ControlTypeMap[ControlTypeSyncInfo], ControlTypeSyncInfo, c.Criticality, c.Value, c.NewCookie, c.RefreshDelete, c.RefreshPresent, c.SyncIdSet, ) } ldap-3.4.6/v3/control_test.go000066400000000000000000000273661450131332300160530ustar00rootroot00000000000000package 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 TestControlSubtreeDelete(t *testing.T) { runControlTest(t, NewControlSubtreeDelete()) } 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 TestControlDirSync(t *testing.T) { runControlTest(t, NewRequestControlDirSync(DirSyncObjectSecurity, 1000, nil)) runControlTest(t, NewRequestControlDirSync(DirSyncObjectSecurity, 1000, []byte("I'm a cookie!"))) } 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 TestDescribeControlSubtreeDelete(t *testing.T) { runAddControlDescriptions(t, NewControlSubtreeDelete(), "Control Type (Subtree Delete Control)") } 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 TestDescribeControlDirSync(t *testing.T) { runAddControlDescriptions(t, NewRequestControlDirSync(DirSyncObjectSecurity, 1000, nil), "Control Type (DirSync)", "Criticality", "Control Value") } 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) } }) } } func TestControlServerSideSortingDecoding(t *testing.T) { control := NewControlServerSideSortingWithSortKeys([]*SortKey{{ MatchingRule: "foo", AttributeType: "foobar", Reverse: true, }, { MatchingRule: "foo", AttributeType: "foobar", Reverse: false, }, { MatchingRule: "", AttributeType: "", Reverse: false, }, { MatchingRule: "totoRule", AttributeType: "", Reverse: false, }, { MatchingRule: "", AttributeType: "totoType", Reverse: false, }}) controlDecoded, err := NewControlServerSideSorting(control.Encode()) if err != nil { t.Fatal(err) } if control.GetControlType() != controlDecoded.GetControlType() { t.Fatalf("control type mismatch: control:%s - decoded:%s", control.GetControlType(), controlDecoded.GetControlType()) } if len(control.SortKeys) != len(controlDecoded.SortKeys) { t.Fatalf("sort keys length mismatch (control: %d - decoded: %d)", len(control.SortKeys), len(controlDecoded.SortKeys)) } for i, sk := range control.SortKeys { dsk := controlDecoded.SortKeys[i] if sk.AttributeType != dsk.AttributeType { t.Fatalf("attribute type mismatch for sortkey %d", i) } if sk.MatchingRule != dsk.MatchingRule { t.Fatalf("matching rule mismatch for sortkey %d", i) } if sk.Reverse != dsk.Reverse { t.Fatalf("reverse mismtach for sortkey %d", i) } } } ldap-3.4.6/v3/debug.go000066400000000000000000000010551450131332300144050ustar00rootroot00000000000000package ldap import ( 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 { logger.Printf(format, args...) } } // PrintPacket dumps a packet. func (debug debugging) PrintPacket(packet *ber.Packet) { if debug { ber.WritePacket(logger.Writer(), packet) } } ldap-3.4.6/v3/del.go000066400000000000000000000024451450131332300140670ustar00rootroot00000000000000package ldap import ( "fmt" 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 { return fmt.Errorf("ldap: unexpected response: %d", packet.Children[1].Tag) } return nil } ldap-3.4.6/v3/dn.go000066400000000000000000000242101450131332300137160ustar00rootroot00000000000000package ldap import ( "bytes" enchex "encoding/hex" "errors" "fmt" "sort" "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 } // String returns a normalized string representation of this attribute type and // value pair which is the a lowercased join of the Type and Value with a "=". func (a *AttributeTypeAndValue) String() string { return strings.ToLower(a.Type) + "=" + a.encodeValue() } func (a *AttributeTypeAndValue) encodeValue() string { // Normalize the value first. // value := strings.ToLower(a.Value) value := a.Value encodedBuf := bytes.Buffer{} escapeChar := func(c byte) { encodedBuf.WriteByte('\\') encodedBuf.WriteByte(c) } escapeHex := func(c byte) { encodedBuf.WriteByte('\\') encodedBuf.WriteString(enchex.EncodeToString([]byte{c})) } for i := 0; i < len(value); i++ { char := value[i] if i == 0 && char == ' ' || char == '#' { // Special case leading space or number sign. escapeChar(char) continue } if i == len(value)-1 && char == ' ' { // Special case trailing space. escapeChar(char) continue } switch char { case '"', '+', ',', ';', '<', '>', '\\': // Each of these special characters must be escaped. escapeChar(char) continue } if char < ' ' || char > '~' { // All special character escapes are handled first // above. All bytes less than ASCII SPACE and all bytes // greater than ASCII TILDE must be hex-escaped. escapeHex(char) continue } // Any other character does not require escaping. encodedBuf.WriteByte(char) } return encodedBuf.String() } // RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514 type RelativeDN struct { Attributes []*AttributeTypeAndValue } // String returns a normalized string representation of this relative DN which // is the a join of all attributes (sorted in increasing order) with a "+". func (r *RelativeDN) String() string { attrs := make([]string, len(r.Attributes)) for i := range r.Attributes { attrs[i] = r.Attributes[i].String() } sort.Strings(attrs) return strings.Join(attrs, "+") } // DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514 type DN struct { RDNs []*RelativeDN } // String returns a normalized string representation of this DN which is the // join of all relative DNs with a ",". func (d *DN) String() string { rdns := make([]string, len(d.RDNs)) for i := range d.RDNs { rdns[i] = d.RDNs[i].String() } return strings.Join(rdns, ",") } // 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 == "": 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 == '+' || 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 == ',' || 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 } // EqualFold 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 } // EqualFold 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.6/v3/dn_test.go000066400000000000000000000200531450131332300147560ustar00rootroot00000000000000package 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 "}, }}, }}, `cn=john.doe;dc=example,dc=net`: {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"cn", "john.doe"}}}, {[]*AttributeTypeAndValue{{"dc", "example"}}}, {[]*AttributeTypeAndValue{{"dc", "net"}}}, }}, // Escaped `;` should not be treated as RDN `cn=john.doe\;weird name,dc=example,dc=net`: {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"cn", "john.doe;weird name"}}}, {[]*AttributeTypeAndValue{{"dc", "example"}}}, {[]*AttributeTypeAndValue{{"dc", "net"}}}, }}, `cn=ZXhhbXBsZVRleHQ=,dc=dummy,dc=com`: {[]*RelativeDN{ {[]*AttributeTypeAndValue{{"cn", "ZXhhbXBsZVRleHQ="}}}, {[]*AttributeTypeAndValue{{"dc", "dummy"}}}, {[]*AttributeTypeAndValue{{"dc", "com"}}}, }}, } 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\\20Doe, 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, }, // Test parsing of `;` for separating RDNs {"cn=john;dc=example,dc=com", "cn=john,dc=example,dc=com", true}, // missing values matter } 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 %q and %q expected %v, got %v", i, a, b, expected, actual) continue } if expected, actual := tc.Equal, b.Equal(a); expected != actual { t.Errorf("%d: when comparing %q and %q expected %v, got %v", i, a, b, expected, actual) continue } if expected, actual := a.Equal(b), a.String() == b.String(); expected != actual { t.Errorf("%d: when asserting string comparison of %q and %q expected equal %v, got %v", i, a, 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.6/v3/doc.go000066400000000000000000000001061450131332300140600ustar00rootroot00000000000000/* Package ldap provides basic LDAP v3 functionality. */ package ldap ldap-3.4.6/v3/error.go000066400000000000000000000313201450131332300144460ustar00rootroot00000000000000package 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()) } func (e *Error) Unwrap() error { return e.Err } // 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 { if ber.Type(response.Children[0].Tag) == ber.Type(ber.TagInteger) || ber.Type(response.Children[0].Tag) == ber.Type(ber.TagEnumerated) { resultCode := uint16(response.Children[0].Value.(int64)) if resultCode == 0 { // No error return nil } if ber.Type(response.Children[1].Tag) == ber.Type(ber.TagOctetString) && ber.Type(response.Children[2].Tag) == ber.Type(ber.TagOctetString) { 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.6/v3/error_test.go000066400000000000000000000147001450131332300155100ustar00rootroot00000000000000package ldap import ( "errors" "io" "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) } } // TestGetLDAPErrorInvalidResponse tests that responses with an unexpected ordering or combination of children // don't cause a panic. func TestGetLDAPErrorInvalidResponse(t *testing.T) { bindResponse := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindResponse, nil, "Bind Response") bindResponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "dc=example,dc=org", "matchedDN")) bindResponse.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(LDAPResultInvalidCredentials), "resultCode")) bindResponse.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(LDAPResultInvalidCredentials), "resultCode")) 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 != ErrorNetwork { t.Errorf("Got incorrect error code in LDAP error; got %v, expected %v", ldapError.ResultCode, ErrorNetwork) } } func TestErrorIs(t *testing.T) { err := NewError(ErrorNetwork, io.EOF) if !errors.Is(err, io.EOF) { t.Errorf("Expected an io.EOF error: %v", err) } } func TestErrorAs(t *testing.T) { var netErr net.InvalidAddrError = "invalid addr" err := NewError(ErrorNetwork, netErr) var target net.InvalidAddrError ok := errors.As(err, &target) if !ok { t.Error("Expected an InvalidAddrError") } } // 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.6/v3/examples_moddn_test.go000066400000000000000000000043331450131332300173570ustar00rootroot00000000000000package 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.6/v3/examples_test.go000066400000000000000000000351271450131332300162030ustar00rootroot00000000000000package ldap import ( "context" "crypto/tls" "crypto/x509" "fmt" "io/ioutil" "log" "time" ) // 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 search asynchronously func ExampleConn_SearchAsync() { l, err := DialURL(fmt.Sprintf("%s:%d", "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, ) ctx, cancel := context.WithCancel(context.Background()) defer cancel() r := l.SearchAsync(ctx, searchRequest, 64) for r.Next() { entry := r.Entry() fmt.Printf("%s has DN %s\n", entry.GetAttributeValue("cn"), entry.DN) } if err := r.Err(); err != nil { log.Fatal(err) } } // This example demonstrates how to do syncrepl (persistent search) func ExampleConn_Syncrepl() { l, err := DialURL(fmt.Sprintf("%s:%d", "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, ) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mode := SyncRequestModeRefreshAndPersist var cookie []byte = nil r := l.Syncrepl(ctx, searchRequest, 64, mode, cookie, false) for r.Next() { entry := r.Entry() if entry != nil { fmt.Printf("%s has DN %s\n", entry.GetAttributeValue("cn"), entry.DN) } controls := r.Controls() if len(controls) != 0 { fmt.Printf("%s", controls) } } if err := r.Err(); err != nil { log.Fatal(err) } } // 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 DirSync to manually execute a // DirSync search request func ExampleConn_DirSync() { conn, err := Dial("tcp", "ad.example.org:389") if err != nil { log.Fatalf("Failed to connect: %s\n", err) } defer conn.Close() _, err = conn.SimpleBind(&SimpleBindRequest{ Username: "cn=Some User,ou=people,dc=example,dc=org", Password: "MySecretPass", }) if err != nil { log.Fatalf("failed to bind: %s", err) } req := &SearchRequest{ BaseDN: `DC=example,DC=org`, Filter: `(&(objectClass=person)(!(objectClass=computer)))`, Attributes: []string{"*"}, Scope: ScopeWholeSubtree, } // This is the initial sync with all entries matching the filter doMore := true var cookie []byte for doMore { res, err := conn.DirSync(req, DirSyncObjectSecurity, 1000, cookie) if err != nil { log.Fatalf("failed to search: %s", err) } for _, entry := range res.Entries { entry.Print() } ctrl := FindControl(res.Controls, ControlTypeDirSync) if ctrl == nil || ctrl.(*ControlDirSync).Flags == 0 { doMore = false } cookie = ctrl.(*ControlDirSync).Cookie } // We're done with the initial sync. Now pull every 15 seconds for the // updated entries - note that you get just the changes, not a full entry. for { res, err := conn.DirSync(req, DirSyncObjectSecurity, 1000, cookie) if err != nil { log.Fatalf("failed to search: %s", err) } for _, entry := range res.Entries { entry.Print() } time.Sleep(15 * time.Second) } } // This example demonstrates how to use DirSync search asynchronously func ExampleConn_DirSyncAsync() { conn, err := Dial("tcp", "ad.example.org:389") if err != nil { log.Fatalf("Failed to connect: %s\n", err) } defer conn.Close() _, err = conn.SimpleBind(&SimpleBindRequest{ Username: "cn=Some User,ou=people,dc=example,dc=org", Password: "MySecretPass", }) if err != nil { log.Fatalf("failed to bind: %s", err) } req := &SearchRequest{ BaseDN: `DC=example,DC=org`, Filter: `(&(objectClass=person)(!(objectClass=computer)))`, Attributes: []string{"*"}, Scope: ScopeWholeSubtree, } ctx, cancel := context.WithCancel(context.Background()) defer cancel() var cookie []byte = nil r := conn.DirSyncAsync(ctx, req, 64, DirSyncObjectSecurity, 1000, cookie) for r.Next() { entry := r.Entry() if entry != nil { entry.Print() } controls := r.Controls() if len(controls) != 0 { fmt.Printf("%s", controls) } } if err := r.Err(); err != nil { log.Fatal(err) } } // This example demonstrates how to use EXTERNAL SASL with TLS client certificates. func ExampleConn_ExternalBind() { ldapCert := "/path/to/cert.pem" ldapKey := "/path/to/key.pem" 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.6/v3/examples_windows_test.go000066400000000000000000000014671450131332300177550ustar00rootroot00000000000000//go:build windows // +build windows package ldap import ( "log" "github.com/go-ldap/ldap/v3/gssapi" ) // This example demonstrates passwordless bind using the current process' user // credentials on Windows (SASL GSSAPI mechanism bind with SSPI client). func ExampleConn_SSPIClient_GSSAPIBind() { // Windows only: Create a GSSAPIClient using Windows built-in SSPI lib // (secur32.dll). // This will use the credentials of the current process' user. sspiClient, err := gssapi.NewSSPIClient() if err != nil { log.Fatal(err) } defer sspiClient.Close() l, err := DialURL("ldap://ldap.example.com:389") if err != nil { log.Fatal(err) } defer l.Close() // Bind using supplied GSSAPIClient implementation err = l.GSSAPIBind(sspiClient, "ldap/ldap.example.com", "") if err != nil { log.Fatal(err) } } ldap-3.4.6/v3/filter.go000066400000000000000000000374201450131332300146110ustar00rootroot00000000000000package 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.Contains(condition.Bytes(), _SymbolAny): 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.6/v3/filter_test.go000066400000000000000000000170401450131332300156440ustar00rootroot00000000000000package 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.6/v3/go.mod000066400000000000000000000005151450131332300140760ustar00rootroot00000000000000module github.com/go-ldap/ldap/v3 go 1.14 require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 github.com/go-asn1-ber/asn1-ber v1.5.5 github.com/google/uuid v1.3.1 github.com/stretchr/testify v1.8.0 golang.org/x/crypto v0.13.0 // indirect ) ldap-3.4.6/v3/go.sum000066400000000000000000000132271450131332300141270ustar00rootroot00000000000000github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ldap-3.4.6/v3/gssapi/000077500000000000000000000000001450131332300142555ustar00rootroot00000000000000ldap-3.4.6/v3/gssapi/sspi.go000066400000000000000000000145361450131332300155730ustar00rootroot00000000000000//go:build windows // +build windows package gssapi import ( "bytes" "encoding/binary" "fmt" "github.com/alexbrainman/sspi" "github.com/alexbrainman/sspi/kerberos" ) // SSPIClient implements ldap.GSSAPIClient interface. // Depends on secur32.dll. type SSPIClient struct { creds *sspi.Credentials ctx *kerberos.ClientContext } // NewSSPIClient returns a client with credentials of the current user. func NewSSPIClient() (*SSPIClient, error) { creds, err := kerberos.AcquireCurrentUserCredentials() if err != nil { return nil, err } return NewSSPIClientWithCredentials(creds), nil } // NewSSPIClientWithCredentials returns a client with the provided credentials. func NewSSPIClientWithCredentials(creds *sspi.Credentials) *SSPIClient { return &SSPIClient{ creds: creds, } } // NewSSPIClientWithUserCredentials returns a client using the provided user's // credentials. func NewSSPIClientWithUserCredentials(domain, username, password string) (*SSPIClient, error) { creds, err := kerberos.AcquireUserCredentials(domain, username, password) if err != nil { return nil, err } return &SSPIClient{ creds: creds, }, nil } // Close deletes any established secure context and closes the client. func (c *SSPIClient) Close() error { err1 := c.DeleteSecContext() err2 := c.creds.Release() if err1 != nil { return err1 } if err2 != nil { return err2 } return nil } // DeleteSecContext destroys any established secure context. func (c *SSPIClient) DeleteSecContext() error { return c.ctx.Release() } // InitSecContext initiates the establishment of a security context for // GSS-API between the client and server. // See RFC 4752 section 3.1. func (c *SSPIClient) InitSecContext(target string, token []byte) ([]byte, bool, error) { sspiFlags := uint32(sspi.ISC_REQ_INTEGRITY | sspi.ISC_REQ_CONFIDENTIALITY | sspi.ISC_REQ_MUTUAL_AUTH) switch token { case nil: ctx, completed, output, err := kerberos.NewClientContextWithFlags(c.creds, target, sspiFlags) if err != nil { return nil, false, err } c.ctx = ctx return output, !completed, nil default: completed, output, err := c.ctx.Update(token) if err != nil { return nil, false, err } if err := c.ctx.VerifyFlags(); err != nil { return nil, false, fmt.Errorf("error verifying flags: %v", err) } return output, !completed, nil } } // NegotiateSaslAuth performs the last step of the SASL handshake. // See RFC 4752 section 3.1. func (c *SSPIClient) NegotiateSaslAuth(token []byte, authzid string) ([]byte, error) { // Using SSPI rather than of GSSAPI, relevant documentation of differences here: // https://learn.microsoft.com/en-us/windows/win32/secauthn/sspi-kerberos-interoperability-with-gssapi // KERB_WRAP_NO_ENCRYPT (SECQOP_WRAP_NO_ENCRYPT) flag indicates Wrap and Unwrap // should only sign & verify (not encrypt & decrypt). const KERB_WRAP_NO_ENCRYPT = 0x80000001 // https://learn.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-decryptmessage flags, inputPayload, err := c.ctx.DecryptMessage(token, 0) if err != nil { return nil, fmt.Errorf("error decrypting message: %w", err) } if flags&KERB_WRAP_NO_ENCRYPT == 0 { // Encrypted message, this is unexpected. return nil, fmt.Errorf("message encrypted") } // `bytes` describes available security context: // "the first octet of resulting cleartext as a // bit-mask specifying the security layers supported by the server and // the second through fourth octets as the maximum size output_message // the server is able to receive (in network byte order). If the // resulting cleartext is not 4 octets long, the client fails the // negotiation. The client verifies that the server maximum buffer is 0 // if the server does not advertise support for any security layer." // From https://www.rfc-editor.org/rfc/rfc4752#section-3.1 if len(inputPayload) != 4 { return nil, fmt.Errorf("bad server token") } if inputPayload[0] == 0x0 && !bytes.Equal(inputPayload, []byte{0x0, 0x0, 0x0, 0x0}) { return nil, fmt.Errorf("bad server token") } // Security layers https://www.rfc-editor.org/rfc/rfc4422#section-3.7 // https://www.rfc-editor.org/rfc/rfc4752#section-3.3 // supportNoSecurity := input[0] & 0b00000001 // supportIntegrity := input[0] & 0b00000010 // supportPrivacy := input[0] & 0b00000100 selectedSec := 0 // Disabled var maxSecMsgSize uint32 if selectedSec != 0 { maxSecMsgSize, _, _, _, err = c.ctx.Sizes() if err != nil { return nil, fmt.Errorf("error getting security context max message size: %w", err) } } // https://learn.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-encryptmessage inputPayload, err = c.ctx.EncryptMessage(handshakePayload(byte(selectedSec), maxSecMsgSize, []byte(authzid)), KERB_WRAP_NO_ENCRYPT, 0) if err != nil { return nil, fmt.Errorf("error encrypting message: %w", err) } return inputPayload, nil } func handshakePayload(secLayer byte, maxSize uint32, authzid []byte) []byte { // construct payload and send unencrypted: // "The client then constructs data, with the first octet containing the // bit-mask specifying the selected security layer, the second through // fourth octets containing in network byte order the maximum size // output_message the client is able to receive (which MUST be 0 if the // client does not support any security layer), and the remaining octets // containing the UTF-8 [UTF8] encoded authorization identity. // (Implementation note: The authorization identity is not terminated // with the zero-valued (%x00) octet (e.g., the UTF-8 encoding of the // NUL (U+0000) character)). The client passes the data to GSS_Wrap // with conf_flag set to FALSE and responds with the generated // output_message. The client can then consider the server // authenticated." // From https://www.rfc-editor.org/rfc/rfc4752#section-3.1 // Client picks security layer to use, 0 is disabled. var selectedSecurity byte = secLayer var truncatedSize uint32 // must be 0 if secLayer is 0 if selectedSecurity != 0 { // Only 3 bytes to describe the max size, set the maximum. truncatedSize = 0b00000000_11111111_11111111_11111111 if truncatedSize > maxSize { truncatedSize = maxSize } } payload := make([]byte, 4, 4+len(authzid)) binary.BigEndian.PutUint32(payload, truncatedSize) payload[0] = selectedSecurity // Overwrites most significant byte of `maxSize` payload = append(payload, []byte(authzid)...) return payload } ldap-3.4.6/v3/ldap.go000066400000000000000000000304041450131332300142370ustar00rootroot00000000000000package ldap import ( "fmt" "io/ioutil" "log" "os" "strings" 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 ApplicationIntermediateResponse = 25 ) // 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", ApplicationIntermediateResponse: "Intermediate 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", } var logger = log.New(os.Stderr, "", log.LstdFlags) // Logger allows clients to override the default logger func Logger(l *log.Logger) { logger = l } // 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) } // EscapeDN escapes distinguished names as described in RFC4514. Characters in the // set `"+,;<>\` are escaped by prepending a backslash, which is also done for trailing // spaces or a leading `#`. Null bytes are replaced with `\00`. func EscapeDN(dn string) string { if dn == "" { return "" } builder := strings.Builder{} for i, r := range dn { // Escape leading and trailing spaces if (i == 0 || i == len(dn)-1) && r == ' ' { builder.WriteRune('\\') builder.WriteRune(r) continue } // Escape leading '#' if i == 0 && r == '#' { builder.WriteRune('\\') builder.WriteRune(r) continue } // Escape characters as defined in RFC4514 switch r { case '"', '+', ',', ';', '<', '>', '\\': builder.WriteRune('\\') builder.WriteRune(r) case '\x00': // Null byte may not be escaped by a leading backslash builder.WriteString("\\00") default: builder.WriteRune(r) } } return builder.String() } ldap-3.4.6/v3/ldap_test.go000066400000000000000000000277501450131332300153100ustar00rootroot00000000000000package ldap import ( "context" "crypto/tls" "log" "testing" ber "github.com/go-asn1-ber/asn1-ber" ) const ( ldapServer = "ldap://ldap.itd.umich.edu:389" ldapsServer = "ldaps://ldap.itd.umich.edu:636" 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)}) _, 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) } }) } } func TestEscapeDN(t *testing.T) { tests := []struct { name string dn string want string }{ {name: "emptyString", dn: "", want: ""}, {name: "comma", dn: "test,user", want: "test\\,user"}, {name: "numberSign", dn: "#test#user#", want: "\\#test#user#"}, {name: "backslash", dn: "\\test\\user\\", want: "\\\\test\\\\user\\\\"}, {name: "whitespaces", dn: " test user ", want: "\\ test user \\ "}, {name: "nullByte", dn: "\u0000te\x00st\x00user" + string(rune(0)), want: "\\00te\\00st\\00user\\00"}, {name: "variousCharacters", dn: "test\"+,;<>\\-_user", want: "test\\\"\\+\\,\\;\\<\\>\\\\-_user"}, {name: "multiByteRunes", dn: "test\u0391user ", want: "test\u0391user\\ "}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := EscapeDN(tt.dn); got != tt.want { t.Errorf("EscapeDN(%s) = %s, expected %s", tt.dn, got, tt.want) } }) } } func TestSearchAsync(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[2], attributes, nil) srs := make([]*Entry, 0) ctx := context.Background() r := l.SearchAsync(ctx, searchRequest, 64) for r.Next() { srs = append(srs, r.Entry()) } if err := r.Err(); err != nil { log.Fatal(err) } t.Logf("TestSearcAsync: %s -> num of entries = %d", searchRequest.Filter, len(srs)) } func TestSearchAsyncAndCancel(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[2], attributes, nil) cancelNum := 10 srs := make([]*Entry, 0) ctx, cancel := context.WithCancel(context.Background()) defer cancel() r := l.SearchAsync(ctx, searchRequest, 0) for r.Next() { srs = append(srs, r.Entry()) if len(srs) == cancelNum { cancel() } } if err := r.Err(); err != nil { log.Fatal(err) } if len(srs) > cancelNum+3 { // the cancellation process is asynchronous, // so it might get some entries after calling cancel() t.Errorf("Got entries %d, expected < %d", len(srs), cancelNum+3) } t.Logf("TestSearchAsyncAndCancel: %s -> num of entries = %d", searchRequest.Filter, len(srs)) } ldap-3.4.6/v3/moddn.go000066400000000000000000000060301450131332300144160ustar00rootroot00000000000000package ldap import ( "fmt" 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 { return fmt.Errorf("ldap: unexpected response: %d", packet.Children[1].Tag) } return nil } ldap-3.4.6/v3/modify.go000066400000000000000000000125651450131332300146160ustar00rootroot00000000000000package ldap import ( "errors" "fmt" 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 { return fmt.Errorf("ldap: 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 // Referral is the returned referral Referral string } // 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: if err = GetLDAPError(packet); err != nil { result.Referral = getReferral(err, packet) return result, 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.6/v3/passwdmodify.go000066400000000000000000000107141450131332300160320ustar00rootroot00000000000000package 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 { if err = GetLDAPError(packet); err != nil { result.Referral = getReferral(err, packet) 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 == ber.TagEmbeddedPDV { passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes()) if len(passwordModifyResponseValue.Children) == 1 { if passwordModifyResponseValue.Children[0].Tag == ber.TagEOC { result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes()) } } } } return result, nil } ldap-3.4.6/v3/request.go000066400000000000000000000062031450131332300150070ustar00rootroot00000000000000package 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 is returned if doRequest is called with a nil connection. 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 } func getReferral(err error, packet *ber.Packet) (referral string) { if !IsErrorWithCode(err, LDAPResultReferral) { return "" } if len(packet.Children) < 2 { return "" } // The packet Tag itself (of child 2) is generally a ber.TagObjectDescriptor with referrals however OpenLDAP // seemingly returns a ber.Tag.GeneralizedTime. Every currently tested LDAP server which returns referrals returns // an ASN.1 BER packet with the Type of ber.TypeConstructed and Class of ber.ClassApplication however. Thus this // check expressly checks these fields instead. // // Related Issues: // - https://github.com/authelia/authelia/issues/4199 (downstream) if len(packet.Children[1].Children) == 0 || (packet.Children[1].TagType != ber.TypeConstructed || packet.Children[1].ClassType != ber.ClassApplication) { return "" } var ok bool for _, child := range packet.Children[1].Children { // The referral URI itself should be contained within a child which has a Tag of ber.BitString or // ber.TagPrintableString, and the Type of ber.TypeConstructed and the Class of ClassContext. As soon as any of // these conditions is not true we can skip this child. if (child.Tag != ber.TagBitString && child.Tag != ber.TagPrintableString) || child.TagType != ber.TypeConstructed || child.ClassType != ber.ClassContext { continue } if referral, ok = child.Children[0].Value.(string); ok { return referral } } return "" } ldap-3.4.6/v3/response.go000066400000000000000000000122161450131332300151560ustar00rootroot00000000000000package ldap import ( "context" "errors" "fmt" ber "github.com/go-asn1-ber/asn1-ber" ) // Response defines an interface to get data from an LDAP server type Response interface { Entry() *Entry Referral() string Controls() []Control Err() error Next() bool } type searchResponse struct { conn *Conn ch chan *SearchSingleResult entry *Entry referral string controls []Control err error } // Entry returns an entry from the given search request func (r *searchResponse) Entry() *Entry { return r.entry } // Referral returns a referral from the given search request func (r *searchResponse) Referral() string { return r.referral } // Controls returns controls from the given search request func (r *searchResponse) Controls() []Control { return r.controls } // Err returns an error when the given search request was failed func (r *searchResponse) Err() error { return r.err } // Next returns whether next data exist or not func (r *searchResponse) Next() bool { res, ok := <-r.ch if !ok { return false } if res == nil { return false } r.err = res.Error if r.err != nil { return false } r.entry = res.Entry r.referral = res.Referral r.controls = res.Controls return true } func (r *searchResponse) start(ctx context.Context, searchRequest *SearchRequest) { go func() { defer func() { close(r.ch) if err := recover(); err != nil { r.conn.err = fmt.Errorf("ldap: recovered panic in searchResponse: %v", err) } }() if r.conn.IsClosing() { return } packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, r.conn.nextMessageID(), "MessageID")) // encode search request err := searchRequest.appendTo(packet) if err != nil { r.ch <- &SearchSingleResult{Error: err} return } r.conn.Debug.PrintPacket(packet) msgCtx, err := r.conn.sendMessage(packet) if err != nil { r.ch <- &SearchSingleResult{Error: err} return } defer r.conn.finishMessage(msgCtx) foundSearchSingleResultDone := false for !foundSearchSingleResultDone { select { case <-ctx.Done(): r.conn.Debug.Printf("%d: %s", msgCtx.id, ctx.Err().Error()) return default: r.conn.Debug.Printf("%d: waiting for response", msgCtx.id) packetResponse, ok := <-msgCtx.responses if !ok { err := NewError(ErrorNetwork, errors.New("ldap: response channel closed")) r.ch <- &SearchSingleResult{Error: err} return } packet, err = packetResponse.ReadPacket() r.conn.Debug.Printf("%d: got response %p", msgCtx.id, packet) if err != nil { r.ch <- &SearchSingleResult{Error: err} return } if r.conn.Debug { if err := addLDAPDescriptions(packet); err != nil { r.ch <- &SearchSingleResult{Error: err} return } ber.PrintPacket(packet) } switch packet.Children[1].Tag { case ApplicationSearchResultEntry: result := &SearchSingleResult{ Entry: &Entry{ DN: packet.Children[1].Children[0].Value.(string), Attributes: unpackAttributes(packet.Children[1].Children[1].Children), }, } if len(packet.Children) != 3 { r.ch <- result continue } decoded, err := DecodeControl(packet.Children[2].Children[0]) if err != nil { werr := fmt.Errorf("failed to decode search result entry: %w", err) result.Error = werr r.ch <- result return } result.Controls = append(result.Controls, decoded) r.ch <- result case ApplicationSearchResultDone: if err := GetLDAPError(packet); err != nil { r.ch <- &SearchSingleResult{Error: err} return } if len(packet.Children) == 3 { result := &SearchSingleResult{} for _, child := range packet.Children[2].Children { decodedChild, err := DecodeControl(child) if err != nil { werr := fmt.Errorf("failed to decode child control: %w", err) r.ch <- &SearchSingleResult{Error: werr} return } result.Controls = append(result.Controls, decodedChild) } r.ch <- result } foundSearchSingleResultDone = true case ApplicationSearchResultReference: ref := packet.Children[1].Children[0].Value.(string) r.ch <- &SearchSingleResult{Referral: ref} case ApplicationIntermediateResponse: decoded, err := DecodeControl(packet.Children[1]) if err != nil { werr := fmt.Errorf("failed to decode intermediate response: %w", err) r.ch <- &SearchSingleResult{Error: werr} return } result := &SearchSingleResult{} result.Controls = append(result.Controls, decoded) r.ch <- result default: err := fmt.Errorf("unknown tag: %d", packet.Children[1].Tag) r.ch <- &SearchSingleResult{Error: err} return } } } r.conn.Debug.Printf("%d: returning", msgCtx.id) }() } func newSearchResponse(conn *Conn, bufferSize int) *searchResponse { var ch chan *SearchSingleResult if bufferSize > 0 { ch = make(chan *SearchSingleResult, bufferSize) } else { ch = make(chan *SearchSingleResult) } return &searchResponse{ conn: conn, ch: ch, } } ldap-3.4.6/v3/search.go000066400000000000000000000530001450131332300145610ustar00rootroot00000000000000package ldap import ( "context" "errors" "fmt" "reflect" "sort" "strconv" "strings" "time" 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) } } // Describe the tag to use for struct field tags const decoderTagName = "ldap" // readTag will read the reflect.StructField value for // the key defined in decoderTagName. If omitempty is // specified, the field may not be filled. func readTag(f reflect.StructField) (string, bool) { val, ok := f.Tag.Lookup(decoderTagName) if !ok { return f.Name, false } opts := strings.Split(val, ",") omit := false if len(opts) == 2 { omit = opts[1] == "omitempty" } return opts[0], omit } // Unmarshal parses the Entry in the value pointed to by i // // Currently, this methods only supports struct fields of type // string, []string, int, int64, []byte, *DN, []*DN or time.Time. Other field types // will not be regarded. If the field type is a string or int but multiple // attribute values are returned, the first value will be used to fill the field. // // Example: // // type UserEntry struct { // // Fields with the tag key `dn` are automatically filled with the // // objects distinguishedName. This can be used multiple times. // DN string `ldap:"dn"` // // // This field will be filled with the attribute value for // // userPrincipalName. An attribute can be read into a struct field // // multiple times. Missing attributes will not result in an error. // UserPrincipalName string `ldap:"userPrincipalName"` // // // memberOf may have multiple values. If you don't // // know the amount of attribute values at runtime, use a string array. // MemberOf []string `ldap:"memberOf"` // // // ID is an integer value, it will fail unmarshaling when the given // // attribute value cannot be parsed into an integer. // ID int `ldap:"id"` // // // LongID is similar to ID but uses an int64 instead. // LongID int64 `ldap:"longId"` // // // Data is similar to MemberOf a slice containing all attribute // // values. // Data []byte `ldap:"data"` // // // Time is parsed with the generalizedTime spec into a time.Time // Created time.Time `ldap:"createdTimestamp"` // // // *DN is parsed with the ParseDN // Owner *ldap.DN `ldap:"owner"` // // // []*DN is parsed with the ParseDN // Children []*ldap.DN `ldap:"children"` // // // This won't work, as the field is not of type string. For this // // to work, you'll have to temporarily store the result in string // // (or string array) and convert it to the desired type afterwards. // UserAccountControl uint32 `ldap:"userPrincipalName"` // } // user := UserEntry{} // // if err := result.Unmarshal(&user); err != nil { // // ... // } func (e *Entry) Unmarshal(i interface{}) (err error) { // Make sure it's a ptr if vo := reflect.ValueOf(i).Kind(); vo != reflect.Ptr { return fmt.Errorf("ldap: cannot use %s, expected pointer to a struct", vo) } sv, st := reflect.ValueOf(i).Elem(), reflect.TypeOf(i).Elem() // Make sure it's pointing to a struct if sv.Kind() != reflect.Struct { return fmt.Errorf("ldap: expected pointer to a struct, got %s", sv.Kind()) } for n := 0; n < st.NumField(); n++ { // Holds struct field value and type fv, ft := sv.Field(n), st.Field(n) // skip unexported fields if ft.PkgPath != "" { continue } // omitempty can be safely discarded, as it's not needed when unmarshalling fieldTag, _ := readTag(ft) // Fill the field with the distinguishedName if the tag key is `dn` if fieldTag == "dn" { fv.SetString(e.DN) continue } values := e.GetAttributeValues(fieldTag) if len(values) == 0 { continue } switch fv.Interface().(type) { case []string: for _, item := range values { fv.Set(reflect.Append(fv, reflect.ValueOf(item))) } case string: fv.SetString(values[0]) case []byte: fv.SetBytes([]byte(values[0])) case int, int64: intVal, err := strconv.ParseInt(values[0], 10, 64) if err != nil { return fmt.Errorf("ldap: could not parse value '%s' into int field", values[0]) } fv.SetInt(intVal) case time.Time: t, err := ber.ParseGeneralizedTime([]byte(values[0])) if err != nil { return fmt.Errorf("ldap: could not parse value '%s' into time.Time field", values[0]) } fv.Set(reflect.ValueOf(t)) case *DN: dn, err := ParseDN(values[0]) if err != nil { return fmt.Errorf("ldap: could not parse value '%s' into *ldap.DN field", values[0]) } fv.Set(reflect.ValueOf(dn)) case []*DN: for _, item := range values { dn, err := ParseDN(item) if err != nil { return fmt.Errorf("ldap: could not parse value '%s' into *ldap.DN field", item) } fv.Set(reflect.Append(fv, reflect.ValueOf(dn))) } default: return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64, []byte, *DN, []*DN or time.Time, got %v", ft.Type) } } return } // 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) } } // appendTo appends all entries of `s` to `r` func (s *SearchResult) appendTo(r *SearchResult) { r.Entries = append(r.Entries, s.Entries...) r.Referrals = append(r.Referrals, s.Referrals...) r.Controls = append(r.Controls, s.Controls...) } // SearchSingleResult holds the server's single entry response to a search request type SearchSingleResult struct { // Entry is the returned entry Entry *Entry // Referral is the returned referral Referral string // Controls are the returned controls Controls []Control // Error is set when the search request was failed Error error } // Print outputs a human-readable description func (s *SearchSingleResult) Print() { s.Entry.Print() } // PrettyPrint outputs a human-readable description with indenting func (s *SearchSingleResult) PrettyPrint(indent int) { s.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) if result != nil { result.appendTo(searchResult) } else { if err == nil { // We have to do this beautifulness in case something absolutely strange happens, which // should only occur in case there is no packet, but also no error. return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received")) } } if err != nil { // If an error occurred, all results that have been received so far will be returned return searchResult, err } 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 if _, err := l.Search(searchRequest); err != nil { return searchResult, err } } 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)) } } } // SearchAsync performs a search request and returns all search results asynchronously. // This means you get all results until an error happens (or the search successfully finished), // e.g. for size / time limited requests all are recieved until the limit is reached. // To stop the search, call cancel function of the context. func (l *Conn) SearchAsync( ctx context.Context, searchRequest *SearchRequest, bufferSize int) Response { r := newSearchResponse(l, bufferSize) r.start(ctx, searchRequest) return r } // Syncrepl is a short name for LDAP Sync Replication engine that works on the // consumer-side. This can perform a persistent search and returns an entry // when the entry is updated on the server side. // To stop the search, call cancel function of the context. func (l *Conn) Syncrepl( ctx context.Context, searchRequest *SearchRequest, bufferSize int, mode ControlSyncRequestMode, cookie []byte, reloadHint bool, ) Response { control := NewControlSyncRequest(mode, cookie, reloadHint) searchRequest.Controls = append(searchRequest.Controls, control) r := newSearchResponse(l, bufferSize) r.start(ctx, searchRequest) return r } // 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 } // DirSync does a Search with dirSync Control. func (l *Conn) DirSync( searchRequest *SearchRequest, flags int64, maxAttrCount int64, cookie []byte, ) (*SearchResult, error) { control := FindControl(searchRequest.Controls, ControlTypeDirSync) if control == nil { c := NewRequestControlDirSync(flags, maxAttrCount, cookie) searchRequest.Controls = append(searchRequest.Controls, c) } else { c := control.(*ControlDirSync) if c.Flags != flags { return nil, fmt.Errorf("flags given in search request (%d) conflicts with flags given in search call (%d)", c.Flags, flags) } if c.MaxAttrCount != maxAttrCount { return nil, fmt.Errorf("MaxAttrCnt given in search request (%d) conflicts with maxAttrCount given in search call (%d)", c.MaxAttrCount, maxAttrCount) } } searchResult, err := l.Search(searchRequest) l.Debug.Printf("Looking for result...") if err != nil { return nil, err } if searchResult == nil { return nil, NewError(ErrorNetwork, errors.New("ldap: packet not received")) } l.Debug.Printf("Looking for DirSync Control...") resultControl := FindControl(searchResult.Controls, ControlTypeDirSync) if resultControl == nil { l.Debug.Printf("Could not find dirSyncControl control. Breaking...") return searchResult, nil } cookie = resultControl.(*ControlDirSync).Cookie if len(cookie) == 0 { l.Debug.Printf("Could not find cookie. Breaking...") return searchResult, nil } return searchResult, nil } // DirSyncDirSyncAsync performs a search request and returns all search results // asynchronously. This is efficient when the server returns lots of entries. func (l *Conn) DirSyncAsync( ctx context.Context, searchRequest *SearchRequest, bufferSize int, flags, maxAttrCount int64, cookie []byte, ) Response { control := NewRequestControlDirSync(flags, maxAttrCount, cookie) searchRequest.Controls = append(searchRequest.Controls, control) r := newSearchResponse(l, bufferSize) r.start(ctx, searchRequest) return r } ldap-3.4.6/v3/search_test.go000066400000000000000000000116501450131332300156250ustar00rootroot00000000000000package ldap import ( "reflect" "testing" "time" "github.com/stretchr/testify/assert" ) // 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") } } func TestEntry_Unmarshal(t *testing.T) { t.Run("passing a struct should fail", func(t *testing.T) { entry := &Entry{} type toStruct struct{} result := toStruct{} err := entry.Unmarshal(result) assert.NotNil(t, err) }) t.Run("passing a ptr to string should fail", func(t *testing.T) { entry := &Entry{} str := "foo" err := entry.Unmarshal(&str) assert.NotNil(t, err) }) t.Run("user struct be decoded", func(t *testing.T) { entry := &Entry{ DN: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com", Attributes: []*EntryAttribute{ { Name: "cn", Values: []string{"mario"}, ByteValues: nil, }, { Name: "mail", Values: []string{"mario@go-ldap.com"}, ByteValues: nil, }, // Tests int value. { Name: "id", Values: []string{"2147483647"}, ByteValues: nil, }, // Tests int64 value. { Name: "longId", Values: []string{"9223372036854775807"}, ByteValues: nil, }, // Tests []byte value. { Name: "data", Values: []string{"data"}, ByteValues: [][]byte{ []byte("data"), }, }, // Tests time.Time value. { Name: "createdTimestamp", Values: []string{"202305041930Z"}, ByteValues: nil, }, // Tests *DN value { Name: "owner", Values: []string{"uid=foo,dc=example,dc=org"}, ByteValues: nil, }, // Tests []*DN value { Name: "children", Values: []string{"uid=bar,dc=example,dc=org", "uid=baz,dc=example,dc=org"}, ByteValues: nil, }, }, } type User struct { Dn string `ldap:"dn"` Cn string `ldap:"cn"` Mail string `ldap:"mail"` ID int `ldap:"id"` LongID int64 `ldap:"longId"` Data []byte `ldap:"data"` Created time.Time `ldap:"createdTimestamp"` Owner *DN `ldap:"owner"` Children []*DN `ldap:"children"` } created, err := time.Parse("200601021504Z", "202305041930Z") if err != nil { t.Errorf("failed to parse ref time: %s", err) } owner, err := ParseDN("uid=foo,dc=example,dc=org") if err != nil { t.Errorf("failed to parse ref DN: %s", err) } var children []*DN for _, child := range []string{"uid=bar,dc=example,dc=org", "uid=baz,dc=example,dc=org"} { dn, err := ParseDN(child) if err != nil { t.Errorf("failed to parse child ref DN: %s", err) } children = append(children, dn) } expect := &User{ Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com", Cn: "mario", Mail: "mario@go-ldap.com", ID: 2147483647, LongID: 9223372036854775807, Data: []byte("data"), Created: created, Owner: owner, Children: children, } result := &User{} err = entry.Unmarshal(result) assert.Nil(t, err) assert.Equal(t, expect, result) }) t.Run("group struct be decoded", func(t *testing.T) { entry := &Entry{ DN: "cn=DREAM_TEAM,ou=Groups,dc=go-ldap,dc=github,dc=com", Attributes: []*EntryAttribute{ { Name: "cn", Values: []string{"DREAM_TEAM"}, ByteValues: nil, }, { Name: "member", Values: []string{"mario", "luigi", "browser"}, ByteValues: nil, }, }, } type Group struct { DN string `ldap:"dn" yaml:"dn" json:"dn"` CN string `ldap:"cn" yaml:"cn" json:"cn"` Members []string `ldap:"member"` } expect := &Group{ DN: "cn=DREAM_TEAM,ou=Groups,dc=go-ldap,dc=github,dc=com", CN: "DREAM_TEAM", Members: []string{"mario", "luigi", "browser"}, } result := &Group{} err := entry.Unmarshal(result) assert.Nil(t, err) assert.Equal(t, expect, result) }) } ldap-3.4.6/v3/unbind.go000066400000000000000000000020071450131332300145740ustar00rootroot00000000000000package ldap import ( "errors" ber "github.com/go-asn1-ber/asn1-ber" ) // ErrConnUnbound is returned when Unbind is called on an already closing connection. 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.6/v3/whoami.go000066400000000000000000000046761450131332300146170ustar00rootroot00000000000000package 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.6/whoami.go000066400000000000000000000046761450131332300142670ustar00rootroot00000000000000package 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 }