/**
 * WinPR: Windows Portable Runtime
 * NTLM Security Package (Message)
 *
 * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <winpr/config.h>

#include "ntlm.h"
#include "../sspi.h"

#include <winpr/crt.h>
#include <winpr/assert.h>
#include <winpr/print.h>
#include <winpr/stream.h>
#include <winpr/sysinfo.h>

#include "ntlm_compute.h"

#include "ntlm_message.h"

#include "../../log.h"
#define TAG WINPR_TAG("sspi.NTLM")

#define NTLM_CheckAndLogRequiredCapacity(tag, s, nmemb, what)                                    \
	Stream_CheckAndLogRequiredCapacityEx(tag, WLOG_WARN, s, nmemb, 1, "%s(%s:%" PRIuz ") " what, \
	                                     __func__, __FILE__, (size_t)__LINE__)

static const char NTLM_SIGNATURE[8] = { 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0' };

static void ntlm_free_message_fields_buffer(NTLM_MESSAGE_FIELDS* fields);

const char* ntlm_get_negotiate_string(UINT32 flag)
{
	if (flag & NTLMSSP_NEGOTIATE_56)
		return "NTLMSSP_NEGOTIATE_56";
	if (flag & NTLMSSP_NEGOTIATE_KEY_EXCH)
		return "NTLMSSP_NEGOTIATE_KEY_EXCH";
	if (flag & NTLMSSP_NEGOTIATE_128)
		return "NTLMSSP_NEGOTIATE_128";
	if (flag & NTLMSSP_RESERVED1)
		return "NTLMSSP_RESERVED1";
	if (flag & NTLMSSP_RESERVED2)
		return "NTLMSSP_RESERVED2";
	if (flag & NTLMSSP_RESERVED3)
		return "NTLMSSP_RESERVED3";
	if (flag & NTLMSSP_NEGOTIATE_VERSION)
		return "NTLMSSP_NEGOTIATE_VERSION";
	if (flag & NTLMSSP_RESERVED4)
		return "NTLMSSP_RESERVED4";
	if (flag & NTLMSSP_NEGOTIATE_TARGET_INFO)
		return "NTLMSSP_NEGOTIATE_TARGET_INFO";
	if (flag & NTLMSSP_REQUEST_NON_NT_SESSION_KEY)
		return "NTLMSSP_REQUEST_NON_NT_SESSION_KEY";
	if (flag & NTLMSSP_RESERVED5)
		return "NTLMSSP_RESERVED5";
	if (flag & NTLMSSP_NEGOTIATE_IDENTIFY)
		return "NTLMSSP_NEGOTIATE_IDENTIFY";
	if (flag & NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY)
		return "NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY";
	if (flag & NTLMSSP_RESERVED6)
		return "NTLMSSP_RESERVED6";
	if (flag & NTLMSSP_TARGET_TYPE_SERVER)
		return "NTLMSSP_TARGET_TYPE_SERVER";
	if (flag & NTLMSSP_TARGET_TYPE_DOMAIN)
		return "NTLMSSP_TARGET_TYPE_DOMAIN";
	if (flag & NTLMSSP_NEGOTIATE_ALWAYS_SIGN)
		return "NTLMSSP_NEGOTIATE_ALWAYS_SIGN";
	if (flag & NTLMSSP_RESERVED7)
		return "NTLMSSP_RESERVED7";
	if (flag & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED)
		return "NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED";
	if (flag & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED)
		return "NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED";
	if (flag & NTLMSSP_NEGOTIATE_ANONYMOUS)
		return "NTLMSSP_NEGOTIATE_ANONYMOUS";
	if (flag & NTLMSSP_RESERVED8)
		return "NTLMSSP_RESERVED8";
	if (flag & NTLMSSP_NEGOTIATE_NTLM)
		return "NTLMSSP_NEGOTIATE_NTLM";
	if (flag & NTLMSSP_RESERVED9)
		return "NTLMSSP_RESERVED9";
	if (flag & NTLMSSP_NEGOTIATE_LM_KEY)
		return "NTLMSSP_NEGOTIATE_LM_KEY";
	if (flag & NTLMSSP_NEGOTIATE_DATAGRAM)
		return "NTLMSSP_NEGOTIATE_DATAGRAM";
	if (flag & NTLMSSP_NEGOTIATE_SEAL)
		return "NTLMSSP_NEGOTIATE_SEAL";
	if (flag & NTLMSSP_NEGOTIATE_SIGN)
		return "NTLMSSP_NEGOTIATE_SIGN";
	if (flag & NTLMSSP_RESERVED10)
		return "NTLMSSP_RESERVED10";
	if (flag & NTLMSSP_REQUEST_TARGET)
		return "NTLMSSP_REQUEST_TARGET";
	if (flag & NTLMSSP_NEGOTIATE_OEM)
		return "NTLMSSP_NEGOTIATE_OEM";
	if (flag & NTLMSSP_NEGOTIATE_UNICODE)
		return "NTLMSSP_NEGOTIATE_UNICODE";
	return "NTLMSSP_NEGOTIATE_UNKNOWN";
}

#if defined(WITH_DEBUG_NTLM)
static void ntlm_print_message_fields(const NTLM_MESSAGE_FIELDS* fields, const char* name)
{
	WINPR_ASSERT(fields);
	WINPR_ASSERT(name);

	WLog_VRB(TAG, "%s (Len: %" PRIu16 " MaxLen: %" PRIu16 " BufferOffset: %" PRIu32 ")", name,
	         fields->Len, fields->MaxLen, fields->BufferOffset);

	if (fields->Len > 0)
		winpr_HexDump(TAG, WLOG_TRACE, fields->Buffer, fields->Len);
}

static void ntlm_print_negotiate_flags(UINT32 flags)
{
	WLog_VRB(TAG, "negotiateFlags \"0x%08" PRIX32 "\"", flags);

	for (int i = 31; i >= 0; i--)
	{
		if ((flags >> i) & 1)
		{
			const char* str = ntlm_get_negotiate_string(1u << i);
			WLog_VRB(TAG, "\t%s (%d),", str, (31 - i));
		}
	}
}

static void ntlm_print_negotiate_message(const SecBuffer* NegotiateMessage,
                                         const NTLM_NEGOTIATE_MESSAGE* message)
{
	WINPR_ASSERT(NegotiateMessage);
	WINPR_ASSERT(message);

	WLog_VRB(TAG, "NEGOTIATE_MESSAGE (length = %" PRIu32 ")", NegotiateMessage->cbBuffer);
	winpr_HexDump(TAG, WLOG_TRACE, NegotiateMessage->pvBuffer, NegotiateMessage->cbBuffer);
	ntlm_print_negotiate_flags(message->NegotiateFlags);

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
		ntlm_print_version_info(&(message->Version));
}

static void ntlm_print_challenge_message(const SecBuffer* ChallengeMessage,
                                         const NTLM_CHALLENGE_MESSAGE* message,
                                         const SecBuffer* ChallengeTargetInfo)
{
	WINPR_ASSERT(ChallengeMessage);
	WINPR_ASSERT(message);

	WLog_VRB(TAG, "CHALLENGE_MESSAGE (length = %" PRIu32 ")", ChallengeMessage->cbBuffer);
	winpr_HexDump(TAG, WLOG_TRACE, ChallengeMessage->pvBuffer, ChallengeMessage->cbBuffer);
	ntlm_print_negotiate_flags(message->NegotiateFlags);

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
		ntlm_print_version_info(&(message->Version));

	ntlm_print_message_fields(&(message->TargetName), "TargetName");
	ntlm_print_message_fields(&(message->TargetInfo), "TargetInfo");

	if (ChallengeTargetInfo && (ChallengeTargetInfo->cbBuffer > 0))
	{
		WLog_VRB(TAG, "ChallengeTargetInfo (%" PRIu32 "):", ChallengeTargetInfo->cbBuffer);
		ntlm_print_av_pair_list(ChallengeTargetInfo->pvBuffer, ChallengeTargetInfo->cbBuffer);
	}
}

static void ntlm_print_authenticate_message(const SecBuffer* AuthenticateMessage,
                                            const NTLM_AUTHENTICATE_MESSAGE* message, UINT32 flags,
                                            const SecBuffer* AuthenticateTargetInfo)
{
	WINPR_ASSERT(AuthenticateMessage);
	WINPR_ASSERT(message);

	WLog_VRB(TAG, "AUTHENTICATE_MESSAGE (length = %" PRIu32 ")", AuthenticateMessage->cbBuffer);
	winpr_HexDump(TAG, WLOG_TRACE, AuthenticateMessage->pvBuffer, AuthenticateMessage->cbBuffer);
	ntlm_print_negotiate_flags(message->NegotiateFlags);

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
		ntlm_print_version_info(&(message->Version));

	if (AuthenticateTargetInfo && (AuthenticateTargetInfo->cbBuffer > 0))
	{
		WLog_VRB(TAG, "AuthenticateTargetInfo (%" PRIu32 "):", AuthenticateTargetInfo->cbBuffer);
		ntlm_print_av_pair_list(AuthenticateTargetInfo->pvBuffer, AuthenticateTargetInfo->cbBuffer);
	}

	ntlm_print_message_fields(&(message->DomainName), "DomainName");
	ntlm_print_message_fields(&(message->UserName), "UserName");
	ntlm_print_message_fields(&(message->Workstation), "Workstation");
	ntlm_print_message_fields(&(message->LmChallengeResponse), "LmChallengeResponse");
	ntlm_print_message_fields(&(message->NtChallengeResponse), "NtChallengeResponse");
	ntlm_print_message_fields(&(message->EncryptedRandomSessionKey), "EncryptedRandomSessionKey");

	if (flags & MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK)
	{
		WLog_VRB(TAG, "MessageIntegrityCheck (length = 16)");
		winpr_HexDump(TAG, WLOG_TRACE, message->MessageIntegrityCheck,
		              sizeof(message->MessageIntegrityCheck));
	}
}

static void ntlm_print_authentication_complete(const NTLM_CONTEXT* context)
{
	WINPR_ASSERT(context);

	WLog_VRB(TAG, "ClientChallenge");
	winpr_HexDump(TAG, WLOG_TRACE, context->ClientChallenge, 8);
	WLog_VRB(TAG, "ServerChallenge");
	winpr_HexDump(TAG, WLOG_TRACE, context->ServerChallenge, 8);
	WLog_VRB(TAG, "SessionBaseKey");
	winpr_HexDump(TAG, WLOG_TRACE, context->SessionBaseKey, 16);
	WLog_VRB(TAG, "KeyExchangeKey");
	winpr_HexDump(TAG, WLOG_TRACE, context->KeyExchangeKey, 16);
	WLog_VRB(TAG, "ExportedSessionKey");
	winpr_HexDump(TAG, WLOG_TRACE, context->ExportedSessionKey, 16);
	WLog_VRB(TAG, "RandomSessionKey");
	winpr_HexDump(TAG, WLOG_TRACE, context->RandomSessionKey, 16);
	WLog_VRB(TAG, "ClientSigningKey");
	winpr_HexDump(TAG, WLOG_TRACE, context->ClientSigningKey, 16);
	WLog_VRB(TAG, "ClientSealingKey");
	winpr_HexDump(TAG, WLOG_TRACE, context->ClientSealingKey, 16);
	WLog_VRB(TAG, "ServerSigningKey");
	winpr_HexDump(TAG, WLOG_TRACE, context->ServerSigningKey, 16);
	WLog_VRB(TAG, "ServerSealingKey");
	winpr_HexDump(TAG, WLOG_TRACE, context->ServerSealingKey, 16);
	WLog_VRB(TAG, "Timestamp");
	winpr_HexDump(TAG, WLOG_TRACE, context->Timestamp, 8);
}
#endif

static BOOL ntlm_read_message_header(wStream* s, NTLM_MESSAGE_HEADER* header, UINT32 expected)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(header);

	if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
		return FALSE;

	Stream_Read(s, header->Signature, 8);
	Stream_Read_UINT32(s, header->MessageType);

	if (strncmp((char*)header->Signature, NTLM_SIGNATURE, 8) != 0)
	{
		char Signature[sizeof(header->Signature) * 3 + 1] = WINPR_C_ARRAY_INIT;
		winpr_BinToHexStringBuffer(header->Signature, sizeof(header->Signature), Signature,
		                           sizeof(Signature), TRUE);

		WLog_ERR(TAG, "NTLM_MESSAGE_HEADER Invalid signature, got %s, expected %s", Signature,
		         NTLM_SIGNATURE);
		return FALSE;
	}

	if (header->MessageType != expected)
	{
		WLog_ERR(TAG, "NTLM_MESSAGE_HEADER Invalid message type, got %s, expected %s",
		         ntlm_message_type_string(header->MessageType), ntlm_message_type_string(expected));
		return FALSE;
	}

	return TRUE;
}

static BOOL ntlm_write_message_header(wStream* s, const NTLM_MESSAGE_HEADER* header)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(header);

	if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, sizeof(NTLM_SIGNATURE) + 4ull,
	                                      "NTLM_MESSAGE_HEADER::header"))
		return FALSE;

	Stream_Write(s, header->Signature, sizeof(NTLM_SIGNATURE));
	Stream_Write_UINT32(s, header->MessageType);

	return TRUE;
}

static BOOL ntlm_populate_message_header(NTLM_MESSAGE_HEADER* header, UINT32 MessageType)
{
	WINPR_ASSERT(header);

	CopyMemory(header->Signature, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE));
	header->MessageType = MessageType;
	return TRUE;
}

static BOOL ntlm_read_message_fields(wStream* s, NTLM_MESSAGE_FIELDS* fields)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(fields);

	if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
		return FALSE;

	ntlm_free_message_fields_buffer(fields);

	Stream_Read_UINT16(s, fields->Len);          /* Len (2 bytes) */
	Stream_Read_UINT16(s, fields->MaxLen);       /* MaxLen (2 bytes) */
	Stream_Read_UINT32(s, fields->BufferOffset); /* BufferOffset (4 bytes) */
	return TRUE;
}

