Counter Strike : Global Offensive Source Code
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.

967 lines
28 KiB

  1. //===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose: Handles all the functions for implementing remote access to the engine
  4. //
  5. //===========================================================================//
  6. #include "server_pch.h"
  7. #include "iclient.h"
  8. #include "net.h"
  9. #include "utlbuffer.h"
  10. #include "utllinkedlist.h"
  11. #include "igameserverdata.h"
  12. #include "sv_remoteaccess.h"
  13. #include "sv_rcon.h"
  14. #include "sv_filter.h"
  15. #include "sys.h"
  16. #include "sys_dll.h"
  17. #include "vprof_engine.h"
  18. #include "PlayerState.h"
  19. #include "sv_log.h"
  20. #ifndef DEDICATED
  21. #include "zip/XZip.h"
  22. #endif
  23. #include "cl_main.h"
  24. // NOTE: This has to be the last file included!
  25. #include "tier0/memdbgon.h"
  26. extern IServerGameDLL *serverGameDLL;
  27. CServerRemoteAccess g_ServerRemoteAccess;
  28. EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerRemoteAccess, IGameServerData, GAMESERVERDATA_INTERFACE_VERSION, g_ServerRemoteAccess);
  29. ConVar sv_rcon_log( "sv_rcon_log", "1", 0, "Enable/disable rcon logging." );
  30. //-----------------------------------------------------------------------------
  31. // Host_Stats_f - prints out interesting stats about the server...
  32. //-----------------------------------------------------------------------------
  33. void Host_Stats_f (void)
  34. {
  35. char stats[512];
  36. g_ServerRemoteAccess.GetStatsString(stats, sizeof(stats));
  37. ConMsg(" CPU NetIn NetOut Uptime Maps FPS Players Svms +-ms ~tick\n%s\n", stats);
  38. // "--cpu- ++++in++ +++out++ ---up-- ++cl+ ---fps- ++user+ ~~hms~~ ~~dev~~ ~tdev~~"
  39. }
  40. static ConCommand stats("stats", Host_Stats_f, "Prints server performance variables" );
  41. //-----------------------------------------------------------------------------
  42. // Purpose: Constructor
  43. //-----------------------------------------------------------------------------
  44. CServerRemoteAccess::CServerRemoteAccess()
  45. {
  46. m_iBytesSent = 0;
  47. m_iBytesReceived = 0;
  48. m_NextListenerID = 0;
  49. m_AdminUIID = INVALID_LISTENER_ID;
  50. m_nScreenshotListener = -1;
  51. m_nBugListener = -1;
  52. }
  53. //-----------------------------------------------------------------------------
  54. // Purpose: unique id to associate data transfers with sessions
  55. //-----------------------------------------------------------------------------
  56. ra_listener_id CServerRemoteAccess::GetNextListenerID( bool authConnection, const netadr_t *adr )
  57. {
  58. int i = m_ListenerIDs.AddToTail();
  59. m_ListenerIDs[i].listenerID = i;
  60. m_ListenerIDs[i].authenticated = !authConnection;
  61. m_ListenerIDs[i].m_bHasAddress = ( adr != NULL );
  62. if ( adr )
  63. {
  64. m_ListenerIDs[i].adr = *adr;
  65. }
  66. return i;
  67. }
  68. bool GetStringHelper( CUtlBuffer & cmd, char *outBuf, int bufSize )
  69. {
  70. outBuf[0] = 0;
  71. cmd.GetString(outBuf, bufSize);
  72. if ( !cmd.IsValid() )
  73. {
  74. cmd.Purge();
  75. return false;
  76. }
  77. return true;
  78. }
  79. //-----------------------------------------------------------------------------
  80. // Purpose: handles a request
  81. //-----------------------------------------------------------------------------
  82. void CServerRemoteAccess::WriteDataRequest( CRConServer *pNetworkListener, ra_listener_id listener, const void *buffer, int bufferSize)
  83. {
  84. m_iBytesReceived += bufferSize;
  85. // ConMsg("RemoteAccess: bytes received: %d\n", m_iBytesReceived);
  86. if ( bufferSize < 2*sizeof(int) ) // check that the buffer contains at least the id and type
  87. {
  88. return;
  89. }
  90. CUtlBuffer cmd(buffer, bufferSize, CUtlBuffer::READ_ONLY);
  91. bool invalidRequest = false;
  92. while ( invalidRequest == false && (int)cmd.TellGet() < (int)(cmd.Size() - 2 * sizeof(int) ) ) // while there is commands to read
  93. {
  94. // parse out the buffer
  95. int requestID = cmd.GetInt();
  96. pNetworkListener->SetRequestID( listener, requestID ); // tell the rcon server the ID so it can reflect it when the console redirect flushes
  97. int requestType = cmd.GetInt();
  98. switch (requestType)
  99. {
  100. case SERVERDATA_REQUESTVALUE:
  101. {
  102. if ( IsAuthenticated(listener) )
  103. {
  104. char variable[256];
  105. if ( !GetStringHelper( cmd, variable, sizeof(variable) ) )
  106. {
  107. invalidRequest = true;
  108. break;
  109. }
  110. RequestValue( listener, requestID, variable);
  111. if ( !GetStringHelper( cmd, variable, sizeof(variable) ) )
  112. {
  113. invalidRequest = true;
  114. break;
  115. }
  116. }
  117. else
  118. {
  119. char variable[256];
  120. if ( !GetStringHelper( cmd, variable, sizeof(variable) ) )
  121. {
  122. invalidRequest = true;
  123. break;
  124. }
  125. if ( !GetStringHelper( cmd, variable, sizeof(variable) ) )
  126. {
  127. invalidRequest = true;
  128. break;
  129. }
  130. }
  131. }
  132. break;
  133. case SERVERDATA_SETVALUE:
  134. {
  135. if ( IsAuthenticated(listener) )
  136. {
  137. char variable[256];
  138. char value[256];
  139. if ( !GetStringHelper( cmd, variable, sizeof(variable) ) )
  140. {
  141. invalidRequest = true;
  142. break;
  143. }
  144. if ( !GetStringHelper( cmd, value, sizeof(value) ) )
  145. {
  146. invalidRequest = true;
  147. break;
  148. }
  149. SetValue(variable, value);
  150. }
  151. else
  152. {
  153. char command[512];
  154. if ( !GetStringHelper( cmd, command, sizeof(command) ) )
  155. {
  156. invalidRequest = true;
  157. break;
  158. }
  159. if ( !GetStringHelper( cmd, command, sizeof(command) ) )
  160. {
  161. invalidRequest = true;
  162. break;
  163. }
  164. }
  165. }
  166. break;
  167. case SERVERDATA_EXECCOMMAND:
  168. {
  169. if ( IsAuthenticated(listener) )
  170. {
  171. char command[512];
  172. if ( !GetStringHelper( cmd, command, sizeof(command) ) )
  173. {
  174. invalidRequest = true;
  175. break;
  176. }
  177. ExecCommand(command);
  178. if ( listener != m_AdminUIID )
  179. {
  180. LogCommand( listener, va( "command \"%s\"", command) );
  181. }
  182. if ( !GetStringHelper( cmd, command, sizeof(command) ) )
  183. {
  184. invalidRequest = true;
  185. break;
  186. }
  187. }
  188. else
  189. {
  190. char command[512];
  191. if ( !GetStringHelper( cmd, command, sizeof(command) ) )
  192. {
  193. invalidRequest = true;
  194. break;
  195. }
  196. if ( !GetStringHelper( cmd, command, sizeof(command) ) )
  197. {
  198. invalidRequest = true;
  199. break;
  200. }
  201. LogCommand( listener, "Bad Password" );
  202. }
  203. }
  204. break;
  205. case SERVERDATA_AUTH:
  206. {
  207. char password[512];
  208. if ( !GetStringHelper( cmd, password, sizeof( password ) ) )
  209. {
  210. invalidRequest = true;
  211. break;
  212. }
  213. CheckPassword( pNetworkListener, listener, requestID, password );
  214. if ( !GetStringHelper( cmd, password, sizeof( password ) ) )
  215. {
  216. invalidRequest = true;
  217. break;
  218. }
  219. if ( m_ListenerIDs[ listener ].authenticated )
  220. {
  221. // if the second string has a non-zero value, it is a userid.
  222. int userID = atoi( password );
  223. ConCommandBase *cmd = g_pCVar->FindCommand( "mp_disable_autokick" );
  224. if ( cmd )
  225. {
  226. Cbuf_AddText( CBUF_SERVER, va( "mp_disable_autokick %d\n", userID ) );
  227. Cbuf_Execute();
  228. }
  229. }
  230. }
  231. break;
  232. case SERVERDATA_TAKE_SCREENSHOT:
  233. #ifndef DEDICATED
  234. m_nScreenshotListener = listener;
  235. CL_TakeJpeg( );
  236. #endif
  237. break;
  238. case SERVERDATA_SEND_CONSOLE_LOG:
  239. {
  240. #ifndef DEDICATED
  241. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  242. if ( GetConsoleLogFileData( buf ) )
  243. {
  244. HZIP hZip = CreateZipZ( 0, 1024 * 1024, ZIP_MEMORY );
  245. void *pMem = NULL;
  246. unsigned long nLen;
  247. ZipAdd( hZip, "console.log", buf.Base(), buf.TellMaxPut(), ZIP_MEMORY );
  248. ZipGetMemory( hZip, &pMem, &nLen );
  249. SendResponseToClient( listener, SERVERDATA_CONSOLE_LOG_RESPONSE, pMem, nLen );
  250. CloseZip( hZip );
  251. }
  252. else
  253. {
  254. LogCommand( listener, "Failed to read console log!\n" );
  255. RespondString( listener, requestID, "Failed to read console log!\n" );
  256. }
  257. #endif
  258. }
  259. break;
  260. #ifdef VPROF_ENABLED
  261. case SERVERDATA_VPROF:
  262. {
  263. char password[25];
  264. if ( !GetStringHelper( cmd, password, sizeof(password) ) )
  265. {
  266. invalidRequest = true;
  267. break;
  268. }
  269. if ( !GetStringHelper( cmd, password, sizeof(password) ) )
  270. {
  271. invalidRequest = true;
  272. break;
  273. }
  274. if ( IsAuthenticated(listener) )
  275. {
  276. RegisterVProfDataListener( listener );
  277. LogCommand( listener, "Remote VProf started!\n" );
  278. RespondString( listener, requestID, "Remote VProf started!\n" );
  279. }
  280. }
  281. break;
  282. case SERVERDATA_REMOVE_VPROF:
  283. {
  284. char password[25];
  285. if ( !GetStringHelper( cmd, password, sizeof(password) ) )
  286. {
  287. invalidRequest = true;
  288. break;
  289. }
  290. if ( !GetStringHelper( cmd, password, sizeof(password) ) )
  291. {
  292. invalidRequest = true;
  293. break;
  294. }
  295. if ( IsAuthenticated(listener) )
  296. {
  297. RemoveVProfDataListener( listener );
  298. LogCommand( listener, "Remote VProf finished!\n" );
  299. RespondString( listener, requestID, "Remote VProf finished!\n" );
  300. }
  301. }
  302. break;
  303. #endif
  304. case SERVERDATA_SEND_REMOTEBUG:
  305. {
  306. if ( CommandLine()->CheckParm( "-remotebug" ) == NULL )
  307. {
  308. Warning( "Received a remote bug request from rcon client, but not running with '-remotebug'. Ignoring.\n" );
  309. RespondString( listener, 0, "Remote machine using wrong bugreporter dll. Try running with '-remotebug'\n" );
  310. invalidRequest = true;
  311. break;
  312. }
  313. if ( IsAuthenticated(listener) )
  314. {
  315. ExecCommand("bug -auto");
  316. LogCommand( listener, "Remote bug submission\n" );
  317. m_nBugListener = listener;
  318. }
  319. }
  320. break;
  321. default:
  322. Assert(!("Unknown requestType in CServerRemoteAccess::WriteDataRequest()"));
  323. cmd.Purge();
  324. invalidRequest = true;
  325. break;
  326. };
  327. }
  328. }
  329. // NOTE: This version is used by the server DLL or server plugins
  330. void CServerRemoteAccess::WriteDataRequest( ra_listener_id listener, const void *buffer, int bufferSize )
  331. {
  332. WriteDataRequest( &RCONServer(), listener, buffer, bufferSize );
  333. }
  334. void CServerRemoteAccess::RemoteBug( const char *pBugPath )
  335. {
  336. if ( m_nBugListener < 0 )
  337. return;
  338. int i = m_ResponsePackets.AddToTail();
  339. m_ResponsePackets[i].responderID = m_nBugListener;
  340. CUtlBuffer &response = m_ResponsePackets[i].packet;
  341. response.PutInt(0);
  342. response.PutInt(SERVERDATA_RESPONSE_REMOTEBUG);
  343. response.PutString(pBugPath);
  344. m_nBugListener = -1;
  345. }
  346. //-----------------------------------------------------------------------------
  347. // Uploads a screenshot to a particular listener
  348. //-----------------------------------------------------------------------------
  349. void CServerRemoteAccess::UploadScreenshot( const char *pFileName )
  350. {
  351. #ifndef DEDICATED
  352. if ( m_nScreenshotListener < 0 )
  353. return;
  354. CUtlBuffer buf( 128 * 1024, 0 );
  355. if ( g_pFullFileSystem->ReadFile( pFileName, "MOD", buf ) )
  356. {
  357. HZIP hZip = CreateZipZ( 0, 1024 * 1024, ZIP_MEMORY );
  358. void *pMem = NULL;
  359. unsigned long nLen;
  360. ZipAdd( hZip, "screenshot.jpg", buf.Base(), buf.TellMaxPut(), ZIP_MEMORY );
  361. ZipGetMemory( hZip, &pMem, &nLen );
  362. SendResponseToClient( m_nScreenshotListener, SERVERDATA_SCREENSHOT_RESPONSE, pMem, nLen );
  363. CloseZip( hZip );
  364. }
  365. else
  366. {
  367. LogCommand( m_nScreenshotListener, "Failed to read screenshot!\n" );
  368. RespondString( m_nScreenshotListener, 0, "Failed to read screenshot!\n" );
  369. }
  370. m_nScreenshotListener = -1;
  371. #endif
  372. }
  373. //-----------------------------------------------------------------------------
  374. // Purpose: log information about a command that ran
  375. //-----------------------------------------------------------------------------
  376. void CServerRemoteAccess::LogCommand( ra_listener_id listener, const char *msg )
  377. {
  378. if ( !sv_rcon_log.GetBool() )
  379. return;
  380. if ( listener < (ra_listener_id)m_ListenerIDs.Count() && m_ListenerIDs[listener].m_bHasAddress )
  381. {
  382. Log_Msg( LOG_SERVER_LOG, "rcon from \"%s\": %s\n", m_ListenerIDs[listener].adr.ToString(), msg );
  383. }
  384. else
  385. {
  386. Log_Msg( LOG_SERVER_LOG, "rcon from \"unknown\": %s\n", msg );
  387. }
  388. }
  389. //-----------------------------------------------------------------------------
  390. // Purpose: checks if this user has provided the correct password
  391. //-----------------------------------------------------------------------------
  392. void CServerRemoteAccess::CheckPassword( CRConServer *pNetworkListener, ra_listener_id listener, int requestID, const char *password )
  393. {
  394. // If the pw does not match, then not authed
  395. if ( !pNetworkListener->IsPassword( password ) )
  396. {
  397. BadPassword( pNetworkListener, listener );
  398. return;
  399. }
  400. // allocate a spot in the list for the response
  401. int i = m_ResponsePackets.AddToTail();
  402. m_ResponsePackets[i].responderID = listener; // record who we need to respond to
  403. CUtlBuffer &response = m_ResponsePackets[i].packet;
  404. // build the response
  405. response.PutInt(requestID);
  406. response.PutInt(SERVERDATA_AUTH_RESPONSE);
  407. response.PutString("");
  408. response.PutString("");
  409. m_ListenerIDs[ listener ].authenticated = true;
  410. }
  411. //-----------------------------------------------------------------------------
  412. // Purpose: returns true if this connection has provided the correct password
  413. //-----------------------------------------------------------------------------
  414. bool CServerRemoteAccess::IsAuthenticated( ra_listener_id listener )
  415. {
  416. Assert( listener >= 0 && listener < (ra_listener_id)m_ListenerIDs.Count() );
  417. return m_ListenerIDs[listener].authenticated;
  418. }
  419. extern ConVar sv_rcon_maxfailures;
  420. //-----------------------------------------------------------------------------
  421. // Purpose: send a bad password packet
  422. // Returns TRUE if socket was closed
  423. //-----------------------------------------------------------------------------
  424. void CServerRemoteAccess::BadPassword( CRConServer *pNetworkListener, ra_listener_id listener )
  425. {
  426. ListenerStore_t& listenerStore = m_ListenerIDs[listener];
  427. listenerStore.authenticated = false;
  428. if ( pNetworkListener->HandleFailedRconAuth( listenerStore.adr ) )
  429. {
  430. // Close the socket if too many failed attempts
  431. pNetworkListener->BCloseAcceptedSocket( listener );
  432. }
  433. else
  434. {
  435. //
  436. // Respond to the rcon user
  437. //
  438. // allocate a spot in the list for the response
  439. int i = m_ResponsePackets.AddToTail();
  440. m_ResponsePackets[i].responderID = listener; // record who we need to respond to
  441. CUtlBuffer &response = m_ResponsePackets[i].packet;
  442. // build the response
  443. response.PutInt(-1); // special flag for bad password
  444. response.PutInt(SERVERDATA_AUTH_RESPONSE);
  445. response.PutString("");
  446. response.PutString("");
  447. }
  448. }
  449. //-----------------------------------------------------------------------------
  450. // Purpose: returns the number of bytes read
  451. //-----------------------------------------------------------------------------
  452. int CServerRemoteAccess::GetDataResponseSize( ra_listener_id listener )
  453. {
  454. for( int i = m_ResponsePackets.Head(); m_ResponsePackets.IsValidIndex(i); i = m_ResponsePackets.Next(i) )
  455. {
  456. // copy response into buffer
  457. if ( m_ResponsePackets[i].responderID != listener ) // not for us, skip to the next entry
  458. continue;
  459. CUtlBuffer &response = m_ResponsePackets[i].packet;
  460. return response.TellPut();
  461. }
  462. return 0;
  463. }
  464. int CServerRemoteAccess::ReadDataResponse( ra_listener_id listener, void *buffer, int bufferSize )
  465. {
  466. for( int i = m_ResponsePackets.Head(); m_ResponsePackets.IsValidIndex(i); i = m_ResponsePackets.Next(i) )
  467. {
  468. // copy response into buffer
  469. if ( m_ResponsePackets[i].responderID != listener ) // not for us, skip to the next entry
  470. continue;
  471. CUtlBuffer &response = m_ResponsePackets[i].packet;
  472. int bytesToCopy = response.TellPut();
  473. Assert(bufferSize >= bytesToCopy);
  474. if (bytesToCopy <= bufferSize)
  475. {
  476. memcpy(buffer, response.Base(), bytesToCopy);
  477. }
  478. else
  479. {
  480. // not enough room in buffer, don't return message
  481. bytesToCopy = 0;
  482. }
  483. m_iBytesSent += bytesToCopy;
  484. // ConMsg("RemoteAccess: bytes sent: %d\n", m_iBytesSent);
  485. // remove from list
  486. m_ResponsePackets.Remove(i);
  487. // return bytes copied
  488. return bytesToCopy;
  489. }
  490. return 0;
  491. }
  492. //-----------------------------------------------------------------------------
  493. // Purpose: looks up a cvar and posts a return value
  494. //-----------------------------------------------------------------------------
  495. void CServerRemoteAccess::RequestValue( ra_listener_id listener, int requestID, const char *variable)
  496. {
  497. // look up the cvar
  498. CUtlBuffer value(0, 256, CUtlBuffer::TEXT_BUFFER); // text-mode buffer
  499. LookupValue(variable, value);
  500. // allocate a spot in the list for the response
  501. int i = m_ResponsePackets.AddToTail();
  502. m_ResponsePackets[i].responderID = listener; // record who we need to respond to
  503. CUtlBuffer &response = m_ResponsePackets[i].packet;
  504. // build the response
  505. response.PutInt(requestID);
  506. response.PutInt(SERVERDATA_RESPONSE_VALUE);
  507. response.PutString(variable);
  508. //Assert(value.TellPut() > 0);
  509. response.PutInt(value.TellPut());
  510. if (value.TellPut())
  511. {
  512. response.Put(value.Base(), value.TellPut());
  513. }
  514. }
  515. //-----------------------------------------------------------------------------
  516. // Purpose: looks up a cvar and posts a return value
  517. //-----------------------------------------------------------------------------
  518. void CServerRemoteAccess::RespondString( ra_listener_id listener, int requestID, const char *pString )
  519. {
  520. // allocate a spot in the list for the response
  521. int i = m_ResponsePackets.AddToTail();
  522. m_ResponsePackets[i].responderID = listener; // record who we need to respond to
  523. CUtlBuffer &response = m_ResponsePackets[i].packet;
  524. // build the response
  525. response.PutInt(requestID);
  526. response.PutInt(SERVERDATA_RESPONSE_STRING);
  527. response.PutString(pString);
  528. }
  529. //-----------------------------------------------------------------------------
  530. // Purpose: Sets a cvar or value
  531. //-----------------------------------------------------------------------------
  532. void CServerRemoteAccess::SetValue(const char *variable, const char *value)
  533. {
  534. // check for special types
  535. if (!stricmp(variable, "map"))
  536. {
  537. // push a map change command
  538. Cbuf_AddText( CBUF_SERVER, va( "changelevel %s\n", value ) );
  539. Cbuf_Execute();
  540. }
  541. else if (!stricmp(variable, "mapcycle"))
  542. {
  543. // write out a new mapcycle file
  544. ConVarRef mapcycle( "mapcyclefile" );
  545. if ( mapcycle.IsValid() )
  546. {
  547. FileHandle_t f = g_pFileSystem->Open(mapcycle.GetString(), "wt");
  548. if (!f)
  549. {
  550. // mapcycle file probably read only, fall pack to temporary file
  551. Msg("Couldn't write to read-only file %s, using file _temp_mapcycle.txt instead.\n", mapcycle.GetString());
  552. mapcycle.SetValue("_temp_mapcycle.txt" );
  553. f = g_pFileSystem->Open(mapcycle.GetString(), "wt");
  554. if (!f)
  555. {
  556. return;
  557. }
  558. }
  559. g_pFileSystem->Write(value, Q_strlen(value) + 1, f);
  560. g_pFileSystem->Close(f);
  561. }
  562. }
  563. else
  564. {
  565. // Stick the cvar set in the command string, so client notification, replication, etc happens
  566. Cbuf_AddText( CBUF_SERVER, va("%s %s", variable, value) );
  567. Cbuf_AddText(CBUF_SERVER, "\n");
  568. Cbuf_Execute();
  569. }
  570. }
  571. //-----------------------------------------------------------------------------
  572. // Purpose: execs a command
  573. //-----------------------------------------------------------------------------
  574. void CServerRemoteAccess::ExecCommand(const char *cmdString)
  575. {
  576. Cbuf_AddText(CBUF_SERVER, (char *)cmdString);
  577. Cbuf_AddText(CBUF_SERVER, "\n");
  578. Cbuf_Execute();
  579. }
  580. //-----------------------------------------------------------------------------
  581. // Purpose: Finds the value of a particular server variable
  582. //-----------------------------------------------------------------------------
  583. bool CServerRemoteAccess::LookupValue(const char *variable, CUtlBuffer &value)
  584. {
  585. Assert(value.IsText());
  586. // first see if it's a cvar
  587. const char *strval = LookupStringValue(variable);
  588. if (strval)
  589. {
  590. value.PutString(strval);
  591. value.PutChar(0);
  592. }
  593. else if (!stricmp(variable, "stats"))
  594. {
  595. char stats[512];
  596. GetStatsString(stats, sizeof(stats));
  597. value.PutString(stats);
  598. value.PutChar(0);
  599. }
  600. else if (!stricmp(variable, "banlist"))
  601. {
  602. // returns a list of banned users and ip's
  603. GetUserBanList(value);
  604. }
  605. else if (!stricmp(variable, "playerlist"))
  606. {
  607. GetPlayerList(value);
  608. }
  609. else if (!stricmp(variable, "maplist"))
  610. {
  611. GetMapList(value);
  612. }
  613. else if (!stricmp(variable, "uptime"))
  614. {
  615. int timeSeconds = (int)(Plat_FloatTime());
  616. value.PutInt(timeSeconds);
  617. value.PutChar(0);
  618. }
  619. else if (!stricmp(variable, "ipaddress"))
  620. {
  621. char addr[25];
  622. Q_snprintf( addr, sizeof(addr), "%s:%i", net_local_adr.ToString(true), sv.GetUDPPort());
  623. value.PutString( addr );
  624. value.PutChar(0);
  625. }
  626. else if (!stricmp(variable, "mapcycle"))
  627. {
  628. ConVarRef mapcycle( "mapcyclefile" );
  629. if ( mapcycle.IsValid() )
  630. {
  631. // send the mapcycle list file
  632. FileHandle_t f = g_pFileSystem->Open(mapcycle.GetString(), "rb" );
  633. if ( f == FILESYSTEM_INVALID_HANDLE )
  634. return true;
  635. int len = g_pFileSystem->Size(f);
  636. char *mapcycleData = (char *)stackalloc( len+1 );
  637. if ( len && g_pFileSystem->Read( mapcycleData, len, f ) )
  638. {
  639. mapcycleData[len] = 0; // Make sure it's null terminated.
  640. value.PutString((const char *)mapcycleData);
  641. value.PutChar(0);
  642. }
  643. else
  644. {
  645. value.PutString( "" );
  646. value.PutChar(0);
  647. }
  648. g_pFileSystem->Close( f );
  649. }
  650. }
  651. else
  652. {
  653. // value not found, null terminate
  654. value.PutChar(0);
  655. return false;
  656. }
  657. return true;
  658. }
  659. //-----------------------------------------------------------------------------
  660. // Purpose: Finds the value of a particular server variable for simple string values
  661. //-----------------------------------------------------------------------------
  662. const char *CServerRemoteAccess::LookupStringValue(const char *variable)
  663. {
  664. static char s_ReturnBuf[32];
  665. IConVar *pVar = g_pCVar->FindVar( variable );
  666. if ( pVar )
  667. {
  668. ConVarRef var( pVar );
  669. if ( var.IsValid() )
  670. return var.GetString();
  671. }
  672. // special types
  673. if ( !Q_stricmp( variable, "map" ) )
  674. return sv.GetMapName();
  675. if ( !Q_stricmp( variable, "playercount" ) )
  676. {
  677. Q_snprintf( s_ReturnBuf, sizeof(s_ReturnBuf) - 1, "%d", sv.GetNumClients() - sv.GetNumProxies());
  678. return s_ReturnBuf;
  679. }
  680. if ( !Q_stricmp( variable, "maxplayers" ) )
  681. {
  682. Q_snprintf( s_ReturnBuf, sizeof(s_ReturnBuf) - 1, "%d", sv.GetMaxClients() );
  683. return s_ReturnBuf;
  684. }
  685. if ( !Q_stricmp( variable, "gamedescription" ) && serverGameDLL )
  686. return serverGameDLL->GetGameDescription();
  687. return NULL;
  688. }
  689. //-----------------------------------------------------------------------------
  690. // Purpose: fills a buffer with a list of all banned IP addresses
  691. //-----------------------------------------------------------------------------
  692. void CServerRemoteAccess::GetUserBanList(CUtlBuffer &value)
  693. {
  694. // add user bans
  695. int i;
  696. for (i = 0; i < g_UserFilters.Count(); i++)
  697. {
  698. value.Printf("%i %s : %.3f min\n", i + 1, GetUserIDString(g_UserFilters[i].userid), g_UserFilters[i].banTime);
  699. }
  700. // add ip filters
  701. for (i = 0; i < g_IPFilters.Count() ; i++)
  702. {
  703. unsigned char b[4];
  704. *(unsigned *)b = g_IPFilters[i].compare;
  705. value.Printf("%i %i.%i.%i.%i : %.3f min\n", i + 1 + g_UserFilters.Count(), b[0], b[1], b[2], b[3], g_IPFilters[i].banTime);
  706. }
  707. value.PutChar(0);
  708. }
  709. void CServerRemoteAccess::GetStatsString(char *buf, int bufSize)
  710. {
  711. float avgIn=0,avgOut=0;
  712. sv.GetNetStats( avgIn, avgOut );
  713. // format: CPU percent, Bandwidth in, Bandwidth out, uptime, changelevels, framerate, total players
  714. _snprintf(buf, bufSize - 1, "%6.1f %8.1f %8.1f %7i %5i %7.2f %7i %7.2f %7.2f %7.2f",
  715. sv.GetCPUUsage() * 100,
  716. avgIn,
  717. avgOut,
  718. (int)(Sys_FloatTime()) / 60,
  719. sv.GetSpawnCount() - 1,
  720. 1.0/host_frametime, // frame rate
  721. sv.GetNumClients() - sv.GetNumProxies(),
  722. 1000.0f*host_frameendtime_computationduration, 1000.0f*host_frametime_stddeviation, 1000.0f*host_framestarttime_stddeviation );
  723. buf[bufSize - 1] = 0;
  724. };
  725. //-----------------------------------------------------------------------------
  726. // Purpose: Fills buffer with details on everyone in the server
  727. //-----------------------------------------------------------------------------
  728. void CServerRemoteAccess::GetPlayerList(CUtlBuffer &value)
  729. {
  730. if ( !serverGameClients )
  731. {
  732. return;
  733. }
  734. for ( int i=0 ; i< sv.GetClientCount() ; i++ )
  735. {
  736. CGameClient *client = sv.Client(i);
  737. if ( !client || !client->IsActive() )
  738. continue;
  739. CPlayerState *pl = serverGameClients->GetPlayerState( client->edict );
  740. if ( !pl )
  741. continue;
  742. // valid user, add to buffer
  743. // format per user, each user seperated by a newline '\n'
  744. // "name authID ipAddress ping loss frags time"
  745. if ( client->IsFakeClient() )
  746. {
  747. value.Printf("\"%s\" %s 0 0 0 %d 0\n",
  748. client->GetClientName(),
  749. client->GetNetworkIDString(),
  750. pl->score);
  751. }
  752. else
  753. {
  754. value.Printf("\"%s\" %s %s %d %d %d %d\n",
  755. client->GetClientName(),
  756. client->GetNetworkIDString(),
  757. client->GetNetChannel()->GetAddress(),
  758. (int)(client->GetNetChannel()->GetAvgLatency(FLOW_OUTGOING) * 1000.0f),
  759. (int)(client->GetNetChannel()->GetAvgLoss(FLOW_INCOMING)),
  760. pl->score,
  761. (int)(client->GetNetChannel()->GetTimeConnected()));
  762. }
  763. }
  764. value.PutChar(0);
  765. }
  766. //-----------------------------------------------------------------------------
  767. // Purpose: Fills buffer with list of maps from this mod
  768. //-----------------------------------------------------------------------------
  769. void CServerRemoteAccess::GetMapList(CUtlBuffer &value)
  770. {
  771. // search the directory structure.
  772. char mapwild[MAX_QPATH];
  773. char friendly_com_gamedir[ MAX_OSPATH ];
  774. strcpy(mapwild, "maps/*.bsp");
  775. Q_strncpy( friendly_com_gamedir, com_gamedir, sizeof(friendly_com_gamedir) );
  776. Q_strlower( friendly_com_gamedir );
  777. char const *findfn = Sys_FindFirst( mapwild, NULL, 0 );
  778. while ( findfn )
  779. {
  780. char curDir[MAX_PATH];
  781. _snprintf(curDir, MAX_PATH, "maps/%s", findfn);
  782. g_pFileSystem->GetLocalPath(curDir, curDir, MAX_PATH);
  783. // limit maps displayed to ones for the mod only
  784. if (strstr(curDir, friendly_com_gamedir))
  785. {
  786. // clean up the map name
  787. char mapName[MAX_PATH];
  788. strcpy(mapName, findfn);
  789. char *extension = strstr(mapName, ".bsp");
  790. if (extension)
  791. {
  792. *extension = 0;
  793. }
  794. // write into buffer
  795. value.PutString(mapName);
  796. value.PutString("\n");
  797. }
  798. findfn = Sys_FindNext( NULL, 0 );
  799. }
  800. Sys_FindClose();
  801. value.PutChar(0);
  802. }
  803. //-----------------------------------------------------------------------------
  804. // Purpose: sends a message to all the watching admin UI's
  805. //-----------------------------------------------------------------------------
  806. void CServerRemoteAccess::SendMessageToAdminUI( ra_listener_id listenerID, const char *message)
  807. {
  808. if ( listenerID != m_AdminUIID )
  809. {
  810. Warning( "ServerRemoteAccess: Sending AdminUI message to non-AdminUI listener\n" );
  811. }
  812. // allocate a spot in the list for the response
  813. int i = m_ResponsePackets.AddToTail();
  814. m_ResponsePackets[i].responderID = listenerID; // record who we need to respond to
  815. CUtlBuffer &response = m_ResponsePackets[i].packet;
  816. // post the message
  817. response.PutInt(0);
  818. response.PutInt(SERVERDATA_UPDATE);
  819. response.PutString(message);
  820. }
  821. //-----------------------------------------------------------------------------
  822. // Purpose: Sends a response to the client
  823. //-----------------------------------------------------------------------------
  824. void CServerRemoteAccess::SendResponseToClient( ra_listener_id listenerID, ServerDataResponseType_t type, void *pData, int nDataLen )
  825. {
  826. // allocate a spot in the list for the response
  827. int i = m_ResponsePackets.AddToTail();
  828. m_ResponsePackets[i].responderID = listenerID; // record who we need to respond to
  829. CUtlBuffer &response = m_ResponsePackets[i].packet;
  830. // post the message
  831. response.PutInt( 0 );
  832. response.PutInt( type );
  833. response.PutInt( nDataLen );
  834. response.Put( pData, nDataLen );
  835. }
  836. //-----------------------------------------------------------------------------
  837. // Purpose: sends an opaque blob of data from VProf to a remote rcon listener
  838. //-----------------------------------------------------------------------------
  839. void CServerRemoteAccess::SendVProfData( ra_listener_id listenerID, bool bGroupData, void *data, int len )
  840. {
  841. Assert( listenerID != m_AdminUIID ); // only RCON clients support this right now
  842. SendResponseToClient( listenerID, bGroupData ? SERVERDATA_VPROF_GROUPS : SERVERDATA_VPROF_DATA, data, len );
  843. }
  844. //-----------------------------------------------------------------------------
  845. // Purpose: C function for rest of engine to access CServerRemoteAccess class
  846. //-----------------------------------------------------------------------------
  847. extern "C" void NotifyDedicatedServerUI(const char *message)
  848. {
  849. if ( g_ServerRemoteAccess.GetAdminUIID() != INVALID_LISTENER_ID ) // if we have an admin UI actually registered
  850. {
  851. g_ServerRemoteAccess.SendMessageToAdminUI( g_ServerRemoteAccess.GetAdminUIID(), message);
  852. }
  853. }