Signed-off-by: Andrew Thornton <art27@cantab.net>tags/v1.9.0-dev
| @@ -1094,12 +1094,12 @@ | |||
| version = "v1.31.1" | |||
| [[projects]] | |||
| digest = "1:7e1c00b9959544fa1ccca7cf0407a5b29ac6d5201059c4fac6f599cb99bfd24d" | |||
| name = "gopkg.in/ldap.v2" | |||
| digest = "1:8a502dedecf5b6d56e36f0d0e6196392baf616634af2c23108b6e8bb89ec57fc" | |||
| name = "gopkg.in/ldap.v3" | |||
| packages = ["."] | |||
| pruneopts = "NUT" | |||
| revision = "bb7a9ca6e4fbc2129e3db588a34bc970ffe811a9" | |||
| version = "v2.5.1" | |||
| revision = "214f299a0ecb2a6c6f6d2b0f13977032b207dc58" | |||
| version = "v3.0.1" | |||
| [[projects]] | |||
| digest = "1:de2e7294c9bd0b7d07ada8e98ad02cbbaabacff90eedebe7454ebdbab50d0d19" | |||
| @@ -1309,7 +1309,7 @@ | |||
| "gopkg.in/editorconfig/editorconfig-core-go.v1", | |||
| "gopkg.in/gomail.v2", | |||
| "gopkg.in/ini.v1", | |||
| "gopkg.in/ldap.v2", | |||
| "gopkg.in/ldap.v3", | |||
| "gopkg.in/macaron.v1", | |||
| "gopkg.in/testfixtures.v2", | |||
| "strk.kbt.io/projects/go/libravatar", | |||
| @@ -95,8 +95,8 @@ ignored = ["google.golang.org/appengine*"] | |||
| version = "1.31.1" | |||
| [[constraint]] | |||
| name = "gopkg.in/ldap.v2" | |||
| version = "2.4.1" | |||
| name = "gopkg.in/ldap.v3" | |||
| version = "3.0.1" | |||
| [[constraint]] | |||
| name = "gopkg.in/macaron.v1" | |||
| @@ -11,9 +11,9 @@ import ( | |||
| "fmt" | |||
| "strings" | |||
| "gopkg.in/ldap.v2" | |||
| "code.gitea.io/gitea/modules/log" | |||
| ldap "gopkg.in/ldap.v3" | |||
| ) | |||
| // 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 | |||
| // Attributes list the attributes of the new entry | |||
| Attributes []Attribute | |||
| // Controls hold optional controls to send with the request | |||
| Controls []Control | |||
| } | |||
| 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 | |||
| func NewAddRequest(dn string) *AddRequest { | |||
| func NewAddRequest(dn string, controls []Control) *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.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID")) | |||
| packet.AppendChild(addRequest.encode()) | |||
| if len(addRequest.Controls) > 0 { | |||
| packet.AppendChild(encodeControls(addRequest.Controls)) | |||
| } | |||
| l.Debug.PrintPacket(packet) | |||
| @@ -100,9 +106,9 @@ func (l *Conn) Add(addRequest *AddRequest) error { | |||
| } | |||
| 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 { | |||
| 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 | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| "gopkg.in/asn1-ber.v1" | |||
| ) | |||
| @@ -18,6 +15,9 @@ type SimpleBindRequest struct { | |||
| Password string | |||
| // Controls are optional controls to send with the bind request | |||
| Controls []Control | |||
| // AllowEmptyPassword sets whether the client allows binding with an empty password | |||
| // (normally used for unauthenticated bind). | |||
| AllowEmptyPassword bool | |||
| } | |||
| // SimpleBindResult contains the response from the server | |||
| @@ -28,9 +28,10 @@ type SimpleBindResult struct { | |||
| // NewSimpleBindRequest returns a bind request | |||
| func NewSimpleBindRequest(username string, password string, controls []Control) *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.ClassContext, ber.TypePrimitive, 0, bindRequest.Password, "Password")) | |||
| request.AppendChild(encodeControls(bindRequest.Controls)) | |||
| return request | |||
| } | |||
| // SimpleBind performs the simple bind operation defined in the given request | |||
| func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) { | |||
| if simpleBindRequest.Password == "" && !simpleBindRequest.AllowEmptyPassword { | |||
| return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client")) | |||
| } | |||
| 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")) | |||
| encodedBindRequest := simpleBindRequest.encode() | |||
| packet.AppendChild(encodedBindRequest) | |||
| if len(simpleBindRequest.Controls) > 0 { | |||
| packet.AppendChild(encodeControls(simpleBindRequest.Controls)) | |||
| } | |||
| if l.Debug { | |||
| ber.PrintPacket(packet) | |||
| @@ -73,7 +79,7 @@ func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResu | |||
| } | |||
| if l.Debug { | |||
| if err := addLDAPDescriptions(packet); err != nil { | |||
| if err = addLDAPDescriptions(packet); err != nil { | |||
| return nil, err | |||
| } | |||
| ber.PrintPacket(packet) | |||
| @@ -85,59 +91,45 @@ func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResu | |||
| if len(packet.Children) == 3 { | |||
| 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 { | |||
| 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 | |||
| Del(delRequest *DelRequest) error | |||
| Modify(modifyRequest *ModifyRequest) error | |||
| ModifyDN(modifyDNRequest *ModifyDNRequest) error | |||
| Compare(dn, attribute, value string) (bool, 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 | |||
| // | |||
| // 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.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) | |||
| packet.AppendChild(request) | |||
| @@ -72,14 +68,16 @@ func (l *Conn) Compare(dn, attribute, value string) (bool, error) { | |||
| } | |||
| if packet.Children[1].Tag == ApplicationCompareResponse { | |||
| resultCode, resultDescription := getLDAPResultCode(packet) | |||
| if resultCode == LDAPResultCompareTrue { | |||
| err := GetLDAPError(packet) | |||
| switch { | |||
| case IsErrorWithCode(err, LDAPResultCompareTrue): | |||
| return true, nil | |||
| } else if resultCode == LDAPResultCompareFalse { | |||
| case IsErrorWithCode(err, LDAPResultCompareFalse): | |||
| 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 | |||
| import ( | |||
| @@ -10,6 +6,7 @@ import ( | |||
| "fmt" | |||
| "log" | |||
| "net" | |||
| "net/url" | |||
| "sync" | |||
| "sync/atomic" | |||
| "time" | |||
| @@ -30,6 +27,13 @@ const ( | |||
| MessageTimeout = 4 | |||
| ) | |||
| const ( | |||
| // DefaultLdapPort default ldap port for pure TCP connection | |||
| DefaultLdapPort = "389" | |||
| // DefaultLdapsPort default ldap port for SSL connection | |||
| DefaultLdapsPort = "636" | |||
| ) | |||
| // PacketResponse contains the packet or error encountered reading a response | |||
| type PacketResponse struct { | |||
| // Packet is the packet read from the server | |||
| @@ -81,10 +85,13 @@ const ( | |||
| // Conn represents an LDAP Connection | |||
| type Conn struct { | |||
| // requestTimeout is loaded atomically | |||
| // so we need to ensure 64-bit alignment on 32-bit platforms. | |||
| requestTimeout int64 | |||
| conn net.Conn | |||
| isTLS bool | |||
| closing uint32 | |||
| closeErr atomicValue | |||
| closeErr atomic.Value | |||
| isStartingTLS bool | |||
| Debug debugging | |||
| chanConfirm chan struct{} | |||
| @@ -94,7 +101,6 @@ type Conn struct { | |||
| wgClose sync.WaitGroup | |||
| outstandingRequests uint | |||
| messageMutex sync.Mutex | |||
| requestTimeout int64 | |||
| } | |||
| 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 | |||
| // and then returns a new Conn for the connection. | |||
| 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 { | |||
| 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.Start() | |||
| return conn, nil | |||
| } | |||
| // DialURL connects to the given ldap URL vie TCP using tls.Dial or net.Dial if ldaps:// | |||
| // or ldap:// specified as protocol. On success a new Conn for the connection | |||
| // is returned. | |||
| func DialURL(addr string) (*Conn, error) { | |||
| lurl, err := url.Parse(addr) | |||
| if err != nil { | |||
| return nil, NewError(ErrorNetwork, err) | |||
| } | |||
| host, port, err := net.SplitHostPort(lurl.Host) | |||
| if err != nil { | |||
| // we asume that error is due to missing port | |||
| host = lurl.Host | |||
| port = "" | |||
| } | |||
| switch lurl.Scheme { | |||
| case "ldap": | |||
| if port == "" { | |||
| port = DefaultLdapPort | |||
| } | |||
| return Dial("tcp", net.JoinHostPort(host, port)) | |||
| case "ldaps": | |||
| if port == "" { | |||
| port = DefaultLdapsPort | |||
| } | |||
| tlsConf := &tls.Config{ | |||
| ServerName: host, | |||
| } | |||
| return DialTLS("tcp", net.JoinHostPort(host, port), tlsConf) | |||
| } | |||
| return nil, NewError(ErrorNetwork, fmt.Errorf("Unknown scheme '%s'", lurl.Scheme)) | |||
| } | |||
| // NewConn returns a new Conn using conn for network I/O. | |||
| func NewConn(conn net.Conn, isTLS bool) *Conn { | |||
| return &Conn{ | |||
| @@ -157,8 +192,8 @@ func (l *Conn) Start() { | |||
| 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 | |||
| } | |||
| @@ -242,30 +277,41 @@ func (l *Conn) StartTLS(config *tls.Config) error { | |||
| ber.PrintPacket(packet) | |||
| } | |||
| if resultCode, message := getLDAPResultCode(packet); resultCode == LDAPResultSuccess { | |||
| if err := GetLDAPError(packet); err == nil { | |||
| conn := tls.Client(l.conn, config) | |||
| if err := conn.Handshake(); err != nil { | |||
| if connErr := conn.Handshake(); connErr != nil { | |||
| 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.conn = conn | |||
| } else { | |||
| return NewError(resultCode, fmt.Errorf("ldap: cannot StartTLS (%s)", message)) | |||
| return err | |||
| } | |||
| go l.reader() | |||
| return nil | |||
| } | |||
| // TLSConnectionState returns the client's TLS connection state. | |||
| // The return values are their zero values if StartTLS did | |||
| // not succeed. | |||
| func (l *Conn) TLSConnectionState() (state tls.ConnectionState, ok bool) { | |||
| tc, ok := l.conn.(*tls.Conn) | |||
| if !ok { | |||
| return | |||
| } | |||
| return tc.ConnectionState(), true | |||
| } | |||
| func (l *Conn) sendMessage(packet *ber.Packet) (*messageContext, error) { | |||
| return l.sendMessageWithFlags(packet, 0) | |||
| } | |||
| func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) (*messageContext, error) { | |||
| if l.isClosing() { | |||
| if l.IsClosing() { | |||
| return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed")) | |||
| } | |||
| l.messageMutex.Lock() | |||
| @@ -304,7 +350,7 @@ func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) | |||
| func (l *Conn) finishMessage(msgCtx *messageContext) { | |||
| close(msgCtx.done) | |||
| if l.isClosing() { | |||
| if l.IsClosing() { | |||
| return | |||
| } | |||
| @@ -325,7 +371,7 @@ func (l *Conn) finishMessage(msgCtx *messageContext) { | |||
| func (l *Conn) sendProcessMessage(message *messagePacket) bool { | |||
| l.messageMutex.Lock() | |||
| defer l.messageMutex.Unlock() | |||
| if l.isClosing() { | |||
| if l.IsClosing() { | |||
| return false | |||
| } | |||
| l.chanMessage <- message | |||
| @@ -340,7 +386,7 @@ func (l *Conn) processMessages() { | |||
| for messageID, msgCtx := range l.messageContexts { | |||
| // If we are closing due to an error, inform anyone who | |||
| // is waiting about the error. | |||
| if l.isClosing() && l.closeErr.Load() != nil { | |||
| if l.IsClosing() && l.closeErr.Load() != nil { | |||
| msgCtx.sendResponse(&PacketResponse{Error: l.closeErr.Load().(error)}) | |||
| } | |||
| 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 { | |||
| msgCtx.sendResponse(&PacketResponse{message.Packet, nil}) | |||
| } 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) | |||
| } | |||
| case MessageTimeout: | |||
| @@ -442,7 +488,7 @@ func (l *Conn) reader() { | |||
| packet, err := ber.ReadPacket(l.conn) | |||
| if err != nil { | |||
| // 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.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 | |||
| import ( | |||
| @@ -22,13 +18,20 @@ const ( | |||
| ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5" | |||
| // ControlTypeManageDsaIT - https://tools.ietf.org/html/rfc3296 | |||
| ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2" | |||
| // ControlTypeMicrosoftNotification - https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx | |||
| ControlTypeMicrosoftNotification = "1.2.840.113556.1.4.528" | |||
| // ControlTypeMicrosoftShowDeleted - https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx | |||
| ControlTypeMicrosoftShowDeleted = "1.2.840.113556.1.4.417" | |||
| ) | |||
| // ControlTypeMap maps controls to text descriptions | |||
| var ControlTypeMap = map[string]string{ | |||
| ControlTypePaging: "Paging", | |||
| ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft", | |||
| ControlTypeManageDsaIT: "Manage DSA IT", | |||
| 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 | |||
| @@ -242,6 +245,64 @@ func NewControlManageDsaIT(Criticality bool) *ControlManageDsaIT { | |||
| return &ControlManageDsaIT{Criticality: Criticality} | |||
| } | |||
| // ControlMicrosoftNotification implements the control described in https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx | |||
| type ControlMicrosoftNotification struct{} | |||
| // GetControlType returns the OID | |||
| func (c *ControlMicrosoftNotification) GetControlType() string { | |||
| return ControlTypeMicrosoftNotification | |||
| } | |||
| // Encode returns the ber packet representation | |||
| func (c *ControlMicrosoftNotification) Encode() *ber.Packet { | |||
| packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") | |||
| packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftNotification, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftNotification]+")")) | |||
| return packet | |||
| } | |||
| // String returns a human-readable description | |||
| func (c *ControlMicrosoftNotification) String() string { | |||
| return fmt.Sprintf( | |||
| "Control Type: %s (%q)", | |||
| ControlTypeMap[ControlTypeMicrosoftNotification], | |||
| ControlTypeMicrosoftNotification) | |||
| } | |||
| // NewControlMicrosoftNotification returns a ControlMicrosoftNotification control | |||
| func NewControlMicrosoftNotification() *ControlMicrosoftNotification { | |||
| return &ControlMicrosoftNotification{} | |||
| } | |||
| // ControlMicrosoftShowDeleted implements the control described in https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx | |||
| type ControlMicrosoftShowDeleted struct{} | |||
| // GetControlType returns the OID | |||
| func (c *ControlMicrosoftShowDeleted) GetControlType() string { | |||
| return ControlTypeMicrosoftShowDeleted | |||
| } | |||
| // Encode returns the ber packet representation | |||
| func (c *ControlMicrosoftShowDeleted) Encode() *ber.Packet { | |||
| packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") | |||
| packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftShowDeleted, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftShowDeleted]+")")) | |||
| return packet | |||
| } | |||
| // String returns a human-readable description | |||
| func (c *ControlMicrosoftShowDeleted) String() string { | |||
| return fmt.Sprintf( | |||
| "Control Type: %s (%q)", | |||
| ControlTypeMap[ControlTypeMicrosoftShowDeleted], | |||
| ControlTypeMicrosoftShowDeleted) | |||
| } | |||
| // NewControlMicrosoftShowDeleted returns a ControlMicrosoftShowDeleted control | |||
| func NewControlMicrosoftShowDeleted() *ControlMicrosoftShowDeleted { | |||
| return &ControlMicrosoftShowDeleted{} | |||
| } | |||
| // FindControl returns the first control of the given type in the list, or nil | |||
| func FindControl(controls []Control, controlType string) Control { | |||
| for _, c := range controls { | |||
| @@ -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 | |||
| func DecodeControl(packet *ber.Packet) Control { | |||
| func DecodeControl(packet *ber.Packet) (Control, error) { | |||
| var ( | |||
| ControlType = "" | |||
| Criticality = false | |||
| @@ -263,7 +324,7 @@ func DecodeControl(packet *ber.Packet) Control { | |||
| switch len(packet.Children) { | |||
| case 0: | |||
| // 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: | |||
| // just type, no criticality or value | |||
| @@ -296,17 +357,20 @@ func DecodeControl(packet *ber.Packet) Control { | |||
| default: | |||
| // more than 3 children is invalid | |||
| return nil | |||
| return nil, fmt.Errorf("more than 3 children is invalid for controls") | |||
| } | |||
| switch ControlType { | |||
| case ControlTypeManageDsaIT: | |||
| return NewControlManageDsaIT(Criticality) | |||
| return NewControlManageDsaIT(Criticality), nil | |||
| case ControlTypePaging: | |||
| value.Description += " (Paging)" | |||
| c := new(ControlPaging) | |||
| 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.Value = nil | |||
| value.AppendChild(valueChildren) | |||
| @@ -318,12 +382,15 @@ func DecodeControl(packet *ber.Packet) Control { | |||
| c.PagingSize = uint32(value.Children[0].Value.(int64)) | |||
| c.Cookie = value.Children[1].Data.Bytes() | |||
| value.Children[1].Value = c.Cookie | |||
| return c | |||
| return c, nil | |||
| case ControlTypeBeheraPasswordPolicy: | |||
| value.Description += " (Password Policy - Behera)" | |||
| c := NewControlBeheraPasswordPolicy() | |||
| 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.Value = nil | |||
| value.AppendChild(valueChildren) | |||
| @@ -335,7 +402,10 @@ func DecodeControl(packet *ber.Packet) Control { | |||
| if child.Tag == 0 { | |||
| //Warning | |||
| 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) | |||
| if ok { | |||
| if warningPacket.Tag == 0 { | |||
| @@ -350,7 +420,10 @@ func DecodeControl(packet *ber.Packet) Control { | |||
| } | |||
| } else if child.Tag == 1 { | |||
| // 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) | |||
| if !ok { | |||
| // what to do? | |||
| @@ -361,22 +434,26 @@ func DecodeControl(packet *ber.Packet) Control { | |||
| c.ErrorString = BeheraPasswordPolicyErrorMap[c.Error] | |||
| } | |||
| } | |||
| return c | |||
| return c, nil | |||
| case ControlTypeVChuPasswordMustChange: | |||
| c := &ControlVChuPasswordMustChange{MustChange: true} | |||
| return c | |||
| return c, nil | |||
| case ControlTypeVChuPasswordWarning: | |||
| c := &ControlVChuPasswordWarning{Expire: -1} | |||
| expireStr := ber.DecodeString(value.Data.Bytes()) | |||
| expire, err := strconv.ParseInt(expireStr, 10, 64) | |||
| if err != nil { | |||
| return nil | |||
| return nil, fmt.Errorf("failed to parse value as int: %s", err) | |||
| } | |||
| c.Expire = expire | |||
| value.Value = c.Expire | |||
| return c | |||
| return c, nil | |||
| case ControlTypeMicrosoftNotification: | |||
| return NewControlMicrosoftNotification(), nil | |||
| case ControlTypeMicrosoftShowDeleted: | |||
| return NewControlMicrosoftShowDeleted(), nil | |||
| default: | |||
| c := new(ControlString) | |||
| c.ControlType = ControlType | |||
| @@ -384,7 +461,7 @@ func DecodeControl(packet *ber.Packet) Control { | |||
| if value != nil { | |||
| 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.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID")) | |||
| packet.AppendChild(delRequest.encode()) | |||
| if delRequest.Controls != nil { | |||
| if len(delRequest.Controls) > 0 { | |||
| packet.AppendChild(encodeControls(delRequest.Controls)) | |||
| } | |||
| @@ -71,9 +71,9 @@ func (l *Conn) Del(delRequest *DelRequest) error { | |||
| } | |||
| 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 { | |||
| 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 | |||
| // | |||
| // https://tools.ietf.org/html/rfc4514 | |||
| @@ -94,7 +90,8 @@ func ParseDN(str string) (*DN, error) { | |||
| for i := 0; i < len(str); i++ { | |||
| char := str[i] | |||
| if escaping { | |||
| switch { | |||
| case escaping: | |||
| unescapedTrailingSpaces = 0 | |||
| escaping = false | |||
| switch char { | |||
| @@ -104,22 +101,22 @@ func ParseDN(str string) (*DN, error) { | |||
| } | |||
| // Not a special character, assume hex encoded octet | |||
| if len(str) == i+1 { | |||
| return nil, errors.New("Got corrupted escaped character") | |||
| return nil, errors.New("got corrupted escaped character") | |||
| } | |||
| dst := []byte{0} | |||
| n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2])) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("Failed to decode escaped character: %s", err) | |||
| return nil, fmt.Errorf("failed to decode escaped character: %s", err) | |||
| } else if n != 1 { | |||
| return nil, fmt.Errorf("Expected 1 byte when un-escaping, got %d", n) | |||
| return nil, fmt.Errorf("expected 1 byte when un-escaping, got %d", n) | |||
| } | |||
| buffer.WriteByte(dst[0]) | |||
| i++ | |||
| } else if char == '\\' { | |||
| case char == '\\': | |||
| unescapedTrailingSpaces = 0 | |||
| escaping = true | |||
| } else if char == '=' { | |||
| case char == '=': | |||
| attribute.Type = stringFromBuffer() | |||
| // Special case: If the first character in the value is # the | |||
| // following data is BER encoded so we can just fast forward | |||
| @@ -135,13 +132,16 @@ func ParseDN(str string) (*DN, error) { | |||
| } | |||
| rawBER, err := enchex.DecodeString(data) | |||
| 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()) | |||
| i += len(data) - 1 | |||
| } | |||
| } else if char == ',' || char == '+' { | |||
| case char == ',' || char == '+': | |||
| // We're done with this RDN or value, push it | |||
| if len(attribute.Type) == 0 { | |||
| return nil, errors.New("incomplete type, value pair") | |||
| @@ -154,10 +154,10 @@ func ParseDN(str string) (*DN, error) { | |||
| rdn = new(RelativeDN) | |||
| rdn.Attributes = make([]*AttributeTypeAndValue, 0) | |||
| } | |||
| } else if char == ' ' && buffer.Len() == 0 { | |||
| case char == ' ' && buffer.Len() == 0: | |||
| // ignore unescaped leading spaces | |||
| continue | |||
| } else { | |||
| default: | |||
| if char == ' ' { | |||
| // Track unescaped spaces in case they are trailing and we need to remove them | |||
| 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 | |||
| 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 | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| "io/ioutil" | |||
| "os" | |||
| @@ -101,13 +98,13 @@ func addLDAPDescriptions(packet *ber.Packet) (err error) { | |||
| switch application { | |||
| case ApplicationBindRequest: | |||
| addRequestDescriptions(packet) | |||
| err = addRequestDescriptions(packet) | |||
| case ApplicationBindResponse: | |||
| addDefaultLDAPResponseDescriptions(packet) | |||
| err = addDefaultLDAPResponseDescriptions(packet) | |||
| case ApplicationUnbindRequest: | |||
| addRequestDescriptions(packet) | |||
| err = addRequestDescriptions(packet) | |||
| case ApplicationSearchRequest: | |||
| addRequestDescriptions(packet) | |||
| err = addRequestDescriptions(packet) | |||
| case ApplicationSearchResultEntry: | |||
| packet.Children[1].Children[0].Description = "Object Name" | |||
| packet.Children[1].Children[1].Description = "Attributes" | |||
| @@ -120,37 +117,37 @@ func addLDAPDescriptions(packet *ber.Packet) (err error) { | |||
| } | |||
| } | |||
| if len(packet.Children) == 3 { | |||
| addControlDescriptions(packet.Children[2]) | |||
| err = addControlDescriptions(packet.Children[2]) | |||
| } | |||
| case ApplicationSearchResultDone: | |||
| addDefaultLDAPResponseDescriptions(packet) | |||
| err = addDefaultLDAPResponseDescriptions(packet) | |||
| case ApplicationModifyRequest: | |||
| addRequestDescriptions(packet) | |||
| err = addRequestDescriptions(packet) | |||
| case ApplicationModifyResponse: | |||
| case ApplicationAddRequest: | |||
| addRequestDescriptions(packet) | |||
| err = addRequestDescriptions(packet) | |||
| case ApplicationAddResponse: | |||
| case ApplicationDelRequest: | |||
| addRequestDescriptions(packet) | |||
| err = addRequestDescriptions(packet) | |||
| case ApplicationDelResponse: | |||
| case ApplicationModifyDNRequest: | |||
| addRequestDescriptions(packet) | |||
| err = addRequestDescriptions(packet) | |||
| case ApplicationModifyDNResponse: | |||
| case ApplicationCompareRequest: | |||
| addRequestDescriptions(packet) | |||
| err = addRequestDescriptions(packet) | |||
| case ApplicationCompareResponse: | |||
| case ApplicationAbandonRequest: | |||
| addRequestDescriptions(packet) | |||
| err = addRequestDescriptions(packet) | |||
| case ApplicationSearchResultReference: | |||
| case ApplicationExtendedRequest: | |||
| addRequestDescriptions(packet) | |||
| err = addRequestDescriptions(packet) | |||
| case ApplicationExtendedResponse: | |||
| } | |||
| return nil | |||
| return err | |||
| } | |||
| func addControlDescriptions(packet *ber.Packet) { | |||
| func addControlDescriptions(packet *ber.Packet) error { | |||
| packet.Description = "Controls" | |||
| for _, child := range packet.Children { | |||
| var value *ber.Packet | |||
| @@ -159,7 +156,7 @@ func addControlDescriptions(packet *ber.Packet) { | |||
| switch len(child.Children) { | |||
| case 0: | |||
| // at least one child is required for control type | |||
| continue | |||
| return fmt.Errorf("at least one child is required for control type") | |||
| case 1: | |||
| // just type, no criticality or value | |||
| @@ -188,8 +185,9 @@ func addControlDescriptions(packet *ber.Packet) { | |||
| default: | |||
| // more than 3 children is invalid | |||
| continue | |||
| return fmt.Errorf("more than 3 children for control packet found") | |||
| } | |||
| if value == nil { | |||
| continue | |||
| } | |||
| @@ -197,7 +195,10 @@ func addControlDescriptions(packet *ber.Packet) { | |||
| case ControlTypePaging: | |||
| value.Description += " (Paging)" | |||
| 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.Value = nil | |||
| valueChildren.Children[1].Value = valueChildren.Children[1].Data.Bytes() | |||
| @@ -210,7 +211,10 @@ func addControlDescriptions(packet *ber.Packet) { | |||
| case ControlTypeBeheraPasswordPolicy: | |||
| value.Description += " (Password Policy - Behera Draft)" | |||
| 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.Value = nil | |||
| value.AppendChild(valueChildren) | |||
| @@ -220,7 +224,10 @@ func addControlDescriptions(packet *ber.Packet) { | |||
| if child.Tag == 0 { | |||
| //Warning | |||
| 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) | |||
| if ok { | |||
| if warningPacket.Tag == 0 { | |||
| @@ -235,7 +242,10 @@ func addControlDescriptions(packet *ber.Packet) { | |||
| } | |||
| } else if child.Tag == 1 { | |||
| // 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) | |||
| if !ok { | |||
| 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.Children[0].Description = "Message ID" | |||
| packet.Children[1].Description = ApplicationMap[uint8(packet.Children[1].Tag)] | |||
| 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" | |||
| if len(packet.Children[1].Children) > 3 { | |||
| packet.Children[1].Children[3].Description = "Referral" | |||
| } | |||
| 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 | |||
| @@ -277,8 +290,13 @@ func DebugBinaryFile(fileName string) error { | |||
| return NewError(ErrorDebugging, err) | |||
| } | |||
| 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) | |||
| 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 | |||
| // | |||
| // https://tools.ietf.org/html/rfc4511 | |||
| @@ -62,54 +58,56 @@ func (p *PartialAttribute) encode() *ber.Packet { | |||
| return seq | |||
| } | |||
| // Change for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511 | |||
| type Change struct { | |||
| // Operation is the type of change to be made | |||
| Operation uint | |||
| // Modification is the attribute to be modified | |||
| Modification PartialAttribute | |||
| } | |||
| func (c *Change) encode() *ber.Packet { | |||
| change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change") | |||
| change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(c.Operation), "Operation")) | |||
| change.AppendChild(c.Modification.encode()) | |||
| return change | |||
| } | |||
| // ModifyRequest as defined in https://tools.ietf.org/html/rfc4511 | |||
| type ModifyRequest struct { | |||
| // DN is the distinguishedName of the directory entry to modify | |||
| DN string | |||
| // 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) { | |||
| 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) { | |||
| 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) { | |||
| 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 { | |||
| request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request") | |||
| request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.DN, "DN")) | |||
| 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) | |||
| return request | |||
| @@ -118,9 +116,11 @@ func (m ModifyRequest) encode() *ber.Packet { | |||
| // NewModifyRequest creates a modify request for the given DN | |||
| func NewModifyRequest( | |||
| dn string, | |||
| controls []Control, | |||
| ) *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.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID")) | |||
| packet.AppendChild(modifyRequest.encode()) | |||
| if len(modifyRequest.Controls) > 0 { | |||
| packet.AppendChild(encodeControls(modifyRequest.Controls)) | |||
| } | |||
| l.Debug.PrintPacket(packet) | |||
| @@ -157,9 +160,9 @@ func (l *Conn) Modify(modifyRequest *ModifyRequest) error { | |||
| } | |||
| 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 { | |||
| log.Printf("Unexpected Response: %d", packet.Children[1].Tag) | |||
| @@ -32,6 +32,8 @@ type PasswordModifyRequest struct { | |||
| type PasswordModifyResult struct { | |||
| // GeneratedPassword holds a password generated by the server, if present | |||
| GeneratedPassword string | |||
| // Referral are the returned referral | |||
| Referral string | |||
| } | |||
| func (r *PasswordModifyRequest) encode() (*ber.Packet, error) { | |||
| @@ -124,12 +126,19 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa | |||
| } | |||
| 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 { | |||
| 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] | |||
| @@ -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 | |||
| // | |||
| // https://tools.ietf.org/html/rfc4511 | |||
| @@ -313,10 +309,10 @@ func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) | |||
| } else { | |||
| castControl, ok := control.(*ControlPaging) | |||
| 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 { | |||
| 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 | |||
| } | |||
| @@ -379,7 +375,7 @@ func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) { | |||
| } | |||
| packet.AppendChild(encodedSearchRequest) | |||
| // encode search controls | |||
| if searchRequest.Controls != nil { | |||
| if len(searchRequest.Controls) > 0 { | |||
| packet.AppendChild(encodeControls(searchRequest.Controls)) | |||
| } | |||
| @@ -431,13 +427,17 @@ func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) { | |||
| } | |||
| result.Entries = append(result.Entries, entry) | |||
| 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 { | |||
| 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 | |||