/** * [CSRD] SourceCBL Integration * * An optimized version of the SourceCBL plugin. */ #pragma semicolon 1 #include #include #pragma newdecls required #include #define PLUGIN_VERSION "0.1.0" public Plugin myinfo = { name = "[CSRD] SourceCBL Integration", author = "nosoop", description = "Personal integartion of the SourceCBL API.", version = PLUGIN_VERSION, url = "https://git.csrd.science/" } #define INVALID_USERID -1 #define SOURCECBL_REQUEST_BAN_ENDPOINT "https://sourcecbl.com/api/steam/" #define SOURCECBL_API_TIMEOUT 5 enum APIResponse { Response_Success = 0, Response_NotAuthorized }; char g_CommunityKickLog[PLATFORM_MAX_PATH]; public void OnPluginStart() { CreateConVar("sm_scbl_enabled", "1", "Teamwork.TF convar for A2S_RULES's sake. Does absolutely nothing else.", FCVAR_NOTIFY); // really though, does this actually matter? we're just going to skip the thing anyways int port = FindConVar("hostport").IntValue; char portString[6]; IntToString(port, portString, sizeof(portString)); RegAdminCmd("cbl_check_steamid", CheckCBLStatus, ADMFLAG_ROOT); BuildPath(Path_SM, g_CommunityKickLog, sizeof(g_CommunityKickLog), "logs/sourcecbl.log"); } public void OnClientAuthorized(int client) { if (!IsFakeClient(client) && !SkipCommunityBanListCheck(client)) { RequestClientBanStatus(client); } } bool SkipCommunityBanListCheck(int client) { // TODO check if account is whitelisted (command group / keyvalues / clientprefs cache, etc.) return CheckCommandAccess(client, "cbl_whitelist", 0); } void RequestClientBanStatus(int client) { char steamid64[64]; if (GetClientAuthId(client, AuthId_SteamID64, steamid64, sizeof(steamid64))) { QueryBanList(steamid64, GetClientUserId(client)); } } public Action CheckCBLStatus(int client, int argc) { char steamid64[64]; GetCmdArgString(steamid64, sizeof(steamid64)); StripQuotes(steamid64); // TODO make sure this is a valid steamid QueryBanList(steamid64); } void QueryBanList(const char[] steamid64, int userid = INVALID_USERID) { // we need to use the steamid64 later on down the line to verify the request even if the // player disconnects DataPack pack = new DataPack(); pack.WriteCell(userid); pack.WriteString(steamid64); HTTPRequestHandle request = Steam_CreateHTTPRequest(HTTPMethod_GET, SOURCECBL_REQUEST_BAN_ENDPOINT); Steam_SetHTTPRequestNetworkActivityTimeout(request, SOURCECBL_API_TIMEOUT); Steam_SetHTTPRequestGetOrPostParameter(request, "steamid", steamid64); Steam_SendHTTPRequest(request, OnAccountStatusReceived, pack); } public int OnAccountStatusReceived(HTTPRequestHandle request, bool bSuccess, HTTPStatusCode status, DataPack pack) { if (!bSuccess || status != HTTPStatusCode_OK) { return; } int maxlen = Steam_GetHTTPResponseBodySize(request); char[] buffer = new char[maxlen]; Steam_GetHTTPResponseBodyData(request, buffer, maxlen); Steam_ReleaseHTTPRequest(request); // they should've just gone with outputting VDF so I could just slap that shit into a buffer and call it a day // also would've preferred SteamID3 so I wouldn't have to use strings pack.Reset(); int userid = pack.ReadCell(); char steamid64[64]; pack.ReadString(steamid64, sizeof(steamid64)); delete pack; /** * The API response (currently) has SteamID64 strings as keys for players that are banned. * It's going to be a painus in the anus if they change the API again. */ JSONObject accountStatus = view_as(json_load(buffer)); if (accountStatus) { APIResponse apiStatus = view_as( accountStatus.GetInt("response", view_as(Response_Success)) ); JSONObjectIterator iterator; if (apiStatus == Response_Success && (iterator = JSONObjectIterator.From(accountStatus))) { char steamid64Key[64]; do { iterator.GetKey(steamid64Key, sizeof(steamid64Key)); if (StrEqual(steamid64Key, steamid64)) { // player in banlist, request ban if (userid != INVALID_USERID) { int client = GetClientOfUserId(userid); if (client) { KickClient(client, "You have been banned by SourceCBL for hacking / cheating." ... "\nVisit www.SourceCBL.com for more information."); LogToFile(g_CommunityKickLog, "Kicked \"%L\" from server.", client); } } } } while ((iterator.Next(accountStatus))); // iterator auto-closes } else if (apiStatus == Response_NotAuthorized) { // TODO shut down everything } delete accountStatus; } }