diff --git a/src/ckdb.c b/src/ckdb.c index dda1ced3..ecff6d53 100644 --- a/src/ckdb.c +++ b/src/ckdb.c @@ -492,33 +492,40 @@ K_TREE *events_ipc_root; K_TREE *events_hash_root; K_LIST *events_free; K_STORE *events_store; +// Emulate a list for lock checking +K_LIST *event_limits_free; +/* N.B. these limits are not production quality + * They'll block anyone who makes a mistake 2 or 3 times :) + * Use optioncontrol OC_LIMITS to set/store them in the database */ EVENT_LIMITS e_limits[] = { - { EVENTID_PASSFAIL, 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, + { EVENTID_PASSFAIL, "PASSFAIL", 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, // It's only possible to create an address account once, so user_lo/hi can never trigger - { EVENTID_CREADDR, 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, + { EVENTID_CREADDR, "CREADDR", 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, // It's only possible to create an account once, so user_lo/hi can never trigger - { EVENTID_CREACC, 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, + { EVENTID_CREACC, "CREACC", 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, // page_api.php with an invalid username - { EVENTID_UNKATTS, 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, + { EVENTID_UNKATTS, "UNKATTS", 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, // 2fa missing/invalid format - { EVENTID_INV2FA, 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, + { EVENTID_INV2FA, "INV2FA", 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, // Wrong 2fa value - { EVENTID_WRONG2FA, 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, + { EVENTID_WRONG2FA, "WRONG2FA", 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, // Invalid address according to btcd - { EVENTID_INVBTC, 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, + { EVENTID_INVBTC, "INVBTC", 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, // Incorrect format/length address - { EVENTID_INCBTC, 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, + { EVENTID_INCBTC, "INCBTC", 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, // Address belongs to some other account - { EVENTID_BTCUSED, 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, + { EVENTID_BTCUSED, "BTCUSED", 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, // It's only possible to create an account once, so user_lo/hi can never trigger - { EVENTID_AUTOACC, 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, + { EVENTID_AUTOACC, "AUTOACC", 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, // Invalid user on auth, CKPool will throttle these - { EVENTID_INVAUTH, 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, + { EVENTID_INVAUTH, "INVAUTH", 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, // Invalid user on chkpass - { EVENTID_INVUSER, 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 } + { EVENTID_INVUSER, "INVUSER", 60, 1, 2*60, 2, 60, 1, 2*60, 2, 24*60*60 }, + // Terminated by NULL name + { -1, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; - +// All access to above and below limits requires the event_limits_free lock int event_limits_hash_lifetime = 24*60*60; // AUTHS authorise.id.json={...} @@ -1300,7 +1307,7 @@ static void alloc_storage() DLPRIO(userinfo, 50); - // Needs to check users and ips + // Needs to check users and ips and uses limits DLPRIO(events, 47); DLPRIO(auths, 44); @@ -1320,10 +1327,12 @@ 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); - DLPRIO(optioncontrol, PRIO_TERMINAL); DLPRIO(paymentaddresses, PRIO_TERMINAL); DLPRIO(ips, PRIO_TERMINAL); @@ -4274,7 +4283,7 @@ static void *socketer(__maybe_unused void *arg) "%s.%ld.failed.ERR", msgline->id, now.tv_sec); - tmp = reply_event(EVENTID_MAX, reply); + tmp = reply_event(EVENTID_NONE, reply); send_unix_msg(sockd, tmp); FREENULL(tmp); break; @@ -6043,10 +6052,12 @@ int main(int argc, char **argv) // Emulate a list for lock checking process_pplns_free = k_lock_only_list("ProcessPPLNS"); workers_db_free = k_lock_only_list("WorkersDB"); + event_limits_free = k_lock_only_list("EventLimits"); #if LOCK_CHECK DLPRIO(process_pplns, 99); DLPRIO(workers_db, 98); + DLPRIO(event_limits, PRIO_TERMINAL); #endif if (confirm_sharesummary) { diff --git a/src/ckdb.h b/src/ckdb.h index 827b99ea..54b1fcdc 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.940" +#define CKDB_VERSION DB_VERSION"-1.950" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -1599,6 +1599,12 @@ extern K_TREE *optioncontrol_root; extern K_LIST *optioncontrol_free; extern K_STORE *optioncontrol_store; +typedef struct oc_trigger { + char *match; + bool exact; + void (*func)(OPTIONCONTROL *, const char *); +} OC_TRIGGER; + // TODO: discarding workinfo,shares // WORKINFO workinfo.id.json={...} typedef struct workinfo { @@ -1997,6 +2003,10 @@ extern K_TREE *events_ipc_root; extern K_TREE *events_hash_root; extern K_LIST *events_free; extern K_STORE *events_store; +// Emulate a list for lock checking +extern K_LIST *event_limits_free; + +#define OC_LIMITS "event_limits_" // Any password failure #define EVENTID_PASSFAIL 0 @@ -2022,7 +2032,7 @@ extern K_STORE *events_store; #define EVENTID_INVAUTH 10 // Unknown chkpass username #define EVENTID_INVUSER 11 -#define EVENTID_MAX 11 +#define EVENTID_NONE 12 #define EVENT_OK -1 @@ -2032,6 +2042,8 @@ extern K_STORE *events_store; * i.e. a single user will not fail the test result for ip */ typedef struct event_limits { int id; + // optioncontrol/display name + char *name; int user_low_time; // how many in above limit = ok (+1 = alert) int user_low_time_limit; diff --git a/src/ckdb_data.c b/src/ckdb_data.c index 8c90d4b0..f891caea 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -4509,6 +4509,7 @@ int check_events(EVENTS *events) EVENTS *e = NULL; int count, secs; int tyme, limit, lifetime; + char name[TXT_SML+1]; pid_t pid; tv_t now; @@ -4527,6 +4528,7 @@ int check_events(EVENTS *events) // All tests below always run all full checks to clean up old events setnow(&now); K_WLOCK(events_free); + K_RLOCK(event_limits_free); // Check hash - same hash passfail on more than one valid User if (events->id == EVENTID_PASSFAIL && *(events->hash)) { e_item = last_events_hash(events->id, events->hash, ctx); @@ -4573,6 +4575,7 @@ int check_events(EVENTS *events) tyme = 0; limit = 1; lifetime = event_limits_hash_lifetime; + STRNCPY(name, "HASH"); } } } @@ -4613,6 +4616,7 @@ int check_events(EVENTS *events) tyme = e_limits[events->id].user_low_time; limit = e_limits[events->id].user_low_time_limit; lifetime = e_limits[events->id].lifetime; + STRNCPY(name, e_limits[events->id].name); } if (alert == false && secs <= e_limits[events->id].user_hi_time && @@ -4622,6 +4626,7 @@ int check_events(EVENTS *events) tyme = e_limits[events->id].user_hi_time; limit = e_limits[events->id].user_hi_time_limit; lifetime = e_limits[events->id].lifetime; + STRNCPY(name, e_limits[events->id].name); } e_item = prev_in_ktree(ctx); } @@ -4664,6 +4669,7 @@ int check_events(EVENTS *events) tyme = e_limits[events->id].ip_low_time; limit = e_limits[events->id].ip_low_time_limit; lifetime = e_limits[events->id].lifetime; + STRNCPY(name, e_limits[events->id].name); } if (alert == false && secs <= e_limits[events->id].ip_hi_time && @@ -4673,6 +4679,7 @@ int check_events(EVENTS *events) tyme = e_limits[events->id].ip_hi_time; limit = e_limits[events->id].ip_hi_time_limit; lifetime = e_limits[events->id].lifetime; + STRNCPY(name, e_limits[events->id].name); } e_item = prev_in_ktree(ctx); } @@ -4719,6 +4726,7 @@ int check_events(EVENTS *events) tyme = e_limits[events->id].ip_low_time; limit = e_limits[events->id].ip_low_time_limit; lifetime = e_limits[events->id].lifetime; + STRNCPY(name, e_limits[events->id].name); } if (alert == false && secs <= e_limits[events->id].ip_hi_time && @@ -4728,6 +4736,7 @@ int check_events(EVENTS *events) tyme = e_limits[events->id].ip_hi_time; limit = e_limits[events->id].ip_hi_time_limit; lifetime = e_limits[events->id].lifetime; + STRNCPY(name, e_limits[events->id].name); } e_item = prev_in_ktree(ctx); } @@ -4737,11 +4746,12 @@ int check_events(EVENTS *events) user2 == false) alert = false; } + K_RUNLOCK(event_limits_free); K_WUNLOCK(events_free); if (alert) { - LOGERR("%s() ALERT ID:%d Lim:%d Time:%d Life:%d %s '%s' '%s'", + LOGERR("%s() ALERT ID:%d %s Lim:%d Time:%d Life:%d %s '%s' '%s'", __func__, - events->id, limit, tyme, lifetime, + events->id, name, limit, tyme, lifetime, events->createinet, st = safe_text_nonull(events->createby), cause_str(cause)); @@ -4760,7 +4770,7 @@ 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, + execl(ckdb_alert_cmd, ckdb_alert_cmd, buf1, name, buf2, buf3, buf4, events->createinet, st, cause_str(cause), NULL); LOGERR("%s() ALERT fork failed to execute (%d)", diff --git a/src/ckdb_dbio.c b/src/ckdb_dbio.c index 9c827785..d9bc179e 100644 --- a/src/ckdb_dbio.c +++ b/src/ckdb_dbio.c @@ -2438,6 +2438,137 @@ foil: return ok; } +void oc_switch_state(OPTIONCONTROL *oc, const char *from) +{ + switch_state = atoi(oc->optionvalue); + LOGWARNING("%s(%s) set switch_state to %d", + from, __func__, switch_state); +} + +void oc_diff_percent(OPTIONCONTROL *oc, __maybe_unused const char *from) +{ + diff_percent = DIFF_VAL(strtod(oc->optionvalue, NULL)); + if (errno == ERANGE) + diff_percent = DIFF_VAL(DIFF_PERCENT_DEFAULT); +} + +/* An event limit setting looks like: + * OC_LIMITS + events_limits.name + '_' + Item + * Item is one of the field names in event_limits + * e.g. user_low_time, user_low_time_limit etc + * as below in the if tests */ +void oc_event_limits(OPTIONCONTROL *oc, const char *from) +{ + bool processed = false; + size_t len; + char *ptr, *ptr2; + int val, i; + + K_WLOCK(event_limits_free); + val = atoi(oc->optionvalue); + ptr = oc->optionname + strlen(OC_LIMITS); + i = -1; + while (e_limits[++i].name) { + len = strlen(e_limits[i].name); + if (strncmp(ptr, e_limits[i].name, len) == 0 && + ptr[len] == '_') { + ptr2 = ptr + len + 1; + if (strcmp(ptr2, "user_low_time") == 0) { + e_limits[i].user_low_time = val; + } else if (strcmp(ptr2, "user_low_time_limit") == 0) { + e_limits[i].user_low_time_limit = val; + } else if (strcmp(ptr2, "user_hi_time") == 0) { + e_limits[i].user_hi_time = val; + } else if (strcmp(ptr2, "user_hi_time_limit") == 0) { + e_limits[i].user_hi_time_limit = val; + } else if (strcmp(ptr2, "ip_low_time") == 0) { + e_limits[i].ip_low_time = val; + } else if (strcmp(ptr2, "ip_low_time_limit") == 0) { + e_limits[i].ip_low_time_limit = val; + } else if (strcmp(ptr2, "ip_hi_time") == 0) { + e_limits[i].ip_hi_time = val; + } else if (strcmp(ptr2, "ip_hi_time_limit") == 0) { + e_limits[i].ip_hi_time_limit = val; + } else if (strcmp(ptr2, "lifetime") == 0) { + e_limits[i].lifetime = val; + } else { + LOGERR("%s(%s): ERR: Unknown %s item '%s' " + "in '%s'", + from, __func__, OC_LIMITS, ptr2, + oc->optionname); + } + processed = true; + break; + } + } + if (!processed) { + if (strcmp(ptr, "hash_lifetime") == 0) { + event_limits_hash_lifetime = val; + processed = true; + } + } + K_WUNLOCK(event_limits_free); + if (!processed) { + LOGERR("%s(%s): ERR: Unknown %s name '%s'", + from, __func__, OC_LIMITS, oc->optionname); + } +} + +OC_TRIGGER oc_trigger[] = { + { SWITCH_STATE_NAME, true, oc_switch_state }, + { DIFF_PERCENT_NAME, true, oc_diff_percent }, + { OC_LIMITS, false, oc_event_limits }, + { NULL, 0, NULL } +}; + +/* For oc items that aren't date/height controlled, and use global variables + * rather than having to look up the value every time it's needed + * Called from within the write lock that loaded/added the oc_item */ +static void optioncontrol_trigger(K_ITEM *oc_item, const char *from) +{ + char cd_buf[DATE_BUFSIZ], cd2_buf[DATE_BUFSIZ]; + OPTIONCONTROL *oc; + int got, i; + + DATA_OPTIONCONTROL(oc, oc_item); + if (CURRENT(&(oc->expirydate))) { + got = -1; + for (i = 0; oc_trigger[i].match; i++) { + if (oc_trigger[i].exact) { + if (strcmp(oc->optionname, + oc_trigger[i].match) == 0) { + got = i; + break; + } + } else { + if (strncmp(oc->optionname, oc_trigger[i].match, + strlen(oc_trigger[i].match)) == 0) { + got = i; + break; + } + } + } + if (got > -1) { + // If it's date/height controlled, display an ERR + if (oc->activationheight != OPTIONCONTROL_HEIGHT || + tv_newer(&date_begin, &(oc->activationdate))) + { + tv_to_buf(&(oc->activationdate), cd_buf, + sizeof(cd_buf)); + tv_to_buf((tv_t *)&date_begin, cd2_buf, + sizeof(cd_buf)); + LOGERR("%s(%s) ERR: ignored '%s' - has " + "height %"PRId32" & date '%s' - " + "expect height %d & date <= '%s'", + from, __func__, oc->optionname, + oc->activationheight, cd_buf, + OPTIONCONTROL_HEIGHT, cd2_buf); + } else + oc_trigger[i].func(oc, from); + } + } +} + K_ITEM *optioncontrol_item_add(PGconn *conn, K_ITEM *oc_item, tv_t *cd, bool begun) { ExecStatusType rescode; @@ -2462,13 +2593,6 @@ K_ITEM *optioncontrol_item_add(PGconn *conn, K_ITEM *oc_item, tv_t *cd, bool beg row->activationheight = OPTIONCONTROL_HEIGHT; } - // Update if it's changed - if (strcmp(row->optionname, DIFF_PERCENT_NAME) == 0) { - diff_percent = DIFF_VAL(strtod(row->optionvalue, NULL)); - if (errno == ERANGE) - diff_percent = DIFF_VAL(DIFF_PERCENT_DEFAULT); - } - INIT_OPTIONCONTROL(&look); look.data = (void *)row; K_RLOCK(optioncontrol_free); @@ -2552,6 +2676,8 @@ nostart: for (n = 0; n < par; n++) free(params[n]); + /* N.B. if the DB update fails, + * optioncontrol_trigger() will not be called */ K_WLOCK(optioncontrol_free); if (!ok) { // Cleanup item passed in @@ -2567,11 +2693,8 @@ nostart: } add_to_ktree(optioncontrol_root, oc_item); k_add_head(optioncontrol_store, oc_item); - if (strcmp(row->optionname, SWITCH_STATE_NAME) == 0) { - switch_state = atoi(row->optionvalue); - LOGWARNING("%s() set switch_state to %d", - __func__, switch_state); - } + + optioncontrol_trigger(oc_item, __func__); } K_WUNLOCK(optioncontrol_free); @@ -2700,21 +2823,7 @@ bool optioncontrol_fill(PGconn *conn) add_to_ktree(optioncontrol_root, item); k_add_head(optioncontrol_store, item); - // There should only be one CURRENT version of switch_state - if (CURRENT(&(row->expirydate)) && - strcmp(row->optionname, SWITCH_STATE_NAME) == 0) { - switch_state = atoi(row->optionvalue); - LOGWARNING("%s() set switch_state to %d", - __func__, switch_state); - } - - // The last loaded CURRENT value will be used - if (CURRENT(&(row->expirydate)) && - strcmp(row->optionname, DIFF_PERCENT_NAME) == 0) { - diff_percent = DIFF_VAL(strtod(row->optionvalue, NULL)); - if (errno == ERANGE) - diff_percent = DIFF_VAL(DIFF_PERCENT_DEFAULT); - } + optioncontrol_trigger(item, __func__); } if (!ok) { free_optioncontrol_data(item);