From 3d01c7ea6f00d67154395683b7f859a5e16090ec Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Fri, 1 Jul 2016 01:29:56 -0400 Subject: [PATCH] fix govet/golint errors --- README.md | 18 +++---------- add.go | 11 +++++++- bind.go | 8 ++++++ conn.go | 33 +++++++++++++++-------- control.go | 68 +++++++++++++++++++++++++++++++++++++++------- del.go | 7 ++++- dn.go | 21 ++++++++------- error.go | 8 +++++- filter.go | 72 ++++++++++++++++++++++++++++--------------------- ldap.go | 3 +++ modify.go | 20 +++++++++++--- passwdmodify.go | 15 ++++++++--- search.go | 38 ++++++++++++++++++++++---- 13 files changed, 233 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index f49b4d6a..d7c60855 100644 --- a/README.md +++ b/README.md @@ -13,39 +13,27 @@ Import the latest version with: import "gopkg.in/ldap.v2" - ## Required Libraries: - gopkg.in/asn1-ber.v1 -## Working: +## Features: - - Connecting to LDAP server + - Connecting to LDAP server (non-TLS, TLS, STARTTLS) - Binding to LDAP server - Searching for entries - - Compiling string filters to LDAP filters + - Filter Compile / Decompile - Paging Search Results - Modify Requests / Responses - Add Requests / Responses - Delete Requests / Responses - - Better Unicode support ## Examples: - search - modify -## Tests Implemented: - - - Filter Compile / Decompile - -## TODO: - - [x] Add Requests / Responses - - [x] Delete Requests / Responses - - [x] Modify DN Requests / Responses - - [ ] Compare Requests / Responses - - [ ] Implement Tests / Benchmarks diff --git a/add.go b/add.go index 7e00cbcc..0e5f6cdb 100644 --- a/add.go +++ b/add.go @@ -16,8 +16,11 @@ import ( "gopkg.in/asn1-ber.v1" ) +// Attribute represents an LDAP attribute type Attribute struct { + // Type is the name of the LDAP attribute Type string + // Vals are the LDAP attribute values Vals []string } @@ -32,8 +35,11 @@ func (a *Attribute) encode() *ber.Packet { return seq } +// AddRequest represents an LDAP AddRequest operation type AddRequest struct { - DN string + // DN identifies the entry being added + DN string + // Attributes list the attributes of the new entry Attributes []Attribute } @@ -48,10 +54,12 @@ func (a AddRequest) encode() *ber.Packet { return request } +// Attribute adds an attribute with the given type and values func (a *AddRequest) Attribute(attrType string, attrVals []string) { a.Attributes = append(a.Attributes, Attribute{Type: attrType, Vals: attrVals}) } +// NewAddRequest returns an AddRequest for the given DN, with no attributes func NewAddRequest(dn string) *AddRequest { return &AddRequest{ DN: dn, @@ -59,6 +67,7 @@ func NewAddRequest(dn string) *AddRequest { } +// Add performs the given AddRequest 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")) diff --git a/bind.go b/bind.go index e0a7afc5..26b3cc72 100644 --- a/bind.go +++ b/bind.go @@ -10,16 +10,22 @@ import ( "gopkg.in/asn1-ber.v1" ) +// SimpleBindRequest represents a username/password bind operation type SimpleBindRequest struct { + // Username is the name of the Directory object that the client wishes to bind as Username string + // Password is the credentials to bind with Password string + // Controls are optional controls to send with the bind request Controls []Control } +// SimpleBindResult contains the response from the server type SimpleBindResult struct { Controls []Control } +// NewSimpleBindRequest returns a bind request func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest { return &SimpleBindRequest{ Username: username, @@ -39,6 +45,7 @@ func (bindRequest *SimpleBindRequest) encode() *ber.Packet { return request } +// SimpleBind performs the simple bind operation defined in the given request func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, 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")) @@ -90,6 +97,7 @@ func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResu return result, nil } +// Bind performs a bind with the given username and password 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")) diff --git a/conn.go b/conn.go index 538166e7..b5bd99ad 100644 --- a/conn.go +++ b/conn.go @@ -17,18 +17,27 @@ import ( ) const ( - MessageQuit = 0 - MessageRequest = 1 + // MessageQuit causes the processMessages loop to exit + MessageQuit = 0 + // MessageRequest sends a request to the server + MessageRequest = 1 + // MessageResponse receives a response from the server MessageResponse = 2 - MessageFinish = 3 - MessageTimeout = 4 + // MessageFinish indicates the client considers a particular message ID to be finished + MessageFinish = 3 + // MessageTimeout indicates the client-specified timeout for a particular message ID has been reached + MessageTimeout = 4 ) +// PacketResponse contains the packet or error encountered reading a response type PacketResponse struct { + // Packet is the packet read from the server Packet *ber.Packet - Error error + // Error is an error encountered while reading + Error error } +// ReadPacket returns the packet or an error func (pr *PacketResponse) ReadPacket() (*ber.Packet, error) { if (pr == nil) || (pr.Packet == nil && pr.Error == nil) { return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve response")) @@ -37,8 +46,10 @@ func (pr *PacketResponse) ReadPacket() (*ber.Packet, error) { } type messageContext struct { - id int64 - done chan struct{} + id int64 + // close(done) should only be called from finishMessage() + done chan struct{} + // close(responses) should only be called from processMessages(), and only sent to from sendResponse() responses chan *PacketResponse } @@ -140,6 +151,7 @@ func NewConn(conn net.Conn, isTLS bool) *Conn { } } +// Start initializes goroutines to read responses and process messages func (l *Conn) Start() { go l.reader() go l.processMessages() @@ -167,7 +179,7 @@ func (l *Conn) Close() { l.wgClose.Wait() } -// Sets the time after a request is sent that a MessageTimeout triggers +// SetTimeout sets the time after a request is sent that a MessageTimeout triggers func (l *Conn) SetTimeout(timeout time.Duration) { if timeout > 0 { l.requestTimeout = timeout @@ -253,15 +265,14 @@ func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) l.Debug.Printf("flags&startTLS = %d", flags&startTLS) if l.isStartingTLS { l.messageMutex.Unlock() - return nil, NewError(ErrorNetwork, errors.New("ldap: connection is in startls phase.")) + return nil, NewError(ErrorNetwork, errors.New("ldap: connection is in startls phase")) } if flags&startTLS != 0 { if l.outstandingRequests != 0 { l.messageMutex.Unlock() return nil, NewError(ErrorNetwork, errors.New("ldap: cannot StartTLS with outstanding requests")) - } else { - l.isStartingTLS = true } + l.isStartingTLS = true } l.outstandingRequests++ diff --git a/control.go b/control.go index 4d829809..4bd84c99 100644 --- a/control.go +++ b/control.go @@ -12,35 +12,48 @@ import ( ) const ( - ControlTypePaging = "1.2.840.113556.1.4.319" - ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1" + // ControlTypePaging - https://www.ietf.org/rfc/rfc2696.txt + ControlTypePaging = "1.2.840.113556.1.4.319" + // ControlTypeBeheraPasswordPolicy - https://tools.ietf.org/html/draft-behera-ldap-password-policy-10 + ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1" + // ControlTypeVChuPasswordMustChange - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00 ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4" - ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5" - ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2" + // ControlTypeVChuPasswordWarning - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00 + ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5" + // ControlTypeManageDsaIT - https://tools.ietf.org/html/rfc3296 + ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2" ) +// ControlTypeMap maps controls to text descriptions var ControlTypeMap = map[string]string{ ControlTypePaging: "Paging", ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft", ControlTypeManageDsaIT: "Manage DSA IT", } +// Control defines an interface controls provide to encode and describe themselves type Control interface { + // GetControlType returns the OID GetControlType() string + // Encode returns the ber packet representation Encode() *ber.Packet + // String returns a human-readable description String() string } +// ControlString implements the Control interface for simple controls type ControlString struct { ControlType string Criticality bool ControlValue string } +// GetControlType returns the OID func (c *ControlString) GetControlType() string { return c.ControlType } +// Encode returns the ber packet representation func (c *ControlString) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.ControlType, "Control Type ("+ControlTypeMap[c.ControlType]+")")) @@ -51,19 +64,25 @@ func (c *ControlString) Encode() *ber.Packet { return packet } +// String returns a human-readable description func (c *ControlString) String() string { return fmt.Sprintf("Control Type: %s (%q) Criticality: %t Control Value: %s", ControlTypeMap[c.ControlType], c.ControlType, c.Criticality, c.ControlValue) } +// ControlPaging implements the paging control described in https://www.ietf.org/rfc/rfc2696.txt type ControlPaging struct { + // PagingSize indicates the page size PagingSize uint32 - Cookie []byte + // Cookie is an opaque value returned by the server to track a paging cursor + Cookie []byte } +// GetControlType returns the OID func (c *ControlPaging) GetControlType() string { return ControlTypePaging } +// Encode returns the ber packet representation func (c *ControlPaging) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypePaging, "Control Type ("+ControlTypeMap[ControlTypePaging]+")")) @@ -81,6 +100,7 @@ func (c *ControlPaging) Encode() *ber.Packet { return packet } +// String returns a human-readable description func (c *ControlPaging) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t PagingSize: %d Cookie: %q", @@ -91,21 +111,29 @@ func (c *ControlPaging) String() string { c.Cookie) } +// SetCookie stores the given cookie in the paging control func (c *ControlPaging) SetCookie(cookie []byte) { c.Cookie = cookie } +// ControlBeheraPasswordPolicy implements the control described in https://tools.ietf.org/html/draft-behera-ldap-password-policy-10 type ControlBeheraPasswordPolicy struct { - Expire int64 - Grace int64 - Error int8 + // Expire contains the number of seconds before a password will expire + Expire int64 + // Grace indicates the remaining number of times a user will be allowed to authenticate with an expired password + Grace int64 + // Error indicates the error code + Error int8 + // ErrorString is a human readable error ErrorString string } +// GetControlType returns the OID func (c *ControlBeheraPasswordPolicy) GetControlType() string { return ControlTypeBeheraPasswordPolicy } +// Encode returns the ber packet representation func (c *ControlBeheraPasswordPolicy) Encode() *ber.Packet { packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeBeheraPasswordPolicy, "Control Type ("+ControlTypeMap[ControlTypeBeheraPasswordPolicy]+")")) @@ -113,6 +141,7 @@ func (c *ControlBeheraPasswordPolicy) Encode() *ber.Packet { return packet } +// String returns a human-readable description func (c *ControlBeheraPasswordPolicy) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t Expire: %d Grace: %d Error: %d, ErrorString: %s", @@ -125,39 +154,49 @@ func (c *ControlBeheraPasswordPolicy) String() string { c.ErrorString) } +// ControlVChuPasswordMustChange implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00 type ControlVChuPasswordMustChange struct { + // MustChange indicates if the password is required to be changed MustChange bool } +// GetControlType returns the OID func (c *ControlVChuPasswordMustChange) GetControlType() string { return ControlTypeVChuPasswordMustChange } +// Encode returns the ber packet representation func (c *ControlVChuPasswordMustChange) Encode() *ber.Packet { return nil } +// String returns a human-readable description func (c *ControlVChuPasswordMustChange) String() string { return fmt.Sprintf( - "Control Type: %s (%q) Criticality: %t MustChange: %b", + "Control Type: %s (%q) Criticality: %t MustChange: %v", ControlTypeMap[ControlTypeVChuPasswordMustChange], ControlTypeVChuPasswordMustChange, false, c.MustChange) } +// ControlVChuPasswordWarning implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00 type ControlVChuPasswordWarning struct { + // Expire indicates the time in seconds until the password expires Expire int64 } +// GetControlType returns the OID func (c *ControlVChuPasswordWarning) GetControlType() string { return ControlTypeVChuPasswordWarning } +// Encode returns the ber packet representation func (c *ControlVChuPasswordWarning) Encode() *ber.Packet { return nil } +// String returns a human-readable description func (c *ControlVChuPasswordWarning) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t Expire: %b", @@ -167,14 +206,18 @@ func (c *ControlVChuPasswordWarning) String() string { c.Expire) } +// ControlManageDsaIT implements the control described in https://tools.ietf.org/html/rfc3296 type ControlManageDsaIT struct { + // Criticality indicates if this control is required Criticality bool } +// GetControlType returns the OID func (c *ControlManageDsaIT) GetControlType() string { return ControlTypeManageDsaIT } +// Encode returns the ber packet representation func (c *ControlManageDsaIT) Encode() *ber.Packet { //FIXME packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") @@ -185,6 +228,7 @@ func (c *ControlManageDsaIT) Encode() *ber.Packet { return packet } +// String returns a human-readable description func (c *ControlManageDsaIT) String() string { return fmt.Sprintf( "Control Type: %s (%q) Criticality: %t", @@ -193,10 +237,12 @@ func (c *ControlManageDsaIT) String() string { c.Criticality) } +// NewControlManageDsaIT returns a ControlManageDsaIT control func NewControlManageDsaIT(Criticality bool) *ControlManageDsaIT { return &ControlManageDsaIT{Criticality: Criticality} } +// FindControl returns the first control of the given type in the list, or nil func FindControl(controls []Control, controlType string) Control { for _, c := range controls { if c.GetControlType() == controlType { @@ -206,6 +252,7 @@ func FindControl(controls []Control, controlType string) Control { return nil } +// DecodeControl returns a control read from the given packet, or nil if no recognized control can be made func DecodeControl(packet *ber.Packet) Control { ControlType := packet.Children[0].Value.(string) Criticality := false @@ -303,6 +350,7 @@ func DecodeControl(packet *ber.Packet) Control { return c } +// NewControlString returns a generic control func NewControlString(controlType string, criticality bool, controlValue string) *ControlString { return &ControlString{ ControlType: controlType, @@ -311,10 +359,12 @@ func NewControlString(controlType string, criticality bool, controlValue string) } } +// NewControlPaging returns a paging control func NewControlPaging(pagingSize uint32) *ControlPaging { return &ControlPaging{PagingSize: pagingSize} } +// NewControlBeheraPasswordPolicy returns a ControlBeheraPasswordPolicy func NewControlBeheraPasswordPolicy() *ControlBeheraPasswordPolicy { return &ControlBeheraPasswordPolicy{ Expire: -1, diff --git a/del.go b/del.go index 81cbf8e1..4fd63dc3 100644 --- a/del.go +++ b/del.go @@ -12,8 +12,11 @@ import ( "gopkg.in/asn1-ber.v1" ) +// DelRequest implements an LDAP deletion request type DelRequest struct { - DN string + // DN is the name of the directory entry to delete + DN string + // Controls hold optional controls to send with the request Controls []Control } @@ -23,6 +26,7 @@ func (d DelRequest) encode() *ber.Packet { return request } +// NewDelRequest creates a delete request for the given DN and controls func NewDelRequest(DN string, Controls []Control) *DelRequest { return &DelRequest{ @@ -31,6 +35,7 @@ func NewDelRequest(DN string, } } +// Del executes the given delete request 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")) diff --git a/dn.go b/dn.go index 5d83c5e9..cc70c894 100644 --- a/dn.go +++ b/dn.go @@ -55,19 +55,25 @@ import ( ber "gopkg.in/asn1-ber.v1" ) +// AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514 type AttributeTypeAndValue struct { - Type string + // Type is the attribute type + Type string + // Value is the attribute value Value string } +// RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514 type RelativeDN struct { Attributes []*AttributeTypeAndValue } +// DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514 type DN struct { RDNs []*RelativeDN } +// ParseDN returns a distinguishedName or an error func ParseDN(str string) (*DN, error) { dn := new(DN) dn.RDNs = make([]*RelativeDN, 0) @@ -94,11 +100,9 @@ func ParseDN(str string) (*DN, error) { dst := []byte{0} n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2])) if err != nil { - return nil, errors.New( - fmt.Sprintf("Failed to decode escaped character: %s", err)) + return nil, fmt.Errorf("Failed to decode escaped character: %s", err) } else if n != 1 { - return nil, errors.New( - fmt.Sprintf("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++ @@ -119,12 +123,11 @@ func ParseDN(str string) (*DN, error) { } else { data = str[i:] } - raw_ber, err := enchex.DecodeString(data) + rawBER, err := enchex.DecodeString(data) if err != nil { - return nil, errors.New( - fmt.Sprintf("Failed to decode BER encoding: %s", err)) + return nil, fmt.Errorf("Failed to decode BER encoding: %s", err) } - packet := ber.DecodePacket(raw_ber) + packet := ber.DecodePacket(rawBER) buffer.WriteString(packet.Data.String()) i += len(data) - 1 } diff --git a/error.go b/error.go index 97404eb6..ff697873 100644 --- a/error.go +++ b/error.go @@ -56,6 +56,7 @@ const ( ErrorUnexpectedResponse = 205 ) +// LDAPResultCodeMap contains string descriptions for LDAP error codes var LDAPResultCodeMap = map[uint8]string{ LDAPResultSuccess: "Success", LDAPResultOperationsError: "Operations Error", @@ -115,8 +116,11 @@ func getLDAPResultCode(packet *ber.Packet) (code uint8, description string) { return ErrorNetwork, "Invalid packet format" } +// Error holds LDAP error information type Error struct { - Err error + // Err is the underlying error + Err error + // ResultCode is the LDAP error code ResultCode uint8 } @@ -124,10 +128,12 @@ 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 diff --git a/filter.go b/filter.go index 63bcec1e..7eae310f 100644 --- a/filter.go +++ b/filter.go @@ -15,6 +15,7 @@ import ( "gopkg.in/asn1-ber.v1" ) +// Filter choices const ( FilterAnd = 0 FilterOr = 1 @@ -28,6 +29,7 @@ const ( FilterExtensibleMatch = 9 ) +// FilterMap contains human readable descriptions of Filter choices var FilterMap = map[uint64]string{ FilterAnd: "And", FilterOr: "Or", @@ -41,18 +43,21 @@ var FilterMap = map[uint64]string{ FilterExtensibleMatch: "Extensible Match", } +// SubstringFilter options const ( FilterSubstringsInitial = 0 FilterSubstringsAny = 1 FilterSubstringsFinal = 2 ) +// FilterSubstringsMap contains human readable descriptions of SubstringFilter choices var FilterSubstringsMap = map[uint64]string{ FilterSubstringsInitial: "Substrings Initial", FilterSubstringsAny: "Substrings Any", FilterSubstringsFinal: "Substrings Final", } +// MatchingRuleAssertion choices const ( MatchingRuleAssertionMatchingRule = 1 MatchingRuleAssertionType = 2 @@ -60,6 +65,7 @@ const ( MatchingRuleAssertionDNAttributes = 4 ) +// MatchingRuleAssertionMap contains human readable descriptions of MatchingRuleAssertion choices var MatchingRuleAssertionMap = map[uint64]string{ MatchingRuleAssertionMatchingRule: "Matching Rule Assertion Matching Rule", MatchingRuleAssertionType: "Matching Rule Assertion Type", @@ -67,6 +73,7 @@ var MatchingRuleAssertionMap = map[uint64]string{ MatchingRuleAssertionDNAttributes: "Matching Rule Assertion DN Attributes", } +// CompileFilter converts a string representation of a filter into a BER-encoded packet func CompileFilter(filter string) (*ber.Packet, error) { if len(filter) == 0 || filter[0] != '(' { return nil, NewError(ErrorFilterCompile, errors.New("ldap: filter does not start with an '('")) @@ -81,6 +88,7 @@ func CompileFilter(filter string) (*ber.Packet, error) { return packet, nil } +// DecompileFilter converts a packet representation of a filter into a string representation func DecompileFilter(packet *ber.Packet) (ret string, err error) { defer func() { if r := recover(); r != nil { @@ -239,11 +247,13 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) { packet.AppendChild(child) return packet, newPos, err default: - READING_ATTR := 0 - READING_EXTENSIBLE_MATCHING_RULE := 1 - READING_CONDITION := 2 + const ( + stateReadingAttr = 0 + stateReadingExtensibleMatchingRule = 1 + stateReadingCondition = 2 + ) - state := READING_ATTR + state := stateReadingAttr attribute := "" extensibleDNAttributes := false @@ -261,56 +271,56 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) { } switch state { - case READING_ATTR: + case stateReadingAttr: switch { // Extensible rule, with only DN-matching case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) extensibleDNAttributes = true - state = READING_CONDITION + state = stateReadingCondition newPos += 5 // Extensible rule, with DN-matching and a matching OID case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:"): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) extensibleDNAttributes = true - state = READING_EXTENSIBLE_MATCHING_RULE + state = stateReadingExtensibleMatchingRule newPos += 4 // Extensible rule, with attr only case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) - state = READING_CONDITION + state = stateReadingCondition newPos += 2 // Extensible rule, with no DN attribute matching case currentRune == ':': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) - state = READING_EXTENSIBLE_MATCHING_RULE - newPos += 1 + state = stateReadingExtensibleMatchingRule + newPos++ // Equality condition case currentRune == '=': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch]) - state = READING_CONDITION - newPos += 1 + state = stateReadingCondition + newPos++ // Greater-than or equal case currentRune == '>' && strings.HasPrefix(remainingFilter, ">="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual]) - state = READING_CONDITION + state = stateReadingCondition newPos += 2 // Less-than or equal case currentRune == '<' && strings.HasPrefix(remainingFilter, "<="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual]) - state = READING_CONDITION + state = stateReadingCondition newPos += 2 // Approx case currentRune == '~' && strings.HasPrefix(remainingFilter, "~="): packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterApproxMatch]) - state = READING_CONDITION + state = stateReadingCondition newPos += 2 // Still reading the attribute name @@ -319,12 +329,12 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) { newPos += currentWidth } - case READING_EXTENSIBLE_MATCHING_RULE: + case stateReadingExtensibleMatchingRule: switch { // Matching rule OID is done case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="): - state = READING_CONDITION + state = stateReadingCondition newPos += 2 // Still reading the matching rule oid @@ -333,7 +343,7 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) { newPos += currentWidth } - case READING_CONDITION: + case stateReadingCondition: // append to the condition condition += fmt.Sprintf("%c", currentRune) newPos += currentWidth @@ -369,9 +379,9 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) { } // Add the value (only required child) - encodedString, err := escapedStringToEncodedBytes(condition) - if err != nil { - return packet, newPos, err + encodedString, encodeErr := escapedStringToEncodedBytes(condition) + if encodeErr != nil { + return packet, newPos, encodeErr } packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchValue, encodedString, MatchingRuleAssertionMap[MatchingRuleAssertionMatchValue])) @@ -401,17 +411,17 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) { default: tag = FilterSubstringsAny } - encodedString, err := escapedStringToEncodedBytes(part) - if err != nil { - return packet, newPos, err + encodedString, encodeErr := escapedStringToEncodedBytes(part) + if encodeErr != nil { + return packet, newPos, encodeErr } seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, tag, encodedString, FilterSubstringsMap[uint64(tag)])) } packet.AppendChild(seq) default: - encodedString, err := escapedStringToEncodedBytes(condition) - if err != nil { - return packet, newPos, err + encodedString, encodeErr := escapedStringToEncodedBytes(condition) + if encodeErr != nil { + return packet, newPos, encodeErr } packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute")) packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, encodedString, "Condition")) @@ -440,12 +450,12 @@ func escapedStringToEncodedBytes(escapedString string) (string, error) { if i+2 > len(escapedString) { return "", NewError(ErrorFilterCompile, errors.New("ldap: missing characters for escape in filter")) } - if escByte, decodeErr := hexpac.DecodeString(escapedString[i+1 : i+3]); decodeErr != nil { + escByte, decodeErr := hexpac.DecodeString(escapedString[i+1 : i+3]) + if decodeErr != nil { return "", NewError(ErrorFilterCompile, errors.New("ldap: invalid characters for escape in filter")) - } else { - buffer.WriteByte(escByte[0]) - i += 2 // +1 from end of loop, so 3 total for \xx. } + buffer.WriteByte(escByte[0]) + i += 2 // +1 from end of loop, so 3 total for \xx. } else { buffer.WriteRune(currentRune) } diff --git a/ldap.go b/ldap.go index 1620aaea..90018be8 100644 --- a/ldap.go +++ b/ldap.go @@ -36,6 +36,7 @@ const ( ApplicationExtendedResponse = 24 ) +// ApplicationMap contains human readable descriptions of LDAP Application Codes var ApplicationMap = map[uint8]string{ ApplicationBindRequest: "Bind Request", ApplicationBindResponse: "Bind Response", @@ -72,6 +73,7 @@ const ( BeheraPasswordInHistory = 8 ) +// BeheraPasswordPolicyErrorMap contains human readable descriptions of Behera Password Policy error codes var BeheraPasswordPolicyErrorMap = map[int8]string{ BeheraPasswordExpired: "Password expired", BeheraAccountLocked: "Account locked", @@ -237,6 +239,7 @@ func addDefaultLDAPResponseDescriptions(packet *ber.Packet) { } } +// DebugBinaryFile reads and prints packets from the given filename func DebugBinaryFile(fileName string) error { file, err := ioutil.ReadFile(fileName) if err != nil { diff --git a/modify.go b/modify.go index 7549f7dd..e4ab6cef 100644 --- a/modify.go +++ b/modify.go @@ -36,14 +36,18 @@ import ( "gopkg.in/asn1-ber.v1" ) +// Change operation choices const ( AddAttribute = 0 DeleteAttribute = 1 ReplaceAttribute = 2 ) +// PartialAttribute for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511 type PartialAttribute struct { + // Type is the type of the partial attribute Type string + // Vals are the values of the partial attribute Vals []string } @@ -58,21 +62,29 @@ func (p *PartialAttribute) encode() *ber.Packet { return seq } +// ModifyRequest as defined in https://tools.ietf.org/html/rfc4511 type ModifyRequest struct { - DN string - AddAttributes []PartialAttribute - DeleteAttributes []PartialAttribute + // 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 } +// Add inserts the given attribute to the list of attributes to add func (m *ModifyRequest) Add(attrType string, attrVals []string) { m.AddAttributes = append(m.AddAttributes, PartialAttribute{Type: attrType, Vals: attrVals}) } +// Delete inserts the given attribute to the list of attributes to delete func (m *ModifyRequest) Delete(attrType string, attrVals []string) { m.DeleteAttributes = append(m.DeleteAttributes, PartialAttribute{Type: attrType, Vals: attrVals}) } +// Replace inserts the given attribute to the list of attributes to replace func (m *ModifyRequest) Replace(attrType string, attrVals []string) { m.ReplaceAttributes = append(m.ReplaceAttributes, PartialAttribute{Type: attrType, Vals: attrVals}) } @@ -103,6 +115,7 @@ func (m ModifyRequest) encode() *ber.Packet { return request } +// NewModifyRequest creates a modify request for the given DN func NewModifyRequest( dn string, ) *ModifyRequest { @@ -111,6 +124,7 @@ func NewModifyRequest( } } +// Modify performs the ModifyRequest 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")) diff --git a/passwdmodify.go b/passwdmodify.go index 4a358513..26110ccf 100644 --- a/passwdmodify.go +++ b/passwdmodify.go @@ -16,13 +16,21 @@ const ( passwordModifyOID = "1.3.6.1.4.1.4203.1.11.1" ) +// PasswordModifyRequest implements the Password Modify Extended Operation as defined in https://www.ietf.org/rfc/rfc3062.txt type PasswordModifyRequest struct { + // UserIdentity is an optional string representation of the user associated with the request. + // This string may or may not be an LDAPDN [RFC2253]. + // If no UserIdentity field is present, the request acts up upon the password of the user currently associated with the LDAP session UserIdentity string - OldPassword string - NewPassword string + // OldPassword, if present, contains the user's current password + OldPassword string + // NewPassword, if present, contains the desired password for this user + NewPassword string } +// PasswordModifyResult holds the server response to a PasswordModifyRequest type PasswordModifyResult struct { + // GeneratedPassword holds a password generated by the server, if present GeneratedPassword string } @@ -47,7 +55,7 @@ func (r *PasswordModifyRequest) encode() (*ber.Packet, error) { return request, nil } -// Create a new PasswordModifyRequest +// NewPasswordModifyRequest creates a new PasswordModifyRequest // // According to the RFC 3602: // userIdentity is a string representing the user associated with the request. @@ -72,6 +80,7 @@ func NewPasswordModifyRequest(userIdentity string, oldPassword string, newPasswo } } +// PasswordModify performs the modification request func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, 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")) diff --git a/search.go b/search.go index 21623ea5..2a99894c 100644 --- a/search.go +++ b/search.go @@ -68,18 +68,21 @@ import ( "gopkg.in/asn1-ber.v1" ) +// scope choices const ( ScopeBaseObject = 0 ScopeSingleLevel = 1 ScopeWholeSubtree = 2 ) +// ScopeMap contains human readable descriptions of scope choices var ScopeMap = map[int]string{ ScopeBaseObject: "Base Object", ScopeSingleLevel: "Single Level", ScopeWholeSubtree: "Whole Subtree", } +// derefAliases const ( NeverDerefAliases = 0 DerefInSearching = 1 @@ -87,6 +90,7 @@ const ( DerefAlways = 3 ) +// DerefMap contains human readable descriptions of derefAliases choices var DerefMap = map[int]string{ NeverDerefAliases: "NeverDerefAliases", DerefInSearching: "DerefInSearching", @@ -114,11 +118,15 @@ func NewEntry(dn string, attributes map[string][]string) *Entry { } } +// Entry represents a single search result entry type Entry struct { - DN string + // DN is the distinguished name of the entry + DN string + // Attributes are the returned attributes for the entry Attributes []*EntryAttribute } +// GetAttributeValues returns the values for the named attribute, or an empty list func (e *Entry) GetAttributeValues(attribute string) []string { for _, attr := range e.Attributes { if attr.Name == attribute { @@ -128,6 +136,7 @@ func (e *Entry) GetAttributeValues(attribute string) []string { return []string{} } +// GetRawAttributeValues returns the byte values for the named attribute, or an empty list func (e *Entry) GetRawAttributeValues(attribute string) [][]byte { for _, attr := range e.Attributes { if attr.Name == attribute { @@ -137,6 +146,7 @@ func (e *Entry) GetRawAttributeValues(attribute string) [][]byte { return [][]byte{} } +// GetAttributeValue returns the first value for the named attribute, or "" func (e *Entry) GetAttributeValue(attribute string) string { values := e.GetAttributeValues(attribute) if len(values) == 0 { @@ -145,6 +155,7 @@ func (e *Entry) GetAttributeValue(attribute string) string { return values[0] } +// GetRawAttributeValue returns the first value for the named attribute, or an empty slice func (e *Entry) GetRawAttributeValue(attribute string) []byte { values := e.GetRawAttributeValues(attribute) if len(values) == 0 { @@ -153,6 +164,7 @@ func (e *Entry) GetRawAttributeValue(attribute string) []byte { return values[0] } +// Print outputs a human-readable description func (e *Entry) Print() { fmt.Printf("DN: %s\n", e.DN) for _, attr := range e.Attributes { @@ -160,6 +172,7 @@ func (e *Entry) Print() { } } +// PrettyPrint outputs a human-readable description indenting func (e *Entry) PrettyPrint(indent int) { fmt.Printf("%sDN: %s\n", strings.Repeat(" ", indent), e.DN) for _, attr := range e.Attributes { @@ -180,38 +193,51 @@ func NewEntryAttribute(name string, values []string) *EntryAttribute { } } +// EntryAttribute holds a single attribute type EntryAttribute struct { - Name string - Values []string + // Name is the name of the attribute + Name string + // Values contain the string values of the attribute + Values []string + // ByteValues contain the raw values of the attribute ByteValues [][]byte } +// Print outputs a human-readable description func (e *EntryAttribute) Print() { fmt.Printf("%s: %s\n", e.Name, e.Values) } +// PrettyPrint outputs a human-readable description with indenting func (e *EntryAttribute) PrettyPrint(indent int) { fmt.Printf("%s%s: %s\n", strings.Repeat(" ", indent), e.Name, e.Values) } +// SearchResult holds the server's response to a search request type SearchResult struct { - Entries []*Entry + // Entries are the returned entries + Entries []*Entry + // Referrals are the returned referrals Referrals []string - Controls []Control + // Controls are the returned controls + Controls []Control } +// Print outputs a human-readable description func (s *SearchResult) Print() { for _, entry := range s.Entries { entry.Print() } } +// PrettyPrint outputs a human-readable description with indenting func (s *SearchResult) PrettyPrint(indent int) { for _, entry := range s.Entries { entry.PrettyPrint(indent) } } +// SearchRequest represents a search request to send to the server type SearchRequest struct { BaseDN string Scope int @@ -247,6 +273,7 @@ func (s *SearchRequest) encode() (*ber.Packet, error) { return request, nil } +// NewSearchRequest creates a new search request func NewSearchRequest( BaseDN string, Scope, DerefAliases, SizeLimit, TimeLimit int, @@ -341,6 +368,7 @@ func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) return searchResult, nil } +// Search performs the given search request func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, 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"))