sourcemod-plugins/scripting/globalbans.sp
2024-02-15 09:00:28 -06:00

333 lines
12 KiB
SourcePawn

#pragma semicolon 1
#pragma newdecls required
#define DEBUG 0
#define PLUGIN_VERSION "1.0"
#define DB_NAME "globalbans"
#define BANFLAG_NONE 0
#define BANFLAG_IPBANNED 1
#define BANFLAG_SUSPENDED 2
#include <sourcemod>
#include <sdktools>
public Plugin myinfo =
{
name = "Global Bans",
author = "jackzmc",
description = "Manages bans via MySQL",
version = PLUGIN_VERSION,
url = "https://github.com/Jackzmc/sourcemod-plugins"
};
static Database g_db;
static ConVar hKickType;
static StringMap pendingInsertQueries;
static int iPendingCounter;
public void OnPluginStart() {
if(!SQL_CheckConfig(DB_NAME)) {
SetFailState("No database entry for " ... DB_NAME ... "; no database to connect to.");
}
if(!ConnectDB()) {
SetFailState("Failed to connect to database.");
}
pendingInsertQueries = new StringMap();
hKickType = CreateConVar("sm_globalbans_kick_type", "1", "0 = Do not kick, just notify\n1 = Kick if banned\n 2 = Kick if cannot reach database", FCVAR_NONE, true, 0.0, true, 2.0);
}
///////////////////////////////////////////////////////////////////////////////
// DB Connections
///////////////////////////////////////////////////////////////////////////////
bool ConnectDB() {
static char error[255];
g_db = SQL_Connect(DB_NAME, true, error, sizeof(error));
if (g_db == null) {
LogError("Database error %s", error);
delete g_db;
return false;
} else {
PrintToServer("Connected to sm database " ... DB_NAME);
SQL_LockDatabase(g_db);
SQL_FastQuery(g_db, "SET NAMES \"UTF8mb4\"");
SQL_UnlockDatabase(g_db);
g_db.SetCharset("utf8mb4");
return true;
}
}
///////////////////////////////////////////////////////////////////////////////
// EVENTS
///////////////////////////////////////////////////////////////////////////////
public void OnClientAuthorized(int client, const char[] auth) {
if(!StrEqual(auth, "BOT", true)) {
static char query[256], ip[32];
GetClientIP(client, ip, sizeof(ip));
g_db.Format(query, sizeof(query), "SELECT `reason`, `steamid`, `expired`, `public_message`, `flags`, `id` FROM `bans` WHERE `expired` = 0 AND `steamid` LIKE 'STEAM_%%:%%:%s' OR ip = '%s'", auth[10], ip);
g_db.Query(DB_OnConnectCheck, query, GetClientUserId(client), DBPrio_High);
}
}
public Action OnBanIdentity(const char[] identity, int time, int flags, const char[] reason, const char[] command, any source) {
if(flags == BANFLAG_AUTHID) {
char executor[32];
if(source > 0 && source <= MaxClients) {
GetClientAuthId(source, AuthId_Steam2, executor, sizeof(executor));
}else{
executor = "CONSOLE";
}
// Setup expiration date
char expiresDate[64];
if(time > 0) {
Format(expiresDate, sizeof(expiresDate), "%d", GetTime() + (time * 60000));
}else{
Format(expiresDate, sizeof(expiresDate), "NULL");
}
int size = 2*strlen(reason)+1;
char[] reasonEscaped = new char[size];
g_db.Escape(reason, reasonEscaped, size);
int querySize = 256+size;
char[] query = new char[querySize];
g_db.Format(query, querySize, "INSERT INTO bans"
..."(steamid, reason, expires, executor, flags, timestamp)"
..."VALUES ('%s', '%s', %s, '%s', %d, UNIX_TIMESTAMP())",
identity,
reasonEscaped,
expiresDate,
executor,
BANFLAG_NONE
);
static char strKey[8];
int key = ++iPendingCounter;
IntToString(key, strKey, sizeof(strKey));
pendingInsertQueries.SetString(strKey, query);
g_db.Format(query, querySize, "SELECT `flags` FROM `bans` WHERE `expired` = 0 AND `steamid` LIKE 'STEAM_%%:%%:%s' OR ip = '%s'", identity[10], identity);
g_db.Query(DB_OnBanPreCheck, query, key);
PrintToServer("Adding %s to OnBanClient queue. Key: %d", identity, key);
}else if(flags == BANFLAG_IP) {
LogMessage("Cannot save IP without steamid: %s [Source: %s]", identity, source);
}
return Plugin_Continue;
}
public Action OnBanClient(int client, int time, int flags, const char[] reason, const char[] kick_message, const char[] command, any source) {
if(GetUserAdmin(client) != INVALID_ADMIN_ID) {
LogMessage("Ignoring OnBanClient with admin id as target");
return Plugin_Stop;
}
char executor[32], identity[32], ip[32];
GetClientAuthId(client, AuthId_Steam2, identity, sizeof(identity));
LogMessage("OnBanClient client=%d flags=%d source=%d, command=%s", client, flags, source, command);
DataPack pack;
if(source > 0 && source <= MaxClients && IsClientConnected(source) && GetClientAuthId(source, AuthId_Steam2, executor, sizeof(executor))) {
pack = new DataPack();
pack.WriteString(identity);
pack.WriteCell(source);
}else{
executor = "CONSOLE";
}
GetClientIP(client, ip, sizeof(ip));
char expiresDate[64];
if(time > 0) {
Format(expiresDate, sizeof(expiresDate), "%d", GetTime() + (time * 60));
} else {
Format(expiresDate, sizeof(expiresDate), "NULL");
}
int size = 2*strlen(reason)+1;
char[] reasonEscaped = new char[size];
g_db.Escape(reason, reasonEscaped, size);
int querySize = 256 + size;
char[] query = new char[querySize];
g_db.Format(query, querySize, "INSERT INTO bans"
..."(steamid, ip, reason, public_message, expires, executor, flags, timestamp)"
..."VALUES ('%s', '%s', '%s', '%s', %s, '%s', %d, UNIX_TIMESTAMP())",
identity,
ip,
reasonEscaped,
kick_message,
expiresDate,
executor,
BANFLAG_NONE
);
static char strKey[8];
int key = ++iPendingCounter;
IntToString(key, strKey, sizeof(strKey));
pendingInsertQueries.SetString(strKey, query);
g_db.Format(query, querySize, "SELECT `flags` FROM `bans` WHERE `expired` = 0 AND `steamid` LIKE 'STEAM_%%:%%:%s' OR ip = '%s'", identity[10], identity);
g_db.Query(DB_OnBanPreCheck, query, key);
PrintToServer("Adding %N to OnBanClient queue. Key: %d", client, key);
return Plugin_Handled;
}
public Action OnRemoveBan(const char[] identity, int flags, const char[] command, any source) {
if(flags == BANFLAG_AUTHID) {
static char query[128];
g_db.Format(query, sizeof(query), "DELETE FROM `bans` WHERE steamid = '%s'", identity);
g_db.Query(DB_GenericCallback, query, flags);
}
return Plugin_Continue;
}
///////////////////////////////////////////////////////////////////////////////
// DB Callbacks
///////////////////////////////////////////////////////////////////////////////
//] DB_OnBanQuery returned error: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1
public void DB_OnBanPreCheck(Database db, DBResultSet results, const char[] error, int key) {
static char strKey[8];
IntToString(key, strKey, sizeof(strKey));
static char query[255];
pendingInsertQueries.GetString(strKey, query, sizeof(query));
if(results == null || db == INVALID_HANDLE) {
LogError("Ban Pre-check: Cannot check for existing ban, ignoring ban attempt. [Query: %s]", query);
} else {
if(results.FetchRow()) {
int flags = results.FetchInt(0);
if(~flags & BANFLAG_SUSPENDED) {
LogMessage("Ban Pre-check: Found existing non-suspended ban, ignoring ban attempt. [Query: %s]", query);
return;
}
}
g_db.Query(DB_OnBanQuery, query);
}
}
public void DB_OnConnectCheck(Database db, DBResultSet results, const char[] error, int user) {
int client = GetClientOfUserId(user);
if(client == 0) return;
if(db == INVALID_HANDLE || results == null) {
LogError("DB_OnConnectCheck returned error: %s", error);
if(hKickType.IntValue == 2) {
KickClient(client, "Could not authenticate at this time.");
LogMessage("Could not connect to database to authorize user '%N' (#%d)", client, user);
}
} else {
//No failure, check the data.
while(results.FetchRow()) { //Is there a ban found?
static char reason[255], steamid[64], public_message[255];
DBResult colResult;
results.FetchString(0, reason, sizeof(reason), colResult);
results.FetchString(1, steamid, sizeof(steamid));
if(colResult == DBVal_Null) {
reason[0] = '\0';
}
bool expired = results.FetchInt(2) == 1; //Check if computed column 'expired' is true
if(results.IsFieldNull(2)) { //If expired null, delete i guess. lol
DeleteBan(steamid);
} else {
int flags = results.FetchInt(4);
if(!expired && (~flags & BANFLAG_SUSPENDED)) {
LogAction(-1, client, "\"%L\" (%s), is banned from server: \"%s\"", client, steamid, reason);
// Fetch public message
results.FetchString(3, public_message, sizeof(public_message), colResult);
if(colResult == DBVal_Null || strlen(public_message) == 0) {
public_message = reason;
}
int id = results.FetchInt(5);
if(hKickType.IntValue > 0) {
if(public_message[0] != '\0')
KickClient(client, "Banned:\n%s\nAppeal at jackz.me/apl/%d", public_message, id);
else
KickClient(client, "You have been banned from this server.\nAppeal at jackz.me/apl/%d", id);
static char query[128];
g_db.Format(query, sizeof(query), "UPDATE bans SET times_tried=times_tried+1 WHERE steamid = '%s'", steamid);
g_db.Query(DB_GenericCallback, query);
} else {
PrintChatToAdmins("\"%L\" was banned from this server for: \"%s\"", client, reason);
}
static char query[128];
g_db.Format(query, sizeof(query), "UPDATE bans SET times_tried=times_tried+1 WHERE steamid = '%s'", steamid);
g_db.Query(DB_GenericCallback, query);
} else {
LogAction(-1, client, "\"%L\" was previously banned from server: \"%s\"", client, reason);
// User was previously banned
PrintChatToAdmins("%N (%s) has a previous suspended/expired ban of reason \"%s\"", client, steamid, reason);
}
}
}
}
}
void DeleteBan(const char[] steamid) {
static char query[128];
g_db.Format(query, sizeof(query), "DELETE FROM `bans` WHERE steamid = '%s'", steamid);
g_db.Query(DB_GenericCallback, query);
}
public void DB_OnBanQuery(Database db, DBResultSet results, const char[] error, any data) {
if(db == INVALID_HANDLE || results == null) {
LogError("DB_OnBanQuery returned error: %s", error);
DataPack pack = data;
if(pack != null) {
pack.Reset();
static char id[32];
pack.ReadString(id, sizeof(id));
int source = pack.ReadCell();
if(StrContains(error, "Duplicate entry") > 0) {
PrintToChat(source, "Could not ban \"%s\", as they were previously banned. Please edit the ban manually on the website (or yell at jackz).", id);
} else {
PrintToChat(source, "Could not ban \"%s\" due to an error: %s", id, error);
}
}
}
}
public void DB_GenericCallback(Database db, DBResultSet results, const char[] error, any data) {
if(db == INVALID_HANDLE || results == null) {
LogError("DB_OnRemoveBanQuery returned error: %s", error);
}
}
stock void PrintChatToAdmins(const char[] format, any ...) {
char buffer[254];
VFormat(buffer, sizeof(buffer), format, 2);
for(int i = 1; i < MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i)) {
AdminId admin = GetUserAdmin(i);
if(admin != INVALID_ADMIN_ID) {
PrintToChat(i, "%s", buffer);
}
}
}
PrintToServer("%s", buffer);
}