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