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