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