22770c324d
Signed-off-by: Andrew Thornton <art27@cantab.net>
450 lines
15 KiB
Go
450 lines
15 KiB
Go
// File contains Search functionality
|
|
//
|
|
// https://tools.ietf.org/html/rfc4511
|
|
//
|
|
// SearchRequest ::= [APPLICATION 3] SEQUENCE {
|
|
// baseObject LDAPDN,
|
|
// scope ENUMERATED {
|
|
// baseObject (0),
|
|
// singleLevel (1),
|
|
// wholeSubtree (2),
|
|
// ... },
|
|
// derefAliases ENUMERATED {
|
|
// neverDerefAliases (0),
|
|
// derefInSearching (1),
|
|
// derefFindingBaseObj (2),
|
|
// derefAlways (3) },
|
|
// sizeLimit INTEGER (0 .. maxInt),
|
|
// timeLimit INTEGER (0 .. maxInt),
|
|
// typesOnly BOOLEAN,
|
|
// filter Filter,
|
|
// attributes AttributeSelection }
|
|
//
|
|
// AttributeSelection ::= SEQUENCE OF selector LDAPString
|
|
// -- The LDAPString is constrained to
|
|
// -- <attributeSelector> in Section 4.5.1.8
|
|
//
|
|
// Filter ::= CHOICE {
|
|
// and [0] SET SIZE (1..MAX) OF filter Filter,
|
|
// or [1] SET SIZE (1..MAX) OF filter Filter,
|
|
// not [2] Filter,
|
|
// equalityMatch [3] AttributeValueAssertion,
|
|
// substrings [4] SubstringFilter,
|
|
// greaterOrEqual [5] AttributeValueAssertion,
|
|
// lessOrEqual [6] AttributeValueAssertion,
|
|
// present [7] AttributeDescription,
|
|
// approxMatch [8] AttributeValueAssertion,
|
|
// extensibleMatch [9] MatchingRuleAssertion,
|
|
// ... }
|
|
//
|
|
// SubstringFilter ::= SEQUENCE {
|
|
// type AttributeDescription,
|
|
// substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE {
|
|
// initial [0] AssertionValue, -- can occur at most once
|
|
// any [1] AssertionValue,
|
|
// final [2] AssertionValue } -- can occur at most once
|
|
// }
|
|
//
|
|
// MatchingRuleAssertion ::= SEQUENCE {
|
|
// matchingRule [1] MatchingRuleId OPTIONAL,
|
|
// type [2] AttributeDescription OPTIONAL,
|
|
// matchValue [3] AssertionValue,
|
|
// dnAttributes [4] BOOLEAN DEFAULT FALSE }
|
|
//
|
|
//
|
|
|
|
package ldap
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"gopkg.in/asn1-ber.v1"
|
|
)
|
|
|
|
// scope choices
|
|
const (
|
|
ScopeBaseObject = 0
|
|
ScopeSingleLevel = 1
|
|
ScopeWholeSubtree = 2
|
|
)
|
|
|
|
// ScopeMap contains human readable descriptions of scope choices
|
|
var ScopeMap = map[int]string{
|
|
ScopeBaseObject: "Base Object",
|
|
ScopeSingleLevel: "Single Level",
|
|
ScopeWholeSubtree: "Whole Subtree",
|
|
}
|
|
|
|
// derefAliases
|
|
const (
|
|
NeverDerefAliases = 0
|
|
DerefInSearching = 1
|
|
DerefFindingBaseObj = 2
|
|
DerefAlways = 3
|
|
)
|
|
|
|
// DerefMap contains human readable descriptions of derefAliases choices
|
|
var DerefMap = map[int]string{
|
|
NeverDerefAliases: "NeverDerefAliases",
|
|
DerefInSearching: "DerefInSearching",
|
|
DerefFindingBaseObj: "DerefFindingBaseObj",
|
|
DerefAlways: "DerefAlways",
|
|
}
|
|
|
|
// NewEntry returns an Entry object with the specified distinguished name and attribute key-value pairs.
|
|
// The map of attributes is accessed in alphabetical order of the keys in order to ensure that, for the
|
|
// same input map of attributes, the output entry will contain the same order of attributes
|
|
func NewEntry(dn string, attributes map[string][]string) *Entry {
|
|
var attributeNames []string
|
|
for attributeName := range attributes {
|
|
attributeNames = append(attributeNames, attributeName)
|
|
}
|
|
sort.Strings(attributeNames)
|
|
|
|
var encodedAttributes []*EntryAttribute
|
|
for _, attributeName := range attributeNames {
|
|
encodedAttributes = append(encodedAttributes, NewEntryAttribute(attributeName, attributes[attributeName]))
|
|
}
|
|
return &Entry{
|
|
DN: dn,
|
|
Attributes: encodedAttributes,
|
|
}
|
|
}
|
|
|
|
// Entry represents a single search result entry
|
|
type Entry struct {
|
|
// DN is the distinguished name of the entry
|
|
DN string
|
|
// Attributes are the returned attributes for the entry
|
|
Attributes []*EntryAttribute
|
|
}
|
|
|
|
// GetAttributeValues returns the values for the named attribute, or an empty list
|
|
func (e *Entry) GetAttributeValues(attribute string) []string {
|
|
for _, attr := range e.Attributes {
|
|
if attr.Name == attribute {
|
|
return attr.Values
|
|
}
|
|
}
|
|
return []string{}
|
|
}
|
|
|
|
// GetRawAttributeValues returns the byte values for the named attribute, or an empty list
|
|
func (e *Entry) GetRawAttributeValues(attribute string) [][]byte {
|
|
for _, attr := range e.Attributes {
|
|
if attr.Name == attribute {
|
|
return attr.ByteValues
|
|
}
|
|
}
|
|
return [][]byte{}
|
|
}
|
|
|
|
// GetAttributeValue returns the first value for the named attribute, or ""
|
|
func (e *Entry) GetAttributeValue(attribute string) string {
|
|
values := e.GetAttributeValues(attribute)
|
|
if len(values) == 0 {
|
|
return ""
|
|
}
|
|
return values[0]
|
|
}
|
|
|
|
// GetRawAttributeValue returns the first value for the named attribute, or an empty slice
|
|
func (e *Entry) GetRawAttributeValue(attribute string) []byte {
|
|
values := e.GetRawAttributeValues(attribute)
|
|
if len(values) == 0 {
|
|
return []byte{}
|
|
}
|
|
return values[0]
|
|
}
|
|
|
|
// Print outputs a human-readable description
|
|
func (e *Entry) Print() {
|
|
fmt.Printf("DN: %s\n", e.DN)
|
|
for _, attr := range e.Attributes {
|
|
attr.Print()
|
|
}
|
|
}
|
|
|
|
// PrettyPrint outputs a human-readable description indenting
|
|
func (e *Entry) PrettyPrint(indent int) {
|
|
fmt.Printf("%sDN: %s\n", strings.Repeat(" ", indent), e.DN)
|
|
for _, attr := range e.Attributes {
|
|
attr.PrettyPrint(indent + 2)
|
|
}
|
|
}
|
|
|
|
// NewEntryAttribute returns a new EntryAttribute with the desired key-value pair
|
|
func NewEntryAttribute(name string, values []string) *EntryAttribute {
|
|
var bytes [][]byte
|
|
for _, value := range values {
|
|
bytes = append(bytes, []byte(value))
|
|
}
|
|
return &EntryAttribute{
|
|
Name: name,
|
|
Values: values,
|
|
ByteValues: bytes,
|
|
}
|
|
}
|
|
|
|
// EntryAttribute holds a single attribute
|
|
type EntryAttribute struct {
|
|
// Name is the name of the attribute
|
|
Name string
|
|
// Values contain the string values of the attribute
|
|
Values []string
|
|
// ByteValues contain the raw values of the attribute
|
|
ByteValues [][]byte
|
|
}
|
|
|
|
// Print outputs a human-readable description
|
|
func (e *EntryAttribute) Print() {
|
|
fmt.Printf("%s: %s\n", e.Name, e.Values)
|
|
}
|
|
|
|
// PrettyPrint outputs a human-readable description with indenting
|
|
func (e *EntryAttribute) PrettyPrint(indent int) {
|
|
fmt.Printf("%s%s: %s\n", strings.Repeat(" ", indent), e.Name, e.Values)
|
|
}
|
|
|
|
// SearchResult holds the server's response to a search request
|
|
type SearchResult struct {
|
|
// Entries are the returned entries
|
|
Entries []*Entry
|
|
// Referrals are the returned referrals
|
|
Referrals []string
|
|
// Controls are the returned controls
|
|
Controls []Control
|
|
}
|
|
|
|
// Print outputs a human-readable description
|
|
func (s *SearchResult) Print() {
|
|
for _, entry := range s.Entries {
|
|
entry.Print()
|
|
}
|
|
}
|
|
|
|
// PrettyPrint outputs a human-readable description with indenting
|
|
func (s *SearchResult) PrettyPrint(indent int) {
|
|
for _, entry := range s.Entries {
|
|
entry.PrettyPrint(indent)
|
|
}
|
|
}
|
|
|
|
// SearchRequest represents a search request to send to the server
|
|
type SearchRequest struct {
|
|
BaseDN string
|
|
Scope int
|
|
DerefAliases int
|
|
SizeLimit int
|
|
TimeLimit int
|
|
TypesOnly bool
|
|
Filter string
|
|
Attributes []string
|
|
Controls []Control
|
|
}
|
|
|
|
func (s *SearchRequest) encode() (*ber.Packet, error) {
|
|
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationSearchRequest, nil, "Search Request")
|
|
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, s.BaseDN, "Base DN"))
|
|
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.Scope), "Scope"))
|
|
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.DerefAliases), "Deref Aliases"))
|
|
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.SizeLimit), "Size Limit"))
|
|
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.TimeLimit), "Time Limit"))
|
|
request.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, s.TypesOnly, "Types Only"))
|
|
// compile and encode filter
|
|
filterPacket, err := CompileFilter(s.Filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
request.AppendChild(filterPacket)
|
|
// encode attributes
|
|
attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
|
|
for _, attribute := range s.Attributes {
|
|
attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
|
|
}
|
|
request.AppendChild(attributesPacket)
|
|
return request, nil
|
|
}
|
|
|
|
// NewSearchRequest creates a new search request
|
|
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,
|
|
}
|
|
}
|
|
|
|
// SearchWithPaging accepts a search request and desired page size in order to execute LDAP queries to fulfill the
|
|
// search request. All paged LDAP query responses will be buffered and the final result will be returned atomically.
|
|
// The following four cases are possible given the arguments:
|
|
// - given SearchRequest missing a control of type ControlTypePaging: we will add one with the desired paging size
|
|
// - given SearchRequest contains a control of type ControlTypePaging that isn't actually a ControlPaging: fail without issuing any queries
|
|
// - given SearchRequest contains a control of type ControlTypePaging with pagingSize equal to the size requested: no change to the search request
|
|
// - given SearchRequest contains a control of type ControlTypePaging with pagingSize not equal to the size requested: fail without issuing any queries
|
|
// A requested pagingSize of 0 is interpreted as no limit by LDAP servers.
|
|
func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) {
|
|
var pagingControl *ControlPaging
|
|
|
|
control := FindControl(searchRequest.Controls, ControlTypePaging)
|
|
if control == nil {
|
|
pagingControl = NewControlPaging(pagingSize)
|
|
searchRequest.Controls = append(searchRequest.Controls, pagingControl)
|
|
} else {
|
|
castControl, ok := control.(*ControlPaging)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected paging control to be of type *ControlPaging, got %v", control)
|
|
}
|
|
if castControl.PagingSize != pagingSize {
|
|
return nil, fmt.Errorf("paging size given in search request (%d) conflicts with size given in search call (%d)", castControl.PagingSize, pagingSize)
|
|
}
|
|
pagingControl = castControl
|
|
}
|
|
|
|
searchResult := new(SearchResult)
|
|
for {
|
|
result, err := l.Search(searchRequest)
|
|
l.Debug.Printf("Looking for Paging Control...")
|
|
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 Paging Control...")
|
|
pagingResult := FindControl(result.Controls, ControlTypePaging)
|
|
if pagingResult == nil {
|
|
pagingControl = nil
|
|
l.Debug.Printf("Could not find paging control. Breaking...")
|
|
break
|
|
}
|
|
|
|
cookie := pagingResult.(*ControlPaging).Cookie
|
|
if len(cookie) == 0 {
|
|
pagingControl = nil
|
|
l.Debug.Printf("Could not find cookie. Breaking...")
|
|
break
|
|
}
|
|
pagingControl.SetCookie(cookie)
|
|
}
|
|
|
|
if pagingControl != nil {
|
|
l.Debug.Printf("Abandoning Paging...")
|
|
pagingControl.PagingSize = 0
|
|
l.Search(searchRequest)
|
|
}
|
|
|
|
return searchResult, nil
|
|
}
|
|
|
|
// Search performs the given search request
|
|
func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
|
|
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
|
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
|
|
// encode search request
|
|
encodedSearchRequest, err := searchRequest.encode()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
packet.AppendChild(encodedSearchRequest)
|
|
// encode search controls
|
|
if len(searchRequest.Controls) > 0 {
|
|
packet.AppendChild(encodeControls(searchRequest.Controls))
|
|
}
|
|
|
|
l.Debug.PrintPacket(packet)
|
|
|
|
msgCtx, err := l.sendMessage(packet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer l.finishMessage(msgCtx)
|
|
|
|
result := &SearchResult{
|
|
Entries: make([]*Entry, 0),
|
|
Referrals: make([]string, 0),
|
|
Controls: make([]Control, 0)}
|
|
|
|
foundSearchResultDone := false
|
|
for !foundSearchResultDone {
|
|
l.Debug.Printf("%d: waiting for response", msgCtx.id)
|
|
packetResponse, ok := <-msgCtx.responses
|
|
if !ok {
|
|
return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
|
|
}
|
|
packet, err = packetResponse.ReadPacket()
|
|
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if l.Debug {
|
|
if err := addLDAPDescriptions(packet); err != nil {
|
|
return nil, 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))
|
|
attr.ByteValues = append(attr.ByteValues, value.ByteValue)
|
|
}
|
|
entry.Attributes = append(entry.Attributes, attr)
|
|
}
|
|
result.Entries = append(result.Entries, entry)
|
|
case 5:
|
|
err := GetLDAPError(packet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(packet.Children) == 3 {
|
|
for _, child := range packet.Children[2].Children {
|
|
decodedChild, err := DecodeControl(child)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode child control: %s", err)
|
|
}
|
|
result.Controls = append(result.Controls, decodedChild)
|
|
}
|
|
}
|
|
foundSearchResultDone = true
|
|
case 19:
|
|
result.Referrals = append(result.Referrals, packet.Children[1].Children[0].Value.(string))
|
|
}
|
|
}
|
|
l.Debug.Printf("%d: returning", msgCtx.id)
|
|
return result, nil
|
|
}
|