www.pudn.com > BTSERVER.rar > link.cpp


/***
*
* RainbowBT Beta 7.7 - A C++ BitTorrent Tracker
* Copyright (C) 2003 Trevor Hogan
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
***/

#include "bnbt.h"
#include "atom.h"
#include "bencode.h"
#include "config.h"
#include "link.h"
#include "md5.h"
#include "server.h"
#include "tracker.h"
#include "util.h"

//
// CLink
//

CLink :: CLink( )
{
	m_mtxQueued.Initialize( );

	m_bKill = false;

	m_strIP = CFG_GetString( "RainbowBT_tlink_connect", string( ) );
	m_strPass = CFG_GetString( "RainbowBT_tlink_password", string( ) );

	memset( &sin, 0, sizeof( sin ) );

	sin.sin_family = AF_INET;

	// map host name

	struct hostent *pHE;

	if( pHE = gethostbyname( m_strIP.c_str( ) ) )
		memcpy( &sin.sin_addr, pHE->h_addr, pHE->h_length );
	else if( ( sin.sin_addr.s_addr = inet_addr( m_strIP.c_str( ) ) ) == INADDR_NONE )
	{
		UTIL_LogPrint( "link error (%s) - unable to get host entry\n", getName( ).c_str( ) );

		Kill( );
	}

	if( ( sin.sin_port = htons( (u_short)CFG_GetInt( "RainbowBT_tlink_port", 5204 ) ) ) == 0 )
	{
		UTIL_LogPrint( "link error (%s) - invalid port %d\n", getName( ).c_str( ), CFG_GetInt( "RainbowBT_tlink_port", 5204 ) );

		Kill( );
	}

	m_sckLink = INVALID_SOCKET;
}

CLink :: ~CLink( )
{
	linkmsg_t lmClose;

	lmClose.len = 0;
	lmClose.type = LINKMSG_CLOSE;
	lmClose.msg.erase( );

	Send( lmClose );

	closesocket( m_sckLink );

	m_mtxQueued.Destroy( );

	UTIL_LogPrint( "link (%s) - link broken\n", getName( ).c_str( ) );
}

void CLink :: Kill( )
{
	m_bKill = true;
}

