////////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  File:   NtlmWinHost.cpp
//
//  Note:
//
//		This program is part of the unit test for the NTLM authentication library.
//		Run this program on a Windows machine, and run Application\Tests\NtlmUnitTest
//		on Singularity.  Specify the @remote command, and provide -user=username
//		and -password=password, and the IP address the machine running this program.
//		NtlmUnitTest will then connect to the Windows machine and perform an NTLM
//		exchange.
//
//	TODO:
//
//		*	Clean up this file.
//

#include <windows.h>
#define SECURITY_WIN32
#include <security.h>

using namespace System;
using namespace System::Net;
using namespace System::Net::Sockets;
using namespace System::Diagnostics;
using namespace System::Runtime::InteropServices;
using namespace System::Threading;
using namespace System::Text;
using namespace System::Reflection;
using namespace System::Runtime::CompilerServices;
using namespace System::Security::Permissions;


typedef System::Byte byte;
typedef System::UInt32 uint;
typedef System::UInt16 ushort;
typedef System::UInt64 ulong;

const int NtlmUnitTestPort = 720;

#pragma comment(lib,"secur32.lib")

[StructLayout(LayoutKind::Sequential)]
value struct TestMessageHeader
{
public:
	uint TotalLength;
	uint MessageType;
};

[StructLayout(LayoutKind::Sequential)]
value struct ResultMessage
{
public:
	int Succeeded;
	// unicode string of error follows, no nul terminator
};

enum class TestMessageType
{
	Negotiate = 1,
	Challenge = 2,
	Response = 3,
	Result = 4,	
};

ref class Util
{
public:

	literal String^ HexDigits = "0123456789abcdef";

	static void DumpException(Exception^ chain)
	{
		for (Exception^ ex = chain; ex != nullptr; ex = ex->InnerException) {
			Console::WriteLine("{0}: {1}", ex->GetType()->FullName, ex->Message);
		}
	}

	static array<byte>^ GetSubArray(array<byte>^ arr, int offset, int length)
	{
		array<byte>^ newarray = gcnew array<byte>(length);
		Buffer::BlockCopy(arr, offset, newarray, 0, length);
		return newarray;
	}

	static String^ ByteArrayToString(array<byte>^ arr)
	{
		StringBuilder^ buf = gcnew StringBuilder(arr->Length * 2);
		for (int i = 0; i < arr->Length; i++) {
			byte b = arr[i];
			buf->Append(HexDigits[b >> 4]);
			buf->Append(HexDigits[b & 0xf]);
		}
		return buf->ToString();
	}

public:
    static String^ FormatMessageFromSystem(ULONG message);

    static void DumpBuffer(PUCHAR buffer, int length);
    static void DumpBuffer(array<byte>^ buffer, int offset, int length);

    [DllImport("KERNEL32.DLL", CharSet = CharSet::Unicode)]
    static int FormatMessage(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId,
        DWORD dwLanguageId, LPTSTR lpBuffer, DWORD nSize, PVOID Arguments);


    generic<typename T> static int CompareArraySpans(array<T>^ array1, int offset1, array<T>^ array2, int offset2, int length)
    {
        for (int i = 0; i < length; i++)
        {
            T element1 = array1[i];
            T element2 = array2[i];
            if (element1 < element2)
                return -1;
            if (element1 > element2)
                return 1;
        }

        return 0;
    }


};


//
//
//struct {
//      byte    protocol[8];     // 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0'
//      byte    type;            // 0x01
//      byte    zero[3];
//      short   flags;           // 0xb203
//      byte    zero[2];
//
//0x10    short   dom_len;         // domain string length
//      short   dom_len;         // domain string length
//      short   dom_off;         // domain string offset
//      byte    zero[2];
//
//0x18    short   host_len;        // host string length
//      short   host_len;        // host string length
//      short   host_off;        // host string offset (always 0x20)
//      byte    zero[2];
//
//0x20    byte    host[*];         // host string (ASCII)
//      byte    dom[*];          // domain string (ASCII)
//  } type-1-message
//
//
//

public enum class NtlmMessageType
{
    Negotiate = 1,
    Challenge = 2,
    Response = 3,
};

[Flags]
public enum class NtlmNegotiateFlags
{
    None = 0,