static BOOL ntlm_write_message_fields(wStream* s, const NTLM_MESSAGE_FIELDS* fields)
{
	UINT16 MaxLen = 0;
	WINPR_ASSERT(s);
	WINPR_ASSERT(fields);

	MaxLen = fields->MaxLen;
	if (fields->MaxLen < 1)
		MaxLen = fields->Len;

	if (!NTLM_CheckAndLogRequiredCapacity(TAG, (s), 8, "NTLM_MESSAGE_FIELDS::header"))
		return FALSE;

	Stream_Write_UINT16(s, fields->Len);          /* Len (2 bytes) */
	Stream_Write_UINT16(s, MaxLen);               /* MaxLen (2 bytes) */
	Stream_Write_UINT32(s, fields->BufferOffset); /* BufferOffset (4 bytes) */
	return TRUE;
}

static BOOL ntlm_read_message_fields_buffer(wStream* s, NTLM_MESSAGE_FIELDS* fields)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(fields);

	if (fields->Len > 0)
	{
		const UINT32 offset = fields->BufferOffset + fields->Len;

		if (fields->BufferOffset > UINT32_MAX - fields->Len)
		{
			WLog_ERR(TAG,
			         "NTLM_MESSAGE_FIELDS::BufferOffset %" PRIu32
			         " too large, maximum allowed is %" PRIu32,
			         fields->BufferOffset, UINT32_MAX - fields->Len);
			return FALSE;
		}

		if (offset > Stream_Length(s))
		{
			WLog_ERR(TAG,
			         "NTLM_MESSAGE_FIELDS::Buffer offset %" PRIu32 " beyond received data %" PRIuz,
			         offset, Stream_Length(s));
			return FALSE;
		}

		fields->Buffer = (PBYTE)malloc(fields->Len);

		if (!fields->Buffer)
		{
			WLog_ERR(TAG, "NTLM_MESSAGE_FIELDS::Buffer allocation of %" PRIu16 "bytes failed",
			         fields->Len);
			return FALSE;
		}

		if (!Stream_SetPosition(s, fields->BufferOffset))
			return FALSE;
		Stream_Read(s, fields->Buffer, fields->Len);
	}

	return TRUE;
}