void CLink :: Go( )
{
	if( m_bKill )
		return;

	// map protocol name to protocol number

	struct protoent *pPE;

	if( ( pPE = getprotobyname( "tcp" ) ) == 0 )
	{
		UTIL_LogPrint( "link error (%s) - unable to get tcp protocol entry (error %d)\n", getName( ).c_str( ), GetLastError( ) );

		return;
	}

	// allocate socket

	if( ( m_sckLink = socket( PF_INET, SOCK_STREAM, pPE->p_proto ) ) == INVALID_SOCKET )
	{
		UTIL_LogPrint( "link error (%s) - unable to allocate socket (error %d)\n", getName( ).c_str( ), GetLastError( ) );

		return;
	}

	// connect socket

	if( connect( m_sckLink, (struct sockaddr *)&sin, sizeof( sin ) ) == SOCKET_ERROR )
	{
		UTIL_LogPrint( "link error (%s) - unable to connect (error %d)\n", getName( ).c_str( ), GetLastError( ) );

		return;
	}

	UTIL_LogPrint( "link (%s) - link established\n", getName( ).c_str( ) );

	struct linkmsg_t lmSend;
	struct linkmsg_t lmReceive;

	lmSend.len = strlen( LINK_VER );
	lmSend.type = LINKMSG_VERSION;
	lmSend.msg = LINK_VER;

	Send( lmSend );

	lmReceive = Receive( true );

	if( lmReceive.type != LINKMSG_VERSION || lmReceive.msg != LINK_VER )
	{
		UTIL_LogPrint( "link error (%s) - incompatible version, disconnecting\n", getName( ).c_str( ) );

		return;
	}

	lmReceive = Receive( true );

	if( lmReceive.type != LINKMSG_INFO )
	{
		UTIL_LogPrint( "link error (%s) - unexpected message, disconnecting\n", getName( ).c_str( ) );

		return;
	}

	CAtom *pInfo = Decode( lmReceive.msg );

	if( pInfo && pInfo->isDicti( ) )
	{
		CAtom *pNonce = ( (CAtomDicti *)pInfo )->getItem( "nonce" );

		string strHashMe;

		if( pNonce )
			strHashMe = m_strPass + ":" + pNonce->toString( );
		else
			strHashMe = m_strPass;

		unsigned char szMD5[16];

		MD5_CTX md5;

		MD5Init( &md5 );
		MD5Update( &md5, (unsigned char *)strHashMe.c_str( ), strHashMe.size( ) );
		MD5Final( szMD5, &md5 );

		lmSend.len = 16;
		lmSend.type = LINKMSG_PASSWORD;
		lmSend.msg = string( (char *)szMD5, 16 );

		Send( lmSend );

		delete pInfo;
	}
	else
	{
		UTIL_LogPrint( "link error (%s) - bad info message, disconnecting\n", getName( ).c_str( ) );

		return;
	}

	lmSend.len = 0;
	lmSend.type = LINKMSG_READY;
	lmSend.msg.erase( );

	Send( lmSend );

	UTIL_LogPrint( "link (%s) - ready\n", getName( ).c_str( ) );

	while( 1 )
	{
		if( m_bKill )
			return;

		// send

		m_mtxQueued.Claim( );
		vector vecTemp = m_vecQueued;
		m_vecQueued.clear( );
		m_mtxQueued.Release( );

		for( vector :: iterator i = vecTemp.begin( ); i != vecTemp.end( ); i++ )
			Send( *i );

		// receive

		lmReceive = Receive( false );

		if( lmReceive.type == LINKMSG_ERROR || lmReceive.type == LINKMSG_NONE )
		{
			// ignore
		}
		else if( lmReceive.type == LINKMSG_ANNOUNCE )
		{
			CAtom *pParams = Decode( lmReceive.msg );

			if( pParams && pParams->isDicti( ) )
			{
				gpServer->getTracker( )->QueueAnnounce( (CAtomDicti *)pParams );

				delete pParams;
			}
		}
		else if( lmReceive.type == LINKMSG_CLOSE )
		{
			UTIL_LogPrint( "link warning (%s) - other end closing connection\n", getName( ).c_str( ) );

			return;
		}
		else
			UTIL_LogPrint( "link warning (%s) - unexpected message %d\n", getName( ).c_str( ), lmReceive.type );
	}
}

void CLink :: Send( struct linkmsg_t lm )
{
	if( m_bKill )
		return;

	m_strSendBuf.erase( );
	m_strSendBuf += CAtomLong( lm.len ).toString( );
	m_strSendBuf += "|";
	m_strSendBuf += CAtomInt( lm.type ).toString( );
	m_strSendBuf += "|";
	m_strSendBuf += lm.msg;

	while( !m_strSendBuf.empty( ) )
	{
		int s = send( m_sckLink, m_strSendBuf.c_str( ), m_strSendBuf.size( ), MSG_NOSIGNAL );

		if( s == SOCKET_ERROR )
		{
			UTIL_LogPrint( "link error (%s) - send error (error %d)\n", getName( ).c_str( ), GetLastError( ) );

			Kill( );

			return;
		}

		if( s > 0 )
			m_strSendBuf = m_strSendBuf.substr( s );
	}
}

struct linkmsg_t CLink :: Receive( bool bBlock )
{
	struct linkmsg_t lm;

	lm.len = 0;
	lm.type = LINKMSG_NONE;

	if( m_bKill )
		return lm;

