//this file is part of NeoMule
//Copyright (C)2006-2007 David Xanatos ( Xanatos@Lycos.at / http://NeoMule.tk )
//
//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., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "stdafx.h"
#ifdef _DEBUG
#include "DebugHelpers.h"
#endif

#include <share.h>
#include <io.h>
#include "emule.h"
#include "otherfunctions.h"
#include "preferences.h"
#include "Neopreferences.h"
#include "updownclient.h"
#include "Functions.h"
#include "clientlist.h"
#include "clientcredits.h"
#include "uploadqueue.h"
#include "Version.h"
#include "NeoVersion.h"
#include "EmuleDlg.h"
#include "Log.h"
#include "StringConversion.h"
#include "OpCodes.h"
#include "SafeFile.h"
#include "Argos.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#ifdef ARGOS // NEO: NA - [NeoArgos] -- Xanatos -->

CArgos::CArgos(){
	CreateAntiNickThiefTag();

	m_XsMonitorList.InitHashTable(2011);
	m_dwLastTrackedCleanUp = ::GetTickCount();

	m_dlpInstance = NULL;
	m_dlpCheckClient = NULL;

	threadEndedEvent = new CEvent(0, 1);
	doRun = true;

	if(NeoPrefs.UseDLPScanner())
		LoadDLPlibrary();

	AfxBeginThread(RunProc, (LPVOID)this, THREAD_PRIORITY_BELOW_NORMAL); // THREAD_PRIORITY_LOWEST
}

CArgos::~CArgos(){
	ClearXsMonitorList();

	//EndThread();
	UnLoadDLPlibrary();
	delete threadEndedEvent;
}

void CArgos::ClearXsMonitorList()
{
	TXsExploitEntry* cur_entry;
	uint32 tmpkey;
	POSITION pos = m_XsMonitorList.GetStartPosition();
	while (pos){
		m_XsMonitorList.GetNextAssoc(pos, tmpkey, cur_entry);
		delete cur_entry;
	}
	m_XsMonitorList.RemoveAll();
}

void CArgos::Process()
{
	uint32 cur_tick = ::GetTickCount();

	if(NeoPrefs.IsXsExploitDetection()){
		// Cleanup tracked client list
		if (m_dwLastTrackedCleanUp + TRACKED_CLEANUP_TIME < cur_tick)
		{
			m_dwLastTrackedCleanUp = cur_tick;
			AddDebugLogLine(false, _T("Cleaning up TrackedXsExploitsList, %i entries on List..."), m_XsMonitorList.GetCount());
			POSITION pos = m_XsMonitorList.GetStartPosition();
			uint32 nKey;
			TXsExploitEntry* pResult;
			while (pos != NULL)
			{
				m_XsMonitorList.GetNextAssoc( pos, nKey, pResult );
				if (pResult->m_dwInserted + KEEPTRACK_TIME*2 < cur_tick ){ // x2 track them for a longer time
					m_XsMonitorList.RemoveKey(nKey);
					delete pResult;
				}
			}
			AddDebugLogLine(false, _T("...done, %i entries left on list"), m_XsMonitorList.GetCount());
		}
	}
}

TXsExploitEntry* CArgos::GetXsExploitEntry(uint32 uIP)
{
	TXsExploitEntry* result = NULL;
	if(m_XsMonitorList.Lookup(uIP,result) == FALSE){
		result = new TXsExploitEntry;
		result->m_uFastXSCounter = 0;
		result->m_uXSReqs = 0;
		result->m_uXSAnswer = 0;
		m_XsMonitorList.SetAt(uIP,result);
	}
	result->m_dwInserted = ::GetTickCount();
	return result;
}

// Anti XS Exploit
void CArgos::IncXSReqs(uint32 uIP)	
{
	TXsExploitEntry* result = GetXsExploitEntry(uIP);
	result->m_uXSReqs++;
}

void CArgos::IncXSAnswer(uint32 uIP)	
{
	TXsExploitEntry* result = GetXsExploitEntry(uIP);
	result->m_uXSAnswer++;
}

