diff --git a/core/PlayerManager.cpp b/core/PlayerManager.cpp index 5d701c5c..0e54712f 100644 --- a/core/PlayerManager.cpp +++ b/core/PlayerManager.cpp @@ -1295,12 +1295,32 @@ void CPlayer::Authorize_Post() void CPlayer::DoPostConnectAuthorization() { + bool delay = false; + + List::iterator iter; + for (iter = g_Players.m_hooks.begin(); + iter != g_Players.m_hooks.end(); + iter++) + { + IClientListener *pListener = (*iter); +#if defined MIN_API_FOR_ADMINCALLS + if (pListener->GetClientListenerVersion() < MIN_API_FOR_ADMINCALLS) + { + continue; + } +#endif + if (!pListener->OnClientPreAdminCheck(m_iIndex)) + { + delay = true; + } + } + cell_t result = 0; PreAdminCheck->PushCell(m_iIndex); PreAdminCheck->Execute(&result); /* Defer, for better or worse */ - if ((ResultType)result >= Pl_Handled) + if (delay || (ResultType)result >= Pl_Handled) { return; } @@ -1318,6 +1338,15 @@ void CPlayer::DoPostConnectAuthorization() NotifyPostAdminChecks(); } +bool CPlayer::RunAdminCacheChecks() +{ + AdminId old_id = GetAdminId(); + + DoBasicAdminChecks(); + + return (GetAdminId() != old_id); +} + void CPlayer::NotifyPostAdminChecks() { if (m_bAdminCheckSignalled) @@ -1328,6 +1357,21 @@ void CPlayer::NotifyPostAdminChecks() /* Block beforehand so they can't double-call */ m_bAdminCheckSignalled = true; + List::iterator iter; + for (iter = g_Players.m_hooks.begin(); + iter != g_Players.m_hooks.end(); + iter++) + { + IClientListener *pListener = (*iter); +#if defined MIN_API_FOR_ADMINCALLS + if (pListener->GetClientListenerVersion() < MIN_API_FOR_ADMINCALLS) + { + continue; + } +#endif + pListener->OnClientPostAdminCheck(m_iIndex); + } + PostAdminCheck->PushCell(m_iIndex); PostAdminCheck->Execute(NULL); } diff --git a/core/PlayerManager.h b/core/PlayerManager.h index 4e1a65ed..767a90dd 100644 --- a/core/PlayerManager.h +++ b/core/PlayerManager.h @@ -48,6 +48,8 @@ using namespace SourceHook; #define PLAYER_LIFE_ALIVE 1 #define PLAYER_LIFE_DEAD 2 +#define MIN_API_FOR_ADMINCALLS 7 + class CPlayer : public IGamePlayer { friend class PlayerManager; @@ -69,8 +71,9 @@ public: IPlayerInfo *GetPlayerInfo(); unsigned int GetLanguageId(); int GetUserId(); -public: + bool RunAdminCacheChecks(); void NotifyPostAdminChecks(); +public: void DoBasicAdminChecks(); bool IsInKickQueue(); void MarkAsBeingKicked(); @@ -108,6 +111,7 @@ class PlayerManager : public SMGlobalClass, public IPlayerManager { + friend class CPlayer; public: PlayerManager(); ~PlayerManager(); diff --git a/public/IPlayerHelpers.h b/public/IPlayerHelpers.h index 34e3aa8e..18efba8b 100644 --- a/public/IPlayerHelpers.h +++ b/public/IPlayerHelpers.h @@ -41,7 +41,7 @@ #include #define SMINTERFACE_PLAYERMANAGER_NAME "IPlayerManager" -#define SMINTERFACE_PLAYERMANAGER_VERSION 6 +#define SMINTERFACE_PLAYERMANAGER_VERSION 7 struct edict_t; class IPlayerInfo; @@ -147,6 +147,42 @@ namespace SourceMod * @return IPlayerInfo pointer, or NULL if none. */ virtual IPlayerInfo *GetPlayerInfo() =0; + + /** + * @brief Runs through Core's admin authorization checks. If the + * client is already an admin, no checks are performed. + * + * Note that this function operates solely against the in-memory admin + * cache. It will check steamids, IPs, names, and verify a password + * if one exists. To implement other authentication schemes, simply + * don't call this function and use IGamePlayer::SetAdminId() instead. + * + * @return True if access changed, false otherwise. + */ + virtual bool RunAdminCacheChecks() =0; + + /** + * @brief Notifies all listeners that the client has completed + * all of your post-connection (in-game, auth, admin) checks. + * + * If you returned "false" from OnClientPreAdminCheck(), you must + * ALWAYS manually invoke this function, even if RunAdminCacheChecks() + * failed or you did not assign an AdminId. Failure to call this + * function could result in plugins (such as reservedslots) not + * working properly. + * + * If you are implementing asynchronous fetches, and the client + * disconnects during your fetching process, you should make sure to + * recognize that case and not call this function. That is, do not + * call this function on mismatched PreCheck calls, or on disconnected + * clients. A good way to check this is to pass userids around, which + * are unique per client connection. + * + * Calling this has no effect if it has already been called on the + * given client (thus it is safe for multiple asynchronous plugins to + * call it at various times). + */ + virtual void NotifyPostAdminChecks() =0; }; /** @@ -230,6 +266,41 @@ namespace SourceMod virtual void OnServerActivated(int max_clients) { } + + /** + * @brief Called once a client is authorized and fully in-game, but + * before admin checks are done. This can be used to override the + * default admin checks for a client. + * + * By default, this function allows the authentication process to + * continue as normal. If you need to delay the cache searching + * process in order to get asynchronous data, then return false here. + * + * If you return false, you must call IPlayerManager::NotifyPostAdminCheck + * for the same client, or else the OnClientPostAdminCheck callback will + * never be called. + * + * @param client Client index. + * @return True to continue normally, false to override + * the authentication process. + */ + virtual bool OnClientPreAdminCheck(int client) + { + return true; + } + + /** + * @brief Called once a client is authorized and fully in-game, and + * after all post-connection authorizations have been passed. If the + * client does not have an AdminId by this stage, it means that no + * admin entry was in the cache that matched, and the user could not + * be authenticated as an admin. + * + * @param client Client index. + */ + virtual void OnClientPostAdminCheck(int client) + { + } }; #define COMMAND_FILTER_ALIVE (1<<0) /**< Only allow alive players */