	if( bBlock )
	{
		while( 1 )
		{
			char pTemp[16384];

			memset( pTemp, 0, sizeof( char ) * 16384 );

			int c = recv( m_sckLink, pTemp, 16384, 0 );

			if( c == SOCKET_ERROR )
			{
				UTIL_LogPrint( "link error (%s) - receive error (error %d)\n", getName( ).c_str( ), GetLastError( ) );

				Kill( );

				return lm;
			}

			if( c > 0 )
				m_strReceiveBuf += string( pTemp, c );

			lm = Parse( );

			if( lm.type != LINKMSG_NONE )
				return lm;
		}
	}
	else
	{
		fd_set fdLink;

		FD_ZERO( &fdLink );
		FD_SET( m_sckLink, &fdLink );

		// block for 100 ms to keep from eating up all cpu time

		struct timeval tv;

		tv.tv_sec = 0;
		tv.tv_usec = 100000;

#ifdef WIN32
		if( select( 1, &fdLink, NULL, NULL, &tv ) == SOCKET_ERROR )
#else
		if( select( m_sckLink + 1, &fdLink, NULL, NULL, &tv ) == SOCKET_ERROR )
#endif
		{
			UTIL_LogPrint( "link warning (%s) - select error (error %d)\n", getName( ).c_str( ), GetLastError( ) );

			FD_ZERO( &fdLink );

			MILLISLEEP( 100 );
		}

		if( FD_ISSET( m_sckLink, &fdLink ) )
		{
			char pTemp[16384];

			memset( pTemp, 0, sizeof( char ) * 16384 );

			int c = recv( m_sckLink, pTemp, 16384, 0 );

			if( c == SOCKET_ERROR )
			{
				UTIL_LogPrint( "link error (%s) - receive error (error %d)\n", getName( ).c_str( ), GetLastError( ) );

				Kill( );

				return lm;
			}

			if( c > 0 )
				m_strReceiveBuf += string( pTemp, c );
		}

		lm = Parse( );
	}

	return lm;
}

struct linkmsg_t CLink :: Parse( )
{
	linkmsg_t lm;

	lm.len = 0;
	lm.type = LINKMSG_NONE;

	if( m_bKill )
		return lm;

	string :: size_type iDelim1 = m_strReceiveBuf.find_first_not_of( "1234567890" );

	if( iDelim1 != string :: npos )
	{
		if( iDelim1 > 0 )
		{
			lm.len = atoi( m_strReceiveBuf.substr( 0, iDelim1 ).c_str( ) );

			string :: size_type iDelim2 = m_strReceiveBuf.find_first_not_of( "1234567890", iDelim1 + 1 );

			if( iDelim2 != string :: npos )
			{
				if( iDelim2 > iDelim1 )
				{
					lm.type = atoi( m_strReceiveBuf.substr( iDelim1 + 1, iDelim2 - iDelim1 - 1 ).c_str( ) );

					if( m_strReceiveBuf.size( ) > iDelim2 + lm.len )
					{
						lm.msg = m_strReceiveBuf.substr( iDelim2 + 1, lm.len );

						m_strReceiveBuf = m_strReceiveBuf.substr( iDelim2 + lm.len + 1 );
					}
				}
				else
				{
					UTIL_LogPrint( "link error (%s) - unexpected character, disconnecting\n", getName( ).c_str( ) );

					Kill( );
				}
			}
		}
		else
		{
			UTIL_LogPrint( "link error (%s) - unexpected character, disconnecting\n", getName( ).c_str( ) );

			Kill( );
		}
	}

	return lm;
}

string CLink :: getName( )
{
	return m_strIP + ":" + CAtomInt( ntohs( sin.sin_port ) ).toString( );
}

void CLink :: Queue( struct linkmsg_t lm )
{
	m_mtxQueued.Claim( );
	m_vecQueued.push_back( lm );
	m_mtxQueued.Release( );
}

void StartLink( )
{
	while( gpServer )
	{
		gpLink = new CLink( );

		gpLink->Go( );

		delete gpLink;

		gpLink = NULL;

		MILLISLEEP( 10000 );
	}
}

//
// CLinkClient
//

CLinkClient :: CLinkClient( SOCKET sckLink, struct sockaddr_in sinAddress )
{
	m_mtxQueued.Initialize( );

	m_bKill = false;
	m_bActive = false;

	m_sckLink = sckLink;

	sin = sinAddress;

	UTIL_LogPrint( "link (%s) - link established\n", getName( ).c_str( ) );
}