bool CArgos::CheckXSAsk(CUpDownClient* Client)
{
	TXsExploitEntry* result = GetXsExploitEntry(Client->GetConnectIP());

	// Check for XS exploid, asking without answering
	// WARNING: official clients do this an accepted but not answered request is already being counted !!!
	// for proper operation "unsigned int nTimePassedClient = dwTickCount - GetLastAskedForSources(); // GetLastSrcAnswerTime();" is nessesery !!!
	if(result->m_uXSReqs > 2 
	 && (!result->m_uXSAnswer || (result->m_uXSReqs / result->m_uXSAnswer > 2))){
		if (thePrefs.GetLogBannedClients())
			AddDebugLogLine(false, _T("Clients: %s (%s), Banreason: XsExploit (no answer)"), Client->GetUserName(), ipstr(Client->GetConnectIP()));
		Client->Ban(_T("XsExploit (no answer)"));
		return true;
	}

	// check for fast XS, to fast asking
	// WARNING: official clients do this due to a inconsistent implementation of the reask roules !!!
	// Bevoure we can count the ask we must go sure the client had got our last answer and set SetLastSrcReqTime() only when we realy answered !!!
	 uint32 timeSinceLastAnswer = ::GetTickCount() - Client->GetLastSrcReqTime();
	if(timeSinceLastAnswer > CONNECTION_LATENCY){ // ~22 sec
		if(timeSinceLastAnswer < (SOURCECLIENTREASKS/2)){ // 20 min
			result->m_uFastXSCounter++;
			if(result->m_uFastXSCounter >= BADCLIENTBAN){
				if (thePrefs.GetLogBannedClients())
					AddDebugLogLine(false, _T("Clients: %s (%s), Banreason: XsExploit (to fast)"), Client->GetUserName(), ipstr(Client->GetConnectIP()));
				Client->Ban(_T("XsExploit (to fast)"));
				return true;
			}
		}
		else if(result->m_uFastXSCounter)
			result->m_uFastXSCounter--;
	}

	return false;
}
// Anti XS Exploit END

void CArgos::CheckClient(CUpDownClient* Client)
{
	if(!doRun)
		return;

	TTestClient* TestClient = new TTestClient;

	TestClient->IP		= Client->GetConnectIP();

	TestClient->strNick	= CString(Client->GetUserName());
	TestClient->strMod	= CString(Client->GetClientModVer());
	TestClient->strSoft = CString(Client->GetClientSoftVer());
	if(Client->HasValidHash())
		md4cpy(TestClient->abyHash,Client->GetUserHash());

	QueueLocker.Lock();
    m_TestClientQueue.AddTail(TestClient);
	QueueLocker.Unlock();
}

/*void CArgos::ArgosCheckSoftware(CUpDownClient* Client)
{
	if (
		((Client->GetVersion()>589) && ((Client->GetSourceExchange1Version()>0) || (Client->GetExtendedRequestsVersion()>0)) && (Client->GetClientSoft()==SO_EDONKEY)) //Fake Donkeys
	 || ((Client->GetClientSoft() == SO_EDONKEYHYBRID) &&  ((Client->GetSourceExchange1Version()>0) || (Client->GetExtendedRequestsVersion()>0))) //Fake Hybrids
	 || (Client->SupportSecIdent()!=0 && (Client->GetClientSoft()==SO_EDONKEYHYBRID || Client->GetClientSoft()==SO_EDONKEY)) // this clients don't support sui
	 || (Client->GetVersion() > MAKE_CLIENT_VERSION(0, 30, 0) && Client->GetMuleVersion() > 0 && Client->GetMuleVersion()!=0x99 && Client->GetClientSoft()==SO_EMULE) //Fake emuleVersion
	 || (Client->GetVersion() == MAKE_CLIENT_VERSION(0,44,3) && Client->GetClientModVer().IsEmpty() && Client->GetUnicodeSupport()==utf8strNone && Client->GetClientSoft()==SO_EMULE)
	){
		if (thePrefs.GetLogBannedClients())
			AddDebugLogLine(false, _T("Clients: %s (%s), Banreason: Invalid Software Version"), Client->GetUserName(), ipstr(Client->GetConnectIP()));
		Client->Ban(_T("Invalid Software Version"));
	}
}*/

