/*

Jabber Protocol Plugin for Miranda IM
Copyright ( C ) 2002-04  Santithorn Bunchua
Copyright ( C ) 2005-12  George Hazan
Copyright ( C ) 2007     Maxim Mluhov

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 "jabber.h"
#include "jabber_iq.h"
#include "jabber_byte.h"
#include "jabber_caps.h"

#define JABBER_NETWORK_BUFFER_SIZE 4096

///////////////// Bytestream sending /////////////////////////

JABBER_BYTE_TRANSFER::~JABBER_BYTE_TRANSFER()
{
	filetransfer* pft = ft;
	if ( pft )
		pft->jbt = NULL;

	mir_free( srcJID );
	mir_free( dstJID );
	mir_free( streamhostJID );
	mir_free( iqId );
	mir_free( sid );

	xi.destroyNode( iqNode );

	// XEP-0065 proxy support
	mir_free( szProxyHost );
	mir_free( szProxyPort );
	mir_free( szProxyJid );
	mir_free( szStreamhostUsed );
}

void CJabberProto::IqResultProxyDiscovery( HXML iqNode, CJabberIqInfo* pInfo )
{
	JABBER_BYTE_TRANSFER *jbt = ( JABBER_BYTE_TRANSFER * )pInfo->GetUserData();

	if ( pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT ) {
		HXML queryNode = xmlGetChild( iqNode , "query" );
		if ( queryNode ) {
			const TCHAR *queryXmlns = xmlGetAttrValue( queryNode, _T( "xmlns" ));
			if (queryXmlns && !_tcscmp( queryXmlns, _T(JABBER_FEAT_BYTESTREAMS))) {
				HXML streamHostNode = xmlGetChild( queryNode , "streamhost" );
				if ( streamHostNode ) {
					const TCHAR *streamJid = xmlGetAttrValue( streamHostNode, _T( "jid" ));
					const TCHAR *streamHost = xmlGetAttrValue( streamHostNode, _T( "host" ));
					const TCHAR *streamPort = xmlGetAttrValue( streamHostNode, _T( "port" ));
					if ( streamJid && streamHost && streamPort ) {
						jbt->szProxyHost = mir_tstrdup( streamHost );
						jbt->szProxyJid = mir_tstrdup( streamJid );
						jbt->szProxyPort = mir_tstrdup( streamPort );
						jbt->bProxyDiscovered = TRUE;
	}	}	}	}	}
	else if ( pInfo->GetIqType() == JABBER_IQ_TYPE_ERROR )
		jbt->state = JBT_ERROR;

	if ( jbt->hProxyEvent )
		SetEvent( jbt->hProxyEvent );
}

void JabberByteSendConnection( HANDLE hConn, DWORD /*dwRemoteIP*/, void* extra )
{
	CJabberProto* ppro = ( CJabberProto* )extra;
	TCHAR szPort[8];
	JABBER_BYTE_TRANSFER *jbt;
	int recvResult, bytesParsed;
	HANDLE hListen;
	JABBER_LIST_ITEM *item;
	char* buffer;
	int datalen;

	NETLIBCONNINFO connInfo = { sizeof(connInfo) }; 
	CallService(MS_NETLIB_GETCONNECTIONINFO, (WPARAM)hConn, (LPARAM)&connInfo);

	mir_sntprintf( szPort, SIZEOF( szPort ), _T("%u"), connInfo.wPort );
	ppro->Log( "bytestream_send_connection incoming connection accepted: %s", connInfo.szIpPort );

	if (( item = ppro->ListGetItemPtr( LIST_BYTE, szPort )) == NULL ) {
		ppro->Log( "No bytestream session is currently active, connection closed." );
		Netlib_CloseHandle( hConn );
		return;
	}

	jbt = item->jbt;

	if (( buffer = ( char* )mir_alloc( JABBER_NETWORK_BUFFER_SIZE )) == NULL ) {
		ppro->Log( "bytestream_send cannot allocate network buffer, connection closed." );
		jbt->state = JBT_ERROR;
		Netlib_CloseHandle( hConn );
		if ( jbt->hEvent != NULL ) SetEvent( jbt->hEvent );
		return;
	}

	hListen = jbt->hConn;
	jbt->hConn = hConn;
	jbt->state = JBT_INIT;
	datalen = 0;
	while ( jbt->state!=JBT_DONE && jbt->state!=JBT_ERROR ) {
		recvResult = Netlib_Recv( hConn, buffer+datalen, JABBER_NETWORK_BUFFER_SIZE-datalen, 0 );
		if ( recvResult <= 0 )
			break;

		datalen += recvResult;
		bytesParsed = ppro->ByteSendParse( hConn, jbt, buffer, datalen );
		if ( bytesParsed < datalen )
			memmove( buffer, buffer+bytesParsed, datalen-bytesParsed );
		datalen -= bytesParsed;
	}

	if ( jbt->hConn )
		Netlib_CloseHandle( jbt->hConn );

	ppro->Log( "bytestream_send_connection closing connection" );
	jbt->hConn = hListen;
	mir_free( buffer );

	if ( jbt->hEvent != NULL )
		SetEvent( jbt->hEvent );
}