CLinkClient :: ~CLinkClient( )
{
	linkmsg_t lmClose;

	lmClose.len = 0;
	lmClose.type = LINKMSG_CLOSE;
	lmClose.msg.erase( );

	Send( lmClose );

	closesocket( m_sckLink );

	m_mtxQueued.Destroy( );

	UTIL_LogPrint( "link (%s) - link broken\n", getName( ).c_str( ) );
}

void CLinkClient :: Kill( )
{
	m_bKill = true;
}

void CLinkClient :: Go( )
{
	struct linkmsg_t lmSend;
	struct linkmsg_t lmReceive;

	lmSend.len = strlen( LINK_VER );
	lmSend.type = LINKMSG_VERSION;
	lmSend.msg = LINK_VER;

	Send( lmSend );

	lmReceive = Receive( true );

	if( lmReceive.type != LINKMSG_VERSION || lmReceive.msg != LINK_VER )
	{
		UTIL_LogPrint( "link error (%s) - incompatible version, disconnecting\n", getName( ).c_str( ) );

		return;
	}

	// todo -> change nonce

	string strNonce = "hello";

	CAtomDicti *pInfo = new CAtomDicti( );

	pInfo->setItem( "nonce", new CAtomString( strNonce ) );

	lmSend.len = pInfo->EncodedLength( );
	lmSend.type = LINKMSG_INFO;
	lmSend.msg = Encode( pInfo );

    Send( lmSend );

	delete pInfo;

	lmReceive = Receive( true );

	if( lmReceive.type != LINKMSG_PASSWORD )
	{
		UTIL_LogPrint( "link error (%s) - unexpected message, disconnecting\n", getName( ).c_str( ) );

		return;
	}

	string strHashMe = gpLinkServer->m_strPass + ":" + strNonce;

	unsigned char szMD5[16];

	MD5_CTX md5;

	MD5Init( &md5 );
	MD5Update( &md5, (unsigned char *)strHashMe.c_str( ), strHashMe.size( ) );
	MD5Final( szMD5, &md5 );

	string strMD5 = string( (char *)szMD5, 16 );

	if( strMD5 == lmReceive.msg )
		UTIL_LogPrint( "link (%s) - password accepted\n", getName( ).c_str( ) );
	else
	{
		UTIL_LogPrint( "link error (%s) - bad password, disconnecting\n", getName( ).c_str( ) );

		return;
	}

	while( 1 )
	{
		if( m_bKill )
			return;

		// send

		if( m_bActive )
		{
			m_mtxQueued.Claim( );
			vector vecTemp = m_vecQueued;
			m_vecQueued.clear( );
			m_mtxQueued.Release( );

			for( vector :: iterator i = vecTemp.begin( ); i != vecTemp.end( ); i++ )
				Send( *i );
		}

		// receive

		lmReceive = Receive( false );

		if( lmReceive.type == LINKMSG_ERROR || lmReceive.type == LINKMSG_NONE )
		{
			// ignore
		}
		else if( lmReceive.type == LINKMSG_READY )
		{
			if( gbDebug )
				UTIL_LogPrint( "link (%s) - ready\n", getName( ).c_str( ) );

			m_bActive = true;
		}
		else if( lmReceive.type == LINKMSG_ANNOUNCE )
		{
			CAtom *pParams = Decode( lmReceive.msg );

			if( pParams && pParams->isDicti( ) )
			{
				gpServer->getTracker( )->QueueAnnounce( (CAtomDicti *)pParams );

				delete pParams;
			}

			gpLinkServer->Queue( lmReceive, getName( ) );
		}
		else if( lmReceive.type == LINKMSG_CLOSE )
		{
			UTIL_LogPrint( "link warning (%s) - other end closing connection\n", getName( ).c_str( ) );

			return;
		}
		else
			UTIL_LogPrint( "link warning (%s) - unexpected message %d\n", getName( ).c_str( ), lmReceive.type );
	}
}

