Skip to content

Commit a3dcdda

Browse files
feat: return referrals for modify operation (#375)
* 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.
1 parent 7d3b8d4 commit a3dcdda

File tree

6 files changed

+90
-26
lines changed

6 files changed

+90
-26
lines changed

modify.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
135135
type ModifyResult struct {
136136
// Controls are the returned controls
137137
Controls []Control
138+
// Referral is the returned referral
139+
Referral string
138140
}
139141

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

158160
switch packet.Children[1].Tag {
159161
case ApplicationModifyResponse:
160-
err := GetLDAPError(packet)
161-
if err != nil {
162-
return nil, err
162+
if err = GetLDAPError(packet); err != nil {
163+
if referral, referralErr := getReferral(err, packet); referralErr != nil {
164+
return result, referralErr
165+
} else {
166+
result.Referral = referral
167+
}
168+
169+
return result, err
163170
}
164171
if len(packet.Children) == 3 {
165172
for _, child := range packet.Children[2].Children {

passwdmodify.go

+8-10
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,13 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa
9595
result := &PasswordModifyResult{}
9696

9797
if packet.Children[1].Tag == ApplicationExtendedResponse {
98-
err := GetLDAPError(packet)
99-
if err != nil {
100-
if IsErrorWithCode(err, LDAPResultReferral) {
101-
for _, child := range packet.Children[1].Children {
102-
if child.Tag == 3 {
103-
result.Referral = child.Children[0].Value.(string)
104-
}
105-
}
98+
if err = GetLDAPError(packet); err != nil {
99+
if referral, referralErr := getReferral(err, packet); referralErr != nil {
100+
return result, referralErr
101+
} else {
102+
result.Referral = referral
106103
}
104+
107105
return result, err
108106
}
109107
} else {
@@ -112,10 +110,10 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa
112110

113111
extendedResponse := packet.Children[1]
114112
for _, child := range extendedResponse.Children {
115-
if child.Tag == 11 {
113+
if child.Tag == ber.TagEmbeddedPDV {
116114
passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes())
117115
if len(passwordModifyResponseValue.Children) == 1 {
118-
if passwordModifyResponseValue.Children[0].Tag == 0 {
116+
if passwordModifyResponseValue.Children[0].Tag == ber.TagEOC {
119117
result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes())
120118
}
121119
}

request.go

+27
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ldap
22

33
import (
44
"errors"
5+
"fmt"
56

67
ber "github.com/go-asn1-ber/asn1-ber"
78
)
@@ -69,3 +70,29 @@ func (l *Conn) readPacket(msgCtx *messageContext) (*ber.Packet, error) {
6970
}
7071
return packet, nil
7172
}
73+
74+
func getReferral(err error, packet *ber.Packet) (referral string, e error) {
75+
if !IsErrorWithCode(err, LDAPResultReferral) {
76+
return "", nil
77+
}
78+
79+
if len(packet.Children) < 2 {
80+
return "", fmt.Errorf("ldap: returned error indicates the packet contains a referral but it doesn't have sufficient child nodes: %w", err)
81+
}
82+
83+
if packet.Children[1].Tag != ber.TagObjectDescriptor {
84+
return "", fmt.Errorf("ldap: returned error indicates the packet contains a referral but the relevant child node isn't an object descriptor: %w", err)
85+
}
86+
87+
var ok bool
88+
89+
for _, child := range packet.Children[1].Children {
90+
if child.Tag == ber.TagBitString && len(child.Children) >= 1 {
91+
if referral, ok = child.Children[0].Value.(string); ok {
92+
return referral, nil
93+
}
94+
}
95+
}
96+
97+
return "", fmt.Errorf("ldap: returned error indicates the packet contains a referral but the referral couldn't be decoded: %w", err)
98+
}

v3/modify.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
135135
type ModifyResult struct {
136136
// Controls are the returned controls
137137
Controls []Control
138+
// Referral is the returned referral
139+
Referral string
138140
}
139141

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

158160
switch packet.Children[1].Tag {
159161
case ApplicationModifyResponse:
160-
err := GetLDAPError(packet)
161-
if err != nil {
162-
return nil, err
162+
if err = GetLDAPError(packet); err != nil {
163+
if referral, referralErr := getReferral(err, packet); referralErr != nil {
164+
return result, referralErr
165+
} else {
166+
result.Referral = referral
167+
}
168+
169+
return result, err
163170
}
164171
if len(packet.Children) == 3 {
165172
for _, child := range packet.Children[2].Children {

v3/passwdmodify.go

+8-10
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,13 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa
9595
result := &PasswordModifyResult{}
9696

9797
if packet.Children[1].Tag == ApplicationExtendedResponse {
98-
err := GetLDAPError(packet)
99-
if err != nil {
100-
if IsErrorWithCode(err, LDAPResultReferral) {
101-
for _, child := range packet.Children[1].Children {
102-
if child.Tag == 3 {
103-
result.Referral = child.Children[0].Value.(string)
104-
}
105-
}
98+
if err = GetLDAPError(packet); err != nil {
99+
if referral, referralErr := getReferral(err, packet); referralErr != nil {
100+
return result, referralErr
101+
} else {
102+
result.Referral = referral
106103
}
104+
107105
return result, err
108106
}
109107
} else {
@@ -112,10 +110,10 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa
112110

113111
extendedResponse := packet.Children[1]
114112
for _, child := range extendedResponse.Children {
115-
if child.Tag == 11 {
113+
if child.Tag == ber.TagEmbeddedPDV {
116114
passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes())
117115
if len(passwordModifyResponseValue.Children) == 1 {
118-
if passwordModifyResponseValue.Children[0].Tag == 0 {
116+
if passwordModifyResponseValue.Children[0].Tag == ber.TagEOC {
119117
result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes())
120118
}
121119
}

v3/request.go

+27
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ldap
22

33
import (
44
"errors"
5+
"fmt"
56

67
ber "github.com/go-asn1-ber/asn1-ber"
78
)
@@ -69,3 +70,29 @@ func (l *Conn) readPacket(msgCtx *messageContext) (*ber.Packet, error) {
6970
}
7071
return packet, nil
7172
}
73+
74+
func getReferral(err error, packet *ber.Packet) (referral string, e error) {
75+
if !IsErrorWithCode(err, LDAPResultReferral) {
76+
return "", nil
77+
}
78+
79+
if len(packet.Children) < 2 {
80+
return "", fmt.Errorf("ldap: returned error indicates the packet contains a referral but it doesn't have sufficient child nodes: %w", err)
81+
}
82+
83+
if packet.Children[1].Tag != ber.TagObjectDescriptor {
84+
return "", fmt.Errorf("ldap: returned error indicates the packet contains a referral but the relevant child node isn't an object descriptor: %w", err)
85+
}
86+
87+
var ok bool
88+
89+
for _, child := range packet.Children[1].Children {
90+
if child.Tag == ber.TagBitString && len(child.Children) >= 1 {
91+
if referral, ok = child.Children[0].Value.(string); ok {
92+
return referral, nil
93+
}
94+
}
95+
}
96+
97+
return "", fmt.Errorf("ldap: returned error indicates the packet contains a referral but the referral couldn't be decoded: %w", err)
98+
}

0 commit comments

Comments
 (0)