void CJabberProto::ByteSendThread( JABBER_BYTE_TRANSFER *jbt )
{
	char* localAddr = NULL;
	DBVARIANT dbv;
	TCHAR szPort[8];
	HANDLE hEvent = NULL;
	TCHAR* proxyJid;
	CJabberIqInfo* pInfo = NULL;
	int nIqId = 0;

	Log( "Thread started: type=bytestream_send" );

	BOOL bDirect = m_options.BsDirect;

	if ( m_options.BsProxyManual ) {
		proxyJid = NULL;
		if ( !DBGetContactSettingString( NULL, m_szModuleName, "BsProxyServer", &dbv )) {
			proxyJid = mir_a2t( dbv.pszVal );
			JFreeVariant( &dbv );
		}

		if ( proxyJid ) {
			jbt->bProxyDiscovered = FALSE;
			jbt->szProxyHost = NULL;
			jbt->szProxyPort = NULL;
			jbt->szProxyJid = NULL;
			jbt->hProxyEvent = CreateEvent( NULL, FALSE, FALSE, NULL );

			pInfo = m_iqManager.AddHandler( &CJabberProto::IqResultProxyDiscovery, JABBER_IQ_TYPE_GET, proxyJid, 0, -1, jbt );
			nIqId = pInfo->GetIqId();
			XmlNodeIq iq( pInfo );
			iq << XQUERY( _T(JABBER_FEAT_BYTESTREAMS));
			m_ThreadInfo->send( iq );

			WaitForSingleObject( jbt->hProxyEvent, INFINITE );
			m_iqManager.ExpireIq ( nIqId );
			CloseHandle( jbt->hProxyEvent );
			jbt->hProxyEvent = NULL;

			mir_free( proxyJid );

			if ( jbt->state == JBT_ERROR && !bDirect ) {
				Log( "Bytestream proxy failure" );
				MsgPopup(  pInfo->GetHContact(), TranslateT("Bytestream Proxy not available"), pInfo->GetReceiver());
				jbt->ft->state = FT_DENIED;
				(this->*jbt->pfnFinal)( FALSE, jbt->ft );
				jbt->ft = NULL;
				delete jbt;
				return;
			}	
	}	}

	pInfo = m_iqManager.AddHandler( &CJabberProto::ByteInitiateResult, JABBER_IQ_TYPE_SET, jbt->dstJID, 0, -1, jbt );
	nIqId = pInfo->GetIqId();
	{
		XmlNodeIq iq( pInfo );
		HXML query = iq << XQUERY( _T(JABBER_FEAT_BYTESTREAMS)) << XATTR( _T("sid"), jbt->sid );

		if ( bDirect ) {
			if ( m_options.BsDirectManual ) {
				if ( !DBGetContactSettingString( NULL, m_szModuleName, "BsDirectAddr", &dbv ))
					localAddr = dbv.pszVal;
			}

			NETLIBBIND nlb = {0};
			nlb.cbSize = sizeof( NETLIBBIND );
			nlb.pfnNewConnectionV2 = JabberByteSendConnection;
			nlb.pExtra = this;
			nlb.wPort = 0;	// Use user-specified incoming port ranges, if available
			jbt->hConn = ( HANDLE ) JCallService( MS_NETLIB_BINDPORT, ( WPARAM ) m_hNetlibUser, ( LPARAM )&nlb );
			if ( jbt->hConn == NULL ) {
				Log( "Cannot allocate port for bytestream_send thread, thread ended." );
				delete jbt;
				return;
			}

			if ( localAddr == NULL )
				localAddr = (char*)CallService( MS_NETLIB_ADDRESSTOSTRING, 1, nlb.dwExternalIP );

			mir_sntprintf( szPort, SIZEOF( szPort ), _T("%d"), nlb.wPort );
			JABBER_LIST_ITEM *item = ListAdd( LIST_BYTE, szPort );
			item->jbt = jbt;
			hEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
			jbt->hEvent = hEvent;
			jbt->hSendEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
			query << XCHILD( _T("streamhost")) << XATTR( _T("jid"), m_ThreadInfo->fullJID ) << XATTR( _T("host"), _A2T(localAddr)) << XATTRI( _T("port"), nlb.wPort );

			NETLIBIPLIST* ihaddr = ( NETLIBIPLIST* )CallService( MS_NETLIB_GETMYIP, 1, 0 );
			for ( unsigned i = 0; i < ihaddr->cbNum; ++i )
				if ( strcmp( localAddr, ihaddr->szIp[i] ))
					query << XCHILD( _T("streamhost")) << XATTR( _T("jid"), m_ThreadInfo->fullJID ) << XATTR( _T("host"), _A2T(ihaddr->szIp[i])) << XATTRI( _T("port"), nlb.wPort );

			mir_free( ihaddr );
			mir_free( localAddr );
		}

		if ( jbt->bProxyDiscovered )
			query << XCHILD( _T("streamhost")) << XATTR( _T("jid"), jbt->szProxyJid ) << XATTR( _T("host"), jbt->szProxyHost ) << XATTR( _T("port"), jbt->szProxyPort );

		jbt->hProxyEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
		jbt->szStreamhostUsed = NULL;

		m_ThreadInfo->send( iq );
	}

	WaitForSingleObject( jbt->hProxyEvent, INFINITE );
	m_iqManager.ExpireIq( nIqId );
	CloseHandle( jbt->hProxyEvent );
	jbt->hProxyEvent = NULL;

	if ( !jbt->szStreamhostUsed ) {
		if ( bDirect ) {
			SetEvent( jbt->hSendEvent );
			CloseHandle( jbt->hSendEvent );
			CloseHandle( hEvent );
			jbt->hEvent = NULL;
			if ( jbt->hConn != NULL )
				Netlib_CloseHandle( jbt->hConn );
			jbt->hConn = NULL;
			ListRemove( LIST_BYTE, szPort );
		}
		(this->*jbt->pfnFinal)(( jbt->state==JBT_DONE )?TRUE:FALSE, jbt->ft );
		jbt->ft = NULL;
		// stupid fix: wait for listening thread exit
		Sleep( 100 );
		delete jbt;
		return;
	}

	if ( jbt->bProxyDiscovered && !_tcscmp( jbt->szProxyJid, jbt->szStreamhostUsed )) {
		// jabber proxy used
		if ( bDirect ) {
			SetEvent( jbt->hSendEvent );
			CloseHandle( jbt->hSendEvent );
			CloseHandle( hEvent );
			jbt->hEvent = NULL;
			if ( jbt->hConn != NULL )
				Netlib_CloseHandle( jbt->hConn );
			jbt->hConn = NULL;
			ListRemove( LIST_BYTE, szPort );
		}
		ByteSendViaProxy( jbt );
	}
	else {
		SetEvent( jbt->hSendEvent );
		WaitForSingleObject( hEvent, INFINITE );
		CloseHandle( hEvent );
		CloseHandle( jbt->hSendEvent );
		jbt->hEvent = NULL;
		(this->*jbt->pfnFinal)(( jbt->state == JBT_DONE ) ? TRUE : FALSE, jbt->ft );
		jbt->ft = NULL;
		if ( jbt->hConn != NULL )
			Netlib_CloseHandle( jbt->hConn );
		jbt->hConn = NULL;
		ListRemove( LIST_BYTE, szPort );
	}

	// stupid fix: wait for listening connection thread exit
	Sleep( 100 );
	delete jbt;
	Log( "Thread ended: type=bytestream_send" );
}

