summaryrefslogtreecommitdiff
path: root/nameblocker.sp
diff options
context:
space:
mode:
authornwrl <n/a>2025-08-06 16:17:26 -0500
committernwrl <n/a>2025-08-06 16:17:26 -0500
commitb7851f3414e2cd52149c8f84329b3d788a7df08b (patch)
tree2e591b024fd8eba08815061f256de88cf07c6d74 /nameblocker.sp
parentf346a514ad5cf006793d31fbd208914735c89dd7 (diff)
Move some things around
Diffstat (limited to 'nameblocker.sp')
-rw-r--r--nameblocker.sp165
1 files changed, 115 insertions, 50 deletions
diff --git a/nameblocker.sp b/nameblocker.sp
index 554b086..3d5498c 100644
--- a/nameblocker.sp
+++ b/nameblocker.sp
@@ -1,19 +1,27 @@
1// Notes: 1// Notes:
2 // Memory allocated via the "new" keyword IS garbage collected. Handles are not. Close handles when you're done with them 2 // Memory allocated via the "new" keyword IS garbage collected. Handles are not. Close handles when you're done with them
3 // 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 3 // 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
4 // The contents of ArrayList objects are lost on map transition / server restart. Any persistent data should be stored in a 4 // The contents of ArrayList objects are lost on map transition / server restart. Any persistent data should be stored in a
5 // key-value file OR in a database 5 // key-value file OR in a database
6 6
7 // Basically every string function will add a null terminator, so add `+ 1` to the end of any defined size string definition 7 // Basically every string function will add a null terminator, so add `+ 1` to the end of any defined size string definition
8 // It is possible to throw an error with the ThrowError function, but anything that would bother should probably disable the 8 // It is possible to throw an error with the ThrowError function, but anything that would bother should probably disable the
9 // plugin entirely, so SetFailState is better 9 // plugin entirely, so SetFailState is better
10 10
11 // Large local var allocations if done commonly should be made static. Increases baseline memory usage, but lowers time spent 11 // Large local var allocations if done commonly should be made static. Increases baseline memory usage, but lowers time spent
12 // allocating memory. That being said, don't bother unless it's actually a problem. Name checks should be relatively 12 // allocating memory. That being said, don't bother unless it's actually a problem. Name checks should be relatively
13 // infrequent, and everything else exponentially less frequent. Dropping a frame or 2 to allocate 2048 bytes of memory 13 // infrequent, and everything else exponentially less frequent. Dropping a frame or 2 to allocate 2048 bytes of memory
14 // every century shouldn't be the end of the world 14 // every century shouldn't be the end of the world
15 15
16 // 16 // The database should contain:
17 // The string used to compile a regex pattern, sanitized to avoid sql injection attacks
18 // The steamid of the admin who added a regex pattern
19 // The time a regex pattern was added
20
21 // In the future, this may change to include regex compilation flags, kick/ban modes, etc. . Depends on how many features
22 // I want to cram into this given how fundamentally shitty SourcePawn as a language is. I'm not trying to keep track of 17
23 // different arrays and make sure that the indicies between them never get out of date. Maybe enum structs will solve my
24 // concerns, maybe not
17 25
18#pragma newdecls required 26#pragma newdecls required
19#pragma semicolon 1 27#pragma semicolon 1
@@ -35,6 +43,8 @@ public Plugin myinfo = {
35 43
36#define PATTERN_MAX_LEN (512 + 1) /* 512 chars + null terminator */ 44#define PATTERN_MAX_LEN (512 + 1) /* 512 chars + null terminator */
37 45
46#define DATABASE_FAIL_MSG "Could not populate regex & pattern lists from database"
47
38enum OperatingMode { 48enum OperatingMode {
39 OP_DISABLED, 49 OP_DISABLED,
40 OP_KICK, 50 OP_KICK,
@@ -57,7 +67,7 @@ ConVar gcvarAmdImmFlag; static const char ADMINIMMUNITYFLAGNAME[] = "nameb
57void logAndFail(const char[] format, any ...) { 67void logAndFail(const char[] format, any ...) {
58 char buf[2048 + 1]; 68 char buf[2048 + 1];
59 VFormat(buf, sizeof(buf), format, 2); 69 VFormat(buf, sizeof(buf), format, 2);
60 70
61 LogError(buf); 71 LogError(buf);
62 SetFailState(buf); 72 SetFailState(buf);
63} 73}
@@ -68,7 +78,7 @@ int concatArgs(char[] buf, int maxbuflen, int maxarglen, int end, int start=1) {
68 78
69 char[] arg = new char[maxarglen]; 79 char[] arg = new char[maxarglen];
70 char[] tmp = new char[maxbuflen]; 80 char[] tmp = new char[maxbuflen];
71 81
72 GetCmdArg(start, tmp, maxarglen); // Priming the first argument to prevent weirdness 82 GetCmdArg(start, tmp, maxarglen); // Priming the first argument to prevent weirdness
73 for(int i = start + 1; i <= end; i++) { 83 for(int i = start + 1; i <= end; i++) {
74 GetCmdArg(i, arg, maxarglen); 84 GetCmdArg(i, arg, maxarglen);
@@ -82,7 +92,7 @@ int concatArgs(char[] buf, int maxbuflen, int maxarglen, int end, int start=1) {
82int registerIntConVar(ConVar& cv, int defaultVal, const char[] name, const char[] desc) { 92int registerIntConVar(ConVar& cv, int defaultVal, const char[] name, const char[] desc) {
83 char tmp[32]; // Consider this a byte array 93 char tmp[32]; // Consider this a byte array
84 Format(tmp, sizeof(tmp), "%d", defaultVal); 94 Format(tmp, sizeof(tmp), "%d", defaultVal);
85 95
86 cv = CreateConVar(name, tmp, desc); 96 cv = CreateConVar(name, tmp, desc);
87 if(cv == null) return -1; 97 if(cv == null) return -1;
88 cv.IntValue = defaultVal; 98 cv.IntValue = defaultVal;
@@ -98,16 +108,7 @@ void xRegisterIntConVar(ConVar& cv, int defaultVal, const char[] name, const cha
98 108
99 109
100public void OnAllPluginsLoaded() { 110public void OnAllPluginsLoaded() {
101 // Initialize and populate datatypes 111 if(loadFromDatabase()) logAndFail(DATABASE_FAIL_MSG);
102 regexlist = new ArrayList(ByteCountToCells(HANDLE_SIZE));
103 if(regexlist == null) logAndFail("Could not initialize regexlist ArrayList");
104
105 patternlist = new ArrayList(ByteCountToCells(PATTERN_MAX_LEN));
106 if(patternlist == null) logAndFail("Could not initialize patternlist ArrayList");
107
108 char sqlerr[256 + 1];
109 db = SQL_DefConnect(sqlerr, sizeof(sqlerr)); // Default connect until I get a little more acquainted with sm's sql api
110 if(db == null) logAndFail("Could not connect to sql database: %s", sqlerr);
111 112
112 // Register convars 113 // Register convars
113 xRegisterIntConVar(gcvarOperMode, view_as<int>(DEFAULTOPERMODE), OPERMODENAME, "Operating mode (disabled, kick, ban, etc.)"); 114 xRegisterIntConVar(gcvarOperMode, view_as<int>(DEFAULTOPERMODE), OPERMODENAME, "Operating mode (disabled, kick, ban, etc.)");
@@ -118,23 +119,53 @@ public void OnAllPluginsLoaded() {
118 AutoExecConfig(true, "nameblocker_cvars"); 119 AutoExecConfig(true, "nameblocker_cvars");
119 120
120 // Register commands 121 // Register commands
121 RegAdminCmd("nb_addpattern", registerPattern, gcvarAdmCmdFlag.IntValue, "Add a regex pattern to the filter list"); 122 RegAdminCmd("nb_addpattern", cmdRegisterPattern, gcvarAdmCmdFlag.IntValue, "Add a regex pattern to the filter list");
122 RegAdminCmd("nb_removepattern", deletePattern, gcvarAdmCmdFlag.IntValue, "Remove a regex pattern from the filter list"); 123 RegAdminCmd("nb_removepattern", cmdDeletePattern, gcvarAdmCmdFlag.IntValue, "Remove a regex pattern from the filter list");
123 RegAdminCmd("nb_modifypattern", replacePattern, gcvarAdmCmdFlag.IntValue, "Replace an existing regex pattern with a new pattern"); 124 RegAdminCmd("nb_modifypattern", cmdReplacePattern, gcvarAdmCmdFlag.IntValue, "Replace an existing regex pattern with a new pattern");
124 RegAdminCmd("nb_listpatterns", listPatterns, gcvarAdmCmdFlag.IntValue, "List current regex patterns and their indicies"); 125 RegAdminCmd("nb_listpatterns", cmdListPatterns, gcvarAdmCmdFlag.IntValue, "List current regex patterns and their indicies");
126}
127
128public void OnConfigsExecuted() {
129 // I'm not sure when this is executed in relation to when OnClientPostAdminCheck is. Sourcemod's API reference says it's ran
130 // once after OnMapStart, but idk if "Map Start" is a sufficient enough game state for players to join
131
132 // Let me illustrate my concern:
133 // Server starts normally, plugin is working
134 // Players join. Nothing weird happens because the plugin loaded everything before a player could have joined
135 // Players/Server initiates a map change WHILE players are still connected. If OnMapStart is when players can start
136 // connecting, and is before OnConfigsExecuted, the following scenarios could happen:
137
138 // 1: OnMapStart fires, players may join
139 // 2: Player joins. This fires OnClientPostAdminCheck
140 // 3: OnClientPostAdminCheck fires checkName, which tries to querry the array lists
141 // 4: ArrayLists are empty or null, causing unexpected behavior
142 // 4.1: ArrayLists are empty, nothing major happens, but a player with an invalid name gets through. Not ideal, but
143 // also not the end of the world
144 // 4.2: ArrayLists are null, trying to use them causes a null pointer dereference, either crashing the plugin &/or
145 // server, or resulting in some other undefined behavior for sourcemod to deal with
146
147 // If OnMapStart doesn't let players join, or rather OnConfigsExecuted fires before players can join, then there's no
148 // problem. Alternatively, I can write something to kick/retry players until OnConfigsExecuted fires
149
150 // As of now, I will simply leave this as a note. No need to go making weird systems if they may not be necessary
151
152 if(loadFromDatabase()) logAndFail(DATABASE_FAIL_MSG);
153
125} 154}
126 155
127public void OnClientPostAdminCheck(int client) { 156public void OnClientPostAdminCheck(int client) {
128 checkName(client); 157 checkName(client);
158
129} 159}
130 160
131public void OnClientSettingsChanged(int client) { 161public void OnClientSettingsChanged(int client) {
132 checkName(client); 162 checkName(client);
163
133} 164}
134 165
135 166
136 167
137public Action registerPattern(int client, int args) { 168public Action cmdRegisterPattern(int client, int args) {
138 if(args < 2) { 169 if(args < 2) {
139 ReplyToCommand(client, "Error: missing regex pattern"); 170 ReplyToCommand(client, "Error: missing regex pattern");
140 return Plugin_Handled; 171 return Plugin_Handled;
@@ -146,7 +177,7 @@ public Action registerPattern(int client, int args) {
146 return Plugin_Handled; 177 return Plugin_Handled;
147 } 178 }
148 179
149 if(insertPattern(pattern, regexlist.Length)) { 180 if(modPattern(DBM_INSERT, regexlist.Length, pattern)) {
150 ReplyToCommand(client, "Error: could not register pattern"); 181 ReplyToCommand(client, "Error: could not register pattern");
151 return Plugin_Handled; 182 return Plugin_Handled;
152 } 183 }
@@ -154,7 +185,7 @@ public Action registerPattern(int client, int args) {
154 return Plugin_Handled; 185 return Plugin_Handled;
155} 186}
156 187
157public Action deletePattern(int client, int args) { 188public Action cmdDeletePattern(int client, int args) {
158 if(args < 2) { 189 if(args < 2) {
159 ReplyToCommand(client, "Error: no pattern index given"); 190 ReplyToCommand(client, "Error: no pattern index given");
160 return Plugin_Handled; 191 return Plugin_Handled;
@@ -166,7 +197,7 @@ public Action deletePattern(int client, int args) {
166 return Plugin_Handled; 197 return Plugin_Handled;
167 } 198 }
168 199
169 if(removePattern(index)) { 200 if(modPattern(DBM_DELETE, index)) {
170 ReplyToCommand(client, "Error: could not remove pattern"); 201 ReplyToCommand(client, "Error: could not remove pattern");
171 return Plugin_Handled; 202 return Plugin_Handled;
172 } 203 }
@@ -174,12 +205,12 @@ public Action deletePattern(int client, int args) {
174 return Plugin_Handled; 205 return Plugin_Handled;
175} 206}
176 207
177public Action replacePattern(int client, int args) { 208public Action cmdReplacePattern(int client, int args) {
178 if(args < 3) { 209 if(args < 3) {
179 ReplyToCommand(client, "Error: missing index, replacement pattern, or both"); 210 ReplyToCommand(client, "Error: missing index, replacement pattern, or both");
180 return Plugin_Handled; 211 return Plugin_Handled;
181 } 212 }
182 213
183 int index; 214 int index;
184 if(!GetCmdArgIntEx(1, index)) { 215 if(!GetCmdArgIntEx(1, index)) {
185 ReplyToCommand(client, "Error: index argument not numerical"); 216 ReplyToCommand(client, "Error: index argument not numerical");
@@ -192,23 +223,15 @@ public Action replacePattern(int client, int args) {
192 return Plugin_Handled; 223 return Plugin_Handled;
193 } 224 }
194 225
195 if(removePattern(index)) { 226 if(modPattern(DBM_INSERT | DBM_DELETE, index, pattern)) {
196 ReplyToCommand(client, "Error: could not remove pattern"); 227 ReplyToCommand(client, "Error: could not remove pattern from list");
197 return Plugin_Handled;
198 }
199 if(insertPattern(pattern, index)) {
200 ReplyToCommand(client, "Error: could not register pattern");
201 return Plugin_Handled; 228 return Plugin_Handled;
202 } 229 }
203 // Preferably this would be atomic as to not lose a pattern, but that's something I can do later
204
205 // I know I can make the database change "atomic" via the use of a transaction, but handling that would necessitate a different
206 // function
207 230
208 return Plugin_Handled; 231 return Plugin_Handled;
209} 232}
210 233
211public Action listPatterns(int client, int args) { 234public Action cmdListPatterns(int client, int args) {
212 for(int i = 0; i < patternlist.Length; i++) { 235 for(int i = 0; i < patternlist.Length; i++) {
213 ReplyToCommand(client, "[%d] %s", i, patternlist.Get(i)); 236 ReplyToCommand(client, "[%d] %s", i, patternlist.Get(i));
214 } 237 }
@@ -218,16 +241,9 @@ public Action listPatterns(int client, int args) {
218 241
219 242
220 243
221int insertPattern(char pattern[PATTERN_MAX_LEN], int index) { 244int aInsertPattern(char pattern[PATTERN_MAX_LEN], Regex res, int index) {
222 if(IsNullString(pattern) || index < 0 || index > regexlist.Length) return -1; 245 if(IsNullString(pattern) || index < 0 || index > regexlist.Length) return -1;
223 246
224 char errstr[512]; RegexError reerr;
225 Regex res = CompileRegex(pattern, gcvarRegexCompFlags.IntValue, errstr, sizeof(errstr), reerr);
226 if(res == null) {
227 LogError("Error: Could not compile regex pattern \"%s\": %s (%d)", pattern, errstr, reerr);
228 return -1;
229 }
230
231 if(index == regexlist.Length) { 247 if(index == regexlist.Length) {
232 regexlist.Push(res); 248 regexlist.Push(res);
233 patternlist.Push(pattern); 249 patternlist.Push(pattern);
@@ -240,18 +256,67 @@ int insertPattern(char pattern[PATTERN_MAX_LEN], int index) {
240 256
241 } 257 }
242 258
243 // TODO: This should also insert the pattern into the database
244
245 return 0; 259 return 0;
246} 260}
247 261
248int removePattern(int index) { 262int aRemovePattern(int index) {
249 if(index < 0 || index >= regexlist.Length) return -1; 263 if(index < 0 || index >= regexlist.Length) return -1;
250 264
251 regexlist.Erase(index); 265 regexlist.Erase(index);
252 patternlist.Erase(index); 266 patternlist.Erase(index);
253 267
254 // TODO: This should also remove the pattern from the database 268 return 0;
269}
270
271enum DBMOD_MODE {
272 DBM_UNDEF,
273 DBM_INSERT = (1<<0),
274 DBM_DELETE = (1<<1),
275 DBM_TOOBIG
276};
277
278int modPattern(DBMOD_MODE mode, int index, char[] pattern="") {
279 if(index < 0 || index > regexlist.Length) return -1;
280 if(mode <= DBM_UNDEF || mode >= DBM_TOOBIG) return -1;
281
282 Transaction modification = SQL_CreateTransaction();
283 if(modification == null) return -1;
284
285 // char errstr[512]; RegexError reerr;
286 // Regex res = CompileRegex(pattern, gcvarRegexCompFlags.IntValue, errstr, sizeof(errstr), reerr);
287 // if(res == null) {
288 // LogError("Error: Could not compile regex pattern \"%s\": %s (%d)", pattern, errstr, reerr);
289 // return -1;
290 // }
291
292 // Update transaction
293 // Update lists
294 // Try to delete before inserting
295
296 return 0;
297}
298
299int loadFromDatabase() {
300 logAndFail("function \"loadFromDatabase\" not implemented");
301
302 // Get database handle
303 char sqlerr[256 + 1];
304 db = SQLite_UseDatabase("sourcemod-local", sqlerr, sizeof(sqlerr));
305 if(db == null) logAndFail("Could not connect to sql database: %s", sqlerr);
306
307 // Initialize table if it doesn't exist
308
309
310 // Initialize and populate datatypes
311 regexlist = new ArrayList(ByteCountToCells(HANDLE_SIZE));
312 if(regexlist == null) logAndFail("Could not initialize regexlist ArrayList");
313
314 patternlist = new ArrayList(ByteCountToCells(PATTERN_MAX_LEN));
315 if(patternlist == null) logAndFail("Could not initialize patternlist ArrayList");
316
317 // select patterns from nameblock table/database
318 // compile each pattern
319 // insert pattern & regex into respective lists
255 320
256 return 0; 321 return 0;
257} 322}