    NegotiateUnicode = 0x00000001,// Text strings are in unicode
    NegotiateOem = 0x00000002,// Text strings are in OEM
    RequestTarget = 0x00000004,// Server should return its authentication realm

    NegotiateSign = 0x00000010,// Request signature capability
    NegotiateSeal = 0x00000020,// Request confidentiality
    NegotiateDatagram = 0x00000040,// Use datagram style authentication
    NegotiateLmKey = 0x00000080,// Use LM session key for sign/seal

    NegotiateNetware = 0x00000100,// NetWare authentication
    NegotiateNtlm = 0x00000200,// NTLM authentication
    NegotiateNtOnly = 0x00000400,// NT authentication only (no LM)
    NegotiateNullSession = 0x00000800,// NULL Sessions on NT 5.0 and beyond

    NegotiateOemDomainSupplied = 0x1000,// Domain Name supplied on negotiate
    NegotiateOemWorkstationSupplied = 0x2000,// Workstation Name supplied on negotiate
    NegotiateLocalCall = 0x00004000,// Indicates client/server are same machine
    NegotiateAlwaysSign = 0x00008000,// Sign for all security levels
};


#define NTLM_CHALLENGE_LENGTH 8

public ref class NtlmConstants
{
public:

    static initonly array<Byte>^ MessageSignature = { 'N', 'T', 'L', 'M', 'S', 'S', 'P', 0 };
};

