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