static BOOL ntlm_write_message_fields_buffer(wStream* s, const NTLM_MESSAGE_FIELDS* fields)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(fields);

	if (fields->Len > 0)
	{
		if (!Stream_SetPosition(s, fields->BufferOffset))
			return FALSE;
		if (!NTLM_CheckAndLogRequiredCapacity(TAG, (s), fields->Len, "NTLM_MESSAGE_FIELDS::Len"))
			return FALSE;

		Stream_Write(s, fields->Buffer, fields->Len);
	}
	return TRUE;
}

void ntlm_free_message_fields_buffer(NTLM_MESSAGE_FIELDS* fields)
{
	if (fields)
	{
		if (fields->Buffer)
		{
			free(fields->Buffer);
			fields->Len = 0;
			fields->MaxLen = 0;
			fields->Buffer = nullptr;
			fields->BufferOffset = 0;
		}
	}
}

static BOOL ntlm_read_negotiate_flags(wStream* s, UINT32* flags, UINT32 required, const char* name)
{
	UINT32 NegotiateFlags = 0;
	char buffer[1024] = WINPR_C_ARRAY_INIT;
	WINPR_ASSERT(s);
	WINPR_ASSERT(flags);
	WINPR_ASSERT(name);

	if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
		return FALSE;

	Stream_Read_UINT32(s, NegotiateFlags); /* NegotiateFlags (4 bytes) */

	if ((NegotiateFlags & required) != required)
	{
		WLog_ERR(TAG, "%s::NegotiateFlags invalid flags 0x08%" PRIx32 ", 0x%08" PRIx32 " required",
		         name, NegotiateFlags, required);
		return FALSE;
	}

	WLog_DBG(TAG, "Read flags %s",
	         ntlm_negotiate_flags_string(buffer, ARRAYSIZE(buffer), NegotiateFlags));
	*flags = NegotiateFlags;
	return TRUE;
}

static BOOL ntlm_write_negotiate_flags(wStream* s, UINT32 flags, const char* name)
{
	char buffer[1024] = WINPR_C_ARRAY_INIT;
	WINPR_ASSERT(s);
	WINPR_ASSERT(name);

	if (!Stream_CheckAndLogRequiredCapacityEx(TAG, WLOG_WARN, s, 4ull, 1ull,
	                                          "%s(%s:%" PRIuz ") %s::NegotiateFlags", __func__,
	                                          __FILE__, (size_t)__LINE__, name))
		return FALSE;

	WLog_DBG(TAG, "Write flags %s", ntlm_negotiate_flags_string(buffer, ARRAYSIZE(buffer), flags));
	Stream_Write_UINT32(s, flags); /* NegotiateFlags (4 bytes) */
	return TRUE;
}

static BOOL ntlm_read_message_integrity_check(wStream* s, size_t* offset, BYTE* data, size_t size,
                                              WINPR_ATTR_UNUSED const char* name)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(offset);
	WINPR_ASSERT(data);
	WINPR_ASSERT(size == WINPR_MD5_DIGEST_LENGTH);
	WINPR_ASSERT(name);

	*offset = Stream_GetPosition(s);

	if (!Stream_CheckAndLogRequiredLength(TAG, s, size))
		return FALSE;

	Stream_Read(s, data, size);
	return TRUE;
}

static BOOL ntlm_write_message_integrity_check(wStream* s, size_t offset, const BYTE* data,
                                               size_t size, WINPR_ATTR_UNUSED const char* name)
{
	size_t pos = 0;

	WINPR_ASSERT(s);
	WINPR_ASSERT(data);
	WINPR_ASSERT(size == WINPR_MD5_DIGEST_LENGTH);
	WINPR_ASSERT(name);

	pos = Stream_GetPosition(s);

	if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, offset, "MessageIntegrityCheck::offset"))
		return FALSE;

	if (!Stream_SetPosition(s, offset))
		return FALSE;
	if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, size, "MessageIntegrityCheck::size"))
		return FALSE;

	Stream_Write(s, data, size);
	return Stream_SetPosition(s, pos);
}

SECURITY_STATUS ntlm_read_NegotiateMessage(NTLM_CONTEXT* context, PSecBuffer buffer)
{
	wStream sbuffer;
	wStream* s = nullptr;
	size_t length = 0;
	const NTLM_NEGOTIATE_MESSAGE empty = WINPR_C_ARRAY_INIT;
	NTLM_NEGOTIATE_MESSAGE* message = nullptr;

	WINPR_ASSERT(context);
	WINPR_ASSERT(buffer);

	message = &context->NEGOTIATE_MESSAGE;
	WINPR_ASSERT(message);

	*message = empty;

	s = Stream_StaticConstInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer);

	if (!s)
		return SEC_E_INTERNAL_ERROR;

	if (!ntlm_read_message_header(s, &message->header, MESSAGE_TYPE_NEGOTIATE))
		return SEC_E_INVALID_TOKEN;

	if (!ntlm_read_negotiate_flags(s, &message->NegotiateFlags,
	                               NTLMSSP_REQUEST_TARGET | NTLMSSP_NEGOTIATE_NTLM |
	                                   NTLMSSP_NEGOTIATE_UNICODE,
	                               "NTLM_NEGOTIATE_MESSAGE"))
		return SEC_E_INVALID_TOKEN;

	context->NegotiateFlags = message->NegotiateFlags;

	/* only set if NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED is set */
	// if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED)
	{
		if (!ntlm_read_message_fields(s, &(message->DomainName))) /* DomainNameFields (8 bytes) */
			return SEC_E_INVALID_TOKEN;
	}

	/* only set if NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED is set */
	// if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED)
	{
		if (!ntlm_read_message_fields(s, &(message->Workstation))) /* WorkstationFields (8 bytes) */
			return SEC_E_INVALID_TOKEN;
	}

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
	{
		if (!ntlm_read_version_info(s, &(message->Version))) /* Version (8 bytes) */
			return SEC_E_INVALID_TOKEN;
	}

	if (!ntlm_read_message_fields_buffer(s, &message->DomainName))
		return SEC_E_INVALID_TOKEN;

	if (!ntlm_read_message_fields_buffer(s, &message->Workstation))
		return SEC_E_INVALID_TOKEN;

	length = Stream_GetPosition(s);
	WINPR_ASSERT(length <= UINT32_MAX);
	buffer->cbBuffer = (ULONG)length;

	if (!sspi_SecBufferAlloc(&context->NegotiateMessage, (ULONG)length))
		return SEC_E_INTERNAL_ERROR;

	CopyMemory(context->NegotiateMessage.pvBuffer, buffer->pvBuffer, buffer->cbBuffer);
	context->NegotiateMessage.BufferType = buffer->BufferType;