public ref class NtlmUtil
{
private:
    
    literal int HeaderLength = 0x10;

public:


    static ushort GetUInt16(array<Byte>^ message, int pos)
    {
        return message[pos] + (message[pos + 1] << 8);
    }

    static array<Byte>^ GetCountedBytesAt(array<Byte>^ message, int pos)
    {
        int length = GetUInt16(message, pos + 0);
        int maxlength = GetUInt16(message, pos + 2);
        int offset = GetUInt16(message, pos + 4);

        if (offset >= message->Length)
            throw gcnew Exception("String has invalid offset");

        if (offset + length > message->Length)
            throw gcnew Exception("String has invalid offset / length");

        return Util::GetSubArray(message, offset, length);
    }

    static String^ GetCountedStringAt(array<Byte>^ message, int pos)
    {
        int length = GetUInt16(message, pos + 0);
        int maxlength = GetUInt16(message, pos + 2);
        int offset = GetUInt16(message, pos + 4);

        if (offset >= message->Length)
            throw gcnew Exception("String has invalid offset");

        if (offset + length > message->Length)
            throw gcnew Exception("String has invalid offset / length");

        String^ result = Encoding::Unicode->GetString(message, offset, length);
        return result;
    }

    static void DumpMessage(array<Byte>^ message)
    {
        DumpMessage(message, message->Length);
    }

    static void DumpMessage(array<Byte>^ message, int length)
    {
        Console::WriteLine("");
        Console::WriteLine("NTLM message:");
        Util::DumpBuffer(message, 0, length);

        if (length < HeaderLength) {
            Console::WriteLine("    Message is invalid; too short");
            return;
        }

        for (int i = 0; i < NtlmConstants::MessageSignature->Length; i++) {
            if (message[i] != NtlmConstants::MessageSignature[i]) {
                Console::WriteLine("    Message is invalid; signature does not match");
                return;
            }
        }

        NtlmMessageType type = (NtlmMessageType)message[8];

        switch (type) {
            case NtlmMessageType::Negotiate:
                {
//
//typedef struct _NEGOTIATE_MESSAGE {
//  UCHAR Signature[sizeof(NTLMSSP_SIGNATURE)];
//  NTLM_MESSAGE_TYPE MessageType;
//  ULONG NegotiateFlags;
//  STRING32 OemDomainName;
//  STRING32 OemWorkstationName;
//  ULONG64 Version;
//} NEGOTIATE_MESSAGE, *PNEGOTIATE_MESSAGE;
//
//
                    Console::WriteLine("    Type: Negotiate");
                    NtlmNegotiateFlags flags = (NtlmNegotiateFlags)GetUInt16(message, 12);
                    Console::WriteLine(String::Format("    Flags: 0x{0:x8}  {1}", (UInt32)flags, flags.ToString()));
                    String^ domain = GetCountedStringAt(message, 0x10);
                    Console::WriteLine("    Domain: " + domain);
                }

                break;

            case NtlmMessageType::Challenge:
                {
                    //
//typedef struct _CHALLENGE_MESSAGE {
    //UCHAR Signature[sizeof(NTLMSSP_SIGNATURE)];             // 0x00
    //NTLM_MESSAGE_TYPE MessageType;                          // 0x08
    //STRING32 TargetName;                                    // 0x0c
    //ULONG NegotiateFlags;                                   // 0x10
    //UCHAR Challenge[MSV1_0_CHALLENGE_LENGTH];               // 0x14
    //ULONG64 ServerContextHandle;                            // 0x20
    //STRING32 TargetInfo;                                    // 0x28
    //ULONG64 Version;                                        // 0x30
                    //                                      // 0x38
//} CHALLENGE_MESSAGE, *PCHALLENGE_MESSAGE;
                    //

                    Console::WriteLine("    Type: Challenge");
                    String^ TargetName = GetCountedStringAt(message, 0x0c);
                    array<Byte>^ Challenge = Util::GetSubArray(message, 0x14, NTLM_CHALLENGE_LENGTH);
                    Console::WriteLine("    TargetName:     " + TargetName);
                    Console::WriteLine("    Challenge:      " + Util::ByteArrayToString(Challenge));

                }
                break;

            case NtlmMessageType::Response:
                {
                    //
//typedef struct _AUTHENTICATE_MESSAGE {
    //UCHAR Signature[sizeof(NTLMSSP_SIGNATURE)];
    //NTLM_MESSAGE_TYPE MessageType;
    //STRING32 LmChallengeResponse;
    //STRING32 NtChallengeResponse;
    //STRING32 DomainName;
    //STRING32 UserName;
    //STRING32 Workstation;
    //STRING32 SessionKey;
    //ULONG NegotiateFlags;
    //ULONG64 Version;
//} AUTHENTICATE_MESSAGE, *PAUTHENTICATE_MESSAGE;
//
//typedef struct _OLD_AUTHENTICATE_MESSAGE {
    //UCHAR Signature[sizeof(NTLMSSP_SIGNATURE)];         // 0
    //NTLM_MESSAGE_TYPE MessageType;                      // 8
    //STRING32 LmChallengeResponse;                       // 12   0x0c
    //STRING32 NtChallengeResponse;                       // 20   0x14
    //STRING32 DomainName;                                // 28   0x1c
    //STRING32 UserName;                                  // 36   0x24
    //STRING32 Workstation;                               // 42   0x2c
//} OLD_AUTHENTICATE_MESSAGE, *POLD_AUTHENTICATE_MESSAGE;
  //

                    Console::WriteLine("    Type: Response");
                    array<Byte>^ LmChallengeResponse = GetCountedBytesAt(message, 0x0c);
                    array<Byte>^ NtChallengeResponse = GetCountedBytesAt(message, 0x14);
                    String^ DomainName = GetCountedStringAt(message, 28);
                    String^ UserName = GetCountedStringAt(message, 36);
                    String^ Workstation = GetCountedStringAt(message, 44);

                    Console::WriteLine("    LmChallengeResponse:  " + Util::ByteArrayToString(LmChallengeResponse));
                    Console::WriteLine("    NtChallengeResponse:  " + Util::ByteArrayToString(NtChallengeResponse));
                    
                    Console::WriteLine("    DomainName:   " + DomainName);
                    Console::WriteLine("    UserName:     " + UserName);
                    Console::WriteLine("    Workstation:  " + Workstation);
                }
                break;

            default:
                Console::WriteLine("    Message is invalid; message type byte is not recognized");
                return;
        }

        Console::WriteLine("");

    }
};

#define ThrowStatus(status, message) do { \
    Console::WriteLine("ThrowStatus: " + message); \
    Console::WriteLine(Util::FormatMessageFromSystem(status)); \
    throw gcnew Exception(String::Format("FAILED: {0} - {1}", message, Util::FormatMessageFromSystem(status))); \
} while (0)


#if 0
void CheckStatus(SECURITY_STATUS status, String^ message)
{
    if (status != SEC_E_OK)
        Throwstatus(status);
    Console::WriteLine(message + " - ok");
}
#else

