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:
Scott Miller 2020-05-11 11:28:01 -05:00 committed by GitHub
parent 182bfc86f8
commit 16cc804086
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 450 additions and 464 deletions

2
go.mod
View File

@ -37,7 +37,7 @@ require (
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
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-sql-driver/mysql v1.4.1
github.com/go-test/deep v1.0.2

2
go.sum
View File

@ -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-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.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.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=

View File

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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
import (

View File

@ -1,8 +1,14 @@
package ldap
import (
"bytes"
"crypto/md5"
enchex "encoding/hex"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"strings"
ber "github.com/go-asn1-ber/asn1-ber"
)
@ -115,6 +121,237 @@ func (l *Conn) UnauthenticatedBind(username string) error {
return err
}
// DigestMD5BindRequest represents a digest-md5 bind operation
type DigestMD5BindRequest struct {
Host string
// Username is the name of the Directory object that the client wishes to bind as
Username string
// Password is the credentials to bind with
Password string
// Controls are optional controls to send with the bind request
Controls []Control
}
func (req *DigestMD5BindRequest) appendTo(envelope *ber.Packet) error {
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "DIGEST-MD5", "SASL Mech"))
request.AppendChild(auth)
envelope.AppendChild(request)
if len(req.Controls) > 0 {
envelope.AppendChild(encodeControls(req.Controls))
}
return nil
}
// DigestMD5BindResult contains the response from the server
type DigestMD5BindResult struct {
Controls []Control
}
// MD5Bind performs a digest-md5 bind with the given host, username and password.
func (l *Conn) MD5Bind(host, username, password string) error {
req := &DigestMD5BindRequest{
Host: host,
Username: username,
Password: password,
}
_, err := l.DigestMD5Bind(req)
return err
}
// DigestMD5Bind performs the digest-md5 bind operation defined in the given request
func (l *Conn) DigestMD5Bind(digestMD5BindRequest *DigestMD5BindRequest) (*DigestMD5BindResult, error) {
if digestMD5BindRequest.Password == "" {
return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client"))
}
msgCtx, err := l.doRequest(digestMD5BindRequest)
if err != nil {
return nil, err
}
defer l.finishMessage(msgCtx)
packet, err := l.readPacket(msgCtx)
if err != nil {
return nil, err
}
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
if l.Debug {
if err = addLDAPDescriptions(packet); err != nil {
return nil, err
}
ber.PrintPacket(packet)
}
result := &DigestMD5BindResult{
Controls: make([]Control, 0),
}
var params map[string]string
if len(packet.Children) == 2 {
if len(packet.Children[1].Children) == 4 {
child := packet.Children[1].Children[0]
if child.Tag != ber.TagEnumerated {
return result, GetLDAPError(packet)
}
if child.Value.(int64) != 14 {
return result, GetLDAPError(packet)
}
child = packet.Children[1].Children[3]
if child.Tag != ber.TagObjectDescriptor {
return result, GetLDAPError(packet)
}
if child.Data == nil {
return result, GetLDAPError(packet)
}
data, _ := ioutil.ReadAll(child.Data)
params, err = parseParams(string(data))
if err != nil {
return result, fmt.Errorf("parsing digest-challenge: %s", err)
}
}
}
if params != nil {
resp := computeResponse(
params,
"ldap/"+strings.ToLower(digestMD5BindRequest.Host),
digestMD5BindRequest.Username,
digestMD5BindRequest.Password,
)
packet = ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "DIGEST-MD5", "SASL Mech"))
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, resp, "Credentials"))
request.AppendChild(auth)
packet.AppendChild(request)
msgCtx, err = l.sendMessage(packet)
if err != nil {
return nil, fmt.Errorf("send message: %s", err)
}
defer l.finishMessage(msgCtx)
packetResponse, ok := <-msgCtx.responses
if !ok {
return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
}
packet, err = packetResponse.ReadPacket()
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
if err != nil {
return nil, fmt.Errorf("read packet: %s", err)
}
}
err = GetLDAPError(packet)
return result, err
}
func parseParams(str string) (map[string]string, error) {
m := make(map[string]string)
var key, value string
var state int
for i := 0; i <= len(str); i++ {
switch state {
case 0: //reading key
if i == len(str) {
return nil, fmt.Errorf("syntax error on %d", i)
}
if str[i] != '=' {
key += string(str[i])
continue
}
state = 1
case 1: //reading value
if i == len(str) {
m[key] = value
break
}
switch str[i] {
case ',':
m[key] = value
state = 0
key = ""
value = ""
case '"':
if value != "" {
return nil, fmt.Errorf("syntax error on %d", i)
}
state = 2
default:
value += string(str[i])
}
case 2: //inside quotes
if i == len(str) {
return nil, fmt.Errorf("syntax error on %d", i)
}
if str[i] != '"' {
value += string(str[i])
} else {
state = 1
}
}
}
return m, nil
}
func computeResponse(params map[string]string, uri, username, password string) string {
nc := "00000001"
qop := "auth"
cnonce := enchex.EncodeToString(randomBytes(16))
x := username + ":" + params["realm"] + ":" + password
y := md5Hash([]byte(x))
a1 := bytes.NewBuffer(y)
a1.WriteString(":" + params["nonce"] + ":" + cnonce)
if len(params["authzid"]) > 0 {
a1.WriteString(":" + params["authzid"])
}
a2 := bytes.NewBuffer([]byte("AUTHENTICATE"))
a2.WriteString(":" + uri)
ha1 := enchex.EncodeToString(md5Hash(a1.Bytes()))
ha2 := enchex.EncodeToString(md5Hash(a2.Bytes()))
kd := ha1
kd += ":" + params["nonce"]
kd += ":" + nc
kd += ":" + cnonce
kd += ":" + qop
kd += ":" + ha2
resp := enchex.EncodeToString(md5Hash([]byte(kd)))
return fmt.Sprintf(
`username="%s",realm="%s",nonce="%s",cnonce="%s",nc=00000001,qop=%s,digest-uri="%s",response=%s`,
username,
params["realm"],
params["nonce"],
cnonce,
qop,
uri,
resp,
)
}
func md5Hash(b []byte) []byte {
hasher := md5.New()
hasher.Write(b)
return hasher.Sum(nil)
}
func randomBytes(len int) []byte {
b := make([]byte, len)
for i := 0; i < len; i++ {
b[i] = byte(rand.Intn(256))
}
return b
}
var externalBindRequest = requestFunc(func(envelope *ber.Packet) error {
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))