void CArgos::CheckForModThief(CUpDownClient* Client)
{
	CString tocomp = Client->GetClientModVer();
	if(tocomp.IsEmpty())
		return;

	CString OurMod = CString(MOD_VERSION); //cache it
	CString OurVersion = StrLine(_T("eMule v%u.%u%c"), VERSION_MJR, VERSION_MIN, _T('a') + VERSION_UPDATE);
	if (StrStrI(tocomp, OurMod) //uses our string
	 && (tocomp.GetLength() != OurMod.GetLength() //but not the correct length
	 || !StrStr(tocomp, OurMod) //but not in correct case		
	 || (Client->GetClientSoftVer() != OurVersion) //or wrong version
	 || Client->SupportsModProt() != 1 // Mod Prot Missing
	 )){
		if (thePrefs.GetLogBannedClients())
			AddDebugLogLine(false, _T("Clients: %s (%s), Banreason: Mod Thief"), Client->GetUserName(), ipstr(Client->GetConnectIP()));
		Client->Ban(_T("Mod Thief"));		
	 }
}

void CArgos::CheckForNickThief(CUpDownClient* Client)
{
	CString tocomp = Client->GetUserName();
	//is he mirroring our current tag?
	if(tocomp.Find(m_sAntiNickThiefTag) != -1){
		if (thePrefs.GetLogBannedClients())
			AddDebugLogLine(false, _T("Clients: %s (%s), Banreason: Nick Thief"), Client->GetUserName(), ipstr(Client->GetConnectIP()));
		Client->Ban(_T("Nick Thief"));		
	}
}

void CArgos::CreateAntiNickThiefTag()
{
	m_sAntiNickThiefTag.Empty();
	int maxchar = 4+rand()%4;
	for(int i = 0; i < maxchar; ++i)
		m_sAntiNickThiefTag.AppendFormat(_T("%c"), ((rand()%2) ? _T('A') : _T('a')) + rand()%25);
}

CString	CArgos::GetAntiNickThiefNick()
{
	return StrLine(NeoPrefs.IsNickThiefDetection() == 1 ? _T("%s [%s]") : _T("%s %s"), thePrefs.GetUserNick(), m_sAntiNickThiefTag);
}

/////////////////////////////////////////////////////////////////////////////////////////
// Scanner Engine:

UINT AFX_CDECL CArgos::RunProc(LPVOID pParam)
{
	DbgSetThreadName("Neo Argos Engine");
	// SLUGFILLER: SafeHash // NEO: STS - [SlugFillerThreadSafe] -- Xanatos -->
	CReadWriteLock lock(&theApp.m_threadlock);
	if (!lock.ReadLock(0))
		return 0;
	// SLUGFILLER: SafeHash // NEO: STS END <-- Xanatos --
    CArgos* cargos = (CArgos*)pParam;

	return cargos->RunInternal();
}

UINT CArgos::RunInternal()
{
	CTypedPtrList<CPtrList, TTestClient*> TestClientQueue;

	while(doRun){
        QueueLocker.Lock();
        while(!m_TestClientQueue.IsEmpty()) {
            TTestClient* TestClient = m_TestClientQueue.RemoveHead();
            TestClientQueue.AddTail(TestClient);
        }
        QueueLocker.Unlock();

		if(TestClientQueue.IsEmpty())
			Sleep(100);
		
		threadLocker.Lock();

		TTestClient* TestClient;
		while(!TestClientQueue.IsEmpty())
		{
			TestClient = TestClientQueue.RemoveHead();
		
			if(TArgosResult* Result = PerformDLPCheck(TestClient))
				VERIFY( PostMessage(theApp.emuledlg->m_hWnd, TM_ARGOS_RESULT, (WPARAM)Result, (LPARAM)TestClient->IP) );

			delete TestClient;
		}

		threadLocker.Unlock();
    }

    QueueLocker.Lock();
	while(!m_TestClientQueue.IsEmpty())
        delete m_TestClientQueue.RemoveHead();
    QueueLocker.Unlock();

	threadEndedEvent->SetEvent();

	return FALSE;
}

void CArgos::EndThread()
{
	threadLocker.Lock();
	doRun = false;
	threadLocker.Unlock();

	threadEndedEvent->Lock();
}

////////////////////////////////////////////////////////////////////////////////////////
// DLP - Dynamic leecher protection
TArgosResult* CArgos::PerformDLPCheck(TTestClient* TestClient)
{
	if(m_dlpCheckClient)
	{
		try
		{
			TArgosResult* Result = NULL;
			if(m_dlpCheckClient(TestClient, Result))
				return Result;
		}
		catch(...){
			ASSERT(0);
			DebugLogError(_T("ARGOS-DLP: Unknown exception while performing dlp check, dlp.dll may be corrupted..."));
		}
	}
	return NULL;
}