#define CheckStatus(status, message) do { \
    if (status != SEC_E_OK) ThrowStatus(status, message); \
    Console::WriteLine(message + " - ok"); \
    } while(false)

#endif

class StringWrapperW
{
private:
    wchar_t* _buffer;

public:
    StringWrapperW(String^ str)
    {
        _buffer = (wchar_t*)(void*)Marshal::StringToHGlobalUni(str);
    }

    ~StringWrapperW()
    {
        Marshal::FreeHGlobal((IntPtr)(void*)_buffer);
    }

    operator wchar_t*()
    {
        return _buffer;
    }
};

class StringWrapperA
{
private:
    char* _buffer;

public:
    StringWrapperA(String^ str)
    {
        _buffer = (char*)(void*)Marshal::StringToHGlobalAnsi(str);
    }

    ~StringWrapperA()
    {
        Marshal::FreeHGlobal((IntPtr)(void*)_buffer);
    }

    operator char*()
    {
        return _buffer;
    }
};



value struct ManagedSecHandle
{
public:
    ULONG_PTR dwLower;
    ULONG_PTR dwUpper;

public:
    ManagedSecHandle(SecHandle handle)
    {
        dwLower = handle.dwLower;
        dwUpper = handle.dwUpper;
    }

    operator SecHandle()
    {
        SecHandle handle;
        handle.dwLower = dwLower;
        handle.dwUpper = dwUpper;
        return handle;
    }
};

typedef ManagedSecHandle ManagedCredHandle;
typedef ManagedSecHandle ManagedCtxtHandle;



