diff options
author | Emma Miler <emma.pi@protonmail.com> | 2022-12-19 19:32:16 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-19 19:32:16 +0100 |
commit | e04f3b36accccb590a2d51b4829256b9964ac3fd (patch) | |
tree | 20ee30c82e6f53e6e772be2e1b9613eebca12bf3 /NorthstarDLL/serverauthentication.cpp | |
parent | 33f18a735986dcd136bf8ba70ad8331306c28227 (diff) | |
download | NorthstarLauncher-e04f3b36accccb590a2d51b4829256b9964ac3fd.tar.gz NorthstarLauncher-e04f3b36accccb590a2d51b4829256b9964ac3fd.zip |
Restructuring (#365)
* Remove launcher proxy
* Restructuring
* More restructuring
* Fix include dirs
* Fix merge
* Remove clang thing
* Filters
* Oops
Diffstat (limited to 'NorthstarDLL/serverauthentication.cpp')
-rw-r--r-- | NorthstarDLL/serverauthentication.cpp | 462 |
1 files changed, 0 insertions, 462 deletions
diff --git a/NorthstarDLL/serverauthentication.cpp b/NorthstarDLL/serverauthentication.cpp deleted file mode 100644 index 350d8521..00000000 --- a/NorthstarDLL/serverauthentication.cpp +++ /dev/null @@ -1,462 +0,0 @@ -#include "pch.h" -#include "serverauthentication.h" -#include "limits.h" -#include "cvar.h" -#include "convar.h" -#include "masterserver.h" -#include "serverpresence.h" -#include "hoststate.h" -#include "maxplayers.h" -#include "bansystem.h" -#include "concommand.h" -#include "dedicated.h" -#include "nsprefix.h" -#include "tier0.h" -#include "r2engine.h" -#include "r2client.h" -#include "r2server.h" - -#include "httplib.h" - -#include <fstream> -#include <filesystem> -#include <thread> - -AUTOHOOK_INIT() - -const char* AUTHSERVER_VERIFY_STRING = "I am a northstar server!"; - -// global vars -ServerAuthenticationManager* g_pServerAuthentication; -CBaseServer__RejectConnectionType CBaseServer__RejectConnection; - -void ServerAuthenticationManager::StartPlayerAuthServer() -{ - if (m_bRunningPlayerAuthThread) - { - spdlog::warn("ServerAuthenticationManager::StartPlayerAuthServer was called while m_bRunningPlayerAuthThread is true"); - return; - } - - g_pServerPresence->SetAuthPort(Cvar_ns_player_auth_port->GetInt()); // set auth port for presence - m_bRunningPlayerAuthThread = true; - - // listen is a blocking call so thread this - std::thread serverThread( - [this] - { - // this is just a super basic way to verify that servers have ports open, masterserver will try to read this before ensuring - // server is legit - m_PlayerAuthServer.Get( - "/verify", - [](const httplib::Request& request, httplib::Response& response) - { response.set_content(AUTHSERVER_VERIFY_STRING, "text/plain"); }); - - m_PlayerAuthServer.Post( - "/authenticate_incoming_player", - [this](const httplib::Request& request, httplib::Response& response) - { - if (!request.has_param("id") || !request.has_param("authToken") || request.body.size() >= R2::PERSISTENCE_MAX_SIZE || - !request.has_param("serverAuthToken") || - strcmp(g_pMasterServerManager->m_sOwnServerAuthToken, request.get_param_value("serverAuthToken").c_str())) - { - response.set_content("{\"success\":false}", "application/json"); - return; - } - - RemoteAuthData newAuthData {}; - strncpy_s(newAuthData.uid, sizeof(newAuthData.uid), request.get_param_value("id").c_str(), sizeof(newAuthData.uid) - 1); - strncpy_s( - newAuthData.username, - sizeof(newAuthData.username), - request.get_param_value("username").c_str(), - sizeof(newAuthData.username) - 1); - - newAuthData.pdataSize = request.body.size(); - newAuthData.pdata = new char[newAuthData.pdataSize]; - memcpy(newAuthData.pdata, request.body.c_str(), newAuthData.pdataSize); - - std::lock_guard<std::mutex> guard(m_AuthDataMutex); - m_RemoteAuthenticationData.insert(std::make_pair(request.get_param_value("authToken"), newAuthData)); - - response.set_content("{\"success\":true}", "application/json"); - }); - - m_PlayerAuthServer.listen("0.0.0.0", Cvar_ns_player_auth_port->GetInt()); - }); - - serverThread.detach(); -} - -void ServerAuthenticationManager::StopPlayerAuthServer() -{ - if (!m_bRunningPlayerAuthThread) - { - spdlog::warn("ServerAuthenticationManager::StopPlayerAuthServer was called while m_bRunningPlayerAuthThread is false"); - return; - } - - m_bRunningPlayerAuthThread = false; - m_PlayerAuthServer.stop(); -} - -void ServerAuthenticationManager::AddPlayer(R2::CBaseClient* player, const char* pToken) -{ - PlayerAuthenticationData additionalData; - additionalData.pdataSize = m_RemoteAuthenticationData[pToken].pdataSize; - additionalData.usingLocalPdata = player->m_iPersistenceReady == R2::ePersistenceReady::READY_INSECURE; - - m_PlayerAuthenticationData.insert(std::make_pair(player, additionalData)); -} - -void ServerAuthenticationManager::RemovePlayer(R2::CBaseClient* player) -{ - if (m_PlayerAuthenticationData.count(player)) - m_PlayerAuthenticationData.erase(player); -} - -bool checkIsPlayerNameValid(const char* name) -{ - int len = strlen(name); - // Restricts name to max 63 characters - if (len >= 64) - return false; - for (int i = 0; i < len; i++) - { - // Restricts the characters in the name to a restricted range in ASCII - if (static_cast<int>(name[i]) < 32 || static_cast<int>(name[i]) > 126) - { - return false; - } - } - return true; -} - -bool ServerAuthenticationManager::VerifyPlayerName(const char* authToken, const char* name) -{ - std::lock_guard<std::mutex> guard(m_AuthDataMutex); - - if (CVar_ns_auth_allow_insecure->GetInt()) - { - spdlog::info("Allowing player with name '{}' because ns_auth_allow_insecure is enabled", name); - return true; - } - - if (!checkIsPlayerNameValid(name)) - { - spdlog::info("Rejecting player with name '{}' because the name contains forbidden characters", name); - return false; - } - // TODO: We should really have a better way of doing this for singleplayer - // Best way of doing this would be to check if server is actually in singleplayer mode, or just running a SP map in multiplayer - // Currently there's not an easy way of checking this, so we just disable this check if mapname starts with `sp_` - // This means that player names are not checked on singleplayer - if ((m_RemoteAuthenticationData.empty() || m_RemoteAuthenticationData.count(std::string(authToken)) == 0) && - strncmp(R2::g_pHostState->m_levelName, "sp_", 3) != 0) - { - spdlog::info("Rejecting player with name '{}' because authToken '{}' was not found", name, authToken); - return false; - } - - const RemoteAuthData& authData = m_RemoteAuthenticationData[authToken]; - - if (*authData.username && strncmp(name, authData.username, 64) != 0) - { - spdlog::info("Rejecting player with name '{}' because name does not match expected name '{}'", name, authData.username); - return false; - } - - return true; -} - -bool ServerAuthenticationManager::CheckDuplicateAccounts(R2::CBaseClient* player) -{ - if (m_bAllowDuplicateAccounts) - return true; - - bool bHasUidPlayer = false; - for (int i = 0; i < R2::GetMaxPlayers(); i++) - if (&R2::g_pClientArray[i] != player && !strcmp(R2::g_pClientArray[i].m_UID, player->m_UID)) - return false; - - return true; -} - -bool ServerAuthenticationManager::AuthenticatePlayer(R2::CBaseClient* player, uint64_t uid, char* authToken) -{ - std::string strUid = std::to_string(uid); - std::lock_guard<std::mutex> guard(m_AuthDataMutex); - - if (!strncmp(R2::g_pHostState->m_levelName, "sp_", 3)) - return true; - - // copy uuid - strcpy(player->m_UID, strUid.c_str()); - - bool authFail = true; - if (!m_RemoteAuthenticationData.empty() && m_RemoteAuthenticationData.count(std::string(authToken))) - { - if (!CheckDuplicateAccounts(player)) - return false; - - // use stored auth data - RemoteAuthData authData = m_RemoteAuthenticationData[authToken]; - - if (!strcmp(strUid.c_str(), authData.uid)) // connecting client's uid is the same as auth's uid - { - // if we're resetting let script handle the reset - if (!m_bForceResetLocalPlayerPersistence || strcmp(authData.uid, R2::g_pLocalPlayerUserID)) - { - // copy pdata into buffer - memcpy(player->m_PersistenceBuffer, authData.pdata, authData.pdataSize); - } - - // set persistent data as ready - player->m_iPersistenceReady = R2::ePersistenceReady::READY_REMOTE; - authFail = false; - } - } - - if (authFail) - { - if (CVar_ns_auth_allow_insecure->GetBool()) - { - // set persistent data as ready - // note: actual placeholder persistent data is populated in script with InitPersistentData() - player->m_iPersistenceReady = R2::ePersistenceReady::READY_INSECURE; - return true; - } - else - return false; - } - - return true; // auth successful, client stays on -} - -bool ServerAuthenticationManager::RemovePlayerAuthData(R2::CBaseClient* player) -{ - if (!Cvar_ns_erase_auth_info->GetBool()) // keep auth data forever - return false; - - // hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect - if (m_bNeedLocalAuthForNewgame && !strcmp(player->m_UID, R2::g_pLocalPlayerUserID)) - return false; - - // we don't have our auth token at this point, so lookup authdata by uid - for (auto& auth : m_RemoteAuthenticationData) - { - if (!strcmp(player->m_UID, auth.second.uid)) - { - // pretty sure this is fine, since we don't iterate after the erase - // i think if we iterated after it'd be undefined behaviour tho - std::lock_guard<std::mutex> guard(m_AuthDataMutex); - - delete[] auth.second.pdata; - m_RemoteAuthenticationData.erase(auth.first); - return true; - } - } - - return false; -} - -void ServerAuthenticationManager::WritePersistentData(R2::CBaseClient* player) -{ - if (player->m_iPersistenceReady == R2::ePersistenceReady::READY_REMOTE) - { - g_pMasterServerManager->WritePlayerPersistentData( - player->m_UID, (const char*)player->m_PersistenceBuffer, m_PlayerAuthenticationData[player].pdataSize); - } - else if (CVar_ns_auth_allow_insecure_write->GetBool()) - { - // todo: write pdata to disk here - } -} - -// auth hooks - -// store these in vars so we can use them in CBaseClient::Connect -// this is fine because ptrs won't decay by the time we use this, just don't use it outside of calls from cbaseclient::connectclient -char* pNextPlayerToken; -uint64_t iNextPlayerUid; - -// clang-format off -AUTOHOOK(CBaseServer__ConnectClient, engine.dll + 0x114430, -void*,, ( - void* self, - void* addr, - void* a3, - uint32_t a4, - uint32_t a5, - int32_t a6, - void* a7, - char* playerName, - char* serverFilter, - void* a10, - char a11, - void* a12, - char a13, - char a14, - int64_t uid, - uint32_t a16, - uint32_t a17)) -// clang-format on -{ - // auth tokens are sent with serverfilter, can't be accessed from player struct to my knowledge, so have to do this here - pNextPlayerToken = serverFilter; - iNextPlayerUid = uid; - - spdlog::info( - "CBaseServer__ClientConnect attempted connection with uid {}, playerName '{}', serverFilter '{}'", uid, playerName, serverFilter); - - if (!g_pServerAuthentication->VerifyPlayerName(pNextPlayerToken, playerName)) - { - CBaseServer__RejectConnection(self, *((int*)self + 3), addr, "Invalid Name.\n"); - return nullptr; - } - if (!g_pBanSystem->IsUIDAllowed(uid)) - { - CBaseServer__RejectConnection(self, *((int*)self + 3), addr, "Banned From server.\n"); - return nullptr; - } - - return CBaseServer__ConnectClient(self, addr, a3, a4, a5, a6, a7, playerName, serverFilter, a10, a11, a12, a13, a14, uid, a16, a17); -} - -// clang-format off -AUTOHOOK(CBaseClient__Connect, engine.dll + 0x101740, -bool,, (R2::CBaseClient* self, char* name, void* netchan_ptr_arg, char b_fake_player_arg, void* a5, char* Buffer, void* a7)) -// clang-format on -{ - // try to auth player, dc if it fails - // we connect regardless of auth, because returning bad from this function can fuck client state p bad - bool ret = CBaseClient__Connect(self, name, netchan_ptr_arg, b_fake_player_arg, a5, Buffer, a7); - if (!ret) - return ret; - - if (!g_pServerAuthentication->VerifyPlayerName(pNextPlayerToken, name)) - { - R2::CBaseClient__Disconnect(self, 1, "Invalid Name.\n"); - return false; - } - if (!g_pBanSystem->IsUIDAllowed(iNextPlayerUid)) - { - R2::CBaseClient__Disconnect(self, 1, "Banned From server.\n"); - return false; - } - if (!g_pServerAuthentication->AuthenticatePlayer(self, iNextPlayerUid, pNextPlayerToken)) - { - R2::CBaseClient__Disconnect(self, 1, "Authentication Failed.\n"); - return false; - } - - g_pServerAuthentication->AddPlayer(self, pNextPlayerToken); - g_pServerLimits->AddPlayer(self); - - return ret; -} - -// clang-format off -AUTOHOOK(CBaseClient__ActivatePlayer, engine.dll + 0x100F80, -void,, (R2::CBaseClient* self)) -// clang-format on -{ - // if we're authed, write our persistent data - // RemovePlayerAuthData returns true if it removed successfully, i.e. on first call only, and we only want to write on >= second call - // (since this func is called on map loads) - if (self->m_iPersistenceReady >= R2::ePersistenceReady::READY && !g_pServerAuthentication->RemovePlayerAuthData(self)) - { - g_pServerAuthentication->m_bForceResetLocalPlayerPersistence = false; - g_pServerAuthentication->WritePersistentData(self); - g_pServerPresence->SetPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); - } - - CBaseClient__ActivatePlayer(self); -} - -// clang-format off -AUTOHOOK(_CBaseClient__Disconnect, engine.dll + 0x1012C0, -void,, (R2::CBaseClient* self, uint32_t unknownButAlways1, const char* pReason, ...)) -// clang-format on -{ - // have to manually format message because can't pass varargs to original func - char buf[1024]; - - va_list va; - va_start(va, pReason); - vsprintf(buf, pReason, va); - va_end(va); - - // this reason is used while connecting to a local server, hacky, but just ignore it - if (strcmp(pReason, "Connection closing")) - { - spdlog::info("Player {} disconnected: \"{}\"", self->m_Name, buf); - - // dcing, write persistent data - if (g_pServerAuthentication->m_PlayerAuthenticationData[self].needPersistenceWriteOnLeave) - g_pServerAuthentication->WritePersistentData(self); - g_pServerAuthentication->RemovePlayerAuthData(self); // won't do anything 99% of the time, but just in case - - g_pServerAuthentication->RemovePlayer(self); - g_pServerLimits->RemovePlayer(self); - } - - else if (g_pServerAuthentication->m_RemoteAuthenticationData.count(self->m_Name)) - { - g_pServerAuthentication->RemovePlayer(self); - g_pServerLimits->RemovePlayer(self); - } - - g_pServerPresence->SetPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); - - _CBaseClient__Disconnect(self, unknownButAlways1, buf); -} - -void ConCommand_ns_resetpersistence(const CCommand& args) -{ - if (*R2::g_pServerState == R2::server_state_t::ss_active) - { - spdlog::error("ns_resetpersistence must be entered from the main menu"); - return; - } - - spdlog::info("resetting persistence on next lobby load..."); - g_pServerAuthentication->m_bForceResetLocalPlayerPersistence = true; -} - -ON_DLL_LOAD_RELIESON("engine.dll", ServerAuthentication, (ConCommand, ConVar), (CModule module)) -{ - AUTOHOOK_DISPATCH() - - g_pServerAuthentication = new ServerAuthenticationManager; - - g_pServerAuthentication->Cvar_ns_player_auth_port = new ConVar("ns_player_auth_port", "8081", FCVAR_GAMEDLL, ""); - g_pServerAuthentication->Cvar_ns_erase_auth_info = - new ConVar("ns_erase_auth_info", "1", FCVAR_GAMEDLL, "Whether auth info should be erased from this server on disconnect or crash"); - g_pServerAuthentication->CVar_ns_auth_allow_insecure = - new ConVar("ns_auth_allow_insecure", "0", FCVAR_GAMEDLL, "Whether this server will allow unauthenicated players to connect"); - g_pServerAuthentication->CVar_ns_auth_allow_insecure_write = new ConVar( - "ns_auth_allow_insecure_write", - "0", - FCVAR_GAMEDLL, - "Whether the pdata of unauthenticated clients will be written to disk when changed"); - - RegisterConCommand( - "ns_resetpersistence", ConCommand_ns_resetpersistence, "resets your pdata when you next enter the lobby", FCVAR_NONE); - - // patch to disable kicking based on incorrect serverfilter in connectclient, since we repurpose it for use as an auth token - module.Offset(0x114655).Patch("EB"); - - // patch to disable fairfight marking players as cheaters and kicking them - module.Offset(0x101012).Patch("E9 90 00"); - - CBaseServer__RejectConnection = module.Offset(0x1182E0).As<CBaseServer__RejectConnectionType>(); - - if (Tier0::CommandLine()->CheckParm("-allowdupeaccounts")) - { - // patch to allow same of multiple account - module.Offset(0x114510).Patch("EB"); - - g_pServerAuthentication->m_bAllowDuplicateAccounts = true; - } -} |