View File

@ -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
import (

View File

@ -112,8 +112,63 @@ var _ Client = &Conn{}
// multiple places will probably result in undesired behaviour.
var DefaultTimeout = 60 * time.Second
// DialOpt configures DialContext.
type DialOpt func(*DialContext)
// DialWithDialer updates net.Dialer in DialContext.
func DialWithDialer(d *net.Dialer) DialOpt {
return func(dc *DialContext) {
dc.d = d
}
}
// DialWithTLSConfig updates tls.Config in DialContext.
func DialWithTLSConfig(tc *tls.Config) DialOpt {
return func(dc *DialContext) {
dc.tc = tc
}
}
// 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
// and then returns a new Conn for the connection.
// @deprecated Use DialURL instead.
func Dial(network, addr string) (*Conn, error) {
c, err := net.DialTimeout(network, addr, DefaultTimeout)
if err != nil {
@ -126,6 +181,7 @@ func Dial(network, addr string) (*Conn, error) {
// DialTLS connects to the given address on the given network using tls.Dial
// and then returns a new Conn for the connection.
// @deprecated Use DialURL instead.
func DialTLS(network, addr string, config *tls.Config) (*Conn, error) {
c, err := tls.DialWithDialer(&net.Dialer{Timeout: DefaultTimeout}, network, addr, config)
if err != nil {
@ -136,44 +192,31 @@ func DialTLS(network, addr string, config *tls.Config) (*Conn, error) {
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)
// DialURL connects to the given ldap URL.
// The following schemas are supported: ldap://, ldaps://, ldapi://.
// On success a new Conn for the connection is returned.
func DialURL(addr string, opts ...DialOpt) (*Conn, error) {
u, err := url.Parse(addr)
if err != nil {
return nil, NewError(ErrorNetwork, err)
}
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 {
// we asume that error is due to missing port
host = lurl.Host
port = ""
return nil, NewError(ErrorNetwork, err)
}
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))
conn := NewConn(c, u.Scheme == "ldaps")
conn.Start()
return conn, nil
}
// 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()
return err
}
ber.PrintPacket(packet)
l.Debug.PrintPacket(packet)
}
if err := GetLDAPError(packet); err == nil {
@ -347,7 +390,12 @@ func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags)
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
}
@ -451,7 +499,7 @@ func (l *Conn) processMessages() {
msgCtx.sendResponse(&PacketResponse{message.Packet, nil})
} else {
log.Printf("Received unexpected message %d, %v", message.MessageID, l.IsClosing())
ber.PrintPacket(message.Packet)
l.Debug.PrintPacket(message.Packet)
}
case MessageTimeout:
// Handle the timeout by closing the channel

View File

@ -1,8 +1,3 @@
//
// https://tools.ietf.org/html/rfc4511
//
// DelRequest ::= [APPLICATION 10] LDAPDN
package ldap
import (

View File

@ -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
import (
@ -48,7 +7,7 @@ import (
"fmt"
"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
@ -69,7 +28,8 @@ type DN struct {
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) {
dn := new(DN)
dn.RDNs = make([]*RelativeDN, 0)

View File

@ -5,10 +5,12 @@ import (
hexpac "encoding/hex"
"errors"
"fmt"
"io"
"strings"
"unicode"
"unicode/utf8"
"github.com/go-asn1-ber/asn1-ber"
ber "github.com/go-asn1-ber/asn1-ber"
)
// Filter choices
@ -69,6 +71,8 @@ var MatchingRuleAssertionMap = map[uint64]string{
MatchingRuleAssertionDNAttributes: "Matching Rule Assertion DN Attributes",
}
var _SymbolAny = []byte{'*'}
// CompileFilter converts a string representation of a filter into a BER-encoded packet
func CompileFilter(filter string) (*ber.Packet, error) {
if len(filter) == 0 || filter[0] != '(' {
@ -88,74 +92,75 @@ func CompileFilter(filter string) (*ber.Packet, error) {
}
// 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() {
if r := recover(); r != nil {
err = NewError(ErrorFilterDecompile, errors.New("ldap: error decompiling filter"))
}
}()
ret = "("
err = nil
buf := bytes.NewBuffer(nil)
buf.WriteByte('(')
childStr := ""
switch packet.Tag {
case FilterAnd:
ret += "&"
buf.WriteByte('&')
for _, child := range packet.Children {
childStr, err = DecompileFilter(child)
if err != nil {
return
}
ret += childStr
buf.WriteString(childStr)
}
case FilterOr:
ret += "|"
buf.WriteByte('|')
for _, child := range packet.Children {
childStr, err = DecompileFilter(child)
if err != nil {
return
}
ret += childStr
buf.WriteString(childStr)
}
case FilterNot:
ret += "!"
buf.WriteByte('!')
childStr, err = DecompileFilter(packet.Children[0])
if err != nil {
return
}
ret += childStr
buf.WriteString(childStr)
case FilterSubstrings:
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
ret += "="
buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes()))
buf.WriteByte('=')
for i, child := range packet.Children[1].Children {
if i == 0 && child.Tag != FilterSubstringsInitial {
ret += "*"
buf.Write(_SymbolAny)
}
ret += EscapeFilter(ber.DecodeString(child.Data.Bytes()))
buf.WriteString(EscapeFilter(ber.DecodeString(child.Data.Bytes())))
if child.Tag != FilterSubstringsFinal {
ret += "*"
buf.Write(_SymbolAny)
}
}
case FilterEqualityMatch:
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
ret += "="
ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes()))
buf.WriteByte('=')
buf.WriteString(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()))
buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes()))
buf.WriteString(">=")
buf.WriteString(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()))
buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes()))
buf.WriteString("<=")
buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes())))
case FilterPresent:
ret += ber.DecodeString(packet.Data.Bytes())
ret += "=*"
buf.WriteString(ber.DecodeString(packet.Data.Bytes()))
buf.WriteString("=*")
case FilterApproxMatch:
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
ret += "~="
ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
buf.WriteString(ber.DecodeString(packet.Children[0].Data.Bytes()))
buf.WriteString("~=")
buf.WriteString(EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes())))
case FilterExtensibleMatch:
attr := ""
dnAttributes := false
@ -176,21 +181,22 @@ func DecompileFilter(packet *ber.Packet) (ret string, err error) {
}
if len(attr) > 0 {
ret += attr
buf.WriteString(attr)
}
if dnAttributes {
ret += ":dn"
buf.WriteString(":dn")
}
if len(matchingRule) > 0 {
ret += ":"
ret += matchingRule
buf.WriteString(":")
buf.WriteString(matchingRule)
}
ret += ":="
ret += EscapeFilter(value)
buf.WriteString(":=")
buf.WriteString(EscapeFilter(value))
}
ret += ")"
return
buf.WriteByte(')')
return buf.String(), nil
}
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
attribute := ""
attribute := bytes.NewBuffer(nil)
extensibleDNAttributes := false
extensibleMatchingRule := ""
condition := ""
extensibleMatchingRule := bytes.NewBuffer(nil)
condition := bytes.NewBuffer(nil)
for newPos < len(filter) {
remainingFilter := filter[newPos:]
@ -324,7 +329,7 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
// Still reading the attribute name
default:
attribute += fmt.Sprintf("%c", currentRune)
attribute.WriteRune(currentRune)
newPos += currentWidth
}
@ -338,13 +343,13 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
// Still reading the matching rule oid
default:
extensibleMatchingRule += fmt.Sprintf("%c", currentRune)
extensibleMatchingRule.WriteRune(currentRune)
newPos += currentWidth
}
case stateReadingCondition:
// append to the condition
condition += fmt.Sprintf("%c", currentRune)
condition.WriteRune(currentRune)
newPos += currentWidth
}
}
@ -368,17 +373,17 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
// }
// Include the matching rule oid, if specified
if len(extensibleMatchingRule) > 0 {
packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchingRule, extensibleMatchingRule, MatchingRuleAssertionMap[MatchingRuleAssertionMatchingRule]))
if extensibleMatchingRule.Len() > 0 {
packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchingRule, extensibleMatchingRule.String(), MatchingRuleAssertionMap[MatchingRuleAssertionMatchingRule]))
}
// Include the attribute, if specified
if len(attribute) > 0 {
packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionType, attribute, MatchingRuleAssertionMap[MatchingRuleAssertionType]))
if attribute.Len() > 0 {
packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionType, attribute.String(), MatchingRuleAssertionMap[MatchingRuleAssertionType]))
}
// Add the value (only required child)
encodedString, encodeErr := escapedStringToEncodedBytes(condition)
encodedString, encodeErr := decodeEscapedSymbols(condition.Bytes())
if encodeErr != nil {
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]))
}
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"))
case packet.Tag == FilterEqualityMatch && bytes.Equal(condition.Bytes(), _SymbolAny):
packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute.String(), FilterMap[FilterPresent])
case packet.Tag == FilterEqualityMatch && bytes.Index(condition.Bytes(), _SymbolAny) > -1:
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute.String(), "Attribute"))
packet.Tag = FilterSubstrings
packet.Description = FilterMap[uint64(packet.Tag)]
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings")
parts := strings.Split(condition, "*")
parts := bytes.Split(condition.Bytes(), _SymbolAny)
for i, part := range parts {
if part == "" {
if len(part) == 0 {
continue
}
var tag ber.Tag
@ -410,7 +415,7 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
default:
tag = FilterSubstringsAny
}
encodedString, encodeErr := escapedStringToEncodedBytes(part)
encodedString, encodeErr := decodeEscapedSymbols(part)
if encodeErr != nil {
return packet, newPos, encodeErr
}
@ -418,11 +423,11 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
}
packet.AppendChild(seq)
default:
encodedString, encodeErr := escapedStringToEncodedBytes(condition)
encodedString, encodeErr := decodeEscapedSymbols(condition.Bytes())
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, attribute.String(), "Attribute"))
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
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))
func decodeEscapedSymbols(src []byte) (string, error) {
var (
buffer bytes.Buffer
offset int
reader = bytes.NewReader(src)
byteHex []byte
byteVal []byte
)
for {
runeVal, runeSize, err := reader.ReadRune()
if err == io.EOF {
return buffer.String(), nil
} else if err != nil {
return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: failed to read filter: %v", err))
} else if runeVal == unicode.ReplacementChar {
return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", offset))
}
// Check for escaped hex characters and convert them to their literal value for transport.
if currentRune == '\\' {
if runeVal == '\\' {
// http://tools.ietf.org/search/rfc4515
// \ (%x5C) is not a valid character unless it is followed by two HEX characters due to not
// being a member of UTF1SUBSET.
if i+2 > len(escapedString) {
return "", NewError(ErrorFilterCompile, errors.New("ldap: missing characters for escape in filter"))
if byteHex == nil {
byteHex = make([]byte, 2)
byteVal = make([]byte, 1)
}
escByte, decodeErr := hexpac.DecodeString(escapedString[i+1 : i+3])
if decodeErr != nil {
return "", NewError(ErrorFilterCompile, errors.New("ldap: invalid characters for escape in filter"))
if _, err := io.ReadFull(reader, byteHex); err != nil {
if err == io.ErrUnexpectedEOF {
return "", NewError(ErrorFilterCompile, errors.New("ldap: missing characters for escape in filter"))
}
return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: invalid characters for escape in filter: %v", err))
}
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 {
buffer.WriteRune(currentRune)
buffer.WriteRune(runeVal)
}
i += currentWidth
offset += runeSize
}
return buffer.String(), nil
}

View File

@ -1,5 +1,5 @@
module github.com/go-ldap/ldap/v3
require github.com/go-asn1-ber/asn1-ber v1.3.1
go 1.13
require github.com/go-asn1-ber/asn1-ber v1.3.1

View File

@ -269,13 +269,18 @@ func addRequestDescriptions(packet *ber.Packet) error {
}
func addDefaultLDAPResponseDescriptions(packet *ber.Packet) error {
err := GetLDAPError(packet)
if err == nil {
return nil
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[err.(*Error).ResultCode] + ")"
packet.Children[1].Children[1].Description = "Matched DN (" + err.(*Error).MatchedDN + ")"
packet.Children[1].Children[2].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"
}

View File

@ -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
import (

View File

@ -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
import (
@ -33,9 +8,10 @@ import (
// Change operation choices
const (
AddAttribute = 0
DeleteAttribute = 1
ReplaceAttribute = 2
AddAttribute = 0
DeleteAttribute = 1
ReplaceAttribute = 2
IncrementAttribute = 3 // (https://tools.ietf.org/html/rfc4525)
)
// PartialAttribute for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
@ -97,6 +73,11 @@ func (req *ModifyRequest) Replace(attrType string, attrVals []string) {
req.appendChange(ReplaceAttribute, attrType, attrVals)
}
// Increment appends the given attribute to the list of changes to be made
func (req *ModifyRequest) Increment(attrType string, attrVal string) {
req.appendChange(IncrementAttribute, attrType, []string{attrVal})
}
func (req *ModifyRequest) appendChange(operation uint, attrType string, attrVals []string) {
req.Changes = append(req.Changes, Change{operation, PartialAttribute{Type: attrType, Vals: attrVals}})
}

View File

@ -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
import (
@ -61,7 +56,7 @@ func (req *PasswordModifyRequest) appendTo(envelope *ber.Packet) error {
// 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.
// This string may or may not be an LDAPDN (RFC 2253).
// If userIdentity is empty then the operation will act on the user associated

View File

@ -29,7 +29,7 @@ func (l *Conn) doRequest(req request) (*messageContext, error) {
}
if l.Debug {
ber.PrintPacket(packet)
l.Debug.PrintPacket(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 {
return nil, err
}
ber.PrintPacket(packet)
l.Debug.PrintPacket(packet)
}
return packet, nil
}

View File

@ -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
import (

2
vendor/modules.txt vendored
View File

@ -238,7 +238,7 @@ github.com/ghodss/yaml
github.com/go-asn1-ber/asn1-ber
# github.com/go-errors/errors v1.0.1
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-ole/go-ole v1.2.1
github.com/go-ole/go-ole