From 168a8486061460cc9170f780e7f4f830e26a9163 Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Thu, 27 Apr 2017 16:20:13 +0200 Subject: [PATCH 1/6] add DirSync Control / Search this implements the dirsync control as described in https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx check ExampleDirSync() in search_test.go how to use --- client.go | 1 + control.go | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++ search.go | 61 +++++++++++++++++++++++++++++++++++ search_test.go | 47 +++++++++++++++++++++++++++ 4 files changed, 196 insertions(+) diff --git a/client.go b/client.go index 055b27b5..79e91a5f 100644 --- a/client.go +++ b/client.go @@ -24,4 +24,5 @@ type Client interface { Search(searchRequest *SearchRequest) (*SearchResult, error) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) + DirSync(searchRequest *SearchRequest, flags, maxAttrCount int64) (*SearchResult, error) } diff --git a/control.go b/control.go index 342f325c..e1f02c28 100644 --- a/control.go +++ b/control.go @@ -22,6 +22,16 @@ 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" + // ControlTypeDirSync - Active Directory DirSync - https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx + ControlTypeDirSync = "1.2.840.113556.1.4.841" +) + +// Flags for DirSync control +const ( + DirSyncIncrementalValues int64 = 2147483648 + DirSyncPublicDataOnly int64 = 8192 + DirSyncAncestorsFirstOrder int64 = 2048 + DirSyncObjectSecurity int64 = 1 ) // ControlTypeMap maps controls to text descriptions @@ -29,6 +39,7 @@ var ControlTypeMap = map[string]string{ ControlTypePaging: "Paging", ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft", ControlTypeManageDsaIT: "Manage DSA IT", + ControlTypeDirSync: "DirSync", } // Control defines an interface controls provide to encode and describe themselves @@ -377,6 +388,26 @@ func DecodeControl(packet *ber.Packet) Control { value.Value = c.Expire return c + case ControlTypeDirSync: + value.Description += " (DirSync)" + c := new(ControlDirSync) + if value.Value != nil { + valueChildren := ber.DecodePacket(value.Data.Bytes()) + value.Data.Truncate(0) + value.Value = nil + value.AppendChild(valueChildren) + } + value = value.Children[0] + value.Description = "DirSync Control Value" + value.Children[0].Description = "Flags" + value.Children[1].Description = "MaxAttrCnt" + value.Children[2].Description = "Cookie" + c.Flags = value.Children[0].Value.(int64) + c.MaxAttrCnt = value.Children[0].Value.(int64) + c.Cookie = value.Children[2].Data.Bytes() + value.Children[2].Value = c.Cookie + return c + default: c := new(ControlString) c.ControlType = ControlType @@ -418,3 +449,59 @@ func encodeControls(controls []Control) *ber.Packet { } return packet } + +// ControlDirSync implements the control described in https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx +type ControlDirSync struct { + Flags int64 + MaxAttrCnt int64 + Cookie []byte +} + +// NewControlDirSync returns a dir sync control +func NewControlDirSync(flags int64, maxAttrCount int64, cookie []byte) *ControlDirSync { + return &ControlDirSync{ + Flags: flags, + MaxAttrCnt: maxAttrCount, + Cookie: cookie, + } +} + +// GetControlType returns the OID +func (c *ControlDirSync) GetControlType() string { + return ControlTypeDirSync +} + +// String returns a human-readable description +func (c *ControlDirSync) String() string { + return fmt.Sprintf("ControlType: %s (%q), Criticality: true, ControlValue: Flags: %d, MaxAttrCnt: %d", ControlTypeMap[ControlTypeDirSync], ControlTypeDirSync, c.Flags, c.MaxAttrCnt) +} + +// Encode returns the ber packet representation +func (c *ControlDirSync) Encode() *ber.Packet { + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") + packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeDirSync, "Control Type ("+ControlTypeMap[ControlTypeDirSync]+")")) + packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, true, "Criticality")) // must be true always + + val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (DirSync)") + + seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "DirSync Control Value") + seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.Flags), "Flags")) + seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.MaxAttrCnt), "MaxAttrCount")) + + cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Cookie") + if len(c.Cookie) != 0 { + cookie.Value = c.Cookie + cookie.Data.Write(c.Cookie) + } + seq.AppendChild(cookie) + + val.AppendChild(seq) + + packet.AppendChild(val) + return packet +} + +// SetCookie stores the given cookie in the dirSync control +func (c *ControlDirSync) SetCookie(cookie []byte) { + c.Cookie = cookie +} diff --git a/search.go b/search.go index 2a99894c..8dafced9 100644 --- a/search.go +++ b/search.go @@ -448,3 +448,64 @@ func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) { l.Debug.Printf("%d: returning", msgCtx.id) return result, nil } + +// DirSync does a Search with dirSync Control. +func (l *Conn) DirSync(searchRequest *SearchRequest, flags int64, maxAttrCount int64) (*SearchResult, error) { + var dirSyncControl *ControlDirSync + + control := FindControl(searchRequest.Controls, ControlTypeDirSync) + if control == nil { + dirSyncControl = NewControlDirSync(flags, maxAttrCount, nil) + searchRequest.Controls = append(searchRequest.Controls, dirSyncControl) + } else { + castControl, ok := control.(*ControlDirSync) + if !ok { + return nil, fmt.Errorf("Expected DirSync control to be of type *ControlDirSync, got %v", control) + } + if castControl.Flags != flags { + return nil, fmt.Errorf("flags given in search request (%d) conflicts with flags given in search call (%d)", castControl.Flags, flags) + } + if castControl.MaxAttrCnt != maxAttrCount { + return nil, fmt.Errorf("MaxAttrCnt given in search request (%d) conflicts with maxAttrCount given in search call (%d)", castControl.MaxAttrCnt, maxAttrCount) + } + dirSyncControl = castControl + } + + searchResult := new(SearchResult) + result, err := l.Search(searchRequest) + l.Debug.Printf("Looking for result...") + if err != nil { + return searchResult, err + } + if result == nil { + return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received")) + } + + for _, entry := range result.Entries { + searchResult.Entries = append(searchResult.Entries, entry) + } + for _, referral := range result.Referrals { + searchResult.Referrals = append(searchResult.Referrals, referral) + } + for _, control := range result.Controls { + searchResult.Controls = append(searchResult.Controls, control) + } + + l.Debug.Printf("Looking for DirSync Control...") + dirSyncResult := FindControl(result.Controls, ControlTypeDirSync) + if dirSyncResult == nil { + dirSyncControl = nil + l.Debug.Printf("Could not find dirSyncControl control. Breaking...") + return searchResult, nil + } + + cookie := dirSyncResult.(*ControlDirSync).Cookie + if len(cookie) == 0 { + dirSyncControl = nil + l.Debug.Printf("Could not find cookie. Breaking...") + return searchResult, nil + } + dirSyncControl.SetCookie(cookie) + + return searchResult, nil +} diff --git a/search_test.go b/search_test.go index efb8147d..8641688f 100644 --- a/search_test.go +++ b/search_test.go @@ -1,8 +1,10 @@ package ldap import ( + "log" "reflect" "testing" + "time" ) // TestNewEntry tests that repeated calls to NewEntry return the same value with the same input @@ -29,3 +31,48 @@ func TestNewEntry(t *testing.T) { iteration = iteration + 1 } } + +func ExampleDirSync() { + conn, err := Dial("tcp", "ad.example.org:389") + if err != nil { + log.Fatalf("Failed to connect: %s\n", err) + } + defer conn.Close() + + _, err = conn.SimpleBind(&SimpleBindRequest{ + Username: "cn=Some User,ou=people,dc=example,dc=org", + Password: "MySecretPass", + }) + + req := &SearchRequest{ + BaseDN: `DC=example,DC=org`, + Filter: `(&(objectClass=person)(!(objectClass=computer)))`, + Attributes: []string{"*"}, + Scope: ScopeWholeSubtree, + } + doMore := true + for doMore { + res, err := conn.DirSync(req, DirSyncObjectSecurity, 1000) + if err != nil { + log.Fatalf("failed to search: %s", err) + } + for _, entry := range res.Entries { + entry.Print() + } + ctrl := FindControl(res.Controls, ControlTypeDirSync) + if ctrl == nil || ctrl.(*ControlDirSync).Flags == 0 { + doMore = false + } + } + for { + res, err := conn.DirSync(req, DirSyncObjectSecurity, 1000) + if err != nil { + log.Fatalf("failed to search: %s", err) + } + for _, entry := range res.Entries { + entry.Print() + } + time.Sleep(15 * time.Second) + } + +} From f9f1bf2850f9f5440d4a54853f1e0e02cd0e7c2d Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Tue, 2 May 2017 10:58:15 +0200 Subject: [PATCH 2/6] add encode/decode tests for DirSync --- control.go | 5 ++++- control_test.go | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/control.go b/control.go index e1f02c28..c8484fcb 100644 --- a/control.go +++ b/control.go @@ -398,12 +398,15 @@ func DecodeControl(packet *ber.Packet) Control { value.AppendChild(valueChildren) } value = value.Children[0] + if len(value.Children) != 3 { // also on initial creation, Cookie is an empty string + return nil + } value.Description = "DirSync Control Value" value.Children[0].Description = "Flags" value.Children[1].Description = "MaxAttrCnt" value.Children[2].Description = "Cookie" c.Flags = value.Children[0].Value.(int64) - c.MaxAttrCnt = value.Children[0].Value.(int64) + c.MaxAttrCnt = value.Children[1].Value.(int64) c.Cookie = value.Children[2].Data.Bytes() value.Children[2].Value = c.Cookie return c diff --git a/control_test.go b/control_test.go index 11527463..fa463a2a 100644 --- a/control_test.go +++ b/control_test.go @@ -27,6 +27,11 @@ func TestControlString(t *testing.T) { runControlTest(t, NewControlString("x", false, "")) } +func TestControlDirSync(t *testing.T) { + runControlTest(t, NewControlDirSync(DirSyncObjectSecurity, 1000, nil)) + runControlTest(t, NewControlDirSync(DirSyncObjectSecurity, 1000, []byte("I'm a cookie!"))) +} + func runControlTest(t *testing.T, originalControl Control) { header := "" if callerpc, _, line, ok := runtime.Caller(1); ok { @@ -74,6 +79,10 @@ func TestDescribeControlString(t *testing.T) { runAddControlDescriptions(t, NewControlString("x", false, ""), "Control Type ()", "Control Value") } +func TestDescribeControlDirSync(t *testing.T) { + runAddControlDescriptions(t, NewControlDirSync(DirSyncObjectSecurity, 1000, nil), "Control Type (DirSync)", "Criticality", "Control Value") +} + func runAddControlDescriptions(t *testing.T, originalControl Control, childDescriptions ...string) { header := "" if callerpc, _, line, ok := runtime.Caller(1); ok { From 930a2571897c5420d3fd7b0351cc2c9d5f0aab3f Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Tue, 2 May 2017 11:54:31 +0200 Subject: [PATCH 3/6] use ber.DecodePacketErr instead of ber.DecodePacket --- control.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/control.go b/control.go index c8484fcb..f4d3561f 100644 --- a/control.go +++ b/control.go @@ -317,7 +317,10 @@ func DecodeControl(packet *ber.Packet) Control { 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 + } value.Data.Truncate(0) value.Value = nil value.AppendChild(valueChildren) @@ -334,7 +337,10 @@ func DecodeControl(packet *ber.Packet) Control { 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 + } value.Data.Truncate(0) value.Value = nil value.AppendChild(valueChildren) @@ -392,7 +398,10 @@ func DecodeControl(packet *ber.Packet) Control { value.Description += " (DirSync)" c := new(ControlDirSync) if value.Value != nil { - valueChildren := ber.DecodePacket(value.Data.Bytes()) + valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) + if err != nil { + return nil + } value.Data.Truncate(0) value.Value = nil value.AppendChild(valueChildren) From dfca44e02f1bbcd0186f977804d90c86515f27f9 Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Tue, 2 May 2017 12:55:33 +0200 Subject: [PATCH 4/6] fix race w/ *Conn.requestTimeout see https://travis-ci.org/go-ldap/ldap/jobs/227891683 --- conn.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/conn.go b/conn.go index e701a9b6..701b6bd1 100644 --- a/conn.go +++ b/conn.go @@ -96,6 +96,7 @@ type Conn struct { once sync.Once outstandingRequests uint messageMutex sync.Mutex + timeoutMutex sync.Mutex requestTimeout time.Duration } @@ -193,7 +194,9 @@ func (l *Conn) Close() { // SetTimeout sets the time after a request is sent that a MessageTimeout triggers func (l *Conn) SetTimeout(timeout time.Duration) { if timeout > 0 { + l.timeoutMutex.Lock() l.requestTimeout = timeout + l.timeoutMutex.Unlock() } } @@ -388,14 +391,17 @@ func (l *Conn) processMessages() { l.messageContexts[message.MessageID] = message.Context // Add timeout if defined - if l.requestTimeout > 0 { + l.timeoutMutex.Lock() + to := l.requestTimeout + l.timeoutMutex.Unlock() + if to > 0 { go func() { defer func() { if err := recover(); err != nil { log.Printf("ldap: recovered panic in RequestTimeout: %v", err) } }() - time.Sleep(l.requestTimeout) + time.Sleep(to) timeoutMessage := &messagePacket{ Op: MessageTimeout, MessageID: message.MessageID, From bb7a9ca6e4fbc2129e3db588a34bc970ffe811a9 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Wed, 22 Nov 2017 23:56:18 -0500 Subject: [PATCH 5/6] Merge pull request #134 from judwhite/feature/fix-race (#141) fix race conditions in conn.go --- .travis.yml | 6 ++++-- Makefile | 2 +- conn.go | 50 +++++++++++++++++++++---------------------------- conn_test.go | 4 ++-- debug.go | 2 +- dn.go | 4 ++-- error_test.go | 4 ++-- example_test.go | 10 +++++----- ldap.go | 2 +- passwdmodify.go | 8 ++++---- search_test.go | 6 +++--- 11 files changed, 46 insertions(+), 52 deletions(-) diff --git a/.travis.yml b/.travis.yml index e32a2aa7..9782c9ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: go env: global: - - VET_VERSIONS="1.6 1.7 tip" - - LINT_VERSIONS="1.6 1.7 tip" + - VET_VERSIONS="1.6 1.7 1.8 1.9 tip" + - LINT_VERSIONS="1.6 1.7 1.8 1.9 tip" go: - 1.2 - 1.3 @@ -10,6 +10,8 @@ go: - 1.5 - 1.6 - 1.7 + - 1.8 + - 1.9 - tip matrix: fast_finish: true diff --git a/Makefile b/Makefile index f7899f59..a9d351c7 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ IS_OLD_GO := $(shell test $(GO_VERSION) -le 2 && echo true) ifeq ($(IS_OLD_GO),true) RACE_FLAG := else - RACE_FLAG := -race + RACE_FLAG := -race -cpu 1,2,4 endif default: fmt vet lint build quicktest diff --git a/conn.go b/conn.go index e701a9b6..eb28eb47 100644 --- a/conn.go +++ b/conn.go @@ -83,20 +83,18 @@ const ( type Conn struct { conn net.Conn isTLS bool - closeCount uint32 + closing uint32 closeErr atomicValue isStartingTLS bool Debug debugging - chanConfirm chan bool + chanConfirm chan struct{} messageContexts map[int64]*messageContext chanMessage chan *messagePacket chanMessageID chan int64 - wgSender sync.WaitGroup wgClose sync.WaitGroup - once sync.Once outstandingRequests uint messageMutex sync.Mutex - requestTimeout time.Duration + requestTimeout int64 } var _ Client = &Conn{} @@ -143,7 +141,7 @@ func DialTLS(network, addr string, config *tls.Config) (*Conn, error) { func NewConn(conn net.Conn, isTLS bool) *Conn { return &Conn{ conn: conn, - chanConfirm: make(chan bool), + chanConfirm: make(chan struct{}), chanMessageID: make(chan int64), chanMessage: make(chan *messagePacket, 10), messageContexts: map[int64]*messageContext{}, @@ -161,20 +159,20 @@ func (l *Conn) Start() { // isClosing returns whether or not we're currently closing. func (l *Conn) isClosing() bool { - return atomic.LoadUint32(&l.closeCount) > 0 + return atomic.LoadUint32(&l.closing) == 1 } // setClosing sets the closing value to true -func (l *Conn) setClosing() { - atomic.AddUint32(&l.closeCount, 1) +func (l *Conn) setClosing() bool { + return atomic.CompareAndSwapUint32(&l.closing, 0, 1) } // Close closes the connection. func (l *Conn) Close() { - l.once.Do(func() { - l.setClosing() - l.wgSender.Wait() + l.messageMutex.Lock() + defer l.messageMutex.Unlock() + if l.setClosing() { l.Debug.Printf("Sending quit message and waiting for confirmation") l.chanMessage <- &messagePacket{Op: MessageQuit} <-l.chanConfirm @@ -182,27 +180,25 @@ func (l *Conn) Close() { l.Debug.Printf("Closing network connection") if err := l.conn.Close(); err != nil { - log.Print(err) + log.Println(err) } l.wgClose.Done() - }) + } l.wgClose.Wait() } // 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 + atomic.StoreInt64(&l.requestTimeout, int64(timeout)) } } // Returns the next available messageID func (l *Conn) nextMessageID() int64 { - if l.chanMessageID != nil { - if messageID, ok := <-l.chanMessageID; ok { - return messageID - } + if messageID, ok := <-l.chanMessageID; ok { + return messageID } return 0 } @@ -327,12 +323,12 @@ func (l *Conn) finishMessage(msgCtx *messageContext) { } func (l *Conn) sendProcessMessage(message *messagePacket) bool { + l.messageMutex.Lock() + defer l.messageMutex.Unlock() if l.isClosing() { return false } - l.wgSender.Add(1) l.chanMessage <- message - l.wgSender.Done() return true } @@ -352,7 +348,6 @@ func (l *Conn) processMessages() { delete(l.messageContexts, messageID) } close(l.chanMessageID) - l.chanConfirm <- true close(l.chanConfirm) }() @@ -361,11 +356,7 @@ func (l *Conn) processMessages() { select { case l.chanMessageID <- messageID: messageID++ - case message, ok := <-l.chanMessage: - if !ok { - l.Debug.Printf("Shutting down - message channel is closed") - return - } + case message := <-l.chanMessage: switch message.Op { case MessageQuit: l.Debug.Printf("Shutting down - quit message received") @@ -388,14 +379,15 @@ func (l *Conn) processMessages() { l.messageContexts[message.MessageID] = message.Context // Add timeout if defined - if l.requestTimeout > 0 { + requestTimeout := time.Duration(atomic.LoadInt64(&l.requestTimeout)) + if requestTimeout > 0 { go func() { defer func() { if err := recover(); err != nil { log.Printf("ldap: recovered panic in RequestTimeout: %v", err) } }() - time.Sleep(l.requestTimeout) + time.Sleep(requestTimeout) timeoutMessage := &messagePacket{ Op: MessageTimeout, MessageID: message.MessageID, diff --git a/conn_test.go b/conn_test.go index 30554d23..488754d1 100644 --- a/conn_test.go +++ b/conn_test.go @@ -188,7 +188,7 @@ func runWithTimeout(t *testing.T, timeout time.Duration, f func()) { } } -// packetTranslatorConn is a helful type which can be used with various tests +// packetTranslatorConn is a helpful type which can be used with various tests // in this package. It implements the net.Conn interface to be used as an // underlying connection for a *ldap.Conn. Most methods are no-ops but the // Read() and Write() methods are able to translate ber-encoded packets for @@ -241,7 +241,7 @@ func (c *packetTranslatorConn) Read(b []byte) (n int, err error) { } // SendResponse writes the given response packet to the response buffer for -// this conection, signalling any goroutine waiting to read a response. +// this connection, signalling any goroutine waiting to read a response. func (c *packetTranslatorConn) SendResponse(packet *ber.Packet) error { c.lock.Lock() defer c.lock.Unlock() diff --git a/debug.go b/debug.go index b8a7ecbf..7279fc25 100644 --- a/debug.go +++ b/debug.go @@ -6,7 +6,7 @@ import ( "gopkg.in/asn1-ber.v1" ) -// debbuging type +// debugging type // - has a Printf method to write the debug output type debugging bool diff --git a/dn.go b/dn.go index 857b2ca7..34e9023a 100644 --- a/dn.go +++ b/dn.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // -// File contains DN parsing functionallity +// File contains DN parsing functionality // // https://tools.ietf.org/html/rfc4514 // @@ -52,7 +52,7 @@ import ( "fmt" "strings" - ber "gopkg.in/asn1-ber.v1" + "gopkg.in/asn1-ber.v1" ) // AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514 diff --git a/error_test.go b/error_test.go index c010ebe3..e456431b 100644 --- a/error_test.go +++ b/error_test.go @@ -49,7 +49,7 @@ func TestConnReadErr(t *testing.T) { // Send the signal after a short amount of time. time.AfterFunc(10*time.Millisecond, func() { conn.signals <- expectedError }) - // This should block until the underlyiny conn gets the error signal + // This should block until the underlying conn gets the error signal // which should bubble up through the reader() goroutine, close the // connection, and _, err := ldapConn.Search(searchReq) @@ -58,7 +58,7 @@ func TestConnReadErr(t *testing.T) { } } -// signalErrConn is a helful type used with TestConnReadErr. It implements the +// signalErrConn is a helpful type used with TestConnReadErr. It implements the // net.Conn interface to be used as a connection for the test. Most methods are // no-ops but the Read() method blocks until it receives a signal which it // returns as an error. diff --git a/example_test.go b/example_test.go index 821189bd..650af0a4 100644 --- a/example_test.go +++ b/example_test.go @@ -9,7 +9,7 @@ import ( ) // ExampleConn_Bind demonstrates how to bind a connection to an ldap user -// allowing access to restricted attrabutes that user has access to +// allowing access to restricted attributes that user has access to func ExampleConn_Bind() { l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) if err != nil { @@ -63,10 +63,10 @@ func ExampleConn_StartTLS() { log.Fatal(err) } - // Opertations via l are now encrypted + // Operations via l are now encrypted } -// ExampleConn_Compare demonstrates how to comapre an attribute with a value +// ExampleConn_Compare demonstrates how to compare an attribute with a value func ExampleConn_Compare() { l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) if err != nil { @@ -215,7 +215,7 @@ func Example_userAuthentication() { log.Fatal(err) } - // Rebind as the read only user for any futher queries + // Rebind as the read only user for any further queries err = l.Bind(bindusername, bindpassword) if err != nil { log.Fatal(err) @@ -240,7 +240,7 @@ func Example_beherappolicy() { if ppolicyControl != nil { ppolicy = ppolicyControl.(*ldap.ControlBeheraPasswordPolicy) } else { - log.Printf("ppolicyControl response not avaliable.\n") + log.Printf("ppolicyControl response not available.\n") } if err != nil { errStr := "ERROR: Cannot bind: " + err.Error() diff --git a/ldap.go b/ldap.go index d27e639d..49692475 100644 --- a/ldap.go +++ b/ldap.go @@ -9,7 +9,7 @@ import ( "io/ioutil" "os" - ber "gopkg.in/asn1-ber.v1" + "gopkg.in/asn1-ber.v1" ) // LDAP Application Codes diff --git a/passwdmodify.go b/passwdmodify.go index 26110ccf..7d8246fd 100644 --- a/passwdmodify.go +++ b/passwdmodify.go @@ -135,10 +135,10 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa extendedResponse := packet.Children[1] for _, child := range extendedResponse.Children { if child.Tag == 11 { - passwordModifyReponseValue := ber.DecodePacket(child.Data.Bytes()) - if len(passwordModifyReponseValue.Children) == 1 { - if passwordModifyReponseValue.Children[0].Tag == 0 { - result.GeneratedPassword = ber.DecodeString(passwordModifyReponseValue.Children[0].Data.Bytes()) + passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes()) + if len(passwordModifyResponseValue.Children) == 1 { + if passwordModifyResponseValue.Children[0].Tag == 0 { + result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes()) } } } diff --git a/search_test.go b/search_test.go index efb8147d..5f77b22e 100644 --- a/search_test.go +++ b/search_test.go @@ -15,7 +15,7 @@ func TestNewEntry(t *testing.T) { "delta": {"value"}, "epsilon": {"value"}, } - exectedEntry := NewEntry(dn, attributes) + executedEntry := NewEntry(dn, attributes) iteration := 0 for { @@ -23,8 +23,8 @@ func TestNewEntry(t *testing.T) { break } testEntry := NewEntry(dn, attributes) - if !reflect.DeepEqual(exectedEntry, testEntry) { - t.Fatalf("consequent calls to NewEntry did not yield the same result:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", exectedEntry, testEntry) + if !reflect.DeepEqual(executedEntry, testEntry) { + t.Fatalf("subsequent calls to NewEntry did not yield the same result:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", executedEntry, testEntry) } iteration = iteration + 1 } From ad451f7ae753ed092117f5ebb6b2201344381d04 Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Wed, 30 May 2018 10:28:55 +0200 Subject: [PATCH 6/6] add some descriptions what is happening in the example --- search_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/search_test.go b/search_test.go index 158edee0..100b7242 100644 --- a/search_test.go +++ b/search_test.go @@ -50,6 +50,7 @@ func ExampleDirSync() { Attributes: []string{"*"}, Scope: ScopeWholeSubtree, } + // This is the initial sync with all entries matching the filter doMore := true for doMore { res, err := conn.DirSync(req, DirSyncObjectSecurity, 1000) @@ -64,6 +65,8 @@ func ExampleDirSync() { doMore = false } } + // We're done with the initial sync. Now pull every 15 seconds for the + // updated entries - note that you get just the changes, not a full entry. for { res, err := conn.DirSync(req, DirSyncObjectSecurity, 1000) if err != nil { @@ -74,5 +77,4 @@ func ExampleDirSync() { } time.Sleep(15 * time.Second) } - }