//=============================================================================
//
//   File : libkvifish.cpp
//   Creation date : Sun Jan 30 2011 12:11:41 CEST by Fabio Bas
//
//   This file is part of the KVIrc irc client distribution
//   Copyright (C) 2011 Fabio Bas (ctrlaltca at gmail dot com)
//
//   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 opinion) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//=============================================================================

#include "libkvifish.h"

#include "KviModule.h"
#include "KviModuleManager.h"
#include "KviKvsEventTable.h"
#include "KviLocale.h"
#include "KviConsoleWindow.h"
#include "KviQueryWindow.h"
#include "KviCString.h"
#include "kvi_out.h"

/*
	@doc: fish
	@type:
		module
	@short:
		A FISH implementation
	@title:
		The FISH module
	@body:
		A FISH implementation
*/

#ifdef COMPILE_CRYPT_SUPPORT
	#include "KviMemory.h"
	#include "KviPointerList.h"
	#include "KviCryptEngine.h"
	#include "KviCryptController.h"
	// KviApplication.cpp
	extern KVIRC_API KviCryptEngineManager * g_pCryptEngineManager;
	
	#if defined(COMPILE_SSL_SUPPORT) && !defined(COMPILE_CRYPTOPP_SUPPORT)

		#include <KviSSL.h>
		#include <openssl/blowfish.h>
		#include <openssl/bn.h>
		#include <openssl/dh.h>
		#include <openssl/err.h>
		#include <openssl/ssl.h>
		#include <openssl/sha.h>

		DH * g_fish_dh = 0;
		
		// ### new sophie-germain 1080bit prime number ###
		// const char *g_fish_prime1080_bin="++ECLiPSE+is+proud+to+present+latest+FiSH+release+featuring+even+more+security+for+you+++shouts+go+out+to+TMG+for+helping+to+generate+this+cool+sophie+germain+prime+number++++/C32L";
		const char *g_fish_prime1080_hex="FBE1022E23D213E8ACFA9AE8B9DFADA3EA6B7AC7A7B7E95AB5EB2DF858921FEADE95E6AC7BE7DE6ADBAB8A783E7AF7A7FA6A2B7BEB1E72EAE2B72F9FA2BFB2A2EFBEFAC868BADB3E828FA8BADFADA3E4CC1BE7E8AFE85E9698A783EB68FA07A77AB6AD7BEB618ACF9CA2897EB28A6189EFA07AB99A8A7FA9AE299EFA7BA66DEAFEFBEFBF0B7D8B";
		// const char *g_fish_prime1080_dec="12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923";
		const char *g_fish_generator="2";

	#elif defined( COMPILE_CRYPTOPP_SUPPORT )
		#include <cryptopp/dh.h>
		#include <cryptopp/integer.h>
		#include <cryptopp/osrng.h>
		#include <cryptopp/sha.h>
		
		CryptoPP::DH * g_fish_dh = 0;
		CryptoPP::SecByteBlock * g_fish_pubKey = 0;
		CryptoPP::SecByteBlock * g_fish_privKey = 0;
		
		const char *g_fish_prime1080_hex="0xFBE1022E23D213E8ACFA9AE8B9DFADA3EA6B7AC7A7B7E95AB5EB2DF858921FEADE95E6AC7BE7DE6ADBAB8A783E7AF7A7FA6A2B7BEB1E72EAE2B72F9FA2BFB2A2EFBEFAC868BADB3E828FA8BADFADA3E4CC1BE7E8AFE85E9698A783EB68FA07A77AB6AD7BEB618ACF9CA2897EB28A6189EFA07AB99A8A7FA9AE299EFA7BA66DEAFEFBEFBF0B7D8B";
		const char *g_fish_generator="0x2";
	#endif

	#define FISH_KEYLEN 180
	#define SHA256_LEN 32

	static bool fish_DH1080_gen(unsigned char ** szPubKey, int * iLen)
	{
	#if defined(COMPILE_SSL_SUPPORT) && !defined(COMPILE_CRYPTOPP_SUPPORT)
		if(!g_fish_dh)
		{
			BIGNUM * dhp = BN_new();
			BN_init(dhp);
			if(!BN_hex2bn(&dhp,g_fish_prime1080_hex))
				return false;

			BIGNUM * dhg = BN_new();
			BN_init(dhg);
			if(!BN_hex2bn(&dhg,g_fish_generator))
				return false;

			g_fish_dh = DH_new();
			g_fish_dh->p = dhp;
			g_fish_dh->g = dhg;

			DH_generate_key(g_fish_dh);
		}

		*iLen = BN_num_bytes(g_fish_dh->pub_key);
		*szPubKey = (unsigned char *) KviMemory::allocate(*iLen);
		BN_bn2bin(g_fish_dh->pub_key, *szPubKey);

		return true;
	#elif defined( COMPILE_CRYPTOPP_SUPPORT )
		try {
			if(!g_fish_dh)
			{
				CryptoPP::AutoSeededRandomPool rnd;
				CryptoPP::Integer dhp(g_fish_prime1080_hex);
				CryptoPP::Integer dhg(g_fish_generator);

				g_fish_dh = new CryptoPP::DH();
				g_fish_dh->AccessGroupParameters().Initialize(dhp, dhg);

				g_fish_pubKey = new CryptoPP::SecByteBlock(g_fish_dh->PublicKeyLength());
				g_fish_privKey = new CryptoPP::SecByteBlock(g_fish_dh->PrivateKeyLength());
				g_fish_dh->GenerateKeyPair(rnd, *g_fish_privKey, *g_fish_pubKey);
			}

			*iLen = g_fish_pubKey->SizeInBytes();
			*szPubKey = (unsigned char *) KviMemory::allocate(*iLen);
			KviMemory::copy(*szPubKey, g_fish_pubKey->BytePtr(), g_fish_pubKey->SizeInBytes());
		} catch(CryptoPP::Exception e)
		{
			qDebug("%s",__tr2qs("Crypto++ threw the following exception: %1").arg(QString(e.what())).toUtf8().data());
			return false;
		}
		return true;
	#else
		qDebug("%s",__tr2qs("FiSH has been compiled without ssl or cryptopp support, unable to proceed").toUtf8().data());
		return false;
	#endif
	}

	bool fish_event_onQueryNotice(KviKvsModuleEventCall * c)
	{
		/*	EVENT("OnQueryNotice", \
			"$0 = source nick\n" \
			"$1 = source username\n" \
			"$2 = source host\n" \
			"$3 = message"),
			*/
		KviKvsVariantList * pParams = c->params();
		if(!pParams)
			return true;

		QString szNick, szMessage, szKey;

		if(!pParams->firstAsString(szNick))
			return true;
		
		pParams->next();
		pParams->next();

		if(!pParams->nextAsString(szMessage))
			return true;

		if(!szMessage.startsWith("DH1080_", Qt::CaseSensitive))
			return true;

		unsigned char * szMyPubKey = 0;
		QByteArray szHisPubKey, szTmp;
		int iMyPubKeyLen, * pMyPubKeyLen = &iMyPubKeyLen;
		if(!fish_DH1080_gen(&szMyPubKey, pMyPubKeyLen))
		{
			#if !defined(COMPILE_SSL_SUPPORT) &&  !defined(COMPILE_CRYPTOPP_SUPPORT)
				c->warning(__tr2qs("FiSH has been compiled without ssl and crypto++ support, unable to proceed"));
				return false;
			#endif
			return false;
		}

		if(szMessage.startsWith("DH1080_INIT ", Qt::CaseSensitive))
		{
			c->window()->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("FiSH: Received DH1080 public key from %1, sending mine...").arg(szNick));
			// fish appends an 'A' to all base64 coded strings
			szHisPubKey = szMessage.mid(12).toAscii();
			szHisPubKey.truncate(FISH_KEYLEN);
			szHisPubKey = QByteArray::fromBase64(szHisPubKey);

			szTmp = QByteArray((char *)szMyPubKey, iMyPubKeyLen).toBase64();
			c->window()->console()->connection()->sendFmtData("NOTICE %s :DH1080_FINISH %sA",
				c->window()->console()->connection()->encodeText(szNick).data(),
				szTmp.data()
				);
		}
		
		if(szMessage.startsWith("DH1080_FINISH ", Qt::CaseSensitive))
		{
			// fish appends an 'A' to all base64 coded strings
			szHisPubKey = szMessage.mid(14).toAscii();
			szHisPubKey.truncate(FISH_KEYLEN);
			szHisPubKey = QByteArray::fromBase64(szHisPubKey);
		}

		if(!c->window()->connection())
			return false;
		
		KviQueryWindow * pWnd = c->window()->connection()->findQuery(szNick);
		if(!pWnd)
		{
			pWnd = c->window()->connection()->createQuery(szNick);
			QString user;
			QString host;
			KviIrcUserDataBase * db = c->window()->connection()->userDataBase();
			if(db)
			{
				KviIrcUserEntry * e = db->find(szNick);
				if(e)
				{
					user = e->user();
					host = e->host();
				}
			}
			pWnd->setTarget(szNick,user,host);
		}
		if(!pWnd)
		{
			c->warning(__tr2qs("Query not found: %Q"),&szNick);
			return false;
		}

		KviCString szFinalKey;

		#if defined(COMPILE_SSL_SUPPORT) && !defined(COMPILE_CRYPTOPP_SUPPORT)
		unsigned char * secret=(unsigned char *) KviMemory::allocate(DH_size(g_fish_dh));
		int secretLen;
		BIGNUM *bn = BN_bin2bn((unsigned char *) szHisPubKey.data(), szHisPubKey.size(),NULL);
		if(-1 == (secretLen = DH_compute_key(secret, bn, g_fish_dh)))
		{
			c->warning(__tr2qs("FiSH: error verificating peer public key (size=%1)").arg(szHisPubKey.size()));
			return false;
		}
		BN_zero(bn);
		BN_free(bn);

		unsigned char * hashedSecret=(unsigned char *) KviMemory::allocate(SHA256_LEN);
		SHA256(secret, secretLen, hashedSecret);

		KviMemory::free(secret);

		szFinalKey.bufferToBase64((char *) hashedSecret, SHA256_LEN);
		//strip the trailing =
		szFinalKey.stripRight('=');
		
		KviMemory::free(hashedSecret);
		#elif defined( COMPILE_CRYPTOPP_SUPPORT )
		try {
			CryptoPP::SecByteBlock byteHisPubKey((unsigned char *) szHisPubKey.data(), szHisPubKey.size());
			CryptoPP::SecByteBlock byteSharedSecret(g_fish_dh->AgreedValueLength());
			CryptoPP::SecByteBlock byteHashedSecret(g_fish_dh->AgreedValueLength());
			
			if(!g_fish_dh->Agree(byteSharedSecret, *g_fish_privKey, byteHisPubKey))
			{
				c->warning(__tr2qs("FiSH: error verificating peer public key (size=%1)").arg(szHisPubKey.size()));
				return false;
			}

			CryptoPP::SHA256().CalculateDigest(byteHashedSecret, byteSharedSecret, g_fish_dh->AgreedValueLength());
			szFinalKey.bufferToBase64((char *) byteHashedSecret.BytePtr(), SHA256_LEN);
			//strip the trailing =
			szFinalKey.stripRight('=');

		} catch(CryptoPP::Exception e)
		{
			c->warning(__tr2qs("Crypto++ threw the following exception: %1").arg(QString(e.what())));
			return false;
		}
		#else
			c->warning(__tr2qs("FiSH has been compiled without ssl and crypto++ support, unable to proceed"));
			return false;
		#endif

		(void)g_pModuleManager->loadModulesByCaps("crypt");

		QString szEngine="Mircryption";
		KviCryptEngine * e = g_pCryptEngineManager->allocateEngine(szEngine);
		if(e)
		{
			if(e->init(szFinalKey.ptr(),szFinalKey.len(),szFinalKey.ptr(),szFinalKey.len()))
			{
				KviCryptSessionInfo * inf = KviCryptController::allocateCryptSessionInfo();
				inf->m_pEngine = e;
				inf->m_szEngineName = szEngine;
				inf->m_bDoEncrypt = true;
				inf->m_bDoDecrypt = true;
			
				pWnd->setCryptSessionInfo(inf);
			} else {
				c->warning(__tr2qs("Failed to initialize the Mircryption engine: %Q"),&(e->lastError()));
				g_pCryptEngineManager->deallocateEngine(e);
			}
		} else {
			c->warning(__tr2qs("The Mircryption crypt engine does not exist"));
		}

		c->window()->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("FiSH: Key for %1 successfully set!").arg(szNick));
		return true;
	}

	static bool fish_cmd_keyx(KviKvsModuleCommandCall * c)
	{
		QString szTarget;
		KVSM_PARAMETERS_BEGIN(c)
			KVSM_PARAMETER("target",KVS_PT_NONEMPTYSTRING,0,szTarget)
		KVSM_PARAMETERS_END(c)

		unsigned char * szMyPubKey = 0;
		KviCString szHisPubKey, szTmp;
		int iMyPubKeyLen, * pMyPubKeyLen = &iMyPubKeyLen;
		if(!fish_DH1080_gen(&szMyPubKey, pMyPubKeyLen))
			return false;

		szTmp.bufferToBase64((char *) szMyPubKey, iMyPubKeyLen);
		c->window()->console()->connection()->sendFmtData("NOTICE %s :DH1080_INIT %sA",
			c->window()->console()->connection()->encodeText(szTarget).data(),
			szTmp.ptr()
			);

		c->window()->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("FiSH: Sent my DH1080 public key to %1, waiting for reply ...").arg(szTarget));
		return true;
	}
