From f346a514ad5cf006793d31fbd208914735c89dd7 Mon Sep 17 00:00:00 2001 From: nwrl Date: Tue, 29 Jul 2025 22:52:21 -0500 Subject: Add some helper functions for error handling --- nameblocker.sp | 242 ++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 153 insertions(+), 89 deletions(-) (limited to 'nameblocker.sp') diff --git a/nameblocker.sp b/nameblocker.sp index 366abf9..554b086 100644 --- a/nameblocker.sp +++ b/nameblocker.sp @@ -1,4 +1,22 @@ +// Notes: + // Memory allocated via the "new" keyword IS garbage collected. Handles are not. Close handles when you're done with them + // Handles are closed when the plugin is unloaded, so if the lifetime of a handle is the plugin's lifetime, don't worry about closing it + // The contents of ArrayList objects are lost on map transition / server restart. Any persistent data should be stored in a + // key-value file OR in a database + + // Basically every string function will add a null terminator, so add `+ 1` to the end of any defined size string definition + // It is possible to throw an error with the ThrowError function, but anything that would bother should probably disable the + // plugin entirely, so SetFailState is better + + // Large local var allocations if done commonly should be made static. Increases baseline memory usage, but lowers time spent + // allocating memory. That being said, don't bother unless it's actually a problem. Name checks should be relatively + // infrequent, and everything else exponentially less frequent. Dropping a frame or 2 to allocate 2048 bytes of memory + // every century shouldn't be the end of the world + + // + #pragma newdecls required +#pragma semicolon 1 #include #include @@ -26,9 +44,6 @@ enum OperatingMode { 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; @@ -36,25 +51,69 @@ ConVar gcvarAdmCmdFlag; static const char ADMCMDFLAGNAME[] = "nameb ConVar gcvarRegexCompFlags; static const char REGEXCOMPFLAGSNAME[] = "nameblock_RegexCompilationFlags"; const int DEFAULTREGEXCOMPFLAGS = (PCRE_CASELESS | PCRE_DOTALL | PCRE_EXTENDED | PCRE_UTF8); ConVar gcvarAmdImmFlag; static const char ADMINIMMUNITYFLAGNAME[] = "nameblock_AdminImmunityFlag"; const int DEFAULTADMIMMFLAG = ADMFLAG_GENERIC; + + +// Logs and throws an error with the same message of upto 2048 characters +void logAndFail(const char[] format, any ...) { + char buf[2048 + 1]; + VFormat(buf, sizeof(buf), format, 2); + + LogError(buf); + SetFailState(buf); +} + +// Concatenate arguments into a single buffer +int concatArgs(char[] buf, int maxbuflen, int maxarglen, int end, int start=1) { + if(maxbuflen <= 0 || start < 0) return -1; + + char[] arg = new char[maxarglen]; + char[] tmp = new char[maxbuflen]; + + GetCmdArg(start, tmp, maxarglen); // Priming the first argument to prevent weirdness + for(int i = start + 1; i <= end; i++) { + GetCmdArg(i, arg, maxarglen); + Format(tmp, maxbuflen, "%s %s", tmp, arg); + } + + return strcopy(buf, maxbuflen, tmp); +} + +// Register a convar who's default value is an integer int registerIntConVar(ConVar& cv, int defaultVal, const char[] name, const char[] desc) { - static char tmp[32]; // Consider this a byte array + char tmp[32]; // Consider this a byte array Format(tmp, sizeof(tmp), "%d", defaultVal); + cv = CreateConVar(name, tmp, desc); + if(cv == null) return -1; cv.IntValue = defaultVal; return 0; } +// `registerIntConVar` wrapper. Calls `logAndFail` if the convar couldn't be registered +void xRegisterIntConVar(ConVar& cv, int defaultVal, const char[] name, const char[] desc) { + if(registerIntConVar(cv, defaultVal, name, desc)) logAndFail("Could not register cvar \"%s\"", name); +} + + + public void OnAllPluginsLoaded() { // Initialize and populate datatypes regexlist = new ArrayList(ByteCountToCells(HANDLE_SIZE)); + if(regexlist == null) logAndFail("Could not initialize regexlist ArrayList"); + patternlist = new ArrayList(ByteCountToCells(PATTERN_MAX_LEN)); - // db = + if(patternlist == null) logAndFail("Could not initialize patternlist ArrayList"); + + char sqlerr[256 + 1]; + db = SQL_DefConnect(sqlerr, sizeof(sqlerr)); // Default connect until I get a little more acquainted with sm's sql api + if(db == null) logAndFail("Could not connect to sql database: %s", sqlerr); - registerIntConVar(gcvarOperMode, view_as(DEFAULTOPERMODE), OPERMODENAME, "Operating mode (disabled, kick, ban, etc.)"); - registerIntConVar(gcvarAdmCmdFlag, DEFAULTADMCMDFLAG, ADMCMDFLAGNAME, "Admin flag to modify pattern list"); - registerIntConVar(gcvarRegexCompFlags, DEFAULTREGEXCOMPFLAGS, REGEXCOMPFLAGSNAME, "Regular expression compilation flags"); - registerIntConVar(gcvarAmdImmFlag, DEFAULTADMIMMFLAG, ADMINIMMUNITYFLAGNAME, "Admin immunity flag"); + // Register convars + xRegisterIntConVar(gcvarOperMode, view_as(DEFAULTOPERMODE), OPERMODENAME, "Operating mode (disabled, kick, ban, etc.)"); + xRegisterIntConVar(gcvarAdmCmdFlag, DEFAULTADMCMDFLAG, ADMCMDFLAGNAME, "Admin flag to modify pattern list"); + xRegisterIntConVar(gcvarRegexCompFlags, DEFAULTREGEXCOMPFLAGS, REGEXCOMPFLAGSNAME, "Regular expression compilation flags"); + xRegisterIntConVar(gcvarAmdImmFlag, DEFAULTADMIMMFLAG, ADMINIMMUNITYFLAGNAME, "Admin immunity flag"); AutoExecConfig(true, "nameblocker_cvars"); @@ -82,12 +141,14 @@ public Action registerPattern(int client, int args) { } char pattern[PATTERN_MAX_LEN]; - GetCmdArg(1, pattern, sizeof(pattern)); - // TODO: Error handling + if(concatArgs(pattern, sizeof(pattern), PATTERN_MAX_LEN, args) <= 0) { + ReplyToCommand(client, "Error: could not get argument list"); + return Plugin_Handled; + } - if(insertPattern(pattern, regexlist.Length - 1)) { + if(insertPattern(pattern, regexlist.Length)) { ReplyToCommand(client, "Error: could not register pattern"); - return Plugin_Handled + return Plugin_Handled; } return Plugin_Handled; @@ -99,7 +160,7 @@ public Action deletePattern(int client, int args) { return Plugin_Handled; } - int index + int index; if(!GetCmdArgIntEx(1, index)) { ReplyToCommand(client, "Error: index argument not numerical"); return Plugin_Handled; @@ -119,23 +180,25 @@ public Action replacePattern(int client, int args) { return Plugin_Handled; } - int index + 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(concatArgs(pattern, sizeof(pattern), PATTERN_MAX_LEN, args, 2) <= 0) { + ReplyToCommand(client, "Error: could not get argument list"); + return Plugin_Handled; + } if(removePattern(index)) { ReplyToCommand(client, "Error: could not remove pattern"); return Plugin_Handled; } - if(insertPattern(pattern, regexlist.Length - 1)) { + if(insertPattern(pattern, index)) { ReplyToCommand(client, "Error: could not register pattern"); - return Plugin_Handled + return Plugin_Handled; } // Preferably this would be atomic as to not lose a pattern, but that's something I can do later @@ -156,23 +219,25 @@ public Action listPatterns(int client, int args) { int insertPattern(char pattern[PATTERN_MAX_LEN], int index) { - if(IsNullString(pattern) || index < 0 || index >= regexlist.Length) return -1; + if(IsNullString(pattern) || index < 0 || index > regexlist.Length) return -1; - static char errstr[512]; static RegexError reerr; + char errstr[512]; 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)) { + if(index == regexlist.Length) { + regexlist.Push(res); + patternlist.Push(pattern); + + } else { regexlist.ShiftUp(index); patternlist.ShiftUp(index); regexlist.Set(index, res); - patternlist.Set(index, pattern); - } else { - regexlist.Push(res); - patternlist.Push(pattern); + patternlist.Set(index, pattern); + } // TODO: This should also insert the pattern into the database @@ -193,14 +258,15 @@ int removePattern(int index) { +// Check's a user's name against the regex list. Returns -1 on error, 0 if skipped, 1 on hit 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; + if(client <= 0) return 0; + if(gcvarOperMode.IntValue == view_as(OP_DISABLED)) return 0; + if(CheckCommandAccess(client, "", gcvarAmdImmFlag.IntValue, true)) return 0; char name[64 + 1]; if(getName(client, name, sizeof(name)) <= 0) { - LogError("Tried to get a client's name for a name check, but could not") + LogError("Tried to get a client's name for a name check, but could not"); return -1; } @@ -218,7 +284,7 @@ int checkName(int client) { break; } - return 0; + return 1; } int getName(int client, char[] buf, int buflen) { @@ -254,7 +320,7 @@ int handleNameHit(int client) { } int handleFailedRegex(int client, RegexError reerr) { - static char regstr[128]; + char regstr[128]; RegexStrError(reerr, regstr, sizeof(regstr)); LogError("Ran into regex error when trying to check user %L's name. Reported regex error: %s", client, regstr); @@ -267,64 +333,62 @@ int handleFailedRegex(int client, RegexError reerr) { int RegexStrError(RegexError err, char[] buf, int buflen) { if(IsNullString(buf)) return -1; + char tmp[64]; switch(err) { - case REGEX_ERROR_NONE: {Format(buf, buflen, "No error");} /* No error */ - - case REGEX_ERROR_ASSERT: {Format(buf, buflen, "Internal error");} - case REGEX_ERROR_BADBR: {Format(buf, buflen, "Invalid repeat counts in {}");} - case REGEX_ERROR_BADPAT: {Format(buf, buflen, "Pattern error");} - case REGEX_ERROR_BADRPT: {Format(buf, buflen, "? * + invalid");} - case REGEX_ERROR_EBRACE: {Format(buf, buflen, "Unbalanced {}");} - case REGEX_ERROR_EBRACK: {Format(buf, buflen, "Unbalanced []");} - case REGEX_ERROR_ECOLLATE: {Format(buf, buflen, "Collation error - not relevant");} - case REGEX_ERROR_ECTYPE: {Format(buf, buflen, "Bad class");} - case REGEX_ERROR_EESCAPE: {Format(buf, buflen, "Bad escape sequence");} - case REGEX_ERROR_EMPTY: {Format(buf, buflen, "Empty expression");} - case REGEX_ERROR_EPAREN: {Format(buf, buflen, "Unbalanced ()");} - case REGEX_ERROR_ERANGE: {Format(buf, buflen, "Bad range inside []");} - case REGEX_ERROR_ESIZE: {Format(buf, buflen, "Expression too big");} - case REGEX_ERROR_ESPACE: {Format(buf, buflen, "Failed to get memory");} - case REGEX_ERROR_ESUBREG: {Format(buf, buflen, "Bad back reference");} - case REGEX_ERROR_INVARG: {Format(buf, buflen, "Bad argument");} - - case REGEX_ERROR_NOMATCH: {Format(buf, buflen, "No match was found");} - case REGEX_ERROR_NULL: {Format(buf, buflen, "Null");} - case REGEX_ERROR_BADOPTION: {Format(buf, buflen, "Bad Option");} - case REGEX_ERROR_BADMAGIC: {Format(buf, buflen, "Bad Magic");} - case REGEX_ERROR_UNKNOWN_OPCODE: {Format(buf, buflen, "Unknown OpCode");} - case REGEX_ERROR_NOMEMORY: {Format(buf, buflen, "No Memory");} - case REGEX_ERROR_NOSUBSTRING: {Format(buf, buflen, "No substring");} - case REGEX_ERROR_MATCHLIMIT: {Format(buf, buflen, "Match limit");} - case REGEX_ERROR_CALLOUT: {Format(buf, buflen, "Callout");} // Never used by PCRE itself - case REGEX_ERROR_BADUTF8: {Format(buf, buflen, "Bad UTF8");} - case REGEX_ERROR_BADUTF8_OFFSET: {Format(buf, buflen, "Bad UTF8 offset");} - case REGEX_ERROR_PARTIAL: {Format(buf, buflen, "Partial");} - case REGEX_ERROR_BADPARTIAL: {Format(buf, buflen, "Bad Partial");} - case REGEX_ERROR_INTERNAL: {Format(buf, buflen, "Internal error");} - case REGEX_ERROR_BADCOUNT: {Format(buf, buflen, "Bad count");} - case REGEX_ERROR_DFA_UITEM: {Format(buf, buflen, "DFA UItem");} - case REGEX_ERROR_DFA_UCOND: {Format(buf, buflen, "DFA UCOND");} - case REGEX_ERROR_DFA_UMLIMIT: {Format(buf, buflen, "DFA UMLIMIT");} - case REGEX_ERROR_DFA_WSSIZE: {Format(buf, buflen, "DFA WSSIZE");} - case REGEX_ERROR_DFA_RECURSE: {Format(buf, buflen, "DFA recurse");} - case REGEX_ERROR_RECURSIONLIMIT: {Format(buf, buflen, "Recursion Limit");} - case REGEX_ERROR_NULLWSLIMIT: {Format(buf, buflen, "NULL WSLIMIT");} /* No longer actually used */ - case REGEX_ERROR_BADNEWLINE: {Format(buf, buflen, "Bad newline");} - case REGEX_ERROR_BADOFFSET: {Format(buf, buflen, "Bad offset");} - case REGEX_ERROR_SHORTUTF8: {Format(buf, buflen, "Short UFT8");} - case REGEX_ERROR_RECURSELOOP: {Format(buf, buflen, "Recurse loop");} - case REGEX_ERROR_JIT_STACKLIMIT: {Format(buf, buflen, "JIT Stacklimit");} - case REGEX_ERROR_BADMODE: {Format(buf, buflen, "Bad mode");} - case REGEX_ERROR_BADENDIANNESS: {Format(buf, buflen, "Bad endianness");} - case REGEX_ERROR_DFA_BADRESTART: {Format(buf, buflen, "DFA Bad Restart");} - case REGEX_ERROR_JIT_BADOPTION: {Format(buf, buflen, "JIT bad option");} - case REGEX_ERROR_BADLENGTH: {Format(buf, buflen, "Bad length");} - - default: {Format(buf, buflen, "Unknown Error");} + case REGEX_ERROR_NONE: {tmp = "No error";} + + case REGEX_ERROR_ASSERT: {tmp = "Internal error";} + case REGEX_ERROR_BADBR: {tmp = "Invalid repeat counts in {}";} + case REGEX_ERROR_BADPAT: {tmp = "Pattern error";} + case REGEX_ERROR_BADRPT: {tmp = "? * + invalid";} + case REGEX_ERROR_EBRACE: {tmp = "Unbalanced {}";} + case REGEX_ERROR_EBRACK: {tmp = "Unbalanced []";} + case REGEX_ERROR_ECOLLATE: {tmp = "Collation error - not relevant";} + case REGEX_ERROR_ECTYPE: {tmp = "Bad class";} + case REGEX_ERROR_EESCAPE: {tmp = "Bad escape sequence";} + case REGEX_ERROR_EMPTY: {tmp = "Empty expression";} + case REGEX_ERROR_EPAREN: {tmp = "Unbalanced ()";} + case REGEX_ERROR_ERANGE: {tmp = "Bad range inside []";} + case REGEX_ERROR_ESIZE: {tmp = "Expression too big";} + case REGEX_ERROR_ESPACE: {tmp = "Failed to get memory";} + case REGEX_ERROR_ESUBREG: {tmp = "Bad back reference";} + case REGEX_ERROR_INVARG: {tmp = "Bad argument";} + + case REGEX_ERROR_NOMATCH: {tmp = "No match was found";} + case REGEX_ERROR_NULL: {tmp = "Null";} + case REGEX_ERROR_BADOPTION: {tmp = "Bad Option";} + case REGEX_ERROR_BADMAGIC: {tmp = "Bad Magic";} + case REGEX_ERROR_UNKNOWN_OPCODE: {tmp = "Unknown OpCode";} + case REGEX_ERROR_NOMEMORY: {tmp = "No Memory";} + case REGEX_ERROR_NOSUBSTRING: {tmp = "No substring";} + case REGEX_ERROR_MATCHLIMIT: {tmp = "Match limit";} + case REGEX_ERROR_CALLOUT: {tmp = "Callout";} // Never used by PCRE itself + case REGEX_ERROR_BADUTF8: {tmp = "Bad UTF8";} + case REGEX_ERROR_BADUTF8_OFFSET: {tmp = "Bad UTF8 offset";} + case REGEX_ERROR_PARTIAL: {tmp = "Partial";} + case REGEX_ERROR_BADPARTIAL: {tmp = "Bad Partial";} + case REGEX_ERROR_INTERNAL: {tmp = "Internal error";} + case REGEX_ERROR_BADCOUNT: {tmp = "Bad count";} + case REGEX_ERROR_DFA_UITEM: {tmp = "DFA UItem";} + case REGEX_ERROR_DFA_UCOND: {tmp = "DFA UCOND";} + case REGEX_ERROR_DFA_UMLIMIT: {tmp = "DFA UMLIMIT";} + case REGEX_ERROR_DFA_WSSIZE: {tmp = "DFA WSSIZE";} + case REGEX_ERROR_DFA_RECURSE: {tmp = "DFA recurse";} + case REGEX_ERROR_RECURSIONLIMIT: {tmp = "Recursion Limit";} + case REGEX_ERROR_NULLWSLIMIT: {tmp = "NULL WSLIMIT";} /* No longer actually used */ + case REGEX_ERROR_BADNEWLINE: {tmp = "Bad newline";} + case REGEX_ERROR_BADOFFSET: {tmp = "Bad offset";} + case REGEX_ERROR_SHORTUTF8: {tmp = "Short UFT8";} + case REGEX_ERROR_RECURSELOOP: {tmp = "Recurse loop";} + case REGEX_ERROR_JIT_STACKLIMIT: {tmp = "JIT Stacklimit";} + case REGEX_ERROR_BADMODE: {tmp = "Bad mode";} + case REGEX_ERROR_BADENDIANNESS: {tmp = "Bad endianness";} + case REGEX_ERROR_DFA_BADRESTART: {tmp = "DFA Bad Restart";} + case REGEX_ERROR_JIT_BADOPTION: {tmp = "JIT bad option";} + case REGEX_ERROR_BADLENGTH: {tmp = "Bad length";} + + default: {tmp = "Unknown Error";} } - // Yeah I know this is terrible, and it's entirely because I'm not certain how string assignments work. If this were C I would - // make a normal char* string and make it point to different text depending on the case - - return 0; + return strcopy(buf, buflen, tmp); } \ No newline at end of file -- cgit v1.2.3