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