#endif //COMPILE_CRYPT_SUPPORT
// =======================================
// module routines
// =======================================
static bool fish_module_init(KviModule * m)
{
#ifdef COMPILE_CRYPT_SUPPORT
	#if defined( COMPILE_SSL_SUPPORT) && !defined(COMPILE_CRYPTOPP_SUPPORT)
		KviSSL::globalSSLInit();
	#endif
	m->kvsRegisterAppEventHandler(KviEvent_OnQueryNotice,fish_event_onQueryNotice);
		
	KVSM_REGISTER_SIMPLE_COMMAND(m,"keyx",fish_cmd_keyx);

	return true;
#else 
	return false;
#endif
}

static bool fish_module_cleanup(KviModule *)
{
	return true;
}

static bool fish_module_can_unload(KviModule *)
{
	return false;
}

// =======================================
// plugin definition structure
// =======================================
KVIRC_MODULE(
	"FISH crypt engine",
	"4.0.0",
	"Fabio Bas <ctrlaltca at gmail dot com>",
	"A FISH implementation",
	fish_module_init,
	fish_module_can_unload,
	0,
	fish_module_cleanup,
	0
)

#ifdef COMPILE_CRYPT_SUPPORT
	#ifndef COMPILE_USE_STANDALONE_MOC_SOURCES
		#include "libkvifish.moc"
	#endif //!COMPILE_USE_STANDALONE_MOC_SOURCES
#endif
