summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mattermost/ldap
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/mattermost/ldap')
-rw-r--r--vendor/github.com/mattermost/ldap/.gitignore0
-rw-r--r--vendor/github.com/mattermost/ldap/.travis.yml32
-rw-r--r--vendor/github.com/mattermost/ldap/CONTRIBUTING.md12
-rw-r--r--vendor/github.com/mattermost/ldap/LICENSE22
-rw-r--r--vendor/github.com/mattermost/ldap/Makefile82
-rw-r--r--vendor/github.com/mattermost/ldap/README.md61
-rw-r--r--vendor/github.com/mattermost/ldap/add.go100
-rw-r--r--vendor/github.com/mattermost/ldap/bind.go152
-rw-r--r--vendor/github.com/mattermost/ldap/client.go30
-rw-r--r--vendor/github.com/mattermost/ldap/compare.go80
-rw-r--r--vendor/github.com/mattermost/ldap/conn.go522
-rw-r--r--vendor/github.com/mattermost/ldap/control.go499
-rw-r--r--vendor/github.com/mattermost/ldap/debug.go37
-rw-r--r--vendor/github.com/mattermost/ldap/del.go64
-rw-r--r--vendor/github.com/mattermost/ldap/dn.go247
-rw-r--r--vendor/github.com/mattermost/ldap/doc.go4
-rw-r--r--vendor/github.com/mattermost/ldap/error.go236
-rw-r--r--vendor/github.com/mattermost/ldap/filter.go465
-rw-r--r--vendor/github.com/mattermost/ldap/go.mod5
-rw-r--r--vendor/github.com/mattermost/ldap/go.sum4
-rw-r--r--vendor/github.com/mattermost/ldap/ldap.go345
-rw-r--r--vendor/github.com/mattermost/ldap/moddn.go85
-rw-r--r--vendor/github.com/mattermost/ldap/modify.go151
-rw-r--r--vendor/github.com/mattermost/ldap/passwdmodify.go131
-rw-r--r--vendor/github.com/mattermost/ldap/request.go66
-rw-r--r--vendor/github.com/mattermost/ldap/search.go425
26 files changed, 3857 insertions, 0 deletions
diff --git a/vendor/github.com/mattermost/ldap/.gitignore b/vendor/github.com/mattermost/ldap/.gitignore
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/.gitignore
diff --git a/vendor/github.com/mattermost/ldap/.travis.yml b/vendor/github.com/mattermost/ldap/.travis.yml
new file mode 100644
index 00000000..f2538fd5
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/.travis.yml
@@ -0,0 +1,32 @@
+sudo: false
+language: go
+go:
+ - "1.5.x"
+ - "1.6.x"
+ - "1.7.x"
+ - "1.8.x"
+ - "1.9.x"
+ - "1.10.x"
+ - "1.11.x"
+ - "1.12.x"
+ - "1.13.x"
+ - tip
+
+git:
+ depth: 1
+
+matrix:
+ fast_finish: true
+ allow_failures:
+ - go: tip
+go_import_path: github.com/go-ldap/ldap
+install:
+ - go get github.com/go-asn1-ber/asn1-ber
+ - go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover
+ - go get github.com/golang/lint/golint || go get golang.org/x/lint/golint || true
+ - go build -v ./...
+script:
+ - make test
+ - make fmt
+ - make vet
+ - make lint
diff --git a/vendor/github.com/mattermost/ldap/CONTRIBUTING.md b/vendor/github.com/mattermost/ldap/CONTRIBUTING.md
new file mode 100644
index 00000000..a7885231
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/CONTRIBUTING.md
@@ -0,0 +1,12 @@
+# 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)
diff --git a/vendor/github.com/mattermost/ldap/LICENSE b/vendor/github.com/mattermost/ldap/LICENSE
new file mode 100644
index 00000000..6c0ed4b3
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/LICENSE
@@ -0,0 +1,22 @@
+The 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.
diff --git a/vendor/github.com/mattermost/ldap/Makefile b/vendor/github.com/mattermost/ldap/Makefile
new file mode 100644
index 00000000..c4966472
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/Makefile
@@ -0,0 +1,82 @@
+.PHONY: default install build test quicktest fmt vet lint
+
+# List of all release tags "supported" by our current Go version
+# E.g. ":go1.1:go1.2:go1.3:go1.4:go1.5:go1.6:go1.7:go1.8:go1.9:go1.10:go1.11:go1.12:"
+GO_RELEASE_TAGS := $(shell go list -f ':{{join (context.ReleaseTags) ":"}}:' runtime)
+
+# Only use the `-race` flag on newer versions of Go (version 1.3 and newer)
+ifeq (,$(findstring :go1.3:,$(GO_RELEASE_TAGS)))
+ RACE_FLAG :=
+else
+ RACE_FLAG := -race -cpu 1,2,4
+endif
+
+# Run `go vet` on Go 1.12 and newer. For Go 1.5-1.11, use `go tool vet`
+ifneq (,$(findstring :go1.12:,$(GO_RELEASE_TAGS)))
+ GO_VET := go vet \
+ -atomic \
+ -bool \
+ -copylocks \
+ -nilfunc \
+ -printf \
+ -rangeloops \
+ -unreachable \
+ -unsafeptr \
+ -unusedresult \
+ .
+else ifneq (,$(findstring :go1.5:,$(GO_RELEASE_TAGS)))
+ GO_VET := go tool vet \
+ -atomic \
+ -bool \
+ -copylocks \
+ -nilfunc \
+ -printf \
+ -shadow \
+ -rangeloops \
+ -unreachable \
+ -unsafeptr \
+ -unusedresult \
+ .
+else
+ GO_VET := @echo "go vet skipped -- not supported on this version of Go"
+endif
+
+default: fmt vet lint build quicktest
+
+install:
+ go get -t -v ./...
+
+build:
+ go build -v ./...
+
+test:
+ go test -v $(RACE_FLAG) -cover ./...
+
+quicktest:
+ go test ./...
+
+# Capture output and force failure when there is non-empty output
+fmt:
+ @echo gofmt -l .
+ @OUTPUT=`gofmt -l . 2>&1`; \
+ if [ "$$OUTPUT" ]; then \
+ echo "gofmt must be run on the following files:"; \
+ echo "$$OUTPUT"; \
+ exit 1; \
+ fi
+
+vet:
+ $(GO_VET)
+
+# https://github.com/golang/lint
+# go get github.com/golang/lint/golint
+# Capture output and force failure when there is non-empty output
+# Only run on go1.5+
+lint:
+ @echo golint ./...
+ @OUTPUT=`command -v golint >/dev/null 2>&1 && golint ./... 2>&1`; \
+ if [ "$$OUTPUT" ]; then \
+ echo "golint errors:"; \
+ echo "$$OUTPUT"; \
+ exit 1; \
+ fi
diff --git a/vendor/github.com/mattermost/ldap/README.md b/vendor/github.com/mattermost/ldap/README.md
new file mode 100644
index 00000000..c61e4883
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/README.md
@@ -0,0 +1,61 @@
+[![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.
+
+## 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
+
+## Examples:
+
+ - search
+ - modify
+
+## 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
diff --git a/vendor/github.com/mattermost/ldap/add.go b/vendor/github.com/mattermost/ldap/add.go
new file mode 100644
index 00000000..a3e6b881
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/add.go
@@ -0,0 +1,100 @@
+//
+// https://tools.ietf.org/html/rfc4511
+//
+// AddRequest ::= [APPLICATION 8] SEQUENCE {
+// entry LDAPDN,
+// attributes AttributeList }
+//
+// AttributeList ::= SEQUENCE OF attribute Attribute
+
+package ldap
+
+import (
+ "log"
+
+ ber "github.com/go-asn1-ber/asn1-ber"
+)
+
+// Attribute represents an LDAP attribute
+type Attribute struct {
+ // Type is the name of the LDAP attribute
+ Type string
+ // Vals are the LDAP attribute values
+ Vals []string
+}
+
+func (a *Attribute) encode() *ber.Packet {
+ seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attribute")
+ seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, a.Type, "Type"))
+ set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue")
+ for _, value := range a.Vals {
+ set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals"))
+ }
+ seq.AppendChild(set)
+ return seq
+}
+
+// AddRequest represents an LDAP AddRequest operation
+type AddRequest struct {
+ // DN identifies the entry being added
+ DN string
+ // Attributes list the attributes of the new entry
+ Attributes []Attribute
+ // Controls hold optional controls to send with the request
+ Controls []Control
+}
+
+func (req *AddRequest) appendTo(envelope *ber.Packet) error {
+ pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationAddRequest, nil, "Add Request")
+ pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN"))
+ attributes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
+ for _, attribute := range req.Attributes {
+ attributes.AppendChild(attribute.encode())
+ }
+ pkt.AppendChild(attributes)
+
+ envelope.AppendChild(pkt)
+ if len(req.Controls) > 0 {
+ envelope.AppendChild(encodeControls(req.Controls))
+ }
+
+ return nil
+}
+
+// Attribute adds an attribute with the given type and values
+func (req *AddRequest) Attribute(attrType string, attrVals []string) {
+ req.Attributes = append(req.Attributes, Attribute{Type: attrType, Vals: attrVals})
+}
+
+// NewAddRequest returns an AddRequest for the given DN, with no attributes
+func NewAddRequest(dn string, controls []Control) *AddRequest {
+ return &AddRequest{
+ DN: dn,
+ Controls: controls,
+ }
+
+}
+
+// Add performs the given AddRequest
+func (l *Conn) Add(addRequest *AddRequest) error {
+ msgCtx, err := l.doRequest(addRequest)
+ if err != nil {
+ return err
+ }
+ defer l.finishMessage(msgCtx)
+
+ packet, err := l.readPacket(msgCtx)
+ if err != nil {
+ return err
+ }
+
+ if packet.Children[1].Tag == ApplicationAddResponse {
+ err := GetLDAPError(packet)
+ if err != nil {
+ return err
+ }
+ } else {
+ log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
+ }
+ return nil
+}
diff --git a/vendor/github.com/mattermost/ldap/bind.go b/vendor/github.com/mattermost/ldap/bind.go
new file mode 100644
index 00000000..15307f50
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/bind.go
@@ -0,0 +1,152 @@
+package ldap
+
+import (
+ "errors"
+ "fmt"
+
+ 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
+}
+
+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)
+}
diff --git a/vendor/github.com/mattermost/ldap/client.go b/vendor/github.com/mattermost/ldap/client.go
new file mode 100644
index 00000000..619677c7
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/client.go
@@ -0,0 +1,30 @@
+package ldap
+
+import (
+ "crypto/tls"
+ "time"
+)
+
+// Client knows how to interact with an LDAP server
+type Client interface {
+ Start()
+ StartTLS(*tls.Config) error
+ Close()
+ SetTimeout(time.Duration)
+
+ Bind(username, password string) error
+ UnauthenticatedBind(username string) error
+ SimpleBind(*SimpleBindRequest) (*SimpleBindResult, error)
+ ExternalBind() error
+
+ Add(*AddRequest) error
+ Del(*DelRequest) error
+ Modify(*ModifyRequest) error
+ ModifyDN(*ModifyDNRequest) error
+
+ Compare(dn, attribute, value string) (bool, error)
+ PasswordModify(*PasswordModifyRequest) (*PasswordModifyResult, error)
+
+ Search(*SearchRequest) (*SearchResult, error)
+ SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
+}
diff --git a/vendor/github.com/mattermost/ldap/compare.go b/vendor/github.com/mattermost/ldap/compare.go
new file mode 100644
index 00000000..04a2e17c
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/compare.go
@@ -0,0 +1,80 @@
+// File contains Compare functionality
+//
+// https://tools.ietf.org/html/rfc4511
+//
+// CompareRequest ::= [APPLICATION 14] SEQUENCE {
+// entry LDAPDN,
+// ava AttributeValueAssertion }
+//
+// AttributeValueAssertion ::= SEQUENCE {
+// attributeDesc AttributeDescription,
+// assertionValue AssertionValue }
+//
+// AttributeDescription ::= LDAPString
+// -- Constrained to <attributedescription>
+// -- [RFC4512]
+//
+// AttributeValue ::= OCTET STRING
+//
+
+package 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)
+}
diff --git a/vendor/github.com/mattermost/ldap/conn.go b/vendor/github.com/mattermost/ldap/conn.go
new file mode 100644
index 00000000..5b7ac478
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/conn.go
@@ -0,0 +1,522 @@
+package ldap
+
+import (
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "log"
+ "net"
+ "net/url"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ ber "github.com/go-asn1-ber/asn1-ber"
+)
+
+const (
+ // MessageQuit causes the processMessages loop to exit
+ MessageQuit = 0
+ // MessageRequest sends a request to the server
+ MessageRequest = 1
+ // MessageResponse receives a response from the server
+ MessageResponse = 2
+ // MessageFinish indicates the client considers a particular message ID to be finished
+ MessageFinish = 3
+ // MessageTimeout indicates the client-specified timeout for a particular message ID has been reached
+ MessageTimeout = 4
+)
+
+const (
+ // DefaultLdapPort default ldap port for pure TCP connection
+ DefaultLdapPort = "389"
+ // DefaultLdapsPort default ldap port for SSL connection
+ DefaultLdapsPort = "636"
+)
+
+// PacketResponse contains the packet or error encountered reading a response
+type PacketResponse struct {
+ // Packet is the packet read from the server
+ Packet *ber.Packet
+ // Error is an error encountered while reading
+ Error error
+}
+
+// ReadPacket returns the packet or an error
+func (pr *PacketResponse) ReadPacket() (*ber.Packet, error) {
+ if (pr == nil) || (pr.Packet == nil && pr.Error == nil) {
+ return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve response"))
+ }
+ return pr.Packet, pr.Error
+}
+
+type messageContext struct {
+ id int64
+ // close(done) should only be called from finishMessage()
+ done chan struct{}
+ // close(responses) should only be called from processMessages(), and only sent to from sendResponse()
+ responses chan *PacketResponse
+}
+
+// sendResponse should only be called within the processMessages() loop which
+// is also responsible for closing the responses channel.
+func (msgCtx *messageContext) sendResponse(packet *PacketResponse) {
+ select {
+ case msgCtx.responses <- packet:
+ // Successfully sent packet to message handler.
+ case <-msgCtx.done:
+ // The request handler is done and will not receive more
+ // packets.
+ }
+}
+
+type messagePacket struct {
+ Op int
+ MessageID int64
+ Packet *ber.Packet
+ Context *messageContext
+}
+
+type sendMessageFlags uint
+
+const (
+ startTLS sendMessageFlags = 1 << iota
+)
+
+// Conn represents an LDAP Connection
+type Conn struct {
+ // requestTimeout is loaded atomically
+ // so we need to ensure 64-bit alignment on 32-bit platforms.
+ requestTimeout int64
+ conn net.Conn
+ isTLS bool
+ closing uint32
+ closeErr atomic.Value
+ isStartingTLS bool
+ Debug debugging
+ chanConfirm chan struct{}
+ messageContexts map[int64]*messageContext
+ chanMessage chan *messagePacket
+ chanMessageID chan int64
+ wgClose sync.WaitGroup
+ outstandingRequests uint
+ messageMutex sync.Mutex
+}
+
+var _ Client = &Conn{}
+
+// DefaultTimeout is a package-level variable that sets the timeout value
+// used for the Dial and DialTLS methods.
+//
+// WARNING: since this is a package-level variable, setting this value from
+// multiple places will probably result in undesired behaviour.
+var DefaultTimeout = 60 * time.Second
+
+// Dial connects to the given address on the given network using net.Dial
+// and then returns a new Conn for the connection.
+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.
+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 vie TCP using tls.Dial or net.Dial if ldaps://
+// or ldap:// specified as protocol. On success a new Conn for the connection
+// is returned.
+func DialURL(addr string) (*Conn, error) {
+ lurl, err := url.Parse(addr)
+ if err != nil {
+ return nil, NewError(ErrorNetwork, err)
+ }
+
+ host, port, err := net.SplitHostPort(lurl.Host)
+ if err != nil {
+ // we asume that error is due to missing port
+ host = lurl.Host
+ port = ""
+ }
+
+ switch lurl.Scheme {
+ case "ldapi":
+ if lurl.Path == "" || lurl.Path == "/" {
+ lurl.Path = "/var/run/slapd/ldapi"
+ }
+ return Dial("unix", lurl.Path)
+ case "ldap":
+ if port == "" {
+ port = DefaultLdapPort
+ }
+ return Dial("tcp", net.JoinHostPort(host, port))
+ case "ldaps":
+ if port == "" {
+ port = DefaultLdapsPort
+ }
+ tlsConf := &tls.Config{
+ ServerName: host,
+ }
+ return DialTLS("tcp", net.JoinHostPort(host, port), tlsConf)
+ }
+
+ return nil, NewError(ErrorNetwork, fmt.Errorf("Unknown scheme '%s'", lurl.Scheme))
+}
+
+// NewConn returns a new Conn using conn for network I/O.
+func NewConn(conn net.Conn, isTLS bool) *Conn {
+ return &Conn{
+ conn: conn,
+ chanConfirm: make(chan struct{}),
+ chanMessageID: make(chan int64),
+ chanMessage: make(chan *messagePacket, 10),
+ messageContexts: map[int64]*messageContext{},
+ requestTimeout: 0,
+ isTLS: isTLS,
+ }
+}
+
+// Start initializes goroutines to read responses and process messages
+func (l *Conn) Start() {
+ l.wgClose.Add(1)
+ go l.reader()
+ go l.processMessages()
+}
+
+// IsClosing returns whether or not we're currently closing.
+func (l *Conn) IsClosing() bool {
+ return atomic.LoadUint32(&l.closing) == 1
+}
+
+// setClosing sets the closing value to true
+func (l *Conn) setClosing() bool {
+ return atomic.CompareAndSwapUint32(&l.closing, 0, 1)
+}
+
+// Close closes the connection.
+func (l *Conn) Close() {
+ l.messageMutex.Lock()
+ defer l.messageMutex.Unlock()
+
+ if l.setClosing() {
+ l.Debug.Printf("Sending quit message and waiting for confirmation")
+ l.chanMessage <- &messagePacket{Op: MessageQuit}
+ <-l.chanConfirm
+ close(l.chanMessage)
+
+ l.Debug.Printf("Closing network connection")
+ if err := l.conn.Close(); err != nil {
+ log.Println(err)
+ }
+
+ l.wgClose.Done()
+ }
+ l.wgClose.Wait()
+}
+
+// SetTimeout sets the time after a request is sent that a MessageTimeout triggers
+func (l *Conn) SetTimeout(timeout time.Duration) {
+ if timeout > 0 {
+ atomic.StoreInt64(&l.requestTimeout, int64(timeout))
+ }
+}
+
+// Returns the next available messageID
+func (l *Conn) nextMessageID() int64 {
+ if messageID, ok := <-l.chanMessageID; ok {
+ return messageID
+ }
+ return 0
+}
+
+// StartTLS sends the command to start a TLS session and then creates a new TLS Client
+func (l *Conn) StartTLS(config *tls.Config) error {
+ if l.isTLS {
+ return NewError(ErrorNetwork, errors.New("ldap: already encrypted"))
+ }
+
+ packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+ packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
+ request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Start TLS")
+ request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, "1.3.6.1.4.1.1466.20037", "TLS Extended Command"))
+ packet.AppendChild(request)
+ l.Debug.PrintPacket(packet)
+
+ msgCtx, err := l.sendMessageWithFlags(packet, startTLS)
+ if err != nil {
+ return err
+ }
+ defer l.finishMessage(msgCtx)
+
+ l.Debug.Printf("%d: waiting for response", msgCtx.id)
+
+ packetResponse, ok := <-msgCtx.responses
+ if !ok {
+ return NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
+ }
+ packet, err = packetResponse.ReadPacket()
+ l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
+ if err != nil {
+ return err
+ }
+
+ if l.Debug {
+ if err := addLDAPDescriptions(packet); err != nil {
+ l.Close()
+ return err
+ }
+ l.Debug.PrintPacket(packet)
+ }
+
+ if err := GetLDAPError(packet); err == nil {
+ conn := tls.Client(l.conn, config)
+
+ if connErr := conn.Handshake(); connErr != nil {
+ l.Close()
+ return NewError(ErrorNetwork, fmt.Errorf("TLS handshake failed (%v)", connErr))
+ }
+
+ l.isTLS = true
+ l.conn = conn
+ } else {
+ return err
+ }
+ go l.reader()
+
+ return nil
+}
+
+// TLSConnectionState returns the client's TLS connection state.
+// The return values are their zero values if StartTLS did
+// not succeed.
+func (l *Conn) TLSConnectionState() (state tls.ConnectionState, ok bool) {
+ tc, ok := l.conn.(*tls.Conn)
+ if !ok {
+ return
+ }
+ return tc.ConnectionState(), true
+}
+
+func (l *Conn) sendMessage(packet *ber.Packet) (*messageContext, error) {
+ return l.sendMessageWithFlags(packet, 0)
+}
+
+func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) (*messageContext, error) {
+ if l.IsClosing() {
+ return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed"))
+ }
+ l.messageMutex.Lock()
+ l.Debug.Printf("flags&startTLS = %d", flags&startTLS)
+ if l.isStartingTLS {
+ l.messageMutex.Unlock()
+ return nil, NewError(ErrorNetwork, errors.New("ldap: connection is in startls phase"))
+ }
+ if flags&startTLS != 0 {
+ if l.outstandingRequests != 0 {
+ l.messageMutex.Unlock()
+ return nil, NewError(ErrorNetwork, errors.New("ldap: cannot StartTLS with outstanding requests"))
+ }
+ l.isStartingTLS = true
+ }
+ l.outstandingRequests++
+
+ l.messageMutex.Unlock()
+
+ responses := make(chan *PacketResponse)
+ messageID := packet.Children[0].Value.(int64)
+ message := &messagePacket{
+ Op: MessageRequest,
+ MessageID: messageID,
+ Packet: packet,
+ Context: &messageContext{
+ id: messageID,
+ done: make(chan struct{}),
+ responses: responses,
+ },
+ }
+ l.sendProcessMessage(message)
+ return message.Context, nil
+}
+
+func (l *Conn) finishMessage(msgCtx *messageContext) {
+ close(msgCtx.done)
+
+ if l.IsClosing() {
+ return
+ }
+
+ l.messageMutex.Lock()
+ l.outstandingRequests--
+ if l.isStartingTLS {
+ l.isStartingTLS = false
+ }
+ l.messageMutex.Unlock()
+
+ message := &messagePacket{
+ Op: MessageFinish,
+ MessageID: msgCtx.id,
+ }
+ l.sendProcessMessage(message)
+}
+
+func (l *Conn) sendProcessMessage(message *messagePacket) bool {
+ l.messageMutex.Lock()
+ defer l.messageMutex.Unlock()
+ if l.IsClosing() {
+ return false
+ }
+ l.chanMessage <- message
+ return true
+}
+
+func (l *Conn) processMessages() {
+ defer func() {
+ if err := recover(); err != nil {
+ log.Printf("ldap: recovered panic in processMessages: %v", err)
+ }
+ for messageID, msgCtx := range l.messageContexts {
+ // If we are closing due to an error, inform anyone who
+ // is waiting about the error.
+ if l.IsClosing() && l.closeErr.Load() != nil {
+ msgCtx.sendResponse(&PacketResponse{Error: l.closeErr.Load().(error)})
+ }
+ l.Debug.Printf("Closing channel for MessageID %d", messageID)
+ close(msgCtx.responses)
+ delete(l.messageContexts, messageID)
+ }
+ close(l.chanMessageID)
+ close(l.chanConfirm)
+ }()
+
+ var messageID int64 = 1
+ for {
+ select {
+ case l.chanMessageID <- messageID:
+ messageID++
+ case message := <-l.chanMessage:
+ switch message.Op {
+ case MessageQuit:
+ l.Debug.Printf("Shutting down - quit message received")
+ return
+ case MessageRequest:
+ // Add to message list and write to network
+ l.Debug.Printf("Sending message %d", message.MessageID)
+
+ buf := message.Packet.Bytes()
+ _, err := l.conn.Write(buf)
+ if err != nil {
+ l.Debug.Printf("Error Sending Message: %s", err.Error())
+ message.Context.sendResponse(&PacketResponse{Error: fmt.Errorf("unable to send request: %s", err)})
+ close(message.Context.responses)
+ break
+ }
+
+ // Only add to messageContexts if we were able to
+ // successfully write the message.
+ l.messageContexts[message.MessageID] = message.Context
+
+ // Add timeout if defined
+ requestTimeout := time.Duration(atomic.LoadInt64(&l.requestTimeout))
+ if requestTimeout > 0 {
+ go func() {
+ defer func() {
+ if err := recover(); err != nil {
+ log.Printf("ldap: recovered panic in RequestTimeout: %v", err)
+ }
+ }()
+ time.Sleep(requestTimeout)
+ timeoutMessage := &messagePacket{
+ Op: MessageTimeout,
+ MessageID: message.MessageID,
+ }
+ l.sendProcessMessage(timeoutMessage)
+ }()
+ }
+ case MessageResponse:
+ l.Debug.Printf("Receiving message %d", message.MessageID)
+ if msgCtx, ok := l.messageContexts[message.MessageID]; ok {
+ msgCtx.sendResponse(&PacketResponse{message.Packet, nil})
+ } else {
+ log.Printf("Received unexpected message %d, %v", message.MessageID, l.IsClosing())
+ l.Debug.PrintPacket(message.Packet)
+ }
+ case MessageTimeout:
+ // Handle the timeout by closing the channel
+ // All reads will return immediately
+ if msgCtx, ok := l.messageContexts[message.MessageID]; ok {
+ l.Debug.Printf("Receiving message timeout for %d", message.MessageID)
+ msgCtx.sendResponse(&PacketResponse{message.Packet, errors.New("ldap: connection timed out")})
+ delete(l.messageContexts, message.MessageID)
+ close(msgCtx.responses)
+ }
+ case MessageFinish:
+ l.Debug.Printf("Finished message %d", message.MessageID)
+ if msgCtx, ok := l.messageContexts[message.MessageID]; ok {
+ delete(l.messageContexts, message.MessageID)
+ close(msgCtx.responses)
+ }
+ }
+ }
+ }
+}
+
+func (l *Conn) reader() {
+ cleanstop := false
+ defer func() {
+ if err := recover(); err != nil {
+ log.Printf("ldap: recovered panic in reader: %v", err)
+ }
+ if !cleanstop {
+ l.Close()
+ }
+ }()
+
+ for {
+ if cleanstop {
+ l.Debug.Printf("reader clean stopping (without closing the connection)")
+ return
+ }
+ packet, err := ber.ReadPacket(l.conn)
+ 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
+ }
+ }
+}
diff --git a/vendor/github.com/mattermost/ldap/control.go b/vendor/github.com/mattermost/ldap/control.go
new file mode 100644
index 00000000..463fe3a3
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/control.go
@@ -0,0 +1,499 @@
+package ldap
+
+import (
+ "fmt"
+ "strconv"
+
+ "github.com/go-asn1-ber/asn1-ber"
+)
+
+const (
+ // ControlTypePaging - https://www.ietf.org/rfc/rfc2696.txt
+ ControlTypePaging = "1.2.840.113556.1.4.319"
+ // ControlTypeBeheraPasswordPolicy - https://tools.ietf.org/html/draft-behera-ldap-password-policy-10
+ ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1"
+ // ControlTypeVChuPasswordMustChange - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
+ ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4"
+ // ControlTypeVChuPasswordWarning - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
+ ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5"
+ // ControlTypeManageDsaIT - https://tools.ietf.org/html/rfc3296
+ ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2"
+
+ // 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"
+)
+
+// ControlTypeMap maps controls to text descriptions
+var ControlTypeMap = map[string]string{
+ ControlTypePaging: "Paging",
+ ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft",
+ ControlTypeManageDsaIT: "Manage DSA IT",
+ ControlTypeMicrosoftNotification: "Change Notification - Microsoft",
+ ControlTypeMicrosoftShowDeleted: "Show Deleted Objects - Microsoft",
+}
+
+// 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{}
+}
+
+// FindControl returns the first control of the given type in the list, or nil
+func FindControl(controls []Control, controlType string) Control {
+ for _, c := range controls {
+ if c.GetControlType() == controlType {
+ return c
+ }
+ }
+ return nil
+}
+
+// DecodeControl returns a control read from the given packet, or nil if no recognized control can be made
+func DecodeControl(packet *ber.Packet) (Control, error) {
+ var (
+ ControlType = ""
+ Criticality = false
+ value *ber.Packet
+ )
+
+ switch len(packet.Children) {
+ case 0:
+ // at least one child is required for control type
+ return nil, fmt.Errorf("at least one child is required for control type")
+
+ case 1:
+ // just type, no criticality or value
+ packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
+ ControlType = packet.Children[0].Value.(string)
+
+ case 2:
+ packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
+ ControlType = packet.Children[0].Value.(string)
+
+ // Children[1] could be criticality or value (both are optional)
+ // duck-type on whether this is a boolean
+ if _, ok := packet.Children[1].Value.(bool); ok {
+ packet.Children[1].Description = "Criticality"
+ Criticality = packet.Children[1].Value.(bool)
+ } else {
+ packet.Children[1].Description = "Control Value"
+ value = packet.Children[1]
+ }
+
+ case 3:
+ packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
+ ControlType = packet.Children[0].Value.(string)
+
+ packet.Children[1].Description = "Criticality"
+ Criticality = packet.Children[1].Value.(bool)
+
+ packet.Children[2].Description = "Control Value"
+ value = packet.Children[2]
+
+ default:
+ // more than 3 children is invalid
+ return nil, fmt.Errorf("more than 3 children is invalid for controls")
+ }
+
+ switch ControlType {
+ case ControlTypeManageDsaIT:
+ return NewControlManageDsaIT(Criticality), nil
+ case ControlTypePaging:
+ value.Description += " (Paging)"
+ c := new(ControlPaging)
+ if value.Value != nil {
+ valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode data bytes: %s", err)
+ }
+ value.Data.Truncate(0)
+ value.Value = nil
+ value.AppendChild(valueChildren)
+ }
+ value = value.Children[0]
+ value.Description = "Search Control Value"
+ value.Children[0].Description = "Paging Size"
+ value.Children[1].Description = "Cookie"
+ c.PagingSize = uint32(value.Children[0].Value.(int64))
+ c.Cookie = value.Children[1].Data.Bytes()
+ value.Children[1].Value = c.Cookie
+ return c, nil
+ case ControlTypeBeheraPasswordPolicy:
+ value.Description += " (Password Policy - Behera)"
+ c := NewControlBeheraPasswordPolicy()
+ if value.Value != nil {
+ valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode data bytes: %s", err)
+ }
+ value.Data.Truncate(0)
+ value.Value = nil
+ value.AppendChild(valueChildren)
+ }
+
+ sequence := value.Children[0]
+
+ for _, child := range sequence.Children {
+ if child.Tag == 0 {
+ //Warning
+ warningPacket := child.Children[0]
+ packet, err := ber.DecodePacketErr(warningPacket.Data.Bytes())
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode data bytes: %s", err)
+ }
+ val, ok := packet.Value.(int64)
+ if ok {
+ 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
+ packet, err := ber.DecodePacketErr(child.Data.Bytes())
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode data bytes: %s", err)
+ }
+ val, ok := packet.Value.(int8)
+ if !ok {
+ // what to do?
+ val = -1
+ }
+ 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
+ default:
+ c := new(ControlString)
+ c.ControlType = ControlType
+ c.Criticality = Criticality
+ if value != nil {
+ c.ControlValue = value.Value.(string)
+ }
+ return c, nil
+ }
+}
+
+// NewControlString returns a generic control
+func NewControlString(controlType string, criticality bool, controlValue string) *ControlString {
+ return &ControlString{
+ ControlType: controlType,
+ Criticality: criticality,
+ ControlValue: controlValue,
+ }
+}
+
+// NewControlPaging returns a paging control
+func NewControlPaging(pagingSize uint32) *ControlPaging {
+ return &ControlPaging{PagingSize: pagingSize}
+}
+
+// NewControlBeheraPasswordPolicy returns a ControlBeheraPasswordPolicy
+func NewControlBeheraPasswordPolicy() *ControlBeheraPasswordPolicy {
+ return &ControlBeheraPasswordPolicy{
+ Expire: -1,
+ Grace: -1,
+ Error: -1,
+ }
+}
+
+func encodeControls(controls []Control) *ber.Packet {
+ packet := ber.Encode(ber.ClassContext, ber.TypeConstructed, 0, nil, "Controls")
+ for _, control := range controls {
+ packet.AppendChild(control.Encode())
+ }
+ return packet
+}
diff --git a/vendor/github.com/mattermost/ldap/debug.go b/vendor/github.com/mattermost/ldap/debug.go
new file mode 100644
index 00000000..dfddd5f2
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/debug.go
@@ -0,0 +1,37 @@
+package ldap
+
+import (
+ "bytes"
+ "log"
+
+ ber "github.com/go-asn1-ber/asn1-ber"
+)
+
+const LDAP_TRACE_PREFIX = "ldap-trace: "
+
+// 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 {
+ format = LDAP_TRACE_PREFIX + format
+ log.Printf(format, args...)
+ }
+}
+
+// PrintPacket dumps a packet.
+func (debug debugging) PrintPacket(packet *ber.Packet) {
+ if debug {
+ var b bytes.Buffer
+ ber.WritePacket(&b, packet)
+ textToPrint := LDAP_TRACE_PREFIX + b.String()
+ log.Printf(textToPrint)
+ }
+}
diff --git a/vendor/github.com/mattermost/ldap/del.go b/vendor/github.com/mattermost/ldap/del.go
new file mode 100644
index 00000000..49811d34
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/del.go
@@ -0,0 +1,64 @@
+//
+// https://tools.ietf.org/html/rfc4511
+//
+// DelRequest ::= [APPLICATION 10] LDAPDN
+
+package ldap
+
+import (
+ "log"
+
+ ber "github.com/go-asn1-ber/asn1-ber"
+)
+
+// DelRequest implements an LDAP deletion request
+type DelRequest struct {
+ // DN is the name of the directory entry to delete
+ DN string
+ // Controls hold optional controls to send with the request
+ Controls []Control
+}
+
+func (req *DelRequest) appendTo(envelope *ber.Packet) error {
+ pkt := ber.Encode(ber.ClassApplication, ber.TypePrimitive, ApplicationDelRequest, req.DN, "Del Request")
+ pkt.Data.Write([]byte(req.DN))
+
+ envelope.AppendChild(pkt)
+ if len(req.Controls) > 0 {
+ envelope.AppendChild(encodeControls(req.Controls))
+ }
+
+ return nil
+}
+
+// NewDelRequest creates a delete request for the given DN and controls
+func NewDelRequest(DN string, Controls []Control) *DelRequest {
+ return &DelRequest{
+ DN: DN,
+ Controls: Controls,
+ }
+}
+
+// Del executes the given delete request
+func (l *Conn) Del(delRequest *DelRequest) error {
+ msgCtx, err := l.doRequest(delRequest)
+ if err != nil {
+ return err
+ }
+ defer l.finishMessage(msgCtx)
+
+ packet, err := l.readPacket(msgCtx)
+ if err != nil {
+ return err
+ }
+
+ if packet.Children[1].Tag == ApplicationDelResponse {
+ err := GetLDAPError(packet)
+ if err != nil {
+ return err
+ }
+ } else {
+ log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
+ }
+ return nil
+}
diff --git a/vendor/github.com/mattermost/ldap/dn.go b/vendor/github.com/mattermost/ldap/dn.go
new file mode 100644
index 00000000..9d32e7fa
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/dn.go
@@ -0,0 +1,247 @@
+// File contains DN parsing functionality
+//
+// https://tools.ietf.org/html/rfc4514
+//
+// distinguishedName = [ relativeDistinguishedName
+// *( COMMA relativeDistinguishedName ) ]
+// relativeDistinguishedName = attributeTypeAndValue
+// *( PLUS attributeTypeAndValue )
+// attributeTypeAndValue = attributeType EQUALS attributeValue
+// attributeType = descr / numericoid
+// attributeValue = string / hexstring
+//
+// ; The following characters are to be escaped when they appear
+// ; in the value to be encoded: ESC, one of <escaped>, leading
+// ; SHARP or SPACE, trailing SPACE, and NULL.
+// string = [ ( leadchar / pair ) [ *( stringchar / pair )
+// ( trailchar / pair ) ] ]
+//
+// leadchar = LUTF1 / UTFMB
+// LUTF1 = %x01-1F / %x21 / %x24-2A / %x2D-3A /
+// %x3D / %x3F-5B / %x5D-7F
+//
+// trailchar = TUTF1 / UTFMB
+// TUTF1 = %x01-1F / %x21 / %x23-2A / %x2D-3A /
+// %x3D / %x3F-5B / %x5D-7F
+//
+// stringchar = SUTF1 / UTFMB
+// SUTF1 = %x01-21 / %x23-2A / %x2D-3A /
+// %x3D / %x3F-5B / %x5D-7F
+//
+// pair = ESC ( ESC / special / hexpair )
+// special = escaped / SPACE / SHARP / EQUALS
+// escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE
+// hexstring = SHARP 1*hexpair
+// hexpair = HEX HEX
+//
+// where the productions <descr>, <numericoid>, <COMMA>, <DQUOTE>,
+// <EQUALS>, <ESC>, <HEX>, <LANGLE>, <NULL>, <PLUS>, <RANGLE>, <SEMI>,
+// <SPACE>, <SHARP>, and <UTFMB> are defined in [RFC4512].
+//
+
+package ldap
+
+import (
+ "bytes"
+ enchex "encoding/hex"
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/go-asn1-ber/asn1-ber"
+)
+
+// AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514
+type AttributeTypeAndValue struct {
+ // Type is the attribute type
+ Type string
+ // Value is the attribute value
+ Value string
+}
+
+// RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514
+type RelativeDN struct {
+ Attributes []*AttributeTypeAndValue
+}
+
+// DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514
+type DN struct {
+ RDNs []*RelativeDN
+}
+
+// ParseDN returns a distinguishedName or an error
+func ParseDN(str string) (*DN, error) {
+ dn := new(DN)
+ dn.RDNs = make([]*RelativeDN, 0)
+ rdn := new(RelativeDN)
+ rdn.Attributes = make([]*AttributeTypeAndValue, 0)
+ buffer := bytes.Buffer{}
+ attribute := new(AttributeTypeAndValue)
+ escaping := false
+
+ unescapedTrailingSpaces := 0
+ stringFromBuffer := func() string {
+ s := buffer.String()
+ s = s[0 : len(s)-unescapedTrailingSpaces]
+ buffer.Reset()
+ unescapedTrailingSpaces = 0
+ return s
+ }
+
+ for i := 0; i < len(str); i++ {
+ char := str[i]
+ switch {
+ case escaping:
+ unescapedTrailingSpaces = 0
+ escaping = false
+ switch char {
+ case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\':
+ buffer.WriteByte(char)
+ continue
+ }
+ // Not a special character, assume hex encoded octet
+ if len(str) == i+1 {
+ return nil, errors.New("got corrupted escaped character")
+ }
+
+ dst := []byte{0}
+ n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2]))
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode escaped character: %s", err)
+ } else if n != 1 {
+ return nil, fmt.Errorf("expected 1 byte when un-escaping, got %d", n)
+ }
+ buffer.WriteByte(dst[0])
+ i++
+ case char == '\\':
+ unescapedTrailingSpaces = 0
+ escaping = true
+ case char == '=':
+ attribute.Type = stringFromBuffer()
+ // Special case: If the first character in the value is # the
+ // following data is BER encoded so we can just fast forward
+ // and decode.
+ if len(str) > i+1 && str[i+1] == '#' {
+ i += 2
+ index := strings.IndexAny(str[i:], ",+")
+ data := str
+ if index > 0 {
+ data = str[i : i+index]
+ } else {
+ data = str[i:]
+ }
+ rawBER, err := enchex.DecodeString(data)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode BER encoding: %s", err)
+ }
+ packet, err := ber.DecodePacketErr(rawBER)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode BER packet: %s", err)
+ }
+ buffer.WriteString(packet.Data.String())
+ i += len(data) - 1
+ }
+ case char == ',' || char == '+':
+ // We're done with this RDN or value, push it
+ if len(attribute.Type) == 0 {
+ return nil, errors.New("incomplete type, value pair")
+ }
+ attribute.Value = stringFromBuffer()
+ rdn.Attributes = append(rdn.Attributes, attribute)
+ attribute = new(AttributeTypeAndValue)
+ if char == ',' {
+ dn.RDNs = append(dn.RDNs, rdn)
+ rdn = new(RelativeDN)
+ rdn.Attributes = make([]*AttributeTypeAndValue, 0)
+ }
+ case char == ' ' && buffer.Len() == 0:
+ // ignore unescaped leading spaces
+ continue
+ default:
+ if char == ' ' {
+ // Track unescaped spaces in case they are trailing and we need to remove them
+ unescapedTrailingSpaces++
+ } else {
+ // Reset if we see a non-space char
+ unescapedTrailingSpaces = 0
+ }
+ buffer.WriteByte(char)
+ }
+ }
+ if buffer.Len() > 0 {
+ if len(attribute.Type) == 0 {
+ return nil, errors.New("DN ended with incomplete type, value pair")
+ }
+ attribute.Value = stringFromBuffer()
+ rdn.Attributes = append(rdn.Attributes, attribute)
+ dn.RDNs = append(dn.RDNs, rdn)
+ }
+ return dn, nil
+}
+
+// Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
+// Returns true if they have the same number of relative distinguished names
+// and corresponding relative distinguished names (by position) are the same.
+func (d *DN) Equal(other *DN) bool {
+ if len(d.RDNs) != len(other.RDNs) {
+ return false
+ }
+ for i := range d.RDNs {
+ if !d.RDNs[i].Equal(other.RDNs[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+// AncestorOf returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN.
+// "ou=widgets,o=acme.com" is an ancestor of "ou=sprockets,ou=widgets,o=acme.com"
+// "ou=widgets,o=acme.com" is not an ancestor of "ou=sprockets,ou=widgets,o=foo.com"
+// "ou=widgets,o=acme.com" is not an ancestor of "ou=widgets,o=acme.com"
+func (d *DN) AncestorOf(other *DN) bool {
+ if len(d.RDNs) >= len(other.RDNs) {
+ return false
+ }
+ // Take the last `len(d.RDNs)` RDNs from the other DN to compare against
+ otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):]
+ for i := range d.RDNs {
+ if !d.RDNs[i].Equal(otherRDNs[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+// Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
+// Relative distinguished names are the same if and only if they have the same number of AttributeTypeAndValues
+// and each attribute of the first RDN is the same as the attribute of the second RDN with the same attribute type.
+// The order of attributes is not significant.
+// Case of attribute types is not significant.
+func (r *RelativeDN) Equal(other *RelativeDN) bool {
+ if len(r.Attributes) != len(other.Attributes) {
+ return false
+ }
+ return r.hasAllAttributes(other.Attributes) && other.hasAllAttributes(r.Attributes)
+}
+
+func (r *RelativeDN) hasAllAttributes(attrs []*AttributeTypeAndValue) bool {
+ for _, attr := range attrs {
+ found := false
+ for _, myattr := range r.Attributes {
+ if myattr.Equal(attr) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return false
+ }
+ }
+ return true
+}
+
+// Equal returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue
+// Case of the attribute type is not significant
+func (a *AttributeTypeAndValue) Equal(other *AttributeTypeAndValue) bool {
+ return strings.EqualFold(a.Type, other.Type) && a.Value == other.Value
+}
diff --git a/vendor/github.com/mattermost/ldap/doc.go b/vendor/github.com/mattermost/ldap/doc.go
new file mode 100644
index 00000000..f20d39bc
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/doc.go
@@ -0,0 +1,4 @@
+/*
+Package ldap provides basic LDAP v3 functionality.
+*/
+package ldap
diff --git a/vendor/github.com/mattermost/ldap/error.go b/vendor/github.com/mattermost/ldap/error.go
new file mode 100644
index 00000000..b1fda2d8
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/error.go
@@ -0,0 +1,236 @@
+package 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
+}
+
+func (e *Error) Error() string {
+ return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error())
+}
+
+// GetLDAPError creates an Error out of a BER packet representing a LDAPResult
+// The return is an error object. It can be casted to a Error structure.
+// This function returns nil if resultCode in the LDAPResult sequence is success(0).
+func GetLDAPError(packet *ber.Packet) error {
+ if packet == nil {
+ return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("Empty packet")}
+ }
+
+ if len(packet.Children) >= 2 {
+ response := packet.Children[1]
+ if response == nil {
+ return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("Empty response in packet")}
+ }
+ if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 {
+ resultCode := uint16(response.Children[0].Value.(int64))
+ if resultCode == 0 { // No error
+ return nil
+ }
+ return &Error{ResultCode: resultCode, MatchedDN: response.Children[1].Value.(string),
+ Err: fmt.Errorf("%s", response.Children[2].Value.(string))}
+ }
+ }
+
+ return &Error{ResultCode: ErrorNetwork, Err: fmt.Errorf("Invalid packet format")}
+}
+
+// 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}
+}
+
+// IsErrorWithCode returns true if the given error is an LDAP error with the given result code
+func IsErrorWithCode(err error, desiredResultCode uint16) bool {
+ if err == nil {
+ return false
+ }
+
+ serverError, ok := err.(*Error)
+ if !ok {
+ return false
+ }
+
+ return serverError.ResultCode == desiredResultCode
+}
diff --git a/vendor/github.com/mattermost/ldap/filter.go b/vendor/github.com/mattermost/ldap/filter.go
new file mode 100644
index 00000000..a3875506
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/filter.go
@@ -0,0 +1,465 @@
+package ldap
+
+import (
+ "bytes"
+ hexpac "encoding/hex"
+ "errors"
+ "fmt"
+ "strings"
+ "unicode/utf8"
+
+ "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",
+}
+
+// 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) (ret string, err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = NewError(ErrorFilterDecompile, errors.New("ldap: error decompiling filter"))
+ }
+ }()
+ ret = "("
+ err = nil
+ childStr := ""
+
+ switch packet.Tag {
+ case FilterAnd:
+ ret += "&"
+ for _, child := range packet.Children {
+ childStr, err = DecompileFilter(child)
+ if err != nil {
+ return
+ }
+ ret += childStr
+ }
+ case FilterOr:
+ ret += "|"
+ for _, child := range packet.Children {
+ childStr, err = DecompileFilter(child)
+ if err != nil {
+ return
+ }
+ ret += childStr
+ }
+ case FilterNot:
+ ret += "!"
+ childStr, err = DecompileFilter(packet.Children[0])
+ if err != nil {
+ return
+ }
+ ret += childStr
+
+ case FilterSubstrings:
+ ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+ ret += "="
+ for i, child := range packet.Children[1].Children {
+ if i == 0 && child.Tag != FilterSubstringsInitial {
+ ret += "*"
+ }
+ ret += EscapeFilter(ber.DecodeString(child.Data.Bytes()))
+ if child.Tag != FilterSubstringsFinal {
+ ret += "*"
+ }
+ }
+ case FilterEqualityMatch:
+ ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+ ret += "="
+ ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
+ case FilterGreaterOrEqual:
+ ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+ ret += ">="
+ ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
+ case FilterLessOrEqual:
+ ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+ ret += "<="
+ ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
+ case FilterPresent:
+ ret += ber.DecodeString(packet.Data.Bytes())
+ ret += "=*"
+ case FilterApproxMatch:
+ ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+ ret += "~="
+ ret += 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 {
+ ret += attr
+ }
+ if dnAttributes {
+ ret += ":dn"
+ }
+ if len(matchingRule) > 0 {
+ ret += ":"
+ ret += matchingRule
+ }
+ ret += ":="
+ ret += EscapeFilter(value)
+ }
+
+ ret += ")"
+ return
+}
+
+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 := ""
+ extensibleDNAttributes := false
+ extensibleMatchingRule := ""
+ condition := ""
+
+ 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 += fmt.Sprintf("%c", 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 += fmt.Sprintf("%c", currentRune)
+ newPos += currentWidth
+ }
+
+ case stateReadingCondition:
+ // append to the condition
+ condition += fmt.Sprintf("%c", 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 len(extensibleMatchingRule) > 0 {
+ packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchingRule, extensibleMatchingRule, MatchingRuleAssertionMap[MatchingRuleAssertionMatchingRule]))
+ }
+
+ // Include the attribute, if specified
+ if len(attribute) > 0 {
+ packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionType, attribute, MatchingRuleAssertionMap[MatchingRuleAssertionType]))
+ }
+
+ // Add the value (only required child)
+ encodedString, encodeErr := escapedStringToEncodedBytes(condition)
+ 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 && condition == "*":
+ packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute, FilterMap[FilterPresent])
+ case packet.Tag == FilterEqualityMatch && strings.Contains(condition, "*"):
+ packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
+ packet.Tag = FilterSubstrings
+ packet.Description = FilterMap[uint64(packet.Tag)]
+ seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings")
+ parts := strings.Split(condition, "*")
+ for i, part := range parts {
+ if part == "" {
+ continue
+ }
+ var tag ber.Tag
+ switch i {
+ case 0:
+ tag = FilterSubstringsInitial
+ case len(parts) - 1:
+ tag = FilterSubstringsFinal
+ default:
+ tag = FilterSubstringsAny
+ }
+ encodedString, encodeErr := escapedStringToEncodedBytes(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 := escapedStringToEncodedBytes(condition)
+ if encodeErr != nil {
+ return packet, newPos, encodeErr
+ }
+ packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "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 escapedStringToEncodedBytes(escapedString string) (string, error) {
+ var buffer bytes.Buffer
+ i := 0
+ for i < len(escapedString) {
+ currentRune, currentWidth := utf8.DecodeRuneInString(escapedString[i:])
+ if currentRune == utf8.RuneError {
+ return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", i))
+ }
+
+ // Check for escaped hex characters and convert them to their literal value for transport.
+ if currentRune == '\\' {
+ // 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 i+2 > len(escapedString) {
+ return "", NewError(ErrorFilterCompile, errors.New("ldap: missing characters for escape in filter"))
+ }
+ escByte, decodeErr := hexpac.DecodeString(escapedString[i+1 : i+3])
+ if decodeErr != nil {
+ return "", NewError(ErrorFilterCompile, errors.New("ldap: invalid characters for escape in filter"))
+ }
+ buffer.WriteByte(escByte[0])
+ i += 2 // +1 from end of loop, so 3 total for \xx.
+ } else {
+ buffer.WriteRune(currentRune)
+ }
+
+ i += currentWidth
+ }
+ return buffer.String(), nil
+}
diff --git a/vendor/github.com/mattermost/ldap/go.mod b/vendor/github.com/mattermost/ldap/go.mod
new file mode 100644
index 00000000..8c15079d
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/go.mod
@@ -0,0 +1,5 @@
+module github.com/mattermost/ldap
+
+require github.com/go-asn1-ber/asn1-ber v1.3.2-0.20191121212151-29be175fc3a3
+
+go 1.13
diff --git a/vendor/github.com/mattermost/ldap/go.sum b/vendor/github.com/mattermost/ldap/go.sum
new file mode 100644
index 00000000..5eb88fee
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/go.sum
@@ -0,0 +1,4 @@
+github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck=
+github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
+github.com/go-asn1-ber/asn1-ber v1.3.2-0.20191121212151-29be175fc3a3 h1:QW2p25fGTu/S0MvEftCo3wV7aEFHBt2m1DTg1HUwh+o=
+github.com/go-asn1-ber/asn1-ber v1.3.2-0.20191121212151-29be175fc3a3/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
diff --git a/vendor/github.com/mattermost/ldap/ldap.go b/vendor/github.com/mattermost/ldap/ldap.go
new file mode 100644
index 00000000..7dbc951a
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/ldap.go
@@ -0,0 +1,345 @@
+package ldap
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+
+ ber "github.com/go-asn1-ber/asn1-ber"
+)
+
+// LDAP Application Codes
+const (
+ ApplicationBindRequest = 0
+ ApplicationBindResponse = 1
+ ApplicationUnbindRequest = 2
+ ApplicationSearchRequest = 3
+ ApplicationSearchResultEntry = 4
+ ApplicationSearchResultDone = 5
+ ApplicationModifyRequest = 6
+ ApplicationModifyResponse = 7
+ ApplicationAddRequest = 8
+ ApplicationAddResponse = 9
+ ApplicationDelRequest = 10
+ ApplicationDelResponse = 11
+ ApplicationModifyDNRequest = 12
+ ApplicationModifyDNResponse = 13
+ ApplicationCompareRequest = 14
+ ApplicationCompareResponse = 15
+ ApplicationAbandonRequest = 16
+ ApplicationSearchResultReference = 19
+ ApplicationExtendedRequest = 23
+ ApplicationExtendedResponse = 24
+)
+
+// ApplicationMap contains human readable descriptions of LDAP Application Codes
+var ApplicationMap = map[uint8]string{
+ ApplicationBindRequest: "Bind Request",
+ ApplicationBindResponse: "Bind Response",
+ ApplicationUnbindRequest: "Unbind Request",
+ ApplicationSearchRequest: "Search Request",
+ ApplicationSearchResultEntry: "Search Result Entry",
+ ApplicationSearchResultDone: "Search Result Done",
+ ApplicationModifyRequest: "Modify Request",
+ ApplicationModifyResponse: "Modify Response",
+ ApplicationAddRequest: "Add Request",
+ ApplicationAddResponse: "Add Response",
+ ApplicationDelRequest: "Del Request",
+ ApplicationDelResponse: "Del Response",
+ ApplicationModifyDNRequest: "Modify DN Request",
+ ApplicationModifyDNResponse: "Modify DN Response",
+ ApplicationCompareRequest: "Compare Request",
+ ApplicationCompareResponse: "Compare Response",
+ ApplicationAbandonRequest: "Abandon Request",
+ ApplicationSearchResultReference: "Search Result Reference",
+ ApplicationExtendedRequest: "Extended Request",
+ ApplicationExtendedResponse: "Extended Response",
+}
+
+// Ldap Behera Password Policy Draft 10 (https://tools.ietf.org/html/draft-behera-ldap-password-policy-10)
+const (
+ BeheraPasswordExpired = 0
+ BeheraAccountLocked = 1
+ BeheraChangeAfterReset = 2
+ BeheraPasswordModNotAllowed = 3
+ BeheraMustSupplyOldPassword = 4
+ BeheraInsufficientPasswordQuality = 5
+ BeheraPasswordTooShort = 6
+ BeheraPasswordTooYoung = 7
+ BeheraPasswordInHistory = 8
+)
+
+// BeheraPasswordPolicyErrorMap contains human readable descriptions of Behera Password Policy error codes
+var BeheraPasswordPolicyErrorMap = map[int8]string{
+ BeheraPasswordExpired: "Password expired",
+ BeheraAccountLocked: "Account locked",
+ BeheraChangeAfterReset: "Password must be changed",
+ BeheraPasswordModNotAllowed: "Policy prevents password modification",
+ BeheraMustSupplyOldPassword: "Policy requires old password in order to change password",
+ BeheraInsufficientPasswordQuality: "Password fails quality checks",
+ BeheraPasswordTooShort: "Password is too short for policy",
+ BeheraPasswordTooYoung: "Password has been changed too recently",
+ BeheraPasswordInHistory: "New password is in list of old passwords",
+}
+
+// Adds descriptions to an LDAP Response packet for debugging
+func addLDAPDescriptions(packet *ber.Packet) (err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = NewError(ErrorDebugging, fmt.Errorf("ldap: cannot process packet to add descriptions: %s", r))
+ }
+ }()
+ packet.Description = "LDAP Response"
+ packet.Children[0].Description = "Message ID"
+
+ application := uint8(packet.Children[1].Tag)
+ packet.Children[1].Description = ApplicationMap[application]
+
+ switch application {
+ case ApplicationBindRequest:
+ err = addRequestDescriptions(packet)
+ case ApplicationBindResponse:
+ err = addDefaultLDAPResponseDescriptions(packet)
+ case ApplicationUnbindRequest:
+ err = addRequestDescriptions(packet)
+ case ApplicationSearchRequest:
+ err = addRequestDescriptions(packet)
+ case ApplicationSearchResultEntry:
+ packet.Children[1].Children[0].Description = "Object Name"
+ packet.Children[1].Children[1].Description = "Attributes"
+ for _, child := range packet.Children[1].Children[1].Children {
+ child.Description = "Attribute"
+ child.Children[0].Description = "Attribute Name"
+ child.Children[1].Description = "Attribute Values"
+ for _, grandchild := range child.Children[1].Children {
+ grandchild.Description = "Attribute Value"
+ }
+ }
+ if len(packet.Children) == 3 {
+ err = addControlDescriptions(packet.Children[2])
+ }
+ case ApplicationSearchResultDone:
+ err = addDefaultLDAPResponseDescriptions(packet)
+ case ApplicationModifyRequest:
+ err = addRequestDescriptions(packet)
+ case ApplicationModifyResponse:
+ case ApplicationAddRequest:
+ err = addRequestDescriptions(packet)
+ case ApplicationAddResponse:
+ case ApplicationDelRequest:
+ err = addRequestDescriptions(packet)
+ case ApplicationDelResponse:
+ case ApplicationModifyDNRequest:
+ err = addRequestDescriptions(packet)
+ case ApplicationModifyDNResponse:
+ case ApplicationCompareRequest:
+ err = addRequestDescriptions(packet)
+ case ApplicationCompareResponse:
+ case ApplicationAbandonRequest:
+ err = addRequestDescriptions(packet)
+ case ApplicationSearchResultReference:
+ case ApplicationExtendedRequest:
+ err = addRequestDescriptions(packet)
+ case ApplicationExtendedResponse:
+ }
+
+ return err
+}
+
+func addControlDescriptions(packet *ber.Packet) error {
+ packet.Description = "Controls"
+ for _, child := range packet.Children {
+ var value *ber.Packet
+ controlType := ""
+ child.Description = "Control"
+ switch len(child.Children) {
+ case 0:
+ // at least one child is required for control type
+ return fmt.Errorf("at least one child is required for control type")
+
+ case 1:
+ // just type, no criticality or value
+ controlType = child.Children[0].Value.(string)
+ child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
+
+ case 2:
+ controlType = child.Children[0].Value.(string)
+ child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
+ // Children[1] could be criticality or value (both are optional)
+ // duck-type on whether this is a boolean
+ if _, ok := child.Children[1].Value.(bool); ok {
+ child.Children[1].Description = "Criticality"
+ } else {
+ child.Children[1].Description = "Control Value"
+ value = child.Children[1]
+ }
+
+ case 3:
+ // criticality and value present
+ controlType = child.Children[0].Value.(string)
+ child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
+ child.Children[1].Description = "Criticality"
+ child.Children[2].Description = "Control Value"
+ value = child.Children[2]
+
+ default:
+ // more than 3 children is invalid
+ return fmt.Errorf("more than 3 children for control packet found")
+ }
+
+ if value == nil {
+ continue
+ }
+ switch controlType {
+ case ControlTypePaging:
+ value.Description += " (Paging)"
+ if value.Value != nil {
+ valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
+ if err != nil {
+ return fmt.Errorf("failed to decode data bytes: %s", err)
+ }
+ value.Data.Truncate(0)
+ value.Value = nil
+ valueChildren.Children[1].Value = valueChildren.Children[1].Data.Bytes()
+ value.AppendChild(valueChildren)
+ }
+ value.Children[0].Description = "Real Search Control Value"
+ value.Children[0].Children[0].Description = "Paging Size"
+ value.Children[0].Children[1].Description = "Cookie"
+
+ case ControlTypeBeheraPasswordPolicy:
+ value.Description += " (Password Policy - Behera Draft)"
+ if value.Value != nil {
+ valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
+ if err != nil {
+ return fmt.Errorf("failed to decode data bytes: %s", err)
+ }
+ value.Data.Truncate(0)
+ value.Value = nil
+ value.AppendChild(valueChildren)
+ }
+ sequence := value.Children[0]
+ for _, child := range sequence.Children {
+ if child.Tag == 0 {
+ //Warning
+ warningPacket := child.Children[0]
+ packet, err := ber.DecodePacketErr(warningPacket.Data.Bytes())
+ if err != nil {
+ return fmt.Errorf("failed to decode data bytes: %s", err)
+ }
+ val, ok := packet.Value.(int64)
+ if ok {
+ 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
+ packet, err := ber.DecodePacketErr(child.Data.Bytes())
+ if err != nil {
+ return fmt.Errorf("failed to decode data bytes: %s", err)
+ }
+ val, ok := packet.Value.(int8)
+ if !ok {
+ val = -1
+ }
+ 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)
+}
diff --git a/vendor/github.com/mattermost/ldap/moddn.go b/vendor/github.com/mattermost/ldap/moddn.go
new file mode 100644
index 00000000..ca48b2b4
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/moddn.go
@@ -0,0 +1,85 @@
+// Package ldap - moddn.go contains ModifyDN functionality
+//
+// https://tools.ietf.org/html/rfc4511
+// ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
+// entry LDAPDN,
+// newrdn RelativeLDAPDN,
+// deleteoldrdn BOOLEAN,
+// newSuperior [0] LDAPDN OPTIONAL }
+//
+//
+package ldap
+
+import (
+ "log"
+
+ ber "github.com/go-asn1-ber/asn1-ber"
+)
+
+// ModifyDNRequest holds the request to modify a DN
+type ModifyDNRequest struct {
+ DN string
+ NewRDN string
+ DeleteOldRDN bool
+ NewSuperior string
+}
+
+// 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,
+ }
+}
+
+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"))
+ 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)
+
+ return nil
+}
+
+// ModifyDN renames the given DN and optionally move to another base (when the "newSup" argument
+// to NewModifyDNRequest() is not "").
+func (l *Conn) ModifyDN(m *ModifyDNRequest) error {
+ msgCtx, err := l.doRequest(m)
+ if err != nil {
+ return err
+ }
+ defer l.finishMessage(msgCtx)
+
+ packet, err := l.readPacket(msgCtx)
+ if err != nil {
+ return err
+ }
+
+ if packet.Children[1].Tag == ApplicationModifyDNResponse {
+ err := GetLDAPError(packet)
+ if err != nil {
+ return err
+ }
+ } else {
+ log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
+ }
+ return nil
+}
diff --git a/vendor/github.com/mattermost/ldap/modify.go b/vendor/github.com/mattermost/ldap/modify.go
new file mode 100644
index 00000000..88f65c1b
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/modify.go
@@ -0,0 +1,151 @@
+// File contains Modify functionality
+//
+// https://tools.ietf.org/html/rfc4511
+//
+// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
+// object LDAPDN,
+// changes SEQUENCE OF change SEQUENCE {
+// operation ENUMERATED {
+// add (0),
+// delete (1),
+// replace (2),
+// ... },
+// modification PartialAttribute } }
+//
+// PartialAttribute ::= SEQUENCE {
+// type AttributeDescription,
+// vals SET OF value AttributeValue }
+//
+// AttributeDescription ::= LDAPString
+// -- Constrained to <attributedescription>
+// -- [RFC4512]
+//
+// AttributeValue ::= OCTET STRING
+//
+
+package ldap
+
+import (
+ "log"
+
+ ber "github.com/go-asn1-ber/asn1-ber"
+)
+
+// Change operation choices
+const (
+ AddAttribute = 0
+ DeleteAttribute = 1
+ ReplaceAttribute = 2
+)
+
+// 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)
+}
+
+func (req *ModifyRequest) appendChange(operation uint, attrType string, attrVals []string) {
+ req.Changes = append(req.Changes, Change{operation, PartialAttribute{Type: attrType, Vals: attrVals}})
+}
+
+func (req *ModifyRequest) appendTo(envelope *ber.Packet) error {
+ pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request")
+ pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN"))
+ changes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Changes")
+ for _, change := range req.Changes {
+ changes.AppendChild(change.encode())
+ }
+ pkt.AppendChild(changes)
+
+ envelope.AppendChild(pkt)
+ if len(req.Controls) > 0 {
+ envelope.AppendChild(encodeControls(req.Controls))
+ }
+
+ return nil
+}
+
+// NewModifyRequest creates a modify request for the given DN
+func NewModifyRequest(dn string, controls []Control) *ModifyRequest {
+ return &ModifyRequest{
+ DN: dn,
+ Controls: controls,
+ }
+}
+
+// Modify performs the ModifyRequest
+func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
+ msgCtx, err := l.doRequest(modifyRequest)
+ if err != nil {
+ return err
+ }
+ defer l.finishMessage(msgCtx)
+
+ packet, err := l.readPacket(msgCtx)
+ if err != nil {
+ return err
+ }
+
+ if packet.Children[1].Tag == ApplicationModifyResponse {
+ err := GetLDAPError(packet)
+ if err != nil {
+ return err
+ }
+ } else {
+ log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
+ }
+ return nil
+}
diff --git a/vendor/github.com/mattermost/ldap/passwdmodify.go b/vendor/github.com/mattermost/ldap/passwdmodify.go
new file mode 100644
index 00000000..135554d9
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/passwdmodify.go
@@ -0,0 +1,131 @@
+// This file contains the password modify extended operation as specified in rfc 3062
+//
+// https://tools.ietf.org/html/rfc3062
+//
+
+package 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:
+// userIdentity is a string representing the user associated with the request.
+// This string may or may not be an LDAPDN (RFC 2253).
+// If userIdentity is empty then the operation will act on the user associated
+// with the session.
+//
+// oldPassword is the current user's password, it can be empty or it can be
+// needed depending on the session user access rights (usually an administrator
+// can change a user's password without knowing the current one) and the
+// password policy (see pwdSafeModify password policy's attribute)
+//
+// newPassword is the desired user's password. If empty the server can return
+// an error or generate a new password that will be available in the
+// PasswordModifyResult.GeneratedPassword
+//
+func NewPasswordModifyRequest(userIdentity string, oldPassword string, newPassword string) *PasswordModifyRequest {
+ return &PasswordModifyRequest{
+ UserIdentity: userIdentity,
+ OldPassword: oldPassword,
+ NewPassword: newPassword,
+ }
+}
+
+// PasswordModify performs the modification request
+func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error) {
+ msgCtx, err := l.doRequest(passwordModifyRequest)
+ if err != nil {
+ return nil, err
+ }
+ defer l.finishMessage(msgCtx)
+
+ packet, err := l.readPacket(msgCtx)
+ if err != nil {
+ return nil, err
+ }
+
+ result := &PasswordModifyResult{}
+
+ if packet.Children[1].Tag == ApplicationExtendedResponse {
+ err := GetLDAPError(packet)
+ if err != nil {
+ if IsErrorWithCode(err, LDAPResultReferral) {
+ for _, child := range packet.Children[1].Children {
+ if child.Tag == 3 {
+ result.Referral = child.Children[0].Value.(string)
+ }
+ }
+ }
+ return result, err
+ }
+ } else {
+ return nil, NewError(ErrorUnexpectedResponse, fmt.Errorf("unexpected Response: %d", packet.Children[1].Tag))
+ }
+
+ extendedResponse := packet.Children[1]
+ for _, child := range extendedResponse.Children {
+ if child.Tag == 11 {
+ passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes())
+ if len(passwordModifyResponseValue.Children) == 1 {
+ if passwordModifyResponseValue.Children[0].Tag == 0 {
+ result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes())
+ }
+ }
+ }
+ }
+
+ return result, nil
+}
diff --git a/vendor/github.com/mattermost/ldap/request.go b/vendor/github.com/mattermost/ldap/request.go
new file mode 100644
index 00000000..8c68f34a
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/request.go
@@ -0,0 +1,66 @@
+package 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")
+)
+
+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) {
+ 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
+}
diff --git a/vendor/github.com/mattermost/ldap/search.go b/vendor/github.com/mattermost/ldap/search.go
new file mode 100644
index 00000000..186246c0
--- /dev/null
+++ b/vendor/github.com/mattermost/ldap/search.go
@@ -0,0 +1,425 @@
+// File contains Search functionality
+//
+// https://tools.ietf.org/html/rfc4511
+//
+// SearchRequest ::= [APPLICATION 3] SEQUENCE {
+// baseObject LDAPDN,
+// scope ENUMERATED {
+// baseObject (0),
+// singleLevel (1),
+// wholeSubtree (2),
+// ... },
+// derefAliases ENUMERATED {
+// neverDerefAliases (0),
+// derefInSearching (1),
+// derefFindingBaseObj (2),
+// derefAlways (3) },
+// sizeLimit INTEGER (0 .. maxInt),
+// timeLimit INTEGER (0 .. maxInt),
+// typesOnly BOOLEAN,
+// filter Filter,
+// attributes AttributeSelection }
+//
+// AttributeSelection ::= SEQUENCE OF selector LDAPString
+// -- The LDAPString is constrained to
+// -- <attributeSelector> in Section 4.5.1.8
+//
+// Filter ::= CHOICE {
+// and [0] SET SIZE (1..MAX) OF filter Filter,
+// or [1] SET SIZE (1..MAX) OF filter Filter,
+// not [2] Filter,
+// equalityMatch [3] AttributeValueAssertion,
+// substrings [4] SubstringFilter,
+// greaterOrEqual [5] AttributeValueAssertion,
+// lessOrEqual [6] AttributeValueAssertion,
+// present [7] AttributeDescription,
+// approxMatch [8] AttributeValueAssertion,
+// extensibleMatch [9] MatchingRuleAssertion,
+// ... }
+//
+// SubstringFilter ::= SEQUENCE {
+// type AttributeDescription,
+// substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE {
+// initial [0] AssertionValue, -- can occur at most once
+// any [1] AssertionValue,
+// final [2] AssertionValue } -- can occur at most once
+// }
+//
+// MatchingRuleAssertion ::= SEQUENCE {
+// matchingRule [1] MatchingRuleId OPTIONAL,
+// type [2] AttributeDescription OPTIONAL,
+// matchValue [3] AssertionValue,
+// dnAttributes [4] BOOLEAN DEFAULT FALSE }
+//
+//
+
+package ldap
+
+import (
+ "errors"
+ "fmt"
+ "sort"
+ "strings"
+
+ ber "github.com/go-asn1-ber/asn1-ber"
+)
+
+// scope choices
+const (
+ ScopeBaseObject = 0
+ ScopeSingleLevel = 1
+ ScopeWholeSubtree = 2
+)
+
+// ScopeMap contains human readable descriptions of scope choices
+var ScopeMap = map[int]string{
+ ScopeBaseObject: "Base Object",
+ ScopeSingleLevel: "Single Level",
+ ScopeWholeSubtree: "Whole Subtree",
+}
+
+// derefAliases
+const (
+ NeverDerefAliases = 0
+ DerefInSearching = 1
+ DerefFindingBaseObj = 2
+ DerefAlways = 3
+)
+
+// DerefMap contains human readable descriptions of derefAliases choices
+var DerefMap = map[int]string{
+ NeverDerefAliases: "NeverDerefAliases",
+ DerefInSearching: "DerefInSearching",
+ DerefFindingBaseObj: "DerefFindingBaseObj",
+ DerefAlways: "DerefAlways",
+}
+
+// NewEntry returns an Entry object with the specified distinguished name and attribute key-value pairs.
+// The map of attributes is accessed in alphabetical order of the keys in order to ensure that, for the
+// same input map of attributes, the output entry will contain the same order of attributes
+func NewEntry(dn string, attributes map[string][]string) *Entry {
+ var attributeNames []string
+ for attributeName := range attributes {
+ attributeNames = append(attributeNames, attributeName)
+ }
+ sort.Strings(attributeNames)
+
+ var encodedAttributes []*EntryAttribute
+ for _, attributeName := range attributeNames {
+ encodedAttributes = append(encodedAttributes, NewEntryAttribute(attributeName, attributes[attributeName]))
+ }
+ return &Entry{
+ DN: dn,
+ Attributes: encodedAttributes,
+ }
+}
+
+// Entry represents a single search result entry
+type Entry struct {
+ // DN is the distinguished name of the entry
+ DN string
+ // Attributes are the returned attributes for the entry
+ Attributes []*EntryAttribute
+}
+
+// GetAttributeValues returns the values for the named attribute, or an empty list
+func (e *Entry) GetAttributeValues(attribute string) []string {
+ for _, attr := range e.Attributes {
+ if attr.Name == attribute {
+ return attr.Values
+ }
+ }
+ return []string{}
+}
+
+// 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{}
+}
+
+// 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]
+}
+
+// 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]
+}
+
+// Print outputs a human-readable description
+func (e *Entry) Print() {
+ fmt.Printf("DN: %s\n", e.DN)
+ for _, attr := range e.Attributes {
+ attr.Print()
+ }
+}
+
+// PrettyPrint outputs a human-readable description indenting
+func (e *Entry) PrettyPrint(indent int) {
+ fmt.Printf("%sDN: %s\n", strings.Repeat(" ", indent), e.DN)
+ for _, attr := range e.Attributes {
+ attr.PrettyPrint(indent + 2)
+ }
+}
+
+// NewEntryAttribute returns a new EntryAttribute with the desired key-value pair
+func NewEntryAttribute(name string, values []string) *EntryAttribute {
+ var bytes [][]byte
+ for _, value := range values {
+ bytes = append(bytes, []byte(value))
+ }
+ return &EntryAttribute{
+ Name: name,
+ Values: values,
+ ByteValues: bytes,
+ }
+}
+
+// EntryAttribute holds a single attribute
+type EntryAttribute struct {
+ // Name is the name of the attribute
+ Name string
+ // Values contain the string values of the attribute
+ Values []string
+ // ByteValues contain the raw values of the attribute
+ ByteValues [][]byte
+}
+
+// Print outputs a human-readable description
+func (e *EntryAttribute) Print() {
+ fmt.Printf("%s: %s\n", e.Name, e.Values)
+}
+
+// PrettyPrint outputs a human-readable description with indenting
+func (e *EntryAttribute) PrettyPrint(indent int) {
+ fmt.Printf("%s%s: %s\n", strings.Repeat(" ", indent), e.Name, e.Values)
+}
+
+// SearchResult holds the server's response to a search request
+type SearchResult struct {
+ // Entries are the returned entries
+ Entries []*Entry
+ // Referrals are the returned referrals
+ Referrals []string
+ // Controls are the returned controls
+ Controls []Control
+}
+
+// Print outputs a human-readable description
+func (s *SearchResult) Print() {
+ for _, entry := range s.Entries {
+ entry.Print()
+ }
+}
+
+// PrettyPrint outputs a human-readable description with indenting
+func (s *SearchResult) PrettyPrint(indent int) {
+ for _, entry := range s.Entries {
+ entry.PrettyPrint(indent)
+ }
+}
+
+// SearchRequest represents a search request to send to the server
+type SearchRequest struct {
+ BaseDN string
+ Scope int
+ DerefAliases int
+ SizeLimit int
+ TimeLimit int
+ TypesOnly bool
+ Filter string
+ Attributes []string
+ Controls []Control
+}
+
+func (req *SearchRequest) appendTo(envelope *ber.Packet) error {
+ pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationSearchRequest, nil, "Search Request")
+ pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.BaseDN, "Base DN"))
+ pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(req.Scope), "Scope"))
+ pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(req.DerefAliases), "Deref Aliases"))
+ pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(req.SizeLimit), "Size Limit"))
+ pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(req.TimeLimit), "Time Limit"))
+ pkt.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, req.TypesOnly, "Types Only"))
+ // compile and encode filter
+ filterPacket, err := CompileFilter(req.Filter)
+ if err != nil {
+ return err
+ }
+ pkt.AppendChild(filterPacket)
+ // encode attributes
+ attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
+ for _, attribute := range req.Attributes {
+ attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
+ }
+ pkt.AppendChild(attributesPacket)
+
+ envelope.AppendChild(pkt)
+ if len(req.Controls) > 0 {
+ envelope.AppendChild(encodeControls(req.Controls))
+ }
+
+ return nil
+}
+
+// NewSearchRequest creates a new search request
+func NewSearchRequest(
+ BaseDN string,
+ Scope, DerefAliases, SizeLimit, TimeLimit int,
+ TypesOnly bool,
+ Filter string,
+ Attributes []string,
+ Controls []Control,
+) *SearchRequest {
+ return &SearchRequest{
+ BaseDN: BaseDN,
+ Scope: Scope,
+ DerefAliases: DerefAliases,
+ SizeLimit: SizeLimit,
+ TimeLimit: TimeLimit,
+ TypesOnly: TypesOnly,
+ Filter: Filter,
+ Attributes: Attributes,
+ Controls: Controls,
+ }
+}
+
+// SearchWithPaging accepts a search request and desired page size in order to execute LDAP queries to fulfill the
+// search request. All paged LDAP query responses will be buffered and the final result will be returned atomically.
+// The following four cases are possible given the arguments:
+// - given SearchRequest missing a control of type ControlTypePaging: we will add one with the desired paging size
+// - given SearchRequest contains a control of type ControlTypePaging that isn't actually a ControlPaging: fail without issuing any queries
+// - given SearchRequest contains a control of type ControlTypePaging with pagingSize equal to the size requested: no change to the search request
+// - given SearchRequest contains a control of type ControlTypePaging with pagingSize not equal to the size requested: fail without issuing any queries
+// A requested pagingSize of 0 is interpreted as no limit by LDAP servers.
+func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) {
+ var pagingControl *ControlPaging
+
+ control := FindControl(searchRequest.Controls, ControlTypePaging)
+ if control == nil {
+ pagingControl = NewControlPaging(pagingSize)
+ searchRequest.Controls = append(searchRequest.Controls, pagingControl)
+ } else {
+ castControl, ok := control.(*ControlPaging)
+ if !ok {
+ return nil, fmt.Errorf("expected paging control to be of type *ControlPaging, got %v", control)
+ }
+ if castControl.PagingSize != pagingSize {
+ return nil, fmt.Errorf("paging size given in search request (%d) conflicts with size given in search call (%d)", castControl.PagingSize, pagingSize)
+ }
+ pagingControl = castControl
+ }
+
+ searchResult := new(SearchResult)
+ for {
+ result, err := l.Search(searchRequest)
+ l.Debug.Printf("Looking for Paging Control...")
+ if err != nil {
+ return searchResult, err
+ }
+ if result == nil {
+ return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
+ }
+
+ for _, entry := range result.Entries {
+ searchResult.Entries = append(searchResult.Entries, entry)
+ }
+ for _, referral := range result.Referrals {
+ searchResult.Referrals = append(searchResult.Referrals, referral)
+ }
+ for _, control := range result.Controls {
+ searchResult.Controls = append(searchResult.Controls, control)
+ }
+
+ l.Debug.Printf("Looking for Paging Control...")
+ pagingResult := FindControl(result.Controls, ControlTypePaging)
+ if pagingResult == nil {
+ pagingControl = nil
+ l.Debug.Printf("Could not find paging control. Breaking...")
+ break
+ }
+
+ cookie := pagingResult.(*ControlPaging).Cookie
+ if len(cookie) == 0 {
+ pagingControl = nil
+ l.Debug.Printf("Could not find cookie. Breaking...")
+ break
+ }
+ pagingControl.SetCookie(cookie)
+ }
+
+ if pagingControl != nil {
+ l.Debug.Printf("Abandoning Paging...")
+ pagingControl.PagingSize = 0
+ l.Search(searchRequest)
+ }
+
+ return searchResult, nil
+}
+
+// Search performs the given search request
+func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
+ msgCtx, err := l.doRequest(searchRequest)
+ if err != nil {
+ return nil, err
+ }
+ defer l.finishMessage(msgCtx)
+
+ result := &SearchResult{
+ Entries: make([]*Entry, 0),
+ Referrals: make([]string, 0),
+ Controls: make([]Control, 0)}
+
+ for {
+ packet, err := l.readPacket(msgCtx)
+ if err != nil {
+ return nil, err
+ }
+
+ switch packet.Children[1].Tag {
+ case 4:
+ entry := new(Entry)
+ entry.DN = packet.Children[1].Children[0].Value.(string)
+ for _, child := range packet.Children[1].Children[1].Children {
+ attr := new(EntryAttribute)
+ attr.Name = child.Children[0].Value.(string)
+ for _, value := range child.Children[1].Children {
+ attr.Values = append(attr.Values, value.Value.(string))
+ attr.ByteValues = append(attr.ByteValues, value.ByteValue)
+ }
+ entry.Attributes = append(entry.Attributes, attr)
+ }
+ result.Entries = append(result.Entries, entry)
+ case 5:
+ err := GetLDAPError(packet)
+ if err != nil {
+ return nil, err
+ }
+ if len(packet.Children) == 3 {
+ for _, child := range packet.Children[2].Children {
+ decodedChild, err := DecodeControl(child)
+ if err != nil {
+ return nil, 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))
+ }
+ }
+}