#if defined(WITH_DEBUG_NTLM)
	ntlm_print_negotiate_message(&context->NegotiateMessage, message);
#endif
	ntlm_change_state(context, NTLM_STATE_CHALLENGE);
	return SEC_I_CONTINUE_NEEDED;
}

SECURITY_STATUS ntlm_write_NegotiateMessage(NTLM_CONTEXT* context, SecBuffer* buffer)
{
	wStream sbuffer;
	wStream* s = nullptr;
	size_t length = 0;
	const NTLM_NEGOTIATE_MESSAGE empty = WINPR_C_ARRAY_INIT;
	NTLM_NEGOTIATE_MESSAGE* message = nullptr;

	WINPR_ASSERT(context);
	WINPR_ASSERT(buffer);

	message = &context->NEGOTIATE_MESSAGE;
	WINPR_ASSERT(message);

	*message = empty;

	s = Stream_StaticInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer);

	if (!s)
		return SEC_E_INTERNAL_ERROR;

	if (!ntlm_populate_message_header(&message->header, MESSAGE_TYPE_NEGOTIATE))
		return SEC_E_INTERNAL_ERROR;

	if (context->NTLMv2)
	{
		message->NegotiateFlags |= NTLMSSP_NEGOTIATE_56;
		message->NegotiateFlags |= NTLMSSP_NEGOTIATE_VERSION;
		message->NegotiateFlags |= NTLMSSP_NEGOTIATE_LM_KEY;
		message->NegotiateFlags |= NTLMSSP_NEGOTIATE_OEM;
	}

	message->NegotiateFlags |= NTLMSSP_NEGOTIATE_KEY_EXCH;
	message->NegotiateFlags |= NTLMSSP_NEGOTIATE_128;
	message->NegotiateFlags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY;
	message->NegotiateFlags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
	message->NegotiateFlags |= NTLMSSP_NEGOTIATE_NTLM;
	message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN;
	message->NegotiateFlags |= NTLMSSP_REQUEST_TARGET;
	message->NegotiateFlags |= NTLMSSP_NEGOTIATE_UNICODE;

	if (context->confidentiality)
		message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL;

	if (context->SendVersionInfo)
		message->NegotiateFlags |= NTLMSSP_NEGOTIATE_VERSION;

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
	{
		if (!ntlm_get_version_info(&(message->Version)))
			return SEC_E_INTERNAL_ERROR;
	}

	context->NegotiateFlags = message->NegotiateFlags;
	/* Message Header (12 bytes) */
	if (!ntlm_write_message_header(s, &message->header))
		return SEC_E_INTERNAL_ERROR;

	if (!ntlm_write_negotiate_flags(s, message->NegotiateFlags, "NTLM_NEGOTIATE_MESSAGE"))
		return SEC_E_INTERNAL_ERROR;

	/* only set if NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED is set */
	/* DomainNameFields (8 bytes) */
	if (!ntlm_write_message_fields(s, &(message->DomainName)))
		return SEC_E_INTERNAL_ERROR;

	/* only set if NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED is set */
	/* WorkstationFields (8 bytes) */
	if (!ntlm_write_message_fields(s, &(message->Workstation)))
		return SEC_E_INTERNAL_ERROR;

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
	{
		if (!ntlm_write_version_info(s, &(message->Version)))
			return SEC_E_INTERNAL_ERROR;
	}

	length = Stream_GetPosition(s);
	WINPR_ASSERT(length <= UINT32_MAX);
	buffer->cbBuffer = (ULONG)length;

	if (!sspi_SecBufferAlloc(&context->NegotiateMessage, (ULONG)length))
		return SEC_E_INTERNAL_ERROR;

	CopyMemory(context->NegotiateMessage.pvBuffer, buffer->pvBuffer, buffer->cbBuffer);
	context->NegotiateMessage.BufferType = buffer->BufferType;
#if defined(WITH_DEBUG_NTLM)
	ntlm_print_negotiate_message(&context->NegotiateMessage, message);
#endif
	ntlm_change_state(context, NTLM_STATE_CHALLENGE);
	return SEC_I_CONTINUE_NEEDED;
}

SECURITY_STATUS ntlm_read_ChallengeMessage(NTLM_CONTEXT* context, PSecBuffer buffer)
{
	SECURITY_STATUS status = SEC_E_INVALID_TOKEN;
	wStream sbuffer;
	wStream* s = nullptr;
	size_t length = 0;
	size_t StartOffset = 0;
	size_t PayloadOffset = 0;
	NTLM_AV_PAIR* AvTimestamp = nullptr;
	const NTLM_CHALLENGE_MESSAGE empty = WINPR_C_ARRAY_INIT;
	NTLM_CHALLENGE_MESSAGE* message = nullptr;

	if (!context || !buffer)
		return SEC_E_INTERNAL_ERROR;

	if (!ntlm_generate_client_challenge(context))
		return SEC_E_INTERNAL_ERROR;
	message = &context->CHALLENGE_MESSAGE;
	WINPR_ASSERT(message);

	*message = empty;

	s = Stream_StaticConstInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer);

	if (!s)
		return SEC_E_INTERNAL_ERROR;

	StartOffset = Stream_GetPosition(s);

	if (!ntlm_read_message_header(s, &message->header, MESSAGE_TYPE_CHALLENGE))
		goto fail;

	if (!ntlm_read_message_fields(s, &(message->TargetName))) /* TargetNameFields (8 bytes) */
		goto fail;

	if (!ntlm_read_negotiate_flags(s, &message->NegotiateFlags, 0, "NTLM_CHALLENGE_MESSAGE"))
		goto fail;

	context->NegotiateFlags = message->NegotiateFlags;

	if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
		goto fail;

	Stream_Read(s, message->ServerChallenge, 8); /* ServerChallenge (8 bytes) */
	CopyMemory(context->ServerChallenge, message->ServerChallenge, 8);
	Stream_Read(s, message->Reserved, 8); /* Reserved (8 bytes), should be ignored */

	if (!ntlm_read_message_fields(s, &(message->TargetInfo))) /* TargetInfoFields (8 bytes) */
		goto fail;

	if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
	{
		if (!ntlm_read_version_info(s, &(message->Version))) /* Version (8 bytes) */
			goto fail;
	}

	/* Payload (variable) */
	PayloadOffset = Stream_GetPosition(s);

	status = SEC_E_INTERNAL_ERROR;
	if (message->TargetName.Len > 0)
	{
		if (!ntlm_read_message_fields_buffer(s, &(message->TargetName)))
			goto fail;
	}

	if (message->TargetInfo.Len > 0)
	{
		size_t cbAvTimestamp = 0;

		if (!ntlm_read_message_fields_buffer(s, &(message->TargetInfo)))
			goto fail;

		context->ChallengeTargetInfo.pvBuffer = message->TargetInfo.Buffer;
		context->ChallengeTargetInfo.cbBuffer = message->TargetInfo.Len;
		AvTimestamp = ntlm_av_pair_get((NTLM_AV_PAIR*)message->TargetInfo.Buffer,
		                               message->TargetInfo.Len, MsvAvTimestamp, &cbAvTimestamp);

		if (AvTimestamp)
		{
			PBYTE ptr = ntlm_av_pair_get_value_pointer(AvTimestamp);

			if (!ptr)
				goto fail;

			if (context->NTLMv2)
				context->UseMIC = TRUE;

			CopyMemory(context->ChallengeTimestamp, ptr, 8);
		}
	}

	length = (PayloadOffset - StartOffset) + message->TargetName.Len + message->TargetInfo.Len;
	if (length > buffer->cbBuffer)
		goto fail;

	if (!sspi_SecBufferAlloc(&context->ChallengeMessage, (ULONG)length))
		goto fail;

	if (context->ChallengeMessage.pvBuffer)
		CopyMemory(context->ChallengeMessage.pvBuffer, Stream_Buffer(s) + StartOffset, length);