#if 0
ref class SspiNtlmSupplicant : NtlmSupplicant
{
public:
    ManagedCredHandle _credentials;
    ManagedCtxtHandle _context;

    virtual array<Byte>^ GetNegotiate(
        String^ username,
        String^ domain,
        String^ password) override
    {
        SECURITY_STATUS status;

        StringWrapperW username_w(username);
        StringWrapperW domain_w(domain);
        StringWrapperW password_w(password);

        //
        // First, generate the NTLM client credentials
        //

        SEC_WINNT_AUTH_IDENTITY clientIdentity;
        ZeroMemory(&clientIdentity, sizeof(clientIdentity));
        clientIdentity.Domain = domain_w;
        clientIdentity.DomainLength = _tcslen(domain_w);
        clientIdentity.User = username_w;
        clientIdentity.UserLength = _tcslen(username_w);
        clientIdentity.Password = password_w;
        clientIdentity.PasswordLength = _tcslen(password_w);
        clientIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;

        TimeStamp expires;

        CredHandle credentials = { 0, 0 };

        status = AcquireCredentialsHandle(
            NULL,
            L"NTLM",
            SECPKG_CRED_OUTBOUND,           // credentials use
            NULL,                           // logon id (LUID, not used)
            &clientIdentity,                // auth data
            NULL,                           // pGetKeyFn - not used
            NULL,                           // pGetKeyArgument - not used
            &credentials,                  // the credentials returned
            &expires                        // not used
            );

        CheckStatus(status, "Client - Failed to acquire credentials");

        _credentials = ManagedCredHandle(credentials);

        TimeStamp contextExpires;

        SecBuffer outputBuffer;

        array<Byte>^ hello = gcnew array<Byte>(0x100);
        pin_ptr<Byte> hello_pinned = &hello[0];

        outputBuffer.BufferType = SECBUFFER_TOKEN;
        outputBuffer.cbBuffer = hello->Length;
        outputBuffer.pvBuffer = hello_pinned;

        SecBufferDesc outputDesc;
        outputDesc.pBuffers = &outputBuffer;
        outputDesc.cBuffers = 1;
        outputDesc.ulVersion = SECBUFFER_VERSION;

        ULONG attributes;
        CtxtHandle context = { 0, 0 };
        status = InitializeSecurityContext(
            &credentials,                   // credentials
            NULL,                           // existing context handle (none)
            NULL,                           // service principal name (none)
            ISC_REQ_CONNECTION,                              // context requirements (most don't apply)
            0,                              // reserved
            SECURITY_NETWORK_DREP,          // data representation
            NULL,                           // input token (none on first call)
            0,                              // reserved
            &context,                       // the new context handle
            &outputDesc,                    // the output buffer
            &attributes,                    // returns context attributes
            &contextExpires                 // when it expires
            );

        switch (status) {
            case SEC_I_CONTINUE_NEEDED:
                // This is the expected case.
                Console::WriteLine("Client - SEC_I_CONTINUE_NEEDED");
                Console::WriteLine(String::Format("    attributes: 0x{0:x}", attributes));
                _context = ManagedCtxtHandle(context);
                return Util::GetSubArray(hello, 0, outputBuffer.cbBuffer);

            default:
                ThrowStatus(status, "Client - AcquireCredentialsHandle");
        }
    }


    // Returns the response.
    virtual array<Byte>^ ProcessChallenge(array<Byte>^ challenge) override
    {
        TimeStamp contextExpires;

        pin_ptr<Byte> challenge_pinned = &challenge[0];

        SecBuffer challengeSecBuffer;
        challengeSecBuffer.BufferType = SECBUFFER_TOKEN;
        challengeSecBuffer.cbBuffer = challenge->Length;
        challengeSecBuffer.pvBuffer = challenge_pinned;

        SecBufferDesc challengeDesc;
        challengeDesc.cBuffers = 1;
        challengeDesc.pBuffers = &challengeSecBuffer;
        challengeDesc.ulVersion = SECBUFFER_VERSION;

        array<Byte>^ response_buffer = gcnew array<Byte>(0x100);
        pin_ptr<Byte> response_pinned = &response_buffer[0];

        SecBuffer outputBuffer;
        outputBuffer.BufferType = SECBUFFER_TOKEN;
        outputBuffer.cbBuffer = response_buffer->Length;
        outputBuffer.pvBuffer = response_pinned;

        SecBufferDesc outputDesc;
        outputDesc.pBuffers = &outputBuffer;
        outputDesc.cBuffers = 1;
        outputDesc.ulVersion = SECBUFFER_VERSION;

        CredHandle credentials = _credentials;
        CtxtHandle context = _context;

        ULONG attributes;
        SECURITY_STATUS status = InitializeSecurityContext(
            &credentials,                   // credentials
            &context,                       // existing context handle
            NULL,                           // service principal name (none)
            0,                              // context requirements (most don't apply)
            0,                              // reserved
            SECURITY_NETWORK_DREP,          // data representation
            &challengeDesc,                 // input token (none on first call)
            0,                              // reserved
            &context,                       // the new context handle
            &outputDesc,                    // the output buffer
            &attributes,                    // returns context attributes
            &contextExpires                 // when it expires
            );

        _context = ManagedCtxtHandle(context);

        switch (status) {
            case SEC_E_OK:
                // This is the expected case.
                Console::WriteLine("Client - SEC_E_OK");
                Console::WriteLine(String::Format("    attributes: 0x{0:x}", attributes));
                return Util::GetSubArray(response_buffer, 0, outputBuffer.cbBuffer);

            default:
                ThrowStatus(status, "Client - AcquireCredentialsHandle");
        }
    }
};
#endif


//
//This class wraps the Windows NTLMSSP, through the SSPI.
//

