Skip to content
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

add DirSync Control / Search #110

Closed
wants to merge 12 commits into from
1 change: 1 addition & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
87 changes: 87 additions & 0 deletions control.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,24 @@ 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
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
Expand Down Expand Up @@ -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())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason DecodePacketErr is not used and checked here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

laziness? ;-) this started as a copy of the paging control above...

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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this safe; assuming Children has at least 3 elements?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the spec says there must be always 3, the cookie will be an empty string if it's unset (which only happens when the client sends the first time

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
Expand Down Expand Up @@ -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
}
61 changes: 61 additions & 0 deletions search.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
47 changes: 47 additions & 0 deletions search_test.go
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -29,3 +31,48 @@ func TestNewEntry(t *testing.T) {
iteration = iteration + 1
}
}

func ExampleDirSync() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there tests that could be added too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, encoding / decoding in control_test.go

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)
}

}