/***************************************************************************
                          cclientssl.cpp  -  description
                             -------------------
    begin                : Sat Dec 7 2002
    copyright            : (C) 2002-2003 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "cclientssl.h"

#include <stdio.h>
#include <string.h>

#include "cclient.h"
#include "core/types.h"

// needed to parse tag to check version for backwards compatibility
#include "cutils.h"

#include "dclib-ssl-use.h"

// define DEBUG_SSL_CHAT 1

/** */
CClientSSL::CClientSSL()
{
	m_pCryptPrivateMessageList = new CStringList<CSSLObject>();
}

/** */
CClientSSL::~CClientSSL()
{
	delete m_pCryptPrivateMessageList;
	m_pCryptPrivateMessageList = 0;
}


#if DCLIB_HAS_SSL == 1
/** */
void CClientSSL::PrivateChat( CClient * client, CMessagePrivateChat * msg )
{
	/**
	 * STATE:
	 *
	 * CLIENT1      		CLIENT2
	 * none S0			none S0
	 * send request S0	-->	receive req S1
	 * receive response S1 	<--   	send response S1
	 * send pk S2     -->   receive pk S2
	 * receive pk S2  <--   send pk S2
	 * send sk S3     <->   send sk S3
	 * crypted        <*>   crypted
	 *
	*/

	CSSLObject * SSLObject = 0;

#ifdef DEBUG_SSL_CHAT
	printf("msg->m_sMessage=%s ", msg->m_sMessage.Data());
	if ( m_pCryptPrivateMessageList->Get( msg->m_sSrcNick, &SSLObject ) != 0 )
	{
		printf("no SSLObject\n");
	}
	else
	{
		printf("m_bHandshakeState=%d\n", SSLObject->m_bHandshakeState);
	}
	
	SSLObject = 0;
#endif

	if ( msg->m_sMessage == "<request secchannel>" )
	{
		// send response
		if ( m_pCryptPrivateMessageList->Get( msg->m_sSrcNick, &SSLObject ) != 0 )
			m_pCryptPrivateMessageList->Add( msg->m_sSrcNick, SSLObject = new CSSLObject() );
		
		bool isOldVersion = false;
		CMessageMyInfo remoteinfo;
		client->UserList()->GetUserMyInfo( msg->m_sSrcNick, &remoteinfo );
		if ( remoteinfo.m_eClientVersion == eucvDCGUI )
		{
			int major = 0;
			int minor = 0;
			int micro = 0;
			if ( CUtils::ParseVersionTag( remoteinfo.m_sVerComment, major, minor, micro ) )
			{
				if ( (major > 0) ||
				     ((major == 0) && (minor > 3)) ||
				     ((major == 0) && (minor == 3) && (micro >= 14))
				   )
				{
					// dclib 0.3.14 or higher, OK
				}
				else
				{
#ifdef DEBUG_SSL_CHAT
					printf("Detected dclib <= 0.3.13, using old handshaking\n");
#endif
					isOldVersion = true;
				}
			}
		}
		
		if ( SSLObject->m_bHandshakeState != 0 )
		{
			if ( isOldVersion )
			{
				if ( SSLObject->m_bHandshakeState == 1 )
				{
#ifdef DEBUG_SSL_CHAT
					printf("<request secchannel> in wrong state %d, adjusting to 0 (old version)\n", SSLObject->m_bHandshakeState);
#endif
					SSLObject->m_bHandshakeState = 0;
				}
				else
				{
#ifdef DEBUG_SSL_CHAT
					printf("<request secchannel> in wrong state %d, ignoring (old version)\n", SSLObject->m_bHandshakeState);
#endif
				}
			}
			else
			{
#ifdef DEBUG_SSL_CHAT
				printf("<request secchannel> in wrong state %d, resetting (new version)\n", SSLObject->m_bHandshakeState);
#endif
				SSLObject->m_bHandshakeState = 0;
			}
			
			// clear message
			msg->m_sMessage.Empty();
			return;
		}

		msg->m_eSecureState = esecsHANDSHAKE;
		SSLObject->m_bHandshakeState = 2;

		/*
		 * Before 0.3.14, each client had to start from getting a <request secchannel>.
		 * The problem with this is that hubs will block it for PM spam
		 * It's now been changed so that a client can start from <request secchannel> or <response secchannel>
		 */
		//client->CDCProto::SendPrivateMessage( client->GetNick(), msg->m_sSrcNick, "<request secchannel>" );
		
		if ( isOldVersion )
		{
#ifdef DEBUG_SSL_CHAT
			printf("Sending extra <request secchannel> for backwards compatibility\n");
#endif
			client->CDCProto::SendPrivateMessage( client->GetNick(), msg->m_sSrcNick, "<request secchannel>" );
			SSLObject->m_bHandshakeState = 1;
		}
		
		client->CDCProto::SendPrivateMessage( client->GetNick(), msg->m_sSrcNick, "<response secchannel>" );

		if ( isOldVersion == false )
		{
			if ( !m_pRSA )
				if ( GenerateRsaKey() == false )
					SSLObject->m_bHandshakeState = 0;
			
			CString s = GetPublicRsaKey();
		
			if ( s.IsEmpty() )
				return;
		
			client->CDCProto::SendPrivateMessage( client->GetNick(), msg->m_sSrcNick, "PK:"+s );
		}

		// clear message
		msg->m_sMessage.Empty();
	}
	else if ( msg->m_sMessage == "<response secchannel>" )
	{
		// send pk
		if ( m_pCryptPrivateMessageList->Get( msg->m_sSrcNick, &SSLObject ) != 0 )
		{
			m_pCryptPrivateMessageList->Add( msg->m_sSrcNick, SSLObject = new CSSLObject() );
			SSLObject->m_bHandshakeState = 0;
		}
		
		// SSLObject->m_bHandshakeState may be 1 with dclib <= 0.3.13
		if ( (SSLObject->m_bHandshakeState != 0) && (SSLObject->m_bHandshakeState != 1) )
		{
			// SSLObject->m_bHandshakeState = 0;
#ifdef DEBUG_SSL_CHAT
			printf("<response secchannel> in wrong state %d, ignoring\n", SSLObject->m_bHandshakeState);
#endif
			msg->m_sMessage.Empty();
			return;
		}

		if ( !m_pRSA )
			if ( GenerateRsaKey() == false )
				SSLObject->m_bHandshakeState = 0;

		CString s = GetPublicRsaKey();

		if ( s.IsEmpty() )
			return;

		msg->m_eSecureState = esecsHANDSHAKE;

		SSLObject->m_bHandshakeState = 2;

		client->CDCProto::SendPrivateMessage( client->GetNick(), msg->m_sSrcNick, "PK:"+s );

		// clear message
		msg->m_sMessage.Empty();
	}
	else if ( msg->m_sMessage.StartsWith("PK:",3) )
	{
		// receive pk, send sk
		if ( m_pCryptPrivateMessageList->Get( msg->m_sSrcNick, &SSLObject ) != 0 )
			return;
		if ( SSLObject->m_bHandshakeState != 2 )
		{
			// SSLObject->m_bHandshakeState = 0;
#ifdef DEBUG_SSL_CHAT
			printf("PK: in wrong state %d, ignoring\n", SSLObject->m_bHandshakeState);
#endif
			msg->m_sMessage.Empty();
			return;
		}
		if ( SetPublicKey( SSLObject, msg->m_sMessage.Mid(3,msg->m_sMessage.Length()-3) ) == false )
		{
			SSLObject->m_bHandshakeState = 0;
			return;
		}

		InitSessionKey( SSLObject );

		CString s = GetSessionKey(SSLObject);

		if ( s.IsEmpty() )
			return;

		msg->m_eSecureState = esecsHANDSHAKE;

		SSLObject->m_bHandshakeState = 3;

		client->CDCProto::SendPrivateMessage( client->GetNick(), msg->m_sSrcNick, "SK:"+s );

		// clear message
		msg->m_sMessage.Empty();
	}
	else if ( msg->m_sMessage.StartsWith("SK:",3) )
	{
		// now we have a secure line
		if ( m_pCryptPrivateMessageList->Get( msg->m_sSrcNick, &SSLObject ) != 0 )
			return;
		if ( SSLObject->m_bHandshakeState != 3 )
		{
			// SSLObject->m_bHandshakeState = 0;
#ifdef DEBUG_SSL_CHAT
			printf("SK: in wrong state %d, ignoring\n", SSLObject->m_bHandshakeState);
#endif
			msg->m_sMessage.Empty();
			return;
		}
		if ( SetSessionKey( SSLObject, msg->m_sMessage.Mid(3,msg->m_sMessage.Length()-3) ) == false )
		{
			SSLObject->m_bHandshakeState = 0;
			return;
		}

		msg->m_eSecureState = esecsENCRYPTED;

		SSLObject->m_bHandshakeState = 4;

		CString s = EncryptData( SSLObject, "Secure channel created." );

		client->CDCProto::SendPrivateMessage( client->GetNick(), msg->m_sSrcNick, "SEC:"+s );

		// clear message
		msg->m_sMessage.Empty();
	}
	else if ( msg->m_sMessage.StartsWith("SEC:",4) )
	{
		// secure data
		if ( m_pCryptPrivateMessageList->Get( msg->m_sSrcNick, &SSLObject ) != 0 )
			return;
		if ( SSLObject->m_bHandshakeState != 4 )
		{
			// you end up here after <close secchannel>
#ifdef DEBUG_SSL_CHAT
			printf("SEC: in wrong state %d, resetting\n", SSLObject->m_bHandshakeState);
#endif
			SSLObject->m_bHandshakeState = 0;

			// clear message
			msg->m_sMessage.Empty();

			return;
		}

		CString s = DecryptData( SSLObject, msg->m_sMessage.Mid(4,msg->m_sMessage.Length()-4) );

		if ( s.NotEmpty() )
		{
			msg->m_sMessage = s;

			if ( s == "<close secchannel>" )
			{
				SSLObject->m_bHandshakeState = 0;

				CString s = EncryptData( SSLObject, "<close secchannel>" );

				client->CDCProto::SendPrivateMessage( client->GetNick(), msg->m_sSrcNick, "SEC:"+s );
				client->CDCProto::SendPrivateMessage( client->GetNick(), msg->m_sSrcNick, "Secure channel closed." );

				// clear message
				msg->m_sMessage.Empty();
			}
			else
			{
				msg->m_eSecureState = esecsENCRYPTED;
			}
		}
	}
	else
	{
		// unsecure data
		if ( m_pCryptPrivateMessageList->Get( msg->m_sSrcNick, &SSLObject ) != 0 )
			return;

		// reset handshake mode
		SSLObject->m_bHandshakeState = 0;
	}
}

