From 87807b523d63276e046d33e30fd93843ad4dcdbe Mon Sep 17 00:00:00 2001 From: nwrl Date: Tue, 29 Jul 2025 19:44:48 -0500 Subject: Add cvars and admin commands --- nameblocker.sp | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 182 insertions(+), 17 deletions(-) diff --git a/nameblocker.sp b/nameblocker.sp index 9190dd2..aec6963 100644 --- a/nameblocker.sp +++ b/nameblocker.sp @@ -11,24 +11,64 @@ public Plugin myinfo = { url = "git.dabikers.online/smnameblocker" }; -#define HANDLE_SIZE 32 +#define HANDLE_SIZE (32) // This exists because you can't sizeof() a Handle, but it IS specified to be a 32bit integer. This should also equal the size of // any other methodmap or descendant of Handle (like Regex) +#define PATTERN_MAX_LEN (512 + 1) /* 512 chars + null terminator */ + enum OperatingMode { OP_DISABLED, OP_KICK, OP_BAN, OP_TOOBIG } -OperatingMode gOperMode = OP_DISABLED; -ArrayList filterlist; +ArrayList regexlist; +ArrayList patternlist; // Note: Contents of ArrayLists are lost on map change / reload. I should store everything in a database, // then use the database to populate the list whenever it is cleared +Database db; + +ConVar gcvarOperMode; static const char OPERMODENAME[] = "nameblock_OperatingMode"; const OperatingMode DEFAULTOPERMODE = OP_KICK; +ConVar gcvarAdmCmdFlag; static const char ADMCMDFLAGNAME[] = "nameblock_AdminCommandFlag"; const int DEFAULTADMCMDFLAG = ADMFLAG_BAN; +ConVar gcvarRegexCompFlags; static const char REGEXCOMPFLAGSNAME[] = "nameblock_RegexCompilationFlags"; const int DEFAULTREGEXCOMPFLAGS = (PCRE_CASELESS | PCRE_DOTALL | PCRE_EXTENDED | PCRE_UTF8); +ConVar gcvarAmdImmFlag; static const char ADMINIMMUNITYFLAG[] = "nameblock_AdminImmunityFlag"; const int DEFAULTADMIMMFLAG = ADMFLAG_GENERIC; + public void OnAllPluginsLoaded() { - filterlist = new ArrayList(ByteCountToCells(HANDLE_SIZE)); + // Initialize and populate datatypes + regexlist = new ArrayList(ByteCountToCells(HANDLE_SIZE)); + patternlist = new ArrayList(ByteCountToCells(PATTERN_MAX_LEN)); + // db = + + // Register convars + char tmp[32]; // Consider this a byte array + Format(tmp, sizeof(tmp), "%d", view_as(DEFAULTOPERMODE)); + gcvarOperMode = CreateConVar(OPERMODENAME, tmp, "Operating mode (disabled, kick, ban, etc.)"); + gcvarOperMode.IntValue = view_as(DEFAULTOPERMODE); + + Format(tmp, sizeof(tmp), "%d", DEFAULTADMCMDFLAG); + gcvarAdmCmdFlag = CreateConVar(ADMCMDFLAGNAME, tmp, "Admin flag to modify pattern list"); + gcvarAdmCmdFlag.IntValue = DEFAULTADMCMDFLAG; + + Format(tmp, sizeof(tmp), "%d", DEFAULTREGEXCOMPFLAGS); + gcvarRegexCompFlags = CreateConVar(REGEXCOMPFLAGSNAME, tmp, "Regular expression compilation flags"); + gcvarRegexCompFlags.IntValue = DEFAULTREGEXCOMPFLAGS; + + Format(tmp, sizeof(tmp), "%d", DEFAULTADMIMMFLAG); + gcvarAmdImmFlag = CreateConVar(ADMINIMMUNITYFLAG, tmp, "Admin immunity flag"); + gcvarAmdImmFlag.IntValue = DEFAULTADMIMMFLAG; + + // I should make a function for setting up convars + + AutoExecConfig(true, "nameblocker_cvars"); + + // Register commands + RegAdminCmd("nb_addpattern", registerPattern, gcvarAdmCmdFlag.IntValue, "Add a regex pattern to the filter list"); + RegAdminCmd("nb_removepattern", deletePattern, gcvarAdmCmdFlag.IntValue, "Remove a regex pattern from the filter list"); + RegAdminCmd("nb_modifypattern", replacePattern, gcvarAdmCmdFlag.IntValue, "Replace an existing regex pattern with a new pattern"); + RegAdminCmd("nb_listpatterns", listPatterns, gcvarAdmCmdFlag.IntValue, "List current regex patterns and their indicies"); } public void OnClientPostAdminCheck(int client) { @@ -41,27 +81,150 @@ public void OnClientSettingsChanged(int client) { -void checkName(int client) { - if(client <= 0) return; +public Action registerPattern(int client, int args) { + if(args < 2) { + ReplyToCommand(client, "Error: missing regex pattern"); + return Plugin_Handled; + } + + char pattern[PATTERN_MAX_LEN]; + GetCmdArg(1, pattern, sizeof(pattern)); + // TODO: Error handling + + if(insertPattern(pattern, regexlist.Length - 1)) { + ReplyToCommand(client, "Error: could not register pattern"); + return Plugin_Handled + } + + return Plugin_Handled; +} + +public Action deletePattern(int client, int args) { + if(args < 2) { + ReplyToCommand(client, "Error: no pattern index given"); + return Plugin_Handled; + } + + int index + if(!GetCmdArgIntEx(1, index)) { + ReplyToCommand(client, "Error: index argument not numerical"); + return Plugin_Handled; + } + + if(removePattern(index)) { + ReplyToCommand(client, "Error: could not remove pattern"); + return Plugin_Handled; + } + + return Plugin_Handled; +} + +public Action replacePattern(int client, int args) { + if(args < 3) { + ReplyToCommand(client, "Error: missing index, replacement pattern, or both"); + return Plugin_Handled; + } + + int index + if(!GetCmdArgIntEx(1, index)) { + ReplyToCommand(client, "Error: index argument not numerical"); + return Plugin_Handled; + } + + char pattern[PATTERN_MAX_LEN]; + GetCmdArg(2, pattern, sizeof(pattern)); + // TODO: Error handling + + if(removePattern(index)) { + ReplyToCommand(client, "Error: could not remove pattern"); + return Plugin_Handled; + } + if(insertPattern(pattern, regexlist.Length - 1)) { + ReplyToCommand(client, "Error: could not register pattern"); + return Plugin_Handled + } + // Preferably this would be atomic as to not lose a pattern, but that's something I can do later + + // I know I can make the database change "atomic" via the use of a transaction, but handling that would necessitate a different + // function + + return Plugin_Handled; +} + +public Action listPatterns(int client, int args) { + for(int i = 0; i < patternlist.Length; i++) { + ReplyToCommand(client, "[%d] %s", i, patternlist.Get(i)); + } + + return Plugin_Handled; +} + + + +int insertPattern(char pattern[PATTERN_MAX_LEN], int index) { + if(IsNullString(pattern) || index < 0 || index >= regexlist.Length) return -1; + + static char errstr[512]; static RegexError reerr; + Regex res = CompileRegex(pattern, gcvarRegexCompFlags.IntValue, errstr, sizeof(errstr), reerr); + if(res == null) { + LogError("Error: Could not compile regex pattern \"%s\": %s (%d)", pattern, errstr, reerr); + return -1; + } + + if(index != (regexlist.Length - 1)) { + regexlist.ShiftUp(index); + patternlist.ShiftUp(index); + regexlist.Set(index, res); + patternlist.Set(index, pattern); + } else { + regexlist.Push(res); + patternlist.Push(pattern); + } + + // TODO: This should also insert the pattern into the database + + return 0; +} + +int removePattern(int index) { + if(index < 0 || index >= regexlist.Length) return -1; + + regexlist.Erase(index); + patternlist.Erase(index); + + // TODO: This should also remove the pattern from the database + + return 0; +} + + + +int checkName(int client) { + if(client <= 0) return -1; + if(gcvarOperMode.IntValue == view_as(OP_DISABLED)) return -1; + if(CheckCommandAccess(client, "", gcvarAmdImmFlag.IntValue, true)) return -1; char name[64 + 1]; - if(getName(client, name, sizeof(name)) <= 0) - return; // TODO: better error checking + if(getName(client, name, sizeof(name)) <= 0) { + LogError("Tried to get a client's name for a name check, but could not") + return -1; + } RegexError reerr; - for(int i = 0, m = 0; i < filterlist.Length; i++) { - m = MatchRegex(filterlist.Get(i), name, reerr); + for(int i = 0, m = 0; i < regexlist.Length; i++) { + m = MatchRegex(regexlist.Get(i), name, reerr); - if(m == 0) continue; if(m < 0) { handleFailedRegex(client, reerr); - return; + return -1; } + if(m == 0) continue; handleNameHit(client); + break; } - return; + return 0; } int getName(int client, char[] buf, int buflen) { @@ -74,11 +237,11 @@ int getName(int client, char[] buf, int buflen) { int handleNameHit(int client) { if(client <= 0) return -1; - switch(gOperMode) { + switch(gcvarOperMode.IntValue) { case OP_DISABLED: {return 0;} case OP_KICK: { - KickClient(client, "Failed Name Check"); - // Log kick + KickClient(client, "Failed name check"); + LogAction(0, client, "Kicked %L for failing a name check", client); return 0; } case OP_BAN: { @@ -87,7 +250,7 @@ int handleNameHit(int client) { // Log ban } default: { - LogError("Operating mode in an invalid state"); + LogError("%L failed a name check, but the operating mode in an invalid state", client); return -1; } } @@ -104,6 +267,8 @@ int handleFailedRegex(int client, RegexError reerr) { return 0; } + + // Note: May or may not be particularly descriptive for any given error int RegexStrError(RegexError err, char[] buf, int buflen) { if(IsNullString(buf)) return -1; -- cgit v1.2.3