Skip to content

Commit cdb0754

Browse files
masato-ssoshimokubo
and
shimokubo
authored
feat: enable DirSync control in search operation (#436)
* feat: enable DirSync control in search operation * fix: fixed ineffectual assignment to err * fix: fixed should replace loop with error * fix: not include Cookie in SearchResult struct --------- Co-authored-by: shimokubo <shimokubo@osstech.co.jp>
1 parent 039466e commit cdb0754

10 files changed

+422
-0
lines changed

client.go

+1
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ type Client interface {
3333

3434
Search(*SearchRequest) (*SearchResult, error)
3535
SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
36+
DirSync(searchRequest *SearchRequest, flags, maxAttrCount int64, cookie []byte) (*SearchResult, error)
3637
}

control.go

+92
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ const (
2929
ControlTypeMicrosoftShowDeleted = "1.2.840.113556.1.4.417"
3030
// ControlTypeMicrosoftServerLinkTTL - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f4f523a8-abc0-4b3a-a471-6b2fef135481?redirectedfrom=MSDN
3131
ControlTypeMicrosoftServerLinkTTL = "1.2.840.113556.1.4.2309"
32+
// ControlTypeDirSync - Active Directory DirSync - https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx
33+
ControlTypeDirSync = "1.2.840.113556.1.4.841"
34+
)
35+
36+
// Flags for DirSync control
37+
const (
38+
DirSyncIncrementalValues int64 = 2147483648
39+
DirSyncPublicDataOnly int64 = 8192
40+
DirSyncAncestorsFirstOrder int64 = 2048
41+
DirSyncObjectSecurity int64 = 1
3242
)
3343

3444
// ControlTypeMap maps controls to text descriptions
@@ -40,6 +50,7 @@ var ControlTypeMap = map[string]string{
4050
ControlTypeMicrosoftNotification: "Change Notification - Microsoft",
4151
ControlTypeMicrosoftShowDeleted: "Show Deleted Objects - Microsoft",
4252
ControlTypeMicrosoftServerLinkTTL: "Return TTL-DNs for link values with associated expiry times - Microsoft",
53+
ControlTypeDirSync: "DirSync",
4354
}
4455

4556
// Control defines an interface controls provide to encode and describe themselves
@@ -490,6 +501,31 @@ func DecodeControl(packet *ber.Packet) (Control, error) {
490501
return NewControlMicrosoftServerLinkTTL(), nil
491502
case ControlTypeSubtreeDelete:
492503
return NewControlSubtreeDelete(), nil
504+
case ControlTypeDirSync:
505+
value.Description += " (DirSync)"
506+
c := new(ControlDirSync)
507+
if value.Value != nil {
508+
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
509+
if err != nil {
510+
return nil, err
511+
}
512+
value.Data.Truncate(0)
513+
value.Value = nil
514+
value.AppendChild(valueChildren)
515+
}
516+
value = value.Children[0]
517+
if len(value.Children) != 3 { // also on initial creation, Cookie is an empty string
518+
return nil, fmt.Errorf("invalid number of children in dirSync control")
519+
}
520+
value.Description = "DirSync Control Value"
521+
value.Children[0].Description = "Flags"
522+
value.Children[1].Description = "MaxAttrCnt"
523+
value.Children[2].Description = "Cookie"
524+
c.Flags = value.Children[0].Value.(int64)
525+
c.MaxAttrCnt = value.Children[1].Value.(int64)
526+
c.Cookie = value.Children[2].Data.Bytes()
527+
value.Children[2].Value = c.Cookie
528+
return c, nil
493529
default:
494530
c := new(ControlString)
495531
c.ControlType = ControlType
@@ -560,3 +596,59 @@ func encodeControls(controls []Control) *ber.Packet {
560596
}
561597
return packet
562598
}
599+
600+
// ControlDirSync implements the control described in https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx
601+
type ControlDirSync struct {
602+
Flags int64
603+
MaxAttrCnt int64
604+
Cookie []byte
605+
}
606+
607+
// NewControlDirSync returns a dir sync control
608+
func NewControlDirSync(flags int64, maxAttrCount int64, cookie []byte) *ControlDirSync {
609+
return &ControlDirSync{
610+
Flags: flags,
611+
MaxAttrCnt: maxAttrCount,
612+
Cookie: cookie,
613+
}
614+
}
615+
616+
// GetControlType returns the OID
617+
func (c *ControlDirSync) GetControlType() string {
618+
return ControlTypeDirSync
619+
}
620+
621+
// String returns a human-readable description
622+
func (c *ControlDirSync) String() string {
623+
return fmt.Sprintf("ControlType: %s (%q), Criticality: true, ControlValue: Flags: %d, MaxAttrCnt: %d", ControlTypeMap[ControlTypeDirSync], ControlTypeDirSync, c.Flags, c.MaxAttrCnt)
624+
}
625+
626+
// Encode returns the ber packet representation
627+
func (c *ControlDirSync) Encode() *ber.Packet {
628+
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
629+
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeDirSync, "Control Type ("+ControlTypeMap[ControlTypeDirSync]+")"))
630+
packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, true, "Criticality")) // must be true always
631+
632+
val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (DirSync)")
633+
634+
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "DirSync Control Value")
635+
seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.Flags), "Flags"))
636+
seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.MaxAttrCnt), "MaxAttrCount"))
637+
638+
cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Cookie")
639+
if len(c.Cookie) != 0 {
640+
cookie.Value = c.Cookie
641+
cookie.Data.Write(c.Cookie)
642+
}
643+
seq.AppendChild(cookie)
644+
645+
val.AppendChild(seq)
646+
647+
packet.AppendChild(val)
648+
return packet
649+
}
650+
651+
// SetCookie stores the given cookie in the dirSync control
652+
func (c *ControlDirSync) SetCookie(cookie []byte) {
653+
c.Cookie = cookie
654+
}