#if defined(WITH_DEBUG_NTLM)
	ntlm_print_challenge_message(&context->ChallengeMessage, message, nullptr);
#endif
	/* AV_PAIRs */

	if (context->NTLMv2)
	{
		if (!ntlm_construct_authenticate_target_info(context))
			goto fail;

		sspi_SecBufferFree(&context->ChallengeTargetInfo);
		context->ChallengeTargetInfo.pvBuffer = context->AuthenticateTargetInfo.pvBuffer;
		context->ChallengeTargetInfo.cbBuffer = context->AuthenticateTargetInfo.cbBuffer;
	}

	ntlm_generate_timestamp(context); /* Timestamp */

	{
		const SECURITY_STATUS rc = ntlm_compute_lm_v2_response(context); /* LmChallengeResponse */
		if (rc != SEC_E_OK)
		{
			status = rc;
			goto fail;
		}
	}

	{
		const SECURITY_STATUS rc2 =
		    ntlm_compute_ntlm_v2_response(context); /* NtChallengeResponse */
		if (rc2 != SEC_E_OK)
		{
			status = rc2;
			goto fail;
		}
	}

	if (!ntlm_generate_key_exchange_key(context)) /* KeyExchangeKey */
		goto fail;
	if (!ntlm_generate_random_session_key(context)) /* RandomSessionKey */
		goto fail;
	if (!ntlm_generate_exported_session_key(context)) /* ExportedSessionKey */
		goto fail;
	if (!ntlm_encrypt_random_session_key(context)) /* EncryptedRandomSessionKey */
		goto fail;

	/* Generate signing keys */
	status = SEC_E_ENCRYPT_FAILURE;
	if (!ntlm_generate_client_signing_key(context))
		goto fail;
	if (!ntlm_generate_server_signing_key(context))
		goto fail;
	/* Generate sealing keys */
	if (!ntlm_generate_client_sealing_key(context))
		goto fail;
	if (!ntlm_generate_server_sealing_key(context))
		goto fail;
	/* Initialize RC4 seal state using client sealing key */
	if (!ntlm_init_rc4_seal_states(context))
		goto fail;
#if defined(WITH_DEBUG_NTLM)
	ntlm_print_authentication_complete(context);
#endif
	ntlm_change_state(context, NTLM_STATE_AUTHENTICATE);
	status = SEC_I_CONTINUE_NEEDED;
fail:
	ntlm_free_message_fields_buffer(&(message->TargetName));
	return status;
}

SECURITY_STATUS ntlm_write_ChallengeMessage(NTLM_CONTEXT* context, SecBuffer* buffer)
{
	wStream sbuffer;
	wStream* s = nullptr;
	size_t length = 0;
	UINT32 PayloadOffset = 0;
	const NTLM_CHALLENGE_MESSAGE empty = WINPR_C_ARRAY_INIT;
	NTLM_CHALLENGE_MESSAGE* message = nullptr;

	WINPR_ASSERT(context);
	WINPR_ASSERT(buffer);

	message = &context->CHALLENGE_MESSAGE;
	WINPR_ASSERT(message);

	*message = empty;

	s = Stream_StaticInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer);

	if (!s)
		return SEC_E_INTERNAL_ERROR;

	if (!ntlm_get_version_info(&(message->Version))) /* Version */
		return SEC_E_INTERNAL_ERROR;
	if (!ntlm_generate_server_challenge(context)) /* Server Challenge */
		return SEC_E_INTERNAL_ERROR;
	ntlm_generate_timestamp(context);           /* Timestamp */

	if (!ntlm_construct_challenge_target_info(context)) /* TargetInfo */
		return SEC_E_INTERNAL_ERROR;

	CopyMemory(message->ServerChallenge, context->ServerChallenge, 8); /* ServerChallenge */
	message->NegotiateFlags = context->NegotiateFlags;
	if (!ntlm_populate_message_header(&message->header, MESSAGE_TYPE_CHALLENGE))
		return SEC_E_INTERNAL_ERROR;

	/* Message Header (12 bytes) */
	if (!ntlm_write_message_header(s, &message->header))
		return SEC_E_INTERNAL_ERROR;

	if (message->NegotiateFlags & NTLMSSP_REQUEST_TARGET)
	{
		message->TargetName.Len = (UINT16)context->TargetName.cbBuffer;
		message->TargetName.Buffer = (PBYTE)context->TargetName.pvBuffer;
	}

	message->NegotiateFlags |= NTLMSSP_NEGOTIATE_TARGET_INFO;

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_TARGET_INFO)
	{
		message->TargetInfo.Len = (UINT16)context->ChallengeTargetInfo.cbBuffer;
		message->TargetInfo.Buffer = (PBYTE)context->ChallengeTargetInfo.pvBuffer;
	}

	PayloadOffset = 48;

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
		PayloadOffset += 8;

	message->TargetName.BufferOffset = PayloadOffset;
	message->TargetInfo.BufferOffset = message->TargetName.BufferOffset + message->TargetName.Len;
	/* TargetNameFields (8 bytes) */
	if (!ntlm_write_message_fields(s, &(message->TargetName)))
		return SEC_E_INTERNAL_ERROR;

	if (!ntlm_write_negotiate_flags(s, message->NegotiateFlags, "NTLM_CHALLENGE_MESSAGE"))
		return SEC_E_INTERNAL_ERROR;

	if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, 16, "NTLM_CHALLENGE_MESSAGE::ServerChallenge"))
		return SEC_E_INTERNAL_ERROR;

	Stream_Write(s, message->ServerChallenge, 8); /* ServerChallenge (8 bytes) */
	Stream_Write(s, message->Reserved, 8);        /* Reserved (8 bytes), should be ignored */

	/* TargetInfoFields (8 bytes) */
	if (!ntlm_write_message_fields(s, &(message->TargetInfo)))
		return SEC_E_INTERNAL_ERROR;

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
	{
		if (!ntlm_write_version_info(s, &(message->Version))) /* Version (8 bytes) */
			return SEC_E_INTERNAL_ERROR;
	}

	/* Payload (variable) */
	if (message->NegotiateFlags & NTLMSSP_REQUEST_TARGET)
	{
		if (!ntlm_write_message_fields_buffer(s, &(message->TargetName)))
			return SEC_E_INTERNAL_ERROR;
	}

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_TARGET_INFO)
	{
		if (!ntlm_write_message_fields_buffer(s, &(message->TargetInfo)))
			return SEC_E_INTERNAL_ERROR;
	}

	length = Stream_GetPosition(s);
	WINPR_ASSERT(length <= UINT32_MAX);
	buffer->cbBuffer = (ULONG)length;

	if (!sspi_SecBufferAlloc(&context->ChallengeMessage, (ULONG)length))
		return SEC_E_INTERNAL_ERROR;

	CopyMemory(context->ChallengeMessage.pvBuffer, Stream_Buffer(s), length);
