mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-25 09:47:30 +00:00
22770c324d
Signed-off-by: Andrew Thornton <art27@cantab.net>
339 lines
11 KiB
Go
339 lines
11 KiB
Go
package ldap
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
|
|
"gopkg.in/asn1-ber.v1"
|
|
)
|
|
|
|
// LDAP Application Codes
|
|
const (
|
|
ApplicationBindRequest = 0
|
|
ApplicationBindResponse = 1
|
|
ApplicationUnbindRequest = 2
|
|
ApplicationSearchRequest = 3
|
|
ApplicationSearchResultEntry = 4
|
|
ApplicationSearchResultDone = 5
|
|
ApplicationModifyRequest = 6
|
|
ApplicationModifyResponse = 7
|
|
ApplicationAddRequest = 8
|
|
ApplicationAddResponse = 9
|
|
ApplicationDelRequest = 10
|
|
ApplicationDelResponse = 11
|
|
ApplicationModifyDNRequest = 12
|
|
ApplicationModifyDNResponse = 13
|
|
ApplicationCompareRequest = 14
|
|
ApplicationCompareResponse = 15
|
|
ApplicationAbandonRequest = 16
|
|
ApplicationSearchResultReference = 19
|
|
ApplicationExtendedRequest = 23
|
|
ApplicationExtendedResponse = 24
|
|
)
|
|
|
|
// ApplicationMap contains human readable descriptions of LDAP Application Codes
|
|
var ApplicationMap = map[uint8]string{
|
|
ApplicationBindRequest: "Bind Request",
|
|
ApplicationBindResponse: "Bind Response",
|
|
ApplicationUnbindRequest: "Unbind Request",
|
|
ApplicationSearchRequest: "Search Request",
|
|
ApplicationSearchResultEntry: "Search Result Entry",
|
|
ApplicationSearchResultDone: "Search Result Done",
|
|
ApplicationModifyRequest: "Modify Request",
|
|
ApplicationModifyResponse: "Modify Response",
|
|
ApplicationAddRequest: "Add Request",
|
|
ApplicationAddResponse: "Add Response",
|
|
ApplicationDelRequest: "Del Request",
|
|
ApplicationDelResponse: "Del Response",
|
|
ApplicationModifyDNRequest: "Modify DN Request",
|
|
ApplicationModifyDNResponse: "Modify DN Response",
|
|
ApplicationCompareRequest: "Compare Request",
|
|
ApplicationCompareResponse: "Compare Response",
|
|
ApplicationAbandonRequest: "Abandon Request",
|
|
ApplicationSearchResultReference: "Search Result Reference",
|
|
ApplicationExtendedRequest: "Extended Request",
|
|
ApplicationExtendedResponse: "Extended Response",
|
|
}
|
|
|
|
// Ldap Behera Password Policy Draft 10 (https://tools.ietf.org/html/draft-behera-ldap-password-policy-10)
|
|
const (
|
|
BeheraPasswordExpired = 0
|
|
BeheraAccountLocked = 1
|
|
BeheraChangeAfterReset = 2
|
|
BeheraPasswordModNotAllowed = 3
|
|
BeheraMustSupplyOldPassword = 4
|
|
BeheraInsufficientPasswordQuality = 5
|
|
BeheraPasswordTooShort = 6
|
|
BeheraPasswordTooYoung = 7
|
|
BeheraPasswordInHistory = 8
|
|
)
|
|
|
|
// BeheraPasswordPolicyErrorMap contains human readable descriptions of Behera Password Policy error codes
|
|
var BeheraPasswordPolicyErrorMap = map[int8]string{
|
|
BeheraPasswordExpired: "Password expired",
|
|
BeheraAccountLocked: "Account locked",
|
|
BeheraChangeAfterReset: "Password must be changed",
|
|
BeheraPasswordModNotAllowed: "Policy prevents password modification",
|
|
BeheraMustSupplyOldPassword: "Policy requires old password in order to change password",
|
|
BeheraInsufficientPasswordQuality: "Password fails quality checks",
|
|
BeheraPasswordTooShort: "Password is too short for policy",
|
|
BeheraPasswordTooYoung: "Password has been changed too recently",
|
|
BeheraPasswordInHistory: "New password is in list of old passwords",
|
|
}
|
|
|
|
// Adds descriptions to an LDAP Response packet for debugging
|
|
func addLDAPDescriptions(packet *ber.Packet) (err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = NewError(ErrorDebugging, errors.New("ldap: cannot process packet to add descriptions"))
|
|
}
|
|
}()
|
|
packet.Description = "LDAP Response"
|
|
packet.Children[0].Description = "Message ID"
|
|
|
|
application := uint8(packet.Children[1].Tag)
|
|
packet.Children[1].Description = ApplicationMap[application]
|
|
|
|
switch application {
|
|
case ApplicationBindRequest:
|
|
err = addRequestDescriptions(packet)
|
|
case ApplicationBindResponse:
|
|
err = addDefaultLDAPResponseDescriptions(packet)
|
|
case ApplicationUnbindRequest:
|
|
err = addRequestDescriptions(packet)
|
|
case ApplicationSearchRequest:
|
|
err = addRequestDescriptions(packet)
|
|
case ApplicationSearchResultEntry:
|
|
packet.Children[1].Children[0].Description = "Object Name"
|
|
packet.Children[1].Children[1].Description = "Attributes"
|
|
for _, child := range packet.Children[1].Children[1].Children {
|
|
child.Description = "Attribute"
|
|
child.Children[0].Description = "Attribute Name"
|
|
child.Children[1].Description = "Attribute Values"
|
|
for _, grandchild := range child.Children[1].Children {
|
|
grandchild.Description = "Attribute Value"
|
|
}
|
|
}
|
|
if len(packet.Children) == 3 {
|
|
err = addControlDescriptions(packet.Children[2])
|
|
}
|
|
case ApplicationSearchResultDone:
|
|
err = addDefaultLDAPResponseDescriptions(packet)
|
|
case ApplicationModifyRequest:
|
|
err = addRequestDescriptions(packet)
|
|
case ApplicationModifyResponse:
|
|
case ApplicationAddRequest:
|
|
err = addRequestDescriptions(packet)
|
|
case ApplicationAddResponse:
|
|
case ApplicationDelRequest:
|
|
err = addRequestDescriptions(packet)
|
|
case ApplicationDelResponse:
|
|
case ApplicationModifyDNRequest:
|
|
err = addRequestDescriptions(packet)
|
|
case ApplicationModifyDNResponse:
|
|
case ApplicationCompareRequest:
|
|
err = addRequestDescriptions(packet)
|
|
case ApplicationCompareResponse:
|
|
case ApplicationAbandonRequest:
|
|
err = addRequestDescriptions(packet)
|
|
case ApplicationSearchResultReference:
|
|
case ApplicationExtendedRequest:
|
|
err = addRequestDescriptions(packet)
|
|
case ApplicationExtendedResponse:
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func addControlDescriptions(packet *ber.Packet) error {
|
|
packet.Description = "Controls"
|
|
for _, child := range packet.Children {
|
|
var value *ber.Packet
|
|
controlType := ""
|
|
child.Description = "Control"
|
|
switch len(child.Children) {
|
|
case 0:
|
|
// at least one child is required for control type
|
|
return fmt.Errorf("at least one child is required for control type")
|
|
|
|
case 1:
|
|
// just type, no criticality or value
|
|
controlType = child.Children[0].Value.(string)
|
|
child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
|
|
|
|
case 2:
|
|
controlType = child.Children[0].Value.(string)
|
|
child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
|
|
// Children[1] could be criticality or value (both are optional)
|
|
// duck-type on whether this is a boolean
|
|
if _, ok := child.Children[1].Value.(bool); ok {
|
|
child.Children[1].Description = "Criticality"
|
|
} else {
|
|
child.Children[1].Description = "Control Value"
|
|
value = child.Children[1]
|
|
}
|
|
|
|
case 3:
|
|
// criticality and value present
|
|
controlType = child.Children[0].Value.(string)
|
|
child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
|
|
child.Children[1].Description = "Criticality"
|
|
child.Children[2].Description = "Control Value"
|
|
value = child.Children[2]
|
|
|
|
default:
|
|
// more than 3 children is invalid
|
|
return fmt.Errorf("more than 3 children for control packet found")
|
|
}
|
|
|
|
if value == nil {
|
|
continue
|
|
}
|
|
switch controlType {
|
|
case ControlTypePaging:
|
|
value.Description += " (Paging)"
|
|
if value.Value != nil {
|
|
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode data bytes: %s", err)
|
|
}
|
|
value.Data.Truncate(0)
|
|
value.Value = nil
|
|
valueChildren.Children[1].Value = valueChildren.Children[1].Data.Bytes()
|
|
value.AppendChild(valueChildren)
|
|
}
|
|
value.Children[0].Description = "Real Search Control Value"
|
|
value.Children[0].Children[0].Description = "Paging Size"
|
|
value.Children[0].Children[1].Description = "Cookie"
|
|
|
|
case ControlTypeBeheraPasswordPolicy:
|
|
value.Description += " (Password Policy - Behera Draft)"
|
|
if value.Value != nil {
|
|
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
|
|
if err != nil {
|
|
return 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 fmt.Errorf("failed to decode data bytes: %s", err)
|
|
}
|
|
val, ok := packet.Value.(int64)
|
|
if ok {
|
|
if warningPacket.Tag == 0 {
|
|
//timeBeforeExpiration
|
|
value.Description += " (TimeBeforeExpiration)"
|
|
warningPacket.Value = val
|
|
} else if warningPacket.Tag == 1 {
|
|
//graceAuthNsRemaining
|
|
value.Description += " (GraceAuthNsRemaining)"
|
|
warningPacket.Value = val
|
|
}
|
|
}
|
|
} else if child.Tag == 1 {
|
|
// Error
|
|
packet, err := ber.DecodePacketErr(child.Data.Bytes())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode data bytes: %s", err)
|
|
}
|
|
val, ok := packet.Value.(int8)
|
|
if !ok {
|
|
val = -1
|
|
}
|
|
child.Description = "Error"
|
|
child.Value = val
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func addRequestDescriptions(packet *ber.Packet) error {
|
|
packet.Description = "LDAP Request"
|
|
packet.Children[0].Description = "Message ID"
|
|
packet.Children[1].Description = ApplicationMap[uint8(packet.Children[1].Tag)]
|
|
if len(packet.Children) == 3 {
|
|
return addControlDescriptions(packet.Children[2])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func addDefaultLDAPResponseDescriptions(packet *ber.Packet) error {
|
|
err := GetLDAPError(packet)
|
|
packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[err.(*Error).ResultCode] + ")"
|
|
packet.Children[1].Children[1].Description = "Matched DN (" + err.(*Error).MatchedDN + ")"
|
|
packet.Children[1].Children[2].Description = "Error Message"
|
|
if len(packet.Children[1].Children) > 3 {
|
|
packet.Children[1].Children[3].Description = "Referral"
|
|
}
|
|
if len(packet.Children) == 3 {
|
|
return addControlDescriptions(packet.Children[2])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DebugBinaryFile reads and prints packets from the given filename
|
|
func DebugBinaryFile(fileName string) error {
|
|
file, err := ioutil.ReadFile(fileName)
|
|
if err != nil {
|
|
return NewError(ErrorDebugging, err)
|
|
}
|
|
ber.PrintBytes(os.Stdout, file, "")
|
|
packet, err := ber.DecodePacketErr(file)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode packet: %s", err)
|
|
}
|
|
if err := addLDAPDescriptions(packet); err != nil {
|
|
return err
|
|
}
|
|
ber.PrintPacket(packet)
|
|
|
|
return nil
|
|
}
|
|
|
|
var hex = "0123456789abcdef"
|
|
|
|
func mustEscape(c byte) bool {
|
|
return c > 0x7f || c == '(' || c == ')' || c == '\\' || c == '*' || c == 0
|
|
}
|
|
|
|
// EscapeFilter escapes from the provided LDAP filter string the special
|
|
// characters in the set `()*\` and those out of the range 0 < c < 0x80,
|
|
// as defined in RFC4515.
|
|
func EscapeFilter(filter string) string {
|
|
escape := 0
|
|
for i := 0; i < len(filter); i++ {
|
|
if mustEscape(filter[i]) {
|
|
escape++
|
|
}
|
|
}
|
|
if escape == 0 {
|
|
return filter
|
|
}
|
|
buf := make([]byte, len(filter)+escape*2)
|
|
for i, j := 0, 0; i < len(filter); i++ {
|
|
c := filter[i]
|
|
if mustEscape(c) {
|
|
buf[j+0] = '\\'
|
|
buf[j+1] = hex[c>>4]
|
|
buf[j+2] = hex[c&0xf]
|
|
j += 3
|
|
} else {
|
|
buf[j] = c
|
|
j++
|
|
}
|
|
}
|
|
return string(buf)
|
|
}
|