]> jfr.im git - irc/quakenet/newserv.git/blob - regexgline/regexgline.c
Expired regexgline weren't removed from the db, not really a problem but nice to...
[irc/quakenet/newserv.git] / regexgline / regexgline.c
1 /* regexgline.c */
2
3 /* TODO:
4
5 FUTURE: natural (sort of) language parsing
6 ^^ CIDR
7
8 PPA: if multiple users match the same user@host or *@host it'll send multiple glines?!
9 */
10
11 #include "regexgline.h"
12 #include "../lib/version.h"
13
14 MODULE_VERSION("");
15
16 typedef struct rg_glinenode {
17 nick *np;
18 struct rg_struct *reason;
19 short punish;
20 struct rg_glinenode *next;
21 } rg_glinenode;
22
23 typedef struct rg_glinelist {
24 struct rg_glinenode *start;
25 struct rg_glinenode *end;
26 } rg_glinelist;
27
28 typedef struct rg_delay {
29 schedule *sch;
30 nick *np;
31 struct rg_struct *reason;
32 short punish;
33 struct rg_delay *next;
34 } rg_delay;
35
36 rg_delay *rg_delays;
37
38 void rg_setdelay(nick *np, struct rg_struct *reason, short punish);
39 void rg_deletedelay(rg_delay *delay);
40 void rg_dodelay(void *arg);
41
42 void rg_dogline(struct rg_glinelist *gll, nick *np, struct rg_struct *rp, char *matched);
43
44 void _init(void) {
45 sstring *max_casualties, *max_spew, *expiry_time, *max_per_gline;
46
47 max_casualties = getcopyconfigitem("regexgline", "maxcasualties", RGStringise(RG_MAX_CASUALTIES_DEFAULT), 8);
48 if(!protectedatoi(max_casualties->content, &rg_max_casualties))
49 rg_max_casualties = RG_MAX_CASUALTIES_DEFAULT;
50
51 freesstring(max_casualties);
52
53 max_spew = getcopyconfigitem("regexgline", "maxspew", RGStringise(RG_MAX_SPEW_DEFAULT), 8);
54 if(!protectedatoi(max_spew->content, &rg_max_spew))
55 rg_max_spew = RG_MAX_SPEW_DEFAULT;
56
57 freesstring(max_spew);
58
59 expiry_time = getcopyconfigitem("regexgline", "expirytime", RGStringise(RG_EXPIRY_TIME_DEFAULT), 8);
60 if(!protectedatoi(expiry_time->content, &rg_expiry_time))
61 rg_expiry_time = RG_EXPIRY_TIME_DEFAULT;
62
63 freesstring(expiry_time);
64
65 max_per_gline = getcopyconfigitem("regexgline", "maxpergline", RGStringise(RG_MAX_PER_GLINE_DEFAULT), 8);
66 if(!protectedatoi(max_per_gline->content, &rg_max_per_gline))
67 rg_max_per_gline = RG_MAX_PER_GLINE_DEFAULT;
68
69 freesstring(max_per_gline);
70
71 rg_delays = NULL;
72
73 if(!rg_dbconnect()) {
74 rg_dbload();
75
76 registercontrolhelpcmd("regexgline", NO_OPER, 4, &rg_gline, "Usage: regexgline <text>\nAdds a new regular expression pattern.");
77 registercontrolhelpcmd("regexdelgline", NO_OPER, 1, &rg_delgline, "Usage: regexdelgline <pattern>\nDeletes a regular expression pattern.");
78 registercontrolhelpcmd("regexglist", NO_OPER, 1, &rg_glist, "Usage: regexglist <pattern>\nLists regular expression patterns.");
79 registercontrolhelpcmd("regexspew", NO_OPER, 1, &rg_spew, "Usage: regexspew <pattern>\nLists users currently on the network which match the given pattern.");
80 registercontrolhelpcmd("regexidlookup", NO_OPER, 1, &rg_idlist, "Usage: regexidlookup <id>\nFinds a regular expression pattern by it's ID number.");
81
82 registerhook(HOOK_NICK_NEWNICK, &rg_nick);
83 registerhook(HOOK_NICK_RENAME, &rg_nick);
84 registerhook(HOOK_NICK_LOSTNICK, &rg_lostnick);
85 rg_startup();
86
87 rg_schedule = schedulerecurring(time(NULL) + 1, 0, 1, rg_checkexpiry, NULL);
88 }
89 }
90
91 void _fini(void) {
92 struct rg_struct *gp = rg_list, *oldgp;
93 rg_delay *delay, *delaynext;
94
95 deregisterhook(HOOK_NICK_NEWNICK, &rg_nick);
96 deregisterhook(HOOK_NICK_RENAME, &rg_nick);
97 deregisterhook(HOOK_NICK_LOSTNICK, &rg_lostnick);
98 deregistercontrolcmd("regexspew", rg_spew);
99 deregistercontrolcmd("regexglist", rg_glist);
100 deregistercontrolcmd("regexdelgline", rg_delgline);
101 deregistercontrolcmd("regexgline", rg_gline);
102 deregistercontrolcmd("regexidlookup", rg_idlist);
103
104 if(rg_delays) {
105 for(delay=rg_delays;delay;delay=delaynext) {
106 delaynext=delay->next;
107 deleteschedule(delay->sch, rg_dodelay, delay);
108 free(delay);
109 }
110 }
111
112 if(rg_schedule) {
113 deleteschedule(rg_schedule, &rg_checkexpiry, NULL);
114 rg_schedule = NULL;
115 }
116
117 for(gp=rg_list;gp;) {
118 oldgp = gp;
119 gp = gp->next;
120 rg_freestruct(oldgp);
121 }
122
123 if(rg_sqlconnected)
124 rg_sqldisconnect();
125 }
126
127 void rg_checkexpiry(void *arg) {
128 struct rg_struct *rp = rg_list, *lp = NULL;
129 time_t current = time(NULL);
130
131 while(rp) {
132 if (current >= rp->expires) {
133 rg_sqlquery("DELETE FROM regexglines WHERE id = %d", rp->id);
134 if (lp) {
135 lp->next = rp->next;
136 rg_freestruct(rp);
137 rp = lp->next;
138 } else {
139 rg_list = rp->next;
140 rg_freestruct(rp);
141 rp = rg_list;
142 }
143 } else {
144 lp = rp;
145 rp = rp->next;
146 }
147 }
148 }
149
150 void rg_setdelay(nick *np, rg_struct *reason, short punish) {
151 rg_delay *delay;
152 delay = (rg_delay *)malloc(sizeof(rg_delay));
153
154 /* Just incase */
155 if(!delay) {
156 killuser(NULL, np, "%s (ID: %08lx)", reason->reason->content, reason->glineid);
157 return;
158 }
159
160 delay->np = np;
161 delay->reason = reason;
162 delay->punish = punish;
163 delay->next = rg_delays;
164 rg_delays = delay;
165
166 delay->sch = scheduleoneshot(time(NULL) + (RG_MINIMUM_DELAY_TIME + (rand() % RG_MAXIMUM_RAND_TIME)), rg_dodelay, delay);
167 }
168
169 void rg_deletedelay(rg_delay *delay) {
170 rg_delay *temp, *prev;
171 prev = NULL;
172 for (temp=rg_delays;temp;temp=temp->next) {
173 if (temp==delay) {
174 if (temp==rg_delays)
175 rg_delays = temp->next;
176 else
177 prev->next = temp->next;
178
179 free(temp);
180 return;
181 }
182
183 prev = temp;
184 }
185 }
186
187 void rg_dodelay(void *arg) {
188 rg_delay *delay = (rg_delay *)arg;
189 char hostname[RG_MASKLEN];
190 int hostlen, usercount = 0;
191
192 /* User or regex gline no longer exists */
193 if((!delay->np) || (!delay->reason)) {
194 rg_deletedelay(delay);
195 return;
196 }
197
198 hostlen = RGBuildHostname(hostname, delay->np);
199
200 /* User has wisely changed nicknames */
201 if(pcre_exec(delay->reason->regex, delay->reason->hint, hostname, hostlen, 0, 0, NULL, 0) < 0) {
202 rg_deletedelay(delay);
203 return;
204 }
205
206 if (delay->reason->type == 5) {
207 usercount = delay->np->host->clonecount;
208 snprintf(hostname, sizeof(hostname), "*@%s", IPtostr(delay->np->p_ipaddr));
209 }
210
211 if((delay->reason->type == 4) || (usercount > rg_max_per_gline)) {
212 nick *tnp;
213
214 for(usercount=0,tnp=delay->np->host->nicks;tnp;tnp=tnp->nextbyhost)
215 if(!ircd_strcmp(delay->np->ident, tnp->ident))
216 usercount++;
217
218 snprintf(hostname, sizeof(hostname), "%s@%s", delay->np->ident, IPtostr(delay->np->p_ipaddr));
219 }
220
221 if ((delay->reason->type == 6) || (usercount > rg_max_per_gline)) {
222 if (IsAccount(delay->np)) {
223 controlwall(NO_OPER, NL_HITS, "%s!%s@%s/%s matched delayed kill regex %08lx", delay->np->nick, delay->np->ident, delay->np->host->name->content, delay->np->authname, delay->reason->glineid);
224 } else {
225 controlwall(NO_OPER, NL_HITS, "%s!%s@%s matched delayed kill regex %08lx", delay->np->nick, delay->np->ident, delay->np->host->name->content, delay->reason->glineid);
226 }
227 killuser(NULL, delay->np, "%s (ID: %08lx)", delay->reason->reason->content, delay->reason->glineid);
228 return;
229 }
230
231 if ((delay->reason->type <= 3) || (delay->reason->type >= 6))
232 return;
233
234 if (delay->reason->type == 4) {
235 if (IsAccount(delay->np)) {
236 controlwall(NO_OPER, NL_HITS, "%s!%s@%s/%s matched delayed user@host gline regex %08lx (hit %d user%s)", delay->np->nick, delay->np->ident, delay->np->host->name->content, delay->np->authname, delay->reason->glineid, usercount, (usercount!=1)?"s":"");
237 } else {
238 controlwall(NO_OPER, NL_HITS, "%s!%s@%s matched delayed user@host gline regex %08lx (hit %d user%s)", delay->np->nick, delay->np->ident, delay->np->host->name->content, delay->reason->glineid, usercount, (usercount!=1)?"s":"");
239 }
240 } else {
241 if (IsAccount(delay->np)) {
242 controlwall(NO_OPER, NL_HITS, "%s!%s@%s/%s matched delayed *@host gline regex %08lx (hit %d user%s)", delay->np->nick, delay->np->ident, delay->np->host->name->content, delay->np->authname, delay->reason->glineid, usercount, (usercount!=1)?"s":"");
243 } else {
244 controlwall(NO_OPER, NL_HITS, "%s!%s@%s matched delayed *@host gline regex %08lx (hit %d user%s)", delay->np->nick, delay->np->ident, delay->np->host->name->content, delay->reason->glineid, usercount, (usercount!=1)?"s":"");
245 }
246 }
247
248 irc_send("%s GL * +%s %d %d :AUTO: %s (ID: %08lx)\r\n", mynumeric->content, hostname, rg_expiry_time, time(NULL), delay->reason->reason->content, delay->reason->glineid);
249 rg_deletedelay(delay);
250 }
251
252 void rg_initglinelist(struct rg_glinelist *gll) {
253 gll->start = NULL;
254 gll->end = NULL;
255 }
256
257 void rg_flushglines(struct rg_glinelist *gll) {
258 struct rg_glinenode *nn, *pn;
259 for(nn=gll->start;nn;nn=pn) {
260 pn = nn->next;
261 if(nn->punish == 3) {
262 if ( IsAccount(nn->np) ) {
263 controlwall(NO_OPER, NL_HITS, "%s!%s@%s/%s matched kill regex %08lx", nn->np->nick, nn->np->ident, nn->np->host->name->content, nn->np->authname, nn->reason->glineid);
264 } else {
265 controlwall(NO_OPER, NL_HITS, "%s!%s@%s matched kill regex %08lx", nn->np->nick, nn->np->ident, nn->np->host->name->content, nn->reason->glineid);
266 }
267
268 killuser(NULL, nn->np, "%s (ID: %08lx)", nn->reason->reason->content, nn->reason->glineid);
269 } else if ((nn->punish == 4) || (nn->punish == 5) || (nn->punish == 6)) {
270 rg_setdelay(nn->np, nn->reason, nn->punish);
271 }
272 free(nn);
273 }
274
275 rg_initglinelist(gll);
276 }
277
278 int rg_dbconnect(void) {
279 sstring *dbhost, *dbusername, *dbpassword, *dbdatabase, *dbport;
280
281 dbhost = getcopyconfigitem("regexgline", "dbhost", "localhost", HOSTLEN);
282 dbusername = getcopyconfigitem("regexgline", "dbusername", "regexgline", 20);
283 dbpassword = getcopyconfigitem("regexgline", "dbpassword", "moo", 20);
284 dbdatabase = getcopyconfigitem("regexgline", "dbdatabase", "regexgline", 20);
285 dbport = getcopyconfigitem("regexgline", "dbport", "3306", 8);
286
287 if(rg_sqlconnect(dbhost->content, dbusername->content, dbpassword->content, dbdatabase->content, strtol(dbport->content, NULL, 10))) {
288 Error("regexgline", ERR_FATAL, "Cannot connect to database host!");
289 return 1; /* PPA: splidge: do something here 8]! */
290 } else {
291 rg_sqlconnected = 1;
292 }
293
294 freesstring(dbhost);
295 freesstring(dbusername);
296 freesstring(dbpassword);
297 freesstring(dbdatabase);
298 freesstring(dbport);
299
300 return 0;
301 }
302
303 int rg_dbload(void) {
304 rg_sqlquery("CREATE TABLE regexglines (id INT(10) PRIMARY KEY AUTO_INCREMENT, gline TEXT NOT NULL, setby VARCHAR(%d) NOT NULL, reason VARCHAR(%d) NOT NULL, expires BIGINT NOT NULL, type TINYINT(4) NOT NULL DEFAULT 1)", ACCOUNTLEN, RG_REASON_MAX);
305 rg_sqlquery("CREATE TABLE regexlogs (id INT(10) PRIMARY KEY AUTO_INCREMENT, host VARCHAR(%d) NOT NULL, account VARCHAR(%d) NOT NULL, event TEXT NOT NULL, arg TEXT NOT NULL, ts TIMESTAMP)", RG_MASKLEN - 1, ACCOUNTLEN);
306 rg_sqlquery("CREATE TABLE regexglinelog (id INT(10) PRIMARY KEY AUTO_INCREMENT, glineid INT(10) NOT NULL, ts TIMESTAMP, nickname VARCHAR(%d) NOT NULL, username VARCHAR(%d) NOT NULL, hostname VARCHAR(%d) NOT NULL, realname VARCHAR(%d))", NICKLEN, USERLEN, HOSTLEN, REALLEN);
307
308 if(!rg_sqlquery("SELECT id, gline, setby, reason, expires, type FROM regexglines")) {
309 rg_sqlresult res;
310 if((res = rg_sqlstoreresult())) {
311 rg_sqlrow row;
312 while((row = rg_sqlgetrow(res))) {
313 if (!rg_newsstruct(row[0], row[1], row[2], row[3], row[4], row[5], 0, 0))
314 rg_sqlquery("DELETE FROM regexglines WHERE id = %s", row[0]);
315 }
316 rg_sqlfree(res);
317 }
318 }
319
320 return 0;
321 }
322
323 void rg_nick(int hooknum, void *arg) {
324 nick *np = (nick *)arg;
325 struct rg_struct *rp;
326 char hostname[RG_MASKLEN];
327 int hostlen;
328 struct rg_glinelist gll;
329
330 rg_initglinelist(&gll);
331
332 hostlen = RGBuildHostname(hostname, np);
333
334 if(IsOper(np) || IsService(np) || IsXOper(np))
335 return;
336
337 for(rp=rg_list;rp;rp=rp->next) {
338 if(pcre_exec(rp->regex, rp->hint, hostname, hostlen, 0, 0, NULL, 0) >= 0) {
339 rg_dogline(&gll, np, rp, hostname);
340 break;
341 }
342 }
343
344 rg_flushglines(&gll);
345 }
346
347 void rg_lostnick(int hooknum, void *arg) {
348 nick *np = (nick *)arg;
349 rg_delay *delay;
350
351 /* Cleanup the delays */
352 for(delay=rg_delays;delay;delay=delay->next)
353 if(delay->np==np)
354 delay->np = NULL;
355 }
356
357 int rg_gline(void *source, int cargc, char **cargv) {
358 nick *np = (nick *)source, *tnp;
359 time_t realexpiry;
360 const char *expirybuf;
361 int expiry, count, j, hostlen;
362 struct rg_struct *rp;
363 struct rg_glinelist gll;
364
365 char eemask[RG_QUERY_BUF_SIZE], eesetby[RG_QUERY_BUF_SIZE], eereason[RG_QUERY_BUF_SIZE];
366 char hostname[RG_MASKLEN];
367
368 if(cargc < 4)
369 return CMD_USAGE;
370
371 if ((strlen(cargv[2]) != 1) || ((cargv[2][0] != '1') && (cargv[2][0] != '2') && (cargv[2][0] != '3') && (cargv[2][0] != '4') && (cargv[2][0] != '5') && (cargv[2][0] != '6'))) {
372 controlreply(np, "Invalid type specified!");
373 return CMD_USAGE;
374 }
375
376 if (!(expiry = durationtolong(cargv[1]))) {
377 controlreply(np, "Invalid duration specified!");
378 return CMD_USAGE;
379 }
380
381 for(rp=rg_list;rp;rp=rp->next) {
382 if (RGMasksEqual(rp->mask->content, cargv[0])) {
383 controlreply(np, "That regexp gline already exists!");
384 return CMD_ERROR;
385 }
386 }
387
388 if (rg_sanitycheck(cargv[0], &count)) {
389 controlreply(np, "Error in expression.");
390 return CMD_ERROR;
391 } else if (count < 0) {
392 controlreply(np, "That expression would hit too many users (%d)!", -count);
393 return CMD_ERROR;
394 }
395
396 realexpiry = expiry + time(NULL);
397
398 rg_sqlescape_string(eemask, cargv[0], strlen(cargv[0]));
399 rg_sqlescape_string(eesetby, np->nick, strlen(np->nick));
400 rg_sqlescape_string(eereason, cargv[3], strlen(cargv[3]));
401
402 rg_sqlquery("INSERT INTO regexglines (gline, setby, reason, expires, type) VALUES ('%s', '%s', '%s', %d, %s)", eemask, eesetby, eereason, realexpiry, cargv[2]);
403 if (!rg_sqlquery("SELECT LAST_INSERT_ID()")) {
404 rg_sqlresult res;
405 if((res = rg_sqlstoreresult())) {
406 rg_sqlrow row;
407 row = rg_sqlgetrow(res);
408 if (row) {
409 int id = atoi(row[0]);
410 if(id == 0) {
411 rp = NULL;
412 } else {
413 rp = rg_newsstruct(row[0], cargv[0], np->nick, cargv[3], "", cargv[2], realexpiry, 0);
414 }
415 rg_sqlfree(res);
416 if(!rp) {
417 rg_sqlquery("DELETE FROM regexglines WHERE gline = '%s' AND setby = '%s' AND reason = '%s' AND expires = %d AND type = %s ORDER BY ID DESC LIMIT 1", eemask, eesetby, eereason, realexpiry, cargv[2]);
418 controlreply(np, "Error allocating - regexgline NOT ADDED.");
419 return CMD_ERROR;
420 }
421 } else {
422 rg_sqlfree(res);
423 rg_sqlquery("DELETE FROM regexglines WHERE gline = '%s' AND setby = '%s' AND reason = '%s' AND expires = %d AND type = %s ORDER BY ID DESC LIMIT 1", eemask, eesetby, eereason, realexpiry, cargv[2]);
424 controlreply(np, "Error selecting ID from database - regexgline NOT ADDED.");
425 return CMD_ERROR;
426 }
427 } else {
428 rg_sqlquery("DELETE FROM regexglines WHERE gline = '%s' AND setby = '%s' AND reason = '%s' AND expires = %d AND type = %s ORDER BY ID DESC LIMIT 1", eemask, eesetby, eereason, realexpiry, cargv[2]);
429 controlreply(np, "Error fetching ID from database - regexgline NOT ADDED.");
430 return CMD_ERROR;
431 }
432 } else {
433 rg_sqlquery("DELETE FROM regexglines WHERE gline = '%s' AND setby = '%s' AND reason = '%s' AND expires = %d AND type = %s ORDER BY ID DESC LIMIT 1", eemask, eesetby, eereason, realexpiry, cargv[2]);
434 controlreply(np, "Error executing query - regexgline NOT ADDED.");
435 return CMD_ERROR;
436 }
437
438 rg_initglinelist(&gll);
439
440 for(j=0;j<NICKHASHSIZE;j++) {
441 for(tnp=nicktable[j];tnp;tnp=tnp->next) {
442 if(IsOper(tnp) || IsService(tnp) || IsXOper(tnp))
443 continue;
444
445 hostlen = RGBuildHostname(hostname, tnp);
446 if(pcre_exec(rp->regex, rp->hint, hostname, hostlen, 0, 0, NULL, 0) >= 0)
447 rg_dogline(&gll, tnp, rp, hostname);
448 }
449 }
450
451 rg_flushglines(&gll);
452
453 expirybuf = longtoduration(expiry, 0);
454
455 rg_logevent(np, "regexgline", "%s %d %d %s", cargv[0], expiry, count, cargv[3]);
456 controlreply(np, "Added regexgline: %s (expires in: %s, hit %d user%s): %s", cargv[0], expirybuf, count, (count!=1)?"s":"", cargv[3]);
457 /* If we are using NO, can we safely assume the user is authed here and use ->authname? */
458 controlwall(NO_OPER, NL_GLINES, "%s!%s@%s/%s added regexgline: %s (expires in: %s, hit %d user%s): %s", np->nick, np->ident, np->host->name->content, np->authname, cargv[0], expirybuf, count, (count!=1)?"s":"", cargv[3]);
459
460 return CMD_OK;
461 }
462
463 int rg_sanitycheck(char *mask, int *count) {
464 const char *error;
465 char hostname[RG_MASKLEN];
466 int erroroffset, hostlen, j, masklen = strlen(mask);
467 pcre *regex;
468 pcre_extra *hint;
469 nick *np;
470
471 if((masklen < RG_MIN_MASK_LEN) || (masklen > RG_REGEXGLINE_MAX))
472 return 1;
473
474 if(!(regex = pcre_compile(mask, RG_PCREFLAGS, &error, &erroroffset, NULL))) {
475 Error("regexgline", ERR_WARNING, "Error compiling expression %s at offset %d: %s", mask, erroroffset, error);
476 return 2;
477 } else {
478 hint = pcre_study(regex, 0, &error);
479 if(error) {
480 Error("regexgline", ERR_WARNING, "Error studying expression %s: %s", mask, error);
481 pcre_free(regex);
482 return 3;
483 }
484 }
485
486 *count = 0;
487 for(j=0;j<NICKHASHSIZE;j++) {
488 for(np=nicktable[j];np;np=np->next) {
489 hostlen = RGBuildHostname(hostname, np);
490 if(pcre_exec(regex, hint, hostname, hostlen, 0, 0, NULL, 0) >= 0) {
491 (*count)++;
492 }
493 }
494 }
495
496 pcre_free(regex);
497 if(hint)
498 pcre_free(hint);
499
500 if(*count >= rg_max_casualties)
501 *count = -(*count);
502
503 return 0;
504 }
505
506 int rg_delgline(void *source, int cargc, char **cargv) {
507 nick *np = (nick *)source;
508 rg_delay *delay;
509 struct rg_struct *rp = rg_list, *last = NULL;
510 int count = 0;
511
512 if(cargc < 1)
513 return CMD_USAGE;
514
515 rg_logevent(np, "regexdelgline", "%s", cargv[0]);
516 while(rp) {
517 if(RGMasksEqual(rp->mask->content, cargv[0])) {
518 count++;
519
520 /* Cleanup the delays */
521 for(delay=rg_delays;delay;delay=delay->next)
522 if(delay->reason==rp)
523 delay->reason = NULL;
524
525 rg_sqlquery("DELETE FROM regexglines WHERE id = %d", rp->id);
526 if(last) {
527 last->next = rp->next;
528 rg_freestruct(rp);
529 rp = last->next;
530 } else {
531 rg_list = rp->next;
532 rg_freestruct(rp);
533 rp = rg_list;
534 }
535 } else {
536 last = rp;
537 rp = rp->next;
538 }
539 }
540 if (count > 0) {
541 controlreply(np, "Deleted (matched: %d).", count);
542 /* If we are using NO, can we safely assume the user is authed here and use ->authname? */
543 controlwall(NO_OPER, NL_GLINES, "%s!%s@%s/%s removed regexgline: %s", np->nick, np->ident, np->host->name->content, np->authname, cargv[0]);
544 } else {
545 controlreply(np, "No glines matched: %s", cargv[0]);
546 }
547 return CMD_OK;
548 }
549
550 int rg_idlist(void *source, int cargc, char **cargv) {
551 nick *np = (nick *)source;
552
553 if(cargc < 1) {
554 return CMD_USAGE;
555 } else if (strlen(cargv[0]) != 8) {
556 controlreply(np, "Invalid gline id!");
557 return CMD_ERROR;
558 } else {
559 struct rg_struct *rp;
560 unsigned long id = 0;
561 int i;
562
563 for(i=0;i<8;i++) {
564 if(0xff == rc_hexlookup[(int)cargv[0][i]]) {
565 controlreply(np, "Invalid gline id!");
566 return CMD_ERROR;
567 } else {
568 id = (id << 4) | rc_hexlookup[(int)cargv[0][i]];
569 }
570 }
571
572 controlreply(np, "Mask Expires Set by Type Reason");
573 for(rp=rg_list;rp;rp=rp->next)
574 if(id == rp->glineid)
575 rg_displaygline(np, rp);
576 controlreply(np, "Done.");
577
578 return CMD_OK;
579 }
580 }
581
582 int rg_glist(void *source, int cargc, char **cargv) {
583 nick *np = (nick *)source;
584 struct rg_struct *rp;
585
586 if(cargc) {
587 int erroroffset;
588 pcre *regex;
589 pcre_extra *hint;
590 const char *error;
591
592 if(!(regex = pcre_compile(cargv[0], RG_PCREFLAGS, &error, &erroroffset, NULL))) {
593 controlreply(np, "Error compiling expression %s at offset %d: %s", cargv[0], erroroffset, error);
594 return CMD_ERROR;
595 } else {
596 hint = pcre_study(regex, 0, &error);
597 if(error) {
598 controlreply(np, "Error studying expression %s: %s", cargv[0], error);
599 pcre_free(regex);
600 return CMD_ERROR;
601 }
602 }
603
604 rg_logevent(np, "regexglist", "%s", cargv[0]);
605 controlreply(np, "Mask Expires Set by Type Reason");
606 for(rp=rg_list;rp;rp=rp->next)
607 if(pcre_exec(regex, hint, rp->mask->content, rp->mask->length, 0, 0, NULL, 0) >= 0)
608 rg_displaygline(np, rp);
609
610 pcre_free(regex);
611 if(hint)
612 pcre_free(hint);
613
614 } else {
615 rg_logevent(np, "regexglist", "");
616 controlreply(np, "Mask Expires Set by Type Reason");
617 for(rp=rg_list;rp;rp=rp->next)
618 rg_displaygline(np, rp);
619 }
620
621 controlreply(np, "Done.");
622 return CMD_OK;
623 }
624
625 void rg_displaygline(nick *np, struct rg_struct *rp) { /* could be a macro? I'll assume the C compiler inlines it */
626 controlreply(np, "%-25s %-20s %-15s %-4d %s", rp->mask->content, longtoduration(rp->expires - time(NULL), 0), rp->setby->content, rp->type, rp->reason->content);
627 }
628
629 int rg_spew(void *source, int cargc, char **cargv) {
630 nick *np = (nick *)source, *tnp;
631 int counter = 0, erroroffset, hostlen, j;
632 pcre *regex;
633 pcre_extra *hint;
634 const char *error;
635 char hostname[RG_MASKLEN];
636 int ovector[30];
637 int pcreret;
638
639 if(cargc < 1)
640 return CMD_USAGE;
641
642 if(!(regex = pcre_compile(cargv[0], RG_PCREFLAGS, &error, &erroroffset, NULL))) {
643 controlreply(np, "Error compiling expression %s at offset %d: %s", cargv[0], erroroffset, error);
644 return CMD_ERROR;
645 } else {
646 hint = pcre_study(regex, 0, &error);
647 if(error) {
648 controlreply(np, "Error studying expression %s: %s", cargv[0], error);
649 pcre_free(regex);
650 return CMD_ERROR;
651 }
652 }
653
654 rg_logevent(np, "regexspew", "%s", cargv[0]);
655
656 for(j=0;j<NICKHASHSIZE;j++) {
657 for(tnp=nicktable[j];tnp;tnp=tnp->next) {
658 hostlen = RGBuildHostname(hostname, tnp);
659 pcreret = pcre_exec(regex, hint, hostname, hostlen, 0, 0, ovector, sizeof(ovector) / sizeof(int));
660 if(pcreret >= 0) {
661 if(counter == rg_max_spew) {
662 controlreply(np, "Reached maximum spew count (%d) - aborting display.", rg_max_spew);
663 } else if (counter < rg_max_spew) {
664 /* 15 should be number of bolds */
665 char boldbuf[RG_MASKLEN + 15], *tp, *fp, *realname = NULL;
666 int boldon = 0;
667 for(tp=hostname,fp=boldbuf;*tp;) {
668 if(tp - hostname == ovector[0]) {
669 *fp++ = '\002';
670 boldon = 1;
671 }
672 if(tp - hostname == ovector[1]) {
673 *fp++ = '\002';
674 boldon = 0;
675 }
676 if(*tp == '\r') {
677 if(boldon)
678 *fp++ = '\002';
679 *fp++ = '\0';
680 realname = fp;
681 if(boldon)
682 *fp++ = '\002';
683 tp++;
684 } else {
685 *fp++ = *tp++;
686 }
687 }
688 if(boldon)
689 *fp++ = '\002';
690 *fp++ = '\0';
691 controlreply(np, "%s (%s) (%dc)", boldbuf, realname, tnp->channels->cursi);
692 }
693 counter++;
694 }
695 }
696 }
697 controlreply(np, "Done - %d matches.", counter);
698
699 pcre_free(regex);
700 if(hint)
701 pcre_free(hint);
702
703 return CMD_OK;
704 }
705
706 int rg_sqlconnect(char *dbhost, char *dbuser, char *dbpass, char *db, unsigned int port) {
707 mysql_init(&rg_sql);
708 if(!mysql_real_connect(&rg_sql, dbhost, dbuser, dbpass, db, port, NULL, 0))
709 return -1;
710 return 0;
711 }
712
713 void rg_sqldisconnect(void) {
714 mysql_close(&rg_sql);
715 }
716
717 void rg_sqlescape_string(char *dest, char *source, size_t length) {
718 if(length >= RG_QUERY_BUF_SIZE)
719 length = RG_QUERY_BUF_SIZE - 1;
720
721 mysql_real_escape_string(&rg_sql, dest, source, length);
722 }
723
724 int rg_sqlquery(char *format, ...) {
725 char rg_sqlquery[RG_QUERY_BUF_SIZE];
726 va_list va;
727
728 va_start(va, format);
729 vsnprintf(rg_sqlquery, sizeof(rg_sqlquery), format, va);
730 va_end(va);
731
732 return mysql_query(&rg_sql, rg_sqlquery);
733 }
734
735 rg_sqlresult rg_sqlstoreresult(void) {
736 return mysql_store_result(&rg_sql);
737 }
738
739 rg_sqlrow rg_sqlgetrow(rg_sqlresult res) {
740 return mysql_fetch_row(res);
741 }
742
743 void rg_sqlfree(rg_sqlresult res) {
744 mysql_free_result(res);
745 }
746
747 void rg_startup(void) {
748 int j, hostlen;
749 nick *np;
750 struct rg_struct *rp;
751 struct rg_glinelist gll;
752 char hostname[RG_MASKLEN];
753
754 rg_initglinelist(&gll);
755
756 for(j=0;j<NICKHASHSIZE;j++) {
757 for(np=nicktable[j];np;np=np->next) {
758 if(IsOper(np) || IsService(np) || IsXOper(np))
759 continue;
760 hostlen = RGBuildHostname(hostname, np);
761 for(rp=rg_list;rp;rp=rp->next) {
762 if(pcre_exec(rp->regex, rp->hint, hostname, hostlen, 0, 0, NULL, 0) >= 0) {
763 rg_dogline(&gll, np, rp, hostname);
764 break;
765 }
766 }
767 }
768 }
769
770 rg_flushglines(&gll);
771 }
772
773 void rg_freestruct(struct rg_struct *rp) {
774 freesstring(rp->mask);
775 freesstring(rp->setby);
776 freesstring(rp->reason);
777 pcre_free(rp->regex);
778 if(rp->hint)
779 pcre_free(rp->hint);
780 free(rp);
781 }
782
783 struct rg_struct *rg_newstruct(time_t expires) {
784 struct rg_struct *rp;
785
786 if (time(NULL) >= expires)
787 return NULL;
788
789 rp = (struct rg_struct*)malloc(sizeof(struct rg_struct));
790 if(rp) {
791 struct rg_struct *tp, *lp;
792 rp->id = 0;
793 rp->mask = NULL;
794 rp->setby = NULL;
795 rp->reason = NULL;
796 rp->expires = expires;
797 rp->type = 0;
798 rp->regex = NULL;
799 rp->hint = NULL;
800
801 for(lp=NULL,tp=rg_list;tp;lp=tp,tp=tp->next) {
802 if (expires <= tp->expires) { /* <= possible, slight speed increase */
803 rp->next = tp;
804 if (lp) {
805 lp->next = rp;
806 } else {
807 rg_list = rp;
808 }
809 break;
810 }
811 }
812 if (!tp) {
813 rp->next = NULL;
814 if (lp) {
815 lp->next = rp;
816 } else {
817 rg_list = rp;
818 }
819 }
820
821 }
822 return rp;
823 }
824
825 struct rg_struct *rg_newsstruct(char *id, char *mask, char *setby, char *reason, char *expires, char *type, time_t iexpires, int iid) {
826 struct rg_struct *newrow, *lp, *cp;
827 time_t rexpires;
828 char glineiddata[1024];
829 if (iexpires == 0) {
830 int qexpires;
831 if(!protectedatoi(expires, &qexpires))
832 return NULL;
833 rexpires = (time_t)qexpires;
834 } else {
835 rexpires = iexpires;
836 }
837
838 newrow = rg_newstruct(rexpires);
839
840 if(newrow) {
841 const char *error;
842 int erroroffset;
843
844 if(!(newrow->regex = pcre_compile(mask, RG_PCREFLAGS, &error, &erroroffset, NULL))) {
845 Error("regexgline", ERR_WARNING, "Error compiling expression %s at offset %d: %s", mask, erroroffset, error);
846 goto dispose;
847 } else {
848 newrow->hint = pcre_study(newrow->regex, 0, &error);
849 if(error) {
850 Error("regexgline", ERR_WARNING, "Error studying expression %s: %s", mask, error);
851 pcre_free(newrow->regex);
852 goto dispose;
853 }
854 }
855
856 if (!iid) {
857 if(!protectedatoi(id, &newrow->id))
858 goto dispose2;
859 } else {
860 newrow->id = iid;
861 }
862
863 newrow->mask = getsstring(mask, RG_REGEXGLINE_MAX);
864 if(!newrow->mask) {
865 Error("regexgline", ERR_WARNING, "Error allocating memory for mask!");
866 goto dispose2;
867 }
868
869 newrow->setby = getsstring(setby, ACCOUNTLEN);
870 if(!newrow->setby) {
871 Error("regexgline", ERR_WARNING, "Error allocating memory for setby!");
872 goto dispose2;
873 }
874
875 newrow->reason = getsstring(reason, RG_REASON_MAX);
876 if(!newrow->reason) {
877 Error("regexgline", ERR_WARNING, "Error allocating memory for reason!");
878 goto dispose2;
879 }
880
881 if(!protectedatoi(type, &newrow->type))
882 newrow->type = 0; /* just in case */
883
884 snprintf(glineiddata, sizeof(glineiddata), "%s regexgline %s %s %s %d %d", mynumeric->content, mask, setby, reason, (int)iexpires, newrow->type);
885 newrow->glineid = crc32(glineiddata);
886 }
887
888 return newrow;
889
890 dispose2:
891 if(newrow->mask)
892 freesstring(newrow->mask);
893 if(newrow->setby)
894 freesstring(newrow->setby);
895 if(newrow->reason)
896 freesstring(newrow->reason);
897 pcre_free(newrow->regex);
898 if(newrow->hint)
899 pcre_free(newrow->hint);
900
901 dispose:
902 for(lp=NULL,cp=rg_list;cp;lp=cp,cp=cp->next) {
903 if(newrow == cp) {
904 if(lp) {
905 lp->next = cp->next;
906 } else {
907 rg_list = cp->next;
908 }
909 free(newrow);
910 break;
911 }
912 }
913 return NULL;
914 }
915
916 int __rg_dogline(struct rg_glinelist *gll, nick *np, struct rg_struct *rp, char *matched) { /* PPA: if multiple users match the same user@host or *@host it'll send multiple glines?! */
917 char hostname[RG_MASKLEN];
918 int usercount = 0;
919
920 rg_loggline(rp, np);
921
922 if (rp->type == 2) {
923 usercount = np->host->clonecount;
924 snprintf(hostname, sizeof(hostname), "*@%s", IPtostr(np->p_ipaddr));
925 }
926
927 if ((rp->type == 1) || (usercount > rg_max_per_gline)) {
928 nick *tnp;
929
930 for(usercount=0,tnp=np->host->nicks;tnp;tnp=tnp->nextbyhost)
931 if(!ircd_strcmp(np->ident, tnp->ident))
932 usercount++;
933
934 snprintf(hostname, sizeof(hostname), "%s@%s", np->ident, IPtostr(np->p_ipaddr));
935 }
936
937 if ((rp->type >= 3) || (usercount > rg_max_per_gline)) {
938 struct rg_glinenode *nn = (struct rg_glinenode *)malloc(sizeof(struct rg_glinenode));
939 if(nn) {
940 nn->next = NULL;
941 if(gll->end) {
942 gll->end->next = nn;
943 gll->end = nn;
944 } else {
945 gll->start = nn;
946 gll->end = nn;
947 }
948 nn->np = np;
949 nn->reason = rp;
950
951 if (rp->type <= 3) {
952 nn->punish = 3;
953 } else {
954 nn->punish = rp->type;
955 }
956 }
957 return usercount;
958 }
959
960 if ((rp->type <= 0) || (rp->type >= 3))
961 return 0;
962
963 if (rp->type == 1) {
964 if (IsAccount(np)) {
965 controlwall(NO_OPER, NL_HITS, "%s!%s@%s/%s matched user@host gline regex %08lx (hit %d user%s)", np->nick, np->ident, np->host->name->content, np->authname, rp->glineid, usercount, (usercount!=1)?"s":"");
966 } else {
967 controlwall(NO_OPER, NL_HITS, "%s!%s@%s matched user@host gline regex %08lx (hit %d user%s)", np->nick, np->ident, np->host->name->content, rp->glineid, usercount, (usercount!=1)?"s":"");
968 }
969 } else {
970 if (IsAccount(np)) {
971 controlwall(NO_OPER, NL_HITS, "%s!%s@%s/%s matched *@host gline regex %08lx (hit %d user%s)", np->nick, np->ident, np->host->name->content, np->authname, rp->glineid, usercount, (usercount!=1)?"s":"");
972 } else {
973 controlwall(NO_OPER, NL_HITS, "%s!%s@%s matched *@host gline regex %08lx (hit %d user%s)", np->nick, np->ident, np->host->name->content, rp->glineid, usercount, (usercount!=1)?"s":"");
974 }
975 }
976
977 irc_send("%s GL * +%s %d %d :AUTO: %s (ID: %08lx)\r\n", mynumeric->content, hostname, rg_expiry_time, time(NULL), rp->reason->content, rp->glineid);
978 return usercount;
979 }
980
981 static int floodprotection = 0;
982 static int lastfloodspam = 0;
983
984 void rg_dogline(struct rg_glinelist *gll, nick *np, struct rg_struct *rp, char *matched) {
985 int t = time(NULL);
986
987 if(t > floodprotection) {
988 floodprotection = t;
989 } else if((floodprotection - t) / 8 > RG_NETWORK_WIDE_MAX_GLINES_PER_8_SEC) {
990 if(t > lastfloodspam + 3600) {
991 channel *cp = findchannel("#twilightzone");
992 if(cp)
993 controlchanmsg(cp, "WARNING! REGEXGLINE DISABLED FOR AN HOUR DUE TO NETWORK WIDE LOOKING GLINE!: %d exceeded %d", (floodprotection - t) / 8, RG_NETWORK_WIDE_MAX_GLINES_PER_8_SEC);
994 controlwall(NO_OPER, NL_MANAGEMENT, "WARNING! REGEXGLINE DISABLED FOR AN HOUR DUE TO NETWORK WIDE LOOKING GLINE!");
995 lastfloodspam = t;
996 floodprotection = t + RG_NETWORK_WIDE_MAX_GLINES_PER_8_SEC * 3600 * 8;
997 }
998 return;
999 }
1000
1001 floodprotection+=__rg_dogline(gll, np, rp, matched);
1002 }
1003
1004 void rg_logevent(nick *np, char *event, char *details, ...) {
1005 char eeevent[RG_QUERY_BUF_SIZE], eedetails[RG_QUERY_BUF_SIZE], eemask[RG_QUERY_BUF_SIZE], eeaccount[RG_QUERY_BUF_SIZE];
1006 char buf[513], account[ACCOUNTLEN + 1], mask[RG_MASKLEN];
1007 int masklen;
1008
1009 return;
1010 va_list va;
1011
1012 va_start(va, details);
1013 vsnprintf(buf, sizeof(buf), details, va);
1014 va_end(va);
1015
1016 if(np) {
1017 if (IsAccount(np)) {
1018 strncpy(account, np->authname, sizeof(account) - 1);
1019 account[sizeof(account) - 1] = '\0';
1020 } else {
1021 account[0] = '\0';
1022 }
1023 masklen = RGBuildHostname(mask, np);
1024 } else {
1025 mask[0] = '\0';
1026 masklen = 0;
1027 }
1028
1029 rg_sqlescape_string(eeevent, event, strlen(event));
1030 rg_sqlescape_string(eedetails, buf, strlen(buf));
1031 rg_sqlescape_string(eeaccount, event, strlen(account));
1032 rg_sqlescape_string(eemask, mask, masklen);
1033
1034 rg_sqlquery("INSERT INTO regexlogs (host, account, event, arg) VALUES ('%s', '%s', '%s', '%s')", eemask, eeaccount, eeevent, eedetails);
1035 }
1036
1037 void rg_loggline(struct rg_struct *rg, nick *np) {
1038 char eenick[RG_QUERY_BUF_SIZE], eeuser[RG_QUERY_BUF_SIZE], eehost[RG_QUERY_BUF_SIZE], eereal[RG_QUERY_BUF_SIZE];
1039
1040 return;
1041 rg_sqlescape_string(eenick, np->nick, strlen(np->nick));
1042 rg_sqlescape_string(eeuser, np->ident, strlen(np->ident));
1043 rg_sqlescape_string(eehost, np->host->name->content, strlen(np->host->name->content));
1044 rg_sqlescape_string(eereal, np->realname->name->content, strlen(np->realname->name->content));
1045
1046 rg_sqlquery("INSERT INTO regexglinelog (glineid, nickname, username, hostname, realname) VALUES (%d, '%s', '%s', '%s', '%s')", rg->id, eenick, eeuser, eehost, eereal);
1047 }