ref class SspiNtlmAuthenticator
{
private:

    ManagedCredHandle _credentials;
    ManagedCtxtHandle _context;

public:

    void Initialize()
    {
        SECURITY_STATUS status;

        CredHandle credentials = { 0, 0 };

        TimeStamp expires;
        status = AcquireCredentialsHandle(
            NULL,
            L"NTLM",
            SECPKG_CRED_INBOUND,            // credentials use
            NULL,                           // logon id (LUID, not used)
            NULL,                           // auth data
            NULL,                           // pGetKeyFn - not used
            NULL,                           // pGetKeyArgument - not used
            &credentials,                   // the credentials returned
            &expires                        // not used
            );

        CheckStatus(status, "Server - AcquireCredentialsHandle");

        _credentials = ManagedCredHandle(credentials);
    }

    array<byte>^ GetChallenge(array<byte>^ negotiate)
    {
        pin_ptr<byte> negotiate_pinned = &negotiate[0];

        SecBuffer negotiateSecBuffer;
        negotiateSecBuffer.BufferType = SECBUFFER_TOKEN | SECBUFFER_READONLY;
        negotiateSecBuffer.cbBuffer = negotiate->Length;
        negotiateSecBuffer.pvBuffer = negotiate_pinned;

        SecBufferDesc negotiateDesc;
        negotiateDesc.cBuffers = 1;
        negotiateDesc.pBuffers = &negotiateSecBuffer;
        negotiateDesc.ulVersion = SECBUFFER_VERSION;

        array<byte>^ challenge_buffer = gcnew array<Byte>(0x200);
        pin_ptr<byte> challenge_pinned = &challenge_buffer[0];

        SecBuffer challengeSecBuffer;
        challengeSecBuffer.BufferType = SECBUFFER_TOKEN;
        challengeSecBuffer.cbBuffer = challenge_buffer->Length;
        challengeSecBuffer.pvBuffer = challenge_pinned;

        SecBufferDesc challengeDesc;
        challengeDesc.ulVersion = SECBUFFER_VERSION;
        challengeDesc.cBuffers = 1;
        challengeDesc.pBuffers = &challengeSecBuffer;

        TimeStamp expires;
        ULONG attributes;
        CredHandle credentials = _credentials;
        CtxtHandle context = { 0, 0 };

        SECURITY_STATUS status = AcceptSecurityContext(
            &credentials,
            NULL,                       // no previous context
            &negotiateDesc,             // input buffer
            0,                          // context requirements
            SECURITY_NETWORK_DREP,      // data representation
            &context,
            &challengeDesc,
            &attributes,
            &expires
            );

        switch (status) {
            case SEC_I_CONTINUE_NEEDED:
			{
                // This is the expected success case.

                Console::WriteLine("Server - AcceptSecurityContext succeeded");
                array<byte>^ challenge = gcnew array<Byte>(challengeSecBuffer.cbBuffer);
                Array::Copy(challenge_buffer, 0, challenge, 0, challenge->Length);
                _context = ManagedCtxtHandle(context);
                return challenge;
			}

            default:
                ThrowStatus(status, "Server - AcceptSecurityContext");
        }
    }

    void VerifyResponse(array<Byte>^ response)
    {
        SECURITY_STATUS status;

        pin_ptr<Byte> response_pinned = &response[0];

        SecBuffer responseSecBuffer;
        responseSecBuffer.BufferType = SECBUFFER_TOKEN;
        responseSecBuffer.cbBuffer = response->Length;
        responseSecBuffer.pvBuffer = response_pinned;

        SecBufferDesc responseDesc;
        responseDesc.ulVersion = SECBUFFER_VERSION;
        responseDesc.pBuffers = &responseSecBuffer;
        responseDesc.cBuffers = 1;

        TimeStamp expires;
        ULONG attributes;
        CtxtHandle context = _context;
        CredHandle credentials = _credentials;

        status = AcceptSecurityContext(
            &credentials,
            &context,                   // previous context
            &responseDesc,              // input buffer
            0,                          // context requirements
            SECURITY_NETWORK_DREP,      // data representation
            &context,
            NULL,                       // no output buffer
            &attributes,
            &expires
            );

        _context = ManagedCtxtHandle(context);

        switch (status) {
            case SEC_E_OK:
                Console::WriteLine("Server - Authentication complete");
                break;

            default:
                ThrowStatus(status, "Server - Authentication failed");
        }

        HANDLE token;

        status = QuerySecurityContextToken(&context, &token);
        CheckStatus(status, "Server - QuerySecurityContextToken");

        Console::WriteLine(String::Format("token - 0x{0:x8}", (ULONG_PTR)token));

    }

};



