@@ -11,6 +11,8 @@ import (
11
11
"strings"
12
12
13
13
ber "github.com/go-asn1-ber/asn1-ber"
14
+ "github.com/Azure/go-ntlmssp"
15
+
14
16
)
15
17
16
18
// SimpleBindRequest represents a username/password bind operation
@@ -387,3 +389,132 @@ func (l *Conn) ExternalBind() error {
387
389
388
390
return GetLDAPError (packet )
389
391
}
392
+
393
+ // NTLMBind performs an NTLMSSP bind leveraging https://github.com/Azure/go-ntlmssp
394
+
395
+ // NTLMBindRequest represents an NTLMSSP bind operation
396
+ type NTLMBindRequest struct {
397
+ // Domain is the AD Domain to authenticate too. If not specified, it will be grabbed from the NTLMSSP Challenge
398
+ Domain string
399
+ // Username is the name of the Directory object that the client wishes to bind as
400
+ Username string
401
+ // Password is the credentials to bind with
402
+ Password string
403
+ // Controls are optional controls to send with the bind request
404
+ Controls []Control
405
+ }
406
+
407
+ func (req * NTLMBindRequest ) appendTo (envelope * ber.Packet ) error {
408
+ request := ber .Encode (ber .ClassApplication , ber .TypeConstructed , ApplicationBindRequest , nil , "Bind Request" )
409
+ request .AppendChild (ber .NewInteger (ber .ClassUniversal , ber .TypePrimitive , ber .TagInteger , 3 , "Version" ))
410
+ request .AppendChild (ber .NewString (ber .ClassUniversal , ber .TypePrimitive , ber .TagOctetString , "" , "User Name" ))
411
+
412
+ // generate an NTLMSSP Negotiation message for the specified domain (it can be blank)
413
+ negMessage , err := ntlmssp .NewNegotiateMessage (req .Domain , "" )
414
+ if err != nil {
415
+ return fmt .Errorf ("err creating negmessage: %s" , err )
416
+ }
417
+
418
+ // append the generated NTLMSSP message as a TagEnumerated BER value
419
+ auth := ber .Encode (ber .ClassContext , ber .TypePrimitive , ber .TagEnumerated , negMessage , "authentication" )
420
+ request .AppendChild (auth )
421
+ envelope .AppendChild (request )
422
+ if len (req .Controls ) > 0 {
423
+ envelope .AppendChild (encodeControls (req .Controls ))
424
+ }
425
+ return nil
426
+ }
427
+
428
+ // NTLMBindResult contains the response from the server
429
+ type NTLMBindResult struct {
430
+ Controls []Control
431
+ }
432
+
433
+ // NTLMBind performs an NTLMSSP Bind with the given domain, username and password
434
+ func (l * Conn ) NTLMBind (domain , username , password string ) error {
435
+ req := & NTLMBindRequest {
436
+ Domain : domain ,
437
+ Username : username ,
438
+ Password : password ,
439
+ }
440
+ _ , err := l .NTLMChallengeBind (req )
441
+ return err
442
+ }
443
+
444
+ // NTLMChallengeBind performs the NTLMSSP bind operation defined in the given request
445
+ func (l * Conn ) NTLMChallengeBind (ntlmBindRequest * NTLMBindRequest ) (* NTLMBindResult , error ) {
446
+ if ntlmBindRequest .Password == "" {
447
+ return nil , NewError (ErrorEmptyPassword , errors .New ("ldap: empty password not allowed by the client" ))
448
+ }
449
+
450
+ msgCtx , err := l .doRequest (ntlmBindRequest )
451
+ if err != nil {
452
+ return nil , err
453
+ }
454
+ defer l .finishMessage (msgCtx )
455
+ packet , err := l .readPacket (msgCtx )
456
+ if err != nil {
457
+ return nil , err
458
+ }
459
+ l .Debug .Printf ("%d: got response %p" , msgCtx .id , packet )
460
+ if l .Debug {
461
+ if err = addLDAPDescriptions (packet ); err != nil {
462
+ return nil , err
463
+ }
464
+ ber .PrintPacket (packet )
465
+ }
466
+ result := & NTLMBindResult {
467
+ Controls : make ([]Control , 0 ),
468
+ }
469
+ var ntlmsspChallenge []byte
470
+
471
+ // now find the NTLM Response Message
472
+ if len (packet .Children ) == 2 {
473
+ if len (packet .Children [1 ].Children ) == 3 {
474
+ child := packet .Children [1 ].Children [1 ]
475
+ ntlmsspChallenge = child .ByteValue
476
+ // Check to make sure we got the right message. It will always start with NTLMSSP
477
+ if ! bytes .Equal (ntlmsspChallenge [:7 ], []byte ("NTLMSSP" )) {
478
+ return result , GetLDAPError (packet )
479
+ }
480
+ l .Debug .Printf ("%d: found ntlmssp challenge" , msgCtx .id )
481
+ }
482
+ }
483
+ if ntlmsspChallenge != nil {
484
+ // generate a response message to the challenge with the given Username/Password
485
+ responseMessage , err := ntlmssp .ProcessChallenge (ntlmsspChallenge , ntlmBindRequest .Username , ntlmBindRequest .Password )
486
+ if err != nil {
487
+ return result , fmt .Errorf ("parsing ntlm-challenge: %s" , err )
488
+ }
489
+ packet = ber .Encode (ber .ClassUniversal , ber .TypeConstructed , ber .TagSequence , nil , "LDAP Request" )
490
+ packet .AppendChild (ber .NewInteger (ber .ClassUniversal , ber .TypePrimitive , ber .TagInteger , l .nextMessageID (), "MessageID" ))
491
+
492
+ request := ber .Encode (ber .ClassApplication , ber .TypeConstructed , ApplicationBindRequest , nil , "Bind Request" )
493
+ request .AppendChild (ber .NewInteger (ber .ClassUniversal , ber .TypePrimitive , ber .TagInteger , 3 , "Version" ))
494
+ request .AppendChild (ber .NewString (ber .ClassUniversal , ber .TypePrimitive , ber .TagOctetString , "" , "User Name" ))
495
+
496
+ // append the challenge response message as a TagEmbeddedPDV BER value
497
+ auth := ber .Encode (ber .ClassContext , ber .TypePrimitive , ber .TagEmbeddedPDV , responseMessage , "authentication" )
498
+
499
+ request .AppendChild (auth )
500
+ packet .AppendChild (request )
501
+ msgCtx , err = l .sendMessage (packet )
502
+ if err != nil {
503
+ return nil , fmt .Errorf ("send message: %s" , err )
504
+ }
505
+ defer l .finishMessage (msgCtx )
506
+ packetResponse , ok := <- msgCtx .responses
507
+ if ! ok {
508
+ return nil , NewError (ErrorNetwork , errors .New ("ldap: response channel closed" ))
509
+ }
510
+ packet , err = packetResponse .ReadPacket ()
511
+ l .Debug .Printf ("%d: got response %p" , msgCtx .id , packet )
512
+ if err != nil {
513
+ return nil , fmt .Errorf ("read packet: %s" , err )
514
+ }
515
+
516
+ }
517
+
518
+ err = GetLDAPError (packet )
519
+ return result , err
520
+ }
0 commit comments