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