#pragma newdecls required #include #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" }; // Planned features: // Soft-ban: Kick players with offending names, but don't ban them. A name change will prevent the blocking // Hard-ban: Ban players with offending names // Moderation commands: A set of admin commands to add, modify, or remove name filters enum { OPMODE_UNDEF, OPMODE_DISABLED, OPMODE_KICK, OPMODE_BAN, OPMODE_TOOBIG }; static const int DEFAULT_OPERATING_MODE = OPMODE_KICK; static const int DEFAULT_REGEX_FLAGS = (PCRE_CASELESS | PCRE_EXTENDED | PCRE_NOTEMPTY | PCRE_UTF8); static const int DEFAULT_ADMIN_IMMUNITY = ADMFLAG_GENERIC; static const char DEFAULT_FAILED_NAME_MSG[] = "Failed name check"; ConVar nb_cvarOperatingMode; static const char nb_cvarOM[] = "nb_OPERATINGMODE"; ConVar nb_cvarRegexFlags; static const char nb_cvarDRFNAME[] = "nb_REGEXFLAGS"; ConVar nb_cvarAdminImmunity; static const char nb_cvarDAINAME[] = "nb_ADMINIMMUNITY"; ConVar nb_cvarFailedNameMessage; static const char nb_cvarFNMNAME[] = "nb_FAILEDNAMEMSG"; ArrayList nb_filterlist; public void OnPluginStart() { // Initialize data types nb_filterlist = new ArrayList(); // Read config and populate filterlist // TODO: This // Register ConVars char tmp[4 + 1]; IntToString(DEFAULT_OPERATING_MODE, tmp, sizeof(tmp)); nb_cvarOperatingMode = CreateConVar(nb_cvarOM, tmp, "Operating mode", 0, true, 0.0, true, 2.0); nb_cvarOperatingMode.SetInt(DEFAULT_OPERATING_MODE); IntToString(DEFAULT_REGEX_FLAGS, tmp, sizeof(tmp)); nb_cvarRegexFlags = CreateConVar(nb_cvarDRFNAME, tmp, "Regex compilation flags"); nb_cvarRegexFlags.SetInt(DEFAULT_REGEX_FLAGS); IntToString(DEFAULT_ADMIN_IMMUNITY, tmp, sizeof(tmp)); nb_cvarAdminImmunity = CreateConVar(nb_cvarDAINAME, tmp, "Admin immunity flags"); nb_cvarAdminImmunity.SetInt(DEFAULT_ADMIN_IMMUNITY); nb_cvarFailedNameMessage = CreateConVar(nb_cvarFNMNAME, DEFAULT_FAILED_NAME_MSG, "Message delivered to users when failing a name check"); nb_cvarFailedNameMessage.SetString(DEFAULT_FAILED_NAME_MSG); AutoExecConfig(true, "nameblocker_cvars"); // Register admin commands RegAdminCmd("nb_addpattern", nb_addpattern, ADMFLAG_BAN, "Add a regex pattern to the filter-list"); RegAdminCmd("nb_delpattern", nb_delpattern, ADMFLAG_BAN, "Remove a regex pattern from the filter-list"); RegAdminCmd("nb_modpattern", nb_modpattern, ADMFLAG_BAN, "Modify (replace) an existing regex pattern in the filter-list"); return; } public void OnClientPostAdminCheck(int client) { checkName(client); } public void OnClientSettingsChanged(int client) { // I'm not sure exactly what settings changes cause this to fire, but supposedly a name change would cause this to run, if // that one user on AlliedForums is correct about OnClientSettingsChanged being Sourcemod's implementation of the player_info // event. If this doesn't work, I can hook player_changename or player_info messages, and barring that I can still just poll // everyone's names every minute or so // Also, if checkName becomes too expensive, I'll create an adt_trie to keep track of users via the GetClientSerial and // GetClientName functions ( -> ) checkName(client); } void checkName(int client) { // Skip if checking is turned off if(GetConVarInt(nb_cvarOperatingMode) <= OPMODE_DISABLED) return; // Don't bother checking / kicking the client if they're an admin / meet the immunity convar if(CheckCommandAccess(client, "", GetConVarInt(nb_cvarAdminImmunity), true)) return; char name[32 + 1]; GetClientName(client, name, sizeof(name)); if(strlen(name) <= 0) { LogError("Tried getting a client's name and couldn't for some reason"); return; } for(int i = 0; i < nb_filterlist.Length; i++) { Regex cur = nb_filterlist.Get(i); RegexError reerr; if(MatchRegex(cur, name, reerr) > 0) handleNameFail(client); } return; } void handleNameFail(int client) { int mode = GetConVarInt(nb_cvarOperatingMode); switch(mode) { case OPMODE_KICK: { char failmsg[512 + 1]; GetConVarString(nb_cvarFailedNameMessage, failmsg, sizeof(failmsg)); KickClient(client, failmsg); } case OPMODE_BAN: { // TODO: Figure out how to interface with other banning tools } default: { LogError("Tried to handle a failed name, but operating mode is invalid") return; } } return; } public Action nb_addpattern(int client, int args) { if(args < 1) { ReplyToCommand(client, "Error: No pattern specified"); return Plugin_Handled; } char pattern[512 + 1]; GetCmdArg(1, pattern, sizeof(pattern)); if(strlen(pattern) <= 0) { ReplyToCommand(client, "Error: Pattern is null or empty"); return Plugin_Handled; } int reflags = GetConVarInt(nb_cvarRegexFlags); if(args < 2) { char flagholder[1 + 1]; // "Array", lol GetCmdArg(2, flagholder, sizeof(flagholder)); reflags = StringToInt(flagholder, 2); } RegexError reerr = REGEX_ERROR_NONE; Regex re = null; char errbuf[512 + 1]; re = CompileRegex(pattern, reflags, errbuf, sizeof(errbuf), reerr); if(re == INVALID_HANDLE) { ReplyToCommand(client, "Error: Could not compile pattern: %s (%d)", errbuf, reerr); return Plugin_Handled; } nb_filterlist.Push(re); char id[8 + 1]; GetClientAuthId(client, AuthId_SteamID64, id, sizeof(id)); id[8] = '\0'; LogAction(client, -1, "Added %s to nameblocker filter list. (Added by: %s)", pattern, id); ShowActivity2(client, "NameBlocker:", "Added %s to nameblocker filter list", pattern); return Plugin_Handled; } public Action nb_delpattern(int client, int args) { if(args < 1) { ReplyToCommand(client, "Error: No pattern targeted"); return Plugin_Handled; } // Get index via GetCmdArg // Check if it's valid // Remove it from the filter-list int index = GetCmdArgInt(1); if(index < 0 || index >= nb_filterlist.Length) { ReplyToCommand(client, "Error: Index out of range. Expected: [0, %d], Got: %d", (nb_filterlist.Length - 1), index); return Plugin_Handled; } char id[8]; GetClientAuthId(client, AuthId_SteamID64, id, sizeof(id)); LogAction(client, -1, "Removed pattern #%d from filter list. (Removed by: %s)", index, id); ShowActivity2(client, "NameBlocker:", "Removed pattern #%d from filter list", index); CloseHandle(nb_filterlist.Get(index)); // Not sure if this is needed. (Source)Pawn is not a garbage collected language, so dropping handles should be bad, but I'm not certain whether CompileRegex allocates memory or not nb_filterlist.Erase(index); return Plugin_Handled; } public Action nb_modpattern(int client, int args) { if(args < 2) { if(args < 1) ReplyToCommand(client, "Error: No pattern targeted for modification"); else ReplyToCommand(client, "Error: No new pattern specified"); return Plugin_Handled; } // Get arguments and check if they're valid // Replace regex handle at index with new regex return Plugin_Handled; }