void CJabberProto::ByteInitiateResult( HXML iqNode, CJabberIqInfo* pInfo )
{
	JABBER_BYTE_TRANSFER *jbt = ( JABBER_BYTE_TRANSFER * )pInfo->GetUserData();

	if ( pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT ) {
		HXML queryNode = xmlGetChild( iqNode , "query" );
		if ( queryNode ) {
			const TCHAR* queryXmlns = xmlGetAttrValue( queryNode, _T("xmlns"));
			if ( queryXmlns && !_tcscmp( queryXmlns, _T( JABBER_FEAT_BYTESTREAMS ))) {
				HXML streamHostNode = xmlGetChild( queryNode ,  "streamhost-used" );
				if ( streamHostNode ) {
					const TCHAR* streamJid = xmlGetAttrValue( streamHostNode, _T("jid"));
					if ( streamJid )
						jbt->szStreamhostUsed = mir_tstrdup( streamJid );
	}	}	}	}

	if ( jbt->hProxyEvent )
		SetEvent( jbt->hProxyEvent );
}

int CJabberProto::ByteSendParse( HANDLE hConn, JABBER_BYTE_TRANSFER *jbt, char* buffer, int datalen )
{
	int nMethods;
	BYTE data[10];
	int i;
	char* str;

	switch ( jbt->state ) {
	case JBT_INIT:
		// received:
		// 00-00 ver ( 0x05 )
		// 01-01 nmethods
		// 02-xx list of methods ( nmethods bytes )
		// send:
		// 00-00 ver ( 0x05 )
		// 01-01 select method ( 0=no auth required )
		if ( datalen>=2 && buffer[0]==5 && buffer[1]+2==datalen ) {
			nMethods = buffer[1];
			for ( i=0; i<nMethods && buffer[2+i]!=0; i++ );
			if ( i < nMethods ) {
				data[1] = 0;
				jbt->state = JBT_CONNECT;
			}
			else {
				data[1] = 0xff;
				jbt->state = JBT_ERROR;
			}
			data[0] = 5;
			Netlib_Send( hConn, ( char* )data, 2, 0 );
		}
		else jbt->state = JBT_ERROR;
		break;

	case JBT_CONNECT:
		// received:
		// 00-00 ver ( 0x05 )
		// 01-01 cmd ( 1=connect )
		// 02-02 reserved ( 0 )
		// 03-03 address type ( 3 )
		// 04-44 dst.addr ( 41 bytes: 1-byte length, 40-byte SHA1 hash of [sid,srcJID,dstJID] )
		// 45-46 dst.port ( 0 )
		// send:
		// 00-00 ver ( 0x05 )
		// 01-01 reply ( 0=success,2=not allowed )
		// 02-02 reserved ( 0 )
		// 03-03 address type ( 1=IPv4 address )
		// 04-07 bnd.addr server bound address
		// 08-09 bnd.port server bound port
		if ( datalen == 47 && *(( DWORD* )buffer )==0x03000105 && buffer[4]==40 && *(( WORD* )( buffer+45 ))==0 ) {
			TCHAR text[256];

			TCHAR *szInitiatorJid = JabberPrepareJid(jbt->srcJID);
			TCHAR *szTargetJid = JabberPrepareJid(jbt->dstJID);
			mir_sntprintf( text, SIZEOF( text ), _T("%s%s%s"), jbt->sid, szInitiatorJid, szTargetJid );
			mir_free(szInitiatorJid);
			mir_free(szTargetJid);

			char* szAuthString = mir_utf8encodeT( text );
			Log( "Auth: '%s'", szAuthString );
			if (( str = JabberSha1( szAuthString )) != NULL ) {
				for ( i=0; i<40 && buffer[i+5]==str[i]; i++ );
				mir_free( str );

				ZeroMemory( data, 10 );
				data[1] = ( i>=20 )?0:2;
				data[0] = 5;
				data[3] = 1;
				Netlib_Send( hConn, ( char* )data, 10, 0 );

				// wait stream activation
				WaitForSingleObject( jbt->hSendEvent, INFINITE );

				if ( jbt->state == JBT_ERROR )
					break;

				if ( i>=20 && (this->*jbt->pfnSend)( hConn, jbt->ft )==TRUE )
					jbt->state = JBT_DONE;
				else
					jbt->state = JBT_ERROR;
			}
			mir_free( szAuthString );
		}
		else
			jbt->state = JBT_ERROR;
		break;
	}

	return datalen;
}

