From 1f993820bc4c548c8290d05d65c9dab29390abdb Mon Sep 17 00:00:00 2001 From: nwrl Date: Sun, 27 Jul 2025 16:55:47 -0500 Subject: Initial Commit --- nameblocker.sp | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 nameblocker.sp (limited to 'nameblocker.sp') diff --git a/nameblocker.sp b/nameblocker.sp new file mode 100644 index 0000000..ebfd116 --- /dev/null +++ b/nameblocker.sp @@ -0,0 +1,216 @@ +#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; +} \ No newline at end of file -- cgit v1.2.3