Signed-off-by: Andrew Thornton <art27@cantab.net>master
@@ -1094,12 +1094,12 @@ | |||||
version = "v1.31.1" | version = "v1.31.1" | ||||
[[projects]] | [[projects]] | ||||
digest = "1:7e1c00b9959544fa1ccca7cf0407a5b29ac6d5201059c4fac6f599cb99bfd24d" | |||||
name = "gopkg.in/ldap.v2" | |||||
digest = "1:8a502dedecf5b6d56e36f0d0e6196392baf616634af2c23108b6e8bb89ec57fc" | |||||
name = "gopkg.in/ldap.v3" | |||||
packages = ["."] | packages = ["."] | ||||
pruneopts = "NUT" | pruneopts = "NUT" | ||||
revision = "bb7a9ca6e4fbc2129e3db588a34bc970ffe811a9" | |||||
version = "v2.5.1" | |||||
revision = "214f299a0ecb2a6c6f6d2b0f13977032b207dc58" | |||||
version = "v3.0.1" | |||||
[[projects]] | [[projects]] | ||||
digest = "1:de2e7294c9bd0b7d07ada8e98ad02cbbaabacff90eedebe7454ebdbab50d0d19" | digest = "1:de2e7294c9bd0b7d07ada8e98ad02cbbaabacff90eedebe7454ebdbab50d0d19" | ||||
@@ -1309,7 +1309,7 @@ | |||||
"gopkg.in/editorconfig/editorconfig-core-go.v1", | "gopkg.in/editorconfig/editorconfig-core-go.v1", | ||||
"gopkg.in/gomail.v2", | "gopkg.in/gomail.v2", | ||||
"gopkg.in/ini.v1", | "gopkg.in/ini.v1", | ||||
"gopkg.in/ldap.v2", | |||||
"gopkg.in/ldap.v3", | |||||
"gopkg.in/macaron.v1", | "gopkg.in/macaron.v1", | ||||
"gopkg.in/testfixtures.v2", | "gopkg.in/testfixtures.v2", | ||||
"strk.kbt.io/projects/go/libravatar", | "strk.kbt.io/projects/go/libravatar", | ||||
@@ -95,8 +95,8 @@ ignored = ["google.golang.org/appengine*"] | |||||
version = "1.31.1" | version = "1.31.1" | ||||
[[constraint]] | [[constraint]] | ||||
name = "gopkg.in/ldap.v2" | |||||
version = "2.4.1" | |||||
name = "gopkg.in/ldap.v3" | |||||
version = "3.0.1" | |||||
[[constraint]] | [[constraint]] | ||||
name = "gopkg.in/macaron.v1" | name = "gopkg.in/macaron.v1" | ||||
@@ -11,9 +11,9 @@ import ( | |||||
"fmt" | "fmt" | ||||
"strings" | "strings" | ||||
"gopkg.in/ldap.v2" | |||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
ldap "gopkg.in/ldap.v3" | |||||
) | ) | ||||
// SecurityProtocol protocol type | // SecurityProtocol protocol type | ||||
@@ -1,13 +0,0 @@ | |||||
// +build go1.4 | |||||
package ldap | |||||
import ( | |||||
"sync/atomic" | |||||
) | |||||
// For compilers that support it, we just use the underlying sync/atomic.Value | |||||
// type. | |||||
type atomicValue struct { | |||||
atomic.Value | |||||
} |
@@ -1,28 +0,0 @@ | |||||
// +build !go1.4 | |||||
package ldap | |||||
import ( | |||||
"sync" | |||||
) | |||||
// This is a helper type that emulates the use of the "sync/atomic.Value" | |||||
// struct that's available in Go 1.4 and up. | |||||
type atomicValue struct { | |||||
value interface{} | |||||
lock sync.RWMutex | |||||
} | |||||
func (av *atomicValue) Store(val interface{}) { | |||||
av.lock.Lock() | |||||
av.value = val | |||||
av.lock.Unlock() | |||||
} | |||||
func (av *atomicValue) Load() interface{} { | |||||
av.lock.RLock() | |||||
ret := av.value | |||||
av.lock.RUnlock() | |||||
return ret | |||||
} |
@@ -1,155 +0,0 @@ | |||||
package ldap | |||||
import ( | |||||
"fmt" | |||||
"gopkg.in/asn1-ber.v1" | |||||
) | |||||
// LDAP Result Codes | |||||
const ( | |||||
LDAPResultSuccess = 0 | |||||
LDAPResultOperationsError = 1 | |||||
LDAPResultProtocolError = 2 | |||||
LDAPResultTimeLimitExceeded = 3 | |||||
LDAPResultSizeLimitExceeded = 4 | |||||
LDAPResultCompareFalse = 5 | |||||
LDAPResultCompareTrue = 6 | |||||
LDAPResultAuthMethodNotSupported = 7 | |||||
LDAPResultStrongAuthRequired = 8 | |||||
LDAPResultReferral = 10 | |||||
LDAPResultAdminLimitExceeded = 11 | |||||
LDAPResultUnavailableCriticalExtension = 12 | |||||
LDAPResultConfidentialityRequired = 13 | |||||
LDAPResultSaslBindInProgress = 14 | |||||
LDAPResultNoSuchAttribute = 16 | |||||
LDAPResultUndefinedAttributeType = 17 | |||||
LDAPResultInappropriateMatching = 18 | |||||
LDAPResultConstraintViolation = 19 | |||||
LDAPResultAttributeOrValueExists = 20 | |||||
LDAPResultInvalidAttributeSyntax = 21 | |||||
LDAPResultNoSuchObject = 32 | |||||
LDAPResultAliasProblem = 33 | |||||
LDAPResultInvalidDNSyntax = 34 | |||||
LDAPResultAliasDereferencingProblem = 36 | |||||
LDAPResultInappropriateAuthentication = 48 | |||||
LDAPResultInvalidCredentials = 49 | |||||
LDAPResultInsufficientAccessRights = 50 | |||||
LDAPResultBusy = 51 | |||||
LDAPResultUnavailable = 52 | |||||
LDAPResultUnwillingToPerform = 53 | |||||
LDAPResultLoopDetect = 54 | |||||
LDAPResultNamingViolation = 64 | |||||
LDAPResultObjectClassViolation = 65 | |||||
LDAPResultNotAllowedOnNonLeaf = 66 | |||||
LDAPResultNotAllowedOnRDN = 67 | |||||
LDAPResultEntryAlreadyExists = 68 | |||||
LDAPResultObjectClassModsProhibited = 69 | |||||
LDAPResultAffectsMultipleDSAs = 71 | |||||
LDAPResultOther = 80 | |||||
ErrorNetwork = 200 | |||||
ErrorFilterCompile = 201 | |||||
ErrorFilterDecompile = 202 | |||||
ErrorDebugging = 203 | |||||
ErrorUnexpectedMessage = 204 | |||||
ErrorUnexpectedResponse = 205 | |||||
) | |||||
// LDAPResultCodeMap contains string descriptions for LDAP error codes | |||||
var LDAPResultCodeMap = map[uint8]string{ | |||||
LDAPResultSuccess: "Success", | |||||
LDAPResultOperationsError: "Operations Error", | |||||
LDAPResultProtocolError: "Protocol Error", | |||||
LDAPResultTimeLimitExceeded: "Time Limit Exceeded", | |||||
LDAPResultSizeLimitExceeded: "Size Limit Exceeded", | |||||
LDAPResultCompareFalse: "Compare False", | |||||
LDAPResultCompareTrue: "Compare True", | |||||
LDAPResultAuthMethodNotSupported: "Auth Method Not Supported", | |||||
LDAPResultStrongAuthRequired: "Strong Auth Required", | |||||
LDAPResultReferral: "Referral", | |||||
LDAPResultAdminLimitExceeded: "Admin Limit Exceeded", | |||||
LDAPResultUnavailableCriticalExtension: "Unavailable Critical Extension", | |||||
LDAPResultConfidentialityRequired: "Confidentiality Required", | |||||
LDAPResultSaslBindInProgress: "Sasl Bind In Progress", | |||||
LDAPResultNoSuchAttribute: "No Such Attribute", | |||||
LDAPResultUndefinedAttributeType: "Undefined Attribute Type", | |||||
LDAPResultInappropriateMatching: "Inappropriate Matching", | |||||
LDAPResultConstraintViolation: "Constraint Violation", | |||||
LDAPResultAttributeOrValueExists: "Attribute Or Value Exists", | |||||
LDAPResultInvalidAttributeSyntax: "Invalid Attribute Syntax", | |||||
LDAPResultNoSuchObject: "No Such Object", | |||||
LDAPResultAliasProblem: "Alias Problem", | |||||
LDAPResultInvalidDNSyntax: "Invalid DN Syntax", | |||||
LDAPResultAliasDereferencingProblem: "Alias Dereferencing Problem", | |||||
LDAPResultInappropriateAuthentication: "Inappropriate Authentication", | |||||
LDAPResultInvalidCredentials: "Invalid Credentials", | |||||
LDAPResultInsufficientAccessRights: "Insufficient Access Rights", | |||||
LDAPResultBusy: "Busy", | |||||
LDAPResultUnavailable: "Unavailable", | |||||
LDAPResultUnwillingToPerform: "Unwilling To Perform", | |||||
LDAPResultLoopDetect: "Loop Detect", | |||||
LDAPResultNamingViolation: "Naming Violation", | |||||
LDAPResultObjectClassViolation: "Object Class Violation", | |||||
LDAPResultNotAllowedOnNonLeaf: "Not Allowed On Non Leaf", | |||||
LDAPResultNotAllowedOnRDN: "Not Allowed On RDN", | |||||
LDAPResultEntryAlreadyExists: "Entry Already Exists", | |||||
LDAPResultObjectClassModsProhibited: "Object Class Mods Prohibited", | |||||
LDAPResultAffectsMultipleDSAs: "Affects Multiple DSAs", | |||||
LDAPResultOther: "Other", | |||||
ErrorNetwork: "Network Error", | |||||
ErrorFilterCompile: "Filter Compile Error", | |||||
ErrorFilterDecompile: "Filter Decompile Error", | |||||
ErrorDebugging: "Debugging Error", | |||||
ErrorUnexpectedMessage: "Unexpected Message", | |||||
ErrorUnexpectedResponse: "Unexpected Response", | |||||
} | |||||
func getLDAPResultCode(packet *ber.Packet) (code uint8, description string) { | |||||
if packet == nil { | |||||
return ErrorUnexpectedResponse, "Empty packet" | |||||
} else if len(packet.Children) >= 2 { | |||||
response := packet.Children[1] | |||||
if response == nil { | |||||
return ErrorUnexpectedResponse, "Empty response in packet" | |||||
} | |||||
if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 { | |||||
// Children[1].Children[2] is the diagnosticMessage which is guaranteed to exist as seen here: https://tools.ietf.org/html/rfc4511#section-4.1.9 | |||||
return uint8(response.Children[0].Value.(int64)), response.Children[2].Value.(string) | |||||
} | |||||
} | |||||
return ErrorNetwork, "Invalid packet format" | |||||
} | |||||
// Error holds LDAP error information | |||||
type Error struct { | |||||
// Err is the underlying error | |||||
Err error | |||||
// ResultCode is the LDAP error code | |||||
ResultCode uint8 | |||||
} | |||||
func (e *Error) Error() string { | |||||
return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error()) | |||||
} | |||||
// NewError creates an LDAP error with the given code and underlying error | |||||
func NewError(resultCode uint8, err error) error { | |||||
return &Error{ResultCode: resultCode, Err: err} | |||||
} | |||||
// IsErrorWithCode returns true if the given error is an LDAP error with the given result code | |||||
func IsErrorWithCode(err error, desiredResultCode uint8) bool { | |||||
if err == nil { | |||||
return false | |||||
} | |||||
serverError, ok := err.(*Error) | |||||
if !ok { | |||||
return false | |||||
} | |||||
return serverError.ResultCode == desiredResultCode | |||||
} |
@@ -41,6 +41,8 @@ type AddRequest struct { | |||||
DN string | DN string | ||||
// Attributes list the attributes of the new entry | // Attributes list the attributes of the new entry | ||||
Attributes []Attribute | Attributes []Attribute | ||||
// Controls hold optional controls to send with the request | |||||
Controls []Control | |||||
} | } | ||||
func (a AddRequest) encode() *ber.Packet { | func (a AddRequest) encode() *ber.Packet { | ||||
@@ -60,9 +62,10 @@ func (a *AddRequest) Attribute(attrType string, attrVals []string) { | |||||
} | } | ||||
// NewAddRequest returns an AddRequest for the given DN, with no attributes | // NewAddRequest returns an AddRequest for the given DN, with no attributes | ||||
func NewAddRequest(dn string) *AddRequest { | |||||
func NewAddRequest(dn string, controls []Control) *AddRequest { | |||||
return &AddRequest{ | return &AddRequest{ | ||||
DN: dn, | |||||
DN: dn, | |||||
Controls: controls, | |||||
} | } | ||||
} | } | ||||
@@ -72,6 +75,9 @@ func (l *Conn) Add(addRequest *AddRequest) error { | |||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") | 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")) | packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID")) | ||||
packet.AppendChild(addRequest.encode()) | packet.AppendChild(addRequest.encode()) | ||||
if len(addRequest.Controls) > 0 { | |||||
packet.AppendChild(encodeControls(addRequest.Controls)) | |||||
} | |||||
l.Debug.PrintPacket(packet) | l.Debug.PrintPacket(packet) | ||||
@@ -100,9 +106,9 @@ func (l *Conn) Add(addRequest *AddRequest) error { | |||||
} | } | ||||
if packet.Children[1].Tag == ApplicationAddResponse { | if packet.Children[1].Tag == ApplicationAddResponse { | ||||
resultCode, resultDescription := getLDAPResultCode(packet) | |||||
if resultCode != 0 { | |||||
return NewError(resultCode, errors.New(resultDescription)) | |||||
err := GetLDAPError(packet) | |||||
if err != nil { | |||||
return err | |||||
} | } | ||||
} else { | } else { | ||||
log.Printf("Unexpected Response: %d", packet.Children[1].Tag) | log.Printf("Unexpected Response: %d", packet.Children[1].Tag) |
@@ -1,11 +1,8 @@ | |||||
// Copyright 2011 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package ldap | package ldap | ||||
import ( | import ( | ||||
"errors" | "errors" | ||||
"fmt" | |||||
"gopkg.in/asn1-ber.v1" | "gopkg.in/asn1-ber.v1" | ||||
) | ) | ||||
@@ -18,6 +15,9 @@ type SimpleBindRequest struct { | |||||
Password string | Password string | ||||
// Controls are optional controls to send with the bind request | // Controls are optional controls to send with the bind request | ||||
Controls []Control | Controls []Control | ||||
// AllowEmptyPassword sets whether the client allows binding with an empty password | |||||
// (normally used for unauthenticated bind). | |||||
AllowEmptyPassword bool | |||||
} | } | ||||
// SimpleBindResult contains the response from the server | // SimpleBindResult contains the response from the server | ||||
@@ -28,9 +28,10 @@ type SimpleBindResult struct { | |||||
// NewSimpleBindRequest returns a bind request | // NewSimpleBindRequest returns a bind request | ||||
func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest { | func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest { | ||||
return &SimpleBindRequest{ | return &SimpleBindRequest{ | ||||
Username: username, | |||||
Password: password, | |||||
Controls: controls, | |||||
Username: username, | |||||
Password: password, | |||||
Controls: controls, | |||||
AllowEmptyPassword: false, | |||||
} | } | ||||
} | } | ||||
@@ -40,17 +41,22 @@ func (bindRequest *SimpleBindRequest) encode() *ber.Packet { | |||||
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, bindRequest.Username, "User Name")) | request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, bindRequest.Username, "User Name")) | ||||
request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, bindRequest.Password, "Password")) | request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, bindRequest.Password, "Password")) | ||||
request.AppendChild(encodeControls(bindRequest.Controls)) | |||||
return request | return request | ||||
} | } | ||||
// SimpleBind performs the simple bind operation defined in the given request | // SimpleBind performs the simple bind operation defined in the given request | ||||
func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) { | func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) { | ||||
if simpleBindRequest.Password == "" && !simpleBindRequest.AllowEmptyPassword { | |||||
return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client")) | |||||
} | |||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") | 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")) | packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID")) | ||||
encodedBindRequest := simpleBindRequest.encode() | encodedBindRequest := simpleBindRequest.encode() | ||||
packet.AppendChild(encodedBindRequest) | packet.AppendChild(encodedBindRequest) | ||||
if len(simpleBindRequest.Controls) > 0 { | |||||
packet.AppendChild(encodeControls(simpleBindRequest.Controls)) | |||||
} | |||||
if l.Debug { | if l.Debug { | ||||
ber.PrintPacket(packet) | ber.PrintPacket(packet) | ||||
@@ -73,7 +79,7 @@ func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResu | |||||
} | } | ||||
if l.Debug { | if l.Debug { | ||||
if err := addLDAPDescriptions(packet); err != nil { | |||||
if err = addLDAPDescriptions(packet); err != nil { | |||||
return nil, err | return nil, err | ||||
} | } | ||||
ber.PrintPacket(packet) | ber.PrintPacket(packet) | ||||
@@ -85,59 +91,45 @@ func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResu | |||||
if len(packet.Children) == 3 { | if len(packet.Children) == 3 { | ||||
for _, child := range packet.Children[2].Children { | for _, child := range packet.Children[2].Children { | ||||
result.Controls = append(result.Controls, DecodeControl(child)) | |||||
decodedChild, decodeErr := DecodeControl(child) | |||||
if decodeErr != nil { | |||||
return nil, fmt.Errorf("failed to decode child control: %s", decodeErr) | |||||
} | |||||
result.Controls = append(result.Controls, decodedChild) | |||||
} | } | ||||
} | } | ||||
resultCode, resultDescription := getLDAPResultCode(packet) | |||||
if resultCode != 0 { | |||||
return result, NewError(resultCode, errors.New(resultDescription)) | |||||
} | |||||
return result, nil | |||||
err = GetLDAPError(packet) | |||||
return result, err | |||||
} | } | ||||
// Bind performs a bind with the given username and password | |||||
// Bind performs a bind with the given username and password. | |||||
// | |||||
// It does not allow unauthenticated bind (i.e. empty password). Use the UnauthenticatedBind method | |||||
// for that. | |||||
func (l *Conn) Bind(username, password string) error { | func (l *Conn) Bind(username, password string) error { | ||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") | |||||
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID")) | |||||
bindRequest := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") | |||||
bindRequest.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) | |||||
bindRequest.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, username, "User Name")) | |||||
bindRequest.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, password, "Password")) | |||||
packet.AppendChild(bindRequest) | |||||
if l.Debug { | |||||
ber.PrintPacket(packet) | |||||
} | |||||
msgCtx, err := l.sendMessage(packet) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
defer l.finishMessage(msgCtx) | |||||
packetResponse, ok := <-msgCtx.responses | |||||
if !ok { | |||||
return NewError(ErrorNetwork, errors.New("ldap: response channel closed")) | |||||
} | |||||
packet, err = packetResponse.ReadPacket() | |||||
l.Debug.Printf("%d: got response %p", msgCtx.id, packet) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
if l.Debug { | |||||
if err := addLDAPDescriptions(packet); err != nil { | |||||
return err | |||||
} | |||||
ber.PrintPacket(packet) | |||||
req := &SimpleBindRequest{ | |||||
Username: username, | |||||
Password: password, | |||||
AllowEmptyPassword: false, | |||||
} | } | ||||
_, err := l.SimpleBind(req) | |||||
return err | |||||
} | |||||
resultCode, resultDescription := getLDAPResultCode(packet) | |||||
if resultCode != 0 { | |||||
return NewError(resultCode, errors.New(resultDescription)) | |||||
// UnauthenticatedBind performs an unauthenticated bind. | |||||
// | |||||
// A username may be provided for trace (e.g. logging) purpose only, but it is normally not | |||||
// authenticated or otherwise validated by the LDAP server. | |||||
// | |||||
// See https://tools.ietf.org/html/rfc4513#section-5.1.2 . | |||||
// See https://tools.ietf.org/html/rfc4513#section-6.3.1 . | |||||
func (l *Conn) UnauthenticatedBind(username string) error { | |||||
req := &SimpleBindRequest{ | |||||
Username: username, | |||||
Password: "", | |||||
AllowEmptyPassword: true, | |||||
} | } | ||||
return nil | |||||
_, err := l.SimpleBind(req) | |||||
return err | |||||
} | } |
@@ -18,6 +18,7 @@ type Client interface { | |||||
Add(addRequest *AddRequest) error | Add(addRequest *AddRequest) error | ||||
Del(delRequest *DelRequest) error | Del(delRequest *DelRequest) error | ||||
Modify(modifyRequest *ModifyRequest) error | Modify(modifyRequest *ModifyRequest) error | ||||
ModifyDN(modifyDNRequest *ModifyDNRequest) error | |||||
Compare(dn, attribute, value string) (bool, error) | Compare(dn, attribute, value string) (bool, error) | ||||
PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error) | PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error) |
@@ -1,7 +1,3 @@ | |||||
// Copyright 2014 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
// | |||||
// File contains Compare functionality | // File contains Compare functionality | ||||
// | // | ||||
// https://tools.ietf.org/html/rfc4511 | // https://tools.ietf.org/html/rfc4511 | ||||
@@ -41,7 +37,7 @@ func (l *Conn) Compare(dn, attribute, value string) (bool, error) { | |||||
ava := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "AttributeValueAssertion") | ava := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "AttributeValueAssertion") | ||||
ava.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "AttributeDesc")) | ava.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "AttributeDesc")) | ||||
ava.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagOctetString, value, "AssertionValue")) | |||||
ava.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "AssertionValue")) | |||||
request.AppendChild(ava) | request.AppendChild(ava) | ||||
packet.AppendChild(request) | packet.AppendChild(request) | ||||
@@ -72,14 +68,16 @@ func (l *Conn) Compare(dn, attribute, value string) (bool, error) { | |||||
} | } | ||||
if packet.Children[1].Tag == ApplicationCompareResponse { | if packet.Children[1].Tag == ApplicationCompareResponse { | ||||
resultCode, resultDescription := getLDAPResultCode(packet) | |||||
if resultCode == LDAPResultCompareTrue { | |||||
err := GetLDAPError(packet) | |||||
switch { | |||||
case IsErrorWithCode(err, LDAPResultCompareTrue): | |||||
return true, nil | return true, nil | ||||
} else if resultCode == LDAPResultCompareFalse { | |||||
case IsErrorWithCode(err, LDAPResultCompareFalse): | |||||
return false, nil | return false, nil | ||||
} else { | |||||
return false, NewError(resultCode, errors.New(resultDescription)) | |||||
default: | |||||
return false, err | |||||
} | } | ||||
} | } | ||||
return false, fmt.Errorf("Unexpected Response: %d", packet.Children[1].Tag) | |||||
return false, fmt.Errorf("unexpected Response: %d", packet.Children[1].Tag) | |||||
} | } |
@@ -1,7 +1,3 @@ | |||||
// Copyright 2011 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package ldap | package ldap | ||||
import ( | import ( | ||||
@@ -10,6 +6,7 @@ import ( | |||||
"fmt" | "fmt" | ||||
"log" | "log" | ||||
"net" | "net" | ||||
"net/url" | |||||
"sync" | "sync" | ||||
"sync/atomic" | "sync/atomic" | ||||
"time" | "time" | ||||
@@ -30,6 +27,13 @@ const ( | |||||
MessageTimeout = 4 | MessageTimeout = 4 | ||||
) | ) | ||||
const ( | |||||
// DefaultLdapPort default ldap port for pure TCP connection | |||||
DefaultLdapPort = "389" | |||||
// DefaultLdapsPort default ldap port for SSL connection | |||||
DefaultLdapsPort = "636" | |||||
) | |||||
// PacketResponse contains the packet or error encountered reading a response | // PacketResponse contains the packet or error encountered reading a response | ||||
type PacketResponse struct { | type PacketResponse struct { | ||||
// Packet is the packet read from the server | // Packet is the packet read from the server | ||||
@@ -81,10 +85,13 @@ const ( | |||||
// Conn represents an LDAP Connection | // Conn represents an LDAP Connection | ||||
type Conn struct { | type Conn struct { | ||||
// requestTimeout is loaded atomically | |||||
// so we need to ensure 64-bit alignment on 32-bit platforms. | |||||
requestTimeout int64 | |||||
conn net.Conn | conn net.Conn | ||||
isTLS bool | isTLS bool | ||||
closing uint32 | closing uint32 | ||||
closeErr atomicValue | |||||
closeErr atomic.Value | |||||
isStartingTLS bool | isStartingTLS bool | ||||
Debug debugging | Debug debugging | ||||
chanConfirm chan struct{} | chanConfirm chan struct{} | ||||
@@ -94,7 +101,6 @@ type Conn struct { | |||||
wgClose sync.WaitGroup | wgClose sync.WaitGroup | ||||
outstandingRequests uint | outstandingRequests uint | ||||
messageMutex sync.Mutex | messageMutex sync.Mutex | ||||
requestTimeout int64 | |||||
} | } | ||||
var _ Client = &Conn{} | var _ Client = &Conn{} | ||||
@@ -121,22 +127,51 @@ 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. | ||||
func DialTLS(network, addr string, config *tls.Config) (*Conn, error) { | func DialTLS(network, addr string, config *tls.Config) (*Conn, error) { | ||||
dc, err := net.DialTimeout(network, addr, DefaultTimeout) | |||||
c, err := tls.DialWithDialer(&net.Dialer{Timeout: DefaultTimeout}, network, addr, config) | |||||
if err != nil { | if err != nil { | ||||
return nil, NewError(ErrorNetwork, err) | return nil, NewError(ErrorNetwork, err) | ||||
} | } | ||||
c := tls.Client(dc, config) | |||||
err = c.Handshake() | |||||
if err != nil { | |||||
// Handshake error, close the established connection before we return an error | |||||
dc.Close() | |||||
return nil, NewError(ErrorNetwork, err) | |||||
} | |||||
conn := NewConn(c, true) | conn := NewConn(c, true) | ||||
conn.Start() | conn.Start() | ||||
return conn, nil | return conn, nil | ||||
} | } | ||||
// DialURL connects to the given ldap URL vie TCP using tls.Dial or net.Dial if ldaps:// | |||||
// or ldap:// specified as protocol. On success a new Conn for the connection | |||||
// is returned. | |||||
func DialURL(addr string) (*Conn, error) { | |||||
lurl, err := url.Parse(addr) | |||||
if err != nil { | |||||
return nil, NewError(ErrorNetwork, err) | |||||
} | |||||
host, port, err := net.SplitHostPort(lurl.Host) | |||||
if err != nil { | |||||
// we asume that error is due to missing port | |||||
host = lurl.Host | |||||
port = "" | |||||
} | |||||
switch lurl.Scheme { | |||||
case "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. | ||||
func NewConn(conn net.Conn, isTLS bool) *Conn { | func NewConn(conn net.Conn, isTLS bool) *Conn { | ||||
return &Conn{ | return &Conn{ | ||||
@@ -157,8 +192,8 @@ func (l *Conn) Start() { | |||||
l.wgClose.Add(1) | l.wgClose.Add(1) | ||||
} | } | ||||
// isClosing returns whether or not we're currently closing. | |||||
func (l *Conn) isClosing() bool { | |||||
// IsClosing returns whether or not we're currently closing. | |||||
func (l *Conn) IsClosing() bool { | |||||
return atomic.LoadUint32(&l.closing) == 1 | return atomic.LoadUint32(&l.closing) == 1 | ||||
} | } | ||||
@@ -242,30 +277,41 @@ func (l *Conn) StartTLS(config *tls.Config) error { | |||||
ber.PrintPacket(packet) | ber.PrintPacket(packet) | ||||
} | } | ||||
if resultCode, message := getLDAPResultCode(packet); resultCode == LDAPResultSuccess { | |||||
if err := GetLDAPError(packet); err == nil { | |||||
conn := tls.Client(l.conn, config) | conn := tls.Client(l.conn, config) | ||||
if err := conn.Handshake(); err != nil { | |||||
if connErr := conn.Handshake(); connErr != nil { | |||||
l.Close() | l.Close() | ||||
return NewError(ErrorNetwork, fmt.Errorf("TLS handshake failed (%v)", err)) | |||||
return NewError(ErrorNetwork, fmt.Errorf("TLS handshake failed (%v)", connErr)) | |||||
} | } | ||||
l.isTLS = true | l.isTLS = true | ||||
l.conn = conn | l.conn = conn | ||||
} else { | } else { | ||||
return NewError(resultCode, fmt.Errorf("ldap: cannot StartTLS (%s)", message)) | |||||
return err | |||||
} | } | ||||
go l.reader() | go l.reader() | ||||
return nil | return nil | ||||
} | } | ||||
// TLSConnectionState returns the client's TLS connection state. | |||||
// The return values are their zero values if StartTLS did | |||||
// not succeed. | |||||
func (l *Conn) TLSConnectionState() (state tls.ConnectionState, ok bool) { | |||||
tc, ok := l.conn.(*tls.Conn) | |||||
if !ok { | |||||
return | |||||
} | |||||
return tc.ConnectionState(), true | |||||
} | |||||
func (l *Conn) sendMessage(packet *ber.Packet) (*messageContext, error) { | func (l *Conn) sendMessage(packet *ber.Packet) (*messageContext, error) { | ||||
return l.sendMessageWithFlags(packet, 0) | return l.sendMessageWithFlags(packet, 0) | ||||
} | } | ||||
func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) (*messageContext, error) { | func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) (*messageContext, error) { | ||||
if l.isClosing() { | |||||
if l.IsClosing() { | |||||
return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed")) | return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed")) | ||||
} | } | ||||
l.messageMutex.Lock() | l.messageMutex.Lock() | ||||
@@ -304,7 +350,7 @@ func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) | |||||
func (l *Conn) finishMessage(msgCtx *messageContext) { | func (l *Conn) finishMessage(msgCtx *messageContext) { | ||||
close(msgCtx.done) | close(msgCtx.done) | ||||
if l.isClosing() { | |||||
if l.IsClosing() { | |||||
return | return | ||||
} | } | ||||
@@ -325,7 +371,7 @@ func (l *Conn) finishMessage(msgCtx *messageContext) { | |||||
func (l *Conn) sendProcessMessage(message *messagePacket) bool { | func (l *Conn) sendProcessMessage(message *messagePacket) bool { | ||||
l.messageMutex.Lock() | l.messageMutex.Lock() | ||||
defer l.messageMutex.Unlock() | defer l.messageMutex.Unlock() | ||||
if l.isClosing() { | |||||
if l.IsClosing() { | |||||
return false | return false | ||||
} | } | ||||
l.chanMessage <- message | l.chanMessage <- message | ||||
@@ -340,7 +386,7 @@ func (l *Conn) processMessages() { | |||||
for messageID, msgCtx := range l.messageContexts { | for messageID, msgCtx := range l.messageContexts { | ||||
// If we are closing due to an error, inform anyone who | // If we are closing due to an error, inform anyone who | ||||
// is waiting about the error. | // is waiting about the error. | ||||
if l.isClosing() && l.closeErr.Load() != nil { | |||||
if l.IsClosing() && l.closeErr.Load() != nil { | |||||
msgCtx.sendResponse(&PacketResponse{Error: l.closeErr.Load().(error)}) | msgCtx.sendResponse(&PacketResponse{Error: l.closeErr.Load().(error)}) | ||||
} | } | ||||
l.Debug.Printf("Closing channel for MessageID %d", messageID) | l.Debug.Printf("Closing channel for MessageID %d", messageID) | ||||
@@ -400,7 +446,7 @@ func (l *Conn) processMessages() { | |||||
if msgCtx, ok := l.messageContexts[message.MessageID]; ok { | if msgCtx, ok := l.messageContexts[message.MessageID]; ok { | ||||
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) | ber.PrintPacket(message.Packet) | ||||
} | } | ||||
case MessageTimeout: | case MessageTimeout: | ||||
@@ -442,7 +488,7 @@ func (l *Conn) reader() { | |||||
packet, err := ber.ReadPacket(l.conn) | packet, err := ber.ReadPacket(l.conn) | ||||
if err != nil { | if err != nil { | ||||
// A read error is expected here if we are closing the connection... | // A read error is expected here if we are closing the connection... | ||||
if !l.isClosing() { | |||||
if !l.IsClosing() { | |||||
l.closeErr.Store(fmt.Errorf("unable to read LDAP response packet: %s", err)) | l.closeErr.Store(fmt.Errorf("unable to read LDAP response packet: %s", err)) | ||||
l.Debug.Printf("reader error: %s", err.Error()) | l.Debug.Printf("reader error: %s", err.Error()) | ||||
} | } |
@@ -1,7 +1,3 @@ | |||||
// Copyright 2011 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package ldap | package ldap | ||||
import ( | import ( | ||||
@@ -22,13 +18,20 @@ const ( | |||||
ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5" | ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5" | ||||
// ControlTypeManageDsaIT - https://tools.ietf.org/html/rfc3296 | // ControlTypeManageDsaIT - https://tools.ietf.org/html/rfc3296 | ||||
ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2" | ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2" | ||||
// ControlTypeMicrosoftNotification - https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx | |||||
ControlTypeMicrosoftNotification = "1.2.840.113556.1.4.528" | |||||
// ControlTypeMicrosoftShowDeleted - https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx | |||||
ControlTypeMicrosoftShowDeleted = "1.2.840.113556.1.4.417" | |||||
) | ) | ||||
// ControlTypeMap maps controls to text descriptions | // ControlTypeMap maps controls to text descriptions | ||||
var ControlTypeMap = map[string]string{ | var ControlTypeMap = map[string]string{ | ||||
ControlTypePaging: "Paging", | |||||
ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft", | |||||
ControlTypeManageDsaIT: "Manage DSA IT", | |||||
ControlTypePaging: "Paging", | |||||
ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft", | |||||
ControlTypeManageDsaIT: "Manage DSA IT", | |||||
ControlTypeMicrosoftNotification: "Change Notification - Microsoft", | |||||
ControlTypeMicrosoftShowDeleted: "Show Deleted Objects - Microsoft", | |||||
} | } | ||||
// Control defines an interface controls provide to encode and describe themselves | // Control defines an interface controls provide to encode and describe themselves | ||||
@@ -242,6 +245,64 @@ func NewControlManageDsaIT(Criticality bool) *ControlManageDsaIT { | |||||
return &ControlManageDsaIT{Criticality: Criticality} | return &ControlManageDsaIT{Criticality: Criticality} | ||||
} | } | ||||
// ControlMicrosoftNotification implements the control described in https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx | |||||
type ControlMicrosoftNotification struct{} | |||||
// GetControlType returns the OID | |||||
func (c *ControlMicrosoftNotification) GetControlType() string { | |||||
return ControlTypeMicrosoftNotification | |||||
} | |||||
// Encode returns the ber packet representation | |||||
func (c *ControlMicrosoftNotification) Encode() *ber.Packet { | |||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") | |||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftNotification, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftNotification]+")")) | |||||
return packet | |||||
} | |||||
// String returns a human-readable description | |||||
func (c *ControlMicrosoftNotification) String() string { | |||||
return fmt.Sprintf( | |||||
"Control Type: %s (%q)", | |||||
ControlTypeMap[ControlTypeMicrosoftNotification], | |||||
ControlTypeMicrosoftNotification) | |||||
} | |||||
// NewControlMicrosoftNotification returns a ControlMicrosoftNotification control | |||||
func NewControlMicrosoftNotification() *ControlMicrosoftNotification { | |||||
return &ControlMicrosoftNotification{} | |||||
} | |||||
// ControlMicrosoftShowDeleted implements the control described in https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx | |||||
type ControlMicrosoftShowDeleted struct{} | |||||
// GetControlType returns the OID | |||||
func (c *ControlMicrosoftShowDeleted) GetControlType() string { | |||||
return ControlTypeMicrosoftShowDeleted | |||||
} | |||||
// Encode returns the ber packet representation | |||||
func (c *ControlMicrosoftShowDeleted) Encode() *ber.Packet { | |||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") | |||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftShowDeleted, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftShowDeleted]+")")) | |||||
return packet | |||||
} | |||||
// String returns a human-readable description | |||||
func (c *ControlMicrosoftShowDeleted) String() string { | |||||
return fmt.Sprintf( | |||||
"Control Type: %s (%q)", | |||||
ControlTypeMap[ControlTypeMicrosoftShowDeleted], | |||||
ControlTypeMicrosoftShowDeleted) | |||||
} | |||||
// NewControlMicrosoftShowDeleted returns a ControlMicrosoftShowDeleted control | |||||
func NewControlMicrosoftShowDeleted() *ControlMicrosoftShowDeleted { | |||||
return &ControlMicrosoftShowDeleted{} | |||||
} | |||||
// FindControl returns the first control of the given type in the list, or nil | // FindControl returns the first control of the given type in the list, or nil | ||||
func FindControl(controls []Control, controlType string) Control { | func FindControl(controls []Control, controlType string) Control { | ||||
for _, c := range controls { | for _, c := range controls { | ||||
@@ -253,7 +314,7 @@ func FindControl(controls []Control, controlType string) Control { | |||||
} | } | ||||
// DecodeControl returns a control read from the given packet, or nil if no recognized control can be made | // DecodeControl returns a control read from the given packet, or nil if no recognized control can be made | ||||
func DecodeControl(packet *ber.Packet) Control { | |||||
func DecodeControl(packet *ber.Packet) (Control, error) { | |||||
var ( | var ( | ||||
ControlType = "" | ControlType = "" | ||||
Criticality = false | Criticality = false | ||||
@@ -263,7 +324,7 @@ func DecodeControl(packet *ber.Packet) Control { | |||||
switch len(packet.Children) { | switch len(packet.Children) { | ||||
case 0: | case 0: | ||||
// at least one child is required for control type | // at least one child is required for control type | ||||
return nil | |||||
return nil, fmt.Errorf("at least one child is required for control type") | |||||
case 1: | case 1: | ||||
// just type, no criticality or value | // just type, no criticality or value | ||||
@@ -296,17 +357,20 @@ func DecodeControl(packet *ber.Packet) Control { | |||||
default: | default: | ||||
// more than 3 children is invalid | // more than 3 children is invalid | ||||
return nil | |||||
return nil, fmt.Errorf("more than 3 children is invalid for controls") | |||||
} | } | ||||
switch ControlType { | switch ControlType { | ||||
case ControlTypeManageDsaIT: | case ControlTypeManageDsaIT: | ||||
return NewControlManageDsaIT(Criticality) | |||||
return NewControlManageDsaIT(Criticality), nil | |||||
case ControlTypePaging: | case ControlTypePaging: | ||||
value.Description += " (Paging)" | value.Description += " (Paging)" | ||||
c := new(ControlPaging) | c := new(ControlPaging) | ||||
if value.Value != nil { | if value.Value != nil { | ||||
valueChildren := ber.DecodePacket(value.Data.Bytes()) | |||||
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) | |||||
if err != nil { | |||||
return nil, fmt.Errorf("failed to decode data bytes: %s", err) | |||||
} | |||||
value.Data.Truncate(0) | value.Data.Truncate(0) | ||||
value.Value = nil | value.Value = nil | ||||
value.AppendChild(valueChildren) | value.AppendChild(valueChildren) | ||||
@@ -318,12 +382,15 @@ func DecodeControl(packet *ber.Packet) Control { | |||||
c.PagingSize = uint32(value.Children[0].Value.(int64)) | c.PagingSize = uint32(value.Children[0].Value.(int64)) | ||||
c.Cookie = value.Children[1].Data.Bytes() | c.Cookie = value.Children[1].Data.Bytes() | ||||
value.Children[1].Value = c.Cookie | value.Children[1].Value = c.Cookie | ||||
return c | |||||
return c, nil | |||||
case ControlTypeBeheraPasswordPolicy: | case ControlTypeBeheraPasswordPolicy: | ||||
value.Description += " (Password Policy - Behera)" | value.Description += " (Password Policy - Behera)" | ||||
c := NewControlBeheraPasswordPolicy() | c := NewControlBeheraPasswordPolicy() | ||||
if value.Value != nil { | if value.Value != nil { | ||||
valueChildren := ber.DecodePacket(value.Data.Bytes()) | |||||
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) | |||||
if err != nil { | |||||
return nil, fmt.Errorf("failed to decode data bytes: %s", err) | |||||
} | |||||
value.Data.Truncate(0) | value.Data.Truncate(0) | ||||
value.Value = nil | value.Value = nil | ||||
value.AppendChild(valueChildren) | value.AppendChild(valueChildren) | ||||
@@ -335,7 +402,10 @@ func DecodeControl(packet *ber.Packet) Control { | |||||
if child.Tag == 0 { | if child.Tag == 0 { | ||||
//Warning | //Warning | ||||
warningPacket := child.Children[0] | warningPacket := child.Children[0] | ||||
packet := ber.DecodePacket(warningPacket.Data.Bytes()) | |||||
packet, err := ber.DecodePacketErr(warningPacket.Data.Bytes()) | |||||
if err != nil { | |||||
return nil, fmt.Errorf("failed to decode data bytes: %s", err) | |||||
} | |||||
val, ok := packet.Value.(int64) | val, ok := packet.Value.(int64) | ||||
if ok { | if ok { | ||||
if warningPacket.Tag == 0 { | if warningPacket.Tag == 0 { | ||||
@@ -350,7 +420,10 @@ func DecodeControl(packet *ber.Packet) Control { | |||||
} | } | ||||
} else if child.Tag == 1 { | } else if child.Tag == 1 { | ||||
// Error | // Error | ||||
packet := ber.DecodePacket(child.Data.Bytes()) | |||||
packet, err := ber.DecodePacketErr(child.Data.Bytes()) | |||||
if err != nil { | |||||
return nil, fmt.Errorf("failed to decode data bytes: %s", err) | |||||
} | |||||
val, ok := packet.Value.(int8) | val, ok := packet.Value.(int8) | ||||
if !ok { | if !ok { | ||||
// what to do? | // what to do? | ||||
@@ -361,22 +434,26 @@ func DecodeControl(packet *ber.Packet) Control { | |||||
c.ErrorString = BeheraPasswordPolicyErrorMap[c.Error] | c.ErrorString = BeheraPasswordPolicyErrorMap[c.Error] | ||||
} | } | ||||
} | } | ||||
return c | |||||
return c, nil | |||||
case ControlTypeVChuPasswordMustChange: | case ControlTypeVChuPasswordMustChange: | ||||
c := &ControlVChuPasswordMustChange{MustChange: true} | c := &ControlVChuPasswordMustChange{MustChange: true} | ||||
return c | |||||
return c, nil | |||||
case ControlTypeVChuPasswordWarning: | case ControlTypeVChuPasswordWarning: | ||||
c := &ControlVChuPasswordWarning{Expire: -1} | c := &ControlVChuPasswordWarning{Expire: -1} | ||||
expireStr := ber.DecodeString(value.Data.Bytes()) | expireStr := ber.DecodeString(value.Data.Bytes()) | ||||
expire, err := strconv.ParseInt(expireStr, 10, 64) | expire, err := strconv.ParseInt(expireStr, 10, 64) | ||||
if err != nil { | if err != nil { | ||||
return nil | |||||
return nil, fmt.Errorf("failed to parse value as int: %s", err) | |||||
} | } | ||||
c.Expire = expire | c.Expire = expire | ||||
value.Value = c.Expire | value.Value = c.Expire | ||||
return c | |||||
return c, nil | |||||
case ControlTypeMicrosoftNotification: | |||||
return NewControlMicrosoftNotification(), nil | |||||
case ControlTypeMicrosoftShowDeleted: | |||||
return NewControlMicrosoftShowDeleted(), nil | |||||
default: | default: | ||||
c := new(ControlString) | c := new(ControlString) | ||||
c.ControlType = ControlType | c.ControlType = ControlType | ||||
@@ -384,7 +461,7 @@ func DecodeControl(packet *ber.Packet) Control { | |||||
if value != nil { | if value != nil { | ||||
c.ControlValue = value.Value.(string) | c.ControlValue = value.Value.(string) | ||||
} | } | ||||
return c | |||||
return c, nil | |||||
} | } | ||||
} | } | ||||
@@ -40,7 +40,7 @@ func (l *Conn) Del(delRequest *DelRequest) error { | |||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") | 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")) | packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID")) | ||||
packet.AppendChild(delRequest.encode()) | packet.AppendChild(delRequest.encode()) | ||||
if delRequest.Controls != nil { | |||||
if len(delRequest.Controls) > 0 { | |||||
packet.AppendChild(encodeControls(delRequest.Controls)) | packet.AppendChild(encodeControls(delRequest.Controls)) | ||||
} | } | ||||
@@ -71,9 +71,9 @@ func (l *Conn) Del(delRequest *DelRequest) error { | |||||
} | } | ||||
if packet.Children[1].Tag == ApplicationDelResponse { | if packet.Children[1].Tag == ApplicationDelResponse { | ||||
resultCode, resultDescription := getLDAPResultCode(packet) | |||||
if resultCode != 0 { | |||||
return NewError(resultCode, errors.New(resultDescription)) | |||||
err := GetLDAPError(packet) | |||||
if err != nil { | |||||
return err | |||||
} | } | ||||
} else { | } else { | ||||
log.Printf("Unexpected Response: %d", packet.Children[1].Tag) | log.Printf("Unexpected Response: %d", packet.Children[1].Tag) |
@@ -1,7 +1,3 @@ | |||||
// Copyright 2015 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
// | |||||
// File contains DN parsing functionality | // File contains DN parsing functionality | ||||
// | // | ||||
// https://tools.ietf.org/html/rfc4514 | // https://tools.ietf.org/html/rfc4514 | ||||
@@ -94,7 +90,8 @@ func ParseDN(str string) (*DN, error) { | |||||
for i := 0; i < len(str); i++ { | for i := 0; i < len(str); i++ { | ||||
char := str[i] | char := str[i] | ||||
if escaping { | |||||
switch { | |||||
case escaping: | |||||
unescapedTrailingSpaces = 0 | unescapedTrailingSpaces = 0 | ||||
escaping = false | escaping = false | ||||
switch char { | switch char { | ||||
@@ -104,22 +101,22 @@ func ParseDN(str string) (*DN, error) { | |||||
} | } | ||||
// Not a special character, assume hex encoded octet | // Not a special character, assume hex encoded octet | ||||
if len(str) == i+1 { | if len(str) == i+1 { | ||||
return nil, errors.New("Got corrupted escaped character") | |||||
return nil, errors.New("got corrupted escaped character") | |||||
} | } | ||||
dst := []byte{0} | dst := []byte{0} | ||||
n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2])) | n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2])) | ||||
if err != nil { | if err != nil { | ||||
return nil, fmt.Errorf("Failed to decode escaped character: %s", err) | |||||
return nil, fmt.Errorf("failed to decode escaped character: %s", err) | |||||
} else if n != 1 { | } else if n != 1 { | ||||
return nil, fmt.Errorf("Expected 1 byte when un-escaping, got %d", n) | |||||
return nil, fmt.Errorf("expected 1 byte when un-escaping, got %d", n) | |||||
} | } | ||||
buffer.WriteByte(dst[0]) | buffer.WriteByte(dst[0]) | ||||
i++ | i++ | ||||
} else if char == '\\' { | |||||
case char == '\\': | |||||
unescapedTrailingSpaces = 0 | unescapedTrailingSpaces = 0 | ||||
escaping = true | escaping = true | ||||
} else if char == '=' { | |||||
case char == '=': | |||||
attribute.Type = stringFromBuffer() | attribute.Type = stringFromBuffer() | ||||
// Special case: If the first character in the value is # the | // Special case: If the first character in the value is # the | ||||
// following data is BER encoded so we can just fast forward | // following data is BER encoded so we can just fast forward | ||||
@@ -135,13 +132,16 @@ func ParseDN(str string) (*DN, error) { | |||||
} | } | ||||
rawBER, err := enchex.DecodeString(data) | rawBER, err := enchex.DecodeString(data) | ||||
if err != nil { | if err != nil { | ||||
return nil, fmt.Errorf("Failed to decode BER encoding: %s", err) | |||||
return nil, fmt.Errorf("failed to decode BER encoding: %s", err) | |||||
} | |||||
packet, err := ber.DecodePacketErr(rawBER) | |||||
if err != nil { | |||||
return nil, fmt.Errorf("failed to decode BER packet: %s", err) | |||||
} | } | ||||
packet := ber.DecodePacket(rawBER) | |||||
buffer.WriteString(packet.Data.String()) | buffer.WriteString(packet.Data.String()) | ||||
i += len(data) - 1 | i += len(data) - 1 | ||||
} | } | ||||
} else if char == ',' || char == '+' { | |||||
case char == ',' || char == '+': | |||||
// We're done with this RDN or value, push it | // We're done with this RDN or value, push it | ||||
if len(attribute.Type) == 0 { | if len(attribute.Type) == 0 { | ||||
return nil, errors.New("incomplete type, value pair") | return nil, errors.New("incomplete type, value pair") | ||||
@@ -154,10 +154,10 @@ func ParseDN(str string) (*DN, error) { | |||||
rdn = new(RelativeDN) | rdn = new(RelativeDN) | ||||
rdn.Attributes = make([]*AttributeTypeAndValue, 0) | rdn.Attributes = make([]*AttributeTypeAndValue, 0) | ||||
} | } | ||||
} else if char == ' ' && buffer.Len() == 0 { | |||||
case char == ' ' && buffer.Len() == 0: | |||||
// ignore unescaped leading spaces | // ignore unescaped leading spaces | ||||
continue | continue | ||||
} else { | |||||
default: | |||||
if char == ' ' { | if char == ' ' { | ||||
// Track unescaped spaces in case they are trailing and we need to remove them | // Track unescaped spaces in case they are trailing and we need to remove them | ||||
unescapedTrailingSpaces++ | unescapedTrailingSpaces++ |
@@ -0,0 +1,234 @@ | |||||
package ldap | |||||
import ( | |||||
"fmt" | |||||
"gopkg.in/asn1-ber.v1" | |||||
) | |||||
// LDAP Result Codes | |||||
const ( | |||||
LDAPResultSuccess = 0 | |||||
LDAPResultOperationsError = 1 | |||||
LDAPResultProtocolError = 2 | |||||
LDAPResultTimeLimitExceeded = 3 | |||||
LDAPResultSizeLimitExceeded = 4 | |||||
LDAPResultCompareFalse = 5 | |||||
LDAPResultCompareTrue = 6 | |||||
LDAPResultAuthMethodNotSupported = 7 | |||||
LDAPResultStrongAuthRequired = 8 | |||||
LDAPResultReferral = 10 | |||||
LDAPResultAdminLimitExceeded = 11 | |||||
LDAPResultUnavailableCriticalExtension = 12 | |||||
LDAPResultConfidentialityRequired = 13 | |||||
LDAPResultSaslBindInProgress = 14 | |||||
LDAPResultNoSuchAttribute = 16 | |||||
LDAPResultUndefinedAttributeType = 17 | |||||
LDAPResultInappropriateMatching = 18 | |||||
LDAPResultConstraintViolation = 19 | |||||
LDAPResultAttributeOrValueExists = 20 | |||||
LDAPResultInvalidAttributeSyntax = 21 | |||||
LDAPResultNoSuchObject = 32 | |||||
LDAPResultAliasProblem = 33 | |||||
LDAPResultInvalidDNSyntax = 34 | |||||
LDAPResultIsLeaf = 35 | |||||
LDAPResultAliasDereferencingProblem = 36 | |||||
LDAPResultInappropriateAuthentication = 48 | |||||
LDAPResultInvalidCredentials = 49 | |||||
LDAPResultInsufficientAccessRights = 50 | |||||
LDAPResultBusy = 51 | |||||
LDAPResultUnavailable = 52 | |||||
LDAPResultUnwillingToPerform = 53 | |||||
LDAPResultLoopDetect = 54 | |||||
LDAPResultSortControlMissing = 60 | |||||
LDAPResultOffsetRangeError = 61 | |||||
LDAPResultNamingViolation = 64 | |||||
LDAPResultObjectClassViolation = 65 | |||||
LDAPResultNotAllowedOnNonLeaf = 66 | |||||
LDAPResultNotAllowedOnRDN = 67 | |||||
LDAPResultEntryAlreadyExists = 68 | |||||
LDAPResultObjectClassModsProhibited = 69 | |||||
LDAPResultResultsTooLarge = 70 | |||||
LDAPResultAffectsMultipleDSAs = 71 | |||||
LDAPResultVirtualListViewErrorOrControlError = 76 | |||||
LDAPResultOther = 80 | |||||
LDAPResultServerDown = 81 | |||||
LDAPResultLocalError = 82 | |||||
LDAPResultEncodingError = 83 | |||||
LDAPResultDecodingError = 84 | |||||
LDAPResultTimeout = 85 | |||||
LDAPResultAuthUnknown = 86 | |||||
LDAPResultFilterError = 87 | |||||
LDAPResultUserCanceled = 88 | |||||
LDAPResultParamError = 89 | |||||
LDAPResultNoMemory = 90 | |||||
LDAPResultConnectError = 91 | |||||
LDAPResultNotSupported = 92 | |||||
LDAPResultControlNotFound = 93 | |||||
LDAPResultNoResultsReturned = 94 | |||||
LDAPResultMoreResultsToReturn = 95 | |||||
LDAPResultClientLoop = 96 | |||||
LDAPResultReferralLimitExceeded = 97 | |||||
LDAPResultInvalidResponse = 100 | |||||
LDAPResultAmbiguousResponse = 101 | |||||
LDAPResultTLSNotSupported = 112 | |||||
LDAPResultIntermediateResponse = 113 | |||||
LDAPResultUnknownType = 114 | |||||
LDAPResultCanceled = 118 | |||||
LDAPResultNoSuchOperation = 119 | |||||
LDAPResultTooLate = 120 | |||||
LDAPResultCannotCancel = 121 | |||||
LDAPResultAssertionFailed = 122 | |||||
LDAPResultAuthorizationDenied = 123 | |||||
LDAPResultSyncRefreshRequired = 4096 | |||||
ErrorNetwork = 200 | |||||
ErrorFilterCompile = 201 | |||||
ErrorFilterDecompile = 202 | |||||
ErrorDebugging = 203 | |||||
ErrorUnexpectedMessage = 204 | |||||
ErrorUnexpectedResponse = 205 | |||||
ErrorEmptyPassword = 206 | |||||
) | |||||
// LDAPResultCodeMap contains string descriptions for LDAP error codes | |||||
var LDAPResultCodeMap = map[uint16]string{ | |||||
LDAPResultSuccess: "Success", | |||||
LDAPResultOperationsError: "Operations Error", | |||||
LDAPResultProtocolError: "Protocol Error", | |||||
LDAPResultTimeLimitExceeded: "Time Limit Exceeded", | |||||
LDAPResultSizeLimitExceeded: "Size Limit Exceeded", | |||||
LDAPResultCompareFalse: "Compare False", | |||||
LDAPResultCompareTrue: "Compare True", | |||||
LDAPResultAuthMethodNotSupported: "Auth Method Not Supported", | |||||
LDAPResultStrongAuthRequired: "Strong Auth Required", | |||||
LDAPResultReferral: "Referral", | |||||
LDAPResultAdminLimitExceeded: "Admin Limit Exceeded", | |||||
LDAPResultUnavailableCriticalExtension: "Unavailable Critical Extension", | |||||
LDAPResultConfidentialityRequired: "Confidentiality Required", | |||||
LDAPResultSaslBindInProgress: "Sasl Bind In Progress", | |||||
LDAPResultNoSuchAttribute: "No Such Attribute", | |||||
LDAPResultUndefinedAttributeType: "Undefined Attribute Type", | |||||
LDAPResultInappropriateMatching: "Inappropriate Matching", | |||||
LDAPResultConstraintViolation: "Constraint Violation", | |||||
LDAPResultAttributeOrValueExists: "Attribute Or Value Exists", | |||||
LDAPResultInvalidAttributeSyntax: "Invalid Attribute Syntax", | |||||
LDAPResultNoSuchObject: "No Such Object", | |||||
LDAPResultAliasProblem: "Alias Problem", | |||||
LDAPResultInvalidDNSyntax: "Invalid DN Syntax", | |||||
LDAPResultIsLeaf: "Is Leaf", | |||||
LDAPResultAliasDereferencingProblem: "Alias Dereferencing Problem", | |||||
LDAPResultInappropriateAuthentication: "Inappropriate Authentication", | |||||
LDAPResultInvalidCredentials: "Invalid Credentials", | |||||
LDAPResultInsufficientAccessRights: "Insufficient Access Rights", | |||||
LDAPResultBusy: "Busy", | |||||
LDAPResultUnavailable: "Unavailable", | |||||
LDAPResultUnwillingToPerform: "Unwilling To Perform", | |||||
LDAPResultLoopDetect: "Loop Detect", | |||||
LDAPResultSortControlMissing: "Sort Control Missing", | |||||
LDAPResultOffsetRangeError: "Result Offset Range Error", | |||||
LDAPResultNamingViolation: "Naming Violation", | |||||
LDAPResultObjectClassViolation: "Object Class Violation", | |||||
LDAPResultResultsTooLarge: "Results Too Large", | |||||
LDAPResultNotAllowedOnNonLeaf: "Not Allowed On Non Leaf", | |||||
LDAPResultNotAllowedOnRDN: "Not Allowed On RDN", | |||||
LDAPResultEntryAlreadyExists: "Entry Already Exists", | |||||
LDAPResultObjectClassModsProhibited: "Object Class Mods Prohibited", | |||||
LDAPResultAffectsMultipleDSAs: "Affects Multiple DSAs", | |||||
LDAPResultVirtualListViewErrorOrControlError: "Failed because of a problem related to the virtual list view", | |||||
LDAPResultOther: "Other", | |||||
LDAPResultServerDown: "Cannot establish a connection", | |||||
LDAPResultLocalError: "An error occurred", | |||||
LDAPResultEncodingError: "LDAP encountered an error while encoding", | |||||
LDAPResultDecodingError: "LDAP encountered an error while decoding", | |||||
LDAPResultTimeout: "LDAP timeout while waiting for a response from the server", | |||||
LDAPResultAuthUnknown: "The auth method requested in a bind request is unknown", | |||||
LDAPResultFilterError: "An error occurred while encoding the given search filter", | |||||
LDAPResultUserCanceled: "The user canceled the operation", | |||||
LDAPResultParamError: "An invalid parameter was specified", | |||||
LDAPResultNoMemory: "Out of memory error", | |||||
LDAPResultConnectError: "A connection to the server could not be established", | |||||
LDAPResultNotSupported: "An attempt has been made to use a feature not supported LDAP", | |||||
LDAPResultControlNotFound: "The controls required to perform the requested operation were not found", | |||||
LDAPResultNoResultsReturned: "No results were returned from the server", | |||||
LDAPResultMoreResultsToReturn: "There are more results in the chain of results", | |||||
LDAPResultClientLoop: "A loop has been detected. For example when following referrals", | |||||
LDAPResultReferralLimitExceeded: "The referral hop limit has been exceeded", | |||||
LDAPResultCanceled: "Operation was canceled", | |||||
LDAPResultNoSuchOperation: "Server has no knowledge of the operation requested for cancellation", | |||||
LDAPResultTooLate: "Too late to cancel the outstanding operation", | |||||
LDAPResultCannotCancel: "The identified operation does not support cancellation or the cancel operation cannot be performed", | |||||
LDAPResultAssertionFailed: "An assertion control given in the LDAP operation evaluated to false causing the operation to not be performed", | |||||
LDAPResultSyncRefreshRequired: "Refresh Required", | |||||
LDAPResultInvalidResponse: "Invalid Response", | |||||
LDAPResultAmbiguousResponse: "Ambiguous Response", | |||||
LDAPResultTLSNotSupported: "Tls Not Supported", | |||||
LDAPResultIntermediateResponse: "Intermediate Response", | |||||
LDAPResultUnknownType: "Unknown Type", | |||||
LDAPResultAuthorizationDenied: "Authorization Denied", | |||||
ErrorNetwork: "Network Error", | |||||
ErrorFilterCompile: "Filter Compile Error", | |||||
ErrorFilterDecompile: "Filter Decompile Error", | |||||
ErrorDebugging: "Debugging Error", | |||||
ErrorUnexpectedMessage: "Unexpected Message", | |||||
ErrorUnexpectedResponse: "Unexpected Response", | |||||
ErrorEmptyPassword: "Empty password not allowed by the client", | |||||
} | |||||
// Error holds LDAP error information | |||||
type Error struct { | |||||
// Err is the underlying error | |||||
Err error | |||||
// ResultCode is the LDAP error code | |||||
ResultCode uint16 | |||||
// MatchedDN is the matchedDN returned if any | |||||
MatchedDN string | |||||
} | |||||
func (e *Error) Error() string { | |||||
return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error()) | |||||
} | |||||
// GetLDAPError creates an Error out of a BER packet representing a LDAPResult | |||||
// The return is an error object. It can be casted to a Error structure. | |||||
// This function returns nil if resultCode in the LDAPResult sequence is success(0). | |||||
func GetLDAPError(packet *ber.Packet) error { | |||||
if packet == nil { | |||||
return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("Empty packet")} | |||||
} else if len(packet.Children) >= 2 { | |||||
response := packet.Children[1] | |||||
if response == nil { | |||||
return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("Empty response in packet")} | |||||
} | |||||
if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 { | |||||
resultCode := uint16(response.Children[0].Value.(int64)) | |||||
if resultCode == 0 { // No error | |||||
return nil | |||||
} | |||||
return &Error{ResultCode: resultCode, MatchedDN: response.Children[1].Value.(string), | |||||
Err: fmt.Errorf(response.Children[2].Value.(string))} | |||||
} | |||||
} | |||||
return &Error{ResultCode: ErrorNetwork, Err: fmt.Errorf("Invalid packet format")} | |||||
} | |||||
// NewError creates an LDAP error with the given code and underlying error | |||||
func NewError(resultCode uint16, err error) error { | |||||
return &Error{ResultCode: resultCode, Err: err} | |||||
} | |||||
// IsErrorWithCode returns true if the given error is an LDAP error with the given result code | |||||
func IsErrorWithCode(err error, desiredResultCode uint16) bool { | |||||
if err == nil { | |||||
return false | |||||
} | |||||
serverError, ok := err.(*Error) | |||||
if !ok { | |||||
return false | |||||
} | |||||
return serverError.ResultCode == desiredResultCode | |||||
} |
@@ -1,7 +1,3 @@ | |||||
// Copyright 2011 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package ldap | package ldap | ||||
import ( | import ( |
@@ -1,11 +1,8 @@ | |||||
// Copyright 2011 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package ldap | package ldap | ||||
import ( | import ( | ||||
"errors" | "errors" | ||||
"fmt" | |||||
"io/ioutil" | "io/ioutil" | ||||
"os" | "os" | ||||
@@ -101,13 +98,13 @@ func addLDAPDescriptions(packet *ber.Packet) (err error) { | |||||
switch application { | switch application { | ||||
case ApplicationBindRequest: | case ApplicationBindRequest: | ||||
addRequestDescriptions(packet) | |||||
err = addRequestDescriptions(packet) | |||||
case ApplicationBindResponse: | case ApplicationBindResponse: | ||||
addDefaultLDAPResponseDescriptions(packet) | |||||
err = addDefaultLDAPResponseDescriptions(packet) | |||||
case ApplicationUnbindRequest: | case ApplicationUnbindRequest: | ||||
addRequestDescriptions(packet) | |||||
err = addRequestDescriptions(packet) | |||||
case ApplicationSearchRequest: | case ApplicationSearchRequest: | ||||
addRequestDescriptions(packet) | |||||
err = addRequestDescriptions(packet) | |||||
case ApplicationSearchResultEntry: | case ApplicationSearchResultEntry: | ||||
packet.Children[1].Children[0].Description = "Object Name" | packet.Children[1].Children[0].Description = "Object Name" | ||||
packet.Children[1].Children[1].Description = "Attributes" | packet.Children[1].Children[1].Description = "Attributes" | ||||
@@ -120,37 +117,37 @@ func addLDAPDescriptions(packet *ber.Packet) (err error) { | |||||
} | } | ||||
} | } | ||||
if len(packet.Children) == 3 { | if len(packet.Children) == 3 { | ||||
addControlDescriptions(packet.Children[2]) | |||||
err = addControlDescriptions(packet.Children[2]) | |||||
} | } | ||||
case ApplicationSearchResultDone: | case ApplicationSearchResultDone: | ||||
addDefaultLDAPResponseDescriptions(packet) | |||||
err = addDefaultLDAPResponseDescriptions(packet) | |||||
case ApplicationModifyRequest: | case ApplicationModifyRequest: | ||||
addRequestDescriptions(packet) | |||||
err = addRequestDescriptions(packet) | |||||
case ApplicationModifyResponse: | case ApplicationModifyResponse: | ||||
case ApplicationAddRequest: | case ApplicationAddRequest: | ||||
addRequestDescriptions(packet) | |||||
err = addRequestDescriptions(packet) | |||||
case ApplicationAddResponse: | case ApplicationAddResponse: | ||||
case ApplicationDelRequest: | case ApplicationDelRequest: | ||||
addRequestDescriptions(packet) | |||||
err = addRequestDescriptions(packet) | |||||
case ApplicationDelResponse: | case ApplicationDelResponse: | ||||
case ApplicationModifyDNRequest: | case ApplicationModifyDNRequest: | ||||
addRequestDescriptions(packet) | |||||
err = addRequestDescriptions(packet) | |||||
case ApplicationModifyDNResponse: | case ApplicationModifyDNResponse: | ||||
case ApplicationCompareRequest: | case ApplicationCompareRequest: | ||||
addRequestDescriptions(packet) | |||||
err = addRequestDescriptions(packet) | |||||
case ApplicationCompareResponse: | case ApplicationCompareResponse: | ||||
case ApplicationAbandonRequest: | case ApplicationAbandonRequest: | ||||
addRequestDescriptions(packet) | |||||
err = addRequestDescriptions(packet) | |||||
case ApplicationSearchResultReference: | case ApplicationSearchResultReference: | ||||
case ApplicationExtendedRequest: | case ApplicationExtendedRequest: | ||||
addRequestDescriptions(packet) | |||||
err = addRequestDescriptions(packet) | |||||
case ApplicationExtendedResponse: | case ApplicationExtendedResponse: | ||||
} | } | ||||
return nil | |||||
return err | |||||
} | } | ||||
func addControlDescriptions(packet *ber.Packet) { | |||||
func addControlDescriptions(packet *ber.Packet) error { | |||||
packet.Description = "Controls" | packet.Description = "Controls" | ||||
for _, child := range packet.Children { | for _, child := range packet.Children { | ||||
var value *ber.Packet | var value *ber.Packet | ||||
@@ -159,7 +156,7 @@ func addControlDescriptions(packet *ber.Packet) { | |||||
switch len(child.Children) { | switch len(child.Children) { | ||||
case 0: | case 0: | ||||
// at least one child is required for control type | // at least one child is required for control type | ||||
continue | |||||
return fmt.Errorf("at least one child is required for control type") | |||||
case 1: | case 1: | ||||
// just type, no criticality or value | // just type, no criticality or value | ||||
@@ -188,8 +185,9 @@ func addControlDescriptions(packet *ber.Packet) { | |||||
default: | default: | ||||
// more than 3 children is invalid | // more than 3 children is invalid | ||||
continue | |||||
return fmt.Errorf("more than 3 children for control packet found") | |||||
} | } | ||||
if value == nil { | if value == nil { | ||||
continue | continue | ||||
} | } | ||||
@@ -197,7 +195,10 @@ func addControlDescriptions(packet *ber.Packet) { | |||||
case ControlTypePaging: | case ControlTypePaging: | ||||
value.Description += " (Paging)" | value.Description += " (Paging)" | ||||
if value.Value != nil { | if value.Value != nil { | ||||
valueChildren := ber.DecodePacket(value.Data.Bytes()) | |||||
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) | |||||
if err != nil { | |||||
return fmt.Errorf("failed to decode data bytes: %s", err) | |||||
} | |||||
value.Data.Truncate(0) | value.Data.Truncate(0) | ||||
value.Value = nil | value.Value = nil | ||||
valueChildren.Children[1].Value = valueChildren.Children[1].Data.Bytes() | valueChildren.Children[1].Value = valueChildren.Children[1].Data.Bytes() | ||||
@@ -210,7 +211,10 @@ func addControlDescriptions(packet *ber.Packet) { | |||||
case ControlTypeBeheraPasswordPolicy: | case ControlTypeBeheraPasswordPolicy: | ||||
value.Description += " (Password Policy - Behera Draft)" | value.Description += " (Password Policy - Behera Draft)" | ||||
if value.Value != nil { | if value.Value != nil { | ||||
valueChildren := ber.DecodePacket(value.Data.Bytes()) | |||||
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) | |||||
if err != nil { | |||||
return fmt.Errorf("failed to decode data bytes: %s", err) | |||||
} | |||||
value.Data.Truncate(0) | value.Data.Truncate(0) | ||||
value.Value = nil | value.Value = nil | ||||
value.AppendChild(valueChildren) | value.AppendChild(valueChildren) | ||||
@@ -220,7 +224,10 @@ func addControlDescriptions(packet *ber.Packet) { | |||||
if child.Tag == 0 { | if child.Tag == 0 { | ||||
//Warning | //Warning | ||||
warningPacket := child.Children[0] | warningPacket := child.Children[0] | ||||
packet := ber.DecodePacket(warningPacket.Data.Bytes()) | |||||
packet, err := ber.DecodePacketErr(warningPacket.Data.Bytes()) | |||||
if err != nil { | |||||
return fmt.Errorf("failed to decode data bytes: %s", err) | |||||
} | |||||
val, ok := packet.Value.(int64) | val, ok := packet.Value.(int64) | ||||
if ok { | if ok { | ||||
if warningPacket.Tag == 0 { | if warningPacket.Tag == 0 { | ||||
@@ -235,7 +242,10 @@ func addControlDescriptions(packet *ber.Packet) { | |||||
} | } | ||||
} else if child.Tag == 1 { | } else if child.Tag == 1 { | ||||
// Error | // Error | ||||
packet := ber.DecodePacket(child.Data.Bytes()) | |||||
packet, err := ber.DecodePacketErr(child.Data.Bytes()) | |||||
if err != nil { | |||||
return fmt.Errorf("failed to decode data bytes: %s", err) | |||||
} | |||||
val, ok := packet.Value.(int8) | val, ok := packet.Value.(int8) | ||||
if !ok { | if !ok { | ||||
val = -1 | val = -1 | ||||
@@ -246,28 +256,31 @@ func addControlDescriptions(packet *ber.Packet) { | |||||
} | } | ||||
} | } | ||||
} | } | ||||
return nil | |||||
} | } | ||||
func addRequestDescriptions(packet *ber.Packet) { | |||||
func addRequestDescriptions(packet *ber.Packet) error { | |||||
packet.Description = "LDAP Request" | packet.Description = "LDAP Request" | ||||
packet.Children[0].Description = "Message ID" | packet.Children[0].Description = "Message ID" | ||||
packet.Children[1].Description = ApplicationMap[uint8(packet.Children[1].Tag)] | packet.Children[1].Description = ApplicationMap[uint8(packet.Children[1].Tag)] | ||||
if len(packet.Children) == 3 { | if len(packet.Children) == 3 { | ||||
addControlDescriptions(packet.Children[2]) | |||||
return addControlDescriptions(packet.Children[2]) | |||||
} | } | ||||
return nil | |||||
} | } | ||||
func addDefaultLDAPResponseDescriptions(packet *ber.Packet) { | |||||
resultCode, _ := getLDAPResultCode(packet) | |||||
packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[resultCode] + ")" | |||||
packet.Children[1].Children[1].Description = "Matched DN" | |||||
func addDefaultLDAPResponseDescriptions(packet *ber.Packet) error { | |||||
err := GetLDAPError(packet) | |||||
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[2].Description = "Error Message" | ||||
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" | ||||
} | } | ||||
if len(packet.Children) == 3 { | if len(packet.Children) == 3 { | ||||
addControlDescriptions(packet.Children[2]) | |||||
return addControlDescriptions(packet.Children[2]) | |||||
} | } | ||||
return nil | |||||
} | } | ||||
// DebugBinaryFile reads and prints packets from the given filename | // DebugBinaryFile reads and prints packets from the given filename | ||||
@@ -277,8 +290,13 @@ func DebugBinaryFile(fileName string) error { | |||||
return NewError(ErrorDebugging, err) | return NewError(ErrorDebugging, err) | ||||
} | } | ||||
ber.PrintBytes(os.Stdout, file, "") | ber.PrintBytes(os.Stdout, file, "") | ||||
packet := ber.DecodePacket(file) | |||||
addLDAPDescriptions(packet) | |||||
packet, err := ber.DecodePacketErr(file) | |||||
if err != nil { | |||||
return fmt.Errorf("failed to decode packet: %s", err) | |||||
} | |||||
if err := addLDAPDescriptions(packet); err != nil { | |||||
return err | |||||
} | |||||
ber.PrintPacket(packet) | ber.PrintPacket(packet) | ||||
return nil | return nil |
@@ -0,0 +1,104 @@ | |||||
// 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 ( | |||||
"errors" | |||||
"log" | |||||
"gopkg.in/asn1-ber.v1" | |||||
) | |||||
// ModifyDNRequest holds the request to modify a DN | |||||
type ModifyDNRequest struct { | |||||
DN string | |||||
NewRDN string | |||||
DeleteOldRDN bool | |||||
NewSuperior string | |||||
} | |||||
// NewModifyDNRequest creates a new request which can be passed to ModifyDN(). | |||||
// | |||||
// To move an object in the tree, set the "newSup" to the new parent entry DN. Use an | |||||
// empty string for just changing the object's RDN. | |||||
// | |||||
// For moving the object without renaming, the "rdn" must be the first | |||||
// RDN of the given DN. | |||||
// | |||||
// A call like | |||||
// mdnReq := NewModifyDNRequest("uid=someone,dc=example,dc=org", "uid=newname", true, "") | |||||
// will setup the request to just rename uid=someone,dc=example,dc=org to | |||||
// uid=newname,dc=example,dc=org. | |||||
func NewModifyDNRequest(dn string, rdn string, delOld bool, newSup string) *ModifyDNRequest { | |||||
return &ModifyDNRequest{ | |||||
DN: dn, | |||||
NewRDN: rdn, | |||||
DeleteOldRDN: delOld, | |||||
NewSuperior: newSup, | |||||
} | |||||
} | |||||
func (m ModifyDNRequest) encode() *ber.Packet { | |||||
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyDNRequest, nil, "Modify DN Request") | |||||
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.DN, "DN")) | |||||
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.NewRDN, "New RDN")) | |||||
request.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, m.DeleteOldRDN, "Delete old RDN")) | |||||
if m.NewSuperior != "" { | |||||
request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, m.NewSuperior, "New Superior")) | |||||
} | |||||
return request | |||||
} | |||||
// ModifyDN renames the given DN and optionally move to another base (when the "newSup" argument | |||||
// to NewModifyDNRequest() is not ""). | |||||
func (l *Conn) ModifyDN(m *ModifyDNRequest) error { | |||||
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")) | |||||
packet.AppendChild(m.encode()) | |||||
l.Debug.PrintPacket(packet) | |||||
msgCtx, err := l.sendMessage(packet) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
defer l.finishMessage(msgCtx) | |||||
l.Debug.Printf("%d: waiting for response", msgCtx.id) | |||||
packetResponse, ok := <-msgCtx.responses | |||||
if !ok { | |||||
return NewError(ErrorNetwork, errors.New("ldap: channel closed")) | |||||
} | |||||
packet, err = packetResponse.ReadPacket() | |||||
l.Debug.Printf("%d: got response %p", msgCtx.id, packet) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
if l.Debug { | |||||
if err := addLDAPDescriptions(packet); err != nil { | |||||
return err | |||||
} | |||||
ber.PrintPacket(packet) | |||||
} | |||||
if packet.Children[1].Tag == ApplicationModifyDNResponse { | |||||
err := GetLDAPError(packet) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
} else { | |||||
log.Printf("Unexpected Response: %d", packet.Children[1].Tag) | |||||
} | |||||
l.Debug.Printf("%d: returning", msgCtx.id) | |||||
return nil | |||||
} |
@@ -1,7 +1,3 @@ | |||||
// Copyright 2014 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
// | |||||
// File contains Modify functionality | // File contains Modify functionality | ||||
// | // | ||||
// https://tools.ietf.org/html/rfc4511 | // https://tools.ietf.org/html/rfc4511 | ||||
@@ -62,54 +58,56 @@ func (p *PartialAttribute) encode() *ber.Packet { | |||||
return seq | return seq | ||||
} | } | ||||
// Change for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511 | |||||
type Change struct { | |||||
// Operation is the type of change to be made | |||||
Operation uint | |||||
// Modification is the attribute to be modified | |||||
Modification PartialAttribute | |||||
} | |||||
func (c *Change) encode() *ber.Packet { | |||||
change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change") | |||||
change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(c.Operation), "Operation")) | |||||
change.AppendChild(c.Modification.encode()) | |||||
return change | |||||
} | |||||
// ModifyRequest as defined in https://tools.ietf.org/html/rfc4511 | // ModifyRequest as defined in https://tools.ietf.org/html/rfc4511 | ||||
type ModifyRequest struct { | type ModifyRequest struct { | ||||
// DN is the distinguishedName of the directory entry to modify | // DN is the distinguishedName of the directory entry to modify | ||||
DN string | DN string | ||||
// AddAttributes contain the attributes to add | |||||
AddAttributes []PartialAttribute | |||||
// DeleteAttributes contain the attributes to delete | |||||
DeleteAttributes []PartialAttribute | |||||
// ReplaceAttributes contain the attributes to replace | |||||
ReplaceAttributes []PartialAttribute | |||||
// Changes contain the attributes to modify | |||||
Changes []Change | |||||
// Controls hold optional controls to send with the request | |||||
Controls []Control | |||||
} | } | ||||
// Add inserts the given attribute to the list of attributes to add | |||||
// Add appends the given attribute to the list of changes to be made | |||||
func (m *ModifyRequest) Add(attrType string, attrVals []string) { | func (m *ModifyRequest) Add(attrType string, attrVals []string) { | ||||
m.AddAttributes = append(m.AddAttributes, PartialAttribute{Type: attrType, Vals: attrVals}) | |||||
m.appendChange(AddAttribute, attrType, attrVals) | |||||
} | } | ||||
// Delete inserts the given attribute to the list of attributes to delete | |||||
// Delete appends the given attribute to the list of changes to be made | |||||
func (m *ModifyRequest) Delete(attrType string, attrVals []string) { | func (m *ModifyRequest) Delete(attrType string, attrVals []string) { | ||||
m.DeleteAttributes = append(m.DeleteAttributes, PartialAttribute{Type: attrType, Vals: attrVals}) | |||||
m.appendChange(DeleteAttribute, attrType, attrVals) | |||||
} | } | ||||
// Replace inserts the given attribute to the list of attributes to replace | |||||
// Replace appends the given attribute to the list of changes to be made | |||||
func (m *ModifyRequest) Replace(attrType string, attrVals []string) { | func (m *ModifyRequest) Replace(attrType string, attrVals []string) { | ||||
m.ReplaceAttributes = append(m.ReplaceAttributes, PartialAttribute{Type: attrType, Vals: attrVals}) | |||||
m.appendChange(ReplaceAttribute, attrType, attrVals) | |||||
} | |||||
func (m *ModifyRequest) appendChange(operation uint, attrType string, attrVals []string) { | |||||
m.Changes = append(m.Changes, Change{operation, PartialAttribute{Type: attrType, Vals: attrVals}}) | |||||
} | } | ||||
func (m ModifyRequest) encode() *ber.Packet { | func (m ModifyRequest) encode() *ber.Packet { | ||||
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request") | request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request") | ||||
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.DN, "DN")) | request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.DN, "DN")) | ||||
changes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Changes") | changes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Changes") | ||||
for _, attribute := range m.AddAttributes { | |||||
change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change") | |||||
change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(AddAttribute), "Operation")) | |||||
change.AppendChild(attribute.encode()) | |||||
changes.AppendChild(change) | |||||
} | |||||
for _, attribute := range m.DeleteAttributes { | |||||
change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change") | |||||
change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(DeleteAttribute), "Operation")) | |||||
change.AppendChild(attribute.encode()) | |||||
changes.AppendChild(change) | |||||
} | |||||
for _, attribute := range m.ReplaceAttributes { | |||||
change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change") | |||||
change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(ReplaceAttribute), "Operation")) | |||||
change.AppendChild(attribute.encode()) | |||||
changes.AppendChild(change) | |||||
for _, change := range m.Changes { | |||||
changes.AppendChild(change.encode()) | |||||
} | } | ||||
request.AppendChild(changes) | request.AppendChild(changes) | ||||
return request | return request | ||||
@@ -118,9 +116,11 @@ func (m ModifyRequest) encode() *ber.Packet { | |||||
// NewModifyRequest creates a modify request for the given DN | // NewModifyRequest creates a modify request for the given DN | ||||
func NewModifyRequest( | func NewModifyRequest( | ||||
dn string, | dn string, | ||||
controls []Control, | |||||
) *ModifyRequest { | ) *ModifyRequest { | ||||
return &ModifyRequest{ | return &ModifyRequest{ | ||||
DN: dn, | |||||
DN: dn, | |||||
Controls: controls, | |||||
} | } | ||||
} | } | ||||
@@ -129,6 +129,9 @@ func (l *Conn) Modify(modifyRequest *ModifyRequest) error { | |||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") | 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")) | packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID")) | ||||
packet.AppendChild(modifyRequest.encode()) | packet.AppendChild(modifyRequest.encode()) | ||||
if len(modifyRequest.Controls) > 0 { | |||||
packet.AppendChild(encodeControls(modifyRequest.Controls)) | |||||
} | |||||
l.Debug.PrintPacket(packet) | l.Debug.PrintPacket(packet) | ||||
@@ -157,9 +160,9 @@ func (l *Conn) Modify(modifyRequest *ModifyRequest) error { | |||||
} | } | ||||
if packet.Children[1].Tag == ApplicationModifyResponse { | if packet.Children[1].Tag == ApplicationModifyResponse { | ||||
resultCode, resultDescription := getLDAPResultCode(packet) | |||||
if resultCode != 0 { | |||||
return NewError(resultCode, errors.New(resultDescription)) | |||||
err := GetLDAPError(packet) | |||||
if err != nil { | |||||
return err | |||||
} | } | ||||
} else { | } else { | ||||
log.Printf("Unexpected Response: %d", packet.Children[1].Tag) | log.Printf("Unexpected Response: %d", packet.Children[1].Tag) |
@@ -32,6 +32,8 @@ type PasswordModifyRequest struct { | |||||
type PasswordModifyResult struct { | type PasswordModifyResult struct { | ||||
// GeneratedPassword holds a password generated by the server, if present | // GeneratedPassword holds a password generated by the server, if present | ||||
GeneratedPassword string | GeneratedPassword string | ||||
// Referral are the returned referral | |||||
Referral string | |||||
} | } | ||||
func (r *PasswordModifyRequest) encode() (*ber.Packet, error) { | func (r *PasswordModifyRequest) encode() (*ber.Packet, error) { | ||||
@@ -124,12 +126,19 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa | |||||
} | } | ||||
if packet.Children[1].Tag == ApplicationExtendedResponse { | if packet.Children[1].Tag == ApplicationExtendedResponse { | ||||
resultCode, resultDescription := getLDAPResultCode(packet) | |||||
if resultCode != 0 { | |||||
return nil, NewError(resultCode, errors.New(resultDescription)) | |||||
err := GetLDAPError(packet) | |||||
if err != nil { | |||||
if IsErrorWithCode(err, LDAPResultReferral) { | |||||
for _, child := range packet.Children[1].Children { | |||||
if child.Tag == 3 { | |||||
result.Referral = child.Children[0].Value.(string) | |||||
} | |||||
} | |||||
} | |||||
return result, err | |||||
} | } | ||||
} else { | } else { | ||||
return nil, NewError(ErrorUnexpectedResponse, fmt.Errorf("Unexpected Response: %d", packet.Children[1].Tag)) | |||||
return nil, NewError(ErrorUnexpectedResponse, fmt.Errorf("unexpected Response: %d", packet.Children[1].Tag)) | |||||
} | } | ||||
extendedResponse := packet.Children[1] | extendedResponse := packet.Children[1] |
@@ -1,7 +1,3 @@ | |||||
// Copyright 2011 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
// | |||||
// File contains Search functionality | // File contains Search functionality | ||||
// | // | ||||
// https://tools.ietf.org/html/rfc4511 | // https://tools.ietf.org/html/rfc4511 | ||||
@@ -313,10 +309,10 @@ func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) | |||||
} else { | } else { | ||||
castControl, ok := control.(*ControlPaging) | castControl, ok := control.(*ControlPaging) | ||||
if !ok { | if !ok { | ||||
return nil, fmt.Errorf("Expected paging control to be of type *ControlPaging, got %v", control) | |||||
return nil, fmt.Errorf("expected paging control to be of type *ControlPaging, got %v", control) | |||||
} | } | ||||
if castControl.PagingSize != pagingSize { | if castControl.PagingSize != pagingSize { | ||||
return nil, fmt.Errorf("Paging size given in search request (%d) conflicts with size given in search call (%d)", castControl.PagingSize, pagingSize) | |||||
return nil, fmt.Errorf("paging size given in search request (%d) conflicts with size given in search call (%d)", castControl.PagingSize, pagingSize) | |||||
} | } | ||||
pagingControl = castControl | pagingControl = castControl | ||||
} | } | ||||
@@ -379,7 +375,7 @@ func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) { | |||||
} | } | ||||
packet.AppendChild(encodedSearchRequest) | packet.AppendChild(encodedSearchRequest) | ||||
// encode search controls | // encode search controls | ||||
if searchRequest.Controls != nil { | |||||
if len(searchRequest.Controls) > 0 { | |||||
packet.AppendChild(encodeControls(searchRequest.Controls)) | packet.AppendChild(encodeControls(searchRequest.Controls)) | ||||
} | } | ||||
@@ -431,13 +427,17 @@ func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) { | |||||
} | } | ||||
result.Entries = append(result.Entries, entry) | result.Entries = append(result.Entries, entry) | ||||
case 5: | case 5: | ||||
resultCode, resultDescription := getLDAPResultCode(packet) | |||||
if resultCode != 0 { | |||||
return result, NewError(resultCode, errors.New(resultDescription)) | |||||
err := GetLDAPError(packet) | |||||
if err != nil { | |||||
return nil, err | |||||
} | } | ||||
if len(packet.Children) == 3 { | if len(packet.Children) == 3 { | ||||
for _, child := range packet.Children[2].Children { | for _, child := range packet.Children[2].Children { | ||||
result.Controls = append(result.Controls, DecodeControl(child)) | |||||
decodedChild, err := DecodeControl(child) | |||||
if err != nil { | |||||
return nil, fmt.Errorf("failed to decode child control: %s", err) | |||||
} | |||||
result.Controls = append(result.Controls, decodedChild) | |||||
} | } | ||||
} | } | ||||
foundSearchResultDone = true | foundSearchResultDone = true |