///////////////// Bytestream receiving /////////////////////////

void CJabberProto::IqResultStreamActivate( HXML iqNode )
{
	int id = JabberGetPacketID( iqNode );

	TCHAR listJid[JABBER_MAX_JID_LEN];
	mir_sntprintf(listJid, SIZEOF( listJid ), _T("ftproxy_%d"), id);

	JABBER_LIST_ITEM *item = ListGetItemPtr( LIST_FTIQID, listJid );
	if ( !item )
		return;

	if ( !lstrcmp( xmlGetAttrValue( iqNode, _T("type")), _T( "result" )))
		item->jbt->bStreamActivated = TRUE;

	if ( item->jbt->hProxyEvent )
		SetEvent( item->jbt->hProxyEvent );
}


void CJabberProto::ByteSendViaProxy( JABBER_BYTE_TRANSFER *jbt )
{
	TCHAR *szHost, *szPort;
	WORD port;
	HANDLE hConn;
	char data[3];
	char* buffer;
	int datalen, bytesParsed, recvResult;
	BOOL validStreamhost;

	if ( jbt == NULL ) return;
	if (( buffer=( char* )mir_alloc( JABBER_NETWORK_BUFFER_SIZE )) == NULL ) {
		m_ThreadInfo->send( XmlNodeIq( _T("error"), jbt->iqId, jbt->srcJID )
			<< XCHILD( _T("error")) << XATTRI( _T("code"), 406 ) << XATTR( _T("type"), _T("auth"))
			<< XCHILDNS( _T("not-acceptable"), _T("urn:ietf:params:xml:ns:xmpp-stanzas")));
		return;
	}

	jbt->state = JBT_INIT;
	validStreamhost = FALSE;
	szPort = jbt->szProxyPort;
	szHost = jbt->szProxyHost;

	port = ( WORD )_ttoi( szPort );
	if ( jbt->streamhostJID ) mir_free( jbt->streamhostJID );
	jbt->streamhostJID = mir_tstrdup( jbt->szProxyJid );

	NETLIBOPENCONNECTION nloc = { 0 };
	nloc.cbSize = sizeof( nloc );
	nloc.szHost = mir_t2a(szHost);
	nloc.wPort = port;
	hConn = ( HANDLE ) JCallService( MS_NETLIB_OPENCONNECTION, ( WPARAM ) m_hNetlibUser, ( LPARAM )&nloc );
	mir_free((void*)nloc.szHost);

	if ( hConn != NULL ) {
		jbt->hConn = hConn;

		data[0] = 5;
		data[1] = 1;
		data[2] = 0;
		Netlib_Send( hConn, data, 3, 0 );

		jbt->state = JBT_INIT;
		datalen = 0;
		while ( jbt->state!=JBT_DONE && jbt->state!=JBT_ERROR && jbt->state!=JBT_SOCKSERR ) {
			recvResult = Netlib_Recv( hConn, buffer+datalen, JABBER_NETWORK_BUFFER_SIZE-datalen, 0 );
			if ( recvResult <= 0 )
				break;

			datalen += recvResult;
			bytesParsed = ByteSendProxyParse( hConn, jbt, buffer, datalen );
			if ( bytesParsed < datalen )
				memmove( buffer, buffer+bytesParsed, datalen-bytesParsed );
			datalen -= bytesParsed;
			if ( jbt->state == JBT_DONE ) validStreamhost = TRUE;
		}
		Netlib_CloseHandle( hConn );
	}
	mir_free( buffer );
	(this->*jbt->pfnFinal)(( jbt->state == JBT_DONE ) ? TRUE : FALSE, jbt->ft );
	jbt->ft = NULL;
	if ( !validStreamhost )
		m_ThreadInfo->send( XmlNodeIq( _T("error"), jbt->iqId, jbt->srcJID )
			<< XCHILD( _T("error")) << XATTRI( _T("code"), 404 ) << XATTR( _T("type"), _T("cancel"))
			<< XCHILDNS( _T("item-not-found"), _T("urn:ietf:params:xml:ns:xmpp-stanzas")));
}