control_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ func TestControlString(t *testing.T) {
4343
runControlTest(t, NewControlString("x", false, ""))
4444
}
4545

46+
func TestControlDirSync(t *testing.T) {
47+
runControlTest(t, NewControlDirSync(DirSyncObjectSecurity, 1000, nil))
48+
runControlTest(t, NewControlDirSync(DirSyncObjectSecurity, 1000, []byte("I'm a cookie!")))
49+
}
50+
4651
func runControlTest(t *testing.T, originalControl Control) {
4752
header := ""
4853
if callerpc, _, line, ok := runtime.Caller(1); ok {
@@ -116,6 +121,10 @@ func TestDescribeControlString(t *testing.T) {
116121
runAddControlDescriptions(t, NewControlString("x", false, ""), "Control Type ()")
117122
}
118123

124+
func TestDescribeControlDirSync(t *testing.T) {
125+
runAddControlDescriptions(t, NewControlDirSync(DirSyncObjectSecurity, 1000, nil), "Control Type (DirSync)", "Criticality", "Control Value")
126+
}
127+
119128
func runAddControlDescriptions(t *testing.T, originalControl Control, childDescriptions ...string) {
120129
header := ""
121130
if callerpc, _, line, ok := runtime.Caller(1); ok {

examples_test.go

+55
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io/ioutil"
88
"log"
9+
"time"
910
)
1011

1112
// This example demonstrates how to bind a connection to an ldap user
@@ -344,6 +345,60 @@ func ExampleControlPaging_manualPaging() {
344345
}
345346
}
346347

348+
// This example demonstrates how to use DirSync to manually execute a
349+
// DirSync search request
350+
func ExampleConn_DirSync() {
351+
conn, err := Dial("tcp", "ad.example.org:389")
352+
if err != nil {
353+
log.Fatalf("Failed to connect: %s\n", err)
354+
}
355+
defer conn.Close()
356+
357+
_, err = conn.SimpleBind(&SimpleBindRequest{
358+
Username: "cn=Some User,ou=people,dc=example,dc=org",
359+
Password: "MySecretPass",
360+
})
361+
if err != nil {
362+
log.Fatalf("failed to bind: %s", err)
363+
}
364+
365+
req := &SearchRequest{
366+
BaseDN: `DC=example,DC=org`,
367+
Filter: `(&(objectClass=person)(!(objectClass=computer)))`,
368+
Attributes: []string{"*"},
369+
Scope: ScopeWholeSubtree,
370+
}
371+
// This is the initial sync with all entries matching the filter
372+
doMore := true
373+
var cookie []byte
374+
for doMore {
375+
res, err := conn.DirSync(req, DirSyncObjectSecurity, 1000, cookie)
376+
if err != nil {
377+
log.Fatalf("failed to search: %s", err)
378+
}
379+
for _, entry := range res.Entries {
380+
entry.Print()
381+
}
382+
ctrl := FindControl(res.Controls, ControlTypeDirSync)
383+
if ctrl == nil || ctrl.(*ControlDirSync).Flags == 0 {
384+
doMore = false
385+
}
386+
cookie = ctrl.(*ControlDirSync).Cookie
387+
}
388+
// We're done with the initial sync. Now pull every 15 seconds for the
389+
// updated entries - note that you get just the changes, not a full entry.
390+
for {
391+
res, err := conn.DirSync(req, DirSyncObjectSecurity, 1000, cookie)
392+
if err != nil {
393+
log.Fatalf("failed to search: %s", err)
394+
}
395+
for _, entry := range res.Entries {
396+
entry.Print()
397+
}
398+
time.Sleep(15 * time.Second)
399+
}
400+
}
401+
347402
// This example demonstrates how to use EXTERNAL SASL with TLS client certificates.
348403
func ExampleConn_ExternalBind() {
349404
ldapCert := "/path/to/cert.pem"

search.go

+54
Original file line numberDiff line numberDiff line change
@@ -582,3 +582,57 @@ func unpackAttributes(children []*ber.Packet) []*EntryAttribute {
582582

583583
return entries
584584
}
585+
586+
// DirSync does a Search with dirSync Control.
587+
func (l *Conn) DirSync(searchRequest *SearchRequest, flags int64, maxAttrCount int64, cookie []byte) (*SearchResult, error) {
588+
var dirSyncControl *ControlDirSync
589+
590+
control := FindControl(searchRequest.Controls, ControlTypeDirSync)
591+
if control == nil {
592+
dirSyncControl = NewControlDirSync(flags, maxAttrCount, cookie)
593+
searchRequest.Controls = append(searchRequest.Controls, dirSyncControl)
594+
} else {
595+
castControl, ok := control.(*ControlDirSync)
596+
if !ok {
597+
return nil, fmt.Errorf("Expected DirSync control to be of type *ControlDirSync, got %v", control)
598+
}
599+
if castControl.Flags != flags {
600+
return nil, fmt.Errorf("flags given in search request (%d) conflicts with flags given in search call (%d)", castControl.Flags, flags)
601+
}
602+
if castControl.MaxAttrCnt != maxAttrCount {
603+
return nil, fmt.Errorf("MaxAttrCnt given in search request (%d) conflicts with maxAttrCount given in search call (%d)", castControl.MaxAttrCnt, maxAttrCount)
604+
}
605+
dirSyncControl = castControl
606+
}
607+
searchResult := new(SearchResult)
608+
result, err := l.Search(searchRequest)
609+
l.Debug.Printf("Looking for result...")
610+
if err != nil {
611+
return searchResult, err
612+
}
613+
if result == nil {
614+
return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
615+
}
616+
617+
searchResult.Entries = append(searchResult.Entries, result.Entries...)
618+
searchResult.Referrals = append(searchResult.Referrals, result.Referrals...)
619+
searchResult.Controls = append(searchResult.Controls, result.Controls...)
620+
621+
l.Debug.Printf("Looking for DirSync Control...")
622+
dirSyncResult := FindControl(result.Controls, ControlTypeDirSync)
623+
if dirSyncResult == nil {
624+
dirSyncControl = nil
625+
l.Debug.Printf("Could not find dirSyncControl control. Breaking...")
626+
return searchResult, nil
627+
}
628+
629+
cookie = dirSyncResult.(*ControlDirSync).Cookie
630+
if len(cookie) == 0 {
631+
dirSyncControl = nil
632+
l.Debug.Printf("Could not find cookie. Breaking...")
633+
return searchResult, nil
634+
}
635+
dirSyncControl.SetCookie(cookie)
636+
637+
return searchResult, nil
638+
}

v3/client.go

+1
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ type Client interface {
3333

3434
Search(*SearchRequest) (*SearchResult, error)
3535
SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
36+
DirSync(searchRequest *SearchRequest, flags, maxAttrCount int64, cookie []byte) (*SearchResult, error)
3637
}

v3/control.go

+92
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ const (
3434
ControlTypeMicrosoftShowDeleted = "1.2.840.113556.1.4.417"
3535
// ControlTypeMicrosoftServerLinkTTL - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f4f523a8-abc0-4b3a-a471-6b2fef135481?redirectedfrom=MSDN
3636
ControlTypeMicrosoftServerLinkTTL = "1.2.840.113556.1.4.2309"
37+
// ControlTypeDirSync - Active Directory DirSync - https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx
38+
ControlTypeDirSync = "1.2.840.113556.1.4.841"
39+
)
40+
41+
// Flags for DirSync control
42+
const (
43+
DirSyncIncrementalValues int64 = 2147483648
44+
DirSyncPublicDataOnly int64 = 8192
45+
DirSyncAncestorsFirstOrder int64 = 2048
46+
DirSyncObjectSecurity int64 = 1
3747
)
3848

3949
// ControlTypeMap maps controls to text descriptions
@@ -47,6 +57,7 @@ var ControlTypeMap = map[string]string{
4757
ControlTypeMicrosoftServerLinkTTL: "Return TTL-DNs for link values with associated expiry times - Microsoft",
4858
ControlTypeServerSideSorting: "Server Side Sorting Request - LDAP Control Extension for Server Side Sorting of Search Results (RFC2891)",
4959
ControlTypeServerSideSortingResult: "Server Side Sorting Results - LDAP Control Extension for Server Side Sorting of Search Results (RFC2891)",
60+
ControlTypeDirSync: "DirSync",
5061
}
5162

5263
// Control defines an interface controls provide to encode and describe themselves
@@ -501,6 +512,31 @@ func DecodeControl(packet *ber.Packet) (Control, error) {
501512
return NewControlServerSideSorting(value)
502513
case ControlTypeServerSideSortingResult:
503514
return NewControlServerSideSortingResult(value)
515+
case ControlTypeDirSync:
516+
value.Description += " (DirSync)"
517+
c := new(ControlDirSync)
518+
if value.Value != nil {
519+
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
520+
if err != nil {
521+
return nil, err
522+
}
523+
value.Data.Truncate(0)
524+
value.Value = nil
525+
value.AppendChild(valueChildren)
526+
}
527+
value = value.Children[0]
528+
if len(value.Children) != 3 { // also on initial creation, Cookie is an empty string
529+
return nil, fmt.Errorf("invalid number of children in dirSync control")
530+
}
531+
value.Description = "DirSync Control Value"
532+
value.Children[0].Description = "Flags"
533+
value.Children[1].Description = "MaxAttrCnt"
534+
value.Children[2].Description = "Cookie"
535+
c.Flags = value.Children[0].Value.(int64)
536+
c.MaxAttrCnt = value.Children[1].Value.(int64)
537+
c.Cookie = value.Children[2].Data.Bytes()
538+
value.Children[2].Value = c.Cookie
539+
return c, nil
504540
default:
505541
c := new(ControlString)
506542
c.ControlType = ControlType
@@ -572,6 +608,62 @@ func encodeControls(controls []Control) *ber.Packet {
572608
return packet
573609
}
574610

611+
// ControlDirSync implements the control described in https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx
612+
type ControlDirSync struct {
613+
Flags int64
614+
MaxAttrCnt int64
615+
Cookie []byte
616+
}
617+
618+
// NewControlDirSync returns a dir sync control
619+
func NewControlDirSync(flags int64, maxAttrCount int64, cookie []byte) *ControlDirSync {
620+
return &ControlDirSync{
621+
Flags: flags,
622+
MaxAttrCnt: maxAttrCount,
623+
Cookie: cookie,
624+
}
625+
}
626+
627+
// GetControlType returns the OID
628+
func (c *ControlDirSync) GetControlType() string {
629+
return ControlTypeDirSync
630+
}
631+
632+
// String returns a human-readable description
633+
func (c *ControlDirSync) String() string {
634+
return fmt.Sprintf("ControlType: %s (%q), Criticality: true, ControlValue: Flags: %d, MaxAttrCnt: %d", ControlTypeMap[ControlTypeDirSync], ControlTypeDirSync, c.Flags, c.MaxAttrCnt)
635+
}
636+
637+
// Encode returns the ber packet representation
638+
func (c *ControlDirSync) Encode() *ber.Packet {
639+
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
640+
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeDirSync, "Control Type ("+ControlTypeMap[ControlTypeDirSync]+")"))
641+
packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, true, "Criticality")) // must be true always
642+
643+
val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (DirSync)")
644+
645+
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "DirSync Control Value")
646+
seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.Flags), "Flags"))
647+
seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.MaxAttrCnt), "MaxAttrCount"))
648+
649+
cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Cookie")
650+
if len(c.Cookie) != 0 {
651+
cookie.Value = c.Cookie
652+
cookie.Data.Write(c.Cookie)
653+
}
654+
seq.AppendChild(cookie)
655+
656+
val.AppendChild(seq)
657+
658+
packet.AppendChild(val)
659+
return packet
660+
}
661+
662+
// SetCookie stores the given cookie in the dirSync control
663+
func (c *ControlDirSync) SetCookie(cookie []byte) {
664+
c.Cookie = cookie
665+
}
666+
575667
// ControlServerSideSorting
576668

577669
type SortKey struct {

0 commit comments

Comments
 (0)