Skip to content

feat: enable DirSync control in search operation #436

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ type Client interface {

Search(*SearchRequest) (*SearchResult, error)
SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
DirSync(searchRequest *SearchRequest, flags, maxAttrCount int64, cookie []byte) (*SearchResult, error)
}
92 changes: 92 additions & 0 deletions control.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ const (
ControlTypeMicrosoftShowDeleted = "1.2.840.113556.1.4.417"
// ControlTypeMicrosoftServerLinkTTL - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f4f523a8-abc0-4b3a-a471-6b2fef135481?redirectedfrom=MSDN
ControlTypeMicrosoftServerLinkTTL = "1.2.840.113556.1.4.2309"
// 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
Expand All @@ -40,6 +50,7 @@ var ControlTypeMap = map[string]string{
ControlTypeMicrosoftNotification: "Change Notification - Microsoft",
ControlTypeMicrosoftShowDeleted: "Show Deleted Objects - Microsoft",
ControlTypeMicrosoftServerLinkTTL: "Return TTL-DNs for link values with associated expiry times - Microsoft",
ControlTypeDirSync: "DirSync",
}

// Control defines an interface controls provide to encode and describe themselves
Expand Down Expand Up @@ -490,6 +501,31 @@ func DecodeControl(packet *ber.Packet) (Control, error) {
return NewControlMicrosoftServerLinkTTL(), nil
case ControlTypeSubtreeDelete:
return NewControlSubtreeDelete(), nil
case ControlTypeDirSync:
value.Description += " (DirSync)"
c := new(ControlDirSync)
if value.Value != nil {
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
if err != nil {
return nil, err
}
value.Data.Truncate(0)
value.Value = nil
value.AppendChild(valueChildren)
}
value = value.Children[0]
if len(value.Children) != 3 { // also on initial creation, Cookie is an empty string
return nil, fmt.Errorf("invalid number of children in dirSync control")
}
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[1].Value.(int64)
c.Cookie = value.Children[2].Data.Bytes()
value.Children[2].Value = c.Cookie
return c, nil
default:
c := new(ControlString)
c.ControlType = ControlType
Expand Down Expand Up @@ -560,3 +596,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
}
9 changes: 9 additions & 0 deletions control_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,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 {
Expand Down Expand Up @@ -116,6 +121,10 @@ func TestDescribeControlString(t *testing.T) {
runAddControlDescriptions(t, NewControlString("x", false, ""), "Control Type ()")
}

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 {
Expand Down
55 changes: 55 additions & 0 deletions examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"log"
"time"
)

// This example demonstrates how to bind a connection to an ldap user
Expand Down Expand Up @@ -344,6 +345,60 @@ func ExampleControlPaging_manualPaging() {
}
}

// This example demonstrates how to use DirSync to manually execute a
// DirSync search request
func ExampleConn_DirSync() {
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",
})
if err != nil {
log.Fatalf("failed to bind: %s", err)
}

req := &SearchRequest{
BaseDN: `DC=example,DC=org`,
Filter: `(&(objectClass=person)(!(objectClass=computer)))`,
Attributes: []string{"*"},
Scope: ScopeWholeSubtree,
}
// This is the initial sync with all entries matching the filter
doMore := true
var cookie []byte
for doMore {
res, err := conn.DirSync(req, DirSyncObjectSecurity, 1000, cookie)
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
}
cookie = ctrl.(*ControlDirSync).Cookie
}
// 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, cookie)
if err != nil {
log.Fatalf("failed to search: %s", err)
}
for _, entry := range res.Entries {
entry.Print()
}
time.Sleep(15 * time.Second)
}
}

// This example demonstrates how to use EXTERNAL SASL with TLS client certificates.
func ExampleConn_ExternalBind() {
ldapCert := "/path/to/cert.pem"
Expand Down
54 changes: 54 additions & 0 deletions search.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,3 +582,57 @@ func unpackAttributes(children []*ber.Packet) []*EntryAttribute {

return entries
}

// DirSync does a Search with dirSync Control.
func (l *Conn) DirSync(searchRequest *SearchRequest, flags int64, maxAttrCount int64, cookie []byte) (*SearchResult, error) {
var dirSyncControl *ControlDirSync

control := FindControl(searchRequest.Controls, ControlTypeDirSync)
if control == nil {
dirSyncControl = NewControlDirSync(flags, maxAttrCount, cookie)
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"))
}

searchResult.Entries = append(searchResult.Entries, result.Entries...)
searchResult.Referrals = append(searchResult.Referrals, result.Referrals...)
searchResult.Controls = append(searchResult.Controls, result.Controls...)

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
}
1 change: 1 addition & 0 deletions v3/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ type Client interface {

Search(*SearchRequest) (*SearchResult, error)
SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
DirSync(searchRequest *SearchRequest, flags, maxAttrCount int64, cookie []byte) (*SearchResult, error)
}
92 changes: 92 additions & 0 deletions v3/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ const (
ControlTypeMicrosoftShowDeleted = "1.2.840.113556.1.4.417"
// ControlTypeMicrosoftServerLinkTTL - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f4f523a8-abc0-4b3a-a471-6b2fef135481?redirectedfrom=MSDN
ControlTypeMicrosoftServerLinkTTL = "1.2.840.113556.1.4.2309"
// 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
Expand All @@ -47,6 +57,7 @@ var ControlTypeMap = map[string]string{
ControlTypeMicrosoftServerLinkTTL: "Return TTL-DNs for link values with associated expiry times - Microsoft",
ControlTypeServerSideSorting: "Server Side Sorting Request - LDAP Control Extension for Server Side Sorting of Search Results (RFC2891)",
ControlTypeServerSideSortingResult: "Server Side Sorting Results - LDAP Control Extension for Server Side Sorting of Search Results (RFC2891)",
ControlTypeDirSync: "DirSync",
}

// Control defines an interface controls provide to encode and describe themselves
Expand Down Expand Up @@ -501,6 +512,31 @@ func DecodeControl(packet *ber.Packet) (Control, error) {
return NewControlServerSideSorting(value)
case ControlTypeServerSideSortingResult:
return NewControlServerSideSortingResult(value)
case ControlTypeDirSync:
value.Description += " (DirSync)"
c := new(ControlDirSync)
if value.Value != nil {
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
if err != nil {
return nil, err
}
value.Data.Truncate(0)
value.Value = nil
value.AppendChild(valueChildren)
}
value = value.Children[0]
if len(value.Children) != 3 { // also on initial creation, Cookie is an empty string
return nil, fmt.Errorf("invalid number of children in dirSync control")
}
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[1].Value.(int64)
c.Cookie = value.Children[2].Data.Bytes()
value.Children[2].Value = c.Cookie
return c, nil
default:
c := new(ControlString)
c.ControlType = ControlType
Expand Down Expand Up @@ -572,6 +608,62 @@ 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
}

// ControlServerSideSorting

type SortKey struct {
Expand Down
Loading