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