/*

Miranda NG: the free IM client for Microsoft* Windows*

Copyright 2012-13 Miranda NG project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "commonheaders.h"

#define NeedBytes(n)   if(bytesRemaining<(n)) pBlob=(PBYTE)DBRead(ofsBlobPtr,(n),&bytesRemaining)
#define MoveAlong(n)   {int x=n; pBlob+=(x); ofsBlobPtr+=(x); bytesRemaining-=(x);}
#define VLT(n) ((n==DBVT_UTF8)?DBVT_ASCIIZ:n)

DBSignature dbSignatureSecured = {"Miranda ICQ SD",0x1A};
DBSignature dbSignatureNonSecured = {"Miranda ICQ SA",0x1A};

CDbxMmapSA::CDbxMmapSA(const TCHAR* tszFileName) :
	CDb3Mmap(tszFileName)
{
}

int CDbxMmapSA::Load(bool bSkipInit)
{
	if ( CDb3Mmap::Load(bSkipInit) != ERROR_SUCCESS)
		return 1;

	if ( CheckDbHeaders())
		return 1;

	if (!bSkipInit) {
		TCHAR* p = _tcsrchr(m_tszProfileName, '\\');
		if (!p)
			return 1;

		if (m_bEncoding && !CheckPassword( LOWORD(m_dbHeader.version), p+1))
			return 1;

		InitDialogs();
	}

	return 0;
}

int CDbxMmapSA::CheckDbHeaders()
{
	if ( memcmp(m_dbHeader.signature, &dbSignatureSecured, sizeof(m_dbHeader.signature)) == 0)
		m_bEncoding = true;
	else if ( memcmp(m_dbHeader.signature, &dbSignatureNonSecured, sizeof(m_dbHeader.signature)) == 0)
		m_bEncoding = false;
	else {
		m_bEncoding = false;
		if ( memcmp(m_dbHeader.signature,&dbSignature,sizeof(m_dbHeader.signature)))
			return EGROKPRF_UNKHEADER;
		if ( LOWORD(m_dbHeader.version) != 0x0700)
			return EGROKPRF_VERNEWER;
	}
	if (m_dbHeader.ofsUser == 0)
		return EGROKPRF_DAMAGED;
	return 0;
}

///////////////////////////////////////////////////////////////////////////////

static DWORD __inline GetSettingValueLength(PBYTE pSetting)
{
	if(pSetting[0]&DBVTF_VARIABLELENGTH) return 2+*(PWORD)(pSetting+1);
	return pSetting[0];
}

void CDbxMmapSA::EncodeContactSettings(HANDLE hContact)
{
	if (!hContact)
		hContact = (HANDLE)m_dbHeader.ofsUser;

	DBContact *contact = (DBContact *)DBRead((DWORD)hContact, sizeof(DBContact), NULL);
	if (contact->ofsFirstSettings) {
		DBContactSettings *setting = (struct DBContactSettings *)DBRead(contact->ofsFirstSettings, sizeof(struct DBContactSettings), NULL);
		DWORD offset = contact->ofsFirstSettings;
		while( true ) {
			DWORD ofsBlobPtr;
			PBYTE pBlob;
			int bytesRemaining;
			DWORD len;

			ofsBlobPtr = offset + offsetof(struct DBContactSettings,blob);
			pBlob = (PBYTE)DBRead(ofsBlobPtr,1,&bytesRemaining);
			while(pBlob[0]) {
				NeedBytes(1);
				NeedBytes(1+pBlob[0]);
				MoveAlong(1+pBlob[0]);

				NeedBytes(5);

				switch(pBlob[0]) {
				case DBVT_DELETED: break;
				case DBVT_BYTE: break;
				case DBVT_WORD:
					CryptoEngine->EncryptMem(pBlob+1, 2, key);
					break;

				case DBVT_DWORD:
					CryptoEngine->EncryptMem(pBlob+1, 4, key);
					break;

				case DBVT_UTF8:
				case DBVT_ASCIIZ:
				case DBVT_BLOB:
					NeedBytes(3+*(PWORD)(pBlob+1));
					len = *(PWORD)(pBlob+1);

					CryptoEngine->EncryptMem(pBlob+3, len, key);
					break;
				}
				NeedBytes(3);
				MoveAlong(1+GetSettingValueLength(pBlob));
				NeedBytes(1);
			}

			if (!setting->ofsNext)
				break;

			offset = setting->ofsNext;
			setting = (struct DBContactSettings *)DBRead(setting->ofsNext, sizeof(struct DBContactSettings), NULL);
		}
	}
}

void CDbxMmapSA::DecodeContactSettings(HANDLE hContact)
{
	if (!hContact)
		hContact = (HANDLE)m_dbHeader.ofsUser;

	DBContact *contact = (DBContact *)DBRead((DWORD)hContact, sizeof(DBContact), NULL);
	if (contact->ofsFirstSettings){
		DBContactSettings *setting = (struct DBContactSettings *)DBRead(contact->ofsFirstSettings, sizeof(struct DBContactSettings), NULL);
		DWORD offset = contact->ofsFirstSettings;
		while (true) {
			DWORD ofsBlobPtr;
			PBYTE pBlob;
			int bytesRemaining;
			DWORD len;
			ofsBlobPtr = offset + offsetof(struct DBContactSettings,blob);
			pBlob = (PBYTE)DBRead(ofsBlobPtr,1,&bytesRemaining);
			while(pBlob[0]) {
				NeedBytes(1);
				NeedBytes(1+pBlob[0]);
				//CopyMemory(szSetting,pBlob+1,pBlob[0]); szSetting[pBlob[0]] = 0;
				MoveAlong(1+pBlob[0]);

				NeedBytes(5);

				switch(pBlob[0]) {
				case DBVT_DELETED: break;
				case DBVT_BYTE: break;
				case DBVT_WORD:
					CryptoEngine->DecryptMem(pBlob+1, 2, key);
					break;

				case DBVT_DWORD:
					CryptoEngine->DecryptMem(pBlob+1, 4, key);
					break;

				case DBVT_UTF8:
				case DBVT_ASCIIZ:
				case DBVT_BLOB:
					NeedBytes(3+*(PWORD)(pBlob+1));
					len = *(PWORD)(pBlob+1);

					CryptoEngine->DecryptMem(pBlob+3, len, key);
					break;
				}
				NeedBytes(3);
				MoveAlong(1+GetSettingValueLength(pBlob));
				NeedBytes(1);
			}

			if (!setting->ofsNext)
				break;
			
			offset = setting->ofsNext;
			setting = (struct DBContactSettings *)DBRead(setting->ofsNext, sizeof(struct DBContactSettings), NULL);
		}
	}
}

void CDbxMmapSA::EncodeEvent(HANDLE hEvent)
{
	DBEvent *dbe = (DBEvent*)DBRead((DWORD)hEvent,sizeof(DBEvent),NULL);
	if (dbe->signature = DBEVENT_SIGNATURE)
		CryptoEngine->EncryptMem(DBRead((DWORD)hEvent + offsetof(DBEvent,blob), dbe->cbBlob, NULL), dbe->cbBlob, key);
}

void CDbxMmapSA::DecodeEvent(HANDLE hEvent)
{
	DBEvent *dbe = (DBEvent*)DBRead((DWORD)hEvent,sizeof(DBEvent),NULL);
	if (dbe->signature = DBEVENT_SIGNATURE)
		CryptoEngine->DecryptMem(DBRead((DWORD)hEvent + offsetof(DBEvent,blob), dbe->cbBlob, NULL), dbe->cbBlob, key);
}

void CDbxMmapSA::EncodeContactEvents(HANDLE hContact)
{
	HANDLE hEvent = FindFirstEvent(hContact);
	while (hEvent != 0) {
		EncodeEvent(hEvent);
		hEvent = FindNextEvent(hEvent);
	}
}

void CDbxMmapSA::DecodeContactEvents(HANDLE hContact)
{
	HANDLE hEvent = FindFirstEvent(hContact);
	while (hEvent != 0) {
		DecodeEvent(hEvent);
		hEvent = FindNextEvent(hEvent);
	}
}

int CDbxMmapSA::WorkInitialCheckHeaders(void)
{
	if (m_bEncoding) {
		cb->pfnAddLogMessage(STATUS_SUCCESS,TranslateT("Database is Secured MMAP database"));

		TCHAR* p = _tcsrchr(m_tszProfileName, '\\');
		if (!p)
			return ERROR_BAD_FORMAT;

		if (!CheckPassword( LOWORD(m_dbHeader.version), p+1)) {
			cb->pfnAddLogMessage(STATUS_FATAL,TranslateT("You are not authorized for access to Database"));
			return ERROR_BAD_FORMAT;
		}

		cb->pfnAddLogMessage(STATUS_SUCCESS,TranslateT("Secured MMAP: authorization successful"));
	}

	if ( LOWORD(m_dbHeader.version) != 0x0700 && !m_bEncoding) {
		cb->pfnAddLogMessage(STATUS_FATAL,TranslateT("Database is marked as belonging to an unknown version of Miranda"));
		return ERROR_BAD_FORMAT;
	}

	return ERROR_SUCCESS;
}