void CLinkClient :: Send( struct linkmsg_t lm )
{
	if( m_bKill )
		return;

	m_strSendBuf.erase( );
	m_strSendBuf += CAtomLong( lm.len ).toString( );
	m_strSendBuf += "|";
	m_strSendBuf += CAtomInt( lm.type ).toString( );
	m_strSendBuf += "|";
	m_strSendBuf += lm.msg;

	while( !m_strSendBuf.empty( ) )
	{
		int s = send( m_sckLink, m_strSendBuf.c_str( ), m_strSendBuf.size( ), MSG_NOSIGNAL );

		if( s == SOCKET_ERROR )
		{
			UTIL_LogPrint( "link error (%s) - send error (error %d)\n", getName( ).c_str( ), GetLastError( ) );

			Kill( );

			return;
		}

		if( s > 0 )
			m_strSendBuf = m_strSendBuf.substr( s );
	}
}

struct linkmsg_t CLinkClient :: Receive( bool bBlock )
{
	struct linkmsg_t lm;

	lm.len = 0;
	lm.type = LINKMSG_NONE;

	if( m_bKill )
		return lm;

	if( bBlock )
	{
		while( 1 )
		{
			char pTemp[16384];

			memset( pTemp, 0, sizeof( char ) * 16384 );

			int c = recv( m_sckLink, pTemp, 16384, 0 );

			if( c == SOCKET_ERROR )
			{
				UTIL_LogPrint( "link error (%s) - receive error (error %d)\n", getName( ).c_str( ), GetLastError( ) );

				Kill( );

				return lm;
			}

			if( c > 0 )
				m_strReceiveBuf += string( pTemp, c );

			lm = Parse( );

			if( lm.type != LINKMSG_NONE )
				return lm;
		}
	}
	else
	{
		fd_set fdLink;

		FD_ZERO( &fdLink );
		FD_SET( m_sckLink, &fdLink );

		// block for 100 ms to keep from eating up all cpu time

		struct timeval tv;

		tv.tv_sec = 0;
		tv.tv_usec = 100000;

#ifdef WIN32
		if( select( 1, &fdLink, NULL, NULL, &tv ) == SOCKET_ERROR )
#else
		if( select( m_sckLink + 1, &fdLink, NULL, NULL, &tv ) == SOCKET_ERROR )
#endif
		{
			UTIL_LogPrint( "link warning (%s) - select error (error %d)\n", getName( ).c_str( ), GetLastError( ) );

			FD_ZERO( &fdLink );

			MILLISLEEP( 100 );
		}

		if( FD_ISSET( m_sckLink, &fdLink ) )
		{
			char pTemp[16384];

			memset( pTemp, 0, sizeof( char ) * 16384 );

			int c = recv( m_sckLink, pTemp, 16384, 0 );

			if( c == SOCKET_ERROR )
			{
				UTIL_LogPrint( "link error (%s) - receive error (error %d)\n", getName( ).c_str( ), GetLastError( ) );

				Kill( );

				return lm;
			}

			if( c > 0 )
				m_strReceiveBuf += string( pTemp, c );
		}

		lm = Parse( );
	}

	return lm;
}

struct linkmsg_t CLinkClient :: Parse( )
{
	linkmsg_t lm;

	lm.len = 0;
	lm.type = LINKMSG_NONE;

	if( m_bKill )
		return lm;

	string :: size_type iDelim1 = m_strReceiveBuf.find_first_not_of( "1234567890" );

	if( iDelim1 != string :: npos )
	{
		if( iDelim1 > 0 )
		{
			lm.len = atoi( m_strReceiveBuf.substr( 0, iDelim1 ).c_str( ) );

			string :: size_type iDelim2 = m_strReceiveBuf.find_first_not_of( "1234567890", iDelim1 + 1 );

			if( iDelim2 != string :: npos )
			{
				if( iDelim2 > iDelim1 )
				{
					lm.type = atoi( m_strReceiveBuf.substr( iDelim1 + 1, iDelim2 - iDelim1 - 1 ).c_str( ) );

					if( m_strReceiveBuf.size( ) > iDelim2 + lm.len )
					{
						lm.msg = m_strReceiveBuf.substr( iDelim2 + 1, lm.len );

						m_strReceiveBuf = m_strReceiveBuf.substr( iDelim2 + lm.len + 1 );
					}
				}
				else
				{
					UTIL_LogPrint( "link error (%s) - unexpected character, disconnecting\n", getName( ).c_str( ) );

					Kill( );
				}
			}
		}
		else
		{
			UTIL_LogPrint( "link error (%s) - unexpected character, disconnecting\n", getName( ).c_str( ) );

			Kill( );
		}
	}

