1package ldap
2
3import (
4	"fmt"
5	"strconv"
6
7	"gopkg.in/asn1-ber.v1"
8)
9
10const (
11	// ControlTypePaging - https://www.ietf.org/rfc/rfc2696.txt
12	ControlTypePaging = "1.2.840.113556.1.4.319"
13	// ControlTypeBeheraPasswordPolicy - https://tools.ietf.org/html/draft-behera-ldap-password-policy-10
14	ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1"
15	// ControlTypeVChuPasswordMustChange - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
16	ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4"
17	// ControlTypeVChuPasswordWarning - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
18	ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5"
19	// ControlTypeManageDsaIT - https://tools.ietf.org/html/rfc3296
20	ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2"
21
22	// ControlTypeMicrosoftNotification - https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx
23	ControlTypeMicrosoftNotification = "1.2.840.113556.1.4.528"
24	// ControlTypeMicrosoftShowDeleted - https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx
25	ControlTypeMicrosoftShowDeleted = "1.2.840.113556.1.4.417"
26)
27
28// ControlTypeMap maps controls to text descriptions
29var ControlTypeMap = map[string]string{
30	ControlTypePaging:                "Paging",
31	ControlTypeBeheraPasswordPolicy:  "Password Policy - Behera Draft",
32	ControlTypeManageDsaIT:           "Manage DSA IT",
33	ControlTypeMicrosoftNotification: "Change Notification - Microsoft",
34	ControlTypeMicrosoftShowDeleted:  "Show Deleted Objects - Microsoft",
35}
36
37// Control defines an interface controls provide to encode and describe themselves
38type Control interface {
39	// GetControlType returns the OID
40	GetControlType() string
41	// Encode returns the ber packet representation
42	Encode() *ber.Packet
43	// String returns a human-readable description
44	String() string
45}
46
47// ControlString implements the Control interface for simple controls
48type ControlString struct {
49	ControlType  string
50	Criticality  bool
51	ControlValue string
52}
53
54// GetControlType returns the OID
55func (c *ControlString) GetControlType() string {
56	return c.ControlType
57}
58
59// Encode returns the ber packet representation
60func (c *ControlString) Encode() *ber.Packet {
61	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
62	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.ControlType, "Control Type ("+ControlTypeMap[c.ControlType]+")"))
63	if c.Criticality {
64		packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality"))
65	}
66	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, string(c.ControlValue), "Control Value"))
67	return packet
68}
69
70// String returns a human-readable description
71func (c *ControlString) String() string {
72	return fmt.Sprintf("Control Type: %s (%q)  Criticality: %t  Control Value: %s", ControlTypeMap[c.ControlType], c.ControlType, c.Criticality, c.ControlValue)
73}
74
75// ControlPaging implements the paging control described in https://www.ietf.org/rfc/rfc2696.txt
76type ControlPaging struct {
77	// PagingSize indicates the page size
78	PagingSize uint32
79	// Cookie is an opaque value returned by the server to track a paging cursor
80	Cookie []byte
81}
82
83// GetControlType returns the OID
84func (c *ControlPaging) GetControlType() string {
85	return ControlTypePaging
86}
87
88// Encode returns the ber packet representation
89func (c *ControlPaging) Encode() *ber.Packet {
90	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
91	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypePaging, "Control Type ("+ControlTypeMap[ControlTypePaging]+")"))
92
93	p2 := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (Paging)")
94	seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Search Control Value")
95	seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.PagingSize), "Paging Size"))
96	cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie")
97	cookie.Value = c.Cookie
98	cookie.Data.Write(c.Cookie)
99	seq.AppendChild(cookie)
100	p2.AppendChild(seq)
101
102	packet.AppendChild(p2)
103	return packet
104}
105
106// String returns a human-readable description
107func (c *ControlPaging) String() string {
108	return fmt.Sprintf(
109		"Control Type: %s (%q)  Criticality: %t  PagingSize: %d  Cookie: %q",
110		ControlTypeMap[ControlTypePaging],
111		ControlTypePaging,
112		false,
113		c.PagingSize,
114		c.Cookie)
115}
116
117// SetCookie stores the given cookie in the paging control
118func (c *ControlPaging) SetCookie(cookie []byte) {
119	c.Cookie = cookie
120}
121
122// ControlBeheraPasswordPolicy implements the control described in https://tools.ietf.org/html/draft-behera-ldap-password-policy-10
123type ControlBeheraPasswordPolicy struct {
124	// Expire contains the number of seconds before a password will expire
125	Expire int64
126	// Grace indicates the remaining number of times a user will be allowed to authenticate with an expired password
127	Grace int64
128	// Error indicates the error code
129	Error int8
130	// ErrorString is a human readable error
131	ErrorString string
132}
133
134// GetControlType returns the OID
135func (c *ControlBeheraPasswordPolicy) GetControlType() string {
136	return ControlTypeBeheraPasswordPolicy
137}
138
139// Encode returns the ber packet representation
140func (c *ControlBeheraPasswordPolicy) Encode() *ber.Packet {
141	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
142	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeBeheraPasswordPolicy, "Control Type ("+ControlTypeMap[ControlTypeBeheraPasswordPolicy]+")"))
143
144	return packet
145}
146
147// String returns a human-readable description
148func (c *ControlBeheraPasswordPolicy) String() string {
149	return fmt.Sprintf(
150		"Control Type: %s (%q)  Criticality: %t  Expire: %d  Grace: %d  Error: %d, ErrorString: %s",
151		ControlTypeMap[ControlTypeBeheraPasswordPolicy],
152		ControlTypeBeheraPasswordPolicy,
153		false,
154		c.Expire,
155		c.Grace,
156		c.Error,
157		c.ErrorString)
158}
159
160// ControlVChuPasswordMustChange implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
161type ControlVChuPasswordMustChange struct {
162	// MustChange indicates if the password is required to be changed
163	MustChange bool
164}
165
166// GetControlType returns the OID
167func (c *ControlVChuPasswordMustChange) GetControlType() string {
168	return ControlTypeVChuPasswordMustChange
169}
170
171// Encode returns the ber packet representation
172func (c *ControlVChuPasswordMustChange) Encode() *ber.Packet {
173	return nil
174}
175
176// String returns a human-readable description
177func (c *ControlVChuPasswordMustChange) String() string {
178	return fmt.Sprintf(
179		"Control Type: %s (%q)  Criticality: %t  MustChange: %v",
180		ControlTypeMap[ControlTypeVChuPasswordMustChange],
181		ControlTypeVChuPasswordMustChange,
182		false,
183		c.MustChange)
184}
185
186// ControlVChuPasswordWarning implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
187type ControlVChuPasswordWarning struct {
188	// Expire indicates the time in seconds until the password expires
189	Expire int64
190}
191
192// GetControlType returns the OID
193func (c *ControlVChuPasswordWarning) GetControlType() string {
194	return ControlTypeVChuPasswordWarning
195}
196
197// Encode returns the ber packet representation
198func (c *ControlVChuPasswordWarning) Encode() *ber.Packet {
199	return nil
200}
201
202// String returns a human-readable description
203func (c *ControlVChuPasswordWarning) String() string {
204	return fmt.Sprintf(
205		"Control Type: %s (%q)  Criticality: %t  Expire: %b",
206		ControlTypeMap[ControlTypeVChuPasswordWarning],
207		ControlTypeVChuPasswordWarning,
208		false,
209		c.Expire)
210}
211
212// ControlManageDsaIT implements the control described in https://tools.ietf.org/html/rfc3296
213type ControlManageDsaIT struct {
214	// Criticality indicates if this control is required
215	Criticality bool
216}
217
218// GetControlType returns the OID
219func (c *ControlManageDsaIT) GetControlType() string {
220	return ControlTypeManageDsaIT
221}
222
223// Encode returns the ber packet representation
224func (c *ControlManageDsaIT) Encode() *ber.Packet {
225	//FIXME
226	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
227	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeManageDsaIT, "Control Type ("+ControlTypeMap[ControlTypeManageDsaIT]+")"))
228	if c.Criticality {
229		packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality"))
230	}
231	return packet
232}
233
234// String returns a human-readable description
235func (c *ControlManageDsaIT) String() string {
236	return fmt.Sprintf(
237		"Control Type: %s (%q)  Criticality: %t",
238		ControlTypeMap[ControlTypeManageDsaIT],
239		ControlTypeManageDsaIT,
240		c.Criticality)
241}
242
243// NewControlManageDsaIT returns a ControlManageDsaIT control
244func NewControlManageDsaIT(Criticality bool) *ControlManageDsaIT {
245	return &ControlManageDsaIT{Criticality: Criticality}
246}
247
248// ControlMicrosoftNotification implements the control described in https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx
249type ControlMicrosoftNotification struct{}
250
251// GetControlType returns the OID
252func (c *ControlMicrosoftNotification) GetControlType() string {
253	return ControlTypeMicrosoftNotification
254}
255
256// Encode returns the ber packet representation
257func (c *ControlMicrosoftNotification) Encode() *ber.Packet {
258	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
259	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftNotification, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftNotification]+")"))
260
261	return packet
262}
263
264// String returns a human-readable description
265func (c *ControlMicrosoftNotification) String() string {
266	return fmt.Sprintf(
267		"Control Type: %s (%q)",
268		ControlTypeMap[ControlTypeMicrosoftNotification],
269		ControlTypeMicrosoftNotification)
270}
271
272// NewControlMicrosoftNotification returns a ControlMicrosoftNotification control
273func NewControlMicrosoftNotification() *ControlMicrosoftNotification {
274	return &ControlMicrosoftNotification{}
275}
276
277// ControlMicrosoftShowDeleted implements the control described in https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx
278type ControlMicrosoftShowDeleted struct{}
279
280// GetControlType returns the OID
281func (c *ControlMicrosoftShowDeleted) GetControlType() string {
282	return ControlTypeMicrosoftShowDeleted
283}
284
285// Encode returns the ber packet representation
286func (c *ControlMicrosoftShowDeleted) Encode() *ber.Packet {
287	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
288	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftShowDeleted, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftShowDeleted]+")"))
289
290	return packet
291}
292
293// String returns a human-readable description
294func (c *ControlMicrosoftShowDeleted) String() string {
295	return fmt.Sprintf(
296		"Control Type: %s (%q)",
297		ControlTypeMap[ControlTypeMicrosoftShowDeleted],
298		ControlTypeMicrosoftShowDeleted)
299}
300
301// NewControlMicrosoftShowDeleted returns a ControlMicrosoftShowDeleted control
302func NewControlMicrosoftShowDeleted() *ControlMicrosoftShowDeleted {
303	return &ControlMicrosoftShowDeleted{}
304}
305
306// FindControl returns the first control of the given type in the list, or nil
307func FindControl(controls []Control, controlType string) Control {
308	for _, c := range controls {
309		if c.GetControlType() == controlType {
310			return c
311		}
312	}
313	return nil
314}
315
316// DecodeControl returns a control read from the given packet, or nil if no recognized control can be made
317func DecodeControl(packet *ber.Packet) (Control, error) {
318	var (
319		ControlType = ""
320		Criticality = false
321		value       *ber.Packet
322	)
323
324	switch len(packet.Children) {
325	case 0:
326		// at least one child is required for control type
327		return nil, fmt.Errorf("at least one child is required for control type")
328
329	case 1:
330		// just type, no criticality or value
331		packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
332		ControlType = packet.Children[0].Value.(string)
333
334	case 2:
335		packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
336		ControlType = packet.Children[0].Value.(string)
337
338		// Children[1] could be criticality or value (both are optional)
339		// duck-type on whether this is a boolean
340		if _, ok := packet.Children[1].Value.(bool); ok {
341			packet.Children[1].Description = "Criticality"
342			Criticality = packet.Children[1].Value.(bool)
343		} else {
344			packet.Children[1].Description = "Control Value"
345			value = packet.Children[1]
346		}
347
348	case 3:
349		packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
350		ControlType = packet.Children[0].Value.(string)
351
352		packet.Children[1].Description = "Criticality"
353		Criticality = packet.Children[1].Value.(bool)
354
355		packet.Children[2].Description = "Control Value"
356		value = packet.Children[2]
357
358	default:
359		// more than 3 children is invalid
360		return nil, fmt.Errorf("more than 3 children is invalid for controls")
361	}
362
363	switch ControlType {
364	case ControlTypeManageDsaIT:
365		return NewControlManageDsaIT(Criticality), nil
366	case ControlTypePaging:
367		value.Description += " (Paging)"
368		c := new(ControlPaging)
369		if value.Value != nil {
370			valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
371			if err != nil {
372				return nil, fmt.Errorf("failed to decode data bytes: %s", err)
373			}
374			value.Data.Truncate(0)
375			value.Value = nil
376			value.AppendChild(valueChildren)
377		}
378		value = value.Children[0]
379		value.Description = "Search Control Value"
380		value.Children[0].Description = "Paging Size"
381		value.Children[1].Description = "Cookie"
382		c.PagingSize = uint32(value.Children[0].Value.(int64))
383		c.Cookie = value.Children[1].Data.Bytes()
384		value.Children[1].Value = c.Cookie
385		return c, nil
386	case ControlTypeBeheraPasswordPolicy:
387		value.Description += " (Password Policy - Behera)"
388		c := NewControlBeheraPasswordPolicy()
389		if value.Value != nil {
390			valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
391			if err != nil {
392				return nil, fmt.Errorf("failed to decode data bytes: %s", err)
393			}
394			value.Data.Truncate(0)
395			value.Value = nil
396			value.AppendChild(valueChildren)
397		}
398
399		sequence := value.Children[0]
400
401		for _, child := range sequence.Children {
402			if child.Tag == 0 {
403				//Warning
404				warningPacket := child.Children[0]
405				packet, err := ber.DecodePacketErr(warningPacket.Data.Bytes())
406				if err != nil {
407					return nil, fmt.Errorf("failed to decode data bytes: %s", err)
408				}
409				val, ok := packet.Value.(int64)
410				if ok {
411					if warningPacket.Tag == 0 {
412						//timeBeforeExpiration
413						c.Expire = val
414						warningPacket.Value = c.Expire
415					} else if warningPacket.Tag == 1 {
416						//graceAuthNsRemaining
417						c.Grace = val
418						warningPacket.Value = c.Grace
419					}
420				}
421			} else if child.Tag == 1 {
422				// Error
423				packet, err := ber.DecodePacketErr(child.Data.Bytes())
424				if err != nil {
425					return nil, fmt.Errorf("failed to decode data bytes: %s", err)
426				}
427				val, ok := packet.Value.(int8)
428				if !ok {
429					// what to do?
430					val = -1
431				}
432				c.Error = val
433				child.Value = c.Error
434				c.ErrorString = BeheraPasswordPolicyErrorMap[c.Error]
435			}
436		}
437		return c, nil
438	case ControlTypeVChuPasswordMustChange:
439		c := &ControlVChuPasswordMustChange{MustChange: true}
440		return c, nil
441	case ControlTypeVChuPasswordWarning:
442		c := &ControlVChuPasswordWarning{Expire: -1}
443		expireStr := ber.DecodeString(value.Data.Bytes())
444
445		expire, err := strconv.ParseInt(expireStr, 10, 64)
446		if err != nil {
447			return nil, fmt.Errorf("failed to parse value as int: %s", err)
448		}
449		c.Expire = expire
450		value.Value = c.Expire
451
452		return c, nil
453	case ControlTypeMicrosoftNotification:
454		return NewControlMicrosoftNotification(), nil
455	case ControlTypeMicrosoftShowDeleted:
456		return NewControlMicrosoftShowDeleted(), nil
457	default:
458		c := new(ControlString)
459		c.ControlType = ControlType
460		c.Criticality = Criticality
461		if value != nil {
462			c.ControlValue = value.Value.(string)
463		}
464		return c, nil
465	}
466}
467
468// NewControlString returns a generic control
469func NewControlString(controlType string, criticality bool, controlValue string) *ControlString {
470	return &ControlString{
471		ControlType:  controlType,
472		Criticality:  criticality,
473		ControlValue: controlValue,
474	}
475}
476
477// NewControlPaging returns a paging control
478func NewControlPaging(pagingSize uint32) *ControlPaging {
479	return &ControlPaging{PagingSize: pagingSize}
480}
481
482// NewControlBeheraPasswordPolicy returns a ControlBeheraPasswordPolicy
483func NewControlBeheraPasswordPolicy() *ControlBeheraPasswordPolicy {
484	return &ControlBeheraPasswordPolicy{
485		Expire: -1,
486		Grace:  -1,
487		Error:  -1,
488	}
489}
490
491func encodeControls(controls []Control) *ber.Packet {
492	packet := ber.Encode(ber.ClassContext, ber.TypeConstructed, 0, nil, "Controls")
493	for _, control := range controls {
494		packet.AppendChild(control.Encode())
495	}
496	return packet
497}
498