ref class Client
{
private:

	initonly Socket^ _socket;

	Client(Socket^ socket)
	{
		_socket = socket;
	}

	array<Byte>^ ReceiveMessage(TestMessageType% type)
	{
		array<byte>^ headerBuffer = gcnew array<byte>(sizeof(TestMessageHeader));
		
		int length = _socket->Receive(headerBuffer, sizeof(TestMessageHeader), SocketFlags::None);
		if (length == 0) {
			throw gcnew Exception("Server has closed socket.");
		}
		
		if (length < (int)sizeof(TestMessageHeader)) {
			throw gcnew Exception("Received short data from server.");
		}
		pin_ptr<byte> headerBuffer_ptr = &headerBuffer[0];

		TestMessageHeader header = *(TestMessageHeader*)headerBuffer_ptr;
		
		if (header.TotalLength < sizeof(TestMessageHeader)) {
			throw gcnew Exception("Received invalid header from server (length is too short)");
		}
		
		if (header.TotalLength > 0x10000) {
			throw gcnew Exception("Received excessively large message from server.");
		}
		
		int bodyLength = (int)(header.TotalLength - sizeof(TestMessageHeader));
		array<byte>^ body = gcnew array<byte>(bodyLength);
		
		length = _socket->Receive(body, bodyLength, SocketFlags::None);
		if (length == 0)
			throw gcnew Exception("Server has closed socket.");
		
		if (length < bodyLength)
			throw gcnew Exception("Received short data (payload) from server.");
		
		type = (TestMessageType)header.MessageType;
		return body;
	}

	array<byte>^ ReceiveExpectedMessage(TestMessageType type)
	{
		TestMessageType actualType;
		array<byte>^ msg = ReceiveMessage(actualType);
		if (actualType != type) {
			Console::WriteLine("Received message, but its type is not the expected type!");
			Console::WriteLine("Received type {0}, wanted type {1}", actualType, type);
			throw gcnew Exception("Invalid message received.");
		}
		return msg;
	}

	void SendMessage(TestMessageType type, array<byte>^ payload)
	{
		TestMessageHeader header;
		header.TotalLength = (uint)(sizeof(TestMessageHeader) + payload->Length);
		header.MessageType = (uint)type;
		
		array<byte>^ headerBuffer = gcnew array<byte>(sizeof(TestMessageHeader));
		pin_ptr<byte> headerBuffer_pinned = &headerBuffer[0];
		*(TestMessageHeader*)headerBuffer_pinned = header;
		_socket->Send(headerBuffer);
		
		_socket->Send(payload);
	}

	void ThreadRoutine()
	{
		try {
			for (;;) {
				Console::WriteLine("Waiting for Negotiate");
				array<Byte>^ negotiate = ReceiveExpectedMessage(TestMessageType::Negotiate);

				Console::WriteLine("Received Negotiate:");
				NtlmUtil::DumpMessage(negotiate);

				SspiNtlmAuthenticator authenticator;
				authenticator.Initialize();

				array<byte>^ challenge = authenticator.GetChallenge(negotiate);

				Console::WriteLine("Sending Challenge:");
				NtlmUtil::DumpMessage(challenge);
				
				SendMessage(TestMessageType::Challenge, challenge);

				Console::WriteLine("Waiting for Response");

				array<byte>^ response = ReceiveExpectedMessage(TestMessageType::Response);

				Console::WriteLine("Received Response:");
				NtlmUtil::DumpMessage(response);

				String^ resulttext;
				bool succeeded;

				try {
					authenticator.VerifyResponse(response);
					Console::WriteLine("Authentication succeeded");
					resulttext = "OK";
					succeeded = true;
				} catch(Exception^ ex) {
					Console::WriteLine("Authentication failed");
					Util::DumpException(ex);
					resulttext = "FAILED: " + ex->Message;
					succeeded = false;
				}

				ResultMessage result;
				result.Succeeded = succeeded ? 1 : 0;

				Console::WriteLine("Sending Result: " + resulttext);

				Encoding^ encoding = Encoding::Unicode;
				array<byte>^ response_buffer = gcnew array<byte>(sizeof(ResultMessage) + encoding->GetByteCount(resulttext));
				pin_ptr<byte> response_pinned = &response_buffer[0];
				*(ResultMessage*)response_pinned = result;
				encoding->GetBytes(resulttext, 0, resulttext->Length, response_buffer, sizeof(ResultMessage));

				SendMessage(TestMessageType::Result, response_buffer);
			}

		} catch (Exception^ ex) {
			Console::WriteLine("Exception on client: " + _socket->RemoteEndPoint->ToString());
			Util::DumpException(ex);
		} finally {
			_socket->Close();
		}
		Console::WriteLine("Client thread has terminated.");
	}

public:
	static void StartThread(Socket^ socket)
	{
		Client^ client = gcnew Client(socket);
		Thread^ thread = gcnew Thread(gcnew ThreadStart(client, &Client::ThreadRoutine));
		thread->Start();
	}
};