	return lm;
}

string CLinkClient :: getName( )
{
	return string( inet_ntoa( sin.sin_addr ) ) + ":" + CAtomInt( ntohs( sin.sin_port ) ).toString( );
}

void CLinkClient :: Queue( struct linkmsg_t lm )
{
	m_mtxQueued.Claim( );
	m_vecQueued.push_back( lm );
	m_mtxQueued.Release( );
}

void StartLinkClient( CLinkClient *pLinkClient )
{
	if( pLinkClient )
	{
		pLinkClient->Go( );

		gpLinkServer->m_mtxLinks.Claim( );

		for( vector :: iterator i = gpLinkServer->m_vecLinks.begin( ); i != gpLinkServer->m_vecLinks.end( ); i++ )
		{
			if( *i == pLinkClient )
			{
				delete *i;

				gpLinkServer->m_vecLinks.erase( i );

				break;
			}
		}

		gpLinkServer->m_mtxLinks.Release( );
	}
}

//
// CLinkServer
//

CLinkServer :: CLinkServer( )
{
	m_mtxLinks.Initialize( );

	m_strBind = CFG_GetString( "RainbowBT_tlink_bind", string( ) );
	m_strPass = CFG_GetString( "RainbowBT_tlink_password", string( ) );

	struct sockaddr_in sin;

	memset( &sin, 0, sizeof( sin ) );

	sin.sin_family = AF_INET;

	if( !m_strBind.empty( ) )
	{
		// bind to m_strBind

		if( gbDebug )
			UTIL_LogPrint( "link server - binding to %s\n", m_strBind.c_str( ) );

		if( ( sin.sin_addr.s_addr = inet_addr( m_strBind.c_str( ) ) ) == INADDR_NONE )
			UTIL_LogPrint( "link server error - unable to bind to %s\n", m_strBind.c_str( ) );
	}
	else
	{
		// bind to all available addresses

		if( gbDebug )
			UTIL_LogPrint( "link server - binding to all available addresses\n" );

		sin.sin_addr.s_addr = INADDR_ANY;
	}

	if( ( sin.sin_port = htons( (u_short)CFG_GetInt( "RainbowBT_tlink_port", 5204 ) ) ) == 0 )
		UTIL_LogPrint( "link server error - invalid port %d\n", CFG_GetInt( "RainbowBT_tlink_port", 5204 ) );

	// map protocol name to protocol number

	struct protoent *pPE;

	if( ( pPE = getprotobyname( "tcp" ) ) == 0 )
		UTIL_LogPrint( "link server error - unable to get tcp protocol entry (error %d)\n", GetLastError( ) );

	// allocate socket

	if( ( m_sckLinkServer = socket( PF_INET, SOCK_STREAM, pPE->p_proto ) ) == INVALID_SOCKET )
		UTIL_LogPrint( "link server error - unable to allocate socket (error %d)\n", GetLastError( ) );

	// bind socket

	int optval = 1;

#ifdef WIN32
	setsockopt( m_sckLinkServer, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof( int ) );
#else
	setsockopt( m_sckLinkServer, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof( int ) );
#endif

	if( bind( m_sckLinkServer, (struct sockaddr *)&sin, sizeof( sin ) ) == SOCKET_ERROR )
		UTIL_LogPrint( "link server error - unable to bind socket (error %d)\n", GetLastError( ) );

	// listen, queue length 1

	if( listen( m_sckLinkServer, 1 ) == SOCKET_ERROR )
		UTIL_LogPrint( "link server error - unable to listen (error %d)\n", GetLastError( ) );

	UTIL_LogPrint( "link server - start\n" );
}

