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