// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ldap
import (
"fmt"
"strconv"
"gopkg.in/asn1-ber.v1"
)
const (
// ControlTypePaging - https://www.ietf.org/rfc/rfc2696.txt
ControlTypePaging = "1.2.840.113556.1.4.319"
// ControlTypeBeheraPasswordPolicy - https://tools.ietf.org/html/draft-behera-ldap-password-policy-10
ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1"
// ControlTypeVChuPasswordMustChange - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4"
// ControlTypeVChuPasswordWarning - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5"
// ControlTypeManageDsaIT - https://tools.ietf.org/html/rfc3296
ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2"
)
// ControlTypeMap maps controls to text descriptions
var ControlTypeMap = map [ string ] string {
ControlTypePaging : "Paging" ,
ControlTypeBeheraPasswordPolicy : "Password Policy - Behera Draft" ,
ControlTypeManageDsaIT : "Manage DSA IT" ,
}
// Control defines an interface controls provide to encode and describe themselves
type Control interface {
// GetControlType returns the OID
GetControlType ( ) string
// Encode returns the ber packet representation
Encode ( ) * ber . Packet
// String returns a human-readable description
String ( ) string
}
// ControlString implements the Control interface for simple controls
type ControlString struct {
ControlType string
Criticality bool
ControlValue string
}
// GetControlType returns the OID
func ( c * ControlString ) GetControlType ( ) string {
return c . ControlType
}
// Encode returns the ber packet representation
func ( c * ControlString ) Encode ( ) * ber . Packet {
packet := ber . Encode ( ber . ClassUniversal , ber . TypeConstructed , ber . TagSequence , nil , "Control" )
packet . AppendChild ( ber . NewString ( ber . ClassUniversal , ber . TypePrimitive , ber . TagOctetString , c . ControlType , "Control Type (" + ControlTypeMap [ c . ControlType ] + ")" ) )
if c . Criticality {
packet . AppendChild ( ber . NewBoolean ( ber . ClassUniversal , ber . TypePrimitive , ber . TagBoolean , c . Criticality , "Criticality" ) )
}
packet . AppendChild ( ber . NewString ( ber . ClassUniversal , ber . TypePrimitive , ber . TagOctetString , string ( c . ControlValue ) , "Control Value" ) )
return packet
}
// String returns a human-readable description
func ( c * ControlString ) String ( ) string {
return fmt . Sprintf ( "Control Type: %s (%q) Criticality: %t Control Value: %s" , ControlTypeMap [ c . ControlType ] , c . ControlType , c . Criticality , c . ControlValue )
}
// ControlPaging implements the paging control described in https://www.ietf.org/rfc/rfc2696.txt
type ControlPaging struct {
// PagingSize indicates the page size
PagingSize uint32
// Cookie is an opaque value returned by the server to track a paging cursor
Cookie [ ] byte
}
// GetControlType returns the OID
func ( c * ControlPaging ) GetControlType ( ) string {
return ControlTypePaging
}
// Encode returns the ber packet representation
func ( c * ControlPaging ) Encode ( ) * ber . Packet {
packet := ber . Encode ( ber . ClassUniversal , ber . TypeConstructed , ber . TagSequence , nil , "Control" )
packet . AppendChild ( ber . NewString ( ber . ClassUniversal , ber . TypePrimitive , ber . TagOctetString , ControlTypePaging , "Control Type (" + ControlTypeMap [ ControlTypePaging ] + ")" ) )
p2 := ber . Encode ( ber . ClassUniversal , ber . TypePrimitive , ber . TagOctetString , nil , "Control Value (Paging)" )
seq := ber . Encode ( ber . ClassUniversal , ber . TypeConstructed , ber . TagSequence , nil , "Search Control Value" )
seq . AppendChild ( ber . NewInteger ( ber . ClassUniversal , ber . TypePrimitive , ber . TagInteger , int64 ( c . PagingSize ) , "Paging Size" ) )
cookie := ber . Encode ( ber . ClassUniversal , ber . TypePrimitive , ber . TagOctetString , nil , "Cookie" )
cookie . Value = c . Cookie
cookie . Data . Write ( c . Cookie )
seq . AppendChild ( cookie )
p2 . AppendChild ( seq )
packet . AppendChild ( p2 )
return packet
}
// String returns a human-readable description
func ( c * ControlPaging ) String ( ) string {
return fmt . Sprintf (
"Control Type: %s (%q) Criticality: %t PagingSize: %d Cookie: %q" ,
ControlTypeMap [ ControlTypePaging ] ,
ControlTypePaging ,
false ,
c . PagingSize ,
c . Cookie )
}
// SetCookie stores the given cookie in the paging control
func ( c * ControlPaging ) SetCookie ( cookie [ ] byte ) {
c . Cookie = cookie
}
// ControlBeheraPasswordPolicy implements the control described in https://tools.ietf.org/html/draft-behera-ldap-password-policy-10
type ControlBeheraPasswordPolicy struct {
// Expire contains the number of seconds before a password will expire
Expire int64
// Grace indicates the remaining number of times a user will be allowed to authenticate with an expired password
Grace int64
// Error indicates the error code
Error int8
// ErrorString is a human readable error
ErrorString string
}
// GetControlType returns the OID
func ( c * ControlBeheraPasswordPolicy ) GetControlType ( ) string {
return ControlTypeBeheraPasswordPolicy
}
// Encode returns the ber packet representation
func ( c * ControlBeheraPasswordPolicy ) Encode ( ) * ber . Packet {
packet := ber . Encode ( ber . ClassUniversal , ber . TypeConstructed , ber . TagSequence , nil , "Control" )
packet . AppendChild ( ber . NewString ( ber . ClassUniversal , ber . TypePrimitive , ber . TagOctetString , ControlTypeBeheraPasswordPolicy , "Control Type (" + ControlTypeMap [ ControlTypeBeheraPasswordPolicy ] + ")" ) )
return packet
}
// String returns a human-readable description
func ( c * ControlBeheraPasswordPolicy ) String ( ) string {
return fmt . Sprintf (
"Control Type: %s (%q) Criticality: %t Expire: %d Grace: %d Error: %d, ErrorString: %s" ,
ControlTypeMap [ ControlTypeBeheraPasswordPolicy ] ,
ControlTypeBeheraPasswordPolicy ,
false ,
c . Expire ,
c . Grace ,
c . Error ,
c . ErrorString )
}
// ControlVChuPasswordMustChange implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
type ControlVChuPasswordMustChange struct {
// MustChange indicates if the password is required to be changed
MustChange bool
}
// GetControlType returns the OID
func ( c * ControlVChuPasswordMustChange ) GetControlType ( ) string {
return ControlTypeVChuPasswordMustChange
}
// Encode returns the ber packet representation
func ( c * ControlVChuPasswordMustChange ) Encode ( ) * ber . Packet {
return nil
}
// String returns a human-readable description
func ( c * ControlVChuPasswordMustChange ) String ( ) string {
return fmt . Sprintf (
"Control Type: %s (%q) Criticality: %t MustChange: %v" ,
ControlTypeMap [ ControlTypeVChuPasswordMustChange ] ,
ControlTypeVChuPasswordMustChange ,
false ,
c . MustChange )
}
// ControlVChuPasswordWarning implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
type ControlVChuPasswordWarning struct {
// Expire indicates the time in seconds until the password expires
Expire int64
}
// GetControlType returns the OID
func ( c * ControlVChuPasswordWarning ) GetControlType ( ) string {
return ControlTypeVChuPasswordWarning
}
// Encode returns the ber packet representation
func ( c * ControlVChuPasswordWarning ) Encode ( ) * ber . Packet {
return nil
}
// String returns a human-readable description
func ( c * ControlVChuPasswordWarning ) String ( ) string {
return fmt . Sprintf (
"Control Type: %s (%q) Criticality: %t Expire: %b" ,
ControlTypeMap [ ControlTypeVChuPasswordWarning ] ,
ControlTypeVChuPasswordWarning ,
false ,
c . Expire )
}
// ControlManageDsaIT implements the control described in https://tools.ietf.org/html/rfc3296
type ControlManageDsaIT struct {
// Criticality indicates if this control is required
Criticality bool
}
// GetControlType returns the OID
func ( c * ControlManageDsaIT ) GetControlType ( ) string {
return ControlTypeManageDsaIT
}
// Encode returns the ber packet representation
func ( c * ControlManageDsaIT ) Encode ( ) * ber . Packet {
//FIXME
packet := ber . Encode ( ber . ClassUniversal , ber . TypeConstructed , ber . TagSequence , nil , "Control" )
packet . AppendChild ( ber . NewString ( ber . ClassUniversal , ber . TypePrimitive , ber . TagOctetString , ControlTypeManageDsaIT , "Control Type (" + ControlTypeMap [ ControlTypeManageDsaIT ] + ")" ) )
if c . Criticality {
packet . AppendChild ( ber . NewBoolean ( ber . ClassUniversal , ber . TypePrimitive , ber . TagBoolean , c . Criticality , "Criticality" ) )
}
return packet
}
// String returns a human-readable description
func ( c * ControlManageDsaIT ) String ( ) string {
return fmt . Sprintf (
"Control Type: %s (%q) Criticality: %t" ,
ControlTypeMap [ ControlTypeManageDsaIT ] ,
ControlTypeManageDsaIT ,
c . Criticality )
}
// NewControlManageDsaIT returns a ControlManageDsaIT control
func NewControlManageDsaIT ( Criticality bool ) * ControlManageDsaIT {
return & ControlManageDsaIT { Criticality : Criticality }
}
// FindControl returns the first control of the given type in the list, or nil
func FindControl ( controls [ ] Control , controlType string ) Control {
for _ , c := range controls {
if c . GetControlType ( ) == controlType {
return c
}
}
return nil
}
// DecodeControl returns a control read from the given packet, or nil if no recognized control can be made
func DecodeControl ( packet * ber . Packet ) Control {
var (
ControlType = ""
Criticality = false
value * ber . Packet
)
switch len ( packet . Children ) {
case 0 :
// at least one child is required for control type
return nil
case 1 :
// just type, no criticality or value
packet . Children [ 0 ] . Description = "Control Type (" + ControlTypeMap [ ControlType ] + ")"
ControlType = packet . Children [ 0 ] . Value . ( string )
case 2 :
packet . Children [ 0 ] . Description = "Control Type (" + ControlTypeMap [ ControlType ] + ")"
ControlType = packet . Children [ 0 ] . Value . ( string )
// Children[1] could be criticality or value (both are optional)
// duck-type on whether this is a boolean
if _ , ok := packet . Children [ 1 ] . Value . ( bool ) ; ok {
packet . Children [ 1 ] . Description = "Criticality"
Criticality = packet . Children [ 1 ] . Value . ( bool )
} else {
packet . Children [ 1 ] . Description = "Control Value"
value = packet . Children [ 1 ]
}
case 3 :
packet . Children [ 0 ] . Description = "Control Type (" + ControlTypeMap [ ControlType ] + ")"
ControlType = packet . Children [ 0 ] . Value . ( string )
packet . Children [ 1 ] . Description = "Criticality"
Criticality = packet . Children [ 1 ] . Value . ( bool )
packet . Children [ 2 ] . Description = "Control Value"
value = packet . Children [ 2 ]
default :
// more than 3 children is invalid
return nil
}
switch ControlType {
case ControlTypeManageDsaIT :
return NewControlManageDsaIT ( Criticality )
case ControlTypePaging :
value . Description += " (Paging)"
c := new ( ControlPaging )
if value . Value != nil {
valueChildren := ber . DecodePacket ( value . Data . Bytes ( ) )
value . Data . Truncate ( 0 )
value . Value = nil
value . AppendChild ( valueChildren )
}
value = value . Children [ 0 ]
value . Description = "Search Control Value"
value . Children [ 0 ] . Description = "Paging Size"
value . Children [ 1 ] . Description = "Cookie"
c . PagingSize = uint32 ( value . Children [ 0 ] . Value . ( int64 ) )
c . Cookie = value . Children [ 1 ] . Data . Bytes ( )
value . Children [ 1 ] . Value = c . Cookie
return c
case ControlTypeBeheraPasswordPolicy :
value . Description += " (Password Policy - Behera)"
c := NewControlBeheraPasswordPolicy ( )
if value . Value != nil {
valueChildren := ber . DecodePacket ( value . Data . Bytes ( ) )
value . Data . Truncate ( 0 )
value . Value = nil
value . AppendChild ( valueChildren )
}
sequence := value . Children [ 0 ]
for _ , child := range sequence . Children {
if child . Tag == 0 {
//Warning
warningPacket := child . Children [ 0 ]
packet := ber . DecodePacket ( warningPacket . Data . Bytes ( ) )
val , ok := packet . Value . ( int64 )
if ok {
if warningPacket . Tag == 0 {
//timeBeforeExpiration
c . Expire = val
warningPacket . Value = c . Expire
} else if warningPacket . Tag == 1 {
//graceAuthNsRemaining
c . Grace = val
warningPacket . Value = c . Grace
}
}
} else if child . Tag == 1 {
// Error
packet := ber . DecodePacket ( child . Data . Bytes ( ) )
val , ok := packet . Value . ( int8 )
if ! ok {
// what to do?
val = - 1
}
c . Error = val
child . Value = c . Error
c . ErrorString = BeheraPasswordPolicyErrorMap [ c . Error ]
}
}
return c
case ControlTypeVChuPasswordMustChange :
c := & ControlVChuPasswordMustChange { MustChange : true }
return c
case ControlTypeVChuPasswordWarning :
c := & ControlVChuPasswordWarning { Expire : - 1 }
expireStr := ber . DecodeString ( value . Data . Bytes ( ) )
expire , err := strconv . ParseInt ( expireStr , 10 , 64 )
if err != nil {
return nil
}
c . Expire = expire
value . Value = c . Expire
return c
default :
c := new ( ControlString )
c . ControlType = ControlType
c . Criticality = Criticality
if value != nil {
c . ControlValue = value . Value . ( string )
}
return c
}
}
// NewControlString returns a generic control
func NewControlString ( controlType string , criticality bool , controlValue string ) * ControlString {
return & ControlString {
ControlType : controlType ,
Criticality : criticality ,
ControlValue : controlValue ,
}
}
// NewControlPaging returns a paging control
func NewControlPaging ( pagingSize uint32 ) * ControlPaging {
return & ControlPaging { PagingSize : pagingSize }
}
// NewControlBeheraPasswordPolicy returns a ControlBeheraPasswordPolicy
func NewControlBeheraPasswordPolicy ( ) * ControlBeheraPasswordPolicy {
return & ControlBeheraPasswordPolicy {
Expire : - 1 ,
Grace : - 1 ,
Error : - 1 ,
}
}
func encodeControls ( controls [ ] Control ) * ber . Packet {
packet := ber . Encode ( ber . ClassContext , ber . TypeConstructed , 0 , nil , "Controls" )
for _ , control := range controls {
packet . AppendChild ( control . Encode ( ) )
}
return packet
}