int CJabberProto::ByteSendProxyParse( HANDLE hConn, JABBER_BYTE_TRANSFER *jbt, char* buffer, int datalen )
{
	int num = datalen;

	switch ( jbt->state ) {
	case JBT_INIT:
		// received:
		// 00-00 ver ( 0x05 )
		// 01-01 selected method ( 0=no auth, 0xff=error )
		// send:
		// 00-00 ver ( 0x05 )
		// 01-01 cmd ( 1=connect )
		// 02-02 reserved ( 0 )
		// 03-03 address type ( 3 )
		// 04-44 dst.addr ( 41 bytes: 1-byte length, 40-byte SHA1 hash of [sid,srcJID,dstJID] )
		// 45-46 dst.port ( 0 )
		if ( datalen==2 && buffer[0]==5 && buffer[1]==0 ) {
			BYTE data[47];
			ZeroMemory( data, sizeof( data ));
			*(( DWORD* )data ) = 0x03000105;
			data[4] = 40;

			TCHAR text[256];

			TCHAR *szInitiatorJid = JabberPrepareJid(jbt->srcJID);
			TCHAR *szTargetJid = JabberPrepareJid(jbt->dstJID);
			mir_sntprintf( text, SIZEOF( text ), _T("%s%s%s"), jbt->sid, szInitiatorJid, szTargetJid );
			mir_free(szInitiatorJid);
			mir_free(szTargetJid);

			char* szAuthString = mir_utf8encodeT( text );
			Log( "Auth: '%s'", szAuthString );
			char* szHash = JabberSha1( szAuthString );
			strncpy(( char* )( data+5 ), szHash, 40 );
			mir_free( szHash );
			Netlib_Send( hConn, ( char* )data, 47, 0 );
			jbt->state = JBT_CONNECT;
			mir_free( szAuthString );
		}
		else jbt->state = JBT_SOCKSERR;
		break;

	case JBT_CONNECT:
		// received:
		// 00-00 ver ( 0x05 )
		// 01-01 reply ( 0=success,2=not allowed )
		// 02-02 reserved ( 0 )
		// 03-03 address type ( 1=IPv4 address,3=host address )
		// 04-mm bnd.addr server bound address ( 4-byte IP if IPv4, 1-byte length + n-byte host address string if host address )
		// nn-nn+1 bnd.port server bound port
		if ( datalen>=5 && buffer[0]==5 && buffer[1]==0 && ( buffer[3]==1 || buffer[3]==3 || buffer[3]==0 )) {
			if ( buffer[3]==1 && datalen>=10 )
				num = 10;
			else if ( buffer[3]==3 && datalen>=buffer[4]+7 )
				num = buffer[4] + 7;
			else if ( buffer[3]==0 && datalen>=6 )
				num = 6;
			else {
				jbt->state = JBT_SOCKSERR;
				break;
			}
			jbt->state = JBT_SENDING;

			jbt->hProxyEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
			jbt->bStreamActivated = FALSE;

			int iqId = SerialNext();

			TCHAR listJid[256];
			mir_sntprintf(listJid, SIZEOF( listJid ), _T("ftproxy_%d"), iqId);

			JABBER_LIST_ITEM *item = ListAdd( LIST_FTIQID, listJid );
			item->jbt = jbt;

			IqAdd( iqId, IQ_PROC_NONE, &CJabberProto::IqResultStreamActivate );
			m_ThreadInfo->send( 
				XmlNodeIq( _T("set"), iqId, jbt->streamhostJID ) << XQUERY( _T(JABBER_FEAT_BYTESTREAMS))
					<< XATTR( _T("sid"), jbt->sid ) << XCHILD( _T("activate"), jbt->dstJID ));

			WaitForSingleObject( jbt->hProxyEvent, INFINITE );

			CloseHandle( jbt->hProxyEvent );
			jbt->hProxyEvent = NULL;

			ListRemove( LIST_FTIQID, listJid );

			if ( jbt->bStreamActivated) 
				jbt->state = (this->*jbt->pfnSend)( hConn, jbt->ft ) ? JBT_DONE : JBT_ERROR;
			else
				jbt->state = JBT_ERROR;
		}
		else jbt->state = JBT_SOCKSERR;
		break;
	}

	return num;
}


