1// File contains Search functionality
2//
3// https://tools.ietf.org/html/rfc4511
4//
5//         SearchRequest ::= [APPLICATION 3] SEQUENCE {
6//              baseObject      LDAPDN,
7//              scope           ENUMERATED {
8//                   baseObject              (0),
9//                   singleLevel             (1),
10//                   wholeSubtree            (2),
11//                   ...  },
12//              derefAliases    ENUMERATED {
13//                   neverDerefAliases       (0),
14//                   derefInSearching        (1),
15//                   derefFindingBaseObj     (2),
16//                   derefAlways             (3) },
17//              sizeLimit       INTEGER (0 ..  maxInt),
18//              timeLimit       INTEGER (0 ..  maxInt),
19//              typesOnly       BOOLEAN,
20//              filter          Filter,
21//              attributes      AttributeSelection }
22//
23//         AttributeSelection ::= SEQUENCE OF selector LDAPString
24//                         -- The LDAPString is constrained to
25//                         -- <attributeSelector> in Section 4.5.1.8
26//
27//         Filter ::= CHOICE {
28//              and             [0] SET SIZE (1..MAX) OF filter Filter,
29//              or              [1] SET SIZE (1..MAX) OF filter Filter,
30//              not             [2] Filter,
31//              equalityMatch   [3] AttributeValueAssertion,
32//              substrings      [4] SubstringFilter,
33//              greaterOrEqual  [5] AttributeValueAssertion,
34//              lessOrEqual     [6] AttributeValueAssertion,
35//              present         [7] AttributeDescription,
36//              approxMatch     [8] AttributeValueAssertion,
37//              extensibleMatch [9] MatchingRuleAssertion,
38//              ...  }
39//
40//         SubstringFilter ::= SEQUENCE {
41//              type           AttributeDescription,
42//              substrings     SEQUENCE SIZE (1..MAX) OF substring CHOICE {
43//                   initial [0] AssertionValue,  -- can occur at most once
44//                   any     [1] AssertionValue,
45//                   final   [2] AssertionValue } -- can occur at most once
46//              }
47//
48//         MatchingRuleAssertion ::= SEQUENCE {
49//              matchingRule    [1] MatchingRuleId OPTIONAL,
50//              type            [2] AttributeDescription OPTIONAL,
51//              matchValue      [3] AssertionValue,
52//              dnAttributes    [4] BOOLEAN DEFAULT FALSE }
53//
54//
55
56package ldap
57
58import (
59	"errors"
60	"fmt"
61	"sort"
62	"strings"
63
64	"gopkg.in/asn1-ber.v1"
65)
66
67// scope choices
68const (
69	ScopeBaseObject   = 0
70	ScopeSingleLevel  = 1
71	ScopeWholeSubtree = 2
72)
73
74// ScopeMap contains human readable descriptions of scope choices
75var ScopeMap = map[int]string{
76	ScopeBaseObject:   "Base Object",
77	ScopeSingleLevel:  "Single Level",
78	ScopeWholeSubtree: "Whole Subtree",
79}
80
81// derefAliases
82const (
83	NeverDerefAliases   = 0
84	DerefInSearching    = 1
85	DerefFindingBaseObj = 2
86	DerefAlways         = 3
87)
88
89// DerefMap contains human readable descriptions of derefAliases choices
90var DerefMap = map[int]string{
91	NeverDerefAliases:   "NeverDerefAliases",
92	DerefInSearching:    "DerefInSearching",
93	DerefFindingBaseObj: "DerefFindingBaseObj",
94	DerefAlways:         "DerefAlways",
95}
96
97// NewEntry returns an Entry object with the specified distinguished name and attribute key-value pairs.
98// The map of attributes is accessed in alphabetical order of the keys in order to ensure that, for the
99// same input map of attributes, the output entry will contain the same order of attributes
100func NewEntry(dn string, attributes map[string][]string) *Entry {
101	var attributeNames []string
102	for attributeName := range attributes {
103		attributeNames = append(attributeNames, attributeName)
104	}
105	sort.Strings(attributeNames)
106
107	var encodedAttributes []*EntryAttribute
108	for _, attributeName := range attributeNames {
109		encodedAttributes = append(encodedAttributes, NewEntryAttribute(attributeName, attributes[attributeName]))
110	}
111	return &Entry{
112		DN:         dn,
113		Attributes: encodedAttributes,
114	}
115}
116
117// Entry represents a single search result entry
118type Entry struct {
119	// DN is the distinguished name of the entry
120	DN string
121	// Attributes are the returned attributes for the entry
122	Attributes []*EntryAttribute
123}
124
125// GetAttributeValues returns the values for the named attribute, or an empty list
126func (e *Entry) GetAttributeValues(attribute string) []string {
127	for _, attr := range e.Attributes {
128		if attr.Name == attribute {
129			return attr.Values
130		}
131	}
132	return []string{}
133}
134
135// GetRawAttributeValues returns the byte values for the named attribute, or an empty list
136func (e *Entry) GetRawAttributeValues(attribute string) [][]byte {
137	for _, attr := range e.Attributes {
138		if attr.Name == attribute {
139			return attr.ByteValues
140		}
141	}
142	return [][]byte{}
143}
144
145// GetAttributeValue returns the first value for the named attribute, or ""
146func (e *Entry) GetAttributeValue(attribute string) string {
147	values := e.GetAttributeValues(attribute)
148	if len(values) == 0 {
149		return ""
150	}
151	return values[0]
152}
153
154// GetRawAttributeValue returns the first value for the named attribute, or an empty slice
155func (e *Entry) GetRawAttributeValue(attribute string) []byte {
156	values := e.GetRawAttributeValues(attribute)
157	if len(values) == 0 {
158		return []byte{}
159	}
160	return values[0]
161}
162
163// Print outputs a human-readable description
164func (e *Entry) Print() {
165	fmt.Printf("DN: %s\n", e.DN)
166	for _, attr := range e.Attributes {
167		attr.Print()
168	}
169}
170
171// PrettyPrint outputs a human-readable description indenting
172func (e *Entry) PrettyPrint(indent int) {
173	fmt.Printf("%sDN: %s\n", strings.Repeat(" ", indent), e.DN)
174	for _, attr := range e.Attributes {
175		attr.PrettyPrint(indent + 2)
176	}
177}
178
179// NewEntryAttribute returns a new EntryAttribute with the desired key-value pair
180func NewEntryAttribute(name string, values []string) *EntryAttribute {
181	var bytes [][]byte
182	for _, value := range values {
183		bytes = append(bytes, []byte(value))
184	}
185	return &EntryAttribute{
186		Name:       name,
187		Values:     values,
188		ByteValues: bytes,
189	}
190}
191
192// EntryAttribute holds a single attribute
193type EntryAttribute struct {
194	// Name is the name of the attribute
195	Name string
196	// Values contain the string values of the attribute
197	Values []string
198	// ByteValues contain the raw values of the attribute
199	ByteValues [][]byte
200}
201
202// Print outputs a human-readable description
203func (e *EntryAttribute) Print() {
204	fmt.Printf("%s: %s\n", e.Name, e.Values)
205}
206
207// PrettyPrint outputs a human-readable description with indenting
208func (e *EntryAttribute) PrettyPrint(indent int) {
209	fmt.Printf("%s%s: %s\n", strings.Repeat(" ", indent), e.Name, e.Values)
210}
211
212// SearchResult holds the server's response to a search request
213type SearchResult struct {
214	// Entries are the returned entries
215	Entries []*Entry
216	// Referrals are the returned referrals
217	Referrals []string
218	// Controls are the returned controls
219	Controls []Control
220}
221
222// Print outputs a human-readable description
223func (s *SearchResult) Print() {
224	for _, entry := range s.Entries {
225		entry.Print()
226	}
227}
228
229// PrettyPrint outputs a human-readable description with indenting
230func (s *SearchResult) PrettyPrint(indent int) {
231	for _, entry := range s.Entries {
232		entry.PrettyPrint(indent)
233	}
234}
235
236// SearchRequest represents a search request to send to the server
237type SearchRequest struct {
238	BaseDN       string
239	Scope        int
240	DerefAliases int
241	SizeLimit    int
242	TimeLimit    int
243	TypesOnly    bool
244	Filter       string
245	Attributes   []string
246	Controls     []Control
247}
248
249func (s *SearchRequest) encode() (*ber.Packet, error) {
250	request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationSearchRequest, nil, "Search Request")
251	request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, s.BaseDN, "Base DN"))
252	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.Scope), "Scope"))
253	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.DerefAliases), "Deref Aliases"))
254	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.SizeLimit), "Size Limit"))
255	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.TimeLimit), "Time Limit"))
256	request.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, s.TypesOnly, "Types Only"))
257	// compile and encode filter
258	filterPacket, err := CompileFilter(s.Filter)
259	if err != nil {
260		return nil, err
261	}
262	request.AppendChild(filterPacket)
263	// encode attributes
264	attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
265	for _, attribute := range s.Attributes {
266		attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
267	}
268	request.AppendChild(attributesPacket)
269	return request, nil
270}
271
272// NewSearchRequest creates a new search request
273func NewSearchRequest(
274	BaseDN string,
275	Scope, DerefAliases, SizeLimit, TimeLimit int,
276	TypesOnly bool,
277	Filter string,
278	Attributes []string,
279	Controls []Control,
280) *SearchRequest {
281	return &SearchRequest{
282		BaseDN:       BaseDN,
283		Scope:        Scope,
284		DerefAliases: DerefAliases,
285		SizeLimit:    SizeLimit,
286		TimeLimit:    TimeLimit,
287		TypesOnly:    TypesOnly,
288		Filter:       Filter,
289		Attributes:   Attributes,
290		Controls:     Controls,
291	}
292}
293
294// SearchWithPaging accepts a search request and desired page size in order to execute LDAP queries to fulfill the
295// search request. All paged LDAP query responses will be buffered and the final result will be returned atomically.
296// The following four cases are possible given the arguments:
297//  - given SearchRequest missing a control of type ControlTypePaging: we will add one with the desired paging size
298//  - given SearchRequest contains a control of type ControlTypePaging that isn't actually a ControlPaging: fail without issuing any queries
299//  - given SearchRequest contains a control of type ControlTypePaging with pagingSize equal to the size requested: no change to the search request
300//  - given SearchRequest contains a control of type ControlTypePaging with pagingSize not equal to the size requested: fail without issuing any queries
301// A requested pagingSize of 0 is interpreted as no limit by LDAP servers.
302func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) {
303	var pagingControl *ControlPaging
304
305	control := FindControl(searchRequest.Controls, ControlTypePaging)
306	if control == nil {
307		pagingControl = NewControlPaging(pagingSize)
308		searchRequest.Controls = append(searchRequest.Controls, pagingControl)
309	} else {
310		castControl, ok := control.(*ControlPaging)
311		if !ok {
312			return nil, fmt.Errorf("expected paging control to be of type *ControlPaging, got %v", control)
313		}
314		if castControl.PagingSize != pagingSize {
315			return nil, fmt.Errorf("paging size given in search request (%d) conflicts with size given in search call (%d)", castControl.PagingSize, pagingSize)
316		}
317		pagingControl = castControl
318	}
319
320	searchResult := new(SearchResult)
321	for {
322		result, err := l.Search(searchRequest)
323		l.Debug.Printf("Looking for Paging Control...")
324		if err != nil {
325			return searchResult, err
326		}
327		if result == nil {
328			return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
329		}
330
331		for _, entry := range result.Entries {
332			searchResult.Entries = append(searchResult.Entries, entry)
333		}
334		for _, referral := range result.Referrals {
335			searchResult.Referrals = append(searchResult.Referrals, referral)
336		}
337		for _, control := range result.Controls {
338			searchResult.Controls = append(searchResult.Controls, control)
339		}
340
341		l.Debug.Printf("Looking for Paging Control...")
342		pagingResult := FindControl(result.Controls, ControlTypePaging)
343		if pagingResult == nil {
344			pagingControl = nil
345			l.Debug.Printf("Could not find paging control.  Breaking...")
346			break
347		}
348
349		cookie := pagingResult.(*ControlPaging).Cookie
350		if len(cookie) == 0 {
351			pagingControl = nil
352			l.Debug.Printf("Could not find cookie.  Breaking...")
353			break
354		}
355		pagingControl.SetCookie(cookie)
356	}
357
358	if pagingControl != nil {
359		l.Debug.Printf("Abandoning Paging...")
360		pagingControl.PagingSize = 0
361		l.Search(searchRequest)
362	}
363
364	return searchResult, nil
365}
366
367// Search performs the given search request
368func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
369	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
370	packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
371	// encode search request
372	encodedSearchRequest, err := searchRequest.encode()
373	if err != nil {
374		return nil, err
375	}
376	packet.AppendChild(encodedSearchRequest)
377	// encode search controls
378	if len(searchRequest.Controls) > 0 {
379		packet.AppendChild(encodeControls(searchRequest.Controls))
380	}
381
382	l.Debug.PrintPacket(packet)
383
384	msgCtx, err := l.sendMessage(packet)
385	if err != nil {
386		return nil, err
387	}
388	defer l.finishMessage(msgCtx)
389
390	result := &SearchResult{
391		Entries:   make([]*Entry, 0),
392		Referrals: make([]string, 0),
393		Controls:  make([]Control, 0)}
394
395	foundSearchResultDone := false
396	for !foundSearchResultDone {
397		l.Debug.Printf("%d: waiting for response", msgCtx.id)
398		packetResponse, ok := <-msgCtx.responses
399		if !ok {
400			return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
401		}
402		packet, err = packetResponse.ReadPacket()
403		l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
404		if err != nil {
405			return nil, err
406		}
407
408		if l.Debug {
409			if err := addLDAPDescriptions(packet); err != nil {
410				return nil, err
411			}
412			ber.PrintPacket(packet)
413		}
414
415		switch packet.Children[1].Tag {
416		case 4:
417			entry := new(Entry)
418			entry.DN = packet.Children[1].Children[0].Value.(string)
419			for _, child := range packet.Children[1].Children[1].Children {
420				attr := new(EntryAttribute)
421				attr.Name = child.Children[0].Value.(string)
422				for _, value := range child.Children[1].Children {
423					attr.Values = append(attr.Values, value.Value.(string))
424					attr.ByteValues = append(attr.ByteValues, value.ByteValue)
425				}
426				entry.Attributes = append(entry.Attributes, attr)
427			}
428			result.Entries = append(result.Entries, entry)
429		case 5:
430			err := GetLDAPError(packet)
431			if err != nil {
432				return nil, err
433			}
434			if len(packet.Children) == 3 {
435				for _, child := range packet.Children[2].Children {
436					decodedChild, err := DecodeControl(child)
437					if err != nil {
438						return nil, fmt.Errorf("failed to decode child control: %s", err)
439					}
440					result.Controls = append(result.Controls, decodedChild)
441				}
442			}
443			foundSearchResultDone = true
444		case 19:
445			result.Referrals = append(result.Referrals, packet.Children[1].Children[0].Value.(string))
446		}
447	}
448	l.Debug.Printf("%d: returning", msgCtx.id)
449	return result, nil
450}
451