ref class NtlmWinHost
{
public:

	static void Main(array<String^>^ args)
	{
		try {
			Socket^ listener = gcnew Socket(AddressFamily::InterNetwork, SocketType::Stream, ProtocolType::Tcp);

			IPEndPoint^ listenAddress = gcnew IPEndPoint(IPAddress::Any, NtlmUnitTestPort);

			try {
				listener->Bind(listenAddress);
			} catch(Exception^) {
				Console::WriteLine("Failed to bind to listen address: " + listenAddress);
				Console::WriteLine("Check to see if any other apps (including an instance of this one) are using the port.");
				throw;
			}

			listener->Listen(4);

			for (;;) {
				Console::WriteLine("Waiting for clients on address: " + listenAddress);
				Socket^ clientsocket = listener->Accept();
				Client::StartThread(clientsocket);
			}

		} catch(Exception^ ex) {
			Util::DumpException(ex);
		}
	}
};

int main(array<String^> ^args)
{
	NtlmWinHost::Main(args);
    return 0;
}









String^ FormatMessageFromSystem(ULONG message)
{
    array<Char>^ buffer = gcnew array<Char>(0x200);
    pin_ptr<Char> buffer_pinned = &buffer[0];
    int length;

    length = Util::FormatMessage(
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        message,
        LANG_NEUTRAL,
        buffer_pinned,
        buffer->Length,
        NULL);

    if (length == 0)
        return String::Format("(unknown error 0x{0:x8} {0})", message);
    else {
        String^ b = gcnew String(buffer, 0, length);
        String^ result = String::Format("{0} (0x{1:x8} {1})", b, message, message);
        return result;
    }
}

String^ Util::FormatMessageFromSystem(ULONG message)
{
    return ::FormatMessageFromSystem(message);
}

void Util::DumpBuffer(PUCHAR buffer, int length)
{
    StringBuilder line;

    for (int i = 0; i < length; i += 0x10) {

        line.Length = 0;

        for (int j = 0; j < 0x10; j++) {

            if (i + j < length) {
                line.Append(" ");
                byte b = buffer[i + j];
                line.Append((Char)HexDigits[b >> 4]);
                line.Append((Char)HexDigits[b & 0xf]);
            } else {
                line.Append("   ");
            }
        }

        line.Append(" : ");
        for (int j = 0; j < 0x10; j++) {

            if (i + j < length) {
                byte b = buffer[i + j];
                if (b >= 32 && b <= 127) {
                    line.Append((Char)b);
                } else {
                    line.Append(".");
                }
            } else {
                break;
            }
        }

        Console::WriteLine(line.ToString());
    }
}


void Util::DumpBuffer(array<byte>^ buffer, int offset, int length)
{
    StringBuilder line;

    for (int i = 0; i < length; i += 0x10) {

        line.Length = 0;

        for (int j = 0; j < 0x10; j++) {

            if (i + j < length) {
                line.Append(" ");
                byte b = buffer[offset + i + j];
                line.Append((Char)HexDigits[b >> 4]);
                line.Append((Char)HexDigits[b & 0xf]);
            } else {
                line.Append("   ");
            }
        }

        line.Append(" : ");
        for (int j = 0; j < 0x10; j++) {

            if (i + j < length) {
                byte b = buffer[offset + i + j];
                if (b >= 32 && b <= 127) {
                    line.Append((Char)b);
                } else {
                    line.Append(".");
                }
            } else {
                break;
            }
        }

        Console::WriteLine(line.ToString());
    }
}



//
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
//
[assembly:AssemblyTitleAttribute("NtlmWinHost")];
[assembly:AssemblyDescriptionAttribute("Unit test for NTLM authentication library")];
[assembly:AssemblyConfigurationAttribute("")];
[assembly:AssemblyCompanyAttribute("Microsoft")];
[assembly:AssemblyProductAttribute("NtlmWinHost")];
[assembly:AssemblyCopyrightAttribute("Copyright (c)  2006")];
[assembly:AssemblyTrademarkAttribute("")];
[assembly:AssemblyCultureAttribute("")];

//
// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
// You can specify all the value or you can default the Revision and Build Numbers
// by using the '*' as shown below:

[assembly:AssemblyVersionAttribute("1.0.*")];

[assembly:ComVisible(false)];

[assembly:CLSCompliantAttribute(true)];

[assembly:SecurityPermission(SecurityAction::RequestMinimum, UnmanagedCode = true)];
