/*

WinPopup Protocol plugin for Miranda IM.

Copyright (C) 2004-2011 Nikolay Raspopov

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 "stdafx.h"

////////////////////////////////////////////////////////////////////////
// Class netbios_name

netbios_name::netbios_name(LPCTSTR n, UCHAR type, bool group, UCHAR lana) :
	m_managed (false),
	m_registered (false),
	m_duplicated (false),
	m_error (false),
	m_lana (lana),
	m_listener (NULL),
	m_dgreceiver (NULL),
	m_term (NULL)
{
	// �������������� ����� � NetBIOS-���
	int len = NCBNAMSZ - 1;
	CT2A nA( n );
	LPCSTR src = (LPCSTR)nA;
	LPSTR dst = (LPSTR)netbiosed.name;
	for ( ; len && *src; --len, ++dst, ++src )
		*dst = *src;
	for ( ; len; --len )
		*dst++ = ' ';
	*dst = (CHAR)type;
	CharUpperBuffA( (LPSTR)netbiosed.name, NCBNAMSZ - 1 );
	CharToOemBuffA( (LPSTR)netbiosed.name, (LPSTR)netbiosed.name, NCBNAMSZ - 1 );
	netbiosed.name_num = 0;
	netbiosed.name_flags = (UCHAR)( group ? GROUP_NAME : UNIQUE_NAME );

	original = GetANSIName();
}

netbios_name::netbios_name(const NAME_BUFFER& n, UCHAR lana) :
	m_managed (false),
	m_registered (false),
	m_duplicated (false),
	m_error (false),
	m_lana (lana),
	m_listener (NULL),
	m_dgreceiver (NULL),
	m_term (NULL)
{
	CopyMemory (&netbiosed, &n, sizeof (NAME_BUFFER));
	original = GetANSIName();
}

netbios_name& netbios_name::operator=(const netbios_name& n)
{
	_ASSERTE (m_managed == false);
	m_managed = n.m_managed;
	_ASSERTE (m_registered == false);
	m_registered = n.m_registered;
	_ASSERTE (m_duplicated == false);
	m_duplicated = n.m_duplicated;
	_ASSERTE (m_error == false);
	m_error = n.m_error;
	m_lana = n.m_lana;
	_ASSERTE( m_listener == NULL );
	m_listener = NULL;
	_ASSERTE( m_dgreceiver == NULL );
	m_dgreceiver = NULL;
	_ASSERTE( m_term == NULL );
	m_term = NULL;
	CopyMemory (&netbiosed, &n.netbiosed, sizeof( NAME_BUFFER ));
	original = n.original;
	return *this;
}

netbios_name& netbios_name::operator= (const UCHAR* n)
{
	_ASSERTE (m_managed == false);
	m_managed = false;
	_ASSERTE (m_registered == false);
	m_registered = false;
	_ASSERTE (m_duplicated == false);
	m_duplicated = false;
	_ASSERTE (m_error == false);
	m_error = false;
	m_lana = 0;
	_ASSERTE( m_listener == NULL );
	m_listener = NULL;
	_ASSERTE( m_dgreceiver == NULL );
	m_dgreceiver = NULL;
	_ASSERTE( m_term == NULL );
	m_term = NULL;
	CopyMemory (netbiosed.name, n, NCBNAMSZ);
	netbiosed.name_num = 0;
	netbiosed.name_flags = UNIQUE_NAME;
	original = GetANSIName();

	return *this;
}

bool netbios_name::operator== (const NAME_BUFFER& n) const
{
	return ( netbiosed.name [NCBNAMSZ - 1] == n.name [NCBNAMSZ - 1] ) &&
		( ( netbiosed.name_flags & GROUP_NAME ) == ( n.name_flags & GROUP_NAME ) ) &&
		( memcmp( netbiosed.name, n.name, NCBNAMSZ - 1 ) == 0 );
}

bool netbios_name::operator!= (const NAME_BUFFER& n) const
{
	return ! operator==( n );
}

bool netbios_name::operator== (const netbios_name& n) const
{
	return ( m_lana == n.m_lana ) && operator==( n.netbiosed );
}

bool netbios_name::operator!= (const netbios_name& n) const
{
	return ( m_lana != n.m_lana ) || operator!=( n.netbiosed );
}

bool netbios_name::Register()
{
	m_managed = true;

	UCHAR ret = AddName ();
	LOG("Register NetBIOS name \"%s\" on lana %d num=%d : 0x%02x \"%s\"", GetANSIFullName(), m_lana, netbiosed.name_num, ret, GetNetbiosError( ret ) );
	m_registered = (ret == NRC_GOODRET);
	m_duplicated = (ret == NRC_DUPNAME);
	if ( ret != NRC_GOODRET && ret != NRC_DUPNAME )
	{
		WarningBox (NULL, (DWORD)MAKE_HRESULT (0, FACILITY_NETBIOS, ret),
			_T("%s: %s"), TranslateT ("Cannot register NetBIOS name"), (LPCTSTR)CA2T( GetANSIFullName() ) );
	}

	if (!m_term)
		m_term = CreateEvent (NULL, TRUE, FALSE, NULL);
	else
		ResetEvent (m_term);

	if ( m_term && !m_listener )
		m_listener = (HANDLE)mir_forkthread( ListenerThread, this );

	if ( m_term && !m_dgreceiver &&
		// NOTE: ��� Win9x ������ ��������� �������� ��������� ��� ���-����������
		// �.�. ����� ���������� ������ ���������� �� ������� Netbios() ���� ����
		// ����������������� ���
		!m_duplicated )
	{
		m_dgreceiver = (HANDLE)mir_forkthread( DatagramReceiverThread, this );
	}

	return m_registered;
}

void netbios_name::AskForDestroy()
{
	if (m_term)
		SetEvent (m_term);
}

void netbios_name::Destroy()
{
	// ������ �������� �������� ������-���������
	if ( m_term ) SetEvent( m_term );

	// �������� ����� (���� �� �������, �� ��������� �� �����������)
	UCHAR ret = DeleteName ();
	LOG("Unregister NetBIOS name \"%s\" on lana %d num=%d : 0x%02x \"%s\"", GetANSIFullName(), m_lana, netbiosed.name_num, ret, GetNetbiosError( ret ) );
	m_registered = !(ret == NRC_GOODRET);
	if ( m_duplicated )
	{
		// �������������� �����
		m_duplicated = false;

		// NOTE: ��������������� �� ���� - ������� ������
		//	uReturn = AddName ();
		//	LOG("Restore NetBIOS name \"%s\" on lana %d : 0x%02x", GetANSIFullName(), m_lana, uReturn);
	}

	// ��������, � ����� �������������� �������
	if ( m_listener )
	{
		if ( m_term ) SetEvent( m_term );
		if (WaitForSingleObject (m_listener, ALMOST_INFINITE) == WAIT_TIMEOUT)
		{
			LOG("Terminate NetBIOS listener!");
			TerminateThread (m_listener, 0);
		}
		m_listener = NULL;
	}

	if ( m_dgreceiver )
	{
		if ( m_term ) SetEvent( m_term );
		if (WaitForSingleObject (m_dgreceiver, ALMOST_INFINITE) == WAIT_TIMEOUT)
		{
			LOG("Terminate NetBIOS datagram receiver!");
			TerminateThread (m_dgreceiver, 0);
		}
		m_dgreceiver = NULL;
	}

	if ( m_term )
	{
		CloseHandle (m_term);
		m_term = NULL;
	}
}

CStringA netbios_name::GetANSIName() const
{
	CStringA sName;
	LPSTR szName = sName.GetBuffer( NCBNAMSZ );
	CopyMemory( szName, (LPCSTR)netbiosed.name, NCBNAMSZ - 1 );
	szName[ NCBNAMSZ - 1 ] = 0;
	sName.ReleaseBuffer();
	sName.Trim();
	sName.OemToAnsi();
	return sName;
}

CStringA netbios_name::GetANSIFullName() const
{
	CStringA sType;
	sType.Format( " <%02X>", GetType() );
	return original + sType;
}

UCHAR netbios_name::GetType () const
{
	return netbiosed.name [NCBNAMSZ - 1];
}

bool netbios_name::IsGroupName () const
{
	return ((netbiosed.name_flags & GROUP_NAME) == GROUP_NAME);
}

bool netbios_name::IsRegistered () const
{
	return m_registered;
}

bool netbios_name::IsDuplicated () const
{
	return m_duplicated;
}

bool netbios_name::IsError () const
{
	return m_error;
}

bool netbios_name::IsOwnName () const
{
	return m_managed;
}

UCHAR netbios_name::GetLana () const
{
	return m_lana;
}

bool netbios_name::GetRealSender (UCHAR lsn, CStringA& sRealFrom) const
{
	sRealFrom.Empty ();

	SESSION_INFO_BLOCK sibSession = {};
	UCHAR dwInfoRes = pluginNetBIOS.Stat( *this, &sibSession );
	if ( dwInfoRes == NRC_GOODRET )
	{
		for ( int i = 0; i < sibSession.sib_header.num_sess; i++ )
		{
			if ( sibSession.sib_Names [i].lsn == lsn )
			{
				// ���� ������
				const char* n = (const char*)sibSession.sib_Names [i].remote_name;
				BYTE j = NCBNAMSZ - 2;
				for ( ; j && ( n [ j ] == ' ' ); --j );
				sRealFrom.Append( n, j + 1 );
				sRealFrom.OemToAnsi();
				return true;
			}
		}
	}
	return false;
}

size_t netbios_name::GetLength() const
{
	return (size_t)original.GetLength();
}

UCHAR netbios_name::AddName()
{
	return pluginNetBIOS.AddName( *this );
}

UCHAR netbios_name::DeleteName()
{
	return pluginNetBIOS.DeleteName( *this );
}

typedef struct _ReceiverData
{
	netbios_name* self;
	UCHAR lsn;
} ReceiverData;

void netbios_name::Listener()
{
	m_error = false;
	while ( WaitForSingleObject( m_term, 50 ) == WAIT_TIMEOUT )
	{
		UCHAR lsn = 0;
		UCHAR ret = pluginNetBIOS.Listen( *this, lsn );
		if ( ret != NRC_GOODRET )
		{
			LOG( "Listener : Closing \"%s\"", GetANSIFullName() );
			m_error = true;
			break;
		}

		LOG( "Listener : Got packet for \"%s\"", GetANSIFullName() );
		if ( ReceiverData* data = (ReceiverData*)mir_alloc( sizeof( ReceiverData ) ) )
		{
			data->self = this;
			data->lsn = lsn;
			mir_forkthread( ReceiverThread, data );
		}
	}
}

void netbios_name::ListenerThread(LPVOID param)
{
	if ( netbios_name* pName = (netbios_name*)param )
	{
		pName->Listener();
		pName->m_listener = NULL;
	}
}

void netbios_name::DatagramReceiver()
{
	UCHAR* SMBBlock = (UCHAR*)mir_alloc( 65536 );
	if ( ! SMBBlock )
	{
		m_error = true;
		return;
	}

	m_error = false;
	while ( WaitForSingleObject ( m_term, 50 ) == WAIT_TIMEOUT )
	{
		ZeroMemory( SMBBlock, 65536 );

		WORD iReadedBytes = 65535;
		netbios_name nname_from;
		UCHAR ret = pluginNetBIOS.RecvDatagram( nname_from, *this, SMBBlock, iReadedBytes );
		if ( ret != NRC_GOODRET )
		{
			// ������ - �����
			m_error = true;
			break;
		}

		nname_from.m_lana = m_lana;

		LOG( "Got datagram from \"%s\" to \"%s\"", nname_from.GetANSIFullName(), GetANSIFullName() );

		// ���� ����������� ���������?
		if ( IsItMe ( CA2T( nname_from.original ) ) )
		{
			LOG( "DatagramReceiver : Ignoring my datagram" );
			continue;
		}

		// ��������� ���������
		if ( iReadedBytes > 2 && *(WORD*)SMBBlock == SM_MAGIC )
		{
			UCHAR iMsgType = SMBBlock[ 2 ];
			switch ( iMsgType )
			{
			case SM_GETSTATUS:
				// �������� ����� ��������
				LOG( "DatagramReceiver : It's status request" );
				pluginNetBIOS.SendStatus( *this, nname_from );
				break;

			case SM_SENDSTATUS:
				// ������� ������� � ������ ��� ������
				if ( iReadedBytes == 2 + 1 + 4 )
				{
					HANDLE hContact = GetContact(  CA2T( nname_from.original ) );
					if ( hContact )
					{
						LOG( "DatagramReceiver : It's status answer" );
						SetContactStatus( hContact, *(__int32*)(SMBBlock + 2 + 1), false );
					}
					else
						LOG( "DatagramReceiver : Unknown contact" );
				}
				else
					LOG( "DatagramReceiver : Invalid format" );
				break;

			case SM_GETAWAYMESSAGE:
				// �������� ����� ����-����������
				LOG( "DatagramReceiver : It's away request" );
				pluginNetBIOS.SendAway( *this, nname_from );
				break;

			case SM_SENDAWAYMESSAGE:
				// ������� ������� � ������ ��� ����-���������
				if ( iReadedBytes >= 2 + 1 + 4 )
				{
					if ( HANDLE hContact = GetContact( CA2T( nname_from.original ) ) )
					{
						LPCSTR szAway = (LPCSTR)( SMBBlock + 2 + 1 + 4 );
						SMBBlock[ iReadedBytes ] = 0;	// ASCII -> ASCIIZ

						LOG( "DatagramReceiver : It's away answer \"%s\"", szAway );
						SetContactAway( hContact, szAway );
					}
					else
						LOG( "DatagramReceiver : Unknown contact" );
				}
				else
					LOG( "DatagramReceiver : Invalid format" );
				break;

			case SM_GETAVATAR:
				// �������� ����� ��������
				LOG( "DatagramReceiver : It's avatar request." );
				pluginNetBIOS.SendAvatar( *this, nname_from );
				break;

			case SM_SENDAVATAR:
				// ������� ������� � ������ ��� ������
				if ( iReadedBytes >= 2 + 1 && iReadedBytes < MAX_AVATAR_SIZE + 3 )
				{
					if ( HANDLE hContact = GetContact( CA2T( nname_from.original ) ) )
					{
						LOG( "DatagramReceiver : It's avatar answer" );
						SetContactAvatar( hContact, SMBBlock + 2 + 1, (DWORD)iReadedBytes - 3 );
					}
					else
						LOG( "DatagramReceiver : Unknown contact" );
				}
				else
					LOG( "DatagramReceiver : Invalid format or too big avatar" );
				break;

			default:
				LOG( "DatagramReceiver : Unsupported message type 0x%02x", iMsgType );
			}
		}
		else
			LOG( "DatagramReceiver : Unsupported data 0x%04x", *(WORD*)SMBBlock );
	}

	mir_free( SMBBlock );
}

void netbios_name::DatagramReceiverThread(LPVOID param)
{
	if ( netbios_name* pName = (netbios_name*)param )
	{
		pName->DatagramReceiver();
		pName->m_dgreceiver = NULL;
	}
}

void netbios_name::Receiver(UCHAR lsn)
{
	// Created by Ilja Razinkov (also known as IPv6), 2002, IPv6Intendo@yandex.ru
	// Keep this comment if you redistribute this file

	UCHAR* SMBBlock = (UCHAR*)mir_alloc( 65536 );
	if ( ! SMBBlock )
		return;

	CStringA sTo, sFrom, sMessage;
	UCHAR nRes;
	for (;;)
	{
		ZeroMemory( SMBBlock, 65536 );

		// ��������� ���������� ����� ������
		WORD iReadedBytes = 65535;
		nRes = pluginNetBIOS.Recv (m_lana, lsn, SMBBlock, iReadedBytes);
		if (nRes != NRC_GOODRET)
		{
			LOG( "Receiver : Error while receiving data block" );
			break;
		}

		// ������� ��� � ��� ������ - ��������� ��� ���-�� �����������
		if ( iReadedBytes < 4 || *(DWORD*)SMBBlock != SMB_MAGIC )
		{
			LOG( "Receiver : Unsupported data 0x%08x", *(DWORD*)SMBBlock );
			break;
		}

		UCHAR iMsgType = SMBBlock [4];
		if (iMsgType != SMBsends &&
			iMsgType != SMBsendstrt &&
			iMsgType != SMBsendend &&
			iMsgType != SMBsendtxt)
		{
			LOG( "Receiver : Unsupported message type 0x%02x", iMsgType );
			break;
		}

		// ��� �������������
		UCHAR szReply [SMB_HEADER_SIZE + 5];
		UCHAR* szReplyData =
			pluginNetBIOS.SetSMBHeaderCommand (szReply, iMsgType, sizeof (szReply));
		if (iMsgType == SMBsendstrt)
		{
			// ��� ���������
			static UCHAR rnd = 1;
			szReplyData [0] = 1;		// ����� ���������
			szReplyData [1] = rnd++;	//
			if ( rnd > 5 )
				rnd = 1;
		}
		nRes = pluginNetBIOS.Send (m_lana, lsn, szReply,
			(WORD)( (iMsgType == SMBsendstrt) ? (SMB_HEADER_SIZE + 5) : (SMB_HEADER_SIZE + 3) ) );
		if ( nRes != NRC_GOODRET )
		{
			// ����������� ������
			LOG( "Receiver : Error while sending ack" );
		}

		// �������� � ����������� �� ���� ���������
		if (iMsgType == SMBsends)
		{
			LOG( "Receiver : Got single-block message" );
			// �������� ���������, ����������� ������ � �������...
			sFrom = (const char*) SMBBlock + SMB_HEADER_SIZE + 4;
			int iFromOffset = sFrom.GetLength ();
			sTo = (const char*) SMBBlock + SMB_HEADER_SIZE + 4 + iFromOffset + 2;
			int iToOffset = sTo.GetLength ();
			sMessage = (const char*) SMBBlock + SMB_HEADER_SIZE + 4 + iFromOffset + 2 +
				iToOffset + 4;
			break;
		}
		else if (iMsgType == SMBsendstrt)
		{
			LOG( "Receiver : Got start of multi-block message" );
			// ���������� ���������, ������, ������� �� ���� � ����
			sFrom = (const char*) SMBBlock + SMB_HEADER_SIZE + 4;
			int iFromOffset = sFrom.GetLength ();
			sTo = (const char*) SMBBlock + SMB_HEADER_SIZE + iFromOffset + 4 + 2;
		}
		else if (iMsgType == SMBsendtxt)
		{
			// ���������� ���������, ���� � �������, �������������...
			int iConcatSize = iReadedBytes - SMB_HEADER_SIZE - 8;
			LOG( "Receiver : Got text (%d-%d bytes) of multi-block message", sMessage.GetLength(), sMessage.GetLength() + iConcatSize - 1 );
			sMessage.Append ((const char*) (SMBBlock + SMB_HEADER_SIZE + 8), iConcatSize);
		}
		else if (iMsgType == SMBsendend)
		{
			LOG( "Receiver : Got end of multi-block message" );
			// ���������� ���������, �����, ��� ��������, �������
			break;
		}
	}

	sMessage.Replace( "\x14", "\r\n" ); // <14> -> <CR><LF>

	sTo.OemToAnsi();
	sFrom.OemToAnsi();
	sMessage.OemToAnsi();

	// ������� ���������� � ������
	CStringA sRealFrom;
	if (GetRealSender (lsn, sRealFrom))
	{
		LOG( "Receiver : Message from \"%s\" (real \"%s\") to \"%s\"", (LPCSTR)sFrom, (LPCSTR)sRealFrom, (LPCSTR)sTo);
		sFrom = sRealFrom;
	}
	else
	{
		LOG( "Receiver : Message from \"%s\" (real sender unknown) to \"%s\"", (LPCSTR)sFrom, (LPCSTR)sTo);
	}

	// ������ �� ��������...
	pluginNetBIOS.Hangup (m_lana, lsn);

	// ��������� ��������� (������ ����������)
	if ( ! sMessage.IsEmpty() )
	{
		ReceiveContactMessage( CA2T( sFrom ), CA2T( sTo ), CA2T( sMessage ), sMessage.GetLength ());
	}
	else
	{
		LOG( "Receiver : Ignoring empty message" );
	}

	mir_free( SMBBlock );
}

void netbios_name::ReceiverThread(LPVOID param)
{
	if ( ReceiverData* data = (ReceiverData*)param )
	{
		data->self->Receiver( data->lsn );
		mir_free( data );
	}
}