diff --git a/src/common/callback.cpp b/src/common/callback.cpp index bf5ef3e6..a5fd14c1 100644 --- a/src/common/callback.cpp +++ b/src/common/callback.cpp @@ -15,6 +15,7 @@ #ifndef DEDICATED #include "engine/client/cl_rcon.h" #include "engine/client/cdll_engine_int.h" +#include "engine/client/clientstate.h" #endif // !DEDICATED #include "engine/client/client.h" #include "engine/net.h" @@ -808,7 +809,8 @@ SIG_GetAdr_f */ void SIG_GetAdr_f(const CCommand& args) { - DetourAddress(); + if (!IsCert() && !IsRetail()) + DetourAddress(); } /* @@ -1458,13 +1460,16 @@ CC_CreateFakePlayer_f #ifndef CLIENT_DLL void CC_CreateFakePlayer_f(const CCommand& args) { + if (!g_pServer->IsActive()) + return; + if (args.ArgC() < 3) { Msg(eDLL_T::SERVER, "usage 'sv_addbot': name(string) teamid(int)\n"); return; } - int numPlayers = g_pServer->GetNumClients(); + const int numPlayers = g_pServer->GetNumClients(); // Already at max, don't create. if (numPlayers >= g_ServerGlobalVariables->m_nMaxClients) @@ -1473,7 +1478,7 @@ void CC_CreateFakePlayer_f(const CCommand& args) const char* playerName = args.Arg(1); int teamNum = atoi(args.Arg(2)); - int maxTeams = int(g_pServer->GetMaxTeams()) + 1; + const int maxTeams = int(g_pServer->GetMaxTeams()) + 1; // Clamp team count, going above the limit will // cause a crash. Going below 0 means that the @@ -1483,9 +1488,44 @@ void CC_CreateFakePlayer_f(const CCommand& args) g_pEngineServer->LockNetworkStringTables(true); - edict_t nHandle = g_pEngineServer->CreateFakeClient(playerName, teamNum); + const edict_t nHandle = g_pEngineServer->CreateFakeClient(playerName, teamNum); g_pServerGameClients->ClientFullyConnect(nHandle, false); g_pEngineServer->LockNetworkStringTables(false); } -#endif // !CLIENT_DLL \ No newline at end of file +#endif // !CLIENT_DLL + +/* +===================== +Cmd_Exec_f + + executes a cfg file +===================== +*/ +void Cmd_Exec_f(const CCommand& args) +{ +#ifndef DEDICATED + // Prevent users from running neo strafe commands and other quick hacks. + // TODO: when reBar becomes a thing, we should verify this function and + // flag users that patch them out. + if (!ThreadInServerFrameThread() && (!sv_cheats->GetBool() && g_pClientState->IsActive())) + { + DevWarning(eDLL_T::ENGINE, "Client is simulating and %s = false; dropped exec command: %s\n", + sv_cheats->GetName(), args.ArgS()); + + return; + } +#endif // !DEDICATED + _Cmd_Exec_f(args); +} + + +void VCallback::Attach() const +{ + DetourAttach(&_Cmd_Exec_f, &Cmd_Exec_f); +} + +void VCallback::Detach() const +{ + DetourDetach(&_Cmd_Exec_f, &Cmd_Exec_f); +} diff --git a/src/common/callback.h b/src/common/callback.h index 7bbe6574..1b275b5a 100644 --- a/src/common/callback.h +++ b/src/common/callback.h @@ -7,6 +7,9 @@ inline bool(*SetupGamemode)(const char* pszPlayList); inline CMemory p_DownloadPlaylists_f; inline void(*_DownloadPlaylists_f)(void); +inline CMemory p_Cmd_Exec_f; +inline void(*_Cmd_Exec_f)(const CCommand& args); + /////////////////////////////////////////////////////////////////////////////// void MP_GameMode_Changed_f(IConVar* pConVar, const char* pOldString, float flOldValue); void MP_HostName_Changed_f(IConVar* pConVar, const char* pOldString, float flOldValue); @@ -87,18 +90,21 @@ class VCallback : public IDetour { LogFunAdr("SetupGamemode", p_SetupGamemode.GetPtr()); LogFunAdr("DownloadPlaylist_f", p_DownloadPlaylists_f.GetPtr()); + LogFunAdr("Cmd_Exec_f", p_Cmd_Exec_f.GetPtr()); } virtual void GetFun(void) const { p_SetupGamemode = g_GameDll.FindPatternSIMD("40 53 48 83 EC 20 48 8B D9 48 C7 C0 ?? ?? ?? ??"); p_DownloadPlaylists_f = g_GameDll.FindPatternSIMD("33 C9 C6 05 ?? ?? ?? ?? ?? E9 ?? ?? ?? ??"); + p_Cmd_Exec_f = g_GameDll.FindPatternSIMD("40 55 53 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B D9"); - SetupGamemode = p_SetupGamemode.RCast(); /*40 53 48 83 EC 20 48 8B D9 48 C7 C0 ?? ?? ?? ??*/ - _DownloadPlaylists_f = p_DownloadPlaylists_f.RCast(); /*33 C9 C6 05 ?? ?? ?? ?? ?? E9 ?? ?? ?? ??*/ + SetupGamemode = p_SetupGamemode.RCast(); + _DownloadPlaylists_f = p_DownloadPlaylists_f.RCast(); + _Cmd_Exec_f = p_Cmd_Exec_f.RCast(); } virtual void GetVar(void) const { } virtual void GetCon(void) const { } - virtual void Attach(void) const { } - virtual void Detach(void) const { } + virtual void Attach(void) const; + virtual void Detach(void) const; }; /////////////////////////////////////////////////////////////////////////////// diff --git a/src/common/global.cpp b/src/common/global.cpp index cd72b74f..1c18136a 100644 --- a/src/common/global.cpp +++ b/src/common/global.cpp @@ -195,6 +195,10 @@ ConVar* cl_threaded_bone_setup = nullptr; ConVar* cl_language = nullptr; +ConVar* cl_onlineAuthToken = nullptr; +ConVar* cl_onlineAuthTokenSignature1 = nullptr; +ConVar* cl_onlineAuthTokenSignature2 = nullptr; + ConVar* con_drawnotify = nullptr; ConVar* con_notifylines = nullptr; ConVar* con_notifytime = nullptr; @@ -403,6 +407,10 @@ void ConVar_StaticInit(void) cl_materialinfo_offset_x = ConVar::StaticCreate("cl_materialinfo_offset_x", "0" , FCVAR_DEVELOPMENTONLY, "X offset for material debug info overlay.", false, 0.f, false, 0.f, nullptr, nullptr); cl_materialinfo_offset_y = ConVar::StaticCreate("cl_materialinfo_offset_y", "420", FCVAR_DEVELOPMENTONLY, "Y offset for material debug info overlay.", false, 0.f, false, 0.f, nullptr, nullptr); + cl_onlineAuthToken = ConVar::StaticCreate("cl_onlineAuthToken", "", FCVAR_HIDDEN | FCVAR_USERINFO | FCVAR_DONTRECORD | FCVAR_SERVER_CANNOT_QUERY | FCVAR_PLATFORM_SYSTEM, "", false, 0.f, false, 0.f, nullptr, nullptr); + cl_onlineAuthTokenSignature1 = ConVar::StaticCreate("cl_onlineAuthTokenSignature1", "", FCVAR_HIDDEN | FCVAR_USERINFO | FCVAR_DONTRECORD | FCVAR_SERVER_CANNOT_QUERY | FCVAR_PLATFORM_SYSTEM, "", false, 0.f, false, 0.f, nullptr, nullptr); + cl_onlineAuthTokenSignature2 = ConVar::StaticCreate("cl_onlineAuthTokenSignature2", "", FCVAR_HIDDEN | FCVAR_USERINFO | FCVAR_DONTRECORD | FCVAR_SERVER_CANNOT_QUERY | FCVAR_PLATFORM_SYSTEM, "", false, 0.f, false, 0.f, nullptr, nullptr); + con_drawnotify = ConVar::StaticCreate("con_drawnotify", "0", FCVAR_RELEASE, "Draws the RUI console to the hud.", false, 0.f, false, 0.f, nullptr, nullptr); con_notifylines = ConVar::StaticCreate("con_notifylines" , "3" , FCVAR_MATERIAL_SYSTEM_THREAD, "Number of console lines to overlay for debugging.", true, 1.f, false, 0.f, nullptr, nullptr); con_notifytime = ConVar::StaticCreate("con_notifytime" , "6" , FCVAR_MATERIAL_SYSTEM_THREAD, "How long to display recent console text to the upper part of the game window.", false, 1.f, false, 50.f, nullptr, nullptr); @@ -473,7 +481,7 @@ void ConVar_StaticInit(void) net_processTimeBudget = ConVar::StaticCreate("net_processTimeBudget" ,"200" , FCVAR_RELEASE , "Net message process time budget in milliseconds (removing netchannel if exceeded).", true, 0.f, false, 0.f, nullptr, "0 = disabled"); //------------------------------------------------------------------------- // NETWORKSYSTEM | - pylon_matchmaking_hostname = ConVar::StaticCreate("pylon_matchmaking_hostname", "ms.r5reloaded.com", FCVAR_RELEASE, "Holds the pylon matchmaking hostname.", false, 0.f, false, 0.f, &MP_HostName_Changed_f, nullptr); + pylon_matchmaking_hostname = ConVar::StaticCreate("pylon_matchmaking_hostname", "ms-dev.r5reloaded.com", FCVAR_RELEASE, "Holds the pylon matchmaking hostname.", false, 0.f, false, 0.f, &MP_HostName_Changed_f, nullptr); pylon_host_update_interval = ConVar::StaticCreate("pylon_host_update_interval", "5" , FCVAR_RELEASE, "Length of time in seconds between each status update interval to master server.", true, 5.f, false, 0.f, nullptr, nullptr); pylon_showdebuginfo = ConVar::StaticCreate("pylon_showdebuginfo" , "0" , FCVAR_RELEASE, "Shows debug output for pylon.", false, 0.f, false, 0.f, nullptr, nullptr); //------------------------------------------------------------------------- @@ -737,7 +745,8 @@ void ConCommand_StaticInit(void) ConCommand::StaticCreate("net_generatekey", "Generates and sets a random base64 net key.", nullptr, FCVAR_RELEASE, NET_GenerateKey_f, nullptr); //------------------------------------------------------------------------- // TIER0 | - ConCommand::StaticCreate("sig_getadr", "Logs the sigscan results to the console.", nullptr, FCVAR_DEVELOPMENTONLY | FCVAR_HIDDEN, SIG_GetAdr_f, nullptr); + if (!IsCert() && !IsRetail()) + ConCommand::StaticCreate("sig_getadr", "Logs the sigscan results to the console.", nullptr, FCVAR_DEVELOPMENTONLY | FCVAR_HIDDEN, SIG_GetAdr_f, nullptr); } //----------------------------------------------------------------------------- diff --git a/src/common/global.h b/src/common/global.h index a4f19f06..0463d290 100644 --- a/src/common/global.h +++ b/src/common/global.h @@ -185,6 +185,11 @@ extern ConVar* cl_threaded_bone_setup; extern ConVar* cl_language; +extern ConVar* cl_onlineAuthToken; +extern ConVar* cl_onlineAuthTokenSignature1; +extern ConVar* cl_onlineAuthTokenSignature2; + + extern ConVar* con_drawnotify; extern ConVar* con_notifylines; extern ConVar* con_notifytime; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index aa88ae2d..72124c81 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -103,6 +103,15 @@ target_link_libraries( ${PROJECT_NAME} PRIVATE ) endif() +if( NOT ${PROJECT_NAME} STREQUAL "client" ) +target_link_libraries( ${PROJECT_NAME} PRIVATE + "libmbedcrypto" + "libmbedtls" + "libmbedx509" + "libjwt" +) +endif() + # Determine the compiler definitions and link libraries per project. if( ${PROJECT_NAME} STREQUAL "gamesdk" ) end_sources() diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index c2a228b3..e52c7b2d 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -11,8 +11,12 @@ #include "core/stdafx.h" #include "tier1/cvar.h" #include "tier1/strtools.h" +#include "mathlib/sha256.h" #include "engine/server/server.h" #include "engine/client/client.h" +#ifndef CLIENT_DLL +#include "jwt/include/decode.h" +#endif // Absolute max string cmd length, any character past this will be NULLED. #define STRINGCMD_MAX_LEN 512 @@ -37,22 +41,156 @@ void CClient::VClear(CClient* pClient) pClient->Clear(); } +static const char JWT_PUBLIC_KEY[] = +"-----BEGIN PUBLIC KEY-----\n" +"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2/335exIZ6LE8pYi6e50\n" +"7tH19tXaeeEJVF5XXpTCXpndXIIWVimvg6xQ381eajySDw93wvG1DzW3U/6LHzyt\n" +"Q++N8w7N+FwnXyoDUD5Y8hheTZv6jjLoYT8ZtsMl20k9UosrbFBTMUhgmIT2dVth\n" +"LH+rT9ohpUNwQXHJvTOs9eY74GyfFw93+32LANBPZ8b+S8S3oZnKFVeCxRkYKsV0\n" +"b34POHVBbXNw6Kt163gR5zaiCfJJtRto9AA7MV2t9pfy8CChs3uJ+Xn7QVHD5cqt\n" +"Msg9MBac2Pvs2j+8wJ/igAVL5L81z3FXVt04id59TfPMUbYhRfY8pk7FB0MCigOH\n" +"dwIDAQAB\n" +"-----END PUBLIC KEY-----\n"; + + +bool CClient::Authenticate(const char* const playerName, char* const reasonBuf, const size_t reasonBufLen) +{ +#ifndef CLIENT_DLL + // don't bother checking origin auth on bots or local clients + if (IsFakeClient() || GetNetChan()->GetRemoteAddress().IsLoopback()) + return true; + +#define FORMAT_ERROR_REASON(fmt, ...) V_snprintf(reasonBuf, reasonBufLen, fmt, ##__VA_ARGS__); + + KeyValues* cl_onlineAuthTokenKv = this->m_ConVars->FindKey("cl_onlineAuthToken"); + KeyValues* cl_onlineAuthTokenSignature1Kv = this->m_ConVars->FindKey("cl_onlineAuthTokenSignature1"); + KeyValues* cl_onlineAuthTokenSignature2Kv = this->m_ConVars->FindKey("cl_onlineAuthTokenSignature2"); + + if (!cl_onlineAuthTokenKv || !cl_onlineAuthTokenSignature1Kv) + { + FORMAT_ERROR_REASON("Missing token"); + return false; + } + + const char* onlineAuthToken = cl_onlineAuthTokenKv->GetString(); + const char* onlineAuthTokenSignature1 = cl_onlineAuthTokenSignature1Kv->GetString(); + const char* onlineAuthTokenSignature2 = cl_onlineAuthTokenSignature2Kv->GetString(); + + const std::string fullToken = Format("%s.%s%s", onlineAuthToken, onlineAuthTokenSignature1, onlineAuthTokenSignature2); + + struct l8w8jwt_decoding_params params; + l8w8jwt_decoding_params_init(¶ms); + + params.alg = L8W8JWT_ALG_RS256; + + params.jwt = (char*)fullToken.c_str(); + params.jwt_length = fullToken.length(); + + params.verification_key = (unsigned char*)JWT_PUBLIC_KEY; + params.verification_key_length = strlen(JWT_PUBLIC_KEY); + + params.validate_exp = 1; + params.exp_tolerance_seconds = 1; + + params.validate_iat = 1; + params.iat_tolerance_seconds = 30; + + l8w8jwt_claim* claims = nullptr; + size_t numClaims = 0; + + enum l8w8jwt_validation_result validation_result; + const int r = l8w8jwt_decode(¶ms, &validation_result, &claims, &numClaims); + + if (r != L8W8JWT_SUCCESS) + { + FORMAT_ERROR_REASON("Code %i", r); + return false; + } + + if (validation_result != L8W8JWT_VALID) + { + char reasonBuffer[256]; + l8w8jwt_get_validation_result_desc(validation_result, reasonBuffer, sizeof(reasonBuffer)); + + FORMAT_ERROR_REASON("%s", reasonBuffer); + return false; + } + + bool foundSessionId = false; + for (size_t i = 0; i < numClaims; ++i) + { + // session id + if (!strcmp(claims[i].key, "sessionId")) + { + const char* const sessionId = claims[i].value; + + const std::string newId = Format( + "%lld-%s-%s", + this->m_DataBlock.userData, + playerName, + g_pMasterServer->GetHostIP().c_str() + ); + + DevMsg(eDLL_T::SERVER, "%s: newId=%s\n", __FUNCTION__, newId.c_str()); + const std::string hashedNewId = sha256(newId); + + if (hashedNewId.compare(sessionId) != 0) + { + FORMAT_ERROR_REASON("Token is not authorized for the connecting client"); + return false; + } + + foundSessionId = true; + } + } + + if (!foundSessionId) + { + FORMAT_ERROR_REASON("No session ID"); + return false; + } + +#undef REJECT_CONNECTION +#endif // !CLIENT_DLL + + return true; +} + //--------------------------------------------------------------------------------- // Purpose: connect new client // Input : *szName - // *pNetChannel - // bFakePlayer - -// *a5 - +// *conVars - // *szMessage - // nMessageSize - // Output : true if connection was successful, false otherwise //--------------------------------------------------------------------------------- -bool CClient::Connect(const char* szName, void* pNetChannel, bool bFakePlayer, void* a5, char* szMessage, int nMessageSize) +bool CClient::Connect(const char* szName, void* pNetChannel, bool bFakePlayer, + CUtlVector* conVars, char* szMessage, int nMessageSize) { #ifndef CLIENT_DLL g_ServerPlayer[GetUserID()].Reset(); // Reset ServerPlayer slot. +#endif + + if (!v_CClient_Connect(this, szName, pNetChannel, bFakePlayer, conVars, szMessage, nMessageSize)) + return false; + +#ifndef CLIENT_DLL + +#define REJECT_CONNECTION(fmt, ...) V_snprintf(szMessage, nMessageSize, fmt, ##__VA_ARGS__); + + char authFailReason[512]; + if (!Authenticate(szName, authFailReason, sizeof(authFailReason))) + { + REJECT_CONNECTION("Failed to verify authentication token [%s]", authFailReason); + return false; + } + +#undef REJECT_CONNECTION #endif // !CLIENT_DLL - return v_CClient_Connect(this, szName, pNetChannel, bFakePlayer, a5, szMessage, nMessageSize); + + return true; } //--------------------------------------------------------------------------------- @@ -66,9 +204,10 @@ bool CClient::Connect(const char* szName, void* pNetChannel, bool bFakePlayer, v // nMessageSize - // Output : true if connection was successful, false otherwise //--------------------------------------------------------------------------------- -bool CClient::VConnect(CClient* pClient, const char* szName, void* pNetChannel, bool bFakePlayer, void* a5, char* szMessage, int nMessageSize) +bool CClient::VConnect(CClient* pClient, const char* szName, void* pNetChannel, bool bFakePlayer, + CUtlVector* conVars, char* szMessage, int nMessageSize) { - return pClient->Connect(szName, pNetChannel, bFakePlayer, a5, szMessage, nMessageSize);; + return pClient->Connect(szName, pNetChannel, bFakePlayer, conVars, szMessage, nMessageSize);; } //--------------------------------------------------------------------------------- @@ -258,8 +397,8 @@ bool CClient::VProcessStringCmd(CClient* pClient, NET_StringCmd* pMsg) bool CClient::VProcessSetConVar(CClient* pClient, NET_SetConVar* pMsg) { #ifndef CLIENT_DLL - CClient* pAdj = AdjustShiftedThisPointer(pClient); - ServerPlayer_t* pSlot = &g_ServerPlayer[pAdj->GetUserID()]; + CClient* const pAdj = AdjustShiftedThisPointer(pClient); + ServerPlayer_t* const pSlot = &g_ServerPlayer[pAdj->GetUserID()]; // This loop never exceeds 255 iterations, NET_SetConVar::ReadFromBuffer(...) // reads and inserts up to 255 entries in the vector (reads a byte for size). @@ -297,6 +436,7 @@ bool CClient::VProcessSetConVar(CClient* pClient, NET_SetConVar* pMsg) continue; } + // Add ConVar to list and set string. pAdj->m_ConVars->SetString(name, value); DevMsg(eDLL_T::SERVER, "UserInfo update from \"%s\": %s = %s\n", pAdj->GetClientName(), name, value); } diff --git a/src/engine/client/client.h b/src/engine/client/client.h index 4f70a017..4b00a65e 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -1,6 +1,7 @@ #pragma once #include "vpc/keyvalues.h" #include "common/protocol.h" +#include "engine/net.h" #include "engine/net_chan.h" #include "public/edict.h" #include "engine/server/datablock_sender.h" @@ -93,13 +94,18 @@ public: inline bool IsHumanPlayer(void) const { if (!IsConnected() || IsFakeClient()) { return false; } return true; } bool SendNetMsgEx(CNetMessage* pMsg, char bLocal, bool bForceReliable, bool bVoice); - bool Connect(const char* szName, void* pNetChannel, bool bFakePlayer, void* a5, char* szMessage, int nMessageSize); + + bool Authenticate(const char* const playerName, char* const reasonBuf, const size_t reasonBufLen); + bool Connect(const char* szName, void* pNetChannel, bool bFakePlayer, + CUtlVector* conVars, char* szMessage, int nMessageSize); void Disconnect(const Reputation_t nRepLvl, const char* szReason, ...); - static bool VConnect(CClient* pClient, const char* szName, void* pNetChannel, bool bFakePlayer, void* a5, char* szMessage, int nMessageSize); void Clear(void); public: // Hook statics: static void VClear(CClient* pClient); + static bool VConnect(CClient* pClient, const char* szName, void* pNetChannel, bool bFakePlayer, + CUtlVector* conVars, char* szMessage, int nMessageSize); + static void VActivatePlayer(CClient* pClient); static void* VSendSnapshot(CClient* pClient, CClientFrame* pFrame, int nTick, int nTickAck); static bool VSendNetMsgEx(CClient* pClient, CNetMessage* pMsg, char bLocal, bool bForceReliable, bool bVoice); @@ -204,7 +210,7 @@ static_assert(sizeof(CClient) == 0x4A4C0); /* ==== CBASECLIENT ===================================================================================================================================================== */ inline CMemory p_CClient_Connect; -inline bool(*v_CClient_Connect)(CClient* pClient, const char* szName, void* pNetChannel, bool bFakePlayer, void* a5, char* szMessage, int nMessageSize); +inline bool(*v_CClient_Connect)(CClient* pClient, const char* szName, void* pNetChannel, bool bFakePlayer, CUtlVector* conVars, char* szMessage, int nMessageSize); inline CMemory p_CClient_Disconnect; inline bool(*v_CClient_Disconnect)(CClient* pClient, const Reputation_t nRepLvl, const char* szReason, ...); @@ -269,7 +275,7 @@ class VClient : public IDetour p_CClient_ProcessSetConVar = g_GameDll.FindPatternSIMD("48 83 EC 28 48 83 C2 20"); p_CClient_SetSignonState = g_GameDll.FindPatternSIMD("48 8B C4 48 89 58 10 48 89 70 18 57 48 81 EC ?? ?? ?? ?? 0F 29 70 E8 8B F2"); - v_CClient_Connect = p_CClient_Connect.RCast(); + v_CClient_Connect = p_CClient_Connect.RCast*, char*, int)>(); v_CClient_Disconnect = p_CClient_Disconnect.RCast(); v_CClient_Clear = p_CClient_Clear.RCast(); v_CClient_ActivatePlayer = p_CClient_ActivatePlayer.RCast(); diff --git a/src/engine/client/clientstate.cpp b/src/engine/client/clientstate.cpp index 4845d9f2..d019e23a 100644 --- a/src/engine/client/clientstate.cpp +++ b/src/engine/client/clientstate.cpp @@ -16,6 +16,7 @@ #include "common/callback.h" #include "cdll_engine_int.h" #include "vgui/vgui_baseui_interface.h" +#include //------------------------------------------------------------------------------ @@ -163,16 +164,76 @@ bool CClientState::VProcessServerTick(CClientState* pClientState, SVC_ServerTick } } +bool CClientState::Authenticate(connectparams_t* connectParams) const +{ + string msToken; // token returned by the masterserver authorising the client to play online + string message; // message returned by the masterserver about the result of the auth + + // verify that the client is not lying about their account identity + // code is immediately discarded upon verification + + const bool ret = g_pMasterServer->AuthForConnection(*g_NucleusID, connectParams->netAdr, g_OriginAuthCode, msToken, message); + if (!ret) + { + Error(eDLL_T::ENGINE, NO_ERROR, "Failed to authenticate for online play: %s\n", message.c_str()); + return false; + } + + // get full token + const char* token = msToken.c_str(); + + // get a pointer to the delimiter that begins the token's signature + const char* tokenSignatureDelim = strrchr(token, '.'); + + if (!tokenSignatureDelim) + { + Error(eDLL_T::ENGINE, NO_ERROR, "Failed to authenticate for online play: %s\n", "Invalid token returned by MS"); + return false; + } + + // replace the delimiter with a null char so the first cvar only takes the header and payload data + *(char*)tokenSignatureDelim = '\0'; + const size_t sigLength = strlen(tokenSignatureDelim) - 1; + + cl_onlineAuthToken->SetValue(token); + + if (sigLength > 0) + { + // get a pointer to the first part of the token signature to store in cl_onlineAuthTokenSignature1 + const char* tokenSignaturePart1 = tokenSignatureDelim + 1; + + cl_onlineAuthTokenSignature1->SetValue(tokenSignaturePart1); + + if (sigLength > 255) + { + // get a pointer to the rest of the token signature to store in cl_onlineAuthTokenSignature2 + const char* tokenSignaturePart2 = tokenSignaturePart1 + 255; + + cl_onlineAuthTokenSignature2->SetValue(tokenSignaturePart2); + } + } + + return true; +} + +void CClientState::VConnect(CClientState* thisptr, connectparams_t* connectParams) +{ + thisptr->Authenticate(connectParams); + CClientState__Connect(thisptr, connectParams); +} + void VClientState::Attach() const { DetourAttach(&CClientState__ConnectionClosing, &CClientState::VConnectionClosing); DetourAttach(&CClientState__ProcessServerTick, &CClientState::VProcessServerTick); + DetourAttach(&CClientState__Connect, &CClientState::VConnect); } void VClientState::Detach() const { DetourDetach(&CClientState__ConnectionClosing, &CClientState::VConnectionClosing); DetourDetach(&CClientState__ProcessServerTick, &CClientState::VProcessServerTick); + DetourDetach(&CClientState__Connect, &CClientState::VConnect); } ///////////////////////////////////////////////////////////////////////////////// diff --git a/src/engine/client/clientstate.h b/src/engine/client/clientstate.h index c3b907b6..3bcd0a80 100644 --- a/src/engine/client/clientstate.h +++ b/src/engine/client/clientstate.h @@ -11,6 +11,16 @@ #include "engine/packed_entity.h" #include "datablock_receiver.h" +struct connectparams_t +{ + const char* netAdr; + const char* netKey; + int unkReconnect; + int unk; + bool challengeRequest; + bool asSpectator_MAYBE; +}; + class CClientSnapshotManager : public IClientSnapshotManager { public: @@ -27,6 +37,7 @@ class CClientState : CS_INetChannelHandler, IConnectionlessPacketHandler, IServe public: // Hook statics. static void VConnectionClosing(CClientState* thisptr, const char* szReason); static bool VProcessServerTick(CClientState* thisptr, SVC_ServerTick* msg); + static void VConnect(CClientState* thisptr, connectparams_t* connectParams); public: bool IsPaused() const; @@ -46,6 +57,8 @@ public: float GetFrameTime(void) const; + bool Authenticate(connectparams_t* connectParams) const; + int m_Socket; int _padding_maybe; CNetChan* m_NetChannel; @@ -199,6 +212,9 @@ extern CClientState** g_pClientState_Shifted; // Shifted by 0x10 forward! inline CMemory p_CClientState__RunFrame; inline void(*CClientState__RunFrame)(CClientState* thisptr); +inline CMemory p_CClientState__Connect; +inline void(*CClientState__Connect)(CClientState* thisptr, connectparams_t* connectParams); + inline CMemory p_CClientState__Disconnect; inline void(*CClientState__Disconnect)(CClientState* thisptr, bool bSendTrackingContext); @@ -214,6 +230,7 @@ class VClientState : public IDetour virtual void GetAdr(void) const { LogFunAdr("CClientState::RunFrame", p_CClientState__RunFrame.GetPtr()); + LogFunAdr("CClientState::Connect", p_CClientState__Connect.GetPtr()); LogFunAdr("CClientState::Disconnect", p_CClientState__Disconnect.GetPtr()); LogFunAdr("CClientState::ConnectionClosing", p_CClientState__ConnectionClosing.GetPtr()); LogFunAdr("CClientState::ProcessServerTick", p_CClientState__ProcessServerTick.GetPtr()); @@ -234,7 +251,11 @@ class VClientState : public IDetour #endif p_CClientState__ProcessServerTick = g_GameDll.FindPatternSIMD("40 57 48 83 EC 20 83 B9 ?? ?? ?? ?? ?? 48 8B F9 7C 66"); + p_CClientState__Connect = g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 81 EC ?? ?? ?? ?? 48 8B 32"); + + CClientState__RunFrame = p_CClientState__RunFrame.RCast(); + CClientState__Connect = p_CClientState__Connect.RCast(); CClientState__Disconnect = p_CClientState__Disconnect.RCast(); CClientState__ConnectionClosing = p_CClientState__ConnectionClosing.RCast(); CClientState__ProcessServerTick = p_CClientState__ProcessServerTick.RCast(); diff --git a/src/engine/host_state.cpp b/src/engine/host_state.cpp index 6c7c86a9..68fc614e 100644 --- a/src/engine/host_state.cpp +++ b/src/engine/host_state.cpp @@ -69,8 +69,9 @@ bool HostState_KeepAlive(const NetGameServer_t& netGameServer) string errorMsg; string hostToken; + string hostIp; - const bool result = g_pMasterServer->PostServerHost(errorMsg, hostToken, netGameServer); + const bool result = g_pMasterServer->PostServerHost(errorMsg, hostToken, hostIp, netGameServer); if (!result) { if (!errorMsg.empty() && g_pMasterServer->GetCurrentError().compare(errorMsg) != NULL) @@ -90,6 +91,9 @@ bool HostState_KeepAlive(const NetGameServer_t& netGameServer) } } + if (hostIp.length() != 0) + g_pMasterServer->SetHostIP(hostIp); + return result; } #endif // !CLIENT_DLL diff --git a/src/engine/server/datablock_sender.h b/src/engine/server/datablock_sender.h index c6bab5be..89005642 100644 --- a/src/engine/server/datablock_sender.h +++ b/src/engine/server/datablock_sender.h @@ -46,8 +46,8 @@ protected: struct ServerDataBlock { - char SnapshotBuffer[295312]; // this might be wrong !!! - void* pUnkBlockStruct; + char blockBuffer[295312]; // this might be wrong !!! + void* userData; char gapC0008[56]; ServerDataBlockSender sender; }; diff --git a/src/gameui/IBrowser.cpp b/src/gameui/IBrowser.cpp index 566cbc86..5544f549 100644 --- a/src/gameui/IBrowser.cpp +++ b/src/gameui/IBrowser.cpp @@ -778,13 +778,18 @@ void CBrowser::SendHostingPostRequest(const NetGameServer_t& gameServer) #ifndef CLIENT_DLL string svHostRequestMessage; string svHostToken; - bool result = g_pMasterServer->PostServerHost(svHostRequestMessage, svHostToken, gameServer); + string svHostIp; + + bool result = g_pMasterServer->PostServerHost(svHostRequestMessage, svHostToken, svHostIp, gameServer); std::lock_guard l(m_Mutex); m_svHostRequestMessage = svHostRequestMessage; m_svHostToken = svHostToken; + if(svHostIp.length() != 0) + g_pMasterServer->SetHostIP(svHostIp); + if (result) { m_HostRequestMessageColor = ImVec4(0.00f, 1.00f, 0.00f, 1.00f); diff --git a/src/networksystem/pylon.cpp b/src/networksystem/pylon.cpp index ea7eda32..f9bb0e18 100644 --- a/src/networksystem/pylon.cpp +++ b/src/networksystem/pylon.cpp @@ -169,7 +169,7 @@ bool CPylon::GetServerByToken(NetGameServer_t& outGameServer, // &netGameServer - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- -bool CPylon::PostServerHost(string& outMessage, string& outToken, const NetGameServer_t& netGameServer) const +bool CPylon::PostServerHost(string& outMessage, string& outToken, string& outHostIp, const NetGameServer_t& netGameServer) const { rapidjson::Document requestJson; requestJson.SetObject(); @@ -210,6 +210,13 @@ bool CPylon::PostServerHost(string& outMessage, string& outToken, const NetGameS outToken = responseJson["token"].GetString(); } + if (responseJson.HasMember("ip") && responseJson["ip"].IsString() && + responseJson.HasMember("port") && responseJson["port"].IsInt()) + { + outHostIp = Format("[%s]:%i", + responseJson["ip"].GetString(), responseJson["port"].GetInt()); + } + return true; } @@ -304,6 +311,35 @@ bool CPylon::CheckForBan(const string& ipAddress, const uint64_t nucleusId, cons return false; } +bool CPylon::AuthForConnection(const uint64_t nucleusId, const char* ipAddress, const char* authCode, string& outToken, string& outMessage) const +{ + rapidjson::Document requestJson; + requestJson.SetObject(); + + rapidjson::Document::AllocatorType& allocator = requestJson.GetAllocator(); + + requestJson.AddMember("id", nucleusId, allocator); + requestJson.AddMember("ip", rapidjson::Value(ipAddress, allocator), allocator); + requestJson.AddMember("code", rapidjson::Value(authCode, allocator), allocator); + + rapidjson::Document responseJson; + + CURLINFO status; + + if (!SendRequest("/client/authenticate", requestJson, responseJson, outMessage, status, "origin auth error")) + { + return false; + } + + if (responseJson.HasMember("token") && responseJson["token"].IsString()) + { + outToken = responseJson["token"].GetString(); + return true; + } + + return false; +} + //----------------------------------------------------------------------------- // Purpose: Sends request to Pylon Master Server. // Input : *endpoint - diff --git a/src/networksystem/pylon.h b/src/networksystem/pylon.h index cd4d4e55..c1d5f878 100644 --- a/src/networksystem/pylon.h +++ b/src/networksystem/pylon.h @@ -11,11 +11,13 @@ public: vector GetServerList(string& outMessage) const; bool GetServerByToken(NetGameServer_t& slOutServer, string& outMessage, const string& svToken) const; - bool PostServerHost(string& outMessage, string& svOutToken, const NetGameServer_t& netGameServer) const; + bool PostServerHost(string& outMessage, string& svOutToken, string& outHostIp, const NetGameServer_t& netGameServer) const; bool GetBannedList(const CBanSystem::BannedList_t& inBannedVec, CBanSystem::BannedList_t& outBannedVec) const; bool CheckForBan(const string& ipAddress, const uint64_t nucleusId, const string& personaName, string& outReason) const; + bool AuthForConnection(const uint64_t nucleusId, const char* ipAddress, const char* authCode, string& outToken, string& outMessage) const; + void ExtractError(const rapidjson::Document& resultBody, string& outMessage, CURLINFO status, const char* errorText = nullptr) const; void ExtractError(const string& response, string& outMessage, CURLINFO status, const char* messageText = nullptr) const; @@ -26,14 +28,19 @@ public: inline const string& GetCurrentToken() const { return m_Token; } inline const string& GetCurrentError() const { return m_ErrorMsg; } + inline const string& GetHostIP() const { return m_HostIP; }; + inline void SetCurrentToken(const string& token) { m_Token = token; } inline void SetCurrentError(const string& error) { m_ErrorMsg = error; } + inline void SetHostIP(const string& ip) { m_HostIP = ip; }; + inline void SetLanguage(const char* lang) { m_Language = lang; }; private: string m_Token; string m_ErrorMsg; + string m_HostIP; string m_Language; }; extern CPylon* g_pMasterServer; diff --git a/src/thirdparty/jwt/include/decode.h b/src/thirdparty/jwt/include/decode.h index a8a31cb4..dd9ac563 100644 --- a/src/thirdparty/jwt/include/decode.h +++ b/src/thirdparty/jwt/include/decode.h @@ -34,6 +34,7 @@ extern "C" { #include #include #include +#include #ifndef L8W8JWT_MAX_KEY_SIZE #define L8W8JWT_MAX_KEY_SIZE 8192 @@ -94,6 +95,31 @@ enum l8w8jwt_validation_result { L8W8JWT_TYP_FAILURE = (unsigned)1 << (unsigned)8 }; +static void l8w8jwt_get_validation_result_desc(enum l8w8jwt_validation_result res, char* out_buffer, size_t buffer_size) +{ +#define JWT_OUTPUT_MSG(msg) snprintf(out_buffer, buffer_size, "%s", msg) +#define JWT_FLAG_STATUS(flag, msg) if(res & flag) { JWT_OUTPUT_MSG(msg); return; } + + if (res == L8W8JWT_VALID) + { + JWT_OUTPUT_MSG("Success"); + return; + } + + JWT_FLAG_STATUS(L8W8JWT_ISS_FAILURE, "Issuer claim is invalid"); + JWT_FLAG_STATUS(L8W8JWT_SUB_FAILURE, "Subject claim is invalid"); + JWT_FLAG_STATUS(L8W8JWT_AUD_FAILURE, "Audience claim is invalid"); + JWT_FLAG_STATUS(L8W8JWT_JTI_FAILURE, "JWT ID claim is invalid"); + JWT_FLAG_STATUS(L8W8JWT_EXP_FAILURE, "Token has expired"); + JWT_FLAG_STATUS(L8W8JWT_NBF_FAILURE, "Token is not yet valid"); + JWT_FLAG_STATUS(L8W8JWT_IAT_FAILURE, "Token has not been issued yet"); + JWT_FLAG_STATUS(L8W8JWT_SIGNATURE_VERIFICATION_FAILURE, "Token signature is invalid"); + JWT_FLAG_STATUS(L8W8JWT_TYP_FAILURE, "Token type is invalid"); + +#undef JWT_OUTPUT_MSG +#undef JWT_FLAG_STATUS +} + /** * Struct containing the parameters to use for decoding and validating a JWT. */