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.

1069 lines
33 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: steam state machine that handles authenticating steam users
  4. //
  5. //===========================================================================//
  6. #ifdef _WIN32
  7. #if !defined( _X360 )
  8. #include "winlite.h"
  9. #include <winsock2.h> // INADDR_ANY defn
  10. #endif
  11. #elif POSIX
  12. #include <netinet/in.h>
  13. #endif
  14. #include "sv_steamauth.h"
  15. #include "sv_filter.h"
  16. #include "inetchannel.h"
  17. #include "netadr.h"
  18. #include "server.h"
  19. #include "proto_oob.h"
  20. #include "host.h"
  21. #include "tier0/vcrmode.h"
  22. #include "sv_plugin.h"
  23. #include "sv_log.h"
  24. #include "filesystem_engine.h"
  25. #include "filesystem_init.h"
  26. #include "tier0/icommandline.h"
  27. #include "steam/steam_gameserver.h"
  28. #include "hltvserver.h"
  29. #include "sys_dll.h"
  30. #if defined( REPLAY_ENABLED )
  31. #include "replayserver.h"
  32. #endif
  33. extern ConVar sv_lan;
  34. extern ConVar sv_visiblemaxplayers;
  35. extern ConVar sv_region;
  36. extern ConVar tv_enable;
  37. static void sv_setsteamblockingcheck_f( IConVar *pConVar, const char *pOldString, float flOldValue );
  38. ConVar sv_steamblockingcheck( "sv_steamblockingcheck", "0", 0,
  39. "Check each new player for Steam blocking compatibility, 1 = message only, 2 >= drop if any member of owning clan blocks,"
  40. "3 >= drop if any player has blocked, 4 >= drop if player has blocked anyone on server", sv_setsteamblockingcheck_f );
  41. #if defined( _X360 )
  42. #include "xbox/xbox_win32stubs.h"
  43. #endif
  44. // memdbgon must be the last include file in a .cpp file!!!
  45. #include "tier0/memdbgon.h"
  46. #pragma warning( disable: 4355 ) // disables ' 'this' : used in base member initializer list'
  47. ConVar sv_master_share_game_socket( "sv_master_share_game_socket", "1", 0,
  48. "Use the game's socket to communicate to the master server. "
  49. "If this is 0, then it will create a socket on -steamport + 1 "
  50. "to communicate to the master server on." );
  51. static char s_szTempMsgBuf[16000];
  52. static void MsgAndLog( const char *fmt, ... )
  53. {
  54. va_list ap;
  55. va_start(ap, fmt);
  56. V_vsprintf_safe( s_szTempMsgBuf, fmt, ap );
  57. // Does Log always print to the console?
  58. //if ( !engine->IsDedicatedServer() )
  59. // Msg("%s", s_szTempMsgBuf );
  60. Log("%s", s_szTempMsgBuf );
  61. }
  62. static void WarningAndLog( const char *fmt, ... )
  63. {
  64. va_list ap;
  65. va_start(ap, fmt);
  66. V_vsprintf_safe( s_szTempMsgBuf, fmt, ap );
  67. // Does Log always print to the console?
  68. Warning("%s", s_szTempMsgBuf );
  69. Log("%s", s_szTempMsgBuf );
  70. }
  71. //-----------------------------------------------------------------------------
  72. // Purpose: singleton accessor
  73. //-----------------------------------------------------------------------------
  74. static CSteam3Server s_Steam3Server;
  75. CSteam3Server &Steam3Server()
  76. {
  77. return s_Steam3Server;
  78. }
  79. //-----------------------------------------------------------------------------
  80. // Purpose: Constructor
  81. //-----------------------------------------------------------------------------
  82. CSteam3Server::CSteam3Server()
  83. #if !defined(NO_STEAM)
  84. :
  85. m_CallbackLogonSuccess( this, &CSteam3Server::OnLogonSuccess ),
  86. m_CallbackLogonFailure( this, &CSteam3Server::OnLogonFailure ),
  87. m_CallbackLoggedOff( this, &CSteam3Server::OnLoggedOff ),
  88. m_CallbackValidateAuthTicketResponse( this, &CSteam3Server::OnValidateAuthTicketResponse ),
  89. m_CallbackPlayerCompatibilityResponse( this, &CSteam3Server::OnComputeNewPlayerCompatibilityResponse ),
  90. m_CallbackGSPolicyResponse( this, &CSteam3Server::OnGSPolicyResponse )
  91. #endif
  92. {
  93. m_bHasActivePlayers = false;
  94. m_bLogOnResult = false;
  95. m_eServerMode = eServerModeInvalid;
  96. m_eServerType = eServerTypeNormal;
  97. m_bWantsSecure = false; // default to insecure currently, this may change
  98. m_bInitialized = false;
  99. m_bWantsPersistentAccountLogon = false;
  100. m_bLogOnFinished = false;
  101. m_bMasterServerUpdaterSharingGameSocket = false;
  102. m_steamIDLanOnly.InstancedSet( 0,0, k_EUniversePublic, k_EAccountTypeInvalid );
  103. m_SteamIDGS.InstancedSet( 1, 0, k_EUniverseInvalid, k_EAccountTypeInvalid );
  104. m_QueryPort = 0;
  105. }
  106. //-----------------------------------------------------------------------------
  107. // Purpose: detect current server mode based on cvars & settings
  108. //-----------------------------------------------------------------------------
  109. EServerMode CSteam3Server::GetCurrentServerMode()
  110. {
  111. if ( sv_lan.GetBool() )
  112. {
  113. return eServerModeNoAuthentication;
  114. }
  115. else if ( CommandLine()->FindParm( "-insecure" ) )
  116. {
  117. return eServerModeAuthentication;
  118. }
  119. else
  120. {
  121. return eServerModeAuthenticationAndSecure;
  122. }
  123. }
  124. //-----------------------------------------------------------------------------
  125. // Purpose: Destructor
  126. //-----------------------------------------------------------------------------
  127. CSteam3Server::~CSteam3Server()
  128. {
  129. Shutdown();
  130. }
  131. void CSteam3Server::Activate( EServerType serverType )
  132. {
  133. // we are active, check if sv_lan changed or we're trying to change server type
  134. if ( GetCurrentServerMode() == m_eServerMode && m_eServerType == serverType )
  135. {
  136. // we are active and LANmode/servertype didnt change. done.
  137. return;
  138. }
  139. if ( BIsActive() )
  140. {
  141. // shut down before we change server mode
  142. Shutdown();
  143. }
  144. m_unIP = INADDR_ANY;
  145. m_usPort = 26900;
  146. if ( CommandLine()->FindParm( "-steamport" ) )
  147. {
  148. m_usPort = CommandLine()->ParmValue( "-steamport", 26900 );
  149. }
  150. ConVarRef ipname( "ip" );
  151. if ( ipname.IsValid() )
  152. {
  153. netadr_t ipaddr;
  154. NET_StringToAdr( ipname.GetString(), &ipaddr );
  155. if ( !ipaddr.IsLoopback() && !ipaddr.IsLocalhost() )
  156. {
  157. m_unIP = ipaddr.GetIPHostByteOrder();
  158. }
  159. }
  160. m_eServerMode = GetCurrentServerMode();
  161. m_eServerType = serverType;
  162. char gamedir[MAX_OSPATH];
  163. Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) );
  164. // Figure out the game port. If we're doing a SrcTV relay, then ignore the NS_SERVER port and don't tell Steam that we have a game server.
  165. uint16 usGamePort = 0;
  166. if ( serverType == eServerTypeNormal )
  167. {
  168. usGamePort = NET_GetUDPPort( NS_SERVER );
  169. }
  170. uint16 usMasterServerUpdaterPort;
  171. if ( sv_master_share_game_socket.GetBool() )
  172. {
  173. m_bMasterServerUpdaterSharingGameSocket = true;
  174. usMasterServerUpdaterPort = MASTERSERVERUPDATERPORT_USEGAMESOCKETSHARE;
  175. if ( serverType == eServerTypeTVRelay )
  176. m_QueryPort = NET_GetUDPPort( NS_HLTV );
  177. else
  178. m_QueryPort = usGamePort;
  179. }
  180. else
  181. {
  182. m_bMasterServerUpdaterSharingGameSocket = false;
  183. usMasterServerUpdaterPort = m_usPort;
  184. m_QueryPort = m_usPort;
  185. }
  186. #ifndef _X360
  187. switch ( m_eServerMode )
  188. {
  189. case eServerModeNoAuthentication:
  190. MsgAndLog( "Initializing Steam libraries for LAN server\n" );
  191. break;
  192. case eServerModeAuthentication:
  193. MsgAndLog( "Initializing Steam libraries for INSECURE Internet server. Authentication and VAC not requested.\n" );
  194. break;
  195. case eServerModeAuthenticationAndSecure:
  196. MsgAndLog( "Initializing Steam libraries for secure Internet server\n" );
  197. break;
  198. default:
  199. WarningAndLog( "Bogus eServermode %d!\n", m_eServerMode );
  200. Assert( !"Bogus server mode?!" );
  201. break;
  202. }
  203. SteamAPI_SetTryCatchCallbacks( false ); // We don't use exceptions, so tell steam not to use try/catch in callback handlers
  204. if ( CommandLine()->FindParm("-hushsteam") || !SteamGameServer_InitSafe(
  205. m_unIP,
  206. m_usPort+1, // Steam lives on -steamport + 1, master server updater lives on -steamport.
  207. usGamePort,
  208. usMasterServerUpdaterPort,
  209. m_eServerMode,
  210. GetSteamInfIDVersionInfo().szVersionString ) )
  211. {
  212. steam_no_good:
  213. #if !defined( NO_STEAM )
  214. WarningAndLog( "*********************************************************\n" );
  215. WarningAndLog( "*\tUnable to load Steam support library.*\n" );
  216. WarningAndLog( "*\tThis server will operate in LAN mode only.*\n" );
  217. WarningAndLog( "*********************************************************\n" );
  218. #endif
  219. m_eServerMode = eServerModeNoAuthentication;
  220. sv_lan.SetValue( true );
  221. return;
  222. }
  223. Init(); // Steam API context init
  224. if ( SteamGameServer() == NULL )
  225. {
  226. Assert( false );
  227. goto steam_no_good;
  228. }
  229. // Note that SteamGameServer_InitSafe() calls SteamAPI_SetBreakpadAppID() for you, which is what we don't want if we wish
  230. // to report crashes under a different AppId. Reset it back to our crashing one now.
  231. if ( sv.IsDedicated() )
  232. {
  233. SteamAPI_SetBreakpadAppID( GetSteamInfIDVersionInfo().ServerAppID );
  234. }
  235. // Set some stuff that should NOT change while the server is
  236. // running
  237. SteamGameServer()->SetProduct( GetSteamInfIDVersionInfo().szProductString );
  238. SteamGameServer()->SetGameDescription( serverGameDLL->GetGameDescription() );
  239. SteamGameServer()->SetDedicatedServer( sv.IsDedicated() );
  240. SteamGameServer()->SetModDir( gamedir );
  241. // Use anonymous logon, or persistent?
  242. if ( m_sAccountToken.IsEmpty() )
  243. {
  244. m_bWantsPersistentAccountLogon = false;
  245. MsgAndLog( "No account token specified; logging into anonymous game server account. (Use sv_setsteamaccount to login to a persistent account.)\n" );
  246. SteamGameServer()->LogOnAnonymous();
  247. }
  248. else
  249. {
  250. m_bWantsPersistentAccountLogon = true;
  251. MsgAndLog( "Logging into Steam game server account\n" );
  252. // TODO: Change this to use just the token when the SDK is updated
  253. SteamGameServer()->LogOn( m_sAccountToken );
  254. }
  255. #endif
  256. SendUpdatedServerDetails();
  257. }
  258. //-----------------------------------------------------------------------------
  259. // Purpose: game server stopped, shutdown Steam game server session
  260. //-----------------------------------------------------------------------------
  261. void CSteam3Server::Shutdown()
  262. {
  263. if ( !BIsActive() )
  264. return;
  265. SteamGameServer_Shutdown();
  266. m_bHasActivePlayers = false;
  267. m_bLogOnResult = false;
  268. m_SteamIDGS = k_steamIDNotInitYetGS;
  269. m_eServerMode = eServerModeInvalid;
  270. Clear(); // Steam API context shutdown
  271. }
  272. //-----------------------------------------------------------------------------
  273. // Purpose: returns true if the userid's are the same
  274. //-----------------------------------------------------------------------------
  275. bool CSteam3Server::CompareUserID( const USERID_t & id1, const USERID_t & id2 )
  276. {
  277. if ( id1.idtype != id2.idtype )
  278. return false;
  279. switch ( id1.idtype )
  280. {
  281. case IDTYPE_STEAM:
  282. case IDTYPE_VALVE:
  283. {
  284. return (id1.steamid == id2.steamid );
  285. }
  286. default:
  287. break;
  288. }
  289. return false;
  290. }
  291. //-----------------------------------------------------------------------------
  292. // Purpose: returns true if this userid is already on this server
  293. //-----------------------------------------------------------------------------
  294. bool CSteam3Server::CheckForDuplicateSteamID( const CBaseClient *client )
  295. {
  296. // in LAN mode we allow reuse of SteamIDs
  297. if ( BLanOnly() )
  298. return false;
  299. // Compare connecting client's ID to other IDs on the server
  300. for ( int i=0 ; i< sv.GetClientCount() ; i++ )
  301. {
  302. const IClient *cl = sv.GetClient( i );
  303. // Not connected, no SteamID yet
  304. if ( !cl->IsConnected() || cl->IsFakeClient() )
  305. continue;
  306. if ( cl->GetNetworkID().idtype != IDTYPE_STEAM )
  307. continue;
  308. // don't compare this client against himself in the list
  309. if ( client == cl )
  310. continue;
  311. if ( !CompareUserID( client->GetNetworkID(), cl->GetNetworkID() ) )
  312. continue;
  313. // SteamID is reused
  314. return true;
  315. }
  316. return false;
  317. }
  318. //-----------------------------------------------------------------------------
  319. // Purpose: Called when secure policy is set
  320. //-----------------------------------------------------------------------------
  321. const CSteamID &CSteam3Server::GetGSSteamID()
  322. {
  323. return m_SteamIDGS;
  324. }
  325. #if !defined(NO_STEAM)
  326. //-----------------------------------------------------------------------------
  327. // Purpose: Called when secure policy is set
  328. //-----------------------------------------------------------------------------
  329. void CSteam3Server::OnGSPolicyResponse( GSPolicyResponse_t *pPolicyResponse )
  330. {
  331. if ( !BIsActive() )
  332. return;
  333. if ( SteamGameServer() && SteamGameServer()->BSecure() )
  334. {
  335. MsgAndLog( "VAC secure mode is activated.\n" );
  336. }
  337. else
  338. {
  339. MsgAndLog( "VAC secure mode disabled.\n" );
  340. }
  341. }
  342. //-----------------------------------------------------------------------------
  343. // Purpose:
  344. //-----------------------------------------------------------------------------
  345. void CSteam3Server::OnLogonSuccess( SteamServersConnected_t *pLogonSuccess )
  346. {
  347. if ( !BIsActive() )
  348. return;
  349. if ( !m_bLogOnResult )
  350. {
  351. m_bLogOnResult = true;
  352. }
  353. if ( !BLanOnly() )
  354. {
  355. MsgAndLog( "Connection to Steam servers successful.\n" );
  356. if ( SteamGameServer() )
  357. {
  358. uint32 ip = SteamGameServer()->GetPublicIP();
  359. MsgAndLog( " Public IP is %d.%d.%d.%d.\n", (ip >> 24) & 255, (ip >> 16) & 255, (ip >> 8) & 255, ip & 255 );
  360. }
  361. }
  362. if ( SteamGameServer() )
  363. {
  364. m_SteamIDGS = SteamGameServer()->GetSteamID();
  365. if ( m_SteamIDGS.BAnonGameServerAccount() )
  366. {
  367. MsgAndLog( "Assigned anonymous gameserver Steam ID %s.\n", m_SteamIDGS.Render() );
  368. }
  369. else if ( m_SteamIDGS.BPersistentGameServerAccount() )
  370. {
  371. MsgAndLog( "Assigned persistent gameserver Steam ID %s.\n", m_SteamIDGS.Render() );
  372. }
  373. else
  374. {
  375. WarningAndLog( "Assigned Steam ID %s, which is of an unexpected type!\n", m_SteamIDGS.Render() );
  376. Assert( !"Unexpected steam ID type!" );
  377. }
  378. }
  379. else
  380. {
  381. m_SteamIDGS = k_steamIDNotInitYetGS;
  382. }
  383. // send updated server details
  384. // OnLogonSuccess() gets called each time we logon, so if we get dropped this gets called
  385. // again and we get need to retell the AM our details
  386. SendUpdatedServerDetails();
  387. }
  388. //-----------------------------------------------------------------------------
  389. // Purpose: callback on unable to connect to the steam3 backend
  390. // Input : eResult -
  391. //-----------------------------------------------------------------------------
  392. void CSteam3Server::OnLogonFailure( SteamServerConnectFailure_t *pLogonFailure )
  393. {
  394. if ( !BIsActive() )
  395. return;
  396. //bool bRetrying = false;
  397. if ( !m_bLogOnResult )
  398. {
  399. if ( pLogonFailure->m_eResult == k_EResultServiceUnavailable )
  400. {
  401. if ( !BLanOnly() )
  402. {
  403. MsgAndLog( "Connection to Steam servers successful (SU).\n" );
  404. }
  405. }
  406. else
  407. {
  408. // we tried to be in secure mode but failed
  409. // force into insecure mode
  410. // eventually change this to set sv_lan as well
  411. if ( !BLanOnly() )
  412. {
  413. WarningAndLog( "Could not establish connection to Steam servers. (Result = %d)\n", pLogonFailure->m_eResult );
  414. // If this was a permanent failure, switch to anonymous
  415. // TODO: Requires SDK update
  416. /*if ( m_bWantsPersistentAccountLogon && ( pLogonFailure->m_eResult == k_EResultInvalidParam || pLogonFailure->m_eResult == k_EResultAccountNotFound ) )
  417. {
  418. WarningAndLog( "Invalid game server account token. Retrying Steam connection with anonymous logon\n" );
  419. m_bWantsPersistentAccountLogon = false;
  420. bRetrying = true;
  421. SteamGameServer()->LogOnAnonymous();
  422. }*/
  423. }
  424. }
  425. }
  426. m_bLogOnResult = true;
  427. //m_bLogOnResult = !bRetrying;
  428. }
  429. //-----------------------------------------------------------------------------
  430. // Purpose:
  431. // Input : eResult -
  432. //-----------------------------------------------------------------------------
  433. void CSteam3Server::OnLoggedOff( SteamServersDisconnected_t *pLoggedOff )
  434. {
  435. if ( !BLanOnly() )
  436. {
  437. WarningAndLog( "Connection to Steam servers lost. (Result = %d)\n", pLoggedOff->m_eResult );
  438. }
  439. }
  440. void CSteam3Server::OnComputeNewPlayerCompatibilityResponse( ComputeNewPlayerCompatibilityResult_t *pCompatibilityResult )
  441. {
  442. CBaseClient *client = ClientFindFromSteamID( pCompatibilityResult->m_SteamIDCandidate );
  443. if ( !client )
  444. return;
  445. if ( sv_steamblockingcheck.GetInt() )
  446. {
  447. if ( sv_steamblockingcheck.GetInt() >= 2 )
  448. {
  449. if ( pCompatibilityResult->m_cClanPlayersThatDontLikeCandidate > 0 )
  450. {
  451. client->Disconnect( "Another player on this server ( member of owning clan ) does not want to play with this player." );
  452. return;
  453. }
  454. }
  455. if ( sv_steamblockingcheck.GetInt() >= 3 )
  456. {
  457. if ( pCompatibilityResult->m_cPlayersThatDontLikeCandidate > 0 )
  458. {
  459. client->Disconnect( "Another player on this server does not want to play with this player." );
  460. return;
  461. }
  462. }
  463. if ( sv_steamblockingcheck.GetInt() >= 4 )
  464. {
  465. if ( pCompatibilityResult->m_cPlayersThatCandidateDoesntLike > 0 )
  466. {
  467. client->Disconnect( "Existing player on this server is on this players block list." );
  468. return;
  469. }
  470. }
  471. if ( pCompatibilityResult->m_cClanPlayersThatDontLikeCandidate > 0 ||
  472. pCompatibilityResult->m_cPlayersThatDontLikeCandidate > 0 ||
  473. pCompatibilityResult->m_cPlayersThatCandidateDoesntLike > 0 )
  474. {
  475. MsgAndLog( "Player %s is blocked by %d players and %d clan members and has blocked %d players on server\n", client->GetClientName(),
  476. pCompatibilityResult->m_cPlayersThatDontLikeCandidate,
  477. pCompatibilityResult->m_cClanPlayersThatDontLikeCandidate,
  478. pCompatibilityResult->m_cPlayersThatCandidateDoesntLike );
  479. }
  480. }
  481. }
  482. //-----------------------------------------------------------------------------
  483. // Purpose:
  484. //-----------------------------------------------------------------------------
  485. void CSteam3Server::OnValidateAuthTicketResponse( ValidateAuthTicketResponse_t *pValidateAuthTicketResponse )
  486. {
  487. //Msg("Steam backend:Got approval for %x\n", pGSClientApprove->m_SteamID.ConvertToUint64() );
  488. // We got the approval message from the back end.
  489. // Note that if we dont get it, we default to approved anyway
  490. // dont need to send anything back
  491. if ( !BIsActive() )
  492. return;
  493. CBaseClient *client = ClientFindFromSteamID( pValidateAuthTicketResponse->m_SteamID );
  494. if ( !client )
  495. return;
  496. if ( pValidateAuthTicketResponse->m_eAuthSessionResponse != k_EAuthSessionResponseOK )
  497. {
  498. OnValidateAuthTicketResponseHelper( client, pValidateAuthTicketResponse->m_eAuthSessionResponse );
  499. return;
  500. }
  501. if ( Filter_IsUserBanned( client->GetNetworkID() ) )
  502. {
  503. sv.RejectConnection( client->GetNetChannel()->GetRemoteAddress(), client->GetClientChallenge(), "#GameUI_ServerRejectBanned" );
  504. client->Disconnect( va( "STEAM UserID %s is banned", client->GetNetworkIDString() ) );
  505. }
  506. else if ( CheckForDuplicateSteamID( client ) )
  507. {
  508. client->Disconnect( "STEAM UserID %s is already\nin use on this server", client->GetNetworkIDString() );
  509. }
  510. else
  511. {
  512. char msg[ 512 ];
  513. sprintf( msg, "\"%s<%i><%s><>\" STEAM USERID validated\n", client->GetClientName(), client->GetUserID(), client->GetNetworkIDString() );
  514. DevMsg( "%s", msg );
  515. g_Log.Printf( "%s", msg );
  516. g_pServerPluginHandler->NetworkIDValidated( client->GetClientName(), client->GetNetworkIDString() );
  517. // Tell IServerGameClients if its version is high enough.
  518. if ( g_iServerGameClientsVersion >= 4 )
  519. {
  520. serverGameClients->NetworkIDValidated( client->GetClientName(), client->GetNetworkIDString() );
  521. }
  522. }
  523. if ( sv_steamblockingcheck.GetInt() >= 1 )
  524. {
  525. SteamGameServer()->ComputeNewPlayerCompatibility( pValidateAuthTicketResponse->m_SteamID );
  526. }
  527. client->SetFullyAuthenticated();
  528. }
  529. //-----------------------------------------------------------------------------
  530. // Purpose: helper for the two places that deny a user connect
  531. // Input : steamID - id to kick
  532. // eDenyReason - reason
  533. // pchOptionalText - some kicks also have a string with them
  534. //-----------------------------------------------------------------------------
  535. void CSteam3Server::OnValidateAuthTicketResponseHelper( CBaseClient *cl, EAuthSessionResponse eAuthSessionResponse )
  536. {
  537. INetChannel *netchan = cl->GetNetChannel();
  538. // If the client is timing out, the Steam failure is probably related (e.g. game crashed). Let's just print that the client timed out.
  539. if ( netchan && netchan->IsTimingOut() )
  540. {
  541. cl->Disconnect( CLIENTNAME_TIMED_OUT, cl->GetClientName() );
  542. return;
  543. }
  544. // Emit a more detailed diagnostic.
  545. WarningAndLog( "STEAMAUTH: Client %s received failure code %d\n", cl->GetClientName(), (int)eAuthSessionResponse );
  546. switch ( eAuthSessionResponse )
  547. {
  548. case k_EAuthSessionResponseUserNotConnectedToSteam:
  549. if ( !BLanOnly() )
  550. cl->Disconnect( INVALID_STEAM_LOGON_NOT_CONNECTED );
  551. break;
  552. case k_EAuthSessionResponseLoggedInElseWhere:
  553. if ( !BLanOnly() )
  554. cl->Disconnect( INVALID_STEAM_LOGGED_IN_ELSEWHERE );
  555. break;
  556. case k_EAuthSessionResponseNoLicenseOrExpired:
  557. cl->Disconnect( "This Steam account does not own this game. \nPlease login to the correct Steam account" );
  558. break;
  559. case k_EAuthSessionResponseVACBanned:
  560. if ( !BLanOnly() )
  561. cl->Disconnect( INVALID_STEAM_VACBANSTATE );
  562. break;
  563. case k_EAuthSessionResponseAuthTicketCanceled:
  564. if ( !BLanOnly() )
  565. cl->Disconnect( INVALID_STEAM_LOGON_TICKET_CANCELED );
  566. break;
  567. case k_EAuthSessionResponseAuthTicketInvalidAlreadyUsed:
  568. case k_EAuthSessionResponseAuthTicketInvalid:
  569. if ( !BLanOnly() )
  570. cl->Disconnect( INVALID_STEAM_TICKET );
  571. break;
  572. case k_EAuthSessionResponseVACCheckTimedOut:
  573. cl->Disconnect( "An issue with your computer is blocking the VAC system. You cannot play on secure servers.\n\nhttps://support.steampowered.com/kb_article.php?ref=2117-ILZV-2837" );
  574. break;
  575. default:
  576. cl->Disconnect( "Client dropped by server" );
  577. break;
  578. }
  579. }
  580. #endif
  581. //-----------------------------------------------------------------------------
  582. // Purpose:
  583. // Input : steamIDFind -
  584. // Output : IClient
  585. //-----------------------------------------------------------------------------
  586. CBaseClient *CSteam3Server::ClientFindFromSteamID( CSteamID & steamIDFind )
  587. {
  588. for ( int i=0 ; i< sv.GetClientCount() ; i++ )
  589. {
  590. CBaseClient *cl = (CBaseClient *)sv.GetClient( i );
  591. // Not connected, no SteamID yet
  592. if ( !cl->IsConnected() || cl->IsFakeClient() )
  593. continue;
  594. if ( cl->GetNetworkID().idtype != IDTYPE_STEAM )
  595. continue;
  596. USERID_t id = cl->GetNetworkID();
  597. if (id.steamid == steamIDFind )
  598. {
  599. return cl;
  600. }
  601. }
  602. return NULL;
  603. }
  604. //-----------------------------------------------------------------------------
  605. // Purpose: tell Steam that a new user connected
  606. //-----------------------------------------------------------------------------
  607. bool CSteam3Server::NotifyClientConnect( CBaseClient *client, uint32 unUserID, netadr_t & adr, const void *pvCookie, uint32 ucbCookie )
  608. {
  609. if ( !BIsActive() )
  610. return true;
  611. if ( !client || client->IsFakeClient() )
  612. return false;
  613. // Make sure their ticket is long enough
  614. if ( ucbCookie <= sizeof(uint64) )
  615. {
  616. WarningAndLog("Client UserID %x connected with invalid ticket size %d\n", unUserID, ucbCookie );
  617. return false;
  618. }
  619. // steamID is prepended to the ticket
  620. CUtlBuffer buffer( pvCookie, ucbCookie, CUtlBuffer::READ_ONLY );
  621. uint64 ulSteamID = buffer.GetInt64();
  622. CSteamID steamID( ulSteamID );
  623. if ( steamID.GetEUniverse() != SteamGameServer()->GetSteamID().GetEUniverse() )
  624. {
  625. WarningAndLog("Client %d %s connected to universe %d, but game server %s is running in universe %d\n", unUserID, steamID.Render(),
  626. steamID.GetEUniverse(), SteamGameServer()->GetSteamID().Render(), SteamGameServer()->GetSteamID().GetEUniverse() );
  627. return false;
  628. }
  629. if ( !steamID.IsValid() || !steamID.BIndividualAccount() )
  630. {
  631. WarningAndLog("Client %d connected from %s with invalid Steam ID %s\n", unUserID, adr.ToString(), steamID.Render() );
  632. return false;
  633. }
  634. // skip the steamID
  635. pvCookie = (uint8 *)pvCookie + sizeof( uint64 );
  636. ucbCookie -= sizeof( uint64 );
  637. EBeginAuthSessionResult eResult = SteamGameServer()->BeginAuthSession( pvCookie, ucbCookie, steamID );
  638. switch ( eResult )
  639. {
  640. case k_EBeginAuthSessionResultOK:
  641. //Msg("S3: BeginAuthSession request for %x was good.\n", steamID.ConvertToUint64( ) );
  642. break;
  643. case k_EBeginAuthSessionResultInvalidTicket:
  644. WarningAndLog("S3: Client connected with invalid ticket: UserID: %x\n", unUserID );
  645. return false;
  646. case k_EBeginAuthSessionResultDuplicateRequest:
  647. WarningAndLog("S3: Duplicate client connection: UserID: %x SteamID %x\n", unUserID, steamID.ConvertToUint64( ) );
  648. return false;
  649. case k_EBeginAuthSessionResultInvalidVersion:
  650. WarningAndLog("S3: Client connected with invalid ticket ( old version ): UserID: %x\n", unUserID );
  651. return false;
  652. case k_EBeginAuthSessionResultGameMismatch:
  653. // This error would be very useful to present to the client.
  654. WarningAndLog("S3: Client connected with ticket for the wrong game: UserID: %x\n", unUserID );
  655. return false;
  656. case k_EBeginAuthSessionResultExpiredTicket:
  657. WarningAndLog("S3: Client connected with expired ticket: UserID: %x\n", unUserID );
  658. return false;
  659. default:
  660. WarningAndLog("S3: Client failed auth session for unknown reason. UserID: %x\n", unUserID );
  661. return false;
  662. }
  663. // first checks ok, we know now the SteamID
  664. client->SetSteamID( steamID );
  665. SendUpdatedServerDetails();
  666. return true;
  667. }
  668. bool CSteam3Server::NotifyLocalClientConnect( CBaseClient *client )
  669. {
  670. CSteamID steamID;
  671. if ( SteamGameServer() )
  672. {
  673. steamID = SteamGameServer()->CreateUnauthenticatedUserConnection();
  674. }
  675. client->SetSteamID( steamID );
  676. SendUpdatedServerDetails();
  677. return true;
  678. }
  679. //-----------------------------------------------------------------------------
  680. // Purpose:
  681. // Input : *client -
  682. //-----------------------------------------------------------------------------
  683. void CSteam3Server::NotifyClientDisconnect( CBaseClient *client )
  684. {
  685. if ( !client || !BIsActive() || !client->IsConnected() || !client->m_SteamID.IsValid() )
  686. return;
  687. // Check if the client has a local (anonymous) steam account. This is the
  688. // case for bots. Currently it's also the case for people who connect
  689. // directly to the SourceTV port.
  690. if ( client->m_SteamID.GetEAccountType() == k_EAccountTypeAnonGameServer )
  691. {
  692. SteamGameServer()->SendUserDisconnect( client->m_SteamID );
  693. // Clear the steam ID, as it was a dummy one that should not be used again
  694. client->m_SteamID = CSteamID();
  695. }
  696. else
  697. {
  698. // All bots should have an anonymous account ID
  699. Assert( !client->IsFakeClient() );
  700. USERID_t id = client->GetNetworkID();
  701. if ( id.idtype != IDTYPE_STEAM )
  702. return;
  703. // Msg("S3: Sending client disconnect for %x\n", steamIDClient.ConvertToUint64( ) );
  704. SteamGameServer()->EndAuthSession( client->m_SteamID );
  705. }
  706. }
  707. //-----------------------------------------------------------------------------
  708. // Purpose:
  709. //-----------------------------------------------------------------------------
  710. void CSteam3Server::NotifyOfLevelChange()
  711. {
  712. // we're changing levels, so we may not respond for a while
  713. if ( m_bHasActivePlayers )
  714. {
  715. m_bHasActivePlayers = false;
  716. SendUpdatedServerDetails();
  717. }
  718. }
  719. //-----------------------------------------------------------------------------
  720. // Purpose:
  721. //-----------------------------------------------------------------------------
  722. void CSteam3Server::NotifyOfServerNameChange()
  723. {
  724. SendUpdatedServerDetails();
  725. }
  726. //-----------------------------------------------------------------------------
  727. // Purpose:
  728. //-----------------------------------------------------------------------------
  729. void CSteam3Server::RunFrame()
  730. {
  731. bool bHasPlayers = ( sv.GetNumClients() > 0 );
  732. if ( m_bHasActivePlayers != bHasPlayers )
  733. {
  734. m_bHasActivePlayers = bHasPlayers;
  735. SendUpdatedServerDetails();
  736. }
  737. static double s_fLastRunCallback = 0.0f;
  738. double fCurtime = Plat_FloatTime();
  739. if ( fCurtime - s_fLastRunCallback > 0.1f )
  740. {
  741. s_fLastRunCallback = fCurtime;
  742. SteamGameServer_RunCallbacks();
  743. }
  744. }
  745. //-----------------------------------------------------------------------------
  746. // Purpose: lets the steam3 servers know our full details
  747. // Input : bChangingLevels - true if we're going to heartbeat slowly for a while
  748. //-----------------------------------------------------------------------------
  749. void CSteam3Server::SendUpdatedServerDetails()
  750. {
  751. if ( !BIsActive() || SteamGameServer() == NULL )
  752. return;
  753. // Fetch counts that include the dummy slots for SourceTV and reply.
  754. int nNumClients = sv.GetNumClients();
  755. int nMaxClients = sv.GetMaxClients();
  756. int nFakeClients = sv.GetNumFakeClients();
  757. // Now remove any dummy slots reserved for the Source TV or replay
  758. // listeners. The fact that these are "players" should be a Source-specific
  759. // implementation artifact, and this kludge --- I mean ELEGANT SOLUTION ---
  760. // should not be propagated to the Steam layer. Steam should be able to report
  761. // exactly what we give it to the master server, etc.
  762. for ( int i = 0 ; i < sv.GetClientCount() ; ++i )
  763. {
  764. CBaseClient *cl = (CBaseClient *)sv.GetClient( i );
  765. if ( !cl->IsConnected() )
  766. continue;
  767. bool bHideClient = false;
  768. if ( cl->IsReplay() || cl->IsHLTV() )
  769. {
  770. Assert( cl->IsFakeClient() );
  771. bHideClient = true;
  772. }
  773. if ( cl->IsFakeClient() && !cl->ShouldReportThisFakeClient() )
  774. {
  775. bHideClient = true;
  776. }
  777. if ( bHideClient )
  778. {
  779. --nNumClients;
  780. --nMaxClients;
  781. --nFakeClients;
  782. // And make sure we don't have any local player authentication
  783. // records within steam for this guy.
  784. if ( cl->m_SteamID.IsValid() )
  785. {
  786. Assert( cl->m_SteamID.BAnonGameServerAccount() );
  787. SteamGameServer()->SendUserDisconnect( cl->m_SteamID );
  788. cl->m_SteamID = CSteamID();
  789. }
  790. }
  791. }
  792. // Apply convar to force reported max player count LAST
  793. if ( sv_visiblemaxplayers.GetInt() > 0 && sv_visiblemaxplayers.GetInt() < nMaxClients )
  794. nMaxClients = sv_visiblemaxplayers.GetInt();
  795. SteamGameServer()->SetMaxPlayerCount( nMaxClients );
  796. SteamGameServer()->SetBotPlayerCount( nFakeClients );
  797. SteamGameServer()->SetPasswordProtected( sv.GetPassword() != NULL );
  798. SteamGameServer()->SetRegion( sv_region.GetString() );
  799. SteamGameServer()->SetServerName( sv.GetName() );
  800. if ( hltv && hltv->IsTVRelay() )
  801. {
  802. // If we're a relay we can't use the local server data for these
  803. SteamGameServer()->SetMapName( hltv->GetMapName() );
  804. SteamGameServer()->SetMaxPlayerCount( hltv->GetMaxClients() );
  805. SteamGameServer()->SetBotPlayerCount( 0 );
  806. }
  807. else
  808. {
  809. const char *pszMap = NULL;
  810. if ( g_iServerGameDLLVersion >= 9 )
  811. pszMap = serverGameDLL->GetServerBrowserMapOverride();
  812. if ( pszMap == NULL || *pszMap == '\0' )
  813. pszMap = sv.GetMapName();
  814. SteamGameServer()->SetMapName( pszMap );
  815. }
  816. if ( hltv && hltv->IsActive() )
  817. {
  818. // This is also the case when we're a relay, in which case we never set a game port, so we'll only have a spectator port
  819. SteamGameServer()->SetSpectatorPort( NET_GetUDPPort( NS_HLTV ) );
  820. SteamGameServer()->SetSpectatorServerName( hltv->GetName() );
  821. }
  822. else
  823. {
  824. SteamGameServer()->SetSpectatorPort( 0 );
  825. }
  826. UpdateGroupSteamID( false );
  827. // Form the game data to send
  828. CUtlString sGameData;
  829. // Start with whatever the game has
  830. if ( g_iServerGameDLLVersion >= 9 )
  831. sGameData = serverGameDLL->GetServerBrowserGameData();
  832. // Add the value of our steam blocking flag
  833. char rgchTag[32];
  834. V_sprintf_safe( rgchTag, "steamblocking:%d", sv_steamblockingcheck.GetInt() );
  835. if ( !sGameData.IsEmpty() )
  836. {
  837. sGameData.Append( "," );
  838. }
  839. sGameData.Append( rgchTag );
  840. SteamGameServer()->SetGameData( sGameData );
  841. // Msg( "CSteam3Server::SendUpdatedServerDetails: nNumClients=%d, nMaxClients=%d, nFakeClients=%d:\n", nNumClients, nMaxClients, nFakeClients );
  842. // for ( int i = 0 ; i < sv.GetClientCount() ; ++i )
  843. // {
  844. // IClient *c = sv.GetClient( i );
  845. // Msg(" %d: %s, connected=%d, replay=%d, fake=%d\n", i, c->GetClientName(), c->IsConnected() ? 1 : 0, c->IsReplay() ? 1 : 0, c->IsFakeClient() ? 1 : 0 );
  846. // }
  847. }
  848. bool CSteam3Server::IsMasterServerUpdaterSharingGameSocket()
  849. {
  850. return m_bMasterServerUpdaterSharingGameSocket;
  851. }
  852. //-----------------------------------------------------------------------------
  853. // Purpose:
  854. //-----------------------------------------------------------------------------
  855. void Heartbeat_f()
  856. {
  857. if( Steam3Server().SteamGameServer() )
  858. {
  859. Steam3Server().SteamGameServer()->ForceHeartbeat();
  860. }
  861. }
  862. static ConCommand heartbeat( "heartbeat", Heartbeat_f, "Force heartbeat of master servers", 0 );
  863. //-----------------------------------------------------------------------------
  864. // Purpose: Select Steam gameserver account to login to
  865. //-----------------------------------------------------------------------------
  866. void sv_setsteamaccount_f( const CCommand &args )
  867. {
  868. if ( Steam3Server().SteamGameServer() && Steam3Server().SteamGameServer()->BLoggedOn() )
  869. {
  870. Warning( "Warning: Game server already logged into steam. You need to use the sv_setsteamaccount command earlier.\n");
  871. return;
  872. }
  873. if ( sv_lan.GetBool() )
  874. {
  875. Warning( "Warning: sv_setsteamaccount is not applicable in LAN mode.\n");
  876. }
  877. if ( args.ArgC() != 2 )
  878. {
  879. Warning( "Usage: sv_setsteamaccount <login_token>\n");
  880. return;
  881. }
  882. Steam3Server().SetAccount( args[1] );
  883. }
  884. static ConCommand sv_setsteamaccount( "sv_setsteamaccount", sv_setsteamaccount_f, "token\nSet game server account token to use for logging in to a persistent game server account", 0 );
  885. static void sv_setsteamgroup_f( IConVar *pConVar, const char *pOldString, float flOldValue );
  886. ConVar sv_steamgroup( "sv_steamgroup", "", FCVAR_NOTIFY, "The ID of the steam group that this server belongs to. You can find your group's ID on the admin profile page in the steam community.", sv_setsteamgroup_f );
  887. void CSteam3Server::UpdateGroupSteamID( bool bForce )
  888. {
  889. if ( sv_steamgroup.GetInt() == 0 && !bForce )
  890. return;
  891. uint unAccountID = Q_atoi( sv_steamgroup.GetString() );
  892. m_SteamIDGroupForBlocking.Set( unAccountID, m_SteamIDGS.GetEUniverse(), k_EAccountTypeClan );
  893. if ( SteamGameServer() )
  894. SteamGameServer()->AssociateWithClan( m_SteamIDGroupForBlocking );
  895. }
  896. static void sv_setsteamgroup_f( IConVar *pConVar, const char *pOldString, float flOldValue )
  897. {
  898. if ( sv_lan.GetBool() )
  899. {
  900. Warning( "Warning: sv_steamgroup is not applicable in LAN mode.\n");
  901. }
  902. Steam3Server().UpdateGroupSteamID( true );
  903. }
  904. static void sv_setsteamblockingcheck_f( IConVar *pConVar, const char *pOldString, float flOldValue )
  905. {
  906. if ( sv_lan.GetBool() )
  907. {
  908. Warning( "Warning: sv_steamblockingcheck is not applicable in LAN mode.\n");
  909. }
  910. }