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