From a5b83801cca65cb397f87a5e30affca26b097fb9 Mon Sep 17 00:00:00 2001 From: Rushaway Date: Wed, 28 Oct 2026 15:38:06 +0100 Subject: [PATCH 1/2] fix(sql): async-safe comms queue, keep DB across maps, index-friendly SteamID lookups - sbpp_comms: replay queued blocks against the main database, not the SQLite queue; keep the connection across maps; only drop it on real connection-lost errors; SQLite queue queries get their own callback - SteamID lookups use authid IN ('STEAM_0:x','STEAM_1:x') instead of REGEXP / LIKE so the authid indexes are usable (main, comms, checker, sleuth); the banlog insert reuses the bid VerifyBan already has - sbpp_main / sbpp_checker / sbpp_sleuth retry failed connects on a timer; null guards on sm_banip / sm_unban / sm_addban / reports - Legacy CloseHandle / WritePackString / ReadPackCell and raw client indexes in async callbacks replaced Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 20 ++ .../sourcemod/scripting/sbpp_checker.sp | 34 ++-- game/addons/sourcemod/scripting/sbpp_comms.sp | 154 +++++++++------ game/addons/sourcemod/scripting/sbpp_main.sp | 178 +++++++++++------- .../addons/sourcemod/scripting/sbpp_sleuth.sp | 20 +- 5 files changed, 264 insertions(+), 142 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbd43d7e5..6026e88fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,26 @@ Legend: ? = Other stuff ``` +(28/10/26): Version 2.2.7-srcdslab +----------------------- +``` +01. ! sbpp_comms: mutes / gags queued while the database was + unreachable were replayed against the local SQLite queue + instead of the main database, so they stayed queued and never + reached the web panel +02. + sbpp_comms keeps its database connection across map changes + instead of reconnecting (and re-checking every player) on + each map; a plain SQL error or a failed SQLite queue query no + longer drops the main connection +03. + SteamID lookups on player connect (bans, comms, SourceSleuth, + Bans Checker) and admin lookups no longer use REGEXP / LIKE, + so MySQL can use the authid indexes instead of a full scan +04. ! sbpp_main, sbpp_checker and SourceSleuth retry a failed database + connection on a timer; sbpp_checker no longer unloads itself, + and sm_banip / sm_unban / sm_addban / reports reply with an + error instead of throwing when the database is unavailable +``` + (26/10/26): Version 2.2.5-srcdslab (**Run updater required**) ----------------------- ``` diff --git a/game/addons/sourcemod/scripting/sbpp_checker.sp b/game/addons/sourcemod/scripting/sbpp_checker.sp index 30f69288c..1883a210c 100644 --- a/game/addons/sourcemod/scripting/sbpp_checker.sp +++ b/game/addons/sourcemod/scripting/sbpp_checker.sp @@ -135,12 +135,18 @@ public void OnDatabaseConnected(Database db, const char[] error, any data) if (db == null) { - g_DatabaseState = DatabaseState_None; - SetFailState("Failed to connect to SourceBans DB, %s", error); + LogError("SourceChecker: Failed to connect to SourceBans DB, retrying: %s", error); + g_DatabaseState = DatabaseState_Wait; + CreateTimer(30.0, Timer_ReconnectDB); + return; } g_DatabaseState = DatabaseState_Connected; g_DB = db; + + // Check the players who connected while the database was unavailable + // (or before it connected on a late load). + LateLoading(); } public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) @@ -204,23 +210,23 @@ public void OnClientAuthorized(int client, const char[] auth) // No usable IP (e.g. SDR client): never match IP bans, an empty address // would match any type=1 row with a blank IP. - char query[512], ip[30], ipCondition[64]; + char query[1024], ip[30], ipCondition[64]; if (SBPP_GetClientPublicIP(client, ip, sizeof(ip))) FormatEx(ipCondition, sizeof(ipCondition), "ip = '%s'", ip); else strcopy(ipCondition, sizeof(ipCondition), "1 = 0"); FormatEx(query, sizeof(query), - "SELECT COUNT(bid) FROM %s_bans WHERE (type = 0 AND authid LIKE 'STEAM_%%:%s') \ + "SELECT COUNT(bid) FROM %s_bans WHERE (type = 0 AND authid IN ('STEAM_0:%s', 'STEAM_1:%s')) \ UNION ALL \ SELECT COUNT(bid) FROM %s_bans WHERE (type = 1 AND %s) \ UNION ALL \ - SELECT COUNT(bid) FROM %s_comms WHERE authid LIKE 'STEAM_%%:%s' AND type = 1 \ + SELECT COUNT(bid) FROM %s_comms WHERE authid IN ('STEAM_0:%s', 'STEAM_1:%s') AND type = 1 \ UNION ALL \ - SELECT COUNT(bid) FROM %s_comms WHERE authid LIKE 'STEAM_%%:%s' AND type = 2", - g_DatabasePrefix, auth[8], + SELECT COUNT(bid) FROM %s_comms WHERE authid IN ('STEAM_0:%s', 'STEAM_1:%s') AND type = 2", + g_DatabasePrefix, auth[8], auth[8], g_DatabasePrefix, ipCondition, - g_DatabasePrefix, auth[8], - g_DatabasePrefix, auth[8] + g_DatabasePrefix, auth[8], auth[8], + g_DatabasePrefix, auth[8], auth[8] ); g_DB.Query(OnConnectBanCheck, query, GetClientUserId(client), DBPrio_Low); @@ -315,7 +321,7 @@ public Action OnListSourceBansCmd(int client, int args) FormatEx(ipCondition, sizeof(ipCondition), "ip = '%s'", ip); else strcopy(ipCondition, sizeof(ipCondition), "1 = 0"); - FormatEx(query, sizeof(query), "SELECT created, %s_admins.user, ends, length, reason, RemoveType FROM %s_bans LEFT JOIN %s_admins ON %s_bans.aid = %s_admins.aid WHERE ((type = 0 AND %s_bans.authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND %s)) AND ((length > '0' AND ends > UNIX_TIMESTAMP()) OR RemoveType IS NOT NULL)", g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, auth[8], ipCondition); + FormatEx(query, sizeof(query), "SELECT created, %s_admins.user, ends, length, reason, RemoveType FROM %s_bans LEFT JOIN %s_admins ON %s_bans.aid = %s_admins.aid WHERE ((type = 0 AND %s_bans.authid IN ('STEAM_0:%s', 'STEAM_1:%s')) OR (type = 1 AND %s)) AND ((length > '0' AND ends > UNIX_TIMESTAMP()) OR RemoveType IS NOT NULL)", g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, auth[8], auth[8], ipCondition); char targetName[MAX_NAME_LENGTH]; GetClientName(target, targetName, sizeof(targetName)); @@ -481,7 +487,7 @@ public Action OnListSourceCommsCmd(int client, int args) } char query[1024]; - FormatEx(query, sizeof(query), "SELECT created, %s_admins.user, ends, length, reason, RemoveType, type FROM %s_comms LEFT JOIN %s_admins ON %s_comms.aid = %s_admins.aid WHERE %s_comms.authid REGEXP '^STEAM_[0-9]:%s$' AND ((length > '0' AND ends > UNIX_TIMESTAMP()) OR RemoveType IS NOT NULL)", g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, auth[8]); + FormatEx(query, sizeof(query), "SELECT created, %s_admins.user, ends, length, reason, RemoveType, type FROM %s_comms LEFT JOIN %s_admins ON %s_comms.aid = %s_admins.aid WHERE %s_comms.authid IN ('STEAM_0:%s', 'STEAM_1:%s') AND ((length > '0' AND ends > UNIX_TIMESTAMP()) OR RemoveType IS NOT NULL)", g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, auth[8], auth[8]); char targetName[MAX_NAME_LENGTH]; GetClientName(target, targetName, sizeof(targetName)); @@ -738,6 +744,10 @@ stock void LateLoading() { if (!IsClientConnected(i) || !IsClientInGame(i) || IsFakeClient(i) || !IsClientAuthorized(i)) continue; + + // Already checked + if (g_iBanCounts[i] != -1) + continue; GetClientAuthId(i, AuthId_Steam2, sSteam32ID, sizeof(sSteam32ID)); OnClientAuthorized(i, sSteam32ID); @@ -764,7 +774,7 @@ void CheckDBConnection(const char[] error) if (g_DatabaseState != DatabaseState_Wait) { g_DatabaseState = DatabaseState_Wait; - CreateTimer(2.0, Timer_ReconnectDB, _, TIMER_FLAG_NO_MAPCHANGE); + CreateTimer(2.0, Timer_ReconnectDB); } } else diff --git a/game/addons/sourcemod/scripting/sbpp_comms.sp b/game/addons/sourcemod/scripting/sbpp_comms.sp index bda525370..af4954dcf 100644 --- a/game/addons/sourcemod/scripting/sbpp_comms.sp +++ b/game/addons/sourcemod/scripting/sbpp_comms.sp @@ -125,6 +125,7 @@ int , g_iPeskyPanels[MAXPLAYERS + 1][PeskyPanels] , g_iConnectLock = 0 , g_iSequence = 0 + , g_iQueueInFlight = 0 // Queue SELECT + its pending MySQL inserts , DefaultTime = 30 , DisUBImCheck = 0 , ConsoleImmunity = 0 @@ -263,16 +264,12 @@ public void OnConfigsExecuted() public void OnMapStart() { ReadConfig(); -} -public void OnMapEnd() -{ - // Clean up on map end just so we can start a fresh connection when we need it later. - // Also it is necessary for using SQL_SetCharset + // The connection is kept across map changes (the charset set in GotDatabase stays on it, + // also across the driver's automatic reconnects), so flush the blocks queued by failed + // inserts here: before, this only happened because the handle was reopened every map. if (g_hDatabase) - delete g_hDatabase; - - g_hDatabase = null; + ProcessQueue(); } @@ -365,9 +362,9 @@ public void VerifyBlock(int client) LEFT JOIN %!s_admins AS a ON a.aid = c.aid \ LEFT JOIN %!s_srvgroups AS g ON g.name = a.srv_group \ WHERE RemoveType IS NULL \ - AND c.authid REGEXP '^STEAM_[0-9]:%s$' \ + AND c.authid IN ('STEAM_0:%s', 'STEAM_1:%s') \ AND (length = '0' OR ends > UNIX_TIMESTAMP())", - DatabasePrefix, DatabasePrefix, DatabasePrefix, g_sSteamIDs[client][8]) >= sizeof(Query) - 1) + DatabasePrefix, DatabasePrefix, DatabasePrefix, g_sSteamIDs[client][8], g_sSteamIDs[client][8]) >= sizeof(Query) - 1) { LogError("VerifyBlock query truncated for %L", client); return; @@ -1371,17 +1368,20 @@ public void GotDatabase(Database db, const char[] error, any data) } g_iConnectLock = 0; - g_DatabaseState = DatabaseState_Connected; - g_hDatabase = db; - // See if the connection is valid. If not, don't un-mark the caches - // as needing rebuilding, in case the next connection request works. - if (!g_hDatabase) + // See if the connection is valid. If not, retry after a delay instead of + // reconnecting on every block check while the database is down. + if (!db) { LogError("Connecting to database failed: %s", error); + g_DatabaseState = DatabaseState_Wait; + CreateTimer(RetryTime, Timer_StopWait); return; } + g_DatabaseState = DatabaseState_Connected; + g_hDatabase = db; + // Set character set to UTF8MB4 in the database // Use SQL_SetCharset to ensure charset is set synchronously before any operations if (!db.SetCharset("utf8mb4")) @@ -1394,9 +1394,7 @@ public void GotDatabase(Database db, const char[] error, any data) } // Process queue - SQLiteDB.Query(Query_ProcessQueue, - "SELECT id, steam_id, time, start_time, reason, name, admin_id, admin_ip, type \ - FROM queue2"); + ProcessQueue(); // Force recheck players ForcePlayersRecheck(); @@ -1417,7 +1415,7 @@ public void Query_AddBlockInsert(Database db, DBResultSet results, const char[] int type = dataPack.ReadCell(); dataPack.ReadString(reason, sizeof(reason)); - if (DB_Conn_Lost(results) || error[0]) + if (DB_Conn_Lost(results, error) || error[0]) { LogError("Query_AddBlockInsert failed: %s", error); @@ -1459,7 +1457,7 @@ public void Query_UnBlockSelect(Database db, DBResultSet results, const char[] e bool hasErrors = false; // If error is not an empty string the query failed - if (DB_Conn_Lost(results) || error[0] != '\0') + if (DB_Conn_Lost(results, error) || error[0] != '\0') { LogError("Query_UnBlockSelect failed: %s", error); if (admin && IsClientInGame(admin)) @@ -1475,7 +1473,7 @@ public void Query_UnBlockSelect(Database db, DBResultSet results, const char[] e } // If there was no results then a ban does not exist for that id - if (!DB_Conn_Lost(results) && !results.RowCount) + if (!DB_Conn_Lost(results, error) && !results.RowCount) { if (admin && IsClientInGame(admin)) { @@ -1657,7 +1655,7 @@ public void Query_UnBlockUpdate(Database db, DBResultSet results, const char[] e dataPack.ReadString(targetAuth, sizeof(targetAuth)); delete dataPack; - if (DB_Conn_Lost(results) || error[0] != '\0') + if (DB_Conn_Lost(results, error) || error[0] != '\0') { LogError("Query_UnBlockUpdate failed: %s", error); if (admin && IsClientInGame(admin)) @@ -1705,6 +1703,16 @@ public void Query_ProcessQueue(Database db, DBResultSet results, const char[] er if (results == null || error[0]) { LogError("Query_ProcessQueue failed: %s", error); + g_iQueueInFlight--; + return; + } + + // db is the local SQLite queue this SELECT ran on: the blocks have to be + // inserted into the main database (g_hDatabase), which is also the handle + // whose escaping rules the INSERT must be built with. + if (!DB_Connect()) + { + g_iQueueInFlight--; return; } @@ -1732,16 +1740,14 @@ public void Query_ProcessQueue(Database db, DBResultSet results, const char[] er results.FetchString(7, adminIp, sizeof(adminIp)); int type = results.FetchInt(8); - if (!DB_Connect()) - continue; // all blocks should be entered into db! - if (db.Format(query, sizeof(query), + if (g_hDatabase.Format(query, sizeof(query), "INSERT INTO %!s_comms (authid, name, created, ends, length, reason, aid, adminIp, admin_name, sid, type) \ VALUES ('%s', '%s', %d, %d, %d, '%s', \ - IFNULL((SELECT aid FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '0'), \ - '%s', IFNULL((SELECT user FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), ''), %d, %d)", - DatabasePrefix, auth, name, startTime, (startTime + (time * 60)), (time * 60), reason, DatabasePrefix, adminAuth, adminAuth[8], adminIp, DatabasePrefix, adminAuth, adminAuth[8], serverID, type) >= sizeof(query) - 1) + IFNULL((SELECT aid FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), '0'), \ + '%s', IFNULL((SELECT user FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), ''), %d, %d)", + DatabasePrefix, auth, name, startTime, (startTime + (time * 60)), (time * 60), reason, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], adminIp, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], serverID, type) >= sizeof(query) - 1) { LogError("Query_ProcessQueue insert query truncated"); continue; @@ -1749,14 +1755,19 @@ public void Query_ProcessQueue(Database db, DBResultSet results, const char[] er #if defined LOG_QUERIES LogToFile(logQuery, "Query_ProcessQueue. QUERY: %s", query); #endif - db.Query(Query_AddBlockFromQueue, query, id); + g_iQueueInFlight++; + g_hDatabase.Query(Query_AddBlockFromQueue, query, id); } + + g_iQueueInFlight--; } public void Query_AddBlockFromQueue(Database db, DBResultSet results, const char[] error, any data) { + g_iQueueInFlight--; + char query[512]; - if (error[0] == '\0') + if (!DB_Conn_Lost(results, error) && error[0] == '\0') { // The insert was successful so delete the record from the queue if (SQLiteDB.Format(query, sizeof(query), @@ -1770,13 +1781,24 @@ public void Query_AddBlockFromQueue(Database db, DBResultSet results, const char #if defined LOG_QUERIES LogToFile(logQuery, "Query_AddBlockFromQueue. QUERY: %s", query); #endif - SQLiteDB.Query(Query_ErrorCheck, query); + SQLiteDB.Query(Query_QueueErrorCheck, query); + } + else + { + LogError("Query_AddBlockFromQueue failed: %s", error); } } +// Errors of queries on the local SQLite queue: never touch the MySQL connection. +public void Query_QueueErrorCheck(Database db, DBResultSet results, const char[] error, any data) +{ + if (results == null) + LogError("%T (%s)", "Failed to query database", LANG_SERVER, error); +} + public void Query_ErrorCheck(Database db, DBResultSet results, const char[] error, any data) { - if (DB_Conn_Lost(results) || error[0]) + if (DB_Conn_Lost(results, error) || error[0]) LogError("%T (%s)", "Failed to query database", LANG_SERVER, error); } @@ -1791,18 +1813,18 @@ public void Query_VerifyBlock(Database db, DBResultSet results, const char[] err if (!client) return; - /* Failure happen. Do retry with delay */ - if (DB_Conn_Lost(results)) + /* Failure happen. Do retry with delay (longer if the connection is still up, i.e. a plain SQL error) */ + if (DB_Conn_Lost(results, error)) { LogError("Query_VerifyBlock failed: %s", error); - ClientRecheck(client); + ClientRecheck(client, g_hDatabase ? RetryTime : 1.0); return; } //SELECT (c.ends - UNIX_TIMESTAMP()) as remaining, c.length, c.type, c.created, c.reason, a.user, //IF (a.immunity>=g.immunity, a.immunity, IFNULL(g.immunity,0)) as immunity, c.aid, c.sid, c.authid //FROM %s_comms c LEFT JOIN %s_admins a ON a.aid=c.aid LEFT JOIN %s_srvgroups g ON g.name = a.srv_group - //WHERE c.authid REGEXP '^STEAM_[0-9]:%s$' AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", + //WHERE c.authid IN ('STEAM_0:%s', 'STEAM_1:%s') AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", if (results.RowCount > 0) { while (results.FetchRow()) @@ -2172,9 +2194,9 @@ stock void setMute(int client, int length, const char[] clientAuth) } } -stock void ClientRecheck(int client) +stock void ClientRecheck(int client, float delay = 1.0) { - CreateTimer(1.0, Timer_ClientRecheck, g_iUserIDs[client], TIMER_FLAG_NO_MAPCHANGE); + CreateTimer(delay, Timer_ClientRecheck, g_iUserIDs[client], TIMER_FLAG_NO_MAPCHANGE); } stock bool IsInvalidSteamID(int client) @@ -2212,10 +2234,16 @@ stock bool DB_Connect() return false; } -stock bool DB_Conn_Lost(DBResultSet db) +// Returns true when the MySQL query failed. The handle is only dropped (and a +// reconnect scheduled) when the error says the connection itself is gone: a +// plain SQL error must not tear down the connection every other query shares. +stock bool DB_Conn_Lost(DBResultSet db, const char[] error) { if (db == null) { + if (!IsConnectionError(error)) + return true; + if (g_hDatabase != null) { LogError("Lost connection to DB. Reconnect after delay."); @@ -2223,10 +2251,11 @@ stock bool DB_Conn_Lost(DBResultSet db) g_hDatabase = null; } + // No TIMER_FLAG_NO_MAPCHANGE: a map change must not leave the state stuck on Wait. if (g_DatabaseState != DatabaseState_Wait) { g_DatabaseState = DatabaseState_Wait; - CreateTimer(RetryTime, Timer_StopWait, _, TIMER_FLAG_NO_MAPCHANGE); + CreateTimer(RetryTime, Timer_StopWait); } return true; @@ -2235,6 +2264,27 @@ stock bool DB_Conn_Lost(DBResultSet db) return false; } +stock bool IsConnectionError(const char[] error) +{ + return StrContains(error, "Lost connection", false) != -1 + || StrContains(error, "gone away", false) != -1 + || StrContains(error, "Can't connect", false) != -1 + || StrContains(error, "Broken pipe", false) != -1; +} + +stock void ProcessQueue() +{ + // Don't read the queue again while rows from the previous pass are still + // being inserted, or they would be sent twice. + if (g_iQueueInFlight > 0) + return; + + g_iQueueInFlight = 1; + SQLiteDB.Query(Query_ProcessQueue, + "SELECT id, steam_id, time, start_time, reason, name, admin_id, admin_ip, type \ + FROM queue2"); +} + stock void InitializeBackupDB() { char error[256]; @@ -2244,7 +2294,7 @@ stock void InitializeBackupDB() SetFailState(error); } - SQLiteDB.Query(Query_ErrorCheck, + SQLiteDB.Query(Query_QueueErrorCheck, "CREATE TABLE IF NOT EXISTS queue2 ( \ id INTEGER PRIMARY KEY, \ steam_id TEXT, \ @@ -2621,7 +2671,7 @@ stock void ProcessUnBlock(int client, int targetId = 0, int type, char[] sReason char query[4096]; if (g_hDatabase.Format(query, sizeof(query), "SELECT c.bid, \ - IFNULL((SELECT aid FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '0') as iaid, \ + IFNULL((SELECT aid FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), '0') as iaid, \ c.aid, \ IF (a.immunity>=g.immunity, a.immunity, IFNULL(g.immunity,0)) as immunity, \ c.type \ @@ -2629,10 +2679,10 @@ stock void ProcessUnBlock(int client, int targetId = 0, int type, char[] sReason LEFT JOIN %!s_admins AS a ON a.aid = c.aid \ LEFT JOIN %!s_srvgroups AS g ON g.name = a.srv_group \ WHERE RemoveType IS NULL \ - AND (c.authid = '%s' OR c.authid REGEXP '^STEAM_[0-9]:%s$') \ + AND c.authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s') \ AND (length = '0' OR ends > UNIX_TIMESTAMP()) \ AND %!s", - DatabasePrefix, adminAuth, adminAuth[8], DatabasePrefix, DatabasePrefix, DatabasePrefix, targetAuth, targetAuth[8], typeWHERE) >= sizeof(query) - 1) + DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], DatabasePrefix, DatabasePrefix, DatabasePrefix, targetAuth, targetAuth[8], targetAuth[8], typeWHERE) >= sizeof(query) - 1) { LogError("ProcessUnBlock select query truncated"); return; @@ -2813,7 +2863,7 @@ stock void InsertTempBlock(int length, int type, const char[] name, const char[] LogToFile(logQuery, "InsertTempBlock. QUERY: %s", sQuery); #endif - SQLiteDB.Query(Query_ErrorCheck, sQuery); + SQLiteDB.Query(Query_QueueErrorCheck, sQuery); } /** @@ -3125,14 +3175,12 @@ stock void MarkClientAsGagged(int target, int time = NOW, int length = -1, const stock void CloseMuteExpireTimer(int target) { - if (g_hMuteExpireTimer[target] != INVALID_HANDLE && CloseHandle(g_hMuteExpireTimer[target])) - g_hMuteExpireTimer[target] = INVALID_HANDLE; + delete g_hMuteExpireTimer[target]; } stock void CloseGagExpireTimer(int target) { - if (g_hGagExpireTimer[target] != INVALID_HANDLE && CloseHandle(g_hGagExpireTimer[target])) - g_hGagExpireTimer[target] = INVALID_HANDLE; + delete g_hGagExpireTimer[target]; } stock void CreateMuteExpireTimer(int target, int remainingTime = 0) @@ -3249,8 +3297,8 @@ stock void SavePunishment(int admin = 0, int target, int type, int length = -1, // bid authid name created ends lenght reason aid adminip sid removedBy removedType removedon type ureason if (g_hDatabase.Format(sQueryAdm, sizeof(sQueryAdm), - "IFNULL((SELECT aid FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), 0)", - DatabasePrefix, adminAuth, adminAuth[8]) >= sizeof(sQueryAdm) - 1) + "IFNULL((SELECT aid FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), 0)", + DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8]) >= sizeof(sQueryAdm) - 1) { LogError("SavePunishment admin subquery truncated"); return; @@ -3258,8 +3306,8 @@ stock void SavePunishment(int admin = 0, int target, int type, int length = -1, char sQueryAdmName[512]; if (g_hDatabase.Format(sQueryAdmName, sizeof(sQueryAdmName), - "IFNULL((SELECT user FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '')", - DatabasePrefix, adminAuth, adminAuth[8]) >= sizeof(sQueryAdmName) - 1) + "IFNULL((SELECT user FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), '')", + DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8]) >= sizeof(sQueryAdmName) - 1) { LogError("SavePunishment admin name subquery truncated"); return; diff --git a/game/addons/sourcemod/scripting/sbpp_main.sp b/game/addons/sourcemod/scripting/sbpp_main.sp index 7e4544e32..9b19ac16c 100644 --- a/game/addons/sourcemod/scripting/sbpp_main.sp +++ b/game/addons/sourcemod/scripting/sbpp_main.sp @@ -214,10 +214,8 @@ public void OnPluginStart() // Catch config error and show link to FAQ if (!SQL_CheckConfig("sourcebans")) { - if (ReasonMenuHandle != INVALID_HANDLE) - CloseHandle(ReasonMenuHandle); - if (HackingMenuHandle != INVALID_HANDLE) - CloseHandle(HackingMenuHandle); + delete ReasonMenuHandle; + delete HackingMenuHandle; LogToFile(logFile, "Database failure: Could not find Database conf \"sourcebans\". See Docs: https://sbpp.github.io/docs/"); SetFailState("Database failure: Could not find Database conf \"sourcebans\""); return; @@ -384,11 +382,11 @@ void CheckClientBan(int client) if (g_sPlayerIP[client][0] == '\0') { /* No usable IP (e.g. SDR client): only check by SteamID. */ - written = DB.Format(Query, sizeof(Query), "SELECT bid, ip FROM %!s_bans WHERE (type = 0 AND authid REGEXP '^STEAM_[0-9]:%s$') AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, g_sSteamIDs[client][8]); + written = DB.Format(Query, sizeof(Query), "SELECT bid, ip FROM %!s_bans WHERE (type = 0 AND authid IN ('STEAM_0:%s', 'STEAM_1:%s')) AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, g_sSteamIDs[client][8], g_sSteamIDs[client][8]); } else { - written = DB.Format(Query, sizeof(Query), "SELECT bid, ip FROM %!s_bans WHERE ((type = 0 AND authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND ip = '%s')) AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, g_sSteamIDs[client][8], g_sPlayerIP[client]); + written = DB.Format(Query, sizeof(Query), "SELECT bid, ip FROM %!s_bans WHERE ((type = 0 AND authid IN ('STEAM_0:%s', 'STEAM_1:%s')) OR (type = 1 AND ip = '%s')) AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, g_sSteamIDs[client][8], g_sSteamIDs[client][8], g_sPlayerIP[client]); } if (written >= sizeof(Query) - 1) @@ -632,6 +630,12 @@ public Action CommandBanIp(int client, int args) strcopy(adminIp, sizeof(adminIp), g_sPlayerIP[client]); } + if (DB == INVALID_HANDLE) + { + ReplyToCommand(client, "%sThe SourceBans database is not available, try again later.", Prefix); + return Plugin_Handled; + } + char sQuery[256]; if (DB.Format(sQuery, sizeof(sQuery), "SELECT bid FROM %!s_bans WHERE type = 1 AND ip = '%s' AND (length = 0 OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, arg) >= sizeof(sQuery) - 1) @@ -693,6 +697,12 @@ public Action CommandUnban(int client, int args) strcopy(adminAuth, sizeof(adminAuth), g_sSteamIDs[client]); } + if (DB == INVALID_HANDLE) + { + ReplyToCommand(client, "%sThe SourceBans database is not available, try again later.", Prefix); + return Plugin_Handled; + } + char query[256]; if (strncmp(arg, "STEAM_", 6) == 0) @@ -788,6 +798,12 @@ public Action CommandAddBan(int client, int args) strcopy(adminIp, sizeof(adminIp), g_sPlayerIP[client]); } + if (DB == INVALID_HANDLE) + { + ReplyToCommand(client, "%sThe SourceBans database is not available, try again later.", Prefix); + return Plugin_Handled; + } + char sQuery[256]; if (DB.Format(sQuery, sizeof sQuery, "SELECT bid FROM %!s_bans WHERE type = 0 AND authid = '%s' AND (length = 0 OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, authid) >= sizeof(sQuery) - 1) @@ -981,10 +997,7 @@ public int HackingSelected(Menu menu, MenuAction action, int param1, int param2) DataPack ReasonPack = Pack.ReadCell(); - if (ReasonPack != INVALID_HANDLE) - { - CloseHandle(ReasonPack); - } + delete ReasonPack; delete Pack; PlayerDataPack[param1] = null; @@ -1150,6 +1163,9 @@ public void GotDatabase(Database db, const char[] error, any data) LogToFile(logFile, "Database failure: %s. See Docs: https://sbpp.github.io/docs/", error); g_bConnecting = false; + // Retry on a timer instead of waiting for the next admin cache rebuild (map change) + CreateTimer(RetryTime, Timer_RetryConnect); + // Parse the overrides backup! ParseBackupConfig_Overrides(); return; @@ -1397,10 +1413,10 @@ public void SelectBanIpCallback(Database db, DBResultSet results, const char[] e if (serverID == -1) { if (db.Format(Query, sizeof(Query), "INSERT INTO %!s_bans (type, ip, authid, name, created, ends, length, reason, aid, adminIp, admin_name, sid, country) VALUES \ - (1, '%s', '%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', (SELECT aid FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '%s', \ - IFNULL((SELECT user FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), ''), \ + (1, '%s', '%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', (SELECT aid FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), '%s', \ + IFNULL((SELECT user FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), ''), \ (SELECT sid FROM %!s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1), ' ')", - DatabasePrefix, ip, targetAuth, targetName, (minutes * 60), (minutes * 60), reason, DatabasePrefix, adminAuth, adminAuth[8], adminIp, DatabasePrefix, adminAuth, adminAuth[8], DatabasePrefix, ServerIp, ServerPort) >= sizeof(Query) - 1) + DatabasePrefix, ip, targetAuth, targetName, (minutes * 60), (minutes * 60), reason, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], adminIp, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], DatabasePrefix, ServerIp, ServerPort) >= sizeof(Query) - 1) { LogError("SelectBanIpCallback insert query truncated"); delete dataPack; @@ -1408,10 +1424,10 @@ public void SelectBanIpCallback(Database db, DBResultSet results, const char[] e } } else { if (db.Format(Query, sizeof(Query), "INSERT INTO %!s_bans (type, ip, authid, name, created, ends, length, reason, aid, adminIp, admin_name, sid, country) VALUES \ - (1, '%s', '%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', (SELECT aid FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '%s', \ - IFNULL((SELECT user FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), ''), \ + (1, '%s', '%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', (SELECT aid FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), '%s', \ + IFNULL((SELECT user FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), ''), \ %d, ' ')", - DatabasePrefix, ip, targetAuth, targetName, (minutes * 60), (minutes * 60), reason, DatabasePrefix, adminAuth, adminAuth[8], adminIp, DatabasePrefix, adminAuth, adminAuth[8], serverID) >= sizeof(Query) - 1) + DatabasePrefix, ip, targetAuth, targetName, (minutes * 60), (minutes * 60), reason, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], adminIp, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], serverID) >= sizeof(Query) - 1) { LogError("SelectBanIpCallback insert query truncated"); delete dataPack; @@ -1532,8 +1548,8 @@ public void SelectUnbanCallback(Database db, DBResultSet results, const char[] e int bid = results.FetchInt(0); char query[1024]; - if (db.Format(query, sizeof(query), "UPDATE %!s_bans SET RemovedBy = (SELECT aid FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), RemoveType = 'U', RemovedOn = UNIX_TIMESTAMP(), ureason = '%s' WHERE bid = %d", - DatabasePrefix, DatabasePrefix, adminAuth, adminAuth[8], reason, bid) >= sizeof(query) - 1) + if (db.Format(query, sizeof(query), "UPDATE %!s_bans SET RemovedBy = (SELECT aid FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), RemoveType = 'U', RemovedOn = UNIX_TIMESTAMP(), ureason = '%s' WHERE bid = %d", + DatabasePrefix, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], reason, bid) >= sizeof(query) - 1) { LogError("SelectUnbanCallback update query truncated"); delete dataPack; @@ -1628,10 +1644,10 @@ public void SelectAddbanCallback(Database db, DBResultSet results, const char[] if (serverID == -1) { if (db.Format(Query, sizeof(Query), "INSERT INTO %!s_bans (authid, name, created, ends, length, reason, aid, adminIp, admin_name, sid, country) VALUES \ - ('%s', '', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', (SELECT aid FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '%s', \ - IFNULL((SELECT user FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), ''), \ + ('%s', '', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', (SELECT aid FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), '%s', \ + IFNULL((SELECT user FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), ''), \ (SELECT sid FROM %!s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1), ' ')", - DatabasePrefix, authid, (minutes * 60), (minutes * 60), reason, DatabasePrefix, adminAuth, adminAuth[8], adminIp, DatabasePrefix, adminAuth, adminAuth[8], DatabasePrefix, ServerIp, ServerPort) >= sizeof(Query) - 1) + DatabasePrefix, authid, (minutes * 60), (minutes * 60), reason, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], adminIp, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], DatabasePrefix, ServerIp, ServerPort) >= sizeof(Query) - 1) { LogError("SelectAddbanCallback insert query truncated"); delete dataPack; @@ -1639,10 +1655,10 @@ public void SelectAddbanCallback(Database db, DBResultSet results, const char[] } } else { if (db.Format(Query, sizeof(Query), "INSERT INTO %!s_bans (authid, name, created, ends, length, reason, aid, adminIp, admin_name, sid, country) VALUES \ - ('%s', '', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', (SELECT aid FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '%s', \ - IFNULL((SELECT user FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), ''), \ + ('%s', '', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', (SELECT aid FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), '%s', \ + IFNULL((SELECT user FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), ''), \ %d, ' ')", - DatabasePrefix, authid, (minutes * 60), (minutes * 60), reason, DatabasePrefix, adminAuth, adminAuth[8], adminIp, DatabasePrefix, adminAuth, adminAuth[8], serverID) >= sizeof(Query) - 1) + DatabasePrefix, authid, (minutes * 60), (minutes * 60), reason, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], adminIp, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], serverID) >= sizeof(Query) - 1) { LogError("SelectAddbanCallback insert query truncated"); delete dataPack; @@ -1744,10 +1760,10 @@ public void ProcessQueueCallback(Database db, DBResultSet results, const char[] { if (DB.Format(query, sizeof(query), "INSERT INTO %!s_bans (ip, authid, name, created, ends, length, reason, aid, adminIp, admin_name, sid) VALUES \ - ('%s', '%s', '%s', %d, %d, %d, '%s', (SELECT aid FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '%s', \ - IFNULL((SELECT user FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), ''), \ + ('%s', '%s', '%s', %d, %d, %d, '%s', (SELECT aid FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), '%s', \ + IFNULL((SELECT user FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), ''), \ (SELECT sid FROM %!s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1))", - DatabasePrefix, ip, auth, name, startTime, startTime + time * 60, time * 60, reason, DatabasePrefix, adminAuth, adminAuth[8], adminIp, DatabasePrefix, adminAuth, adminAuth[8], DatabasePrefix, ServerIp, ServerPort) >= sizeof(query) - 1) + DatabasePrefix, ip, auth, name, startTime, startTime + time * 60, time * 60, reason, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], adminIp, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], DatabasePrefix, ServerIp, ServerPort) >= sizeof(query) - 1) { LogError("ProcessQueueCallback insert query truncated"); continue; @@ -1757,10 +1773,10 @@ public void ProcessQueueCallback(Database db, DBResultSet results, const char[] { if (DB.Format(query, sizeof(query), "INSERT INTO %!s_bans (ip, authid, name, created, ends, length, reason, aid, adminIp, admin_name, sid) VALUES \ - ('%s', '%s', '%s', %d, %d, %d, '%s', (SELECT aid FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '%s', \ - IFNULL((SELECT user FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), ''), \ + ('%s', '%s', '%s', %d, %d, %d, '%s', (SELECT aid FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), '%s', \ + IFNULL((SELECT user FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), ''), \ %d)", - DatabasePrefix, ip, auth, name, startTime, startTime + time * 60, time * 60, reason, DatabasePrefix, adminAuth, adminAuth[8], adminIp, DatabasePrefix, adminAuth, adminAuth[8], serverID) >= sizeof(query) - 1) + DatabasePrefix, ip, auth, name, startTime, startTime + time * 60, time * 60, reason, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], adminIp, DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], serverID) >= sizeof(query) - 1) { LogError("ProcessQueueCallback insert query truncated"); continue; @@ -1887,7 +1903,7 @@ public void VerifyBan(Database db, DBResultSet results, const char[] error, int { LogToFile(logFile, "Verify Ban Query Failed: %s", error); if (PlayerRecheck[client] == INVALID_HANDLE) - PlayerRecheck[client] = CreateTimer(RetryTime, ClientRecheck, client); + PlayerRecheck[client] = CreateTimer(RetryTime, ClientRecheck, userid); return; } @@ -1897,13 +1913,14 @@ public void VerifyBan(Database db, DBResultSet results, const char[] error, int if (results.RowCount > 0) { char Query[512]; + int iBid = 0; // Amending to ban record's IP field if (results.FetchRow()) { char sIP[16]; - int iBid = results.FetchInt(0); + iBid = results.FetchInt(0); results.FetchString(1, sIP, sizeof sIP); if (StrEqual(sIP, "") && clientIp[0] != '\0') @@ -1912,7 +1929,7 @@ public void VerifyBan(Database db, DBResultSet results, const char[] error, int if (DB.Format(sQuery, sizeof sQuery, "UPDATE %!s_bans SET `ip` = '%s' WHERE `bid` = '%d'", DatabasePrefix, clientIp, iBid) < sizeof(sQuery) - 1) { - DB.Query(SQL_OnIPMend, sQuery, client); + DB.Query(SQL_OnIPMend, sQuery, userid); } else { @@ -1921,22 +1938,13 @@ public void VerifyBan(Database db, DBResultSet results, const char[] error, int } } - // Without a usable IP (e.g. SDR client) never match IP bans: an empty - // address would otherwise match any type=1 row with a blank IP. - // clientIp comes from GetClientIP (dotted digits), so the condition - // is inserted unescaped (%!s) below. - char ipCondition[64]; - if (clientIp[0] != '\0') - FormatEx(ipCondition, sizeof(ipCondition), "ip = '%s'", clientIp); - else - strcopy(ipCondition, sizeof(ipCondition), "1 = 0"); - + // iBid is the active ban VerifyBan just matched: no need to look it up + // again with a sub-select on %!s_bans. if (serverID == -1) { if (db.Format(Query, sizeof(Query), "INSERT INTO %!s_banlog (sid ,time ,name ,bid) VALUES \ - ((SELECT sid FROM %!s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1), UNIX_TIMESTAMP(), '%s', \ - (SELECT bid FROM %!s_bans WHERE ((type = 0 AND authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND %!s)) AND RemoveType IS NULL LIMIT 0,1))", - DatabasePrefix, DatabasePrefix, ServerIp, ServerPort, g_sName[client], DatabasePrefix, clientAuth[8], ipCondition) < sizeof(Query) - 1) + ((SELECT sid FROM %!s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1), UNIX_TIMESTAMP(), '%s', %d)", + DatabasePrefix, DatabasePrefix, ServerIp, ServerPort, g_sName[client], iBid) < sizeof(Query) - 1) { db.Query(ErrorCheckCallback, Query, client, DBPrio_High); } @@ -1948,9 +1956,8 @@ public void VerifyBan(Database db, DBResultSet results, const char[] error, int else { if (db.Format(Query, sizeof(Query), "INSERT INTO %!s_banlog (sid ,time ,name ,bid) VALUES \ - (%d, UNIX_TIMESTAMP(), '%s', \ - (SELECT bid FROM %!s_bans WHERE ((type = 0 AND authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND %!s)) AND RemoveType IS NULL LIMIT 0,1))", - DatabasePrefix, serverID, g_sName[client], DatabasePrefix, clientAuth[8], ipCondition) < sizeof(Query) - 1) + (%d, UNIX_TIMESTAMP(), '%s', %d)", + DatabasePrefix, serverID, g_sName[client], iBid) < sizeof(Query) - 1) { db.Query(ErrorCheckCallback, Query, client, DBPrio_High); } @@ -1996,12 +2003,20 @@ public void VerifyBan(Database db, DBResultSet results, const char[] error, int PlayerStatus[client] = true; } -public void SQL_OnIPMend(Database db, DBResultSet results, const char[] error, int client) +public void SQL_OnIPMend(Database db, DBResultSet results, const char[] error, int userid) { if (results == null) { - // We now using SteamID Format2 - LogToFile(logFile, "Failed to mend IP address for %s (%s): %s", g_sSteamIDs[client], g_sPlayerIP[client], error); + int client = GetClientOfUserId(userid); + if (client) + { + // We now using SteamID Format2 + LogToFile(logFile, "Failed to mend IP address for %s (%s): %s", g_sSteamIDs[client], g_sPlayerIP[client], error); + } + else + { + LogToFile(logFile, "Failed to mend IP address (player left): %s", error); + } } } @@ -2413,8 +2428,14 @@ public void OverridesDone(Database db, DBResultSet results, const char[] error, // TIMER CALL BACKS // -public Action ClientRecheck(Handle timer, any client) +public Action ClientRecheck(Handle timer, int userid) { + // OnClientDisconnect kills this timer, but resolve the userid anyway + // rather than trusting a raw client index. + int client = GetClientOfUserId(userid); + if (!client) + return Plugin_Stop; + PlayerRecheck[client] = INVALID_HANDLE; if (IsClientConnected(client) && IsClientAuthorized(client)) @@ -2425,6 +2446,17 @@ public Action ClientRecheck(Handle timer, any client) return Plugin_Stop; } +public Action Timer_RetryConnect(Handle timer) +{ + if (DB == INVALID_HANDLE && !g_bConnecting) + { + g_bConnecting = true; + Database.Connect(GotDatabase, "sourcebans"); + } + + return Plugin_Stop; +} + /* public Action PruneBans(Handle timer) { @@ -2665,7 +2697,7 @@ public int Native_SBBanPlayer(Handle plugin, int numParams) // Validates a SteamID2 authid ("STEAM_X:Y:Z" with X/Y single digits and Z a // non-empty run of digits). A prefix-only check would let malformed strings // such as "STEAM_junk" through, and the INSERT below slices authid[8] to build -// the '^STEAM_[0-9]:%s$' REGEXP, so a bad tail silently produces a garbage row. +// the 'STEAM_0:%s' / 'STEAM_1:%s' lookups, so a bad tail silently produces a garbage row. static bool UTIL_IsValidSteamID2(const char[] authid) { if (strncmp(authid, "STEAM_", 6, false) != 0) @@ -2820,24 +2852,24 @@ public void DB_OnBanBySteamIdSelect(Database db, DBResultSet results, const char { FormatEx(query, sizeof(query), "INSERT INTO %s_bans (authid, name, created, ends, length, reason, aid, adminIp, admin_name, sid, country) VALUES \ ('%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', \ - IFNULL((SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'),'0'), '%s', \ - IFNULL((SELECT user FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), ''), \ + IFNULL((SELECT aid FROM %s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')),'0'), '%s', \ + IFNULL((SELECT user FROM %s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), ''), \ (SELECT sid FROM %s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1), ' ')", DatabasePrefix, steamIdEscaped, nameEscaped, (iTime * 60), (iTime * 60), reasonEscaped, - DatabasePrefix, adminAuth, adminAuth[8], adminIp, - DatabasePrefix, adminAuth, adminAuth[8], + DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], adminIp, + DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], DatabasePrefix, ServerIp, ServerPort); } else { FormatEx(query, sizeof(query), "INSERT INTO %s_bans (authid, name, created, ends, length, reason, aid, adminIp, admin_name, sid, country) VALUES \ ('%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', \ - IFNULL((SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'),'0'), '%s', \ - IFNULL((SELECT user FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), ''), \ + IFNULL((SELECT aid FROM %s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')),'0'), '%s', \ + IFNULL((SELECT user FROM %s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), ''), \ %d, ' ')", DatabasePrefix, steamIdEscaped, nameEscaped, (iTime * 60), (iTime * 60), reasonEscaped, - DatabasePrefix, adminAuth, adminAuth[8], adminIp, - DatabasePrefix, adminAuth, adminAuth[8], + DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], adminIp, + DatabasePrefix, adminAuth, adminAuth[8], adminAuth[8], serverID); } @@ -2884,6 +2916,14 @@ public int Native_SBReportPlayer(Handle plugin, int numParams) , iTarget = GetNativeCell(2) , iReasonLen; + if (DB == INVALID_HANDLE) + { + LogToFile(logFile, "Failed to submit report: the database is not available"); + if (iReporter > 0 && iReporter <= MaxClients && IsClientInGame(iReporter)) + PrintToChat(iReporter, "%sThe report could not be sent, the SourceBans database is not available.", Prefix); + return 0; + } + int iTime = GetTime(); GetNativeStringLength(3, iReasonLen); @@ -2995,7 +3035,7 @@ public bool CreateBan(int client, int target, int time, const char[] reason) DataPack dataPack = new DataPack(); DataPack reasonPack = new DataPack(); - WritePackString(reasonPack, reason); + reasonPack.WriteString(reason); dataPack.WriteCell(admin); dataPack.WriteCell(target); @@ -3076,10 +3116,10 @@ stock void UTIL_InsertBan(int time, const char[] Name, const char[] Authid, cons if (serverID == -1) { if (DB.Format(Query, sizeof(Query), "INSERT INTO %!s_bans (ip, authid, name, created, ends, length, reason, aid, adminIp, admin_name, sid, country) VALUES \ - ('%s', '%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', IFNULL((SELECT aid FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'),'0'), '%s', \ - IFNULL((SELECT user FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), ''), \ + ('%s', '%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', IFNULL((SELECT aid FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')),'0'), '%s', \ + IFNULL((SELECT user FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), ''), \ (SELECT sid FROM %!s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1), ' ')", - DatabasePrefix, Ip, Authid, Name, (time * 60), (time * 60), Reason, DatabasePrefix, AdminAuthid, AdminAuthid[8], AdminIp, DatabasePrefix, AdminAuthid, AdminAuthid[8], DatabasePrefix, ServerIp, ServerPort) >= sizeof(Query) - 1) + DatabasePrefix, Ip, Authid, Name, (time * 60), (time * 60), Reason, DatabasePrefix, AdminAuthid, AdminAuthid[8], AdminAuthid[8], AdminIp, DatabasePrefix, AdminAuthid, AdminAuthid[8], AdminAuthid[8], DatabasePrefix, ServerIp, ServerPort) >= sizeof(Query) - 1) { LogError("UTIL_InsertBan query truncated"); CleanupBanDataPack(dataPack); @@ -3087,10 +3127,10 @@ stock void UTIL_InsertBan(int time, const char[] Name, const char[] Authid, cons } } else { if (DB.Format(Query, sizeof(Query), "INSERT INTO %!s_bans (ip, authid, name, created, ends, length, reason, aid, adminIp, admin_name, sid, country) VALUES \ - ('%s', '%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', IFNULL((SELECT aid FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'),'0'), '%s', \ - IFNULL((SELECT user FROM %!s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), ''), \ + ('%s', '%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', IFNULL((SELECT aid FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')),'0'), '%s', \ + IFNULL((SELECT user FROM %!s_admins WHERE authid IN ('%s', 'STEAM_0:%s', 'STEAM_1:%s')), ''), \ %d, ' ')", - DatabasePrefix, Ip, Authid, Name, (time * 60), (time * 60), Reason, DatabasePrefix, AdminAuthid, AdminAuthid[8], AdminIp, DatabasePrefix, AdminAuthid, AdminAuthid[8], serverID) >= sizeof(Query) - 1) + DatabasePrefix, Ip, Authid, Name, (time * 60), (time * 60), Reason, DatabasePrefix, AdminAuthid, AdminAuthid[8], AdminAuthid[8], AdminIp, DatabasePrefix, AdminAuthid, AdminAuthid[8], AdminAuthid[8], serverID) >= sizeof(Query) - 1) { LogError("UTIL_InsertBan query truncated"); CleanupBanDataPack(dataPack); diff --git a/game/addons/sourcemod/scripting/sbpp_sleuth.sp b/game/addons/sourcemod/scripting/sbpp_sleuth.sp index e718e5f69..8b059a8d2 100644 --- a/game/addons/sourcemod/scripting/sbpp_sleuth.sp +++ b/game/addons/sourcemod/scripting/sbpp_sleuth.sp @@ -161,8 +161,10 @@ public void SQL_OnConnect(Database db, const char[] error, any data) if (db == null) { - g_DatabaseState = DatabaseState_None; LogError("SourceSleuth: Database connection error: %s", error); + // Nothing else triggers a reconnect: retry on a timer. + g_DatabaseState = DatabaseState_Wait; + CreateTimer(30.0, Timer_ReconnectDB); return; } @@ -222,14 +224,16 @@ public void OnClientPostAdminCheck(int client) * player who is already banned is left to sbpp_main. Matching the * player's own ban would re-ban them on every reconnect. Oldest * ban first so length-based actions don't compound off bans - * SourceSleuth issued itself. */ + * SourceSleuth issued itself. Both ban types (0 = SteamID, 1 = IP) + * record an IP: "type IN (0, 1)" keeps that and lets MySQL use + * the type_ip index. */ FormatEx(query, sizeof(query), "SELECT length FROM %s_bans \ - WHERE ip = '%s' AND RemoveType IS NULL \ - AND authid NOT REGEXP '^STEAM_[0-9]:%s$' \ + WHERE type IN (0, 1) AND ip = '%s' AND RemoveType IS NULL \ + AND authid NOT IN ('STEAM_0:%s', 'STEAM_1:%s') \ AND (ends > %d OR ((1 = %d AND length = 0 AND ends > %d) OR (0 = %d AND length = 0))) \ - AND NOT EXISTS (SELECT 1 FROM %s_bans WHERE type = 0 AND authid REGEXP '^STEAM_[0-9]:%s$' AND (length = 0 OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL) \ + AND NOT EXISTS (SELECT 1 FROM %s_bans WHERE type = 0 AND authid IN ('STEAM_0:%s', 'STEAM_1:%s') AND (length = 0 OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL) \ ORDER BY created ASC, bid ASC", - Prefix, IP, steamid[8], g_cVar_bantype.IntValue == 0 ? GetTime() : 0, g_cVar_excludeOld.IntValue, GetTime() - g_cVar_excludeTime.IntValue, g_cVar_excludeOld.IntValue, Prefix, steamid[8]); + Prefix, IP, steamid[8], steamid[8], g_cVar_bantype.IntValue == 0 ? GetTime() : 0, g_cVar_excludeOld.IntValue, GetTime() - g_cVar_excludeTime.IntValue, g_cVar_excludeOld.IntValue, Prefix, steamid[8], steamid[8]); DataPack datapack = new DataPack(); @@ -255,7 +259,7 @@ public void SQL_CheckHim(Database db, DBResultSet results, const char[] error, D int client; char steamid[32], IP[32]; - client = GetClientOfUserId(ReadPackCell(dataPack)); + client = GetClientOfUserId(dataPack.ReadCell()); dataPack.ReadString(steamid, sizeof(steamid)); dataPack.ReadString(IP, sizeof(IP)); delete dataPack; @@ -387,7 +391,7 @@ void CheckDBConnection(const char[] error) if (g_DatabaseState != DatabaseState_Wait) { g_DatabaseState = DatabaseState_Wait; - CreateTimer(2.0, Timer_ReconnectDB, _, TIMER_FLAG_NO_MAPCHANGE); + CreateTimer(2.0, Timer_ReconnectDB); } } else From aedf11e428698f6b49ec4946a98b21ba95f9a9be Mon Sep 17 00:00:00 2001 From: Rushaway Date: Thu, 29 Oct 2026 08:49:39 +0100 Subject: [PATCH 2/2] fix(sql): keep one connect retry timer, recheck loading players on reconnect - sbpp_main: GotDatabase() schedules Timer_RetryConnect only when no retry is pending (g_hRetryConnect). A connect started by OnRebuildAdminCache while a retry was already pending used to start a second retry chain, so a long outage ended up with roughly one chain per map change. - sbpp_checker: LateLoading() no longer requires IsClientInGame(). A player authorized while the database was down and still loading when it reconnected was never checked, and the counts stayed at -1. --- CHANGELOG.md | 3 ++- game/addons/sourcemod/scripting/sbpp_checker.sp | 4 +++- game/addons/sourcemod/scripting/sbpp_main.sp | 12 ++++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6026e88fa..92c80d8c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,8 @@ Legend: Bans Checker) and admin lookups no longer use REGEXP / LIKE, so MySQL can use the authid indexes instead of a full scan 04. ! sbpp_main, sbpp_checker and SourceSleuth retry a failed database - connection on a timer; sbpp_checker no longer unloads itself, + connection on a timer; sbpp_checker no longer unloads itself + and checks the players who joined while it was disconnected, and sm_banip / sm_unban / sm_addban / reports reply with an error instead of throwing when the database is unavailable ``` diff --git a/game/addons/sourcemod/scripting/sbpp_checker.sp b/game/addons/sourcemod/scripting/sbpp_checker.sp index 1883a210c..ed975e4db 100644 --- a/game/addons/sourcemod/scripting/sbpp_checker.sp +++ b/game/addons/sourcemod/scripting/sbpp_checker.sp @@ -742,7 +742,9 @@ stock void LateLoading() char sSteam32ID[64]; for (int i = 1; i <= MaxClients; i++) { - if (!IsClientConnected(i) || !IsClientInGame(i) || IsFakeClient(i) || !IsClientAuthorized(i)) + // No IsClientInGame(): a player authorized while the database was down can still be + // loading when it reconnects, and OnClientAuthorized() doesn't need them in game. + if (!IsClientConnected(i) || IsFakeClient(i) || !IsClientAuthorized(i)) continue; // Already checked diff --git a/game/addons/sourcemod/scripting/sbpp_main.sp b/game/addons/sourcemod/scripting/sbpp_main.sp index 9b19ac16c..d0a8dcd12 100644 --- a/game/addons/sourcemod/scripting/sbpp_main.sp +++ b/game/addons/sourcemod/scripting/sbpp_main.sp @@ -124,6 +124,7 @@ GlobalForward g_hFwd_OnBanAdded , g_hFwd_OnClientPostAdminCheck; Handle PlayerRecheck[MAXPLAYERS + 1] = { INVALID_HANDLE, ... }; /* Timer handle */ +Handle g_hRetryConnect = null; /* Pending connect retry timer, at most one */ DataPack PlayerDataPack[MAXPLAYERS + 1] = { null, ... }; @@ -1163,8 +1164,13 @@ public void GotDatabase(Database db, const char[] error, any data) LogToFile(logFile, "Database failure: %s. See Docs: https://sbpp.github.io/docs/", error); g_bConnecting = false; - // Retry on a timer instead of waiting for the next admin cache rebuild (map change) - CreateTimer(RetryTime, Timer_RetryConnect); + // Retry on a timer instead of waiting for the next admin cache rebuild (map change). + // A connect started by OnRebuildAdminCache can fail while a retry is already pending: + // keep a single timer so failed connects don't stack one retry chain per map change. + if (g_hRetryConnect == null) + { + g_hRetryConnect = CreateTimer(RetryTime, Timer_RetryConnect); + } // Parse the overrides backup! ParseBackupConfig_Overrides(); @@ -2448,6 +2454,8 @@ public Action ClientRecheck(Handle timer, int userid) public Action Timer_RetryConnect(Handle timer) { + g_hRetryConnect = null; + if (DB == INVALID_HANDLE && !g_bConnecting) { g_bConnecting = true;