*/
#include "regexgline.h"
+#include "../lib/version.h"
+#include "../dbapi/dbapi.h"
+#include "../lib/stringbuf.h"
+#include "../core/hooks.h"
+#include "../server/server.h"
+#include "../lib/strlfunc.h"
+#include <stdint.h>
+
+#define INSTANT_IDENT_GLINE 1
+#define INSTANT_HOST_GLINE 2
+#define INSTANT_KILL 3
+#define DELAYED_IDENT_GLINE 4
+#define DELAYED_HOST_GLINE 5
+#define DELAYED_KILL 6
+
+#define RESERVED_NICK_GLINE_DURATION 3600 /* 1h */
+
+MODULE_VERSION("1.44");
typedef struct rg_glinenode {
nick *np;
struct rg_delay *next;
} rg_delay;
+#define GLINE_HEADER " ID Expires Set by Class Type Last seen (ago) Hits(p) Hits Reason"
+
rg_delay *rg_delays;
void rg_setdelay(nick *np, struct rg_struct *reason, short punish);
void rg_dodelay(void *arg);
void rg_dogline(struct rg_glinelist *gll, nick *np, struct rg_struct *rp, char *matched);
+void rg_flush_schedule(void *arg);
+
+static char *gvhost(nick *np);
+
+static DBModuleIdentifier dbid;
+static unsigned long highestid = 0;
+static int attached = 0, started = 0;
+
+static unsigned int getrgmarker(void);
+
+#define RESERVED_NICK_CLASS "reservednick"
+/* shadowserver only reports classes[0] */
+static const char *classes[] = { "drone", "proxy", "spam", "other", RESERVED_NICK_CLASS, (char *)0 };
void _init(void) {
sstring *max_casualties, *max_spew, *expiry_time, *max_per_gline;
rg_delays = NULL;
- if(!rg_dbconnect()) {
+ if(dbconnected()) {
+ attached = 1;
+ dbid = dbgetid();
rg_dbload();
-
- registercontrolhelpcmd("regexgline", NO_OPER, 4, &rg_gline, "Usage: regexgline <text>\nAdds a new regular expression pattern.");
- registercontrolhelpcmd("regexdelgline", NO_OPER, 1, &rg_delgline, "Usage: regexdelgline <pattern>\nDeletes a regular expression pattern.");
- registercontrolhelpcmd("regexglist", NO_OPER, 1, &rg_glist, "Usage: regexglist <pattern>\nLists regular expression patterns.");
- registercontrolhelpcmd("regexspew", NO_OPER, 1, &rg_spew, "Usage: regexspew <pattern>\nLists users currently on the network which match the given pattern.");
- registercontrolhelpcmd("regexidlookup", NO_OPER, 1, &rg_idlist, "Usage: regexidlookup <id>\nFinds a regular expression pattern by it's ID number.");
-
- registerhook(HOOK_NICK_NEWNICK, &rg_nick);
- registerhook(HOOK_NICK_RENAME, &rg_nick);
- registerhook(HOOK_NICK_LOSTNICK, &rg_lostnick);
- rg_startup();
-
- rg_schedule = schedulerecurring(time(NULL) + 1, 0, 1, rg_checkexpiry, NULL);
+ } else {
+ Error("regexgline", ERR_STOP, "Could not connect to database.");
}
}
-
+
void _fini(void) {
struct rg_struct *gp = rg_list, *oldgp;
rg_delay *delay, *delaynext;
- deregisterhook(HOOK_NICK_NEWNICK, &rg_nick);
- deregisterhook(HOOK_NICK_RENAME, &rg_nick);
- deregisterhook(HOOK_NICK_LOSTNICK, &rg_lostnick);
- deregistercontrolcmd("regexspew", rg_spew);
- deregistercontrolcmd("regexglist", rg_glist);
- deregistercontrolcmd("regexdelgline", rg_delgline);
- deregistercontrolcmd("regexgline", rg_gline);
- deregistercontrolcmd("regexidlookup", rg_idlist);
-
+ if(started) {
+ deregisterhook(HOOK_NICK_NEWNICK, &rg_nick);
+ deregisterhook(HOOK_NICK_RENAME, &rg_nick);
+ deregisterhook(HOOK_NICK_LOSTNICK, &rg_lostnick);
+ deregistercontrolcmd("regexspew", rg_spew);
+ deregistercontrolcmd("regexglist", rg_glist);
+ deregistercontrolcmd("regexdelgline", rg_delgline);
+ deregistercontrolcmd("regexgline", rg_gline);
+ deregistercontrolcmd("regexidlookup", rg_idlist);
+ }
+
if(rg_delays) {
for(delay=rg_delays;delay;delay=delaynext) {
delaynext=delay->next;
deleteschedule(rg_schedule, &rg_checkexpiry, NULL);
rg_schedule = NULL;
}
-
+
+ deleteallschedules(rg_flush_schedule);
+ rg_flush_schedule(NULL);
+
for(gp=rg_list;gp;) {
oldgp = gp;
gp = gp->next;
rg_freestruct(oldgp);
}
-
- if(rg_sqlconnected)
- rg_sqldisconnect();
+
+ if(attached) {
+ dbdetach("regexgline");
+ dbfreeid(dbid);
+ }
+}
+
+static int ignorable_nick(nick *np) {
+ if(IsOper(np) || IsService(np) || IsXOper(np) || SIsService(&serverlist[homeserver(np->numeric)]))
+ return 1;
+ return 0;
}
void rg_checkexpiry(void *arg) {
while(rp) {
if (current >= rp->expires) {
+ dbquery("DELETE FROM regexglines WHERE id = %d", rp->id);
if (lp) {
lp->next = rp->next;
rg_freestruct(rp);
delay->sch = scheduleoneshot(time(NULL) + (RG_MINIMUM_DELAY_TIME + (rand() % RG_MAXIMUM_RAND_TIME)), rg_dodelay, delay);
}
+static void rg_shadowserver(nick *np, struct rg_struct *reason, int type) {
+ char buf[1024];
+
+ if(reason->class != classes[0]) /* drone */
+ return;
+
+ 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);
+
+ triggerhook(HOOK_SHADOW_SERVER, (void *)buf);
+}
+
void rg_deletedelay(rg_delay *delay) {
rg_delay *temp, *prev;
prev = NULL;
return;
}
- if (delay->reason->type == 5) {
+ if (delay->reason->type == DELAYED_HOST_GLINE) {
usercount = delay->np->host->clonecount;
- snprintf(hostname, sizeof(hostname), "*@%s", IPtostr(delay->np->ipaddress));
+ snprintf(hostname, sizeof(hostname), "*@%s", IPtostr(delay->np->p_ipaddr));
}
- if((delay->reason->type == 4) || (usercount > rg_max_per_gline)) {
+ if((delay->reason->type == DELAYED_IDENT_GLINE) || (usercount > rg_max_per_gline)) {
nick *tnp;
for(usercount=0,tnp=delay->np->host->nicks;tnp;tnp=tnp->nextbyhost)
if(!ircd_strcmp(delay->np->ident, tnp->ident))
usercount++;
- snprintf(hostname, sizeof(hostname), "%s@%s", delay->np->ident, IPtostr(delay->np->ipaddress));
+ snprintf(hostname, sizeof(hostname), "%s@%s", delay->np->ident, IPtostr(delay->np->p_ipaddr));
}
- if ((delay->reason->type == 6) || (usercount > rg_max_per_gline)) {
- if (IsAccount(delay->np)) {
- 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);
- } else {
- 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);
- }
+ if ((delay->reason->type == DELAYED_KILL) || (usercount > rg_max_per_gline)) {
+ controlwall(NO_OPER, NL_HITS, "%s matched delayed kill regex %08lx (class: %s)", gvhost(delay->np), delay->reason->glineid, delay->reason->class);
+
+ rg_shadowserver(delay->np, delay->reason, DELAYED_KILL);
killuser(NULL, delay->np, "%s (ID: %08lx)", delay->reason->reason->content, delay->reason->glineid);
return;
}
- if ((delay->reason->type <= 3) || (delay->reason->type >= 6))
- return;
-
- if (delay->reason->type == 4) {
- if (IsAccount(delay->np)) {
- 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":"");
- } else {
- 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":"");
- }
+ if (delay->reason->type == DELAYED_IDENT_GLINE) {
+ 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":"");
+ } else if (delay->reason->type == DELAYED_HOST_GLINE) {
+ 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":"");
} else {
- if (IsAccount(delay->np)) {
- 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":"");
- } else {
- 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":"");
- }
+ return;
}
- irc_send("%s GL * +%s %d :%s (ID: %08lx)\r\n", mynumeric->content, hostname, rg_expiry_time, delay->reason->reason->content, delay->reason->glineid);
+ rg_shadowserver(delay->np, delay->reason, delay->reason->type);
+ 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);
rg_deletedelay(delay);
}
struct rg_glinenode *nn, *pn;
for(nn=gll->start;nn;nn=pn) {
pn = nn->next;
- if(nn->punish == 3) {
- if ( IsAccount(nn->np) ) {
- 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);
- } else {
- 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);
- }
-
+ if(nn->punish == INSTANT_KILL) {
+ controlwall(NO_OPER, NL_HITS, "%s matched kill regex %08lx (class: %s)", gvhost(nn->np), nn->reason->glineid, nn->reason->class);
+
+ rg_shadowserver(nn->np, nn->reason, nn->punish);
killuser(NULL, nn->np, "%s (ID: %08lx)", nn->reason->reason->content, nn->reason->glineid);
- } else if ((nn->punish == 4) || (nn->punish == 5) || (nn->punish == 6)) {
+ } else if ((nn->punish == DELAYED_IDENT_GLINE) || (nn->punish == DELAYED_HOST_GLINE) || (nn->punish == DELAYED_KILL)) {
rg_setdelay(nn->np, nn->reason, nn->punish);
}
free(nn);
rg_initglinelist(gll);
}
-int rg_dbconnect(void) {
- sstring *dbhost, *dbusername, *dbpassword, *dbdatabase, *dbport;
-
- dbhost = getcopyconfigitem("regexgline", "dbhost", "localhost", HOSTLEN);
- dbusername = getcopyconfigitem("regexgline", "dbusername", "regexgline", 20);
- dbpassword = getcopyconfigitem("regexgline", "dbpassword", "moo", 20);
- dbdatabase = getcopyconfigitem("regexgline", "dbdatabase", "regexgline", 20);
- dbport = getcopyconfigitem("regexgline", "dbport", "3306", 8);
+static void dbloaddata(DBConn *dbconn, void *arg) {
+ DBResult *dbres = dbgetresult(dbconn);
- if(rg_sqlconnect(dbhost->content, dbusername->content, dbpassword->content, dbdatabase->content, strtol(dbport->content, NULL, 10))) {
- Error("regexgline", ERR_FATAL, "Cannot connect to database host!");
- return 1; /* PPA: splidge: do something here 8]! */
- } else {
- rg_sqlconnected = 1;
+ if(!dbquerysuccessful(dbres)) {
+ Error("chanserv", ERR_ERROR, "Error loading DB");
+ return;
}
-
- freesstring(dbhost);
- freesstring(dbusername);
- freesstring(dbpassword);
- freesstring(dbdatabase);
- freesstring(dbport);
- return 0;
+ if (dbnumfields(dbres) != 9) {
+ Error("regexgline", ERR_ERROR, "DB format error");
+ return;
+ }
+
+ while(dbfetchrow(dbres)) {
+ unsigned long id, hitssaved;
+ time_t lastseen;
+ char *gline, *setby, *reason, *expires, *type, *class;
+
+ id = strtoul(dbgetvalue(dbres, 0), NULL, 10);
+ if(id > highestid)
+ highestid = id;
+
+ gline = dbgetvalue(dbres, 1);
+ setby = dbgetvalue(dbres, 2);
+ reason = dbgetvalue(dbres, 3);
+ expires = dbgetvalue(dbres, 4);
+ type = dbgetvalue(dbres, 5);
+ class = dbgetvalue(dbres, 6);
+
+ lastseen = strtoul(dbgetvalue(dbres, 7), NULL, 10);
+ hitssaved = strtoul(dbgetvalue(dbres, 8), NULL, 10);
+
+ if (!rg_newsstruct(id, gline, setby, reason, expires, type, 0, class, lastseen, hitssaved))
+ dbquery("DELETE FROM regexgline.glines WHERE id = %lu", id);
+ }
+
+ dbclear(dbres);
}
-int rg_dbload(void) {
- 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);
- 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);
- 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);
-
- if(!rg_sqlquery("SELECT id, gline, setby, reason, expires, type FROM regexglines")) {
- rg_sqlresult res;
- if((res = rg_sqlstoreresult())) {
- rg_sqlrow row;
- while((row = rg_sqlgetrow(res))) {
- if (!rg_newsstruct(row[0], row[1], row[2], row[3], row[4], row[5], 0, 0))
- rg_sqlquery("DELETE FROM regexglines WHERE id = %s", row[0]);
- }
- rg_sqlfree(res);
- }
- }
-
- return 0;
+static void dbloadfini(DBConn *dbconn, void *arg) {
+ started = 1;
+ StringBuf b;
+ const char **p;
+ char helpbuf[8192 * 2], allclasses[8192];
+
+ sbinit(&b, (char *)allclasses, sizeof(allclasses));
+ for(p=classes;*p;p++) {
+ sbaddstr(&b, (char *)*p);
+ sbaddchar(&b, ' ');
+ }
+ sbterminate(&b);
+
+ snprintf(helpbuf, sizeof(helpbuf),
+ "Usage: regexgline <regex> <duration> <type> <class> <reason>\n"
+ "Adds a new regular expression pattern.\n"
+ "Duration is represented as 3d, 3M etc.\n"
+ "Class is one of the following: %s\n"
+ "Type is an integer which represents the following:\n"
+ "1 - Instant USER@IP GLINE (igu)\n"
+ "2 - Instant *@IP GLINE (igh)\n"
+ "3 - Instant KILL (ik)\n"
+ "4 - Delayed USER@IP GLINE (dgu)\n"
+ "5 - Delayed *@IP GLINE (dgh)\n"
+ "6 - Delayed KILL (dk)\n"
+ "Note that some classes may have additional side effects (e.g. 'reservednick' also sets nick style glines).",
+ allclasses);
+
+ registercontrolhelpcmd("regexgline", NO_OPER, 5, &rg_gline, helpbuf);
+ registercontrolhelpcmd("regexdelgline", NO_OPER, 1, &rg_delgline, "Usage: regexdelgline <pattern>\nDeletes a regular expression pattern.");
+ registercontrolhelpcmd("regexglist", NO_OPER, 1, &rg_glist, "Usage: regexglist <pattern>\nLists regular expression patterns.");
+ registercontrolhelpcmd("regexspew", NO_OPER, 1, &rg_spew, "Usage: regexspew <pattern>\nLists users currently on the network which match the given pattern.");
+ registercontrolhelpcmd("regexidlookup", NO_OPER, 1, &rg_idlist, "Usage: regexidlookup <id>\nFinds a regular expression pattern by it's ID number.");
+
+ registerhook(HOOK_NICK_NEWNICK, &rg_nick);
+ registerhook(HOOK_NICK_RENAME, &rg_nick);
+ registerhook(HOOK_NICK_LOSTNICK, &rg_lostnick);
+ rg_startup();
+
+ rg_schedule = schedulerecurring(time(NULL) + 1, 0, 1, rg_checkexpiry, NULL);
+ schedulerecurring(time(NULL) + 60, 0, 60, rg_flush_schedule, NULL);
+}
+
+void rg_dbload(void) {
+ dbattach("regexgline");
+ 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);
+ 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);
+ 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);
+
+ dbloadtable("regexgline.glines", NULL, dbloaddata, dbloadfini);
}
void rg_nick(int hooknum, void *arg) {
rg_initglinelist(&gll);
hostlen = RGBuildHostname(hostname, np);
-
+
+ if(ignorable_nick(np))
+ return;
+
for(rp=rg_list;rp;rp=rp->next) {
if(pcre_exec(rp->regex, rp->hint, hostname, hostlen, 0, 0, NULL, 0) >= 0) {
rg_dogline(&gll, np, rp, hostname);
int expiry, count, j, hostlen;
struct rg_struct *rp;
struct rg_glinelist gll;
+ const char **p;
- char eemask[RG_QUERY_BUF_SIZE], eesetby[RG_QUERY_BUF_SIZE], eereason[RG_QUERY_BUF_SIZE];
- char hostname[RG_MASKLEN];
+ char eemask[RG_QUERY_BUF_SIZE], eesetby[RG_QUERY_BUF_SIZE], eereason[RG_QUERY_BUF_SIZE], eeclass[RG_QUERY_BUF_SIZE];
+ char hostname[RG_MASKLEN], *class, *reason, *regex, type;
- if(cargc < 4)
+ if(cargc < 5)
return CMD_USAGE;
-
- 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'))) {
+
+ type = cargv[2][0];
+ if ((strlen(cargv[2]) != 1) || ((type != '1') && (type != '2') && (type != '3') && (type != '4') && (type != '5') && (type != '6'))) {
controlreply(np, "Invalid type specified!");
return CMD_USAGE;
}
+ regex = cargv[0];
+ class = cargv[3];
+ reason = cargv[4];
+
+ for(p=classes;*p;p++)
+ if(!strcasecmp(class, *p))
+ break;
+
+ if(!*p) {
+ controlreply(np, "Bad class supplied.");
+ return CMD_USAGE;
+ }
+
if (!(expiry = durationtolong(cargv[1]))) {
controlreply(np, "Invalid duration specified!");
return CMD_USAGE;
}
for(rp=rg_list;rp;rp=rp->next) {
- if (RGMasksEqual(rp->mask->content, cargv[0])) {
- controlreply(np, "That regexp gline already exists!");
+ if (RGMasksEqual(rp->mask->content, regex)) {
+ controlreply(np, "That regexgline already exists!");
return CMD_ERROR;
}
}
- if (rg_sanitycheck(cargv[0], &count)) {
+ if (rg_sanitycheck(regex, &count)) {
controlreply(np, "Error in expression.");
return CMD_ERROR;
} else if (count < 0) {
realexpiry = expiry + time(NULL);
- rg_sqlescape_string(eemask, cargv[0], strlen(cargv[0]));
- rg_sqlescape_string(eesetby, np->nick, strlen(np->nick));
- rg_sqlescape_string(eereason, cargv[3], strlen(cargv[3]));
-
- rg_sqlquery("INSERT INTO regexglines (gline, setby, reason, expires, type) VALUES ('%s', '%s', '%s', %d, %s)", eemask, eesetby, eereason, realexpiry, cargv[2]);
- 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])) {
- rg_sqlresult res;
- if((res = rg_sqlstoreresult())) {
- rg_sqlrow row;
- row = rg_sqlgetrow(res);
- if (row) {
- rp = rg_newsstruct(row[0], cargv[0], np->nick, cargv[3], "", cargv[2], realexpiry, 0);
- rg_sqlfree(res);
- if(!rp) {
- 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]);
- controlreply(np, "Error allocating - regexgline NOT ADDED.");
- return CMD_ERROR;
- }
- } else {
- rg_sqlfree(res);
- 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]);
- controlreply(np, "Error selecting ID from database - regexgline NOT ADDED.");
- return CMD_ERROR;
- }
- } else {
- 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]);
- controlreply(np, "Error fetching ID from database - regexgline NOT ADDED.");
- return CMD_ERROR;
- }
- } else {
- 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]);
- controlreply(np, "Error executing query - regexgline NOT ADDED.");
- return CMD_ERROR;
- }
+ dbescapestring(eemask, regex, strlen(regex));
+ dbescapestring(eesetby, np->nick, strlen(np->nick));
+ dbescapestring(eeclass, class, strlen(class));
+ dbescapestring(eereason, reason, strlen(reason));
+
+ highestid = highestid + 1;
+ 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);
+ rp = rg_newsstruct(highestid, regex, np->nick, reason, "", cargv[2], realexpiry, class, 0, 0);
rg_initglinelist(&gll);
for(j=0;j<NICKHASHSIZE;j++) {
for(tnp=nicktable[j];tnp;tnp=tnp->next) {
+ if(ignorable_nick(tnp))
+ continue;
+
hostlen = RGBuildHostname(hostname, tnp);
if(pcre_exec(rp->regex, rp->hint, hostname, hostlen, 0, 0, NULL, 0) >= 0)
rg_dogline(&gll, tnp, rp, hostname);
expirybuf = longtoduration(expiry, 0);
- rg_logevent(np, "regexgline", "%s %d %d %s", cargv[0], expiry, count, cargv[3]);
- controlreply(np, "Added regexgline: %s (expires in: %s, hit %d user%s): %s", cargv[0], expirybuf, count, (count!=1)?"s":"", cargv[3]);
+ rg_logevent(np, "regexgline", "%s %d %d %s %s", regex, expiry, count, class, reason);
+ controlreply(np, "Added regexgline: %s (class: %s, expires in: %s, hit %d user%s): %s", regex, class, expirybuf, count, (count!=1)?"s":"", reason);
/* If we are using NO, can we safely assume the user is authed here and use ->authname? */
- 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]);
+ 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);
return CMD_OK;
}
if(delay->reason==rp)
delay->reason = NULL;
- rg_sqlquery("DELETE FROM regexglines WHERE id = %d", rp->id);
+ dbquery("DELETE FROM regexgline.glines WHERE id = %d", rp->id);
if(last) {
last->next = rp->next;
rg_freestruct(rp);
} else {
struct rg_struct *rp;
unsigned long id = 0;
- int i;
+ int i, longest = 0;
+ unsigned int m;
for(i=0;i<8;i++) {
if(0xff == rc_hexlookup[(int)cargv[0][i]]) {
}
}
- controlreply(np, "Mask Expires Set by Type Reason");
+ m = getrgmarker();
+ controlreply(np, GLINE_HEADER);
+ for(rp=rg_list;rp;rp=rp->next) {
+ if(id == rp->glineid) {
+ rp->marker = m;
+ if(rp->mask->length > longest)
+ longest = rp->mask->length;
+ }
+ }
+
for(rp=rg_list;rp;rp=rp->next)
- if(id == rp->glineid)
- rg_displaygline(np, rp);
+ if(rp->marker == m)
+ rg_displaygline(np, rp, longest);
controlreply(np, "Done.");
return CMD_OK;
int rg_glist(void *source, int cargc, char **cargv) {
nick *np = (nick *)source;
struct rg_struct *rp;
+ int longest = 0;
if(cargc) {
int erroroffset;
pcre *regex;
pcre_extra *hint;
const char *error;
-
+ unsigned int m;
+
if(!(regex = pcre_compile(cargv[0], RG_PCREFLAGS, &error, &erroroffset, NULL))) {
controlreply(np, "Error compiling expression %s at offset %d: %s", cargv[0], erroroffset, error);
return CMD_ERROR;
return CMD_ERROR;
}
}
-
+
+ m = getrgmarker();
rg_logevent(np, "regexglist", "%s", cargv[0]);
- controlreply(np, "Mask Expires Set by Type Reason");
+ controlreply(np, GLINE_HEADER);
+ for(rp=rg_list;rp;rp=rp->next) {
+ if(pcre_exec(regex, hint, rp->mask->content, rp->mask->length, 0, 0, NULL, 0) >= 0) {
+ rp->marker = m;
+ if(rp->mask->length > longest)
+ longest = rp->mask->length;
+ }
+ }
+
for(rp=rg_list;rp;rp=rp->next)
- if(pcre_exec(regex, hint, rp->mask->content, rp->mask->length, 0, 0, NULL, 0) >= 0)
- rg_displaygline(np, rp);
-
+ if(rp->marker == m)
+ rg_displaygline(np, rp, longest);
+
pcre_free(regex);
if(hint)
pcre_free(hint);
} else {
- rg_logevent(np, "regexglist", "");
- controlreply(np, "Mask Expires Set by Type Reason");
+ rg_logevent(np, "regexglist", "%s", "");
+ controlreply(np, GLINE_HEADER);
+ for(rp=rg_list;rp;rp=rp->next)
+ if(rp->mask->length > longest)
+ longest = rp->mask->length;
+
for(rp=rg_list;rp;rp=rp->next)
- rg_displaygline(np, rp);
+ rg_displaygline(np, rp, longest);
}
controlreply(np, "Done.");
return CMD_OK;
}
-void rg_displaygline(nick *np, struct rg_struct *rp) { /* could be a macro? I'll assume the C compiler inlines it */
- controlreply(np, "%-25s %-20s %-15s %-4d %s", rp->mask->content, longtoduration(rp->expires - time(NULL), 0), rp->setby->content, rp->type, rp->reason->content);
+char *displaytype(int type) {
+ char *ctype;
+ static char ctypebuf[10];
+
+ switch(type) {
+ case 1:
+ ctype = "igu";
+ break;
+ case 2:
+ ctype = "igh";
+ break;
+ case 3:
+ ctype = "ik";
+ break;
+ case 4:
+ ctype = "dgu";
+ break;
+ case 5:
+ ctype = "dgh";
+ break;
+ case 6:
+ ctype = "dk";
+ break;
+ default:
+ ctype = "??";
+ }
+
+ snprintf(ctypebuf, sizeof(ctype), "%1d:%s", type, ctype);
+ return ctypebuf;
+}
+
+char *getsep(int longest) {
+ static int lastlongest = -1;
+ static char lenbuf[1024];
+
+ longest = 125;
+/*
+ if(longest < 100)
+ longest = 100;
+
+ if(longest >= sizeof(lenbuf) - 20)
+ longest = sizeof(lenbuf) - 20;
+*/
+ longest+=4;
+ if(lastlongest == -1) {
+ int i;
+
+ for(i=0;i<sizeof(lenbuf)-1;i++)
+ lenbuf[i] = '-';
+ lenbuf[sizeof(lenbuf)-1] = '\0';
+ lastlongest = 0;
+ }
+
+ if(lastlongest != longest) {
+ lenbuf[lastlongest] = '-';
+ lenbuf[longest] = '\0';
+ lastlongest = longest;
+ }
+
+ return lenbuf;
+}
+
+void rg_displaygline(nick *np, struct rg_struct *rp, int longest) { /* could be a macro? I'll assume the C compiler inlines it */
+ char *sep = getsep(longest);
+/* 12345678 12345678901234567890 123456789012345 12345678 12345 12345678901234567890 1234567 1234567 123456
+ ID Expires Set by Class Type Last seen (ago) Hits(s) Hits Reason
+*/
+
+ char d[512];
+ time_t t = time(NULL);
+
+ if(rp->lastseen == 0) {
+ strlcpy(d, "(never)", sizeof(d));
+ } else {
+ strlcpy(d, longtoduration(t - rp->lastseen, 2), sizeof(d));
+ }
+
+ controlreply(np, "%s", rp->mask->content);
+ 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);
+ controlreply(np, "%s", sep);
}
int rg_spew(void *source, int cargc, char **cargv) {
return CMD_OK;
}
-int rg_sqlconnect(char *dbhost, char *dbuser, char *dbpass, char *db, unsigned int port) {
- mysql_init(&rg_sql);
- if(!mysql_real_connect(&rg_sql, dbhost, dbuser, dbpass, db, port, NULL, 0))
- return -1;
- return 0;
-}
-
-void rg_sqldisconnect(void) {
- mysql_close(&rg_sql);
-}
-
-void rg_sqlescape_string(char *dest, char *source, size_t length) {
- if(length >= RG_QUERY_BUF_SIZE)
- length = RG_QUERY_BUF_SIZE - 1;
-
- mysql_escape_string(dest, source, length);
-}
-
-int rg_sqlquery(char *format, ...) {
- char rg_sqlquery[RG_QUERY_BUF_SIZE];
- va_list va;
-
- va_start(va, format);
- vsnprintf(rg_sqlquery, sizeof(rg_sqlquery), format, va);
- va_end(va);
-
- return mysql_query(&rg_sql, rg_sqlquery);
-}
-
-rg_sqlresult rg_sqlstoreresult(void) {
- return mysql_store_result(&rg_sql);
-}
-
-rg_sqlrow rg_sqlgetrow(rg_sqlresult res) {
- return mysql_fetch_row(res);
-}
-
-void rg_sqlfree(rg_sqlresult res) {
- mysql_free_result(res);
-}
-
void rg_startup(void) {
int j, hostlen;
nick *np;
for(j=0;j<NICKHASHSIZE;j++) {
for(np=nicktable[j];np;np=np->next) {
+ if(ignorable_nick(np))
+ continue;
hostlen = RGBuildHostname(hostname, np);
for(rp=rg_list;rp;rp=rp->next) {
if(pcre_exec(rp->regex, rp->hint, hostname, hostlen, 0, 0, NULL, 0) >= 0) {
rp = (struct rg_struct*)malloc(sizeof(struct rg_struct));
if(rp) {
struct rg_struct *tp, *lp;
- rp->id = 0;
- rp->mask = NULL;
- rp->setby = NULL;
- rp->reason = NULL;
+
+ memset(rp, 0, sizeof(rg_struct));
rp->expires = expires;
- rp->type = 0;
- rp->regex = NULL;
- rp->hint = NULL;
for(lp=NULL,tp=rg_list;tp;lp=tp,tp=tp->next) {
if (expires <= tp->expires) { /* <= possible, slight speed increase */
return rp;
}
-struct rg_struct *rg_newsstruct(char *id, char *mask, char *setby, char *reason, char *expires, char *type, time_t iexpires, int iid) {
+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) {
struct rg_struct *newrow, *lp, *cp;
time_t rexpires;
char glineiddata[1024];
+ const char **p;
+
if (iexpires == 0) {
int qexpires;
if(!protectedatoi(expires, &qexpires))
const char *error;
int erroroffset;
+ for(p=classes;*p;p++) {
+ if(!strcasecmp(class, *p)) {
+ newrow->class = *p;
+ break;
+ }
+ }
+
+ if(!*p)
+ newrow->class = "unknown";
+
if(!(newrow->regex = pcre_compile(mask, RG_PCREFLAGS, &error, &erroroffset, NULL))) {
Error("regexgline", ERR_WARNING, "Error compiling expression %s at offset %d: %s", mask, erroroffset, error);
goto dispose;
}
}
- if (!iid) {
- if(!protectedatoi(id, &newrow->id))
- goto dispose2;
- } else {
- newrow->id = iid;
- }
-
+ newrow->id = id;
+ newrow->hitssaved = hitssaved;
+ newrow->lastseen = lastseen;
+
newrow->mask = getsstring(mask, RG_REGEXGLINE_MAX);
if(!newrow->mask) {
Error("regexgline", ERR_WARNING, "Error allocating memory for mask!");
return NULL;
}
-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?! */
+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?! */
char hostname[RG_MASKLEN];
int usercount = 0;
+ int validdelay;
rg_loggline(rp, np);
- if (rp->type == 2) {
+ if (rp->type == INSTANT_HOST_GLINE) {
usercount = np->host->clonecount;
- snprintf(hostname, sizeof(hostname), "*@%s", IPtostr(np->ipaddress));
+ snprintf(hostname, sizeof(hostname), "*@%s", IPtostr(np->p_ipaddr));
}
- if ((rp->type == 1) || (usercount > rg_max_per_gline)) {
+ if ((rp->type == INSTANT_IDENT_GLINE) || (usercount > rg_max_per_gline)) {
nick *tnp;
for(usercount=0,tnp=np->host->nicks;tnp;tnp=tnp->nextbyhost)
if(!ircd_strcmp(np->ident, tnp->ident))
usercount++;
- snprintf(hostname, sizeof(hostname), "%s@%s", np->ident, IPtostr(np->ipaddress));
+ snprintf(hostname, sizeof(hostname), "%s@%s", np->ident, IPtostr(np->p_ipaddr));
}
- if ((rp->type >= 3) || (usercount > rg_max_per_gline)) {
+ if(!strcmp(rp->class, RESERVED_NICK_CLASS))
+ 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);
+
+ validdelay = (rp->type == INSTANT_KILL) || (rp->type == DELAYED_IDENT_GLINE) || (rp->type == DELAYED_HOST_GLINE) || (rp->type == DELAYED_KILL);
+ if (validdelay || (usercount > rg_max_per_gline)) {
struct rg_glinenode *nn = (struct rg_glinenode *)malloc(sizeof(struct rg_glinenode));
if(nn) {
nn->next = NULL;
gll->start = nn;
gll->end = nn;
}
+
nn->np = np;
nn->reason = rp;
-
- if (rp->type <= 3) {
- nn->punish = 3;
+ if(!validdelay) {
+ nn->punish = INSTANT_KILL;
} else {
nn->punish = rp->type;
}
}
- return;
+ return usercount;
}
- if ((rp->type <= 0) || (rp->type >= 3))
- return;
+ if (rp->type == INSTANT_IDENT_GLINE) {
+ 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":"");
+ } else if(rp->type == INSTANT_HOST_GLINE) {
+ 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":"");
+ } else {
+ return 0;
+ }
- if (rp->type == 1) {
- if (IsAccount(np)) {
- 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":"");
- } else {
- 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":"");
- }
+ rg_shadowserver(np, rp, rp->type);
+ 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);
+ return usercount;
+}
+
+static char *gvhost(nick *np) {
+ static char buf[NICKLEN+1+USERLEN+1+HOSTLEN+1+ACCOUNTLEN+4+REALLEN+1+10];
+
+ if(IsAccount(np)) {
+ snprintf(buf, sizeof(buf), "%s!%s@%s/%s r(%s)", np->nick, np->ident, np->host->name->content, np->authname, np->realname->name->content);
} else {
- if (IsAccount(np)) {
- 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":"");
- } else {
- 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":"");
+ snprintf(buf, sizeof(buf), "%s!%s@%s r(%s)", np->nick, np->ident, np->host->name->content, np->realname->name->content);
+ }
+
+ return buf;
+}
+
+static int floodprotection = 0;
+static int lastfloodspam = 0;
+
+void rg_dogline(struct rg_glinelist *gll, nick *np, struct rg_struct *rp, char *matched) {
+ int t = time(NULL);
+
+ if(t > floodprotection) {
+ floodprotection = t;
+ } else if((floodprotection - t) / 8 > RG_NETWORK_WIDE_MAX_GLINES_PER_8_SEC) {
+ if(t > lastfloodspam + 3600) {
+ channel *cp = findchannel("#twilightzone");
+ if(cp)
+ 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);
+ controlwall(NO_OPER, NL_MANAGEMENT, "WARNING! REGEXGLINE DISABLED FOR AN HOUR DUE TO NETWORK WIDE LOOKING GLINE!");
+ lastfloodspam = t;
+ floodprotection = t + RG_NETWORK_WIDE_MAX_GLINES_PER_8_SEC * 3600 * 8;
}
+ return;
}
-
- irc_send("%s GL * +%s %d :%s (ID: %08lx)\r\n", mynumeric->content, hostname, rg_expiry_time, rp->reason->content, rp->glineid);
+
+ floodprotection+=__rg_dogline(gll, np, rp, matched);
}
void rg_logevent(nick *np, char *event, char *details, ...) {
char eeevent[RG_QUERY_BUF_SIZE], eedetails[RG_QUERY_BUF_SIZE], eemask[RG_QUERY_BUF_SIZE], eeaccount[RG_QUERY_BUF_SIZE];
char buf[513], account[ACCOUNTLEN + 1], mask[RG_MASKLEN];
int masklen;
-
+
va_list va;
-
- va_start(va, details);
- vsnprintf(buf, sizeof(buf), details, va);
- va_end(va);
-
+
+ if(details) {
+ va_start(va, details);
+ vsnprintf(buf, sizeof(buf), details, va);
+ va_end(va);
+ } else {
+ buf[0] = '\0';
+ }
+
if(np) {
if (IsAccount(np)) {
strncpy(account, np->authname, sizeof(account) - 1);
masklen = 0;
}
- rg_sqlescape_string(eeevent, event, strlen(event));
- rg_sqlescape_string(eedetails, buf, strlen(buf));
- rg_sqlescape_string(eeaccount, event, strlen(account));
- rg_sqlescape_string(eemask, mask, masklen);
+ dbescapestring(eeevent, event, strlen(event));
+ dbescapestring(eedetails, buf, strlen(buf));
+ dbescapestring(eeaccount, account, strlen(account));
+ dbescapestring(eemask, mask, masklen);
- rg_sqlquery("INSERT INTO regexlogs (host, account, event, arg) VALUES ('%s', '%s', '%s', '%s')", eemask, eeaccount, eeevent, eedetails);
+ dbquery("INSERT INTO regexgline.clog (host, account, event, arg, ts) VALUES ('%s', '%s', '%s', '%s', NOW())", eemask, eeaccount, eeevent, eedetails);
}
void rg_loggline(struct rg_struct *rg, nick *np) {
char eenick[RG_QUERY_BUF_SIZE], eeuser[RG_QUERY_BUF_SIZE], eehost[RG_QUERY_BUF_SIZE], eereal[RG_QUERY_BUF_SIZE];
- rg_sqlescape_string(eenick, np->nick, strlen(np->nick));
- rg_sqlescape_string(eeuser, np->ident, strlen(np->ident));
- rg_sqlescape_string(eehost, np->host->name->content, strlen(np->host->name->content));
- rg_sqlescape_string(eereal, np->realname->name->content, strlen(np->realname->name->content));
+ rg->hits++;
+ rg->hitssaved++;
+ rg->lastseen = time(NULL);
+ rg->dirty = 1;
+
+ /* @paul: disabled */
+
+ return;
+ dbescapestring(eenick, np->nick, strlen(np->nick));
+ dbescapestring(eeuser, np->ident, strlen(np->ident));
+ dbescapestring(eehost, np->host->name->content, strlen(np->host->name->content));
+ dbescapestring(eereal, np->realname->name->content, strlen(np->realname->name->content));
- rg_sqlquery("INSERT INTO regexglinelog (glineid, nickname, username, hostname, realname) VALUES (%d, '%s', '%s', '%s', '%s')", rg->id, eenick, eeuser, eehost, eereal);
+ dbquery("INSERT INTO regexgline.glog (glineid, nickname, username, hostname, realname, ts) VALUES (%d, '%s', '%s', '%s', '%s', NOW())", rg->id, eenick, eeuser, eehost, eereal);
}
+
+static unsigned int getrgmarker(void) {
+ static unsigned int marker = 0;
+
+ marker++;
+ if(!marker) {
+ struct rg_struct *l;
+
+ /* If we wrapped to zero, zap the marker on all hosts */
+ for(l=rg_list;l;l=l->next)
+ l->marker=0;
+ marker++;
+ }
+
+ return marker;
+}
+
+void rg_flush_schedule(void *arg) {
+ struct rg_struct *l;
+
+ for(l=rg_list;l;l=l->next) {
+ if(!l->dirty)
+ continue;
+
+ dbquery("UPDATE regexgline.glines SET lastseen = %jd, hits = %lu WHERE id = %d", (intmax_t)l->lastseen, l->hitssaved, l->id);
+
+ l->dirty = 0;
+ }
+}
+