Skip to content

Commit

Permalink
feat: return referrals for modify operation (go-ldap#375)
Browse files Browse the repository at this point in the history
* feat: return referrals for modify operation

This implements returning the referral for the modify operation. Tested against a Microsoft Active Directory Read-only Domain Controller.
  • Loading branch information
james-d-elliott authored and inv2004 committed Jan 17, 2023
1 parent e4f2c01 commit 5b2747f
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 26 deletions.
13 changes: 10 additions & 3 deletions modify.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
type ModifyResult struct {
// Controls are the returned controls
Controls []Control
// Referral is the returned referral
Referral string
}

// ModifyWithResult performs the ModifyRequest and returns the result
Expand All @@ -157,9 +159,14 @@ func (l *Conn) ModifyWithResult(modifyRequest *ModifyRequest) (*ModifyResult, er

switch packet.Children[1].Tag {
case ApplicationModifyResponse:
err := GetLDAPError(packet)
if err != nil {
return nil, err
if err = GetLDAPError(packet); err != nil {
if referral, referralErr := getReferral(err, packet); referralErr != nil {
return result, referralErr
} else {
result.Referral = referral
}

return result, err
}
if len(packet.Children) == 3 {
for _, child := range packet.Children[2].Children {
Expand Down
18 changes: 8 additions & 10 deletions passwdmodify.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,13 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa
result := &PasswordModifyResult{}

if packet.Children[1].Tag == ApplicationExtendedResponse {
err := GetLDAPError(packet)
if err != nil {
if IsErrorWithCode(err, LDAPResultReferral) {
for _, child := range packet.Children[1].Children {
if child.Tag == 3 {
result.Referral = child.Children[0].Value.(string)
}
}
if err = GetLDAPError(packet); err != nil {
if referral, referralErr := getReferral(err, packet); referralErr != nil {
return result, referralErr
} else {
result.Referral = referral
}

return result, err
}
} else {
Expand All @@ -112,10 +110,10 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa

extendedResponse := packet.Children[1]
for _, child := range extendedResponse.Children {
if child.Tag == 11 {
if child.Tag == ber.TagEmbeddedPDV {
passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes())
if len(passwordModifyResponseValue.Children) == 1 {
if passwordModifyResponseValue.Children[0].Tag == 0 {
if passwordModifyResponseValue.Children[0].Tag == ber.TagEOC {
result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes())
}
}
Expand Down
27 changes: 27 additions & 0 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ldap

import (
"errors"
"fmt"

ber "github.com/go-asn1-ber/asn1-ber"
)
Expand Down Expand Up @@ -69,3 +70,29 @@ func (l *Conn) readPacket(msgCtx *messageContext) (*ber.Packet, error) {
}
return packet, nil
}

func getReferral(err error, packet *ber.Packet) (referral string, e error) {
if !IsErrorWithCode(err, LDAPResultReferral) {
return "", nil
}

if len(packet.Children) < 2 {
return "", fmt.Errorf("ldap: returned error indicates the packet contains a referral but it doesn't have sufficient child nodes: %w", err)
}

if packet.Children[1].Tag != ber.TagObjectDescriptor {
return "", fmt.Errorf("ldap: returned error indicates the packet contains a referral but the relevant child node isn't an object descriptor: %w", err)
}

var ok bool

for _, child := range packet.Children[1].Children {
if child.Tag == ber.TagBitString && len(child.Children) >= 1 {
if referral, ok = child.Children[0].Value.(string); ok {
return referral, nil
}
}
}

return "", fmt.Errorf("ldap: returned error indicates the packet contains a referral but the referral couldn't be decoded: %w", err)
}
13 changes: 10 additions & 3 deletions v3/modify.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
type ModifyResult struct {
// Controls are the returned controls
Controls []Control
// Referral is the returned referral
Referral string
}

// ModifyWithResult performs the ModifyRequest and returns the result
Expand All @@ -157,9 +159,14 @@ func (l *Conn) ModifyWithResult(modifyRequest *ModifyRequest) (*ModifyResult, er

switch packet.Children[1].Tag {
case ApplicationModifyResponse:
err := GetLDAPError(packet)
if err != nil {
return nil, err
if err = GetLDAPError(packet); err != nil {
if referral, referralErr := getReferral(err, packet); referralErr != nil {
return result, referralErr
} else {
result.Referral = referral
}

return result, err
}
if len(packet.Children) == 3 {
for _, child := range packet.Children[2].Children {
Expand Down
18 changes: 8 additions & 10 deletions v3/passwdmodify.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,13 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa
result := &PasswordModifyResult{}

if packet.Children[1].Tag == ApplicationExtendedResponse {
err := GetLDAPError(packet)
if err != nil {
if IsErrorWithCode(err, LDAPResultReferral) {
for _, child := range packet.Children[1].Children {
if child.Tag == 3 {
result.Referral = child.Children[0].Value.(string)
}
}
if err = GetLDAPError(packet); err != nil {
if referral, referralErr := getReferral(err, packet); referralErr != nil {
return result, referralErr
} else {
result.Referral = referral
}

return result, err
}
} else {
Expand All @@ -112,10 +110,10 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa

extendedResponse := packet.Children[1]
for _, child := range extendedResponse.Children {
if child.Tag == 11 {
if child.Tag == ber.TagEmbeddedPDV {
passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes())
if len(passwordModifyResponseValue.Children) == 1 {
if passwordModifyResponseValue.Children[0].Tag == 0 {
if passwordModifyResponseValue.Children[0].Tag == ber.TagEOC {
result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes())
}
}
Expand Down
27 changes: 27 additions & 0 deletions v3/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ldap

import (
"errors"
"fmt"

ber "github.com/go-asn1-ber/asn1-ber"
)
Expand Down Expand Up @@ -69,3 +70,29 @@ func (l *Conn) readPacket(msgCtx *messageContext) (*ber.Packet, error) {
}
return packet, nil
}

func getReferral(err error, packet *ber.Packet) (referral string, e error) {
if !IsErrorWithCode(err, LDAPResultReferral) {
return "", nil
}

if len(packet.Children) < 2 {
return "", fmt.Errorf("ldap: returned error indicates the packet contains a referral but it doesn't have sufficient child nodes: %w", err)
}

if packet.Children[1].Tag != ber.TagObjectDescriptor {
return "", fmt.Errorf("ldap: returned error indicates the packet contains a referral but the relevant child node isn't an object descriptor: %w", err)
}

var ok bool

for _, child := range packet.Children[1].Children {
if child.Tag == ber.TagBitString && len(child.Children) >= 1 {
if referral, ok = child.Children[0].Value.(string); ok {
return referral, nil
}
}
}

return "", fmt.Errorf("ldap: returned error indicates the packet contains a referral but the referral couldn't be decoded: %w", err)
}

0 comments on commit 5b2747f

Please sign in to comment.