#pragma newdecls required #include #include public Plugin myinfo = { name = "SM Name Blocker", description = "A simple plugin to stop people with blacklisted names from joining a server", author = "NW/RL", version = "alpha-0.1", url = "git.dabikers.online/smnameblocker" }; #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) enum OperatingMode { OP_DISABLED, OP_KICK, OP_BAN, OP_TOOBIG } OperatingMode gOperMode = OP_DISABLED; ArrayList filterlist; // 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 public void OnAllPluginsLoaded() { filterlist = new ArrayList(ByteCountToCells(HANDLE_SIZE)); } public void OnClientPostAdminCheck(int client) { checkName(client); } public void OnClientSettingsChanged(int client) { checkName(client); } void checkName(int client) { if(client <= 0) return; char name[64 + 1]; if(getName(client, name, sizeof(name)) <= 0) return; // TODO: better error checking RegexError reerr; for(int i = 0, m = 0; i < filterlist.Length; i++) { m = MatchRegex(filterlist.Get(i), name, reerr); if(m == 0) continue; if(m < 0) { handleFailedRegex(client, reerr); return; } handleNameHit(client); } return; } int getName(int client, char[] buf, int buflen) { if(client <= 0 || buflen <= 0) return -1; if(IsNullString(buf)) return -1; return Format(buf, buflen, "%N", client); } int handleNameHit(int client) { if(client <= 0) return -1; switch(gOperMode) { case OP_DISABLED: {return 0;} case OP_KICK: { KickClient(client, "Failed Name Check"); // Log kick return 0; } case OP_BAN: { // BanClient() // TODO: Interop with other ban systems // Log ban } default: { LogError("Operating mode in an invalid state"); return -1; } } LogError("Broke out of switch statement that shouldn't have happened"); return -1; // Shouldn't get to this point } int handleFailedRegex(int client, RegexError reerr) { static 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); 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; 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");} } // 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; }