Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

636 lines
19 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: implementation of the rcon server
  4. //
  5. //===========================================================================//
  6. #if defined(_WIN32)
  7. #if !defined(_X360)
  8. #include <winsock.h>
  9. #endif
  10. #undef SetPort // winsock screws with the SetPort string... *sigh*
  11. #define socklen_t int
  12. #define MSG_NOSIGNAL 0
  13. #elif POSIX
  14. #include <sys/types.h>
  15. #include <sys/socket.h>
  16. #include <netinet/in.h>
  17. #include <netinet/tcp.h>
  18. #include <errno.h>
  19. #include <sys/ioctl.h>
  20. #define closesocket close
  21. #define WSAGetLastError() errno
  22. #define ioctlsocket ioctl
  23. #ifdef OSX
  24. #define MSG_NOSIGNAL 0
  25. #endif
  26. #endif
  27. #include <tier0/dbg.h>
  28. #include "utlbuffer.h"
  29. #include "server.h"
  30. #include "sv_rcon.h"
  31. #include "proto_oob.h" // PORT_RCON define
  32. #include "sv_remoteaccess.h"
  33. #include "cl_rcon.h"
  34. #include "sv_filter.h"
  35. #if defined( _X360 )
  36. #include "xbox/xbox_win32stubs.h"
  37. #endif
  38. // memdbgon must be the last include file in a .cpp file!!!
  39. #include "tier0/memdbgon.h"
  40. #ifdef ENABLE_RPT
  41. class CRPTServer : public CRConServer
  42. {
  43. typedef CRConServer BaseClass;
  44. public:
  45. virtual void OnSocketAccepted( SocketHandle_t hSocket, const netadr_t & netAdr, void** ppData )
  46. {
  47. BaseClass::OnSocketAccepted( hSocket, netAdr, ppData );
  48. // Enable cheats on this client only
  49. Cmd_SetRptActive( true );
  50. }
  51. virtual void OnSocketClosed( SocketHandle_t hSocket, const netadr_t & netAdr, void* pData )
  52. {
  53. Cmd_SetRptActive( false );
  54. BaseClass::OnSocketClosed( hSocket, netAdr, pData );
  55. }
  56. };
  57. static CRPTServer g_RPTServer;
  58. CRConServer & RPTServer()
  59. {
  60. return g_RPTServer;
  61. }
  62. #endif // ENABLE_RPT
  63. static CRConServer g_RCONServer;
  64. CRConServer & RCONServer()
  65. {
  66. return g_RCONServer;
  67. }
  68. static void RconPasswordChanged_f( IConVar *pConVar, const char *pOldString, float flOldValue )
  69. {
  70. ConVarRef var( pConVar );
  71. const char *pPassword = var.GetString();
  72. #ifndef SWDS
  73. RCONClient().SetPassword( pPassword );
  74. #endif
  75. RCONServer().SetPassword( pPassword );
  76. }
  77. ConVar rcon_password ( "rcon_password", "", FCVAR_SERVER_CANNOT_QUERY|FCVAR_DONTRECORD, "remote console password.", RconPasswordChanged_f );
  78. ConVar sv_rcon_banpenalty( "sv_rcon_banpenalty", "0", 0, "Number of minutes to ban users who fail rcon authentication", true, 0, false, 0 );
  79. 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 );
  80. 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 );
  81. ConVar sv_rcon_minfailuretime( "sv_rcon_minfailuretime", "30", 0, "Number of seconds to track failed rcon authentications", true, 1, false, 0 );
  82. 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'" );
  83. ConVar sv_rcon_maxpacketsize( "sv_rcon_maxpacketsize", "1024", 0, "The maximum number of bytes to allow in a command packet", true, 0, false, 0 );
  84. 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 );
  85. //-----------------------------------------------------------------------------
  86. // Purpose: Constructor
  87. //-----------------------------------------------------------------------------
  88. #pragma warning ( disable : 4355 )
  89. CRConServer::CRConServer() : m_Socket( this )
  90. {
  91. }
  92. CRConServer::CRConServer( const char *pNetAddress ) : m_Socket( this )
  93. {
  94. SetAddress( pNetAddress );
  95. }
  96. #pragma warning ( default : 4355 )
  97. //-----------------------------------------------------------------------------
  98. // Purpose: Destructor
  99. //-----------------------------------------------------------------------------
  100. CRConServer::~CRConServer()
  101. {
  102. }
  103. //-----------------------------------------------------------------------------
  104. // Allows a server to request a listening client to connect to it
  105. //-----------------------------------------------------------------------------
  106. bool CRConServer::ConnectToListeningClient( const netadr_t &adr, bool bSingleSocket )
  107. {
  108. if ( m_Socket.ConnectSocket( adr, bSingleSocket ) < 0 )
  109. {
  110. ConWarning( "Unable to connect to remote client (%s)\n", adr.ToString() );
  111. return false;
  112. }
  113. return true;
  114. }
  115. //-----------------------------------------------------------------------------
  116. // Purpose: returns true if the listening socket is created and listening
  117. //-----------------------------------------------------------------------------
  118. bool CRConServer::IsConnected()
  119. {
  120. return m_Socket.IsListening();
  121. }
  122. void CRConServer::SetPassword( const char *pPassword )
  123. {
  124. m_Socket.CloseAllAcceptedSockets();
  125. m_Password = pPassword;
  126. }
  127. bool CRConServer::HasPassword() const
  128. {
  129. return !m_Password.IsEmpty();
  130. }
  131. bool CRConServer::IsPassword( const char *pPassword ) const
  132. {
  133. // Must have a password set to allow any rconning.
  134. if ( !HasPassword() )
  135. return false;
  136. // If the pw does not match, then not authed
  137. return ( Q_strcmp( pPassword, m_Password.Get() ) == 0 );
  138. }
  139. //-----------------------------------------------------------------------------
  140. // Purpose: Set the address to bind to
  141. //-----------------------------------------------------------------------------
  142. void CRConServer::SetAddress( const char *pNetAddress )
  143. {
  144. NET_StringToAdr( pNetAddress, &m_Address );
  145. if ( m_Address.GetPort() == 0 )
  146. {
  147. m_Address.SetPort( PORT_RCON );
  148. }
  149. }
  150. bool CRConServer::CreateSocket()
  151. {
  152. return m_Socket.CreateListenSocket( m_Address );
  153. }
  154. //-----------------------------------------------------------------------------
  155. // Inherited from ISocketCreatorListener
  156. //-----------------------------------------------------------------------------
  157. bool CRConServer::ShouldAcceptSocket( SocketHandle_t hSocket, const netadr_t & netAdr )
  158. {
  159. return !Filter_ShouldDiscard( netAdr );
  160. }
  161. void CRConServer::OnSocketAccepted( SocketHandle_t hSocket, const netadr_t &netAdr, void** ppData )
  162. {
  163. ConnectedRConSocket_t *pNewSocket = new ConnectedRConSocket_t;
  164. pNewSocket->lastRequestID = 0;
  165. pNewSocket->authed = false;
  166. pNewSocket->listenerID = g_ServerRemoteAccess.GetNextListenerID( true, &netAdr );
  167. *ppData = pNewSocket;
  168. }
  169. void CRConServer::OnSocketClosed( SocketHandle_t hSocket, const netadr_t &netAdr, void* pData )
  170. {
  171. m_bSocketDeleted = true;
  172. ConnectedRConSocket_t *pOldSocket = (ConnectedRConSocket_t*)( pData );
  173. delete pOldSocket;
  174. }
  175. //-----------------------------------------------------------------------------
  176. // Purpose: accept new connections and walk open sockets and handle any incoming data
  177. //-----------------------------------------------------------------------------
  178. void CRConServer::RunFrame()
  179. {
  180. m_Socket.RunFrame();
  181. m_bSocketDeleted = false;
  182. // handle incoming data
  183. // NOTE: Have to iterate in reverse since we may be killing sockets
  184. int nCount = m_Socket.GetAcceptedSocketCount();
  185. for ( int i = nCount - 1; i >= 0; --i )
  186. {
  187. // process any outgoing data for this socket
  188. ConnectedRConSocket_t *pData = GetSocketData( i );
  189. SocketHandle_t hSocket = m_Socket.GetAcceptedSocketHandle( i );
  190. const netadr_t& socketAdr = m_Socket.GetAcceptedSocketAddress( i );
  191. while ( pData->m_OutstandingSends.Count() > 0 )
  192. {
  193. CUtlBuffer &packet = pData->m_OutstandingSends[ pData->m_OutstandingSends.Head()];
  194. bool bSent = SendRCONResponse( i, packet.PeekGet(), packet.TellPut() - packet.TellGet(), true );
  195. if ( bSent ) // all this packet was sent, remove it
  196. {
  197. pData->m_OutstandingSends.Remove( pData->m_OutstandingSends.Head() ); // delete this entry no matter what, SendRCONResponse() will re-queue if needed
  198. }
  199. else // must have blocked part way through, SendRCONResponse
  200. // fixed up the queued entry
  201. {
  202. break;
  203. }
  204. }
  205. int sendLen = g_ServerRemoteAccess.GetDataResponseSize( pData->listenerID );
  206. if ( sendLen > 0 )
  207. {
  208. char sendBuf[4096];
  209. char *pBuf = sendBuf;
  210. bool bAllocate = ( sendLen + sizeof(int) > sizeof(sendBuf) );
  211. if ( bAllocate )
  212. {
  213. pBuf = new char[sendLen + sizeof(int)];
  214. }
  215. memcpy( pBuf, &sendLen, sizeof(sendLen) ); // copy the size of the packet in
  216. g_ServerRemoteAccess.ReadDataResponse( pData->listenerID, pBuf + sizeof(int), sendLen );
  217. SendRCONResponse( i, pBuf, sendLen + sizeof(int) );
  218. if ( bAllocate )
  219. {
  220. delete [] pBuf;
  221. }
  222. }
  223. // check for incoming data
  224. int pendingLen = 0;
  225. unsigned long readLen = 0;
  226. char ch;
  227. pendingLen = recv( hSocket, &ch, sizeof(ch), MSG_PEEK );
  228. if ( pendingLen == -1 && SocketWouldBlock() )
  229. continue;
  230. if ( pendingLen == 0 )
  231. {
  232. m_Socket.CloseAcceptedSocket( i );
  233. continue;
  234. }
  235. if ( pendingLen < 0 )
  236. {
  237. //DevMsg( "RCON Cmd: peek error %s\n", NET_ErrorString(WSAGetLastError()));
  238. m_Socket.CloseAcceptedSocket( i );
  239. continue;
  240. }
  241. // find out how much we have to read
  242. ioctlsocket( hSocket, FIONREAD, &readLen );
  243. if ( readLen > sizeof(int) ) // we have a command to process
  244. {
  245. CUtlBuffer & response = pData->packetbuffer;
  246. response.EnsureCapacity( response.TellPut() + readLen );
  247. char *recvBuf = (char *)_alloca( min( 1024ul, readLen ) ); // a buffer used for recv()
  248. unsigned int len = 0;
  249. while ( len < readLen )
  250. {
  251. int recvLen = recv( hSocket, recvBuf , min(1024ul, readLen - len) , 0 );
  252. if ( recvLen == 0 ) // socket was closed
  253. {
  254. m_Socket.CloseAcceptedSocket( i );
  255. break;
  256. }
  257. if ( recvLen < 0 && !SocketWouldBlock() )
  258. {
  259. Warning( "RCON Cmd: recv error (%s)\n", NET_ErrorString( WSAGetLastError() ) );
  260. break;
  261. }
  262. response.Put( recvBuf, recvLen );
  263. len += recvLen;
  264. }
  265. response.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  266. int size = response.GetInt();
  267. if ( sv_rcon_maxpacketsize.GetInt() > 0 && size > sv_rcon_maxpacketsize.GetInt() )
  268. {
  269. if ( sv_rcon_maxpacketbans.GetBool() )
  270. {
  271. HandleFailedRconAuth( socketAdr );
  272. }
  273. m_Socket.CloseAcceptedSocket( i );
  274. continue;
  275. }
  276. while ( size > 0 && size <= response.TellPut() - response.TellGet() )
  277. {
  278. SV_RedirectStart( RD_SOCKET, &socketAdr );
  279. g_ServerRemoteAccess.WriteDataRequest( this, pData->listenerID, response.PeekGet(), size );
  280. SV_RedirectEnd();
  281. if ( m_bSocketDeleted )
  282. return;
  283. response.SeekGet( CUtlBuffer::SEEK_CURRENT, size ); // eat up the buffer we just sent
  284. if ( response.TellPut() - response.TellGet() >= sizeof(int) )
  285. {
  286. size = response.GetInt(); // read how much is in this packet
  287. }
  288. else
  289. {
  290. size = 0; // finished the packet
  291. }
  292. }
  293. // Check and see if socket was closed as a result of processing - this can happen if the user has entered too many passwords
  294. int nNewCount = m_Socket.GetAcceptedSocketCount();
  295. if ( 0 == nNewCount || i > nNewCount || pData != GetSocketData( i ) )
  296. {
  297. response.Purge();
  298. break;
  299. }
  300. if ( size > 0 || (response.TellPut() - response.TellGet() > 0))
  301. {
  302. // trim the bytes that were just processed
  303. CUtlBuffer tmpBuf;
  304. if ( response.TellPut() - response.TellGet() > 0 )
  305. {
  306. tmpBuf.Put( response.PeekGet(), response.TellPut() - response.TellGet() );
  307. }
  308. response.Purge();
  309. if ( size > 0 )
  310. {
  311. response.Put( &size, sizeof(size));
  312. }
  313. if ( tmpBuf.TellPut() > 0 )
  314. {
  315. response.Put( tmpBuf.Base(), tmpBuf.TellPut() );
  316. }
  317. }
  318. else
  319. {
  320. response.Purge();
  321. }
  322. }
  323. } // for each socket
  324. }
  325. //-----------------------------------------------------------------------------
  326. // Purpose: flush the response of a network command back to a user
  327. //-----------------------------------------------------------------------------
  328. void CRConServer::FinishRedirect( const char *msg, const netadr_t &adr )
  329. {
  330. // NOTE: Has to iterate in reverse; SendRCONResponse can close sockets
  331. int nCount = m_Socket.GetAcceptedSocketCount();
  332. for ( int i = nCount - 1; i >= 0; --i )
  333. {
  334. const netadr_t& socketAdr = m_Socket.GetAcceptedSocketAddress( i );
  335. if ( !adr.CompareAdr( socketAdr ) )
  336. continue;
  337. CUtlBuffer response;
  338. // build the response
  339. ConnectedRConSocket_t *pSocketData = GetSocketData( i );
  340. response.PutInt(0); // the size, this gets set once we make the packet
  341. response.PutInt(pSocketData->lastRequestID);
  342. response.PutInt(SERVERDATA_RESPONSE_VALUE);
  343. response.PutString(msg);
  344. response.PutString("");
  345. int size = response.TellPut() - sizeof(int);
  346. response.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
  347. response.PutInt(size); // the size
  348. response.SeekPut( CUtlBuffer::SEEK_CURRENT, size );
  349. // OutputDebugString( va("RCON: String is %i long\n", Q_strlen(msg)) ); // can't use DevMsg(), we are potentially inside the RedirectFlush() function
  350. // printf("RCON: String is %i long, packet size %i\n", Q_strlen(msg), size );
  351. SendRCONResponse( i, response.Base(), response.TellPut() );
  352. }
  353. }
  354. //-----------------------------------------------------------------------------
  355. // Purpose: set the current outstanding request ID for this connection, used by the redirect flush above
  356. //-----------------------------------------------------------------------------
  357. void CRConServer::SetRequestID( ra_listener_id listener, int iRequestID )
  358. {
  359. int nCount = m_Socket.GetAcceptedSocketCount();
  360. for ( int i = 0; i < nCount; i++ )
  361. {
  362. ConnectedRConSocket_t *pSocketData = GetSocketData( i );
  363. if ( pSocketData->listenerID == listener)
  364. {
  365. pSocketData->lastRequestID = iRequestID;
  366. }
  367. }
  368. }
  369. //-----------------------------------------------------------------------------
  370. // Purpose: send a buffer to a particular connection
  371. //-----------------------------------------------------------------------------
  372. bool CRConServer::SendRCONResponse( int nIndex, const void *data, int len, bool fromQueue )
  373. {
  374. SocketHandle_t hSocket = m_Socket.GetAcceptedSocketHandle( nIndex );
  375. if ( hSocket < 0 )
  376. return false;
  377. ConnectedRConSocket_t *pSocketData = GetSocketData( nIndex );
  378. // if we already have queued data pending then just add this to the end
  379. // of the queue
  380. if ( !fromQueue && pSocketData->m_OutstandingSends.Count() > 0 )
  381. {
  382. if ( pSocketData->m_OutstandingSends.Count() > RCON_MAX_OUTSTANDING_SENDS )
  383. {
  384. m_Socket.CloseAcceptedSocket( nIndex );
  385. return false;
  386. }
  387. int index = pSocketData->m_OutstandingSends.AddToTail();
  388. pSocketData->m_OutstandingSends[index].Put( data, len );
  389. return true;
  390. }
  391. Assert( !( fromQueue && data != (pSocketData->m_OutstandingSends[pSocketData->m_OutstandingSends.Head()].Base())));
  392. int sendLen = 0;
  393. while ( sendLen < len )
  394. {
  395. int ret = send( hSocket, (const char *)data + sendLen, len - sendLen, MSG_NOSIGNAL );
  396. if ( ret == -1 )
  397. {
  398. // can't finish sending this right now, push it back
  399. // on the TOP of the queue to be sent next time around
  400. if ( !SocketWouldBlock() )
  401. {
  402. m_Socket.CloseAcceptedSocket( nIndex );
  403. return false;
  404. }
  405. if ( !fromQueue ) // we don't have an entry for this
  406. // yet, add a new one
  407. {
  408. int index = pSocketData->m_OutstandingSends.AddToHead();
  409. pSocketData->m_OutstandingSends[index].Put( (void *)((char *)data + sendLen), len - sendLen );
  410. }
  411. else // update the existing queued item to show we
  412. // sent some of it (we only ever send the head of the list)
  413. {
  414. pSocketData->m_OutstandingSends[pSocketData->m_OutstandingSends.Head()].SeekGet( CUtlBuffer::SEEK_CURRENT, sendLen );
  415. }
  416. return false;
  417. }
  418. else if ( ret > 0 )
  419. {
  420. sendLen += ret;
  421. }
  422. }
  423. // printf("RCON: Sending packet %i in len\n", len);
  424. // OutputDebugString( va("RCON: Sending packet %i in len\n", len) ); // can't use DevMsg(), we are potentially inside the RedirectFlush() function
  425. return true;
  426. }
  427. //-----------------------------------------------------------------------------
  428. // Purpose: compares failed rcons based on most recent failure time
  429. //-----------------------------------------------------------------------------
  430. bool CRConServer::FailedRCon_t::operator<(const struct CRConServer::FailedRCon_t &rhs) const
  431. {
  432. int myTime = 0;
  433. int rhsTime = 0;
  434. if ( badPasswordTimes.Count() )
  435. myTime = badPasswordTimes[ badPasswordTimes.Count() - 1 ];
  436. if ( rhs.badPasswordTimes.Count() )
  437. rhsTime = rhs.badPasswordTimes[ rhs.badPasswordTimes.Count() - 1 ];
  438. return myTime < rhsTime;
  439. }
  440. //-----------------------------------------------------------------------------
  441. // Purpose: tracks failed rcon attempts and bans repeat offenders
  442. //-----------------------------------------------------------------------------
  443. bool CRConServer::HandleFailedRconAuth( const netadr_t & adr )
  444. {
  445. if ( sv_rcon_whitelist_address.GetString()[0] )
  446. {
  447. if ( !V_strcmp( adr.ToString( true ), sv_rcon_whitelist_address.GetString() ) )
  448. {
  449. ConMsg( "Rcon auth failed from rcon whitelist address %s\n", adr.ToString() );
  450. return false;
  451. }
  452. }
  453. int i;
  454. FailedRCon_t *failedRcon = NULL;
  455. int nCount = m_failedRcons.Count();
  456. for ( i=0; i < nCount; ++i )
  457. {
  458. if ( adr.CompareAdr( m_failedRcons[i].adr, true ) )
  459. {
  460. failedRcon = &m_failedRcons[i];
  461. break;
  462. }
  463. }
  464. if ( !failedRcon )
  465. {
  466. // remove an old rcon if necessary
  467. if ( nCount >= 32 )
  468. {
  469. // look for the one with the oldest failure
  470. int indexToRemove = -1;
  471. for ( i=0; i < nCount; ++i )
  472. {
  473. if ( indexToRemove < 0 || m_failedRcons[i] < m_failedRcons[indexToRemove] )
  474. {
  475. indexToRemove = i;
  476. }
  477. }
  478. if ( indexToRemove >= 0 )
  479. {
  480. m_failedRcons.Remove( indexToRemove );
  481. }
  482. }
  483. // add the new rcon
  484. int index = m_failedRcons.AddToTail();
  485. failedRcon = &m_failedRcons[index];
  486. failedRcon->adr = adr;
  487. failedRcon->badPasswordCount = 0;
  488. failedRcon->badPasswordTimes.RemoveAll();
  489. }
  490. // update this failed rcon
  491. ++failedRcon->badPasswordCount;
  492. failedRcon->badPasswordTimes.AddToTail( sv.GetTime() );
  493. // remove old failure times (sv_rcon_maxfailures is limited to 20, so we won't be hurting anything by pruning)
  494. while ( failedRcon->badPasswordTimes.Count() > 20 )
  495. {
  496. failedRcon->badPasswordTimes.Remove( 0 );
  497. }
  498. // sanity-check the rcon banning cvars
  499. if ( sv_rcon_maxfailures.GetInt() < sv_rcon_minfailures.GetInt() )
  500. {
  501. int temp = sv_rcon_maxfailures.GetInt();
  502. sv_rcon_maxfailures.SetValue( sv_rcon_minfailures.GetInt() );
  503. sv_rcon_minfailures.SetValue( temp );
  504. }
  505. // ConMsg( "%d of %d bad password times tracked\n", failedRcon->badPasswordTimes.Count(), failedRcon->badPasswordCount );
  506. // ConMsg( "min=%d, max=%d, time=%.2f\n", sv_rcon_minfailures.GetInt(), sv_rcon_maxfailures.GetInt(), sv_rcon_minfailuretime.GetFloat() );
  507. // check if the user should be banned based on total failed attempts
  508. if ( failedRcon->badPasswordCount > sv_rcon_maxfailures.GetInt() )
  509. {
  510. ConMsg( "Banning %s for rcon hacking attempts\n", failedRcon->adr.ToString( true ) );
  511. Cbuf_AddText( va( "addip %i %s\n", sv_rcon_banpenalty.GetInt(), failedRcon->adr.ToString( true ) ) );
  512. Cbuf_Execute();
  513. return true;
  514. }
  515. // check if the user should be banned based on recent failed attempts
  516. int recentFailures = 0;
  517. for ( i=failedRcon->badPasswordTimes.Count()-1; i>=0; --i )
  518. {
  519. if ( failedRcon->badPasswordTimes[i] + sv_rcon_minfailuretime.GetFloat() >= sv.GetTime() )
  520. {
  521. ++recentFailures;
  522. }
  523. }
  524. if ( recentFailures > sv_rcon_minfailures.GetInt() )
  525. {
  526. ConMsg( "Banning %s for rcon hacking attempts\n", failedRcon->adr.ToString( true ) );
  527. Cbuf_AddText( va( "addip %i %s\n", sv_rcon_banpenalty.GetInt(), failedRcon->adr.ToString( true ) ) );
  528. Cbuf_Execute();
  529. return true;
  530. }
  531. return false;
  532. }
  533. bool CRConServer::BCloseAcceptedSocket( ra_listener_id listener )
  534. {
  535. int nCount = m_Socket.GetAcceptedSocketCount();
  536. for ( int i = 0; i < nCount; i++ )
  537. {
  538. ConnectedRConSocket_t *pSocketData = GetSocketData( i );
  539. if ( pSocketData->listenerID == listener )
  540. {
  541. m_Socket.CloseAcceptedSocket( i );
  542. return true;
  543. }
  544. }
  545. return false;
  546. }