summaryrefslogtreecommitdiff
path: root/nameblocker.sp
diff options
context:
space:
mode:
Diffstat (limited to 'nameblocker.sp')
-rw-r--r--nameblocker.sp216
1 files changed, 216 insertions, 0 deletions
diff --git a/nameblocker.sp b/nameblocker.sp
new file mode 100644
index 0000000..ebfd116
--- /dev/null
+++ b/nameblocker.sp
@@ -0,0 +1,216 @@
1#pragma newdecls required
2
3#include <sourcemod>
4#include <regex>
5#include <convars>
6
7public Plugin myinfo = {
8 name = "SM Name Blocker",
9 description = "A simple plugin to stop people with blacklisted names from joining a server",
10 author = "NW/RL",
11 version = "alpha-0.1",
12 url = "git.dabikers.online/smnameblocker"
13};
14
15// Planned features:
16 // Soft-ban: Kick players with offending names, but don't ban them. A name change will prevent the blocking
17 // Hard-ban: Ban players with offending names
18 // Moderation commands: A set of admin commands to add, modify, or remove name filters
19
20enum {
21 OPMODE_UNDEF,
22
23 OPMODE_DISABLED,
24 OPMODE_KICK,
25 OPMODE_BAN,
26
27 OPMODE_TOOBIG
28};
29
30static const int DEFAULT_OPERATING_MODE = OPMODE_KICK;
31static const int DEFAULT_REGEX_FLAGS = (PCRE_CASELESS | PCRE_EXTENDED | PCRE_NOTEMPTY | PCRE_UTF8);
32static const int DEFAULT_ADMIN_IMMUNITY = ADMFLAG_GENERIC;
33static const char DEFAULT_FAILED_NAME_MSG[] = "Failed name check";
34
35ConVar nb_cvarOperatingMode; static const char nb_cvarOM[] = "nb_OPERATINGMODE";
36ConVar nb_cvarRegexFlags; static const char nb_cvarDRFNAME[] = "nb_REGEXFLAGS";
37ConVar nb_cvarAdminImmunity; static const char nb_cvarDAINAME[] = "nb_ADMINIMMUNITY";
38ConVar nb_cvarFailedNameMessage; static const char nb_cvarFNMNAME[] = "nb_FAILEDNAMEMSG";
39
40ArrayList nb_filterlist;
41
42public void OnPluginStart() {
43 // Initialize data types
44 nb_filterlist = new ArrayList();
45
46 // Read config and populate filterlist
47 // TODO: This
48
49 // Register ConVars
50 char tmp[4 + 1]; IntToString(DEFAULT_OPERATING_MODE, tmp, sizeof(tmp));
51 nb_cvarOperatingMode = CreateConVar(nb_cvarOM, tmp, "Operating mode", 0, true, 0.0, true, 2.0);
52 nb_cvarOperatingMode.SetInt(DEFAULT_OPERATING_MODE);
53
54 IntToString(DEFAULT_REGEX_FLAGS, tmp, sizeof(tmp));
55 nb_cvarRegexFlags = CreateConVar(nb_cvarDRFNAME, tmp, "Regex compilation flags");
56 nb_cvarRegexFlags.SetInt(DEFAULT_REGEX_FLAGS);
57
58 IntToString(DEFAULT_ADMIN_IMMUNITY, tmp, sizeof(tmp));
59 nb_cvarAdminImmunity = CreateConVar(nb_cvarDAINAME, tmp, "Admin immunity flags");
60 nb_cvarAdminImmunity.SetInt(DEFAULT_ADMIN_IMMUNITY);
61
62 nb_cvarFailedNameMessage = CreateConVar(nb_cvarFNMNAME, DEFAULT_FAILED_NAME_MSG, "Message delivered to users when failing a name check");
63 nb_cvarFailedNameMessage.SetString(DEFAULT_FAILED_NAME_MSG);
64
65 AutoExecConfig(true, "nameblocker_cvars");
66
67 // Register admin commands
68 RegAdminCmd("nb_addpattern", nb_addpattern, ADMFLAG_BAN, "Add a regex pattern to the filter-list");
69 RegAdminCmd("nb_delpattern", nb_delpattern, ADMFLAG_BAN, "Remove a regex pattern from the filter-list");
70 RegAdminCmd("nb_modpattern", nb_modpattern, ADMFLAG_BAN, "Modify (replace) an existing regex pattern in the filter-list");
71
72 return;
73}
74
75public void OnClientPostAdminCheck(int client) {
76 checkName(client);
77
78}
79
80public void OnClientSettingsChanged(int client) {
81 // I'm not sure exactly what settings changes cause this to fire, but supposedly a name change would cause this to run, if
82 // that one user on AlliedForums is correct about OnClientSettingsChanged being Sourcemod's implementation of the player_info
83 // event. If this doesn't work, I can hook player_changename or player_info messages, and barring that I can still just poll
84 // everyone's names every minute or so
85
86 // Also, if checkName becomes too expensive, I'll create an adt_trie to keep track of users via the GetClientSerial and
87 // GetClientName functions (<K:V> -> <Serial:Name>)
88
89 checkName(client);
90}
91
92void checkName(int client) {
93 // Skip if checking is turned off
94 if(GetConVarInt(nb_cvarOperatingMode) <= OPMODE_DISABLED)
95 return;
96
97 // Don't bother checking / kicking the client if they're an admin / meet the immunity convar
98 if(CheckCommandAccess(client, "", GetConVarInt(nb_cvarAdminImmunity), true))
99 return;
100
101 char name[32 + 1];
102 GetClientName(client, name, sizeof(name));
103 if(strlen(name) <= 0) {
104 LogError("Tried getting a client's name and couldn't for some reason");
105 return;
106 }
107
108 for(int i = 0; i < nb_filterlist.Length; i++) {
109 Regex cur = nb_filterlist.Get(i); RegexError reerr;
110 if(MatchRegex(cur, name, reerr) > 0)
111 handleNameFail(client);
112 }
113
114 return;
115}
116
117void handleNameFail(int client) {
118 int mode = GetConVarInt(nb_cvarOperatingMode);
119 switch(mode) {
120 case OPMODE_KICK: {
121 char failmsg[512 + 1];
122 GetConVarString(nb_cvarFailedNameMessage, failmsg, sizeof(failmsg));
123 KickClient(client, failmsg);
124 }
125
126 case OPMODE_BAN: {
127 // TODO: Figure out how to interface with other banning tools
128 }
129
130 default: {
131 LogError("Tried to handle a failed name, but operating mode is invalid")
132 return;
133 }
134 }
135
136 return;
137}
138
139public Action nb_addpattern(int client, int args) {
140 if(args < 1) {
141 ReplyToCommand(client, "Error: No pattern specified");
142 return Plugin_Handled;
143 }
144
145 char pattern[512 + 1];
146 GetCmdArg(1, pattern, sizeof(pattern));
147 if(strlen(pattern) <= 0) {
148 ReplyToCommand(client, "Error: Pattern is null or empty");
149 return Plugin_Handled;
150 }
151
152 int reflags = GetConVarInt(nb_cvarRegexFlags);
153 if(args < 2) {
154 char flagholder[1 + 1]; // "Array", lol
155 GetCmdArg(2, flagholder, sizeof(flagholder));
156 reflags = StringToInt(flagholder, 2);
157 }
158
159 RegexError reerr = REGEX_ERROR_NONE; Regex re = null; char errbuf[512 + 1];
160 re = CompileRegex(pattern, reflags, errbuf, sizeof(errbuf), reerr);
161 if(re == INVALID_HANDLE) {
162 ReplyToCommand(client, "Error: Could not compile pattern: %s (%d)", errbuf, reerr);
163 return Plugin_Handled;
164 }
165
166 nb_filterlist.Push(re);
167
168 char id[8 + 1];
169 GetClientAuthId(client, AuthId_SteamID64, id, sizeof(id)); id[8] = '\0';
170 LogAction(client, -1, "Added %s to nameblocker filter list. (Added by: %s)", pattern, id);
171 ShowActivity2(client, "NameBlocker:", "Added %s to nameblocker filter list", pattern);
172
173 return Plugin_Handled;
174}
175
176public Action nb_delpattern(int client, int args) {
177 if(args < 1) {
178 ReplyToCommand(client, "Error: No pattern targeted");
179 return Plugin_Handled;
180 }
181
182 // Get index via GetCmdArg
183 // Check if it's valid
184 // Remove it from the filter-list
185
186 int index = GetCmdArgInt(1);
187 if(index < 0 || index >= nb_filterlist.Length) {
188 ReplyToCommand(client, "Error: Index out of range. Expected: [0, %d], Got: %d", (nb_filterlist.Length - 1), index);
189 return Plugin_Handled;
190 }
191
192 char id[8];
193 GetClientAuthId(client, AuthId_SteamID64, id, sizeof(id));
194 LogAction(client, -1, "Removed pattern #%d from filter list. (Removed by: %s)", index, id);
195 ShowActivity2(client, "NameBlocker:", "Removed pattern #%d from filter list", index);
196 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
197 nb_filterlist.Erase(index);
198
199 return Plugin_Handled;
200}
201
202public Action nb_modpattern(int client, int args) {
203 if(args < 2) {
204 if(args < 1)
205 ReplyToCommand(client, "Error: No pattern targeted for modification");
206 else
207 ReplyToCommand(client, "Error: No new pattern specified");
208
209 return Plugin_Handled;
210 }
211
212 // Get arguments and check if they're valid
213 // Replace regex handle at index with new regex
214
215 return Plugin_Handled;
216} \ No newline at end of file