5 FUTURE: natural (sort of) language parsing
8 PPA: if multiple users match the same user@host or *@host it'll send multiple glines?!
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"
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
27 #define RESERVED_NICK_GLINE_DURATION 3600 /* 1h */
29 MODULE_VERSION("1.44");
31 typedef struct rg_glinenode
{
33 struct rg_struct
*reason
;
35 struct rg_glinenode
*next
;
38 typedef struct rg_glinelist
{
39 struct rg_glinenode
*start
;
40 struct rg_glinenode
*end
;
43 typedef struct rg_delay
{
46 struct rg_struct
*reason
;
48 struct rg_delay
*next
;
51 #define GLINE_HEADER " ID Expires Set by Class Type Last seen (ago) Hits(p) Hits Reason"
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
);
59 void rg_dogline(struct rg_glinelist
*gll
, nick
*np
, struct rg_struct
*rp
, char *matched
);
60 void rg_flush_schedule(void *arg
);
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
);
67 static DBModuleIdentifier dbid
;
68 static unsigned long highestid
= 0;
69 static int attached
= 0, started
= 0;
71 static unsigned int getrgmarker(void);
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 };
77 void rg_initglinelist(struct rg_glinelist
*gll
);
78 void rg_flushglines(struct rg_glinelist
*gll
);
81 sstring
*max_casualties
, *max_spew
, *expiry_time
, *max_per_gline
;
83 max_casualties
= getcopyconfigitem("regexgline", "maxcasualties", RGStringise(RG_MAX_CASUALTIES_DEFAULT
), 8);
84 if(!protectedatoi(max_casualties
->content
, &rg_max_casualties
))
85 rg_max_casualties
= RG_MAX_CASUALTIES_DEFAULT
;
87 freesstring(max_casualties
);
89 max_spew
= getcopyconfigitem("regexgline", "maxspew", RGStringise(RG_MAX_SPEW_DEFAULT
), 8);
90 if(!protectedatoi(max_spew
->content
, &rg_max_spew
))
91 rg_max_spew
= RG_MAX_SPEW_DEFAULT
;
93 freesstring(max_spew
);
95 expiry_time
= getcopyconfigitem("regexgline", "expirytime", RGStringise(RG_EXPIRY_TIME_DEFAULT
), 8);
96 if(!protectedatoi(expiry_time
->content
, &rg_expiry_time
))
97 rg_expiry_time
= RG_EXPIRY_TIME_DEFAULT
;
99 freesstring(expiry_time
);
101 max_per_gline
= getcopyconfigitem("regexgline", "maxpergline", RGStringise(RG_MAX_PER_GLINE_DEFAULT
), 8);
102 if(!protectedatoi(max_per_gline
->content
, &rg_max_per_gline
))
103 rg_max_per_gline
= RG_MAX_PER_GLINE_DEFAULT
;
105 freesstring(max_per_gline
);
114 Error("regexgline", ERR_STOP
, "Could not connect to database.");
118 static void rg_count_match(struct rg_struct
*rp
, nick
*np
, char *hostname
, void *arg
) {
119 void **varg
= (void **)arg
;
120 int *count
= (int *)varg
[0];
125 static void rg_gline_reply_match(struct rg_struct
*rp
, nick
*np
, char *hostname
, void *arg
) {
126 void **varg
= (void **)arg
;
128 rg_count_match(rp
, np
, hostname
, arg
);
129 rg_gline_match(rp
, np
, hostname
, varg
[1]);
132 int rg_rescan(void *source
, int cargc
, char **cargv
) {
135 nick
*np
= (nick
*)source
, *tnp
;
138 struct rg_glinelist gll
;
142 gline
= !strcmp(cargv
[0], "-g");
149 controlreply(np
, "G-line mode activated.");
151 rg_initglinelist(&gll
);
154 fn
= rg_gline_reply_match
;
157 controlreply(np
, "Beginning scan, this may take a while...");
159 for(j
=0;j
<NICKHASHSIZE
;j
++)
160 for(tnp
=nicktable
[j
];tnp
;tnp
=tnp
->next
)
161 rg_scannick(tnp
, fn
, arg
);
163 controlreply(np
, "Scan completed, %d hits.", count
);
166 rg_flushglines(&gll
);
172 struct rg_struct
*gp
= rg_list
, *oldgp
;
173 rg_delay
*delay
, *delaynext
;
176 deregisterhook(HOOK_NICK_NEWNICK
, &rg_nick
);
177 deregisterhook(HOOK_NICK_RENAME
, &rg_rename
);
178 deregisterhook(HOOK_NICK_LOSTNICK
, &rg_lostnick
);
179 deregistercontrolcmd("regexspew", rg_spew
);
180 deregistercontrolcmd("regexglist", rg_glist
);
181 deregistercontrolcmd("regexdelgline", rg_delgline
);
182 deregistercontrolcmd("regexgline", rg_gline
);
183 deregistercontrolcmd("regexidlookup", rg_idlist
);
184 deregistercontrolcmd("regexrescan", rg_rescan
);
188 for(delay
=rg_delays
;delay
;delay
=delaynext
) {
189 delaynext
=delay
->next
;
190 deleteschedule(delay
->sch
, rg_dodelay
, delay
);
196 deleteschedule(rg_schedule
, &rg_checkexpiry
, NULL
);
200 deleteallschedules(rg_flush_schedule
);
201 rg_flush_schedule(NULL
);
203 for(gp
=rg_list
;gp
;) {
206 rg_freestruct(oldgp
);
210 dbdetach("regexgline");
215 static int ignorable_nick(nick
*np
) {
216 if(IsOper(np
) || IsService(np
) || IsXOper(np
) || SIsService(&serverlist
[homeserver(np
->numeric
)]))
221 void rg_checkexpiry(void *arg
) {
222 struct rg_struct
*rp
= rg_list
, *lp
= NULL
;
223 time_t current
= time(NULL
);
226 if (current
>= rp
->expires
) {
227 dbquery("DELETE FROM regexglines WHERE id = %d", rp
->id
);
244 void rg_setdelay(nick
*np
, rg_struct
*reason
, short punish
) {
246 delay
= (rg_delay
*)malloc(sizeof(rg_delay
));
250 killuser(NULL
, np
, "%s (ID: %08lx)", reason
->reason
->content
, reason
->glineid
);
255 delay
->reason
= reason
;
256 delay
->punish
= punish
;
257 delay
->next
= rg_delays
;
260 delay
->sch
= scheduleoneshot(time(NULL
) + (RG_MINIMUM_DELAY_TIME
+ (rand() % RG_MAXIMUM_RAND_TIME
)), rg_dodelay
, delay
);
263 static void rg_shadowserver(nick
*np
, struct rg_struct
*reason
, int type
) {
266 if(reason
->class != classes
[0]) /* drone */
269 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 triggerhook(HOOK_SHADOW_SERVER
, (void *)buf
);
274 void rg_deletedelay(rg_delay
*delay
) {
275 rg_delay
*temp
, *prev
;
277 for (temp
=rg_delays
;temp
;temp
=temp
->next
) {
280 rg_delays
= temp
->next
;
282 prev
->next
= temp
->next
;
292 void rg_dodelay(void *arg
) {
293 rg_delay
*delay
= (rg_delay
*)arg
;
294 char hostname
[RG_MASKLEN
];
295 int hostlen
, usercount
= 0;
297 /* User or regex gline no longer exists */
298 if((!delay
->np
) || (!delay
->reason
)) {
299 rg_deletedelay(delay
);
303 hostlen
= RGBuildHostname(hostname
, delay
->np
);
305 /* User has wisely changed nicknames */
306 if(pcre_exec(delay
->reason
->regex
, delay
->reason
->hint
, hostname
, hostlen
, 0, 0, NULL
, 0) < 0) {
307 rg_deletedelay(delay
);
311 if (delay
->reason
->type
== DELAYED_HOST_GLINE
) {
312 usercount
= delay
->np
->host
->clonecount
;
313 snprintf(hostname
, sizeof(hostname
), "*@%s", IPtostr(delay
->np
->p_ipaddr
));
316 if((delay
->reason
->type
== DELAYED_IDENT_GLINE
) || (usercount
> rg_max_per_gline
)) {
319 for(usercount
=0,tnp
=delay
->np
->host
->nicks
;tnp
;tnp
=tnp
->nextbyhost
)
320 if(!ircd_strcmp(delay
->np
->ident
, tnp
->ident
))
323 snprintf(hostname
, sizeof(hostname
), "%s@%s", delay
->np
->ident
, IPtostr(delay
->np
->p_ipaddr
));
326 if ((delay
->reason
->type
== DELAYED_KILL
) || (usercount
> rg_max_per_gline
)) {
327 controlwall(NO_OPER
, NL_HITS
, "%s matched delayed kill regex %08lx (class: %s)", gvhost(delay
->np
), delay
->reason
->glineid
, delay
->reason
->class);
329 rg_shadowserver(delay
->np
, delay
->reason
, DELAYED_KILL
);
330 killuser(NULL
, delay
->np
, "%s (ID: %08lx)", delay
->reason
->reason
->content
, delay
->reason
->glineid
);
334 if (delay
->reason
->type
== DELAYED_IDENT_GLINE
) {
335 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":"");
336 } else if (delay
->reason
->type
== DELAYED_HOST_GLINE
) {
337 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":"");
342 rg_shadowserver(delay
->np
, delay
->reason
, delay
->reason
->type
);
343 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
);
344 rg_deletedelay(delay
);
347 void rg_initglinelist(struct rg_glinelist
*gll
) {
352 void rg_flushglines(struct rg_glinelist
*gll
) {
353 struct rg_glinenode
*nn
, *pn
;
354 for(nn
=gll
->start
;nn
;nn
=pn
) {
356 if(nn
->punish
== INSTANT_KILL
) {
357 controlwall(NO_OPER
, NL_HITS
, "%s matched kill regex %08lx (class: %s)", gvhost(nn
->np
), nn
->reason
->glineid
, nn
->reason
->class);
359 rg_shadowserver(nn
->np
, nn
->reason
, nn
->punish
);
360 killuser(NULL
, nn
->np
, "%s (ID: %08lx)", nn
->reason
->reason
->content
, nn
->reason
->glineid
);
361 } else if ((nn
->punish
== DELAYED_IDENT_GLINE
) || (nn
->punish
== DELAYED_HOST_GLINE
) || (nn
->punish
== DELAYED_KILL
)) {
362 rg_setdelay(nn
->np
, nn
->reason
, nn
->punish
);
367 rg_initglinelist(gll
);
370 static void dbloaddata(DBConn
*dbconn
, void *arg
) {
371 DBResult
*dbres
= dbgetresult(dbconn
);
373 if(!dbquerysuccessful(dbres
)) {
374 Error("chanserv", ERR_ERROR
, "Error loading DB");
378 if (dbnumfields(dbres
) != 9) {
379 Error("regexgline", ERR_ERROR
, "DB format error");
383 while(dbfetchrow(dbres
)) {
384 unsigned long id
, hitssaved
;
386 char *gline
, *setby
, *reason
, *expires
, *type
, *class;
388 id
= strtoul(dbgetvalue(dbres
, 0), NULL
, 10);
392 gline
= dbgetvalue(dbres
, 1);
393 setby
= dbgetvalue(dbres
, 2);
394 reason
= dbgetvalue(dbres
, 3);
395 expires
= dbgetvalue(dbres
, 4);
396 type
= dbgetvalue(dbres
, 5);
397 class = dbgetvalue(dbres
, 6);
399 lastseen
= strtoul(dbgetvalue(dbres
, 7), NULL
, 10);
400 hitssaved
= strtoul(dbgetvalue(dbres
, 8), NULL
, 10);
402 if (!rg_newsstruct(id
, gline
, setby
, reason
, expires
, type
, 0, class, lastseen
, hitssaved
))
403 dbquery("DELETE FROM regexgline.glines WHERE id = %lu", id
);
409 static void dbloadfini(DBConn
*dbconn
, void *arg
) {
413 char helpbuf
[8192 * 2], allclasses
[8192];
415 sbinit(&b
, (char *)allclasses
, sizeof(allclasses
));
416 for(p
=classes
;*p
;p
++) {
417 sbaddstr(&b
, (char *)*p
);
422 snprintf(helpbuf
, sizeof(helpbuf
),
423 "Usage: regexgline <regex> <duration> <type> <class> <reason>\n"
424 "Adds a new regular expression pattern.\n"
425 "Duration is represented as 3d, 3M etc.\n"
426 "Class is one of the following: %s\n"
427 "Type is an integer which represents the following:\n"
428 "1 - Instant USER@IP GLINE (igu)\n"
429 "2 - Instant *@IP GLINE (igh)\n"
430 "3 - Instant KILL (ik)\n"
431 "4 - Delayed USER@IP GLINE (dgu)\n"
432 "5 - Delayed *@IP GLINE (dgh)\n"
433 "6 - Delayed KILL (dk)\n"
434 "Note that some classes may have additional side effects (e.g. 'reservednick' also sets nick style glines).",
437 registercontrolhelpcmd("regexgline", NO_OPER
, 5, &rg_gline
, helpbuf
);
438 registercontrolhelpcmd("regexdelgline", NO_OPER
, 1, &rg_delgline
, "Usage: regexdelgline <pattern>\nDeletes a regular expression pattern.");
439 registercontrolhelpcmd("regexglist", NO_OPER
, 1, &rg_glist
, "Usage: regexglist <pattern>\nLists regular expression patterns.");
440 registercontrolhelpcmd("regexspew", NO_OPER
, 1, &rg_spew
, "Usage: regexspew <pattern>\nLists users currently on the network which match the given pattern.");
441 registercontrolhelpcmd("regexidlookup", NO_OPER
, 1, &rg_idlist
, "Usage: regexidlookup <id>\nFinds a regular expression pattern by it's ID number.");
442 registercontrolhelpcmd("regexrescan", NO_OPER
, 1, &rg_rescan
, "Usage: regexrescan ?-g?\nRescans the net for missed clients, optionally glining matches (used for debugging).");
444 registerhook(HOOK_NICK_NEWNICK
, &rg_nick
);
445 registerhook(HOOK_NICK_RENAME
, &rg_rename
);
446 registerhook(HOOK_NICK_LOSTNICK
, &rg_lostnick
);
449 rg_schedule
= schedulerecurring(time(NULL
) + 1, 0, 1, rg_checkexpiry
, NULL
);
450 schedulerecurring(time(NULL
) + 60, 0, 60, rg_flush_schedule
, NULL
);
453 void rg_dbload(void) {
454 dbattach("regexgline");
455 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
);
456 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
);
457 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 dbloadtable("regexgline.glines", NULL
, dbloaddata
, dbloadfini
);
462 static void rg_scannick(nick
*np
, scannick_fn
*fn
, void *arg
) {
463 struct rg_struct
*rp
;
464 char hostname
[RG_MASKLEN
];
467 if(ignorable_nick(np
))
470 hostlen
= RGBuildHostname(hostname
, np
);
472 for(rp
=rg_list
;rp
;rp
=rp
->next
) {
473 if(pcre_exec(rp
->regex
, rp
->hint
, hostname
, hostlen
, 0, 0, NULL
, 0) >= 0) {
474 fn(rp
, np
, hostname
, arg
);
480 static void rg_gline_match(struct rg_struct
*rp
, nick
*np
, char *hostname
, void *arg
) {
481 struct rg_glinelist
*gll
= (struct rg_glinelist
*)arg
;
483 rg_dogline(gll
, np
, rp
, hostname
);
486 void rg_rename(int hooknum
, void *arg
) {
487 void **harg
= (void **)arg
;
488 rg_nick(hooknum
, harg
[0]);
491 void rg_nick(int hooknum
, void *arg
) {
492 nick
*np
= (nick
*)arg
;
493 struct rg_glinelist gll
;
495 rg_initglinelist(&gll
);
497 rg_scannick(np
, rg_gline_match
, &gll
);
499 rg_flushglines(&gll
);
502 void rg_lostnick(int hooknum
, void *arg
) {
503 nick
*np
= (nick
*)arg
;
506 /* Cleanup the delays */
507 for(delay
=rg_delays
;delay
;delay
=delay
->next
)
512 int rg_gline(void *source
, int cargc
, char **cargv
) {
513 nick
*np
= (nick
*)source
, *tnp
;
515 const char *expirybuf
;
516 int expiry
, count
, j
, hostlen
;
517 struct rg_struct
*rp
;
518 struct rg_glinelist gll
;
521 char eemask
[RG_QUERY_BUF_SIZE
], eesetby
[RG_QUERY_BUF_SIZE
], eereason
[RG_QUERY_BUF_SIZE
], eeclass
[RG_QUERY_BUF_SIZE
];
522 char hostname
[RG_MASKLEN
], *class, *reason
, *regex
, type
;
528 if ((strlen(cargv
[2]) != 1) || ((type
!= '1') && (type
!= '2') && (type
!= '3') && (type
!= '4') && (type
!= '5') && (type
!= '6'))) {
529 controlreply(np
, "Invalid type specified!");
537 for(p
=classes
;*p
;p
++)
538 if(!strcasecmp(class, *p
))
542 controlreply(np
, "Bad class supplied.");
546 if (!(expiry
= durationtolong(cargv
[1]))) {
547 controlreply(np
, "Invalid duration specified!");
551 for(rp
=rg_list
;rp
;rp
=rp
->next
) {
552 if (RGMasksEqual(rp
->mask
->content
, regex
)) {
553 controlreply(np
, "That regexgline already exists!");
558 if (rg_sanitycheck(regex
, &count
)) {
559 controlreply(np
, "Error in expression.");
561 } else if (count
< 0) {
562 controlreply(np
, "That expression would hit too many users (%d)!", -count
);
566 realexpiry
= expiry
+ time(NULL
);
568 dbescapestring(eemask
, regex
, strlen(regex
));
569 dbescapestring(eesetby
, np
->nick
, strlen(np
->nick
));
570 dbescapestring(eeclass
, class, strlen(class));
571 dbescapestring(eereason
, reason
, strlen(reason
));
573 highestid
= highestid
+ 1;
574 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
);
575 rp
= rg_newsstruct(highestid
, regex
, np
->nick
, reason
, "", cargv
[2], realexpiry
, class, 0, 0);
577 rg_initglinelist(&gll
);
579 for(j
=0;j
<NICKHASHSIZE
;j
++) {
580 for(tnp
=nicktable
[j
];tnp
;tnp
=tnp
->next
) {
581 if(ignorable_nick(tnp
))
584 hostlen
= RGBuildHostname(hostname
, tnp
);
585 if(pcre_exec(rp
->regex
, rp
->hint
, hostname
, hostlen
, 0, 0, NULL
, 0) >= 0)
586 rg_dogline(&gll
, tnp
, rp
, hostname
);
590 rg_flushglines(&gll
);
592 expirybuf
= longtoduration(expiry
, 0);
594 rg_logevent(np
, "regexgline", "%s %d %d %s %s", regex
, expiry
, count
, class, reason
);
595 controlreply(np
, "Added regexgline: %s (class: %s, expires in: %s, hit %d user%s): %s", regex
, class, expirybuf
, count
, (count
!=1)?"s":"", reason
);
596 /* If we are using NO, can we safely assume the user is authed here and use ->authname? */
597 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
);
602 int rg_sanitycheck(char *mask
, int *count
) {
604 char hostname
[RG_MASKLEN
];
605 int erroroffset
, hostlen
, j
, masklen
= strlen(mask
);
610 if((masklen
< RG_MIN_MASK_LEN
) || (masklen
> RG_REGEXGLINE_MAX
))
613 if(!(regex
= pcre_compile(mask
, RG_PCREFLAGS
, &error
, &erroroffset
, NULL
))) {
614 Error("regexgline", ERR_WARNING
, "Error compiling expression %s at offset %d: %s", mask
, erroroffset
, error
);
617 hint
= pcre_study(regex
, 0, &error
);
619 Error("regexgline", ERR_WARNING
, "Error studying expression %s: %s", mask
, error
);
626 for(j
=0;j
<NICKHASHSIZE
;j
++) {
627 for(np
=nicktable
[j
];np
;np
=np
->next
) {
628 hostlen
= RGBuildHostname(hostname
, np
);
629 if(pcre_exec(regex
, hint
, hostname
, hostlen
, 0, 0, NULL
, 0) >= 0) {
639 if(*count
>= rg_max_casualties
)
645 int rg_delgline(void *source
, int cargc
, char **cargv
) {
646 nick
*np
= (nick
*)source
;
648 struct rg_struct
*rp
= rg_list
, *last
= NULL
;
654 rg_logevent(np
, "regexdelgline", "%s", cargv
[0]);
656 if(RGMasksEqual(rp
->mask
->content
, cargv
[0])) {
659 /* Cleanup the delays */
660 for(delay
=rg_delays
;delay
;delay
=delay
->next
)
661 if(delay
->reason
==rp
)
662 delay
->reason
= NULL
;
664 dbquery("DELETE FROM regexgline.glines WHERE id = %d", rp
->id
);
666 last
->next
= rp
->next
;
680 controlreply(np
, "Deleted (matched: %d).", count
);
681 /* If we are using NO, can we safely assume the user is authed here and use ->authname? */
682 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 controlreply(np
, "No glines matched: %s", cargv
[0]);
689 int rg_idlist(void *source
, int cargc
, char **cargv
) {
690 nick
*np
= (nick
*)source
;
694 } else if (strlen(cargv
[0]) != 8) {
695 controlreply(np
, "Invalid gline id!");
698 struct rg_struct
*rp
;
699 unsigned long id
= 0;
704 if(0xff == rc_hexlookup
[(int)cargv
[0][i
]]) {
705 controlreply(np
, "Invalid gline id!");
708 id
= (id
<< 4) | rc_hexlookup
[(int)cargv
[0][i
]];
713 controlreply(np
, GLINE_HEADER
);
714 for(rp
=rg_list
;rp
;rp
=rp
->next
) {
715 if(id
== rp
->glineid
) {
717 if(rp
->mask
->length
> longest
)
718 longest
= rp
->mask
->length
;
722 for(rp
=rg_list
;rp
;rp
=rp
->next
)
724 rg_displaygline(np
, rp
, longest
);
725 controlreply(np
, "Done.");
731 int rg_glist(void *source
, int cargc
, char **cargv
) {
732 nick
*np
= (nick
*)source
;
733 struct rg_struct
*rp
;
743 if(!(regex
= pcre_compile(cargv
[0], RG_PCREFLAGS
, &error
, &erroroffset
, NULL
))) {
744 controlreply(np
, "Error compiling expression %s at offset %d: %s", cargv
[0], erroroffset
, error
);
747 hint
= pcre_study(regex
, 0, &error
);
749 controlreply(np
, "Error studying expression %s: %s", cargv
[0], error
);
756 rg_logevent(np
, "regexglist", "%s", cargv
[0]);
757 controlreply(np
, GLINE_HEADER
);
758 for(rp
=rg_list
;rp
;rp
=rp
->next
) {
759 if(pcre_exec(regex
, hint
, rp
->mask
->content
, rp
->mask
->length
, 0, 0, NULL
, 0) >= 0) {
761 if(rp
->mask
->length
> longest
)
762 longest
= rp
->mask
->length
;
766 for(rp
=rg_list
;rp
;rp
=rp
->next
)
768 rg_displaygline(np
, rp
, longest
);
775 rg_logevent(np
, "regexglist", "%s", "");
776 controlreply(np
, GLINE_HEADER
);
777 for(rp
=rg_list
;rp
;rp
=rp
->next
)
778 if(rp
->mask
->length
> longest
)
779 longest
= rp
->mask
->length
;
781 for(rp
=rg_list
;rp
;rp
=rp
->next
)
782 rg_displaygline(np
, rp
, longest
);
785 controlreply(np
, "Done.");
789 char *displaytype(int type
) {
791 static char ctypebuf
[10];
816 snprintf(ctypebuf
, sizeof(ctype
), "%1d:%s", type
, ctype
);
820 char *getsep(int longest
) {
821 static int lastlongest
= -1;
822 static char lenbuf
[1024];
829 if(longest >= sizeof(lenbuf) - 20)
830 longest = sizeof(lenbuf) - 20;
833 if(lastlongest
== -1) {
836 for(i
=0;i
<sizeof(lenbuf
)-1;i
++)
838 lenbuf
[sizeof(lenbuf
)-1] = '\0';
842 if(lastlongest
!= longest
) {
843 lenbuf
[lastlongest
] = '-';
844 lenbuf
[longest
] = '\0';
845 lastlongest
= longest
;
851 void rg_displaygline(nick
*np
, struct rg_struct
*rp
, int longest
) { /* could be a macro? I'll assume the C compiler inlines it */
852 char *sep
= getsep(longest
);
853 /* 12345678 12345678901234567890 123456789012345 12345678 12345 12345678901234567890 1234567 1234567 123456
854 ID Expires Set by Class Type Last seen (ago) Hits(s) Hits Reason
858 time_t t
= time(NULL
);
860 if(rp
->lastseen
== 0) {
861 strlcpy(d
, "(never)", sizeof(d
));
863 strlcpy(d
, longtoduration(t
- rp
->lastseen
, 2), sizeof(d
));
866 controlreply(np
, "%s", rp
->mask
->content
);
867 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
);
868 controlreply(np
, "%s", sep
);
871 int rg_spew(void *source
, int cargc
, char **cargv
) {
872 nick
*np
= (nick
*)source
, *tnp
;
873 int counter
= 0, erroroffset
, hostlen
, j
;
877 char hostname
[RG_MASKLEN
];
884 if(!(regex
= pcre_compile(cargv
[0], RG_PCREFLAGS
, &error
, &erroroffset
, NULL
))) {
885 controlreply(np
, "Error compiling expression %s at offset %d: %s", cargv
[0], erroroffset
, error
);
888 hint
= pcre_study(regex
, 0, &error
);
890 controlreply(np
, "Error studying expression %s: %s", cargv
[0], error
);
896 rg_logevent(np
, "regexspew", "%s", cargv
[0]);
898 for(j
=0;j
<NICKHASHSIZE
;j
++) {
899 for(tnp
=nicktable
[j
];tnp
;tnp
=tnp
->next
) {
900 hostlen
= RGBuildHostname(hostname
, tnp
);
901 pcreret
= pcre_exec(regex
, hint
, hostname
, hostlen
, 0, 0, ovector
, sizeof(ovector
) / sizeof(int));
903 if(counter
== rg_max_spew
) {
904 controlreply(np
, "Reached maximum spew count (%d) - aborting display.", rg_max_spew
);
905 } else if (counter
< rg_max_spew
) {
906 /* 15 should be number of bolds */
907 char boldbuf
[RG_MASKLEN
+ 15], *tp
, *fp
, *realname
= NULL
;
909 for(tp
=hostname
,fp
=boldbuf
;*tp
;) {
910 if(tp
- hostname
== ovector
[0]) {
914 if(tp
- hostname
== ovector
[1]) {
933 controlreply(np
, "%s (%s) (%dc)", boldbuf
, realname
, tnp
->channels
->cursi
);
939 controlreply(np
, "Done - %d matches.", counter
);
948 void rg_startup(void) {
951 struct rg_glinelist gll
;
953 rg_initglinelist(&gll
);
955 for(j
=0;j
<NICKHASHSIZE
;j
++)
956 for(np
=nicktable
[j
];np
;np
=np
->next
)
957 rg_scannick(np
, rg_gline_match
, &gll
);
959 rg_flushglines(&gll
);
962 void rg_freestruct(struct rg_struct
*rp
) {
963 freesstring(rp
->mask
);
964 freesstring(rp
->setby
);
965 freesstring(rp
->reason
);
966 pcre_free(rp
->regex
);
972 struct rg_struct
*rg_newstruct(time_t expires
) {
973 struct rg_struct
*rp
;
975 if (time(NULL
) >= expires
)
978 rp
= (struct rg_struct
*)malloc(sizeof(struct rg_struct
));
980 struct rg_struct
*tp
, *lp
;
982 memset(rp
, 0, sizeof(rg_struct
));
983 rp
->expires
= expires
;
985 for(lp
=NULL
,tp
=rg_list
;tp
;lp
=tp
,tp
=tp
->next
) {
986 if (expires
<= tp
->expires
) { /* <= possible, slight speed increase */
1009 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
) {
1010 struct rg_struct
*newrow
, *lp
, *cp
;
1012 char glineiddata
[1024];
1015 if (iexpires
== 0) {
1017 if(!protectedatoi(expires
, &qexpires
))
1019 rexpires
= (time_t)qexpires
;
1021 rexpires
= iexpires
;
1024 newrow
= rg_newstruct(rexpires
);
1030 for(p
=classes
;*p
;p
++) {
1031 if(!strcasecmp(class, *p
)) {
1038 newrow
->class = "unknown";
1040 if(!(newrow
->regex
= pcre_compile(mask
, RG_PCREFLAGS
, &error
, &erroroffset
, NULL
))) {
1041 Error("regexgline", ERR_WARNING
, "Error compiling expression %s at offset %d: %s", mask
, erroroffset
, error
);
1044 newrow
->hint
= pcre_study(newrow
->regex
, 0, &error
);
1046 Error("regexgline", ERR_WARNING
, "Error studying expression %s: %s", mask
, error
);
1047 pcre_free(newrow
->regex
);
1053 newrow
->hitssaved
= hitssaved
;
1054 newrow
->lastseen
= lastseen
;
1056 newrow
->mask
= getsstring(mask
, RG_REGEXGLINE_MAX
);
1058 Error("regexgline", ERR_WARNING
, "Error allocating memory for mask!");
1062 newrow
->setby
= getsstring(setby
, ACCOUNTLEN
);
1063 if(!newrow
->setby
) {
1064 Error("regexgline", ERR_WARNING
, "Error allocating memory for setby!");
1068 newrow
->reason
= getsstring(reason
, RG_REASON_MAX
);
1069 if(!newrow
->reason
) {
1070 Error("regexgline", ERR_WARNING
, "Error allocating memory for reason!");
1074 if(!protectedatoi(type
, &newrow
->type
))
1075 newrow
->type
= 0; /* just in case */
1077 snprintf(glineiddata
, sizeof(glineiddata
), "%s regexgline %s %s %s %d %d", mynumeric
->content
, mask
, setby
, reason
, (int)iexpires
, newrow
->type
);
1078 newrow
->glineid
= crc32(glineiddata
);
1085 freesstring(newrow
->mask
);
1087 freesstring(newrow
->setby
);
1089 freesstring(newrow
->reason
);
1090 pcre_free(newrow
->regex
);
1092 pcre_free(newrow
->hint
);
1095 for(lp
=NULL
,cp
=rg_list
;cp
;lp
=cp
,cp
=cp
->next
) {
1098 lp
->next
= cp
->next
;
1109 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?! */
1110 char hostname
[RG_MASKLEN
];
1114 rg_loggline(rp
, np
);
1116 if (rp
->type
== INSTANT_HOST_GLINE
) {
1117 usercount
= np
->host
->clonecount
;
1118 snprintf(hostname
, sizeof(hostname
), "*@%s", IPtostr(np
->p_ipaddr
));
1121 if ((rp
->type
== INSTANT_IDENT_GLINE
) || (usercount
> rg_max_per_gline
)) {
1124 for(usercount
=0,tnp
=np
->host
->nicks
;tnp
;tnp
=tnp
->nextbyhost
)
1125 if(!ircd_strcmp(np
->ident
, tnp
->ident
))
1128 snprintf(hostname
, sizeof(hostname
), "%s@%s", np
->ident
, IPtostr(np
->p_ipaddr
));
1131 if(!strcmp(rp
->class, RESERVED_NICK_CLASS
))
1132 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 validdelay
= (rp
->type
== INSTANT_KILL
) || (rp
->type
== DELAYED_IDENT_GLINE
) || (rp
->type
== DELAYED_HOST_GLINE
) || (rp
->type
== DELAYED_KILL
);
1135 if (validdelay
|| (usercount
> rg_max_per_gline
)) {
1136 struct rg_glinenode
*nn
= (struct rg_glinenode
*)malloc(sizeof(struct rg_glinenode
));
1140 gll
->end
->next
= nn
;
1150 nn
->punish
= INSTANT_KILL
;
1152 nn
->punish
= rp
->type
;
1158 if (rp
->type
== INSTANT_IDENT_GLINE
) {
1159 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":"");
1160 } else if(rp
->type
== INSTANT_HOST_GLINE
) {
1161 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":"");
1166 rg_shadowserver(np
, rp
, rp
->type
);
1167 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
);
1171 static char *gvhost(nick
*np
) {
1172 static char buf
[NICKLEN
+1+USERLEN
+1+HOSTLEN
+1+ACCOUNTLEN
+4+REALLEN
+1+10];
1175 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 snprintf(buf
, sizeof(buf
), "%s!%s@%s r(%s)", np
->nick
, np
->ident
, np
->host
->name
->content
, np
->realname
->name
->content
);
1183 static int floodprotection
= 0;
1184 static int lastfloodspam
= 0;
1186 void rg_dogline(struct rg_glinelist
*gll
, nick
*np
, struct rg_struct
*rp
, char *matched
) {
1189 if(t
> floodprotection
) {
1190 floodprotection
= t
;
1191 } else if((floodprotection
- t
) / 8 > RG_NETWORK_WIDE_MAX_GLINES_PER_8_SEC
) {
1192 if(t
> lastfloodspam
+ 3600) {
1193 channel
*cp
= findchannel("#twilightzone");
1195 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
);
1196 controlwall(NO_OPER
, NL_MANAGEMENT
, "WARNING! REGEXGLINE DISABLED FOR AN HOUR DUE TO NETWORK WIDE LOOKING GLINE!");
1198 floodprotection
= t
+ RG_NETWORK_WIDE_MAX_GLINES_PER_8_SEC
* 3600 * 8;
1203 floodprotection
+=__rg_dogline(gll
, np
, rp
, matched
);
1206 void rg_logevent(nick
*np
, char *event
, char *details
, ...) {
1207 char eeevent
[RG_QUERY_BUF_SIZE
], eedetails
[RG_QUERY_BUF_SIZE
], eemask
[RG_QUERY_BUF_SIZE
], eeaccount
[RG_QUERY_BUF_SIZE
];
1208 char buf
[513], account
[ACCOUNTLEN
+ 1], mask
[RG_MASKLEN
];
1214 va_start(va
, details
);
1215 vsnprintf(buf
, sizeof(buf
), details
, va
);
1222 if (IsAccount(np
)) {
1223 strncpy(account
, np
->authname
, sizeof(account
) - 1);
1224 account
[sizeof(account
) - 1] = '\0';
1228 masklen
= RGBuildHostname(mask
, np
);
1234 dbescapestring(eeevent
, event
, strlen(event
));
1235 dbescapestring(eedetails
, buf
, strlen(buf
));
1236 dbescapestring(eeaccount
, account
, strlen(account
));
1237 dbescapestring(eemask
, mask
, masklen
);
1239 dbquery("INSERT INTO regexgline.clog (host, account, event, arg, ts) VALUES ('%s', '%s', '%s', '%s', NOW())", eemask
, eeaccount
, eeevent
, eedetails
);
1242 void rg_loggline(struct rg_struct
*rg
, nick
*np
) {
1243 char eenick
[RG_QUERY_BUF_SIZE
], eeuser
[RG_QUERY_BUF_SIZE
], eehost
[RG_QUERY_BUF_SIZE
], eereal
[RG_QUERY_BUF_SIZE
];
1247 rg
->lastseen
= time(NULL
);
1250 /* @paul: disabled */
1253 dbescapestring(eenick
, np
->nick
, strlen(np
->nick
));
1254 dbescapestring(eeuser
, np
->ident
, strlen(np
->ident
));
1255 dbescapestring(eehost
, np
->host
->name
->content
, strlen(np
->host
->name
->content
));
1256 dbescapestring(eereal
, np
->realname
->name
->content
, strlen(np
->realname
->name
->content
));
1258 dbquery("INSERT INTO regexgline.glog (glineid, nickname, username, hostname, realname, ts) VALUES (%d, '%s', '%s', '%s', '%s', NOW())", rg
->id
, eenick
, eeuser
, eehost
, eereal
);
1261 static unsigned int getrgmarker(void) {
1262 static unsigned int marker
= 0;
1266 struct rg_struct
*l
;
1268 /* If we wrapped to zero, zap the marker on all hosts */
1269 for(l
=rg_list
;l
;l
=l
->next
)
1277 void rg_flush_schedule(void *arg
) {
1278 struct rg_struct
*l
;
1280 for(l
=rg_list
;l
;l
=l
->next
) {
1284 dbquery("UPDATE regexgline.glines SET lastseen = %jd, hits = %lu WHERE id = %d", (intmax_t)l
->lastseen
, l
->hitssaved
, l
->id
);