void DLPLoger(int uType,CStringW strMessage)
{
	switch(uType)
	{
	case  1: 
		ModLog(LOG_SUCCESS,GetResString(IDS_X_DLP_LOG),strMessage); break;
	case -1: 
		ModLogError(GetResString(IDS_X_DLP_LOG),strMessage); break;
	case -2: 
		ModLogWarning(GetResString(IDS_X_DLP_LOG),strMessage);	break;
	case  0: 
	default:
		ModLog(GetResString(IDS_X_DLP_LOG),strMessage);
	}
}

bool CArgos::LoadDLPlibrary()
{
	if (!PathFileExists(GetDefaultFilePathDLP())){
		ModLog(LOG_WARNING, GetResString(IDS_X_ARGOS_DLP_FILE_MISSING));
		return false;
	}

	threadLocker.Lock();

	uint8 uError = 0;
	
	if(m_dlpInstance)
		UnLoadDLPlibrary();

	try
	{
		m_dlpInstance = ::LoadLibrary(GetDefaultFilePathDLP());
		if(m_dlpInstance)
		{
			DLPGETVERSION dlpGetVersion = (DLPGETVERSION)GetProcAddress(m_dlpInstance,("dlpGetVersion"));
			if(dlpGetVersion && dlpGetVersion() >= 2) // 1 is not compatible anymore
			{
				DLPINITIALISE dlpInitialise	= (DLPINITIALISE)GetProcAddress(m_dlpInstance,("dlpInitialise"));
				if(dlpInitialise)
				{
					const UINT uDetectionLevel	= NeoPrefs.GetDetectionLevel();
					const UINT uModDetection	= NeoPrefs.IsLeecherModDetection();
					const UINT uNickDetection	= NeoPrefs.IsLeecherNickDetection();
					const UINT uHashDetection	= NeoPrefs.IsLeecherHashDetection();
					UINT uPreferences = 
					//	((u					& 0xff)	<<  24) |
					//	((u					& 0x03)	<<  22) |
					//	((u					& 0x03)	<<  20) |
					//	((u					& 0x03)	<<  18) |
					//	((u					& 0x03)	<<  16) |
					//	((u					& 0x03)	<<  14) |
						((uHashDetection	& 0x03)	<<  12) |
						((uNickDetection	& 0x03)	<<  10) |
						((uModDetection		& 0x03)	<<  8 ) |
					//	((u					& 0x0f)	<<  4 ) |
						((uDetectionLevel	& 0x0f)	<<  0 ) ;

					if(dlpInitialise(uPreferences, &DLPLoger))
					{
						m_dlpCheckClient = (DLPCHECKCLIENT)GetProcAddress(m_dlpInstance,("dlpCheckClient"));
						if(m_dlpCheckClient)
						{
							ModLog(GetResString(IDS_X_ARGOS_DLP_LOADED));
							threadLocker.Unlock();
							return true;
						}
						else
							uError = 1;
					}
					else
						uError = 3;
				}
				else
					uError = 1;
			}
			else
				uError = 1;
		}
		else
			uError = 1;
		
		if(m_dlpInstance)
			::FreeLibrary(m_dlpInstance);
		m_dlpInstance = NULL;
		m_dlpCheckClient = NULL;
	}
	catch(...){
		ASSERT(0);
		uError = 2;
		if(m_dlpInstance){
			HINSTANCE dlpInstance = m_dlpInstance;
			m_dlpInstance = NULL;
			::FreeLibrary(dlpInstance);
		}
		m_dlpCheckClient = NULL;
	}

	ModLog(LOG_ERROR,GetResString(IDS_X_ARGOS_DLP_LOAD_FAILED),uError == 2 ? GetResString(IDS_X_FATAL) : uError == 3 ? GetResString(IDS_X_INTERNAL) : GetResString(IDS_X_GENERIC));

	threadLocker.Unlock();
	return false;
}

void CArgos::UnLoadDLPlibrary()
{
	if(m_dlpInstance){
		::FreeLibrary(m_dlpInstance);
		ModLog(GetResString(IDS_X_ARGOS_DLP_UNLOADED));
	}

	m_dlpInstance = NULL;
	m_dlpCheckClient = NULL;
}

CString CArgos::GetDefaultFilePathDLP() const
{
	return thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) +  DFLT_DLP_FILENAME;
}
#endif // ARGOS // NEO: NA END <-- Xanatos --