summaryrefslogtreecommitdiff
path: root/engine/sv_rcon.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/sv_rcon.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'engine/sv_rcon.cpp')
-rw-r--r--engine/sv_rcon.cpp636
1 files changed, 636 insertions, 0 deletions
diff --git a/engine/sv_rcon.cpp b/engine/sv_rcon.cpp
new file mode 100644
index 0000000..f3317b9
--- /dev/null
+++ b/engine/sv_rcon.cpp
@@ -0,0 +1,636 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: implementation of the rcon server
+//
+//===========================================================================//
+
+
+#if defined(_WIN32)
+#if !defined(_X360)
+#include <winsock.h>
+#endif
+#undef SetPort // winsock screws with the SetPort string... *sigh*
+#define socklen_t int
+#define MSG_NOSIGNAL 0
+#elif POSIX
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#define closesocket close
+#define WSAGetLastError() errno
+#define ioctlsocket ioctl
+#ifdef OSX
+#define MSG_NOSIGNAL 0
+#endif
+#endif
+#include <tier0/dbg.h>
+#include "utlbuffer.h"
+#include "server.h"
+#include "sv_rcon.h"
+#include "proto_oob.h" // PORT_RCON define
+#include "sv_remoteaccess.h"
+#include "cl_rcon.h"
+#include "sv_filter.h"
+
+#if defined( _X360 )
+#include "xbox/xbox_win32stubs.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#ifdef ENABLE_RPT
+class CRPTServer : public CRConServer
+{
+ typedef CRConServer BaseClass;
+
+public:
+ virtual void OnSocketAccepted( SocketHandle_t hSocket, const netadr_t & netAdr, void** ppData )
+ {
+ BaseClass::OnSocketAccepted( hSocket, netAdr, ppData );
+
+ // Enable cheats on this client only
+ Cmd_SetRptActive( true );
+ }
+
+ virtual void OnSocketClosed( SocketHandle_t hSocket, const netadr_t & netAdr, void* pData )
+ {
+ Cmd_SetRptActive( false );
+ BaseClass::OnSocketClosed( hSocket, netAdr, pData );
+ }
+};
+
+
+static CRPTServer g_RPTServer;
+CRConServer & RPTServer()
+{
+ return g_RPTServer;
+}
+#endif // ENABLE_RPT
+
+static CRConServer g_RCONServer;
+CRConServer & RCONServer()
+{
+ return g_RCONServer;
+}
+
+static void RconPasswordChanged_f( IConVar *pConVar, const char *pOldString, float flOldValue )
+{
+ ConVarRef var( pConVar );
+ const char *pPassword = var.GetString();
+#ifndef SWDS
+ RCONClient().SetPassword( pPassword );
+#endif
+ RCONServer().SetPassword( pPassword );
+
+}
+ConVar rcon_password ( "rcon_password", "", FCVAR_SERVER_CANNOT_QUERY|FCVAR_DONTRECORD, "remote console password.", RconPasswordChanged_f );
+
+ConVar sv_rcon_banpenalty( "sv_rcon_banpenalty", "0", 0, "Number of minutes to ban users who fail rcon authentication", true, 0, false, 0 );
+ConVar sv_rcon_maxfailures( "sv_rcon_maxfailures", "10", 0, "Max number of times a user can fail rcon authentication before being banned", true, 1, true, 20 );
+ConVar sv_rcon_minfailures( "sv_rcon_minfailures", "5", 0, "Number of times a user can fail rcon authentication in sv_rcon_minfailuretime before being banned", true, 1, true, 20 );
+ConVar sv_rcon_minfailuretime( "sv_rcon_minfailuretime", "30", 0, "Number of seconds to track failed rcon authentications", true, 1, false, 0 );
+ConVar sv_rcon_whitelist_address( "sv_rcon_whitelist_address", "", 0, "When set, rcon failed authentications will never ban this address, e.g. '127.0.0.1'" );
+
+ConVar sv_rcon_maxpacketsize( "sv_rcon_maxpacketsize", "1024", 0, "The maximum number of bytes to allow in a command packet", true, 0, false, 0 );
+ConVar sv_rcon_maxpacketbans( "sv_rcon_maxpacketbans", "1", 0, "Ban IPs for sending RCON packets exceeding the value specified in sv_rcon_maxpacketsize", true, 0, true, 1 );
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+#pragma warning ( disable : 4355 )
+
+CRConServer::CRConServer() : m_Socket( this )
+{
+}
+
+CRConServer::CRConServer( const char *pNetAddress ) : m_Socket( this )
+{
+ SetAddress( pNetAddress );
+}
+
+#pragma warning ( default : 4355 )
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CRConServer::~CRConServer()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Allows a server to request a listening client to connect to it
+//-----------------------------------------------------------------------------
+bool CRConServer::ConnectToListeningClient( const netadr_t &adr, bool bSingleSocket )
+{
+ if ( m_Socket.ConnectSocket( adr, bSingleSocket ) < 0 )
+ {
+ ConWarning( "Unable to connect to remote client (%s)\n", adr.ToString() );
+ return false;
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: returns true if the listening socket is created and listening
+//-----------------------------------------------------------------------------
+bool CRConServer::IsConnected()
+{
+ return m_Socket.IsListening();
+}
+
+void CRConServer::SetPassword( const char *pPassword )
+{
+ m_Socket.CloseAllAcceptedSockets();
+ m_Password = pPassword;
+}
+
+bool CRConServer::HasPassword() const
+{
+ return !m_Password.IsEmpty();
+}
+
+bool CRConServer::IsPassword( const char *pPassword ) const
+{
+ // Must have a password set to allow any rconning.
+ if ( !HasPassword() )
+ return false;
+
+ // If the pw does not match, then not authed
+ return ( Q_strcmp( pPassword, m_Password.Get() ) == 0 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the address to bind to
+//-----------------------------------------------------------------------------
+void CRConServer::SetAddress( const char *pNetAddress )
+{
+ NET_StringToAdr( pNetAddress, &m_Address );
+ if ( m_Address.GetPort() == 0 )
+ {
+ m_Address.SetPort( PORT_RCON );
+ }
+}
+
+bool CRConServer::CreateSocket()
+{
+ return m_Socket.CreateListenSocket( m_Address );
+}
+
+
+//-----------------------------------------------------------------------------
+// Inherited from ISocketCreatorListener
+//-----------------------------------------------------------------------------
+bool CRConServer::ShouldAcceptSocket( SocketHandle_t hSocket, const netadr_t & netAdr )
+{
+ return !Filter_ShouldDiscard( netAdr );
+}
+
+void CRConServer::OnSocketAccepted( SocketHandle_t hSocket, const netadr_t &netAdr, void** ppData )
+{
+ ConnectedRConSocket_t *pNewSocket = new ConnectedRConSocket_t;
+ pNewSocket->lastRequestID = 0;
+ pNewSocket->authed = false;
+ pNewSocket->listenerID = g_ServerRemoteAccess.GetNextListenerID( true, &netAdr );
+ *ppData = pNewSocket;
+}
+
+void CRConServer::OnSocketClosed( SocketHandle_t hSocket, const netadr_t &netAdr, void* pData )
+{
+ m_bSocketDeleted = true;
+ ConnectedRConSocket_t *pOldSocket = (ConnectedRConSocket_t*)( pData );
+ delete pOldSocket;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: accept new connections and walk open sockets and handle any incoming data
+//-----------------------------------------------------------------------------
+void CRConServer::RunFrame()
+{
+ m_Socket.RunFrame();
+ m_bSocketDeleted = false;
+
+ // handle incoming data
+ // NOTE: Have to iterate in reverse since we may be killing sockets
+ int nCount = m_Socket.GetAcceptedSocketCount();
+ for ( int i = nCount - 1; i >= 0; --i )
+ {
+ // process any outgoing data for this socket
+ ConnectedRConSocket_t *pData = GetSocketData( i );
+ SocketHandle_t hSocket = m_Socket.GetAcceptedSocketHandle( i );
+ const netadr_t& socketAdr = m_Socket.GetAcceptedSocketAddress( i );
+ while ( pData->m_OutstandingSends.Count() > 0 )
+ {
+ CUtlBuffer &packet = pData->m_OutstandingSends[ pData->m_OutstandingSends.Head()];
+ bool bSent = SendRCONResponse( i, packet.PeekGet(), packet.TellPut() - packet.TellGet(), true );
+ if ( bSent ) // all this packet was sent, remove it
+ {
+ pData->m_OutstandingSends.Remove( pData->m_OutstandingSends.Head() ); // delete this entry no matter what, SendRCONResponse() will re-queue if needed
+ }
+ else // must have blocked part way through, SendRCONResponse
+ // fixed up the queued entry
+ {
+ break;
+ }
+ }
+
+ int sendLen = g_ServerRemoteAccess.GetDataResponseSize( pData->listenerID );
+ if ( sendLen > 0 )
+ {
+ char sendBuf[4096];
+ char *pBuf = sendBuf;
+ bool bAllocate = ( sendLen + sizeof(int) > sizeof(sendBuf) );
+ if ( bAllocate )
+ {
+ pBuf = new char[sendLen + sizeof(int)];
+ }
+ memcpy( pBuf, &sendLen, sizeof(sendLen) ); // copy the size of the packet in
+ g_ServerRemoteAccess.ReadDataResponse( pData->listenerID, pBuf + sizeof(int), sendLen );
+ SendRCONResponse( i, pBuf, sendLen + sizeof(int) );
+ if ( bAllocate )
+ {
+ delete [] pBuf;
+ }
+ }
+
+ // check for incoming data
+ int pendingLen = 0;
+ unsigned long readLen = 0;
+ char ch;
+ pendingLen = recv( hSocket, &ch, sizeof(ch), MSG_PEEK );
+ if ( pendingLen == -1 && SocketWouldBlock() )
+ continue;
+
+ if ( pendingLen == 0 )
+ {
+ m_Socket.CloseAcceptedSocket( i );
+ continue;
+ }
+
+ if ( pendingLen < 0 )
+ {
+ //DevMsg( "RCON Cmd: peek error %s\n", NET_ErrorString(WSAGetLastError()));
+ m_Socket.CloseAcceptedSocket( i );
+ continue;
+ }
+
+ // find out how much we have to read
+ ioctlsocket( hSocket, FIONREAD, &readLen );
+ if ( readLen > sizeof(int) ) // we have a command to process
+ {
+ CUtlBuffer & response = pData->packetbuffer;
+ response.EnsureCapacity( response.TellPut() + readLen );
+ char *recvBuf = (char *)_alloca( min( 1024ul, readLen ) ); // a buffer used for recv()
+ unsigned int len = 0;
+ while ( len < readLen )
+ {
+ int recvLen = recv( hSocket, recvBuf , min(1024ul, readLen - len) , 0 );
+ if ( recvLen == 0 ) // socket was closed
+ {
+ m_Socket.CloseAcceptedSocket( i );
+ break;
+ }
+
+ if ( recvLen < 0 && !SocketWouldBlock() )
+ {
+ Warning( "RCON Cmd: recv error (%s)\n", NET_ErrorString( WSAGetLastError() ) );
+ break;
+ }
+
+ response.Put( recvBuf, recvLen );
+ len += recvLen;
+ }
+
+ response.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
+
+ int size = response.GetInt();
+
+ if ( sv_rcon_maxpacketsize.GetInt() > 0 && size > sv_rcon_maxpacketsize.GetInt() )
+ {
+ if ( sv_rcon_maxpacketbans.GetBool() )
+ {
+ HandleFailedRconAuth( socketAdr );
+ }
+
+ m_Socket.CloseAcceptedSocket( i );
+ continue;
+ }
+
+ while ( size > 0 && size <= response.TellPut() - response.TellGet() )
+ {
+ SV_RedirectStart( RD_SOCKET, &socketAdr );
+ g_ServerRemoteAccess.WriteDataRequest( this, pData->listenerID, response.PeekGet(), size );
+ SV_RedirectEnd();
+ if ( m_bSocketDeleted )
+ return;
+ response.SeekGet( CUtlBuffer::SEEK_CURRENT, size ); // eat up the buffer we just sent
+
+ if ( response.TellPut() - response.TellGet() >= sizeof(int) )
+ {
+ size = response.GetInt(); // read how much is in this packet
+ }
+ else
+ {
+ size = 0; // finished the packet
+ }
+ }
+
+ // Check and see if socket was closed as a result of processing - this can happen if the user has entered too many passwords
+ int nNewCount = m_Socket.GetAcceptedSocketCount();
+ if ( 0 == nNewCount || i > nNewCount || pData != GetSocketData( i ) )
+ {
+ response.Purge();
+ break;
+ }
+
+ if ( size > 0 || (response.TellPut() - response.TellGet() > 0))
+ {
+ // trim the bytes that were just processed
+ CUtlBuffer tmpBuf;
+ if ( response.TellPut() - response.TellGet() > 0 )
+ {
+ tmpBuf.Put( response.PeekGet(), response.TellPut() - response.TellGet() );
+ }
+
+ response.Purge();
+
+ if ( size > 0 )
+ {
+ response.Put( &size, sizeof(size));
+ }
+
+ if ( tmpBuf.TellPut() > 0 )
+ {
+ response.Put( tmpBuf.Base(), tmpBuf.TellPut() );
+ }
+ }
+ else
+ {
+ response.Purge();
+ }
+ }
+ } // for each socket
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: flush the response of a network command back to a user
+//-----------------------------------------------------------------------------
+void CRConServer::FinishRedirect( const char *msg, const netadr_t &adr )
+{
+ // NOTE: Has to iterate in reverse; SendRCONResponse can close sockets
+ int nCount = m_Socket.GetAcceptedSocketCount();
+ for ( int i = nCount - 1; i >= 0; --i )
+ {
+ const netadr_t& socketAdr = m_Socket.GetAcceptedSocketAddress( i );
+ if ( !adr.CompareAdr( socketAdr ) )
+ continue;
+
+ CUtlBuffer response;
+
+ // build the response
+ ConnectedRConSocket_t *pSocketData = GetSocketData( i );
+ response.PutInt(0); // the size, this gets set once we make the packet
+ response.PutInt(pSocketData->lastRequestID);
+ response.PutInt(SERVERDATA_RESPONSE_VALUE);
+ response.PutString(msg);
+ response.PutString("");
+ int size = response.TellPut() - sizeof(int);
+ response.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
+ response.PutInt(size); // the size
+ response.SeekPut( CUtlBuffer::SEEK_CURRENT, size );
+
+
+// OutputDebugString( va("RCON: String is %i long\n", Q_strlen(msg)) ); // can't use DevMsg(), we are potentially inside the RedirectFlush() function
+// printf("RCON: String is %i long, packet size %i\n", Q_strlen(msg), size );
+
+ SendRCONResponse( i, response.Base(), response.TellPut() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: set the current outstanding request ID for this connection, used by the redirect flush above
+//-----------------------------------------------------------------------------
+void CRConServer::SetRequestID( ra_listener_id listener, int iRequestID )
+{
+ int nCount = m_Socket.GetAcceptedSocketCount();
+ for ( int i = 0; i < nCount; i++ )
+ {
+ ConnectedRConSocket_t *pSocketData = GetSocketData( i );
+ if ( pSocketData->listenerID == listener)
+ {
+ pSocketData->lastRequestID = iRequestID;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: send a buffer to a particular connection
+//-----------------------------------------------------------------------------
+bool CRConServer::SendRCONResponse( int nIndex, const void *data, int len, bool fromQueue )
+{
+ SocketHandle_t hSocket = m_Socket.GetAcceptedSocketHandle( nIndex );
+ if ( hSocket < 0 )
+ return false;
+
+ ConnectedRConSocket_t *pSocketData = GetSocketData( nIndex );
+
+ // if we already have queued data pending then just add this to the end
+ // of the queue
+ if ( !fromQueue && pSocketData->m_OutstandingSends.Count() > 0 )
+ {
+ if ( pSocketData->m_OutstandingSends.Count() > RCON_MAX_OUTSTANDING_SENDS )
+ {
+ m_Socket.CloseAcceptedSocket( nIndex );
+ return false;
+ }
+
+ int index = pSocketData->m_OutstandingSends.AddToTail();
+ pSocketData->m_OutstandingSends[index].Put( data, len );
+ return true;
+ }
+
+ Assert( !( fromQueue && data != (pSocketData->m_OutstandingSends[pSocketData->m_OutstandingSends.Head()].Base())));
+
+ int sendLen = 0;
+ while ( sendLen < len )
+ {
+ int ret = send( hSocket, (const char *)data + sendLen, len - sendLen, MSG_NOSIGNAL );
+ if ( ret == -1 )
+ {
+ // can't finish sending this right now, push it back
+ // on the TOP of the queue to be sent next time around
+ if ( !SocketWouldBlock() )
+ {
+ m_Socket.CloseAcceptedSocket( nIndex );
+ return false;
+ }
+
+ if ( !fromQueue ) // we don't have an entry for this
+ // yet, add a new one
+ {
+ int index = pSocketData->m_OutstandingSends.AddToHead();
+ pSocketData->m_OutstandingSends[index].Put( (void *)((char *)data + sendLen), len - sendLen );
+ }
+ else // update the existing queued item to show we
+ // sent some of it (we only ever send the head of the list)
+ {
+ pSocketData->m_OutstandingSends[pSocketData->m_OutstandingSends.Head()].SeekGet( CUtlBuffer::SEEK_CURRENT, sendLen );
+ }
+ return false;
+ }
+ else if ( ret > 0 )
+ {
+ sendLen += ret;
+ }
+ }
+// printf("RCON: Sending packet %i in len\n", len);
+// OutputDebugString( va("RCON: Sending packet %i in len\n", len) ); // can't use DevMsg(), we are potentially inside the RedirectFlush() function
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: compares failed rcons based on most recent failure time
+//-----------------------------------------------------------------------------
+bool CRConServer::FailedRCon_t::operator<(const struct CRConServer::FailedRCon_t &rhs) const
+{
+ int myTime = 0;
+ int rhsTime = 0;
+
+ if ( badPasswordTimes.Count() )
+ myTime = badPasswordTimes[ badPasswordTimes.Count() - 1 ];
+
+ if ( rhs.badPasswordTimes.Count() )
+ rhsTime = rhs.badPasswordTimes[ rhs.badPasswordTimes.Count() - 1 ];
+
+ return myTime < rhsTime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: tracks failed rcon attempts and bans repeat offenders
+//-----------------------------------------------------------------------------
+bool CRConServer::HandleFailedRconAuth( const netadr_t & adr )
+{
+ if ( sv_rcon_whitelist_address.GetString()[0] )
+ {
+ if ( !V_strcmp( adr.ToString( true ), sv_rcon_whitelist_address.GetString() ) )
+ {
+ ConMsg( "Rcon auth failed from rcon whitelist address %s\n", adr.ToString() );
+ return false;
+ }
+ }
+
+ int i;
+ FailedRCon_t *failedRcon = NULL;
+ int nCount = m_failedRcons.Count();
+ for ( i=0; i < nCount; ++i )
+ {
+ if ( adr.CompareAdr( m_failedRcons[i].adr, true ) )
+ {
+ failedRcon = &m_failedRcons[i];
+ break;
+ }
+ }
+
+ if ( !failedRcon )
+ {
+ // remove an old rcon if necessary
+ if ( nCount >= 32 )
+ {
+ // look for the one with the oldest failure
+ int indexToRemove = -1;
+ for ( i=0; i < nCount; ++i )
+ {
+ if ( indexToRemove < 0 || m_failedRcons[i] < m_failedRcons[indexToRemove] )
+ {
+ indexToRemove = i;
+ }
+ }
+ if ( indexToRemove >= 0 )
+ {
+ m_failedRcons.Remove( indexToRemove );
+ }
+ }
+
+ // add the new rcon
+ int index = m_failedRcons.AddToTail();
+ failedRcon = &m_failedRcons[index];
+ failedRcon->adr = adr;
+ failedRcon->badPasswordCount = 0;
+ failedRcon->badPasswordTimes.RemoveAll();
+ }
+
+ // update this failed rcon
+ ++failedRcon->badPasswordCount;
+ failedRcon->badPasswordTimes.AddToTail( sv.GetTime() );
+
+ // remove old failure times (sv_rcon_maxfailures is limited to 20, so we won't be hurting anything by pruning)
+ while ( failedRcon->badPasswordTimes.Count() > 20 )
+ {
+ failedRcon->badPasswordTimes.Remove( 0 );
+ }
+
+ // sanity-check the rcon banning cvars
+ if ( sv_rcon_maxfailures.GetInt() < sv_rcon_minfailures.GetInt() )
+ {
+ int temp = sv_rcon_maxfailures.GetInt();
+ sv_rcon_maxfailures.SetValue( sv_rcon_minfailures.GetInt() );
+ sv_rcon_minfailures.SetValue( temp );
+ }
+
+// ConMsg( "%d of %d bad password times tracked\n", failedRcon->badPasswordTimes.Count(), failedRcon->badPasswordCount );
+// ConMsg( "min=%d, max=%d, time=%.2f\n", sv_rcon_minfailures.GetInt(), sv_rcon_maxfailures.GetInt(), sv_rcon_minfailuretime.GetFloat() );
+
+ // check if the user should be banned based on total failed attempts
+ if ( failedRcon->badPasswordCount > sv_rcon_maxfailures.GetInt() )
+ {
+ ConMsg( "Banning %s for rcon hacking attempts\n", failedRcon->adr.ToString( true ) );
+ Cbuf_AddText( va( "addip %i %s\n", sv_rcon_banpenalty.GetInt(), failedRcon->adr.ToString( true ) ) );
+ Cbuf_Execute();
+ return true;
+ }
+
+ // check if the user should be banned based on recent failed attempts
+ int recentFailures = 0;
+ for ( i=failedRcon->badPasswordTimes.Count()-1; i>=0; --i )
+ {
+ if ( failedRcon->badPasswordTimes[i] + sv_rcon_minfailuretime.GetFloat() >= sv.GetTime() )
+ {
+ ++recentFailures;
+ }
+ }
+ if ( recentFailures > sv_rcon_minfailures.GetInt() )
+ {
+ ConMsg( "Banning %s for rcon hacking attempts\n", failedRcon->adr.ToString( true ) );
+ Cbuf_AddText( va( "addip %i %s\n", sv_rcon_banpenalty.GetInt(), failedRcon->adr.ToString( true ) ) );
+ Cbuf_Execute();
+ return true;
+ }
+
+ return false;
+}
+
+bool CRConServer::BCloseAcceptedSocket( ra_listener_id listener )
+{
+ int nCount = m_Socket.GetAcceptedSocketCount();
+ for ( int i = 0; i < nCount; i++ )
+ {
+ ConnectedRConSocket_t *pSocketData = GetSocketData( i );
+ if ( pSocketData->listenerID == listener )
+ {
+ m_Socket.CloseAcceptedSocket( i );
+ return true;
+ }
+ }
+ return false;
+}