package ntlm

import (
	
	
	
	
	
	
	
	
	

	
)

// NTLM v2 client
type Client struct {
	User        string
	Password    string
	Hash        []byte
	Domain      string // e.g "WORKGROUP", "MicrosoftAccount"
	Workstation string // e.g "localhost", "HOME-PC"

	TargetSPN       string           // SPN ::= "service/hostname[:port]"; e.g "cifs/remotehost:1020"
	channelBindings *channelBindings // reserved for future implementation

	nmsg    []byte
	session *Session
}

func ( *Client) () ( []byte,  error) {
	//        NegotiateMessage
	//   0-8: Signature
	//  8-12: MessageType
	// 12-16: NegotiateFlags
	// 16-24: DomainNameFields
	// 24-32: WorkstationFields
	// 32-40: Version
	//   40-: Payload

	 := 32 + 8

	 = make([]byte, )

	copy([:8], signature)
	le.PutUint32([8:12], NtLmNegotiate)
	le.PutUint32([12:16], defaultFlags)

	copy([32:], version)

	.nmsg = 

	return , nil
}

func ( *Client) ( []byte) ( []byte,  error) {
	//        ChallengeMessage
	//   0-8: Signature
	//  8-12: MessageType
	// 12-20: TargetNameFields
	// 20-24: NegotiateFlags
	// 24-32: ServerChallenge
	// 32-40: _
	// 40-48: TargetInfoFields
	// 48-56: Version
	//   56-: Payload

	if len() < 48 {
		return nil, errors.New("message length is too short")
	}

	if !bytes.Equal([:8], signature) {
		return nil, errors.New("invalid signature")
	}

	if le.Uint32([8:12]) != NtLmChallenge {
		return nil, errors.New("invalid message type")
	}

	 := le.Uint32(.nmsg[12:16]) & le.Uint32([20:24])

	if &NTLMSSP_REQUEST_TARGET == 0 {
		return nil, errors.New("invalid negotiate flags")
	}

	 := le.Uint16([12:14])    // cmsg.TargetNameLen
	 := le.Uint16([14:16]) // cmsg.TargetNameMaxLen
	if  <  {
		return nil, errors.New("invalid target name format")
	}
	 := le.Uint32([16:20]) // cmsg.TargetNameBufferOffset
	if len() < int(+uint32()) {
		return nil, errors.New("invalid target name format")
	}
	 := [ : +uint32()] // cmsg.TargetName

	if &NTLMSSP_NEGOTIATE_TARGET_INFO == 0 {
		return nil, errors.New("invalid negotiate flags")
	}

	 := le.Uint16([40:42])    // cmsg.TargetInfoLen
	 := le.Uint16([42:44]) // cmsg.TargetInfoMaxLen
	if  <  {
		return nil, errors.New("invalid target info format")
	}
	 := le.Uint32([44:48]) // cmsg.TargetInfoBufferOffset
	if len() < int(+uint32()) {
		return nil, errors.New("invalid target info format")
	}
	 := [ : +uint32()] // cmsg.TargetInfo
	 := newTargetInfoEncoder(, utf16le.EncodeStringToBytes(.TargetSPN))
	if  == nil {
		return nil, errors.New("invalid target info format")
	}

	//        AuthenticateMessage
	//   0-8: Signature
	//  8-12: MessageType
	// 12-20: LmChallengeResponseFields
	// 20-28: NtChallengeResponseFields
	// 28-36: DomainNameFields
	// 36-44: UserNameFields
	// 44-52: WorkstationFields
	// 52-60: EncryptedRandomSessionKeyFields
	// 60-64: NegotiateFlags
	// 64-72: Version
	// 72-88: MIC
	//   88-: Payload

	 := 64 + 8 + 16

	 := utf16le.EncodeStringToBytes(.Domain)
	 := utf16le.EncodeStringToBytes(.User)
	 := utf16le.EncodeStringToBytes(.Workstation)

	if  == nil {
		 = 
	}

	// LmChallengeResponseLen = 24
	// NtChallengeResponseLen =
	//   len(Response) = 16
	//	 len(NTLMv2ClientChallenge) =
	//     min len size = 28
	//     target info size
	//     padding = 4
	// len(EncryptedRandomSessionKey) = 0 or 16

	 = make([]byte, +len()+len()+len()+
		24+
		(16+(28+.size()+4))+
		16)

	copy([:8], signature)
	le.PutUint32([8:12], NtLmAuthenticate)

	if  != nil {
		 := copy([:], )
		le.PutUint16([28:30], uint16())
		le.PutUint16([30:32], uint16())
		le.PutUint32([32:36], uint32())
		 += 
	}

	if  != nil {
		 := copy([:], )
		le.PutUint16([36:38], uint16())
		le.PutUint16([38:40], uint16())
		le.PutUint32([40:44], uint32())
		 += 
	}

	if  != nil {
		 := copy([:], )
		le.PutUint16([44:46], uint16())
		le.PutUint16([46:48], uint16())
		le.PutUint32([48:52], uint32())
		 += 
	}

	if .User != "" || .Password != "" || .Hash != nil {
		var  error
		var  hash.Hash

		if .Hash != nil {
			 := utf16le.EncodeStringToBytes(strings.ToUpper(.User))

			 = hmac.New(md5.New, ntowfv2Hash(, .Hash, ))
		} else {
			 := utf16le.EncodeStringToBytes(strings.ToUpper(.User))
			 := utf16le.EncodeStringToBytes(.Password)

			 = hmac.New(md5.New, ntowfv2(, , ))
		}

		//        LMv2Response
		//  0-16: Response
		// 16-24: ChallengeFromClient

		 := [ : +24]
		{
			le.PutUint16([12:14], uint16(len()))
			le.PutUint16([14:16], uint16(len()))
			le.PutUint32([16:20], uint32())

			 += 24
		}

		//        NTLMv2Response
		//  0-16: Response
		//   16-: NTLMv2ClientChallenge

		 := [ : len()-16]
		{
			 := [16:]

			//        NTLMv2ClientChallenge
			//   0-1: RespType
			//   1-2: HiRespType
			//   2-4: _
			//   4-8: _
			//  8-16: TimeStamp
			// 16-24: ChallengeFromClient
			// 24-28: _
			//   28-: AvPairs

			 := [24:32]

			 := [16:24]

			,  := rand.Read()
			if  != nil {
				return nil, 
			}

			,  := .InfoMap[MsvAvTimestamp]
			if ! {
				 = [8:16]
				le.PutUint64(, uint64((time.Now().UnixNano()/100)+116444736000000000))
			}

			encodeNtlmv2Response(, , , , , )

			le.PutUint16([20:22], uint16(len()))
			le.PutUint16([22:24], uint16(len()))
			le.PutUint32([24:28], uint32())

			 = len() - 16
		}

		 := new(Session)

		.isClientSide = true

		.user = .User
		.negotiateFlags = 
		.infoMap = .InfoMap

		.Reset()
		.Write([:16])
		 := .Sum(nil)

		 :=  // if ntlm version == 2

		if &NTLMSSP_NEGOTIATE_KEY_EXCH != 0 {
			.exportedSessionKey = make([]byte, 16)
			,  := rand.Read(.exportedSessionKey)
			if  != nil {
				return nil, 
			}
			,  := rc4.NewCipher()
			if  != nil {
				return nil, 
			}
			 := [:]
			.XORKeyStream(, .exportedSessionKey)

			le.PutUint16([52:54], 16)          // amsg.EncryptedRandomSessionKeyLen
			le.PutUint16([54:56], 16)          // amsg.EncryptedRandomSessionKeyMaxLen
			le.PutUint32([56:60], uint32()) // amsg.EncryptedRandomSessionKeyBufferOffset
		} else {
			.exportedSessionKey = 
		}

		le.PutUint32([60:64], )

		copy([64:], version)
		 = hmac.New(md5.New, .exportedSessionKey)
		.Write(.nmsg)
		.Write()
		.Write()
		.Sum([:72]) // amsg.MIC

		{
			.clientSigningKey = signKey(, .exportedSessionKey, true)
			.serverSigningKey = signKey(, .exportedSessionKey, false)

			.clientHandle,  = rc4.NewCipher(sealKey(, .exportedSessionKey, true))
			if  != nil {
				return nil, 
			}

			.serverHandle,  = rc4.NewCipher(sealKey(, .exportedSessionKey, false))
			if  != nil {
				return nil, 
			}
		}

		.session = 
	}

	return , nil
}

func ( *Client) () *Session {
	return .session
}