summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Mitton <mmitton@gmail.com>2011-02-18 07:36:55 +0100
committerMichael Mitton <mmitton@gmail.com>2011-02-18 07:36:55 +0100
commit1094e1befc82d86ecbd90193c68ed44a7026d8fa (patch)
treea3e9c753ee197eef91d35af85351e83465415908
downloadldap-1094e1befc82d86ecbd90193c68ed44a7026d8fa.tar
ldap-1094e1befc82d86ecbd90193c68ed44a7026d8fa.tar.gz
ldap-1094e1befc82d86ecbd90193c68ed44a7026d8fa.tar.bz2
ldap-1094e1befc82d86ecbd90193c68ed44a7026d8fa.tar.lz
ldap-1094e1befc82d86ecbd90193c68ed44a7026d8fa.tar.xz
ldap-1094e1befc82d86ecbd90193c68ed44a7026d8fa.tar.zst
ldap-1094e1befc82d86ecbd90193c68ed44a7026d8fa.zip
-rw-r--r--Makefile16
-rw-r--r--README27
-rw-r--r--bind.go50
-rw-r--r--conn.go270
-rw-r--r--control.go157
-rw-r--r--filter.go249
-rw-r--r--filter_test.go78
-rw-r--r--ldap.go291
-rw-r--r--search.go244
9 files changed, 1382 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d03d676
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,16 @@
+# Copyright 2009 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+include $(GOROOT)/src/Make.inc
+
+TARG=github.com/mmitton/ldap
+GOFILES=\
+ bind.go\
+ conn.go\
+ control.go\
+ filter.go\
+ ldap.go\
+ search.go\
+
+include $(GOROOT)/src/Make.pkg
diff --git a/README b/README
new file mode 100644
index 0000000..085da22
--- /dev/null
+++ b/README
@@ -0,0 +1,27 @@
+Basic LDAP v3 functionality for the GO programming language.
+
+Required Librarys:
+ github.com/mmitton/asn1/ber
+
+Working:
+ Connecting to LDAP server
+ Binding to LDAP server
+ Searching for entries
+ Compiling string filters to LDAP filters
+ Paging Search Results
+ Mulitple internal goroutines to handle network traffic
+ Makes library goroutine safe
+ Can perform multiple search requests at the same time and return
+ the results to the proper goroutine. All requests are blocking
+ requests, so the goroutine does not need special handling
+
+Tests Implemented:
+ Filter Compile / Decompile
+
+TODO:
+ Modify Requests / Responses
+ Add Requests / Responses
+ Delete Requests / Responses
+ Modify DN Requests / Responses
+ Compare Requests / Responses
+ Implement Tests / Benchmarks
diff --git a/bind.go b/bind.go
new file mode 100644
index 0000000..9176eca
--- /dev/null
+++ b/bind.go
@@ -0,0 +1,50 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// File contains Bind functionality
+package ldap
+
+import (
+ "github.com/mmitton/asn1-ber"
+ "os"
+)
+
+func (l *Conn) Bind( username, password string ) *Error {
+ messageID := l.nextMessageID()
+
+ packet := ber.Encode( ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request" )
+ packet.AppendChild( ber.NewInteger( ber.ClassUniversal, ber.TypePrimative, ber.TagInteger, messageID, "MessageID" ) )
+ bindRequest := ber.Encode( ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request" )
+ bindRequest.AppendChild( ber.NewInteger( ber.ClassUniversal, ber.TypePrimative, ber.TagInteger, 3, "Version" ) )
+ bindRequest.AppendChild( ber.NewString( ber.ClassUniversal, ber.TypePrimative, ber.TagOctetString, username, "User Name" ) )
+ bindRequest.AppendChild( ber.NewString( ber.ClassContext, ber.TypePrimative, 0, password, "Password" ) )
+ packet.AppendChild( bindRequest )
+
+ if l.Debug {
+ ber.PrintPacket( packet )
+ }
+
+ channel, err := l.sendMessage( packet )
+ if err != nil {
+ return err
+ }
+ if channel == nil {
+ return NewError( ErrorNetwork, os.NewError( "Could not send message" ) )
+ }
+ defer l.finishMessage( messageID )
+ packet = <-channel
+
+ if packet != nil {
+ return NewError( ErrorNetwork, os.NewError( "Could not retrieve response" ) )
+ }
+
+ if l.Debug {
+ if err := addLDAPDescriptions( packet ); err != nil {
+ return NewError( ErrorDebugging, err )
+ }
+ ber.PrintPacket( packet )
+ }
+
+ return nil
+}
diff --git a/conn.go b/conn.go
new file mode 100644
index 0000000..18d778c
--- /dev/null
+++ b/conn.go
@@ -0,0 +1,270 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This package provides LDAP client functions.
+package ldap
+
+import (
+ "github.com/mmitton/asn1-ber"
+ "crypto/tls"
+ "fmt"
+ "net"
+ "os"
+)
+
+// LDAP Connection
+type Conn struct {
+ conn net.Conn
+ isSSL bool
+ Debug bool
+
+ chanResults map[ uint64 ] chan *ber.Packet
+ chanProcessMessage chan *messagePacket
+ chanMessageID chan uint64
+}
+
+// Dial connects to the given address on the given network using net.Dial
+// and then returns a new Conn for the connection.
+func Dial(network, addr string) (*Conn, *Error) {
+ c, err := net.Dial(network, "", addr)
+ if err != nil {
+ return nil, NewError( ErrorNetwork, err )
+ }
+ conn := NewConn(c)
+ conn.start()
+ return conn, nil
+}
+
+// Dial connects to the given address on the given network using net.Dial
+// and then sets up SSL connection and returns a new Conn for the connection.
+func DialSSL(network, addr string) (*Conn, *Error) {
+ c, err := tls.Dial(network, "", addr, nil)
+ if err != nil {
+ return nil, NewError( ErrorNetwork, err )
+ }
+ conn := NewConn(c)
+ conn.isSSL = true
+
+ conn.start()
+ return conn, nil
+}
+
+// Dial connects to the given address on the given network using net.Dial
+// and then starts a TLS session and returns a new Conn for the connection.
+func DialTLS(network, addr string) (*Conn, *Error) {
+ c, err := net.Dial(network, "", addr)
+ if err != nil {
+ return nil, NewError( ErrorNetwork, err )
+ }
+ conn := NewConn(c)
+
+ err = conn.startTLS()
+ if err != nil {
+ conn.Close()
+ return nil, NewError( ErrorNetwork, err )
+ }
+ conn.start()
+ return conn, nil
+}
+
+// NewConn returns a new Conn using conn for network I/O.
+func NewConn(conn net.Conn) *Conn {
+ return &Conn{
+ conn: conn,
+ isSSL: false,
+ Debug: false,
+ chanResults: map[uint64] chan *ber.Packet{},
+ chanProcessMessage: make( chan *messagePacket ),
+ chanMessageID: make( chan uint64 ),
+ }
+}
+
+func (l *Conn) start() {
+ go l.reader()
+ go l.processMessages()
+}
+
+// Close closes the connection.
+func (l *Conn) Close() *Error {
+ if l.chanProcessMessage != nil {
+ message_packet := &messagePacket{ Op: MessageQuit }
+ l.chanProcessMessage <- message_packet
+ l.chanProcessMessage = nil
+ }
+
+ if l.conn != nil {
+ err := l.conn.Close()
+ if err != nil {
+ return NewError( ErrorNetwork, err )
+ }
+ l.conn = nil
+ }
+ return nil
+}
+
+// Returns the next available messageID
+func (l *Conn) nextMessageID() uint64 {
+ messageID := <-l.chanMessageID
+ return messageID
+}
+
+// StartTLS sends the command to start a TLS session and then creates a new TLS Client
+func (l *Conn) startTLS() *Error {
+ messageID := l.nextMessageID()
+
+ if l.isSSL {
+ return NewError( ErrorNetwork, os.NewError( "Already encrypted" ) )
+ }
+
+ packet := ber.Encode( ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request" )
+ packet.AppendChild( ber.NewInteger( ber.ClassUniversal, ber.TypePrimative, ber.TagInteger, messageID, "MessageID" ) )
+ startTLS := ber.Encode( ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Start TLS" )
+ startTLS.AppendChild( ber.NewString( ber.ClassContext, ber.TypePrimative, 0, "1.3.6.1.4.1.1466.20037", "TLS Extended Command" ) )
+ packet.AppendChild( startTLS )
+ if l.Debug {
+ ber.PrintPacket( packet )
+ }
+
+ _, err := l.conn.Write( packet.Bytes() )
+ if err != nil {
+ return NewError( ErrorNetwork, err )
+ }
+
+ packet, err = ber.ReadPacket( l.conn )
+ if err != nil {
+ return NewError( ErrorNetwork, err )
+ }
+
+ if l.Debug {
+ if err := addLDAPDescriptions( packet ); err != nil {
+ return NewError( ErrorDebugging, err )
+ }
+ ber.PrintPacket( packet )
+ }
+
+ if packet.Children[ 1 ].Children[ 0 ].Value.(uint64) == 0 {
+ conn := tls.Client( l.conn, nil )
+ l.isSSL = true
+ l.conn = conn
+ }
+
+ return nil
+}
+
+const (
+ MessageQuit = 0
+ MessageRequest = 1
+ MessageResponse = 2
+ MessageFinish = 3
+)
+
+type messagePacket struct {
+ Op int
+ MessageID uint64
+ Packet *ber.Packet
+ Channel chan *ber.Packet
+}
+
+func (l *Conn) sendMessage( p *ber.Packet ) (out chan *ber.Packet, err *Error) {
+ message_id := p.Children[ 0 ].Value.(uint64)
+ out = make(chan *ber.Packet)
+
+ message_packet := &messagePacket{ Op: MessageRequest, MessageID: message_id, Packet: p, Channel: out }
+ if l.chanProcessMessage == nil {
+ err = NewError( ErrorNetwork, os.NewError( "Connection closed" ) )
+ return
+ }
+ l.chanProcessMessage <- message_packet
+ return
+}
+
+func (l *Conn) processMessages() {
+ defer l.closeAllChannels()
+
+ var message_id uint64 = 1
+ var message_packet *messagePacket
+ for {
+ select {
+ case l.chanMessageID <- message_id:
+ if l.conn == nil {
+ return
+ }
+ message_id++
+ case message_packet = <-l.chanProcessMessage:
+ if l.conn == nil {
+ return
+ }
+ switch message_packet.Op {
+ case MessageQuit:
+ // Close all channels and quit
+ if l.Debug {
+ fmt.Printf( "Shutting down\n" )
+ }
+ return
+ case MessageRequest:
+ // Add to message list and write to network
+ if l.Debug {
+ fmt.Printf( "Sending message %d\n", message_packet.MessageID )
+ }
+ l.chanResults[ message_packet.MessageID ] = message_packet.Channel
+ l.conn.Write( message_packet.Packet.Bytes() )
+ case MessageResponse:
+ // Pass back to waiting goroutine
+ if l.Debug {
+ fmt.Printf( "Receiving message %d\n", message_packet.MessageID )
+ }
+ chanResult := l.chanResults[ message_packet.MessageID ]
+ if chanResult == nil {
+ fmt.Printf( "Unexpected Message Result: %d", message_id )
+ } else {
+ chanResult <- message_packet.Packet
+ }
+ case MessageFinish:
+ // Remove from message list
+ if l.Debug {
+ fmt.Printf( "Finished message %d\n", message_packet.MessageID )
+ }
+ l.chanResults[ message_packet.MessageID ] = nil, false
+ }
+ }
+ }
+}
+
+func (l *Conn) closeAllChannels() {
+ for MessageID, Channel := range l.chanResults {
+ if l.Debug {
+ fmt.Printf( "Closing channel for MessageID %d\n", MessageID );
+ }
+ close( Channel )
+ l.chanResults[ MessageID ] = nil, false
+ }
+ close( l.chanMessageID )
+ l.chanMessageID = nil
+}
+
+func (l *Conn) finishMessage( MessageID uint64 ) {
+ message_packet := &messagePacket{ Op: MessageFinish, MessageID: MessageID }
+ if l.chanProcessMessage != nil {
+ l.chanProcessMessage <- message_packet
+ }
+}
+
+func (l *Conn) reader() {
+ for {
+ p, err := ber.ReadPacket( l.conn )
+ if err != nil {
+ if l.Debug {
+ fmt.Printf( "ldap.reader: %s\n", err.String() )
+ }
+ break
+ }
+
+ message_id := p.Children[ 0 ].Value.(uint64)
+ message_packet := &messagePacket{ Op: MessageResponse, MessageID: message_id, Packet: p }
+ l.chanProcessMessage <- message_packet
+ }
+
+ l.Close()
+}
+
diff --git a/control.go b/control.go
new file mode 100644
index 0000000..af145b2
--- /dev/null
+++ b/control.go
@@ -0,0 +1,157 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This package provides LDAP client functions.
+package ldap
+
+import (
+ "github.com/mmitton/asn1-ber"
+ "fmt"
+)
+
+const (
+ ControlTypePaging = "1.2.840.113556.1.4.319"
+)
+
+var ControlTypeMap = map[ string ] string {
+ ControlTypePaging : "Paging",
+}
+
+type Control interface {
+ GetControlType() string
+ Encode() *ber.Packet
+ String() string
+}
+
+type ControlString struct {
+ ControlType string
+ Criticality bool
+ ControlValue string
+}
+
+func (c *ControlString) GetControlType() string {
+ return c.ControlType
+}
+
+func (c *ControlString) Encode() (p *ber.Packet) {
+ p = ber.Encode( ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control" )
+ p.AppendChild( ber.NewString( ber.ClassUniversal, ber.TypePrimative, ber.TagOctetString, c.ControlType, "Control Type (" + ControlTypeMap[ c.ControlType ] + ")" ) )
+ if c.Criticality {
+ p.AppendChild( ber.NewBoolean( ber.ClassUniversal, ber.TypePrimative, ber.TagBoolean, c.Criticality, "Criticality" ) )
+ }
+ p.AppendChild( ber.NewString( ber.ClassUniversal, ber.TypePrimative, ber.TagOctetString, c.ControlValue, "Control Value" ) )
+ return
+}
+
+func (c *ControlString) String() string {
+ return fmt.Sprintf( "Control Type: %s (%q) Criticality: %s Control Value: %s", ControlTypeMap[ c.ControlType ], c.ControlType, c.Criticality, c.ControlValue )
+}
+
+type ControlPaging struct {
+ PagingSize uint32
+ Cookie []byte
+}
+
+func (c *ControlPaging) GetControlType() string {
+ return ControlTypePaging
+}
+
+func (c *ControlPaging) Encode() (p *ber.Packet) {
+ p = ber.Encode( ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control" )
+ p.AppendChild( ber.NewString( ber.ClassUniversal, ber.TypePrimative, ber.TagOctetString, ControlTypePaging, "Control Type (" + ControlTypeMap[ ControlTypePaging ] + ")" ) )
+
+ p2 := ber.Encode( ber.ClassUniversal, ber.TypePrimative, ber.TagOctetString, nil, "Control Value (Paging)" )
+ seq := ber.Encode( ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Search Control Value" )
+ seq.AppendChild( ber.NewInteger( ber.ClassUniversal, ber.TypePrimative, ber.TagInteger, uint64(c.PagingSize), "Paging Size" ) )
+ cookie := ber.Encode( ber.ClassUniversal, ber.TypePrimative, ber.TagOctetString, nil, "Cookie" )
+ cookie.Value = c.Cookie
+ cookie.Data.Write( c.Cookie )
+ seq.AppendChild( cookie )
+ p2.AppendChild( seq )
+
+ p.AppendChild( p2 )
+ return
+}
+
+func (c *ControlPaging) String() string {
+ return fmt.Sprintf(
+ "Control Type: %s (%q) Criticality: %s PagingSize: %d Cookie: %q",
+ ControlTypeMap[ ControlTypePaging ],
+ ControlTypePaging,
+ false,
+ c.PagingSize,
+ c.Cookie )
+}
+
+func (c *ControlPaging) SetCookie( Cookie []byte ) {
+ c.Cookie = Cookie
+}
+
+func FindControl( Controls []Control, ControlType string ) Control {
+ for _, c := range Controls {
+ if c.GetControlType() == ControlType {
+ return c
+ }
+ }
+ return nil
+}
+
+func DecodeControl( p *ber.Packet ) Control {
+ ControlType := p.Children[ 0 ].Value.(string)
+ Criticality := false
+
+ p.Children[ 0 ].Description = "Control Type (" + ControlTypeMap[ ControlType ] + ")"
+ value := p.Children[ 1 ]
+ if len( p.Children ) == 3 {
+ value = p.Children[ 2 ]
+ p.Children[ 1 ].Description = "Criticality"
+ Criticality = p.Children[ 1 ].Value.(bool)
+ }
+
+ value.Description = "Control Value"
+ switch ControlType {
+ case ControlTypePaging:
+ value.Description += " (Paging)"
+ c := new( ControlPaging )
+ if value.Value != nil {
+ value_children := ber.DecodePacket( value.Data.Bytes() )
+ value.Data.Truncate( 0 )
+ value.Value = nil
+ value.AppendChild( value_children )
+ }
+ value = value.Children[ 0 ]
+ value.Description = "Search Control Value"
+ value.Children[ 0 ].Description = "Paging Size"
+ value.Children[ 1 ].Description = "Cookie"
+ c.PagingSize = uint32( value.Children[ 0 ].Value.(uint64) )
+ c.Cookie = value.Children[ 1 ].Data.Bytes()
+ value.Children[ 1 ].Value = c.Cookie
+ return c
+ }
+ c := new( ControlString )
+ c.ControlType = ControlType
+ c.Criticality = Criticality
+ c.ControlValue = value.Value.(string)
+ return c
+}
+
+func NewControlString( ControlType string, Criticality bool, ControlValue string ) *ControlString {
+ return &ControlString{
+ ControlType: ControlType,
+ Criticality: Criticality,
+ ControlValue: ControlValue,
+ }
+}
+
+func NewControlPaging( PagingSize uint32 ) *ControlPaging {
+ return &ControlPaging{ PagingSize: PagingSize }
+}
+
+func encodeControls( Controls []Control ) *ber.Packet {
+ p := ber.Encode( ber.ClassContext, ber.TypeConstructed, 0, nil, "Controls" )
+ for _, control := range Controls {
+ p.AppendChild( control.Encode() )
+ }
+ return p
+}
diff --git a/filter.go b/filter.go
new file mode 100644
index 0000000..1b8fd1e
--- /dev/null
+++ b/filter.go
@@ -0,0 +1,249 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// File contains a filter compiler/decompiler
+package ldap
+
+import (
+ "fmt"
+ "os"
+ "github.com/mmitton/asn1-ber"
+)
+
+const (
+ FilterAnd = 0
+ FilterOr = 1
+ FilterNot = 2
+ FilterEqualityMatch = 3
+ FilterSubstrings = 4
+ FilterGreaterOrEqual = 5
+ FilterLessOrEqual = 6
+ FilterPresent = 7
+ FilterApproxMatch = 8
+ FilterExtensibleMatch = 9
+)
+
+var FilterMap = map[ uint64 ] string {
+ FilterAnd : "And",
+ FilterOr : "Or",
+ FilterNot : "Not",
+ FilterEqualityMatch : "Equality Match",
+ FilterSubstrings : "Substrings",
+ FilterGreaterOrEqual : "Greater Or Equal",
+ FilterLessOrEqual : "Less Or Equal",
+ FilterPresent : "Present",
+ FilterApproxMatch : "Approx Match",
+ FilterExtensibleMatch : "Extensible Match",
+}
+
+const (
+ FilterSubstringsInitial = 0
+ FilterSubstringsAny = 1
+ FilterSubstringsFinal = 2
+)
+
+var FilterSubstringsMap = map[ uint64 ] string {
+ FilterSubstringsInitial : "Substrings Initial",
+ FilterSubstringsAny : "Substrings Any",
+ FilterSubstringsFinal : "Substrings Final",
+}
+
+func CompileFilter( filter string ) ( *ber.Packet, *Error ) {
+ if len( filter ) == 0 || filter[ 0 ] != '(' {
+ return nil, NewError( ErrorFilterCompile, os.NewError( "Filter does not start with an '('" ) )
+ }
+ packet, pos, err := compileFilter( filter, 1 )
+ if err != nil {
+ return nil, err
+ }
+ if pos != len( filter ) {
+ return nil, NewError( ErrorFilterCompile, os.NewError( "Finished compiling filter with extra at end.\n" + fmt.Sprint( filter[pos:] ) ) )
+ }
+ return packet, nil
+}
+
+func DecompileFilter( packet *ber.Packet ) (ret string, err *Error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = NewError( ErrorFilterDecompile, os.NewError( "Error decompiling filter" ) )
+ }
+ }()
+ ret = "("
+ err = nil
+ child_str := ""
+
+ switch packet.Tag {
+ case FilterAnd:
+ ret += "&"
+ for _, child := range packet.Children {
+ child_str, err = DecompileFilter( child )
+ if err != nil {
+ return
+ }
+ ret += child_str
+ }
+ case FilterOr:
+ ret += "|"
+ for _, child := range packet.Children {
+ child_str, err = DecompileFilter( child )
+ if err != nil {
+ return
+ }
+ ret += child_str
+ }
+ case FilterNot:
+ ret += "!"
+ child_str, err = DecompileFilter( packet.Children[ 0 ] )
+ if err != nil {
+ return
+ }
+ ret += child_str
+
+ case FilterSubstrings:
+ ret += ber.DecodeString( packet.Children[ 0 ].Data.Bytes() )
+ ret += "="
+ switch packet.Children[ 1 ].Children[ 0 ].Tag {
+ case FilterSubstringsInitial:
+ ret += ber.DecodeString( packet.Children[ 1 ].Children[ 0 ].Data.Bytes() ) + "*"
+ case FilterSubstringsAny:
+ ret += "*" + ber.DecodeString( packet.Children[ 1 ].Children[ 0 ].Data.Bytes() ) + "*"
+ case FilterSubstringsFinal:
+ ret += "*" + ber.DecodeString( packet.Children[ 1 ].Children[ 0 ].Data.Bytes() )
+ }
+ case FilterEqualityMatch:
+ ret += ber.DecodeString( packet.Children[ 0 ].Data.Bytes() )
+ ret += "="
+ ret += ber.DecodeString( packet.Children[ 1 ].Data.Bytes() )
+ case FilterGreaterOrEqual:
+ ret += ber.DecodeString( packet.Children[ 0 ].Data.Bytes() )
+ ret += ">="
+ ret += ber.DecodeString( packet.Children[ 1 ].Data.Bytes() )
+ case FilterLessOrEqual:
+ ret += ber.DecodeString( packet.Children[ 0 ].Data.Bytes() )
+ ret += "<="
+ ret += ber.DecodeString( packet.Children[ 1 ].Data.Bytes() )
+ case FilterPresent:
+ ret += ber.DecodeString( packet.Children[ 0 ].Data.Bytes() )
+ ret += "=*"
+ case FilterApproxMatch:
+ ret += ber.DecodeString( packet.Children[ 0 ].Data.Bytes() )
+ ret += "~="
+ ret += ber.DecodeString( packet.Children[ 1 ].Data.Bytes() )
+ }
+
+ ret += ")"
+ return
+}
+
+func compileFilterSet( filter string, pos int, parent *ber.Packet ) ( int, *Error ) {
+ for pos < len( filter ) && filter[ pos ] == '(' {
+ child, new_pos, err := compileFilter( filter, pos + 1 )
+ if err != nil {
+ return pos, err
+ }
+ pos = new_pos
+ parent.AppendChild( child )
+ }
+ if pos == len( filter ) {
+ return pos, NewError( ErrorFilterCompile, os.NewError( "Unexpected end of filter" ) )
+ }
+
+ return pos + 1, nil
+}
+
+func compileFilter( filter string, pos int ) ( p *ber.Packet, new_pos int, err *Error ) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = NewError( ErrorFilterCompile, os.NewError( "Error compiling filter" ) )
+ }
+ }()
+ p = nil
+ new_pos = pos
+ err = nil
+
+ switch filter[pos] {
+ case '(':
+ p, new_pos, err = compileFilter( filter, pos + 1 )
+ new_pos++
+ return
+ case '&':
+ p = ber.Encode( ber.ClassContext, ber.TypeConstructed, FilterAnd, nil, FilterMap[ FilterAnd ] )
+ new_pos, err = compileFilterSet( filter, pos + 1, p )
+ return
+ case '|':
+ p = ber.Encode( ber.ClassContext, ber.TypeConstructed, FilterOr, nil, FilterMap[ FilterOr ] )
+ new_pos, err = compileFilterSet( filter, pos + 1, p )
+ return
+ case '!':
+ p = ber.Encode( ber.ClassContext, ber.TypeConstructed, FilterNot, nil, FilterMap[ FilterNot ] )
+ var child *ber.Packet
+ child, new_pos, err = compileFilter( filter, pos + 1 )
+ p.AppendChild( child )
+ return
+ default:
+ attribute := ""
+ condition := ""
+ for new_pos < len( filter ) && filter[ new_pos ] != ')' {
+ switch {
+ case p != nil:
+ condition += fmt.Sprintf( "%c", filter[ new_pos ] )
+ case filter[ new_pos ] == '=':
+ p = ber.Encode( ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[ FilterEqualityMatch ] )
+ case filter[ new_pos ] == '>' && filter[ new_pos + 1 ] == '=':
+ p = ber.Encode( ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[ FilterGreaterOrEqual ] )
+ new_pos++
+ case filter[ new_pos ] == '<' && filter[ new_pos + 1 ] == '=':
+ p = ber.Encode( ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[ FilterLessOrEqual ] )
+ new_pos++
+ case filter[ new_pos ] == '~' && filter[ new_pos + 1 ] == '=':
+ p = ber.Encode( ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[ FilterLessOrEqual ] )
+ new_pos++
+ case p == nil:
+ attribute += fmt.Sprintf( "%c", filter[ new_pos ] )
+ }
+ new_pos++
+ }
+ if new_pos == len( filter ) {
+ err = NewError( ErrorFilterCompile, os.NewError( "Unexpected end of filter" ) )
+ return
+ }
+ if p == nil {
+ err = NewError( ErrorFilterCompile, os.NewError( "Error parsing filter" ) )
+ return
+ }
+ p.AppendChild( ber.NewString( ber.ClassUniversal, ber.TypePrimative, ber.TagOctetString, attribute, "Attribute" ) )
+ switch {
+ case p.Tag == FilterEqualityMatch && condition == "*":
+ p.Tag = FilterPresent
+ p.Description = FilterMap[ uint64(p.Tag) ]
+ case p.Tag == FilterEqualityMatch && condition[ 0 ] == '*' && condition[ len( condition ) - 1 ] == '*':
+ // Any
+ p.Tag = FilterSubstrings
+ p.Description = FilterMap[ uint64(p.Tag) ]
+ seq := ber.Encode( ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings" )
+ seq.AppendChild( ber.NewString( ber.ClassContext, ber.TypePrimative, FilterSubstringsAny, condition[ 1 : len( condition ) - 1 ], "Any Substring" ) )
+ p.AppendChild( seq )
+ case p.Tag == FilterEqualityMatch && condition[ 0 ] == '*':
+ // Final
+ p.Tag = FilterSubstrings
+ p.Description = FilterMap[ uint64(p.Tag) ]
+ seq := ber.Encode( ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings" )
+ seq.AppendChild( ber.NewString( ber.ClassContext, ber.TypePrimative, FilterSubstringsFinal, condition[ 1: ], "Final Substring" ) )
+ p.AppendChild( seq )
+ case p.Tag == FilterEqualityMatch && condition[ len( condition ) - 1 ] == '*':
+ // Initial
+ p.Tag = FilterSubstrings
+ p.Description = FilterMap[ uint64(p.Tag) ]
+ seq := ber.Encode( ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings" )
+ seq.AppendChild( ber.NewString( ber.ClassContext, ber.TypePrimative, FilterSubstringsInitial, condition[ :len( condition ) - 1 ], "Initial Substring" ) )
+ p.AppendChild( seq )
+ default:
+ p.AppendChild( ber.NewString( ber.ClassUniversal, ber.TypePrimative, ber.TagOctetString, condition, "Condition" ) )
+ }
+ new_pos++
+ return
+ }
+ err = NewError( ErrorFilterCompile, os.NewError( "Reached end of filter without closing parens" ) )
+ return
+}
diff --git a/filter_test.go b/filter_test.go
new file mode 100644
index 0000000..4b403e4
--- /dev/null
+++ b/filter_test.go
@@ -0,0 +1,78 @@
+package ldap
+
+import (
+ "github.com/mmitton/asn1-ber"
+ "testing"
+)
+
+type compile_test struct {
+ filter_str string
+ filter_type int
+}
+
+
+var test_filters = []compile_test {
+ compile_test{ filter_str: "(&(sn=Miller)(givenName=Bob))", filter_type: FilterAnd },
+ compile_test{ filter_str: "(|(sn=Miller)(givenName=Bob))", filter_type: FilterOr },
+ compile_test{ filter_str: "(!(sn=Miller))", filter_type: FilterNot },
+ compile_test{ filter_str: "(sn=Miller)", filter_type: FilterEqualityMatch },
+ compile_test{ filter_str: "(sn=Mill*)", filter_type: FilterSubstrings },
+ compile_test{ filter_str: "(sn=*Mill)", filter_type: FilterSubstrings },
+ compile_test{ filter_str: "(sn=*Mill*)", filter_type: FilterSubstrings },
+ compile_test{ filter_str: "(sn>=Miller)", filter_type: FilterGreaterOrEqual },
+ compile_test{ filter_str: "(sn<=Miller)", filter_type: FilterLessOrEqual },
+ compile_test{ filter_str: "(sn=*)", filter_type: FilterPresent },
+ compile_test{ filter_str: "(sn~=Miller)", filter_type: FilterApproxMatch },
+ // compile_test{ filter_str: "()", filter_type: FilterExtensibleMatch },
+}
+
+func TestFilter( t *testing.T ) {
+ // Test Compiler and Decompiler
+ for _, i := range test_filters {
+ filter, err := CompileFilter( i.filter_str )
+ if err != nil {
+ t.Errorf( "Problem compiling %s - %s", err.String() )
+ } else if filter.Tag != uint8(i.filter_type) {
+ t.Errorf( "%q Expected %q got %q", i.filter_str, FilterMap[ uint64(i.filter_type) ], FilterMap[ uint64(filter.Tag) ] )
+ } else {
+ o, err := DecompileFilter( filter )
+ if err != nil {
+ t.Errorf( "Problem compiling %s - %s", i, err.String() )
+ } else if i.filter_str != o {
+ t.Errorf( "%q expected, got %q", i.filter_str, o )
+ }
+ }
+ }
+}
+
+func BenchmarkFilterCompile( b *testing.B ) {
+ b.StopTimer()
+ filters := make([]string, len( test_filters ) )
+
+ // Test Compiler and Decompiler
+ for idx, i := range test_filters {
+ filters[ idx ] = i.filter_str
+ }
+
+ max_idx := len( filters )
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ CompileFilter( filters[ i % max_idx ] )
+ }
+}
+
+func BenchmarkFilterDecompile( b *testing.B ) {
+ b.StopTimer()
+ filters := make([]*ber.Packet, len( test_filters ) )
+
+ // Test Compiler and Decompiler
+ for idx, i := range test_filters {
+ filters[ idx ], _ = CompileFilter( i.filter_str )
+ }
+
+ max_idx := len( filters )
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ DecompileFilter( filters[ i % max_idx ] )
+ }
+}
diff --git a/ldap.go b/ldap.go
new file mode 100644
index 0000000..22605e3
--- /dev/null
+++ b/ldap.go
@@ -0,0 +1,291 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This package provides LDAP client functions.
+package ldap
+
+import (
+ "github.com/mmitton/asn1-ber"
+ "fmt"
+ "io/ioutil"
+ "os"
+)
+
+// LDAP Application Codes
+const (
+ ApplicationBindRequest = 0
+ ApplicationBindResponse = 1
+ ApplicationUnbindRequest = 2
+ ApplicationSearchRequest = 3
+ ApplicationSearchResultEntry = 4
+ ApplicationSearchResultDone = 5
+ ApplicationModifyRequest = 6
+ ApplicationModifyResponse = 7
+ ApplicationAddRequest = 8
+ ApplicationAddResponse = 9
+ ApplicationDelRequest = 10
+ ApplicationDelResponse = 11
+ ApplicationModifyDNRequest = 12
+ ApplicationModifyDNResponse = 13
+ ApplicationCompareRequest = 14
+ ApplicationCompareResponse = 15
+ ApplicationAbandonRequest = 16
+ ApplicationSearchResultReference = 19
+ ApplicationExtendedRequest = 23
+ ApplicationExtendedResponse = 24
+)
+
+var ApplicationMap = map[ uint8 ] string {
+ ApplicationBindRequest : "Bind Request",
+ ApplicationBindResponse : "Bind Response",
+ ApplicationUnbindRequest : "Unbind Request",
+ ApplicationSearchRequest : "Search Request",
+ ApplicationSearchResultEntry : "Search Result Entry",
+ ApplicationSearchResultDone : "Search Result Done",
+ ApplicationModifyRequest : "Modify Request",
+ ApplicationModifyResponse : "Modify Response",
+ ApplicationAddRequest : "Add Request",
+ ApplicationAddResponse : "Add Response",
+ ApplicationDelRequest : "Del Request",
+ ApplicationDelResponse : "Del Response",
+ ApplicationModifyDNRequest : "Modify DN Request",
+ ApplicationModifyDNResponse : "Modify DN Response",
+ ApplicationCompareRequest : "Compare Request",
+ ApplicationCompareResponse : "Compare Response",
+ ApplicationAbandonRequest : "Abandon Request",
+ ApplicationSearchResultReference : "Search Result Reference",
+ ApplicationExtendedRequest : "Extended Request",
+ ApplicationExtendedResponse : "Extended Response",
+}
+
+// LDAP Result Codes
+const (
+ LDAPResultSuccess = 0
+ LDAPResultOperationsError = 1
+ LDAPResultProtocolError = 2
+ LDAPResultTimeLimitExceeded = 3
+ LDAPResultSizeLimitExceeded = 4
+ LDAPResultCompareFalse = 5
+ LDAPResultCompareTrue = 6
+ LDAPResultAuthMethodNotSupported = 7
+ LDAPResultStrongAuthRequired = 8
+ LDAPResultReferral = 10
+ LDAPResultAdminLimitExceeded = 11
+ LDAPResultUnavailableCriticalExtension = 12
+ LDAPResultConfidentialityRequired = 13
+ LDAPResultSaslBindInProgress = 14
+ LDAPResultNoSuchAttribute = 16
+ LDAPResultUndefinedAttributeType = 17
+ LDAPResultInappropriateMatching = 18
+ LDAPResultConstraintViolation = 19
+ LDAPResultAttributeOrValueExists = 20
+ LDAPResultInvalidAttributeSyntax = 21
+ LDAPResultNoSuchObject = 32
+ LDAPResultAliasProblem = 33
+ LDAPResultInvalidDNSyntax = 34
+ LDAPResultAliasDereferencingProblem = 36
+ LDAPResultInappropriateAuthentication = 48
+ LDAPResultInvalidCredentials = 49
+ LDAPResultInsufficientAccessRights = 50
+ LDAPResultBusy = 51
+ LDAPResultUnavailable = 52
+ LDAPResultUnwillingToPerform = 53
+ LDAPResultLoopDetect = 54
+ LDAPResultNamingViolation = 64
+ LDAPResultObjectClassViolation = 65
+ LDAPResultNotAllowedOnNonLeaf = 66
+ LDAPResultNotAllowedOnRDN = 67
+ LDAPResultEntryAlreadyExists = 68
+ LDAPResultObjectClassModsProhibited = 69
+ LDAPResultAffectsMultipleDSAs = 71
+ LDAPResultOther = 80
+
+ ErrorNetwork = 200
+ ErrorFilterCompile = 201
+ ErrorFilterDecompile = 202
+ ErrorDebugging = 203
+)
+
+var LDAPResultCodeMap = map[uint8] string {
+ LDAPResultSuccess : "Success",
+ LDAPResultOperationsError : "Operations Error",
+ LDAPResultProtocolError : "Protocol Error",
+ LDAPResultTimeLimitExceeded : "Time Limit Exceeded",
+ LDAPResultSizeLimitExceeded : "Size Limit Exceeded",
+ LDAPResultCompareFalse : "Compare False",
+ LDAPResultCompareTrue : "Compare True",
+ LDAPResultAuthMethodNotSupported : "Auth Method Not Supported",
+ LDAPResultStrongAuthRequired : "Strong Auth Required",
+ LDAPResultReferral : "Referral",
+ LDAPResultAdminLimitExceeded : "Admin Limit Exceeded",
+ LDAPResultUnavailableCriticalExtension : "Unavailable Critical Extension",
+ LDAPResultConfidentialityRequired : "Confidentiality Required",
+ LDAPResultSaslBindInProgress : "Sasl Bind In Progress",
+ LDAPResultNoSuchAttribute : "No Such Attribute",
+ LDAPResultUndefinedAttributeType : "Undefined Attribute Type",
+ LDAPResultInappropriateMatching : "Inappropriate Matching",
+ LDAPResultConstraintViolation : "Constraint Violation",
+ LDAPResultAttributeOrValueExists : "Attribute Or Value Exists",
+ LDAPResultInvalidAttributeSyntax : "Invalid Attribute Syntax",
+ LDAPResultNoSuchObject : "No Such Object",
+ LDAPResultAliasProblem : "Alias Problem",
+ LDAPResultInvalidDNSyntax : "Invalid DN Syntax",
+ LDAPResultAliasDereferencingProblem : "Alias Dereferencing Problem",
+ LDAPResultInappropriateAuthentication : "Inappropriate Authentication",
+ LDAPResultInvalidCredentials : "Invalid Credentials",
+ LDAPResultInsufficientAccessRights : "Insufficient Access Rights",
+ LDAPResultBusy : "Busy",
+ LDAPResultUnavailable : "Unavailable",
+ LDAPResultUnwillingToPerform : "Unwilling To Perform",
+ LDAPResultLoopDetect : "Loop Detect",
+ LDAPResultNamingViolation : "Naming Violation",
+ LDAPResultObjectClassViolation : "Object Class Violation",
+ LDAPResultNotAllowedOnNonLeaf : "Not Allowed On Non Leaf",
+ LDAPResultNotAllowedOnRDN : "Not Allowed On RDN",
+ LDAPResultEntryAlreadyExists : "Entry Already Exists",
+ LDAPResultObjectClassModsProhibited : "Object Class Mods Prohibited",
+ LDAPResultAffectsMultipleDSAs : "Affects Multiple DSAs",
+ LDAPResultOther : "Other",
+}
+
+// Adds descriptions to an LDAP Response packet for debugging
+func addLDAPDescriptions( packet *ber.Packet ) (err *Error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = NewError( ErrorDebugging, os.NewError( "Cannot process packet to add descriptions" ) )
+ }
+ }()
+ packet.Description = "LDAP Response"
+ packet.Children[ 0 ].Description = "Message ID";
+
+ application := packet.Children[ 1 ].Tag
+ packet.Children[ 1 ].Description = ApplicationMap[ application ]
+
+ switch application {
+ case ApplicationBindRequest:
+ addRequestDescriptions( packet )
+ case ApplicationBindResponse:
+ addDefaultLDAPResponseDescriptions( packet )
+ case ApplicationUnbindRequest:
+ addRequestDescriptions( packet )
+ case ApplicationSearchRequest:
+ addRequestDescriptions( packet )
+ case ApplicationSearchResultEntry:
+ packet.Children[ 1 ].Children[ 0 ].Description = "Object Name"
+ packet.Children[ 1 ].Children[ 1 ].Description = "Attributes"
+ for _, child := range packet.Children[ 1 ].Children[ 1 ].Children {
+ child.Description = "Attribute"
+ child.Children[ 0 ].Description = "Attribute Name"
+ child.Children[ 1 ].Description = "Attribute Values"
+ for _, grandchild := range child.Children[ 1 ].Children {
+ grandchild.Description = "Attribute Value"
+ }
+ }
+ if len( packet.Children ) == 3 {
+ addControlDescriptions( packet.Children[ 2 ] )
+ }
+ case ApplicationSearchResultDone:
+ addDefaultLDAPResponseDescriptions( packet )
+ case ApplicationModifyRequest:
+ addRequestDescriptions( packet )
+ case ApplicationModifyResponse:
+ case ApplicationAddRequest:
+ addRequestDescriptions( packet )
+ case ApplicationAddResponse:
+ case ApplicationDelRequest:
+ addRequestDescriptions( packet )
+ case ApplicationDelResponse:
+ case ApplicationModifyDNRequest:
+ addRequestDescriptions( packet )
+ case ApplicationModifyDNResponse:
+ case ApplicationCompareRequest:
+ addRequestDescriptions( packet )
+ case ApplicationCompareResponse:
+ case ApplicationAbandonRequest:
+ addRequestDescriptions( packet )
+ case ApplicationSearchResultReference:
+ case ApplicationExtendedRequest:
+ addRequestDescriptions( packet )
+ case ApplicationExtendedResponse:
+ }
+
+ return nil
+}
+
+func addControlDescriptions( packet *ber.Packet ) {
+ packet.Description = "Controls"
+ for _, child := range packet.Children {
+ child.Description = "Control"
+ child.Children[ 0 ].Description = "Control Type (" + ControlTypeMap[ child.Children[ 0 ].Value.(string) ] + ")"
+ value := child.Children[ 1 ]
+ if len( child.Children ) == 3 {
+ child.Children[ 1 ].Description = "Criticality"
+ value = child.Children[ 2 ]
+ }
+ value.Description = "Control Value"
+
+ switch child.Children[ 0 ].Value.(string) {
+ case ControlTypePaging:
+ value.Description += " (Paging)"
+ if value.Value != nil {
+ value_children := ber.DecodePacket( value.Data.Bytes() )
+ value.Data.Truncate( 0 )
+ value.Value = nil
+ value_children.Children[ 1 ].Value = value_children.Children[ 1 ].Data.Bytes()
+ value.AppendChild( value_children )
+ }
+ value.Children[ 0 ].Description = "Real Search Control Value"
+ value.Children[ 0 ].Children[ 0 ].Description = "Paging Size"
+ value.Children[ 0 ].Children[ 1 ].Description = "Cookie"
+ }
+ }
+}
+
+func addRequestDescriptions( packet *ber.Packet ) {
+ packet.Description = "LDAP Request"
+ packet.Children[ 0 ].Description = "Message ID"
+ packet.Children[ 1 ].Description = ApplicationMap[ packet.Children[ 1 ].Tag ];
+ if len( packet.Children ) == 3 {
+ addControlDescriptions( packet.Children[ 2 ] )
+ }
+}
+
+func addDefaultLDAPResponseDescriptions( packet *ber.Packet ) {
+ resultCode := packet.Children[ 1 ].Children[ 0 ].Value.(uint64)
+ packet.Children[ 1 ].Children[ 0 ].Description = "Result Code (" + LDAPResultCodeMap[ uint8(resultCode) ] + ")";
+ packet.Children[ 1 ].Children[ 1 ].Description = "Matched DN";
+ packet.Children[ 1 ].Children[ 2 ].Description = "Error Message";
+ if len( packet.Children[ 1 ].Children ) > 3 {
+ packet.Children[ 1 ].Children[ 3 ].Description = "Referral";
+ }
+ if len( packet.Children ) == 3 {
+ addControlDescriptions( packet.Children[ 2 ] )
+ }
+}
+
+func DebugBinaryFile( FileName string ) *Error {
+ file, err := ioutil.ReadFile( FileName )
+ if err != nil {
+ return NewError( ErrorDebugging, err )
+ }
+ ber.PrintBytes( file, "" )
+ packet := ber.DecodePacket( file )
+ addLDAPDescriptions( packet )
+ ber.PrintPacket( packet )
+
+ return nil
+}
+
+type Error struct {
+ Err os.Error
+ ResultCode uint8
+}
+
+func (e *Error) String() string {
+ return fmt.Sprintf( "LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[ e.ResultCode ], e.Err.String() )
+}
+
+func NewError( ResultCode uint8, Err os.Error ) (* Error) {
+ return &Error{ ResultCode: ResultCode, Err: Err }
+}
diff --git a/search.go b/search.go
new file mode 100644
index 0000000..011dc94
--- /dev/null
+++ b/search.go
@@ -0,0 +1,244 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// File contains Search functionality
+package ldap
+
+import (
+ "github.com/mmitton/asn1-ber"
+ "fmt"
+ "os"
+)
+
+const (
+ ScopeBaseObject = 0
+ ScopeSingleLevel = 1
+ ScopeWholeSubtree = 2
+)
+
+var ScopeMap = map[ int ] string {
+ ScopeBaseObject : "Base Object",
+ ScopeSingleLevel : "Single Level",
+ ScopeWholeSubtree : "Whole Subtree",
+}
+
+const (
+ NeverDerefAliases = 0
+ DerefInSearching = 1
+ DerefFindingBaseObj = 2
+ DerefAlways = 3
+)
+
+var DerefMap = map[ int ] string {
+ NeverDerefAliases : "NeverDerefAliases",
+ DerefInSearching : "DerefInSearching",
+ DerefFindingBaseObj : "DerefFindingBaseObj",
+ DerefAlways : "DerefAlways",
+}
+
+type Entry struct {
+ DN string
+ Attributes []*EntryAttribute
+}
+
+type EntryAttribute struct {
+ Name string
+ Values []string
+}
+
+type SearchResult struct {
+ Entries []*Entry
+ Referrals []string
+ Controls []Control
+}
+
+func (e *Entry) GetAttributeValues( Attribute string ) []string {
+ for _, attr := range e.Attributes {
+ if attr.Name == Attribute {
+ return attr.Values
+ }
+ }
+
+ return []string{ }
+}
+
+func (e *Entry) GetAttributeValue( Attribute string ) string {
+ values := e.GetAttributeValues( Attribute )
+ if len( values ) == 0 {
+ return ""
+ }
+ return values[ 0 ]
+}
+
+type SearchRequest struct {
+ BaseDN string
+ Scope int
+ DerefAliases int
+ SizeLimit int
+ TimeLimit int
+ TypesOnly bool
+ Filter string
+ Attributes []string
+ Controls []Control
+}
+
+func NewSearchRequest(
+ BaseDN string,
+ Scope, DerefAliases, SizeLimit, TimeLimit int,
+ TypesOnly bool,
+ Filter string,
+ Attributes []string,
+ Controls []Control,
+ ) (*SearchRequest) {
+ return &SearchRequest{
+ BaseDN: BaseDN,
+ Scope: Scope,
+ DerefAliases: DerefAliases,
+ SizeLimit: SizeLimit,
+ TimeLimit: TimeLimit,
+ TypesOnly: TypesOnly,
+ Filter: Filter,
+ Attributes: Attributes,
+ Controls: Controls,
+ }
+}
+
+func (l *Conn) SearchWithPaging( SearchRequest *SearchRequest, PagingSize uint32 ) (*SearchResult, *Error) {
+ if SearchRequest.Controls == nil {
+ SearchRequest.Controls = make( []Control, 0 )
+ }
+
+ PagingControl := NewControlPaging( PagingSize )
+ SearchRequest.Controls = append( SearchRequest.Controls, PagingControl )
+ SearchResult := new( SearchResult )
+ for {
+ result, err := l.Search( SearchRequest )
+ if err != nil {
+ return SearchResult, err
+ }
+ if result == nil {
+ return SearchResult, NewError( ErrorNetwork, os.NewError( "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 )
+ }
+
+ paging_result := FindControl( result.Controls, ControlTypePaging )
+ if paging_result == nil {
+ PagingControl = nil
+ break
+ }
+
+ cookie := paging_result.(*ControlPaging).Cookie
+ if len( cookie ) == 0 {
+ PagingControl = nil
+ break
+ }
+ PagingControl.SetCookie( cookie )
+ }
+
+ if PagingControl != nil {
+ PagingControl.PagingSize = 0
+ l.Search( SearchRequest )
+ }
+
+ return SearchResult, nil
+}
+
+func (l *Conn) Search( SearchRequest *SearchRequest ) (*SearchResult, *Error) {
+ messageID := l.nextMessageID()
+
+ packet := ber.Encode( ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request" )
+ packet.AppendChild( ber.NewInteger( ber.ClassUniversal, ber.TypePrimative, ber.TagInteger, messageID, "MessageID" ) )
+ searchRequest := ber.Encode( ber.ClassApplication, ber.TypeConstructed, ApplicationSearchRequest, nil, "Search Request" )
+ searchRequest.AppendChild( ber.NewString( ber.ClassUniversal, ber.TypePrimative, ber.TagOctetString, SearchRequest.BaseDN, "Base DN" ) )
+ searchRequest.AppendChild( ber.NewInteger( ber.ClassUniversal, ber.TypePrimative, ber.TagEnumerated, uint64(SearchRequest.Scope), "Scope" ) )
+ searchRequest.AppendChild( ber.NewInteger( ber.ClassUniversal, ber.TypePrimative, ber.TagEnumerated, uint64(SearchRequest.DerefAliases), "Deref Aliases" ) )
+ searchRequest.AppendChild( ber.NewInteger( ber.ClassUniversal, ber.TypePrimative, ber.TagInteger, uint64(SearchRequest.SizeLimit), "Size Limit" ) )
+ searchRequest.AppendChild( ber.NewInteger( ber.ClassUniversal, ber.TypePrimative, ber.TagInteger, uint64(SearchRequest.TimeLimit), "Time Limit" ) )
+ searchRequest.AppendChild( ber.NewBoolean( ber.ClassUniversal, ber.TypePrimative, ber.TagBoolean, SearchRequest.TypesOnly, "Types Only" ) )
+ filterPacket, err := CompileFilter( SearchRequest.Filter )
+ if err != nil {
+ return nil, err
+ }
+ searchRequest.AppendChild( filterPacket )
+ attributesPacket := ber.Encode( ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes" )
+ for _, attribute := range SearchRequest.Attributes {
+ attributesPacket.AppendChild( ber.NewString( ber.ClassUniversal, ber.TypePrimative, ber.TagOctetString, attribute, "Attribute" ) )
+ }
+ searchRequest.AppendChild( attributesPacket )
+ packet.AppendChild( searchRequest )
+ if SearchRequest.Controls != nil {
+ packet.AppendChild( encodeControls( SearchRequest.Controls ) )
+ }
+
+ if l.Debug {
+ ber.PrintPacket( packet )
+ }
+
+ channel, err := l.sendMessage( packet )
+ if err != nil {
+ return nil, err
+ }
+ if channel == nil {
+ return nil, NewError( ErrorNetwork, os.NewError( "Could not send message" ) )
+ }
+ defer l.finishMessage( messageID )
+
+ result := new( SearchResult )
+
+ foundSearchResultDone := false
+ for !foundSearchResultDone {
+ if l.Debug {
+ fmt.Printf( "%d: waiting for response\n", messageID )
+ }
+ packet = <-channel
+ if l.Debug {
+ fmt.Printf( "%d: got response\n", messageID, packet )
+ }
+ if packet == nil {
+ return nil, NewError( ErrorNetwork, os.NewError( "Could not retrieve message" ) )
+ }
+
+ if l.Debug {
+ if err := addLDAPDescriptions( packet ); err != nil {
+ return nil, NewError( ErrorDebugging, err )
+ }
+ ber.PrintPacket( packet )
+ }
+
+ switch packet.Children[ 1 ].Tag {
+ case 4:
+ entry := new( Entry )
+ entry.DN = packet.Children[ 1 ].Children[ 0 ].Value.(string)
+ for _, child := range packet.Children[ 1 ].Children[ 1 ].Children {
+ attr := new( EntryAttribute )
+ attr.Name = child.Children[ 0 ].Value.(string)
+ for _, value := range child.Children[ 1 ].Children {
+ attr.Values = append( attr.Values, value.Value.(string) )
+ }
+ entry.Attributes = append( entry.Attributes, attr )
+ }
+ result.Entries = append( result.Entries, entry )
+ case 5:
+ if len( packet.Children ) == 3 {
+ for _, child := range packet.Children[ 2 ].Children {
+ result.Controls = append( result.Controls, DecodeControl( child ) )
+ }
+ }
+ foundSearchResultDone = true
+ case 19:
+ result.Referrals = append( result.Referrals, packet.Children[ 1 ].Children[ 0 ].Value.(string) )
+ }
+ }
+
+ return result, nil
+}