]> jfr.im git - irc/quakenet/newserv.git/blob - regexgline/regexgline.c
Merge.
[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 #include "../dbapi/dbapi.h"
14 #include "../lib/stringbuf.h"
15 #include "../core/hooks.h"
16 #include "../server/server.h"
17 #include "../lib/strlfunc.h"
18 #include <stdint.h>
19
20 #define INSTANT_IDENT_GLINE 1
21 #define INSTANT_HOST_GLINE 2
22 #define INSTANT_KILL 3
23 #define DELAYED_IDENT_GLINE 4
24 #define DELAYED_HOST_GLINE 5
25 #define DELAYED_KILL 6
26
27 MODULE_VERSION("1.43");
28
29 typedef struct rg_glinenode {
30 nick *np;
31 struct rg_struct *reason;
32 short punish;
33 struct rg_glinenode *next;
34 } rg_glinenode;
35
36 typedef struct rg_glinelist {
37 struct rg_glinenode *start;
38 struct rg_glinenode *end;
39 } rg_glinelist;
40
41 typedef struct rg_delay {
42 schedule *sch;
43 nick *np;
44 struct rg_struct *reason;
45 short punish;
46 struct rg_delay *next;
47 } rg_delay;
48
49 #define GLINE_HEADER " ID Expires Set by Class Type Last seen (ago) Hits(p) Hits Reason"
50
51 rg_delay *rg_delays;
52
53 void rg_setdelay(nick *np, struct rg_struct *reason, short punish);
54 void rg_deletedelay(rg_delay *delay);
55 void rg_dodelay(void *arg);
56
57 void rg_dogline(struct rg_glinelist *gll, nick *np, struct rg_struct *rp, char *matched);
58 void rg_flush_schedule(void *arg);
59
60 static DBModuleIdentifier dbid;
61 static unsigned long highestid = 0;
62 static int attached = 0, started = 0;
63
64 static unsigned int getrgmarker(void);
65
66 /* shadowserver only reports classes[0] */
67 static const char *classes[] = { "drone", "proxy", "spam", "fakeauth", "other", (char *)0 };
68
69 void _init(void) {
70 sstring *max_casualties, *max_spew, *expiry_time, *max_per_gline;
71
72 max_casualties = getcopyconfigitem("regexgline", "maxcasualties", RGStringise(RG_MAX_CASUALTIES_DEFAULT), 8);
73 if(!protectedatoi(max_casualties->content, &rg_max_casualties))
74 rg_max_casualties = RG_MAX_CASUALTIES_DEFAULT;
75
76 freesstring(max_casualties);
77
78 max_spew = getcopyconfigitem("regexgline", "maxspew", RGStringise(RG_MAX_SPEW_DEFAULT), 8);
79 if(!protectedatoi(max_spew->content, &rg_max_spew))
80 rg_max_spew = RG_MAX_SPEW_DEFAULT;
81
82 freesstring(max_spew);
83
84 expiry_time = getcopyconfigitem("regexgline", "expirytime", RGStringise(RG_EXPIRY_TIME_DEFAULT), 8);
85 if(!protectedatoi(expiry_time->content, &rg_expiry_time))
86 rg_expiry_time = RG_EXPIRY_TIME_DEFAULT;
87
88 freesstring(expiry_time);
89
90 max_per_gline = getcopyconfigitem("regexgline", "maxpergline", RGStringise(RG_MAX_PER_GLINE_DEFAULT), 8);
91 if(!protectedatoi(max_per_gline->content, &rg_max_per_gline))
92 rg_max_per_gline = RG_MAX_PER_GLINE_DEFAULT;
93
94 freesstring(max_per_gline);
95
96 rg_delays = NULL;
97
98 if(dbconnected()) {
99 attached = 1;
100 dbid = dbgetid();
101 rg_dbload();
102 } else {
103 Error("regexgline", ERR_STOP, "Could not connect to database.");
104 }
105 }
106
107 void _fini(void) {
108 struct rg_struct *gp = rg_list, *oldgp;
109 rg_delay *delay, *delaynext;
110
111 if(started) {
112 deregisterhook(HOOK_NICK_NEWNICK, &rg_nick);
113 deregisterhook(HOOK_NICK_RENAME, &rg_nick);
114 deregisterhook(HOOK_NICK_LOSTNICK, &rg_lostnick);
115 deregistercontrolcmd("regexspew", rg_spew);
116 deregistercontrolcmd("regexglist", rg_glist);
117 deregistercontrolcmd("regexdelgline", rg_delgline);
118 deregistercontrolcmd("regexgline", rg_gline);
119 deregistercontrolcmd("regexidlookup", rg_idlist);
120 }
121
122 if(rg_delays) {
123 for(delay=rg_delays;delay;delay=delaynext) {
124 delaynext=delay->next;
125 deleteschedule(delay->sch, rg_dodelay, delay);
126 free(delay);
127 }
128 }
129
130 if(rg_schedule) {
131 deleteschedule(rg_schedule, &rg_checkexpiry, NULL);
132 rg_schedule = NULL;
133 }
134
135 deleteallschedules(rg_flush_schedule);
136 rg_flush_schedule(NULL);
137
138 for(gp=rg_list;gp;) {
139 oldgp = gp;
140 gp = gp->next;
141 rg_freestruct(oldgp);
142 }
143
144 if(attached) {
145 dbdetach("regexgline");
146 dbfreeid(dbid);
147 }
148 }
149
150 void rg_checkexpiry(void *arg) {
151 struct rg_struct *rp = rg_list, *lp = NULL;
152 time_t current = time(NULL);
153
154 while(rp) {
155 if (current >= rp->expires) {
156 rg_sqlquery("DELETE FROM regexglines WHERE id = %d", rp->id);
157 if (lp) {
158 lp->next = rp->next;
159 rg_freestruct(rp);
160 rp = lp->next;
161 } else {
162 rg_list = rp->next;
163 rg_freestruct(rp);
164 rp = rg_list;
165 }
166 } else {
167 lp = rp;
168 rp = rp->next;
169 }
170 }
171 }
172
173 void rg_setdelay(nick *np, rg_struct *reason, short punish) {
174 rg_delay *delay;
175 delay = (rg_delay *)malloc(sizeof(rg_delay));
176
177 /* Just incase */
178 if(!delay) {
179 killuser(NULL, np, "%s (ID: %08lx)", reason->reason->content, reason->glineid);
180 return;
181 }
182
183 delay->np = np;
184 delay->reason = reason;
185 delay->punish = punish;
186 delay->next = rg_delays;
187 rg_delays = delay;
188
189 delay->sch = scheduleoneshot(time(NULL) + (RG_MINIMUM_DELAY_TIME + (rand() % RG_MAXIMUM_RAND_TIME)), rg_dodelay, delay);
190 }
191
192 static void rg_shadowserver(nick *np, struct rg_struct *reason, int type) {
193 char buf[1024];
194
195 if(reason->class != classes[0]) /* drone */
196 return;
197
198 snprintf(buf, sizeof(buf), "regex-ban %lu %s!%s@%s %s %s", time(NULL), np->nick, np->ident, np->host->name->content, reason->mask->content, serverlist[homeserver(np->numeric)].name->content);
199
200 triggerhook(HOOK_SHADOW_SERVER, (void *)buf);
201 }
202
203 void rg_deletedelay(rg_delay *delay) {
204 rg_delay *temp, *prev;
205 prev = NULL;
206 for (temp=rg_delays;temp;temp=temp->next) {
207 if (temp==delay) {
208 if (temp==rg_delays)
209 rg_delays = temp->next;
210 else
211 prev->next = temp->next;
212
213 free(temp);
214 return;
215 }
216
217 prev = temp;
218 }
219 }
220
221 void rg_dodelay(void *arg) {
222 rg_delay *delay = (rg_delay *)arg;
223 char hostname[RG_MASKLEN];
224 int hostlen, usercount = 0;
225
226 /* User or regex gline no longer exists */
227 if((!delay->np) || (!delay->reason)) {
228 rg_deletedelay(delay);
229 return;
230 }
231
232 hostlen = RGBuildHostname(hostname, delay->np);
233
234 /* User has wisely changed nicknames */
235 if(pcre_exec(delay->reason->regex, delay->reason->hint, hostname, hostlen, 0, 0, NULL, 0) < 0) {
236 rg_deletedelay(delay);
237 return;
238 }
239
240 if (delay->reason->type == DELAYED_HOST_GLINE) {
241 usercount = delay->np->host->clonecount;
242 snprintf(hostname, sizeof(hostname), "*@%s", IPtostr(delay->np->p_ipaddr));
243 }
244
245 if((delay->reason->type == DELAYED_IDENT_GLINE) || (usercount > rg_max_per_gline)) {
246 nick *tnp;
247
248 for(usercount=0,tnp=delay->np->host->nicks;tnp;tnp=tnp->nextbyhost)
249 if(!ircd_strcmp(delay->np->ident, tnp->ident))
250 usercount++;
251
252 snprintf(hostname, sizeof(hostname), "%s@%s", delay->np->ident, IPtostr(delay->np->p_ipaddr));
253 }
254
255 if ((delay->reason->type == DELAYED_KILL) || (usercount > rg_max_per_gline)) {
256 if (IsAccount(delay->np)) {
257 controlwall(NO_OPER, NL_HITS, "%s!%s@%s/%s matched delayed kill regex %08lx (class: %s)", delay->np->nick, delay->np->ident, delay->np->host->name->content, delay->np->authname, delay->reason->glineid, delay->reason->class);
258 } else {
259 controlwall(NO_OPER, NL_HITS, "%s!%s@%s matched delayed kill regex %08lx (class: %s)", delay->np->nick, delay->np->ident, delay->np->host->name->content, delay->reason->glineid, delay->reason->class);
260 }
261
262 rg_shadowserver(delay->np, delay->reason, DELAYED_KILL);
263 killuser(NULL, delay->np, "%s (ID: %08lx)", delay->reason->reason->content, delay->reason->glineid);
264 return;
265 }
266
267 if (delay->reason->type == DELAYED_IDENT_GLINE) {
268 if (IsAccount(delay->np)) {
269 controlwall(NO_OPER, NL_HITS, "%s!%s@%s/%s matched delayed user@host gline regex %08lx (class: %s, hit %d user%s)", delay->np->nick, delay->np->ident, delay->np->host->name->content, delay->np->authname, delay->reason->glineid, delay->reason->class, usercount, (usercount!=1)?"s":"");
270 } else {
271 controlwall(NO_OPER, NL_HITS, "%s!%s@%s matched delayed user@host gline regex %08lx (class: %s, hit %d user%s)", delay->np->nick, delay->np->ident, delay->np->host->name->content, delay->reason->glineid, delay->reason->class, usercount, (usercount!=1)?"s":"");
272 }
273 } else if (delay->reason->type == DELAYED_HOST_GLINE) {
274 if (IsAccount(delay->np)) {
275 controlwall(NO_OPER, NL_HITS, "%s!%s@%s/%s matched delayed *@host gline regex %08lx (class: %s, hit %d user%s)", delay->np->nick, delay->np->ident, delay->np->host->name->content, delay->np->authname, delay->reason->glineid, delay->reason->class, usercount, (usercount!=1)?"s":"");
276 } else {
277 controlwall(NO_OPER, NL_HITS, "%s!%s@%s matched delayed *@host gline regex %08lx (class: %s, hit %d user%s)", delay->np->nick, delay->np->ident, delay->np->host->name->content, delay->reason->glineid, delay->reason->class, usercount, (usercount!=1)?"s":"");
278 }
279 } else {
280 return;
281 }
282
283 rg_shadowserver(delay->np, delay->reason, delay->reason->type);
284 irc_send("%s GL * +%s %d %jd :AUTO: %s (ID: %08lx)\r\n", mynumeric->content, hostname, rg_expiry_time, (intmax_t)time(NULL), delay->reason->reason->content, delay->reason->glineid);
285 rg_deletedelay(delay);
286 }
287
288 void rg_initglinelist(struct rg_glinelist *gll) {
289 gll->start = NULL;
290 gll->end = NULL;
291 }
292
293 void rg_flushglines(struct rg_glinelist *gll) {
294 struct rg_glinenode *nn, *pn;
295 for(nn=gll->start;nn;nn=pn) {
296 pn = nn->next;
297 if(nn->punish == INSTANT_KILL) {
298 if ( IsAccount(nn->np) ) {
299 controlwall(NO_OPER, NL_HITS, "%s!%s@%s/%s matched kill regex %08lx (class: %s)", nn->np->nick, nn->np->ident, nn->np->host->name->content, nn->np->authname, nn->reason->glineid, nn->reason->class);
300 } else {
301 controlwall(NO_OPER, NL_HITS, "%s!%s@%s matched kill regex %08lx (class: %s)", nn->np->nick, nn->np->ident, nn->np->host->name->content, nn->reason->glineid, nn->reason->class);
302 }
303
304 rg_shadowserver(nn->np, nn->reason, nn->punish);
305 killuser(NULL, nn->np, "%s (ID: %08lx)", nn->reason->reason->content, nn->reason->glineid);
306 } else if ((nn->punish == DELAYED_IDENT_GLINE) || (nn->punish == DELAYED_HOST_GLINE) || (nn->punish == DELAYED_KILL)) {
307 rg_setdelay(nn->np, nn->reason, nn->punish);
308 }
309 free(nn);
310 }
311
312 rg_initglinelist(gll);
313 }
314
315 static void dbloaddata(DBConn *dbconn, void *arg) {
316 DBResult *dbres = dbgetresult(dbconn);
317
318 if(!dbquerysuccessful(dbres)) {
319 Error("chanserv", ERR_ERROR, "Error loading DB");
320 return;
321 }
322
323 if (dbnumfields(dbres) != 9) {
324 Error("regexgline", ERR_ERROR, "DB format error");
325 return;
326 }
327
328 while(dbfetchrow(dbres)) {
329 unsigned long id, hitssaved;
330 time_t lastseen;
331 char *gline, *setby, *reason, *expires, *type, *class;
332
333 id = strtoul(dbgetvalue(dbres, 0), NULL, 10);
334 if(id > highestid)
335 highestid = id;
336
337 gline = dbgetvalue(dbres, 1);
338 setby = dbgetvalue(dbres, 2);
339 reason = dbgetvalue(dbres, 3);
340 expires = dbgetvalue(dbres, 4);
341 type = dbgetvalue(dbres, 5);
342 class = dbgetvalue(dbres, 6);
343
344 lastseen = strtoul(dbgetvalue(dbres, 7), NULL, 10);
345 hitssaved = strtoul(dbgetvalue(dbres, 8), NULL, 10);
346
347 if (!rg_newsstruct(id, gline, setby, reason, expires, type, 0, class, lastseen, hitssaved))
348 dbquery("DELETE FROM regexgline.glines WHERE id = %lu", id);
349 }
350
351 dbclear(dbres);
352 }
353
354 static void dbloadfini(DBConn *dbconn, void *arg) {
355 started = 1;
356 StringBuf b;
357 const char **p;
358 char helpbuf[8192 * 2], allclasses[8192];
359
360 sbinit(&b, (char *)allclasses, sizeof(allclasses));
361 for(p=classes;*p;p++) {
362 sbaddstr(&b, (char *)*p);
363 sbaddchar(&b, ' ');
364 }
365 sbterminate(&b);
366
367 snprintf(helpbuf, sizeof(helpbuf),
368 "Usage: regexgline <regex> <duration> <type> <class> <reason>\n"
369 "Adds a new regular expression pattern.\n"
370 "Duration is represented as 3d, 3M etc.\n"
371 "Class is one of the following: %s\n"
372 "Type is an integer which represents the following:\n"
373 "1 - Instant USER@IP GLINE (igu)\n"
374 "2 - Instant *@IP GLINE (igh)\n"
375 "3 - Instant KILL (ik)\n"
376 "4 - Delayed USER@IP GLINE (dgu)\n"
377 "5 - Delayed *@IP GLINE (dgh)\n"
378 "6 - Delayed KILL (dk)",
379 allclasses);
380
381 registercontrolhelpcmd("regexgline", NO_OPER, 5, &rg_gline, helpbuf);
382 registercontrolhelpcmd("regexdelgline", NO_OPER, 1, &rg_delgline, "Usage: regexdelgline <pattern>\nDeletes a regular expression pattern.");
383 registercontrolhelpcmd("regexglist", NO_OPER, 1, &rg_glist, "Usage: regexglist <pattern>\nLists regular expression patterns.");
384 registercontrolhelpcmd("regexspew", NO_OPER, 1, &rg_spew, "Usage: regexspew <pattern>\nLists users currently on the network which match the given pattern.");
385 registercontrolhelpcmd("regexidlookup", NO_OPER, 1, &rg_idlist, "Usage: regexidlookup <id>\nFinds a regular expression pattern by it's ID number.");
386
387 registerhook(HOOK_NICK_NEWNICK, &rg_nick);
388 registerhook(HOOK_NICK_RENAME, &rg_nick);
389 registerhook(HOOK_NICK_LOSTNICK, &rg_lostnick);
390 rg_startup();
391
392 rg_schedule = schedulerecurring(time(NULL) + 1, 0, 1, rg_checkexpiry, NULL);
393 schedulerecurring(time(NULL) + 60, 0, 60, rg_flush_schedule, NULL);
394 }
395
396 void rg_dbload(void) {
397 dbattach("regexgline");
398 dbcreatequery("CREATE TABLE regexgline.glines (id INT NOT NULL PRIMARY KEY, gline TEXT NOT NULL, setby VARCHAR(%d) NOT NULL, reason VARCHAR(%d) NOT NULL, expires INT NOT NULL, type INT NOT NULL DEFAULT 1, class TEXT NOT NULL, lastseen INT DEFAULT 0, hits INT DEFAULT 0)", ACCOUNTLEN, RG_REASON_MAX);
399 dbcreatequery("CREATE TABLE regexgline.clog (host VARCHAR(%d) NOT NULL, account VARCHAR(%d) NOT NULL, event TEXT NOT NULL, arg TEXT NOT NULL, ts TIMESTAMP)", RG_MASKLEN - 1, ACCOUNTLEN);
400 dbcreatequery("CREATE TABLE regexgline.glog (glineid INT 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);
401
402 dbloadtable("regexgline.glines", NULL, dbloaddata, dbloadfini);
403 }
404
405 void rg_nick(int hooknum, void *arg) {
406 nick *np = (nick *)arg;
407 struct rg_struct *rp;
408 char hostname[RG_MASKLEN];
409 int hostlen;
410 struct rg_glinelist gll;
411
412 rg_initglinelist(&gll);
413
414 hostlen = RGBuildHostname(hostname, np);
415
416 if(IsOper(np) || IsService(np) || IsXOper(np))
417 return;
418
419 for(rp=rg_list;rp;rp=rp->next) {
420 if(pcre_exec(rp->regex, rp->hint, hostname, hostlen, 0, 0, NULL, 0) >= 0) {
421 rg_dogline(&gll, np, rp, hostname);
422 break;
423 }
424 }
425
426 rg_flushglines(&gll);
427 }
428
429 void rg_lostnick(int hooknum, void *arg) {
430 nick *np = (nick *)arg;
431 rg_delay *delay;
432
433 /* Cleanup the delays */
434 for(delay=rg_delays;delay;delay=delay->next)
435 if(delay->np==np)
436 delay->np = NULL;
437 }
438
439 int rg_gline(void *source, int cargc, char **cargv) {
440 nick *np = (nick *)source, *tnp;
441 time_t realexpiry;
442 const char *expirybuf;
443 int expiry, count, j, hostlen;
444 struct rg_struct *rp;
445 struct rg_glinelist gll;
446 const char **p;
447
448 char eemask[RG_QUERY_BUF_SIZE], eesetby[RG_QUERY_BUF_SIZE], eereason[RG_QUERY_BUF_SIZE], eeclass[RG_QUERY_BUF_SIZE];
449 char hostname[RG_MASKLEN], *class, *reason, *regex, type;
450
451 if(cargc < 5)
452 return CMD_USAGE;
453
454 type = cargv[2][0];
455 if ((strlen(cargv[2]) != 1) || ((type != '1') && (type != '2') && (type != '3') && (type != '4') && (type != '5') && (type != '6'))) {
456 controlreply(np, "Invalid type specified!");
457 return CMD_USAGE;
458 }
459
460 regex = cargv[0];
461 class = cargv[3];
462 reason = cargv[4];
463
464 for(p=classes;*p;p++)
465 if(!strcasecmp(class, *p))
466 break;
467
468 if(!*p) {
469 controlreply(np, "Bad class supplied.");
470 return CMD_USAGE;
471 }
472
473 if (!(expiry = durationtolong(cargv[1]))) {
474 controlreply(np, "Invalid duration specified!");
475 return CMD_USAGE;
476 }
477
478 for(rp=rg_list;rp;rp=rp->next) {
479 if (RGMasksEqual(rp->mask->content, regex)) {
480 controlreply(np, "That regexgline already exists!");
481 return CMD_ERROR;
482 }
483 }
484
485 if (rg_sanitycheck(regex, &count)) {
486 controlreply(np, "Error in expression.");
487 return CMD_ERROR;
488 } else if (count < 0) {
489 controlreply(np, "That expression would hit too many users (%d)!", -count);
490 return CMD_ERROR;
491 }
492
493 realexpiry = expiry + time(NULL);
494
495 dbescapestring(eemask, regex, strlen(regex));
496 dbescapestring(eesetby, np->nick, strlen(np->nick));
497 dbescapestring(eeclass, class, strlen(class));
498 dbescapestring(eereason, reason, strlen(reason));
499
500 highestid = highestid + 1;
501 dbquery("INSERT INTO regexgline.glines (id, gline, setby, reason, expires, type, class, lastseen, hits) VALUES (%lu, '%s', '%s', '%s', %lu, %c, '%s', 0, 0)", highestid, eemask, eesetby, eereason, realexpiry, type, eeclass);
502 rp = rg_newsstruct(highestid, regex, np->nick, reason, "", cargv[2], realexpiry, class, 0, 0);
503
504 rg_initglinelist(&gll);
505
506 for(j=0;j<NICKHASHSIZE;j++) {
507 for(tnp=nicktable[j];tnp;tnp=tnp->next) {
508 if(IsOper(tnp) || IsService(tnp) || IsXOper(tnp))
509 continue;
510
511 hostlen = RGBuildHostname(hostname, tnp);
512 if(pcre_exec(rp->regex, rp->hint, hostname, hostlen, 0, 0, NULL, 0) >= 0)
513 rg_dogline(&gll, tnp, rp, hostname);
514 }
515 }
516
517 rg_flushglines(&gll);
518
519 expirybuf = longtoduration(expiry, 0);
520
521 rg_logevent(np, "regexgline", "%s %d %d %s %s", regex, expiry, count, class, reason);
522 controlreply(np, "Added regexgline: %s (class: %s, expires in: %s, hit %d user%s): %s", regex, class, expirybuf, count, (count!=1)?"s":"", reason);
523 /* If we are using NO, can we safely assume the user is authed here and use ->authname? */
524 controlwall(NO_OPER, NL_GLINES, "%s!%s@%s/%s added regexgline: %s (class: %s, expires in: %s, hit %d user%s): %s", np->nick, np->ident, np->host->name->content, np->authname, regex, class, expirybuf, count, (count!=1)?"s":"", reason);
525
526 return CMD_OK;
527 }
528
529 int rg_sanitycheck(char *mask, int *count) {
530 const char *error;
531 char hostname[RG_MASKLEN];
532 int erroroffset, hostlen, j, masklen = strlen(mask);
533 pcre *regex;
534 pcre_extra *hint;
535 nick *np;
536
537 if((masklen < RG_MIN_MASK_LEN) || (masklen > RG_REGEXGLINE_MAX))
538 return 1;
539
540 if(!(regex = pcre_compile(mask, RG_PCREFLAGS, &error, &erroroffset, NULL))) {
541 Error("regexgline", ERR_WARNING, "Error compiling expression %s at offset %d: %s", mask, erroroffset, error);
542 return 2;
543 } else {
544 hint = pcre_study(regex, 0, &error);
545 if(error) {
546 Error("regexgline", ERR_WARNING, "Error studying expression %s: %s", mask, error);
547 pcre_free(regex);
548 return 3;
549 }
550 }
551
552 *count = 0;
553 for(j=0;j<NICKHASHSIZE;j++) {
554 for(np=nicktable[j];np;np=np->next) {
555 hostlen = RGBuildHostname(hostname, np);
556 if(pcre_exec(regex, hint, hostname, hostlen, 0, 0, NULL, 0) >= 0) {
557 (*count)++;
558 }
559 }
560 }
561
562 pcre_free(regex);
563 if(hint)
564 pcre_free(hint);
565
566 if(*count >= rg_max_casualties)
567 *count = -(*count);
568
569 return 0;
570 }
571
572 int rg_delgline(void *source, int cargc, char **cargv) {
573 nick *np = (nick *)source;
574 rg_delay *delay;
575 struct rg_struct *rp = rg_list, *last = NULL;
576 int count = 0;
577
578 if(cargc < 1)
579 return CMD_USAGE;
580
581 rg_logevent(np, "regexdelgline", "%s", cargv[0]);
582 while(rp) {
583 if(RGMasksEqual(rp->mask->content, cargv[0])) {
584 count++;
585
586 /* Cleanup the delays */
587 for(delay=rg_delays;delay;delay=delay->next)
588 if(delay->reason==rp)
589 delay->reason = NULL;
590
591 dbquery("DELETE FROM regexgline.glines WHERE id = %d", rp->id);
592 if(last) {
593 last->next = rp->next;
594 rg_freestruct(rp);
595 rp = last->next;
596 } else {
597 rg_list = rp->next;
598 rg_freestruct(rp);
599 rp = rg_list;
600 }
601 } else {
602 last = rp;
603 rp = rp->next;
604 }
605 }
606 if (count > 0) {
607 controlreply(np, "Deleted (matched: %d).", count);
608 /* If we are using NO, can we safely assume the user is authed here and use ->authname? */
609 controlwall(NO_OPER, NL_GLINES, "%s!%s@%s/%s removed regexgline: %s", np->nick, np->ident, np->host->name->content, np->authname, cargv[0]);
610 } else {
611 controlreply(np, "No glines matched: %s", cargv[0]);
612 }
613 return CMD_OK;
614 }
615
616 int rg_idlist(void *source, int cargc, char **cargv) {
617 nick *np = (nick *)source;
618
619 if(cargc < 1) {
620 return CMD_USAGE;
621 } else if (strlen(cargv[0]) != 8) {
622 controlreply(np, "Invalid gline id!");
623 return CMD_ERROR;
624 } else {
625 struct rg_struct *rp;
626 unsigned long id = 0;
627 int i, longest = 0;
628 unsigned int m;
629
630 for(i=0;i<8;i++) {
631 if(0xff == rc_hexlookup[(int)cargv[0][i]]) {
632 controlreply(np, "Invalid gline id!");
633 return CMD_ERROR;
634 } else {
635 id = (id << 4) | rc_hexlookup[(int)cargv[0][i]];
636 }
637 }
638
639 m = getrgmarker();
640 controlreply(np, GLINE_HEADER);
641 for(rp=rg_list;rp;rp=rp->next) {
642 if(id == rp->glineid) {
643 rp->marker = m;
644 if(rp->mask->length > longest)
645 longest = rp->mask->length;
646 }
647 }
648
649 for(rp=rg_list;rp;rp=rp->next)
650 if(rp->marker == m)
651 rg_displaygline(np, rp, longest);
652 controlreply(np, "Done.");
653
654 return CMD_OK;
655 }
656 }
657
658 int rg_glist(void *source, int cargc, char **cargv) {
659 nick *np = (nick *)source;
660 struct rg_struct *rp;
661 int longest = 0;
662
663 if(cargc) {
664 int erroroffset;
665 pcre *regex;
666 pcre_extra *hint;
667 const char *error;
668 unsigned int m;
669
670 if(!(regex = pcre_compile(cargv[0], RG_PCREFLAGS, &error, &erroroffset, NULL))) {
671 controlreply(np, "Error compiling expression %s at offset %d: %s", cargv[0], erroroffset, error);
672 return CMD_ERROR;
673 } else {
674 hint = pcre_study(regex, 0, &error);
675 if(error) {
676 controlreply(np, "Error studying expression %s: %s", cargv[0], error);
677 pcre_free(regex);
678 return CMD_ERROR;
679 }
680 }
681
682 m = getrgmarker();
683 rg_logevent(np, "regexglist", "%s", cargv[0]);
684 controlreply(np, GLINE_HEADER);
685 for(rp=rg_list;rp;rp=rp->next) {
686 if(pcre_exec(regex, hint, rp->mask->content, rp->mask->length, 0, 0, NULL, 0) >= 0) {
687 rp->marker = m;
688 if(rp->mask->length > longest)
689 longest = rp->mask->length;
690 }
691 }
692
693 for(rp=rg_list;rp;rp=rp->next)
694 if(rp->marker == m)
695 rg_displaygline(np, rp, longest);
696
697 pcre_free(regex);
698 if(hint)
699 pcre_free(hint);
700
701 } else {
702 rg_logevent(np, "regexglist", "%s", "");
703 controlreply(np, GLINE_HEADER);
704 for(rp=rg_list;rp;rp=rp->next)
705 if(rp->mask->length > longest)
706 longest = rp->mask->length;
707
708 for(rp=rg_list;rp;rp=rp->next)
709 rg_displaygline(np, rp, longest);
710 }
711
712 controlreply(np, "Done.");
713 return CMD_OK;
714 }
715
716 char *displaytype(int type) {
717 char *ctype;
718 static char ctypebuf[10];
719
720 switch(type) {
721 case 1:
722 ctype = "igu";
723 break;
724 case 2:
725 ctype = "igh";
726 break;
727 case 3:
728 ctype = "ik";
729 break;
730 case 4:
731 ctype = "dgu";
732 break;
733 case 5:
734 ctype = "dgh";
735 break;
736 case 6:
737 ctype = "dk";
738 break;
739 default:
740 ctype = "??";
741 }
742
743 snprintf(ctypebuf, sizeof(ctype), "%1d:%s", type, ctype);
744 return ctypebuf;
745 }
746
747 char *getsep(int longest) {
748 static int lastlongest = -1;
749 static char lenbuf[1024];
750
751 longest = 125;
752 /*
753 if(longest < 100)
754 longest = 100;
755
756 if(longest >= sizeof(lenbuf) - 20)
757 longest = sizeof(lenbuf) - 20;
758 */
759 longest+=4;
760 if(lastlongest == -1) {
761 int i;
762
763 for(i=0;i<sizeof(lenbuf)-1;i++)
764 lenbuf[i] = '-';
765 lenbuf[sizeof(lenbuf)-1] = '\0';
766 lastlongest = 0;
767 }
768
769 if(lastlongest != longest) {
770 lenbuf[lastlongest] = '-';
771 lenbuf[longest] = '\0';
772 lastlongest = longest;
773 }
774
775 return lenbuf;
776 }
777
778 void rg_displaygline(nick *np, struct rg_struct *rp, int longest) { /* could be a macro? I'll assume the C compiler inlines it */
779 char *sep = getsep(longest);
780 /* 12345678 12345678901234567890 123456789012345 12345678 12345 12345678901234567890 1234567 1234567 123456
781 ID Expires Set by Class Type Last seen (ago) Hits(s) Hits Reason
782 */
783
784 char d[512];
785 time_t t = time(NULL);
786
787 if(rp->lastseen == 0) {
788 strlcpy(d, "(never)", sizeof(d));
789 } else {
790 strlcpy(d, longtoduration(t - rp->lastseen, 2), sizeof(d));
791 }
792
793 controlreply(np, "%s", rp->mask->content);
794 controlreply(np, " %08lx %-20s %-15s %-8s %-5s %-20s %-7lu %-7lu %s", rp->glineid, longtoduration(rp->expires - t, 2), rp->setby->content, rp->class, displaytype(rp->type), d, rp->hitssaved, rp->hits, rp->reason->content);
795 controlreply(np, "%s", sep);
796 }
797
798 int rg_spew(void *source, int cargc, char **cargv) {
799 nick *np = (nick *)source, *tnp;
800 int counter = 0, erroroffset, hostlen, j;
801 pcre *regex;
802 pcre_extra *hint;
803 const char *error;
804 char hostname[RG_MASKLEN];
805 int ovector[30];
806 int pcreret;
807
808 if(cargc < 1)
809 return CMD_USAGE;
810
811 if(!(regex = pcre_compile(cargv[0], RG_PCREFLAGS, &error, &erroroffset, NULL))) {
812 controlreply(np, "Error compiling expression %s at offset %d: %s", cargv[0], erroroffset, error);
813 return CMD_ERROR;
814 } else {
815 hint = pcre_study(regex, 0, &error);
816 if(error) {
817 controlreply(np, "Error studying expression %s: %s", cargv[0], error);
818 pcre_free(regex);
819 return CMD_ERROR;
820 }
821 }
822
823 rg_logevent(np, "regexspew", "%s", cargv[0]);
824
825 for(j=0;j<NICKHASHSIZE;j++) {
826 for(tnp=nicktable[j];tnp;tnp=tnp->next) {
827 hostlen = RGBuildHostname(hostname, tnp);
828 pcreret = pcre_exec(regex, hint, hostname, hostlen, 0, 0, ovector, sizeof(ovector) / sizeof(int));
829 if(pcreret >= 0) {
830 if(counter == rg_max_spew) {
831 controlreply(np, "Reached maximum spew count (%d) - aborting display.", rg_max_spew);
832 } else if (counter < rg_max_spew) {
833 /* 15 should be number of bolds */
834 char boldbuf[RG_MASKLEN + 15], *tp, *fp, *realname = NULL;
835 int boldon = 0;
836 for(tp=hostname,fp=boldbuf;*tp;) {
837 if(tp - hostname == ovector[0]) {
838 *fp++ = '\002';
839 boldon = 1;
840 }
841 if(tp - hostname == ovector[1]) {
842 *fp++ = '\002';
843 boldon = 0;
844 }
845 if(*tp == '\r') {
846 if(boldon)
847 *fp++ = '\002';
848 *fp++ = '\0';
849 realname = fp;
850 if(boldon)
851 *fp++ = '\002';
852 tp++;
853 } else {
854 *fp++ = *tp++;
855 }
856 }
857 if(boldon)
858 *fp++ = '\002';
859 *fp++ = '\0';
860 controlreply(np, "%s (%s) (%dc)", boldbuf, realname, tnp->channels->cursi);
861 }
862 counter++;
863 }
864 }
865 }
866 controlreply(np, "Done - %d matches.", counter);
867
868 pcre_free(regex);
869 if(hint)
870 pcre_free(hint);
871
872 return CMD_OK;
873 }
874
875 void rg_startup(void) {
876 int j, hostlen;
877 nick *np;
878 struct rg_struct *rp;
879 struct rg_glinelist gll;
880 char hostname[RG_MASKLEN];
881
882 rg_initglinelist(&gll);
883
884 for(j=0;j<NICKHASHSIZE;j++) {
885 for(np=nicktable[j];np;np=np->next) {
886 if(IsOper(np) || IsService(np) || IsXOper(np))
887 continue;
888 hostlen = RGBuildHostname(hostname, np);
889 for(rp=rg_list;rp;rp=rp->next) {
890 if(pcre_exec(rp->regex, rp->hint, hostname, hostlen, 0, 0, NULL, 0) >= 0) {
891 rg_dogline(&gll, np, rp, hostname);
892 break;
893 }
894 }
895 }
896 }
897
898 rg_flushglines(&gll);
899 }
900
901 void rg_freestruct(struct rg_struct *rp) {
902 freesstring(rp->mask);
903 freesstring(rp->setby);
904 freesstring(rp->reason);
905 pcre_free(rp->regex);
906 if(rp->hint)
907 pcre_free(rp->hint);
908 free(rp);
909 }
910
911 struct rg_struct *rg_newstruct(time_t expires) {
912 struct rg_struct *rp;
913
914 if (time(NULL) >= expires)
915 return NULL;
916
917 rp = (struct rg_struct*)malloc(sizeof(struct rg_struct));
918 if(rp) {
919 struct rg_struct *tp, *lp;
920
921 memset(rp, 0, sizeof(rg_struct));
922 rp->expires = expires;
923
924 for(lp=NULL,tp=rg_list;tp;lp=tp,tp=tp->next) {
925 if (expires <= tp->expires) { /* <= possible, slight speed increase */
926 rp->next = tp;
927 if (lp) {
928 lp->next = rp;
929 } else {
930 rg_list = rp;
931 }
932 break;
933 }
934 }
935 if (!tp) {
936 rp->next = NULL;
937 if (lp) {
938 lp->next = rp;
939 } else {
940 rg_list = rp;
941 }
942 }
943
944 }
945 return rp;
946 }
947
948 struct rg_struct *rg_newsstruct(unsigned long id, char *mask, char *setby, char *reason, char *expires, char *type, time_t iexpires, char *class, time_t lastseen, unsigned int hitssaved) {
949 struct rg_struct *newrow, *lp, *cp;
950 time_t rexpires;
951 char glineiddata[1024];
952 const char **p;
953
954 if (iexpires == 0) {
955 int qexpires;
956 if(!protectedatoi(expires, &qexpires))
957 return NULL;
958 rexpires = (time_t)qexpires;
959 } else {
960 rexpires = iexpires;
961 }
962
963 newrow = rg_newstruct(rexpires);
964
965 if(newrow) {
966 const char *error;
967 int erroroffset;
968
969 for(p=classes;*p;p++) {
970 if(!strcasecmp(class, *p)) {
971 newrow->class = *p;
972 break;
973 }
974 }
975
976 if(!*p)
977 newrow->class = "unknown";
978
979 if(!(newrow->regex = pcre_compile(mask, RG_PCREFLAGS, &error, &erroroffset, NULL))) {
980 Error("regexgline", ERR_WARNING, "Error compiling expression %s at offset %d: %s", mask, erroroffset, error);
981 goto dispose;
982 } else {
983 newrow->hint = pcre_study(newrow->regex, 0, &error);
984 if(error) {
985 Error("regexgline", ERR_WARNING, "Error studying expression %s: %s", mask, error);
986 pcre_free(newrow->regex);
987 goto dispose;
988 }
989 }
990
991 newrow->id = id;
992 newrow->hitssaved = hitssaved;
993 newrow->lastseen = lastseen;
994
995 newrow->mask = getsstring(mask, RG_REGEXGLINE_MAX);
996 if(!newrow->mask) {
997 Error("regexgline", ERR_WARNING, "Error allocating memory for mask!");
998 goto dispose2;
999 }
1000
1001 newrow->setby = getsstring(setby, ACCOUNTLEN);
1002 if(!newrow->setby) {
1003 Error("regexgline", ERR_WARNING, "Error allocating memory for setby!");
1004 goto dispose2;
1005 }
1006
1007 newrow->reason = getsstring(reason, RG_REASON_MAX);
1008 if(!newrow->reason) {
1009 Error("regexgline", ERR_WARNING, "Error allocating memory for reason!");
1010 goto dispose2;
1011 }
1012
1013 if(!protectedatoi(type, &newrow->type))
1014 newrow->type = 0; /* just in case */
1015
1016 snprintf(glineiddata, sizeof(glineiddata), "%s regexgline %s %s %s %d %d", mynumeric->content, mask, setby, reason, (int)iexpires, newrow->type);
1017 newrow->glineid = crc32(glineiddata);
1018 }
1019
1020 return newrow;
1021
1022 dispose2:
1023 if(newrow->mask)
1024 freesstring(newrow->mask);
1025 if(newrow->setby)
1026 freesstring(newrow->setby);
1027 if(newrow->reason)
1028 freesstring(newrow->reason);
1029 pcre_free(newrow->regex);
1030 if(newrow->hint)
1031 pcre_free(newrow->hint);
1032
1033 dispose:
1034 for(lp=NULL,cp=rg_list;cp;lp=cp,cp=cp->next) {
1035 if(newrow == cp) {
1036 if(lp) {
1037 lp->next = cp->next;
1038 } else {
1039 rg_list = cp->next;
1040 }
1041 free(newrow);
1042 break;
1043 }
1044 }
1045 return NULL;
1046 }
1047
1048 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?! */
1049 char hostname[RG_MASKLEN];
1050 int usercount = 0;
1051 int validdelay;
1052
1053 rg_loggline(rp, np);
1054
1055 if (rp->type == INSTANT_HOST_GLINE) {
1056 usercount = np->host->clonecount;
1057 snprintf(hostname, sizeof(hostname), "*@%s", IPtostr(np->p_ipaddr));
1058 }
1059
1060 if ((rp->type == INSTANT_IDENT_GLINE) || (usercount > rg_max_per_gline)) {
1061 nick *tnp;
1062
1063 for(usercount=0,tnp=np->host->nicks;tnp;tnp=tnp->nextbyhost)
1064 if(!ircd_strcmp(np->ident, tnp->ident))
1065 usercount++;
1066
1067 snprintf(hostname, sizeof(hostname), "%s@%s", np->ident, IPtostr(np->p_ipaddr));
1068 }
1069
1070 validdelay = (rp->type == INSTANT_KILL) || (rp->type == DELAYED_IDENT_GLINE) || (rp->type == DELAYED_HOST_GLINE) || (rp->type == DELAYED_KILL);
1071 if (validdelay || (usercount > rg_max_per_gline)) {
1072 struct rg_glinenode *nn = (struct rg_glinenode *)malloc(sizeof(struct rg_glinenode));
1073 if(nn) {
1074 nn->next = NULL;
1075 if(gll->end) {
1076 gll->end->next = nn;
1077 gll->end = nn;
1078 } else {
1079 gll->start = nn;
1080 gll->end = nn;
1081 }
1082
1083 nn->np = np;
1084 nn->reason = rp;
1085 if(!validdelay) {
1086 nn->punish = INSTANT_KILL;
1087 } else {
1088 nn->punish = rp->type;
1089 }
1090 }
1091 return usercount;
1092 }
1093
1094 if (rp->type == INSTANT_IDENT_GLINE) {
1095 if (IsAccount(np)) {
1096 controlwall(NO_OPER, NL_HITS, "%s!%s@%s/%s matched user@host gline regex %08lx (class: %s, hit %d user%s)", np->nick, np->ident, np->host->name->content, np->authname, rp->glineid, rp->class, usercount, (usercount!=1)?"s":"");
1097 } else {
1098 controlwall(NO_OPER, NL_HITS, "%s!%s@%s matched user@host gline regex %08lx (class: %s, hit %d user%s)", np->nick, np->ident, np->host->name->content, rp->glineid, rp->class, usercount, (usercount!=1)?"s":"");
1099 }
1100 } else if(rp->type == INSTANT_HOST_GLINE) {
1101 if (IsAccount(np)) {
1102 controlwall(NO_OPER, NL_HITS, "%s!%s@%s/%s matched *@host gline regex %08lx (class: %s, hit %d user%s)", np->nick, np->ident, np->host->name->content, np->authname, rp->glineid, rp->class, usercount, (usercount!=1)?"s":"");
1103 } else {
1104 controlwall(NO_OPER, NL_HITS, "%s!%s@%s matched *@host gline regex %08lx (class: %s, hit %d user%s)", np->nick, np->ident, np->host->name->content, rp->glineid, rp->class, usercount, (usercount!=1)?"s":"");
1105 }
1106 } else {
1107 return 0;
1108 }
1109
1110 rg_shadowserver(np, rp, rp->type);
1111 irc_send("%s GL * +%s %d %jd :AUTO: %s (ID: %08lx)\r\n", mynumeric->content, hostname, rg_expiry_time, (intmax_t)time(NULL), rp->reason->content, rp->glineid);
1112 return usercount;
1113 }
1114
1115 static int floodprotection = 0;
1116 static int lastfloodspam = 0;
1117
1118 void rg_dogline(struct rg_glinelist *gll, nick *np, struct rg_struct *rp, char *matched) {
1119 int t = time(NULL);
1120
1121 if(t > floodprotection) {
1122 floodprotection = t;
1123 } else if((floodprotection - t) / 8 > RG_NETWORK_WIDE_MAX_GLINES_PER_8_SEC) {
1124 if(t > lastfloodspam + 3600) {
1125 channel *cp = findchannel("#twilightzone");
1126 if(cp)
1127 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);
1128 controlwall(NO_OPER, NL_MANAGEMENT, "WARNING! REGEXGLINE DISABLED FOR AN HOUR DUE TO NETWORK WIDE LOOKING GLINE!");
1129 lastfloodspam = t;
1130 floodprotection = t + RG_NETWORK_WIDE_MAX_GLINES_PER_8_SEC * 3600 * 8;
1131 }
1132 return;
1133 }
1134
1135 floodprotection+=__rg_dogline(gll, np, rp, matched);
1136 }
1137
1138 void rg_logevent(nick *np, char *event, char *details, ...) {
1139 char eeevent[RG_QUERY_BUF_SIZE], eedetails[RG_QUERY_BUF_SIZE], eemask[RG_QUERY_BUF_SIZE], eeaccount[RG_QUERY_BUF_SIZE];
1140 char buf[513], account[ACCOUNTLEN + 1], mask[RG_MASKLEN];
1141 int masklen;
1142
1143 va_list va;
1144
1145 if(details) {
1146 va_start(va, details);
1147 vsnprintf(buf, sizeof(buf), details, va);
1148 va_end(va);
1149 } else {
1150 buf[0] = '\0';
1151 }
1152
1153 if(np) {
1154 if (IsAccount(np)) {
1155 strncpy(account, np->authname, sizeof(account) - 1);
1156 account[sizeof(account) - 1] = '\0';
1157 } else {
1158 account[0] = '\0';
1159 }
1160 masklen = RGBuildHostname(mask, np);
1161 } else {
1162 mask[0] = '\0';
1163 masklen = 0;
1164 }
1165
1166 dbescapestring(eeevent, event, strlen(event));
1167 dbescapestring(eedetails, buf, strlen(buf));
1168 dbescapestring(eeaccount, account, strlen(account));
1169 dbescapestring(eemask, mask, masklen);
1170
1171 dbquery("INSERT INTO regexgline.clog (host, account, event, arg, ts) VALUES ('%s', '%s', '%s', '%s', NOW())", eemask, eeaccount, eeevent, eedetails);
1172 }
1173
1174 void rg_loggline(struct rg_struct *rg, nick *np) {
1175 char eenick[RG_QUERY_BUF_SIZE], eeuser[RG_QUERY_BUF_SIZE], eehost[RG_QUERY_BUF_SIZE], eereal[RG_QUERY_BUF_SIZE];
1176
1177 rg->hits++;
1178 rg->hitssaved++;
1179 rg->lastseen = time(NULL);
1180 rg->dirty = 1;
1181
1182 /* @paul: disabled */
1183
1184 return;
1185 dbescapestring(eenick, np->nick, strlen(np->nick));
1186 dbescapestring(eeuser, np->ident, strlen(np->ident));
1187 dbescapestring(eehost, np->host->name->content, strlen(np->host->name->content));
1188 dbescapestring(eereal, np->realname->name->content, strlen(np->realname->name->content));
1189
1190 dbquery("INSERT INTO regexgline.glog (glineid, nickname, username, hostname, realname, ts) VALUES (%d, '%s', '%s', '%s', '%s', NOW())", rg->id, eenick, eeuser, eehost, eereal);
1191 }
1192
1193 static unsigned int getrgmarker(void) {
1194 static unsigned int marker = 0;
1195
1196 marker++;
1197 if(!marker) {
1198 struct rg_struct *l;
1199
1200 /* If we wrapped to zero, zap the marker on all hosts */
1201 for(l=rg_list;l;l=l->next)
1202 l->marker=0;
1203 marker++;
1204 }
1205
1206 return marker;
1207 }
1208
1209 void rg_flush_schedule(void *arg) {
1210 struct rg_struct *l;
1211
1212 for(l=rg_list;l;l=l->next) {
1213 if(!l->dirty)
1214 continue;
1215
1216 dbquery("UPDATE regexgline.glines SET lastseen = %jd, hits = %lu WHERE id = %d", (intmax_t)l->lastseen, l->hitssaved, l->id);
1217
1218 l->dirty = 0;
1219 }
1220 }
1221