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.

416 lines
12 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Deals with remote perf testing on customer machines
  4. //
  5. // $Workfile: $
  6. // $NoKeywords: $
  7. //===========================================================================//
  8. #include "server_pch.h"
  9. #include "cl_rcon.h"
  10. #include "cl_steamauth.h"
  11. // memdbgon must be the last include file in a .cpp file!!!
  12. #include "tier0/memdbgon.h"
  13. #ifdef ENABLE_RPT
  14. //-----------------------------------------------------------------------------
  15. // Remote perf testing
  16. //
  17. // Description: Here's how rpt works. The 'customer' machine is the one
  18. // we want to do testing on. The 'valve' machine is the programmer machine
  19. // that has rcon access to the customer machine. The 'server' machine is the
  20. // machine running a dedicated or listen server which both the customer + valve
  21. // machines are connected to.
  22. //
  23. // Step 0) Customer runs with -rpt on the commandline, which turns on -condebug,
  24. // clears out the console.log, and potentially other things.
  25. //
  26. // Step 1) Customer machine types rpt_password <password>. We store the password
  27. // on the customer machine only and never forward it to the server to avoid
  28. // having to worry about malicious servers. The password is stored in the RPTServer
  29. // password The customer machine then forwards
  30. // a command "rpt_client_enable 1" to the server indicating that it is a client
  31. // which can be controlled via rpt.
  32. //
  33. // Step 2) Server machine receives the "rpt_client_enable 1" command. It stores
  34. // some state indicating the client it received that message from can be rpt controlled.
  35. //
  36. // Step 3) Valve machine types rpt_start <password> [<port #>]. The port specification
  37. // is optional. The valve machine's rpt rcon client puts itself into a listening
  38. // mode, waiting for connection requests from the customer machine. Once
  39. // connected, the RPTClient rcon client will be the mechanism by which the valve
  40. // machine sends rcon commands to the customer machine. After this is done,
  41. // the Valve machine sends a command "rpt_server_enable 1" to the server, indicating
  42. // it wishes to connect to an rpt_client.
  43. //
  44. // Step 4) Server machine receives the 'rpt_server_enable 1' command. This
  45. // command can only be received from IP addresses we know are at Valve,
  46. // and from steam clients in the Valve group. This will turn cheats on on the
  47. // server and will forward a message to the customer machine 'rpt_connect <valve ip address>'.
  48. // It will only try to send this message to a client which has previously
  49. // enabled rpt with the 'rpt_client_enable 1' command.
  50. //
  51. // Step 5) Customer machine receives the 'rpt_connect' command. This command
  52. // can only work if the ip address specified comes from IP addresses we know
  53. // are at Valve. The customer machine then attempts to connect to the RPTClient
  54. // on the Valve machine, which establishes the connection to the customer
  55. // RPTServer. The rcon classes will handle password authentication.
  56. //
  57. // Step 6) Valve machine types rpt <console commands> which are then sent
  58. // to the customer machine through the custom tcp connection set up by the previous steps
  59. //
  60. // Step 7) Customer types 'rpt_password' with no password, which sends the
  61. // message to the server 'rpt_client_enable 0'.
  62. //
  63. // Step 8) Server receives 'rpt_client_enable 0', which marks it as not
  64. // willing to accept rpt connections. Future attempts to connect via
  65. // 'rpt_server_enable 1' will fail.
  66. //
  67. // Step 9) Valve machine types rpt_end, which sends a command 'rpt_server_enable 0'
  68. // to the server, which deactivates cheats on the server. It also disconnects
  69. // the tcp connection to the customer machine.
  70. //-----------------------------------------------------------------------------
  71. static int g_nRptClientSlot = -1;
  72. static int g_nRptServerSlot = -1;
  73. // NOTE: This is expected to be executed by the customer, using rpt_password <password>
  74. // This data will be stored in the client only. Server doesn't get access to the password
  75. // to prevent malicious servers from running nasty commands on clients
  76. #ifndef SWDS
  77. CON_COMMAND_F( rpt_password, "", FCVAR_DONTRECORD | FCVAR_HIDDEN )
  78. {
  79. if ( CommandLine()->FindParm( "-rpt" ) == 0 )
  80. {
  81. ConMsg( "This command will not work unless the game is launched with -rpt\n" );
  82. return;
  83. }
  84. if ( args.ArgC() > 2 )
  85. {
  86. ConMsg( "Incorrect # of arguments.\n" );
  87. return;
  88. }
  89. // Doesn't work on dedicated servers
  90. if ( NET_IsDedicated() || !NET_IsMultiplayer() || ( args.ArgC() > 2 ) )
  91. {
  92. ConMsg( "Failed!\n" );
  93. return;
  94. }
  95. bool bWasEnabled = RPTServer().HasPassword();
  96. char buf[255];
  97. if ( args.ArgC() == 1 )
  98. {
  99. if ( !bWasEnabled )
  100. return;
  101. RPTServer().SetPassword( NULL );
  102. ConMsg( "Disabling...\n" );
  103. Q_snprintf( buf, sizeof( buf ), "rpt_client_enable 0" );
  104. }
  105. else if ( args.ArgC() == 2 )
  106. {
  107. RPTServer().SetPassword( args.Arg( 1 ) );
  108. ConMsg( "New password : %s\n", args.Arg( 1 ) );
  109. if ( bWasEnabled )
  110. return;
  111. ConMsg( "Enabling...\n" );
  112. Q_snprintf( buf, sizeof( buf ), "rpt_client_enable 1" );
  113. }
  114. // Send a command to the server indicating this client can be rpted
  115. CCommand argsClient;
  116. argsClient.Tokenize( buf );
  117. Cmd_ForwardToServer( argsClient, true );
  118. }
  119. #endif
  120. // NOTE: This is autogenerated by clients typing rpt_password. The client keeps the
  121. // password, but tells the server it can be rpted. Only 1 client can be rpted at a time.
  122. CON_COMMAND_F( rpt_client_enable, "", FCVAR_DONTRECORD | FCVAR_HIDDEN | FCVAR_CLIENTCMD_CAN_EXECUTE )
  123. {
  124. if ( args.ArgC() != 2 )
  125. return;
  126. if ( cmd_clientslot < 0 )
  127. return;
  128. CGameClient *pClient = sv.Client( cmd_clientslot );
  129. if ( !pClient )
  130. return;
  131. bool bEnable = ( atoi( args.Arg( 1 ) ) != 0 );
  132. g_nRptClientSlot = bEnable ? cmd_clientslot : -1;
  133. }
  134. void SV_NotifyRPTOfDisconnect( int nClientSlot )
  135. {
  136. if ( nClientSlot == g_nRptClientSlot )
  137. {
  138. g_nRptClientSlot = -1;
  139. }
  140. if ( nClientSlot == g_nRptServerSlot )
  141. {
  142. g_nRptServerSlot = -1;
  143. }
  144. RPTServer().SetPassword( NULL );
  145. #ifndef SWDS
  146. RPTClient().SetPassword( NULL );
  147. RPTClient().CloseListenSocket( );
  148. #endif
  149. }
  150. #ifndef SWDS
  151. void CL_NotifyRPTOfDisconnect( )
  152. {
  153. RPTServer().SetPassword( NULL );
  154. RPTClient().SetPassword( NULL );
  155. RPTClient().CloseListenSocket( );
  156. }
  157. #endif
  158. #ifndef SWDS
  159. // This runs on the valve client machine
  160. CON_COMMAND_F( rpt_start, "", FCVAR_DONTRECORD | FCVAR_HIDDEN )
  161. {
  162. if ( args.ArgC() < 2 || args.ArgC() > 3 )
  163. {
  164. ConMsg( "Incorrect # of arguments.\n" );
  165. return;
  166. }
  167. // Listen for connections from the customer machine
  168. netadr_t rptAddr = net_local_adr;
  169. if ( args.ArgC() == 3 )
  170. {
  171. rptAddr.SetPort( atoi( args.Arg( 2 ) ) );
  172. }
  173. else
  174. {
  175. rptAddr.SetPort( PORT_RPT_LISTEN );
  176. }
  177. RPTClient().SetPassword( args.Arg( 1 ) );
  178. RPTClient().CreateListenSocket( rptAddr );
  179. char pDir[MAX_PATH];
  180. #ifdef WIN32
  181. int nDay, nMonth, nYear;
  182. GetCurrentDate( &nDay, &nMonth, &nYear );
  183. Q_snprintf( pDir, sizeof(pDir), "rpt/%d_%d_%d", nMonth, nDay, nYear );
  184. #elif POSIX
  185. time_t now = time(NULL);
  186. struct tm *tm = localtime( &now );
  187. Q_snprintf( pDir, sizeof(pDir), "rpt/%d_%d_%d", tm->tm_mon, tm->tm_wday, tm->tm_year + 1900 );
  188. #else
  189. #error
  190. #endif
  191. RPTClient().SetRemoteFileDirectory( pDir );
  192. // Send a command to the server indicating we want to connect to a remote client
  193. char pBuf[256];
  194. Q_snprintf( pBuf, sizeof(pBuf), "rpt_server_enable 1 %s\n", rptAddr.ToString() );
  195. CCommand argsClient;
  196. argsClient.Tokenize( pBuf );
  197. Cmd_ForwardToServer( argsClient, true );
  198. }
  199. CON_COMMAND_F( rpt_end, "", FCVAR_DONTRECORD | FCVAR_HIDDEN )
  200. {
  201. if ( args.ArgC() != 1 )
  202. {
  203. ConMsg( "Incorrect # of arguments.\n" );
  204. return;
  205. }
  206. RPTClient().SetPassword( NULL );
  207. RPTClient().CloseListenSocket( );
  208. // Send a command to the server indicating we want to disconnect from a remote client
  209. char pBuf[256];
  210. Q_snprintf( pBuf, sizeof(pBuf), "rpt_server_enable 0\n" );
  211. CCommand argsClient;
  212. argsClient.Tokenize( pBuf );
  213. Cmd_ForwardToServer( argsClient, true );
  214. }
  215. #endif
  216. // This is the steam id for user 'remote_perf_test'. See wiki for password.
  217. // http://intranet.valvesoftware.com/wiki/index.php/Debugging_problems_on_customer_machines
  218. static uint64 s_ValveMask = 0xFAB2423BFFA352AFull;
  219. static uint64 s_pValveIDs[] =
  220. {
  221. 76561197995463203ll ^ s_ValveMask,
  222. };
  223. //-----------------------------------------------------------------------------
  224. // Purpose:
  225. //-----------------------------------------------------------------------------
  226. static bool IsValveIPAddress( const netadr_t &adr )
  227. {
  228. // Only accept this from clients inside of valve
  229. netadr_t valveIP1, valveIP2;
  230. valveIP1.SetIP( 0xCF, 0xAD, 0xB2, 0xFF );
  231. valveIP2.SetIP( 0xCF, 0xAD, 0xB3, 0xFF );
  232. return ( adr.CompareClassCAdr( valveIP1 ) || adr.CompareClassCAdr( valveIP2 ) || adr.IsLoopback() );
  233. }
  234. static bool PlayerIsValveEmployee( int nClientSlot )
  235. {
  236. CGameClient *pClient = sv.Client( nClientSlot );
  237. if ( !pClient )
  238. return false;
  239. const netadr_t& adr = pClient->m_NetChannel->GetRemoteAddress();
  240. if ( !IsValveIPAddress( adr ) )
  241. return false;
  242. // If Steam is running and connected to beta, player is valve
  243. if ( k_EUniverseBeta == GetSteamUniverse() )
  244. return true;
  245. if ( !pClient->IsFullyAuthenticated() )
  246. return false;
  247. player_info_t pi;
  248. if ( !sv.GetPlayerInfo( nClientSlot, &pi ) )
  249. return false;
  250. if ( !pi.friendsID )
  251. return false;
  252. CSteamID steamIDForPlayer( pi.friendsID, 1, k_EUniversePublic, k_EAccountTypeIndividual );
  253. for ( int i = 0; i < ARRAYSIZE(s_pValveIDs); i++ )
  254. {
  255. if ( steamIDForPlayer.ConvertToUint64() == (s_pValveIDs[i] ^ s_ValveMask) )
  256. return true;
  257. }
  258. return false;
  259. }
  260. // NOTE: This is expected to be called on the server as a result of rpt running
  261. CON_COMMAND_F( rpt_server_enable, "", FCVAR_DONTRECORD | FCVAR_HIDDEN | FCVAR_CLIENTCMD_CAN_EXECUTE )
  262. {
  263. if ( args.ArgC() != 5 && args.ArgC() != 2 )
  264. return;
  265. if ( cmd_clientslot < 0 )
  266. return;
  267. if ( g_nRptServerSlot >= 0 && g_nRptServerSlot != cmd_clientslot )
  268. return;
  269. if ( !PlayerIsValveEmployee( cmd_clientslot ) )
  270. return;
  271. if ( g_nRptClientSlot < 0 )
  272. {
  273. ConMsg( "No valid clients.\n" );
  274. g_nRptServerSlot = -1;
  275. return;
  276. }
  277. bool bEnable = atoi( args.Arg( 1 ) ) != 0;
  278. if ( !bEnable )
  279. {
  280. g_nRptServerSlot = -1;
  281. return;
  282. }
  283. // Tell the customer machine to connect to the ip specified if the password matches
  284. CGameClient *pClient = sv.Client( cmd_clientslot );
  285. netadr_t adr( args.Arg( 2 ) );
  286. netadr_t adrClient = pClient->m_NetChannel->GetRemoteAddress();
  287. if ( !adrClient.IsLoopback() && !adr.CompareAdr( adrClient, true ) )
  288. {
  289. ConMsg( "Invalid server IP address.\n" );
  290. return;
  291. }
  292. adr.SetPort( atoi( args.Arg( 4 ) ) );
  293. // NOTE: Trickiness. Need to activate cheats, and that *must* be controlled via
  294. // the server in a method protected by IP checks.
  295. if ( g_nRptServerSlot < 0 )
  296. {
  297. g_nRptServerSlot = cmd_clientslot;
  298. }
  299. char pBuf[256];
  300. Q_snprintf( pBuf, sizeof(pBuf), "rpt_connect %s\n", adr.ToString() );
  301. SV_ExecuteRemoteCommand( pBuf, g_nRptClientSlot );
  302. }
  303. #ifndef SWDS
  304. // NOTE: This executes on the client, and is generated by the server automatically
  305. // when another client tries to connect
  306. CON_COMMAND_F( rpt_connect, "", FCVAR_DONTRECORD | FCVAR_HIDDEN | FCVAR_SERVER_CAN_EXECUTE )
  307. {
  308. if ( CommandLine()->FindParm( "-rpt" ) == 0 )
  309. return;
  310. if ( args.ArgC() != 4 )
  311. return;
  312. const char *pAddress = args.Arg( 1 );
  313. netadr_t adr( pAddress );
  314. adr.SetPort( atoi( args.Arg( 3 ) ) );
  315. // Only accept this from clients inside of valve
  316. if ( !IsValveIPAddress( adr ) )
  317. return;
  318. RPTServer().ConnectToListeningClient( adr, true );
  319. }
  320. // Method to run rcon commands on an rpt-controlled machine
  321. CON_COMMAND_F( rpt, "Issue an rpt command.", FCVAR_DONTRECORD | FCVAR_HIDDEN )
  322. {
  323. char message[1024]; // Command message
  324. char szParam[ 256 ];
  325. message[0] = 0;
  326. for (int i=1 ; i<args.ArgC() ; i++)
  327. {
  328. const char *pParam = args[i];
  329. // put quotes around empty arguments so we can pass things like this: rcon sv_password ""
  330. // otherwise the "" on the end is lost
  331. if ( strchr( pParam, ' ' ) || ( Q_strlen( pParam ) == 0 ) )
  332. {
  333. Q_snprintf( szParam, sizeof( szParam ), "\"%s\"", pParam );
  334. Q_strncat( message, szParam, sizeof( message ), COPY_ALL_CHARACTERS );
  335. }
  336. else
  337. {
  338. Q_strncat( message, pParam, sizeof( message ), COPY_ALL_CHARACTERS );
  339. }
  340. if ( i != ( args.ArgC() - 1 ) )
  341. {
  342. Q_strncat (message, " ", sizeof( message ), COPY_ALL_CHARACTERS);
  343. }
  344. }
  345. RPTClient().SendCmd( message );
  346. }
  347. #endif // SWDS
  348. #endif // ENABLE_RPT