CLinkServer :: ~CLinkServer( )
{
	closesocket( m_sckLinkServer );

	for( vector :: iterator i = m_vecLinks.begin( ); i != m_vecLinks.end( ); i++ )
		(*i)->Kill( );

	unsigned long iStart = GetTime( );

	while( 1 )
	{
		if( m_vecLinks.size( ) > 0 )
		{
			UTIL_LogPrint( "link server - waiting for %d links to disconnect\n", m_vecLinks.size( ) );

			MILLISLEEP( 1000 );
		}
		else
			break;

		if( GetTime( ) - iStart > 60 )
		{
			UTIL_LogPrint( "link server - waited 60 seconds, exiting anyway\n" );

			break;
		}
	}

	m_mtxLinks.Destroy( );

	UTIL_LogPrint( "link server - exit\n" );
}

void CLinkServer :: Update( )
{
	fd_set fdLinkServer;

	FD_ZERO( &fdLinkServer );
	FD_SET( m_sckLinkServer, &fdLinkServer );

	struct timeval tv;

	tv.tv_sec = 0;
	tv.tv_usec = 0;

#ifdef WIN32
	if( select( 1, &fdLinkServer, NULL, NULL, &tv ) == SOCKET_ERROR )
#else
	if( select( m_sckLinkServer + 1, &fdLinkServer, NULL, NULL, &tv ) == SOCKET_ERROR )
#endif
	{
		UTIL_LogPrint( "link server error - select error (error %d)\n", GetLastError( ) );

		FD_ZERO( &fdLinkServer );

		MILLISLEEP( 100 );
	}

	if( FD_ISSET( m_sckLinkServer, &fdLinkServer ) )
	{
		struct sockaddr_in adrFrom;

		int iAddrLen = sizeof( adrFrom );

		SOCKET sckLinkClient;

#ifdef WIN32
		if( ( sckLinkClient = accept( m_sckLinkServer, (struct sockaddr *)&adrFrom, &iAddrLen ) ) == INVALID_SOCKET )
#else
		if( ( sckLinkClient = accept( m_sckLinkServer, (struct sockaddr *)&adrFrom, (socklen_t *)&iAddrLen ) ) == INVALID_SOCKET )
#endif
			UTIL_LogPrint( "link server error - accept error (error %d)\n", GetLastError( ) );
		else
		{
			CLinkClient *pLinkClient = new CLinkClient( sckLinkClient, adrFrom );

#ifdef WIN32
			if( _beginthread( ( void (*)(void *) )StartLinkClient, 0, (void *)pLinkClient ) == -1 )
				UTIL_LogPrint( "error - unable to spawn link client thread\n" );
#else
			pthread_t thread;

			// set detached state since we don't need to join with any threads

			pthread_attr_t attr;
			pthread_attr_init( &attr );
			pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED );

			int c = pthread_create( &thread, &attr, ( void * (*)(void *) )StartLinkClient, (void *)pLinkClient );

			if( c != 0 )
				UTIL_LogPrint( "error - unable to spawn link thread (error %s)\n", strerror( c ) );
#endif

			m_mtxLinks.Claim( );
			m_vecLinks.push_back( pLinkClient );
			m_mtxLinks.Release( );
		}
	}
}

void CLinkServer :: Queue( struct linkmsg_t lm )
{
	m_mtxLinks.Claim( );

	for( vector :: iterator i = m_vecLinks.begin( ); i != m_vecLinks.end( ); i++ )
		(*i)->Queue( lm );

	m_mtxLinks.Release( );
}

void CLinkServer :: Queue( struct linkmsg_t lm, string strExclude )
{
	m_mtxLinks.Claim( );

	for( vector :: iterator i = m_vecLinks.begin( ); i != m_vecLinks.end( ); i++ )
	{
		if( (*i)->getName( ) != strExclude )
			(*i)->Queue( lm );
	}

	m_mtxLinks.Release( );
}