diff --git a/AMBuildScript b/AMBuildScript index d2a5772b..f14c01ab 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -218,6 +218,8 @@ class SMConfig(object): '_CRT_NONSTDC_NO_DEPRECATE', '_ITERATOR_DEBUG_LEVEL=0', ] + if cxx.version < 1800: + cxx.defines += 'strtoull=_strtoui64' cxx.cflags += [ '/W3', ] diff --git a/core/PlayerManager.cpp b/core/PlayerManager.cpp index c9600edf..09388bf7 100644 --- a/core/PlayerManager.cpp +++ b/core/PlayerManager.cpp @@ -403,12 +403,9 @@ void PlayerManager::RunAuthChecks() for (unsigned int i=1; i<=m_AuthQueue[0]; i++) { pPlayer = &m_Players[m_AuthQueue[i]]; -#if SOURCE_ENGINE == SE_DOTA - authstr = engine->GetPlayerNetworkIDString(pPlayer->m_iIndex - 1); -#else - authstr = engine->GetPlayerNetworkIDString(pPlayer->m_pEdict); -#endif - pPlayer->SetAuthString(authstr); + pPlayer->UpdateAuthIds(); + + authstr = pPlayer->m_AuthID.c_str(); if (!pPlayer->IsAuthStringValidated()) { @@ -620,15 +617,10 @@ void PlayerManager::OnClientPutInServer(edict_t *pEntity, const char *playername { /* Run manual connection routines */ char error[255]; - const char *authid; -#if SOURCE_ENGINE == SE_DOTA - authid = engine->GetPlayerNetworkIDString(client - 1); -#else - authid = engine->GetPlayerNetworkIDString(pEntity); -#endif - pPlayer->SetAuthString(authid); - pPlayer->Authorize(); + pPlayer->m_bFakeClient = true; + pPlayer->UpdateAuthIds(); + pPlayer->Authorize(); /* * While we're already filtered to just bots, we'll do other checks to @@ -720,13 +712,13 @@ void PlayerManager::OnClientPutInServer(edict_t *pEntity, const char *playername for (iter=m_hooks.begin(); iter!=m_hooks.end(); iter++) { pListener = (*iter); - pListener->OnClientAuthorized(client, authid); + pListener->OnClientAuthorized(client, pPlayer->m_AuthID.c_str()); } /* Finally, tell plugins */ if (m_clauth->GetFunctionCount()) { m_clauth->PushCell(client); - m_clauth->PushString(authid); + m_clauth->PushString(pPlayer->m_AuthID.c_str()); m_clauth->Execute(NULL); } pPlayer->Authorize_Post(); @@ -1552,22 +1544,47 @@ void PlayerManager::ProcessCommandTarget(cmd_target_info_t *info) } /* Do we need to look for a steam id? */ + int steamIdType = 0; if (strncmp(&info->pattern[1], "STEAM_", 6) == 0) { - size_t p, len; + steamIdType = 2; + } + else if (strncmp(&info->pattern[1], "[U:", 3) == 0) + { + steamIdType = 3; + } + + if (steamIdType > 0) + { char new_pattern[256]; - - strcpy(new_pattern, "STEAM_"); - len = strlen(&info->pattern[7]); - for (p = 0; p < len; p++) + if (steamIdType == 2) { - new_pattern[6 + p] = info->pattern[7 + p]; - if (new_pattern[6 + p] == '_') + size_t p, len; + + strcpy(new_pattern, "STEAM_"); + len = strlen(&info->pattern[7]); + for (p = 0; p < len; p++) { - new_pattern[6 + p] = ':'; + new_pattern[6 + p] = info->pattern[7 + p]; + if (new_pattern[6 + p] == '_') + { + new_pattern[6 + p] = ':'; + } } + new_pattern[6 + p] = '\0'; + } + else + { + size_t p = 0; + char c; + while ((c = info->pattern[p + 1]) != '\0') + { + new_pattern[p] = (c == '_') ? ':' : c; + ++p; + } + + new_pattern[p] = '\0'; } - new_pattern[6 + p] = '\0'; for (int i = 1; i <= max_clients; i++) { @@ -1579,8 +1596,10 @@ void PlayerManager::ProcessCommandTarget(cmd_target_info_t *info) { continue; } - const char *authstr = pTarget->GetAuthString(false); // We want to make it easy for people to be kicked/banned, so don't require validation for command targets. - if (authstr && strcmp(authstr, new_pattern) == 0) + + // We want to make it easy for people to be kicked/banned, so don't require validation for command targets. + const char *steamId = steamIdType == 2 ? pTarget->GetSteam2Id(false) : pTarget->GetSteam3Id(false); + if (steamId && strcmp(steamId, new_pattern) == 0) { if ((info->reason = FilterCommandTarget(pAdmin, pTarget, info->flags)) == COMMAND_TARGET_VALID) @@ -1914,7 +1933,6 @@ CPlayer::CPlayer() m_bIsSourceTV = false; m_bIsReplay = false; m_Serial.value = -1; - m_SteamId = k_steamIDNil; #if SOURCE_ENGINE == SE_CSGO m_LanguageCookie = InvalidQueryCvarCookie; #endif @@ -1963,14 +1981,99 @@ void CPlayer::Connect() } } -void CPlayer::SetAuthString(const char *steamid) +void CPlayer::UpdateAuthIds() { if (m_IsAuthorized) { return; } + + // First cache engine networkid + const char *authstr; +#if SOURCE_ENGINE == SE_DOTA + authstr = engine->GetPlayerNetworkIDString(m_iIndex - 1); +#else + authstr = engine->GetPlayerNetworkIDString(m_pEdict); +#endif + m_AuthID.assign(authstr); + + // Then, cache SteamId + if (IsFakeClient()) + { + m_SteamId = k_steamIDNil; + } + else + { +#if SOURCE_ENGINE < SE_ORANGEBOX + const char * pAuth = GetAuthString(); + /* STEAM_0:1:123123 | STEAM_ID_LAN | STEAM_ID_PENDING */ + if (pAuth && (strlen(pAuth) > 10) && pAuth[8] != '_') + { + m_SteamId = CSteamID(atoi(&pAuth[8]) | (atoi(&pAuth[10]) << 1), + k_unSteamUserDesktopInstance, k_EUniversePublic, k_EAccountTypeIndividual); + } +#else + const CSteamID *steamId; +#if SOURCE_ENGINE == SE_DOTA + steamId = engine->GetClientSteamID(m_iIndex); +#else + steamId = engine->GetClientSteamID(m_pEdict); +#endif - m_AuthID.assign(steamid); + if (steamId) + { + m_SteamId = (*steamId); + } +#endif + } + + // Now cache Steam2/3 rendered ids + if (IsFakeClient()) + { + m_Steam2Id = "BOT"; + m_Steam3Id = "BOT"; + return; + } + + if (!m_SteamId.IsValid()) + { + if (g_HL2.IsLANServer()) + { + m_Steam2Id = "STEAM_ID_LAN"; + m_Steam3Id = "STEAM_ID_LAN"; + return; + } + else + { + m_Steam2Id = "STEAM_ID_PENDING"; + m_Steam3Id = "STEAM_ID_PENDING"; + } + + return; + } + + EUniverse steam2universe = m_SteamId.GetEUniverse(); + if (atoi(g_pGameConf->GetKeyValue("UseInvalidUniverseInSteam2IDs")) == 1) + { + steam2universe = k_EUniverseInvalid; + } + + char szAuthBuffer[64]; + snprintf(szAuthBuffer, sizeof(szAuthBuffer), "STEAM_%u:%u:%u", steam2universe, m_SteamId.GetAccountID() & 1, m_SteamId.GetAccountID() >> 1); + + m_Steam2Id = szAuthBuffer; + + // TODO: make sure all hl2sdks' steamclientpublic.h have k_unSteamUserDesktopInstance. + if (m_SteamId.GetUnAccountInstance() == 1 /* k_unSteamUserDesktopInstance */) + { + snprintf(szAuthBuffer, sizeof(szAuthBuffer), "[U:%u:%u]", m_SteamId.GetEUniverse(), m_SteamId.GetAccountID()); + } + else + { + snprintf(szAuthBuffer, sizeof(szAuthBuffer), "[U:%u:%u:%u]", m_SteamId.GetEUniverse(), m_SteamId.GetAccountID(), m_SteamId.GetUnAccountInstance()); + } + + m_Steam3Id = szAuthBuffer; } // Ensure a valid AuthString is set before calling. @@ -1988,6 +2091,9 @@ void CPlayer::Disconnect() m_Name.clear(); m_Ip.clear(); m_AuthID.clear(); + m_SteamId = k_steamIDNil; + m_Steam2Id = ""; + m_Steam3Id = ""; m_pEdict = NULL; m_Info = NULL; m_bAdminCheckSignalled = false; @@ -1997,7 +2103,6 @@ void CPlayer::Disconnect() m_bIsSourceTV = false; m_bIsReplay = false; m_Serial.value = -1; - m_SteamId = k_steamIDNil; #if SOURCE_ENGINE == SE_CSGO m_LanguageCookie = InvalidQueryCvarCookie; #endif @@ -2035,41 +2140,35 @@ const char *CPlayer::GetAuthString(bool validated) const CSteamID &CPlayer::GetSteamId(bool validated) { - if (IsFakeClient() || (validated && !IsAuthStringValidated())) + if (validated && !IsAuthStringValidated()) { static const CSteamID invalidId = k_steamIDNil; return invalidId; } - - if (m_SteamId.IsValid()) - { - return m_SteamId; - } - -#if SOURCE_ENGINE < SE_ORANGEBOX - const char * pAuth = GetAuthString(); - /* STEAM_0:1:123123 | STEAM_ID_LAN | STEAM_ID_PENDING */ - if (pAuth && (strlen(pAuth) > 10) && pAuth[8] != '_') - { - m_SteamId = CSteamID(atoi(&pAuth[8]) | (atoi(&pAuth[10]) << 1), - k_unSteamUserDesktopInstance, k_EUniversePublic, k_EAccountTypeIndividual); - } -#else - const CSteamID *steamId; -#if SOURCE_ENGINE == SE_DOTA - steamId = engine->GetClientSteamID(m_iIndex); -#else - steamId = engine->GetClientSteamID(m_pEdict); -#endif - - if (steamId) - { - m_SteamId = (*steamId); - } -#endif + return m_SteamId; } +const char *CPlayer::GetSteam2Id(bool validated) +{ + if (validated && !IsAuthStringValidated()) + { + return NULL; + } + + return m_Steam2Id.chars(); +} + +const char *CPlayer::GetSteam3Id(bool validated) +{ + if (validated && !IsAuthStringValidated()) + { + return NULL; + } + + return m_Steam3Id.chars(); +} + unsigned int CPlayer::GetSteamAccountID(bool validated) { if (!IsFakeClient() && (!validated || IsAuthStringValidated())) diff --git a/core/PlayerManager.h b/core/PlayerManager.h index cfd57cc0..22ca01fc 100644 --- a/core/PlayerManager.h +++ b/core/PlayerManager.h @@ -41,6 +41,7 @@ #include #include #include +#include #include "ConVarManager.h" #include @@ -75,6 +76,8 @@ public: unsigned int GetSteamAccountID(bool validated = true); const CSteamID &GetSteamId(bool validated = true); uint64_t GetSteamId64(bool validated = true) { return GetSteamId(validated).ConvertToUint64(); } + const char *GetSteam2Id(bool validated = true); + const char *GetSteam3Id(bool validated = true); edict_t *GetEdict(); bool IsInGame(); bool WasCountedAsInGame(); @@ -107,7 +110,7 @@ private: void Disconnect(); void SetName(const char *name); void DumpAdmin(bool deleting); - void SetAuthString(const char *auth); + void UpdateAuthIds(); void Authorize(); void Authorize_Post(); void DoPostConnectAuthorization(); @@ -121,6 +124,8 @@ private: String m_Ip; String m_IpNoPort; String m_AuthID; + ke::AString m_Steam2Id; + ke::AString m_Steam3Id; AdminId m_Admin; bool m_TempAdmin; edict_t *m_pEdict; diff --git a/core/logic/AdminCache.cpp b/core/logic/AdminCache.cpp index a10ae6d1..8e950a9f 100644 --- a/core/logic/AdminCache.cpp +++ b/core/logic/AdminCache.cpp @@ -1056,6 +1056,58 @@ bool AdminCache::GetMethodIndex(const char *name, unsigned int *_index) return false; } +/* + * Converts Steam2 id, Steam3 id, or SteamId64 to unified, legacy + * admin identity format. (account id part of Steam2 format) + */ +bool AdminCache::GetUnifiedSteamIdentity(const char *ident, char *out, size_t maxlen) +{ + int len = strlen(ident); + /* If the id was a steam id strip off the STEAM_*: part */ + if (len >= 11 && !strncmp(ident, "STEAM_", 6) && ident[8] != '_') + { + // non-bot/lan Steam2 Id + snprintf(out, maxlen, "%s", ident[8]); + return true; + } + else if (len >= 7 && !strncmp(ident, "[U:", 3) && ident[len-1] == ']') + { + // Steam3 Id + uint32_t accountId = strtoul(&ident[5], nullptr, 10); + snprintf(out, maxlen, "%u:%u", accountId & 1, accountId >> 1); + return true; + } + else + { + // some constants from steamclientpublic.h + static const uint32_t k_EAccountTypeIndividual = 1; + static const int k_EUniverseInvalid = 0; + static const int k_EUniverseMax = 5; + static const unsigned int k_unSteamUserWebInstance = 4; + + uint64_t steamId = strtoull(ident, nullptr, 10); + if (steamId > 0) + { + // Make some attempt at being sure it's a valid id rather than other number, + // even though we're only going to use the lower 32 bits. + uint32_t accountId = steamId & 0xFFFFFFFF; + uint32_t accountType = (steamId >> 52) & 0xF; + int universe = steamId >> 56; + uint32_t accountInstance = (steamId >> 32) & 0xFFFFF; + if (accountId > 0 + && universe > k_EUniverseInvalid && universe < k_EUniverseMax + && accountType == k_EAccountTypeIndividual && accountInstance <= k_unSteamUserWebInstance + ) + { + snprintf(out, maxlen, "%u:%u", accountId & 1, accountId >> 1); + return true; + } + } + } + + return false; +} + bool AdminCache::BindAdminIdentity(AdminId id, const char *auth, const char *ident) { if (ident[0] == '\0') @@ -1073,10 +1125,14 @@ bool AdminCache::BindAdminIdentity(AdminId id, const char *auth, const char *ide if (!m_AuthTables.retrieve(auth, &method)) return false; - /* If the id was a steam id strip off the STEAM_*: part */ - if (strcmp(auth, "steam") == 0 && strncmp(ident, "STEAM_", 6) == 0) + /* If the auth type is steam, the id could be in a number of formats. Unify it. */ + char steamIdent[16]; + if (strcmp(auth, "steam") == 0) { - ident += 8; + if (!GetUnifiedSteamIdentity(ident, steamIdent, sizeof(steamIdent))) + return false; + + ident = steamIdent; } if (method->identities.contains(ident)) @@ -1097,10 +1153,14 @@ AdminId AdminCache::FindAdminByIdentity(const char *auth, const char *identity) if (!m_AuthTables.retrieve(auth, &method)) return INVALID_ADMIN_ID; - /* If the id was a steam id strip off the STEAM_*: part */ - if (strcmp(auth, "steam") == 0 && strncmp(identity, "STEAM_", 6) == 0) + /* If the auth type is steam, the id could be in a number of formats. Unify it. */ + char steamIdent[16]; + if (strcmp(auth, "steam") == 0) { - identity += 8; + if (!GetUnifiedSteamIdentity(identity, steamIdent, sizeof(steamIdent))) + return INVALID_ADMIN_ID; + + identity = steamIdent; } AdminId id; diff --git a/core/logic/AdminCache.h b/core/logic/AdminCache.h index ec32be8c..0254cb85 100644 --- a/core/logic/AdminCache.h +++ b/core/logic/AdminCache.h @@ -198,6 +198,7 @@ private: bool GetMethodIndex(const char *name, unsigned int *_index); const char *GetMethodName(unsigned int index); void NameFlag(const char *str, AdminFlag flag); + bool GetUnifiedSteamIdentity(const char *ident, char *out, size_t maxlen); public: typedef StringHashMap FlagMap; diff --git a/core/logic/smn_players.cpp b/core/logic/smn_players.cpp index 34b24798..96bd262d 100644 --- a/core/logic/smn_players.cpp +++ b/core/logic/smn_players.cpp @@ -352,72 +352,37 @@ static cell_t SteamIdToLocal(IPluginContext *pCtx, int index, AuthIdType authTyp { return pCtx->ThrowNativeError("Client %d is not connected", index); } - + + const char *authstr; + switch (authType) { case AuthIdType::Engine: + authstr = pPlayer->GetAuthString(validate); + if (!authstr || authstr[0] == '\0') { - const char *authstr = pPlayer->GetAuthString(validate); - if (!authstr || authstr[0] == '\0') - { - return 0; - } - - pCtx->StringToLocal(local_addr, bytes, authstr); + return 0; } + + pCtx->StringToLocal(local_addr, bytes, authstr); break; case AuthIdType::Steam2: - case AuthIdType::Steam3: + authstr = pPlayer->GetSteam2Id(validate); + if (!authstr || authstr[0] == '\0') { - if (pPlayer->IsFakeClient()) - { - pCtx->StringToLocal(local_addr, bytes, "BOT"); - return 1; - } - - uint64_t steamId = pPlayer->GetSteamId64(validate); - if (steamId == 0) - { - if (gamehelpers->IsLANServer()) - { - pCtx->StringToLocal(local_addr, bytes, "STEAM_ID_LAN"); - return 1; - } - else if (!validate) - { - pCtx->StringToLocal(local_addr, bytes, "STEAM_ID_PENDING"); - return 1; - } - else - { - return 0; - } - } - - char szAuth[64]; - unsigned int universe = steamId >> 56; - unsigned int accountId = steamId & 0xFFFFFFFF; - unsigned int instance = (steamId >> 32) & 0x000FFFFF; - if (authType == AuthIdType::Steam2) - { - if (atoi(g_pGameConf->GetKeyValue("UseInvalidUniverseInSteam2IDs")) == 1) - { - universe = 0; - } - - snprintf(szAuth, sizeof(szAuth), "STEAM_%u:%u:%u", universe, accountId & 1, accountId >> 1); - } - else if (instance != 1) - { - snprintf(szAuth, sizeof(szAuth), "[U:%u:%u:%u]", universe, accountId, instance); - } - else - { - snprintf(szAuth, sizeof(szAuth), "[U:%u:%u]", universe, accountId); - } - - pCtx->StringToLocal(local_addr, bytes, szAuth); + return 0; } + + pCtx->StringToLocal(local_addr, bytes, authstr); + break; + case AuthIdType::Steam3: + authstr = pPlayer->GetSteam3Id(validate); + if (!authstr || authstr[0] == '\0') + { + return 0; + } + + pCtx->StringToLocal(local_addr, bytes, authstr); break; case AuthIdType::SteamId64: diff --git a/plugins/admin-flatfile/admin-simple.sp b/plugins/admin-flatfile/admin-simple.sp index 507a41d7..c04be2b7 100644 --- a/plugins/admin-flatfile/admin-simple.sp +++ b/plugins/admin-flatfile/admin-simple.sp @@ -98,6 +98,13 @@ DecodeAuthMethod(const String:auth[], String:method[32], &offset) { if ((StrContains(auth, "STEAM_") == 0) || (strncmp("0:", auth, 2) == 0) || (strncmp("1:", auth, 2) == 0)) { + // Steam2 Id + strcopy(method, sizeof(method), AUTHMETHOD_STEAM); + offset = 0; + } + else if (!strncmp(auth, "[U:", 3) && auth[strlen(auth) - 1] == ']') + { + // Steam3 Id strcopy(method, sizeof(method), AUTHMETHOD_STEAM); offset = 0; } diff --git a/plugins/admin-sql-threaded.sp b/plugins/admin-sql-threaded.sp index 60bc09c0..158b826f 100644 --- a/plugins/admin-sql-threaded.sp +++ b/plugins/admin-sql-threaded.sp @@ -477,7 +477,7 @@ FetchUser(Handle:db, client) GetClientIP(client, ipaddr, sizeof(ipaddr)); steamid[0] = '\0'; - if (GetClientAuthString(client, steamid, sizeof(steamid))) + if (GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid))) { if (StrEqual(steamid, "STEAM_ID_LAN")) { diff --git a/plugins/adminmenu/dynamicmenu.sp b/plugins/adminmenu/dynamicmenu.sp index 4deba54a..6ab9761e 100644 --- a/plugins/adminmenu/dynamicmenu.sp +++ b/plugins/adminmenu/dynamicmenu.sp @@ -534,7 +534,7 @@ public ParamCheck(client) } case SteamId: { - if (GetClientAuthString(i, infoBuffer, sizeof(infoBuffer))) + if (GetClientAuthId(i, AuthId_Steam2, infoBuffer, sizeof(infoBuffer))) AddMenuItem(itemMenu, infoBuffer, nameBuffer); } case IpAddress: diff --git a/plugins/include/admin.inc b/plugins/include/admin.inc index b126ceed..d27b281b 100644 --- a/plugins/include/admin.inc +++ b/plugins/include/admin.inc @@ -356,7 +356,7 @@ native GetAdminUsername(AdminId:id, String:name[], maxlength); * @param auth Auth method to use, predefined or from RegisterAuthIdentType(). * @param ident String containing the arbitrary, unique identity. * @return True on success, false if the auth method was not found, - * or ident was already taken. + * ident was already taken, or ident invalid for auth method. */ native bool:BindAdminIdentity(AdminId:id, const String:auth[], const String:ident[]); diff --git a/public/IPlayerHelpers.h b/public/IPlayerHelpers.h index 35a174a7..71c4a333 100644 --- a/public/IPlayerHelpers.h +++ b/public/IPlayerHelpers.h @@ -276,6 +276,24 @@ namespace SourceMod * @return Steam Id or 0 if not available. */ virtual uint64_t GetSteamId64(bool validated = true) =0; + + /** + * @brief Returns the client's Steam ID rendered in Steam2 format. + * + * @param validated Check backend validation status. + * + * @return True on success or false if not available. + */ + virtual const char *GetSteam2Id(bool validated = true) =0; + + /** + * @brief Returns the client's Steam ID rendered in Steam3 format. + * + * @param validated Check backend validation status. + * + * @return True on success or false if not available. + */ + virtual const char *GetSteam3Id(bool validated = true) =0; }; /**