#if defined(WITH_DEBUG_NTLM)
	ntlm_print_challenge_message(&context->ChallengeMessage, message,
	                             &context->ChallengeTargetInfo);
#endif
	ntlm_change_state(context, NTLM_STATE_AUTHENTICATE);
	return SEC_I_CONTINUE_NEEDED;
}

SECURITY_STATUS ntlm_read_AuthenticateMessage(NTLM_CONTEXT* context, PSecBuffer buffer)
{
	SECURITY_STATUS status = SEC_E_INVALID_TOKEN;
	wStream sbuffer;
	wStream* s = nullptr;
	size_t length = 0;
	UINT32 flags = 0;
	NTLM_AV_PAIR* AvFlags = nullptr;
	size_t PayloadBufferOffset = 0;
	const NTLM_AUTHENTICATE_MESSAGE empty = WINPR_C_ARRAY_INIT;
	NTLM_AUTHENTICATE_MESSAGE* message = nullptr;
	SSPI_CREDENTIALS* credentials = nullptr;

	WINPR_ASSERT(context);
	WINPR_ASSERT(buffer);

	credentials = context->credentials;
	WINPR_ASSERT(credentials);

	message = &context->AUTHENTICATE_MESSAGE;
	WINPR_ASSERT(message);

	*message = empty;

	s = Stream_StaticConstInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer);

	if (!s)
		return SEC_E_INTERNAL_ERROR;

	if (!ntlm_read_message_header(s, &message->header, MESSAGE_TYPE_AUTHENTICATE))
		goto fail;

	if (!ntlm_read_message_fields(
	        s, &(message->LmChallengeResponse))) /* LmChallengeResponseFields (8 bytes) */
		goto fail;

	if (!ntlm_read_message_fields(
	        s, &(message->NtChallengeResponse))) /* NtChallengeResponseFields (8 bytes) */
		goto fail;

	if (!ntlm_read_message_fields(s, &(message->DomainName))) /* DomainNameFields (8 bytes) */
		goto fail;

	if (!ntlm_read_message_fields(s, &(message->UserName))) /* UserNameFields (8 bytes) */
		goto fail;

	if (!ntlm_read_message_fields(s, &(message->Workstation))) /* WorkstationFields (8 bytes) */
		goto fail;

	if (!ntlm_read_message_fields(
	        s,
	        &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKeyFields (8 bytes) */
		goto fail;

	if (!ntlm_read_negotiate_flags(s, &message->NegotiateFlags, 0, "NTLM_AUTHENTICATE_MESSAGE"))
		goto fail;

	context->NegotiateKeyExchange = (message->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH) != 0;

	if ((context->NegotiateKeyExchange && !message->EncryptedRandomSessionKey.Len) ||
	    (!context->NegotiateKeyExchange && message->EncryptedRandomSessionKey.Len))
		goto fail;

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
	{
		if (!ntlm_read_version_info(s, &(message->Version))) /* Version (8 bytes) */
			goto fail;
	}

	PayloadBufferOffset = Stream_GetPosition(s);

	status = SEC_E_INTERNAL_ERROR;
	if (!ntlm_read_message_fields_buffer(s, &(message->DomainName))) /* DomainName */
		goto fail;

	if (!ntlm_read_message_fields_buffer(s, &(message->UserName))) /* UserName */
		goto fail;

	if (!ntlm_read_message_fields_buffer(s, &(message->Workstation))) /* Workstation */
		goto fail;

	if (!ntlm_read_message_fields_buffer(s,
	                                     &(message->LmChallengeResponse))) /* LmChallengeResponse */
		goto fail;

	if (!ntlm_read_message_fields_buffer(s,
	                                     &(message->NtChallengeResponse))) /* NtChallengeResponse */
		goto fail;

	if (message->NtChallengeResponse.Len > 0)
	{
		size_t cbAvFlags = 0;
		wStream ssbuffer;
		wStream* snt = Stream_StaticConstInit(&ssbuffer, message->NtChallengeResponse.Buffer,
		                                      message->NtChallengeResponse.Len);

		if (!snt)
			goto fail;

		status = SEC_E_INVALID_TOKEN;
		if (!ntlm_read_ntlm_v2_response(snt, &(context->NTLMv2Response)))
			goto fail;
		status = SEC_E_INTERNAL_ERROR;

		context->NtChallengeResponse.pvBuffer = message->NtChallengeResponse.Buffer;
		context->NtChallengeResponse.cbBuffer = message->NtChallengeResponse.Len;
		sspi_SecBufferFree(&(context->ChallengeTargetInfo));
		context->ChallengeTargetInfo.pvBuffer = (void*)context->NTLMv2Response.Challenge.AvPairs;
		context->ChallengeTargetInfo.cbBuffer = message->NtChallengeResponse.Len - (28 + 16);
		CopyMemory(context->ClientChallenge, context->NTLMv2Response.Challenge.ClientChallenge, 8);
		AvFlags =
		    ntlm_av_pair_get(context->NTLMv2Response.Challenge.AvPairs,
		                     context->NTLMv2Response.Challenge.cbAvPairs, MsvAvFlags, &cbAvFlags);

		if (AvFlags)
			flags = winpr_Data_Get_UINT32(ntlm_av_pair_get_value_pointer(AvFlags));
	}

	if (!ntlm_read_message_fields_buffer(
	        s, &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKey */
		goto fail;

	if (message->EncryptedRandomSessionKey.Len > 0)
	{
		if (message->EncryptedRandomSessionKey.Len != 16)
			goto fail;

		CopyMemory(context->EncryptedRandomSessionKey, message->EncryptedRandomSessionKey.Buffer,
		           16);
	}

	length = Stream_GetPosition(s);
	WINPR_ASSERT(length <= UINT32_MAX);

	if (!sspi_SecBufferAlloc(&context->AuthenticateMessage, (ULONG)length))
		goto fail;

	CopyMemory(context->AuthenticateMessage.pvBuffer, Stream_Buffer(s), length);
	buffer->cbBuffer = (ULONG)length;
	if (!Stream_SetPosition(s, PayloadBufferOffset))
		goto fail;

	if (flags & MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK)
	{
		status = SEC_E_INVALID_TOKEN;
		if (!ntlm_read_message_integrity_check(
		        s, &context->MessageIntegrityCheckOffset, message->MessageIntegrityCheck,
		        sizeof(message->MessageIntegrityCheck), "NTLM_AUTHENTICATE_MESSAGE"))
			goto fail;
	}

	status = SEC_E_INTERNAL_ERROR;

#if defined(WITH_DEBUG_NTLM)
	ntlm_print_authenticate_message(&context->AuthenticateMessage, message, flags, nullptr);
#endif

	if (message->UserName.Len > 0)
	{
		credentials->identity.User = (UINT16*)malloc(message->UserName.Len);

		if (!credentials->identity.User)
			goto fail;

		CopyMemory(credentials->identity.User, message->UserName.Buffer, message->UserName.Len);
		credentials->identity.UserLength = message->UserName.Len / 2;
	}

	if (message->DomainName.Len > 0)
	{
		credentials->identity.Domain = (UINT16*)malloc(message->DomainName.Len);

		if (!credentials->identity.Domain)
			goto fail;

		CopyMemory(credentials->identity.Domain, message->DomainName.Buffer,
		           message->DomainName.Len);
		credentials->identity.DomainLength = message->DomainName.Len / 2;
	}

	if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY)
	{
		const SECURITY_STATUS rc = ntlm_compute_lm_v2_response(context); /* LmChallengeResponse */
		if (rc != SEC_E_OK)
			return rc;
	}

	{
		const SECURITY_STATUS rc = ntlm_compute_ntlm_v2_response(context); /* NtChallengeResponse */
		if (rc != SEC_E_OK)
			return rc;
	}

	/* KeyExchangeKey */
	if (!ntlm_generate_key_exchange_key(context))
		return SEC_E_INTERNAL_ERROR;
	/* EncryptedRandomSessionKey */
	if (!ntlm_decrypt_random_session_key(context))
		return SEC_E_INTERNAL_ERROR;
	/* ExportedSessionKey */
	if (!ntlm_generate_exported_session_key(context))
		return SEC_E_INTERNAL_ERROR;

	if (flags & MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK)
	{
		BYTE messageIntegrityCheck[16] = WINPR_C_ARRAY_INIT;

		if (!ntlm_compute_message_integrity_check(context, messageIntegrityCheck,
		                                          sizeof(messageIntegrityCheck)))
			return SEC_E_INTERNAL_ERROR;
		CopyMemory(
		    &((PBYTE)context->AuthenticateMessage.pvBuffer)[context->MessageIntegrityCheckOffset],
		    message->MessageIntegrityCheck, sizeof(message->MessageIntegrityCheck));

		if (memcmp(messageIntegrityCheck, message->MessageIntegrityCheck,
		           sizeof(message->MessageIntegrityCheck)) != 0)
		{
			WLog_ERR(TAG, "Message Integrity Check (MIC) verification failed!");
#ifdef WITH_DEBUG_NTLM
			WLog_ERR(TAG, "Expected MIC:");
			winpr_HexDump(TAG, WLOG_ERROR, messageIntegrityCheck, sizeof(messageIntegrityCheck));
			WLog_ERR(TAG, "Actual MIC:");
			winpr_HexDump(TAG, WLOG_ERROR, message->MessageIntegrityCheck,
			              sizeof(message->MessageIntegrityCheck));
#endif
			return SEC_E_MESSAGE_ALTERED;
		}
	}
	else
	{
		/* no mic message was present

		   https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/f9e6fbc4-a953-4f24-b229-ccdcc213b9ec
		   the mic is optional, as not supported in Windows NT, Windows 2000, Windows XP, and
		   Windows Server 2003 and, as it seems, in the NTLMv2 implementation of Qt5.

		   now check the NtProofString, to detect if the entered client password matches the
		   expected password.
		   */

#ifdef WITH_DEBUG_NTLM
		WLog_VRB(TAG, "No MIC present, using NtProofString for verification.");
#endif

		if (memcmp(context->NTLMv2Response.Response, context->NtProofString, 16) != 0)
		{
			WLog_ERR(TAG, "NtProofString verification failed!");
#ifdef WITH_DEBUG_NTLM
			WLog_ERR(TAG, "Expected NtProofString:");
			winpr_HexDump(TAG, WLOG_ERROR, context->NtProofString, sizeof(context->NtProofString));
			WLog_ERR(TAG, "Actual NtProofString:");
			winpr_HexDump(TAG, WLOG_ERROR, context->NTLMv2Response.Response,
			              sizeof(context->NTLMv2Response));
#endif
			return SEC_E_LOGON_DENIED;
		}
	}

	/* Generate signing keys */
	if (!ntlm_generate_client_signing_key(context))
		return SEC_E_INTERNAL_ERROR;
	if (!ntlm_generate_server_signing_key(context))
		return SEC_E_INTERNAL_ERROR;
	/* Generate sealing keys */
	if (!ntlm_generate_client_sealing_key(context))
		return SEC_E_INTERNAL_ERROR;
	if (!ntlm_generate_server_sealing_key(context))
		return SEC_E_INTERNAL_ERROR;
	/* Initialize RC4 seal state */
	if (!ntlm_init_rc4_seal_states(context))
		return SEC_E_INTERNAL_ERROR;
#if defined(WITH_DEBUG_NTLM)
	ntlm_print_authentication_complete(context);
#endif
	ntlm_change_state(context, NTLM_STATE_FINAL);
	ntlm_free_message_fields_buffer(&(message->DomainName));
	ntlm_free_message_fields_buffer(&(message->UserName));
	ntlm_free_message_fields_buffer(&(message->Workstation));
	ntlm_free_message_fields_buffer(&(message->LmChallengeResponse));
	ntlm_free_message_fields_buffer(&(message->NtChallengeResponse));
	ntlm_free_message_fields_buffer(&(message->EncryptedRandomSessionKey));
	return SEC_E_OK;

fail:
	return status;
}

/**
 * Send NTLMSSP AUTHENTICATE_MESSAGE. msdn{cc236643}
 *
 * @param context Pointer to the NTLM context
 * @param buffer The buffer to write
 */

SECURITY_STATUS ntlm_write_AuthenticateMessage(NTLM_CONTEXT* context, SecBuffer* buffer)
{
	wStream sbuffer;
	wStream* s = nullptr;
	size_t length = 0;
	UINT32 PayloadBufferOffset = 0;
	const NTLM_AUTHENTICATE_MESSAGE empty = WINPR_C_ARRAY_INIT;
	NTLM_AUTHENTICATE_MESSAGE* message = nullptr;
	SSPI_CREDENTIALS* credentials = nullptr;

	WINPR_ASSERT(context);
	WINPR_ASSERT(buffer);

	credentials = context->credentials;
	WINPR_ASSERT(credentials);

	message = &context->AUTHENTICATE_MESSAGE;
	WINPR_ASSERT(message);

	*message = empty;

	s = Stream_StaticInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer);

	if (!s)
		return SEC_E_INTERNAL_ERROR;

	if (context->NTLMv2)
	{
		message->NegotiateFlags |= NTLMSSP_NEGOTIATE_56;

		if (context->SendVersionInfo)
			message->NegotiateFlags |= NTLMSSP_NEGOTIATE_VERSION;
	}

	if (context->UseMIC)
		message->NegotiateFlags |= NTLMSSP_NEGOTIATE_TARGET_INFO;

	if (context->SendWorkstationName)
		message->NegotiateFlags |= NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED;

	if (context->confidentiality)
		message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL;

	if (context->CHALLENGE_MESSAGE.NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
		message->NegotiateFlags |= NTLMSSP_NEGOTIATE_KEY_EXCH;

	message->NegotiateFlags |= NTLMSSP_NEGOTIATE_128;
	message->NegotiateFlags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY;
	message->NegotiateFlags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
	message->NegotiateFlags |= NTLMSSP_NEGOTIATE_NTLM;
	message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN;
	message->NegotiateFlags |= NTLMSSP_REQUEST_TARGET;
	message->NegotiateFlags |= NTLMSSP_NEGOTIATE_UNICODE;

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
	{
		if (!ntlm_get_version_info(&(message->Version)))
			return SEC_E_INTERNAL_ERROR;
	}

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED)
	{
		message->Workstation.Len = context->Workstation.Length;
		message->Workstation.Buffer = (BYTE*)context->Workstation.Buffer;
	}

	if (credentials->identity.DomainLength > 0)
	{
		message->NegotiateFlags |= NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED;
		message->DomainName.Len = (UINT16)credentials->identity.DomainLength * sizeof(WCHAR);
		message->DomainName.Buffer = (BYTE*)credentials->identity.Domain;
	}

	message->UserName.Len = (UINT16)credentials->identity.UserLength * sizeof(WCHAR);
	message->UserName.Buffer = (BYTE*)credentials->identity.User;
	message->LmChallengeResponse.Len = (UINT16)context->LmChallengeResponse.cbBuffer;
	message->LmChallengeResponse.Buffer = (BYTE*)context->LmChallengeResponse.pvBuffer;
	message->NtChallengeResponse.Len = (UINT16)context->NtChallengeResponse.cbBuffer;
	message->NtChallengeResponse.Buffer = (BYTE*)context->NtChallengeResponse.pvBuffer;

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
	{
		message->EncryptedRandomSessionKey.Len = 16;
		message->EncryptedRandomSessionKey.Buffer = context->EncryptedRandomSessionKey;
	}

	PayloadBufferOffset = 64;

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
		PayloadBufferOffset += 8; /* Version (8 bytes) */

	if (context->UseMIC)
		PayloadBufferOffset += 16; /* Message Integrity Check (16 bytes) */

	message->DomainName.BufferOffset = PayloadBufferOffset;
	message->UserName.BufferOffset = message->DomainName.BufferOffset + message->DomainName.Len;
	message->Workstation.BufferOffset = message->UserName.BufferOffset + message->UserName.Len;
	message->LmChallengeResponse.BufferOffset =
	    message->Workstation.BufferOffset + message->Workstation.Len;
	message->NtChallengeResponse.BufferOffset =
	    message->LmChallengeResponse.BufferOffset + message->LmChallengeResponse.Len;
	message->EncryptedRandomSessionKey.BufferOffset =
	    message->NtChallengeResponse.BufferOffset + message->NtChallengeResponse.Len;
	if (!ntlm_populate_message_header(&message->header, MESSAGE_TYPE_AUTHENTICATE))
		return SEC_E_INVALID_TOKEN;
	if (!ntlm_write_message_header(s, &message->header)) /* Message Header (12 bytes) */
		return SEC_E_INTERNAL_ERROR;
	if (!ntlm_write_message_fields(
	        s, &(message->LmChallengeResponse))) /* LmChallengeResponseFields (8 bytes) */
		return SEC_E_INTERNAL_ERROR;
	if (!ntlm_write_message_fields(
	        s, &(message->NtChallengeResponse))) /* NtChallengeResponseFields (8 bytes) */
		return SEC_E_INTERNAL_ERROR;
	if (!ntlm_write_message_fields(s, &(message->DomainName))) /* DomainNameFields (8 bytes) */
		return SEC_E_INTERNAL_ERROR;
	if (!ntlm_write_message_fields(s, &(message->UserName))) /* UserNameFields (8 bytes) */
		return SEC_E_INTERNAL_ERROR;
	if (!ntlm_write_message_fields(s, &(message->Workstation))) /* WorkstationFields (8 bytes) */
		return SEC_E_INTERNAL_ERROR;
	if (!ntlm_write_message_fields(
	        s,
	        &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKeyFields (8 bytes) */
		return SEC_E_INTERNAL_ERROR;
	if (!ntlm_write_negotiate_flags(s, message->NegotiateFlags, "NTLM_AUTHENTICATE_MESSAGE"))
		return SEC_E_INTERNAL_ERROR;

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
	{
		if (!ntlm_write_version_info(s, &(message->Version))) /* Version (8 bytes) */
			return SEC_E_INTERNAL_ERROR;
	}

	if (context->UseMIC)
	{
		const BYTE data[WINPR_MD5_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT;

		context->MessageIntegrityCheckOffset = Stream_GetPosition(s);
		if (!ntlm_write_message_integrity_check(s, Stream_GetPosition(s), data, sizeof(data),
		                                        "NTLM_AUTHENTICATE_MESSAGE"))
			return SEC_E_INTERNAL_ERROR;
	}

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED)
	{
		if (!ntlm_write_message_fields_buffer(s, &(message->DomainName))) /* DomainName */
			return SEC_E_INTERNAL_ERROR;
	}

	if (!ntlm_write_message_fields_buffer(s, &(message->UserName))) /* UserName */
		return SEC_E_INTERNAL_ERROR;

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED)
	{
		if (!ntlm_write_message_fields_buffer(s, &(message->Workstation))) /* Workstation */
			return SEC_E_INTERNAL_ERROR;
	}

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY)
	{
		if (!ntlm_write_message_fields_buffer(
		        s, &(message->LmChallengeResponse))) /* LmChallengeResponse */
			return SEC_E_INTERNAL_ERROR;
	}
	if (!ntlm_write_message_fields_buffer(
	        s, &(message->NtChallengeResponse))) /* NtChallengeResponse */
		return SEC_E_INTERNAL_ERROR;

	if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
	{
		if (!ntlm_write_message_fields_buffer(
		        s, &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKey */
			return SEC_E_INTERNAL_ERROR;
	}

	length = Stream_GetPosition(s);
	WINPR_ASSERT(length <= UINT32_MAX);

	if (!sspi_SecBufferAlloc(&context->AuthenticateMessage, (ULONG)length))
		return SEC_E_INTERNAL_ERROR;

	CopyMemory(context->AuthenticateMessage.pvBuffer, Stream_Buffer(s), length);
	buffer->cbBuffer = (ULONG)length;

	if (context->UseMIC)
	{
		/* Message Integrity Check */
		if (!ntlm_compute_message_integrity_check(context, message->MessageIntegrityCheck,
		                                          sizeof(message->MessageIntegrityCheck)))
			return SEC_E_INTERNAL_ERROR;
		if (!ntlm_write_message_integrity_check(
		        s, context->MessageIntegrityCheckOffset, message->MessageIntegrityCheck,
		        sizeof(message->MessageIntegrityCheck), "NTLM_AUTHENTICATE_MESSAGE"))
			return SEC_E_INTERNAL_ERROR;
	}

#if defined(WITH_DEBUG_NTLM)
	ntlm_print_authenticate_message(&context->AuthenticateMessage, message,
	                                context->UseMIC ? MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK : 0,
	                                &context->AuthenticateTargetInfo);
#endif
	ntlm_change_state(context, NTLM_STATE_FINAL);
	return SEC_E_OK;
}
