Skip to content

Commit 83f96f1

Browse files
authored
Sync v3 folder with root (#260)
Address #258
1 parent 1972b41 commit 83f96f1

File tree

1 file changed

+237
-0
lines changed

1 file changed

+237
-0
lines changed

v3/bind.go

+237
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package ldap
22

33
import (
4+
"bytes"
5+
"crypto/md5"
6+
enchex "encoding/hex"
47
"errors"
58
"fmt"
9+
"io/ioutil"
10+
"math/rand"
11+
"strings"
612

713
ber "github.com/go-asn1-ber/asn1-ber"
814
)
@@ -115,6 +121,237 @@ func (l *Conn) UnauthenticatedBind(username string) error {
115121
return err
116122
}
117123

124+
// DigestMD5BindRequest represents a digest-md5 bind operation
125+
type DigestMD5BindRequest struct {
126+
Host string
127+
// Username is the name of the Directory object that the client wishes to bind as
128+
Username string
129+
// Password is the credentials to bind with
130+
Password string
131+
// Controls are optional controls to send with the bind request
132+
Controls []Control
133+
}
134+
135+
func (req *DigestMD5BindRequest) appendTo(envelope *ber.Packet) error {
136+
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
137+
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
138+
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
139+
140+
auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
141+
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "DIGEST-MD5", "SASL Mech"))
142+
request.AppendChild(auth)
143+
envelope.AppendChild(request)
144+
if len(req.Controls) > 0 {
145+
envelope.AppendChild(encodeControls(req.Controls))
146+
}
147+
return nil
148+
}
149+
150+
// DigestMD5BindResult contains the response from the server
151+
type DigestMD5BindResult struct {
152+
Controls []Control
153+
}
154+
155+
// MD5Bind performs a digest-md5 bind with the given host, username and password.
156+
func (l *Conn) MD5Bind(host, username, password string) error {
157+
req := &DigestMD5BindRequest{
158+
Host: host,
159+
Username: username,
160+
Password: password,
161+
}
162+
_, err := l.DigestMD5Bind(req)
163+
return err
164+
}
165+
166+
// DigestMD5Bind performs the digest-md5 bind operation defined in the given request
167+
func (l *Conn) DigestMD5Bind(digestMD5BindRequest *DigestMD5BindRequest) (*DigestMD5BindResult, error) {
168+
if digestMD5BindRequest.Password == "" {
169+
return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client"))
170+
}
171+
172+
msgCtx, err := l.doRequest(digestMD5BindRequest)
173+
if err != nil {
174+
return nil, err
175+
}
176+
defer l.finishMessage(msgCtx)
177+
178+
packet, err := l.readPacket(msgCtx)
179+
if err != nil {
180+
return nil, err
181+
}
182+
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
183+
if l.Debug {
184+
if err = addLDAPDescriptions(packet); err != nil {
185+
return nil, err
186+
}
187+
ber.PrintPacket(packet)
188+
}
189+
190+
result := &DigestMD5BindResult{
191+
Controls: make([]Control, 0),
192+
}
193+
var params map[string]string
194+
if len(packet.Children) == 2 {
195+
if len(packet.Children[1].Children) == 4 {
196+
child := packet.Children[1].Children[0]
197+
if child.Tag != ber.TagEnumerated {
198+
return result, GetLDAPError(packet)
199+
}
200+
if child.Value.(int64) != 14 {
201+
return result, GetLDAPError(packet)
202+
}
203+
child = packet.Children[1].Children[3]
204+
if child.Tag != ber.TagObjectDescriptor {
205+
return result, GetLDAPError(packet)
206+
}
207+
if child.Data == nil {
208+
return result, GetLDAPError(packet)
209+
}
210+
data, _ := ioutil.ReadAll(child.Data)
211+
params, err = parseParams(string(data))
212+
if err != nil {
213+
return result, fmt.Errorf("parsing digest-challenge: %s", err)
214+
}
215+
}
216+
}
217+
218+
if params != nil {
219+
resp := computeResponse(
220+
params,
221+
"ldap/"+strings.ToLower(digestMD5BindRequest.Host),
222+
digestMD5BindRequest.Username,
223+
digestMD5BindRequest.Password,
224+
)
225+
packet = ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
226+
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
227+
228+
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
229+
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
230+
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
231+
232+
auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
233+
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "DIGEST-MD5", "SASL Mech"))
234+
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, resp, "Credentials"))
235+
request.AppendChild(auth)
236+
packet.AppendChild(request)
237+
msgCtx, err = l.sendMessage(packet)
238+
if err != nil {
239+
return nil, fmt.Errorf("send message: %s", err)
240+
}
241+
defer l.finishMessage(msgCtx)
242+
packetResponse, ok := <-msgCtx.responses
243+
if !ok {
244+
return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
245+
}
246+
packet, err = packetResponse.ReadPacket()
247+
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
248+
if err != nil {
249+
return nil, fmt.Errorf("read packet: %s", err)
250+
}
251+
}
252+
253+
err = GetLDAPError(packet)
254+
return result, err
255+
}
256+
257+
func parseParams(str string) (map[string]string, error) {
258+
m := make(map[string]string)
259+
var key, value string
260+
var state int
261+
for i := 0; i <= len(str); i++ {
262+
switch state {
263+
case 0: //reading key
264+
if i == len(str) {
265+
return nil, fmt.Errorf("syntax error on %d", i)
266+
}
267+
if str[i] != '=' {
268+
key += string(str[i])
269+
continue
270+
}
271+
state = 1
272+
case 1: //reading value
273+
if i == len(str) {
274+
m[key] = value
275+
break
276+
}
277+
switch str[i] {
278+
case ',':
279+
m[key] = value
280+
state = 0
281+
key = ""
282+
value = ""
283+
case '"':
284+
if value != "" {
285+
return nil, fmt.Errorf("syntax error on %d", i)
286+
}
287+
state = 2
288+
default:
289+
value += string(str[i])
290+
}
291+
case 2: //inside quotes
292+
if i == len(str) {
293+
return nil, fmt.Errorf("syntax error on %d", i)
294+
}
295+
if str[i] != '"' {
296+
value += string(str[i])
297+
} else {
298+
state = 1
299+
}
300+
}
301+
}
302+
return m, nil
303+
}
304+
305+
func computeResponse(params map[string]string, uri, username, password string) string {
306+
nc := "00000001"
307+
qop := "auth"
308+
cnonce := enchex.EncodeToString(randomBytes(16))
309+
x := username + ":" + params["realm"] + ":" + password
310+
y := md5Hash([]byte(x))
311+
312+
a1 := bytes.NewBuffer(y)
313+
a1.WriteString(":" + params["nonce"] + ":" + cnonce)
314+
if len(params["authzid"]) > 0 {
315+
a1.WriteString(":" + params["authzid"])
316+
}
317+
a2 := bytes.NewBuffer([]byte("AUTHENTICATE"))
318+
a2.WriteString(":" + uri)
319+
ha1 := enchex.EncodeToString(md5Hash(a1.Bytes()))
320+
ha2 := enchex.EncodeToString(md5Hash(a2.Bytes()))
321+
322+
kd := ha1
323+
kd += ":" + params["nonce"]
324+
kd += ":" + nc
325+
kd += ":" + cnonce
326+
kd += ":" + qop
327+
kd += ":" + ha2
328+
resp := enchex.EncodeToString(md5Hash([]byte(kd)))
329+
return fmt.Sprintf(
330+
`username="%s",realm="%s",nonce="%s",cnonce="%s",nc=00000001,qop=%s,digest-uri="%s",response=%s`,
331+
username,
332+
params["realm"],
333+
params["nonce"],
334+
cnonce,
335+
qop,
336+
uri,
337+
resp,
338+
)
339+
}
340+
341+
func md5Hash(b []byte) []byte {
342+
hasher := md5.New()
343+
hasher.Write(b)
344+
return hasher.Sum(nil)
345+
}
346+
347+
func randomBytes(len int) []byte {
348+
b := make([]byte, len)
349+
for i := 0; i < len; i++ {
350+
b[i] = byte(rand.Intn(256))
351+
}
352+
return b
353+
}
354+
118355
var externalBindRequest = requestFunc(func(envelope *ber.Packet) error {
119356
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
120357
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))

0 commit comments

Comments
 (0)