/*---------------------------------------------------------------------------*/
/* ntlm.h                                                                    */
/* copyright (c) innovaphone 2016                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

// Some good descriptions of the NTLM protocol as well as the data structures
// can be found at the following web pages:
//
// http://davenport.sourceforge.net/ntlm.html
// https://developer.gnome.org/evolution-exchange/stable/ximian-connector-ntlm.html
// http://www.innovation.ch/personal/ronald/ntlm.html *
//
// * This page seems to be outdated, but has a good quasi C code of the authentication
//   type 3 message, wich is the base of the code implemented here.
//
//
// The messages have the folowing structures
//
// NOTE 1: One funny thing of the protocol is, that the length and max-length fields 
// ======= always needs to have the same value; maybe only Microsoft know's why...)
//
// NOTE 2: The additional informations or names (like domain, workstation name, etc.)
// ======= Don't need to be in that order at the end of the header as described here.
//         The order of that data will be defined through the offset of them.
//
// Structure of Type 1 Message
//
// Offset |   Type   | Field
// ---------------------------------------------------
//   0    |  char[8] | NTLM Header ID  = 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0'
//   8    |  dword   | Message type (always 1)
//  12    |  dword   | flags - combination of the the ntlm_flags values
//  16    |  word    | domainLength = Length of domain name (optional, could be 0)
//  18    |  word    | domainLengthMax = maximum length of domain name (always the same as domainLengh)
//  20    |  dword   | domainOffset - the offset where the domain starts inside the memory buffer (optional, could be 0)
//  24    |  word    | workstationLength = Length of domain name (optional, could be 0)
//  26    |  word    | workstationLengthMax = maximum length of workstation name (always the same as workstationLength)
//  28    |  dword   | workstationOffset - the offset where the workstation starts inside the memory buffer (optional, could be 0)
//  32    |  byte    | OS major version number (optional)
//  33    |  byte    | OS minor version number (optional)
//  34    |  word    | OS build number (optional)
//  36    |  dword   | unkown, set to 0x0000000f
//  40    |  ....    | The domain name (optional)
//  ...   |  ....    | The workstation name (optional)
//
// Note that domain and workstation name are optional and only need to be send when the flags for them
// (NTLM_NEGOTIATE_DOMAIN or NTLM_NEGOTIATE_WORKSTATION) are set.
// If the optional os version number is not given, the whole block from byte 32 - 40 must be ignored.
// In that case, the data block starts at byte 32.
//
//
//
// Structure of Type 2 Message
//
// Offset |   Type   | Field
// ---------------------------------------------------
//   0    |  char[8] | NTLM Header ID  = 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0'
//   8    |  dword   | Message type (always 2)
//  12    |  word    | domainLength = Length of domain name
//  14    |  word    | domainLengthMax = maximum length of domain name (always the same as domainLengh)
//  16    |  dword   | domainOffset - the offset where the domain starts inside the memory buffer
//  20    |  dword   | flags
//  24    |  char[8] | nonce value, need for the calculation of Type 3 message
//  32    |  char[8] | Context (optional - set to zero values)
//  40    |  word    | dataLength - length of additional data (target information - optional)
//  42    |  word    | dataLengthMax - maximum lenth of additional data (optional)
//  44    |  dword   | dataOffset - offset to first entrance of additional data inside the memory buffer (optional)
//  48    |  ....    | Domain-Name in unicode. The length is given in domainLength
//  ...   |  ....    | Additional data  (optional)
//
// Additional data has the following format:
//
//   0    |  word    | Type of the data (one of the ntlm_type_flags values
//   2    |  word    | length in bytes of the data
//   4    |  char[]  | The data itsef
//
//
// Structure of Type 3 Message
//
// Offset |   Type   | Field
// ---------------------------------------------------
//   0    |  char[8] | NTLM Header ID  = 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0'
//   8    |  dword   | Message type (always 3)
//  12    |  word    | LAN Manager response length (always 0x18)
//  14    |  word    | LAN Manager response max length
//  16    |  dword   | LAN Manager response offset
//  20    |  word    | NTLM response length (always 0x18)
//  22    |  word    | NTLM response max length
//  24    |  dword   | NTLM response offset
//  28    |  word    | NT domain name length
//  30    |  word    | NT domain name max length
//  32    |  dword   | NT domain name offset (always 0x0040)
//  36    |  word    | Username length
//  38    |  word    | Username max length
//  40    |  dword   | Username offset
//  44    |  word    | Local workstation name length
//  46    |  word    | Local workstation name max length
//  48    |  dword   | Local workstation name offset
//  52    |  word    | Session key length (optional, could be 0)
//  54    |  word    | Session key max length (optional, could be 0)
//  56    |  dword   | Session key offset (optional, could be 0)
//  60    |  dword   | Flags
//  64    |  ....    | NT domain name (UCS-16LE)
//  ...   |  ....    | Username (UCS-16LE)
//  ...   |  ....    | Local workstation name (UCS-16LE)
//  ...   |  ....    | LAN Manager response
//  ...   |  ....    | NT response
//
// Note that the flags NTLM_NEGOTIATE_DOMAIN and/or NTLM_NEGOTIATE_WORKSTATION had to be set if
// the name of the domain and/or workstation need to be send too.
//

// For NTLMv2, the NTv2 part of the Type 3 Message has a variable size, depending on the size of the Type 2 message Target Information block.
// The format of the NT2 data will be calculated from the following blob:
//
// Offset |   Type   | Field
// ---------------------------------------------------
//    0   |  word    | Blob signature (0x01010000)
//    4   |  word    | reserved (set to 0)
//    8   |  long64  | Timestamp, little endian. 64bit signed value representing the time in thenths of microsecends since Januar 1st, 1601.
//   16   |  char[8] | Client nonce (random number)
//   24   |  char[4] | Unkown
//   28   |  ....    | Target information block from the Type 2 message.
//   ...  |  char[4] | Unkown
//
// To this blob, the challange from the Type 2 message will be added as well as a 16 byte NTv2 hash (hmac md5) which includes the NTLMv1 password hash.


static constexpr size_t NTLM_TYPE_3_MESSAGE_BASE_SIZE = 64;
static constexpr size_t NTLM_TYPE_3_MESSAGE_LM_RESPONSE_SIZE = 24;
static constexpr size_t NTLM_TYPE_3_MESSAGE_NT1_RESPONSE_SIZE = 24;
static constexpr size_t NTLM_TYPE_2_MESSAGE_CHALLENGE_SIZE = 8;
static constexpr size_t NTLM_TYPE_3_MESSAGE_NT2_BLOB_BASE_SIZE = 32;
static constexpr size_t NTLM_TYPE_3_MESSAGE_NT2_HASH_SIZE = 16;
static constexpr dword NTLM_TYPE_1_MESSAGE = 0x00000001;
static constexpr dword NTLM_TYPE_2_MESSAGE = 0x00000002;
static constexpr dword NTLM_TYPE_3_MESSAGE = 0x00000003;

// Indicies to write the data to...
static constexpr int IDX_NTLM_HEADER = 0;
static constexpr int IDX_NTLM_MESSAGE_TYPE = 8;

// Indicies for Type 1 message
static constexpr int IDX_TYPE_1_MESSAGE_FLAGS = 12;
static constexpr int IDX_TYPE_1_MESSAGE_DOMAIN_LEN = 16;
static constexpr int IDX_TYPE_1_MESSAGE_DOMAIN_LEN_MAX = 18;
static constexpr int IDX_TYPE_1_MESSAGE_DOMAIN_OFFSET = 20;
static constexpr int IDX_TYPE_1_MESSAGE_WORKSTATION_LEN = 24;
static constexpr int IDX_TYPE_1_MESSAGE_WORKSTATION_LEN_MAX = 26;
static constexpr int IDX_TYPE_1_MESSAGE_WORKSTATION_OFFSET = 28;
static constexpr int IDX_TYPE_1_OS_MAJOR_VERSION = 32;
static constexpr int IDX_TYPE_1_OS_MAINOR_VERSION = 33;
static constexpr int IDX_TYPE_1_OS_BUILD_NUMBER = 34;
static constexpr int IDX_TYPE_1_OS_VERSION_UNKOWN_DATA = 36;
static constexpr int IDX_TYPE_1_DATA_START_NO_OS_VERSION = 32;
static constexpr int IDX_TYPE_1_DATA_START_OS_VERSION = 40;

// Indicies for Type 2 message
static constexpr int IDX_TYPE_2_TARGET_NAME_LEN = 12;
static constexpr int IDX_TYPE_2_TARGET_NAME_LEN_MAX = 14;
static constexpr int IDX_TYPE_2_TARGET_NAME_OFFSET = 16;
static constexpr int IDX_TYPE_2_FLAGS = 20;
static constexpr int IDX_TYPE_2_CHALLENGE = 24;
static constexpr int IDX_TYPE_2_CONTEXT = 32;
static constexpr int IDX_TYPE_2_TARGET_INFORMATION_LEN = 40;
static constexpr int IDX_TYPE_2_TARGET_INFORMATION_LEN_MAX = 42;
static constexpr int IDX_TYPE_2_TARGET_INFORMATION_OFFSET = 44;


// Indicies for Type 3 message
static constexpr int IDX_LM_RESPONSE_LENGTH = 12;
static constexpr int IDX_LM_RESPONSE_LENGTH_MAX = 14;
static constexpr int IDX_LM_RESPONSE_OFFSET = 16;
static constexpr int IDX_NT_RESPONSE_LENGTH = 20;
static constexpr int IDX_NT_RESPONSE_LENGTH_MAX = 22;
static constexpr int IDX_NT_RESPONSE_OFFSET = 24;
static constexpr int IDX_DOMAIN_LENGTH = 28;
static constexpr int IDX_DOMAIN_LENGTH_MAX = 30;
static constexpr int IDX_DOMAIN_OFFSET = 32;
static constexpr int IDX_USER_NAME_LENGTH = 36;
static constexpr int IDX_USER_NAME_LENGTH_MAX = 38;
static constexpr int IDX_USER_NAME_OFFSET = 40;
static constexpr int IDX_WORKSTATION_LENGTH = 44;
static constexpr int IDX_WORKSTATION_LENGTH_MAX = 46;
static constexpr int IDX_WORKSTATION_OFFSET = 48;
static constexpr int IDX_SESSION_KEY_LENGTH = 52;
static constexpr int IDX_SESSION_KEY_LENGTH_MAX = 54;
static constexpr int IDX_SESSION_KEY_OFFSET = 56;
static constexpr int IDX_TYPE_3_MESSAGE_FLAGS = 60;
static constexpr int IDX_DATA_BASE = 64;

// Indicies for the v2 blob data block
static constexpr int IDX_V2_BLOB_SIGNATURE = 0;
static constexpr int IDX_V2_BLOB_RESERVED = 4;
static constexpr int IDX_V2_BLOB_TIMESTAMPT = 8;
static constexpr int IDX_V2_BLOB_CLIENT_CONCE = 16;
static constexpr int IDX_V2_BLOB_UNKOWN = 24;
static constexpr int IDX_V2_BLOB_TARGET_INFORMATION = 28;


enum ntlm_flags {
    NTLM_NEGOTIATE_UNICODE      = 0x00000001,  // The client sets this flag to indicate that it supports Unicode strings.
    NTLM_NEGOTIATE_OEM          = 0x00000002,  // This is set to indicate that the client supports OEM strings.
    NTLM_REQUEST_TARGET         = 0x00000004,  // This requests that the server send the authentication target with the Type 2 reply.
    NTLM_UNKNOWN_FLAG_1         = 0x00000008,  // Unknown 

    NTLM_NEGOTIATE_SIGN         = 0x00000010,  // Negotiate Sign
    NTLM_NEGOTIATE_SEAL         = 0x00000020,  // Negotiate Seal
    NTLM_NEGOTIATE_DATAGRAM     = 0x00000040,  // Negotiate Datagram Style
    NTLM_NEGOTIATE_LM_KEY       = 0x00000080,  // Negotiate Lan Manager Key

    NTLM_NEGOTIATE_NETWARE      = 0x00000100,  // Negotiate Netware
    NTLM_NEGOTIATE_NTLM         = 0x00000200,  // Indicates that NTLM authentication is supported.
    NTLM_UNKOWN_FLAG_2          = 0x00000400,  // Unknown 
    NTLM_NEGOTIATE_ANONYMOUS    = 0x00000800,  // Negotiate Anonymous

    NTLM_NEGOTIATE_DOMAIN       = 0x00001000,  // When set, the client will send with the message the name of the domain in which the workstation has membership.
    NTLM_NEGOTIATE_WORKSTATION  = 0x00002000,  // Indicates that the client is sending its workstation name with the message.
    NTLM_NEGOTIATE_LOCAL_CALL   = 0x00004000,  // Negotiate Local Call
    NTLM_NEGOTIATE_ALWAYS_SIGN  = 0x00008000,  // Indicates that communication between the client and server after authentication should carry a "dummy" signature.

    NTLM_TARGET_TYPE_DOMAIN     = 0x00010000,  // The server sets this flag to indicate that the authentication target is being sent with the message and represents a domain.
    NTLM_TARGET_TYPE_SERVER     = 0x00020000,  // The server sets this flag to indicate that the authentication target is being sent with the message and represents a server.
    NTLM_TARGET_TYPE_SHARE      = 0x00040000,  // The server apparently sets this flag to indicate that the authentication target is being sent with the message and represents a network share.This has not been confirmed.
    NTLM_NEGOTIATE_NTLM2        = 0x00080000,  // Indicates that this client supports the NTLM2 signing and sealing scheme; if negotiated, this can also affect the response calculations.
	 
	NTLM_TARGET_INFO            = 0x00800000,  // Indicates that an type 2 message provides an target information block.

    //                          = 0x01000000   // unknown
    //                          = 0x02000000   // unknown
    //                          = 0x04000000   // unknown
    //                          = 0x08000000   // unknown

    //                          = 0x10000000;  // unknown
    NTLM_NEGOTIATE_128          = 0x20000000,  // Indicates that this client supports strong (128-bit) encryption.
    NTLM_NEGOTIATE_KEY_EXCHANGE = 0x40000000,  // Negotiate Key Exchange
    NTLM_NEGOTIATE_56           = 0x80000000   // Indicates that this client supports medium (56-bit) encryption.
};


enum ntlm_name_type {
    NTLM_TYPE_WINS_NAME        = 0x0001,      // WINS name of server
    NTLM_TYPE_NT_DOMAIN_NAME   = 0x0002,      // NT domain name
    NTLM_TYPE_DNS_NAME         = 0x0003,      // DNS name of server
    NTLM_TYPE_WIN2000_NAME     = 0x0004       // Windows 2000 domain name
};


enum ntlm_target_information_type {
	NTLM_TI_SERVER_NAME = 0x0001,
	NTLM_TI_DOMAIN_NAME = 0x0002,
	NTLM_TI_FQ_DNS      = 0x0003,
	NTLM_TI_DNS_DOMAIN  = 0x0004
};


class NTLMType1Message {
protected:
	std::vector<byte> buffer;
	const std::string domain;
	const std::string workstation;
	dword flags;
	bool needBuild;
	void Build();

public:
    NTLMType1Message(const char * domnain = nullptr, const char * workstation = nullptr);
    ~NTLMType1Message();

    void SetFlag(ntlm_flags flag);
    dword Flags();

	byte * GetByteStream();
	size_t GetByteStreamLength();
};


class NTLMType2Message {
protected:
	std::vector<byte> buffer;
	std::string targetName;
	dword flags;
	
	std::string ReadUNICODEString(const byte * ucstrBuffer, size_t len);
	bool CheckHeader();
	void ReadTargetName();
	
public:
    NTLMType2Message(const byte * b64Data);
	
    ~NTLMType2Message();

	const char * TargetName();
    dword Flags();
	bool FlagSet(dword flag) { return (this->Flags() & flag) != 0; }
	const byte * Challenge();
	size_t TargetInformationSize();
	const byte * TargetInformationData();
};


class NTLMType3Message {
protected:
    NTLMType2Message * type2Message;
	std::string user;
	std::string password;
	std::string domainName;
	std::string workstationName;
	std::vector<byte> buffer;
	dword flags;
	size_t ntResponseSize;
	bool needBuild;
	bool useUnicode;
	byte v2ClientNonce[8];

	void AllocBuffer();
	void Build();
	
	size_t WriteLMResponse(size_t offset);
	void WriteNTResponse(size_t offset);
	size_t WriteLMv2Response(size_t offset, const byte * v2Hash, const byte * v1PwdHash);
	void WriteNT2Response(size_t offset, const byte * v2Hash);
	
	std::vector<char> StringToUnicode(const std::string & str);
	void CreateHashHmacMD5(const byte * data, size_t dataLen, const byte * key, size_t keyLen, byte * out);
	void CreateV2ClientNonce();
	void CreateV2Hash(byte * hash, byte * v1PasswordHash);
	void CreateNTLMPasswordHash(byte * dest);
	std::vector<byte> CreateBlob();

public:
    NTLMType3Message(const char * user,
                     const char * password,
		             const char * domainName,
                     const char * workstationName,
                     NTLMType2Message * const type2Msg);
    ~NTLMType3Message();

    void SetFlag(ntlm_flags flag);
    dword GetFlags();

    const byte * GetByteStream();
	size_t GetByteStreamLength();
};



class NTv2Blob {
private:
	std::vector<byte> buffer;
	
public:
	NTv2Blob(NTLMType2Message * type2Message, const byte (&clientNonce)[8]);
	~NTv2Blob()	{}
	
	std::vector<byte>::iterator Begin() { return this->buffer.begin(); }
	std::vector<byte>::iterator End() { return this->buffer.end(); }
	const byte * Data()	{ return this->buffer.data(); }
	size_t Size() { return this->buffer.size(); }
};