@@ -11,6 +11,8 @@ import (
1111 "strings"
1212
1313 ber "github.com/go-asn1-ber/asn1-ber"
14+ "github.com/Azure/go-ntlmssp"
15+
1416)
1517
1618// SimpleBindRequest represents a username/password bind operation
@@ -387,3 +389,132 @@ func (l *Conn) ExternalBind() error {
387389
388390 return GetLDAPError (packet )
389391}
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