forked from Shiloh/githaven
22770c324d
Signed-off-by: Andrew Thornton <art27@cantab.net>
498 lines
17 KiB
Go
498 lines
17 KiB
Go
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"
|
|
|
|
// ControlTypeMicrosoftNotification - https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx
|
|
ControlTypeMicrosoftNotification = "1.2.840.113556.1.4.528"
|
|
// ControlTypeMicrosoftShowDeleted - https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx
|
|
ControlTypeMicrosoftShowDeleted = "1.2.840.113556.1.4.417"
|
|
)
|
|
|
|
// ControlTypeMap maps controls to text descriptions
|
|
var ControlTypeMap = map[string]string{
|
|
ControlTypePaging: "Paging",
|
|
ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft",
|
|
ControlTypeManageDsaIT: "Manage DSA IT",
|
|
ControlTypeMicrosoftNotification: "Change Notification - Microsoft",
|
|
ControlTypeMicrosoftShowDeleted: "Show Deleted Objects - Microsoft",
|
|
}
|
|
|
|
// 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}
|
|
}
|
|
|
|
// ControlMicrosoftNotification implements the control described in https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx
|
|
type ControlMicrosoftNotification struct{}
|
|
|
|
// GetControlType returns the OID
|
|
func (c *ControlMicrosoftNotification) GetControlType() string {
|
|
return ControlTypeMicrosoftNotification
|
|
}
|
|
|
|
// Encode returns the ber packet representation
|
|
func (c *ControlMicrosoftNotification) Encode() *ber.Packet {
|
|
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
|
|
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftNotification, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftNotification]+")"))
|
|
|
|
return packet
|
|
}
|
|
|
|
// String returns a human-readable description
|
|
func (c *ControlMicrosoftNotification) String() string {
|
|
return fmt.Sprintf(
|
|
"Control Type: %s (%q)",
|
|
ControlTypeMap[ControlTypeMicrosoftNotification],
|
|
ControlTypeMicrosoftNotification)
|
|
}
|
|
|
|
// NewControlMicrosoftNotification returns a ControlMicrosoftNotification control
|
|
func NewControlMicrosoftNotification() *ControlMicrosoftNotification {
|
|
return &ControlMicrosoftNotification{}
|
|
}
|
|
|
|
// ControlMicrosoftShowDeleted implements the control described in https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx
|
|
type ControlMicrosoftShowDeleted struct{}
|
|
|
|
// GetControlType returns the OID
|
|
func (c *ControlMicrosoftShowDeleted) GetControlType() string {
|
|
return ControlTypeMicrosoftShowDeleted
|
|
}
|
|
|
|
// Encode returns the ber packet representation
|
|
func (c *ControlMicrosoftShowDeleted) Encode() *ber.Packet {
|
|
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
|
|
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftShowDeleted, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftShowDeleted]+")"))
|
|
|
|
return packet
|
|
}
|
|
|
|
// String returns a human-readable description
|
|
func (c *ControlMicrosoftShowDeleted) String() string {
|
|
return fmt.Sprintf(
|
|
"Control Type: %s (%q)",
|
|
ControlTypeMap[ControlTypeMicrosoftShowDeleted],
|
|
ControlTypeMicrosoftShowDeleted)
|
|
}
|
|
|
|
// NewControlMicrosoftShowDeleted returns a ControlMicrosoftShowDeleted control
|
|
func NewControlMicrosoftShowDeleted() *ControlMicrosoftShowDeleted {
|
|
return &ControlMicrosoftShowDeleted{}
|
|
}
|
|
|
|
// 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, error) {
|
|
var (
|
|
ControlType = ""
|
|
Criticality = false
|
|
value *ber.Packet
|
|
)
|
|
|
|
switch len(packet.Children) {
|
|
case 0:
|
|
// at least one child is required for control type
|
|
return nil, fmt.Errorf("at least one child is required for control type")
|
|
|
|
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, fmt.Errorf("more than 3 children is invalid for controls")
|
|
}
|
|
|
|
switch ControlType {
|
|
case ControlTypeManageDsaIT:
|
|
return NewControlManageDsaIT(Criticality), nil
|
|
case ControlTypePaging:
|
|
value.Description += " (Paging)"
|
|
c := new(ControlPaging)
|
|
if value.Value != nil {
|
|
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode data bytes: %s", err)
|
|
}
|
|
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, nil
|
|
case ControlTypeBeheraPasswordPolicy:
|
|
value.Description += " (Password Policy - Behera)"
|
|
c := NewControlBeheraPasswordPolicy()
|
|
if value.Value != nil {
|
|
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode data bytes: %s", err)
|
|
}
|
|
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, err := ber.DecodePacketErr(warningPacket.Data.Bytes())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode data bytes: %s", err)
|
|
}
|
|
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, err := ber.DecodePacketErr(child.Data.Bytes())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode data bytes: %s", err)
|
|
}
|
|
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, nil
|
|
case ControlTypeVChuPasswordMustChange:
|
|
c := &ControlVChuPasswordMustChange{MustChange: true}
|
|
return c, nil
|
|
case ControlTypeVChuPasswordWarning:
|
|
c := &ControlVChuPasswordWarning{Expire: -1}
|
|
expireStr := ber.DecodeString(value.Data.Bytes())
|
|
|
|
expire, err := strconv.ParseInt(expireStr, 10, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse value as int: %s", err)
|
|
}
|
|
c.Expire = expire
|
|
value.Value = c.Expire
|
|
|
|
return c, nil
|
|
case ControlTypeMicrosoftNotification:
|
|
return NewControlMicrosoftNotification(), nil
|
|
case ControlTypeMicrosoftShowDeleted:
|
|
return NewControlMicrosoftShowDeleted(), nil
|
|
default:
|
|
c := new(ControlString)
|
|
c.ControlType = ControlType
|
|
c.Criticality = Criticality
|
|
if value != nil {
|
|
c.ControlValue = value.Value.(string)
|
|
}
|
|
return c, nil
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|