void __cdecl CJabberProto::ByteReceiveThread( JABBER_BYTE_TRANSFER *jbt )
{
	HXML iqNode, queryNode = NULL, n;
	const TCHAR *sid = NULL, *from = NULL, *to = NULL, *szId = NULL, *szHost, *szPort, *str;
	int i;
	WORD port;
	HANDLE hConn;
	char data[3];
	char* buffer;
	int datalen, bytesParsed, recvResult;
	BOOL validStreamhost = FALSE;

	if ( jbt == NULL ) return;

	jbt->state = JBT_INIT;

	if ( iqNode = jbt->iqNode ) {
		from = xmlGetAttrValue( iqNode, _T("from"));
		to = xmlGetAttrValue( iqNode, _T("to"));
		szId = xmlGetAttrValue( iqNode, _T("id"));

		queryNode = xmlGetChild( iqNode , "query" );
		if ( queryNode )
			sid = xmlGetAttrValue( queryNode, _T("sid"));
	}

	if ( szId && from && to && sid && ( n = xmlGetChild( queryNode , "streamhost" ))!=NULL ) {
		jbt->iqId = mir_tstrdup( szId );
		jbt->srcJID = mir_tstrdup( from );
		jbt->dstJID = mir_tstrdup( to );
		jbt->sid = mir_tstrdup( sid );

		if (( buffer=( char* )mir_alloc( JABBER_NETWORK_BUFFER_SIZE ))) {
			for ( i=1; ( n = xmlGetNthChild( queryNode, _T("streamhost"), i ))!=NULL; i++ ) {
				if (( szHost = xmlGetAttrValue( n, _T("host"))) != NULL &&
					( szPort = xmlGetAttrValue( n, _T("port"))) != NULL &&
					( str = xmlGetAttrValue( n, _T("jid"))) != NULL ) {

						port = ( WORD )_ttoi( szPort );
						if ( jbt->streamhostJID ) mir_free( jbt->streamhostJID );
						jbt->streamhostJID = mir_tstrdup( str );

						Log( "bytestream_recv connecting to " TCHAR_STR_PARAM ":%d", szHost, port );
						NETLIBOPENCONNECTION nloc = { 0 };
						nloc.cbSize = sizeof( nloc );
						nloc.szHost = mir_t2a(szHost);
						nloc.wPort = port;
						hConn = ( HANDLE ) JCallService( MS_NETLIB_OPENCONNECTION, ( WPARAM ) m_hNetlibUser, ( LPARAM )&nloc );
						mir_free((void*)nloc.szHost);

						if ( hConn == NULL ) {
							Log( "bytestream_recv_connection connection failed ( %d ), try next streamhost", WSAGetLastError());
							continue;
						}

						jbt->hConn = hConn;

						data[0] = 5;
						data[1] = 1;
						data[2] = 0;
						Netlib_Send( hConn, data, 3, 0 );

						jbt->state = JBT_INIT;
						datalen = 0;
						while ( jbt->state!=JBT_DONE && jbt->state!=JBT_ERROR && jbt->state!=JBT_SOCKSERR ) {
							recvResult = Netlib_Recv( hConn, buffer+datalen, JABBER_NETWORK_BUFFER_SIZE-datalen, 0 );
							if ( recvResult <= 0 ) break;
							datalen += recvResult;
							bytesParsed = ByteReceiveParse( hConn, jbt, buffer, datalen );
							if ( bytesParsed < datalen )
								memmove( buffer, buffer+bytesParsed, datalen-bytesParsed );
							datalen -= bytesParsed;
							if ( jbt->state == JBT_RECVING ) validStreamhost = TRUE;
						}
						Netlib_CloseHandle( hConn );
						Log( "bytestream_recv_connection closing connection" );
				}
				if ( jbt->state==JBT_ERROR || validStreamhost==TRUE )
					break;
				Log( "bytestream_recv_connection stream cannot be established, try next streamhost" );
			}
			mir_free( buffer );
		}
	}

	(this->*jbt->pfnFinal)(( jbt->state==JBT_DONE )?TRUE:FALSE, jbt->ft );
	jbt->ft = NULL;
	if ( !validStreamhost && szId && from ) {
		Log( "bytestream_recv_connection session not completed" );

		m_ThreadInfo->send( XmlNodeIq( _T("error"), szId, from )
			<< XCHILD( _T("error")) << XATTRI( _T("code"), 404 ) << XATTR( _T("type"), _T("cancel"))
			<< XCHILDNS( _T("item-not-found"), _T("urn:ietf:params:xml:ns:xmpp-stanzas")));
	}

	delete jbt;
	Log( "Thread ended: type=bytestream_recv" );
}

