Upgrade go-ldap to 3.1.10, containing the send race fix (#8937)
* Upgrade go-ldap to 3.1.10, containing the send race fix
This commit is contained in:
parent
182bfc86f8
commit
16cc804086
2
go.mod
2
go.mod
|
@ -37,7 +37,7 @@ require (
|
||||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa
|
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa
|
||||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
|
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
|
||||||
github.com/go-errors/errors v1.0.1
|
github.com/go-errors/errors v1.0.1
|
||||||
github.com/go-ldap/ldap/v3 v3.1.3
|
github.com/go-ldap/ldap/v3 v3.1.10
|
||||||
github.com/go-ole/go-ole v1.2.1 // indirect
|
github.com/go-ole/go-ole v1.2.1 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.4.1
|
github.com/go-sql-driver/mysql v1.4.1
|
||||||
github.com/go-test/deep v1.0.2
|
github.com/go-test/deep v1.0.2
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -220,6 +220,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-ldap/ldap/v3 v3.1.3 h1:RIgdpHXJpsUqUK5WXwKyVsESrGFqo5BRWPk3RR4/ogQ=
|
github.com/go-ldap/ldap/v3 v3.1.3 h1:RIgdpHXJpsUqUK5WXwKyVsESrGFqo5BRWPk3RR4/ogQ=
|
||||||
github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8=
|
github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8=
|
||||||
|
github.com/go-ldap/ldap/v3 v3.1.10 h1:7WsKqasmPThNvdl0Q5GPpbTDD/ZD98CfuawrMIuh7qQ=
|
||||||
|
github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=
|
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
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
|
|
|
@ -1,12 +0,0 @@
|
||||||
# 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)
|
|
|
@ -1,82 +0,0 @@
|
||||||
.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
|
|
|
@ -1,40 +0,0 @@
|
||||||
[![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
|
|
||||||
|
|
||||||
## 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
|
|
|
@ -1,12 +1,3 @@
|
||||||
//
|
|
||||||
// https://tools.ietf.org/html/rfc4511
|
|
||||||
//
|
|
||||||
// AddRequest ::= [APPLICATION 8] SEQUENCE {
|
|
||||||
// entry LDAPDN,
|
|
||||||
// attributes AttributeList }
|
|
||||||
//
|
|
||||||
// AttributeList ::= SEQUENCE OF attribute Attribute
|
|
||||||
|
|
||||||
package ldap
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
package ldap
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
enchex "encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
|
||||||
ber "github.com/go-asn1-ber/asn1-ber"
|
ber "github.com/go-asn1-ber/asn1-ber"
|
||||||
)
|
)
|
||||||
|
@ -115,6 +121,237 @@ func (l *Conn) UnauthenticatedBind(username string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DigestMD5BindRequest represents a digest-md5 bind operation
|
||||||
|
type DigestMD5BindRequest struct {
|
||||||
|
Host string
|
||||||
|
// Username is the name of the Directory object that the client wishes to bind as
|
||||||
|
Username string
|
||||||
|
// Password is the credentials to bind with
|
||||||
|
Password string
|
||||||
|
// Controls are optional controls to send with the bind request
|
||||||
|
Controls []Control
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *DigestMD5BindRequest) appendTo(envelope *ber.Packet) error {
|
||||||
|
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
|
||||||
|
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
|
||||||
|
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
|
||||||
|
|
||||||
|
auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
|
||||||
|
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "DIGEST-MD5", "SASL Mech"))
|
||||||
|
request.AppendChild(auth)
|
||||||
|
envelope.AppendChild(request)
|
||||||
|
if len(req.Controls) > 0 {
|
||||||
|
envelope.AppendChild(encodeControls(req.Controls))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DigestMD5BindResult contains the response from the server
|
||||||
|
type DigestMD5BindResult struct {
|
||||||
|
Controls []Control
|
||||||
|
}
|
||||||
|
|
||||||
|
// MD5Bind performs a digest-md5 bind with the given host, username and password.
|
||||||
|
func (l *Conn) MD5Bind(host, username, password string) error {
|
||||||
|
req := &DigestMD5BindRequest{
|
||||||
|
Host: host,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
_, err := l.DigestMD5Bind(req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DigestMD5Bind performs the digest-md5 bind operation defined in the given request
|
||||||
|
func (l *Conn) DigestMD5Bind(digestMD5BindRequest *DigestMD5BindRequest) (*DigestMD5BindResult, error) {
|
||||||
|
if digestMD5BindRequest.Password == "" {
|
||||||
|
return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client"))
|
||||||
|
}
|
||||||
|
|
||||||
|
msgCtx, err := l.doRequest(digestMD5BindRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer l.finishMessage(msgCtx)
|
||||||
|
|
||||||
|
packet, err := l.readPacket(msgCtx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
||||||
|
if l.Debug {
|
||||||
|
if err = addLDAPDescriptions(packet); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ber.PrintPacket(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &DigestMD5BindResult{
|
||||||
|
Controls: make([]Control, 0),
|
||||||
|
}
|
||||||
|
var params map[string]string
|
||||||
|
if len(packet.Children) == 2 {
|
||||||
|
if len(packet.Children[1].Children) == 4 {
|
||||||
|
child := packet.Children[1].Children[0]
|
||||||
|
if child.Tag != ber.TagEnumerated {
|
||||||
|
return result, GetLDAPError(packet)
|
||||||
|
}
|
||||||
|
if child.Value.(int64) != 14 {
|
||||||
|
return result, GetLDAPError(packet)
|
||||||
|
}
|
||||||
|
child = packet.Children[1].Children[3]
|
||||||
|
if child.Tag != ber.TagObjectDescriptor {
|
||||||
|
return result, GetLDAPError(packet)
|
||||||
|
}
|
||||||
|
if child.Data == nil {
|
||||||
|
return result, GetLDAPError(packet)
|
||||||
|
}
|
||||||
|
data, _ := ioutil.ReadAll(child.Data)
|
||||||
|
params, err = parseParams(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return result, fmt.Errorf("parsing digest-challenge: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if params != nil {
|
||||||
|
resp := computeResponse(
|
||||||
|
params,
|
||||||
|
"ldap/"+strings.ToLower(digestMD5BindRequest.Host),
|
||||||
|
digestMD5BindRequest.Username,
|
||||||
|
digestMD5BindRequest.Password,
|
||||||
|
)
|
||||||
|
packet = ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
||||||
|
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
|
||||||
|
|
||||||
|
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
|
||||||
|
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
|
||||||
|
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
|
||||||
|
|
||||||
|
auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
|
||||||
|
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "DIGEST-MD5", "SASL Mech"))
|
||||||
|
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, resp, "Credentials"))
|
||||||
|
request.AppendChild(auth)
|
||||||
|
packet.AppendChild(request)
|
||||||
|
msgCtx, err = l.sendMessage(packet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("send message: %s", err)
|
||||||
|
}
|
||||||
|
defer l.finishMessage(msgCtx)
|
||||||
|
packetResponse, ok := <-msgCtx.responses
|
||||||
|
if !ok {
|
||||||
|
return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
|
||||||
|
}
|
||||||
|
packet, err = packetResponse.ReadPacket()
|
||||||
|
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read packet: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = GetLDAPError(packet)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseParams(str string) (map[string]string, error) {
|
||||||
|
m := make(map[string]string)
|
||||||
|
var key, value string
|
||||||
|
var state int
|
||||||
|
for i := 0; i <= len(str); i++ {
|
||||||
|
switch state {
|
||||||
|
case 0: //reading key
|
||||||
|
if i == len(str) {
|
||||||
|
return nil, fmt.Errorf("syntax error on %d", i)
|
||||||
|
}
|
||||||
|
if str[i] != '=' {
|
||||||
|
key += string(str[i])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
state = 1
|
||||||
|
case 1: //reading value
|
||||||
|
if i == len(str) {
|
||||||
|
m[key] = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch str[i] {
|
||||||
|
case ',':
|
||||||
|
m[key] = value
|
||||||
|
state = 0
|
||||||
|
key = ""
|
||||||
|
value = ""
|
||||||
|
case '"':
|
||||||
|
if value != "" {
|
||||||
|
return nil, fmt.Errorf("syntax error on %d", i)
|
||||||
|
}
|
||||||
|
state = 2
|
||||||
|
default:
|
||||||
|
value += string(str[i])
|
||||||
|
}
|
||||||
|
case 2: //inside quotes
|
||||||
|
if i == len(str) {
|
||||||
|
return nil, fmt.Errorf("syntax error on %d", i)
|
||||||
|
}
|
||||||
|
if str[i] != '"' {
|
||||||
|
value += string(str[i])
|
||||||
|
} else {
|
||||||
|
state = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeResponse(params map[string]string, uri, username, password string) string {
|
||||||
|
nc := "00000001"
|
||||||
|
qop := "auth"
|
||||||
|
cnonce := enchex.EncodeToString(randomBytes(16))
|
||||||
|
x := username + ":" + params["realm"] + ":" + password
|
||||||
|
y := md5Hash([]byte(x))
|
||||||
|
|
||||||
|
a1 := bytes.NewBuffer(y)
|
||||||
|
a1.WriteString(":" + params["nonce"] + ":" + cnonce)
|
||||||
|
if len(params["authzid"]) > 0 {
|
||||||
|
a1.WriteString(":" + params["authzid"])
|
||||||
|
}
|
||||||
|
a2 := bytes.NewBuffer([]byte("AUTHENTICATE"))
|
||||||
|
a2.WriteString(":" + uri)
|
||||||
|
ha1 := enchex.EncodeToString(md5Hash(a1.Bytes()))
|
||||||
|
ha2 := enchex.EncodeToString(md5Hash(a2.Bytes()))
|
||||||
|
|
||||||
|
kd := ha1
|
||||||
|
kd += ":" + params["nonce"]
|
||||||
|
kd += ":" + nc
|
||||||
|
kd += ":" + cnonce
|
||||||
|
kd += ":" + qop
|
||||||
|
kd += ":" + ha2
|
||||||
|
resp := enchex.EncodeToString(md5Hash([]byte(kd)))
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`username="%s",realm="%s",nonce="%s",cnonce="%s",nc=00000001,qop=%s,digest-uri="%s",response=%s`,
|
||||||
|
username,
|
||||||
|
params["realm"],
|
||||||
|
params["nonce"],
|
||||||
|
cnonce,
|
||||||
|
qop,
|
||||||
|
uri,
|
||||||
|
resp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func md5Hash(b []byte) []byte {
|
||||||
|
hasher := md5.New()
|
||||||
|
hasher.Write(b)
|
||||||
|
return hasher.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomBytes(len int) []byte {
|
||||||
|
b := make([]byte, len)
|
||||||
|
for i := 0; i < len; i++ {
|
||||||
|
b[i] = byte(rand.Intn(256))
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
var externalBindRequest = requestFunc(func(envelope *ber.Packet) error {
|
var externalBindRequest = requestFunc(func(envelope *ber.Packet) error {
|
||||||
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
|
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.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
|
||||||
|
|
|
@ -1,22 +1,3 @@
|
||||||
// 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
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -112,8 +112,63 @@ var _ Client = &Conn{}
|
||||||
// multiple places will probably result in undesired behaviour.
|
// multiple places will probably result in undesired behaviour.
|
||||||
var DefaultTimeout = 60 * time.Second
|
var DefaultTimeout = 60 * time.Second
|
||||||
|
|
||||||
|
// DialOpt configures DialContext.
|
||||||
|
type DialOpt func(*DialContext)
|
||||||
|
|
||||||
|
// DialWithDialer updates net.Dialer in DialContext.
|
||||||
|
func DialWithDialer(d *net.Dialer) DialOpt {
|
||||||
|
return func(dc *DialContext) {
|
||||||
|
dc.d = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialWithTLSConfig updates tls.Config in DialContext.
|
||||||
|
func DialWithTLSConfig(tc *tls.Config) DialOpt {
|
||||||
|
return func(dc *DialContext) {
|
||||||
|
dc.tc = tc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialContext contains necessary parameters to dial the given ldap URL.
|
||||||
|
type DialContext struct {
|
||||||
|
d *net.Dialer
|
||||||
|
tc *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DialContext) dial(u *url.URL) (net.Conn, error) {
|
||||||
|
if u.Scheme == "ldapi" {
|
||||||
|
if u.Path == "" || u.Path == "/" {
|
||||||
|
u.Path = "/var/run/slapd/ldapi"
|
||||||
|
}
|
||||||
|
return dc.d.Dial("unix", u.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
host, port, err := net.SplitHostPort(u.Host)
|
||||||
|
if err != nil {
|
||||||
|
// we asume that error is due to missing port
|
||||||
|
host = u.Host
|
||||||
|
port = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "ldap":
|
||||||
|
if port == "" {
|
||||||
|
port = DefaultLdapPort
|
||||||
|
}
|
||||||
|
return dc.d.Dial("tcp", net.JoinHostPort(host, port))
|
||||||
|
case "ldaps":
|
||||||
|
if port == "" {
|
||||||
|
port = DefaultLdapsPort
|
||||||
|
}
|
||||||
|
return tls.DialWithDialer(dc.d, "tcp", net.JoinHostPort(host, port), dc.tc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Unknown scheme '%s'", u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
// Dial connects to the given address on the given network using net.Dial
|
// Dial connects to the given address on the given network using net.Dial
|
||||||
// and then returns a new Conn for the connection.
|
// and then returns a new Conn for the connection.
|
||||||
|
// @deprecated Use DialURL instead.
|
||||||
func Dial(network, addr string) (*Conn, error) {
|
func Dial(network, addr string) (*Conn, error) {
|
||||||
c, err := net.DialTimeout(network, addr, DefaultTimeout)
|
c, err := net.DialTimeout(network, addr, DefaultTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -126,6 +181,7 @@ func Dial(network, addr string) (*Conn, error) {
|
||||||
|
|
||||||
// DialTLS connects to the given address on the given network using tls.Dial
|
// DialTLS connects to the given address on the given network using tls.Dial
|
||||||
// and then returns a new Conn for the connection.
|
// and then returns a new Conn for the connection.
|
||||||
|
// @deprecated Use DialURL instead.
|
||||||
func DialTLS(network, addr string, config *tls.Config) (*Conn, error) {
|
func DialTLS(network, addr string, config *tls.Config) (*Conn, error) {
|
||||||
c, err := tls.DialWithDialer(&net.Dialer{Timeout: DefaultTimeout}, network, addr, config)
|
c, err := tls.DialWithDialer(&net.Dialer{Timeout: DefaultTimeout}, network, addr, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -136,44 +192,31 @@ func DialTLS(network, addr string, config *tls.Config) (*Conn, error) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialURL connects to the given ldap URL vie TCP using tls.Dial or net.Dial if ldaps://
|
// DialURL connects to the given ldap URL.
|
||||||
// or ldap:// specified as protocol. On success a new Conn for the connection
|
// The following schemas are supported: ldap://, ldaps://, ldapi://.
|
||||||
// is returned.
|
// On success a new Conn for the connection is returned.
|
||||||
func DialURL(addr string) (*Conn, error) {
|
func DialURL(addr string, opts ...DialOpt) (*Conn, error) {
|
||||||
lurl, err := url.Parse(addr)
|
u, err := url.Parse(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewError(ErrorNetwork, err)
|
return nil, NewError(ErrorNetwork, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
host, port, err := net.SplitHostPort(lurl.Host)
|
var dc DialContext
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&dc)
|
||||||
|
}
|
||||||
|
if dc.d == nil {
|
||||||
|
dc.d = &net.Dialer{Timeout: DefaultTimeout}
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := dc.dial(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// we asume that error is due to missing port
|
return nil, NewError(ErrorNetwork, err)
|
||||||
host = lurl.Host
|
|
||||||
port = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch lurl.Scheme {
|
conn := NewConn(c, u.Scheme == "ldaps")
|
||||||
case "ldapi":
|
conn.Start()
|
||||||
if lurl.Path == "" || lurl.Path == "/" {
|
return conn, nil
|
||||||
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.
|
// NewConn returns a new Conn using conn for network I/O.
|
||||||
|
@ -278,7 +321,7 @@ func (l *Conn) StartTLS(config *tls.Config) error {
|
||||||
l.Close()
|
l.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ber.PrintPacket(packet)
|
l.Debug.PrintPacket(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := GetLDAPError(packet); err == nil {
|
if err := GetLDAPError(packet); err == nil {
|
||||||
|
@ -347,7 +390,12 @@ func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags)
|
||||||
responses: responses,
|
responses: responses,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
l.sendProcessMessage(message)
|
if !l.sendProcessMessage(message) {
|
||||||
|
if l.IsClosing() {
|
||||||
|
return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed"))
|
||||||
|
}
|
||||||
|
return nil, NewError(ErrorNetwork, errors.New("ldap: could not send message for unknown reason"))
|
||||||
|
}
|
||||||
return message.Context, nil
|
return message.Context, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,7 +499,7 @@ func (l *Conn) processMessages() {
|
||||||
msgCtx.sendResponse(&PacketResponse{message.Packet, nil})
|
msgCtx.sendResponse(&PacketResponse{message.Packet, nil})
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Received unexpected message %d, %v", message.MessageID, l.IsClosing())
|
log.Printf("Received unexpected message %d, %v", message.MessageID, l.IsClosing())
|
||||||
ber.PrintPacket(message.Packet)
|
l.Debug.PrintPacket(message.Packet)
|
||||||
}
|
}
|
||||||
case MessageTimeout:
|
case MessageTimeout:
|
||||||
// Handle the timeout by closing the channel
|
// Handle the timeout by closing the channel
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
//
|
|
||||||
// https://tools.ietf.org/html/rfc4511
|
|
||||||
//
|
|
||||||
// DelRequest ::= [APPLICATION 10] LDAPDN
|
|
||||||
|
|
||||||
package ldap
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,44 +1,3 @@
|
||||||
// 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
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -48,7 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-asn1-ber/asn1-ber"
|
ber "github.com/go-asn1-ber/asn1-ber"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514
|
// AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514
|
||||||
|
@ -69,7 +28,8 @@ type DN struct {
|
||||||
RDNs []*RelativeDN
|
RDNs []*RelativeDN
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseDN returns a distinguishedName or an error
|
// ParseDN returns a distinguishedName or an error.
|
||||||
|
// The function respects https://tools.ietf.org/html/rfc4514
|
||||||
func ParseDN(str string) (*DN, error) {
|
func ParseDN(str string) (*DN, error) {
|
||||||
dn := new(DN)
|
dn := new(DN)
|
||||||
dn.RDNs = make([]*RelativeDN, 0)
|
dn.RDNs = make([]*RelativeDN, 0)
|
||||||
|
|
|
@ -5,10 +5,12 @@ import (
|
||||||
hexpac "encoding/hex"
|
hexpac "encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/go-asn1-ber/asn1-ber"
|
ber "github.com/go-asn1-ber/asn1-ber"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Filter choices
|
// Filter choices
|
||||||
|
@ -69,6 +71,8 @@ var MatchingRuleAssertionMap = map[uint64]string{
|
||||||
MatchingRuleAssertionDNAttributes: "Matching Rule Assertion DN Attributes",
|
MatchingRuleAssertionDNAttributes: "Matching Rule Assertion DN Attributes",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _SymbolAny = []byte{'*'}
|
||||||
|
|
||||||
// CompileFilter converts a string representation of a filter into a BER-encoded packet
|
// CompileFilter converts a string representation of a filter into a BER-encoded packet
|
||||||
func CompileFilter(filter string) (*ber.Packet, error) {
|
func CompileFilter(filter string) (*ber.Packet, error) {
|
||||||
if len(filter) == 0 || filter[0] != '(' {
|
if len(filter) == 0 || filter[0] != '(' {
|
||||||
|
@ -88,74 +92,75 @@ func CompileFilter(filter string) (*ber.Packet, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecompileFilter converts a packet representation of a filter into a string representation
|
// DecompileFilter converts a packet representation of a filter into a string representation
|
||||||
func DecompileFilter(packet *ber.Packet) (ret string, err error) {
|
func DecompileFilter(packet *ber.Packet) (_ string, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
err = NewError(ErrorFilterDecompile, errors.New("ldap: error decompiling filter"))
|
err = NewError(ErrorFilterDecompile, errors.New("ldap: error decompiling filter"))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
ret = "("
|
|
||||||
err = nil
|
buf := bytes.NewBuffer(nil)
|
||||||
|
buf.WriteByte('(')
|
||||||
childStr := ""
|
childStr := ""
|
||||||
|
|
||||||
switch packet.Tag {
|
switch packet.Tag {
|
||||||
case FilterAnd:
|
case FilterAnd:
|
||||||
ret += "&"
|
buf.WriteByte('&')
|
||||||
for _, child := range packet.Children {
|
for _, child := range packet.Children {
|
||||||
childStr, err = DecompileFilter(child)
|
childStr, err = DecompileFilter(child)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ret += childStr
|
buf.WriteString(childStr)
|
||||||
}
|
}
|
||||||
case FilterOr:
|
case FilterOr:
|
||||||
ret += "|"
|
buf.WriteByte('|')
|
||||||
for _, child := range packet.Children {
|
for _, child := range packet.Children {
|
||||||
childStr, err = DecompileFilter(child)
|
childStr, err = DecompileFilter(child)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ret += childStr
|
buf.WriteString(childStr)
|
||||||
}
|
}
|
||||||
case FilterNot:
|
case FilterNot:
|
||||||
ret += "!"
|
buf.WriteByte('!')
|
||||||
childStr, err = DecompileFilter(packet.Children[0])
|
childStr, err = DecompileFilter(packet.Children[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ret += childStr
|
buf.WriteString(childStr)
|
||||||
|
|
||||||
case FilterSubstrings:
|
case FilterSubstrings:
|
||||||
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes()))
|
||||||
ret += "="
|
buf.WriteByte('=')
|
||||||
for i, child := range packet.Children[1].Children {
|
for i, child := range packet.Children[1].Children {
|
||||||
if i == 0 && child.Tag != FilterSubstringsInitial {
|
if i == 0 && child.Tag != FilterSubstringsInitial {
|
||||||
ret += "*"
|
buf.Write(_SymbolAny)
|
||||||
}
|
}
|
||||||
ret += EscapeFilter(ber.DecodeString(child.Data.Bytes()))
|
buf.WriteString(EscapeFilter(ber.DecodeString(child.Data.Bytes())))
|
||||||
if child.Tag != FilterSubstringsFinal {
|
if child.Tag != FilterSubstringsFinal {
|
||||||
ret += "*"
|
buf.Write(_SymbolAny)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case FilterEqualityMatch:
|
case FilterEqualityMatch:
|
||||||
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes()))
|
||||||
ret += "="
|
buf.WriteByte('=')
|
||||||
ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
|
buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes())))
|
||||||
case FilterGreaterOrEqual:
|
case FilterGreaterOrEqual:
|
||||||
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes()))
|
||||||
ret += ">="
|
buf.WriteString(">=")
|
||||||
ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
|
buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes())))
|
||||||
case FilterLessOrEqual:
|
case FilterLessOrEqual:
|
||||||
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes()))
|
||||||
ret += "<="
|
buf.WriteString("<=")
|
||||||
ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
|
buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes())))
|
||||||
case FilterPresent:
|
case FilterPresent:
|
||||||
ret += ber.DecodeString(packet.Data.Bytes())
|
buf.WriteString(ber.DecodeString(packet.Data.Bytes()))
|
||||||
ret += "=*"
|
buf.WriteString("=*")
|
||||||
case FilterApproxMatch:
|
case FilterApproxMatch:
|
||||||
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes()))
|
||||||
ret += "~="
|
buf.WriteString("~=")
|
||||||
ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
|
buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes())))
|
||||||
case FilterExtensibleMatch:
|
case FilterExtensibleMatch:
|
||||||
attr := ""
|
attr := ""
|
||||||
dnAttributes := false
|
dnAttributes := false
|
||||||
|
@ -176,21 +181,22 @@ func DecompileFilter(packet *ber.Packet) (ret string, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(attr) > 0 {
|
if len(attr) > 0 {
|
||||||
ret += attr
|
buf.WriteString(attr)
|
||||||
}
|
}
|
||||||
if dnAttributes {
|
if dnAttributes {
|
||||||
ret += ":dn"
|
buf.WriteString(":dn")
|
||||||
}
|
}
|
||||||
if len(matchingRule) > 0 {
|
if len(matchingRule) > 0 {
|
||||||
ret += ":"
|
buf.WriteString(":")
|
||||||
ret += matchingRule
|
buf.WriteString(matchingRule)
|
||||||
}
|
}
|
||||||
ret += ":="
|
buf.WriteString(":=")
|
||||||
ret += EscapeFilter(value)
|
buf.WriteString(EscapeFilter(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
ret += ")"
|
buf.WriteByte(')')
|
||||||
return
|
|
||||||
|
return buf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func compileFilterSet(filter string, pos int, parent *ber.Packet) (int, error) {
|
func compileFilterSet(filter string, pos int, parent *ber.Packet) (int, error) {
|
||||||
|
@ -253,11 +259,10 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
|
||||||
)
|
)
|
||||||
|
|
||||||
state := stateReadingAttr
|
state := stateReadingAttr
|
||||||
|
attribute := bytes.NewBuffer(nil)
|
||||||
attribute := ""
|
|
||||||
extensibleDNAttributes := false
|
extensibleDNAttributes := false
|
||||||
extensibleMatchingRule := ""
|
extensibleMatchingRule := bytes.NewBuffer(nil)
|
||||||
condition := ""
|
condition := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
for newPos < len(filter) {
|
for newPos < len(filter) {
|
||||||
remainingFilter := filter[newPos:]
|
remainingFilter := filter[newPos:]
|
||||||
|
@ -324,7 +329,7 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
|
||||||
|
|
||||||
// Still reading the attribute name
|
// Still reading the attribute name
|
||||||
default:
|
default:
|
||||||
attribute += fmt.Sprintf("%c", currentRune)
|
attribute.WriteRune(currentRune)
|
||||||
newPos += currentWidth
|
newPos += currentWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,13 +343,13 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
|
||||||
|
|
||||||
// Still reading the matching rule oid
|
// Still reading the matching rule oid
|
||||||
default:
|
default:
|
||||||
extensibleMatchingRule += fmt.Sprintf("%c", currentRune)
|
extensibleMatchingRule.WriteRune(currentRune)
|
||||||
newPos += currentWidth
|
newPos += currentWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
case stateReadingCondition:
|
case stateReadingCondition:
|
||||||
// append to the condition
|
// append to the condition
|
||||||
condition += fmt.Sprintf("%c", currentRune)
|
condition.WriteRune(currentRune)
|
||||||
newPos += currentWidth
|
newPos += currentWidth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -368,17 +373,17 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Include the matching rule oid, if specified
|
// Include the matching rule oid, if specified
|
||||||
if len(extensibleMatchingRule) > 0 {
|
if extensibleMatchingRule.Len() > 0 {
|
||||||
packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchingRule, extensibleMatchingRule, MatchingRuleAssertionMap[MatchingRuleAssertionMatchingRule]))
|
packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchingRule, extensibleMatchingRule.String(), MatchingRuleAssertionMap[MatchingRuleAssertionMatchingRule]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include the attribute, if specified
|
// Include the attribute, if specified
|
||||||
if len(attribute) > 0 {
|
if attribute.Len() > 0 {
|
||||||
packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionType, attribute, MatchingRuleAssertionMap[MatchingRuleAssertionType]))
|
packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionType, attribute.String(), MatchingRuleAssertionMap[MatchingRuleAssertionType]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the value (only required child)
|
// Add the value (only required child)
|
||||||
encodedString, encodeErr := escapedStringToEncodedBytes(condition)
|
encodedString, encodeErr := decodeEscapedSymbols(condition.Bytes())
|
||||||
if encodeErr != nil {
|
if encodeErr != nil {
|
||||||
return packet, newPos, encodeErr
|
return packet, newPos, encodeErr
|
||||||
}
|
}
|
||||||
|
@ -389,16 +394,16 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
|
||||||
packet.AppendChild(ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionDNAttributes, extensibleDNAttributes, MatchingRuleAssertionMap[MatchingRuleAssertionDNAttributes]))
|
packet.AppendChild(ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionDNAttributes, extensibleDNAttributes, MatchingRuleAssertionMap[MatchingRuleAssertionDNAttributes]))
|
||||||
}
|
}
|
||||||
|
|
||||||
case packet.Tag == FilterEqualityMatch && condition == "*":
|
case packet.Tag == FilterEqualityMatch && bytes.Equal(condition.Bytes(), _SymbolAny):
|
||||||
packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute, FilterMap[FilterPresent])
|
packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute.String(), FilterMap[FilterPresent])
|
||||||
case packet.Tag == FilterEqualityMatch && strings.Contains(condition, "*"):
|
case packet.Tag == FilterEqualityMatch && bytes.Index(condition.Bytes(), _SymbolAny) > -1:
|
||||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
|
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute.String(), "Attribute"))
|
||||||
packet.Tag = FilterSubstrings
|
packet.Tag = FilterSubstrings
|
||||||
packet.Description = FilterMap[uint64(packet.Tag)]
|
packet.Description = FilterMap[uint64(packet.Tag)]
|
||||||
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings")
|
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings")
|
||||||
parts := strings.Split(condition, "*")
|
parts := bytes.Split(condition.Bytes(), _SymbolAny)
|
||||||
for i, part := range parts {
|
for i, part := range parts {
|
||||||
if part == "" {
|
if len(part) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var tag ber.Tag
|
var tag ber.Tag
|
||||||
|
@ -410,7 +415,7 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
|
||||||
default:
|
default:
|
||||||
tag = FilterSubstringsAny
|
tag = FilterSubstringsAny
|
||||||
}
|
}
|
||||||
encodedString, encodeErr := escapedStringToEncodedBytes(part)
|
encodedString, encodeErr := decodeEscapedSymbols(part)
|
||||||
if encodeErr != nil {
|
if encodeErr != nil {
|
||||||
return packet, newPos, encodeErr
|
return packet, newPos, encodeErr
|
||||||
}
|
}
|
||||||
|
@ -418,11 +423,11 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
|
||||||
}
|
}
|
||||||
packet.AppendChild(seq)
|
packet.AppendChild(seq)
|
||||||
default:
|
default:
|
||||||
encodedString, encodeErr := escapedStringToEncodedBytes(condition)
|
encodedString, encodeErr := decodeEscapedSymbols(condition.Bytes())
|
||||||
if encodeErr != nil {
|
if encodeErr != nil {
|
||||||
return packet, newPos, encodeErr
|
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, attribute.String(), "Attribute"))
|
||||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, encodedString, "Condition"))
|
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, encodedString, "Condition"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,34 +437,51 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert from "ABC\xx\xx\xx" form to literal bytes for transport
|
// Convert from "ABC\xx\xx\xx" form to literal bytes for transport
|
||||||
func escapedStringToEncodedBytes(escapedString string) (string, error) {
|
func decodeEscapedSymbols(src []byte) (string, error) {
|
||||||
var buffer bytes.Buffer
|
|
||||||
i := 0
|
var (
|
||||||
for i < len(escapedString) {
|
buffer bytes.Buffer
|
||||||
currentRune, currentWidth := utf8.DecodeRuneInString(escapedString[i:])
|
offset int
|
||||||
if currentRune == utf8.RuneError {
|
reader = bytes.NewReader(src)
|
||||||
return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", i))
|
byteHex []byte
|
||||||
|
byteVal []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
runeVal, runeSize, err := reader.ReadRune()
|
||||||
|
if err == io.EOF {
|
||||||
|
return buffer.String(), nil
|
||||||
|
} else if err != nil {
|
||||||
|
return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: failed to read filter: %v", err))
|
||||||
|
} else if runeVal == unicode.ReplacementChar {
|
||||||
|
return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for escaped hex characters and convert them to their literal value for transport.
|
if runeVal == '\\' {
|
||||||
if currentRune == '\\' {
|
|
||||||
// http://tools.ietf.org/search/rfc4515
|
// http://tools.ietf.org/search/rfc4515
|
||||||
// \ (%x5C) is not a valid character unless it is followed by two HEX characters due to not
|
// \ (%x5C) is not a valid character unless it is followed by two HEX characters due to not
|
||||||
// being a member of UTF1SUBSET.
|
// being a member of UTF1SUBSET.
|
||||||
if i+2 > len(escapedString) {
|
if byteHex == nil {
|
||||||
return "", NewError(ErrorFilterCompile, errors.New("ldap: missing characters for escape in filter"))
|
byteHex = make([]byte, 2)
|
||||||
|
byteVal = make([]byte, 1)
|
||||||
}
|
}
|
||||||
escByte, decodeErr := hexpac.DecodeString(escapedString[i+1 : i+3])
|
|
||||||
if decodeErr != nil {
|
if _, err := io.ReadFull(reader, byteHex); err != nil {
|
||||||
return "", NewError(ErrorFilterCompile, errors.New("ldap: invalid characters for escape in filter"))
|
if err == io.ErrUnexpectedEOF {
|
||||||
|
return "", NewError(ErrorFilterCompile, errors.New("ldap: missing characters for escape in filter"))
|
||||||
|
}
|
||||||
|
return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: invalid characters for escape in filter: %v", err))
|
||||||
}
|
}
|
||||||
buffer.WriteByte(escByte[0])
|
|
||||||
i += 2 // +1 from end of loop, so 3 total for \xx.
|
if _, err := hexpac.Decode(byteVal, byteHex); err != nil {
|
||||||
|
return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: invalid characters for escape in filter: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.Write(byteVal)
|
||||||
} else {
|
} else {
|
||||||
buffer.WriteRune(currentRune)
|
buffer.WriteRune(runeVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
i += currentWidth
|
offset += runeSize
|
||||||
}
|
}
|
||||||
return buffer.String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
module github.com/go-ldap/ldap/v3
|
module github.com/go-ldap/ldap/v3
|
||||||
|
|
||||||
require github.com/go-asn1-ber/asn1-ber v1.3.1
|
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
|
require github.com/go-asn1-ber/asn1-ber v1.3.1
|
||||||
|
|
|
@ -269,13 +269,18 @@ func addRequestDescriptions(packet *ber.Packet) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func addDefaultLDAPResponseDescriptions(packet *ber.Packet) error {
|
func addDefaultLDAPResponseDescriptions(packet *ber.Packet) error {
|
||||||
err := GetLDAPError(packet)
|
resultCode := uint16(LDAPResultSuccess)
|
||||||
if err == nil {
|
matchedDN := ""
|
||||||
return nil
|
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[err.(*Error).ResultCode] + ")"
|
|
||||||
packet.Children[1].Children[1].Description = "Matched DN (" + err.(*Error).MatchedDN + ")"
|
packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[resultCode] + ")"
|
||||||
packet.Children[1].Children[2].Description = "Error Message"
|
packet.Children[1].Children[1].Description = "Matched DN (" + matchedDN + ")"
|
||||||
|
packet.Children[1].Children[2].Description = description
|
||||||
if len(packet.Children[1].Children) > 3 {
|
if len(packet.Children[1].Children) > 3 {
|
||||||
packet.Children[1].Children[3].Description = "Referral"
|
packet.Children[1].Children[3].Description = "Referral"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,3 @@
|
||||||
// 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
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,28 +1,3 @@
|
||||||
// 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
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -33,9 +8,10 @@ import (
|
||||||
|
|
||||||
// Change operation choices
|
// Change operation choices
|
||||||
const (
|
const (
|
||||||
AddAttribute = 0
|
AddAttribute = 0
|
||||||
DeleteAttribute = 1
|
DeleteAttribute = 1
|
||||||
ReplaceAttribute = 2
|
ReplaceAttribute = 2
|
||||||
|
IncrementAttribute = 3 // (https://tools.ietf.org/html/rfc4525)
|
||||||
)
|
)
|
||||||
|
|
||||||
// PartialAttribute for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
|
// PartialAttribute for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
|
||||||
|
@ -97,6 +73,11 @@ func (req *ModifyRequest) Replace(attrType string, attrVals []string) {
|
||||||
req.appendChange(ReplaceAttribute, attrType, attrVals)
|
req.appendChange(ReplaceAttribute, attrType, attrVals)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Increment appends the given attribute to the list of changes to be made
|
||||||
|
func (req *ModifyRequest) Increment(attrType string, attrVal string) {
|
||||||
|
req.appendChange(IncrementAttribute, attrType, []string{attrVal})
|
||||||
|
}
|
||||||
|
|
||||||
func (req *ModifyRequest) appendChange(operation uint, attrType string, attrVals []string) {
|
func (req *ModifyRequest) appendChange(operation uint, attrType string, attrVals []string) {
|
||||||
req.Changes = append(req.Changes, Change{operation, PartialAttribute{Type: attrType, Vals: attrVals}})
|
req.Changes = append(req.Changes, Change{operation, PartialAttribute{Type: attrType, Vals: attrVals}})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file contains the password modify extended operation as specified in rfc 3062
|
|
||||||
//
|
|
||||||
// https://tools.ietf.org/html/rfc3062
|
|
||||||
//
|
|
||||||
|
|
||||||
package ldap
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -61,7 +56,7 @@ func (req *PasswordModifyRequest) appendTo(envelope *ber.Packet) error {
|
||||||
|
|
||||||
// NewPasswordModifyRequest creates a new PasswordModifyRequest
|
// NewPasswordModifyRequest creates a new PasswordModifyRequest
|
||||||
//
|
//
|
||||||
// According to the RFC 3602:
|
// According to the RFC 3602 (https://tools.ietf.org/html/rfc3062):
|
||||||
// userIdentity is a string representing the user associated with the request.
|
// userIdentity is a string representing the user associated with the request.
|
||||||
// This string may or may not be an LDAPDN (RFC 2253).
|
// This string may or may not be an LDAPDN (RFC 2253).
|
||||||
// If userIdentity is empty then the operation will act on the user associated
|
// If userIdentity is empty then the operation will act on the user associated
|
||||||
|
|
|
@ -29,7 +29,7 @@ func (l *Conn) doRequest(req request) (*messageContext, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.Debug {
|
if l.Debug {
|
||||||
ber.PrintPacket(packet)
|
l.Debug.PrintPacket(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
msgCtx, err := l.sendMessage(packet)
|
msgCtx, err := l.sendMessage(packet)
|
||||||
|
@ -60,7 +60,7 @@ func (l *Conn) readPacket(msgCtx *messageContext) (*ber.Packet, error) {
|
||||||
if err = addLDAPDescriptions(packet); err != nil {
|
if err = addLDAPDescriptions(packet); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ber.PrintPacket(packet)
|
l.Debug.PrintPacket(packet)
|
||||||
}
|
}
|
||||||
return packet, nil
|
return packet, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +1,3 @@
|
||||||
// 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
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -238,7 +238,7 @@ github.com/ghodss/yaml
|
||||||
github.com/go-asn1-ber/asn1-ber
|
github.com/go-asn1-ber/asn1-ber
|
||||||
# github.com/go-errors/errors v1.0.1
|
# github.com/go-errors/errors v1.0.1
|
||||||
github.com/go-errors/errors
|
github.com/go-errors/errors
|
||||||
# github.com/go-ldap/ldap/v3 v3.1.3
|
# github.com/go-ldap/ldap/v3 v3.1.10
|
||||||
github.com/go-ldap/ldap/v3
|
github.com/go-ldap/ldap/v3
|
||||||
# github.com/go-ole/go-ole v1.2.1
|
# github.com/go-ole/go-ole v1.2.1
|
||||||
github.com/go-ole/go-ole
|
github.com/go-ole/go-ole
|
||||||
|
|
Loading…
Reference in New Issue