/** */
CString CClientSSL::EncryptMessage( CClient * /*client*/, CString nick, CString message )
{
	CString s;
	CSSLObject * SSLObject;

	if ( m_pCryptPrivateMessageList->Get( nick, &SSLObject ) == 0 )
	{
		if ( SSLObject->m_bHandshakeState == 4 )
		{
			s = EncryptData( SSLObject, message );

			if ( s.NotEmpty() )
			{
				s = "SEC:" + s;
			}
		}
	}

	return s;
}

/** */
void CClientSSL::LeaveHub( CClient * /*client*/, CString nick )
{
	CSSLObject * SSLObject;

	if ( m_pCryptPrivateMessageList->Get( nick, &SSLObject ) == 0 )
	{
		SSLObject->m_bHandshakeState = 0;
	}
}

/** */
void CClientSSL::Init()
{
	m_pCryptPrivateMessageList->Clear();
}

#else // DCLIB_HAS_SSL

/** */
void CClientSSL::PrivateChat( CClient * /* client */, CMessagePrivateChat * /* msg */ )
{
	// nothing
}

/** */
CString CClientSSL::EncryptMessage( CClient * /* client */, CString /* nick */, CString /* message */ )
{
	return CString();
}

/** */
void CClientSSL::LeaveHub( CClient * /* client */, CString /* nick */ )
{
	// nothing
}

/** */
void CClientSSL::Init()
{
	// nothing
}

#endif // DCLIB_HAS_SSL