int CJabberProto::ByteReceiveParse( HANDLE hConn, JABBER_BYTE_TRANSFER *jbt, char* buffer, int datalen )
{
	int bytesReceived, num = datalen;

	switch ( jbt->state ) {
	case JBT_INIT:
		// received:
		// 00-00 ver ( 0x05 )
		// 01-01 selected method ( 0=no auth, 0xff=error )
		// send:
		// 00-00 ver ( 0x05 )
		// 01-01 cmd ( 1=connect )
		// 02-02 reserved ( 0 )
		// 03-03 address type ( 3 )
		// 04-44 dst.addr ( 41 bytes: 1-byte length, 40-byte SHA1 hash of [sid,srcJID,dstJID] )
		// 45-46 dst.port ( 0 )
		if ( datalen==2 && buffer[0]==5 && buffer[1]==0 ) {
			BYTE data[47];
			ZeroMemory( data, sizeof( data ));
			*(( DWORD* )data ) = 0x03000105;
			data[4] = 40;

			TCHAR text[JABBER_MAX_JID_LEN*2];
			TCHAR *szInitiatorJid = JabberPrepareJid(jbt->srcJID);
			TCHAR *szTargetJid = JabberPrepareJid(jbt->dstJID);
			mir_sntprintf( text, SIZEOF( text ), _T("%s%s%s"), jbt->sid, szInitiatorJid, szTargetJid );
			mir_free(szInitiatorJid);
			mir_free(szTargetJid);
			char* szAuthString = mir_utf8encodeT( text );
			Log( "Auth: '%s'", szAuthString );
			char* szHash = JabberSha1( szAuthString );
			strncpy(( char* )( data+5 ), szHash, 40 );
			mir_free( szHash );
			Netlib_Send( hConn, ( char* )data, 47, 0 );
			jbt->state = JBT_CONNECT;
			mir_free( szAuthString );
		}
		else jbt->state = JBT_SOCKSERR;
		break;

	case JBT_CONNECT:
		// received:
		// 00-00 ver ( 0x05 )
		// 01-01 reply ( 0=success,2=not allowed )
		// 02-02 reserved ( 0 )
		// 03-03 address type ( 1=IPv4 address,3=host address )
		// 04-mm bnd.addr server bound address ( 4-byte IP if IPv4, 1-byte length + n-byte host address string if host address )
		// nn-nn+1 bnd.port server bound port
		if ( datalen>=5 && buffer[0]==5 && buffer[1]==0 && ( buffer[3]==1 || buffer[3]==3 || buffer[3]==0 )) {
			if ( buffer[3]==1 && datalen>=10 )
				num = 10;
			else if ( buffer[3]==3 && datalen>=buffer[4]+7 )
				num = buffer[4] + 7;
			else if ( buffer[3]==0 && datalen>=6 )
				num = 6;
			else {
				jbt->state = JBT_SOCKSERR;
				break;
			}
			jbt->state = JBT_RECVING;

			m_ThreadInfo->send(
				XmlNodeIq( _T("result"), jbt->iqId, jbt->srcJID ) << XQUERY( _T(JABBER_FEAT_BYTESTREAMS))
					<< XCHILD( _T("streamhost-used")) << XATTR( _T("jid"), jbt->streamhostJID ));
		}
		else jbt->state = JBT_SOCKSERR;
		break;

	case JBT_RECVING:
		bytesReceived = (this->*jbt->pfnRecv)( hConn, jbt->ft, buffer, datalen );
		if ( bytesReceived < 0 )
			jbt->state = JBT_ERROR;
		else if ( bytesReceived == 0 )
			jbt->state = JBT_DONE;
		break;
	}

	return num;
}