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