diff --git a/src/ckdb.c b/src/ckdb.c index ecff6d53..6d982f59 100644 --- a/src/ckdb.c +++ b/src/ckdb.c @@ -1216,7 +1216,7 @@ static void alloc_storage() ips_free = k_new_list("IPs", sizeof(IPS), ALLOC_IPS, LIMIT_IPS, true); ips_store = k_new_store(ips_free); ips_root = new_ktree(NULL, cmp_ips, ips_free); - ips_add(IPS_GROUP_OK, "127.0.0.1", "local", false, true, 0); + ips_add(IPS_GROUP_OK, "127.0.0.1", "local", false, true, 0, false); events_free = k_new_list("Events", sizeof(EVENTS), ALLOC_EVENTS, LIMIT_EVENTS, true); @@ -1307,9 +1307,14 @@ static void alloc_storage() DLPRIO(userinfo, 50); - // Needs to check users and ips and uses limits + // Uses event_limits + DLPRIO(optioncontrol, 48); + + // Needs to check users and ips and uses events_limits DLPRIO(events, 47); + // events_limits 46 (events-1) above users + DLPRIO(auths, 44); DLPRIO(users, 43); DLPRIO(useratts, 42); @@ -1327,9 +1332,6 @@ static void alloc_storage() DLPRIO(poolstats, 11); DLPRIO(userstats, 10); - // Uses limits lock for events_limits - DLPRIO(optioncontrol, 5); - // Don't currently nest any locks in these: DLPRIO(workers, PRIO_TERMINAL); DLPRIO(idcontrol, PRIO_TERMINAL); @@ -4393,6 +4395,7 @@ static void *socketer(__maybe_unused void *arg) case CMD_SHSTA: case CMD_USERINFO: case CMD_LOCKS: + case CMD_EVENTS: msgline->sockd = sockd; sockd = -1; K_WLOCK(workqueue_free); @@ -4651,6 +4654,7 @@ static void reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) case CMD_BTCSET: case CMD_QUERY: case CMD_LOCKS: + case CMD_EVENTS: LOGERR("%s() INVALID message line %"PRIu64 " ignored '%.42s...", __func__, count, @@ -5810,6 +5814,7 @@ int main(int argc, char **argv) char buf[512]; ckpool_t ckp; int c, ret, i = 0, j; + size_t len; char *kill; tv_t now; @@ -5824,6 +5829,11 @@ int main(int argc, char **argv) while ((c = getopt_long(argc, argv, "a:c:d:ghi:kl:mM:n:p:P:r:R:s:S:t:u:U:vw:yY:", long_options, &i)) != -1) { switch(c) { case 'a': + len = strlen(optarg); + if (len > MAX_ALERT_CMD) + quit(1, "ckdb_alert_cmd (%d) too large," + " limit %d", + (int)len, MAX_ALERT_CMD); ckdb_alert_cmd = strdup(optarg); break; case 'c': @@ -6057,7 +6067,7 @@ int main(int argc, char **argv) #if LOCK_CHECK DLPRIO(process_pplns, 99); DLPRIO(workers_db, 98); - DLPRIO(event_limits, PRIO_TERMINAL); + DLPRIO(event_limits, 46); // events-1 #endif if (confirm_sharesummary) { diff --git a/src/ckdb.h b/src/ckdb.h index 54b1fcdc..987dbf25 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -51,7 +51,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.4" -#define CKDB_VERSION DB_VERSION"-1.950" +#define CKDB_VERSION DB_VERSION"-1.951" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -353,6 +353,9 @@ extern cklock_t last_lock; #define STR_SHAREERRORS "shareerror" #define STR_AGEWORKINFO "ageworkinfo" +// Fixed size required - increase if you need larger +#define MAX_ALERT_CMD 255 +// Access using event_limits_free lock extern char *ckdb_alert_cmd; extern char *btc_server; @@ -676,6 +679,7 @@ enum cmd_values { CMD_BTCSET, CMD_QUERY, CMD_LOCKS, + CMD_EVENTS, CMD_END }; @@ -2981,7 +2985,8 @@ extern bool payouts_add(PGconn *conn, bool add, K_ITEM *p_item, extern K_ITEM *payouts_full_expire(PGconn *conn, int64_t payoutid, tv_t *now, bool lock); extern bool payouts_fill(PGconn *conn); -extern void ips_add(char *group, char *ip, char *des, bool log, bool cclass, int life); +extern void ips_add(char *group, char *ip, char *des, bool log, bool cclass, + int life, bool locked); extern int _events_add(int id, char *by, char *inet, tv_t *cd, K_TREE *trf_root); #define events_add(_id, _trf_root) _events_add(_id, NULL, NULL, NULL, _trf_root) extern bool auths_add(PGconn *conn, char *poolinstance, char *username, diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index a11595dc..7545635a 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -7484,6 +7484,289 @@ static char *cmd_locks(__maybe_unused PGconn *conn, char *cmd, char *id, return strdup(reply); } +static void event_tree(K_TREE *event_tree, char *list, char *reply, size_t siz, + char *buf, size_t *off, size_t *len, int *rows) +{ + K_TREE_CTX ctx[1]; + K_ITEM *e_item; + EVENTS *e; + + e_item = first_in_ktree(event_tree, ctx); + while (e_item) { + DATA_EVENTS(e, e_item); + if (CURRENT(&(e->expirydate))) { + snprintf(reply, siz, "list:%d=%s%c", + *rows, list, FLDSEP); + APPEND_REALLOC(buf, *off, *len, reply); + + snprintf(reply, siz, "id:%d=%d%c", + *rows, e->id, FLDSEP); + APPEND_REALLOC(buf, *off, *len, reply); + + snprintf(reply, siz, "user:%d=%s%c", + *rows, e->createby, FLDSEP); + APPEND_REALLOC(buf, *off, *len, reply); + + if (event_tree == events_ipc_root) { + snprintf(reply, siz, "ipc:%d=%s%c", + *rows, e->ipc, FLDSEP); + APPEND_REALLOC(buf, *off, *len, reply); + } else { + snprintf(reply, siz, "ip:%d=%s%c", + *rows, e->createinet, FLDSEP); + APPEND_REALLOC(buf, *off, *len, reply); + } + + if (event_tree == events_hash_root) { + snprintf(reply, siz, "hash:%d=%.8s%c", + *rows, e->hash, FLDSEP); + APPEND_REALLOC(buf, *off, *len, reply); + } + + snprintf(reply, siz, CDTRF":%d=%ld%c", + (*rows)++, e->createdate.tv_sec, FLDSEP); + APPEND_REALLOC(buf, *off, *len, reply); + } + e_item = next_in_ktree(ctx); + } +} + +// Events status/settings +static char *cmd_events(__maybe_unused PGconn *conn, char *cmd, char *id, + __maybe_unused tv_t *now, __maybe_unused char *by, + __maybe_unused char *code, __maybe_unused char *inet, + __maybe_unused tv_t *cd, K_TREE *trf_root) +{ + K_ITEM *i_action, *i_cmd, *i_list, *i_ip, *i_lifetime, *i_des, *i_item; + K_TREE_CTX ctx[1]; + IPS *ips; + char *action, *alert_cmd, *list, *ip, *des; + char reply[1024] = ""; + size_t siz = sizeof(reply); + char tmp[1024] = ""; + char *buf = NULL; + size_t len, off; + int i, rows, oldlife, lifetime; + + LOGDEBUG("%s(): cmd '%s'", __func__, cmd); + + i_action = require_name(trf_root, "action", 1, NULL, reply, siz); + if (!i_action) + return strdup(reply); + action = transfer_data(i_action); + + if (strcasecmp(action, "cmd") == 0) { + /* Change ckdb_alert_cmd to 'cmd' + * blank to disable it */ + i_cmd = require_name(trf_root, "cmd", 0, NULL, reply, siz); + if (!i_cmd) + return strdup(reply); + alert_cmd = transfer_data(i_cmd); + if (strlen(alert_cmd) > MAX_ALERT_CMD) + return strdup("Invalid cmd length - limit " STRINT(MAX_ALERT_CMD)); + K_WLOCK(event_limits_free); + FREENULL(ckdb_alert_cmd); + if (*alert_cmd) + ckdb_alert_cmd = strdup(alert_cmd); + K_WUNLOCK(event_limits_free); + APPEND_REALLOC_INIT(buf, off, len); + if (*alert_cmd) + APPEND_REALLOC(buf, off, len, "ok.cmd set"); + else + APPEND_REALLOC(buf, off, len, "ok.cmd disabled"); + } else if (strcasecmp(action, "settings") == 0) { + // Return all current event settings + APPEND_REALLOC_INIT(buf, off, len); + APPEND_REALLOC(buf, off, len, "ok."); + K_RLOCK(event_limits_free); + i = -1; + while (e_limits[++i].name) { + +#define EVENTFLD(_fld) do { \ + snprintf(tmp, sizeof(tmp), "%s_" #_fld "=%d%c", \ + e_limits[i].name, e_limits[i]._fld, FLDSEP); \ + APPEND_REALLOC(buf, off, len, tmp); \ + } while (0) + + EVENTFLD(user_low_time); + EVENTFLD(user_low_time_limit); + EVENTFLD(user_hi_time); + EVENTFLD(user_hi_time_limit); + EVENTFLD(ip_low_time); + EVENTFLD(ip_low_time_limit); + EVENTFLD(ip_hi_time); + EVENTFLD(ip_hi_time_limit); + EVENTFLD(lifetime); + } + snprintf(tmp, sizeof(tmp), "event_limits_hash_lifetime=%d", + event_limits_hash_lifetime); + APPEND_REALLOC(buf, off, len, tmp); + K_RUNLOCK(event_limits_free); + } else if (strcasecmp(action, "events") == 0) { + /* List the event tree contents + * List is 'all' or one of: hash, user, ip or ipc <- tree names + * Output can be large - check web Admin->ckp for tree sizes */ + bool all, one = false; + i_list = require_name(trf_root, "list", 1, NULL, reply, siz); + if (!i_list) + return strdup(reply); + list = transfer_data(i_list); + APPEND_REALLOC_INIT(buf, off, len); + APPEND_REALLOC(buf, off, len, "ok."); + rows = 0; + all = (strcmp(list, "all") == 0); + K_RLOCK(events_free); + if (all || strcmp(list, "user") == 0) { + one = true; + event_tree(events_user_root, "user", reply, siz, buf, + &off, &len, &rows); + } + if (all || strcmp(list, "ip") == 0) { + one = true; + event_tree(events_ip_root, "ip", reply, siz, buf, + &off, &len, &rows); + } + if (all || strcmp(list, "ipc") == 0) { + one = true; + event_tree(events_ipc_root, "ipc", reply, siz, buf, + &off, &len, &rows); + } + if (all || strcmp(list, "hash") == 0) { + one = true; + event_tree(events_hash_root, "hash", reply, siz, buf, + &off, &len, &rows); + } + K_RUNLOCK(events_free); + if (!one) { + free(buf); + snprintf(reply, siz, "unknown stats list '%s'", list); + LOGERR("%s() %s.%s", __func__, id, reply); + return strdup(reply); + } + snprintf(tmp, sizeof(tmp), "rows=%d", rows); + APPEND_REALLOC(buf, off, len, tmp); + } else if (strcasecmp(action, "ips") == 0) { + // List the ips tree contents + APPEND_REALLOC_INIT(buf, off, len); + APPEND_REALLOC(buf, off, len, "ok."); + rows = 0; + K_RLOCK(ips_free); + i_item = first_in_ktree(ips_root, ctx); + while (i_item) { + DATA_IPS(ips, i_item); + if (CURRENT(&(ips->expirydate))) { + snprintf(tmp, sizeof(tmp), "group:%d=%s%c", + rows, ips->group, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "ip:%d=%s%c", + rows, ips->ip, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "description:%d=%s%c", + rows, ips->description ? : EMPTY, + FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "lifetime:%d=%d%c", + rows, ips->lifetime, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "log:%d=%c%c", + rows, ips->log ? TRUE_CHR : FALSE_CHR, + FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(reply, siz, CDTRF":%d=%ld%c", + rows++, ips->createdate.tv_sec, + FLDSEP); + APPEND_REALLOC(buf, off, len, reply); + } + i_item = next_in_ktree(ctx); + } + K_RUNLOCK(ips_free); + snprintf(tmp, sizeof(tmp), "rows=%d", rows); + APPEND_REALLOC(buf, off, len, tmp); + } else if (strcasecmp(action, "ban") == 0) { + // Ban the ip with optional lifetime + bool found = false; + oldlife = 0; + i_ip = require_name(trf_root, "ip", 1, NULL, reply, siz); + if (!i_ip) + return strdup(reply); + ip = transfer_data(i_ip); + i_lifetime = optional_name(trf_root, "lifetime", 1, + (char *)intpatt, reply, siz); + if (i_lifetime) + lifetime = atoi(transfer_data(i_lifetime)); + else { + if (*reply) + return strdup(reply); + // default to almost 42 years :) + lifetime = 60*60*24*365*42; + } + i_des = optional_name(trf_root, "des", 1, NULL, reply, siz); + if (i_des) + des = transfer_data(i_des); + else { + if (*reply) + return strdup(reply); + des = NULL; + } + K_WLOCK(ips_free); + i_item = find_ips(IPS_GROUP_BAN, ip); + if (i_item) { + found = true; + DATA_IPS(ips, i_item); + oldlife = ips->lifetime; + ips->lifetime = lifetime; + // Don't change it if it's not supplied + if (des) { + LIST_MEM_SUB(ips_free, ips->description); + FREENULL(ips->description); + ips->description = strdup(des); + LIST_MEM_ADD(ips_free, ips->description); + } + } else { + ips_add(IPS_GROUP_BAN, ip, des, true, false, + lifetime, true); + } + K_WUNLOCK(ips_free); + APPEND_REALLOC_INIT(buf, off, len); + APPEND_REALLOC(buf, off, len, "ok."); + if (found) { + snprintf(tmp, sizeof(tmp), "already %s %d->%d", + ip, oldlife, lifetime); + } else + snprintf(tmp, sizeof(tmp), "ban %s %d", ip, lifetime); + APPEND_REALLOC(buf, off, len, tmp); + } else if (strcasecmp(action, "unban") == 0) { + /* Unban the ip - sets lifetime to 1 meaning + * it expires 1s after it was created */ + bool found = false; + i_ip = require_name(trf_root, "ip", 1, NULL, reply, siz); + if (!i_ip) + return strdup(reply); + ip = transfer_data(i_ip); + K_WLOCK(ips_free); + i_item = find_ips(IPS_GROUP_BAN, ip); + if (i_item) { + found = true; + DATA_IPS(ips, i_item); + ips->lifetime = 1; + } + K_WUNLOCK(ips_free); + APPEND_REALLOC_INIT(buf, off, len); + if (found) { + APPEND_REALLOC(buf, off, len, "ok."); + APPEND_REALLOC(buf, off, len, ip); + APPEND_REALLOC(buf, off, len, " unbanned"); + } else + APPEND_REALLOC(buf, off, len, "ERR.unknown ip"); + } else { + snprintf(reply, siz, "unknown action '%s'", action); + LOGERR("%s() %s.%s", __func__, id, reply); + return strdup(reply); + } + + return buf; +} + /* The socket command format is as follows: * Basic structure: * cmd.ID.fld1=value1 FLDSEP fld2=value2 FLDSEP fld3=... @@ -7594,5 +7877,6 @@ struct CMDS ckdb_cmds[] = { { CMD_BTCSET, "btcset", false, false, cmd_btcset, SEQ_NONE, ACCESS_SYSTEM }, { CMD_QUERY, "query", false, false, cmd_query, SEQ_NONE, ACCESS_SYSTEM }, { CMD_LOCKS, "locks", false, false, cmd_locks, SEQ_NONE, ACCESS_SYSTEM }, + { CMD_EVENTS, "events", false, false, cmd_events, SEQ_NONE, ACCESS_SYSTEM }, { CMD_END, NULL, false, false, NULL, SEQ_NONE, 0 } }; diff --git a/src/ckdb_data.c b/src/ckdb_data.c index f891caea..b5d61064 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -4507,14 +4507,21 @@ int check_events(EVENTS *events) K_ITEM *i_item, *e_item = NULL, *tmp_item, *u_item; K_TREE_CTX ctx[1]; EVENTS *e = NULL; + char cmd[MAX_ALERT_CMD+1]; int count, secs; int tyme, limit, lifetime; char name[TXT_SML+1]; pid_t pid; tv_t now; + K_RLOCK(event_limits_free); + if (ckdb_alert_cmd) + STRNCPY(cmd, ckdb_alert_cmd); + else + cmd[0] = '\0'; + K_RUNLOCK(event_limits_free); // No way to send an alert, so don't test - if (!ckdb_alert_cmd) + if (!cmd[0]) return EVENT_OK; K_RLOCK(ips_free); @@ -4757,7 +4764,7 @@ int check_events(EVENTS *events) cause_str(cause)); FREENULL(st); ips_add(IPS_GROUP_BAN, events->createinet, - (char *)cause_str(cause), true, false, lifetime); + (char *)cause_str(cause), true, false, lifetime, false); pid = fork(); if (pid < 0) { LOGERR("%s() ALERT failed to fork (%d)", @@ -4770,9 +4777,9 @@ int check_events(EVENTS *events) snprintf(buf3, sizeof(buf3), "%d", tyme); snprintf(buf4, sizeof(buf4), "%d", lifetime); st = safe_text_nonull(events->createby); - execl(ckdb_alert_cmd, ckdb_alert_cmd, buf1, name, - buf2, buf3, buf4, events->createinet, - st, cause_str(cause), NULL); + execl(cmd, cmd, buf1, name, buf2, buf3, buf4, + events->createinet, st, + cause_str(cause), NULL); LOGERR("%s() ALERT fork failed to execute (%d)", __func__, errno); FREENULL(st); diff --git a/src/ckdb_dbio.c b/src/ckdb_dbio.c index d9bc179e..b7b77695 100644 --- a/src/ckdb_dbio.c +++ b/src/ckdb_dbio.c @@ -6253,7 +6253,8 @@ bool payouts_fill(PGconn *conn) return ok; } -void ips_add(char *group, char *ip, char *des, bool log, bool cclass, int life) +void ips_add(char *group, char *ip, char *des, bool log, bool cclass, int life, + bool locked) { K_ITEM *i_item, *i2_item; IPS *ips, *ips2; @@ -6262,7 +6263,8 @@ void ips_add(char *group, char *ip, char *des, bool log, bool cclass, int life) bool ok; setnow(&now); - K_WLOCK(ips_free); + if (!locked) + K_WLOCK(ips_free); i_item = k_unlink_head(ips_free); DATA_IPS(ips, i_item); STRNCPY(ips->group, group); @@ -6304,7 +6306,8 @@ void ips_add(char *group, char *ip, char *des, bool log, bool cclass, int life) } else k_add_head(ips_free, i2_item); } - K_WUNLOCK(ips_free); + if (!locked) + K_WUNLOCK(ips_free); } // trf_root overrides by,inet,cd fields