From 5f4bbd74e4e31e84c771ba3d28f02546969e209d Mon Sep 17 00:00:00 2001 From: kanoi Date: Thu, 18 Feb 2016 20:27:56 +1100 Subject: [PATCH] ckdb - add OK events (OVENTS) to allow limiting valid accesses --- src/ckdb.c | 81 ++++-- src/ckdb.h | 60 ++++- src/ckdb_data.c | 673 ++++++++++++++++++++++++++++++++++-------------- src/ckdb_dbio.c | 381 ++++++++++++++++++++++----- 4 files changed, 922 insertions(+), 273 deletions(-) diff --git a/src/ckdb.c b/src/ckdb.c index 7dd25cb7..7790fa30 100644 --- a/src/ckdb.c +++ b/src/ckdb.c @@ -495,39 +495,71 @@ K_STORE *events_store; // Emulate a list for lock checking K_LIST *event_limits_free; +// OVENTS (OK EVENTS) +K_TREE *ovents_root; +K_LIST *ovents_free; +K_STORE *ovents_store; + /* 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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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; +/* N.B. these limits are not production quality + * They'll block anyone who does anything more than once a minute + * Use optioncontrol OC_OLIMITS to set/store them in the database */ +EVENT_LIMITS o_limits[] = { +// Homepage valid access - most web access includes Homepage - so this isn't actually counted +{ OVENTID_HOMEPAGE, "HOMEPAGE", 60, 1, 10*60, 10, 60, 1, 10*60, 10, 24*60*60 }, +// Blocks valid access +{ OVENTID_BLOCKS, "BLOCKS", 60, 1, 10*60, 10, 60, 1, 10*60, 10, 24*60*60 }, +// API valid access +{ OVENTID_API, "API", 60, 1, 10*60, 10, 60, 1, 10*60, 10, 24*60*60 }, +// Add/Update single payment address +{ OVENTID_ONEADDR, "ONEADDR", 60, 1, 10*60, 10, 60, 1, 10*60, 10, 24*60*60 }, +// Add/Update multi payment address +{ OVENTID_MULTIADDR, "MULTIADDR", 60, 1, 10*60, 10, 60, 1, 10*60, 10, 24*60*60 }, +// Workers valid access +{ OVENTID_WORKERS, "WORKERS", 60, 1, 10*60, 10, 60, 1, 10*60, 10, 24*60*60 }, +// Other valid access +{ OVENTID_OTHER, "OTHER", 60, 1, 10*60, 10, 60, 1, 10*60, 10, 24*60*60 }, + // Terminated by NULL name + { -1, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0 } +}; + +// mulitply IP limit by this to get IPC limit +double ovent_limits_ipc_factor = 2.0; +// maximum lifetime of all o_limits - set by code +int o_limits_max_lifetime = -1; + // AUTHS authorise.id.json={...} K_TREE *auths_root; K_LIST *auths_free; @@ -1226,6 +1258,11 @@ static void alloc_storage() events_ipc_root = new_ktree(NULL, cmp_events_ipc, events_free); events_hash_root = new_ktree(NULL, cmp_events_hash, events_free); + ovents_free = k_new_list("OKEvents", sizeof(OVENTS), + ALLOC_OVENTS, LIMIT_OVENTS, true); + ovents_store = k_new_store(ovents_free); + ovents_root = new_ktree(NULL, cmp_ovents, ovents_free); + auths_free = k_new_list("Auths", sizeof(AUTHS), ALLOC_AUTHS, LIMIT_AUTHS, true); auths_store = k_new_store(auths_free); @@ -1308,12 +1345,13 @@ static void alloc_storage() DLPRIO(userinfo, 50); // Uses event_limits - DLPRIO(optioncontrol, 48); + DLPRIO(optioncontrol, 49); // Needs to check users and ips and uses events_limits - DLPRIO(events, 47); + DLPRIO(events, 48); + DLPRIO(ovents, 47); - // events_limits 46 (events-1) above users + // events_limits 46 (events-2) above users DLPRIO(auths, 44); DLPRIO(users, 43); @@ -1526,7 +1564,10 @@ static void dealloc_storage() FREE_ALL(poolstats); FREE_TREE(events_user); FREE_TREE(events_ip); + FREE_TREE(events_ipc); + FREE_TREE(events_hash); FREE_LISTS(events); + FREE_ALL(ovents); FREE_ALL(ips); FREE_ALL(auths); @@ -6068,9 +6109,17 @@ int main(int argc, char **argv) #if LOCK_CHECK DLPRIO(process_pplns, 99); DLPRIO(workers_db, 98); - DLPRIO(event_limits, 46); // events-1 + DLPRIO(event_limits, 46); // events-2 #endif + // set initial value + o_limits_max_lifetime = -1; + i = -1; + while (o_limits[++i].name) { + if (o_limits_max_lifetime < o_limits[i].lifetime) + o_limits_max_lifetime = o_limits[i].lifetime; + } + if (confirm_sharesummary) { // TODO: add a system lock to stop running 2 at once? confirm_summaries(); diff --git a/src/ckdb.h b/src/ckdb.h index dd87b0fc..b8e69370 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.952" +#define CKDB_VERSION DB_VERSION"-1.953" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -2045,6 +2045,53 @@ extern K_LIST *event_limits_free; #define EVENT_OK -1 +// Homepage valid access +#define OVENTID_HOMEPAGE 0 +// Blocks valid access +#define OVENTID_BLOCKS 1 +// API valid access +#define OVENTID_API 2 +// Add/Update single payment address +#define OVENTID_ONEADDR 3 +// Add/Update multi payment address +#define OVENTID_MULTIADDR 4 +// Workers valid access +#define OVENTID_WORKERS 5 +// Other valid access +#define OVENTID_OTHER 6 +#define OVENTID_NONE 7 + +// OVENTS RAM only (OK EVENTS) +typedef struct ovents { + // user, ip or ipc + char key[TXT_SML+1]; + // One record per hour time/3600 + int hour; + // per id per minute + int count[OVENTID_NONE * 60]; + HISTORYDATECONTROLFIELDS; +} OVENTS; + +#define ALLOC_OVENTS 1000 +#define LIMIT_OVENTS 0 +#define INIT_OVENTS(_item) INIT_GENERIC(_item, ovents) +#define DATA_OVENTS(_var, _item) DATA_GENERIC(_var, _item, ovents, true) +#define DATA_OVENTS_NULL(_var, _item) DATA_GENERIC(_var, _item, ovents, false) + +extern K_TREE *ovents_root; +extern K_LIST *ovents_free; +extern K_STORE *ovents_store; + +#define SEC_TO_HOUR(_s) ((int)((_s) / 3600)) +#define TV_TO_HOUR(_tv) SEC_TO_HOUR((_tv)->tv_sec) +#define SEC_TO_MIN(_s) ((int)(((_s) % 3600) / 60)) +#define TV_TO_MIN(_tv) SEC_TO_MIN((_tv)->tv_sec) +#define IDMIN(_id, _min) ((_id) * 60 + (_min)) + +#define OC_OLIMITS "ovent_limits_" + +#define OVENT_OK EVENT_OK + /* user limits are checked for matching id+user * ip limits are checked for matching id+ip, * however, it requires more than one user found with the given ip @@ -2073,6 +2120,13 @@ extern EVENT_LIMITS e_limits[]; // Has a fixed limit of 1 event allowed extern int event_limits_hash_lifetime; +// o_limits uses the event_limits_free lock (since it must) +extern EVENT_LIMITS o_limits[]; +// multiply IP limit by this to get IPC limit +extern double ovent_limits_ipc_factor; +// maximum lifetime of all o_limits - set by code +extern int o_limits_max_lifetime; + // AUTHS authorise.id.json={...} typedef struct auths { int64_t authid; @@ -2782,6 +2836,10 @@ extern int check_events(EVENTS *events); extern char *_reply_event(int event, char *buf, bool fre); #define reply_event(_event, _buf) _reply_event(_event, _buf, false) #define reply_event_free(_event, _buf) _reply_event(_event, _buf, true) +extern cmp_t cmp_ovents(K_ITEM *a, K_ITEM *b); +extern K_ITEM *find_ovents(char *key, int hour, K_TREE_CTX *ctx); +extern K_ITEM *last_ovents(char *key, K_TREE_CTX *ctx); +extern int check_ovents(int id, char *u_key, char *i_key, char *c_key, tv_t *now); extern cmp_t cmp_auths(K_ITEM *a, K_ITEM *b); extern cmp_t cmp_poolstats(K_ITEM *a, K_ITEM *b); extern void dsp_userstats(K_ITEM *item, FILE *stream); diff --git a/src/ckdb_data.c b/src/ckdb_data.c index b5d61064..8464b77e 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -4498,7 +4498,7 @@ static const char *cause_str(enum event_cause cause) } } -// return EVENT_OK or timeout seconds +// return EVENT_OK or -timeout seconds int check_events(EVENTS *events) { bool alert = false, user1, user2; @@ -4539,220 +4539,222 @@ int check_events(EVENTS *events) // 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); - if (e_item) { - DATA_EVENTS(e, e_item); - user1 = false; - while (e_item && e->id == events->id && - strcmp(e->hash, events->hash) == 0 && - CURRENT(&(e->expirydate))) { - // rounded down seconds - secs = (int)tvdiff(&now, &(e->createdate)); - // Is this event too old? - if (secs >= event_limits_hash_lifetime) { - tmp_item = e_item; - e_item = prev_in_ktree(ctx); - // Discard the old event - e is still tmp_item - remove_from_ktree(events_hash_root, tmp_item); - if (--(e->trees) < 1) { - // No longer in any of the event trees - k_unlink_item(events_store, tmp_item); - k_add_head(events_free, tmp_item); - } - continue; - } - if (!alert) { - if (!user1) { - K_RLOCK(users_free); - u_item = find_users(e->createby); - K_RUNLOCK(users_free); - if (u_item) { - // Remember the username - STRNCPY(createby, e->createby); - user1 = true; - } - } else { - // Don't check username case mistyping errors - if (strcasecmp(createby, e->createby) != 0) { - K_RLOCK(users_free); - u_item = find_users(e->createby); - K_RUNLOCK(users_free); - if (u_item) { - alert = true; - cause = CAUSE_HASH; - tyme = 0; - limit = 1; - lifetime = event_limits_hash_lifetime; - STRNCPY(name, "HASH"); - } - } - } - } - e_item = prev_in_ktree(ctx); - } - } - } - // Check User - e_item = last_events_user(events->id, events->createby, ctx); - if (e_item) { - DATA_EVENTS(e, e_item); - count = 0; + DATA_EVENTS_NULL(e, e_item); + user1 = false; while (e_item && e->id == events->id && - strcmp(e->createby, events->createby) == 0 && + strcmp(e->hash, events->hash) == 0 && CURRENT(&(e->expirydate))) { - count++; // rounded down seconds secs = (int)tvdiff(&now, &(e->createdate)); // Is this event too old? - if (secs >= e_limits[events->id].lifetime) { + if (secs >= event_limits_hash_lifetime) { tmp_item = e_item; e_item = prev_in_ktree(ctx); - // Discard the old event - e is still tmp_item - remove_from_ktree(events_user_root, tmp_item); - if (--e->trees < 1) { + // Discard the old event - e is still old + remove_from_ktree(events_hash_root, tmp_item); + if (--(e->trees) < 1) { // No longer in any of the event trees k_unlink_item(events_store, tmp_item); k_add_head(events_free, tmp_item); } + DATA_EVENTS_NULL(e, e_item); continue; } - if (alert == false && - secs <= e_limits[events->id].user_low_time && - count > e_limits[events->id].user_low_time_limit) { - alert = true; - cause = CAUSE_USER_LO; - 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 && - count > e_limits[events->id].user_hi_time_limit) { - alert = true; - cause = CAUSE_USER_HI; - 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); + if (!alert) { + if (!user1) { + K_RLOCK(users_free); + u_item = find_users(e->createby); + K_RUNLOCK(users_free); + if (u_item) { + // Remember the username + STRNCPY(createby, e->createby); + user1 = true; + } + } else { + // Don't check username case mistyping errors + if (strcasecmp(createby, e->createby) != 0) { + K_RLOCK(users_free); + u_item = find_users(e->createby); + K_RUNLOCK(users_free); + if (u_item) { + alert = true; + cause = CAUSE_HASH; + tyme = 0; + limit = 1; + lifetime = event_limits_hash_lifetime; + STRNCPY(name, "HASH"); + } + } + } } e_item = prev_in_ktree(ctx); + DATA_EVENTS_NULL(e, e_item); + } + } + // Check User + e_item = last_events_user(events->id, events->createby, ctx); + DATA_EVENTS_NULL(e, e_item); + count = 0; + while (e_item && e->id == events->id && + strcmp(e->createby, events->createby) == 0 && + CURRENT(&(e->expirydate))) { + // rounded down seconds + secs = (int)tvdiff(&now, &(e->createdate)); + // Is this event too old? + if (secs >= e_limits[events->id].lifetime) { + tmp_item = e_item; + e_item = prev_in_ktree(ctx); + // Discard the old event - e is still tmp_item + remove_from_ktree(events_user_root, tmp_item); + if (--e->trees < 1) { + // No longer in any of the event trees + k_unlink_item(events_store, tmp_item); + k_add_head(events_free, tmp_item); + } + DATA_EVENTS_NULL(e, e_item); + continue; + } + count++; + if (alert == false && + secs <= e_limits[events->id].user_low_time && + count > e_limits[events->id].user_low_time_limit) { + alert = true; + cause = CAUSE_USER_LO; + 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 && + count > e_limits[events->id].user_hi_time_limit) { + alert = true; + cause = CAUSE_USER_HI; + 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); + DATA_EVENTS_NULL(e, e_item); } // Check IP e_item = last_events_ip(events->id, events->createinet, ctx); - if (e_item) { - DATA_EVENTS(e, e_item); - count = 0; - // Remember the first username - STRNCPY(createby, e->createby); - user2 = false; - while (e_item && e->id == events->id && - strcmp(e->createinet, events->createinet) == 0 && - CURRENT(&(e->expirydate))) { - count++; - // rounded down seconds - secs = (int)tvdiff(&now, &(e->createdate)); - // Is this event too old? - if (secs >= e_limits[events->id].lifetime) { - tmp_item = e_item; - e_item = prev_in_ktree(ctx); - // Discard the old event - e is still tmp_item - remove_from_ktree(events_ip_root, tmp_item); - if (--e->trees < 1) { - // No longer in any of the event trees - k_unlink_item(events_store, tmp_item); - k_add_head(events_free, tmp_item); - } - continue; - } - // Allow username case typing errors - if (strcasecmp(createby, e->createby) != 0) - user2 = true; - if (alert == false && - secs <= e_limits[events->id].ip_low_time && - count > e_limits[events->id].ip_low_time_limit) { - alert = true; - cause = CAUSE_IP_LO; - 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 && - count > e_limits[events->id].ip_hi_time_limit) { - alert = true; - cause = CAUSE_IP_HI; - 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); - } + DATA_EVENTS_NULL(e, e_item); + count = 0; + // Remember the first username + STRNCPY(createby, e->createby); + user2 = false; + while (e_item && e->id == events->id && + strcmp(e->createinet, events->createinet) == 0 && + CURRENT(&(e->expirydate))) { + // rounded down seconds + secs = (int)tvdiff(&now, &(e->createdate)); + // Is this event too old? + if (secs >= e_limits[events->id].lifetime) { + tmp_item = e_item; e_item = prev_in_ktree(ctx); + // Discard the old event - e is still tmp_item + remove_from_ktree(events_ip_root, tmp_item); + if (--e->trees < 1) { + // No longer in any of the event trees + k_unlink_item(events_store, tmp_item); + k_add_head(events_free, tmp_item); + } + DATA_EVENTS_NULL(e, e_item); + continue; } - /* If the ip alert was a single user then it's not an ip failure - * since the User check already covers that */ - if (alert && (cause == CAUSE_IP_LO || cause == CAUSE_IP_HI) && - user2 == false) - alert = false; + count++; + // Allow username case typing errors + if (strcasecmp(createby, e->createby) != 0) + user2 = true; + if (alert == false && + secs <= e_limits[events->id].ip_low_time && + count > e_limits[events->id].ip_low_time_limit) { + alert = true; + cause = CAUSE_IP_LO; + 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 && + count > e_limits[events->id].ip_hi_time_limit) { + alert = true; + cause = CAUSE_IP_HI; + 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); + DATA_EVENTS_NULL(e, e_item); } + /* If the ip alert was a single user then it's not an ip failure + * since the User check already covers that */ + if (alert && (cause == CAUSE_IP_LO || cause == CAUSE_IP_HI) && + user2 == false) + alert = false; + // Check IPC (Class C IP) use same rules as for IP e_item = last_events_ipc(events->id, events->ipc, ctx); - if (e_item) { - DATA_EVENTS(e, e_item); - count = 0; - // Remember the first username - STRNCPY(createby, e->createby); - user2 = false; - while (e_item && e->id == events->id && - strcmp(e->ipc, events->ipc) == 0 && - CURRENT(&(e->expirydate))) { - count++; - // rounded down seconds - secs = (int)tvdiff(&now, &(e->createdate)); - // Is this event too old? - if (secs >= e_limits[events->id].lifetime) { - tmp_item = e_item; - e_item = prev_in_ktree(ctx); - // Discard the old event - e is still tmp_item - remove_from_ktree(events_ipc_root, tmp_item); - if (--e->trees < 1) { - k_unlink_item(events_store, tmp_item); - k_add_head(events_free, tmp_item); - } - continue; - } - // Allow username case typing errors - if (strcasecmp(createby, e->createby) != 0) - user2 = true; - if (alert == false && - secs <= e_limits[events->id].ip_low_time && - count > e_limits[events->id].ip_low_time_limit) { - alert = true; - cause = CAUSE_IPC_LO; - 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 && - count > e_limits[events->id].ip_hi_time_limit) { - alert = true; - cause = CAUSE_IPC_HI; - 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); - } + DATA_EVENTS_NULL(e, e_item); + count = 0; + // Remember the first username + STRNCPY(createby, e->createby); + user2 = false; + while (e_item && e->id == events->id && + strcmp(e->ipc, events->ipc) == 0 && + CURRENT(&(e->expirydate))) { + // rounded down seconds + secs = (int)tvdiff(&now, &(e->createdate)); + // Is this event too old? + if (secs >= e_limits[events->id].lifetime) { + tmp_item = e_item; e_item = prev_in_ktree(ctx); + // Discard the old event - e is still tmp_item + remove_from_ktree(events_ipc_root, tmp_item); + if (--e->trees < 1) { + k_unlink_item(events_store, tmp_item); + k_add_head(events_free, tmp_item); + } + DATA_EVENTS_NULL(e, e_item); + continue; + } + count++; + // Allow username case typing errors + if (strcasecmp(createby, e->createby) != 0) + user2 = true; + if (alert == false && + secs <= e_limits[events->id].ip_low_time && + count > e_limits[events->id].ip_low_time_limit) { + alert = true; + cause = CAUSE_IPC_LO; + 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 the ipc alert was a single user then it's not an ipc failure - * since the User check already covers that */ - if (alert && (cause == CAUSE_IPC_LO || cause == CAUSE_IPC_HI) && - user2 == false) - alert = false; + if (alert == false && + secs <= e_limits[events->id].ip_hi_time && + count > e_limits[events->id].ip_hi_time_limit) { + alert = true; + cause = CAUSE_IPC_HI; + 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); + DATA_EVENTS_NULL(e, e_item); } + /* If the ipc alert was a single user then it's not an ipc failure + * since the User check already covers that */ + if (alert && (cause == CAUSE_IPC_LO || cause == CAUSE_IPC_HI) && + user2 == false) + alert = false; + K_RUNLOCK(event_limits_free); K_WUNLOCK(events_free); if (alert) { @@ -4786,15 +4788,17 @@ int check_events(EVENTS *events) exit(0); } } - return lifetime; + return -lifetime; } return EVENT_OK; } static char lurt[] = "alert="; static size_t lurtsiz = sizeof(lurt); -static char toomany[] = "Too many failures, come back later"; -static size_t toomanysiz = sizeof(toomany); +static char tmf[] = "Too many failures, come back later"; +static size_t tmfsiz = sizeof(tmf); +static char tma[] = "Too many accesses, come back later"; +static size_t tmasiz = sizeof(tma); char *_reply_event(int event, char *buf, bool fre) { @@ -4814,16 +4818,299 @@ char *_reply_event(int event, char *buf, bool fre) len = strlen(buf); len += 1 + lurtsiz; - len += toomanysiz + 1; + if (event < 0) + len += tmfsiz; + else + len += tmasiz; reply = malloc(len); if (!reply) quithere(1, "malloc (%d) OOM", (int)len); - snprintf(reply, len, "%s%c%s%s", buf, FLDSEP, lurt, toomany); + snprintf(reply, len, "%s%c%s%s", buf, FLDSEP, lurt, + event < 0 ? tmf : tma); if (fre) free(buf); return reply; } +// order by key asc,hour asc,expirydate desc +cmp_t cmp_ovents(K_ITEM *a, K_ITEM *b) +{ + OVENTS *ea, *eb; + DATA_OVENTS(ea, a); + DATA_OVENTS(eb, b); + cmp_t c = CMP_STR(ea->key, eb->key); + if (c == 0) { + c = CMP_INT(ea->hour, eb->hour); + if (c == 0) { + c = CMP_TV(eb->expirydate, ea->expirydate); + } + } + return c; +} + +K_ITEM *find_ovents(char *key, int hour, K_TREE_CTX *ctx) +{ + K_TREE_CTX ctx0[1]; + OVENTS ovents; + K_ITEM look; + + if (ctx == NULL) + ctx = ctx0; + + STRNCPY(ovents.key, key); + ovents.hour = hour; + copy_tv(&(ovents.expirydate), &default_expiry); + + INIT_OVENTS(&look); + look.data = (void *)(&ovents); + return find_in_ktree(ovents_root, &look, ctx); +} + +K_ITEM *last_ovents(char *key, K_TREE_CTX *ctx) +{ + K_TREE_CTX ctx0[1]; + OVENTS ovents; + K_ITEM look; + + if (ctx == NULL) + ctx = ctx0; + + STRNCPY(ovents.key, key); + ovents.hour = SEC_TO_HOUR(DATE_S_EOT); + copy_tv(&(ovents.expirydate), &default_expiry); + + INIT_OVENTS(&look); + look.data = (void *)(&ovents); + return find_before_in_ktree(ovents_root, &look, ctx); +} + +enum was_alert { + ALERT_NO, + ALERT_LO, + ALERT_HI +}; + +// ovents must be W locked and event_limits R locked +static enum was_alert check_one_ovent(int id, char *key, tv_t *now, + int low_time, int low_time_limit, + int hi_time, int hi_time_limit) +{ + K_TREE_CTX ctx[1]; + K_ITEM *o_item = NULL, *tmp_item; + OVENTS *ovents = NULL; + int low_count, hi_count, hour, min; + enum was_alert ret = ALERT_NO; + bool alert = false; + + o_item = last_ovents(key, ctx); + if (!o_item) + return ret; + + low_count = hi_count = 0; + hour = TV_TO_HOUR(now); + DATA_OVENTS(ovents, o_item); + while (o_item && strcmp(key, ovents->key) == 0 && + CURRENT(&(ovents->expirydate))) { + // Is this event too old? + if (((hour * 3600) - ((ovents->hour + 1) * 3600)) > + o_limits_max_lifetime) { + tmp_item = o_item; + // Get the prev event + o_item = prev_in_ktree(ctx); + DATA_OVENTS_NULL(ovents, o_item); + // Discard the old event + remove_from_ktree(ovents_root, tmp_item); + k_unlink_item(ovents_store, tmp_item); + k_add_head(ovents_free, tmp_item); + continue; + } + if (alert == false) { + min = hour * 3600 - low_time - ovents->hour * 3600; + if (min < 0) + min = 0; + while (min < 60) { + low_count += ovents->count[IDMIN(id, min)]; + min++; + } + if (low_count > low_time_limit) { + alert = true; + ret = ALERT_LO; + } + } + if (alert == false) { + min = hour * 3600 - hi_time - ovents->hour * 3600; + if (min < 0) + min = 0; + while (min < 60) { + hi_count += ovents->count[IDMIN(id, min)]; + min++; + } + if (hi_count > hi_time_limit) { + alert = true; + ret = ALERT_HI; + } + } + o_item = prev_in_ktree(ctx); + DATA_OVENTS_NULL(ovents, o_item); + } + return ret; +} + +// return OVENT_OK or +timeout seconds +int check_ovents(int id, char *u_key, char *i_key, char *c_key, tv_t *now) +{ + K_ITEM *i_item; + enum was_alert was; + bool alert = false; + char *st = NULL; + enum event_cause cause = CAUSE_NONE; + char cmd[MAX_ALERT_CMD+1]; + int tyme, limit, lifetime; + char name[TXT_SML+1]; + pid_t pid; + + 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 (!cmd[0]) + return OVENT_OK; + + if (i_key[0]) { + K_RLOCK(ips_free); + i_item = find_ips(IPS_GROUP_OK, i_key); + if (!i_item) + i_item = find_ips(IPS_GROUP_OK, i_key); + K_RUNLOCK(ips_free); + if (i_item) + return OVENT_OK; + } + + K_WLOCK(events_free); + K_RLOCK(event_limits_free); + + if (u_key[0]) { + was = check_one_ovent(id, u_key, now, + o_limits[id].user_low_time, + o_limits[id].user_low_time_limit, + o_limits[id].user_hi_time, + o_limits[id].user_hi_time_limit); + if (was != ALERT_NO) { + alert = true; + if (was == ALERT_LO) { + cause = CAUSE_USER_LO; + tyme = o_limits[id].user_low_time; + limit = o_limits[id].user_low_time_limit; + } else { + cause = CAUSE_USER_HI; + tyme = o_limits[id].user_hi_time; + limit = o_limits[id].user_hi_time_limit; + } + lifetime = o_limits[id].lifetime; + STRNCPY(name, o_limits[id].name); + } + } + + /* If we already have the alert, the check_one_ovent() + * cleanup isn't needed since the first call cleans up all */ + if (alert == false && i_key[0]) { + was = check_one_ovent(id, i_key, now, + o_limits[id].ip_low_time, + o_limits[id].ip_low_time_limit, + o_limits[id].ip_hi_time, + o_limits[id].ip_hi_time_limit); + if (was != ALERT_NO) { + alert = true; + if (was == ALERT_LO) { + cause = CAUSE_IP_LO; + tyme = o_limits[id].ip_low_time; + limit = o_limits[id].ip_low_time_limit; + } else { + cause = CAUSE_IP_HI; + tyme = o_limits[id].ip_hi_time; + limit = o_limits[id].ip_hi_time_limit; + } + lifetime = o_limits[id].lifetime; + STRNCPY(name, o_limits[id].name); + } + } + + if (alert == false && c_key[0]) { + was = check_one_ovent(id, c_key, now, + o_limits[id].ip_low_time, + (int)(ovent_limits_ipc_factor * + (double)(o_limits[id].ip_low_time_limit)), + o_limits[id].ip_hi_time, + (int)(ovent_limits_ipc_factor * + (double)(o_limits[id].ip_hi_time_limit))); + if (was != ALERT_NO) { + alert = true; + if (was == ALERT_LO) { + cause = CAUSE_IPC_LO; + tyme = o_limits[id].ip_low_time; + limit = (int)(ovent_limits_ipc_factor * + (double)(o_limits[id].ip_low_time_limit)); + } else { + cause = CAUSE_IPC_HI; + tyme = o_limits[id].ip_hi_time; + limit = (int)(ovent_limits_ipc_factor * + (double)(o_limits[id].ip_hi_time_limit)); + } + lifetime = o_limits[id].lifetime; + STRNCPY(name, o_limits[id].name); + } + } + K_RUNLOCK(event_limits_free); + K_WUNLOCK(events_free); + if (alert) { + LOGERR("%s() OLERT ID:%d %s Lim:%d Time:%d Life:%d '%s/%s' " + "'%s' '%s'", __func__, + id, name, limit, tyme, lifetime, i_key, c_key, + st = safe_text(u_key), cause_str(cause)); + FREENULL(st); + if (!i_key[0]) { + LOGERR("%s() OLERT ID:%d '%s' can't ban, no IP", + __func__, id, st = safe_text(u_key)); + FREENULL(st); + } else { + ips_add(IPS_GROUP_BAN, i_key, (char *)cause_str(cause), + true, false, lifetime, false); + pid = fork(); + if (pid < 0) { + LOGERR("%s() OLERT failed to fork (%d)", + __func__, errno); + } else { + if (pid == 0) { + char buf1[16], buf2[16]; + char buf3[16], buf4[16]; + snprintf(buf1, sizeof(buf1), + "%d", id); + snprintf(buf2, sizeof(buf2), + "%d", limit); + snprintf(buf3, sizeof(buf3), + "%d", tyme); + snprintf(buf4, sizeof(buf4), + "%d", lifetime); + st = safe_text_nonull(u_key); + execl(cmd, cmd, buf1, name, buf2, buf3, + buf4, i_key, st, + cause_str(cause), NULL); + LOGERR("%s() OLERT fork failed to " + "execute (%d)", + __func__, errno); + FREENULL(st); + exit(0); + } + } + } + return lifetime; + } + return OVENT_OK; +} + // order by userid asc,createdate asc,authid asc,expirydate desc cmp_t cmp_auths(K_ITEM *a, K_ITEM *b) { diff --git a/src/ckdb_dbio.c b/src/ckdb_dbio.c index ae6535e3..6e1ff7ee 100644 --- a/src/ckdb_dbio.c +++ b/src/ckdb_dbio.c @@ -2453,10 +2453,11 @@ void oc_diff_percent(OPTIONCONTROL *oc, __maybe_unused const char *from) } /* An event limit setting looks like: - * OC_LIMITS + events_limits.name + '_' + Item + * OC_LIMITS + event_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 */ + * as below in the if tests + * lifetime values can't = -EVENT_OK */ void oc_event_limits(OPTIONCONTROL *oc, const char *from) { bool processed = false; @@ -2490,7 +2491,14 @@ void oc_event_limits(OPTIONCONTROL *oc, const char *from) } 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; + if (val != -(EVENT_OK)) + e_limits[i].lifetime = val; + else { + LOGERR("%s(%s): ERR: lifetime can't be" + " %d in '%s'", + from, __func__, -(EVENT_OK), + oc->optionname); + } } else { LOGERR("%s(%s): ERR: Unknown %s item '%s' " "in '%s'", @@ -2503,7 +2511,14 @@ void oc_event_limits(OPTIONCONTROL *oc, const char *from) } if (!processed) { if (strcmp(ptr, "hash_lifetime") == 0) { - event_limits_hash_lifetime = val; + if (val != -(EVENT_OK)) + event_limits_hash_lifetime = val; + else { + LOGERR("%s(%s): ERR: lifetime can't be" + " %d in '%s'", + from, __func__, -(EVENT_OK), + oc->optionname); + } processed = true; } } @@ -2514,6 +2529,85 @@ void oc_event_limits(OPTIONCONTROL *oc, const char *from) } } +/* An ovent limit setting looks like: + * OC_OLIMITS + event_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 + * lifetime values can't = OVENT_OK */ +void oc_ovent_limits(OPTIONCONTROL *oc, const char *from) +{ + bool processed = false, lifetime_changed = false; + size_t len; + char *ptr, *ptr2; + int val, i; + + K_WLOCK(event_limits_free); + val = atoi(oc->optionvalue); + ptr = oc->optionname + strlen(OC_OLIMITS); + i = -1; + while (o_limits[++i].name) { + len = strlen(o_limits[i].name); + if (strncmp(ptr, o_limits[i].name, len) == 0 && + ptr[len] == '_') { + ptr2 = ptr + len + 1; + if (strcmp(ptr2, "user_low_time") == 0) { + o_limits[i].user_low_time = val; + } else if (strcmp(ptr2, "user_low_time_limit") == 0) { + o_limits[i].user_low_time_limit = val; + } else if (strcmp(ptr2, "user_hi_time") == 0) { + o_limits[i].user_hi_time = val; + } else if (strcmp(ptr2, "user_hi_time_limit") == 0) { + o_limits[i].user_hi_time_limit = val; + } else if (strcmp(ptr2, "ip_low_time") == 0) { + o_limits[i].ip_low_time = val; + } else if (strcmp(ptr2, "ip_low_time_limit") == 0) { + o_limits[i].ip_low_time_limit = val; + } else if (strcmp(ptr2, "ip_hi_time") == 0) { + o_limits[i].ip_hi_time = val; + } else if (strcmp(ptr2, "ip_hi_time_limit") == 0) { + o_limits[i].ip_hi_time_limit = val; + } else if (strcmp(ptr2, "lifetime") == 0) { + if (val != OVENT_OK) { + o_limits[i].lifetime = val; + lifetime_changed = true; + } else { + LOGERR("%s(%s): ERR: lifetime can't be" + " %d in '%s'", + from, __func__, OVENT_OK, + oc->optionname); + } + } else { + LOGERR("%s(%s): ERR: Unknown %s item '%s' " + "in '%s'", + from, __func__, OC_OLIMITS, ptr2, + oc->optionname); + } + processed = true; + break; + } + } + if (!processed) { + if (strcmp(ptr, "ipc_factor") == 0) { + ovent_limits_ipc_factor = atof(oc->optionvalue); + processed = true; + } + } + if (lifetime_changed) { + o_limits_max_lifetime = -1; + i = -1; + while (o_limits[++i].name) { + if (o_limits_max_lifetime < o_limits[i].lifetime) + o_limits_max_lifetime = o_limits[i].lifetime; + } + } + K_WUNLOCK(event_limits_free); + if (!processed) { + LOGERR("%s(%s): ERR: Unknown %s name '%s'", + from, __func__, OC_OLIMITS, oc->optionname); + } +} + /* IPS for IPS_GROUP_OK/BAN look like: * optionname: (OC_IPS_OK or OC_IPS_BAN) + description * optionvalue: is the IP address @@ -2538,6 +2632,7 @@ OC_TRIGGER oc_trigger[] = { { SWITCH_STATE_NAME, true, oc_switch_state }, { DIFF_PERCENT_NAME, true, oc_diff_percent }, { OC_LIMITS, false, oc_event_limits }, + { OC_OLIMITS, false, oc_ovent_limits }, { OC_IPS, false, oc_ips }, { NULL, 0, NULL } }; @@ -2769,8 +2864,10 @@ bool optioncontrol_fill(PGconn *conn) PGresult *res; K_ITEM *item; OPTIONCONTROL *row; + K_TREE_CTX ctx[1]; + IPS *ips; char *params[1]; - int n, i, par = 0; + int n, i, par = 0, ban_count, ok_count; char *field; char *sel; int fields = 4; @@ -2859,6 +2956,25 @@ bool optioncontrol_fill(PGconn *conn) LOGWARNING("%s(): loaded %d optioncontrol records", __func__, n); LOGWARNING("%s() switch_state initially %d", __func__, switch_state); + + ok_count = ban_count = 0; + K_RLOCK(ips_free); + item = first_in_ktree(ips_root, ctx); + while (item) { + DATA_IPS(ips, item); + if (CURRENT(&(ips->expirydate))) { + if (strcmp(ips->group, IPS_GROUP_OK) == 0) + ok_count++; + else if (strcmp(ips->group, IPS_GROUP_BAN) == 0) + ban_count++; + } + item = next_in_ktree(ctx); + } + K_RUNLOCK(ips_free); + + LOGWARNING("%s() IPS: %s:%d %s:%d", + __func__, IPS_GROUP_OK, ok_count, + IPS_GROUP_BAN, ban_count); } return ok; @@ -6274,63 +6390,6 @@ bool payouts_fill(PGconn *conn) return ok; } -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; - char *dot; - tv_t now; - bool ok; - - setnow(&now); - if (!locked) - K_WLOCK(ips_free); - i_item = k_unlink_head(ips_free); - DATA_IPS(ips, i_item); - STRNCPY(ips->group, group); - STRNCPY(ips->ip, ip); - ips->lifetime = life; - if (des) { - ips->description = strdup(des); - if (!ips->description) - quithere(1, "strdup OOM"); - LIST_MEM_ADD(ips_free, ips->description); - } - ips->log = log; - HISTORYDATEDEFAULT(ips, &now); - add_to_ktree(ips_root, i_item); - k_add_head(ips_store, i_item); - if (cclass) { - i2_item = k_unlink_head(ips_free); - DATA_IPS(ips2, i2_item); - memcpy(ips2, ips, sizeof(*ips2)); - ok = false; - dot = strchr(ips->ip, '.'); - if (dot) { - dot = strchr(dot+1, '.'); - if (dot) { - dot = strchr(dot+1, '.'); - if (dot) { - *dot = '\0'; - ok = true; - } - } - } - if (ok) { - if (des) { - ips2->description = strdup(des); - LIST_MEM_ADD(ips_free, ips2->description); - } - add_to_ktree(ips_root, i2_item); - k_add_head(ips_store, i2_item); - } else - k_add_head(ips_free, i2_item); - } - if (!locked) - K_WUNLOCK(ips_free); -} - // trf_root overrides by,inet,cd fields int _events_add(int id, char *by, char *inet, tv_t *cd, K_TREE *trf_root) { @@ -6392,7 +6451,7 @@ int _events_add(int id, char *by, char *inet, tv_t *cd, K_TREE *trf_root) if (dot) { dot = strchr(dot+1, '.'); if (dot) - *dot = '\0'; + *(dot+1) = '\0'; } } } @@ -6444,6 +6503,202 @@ int _events_add(int id, char *by, char *inet, tv_t *cd, K_TREE *trf_root) return check_events(&events); } +// trf_root overrides by,inet,cd fields +int _ovents_add(int id, char *by, char *inet, tv_t *cd, K_TREE *trf_root) +{ + K_ITEM *o_item, *i_webtime, *i_username; + OVENTS ovents, *d_ovents; + char reply[1024] = ""; + size_t siz = sizeof(reply); + char u_key[TXT_SML+1], i_key[TXT_SML+1], c_key[TXT_SML+1]; + int hour, min; + bool gotc; + + LOGDEBUG("%s(): add", __func__); + + bzero(&ovents, sizeof(ovents)); + + // Default to now if not specified + setnow(&(ovents.createdate)); + + if (by) + STRNCPY(ovents.createby, by); + + if (inet) + STRNCPY(ovents.createinet, inet); + + if (cd) + copy_tv(&(ovents.createdate), cd); + + // trf_root values overrides parameters + HISTORYDATETRANSFER(trf_root, &ovents); + + // username overrides createby + i_username = optional_name(trf_root, "username", 1, NULL, + reply, siz); + if (i_username) + STRNCPY(ovents.createby, transfer_data(i_username)); + + // webtime overrides + i_webtime = optional_name(trf_root, "webtime", 1, NULL, + reply, siz); + if (i_webtime) { + TXT_TO_CTV("webtime", transfer_data(i_webtime), + ovents.createdate); + } + + STRNCPY(u_key, ovents.createby); + STRNCPY(i_key, ovents.createinet); + gotc = false; + if (i_key[0]) { + char *dot; + STRNCPY(c_key, i_key); + dot = strchr(c_key, '.'); + if (dot) { + dot = strchr(dot+1, '.'); + if (dot) { + dot = strchr(dot+1, '.'); + if (dot) { + *(dot+1) = '\0'; + gotc = true; + } + } + } + } + if (!gotc) + c_key[0] = '\0'; + + if (!u_key[0] && !i_key[0] && !c_key[0]) + return OVENT_OK; + + hour = TV_TO_HOUR(&(ovents.createdate)); + min = TV_TO_MIN(&(ovents.createdate)); + + K_WLOCK(ovents_free); + if (u_key[0]) { + o_item = find_ovents(u_key, hour, NULL); + if (o_item) { + DATA_OVENTS(d_ovents, o_item); + d_ovents->count[IDMIN(id, min)]++; + } else { + o_item = k_unlink_head(ovents_free); + DATA_OVENTS(d_ovents, o_item); + bzero(d_ovents, sizeof(*d_ovents)); + STRNCPY(d_ovents->key, u_key); + d_ovents->hour = hour; + d_ovents->count[IDMIN(id, min)]++; + copy_tv(&(d_ovents->createdate), &(ovents.createdate)); + STRNCPY(d_ovents->createby, ovents.createby); + STRNCPY(d_ovents->createinet, ovents.createinet); + copy_tv(&(d_ovents->expirydate), &default_expiry); + k_add_head(ovents_store, o_item); + add_to_ktree(ovents_root, o_item); + } + } + + if (i_key[0]) { + o_item = find_ovents(i_key, hour, NULL); + if (o_item) { + DATA_OVENTS(d_ovents, o_item); + d_ovents->count[IDMIN(id, min)]++; + } else { + o_item = k_unlink_head(ovents_free); + DATA_OVENTS(d_ovents, o_item); + bzero(d_ovents, sizeof(*d_ovents)); + STRNCPY(d_ovents->key, i_key); + d_ovents->hour = hour; + d_ovents->count[IDMIN(id, min)]++; + copy_tv(&(d_ovents->createdate), &(ovents.createdate)); + STRNCPY(d_ovents->createby, ovents.createby); + STRNCPY(d_ovents->createinet, ovents.createinet); + copy_tv(&(d_ovents->expirydate), &default_expiry); + k_add_head(ovents_store, o_item); + add_to_ktree(ovents_root, o_item); + } + } + + if (c_key[0]) { + o_item = find_ovents(c_key, hour, NULL); + if (o_item) { + DATA_OVENTS(d_ovents, o_item); + d_ovents->count[IDMIN(id, min)]++; + } else { + o_item = k_unlink_head(ovents_free); + DATA_OVENTS(d_ovents, o_item); + bzero(d_ovents, sizeof(*d_ovents)); + STRNCPY(d_ovents->key, c_key); + d_ovents->hour = hour; + d_ovents->count[IDMIN(id, min)]++; + copy_tv(&(d_ovents->createdate), &(ovents.createdate)); + STRNCPY(d_ovents->createby, ovents.createby); + STRNCPY(d_ovents->createinet, ovents.createinet); + copy_tv(&(d_ovents->expirydate), &default_expiry); + k_add_head(ovents_store, o_item); + add_to_ktree(ovents_root, o_item); + } + } + K_WUNLOCK(ovents_free); + + return check_ovents(id, u_key, i_key, c_key, &(ovents.createdate)); +} + +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; + char *dot; + tv_t now; + bool ok; + + setnow(&now); + if (!locked) + K_WLOCK(ips_free); + i_item = k_unlink_head(ips_free); + DATA_IPS(ips, i_item); + STRNCPY(ips->group, group); + STRNCPY(ips->ip, ip); + ips->lifetime = life; + if (des) { + ips->description = strdup(des); + if (!ips->description) + quithere(1, "strdup OOM"); + LIST_MEM_ADD(ips_free, ips->description); + } + ips->log = log; + HISTORYDATEDEFAULT(ips, &now); + add_to_ktree(ips_root, i_item); + k_add_head(ips_store, i_item); + if (cclass) { + i2_item = k_unlink_head(ips_free); + DATA_IPS(ips2, i2_item); + memcpy(ips2, ips, sizeof(*ips2)); + ok = false; + dot = strchr(ips->ip, '.'); + if (dot) { + dot = strchr(dot+1, '.'); + if (dot) { + dot = strchr(dot+1, '.'); + if (dot) { + *(dot+1) = '\0'; + ok = true; + } + } + } + if (ok) { + if (des) { + ips2->description = strdup(des); + LIST_MEM_ADD(ips_free, ips2->description); + } + add_to_ktree(ips_root, i2_item); + k_add_head(ips_store, i2_item); + } else + k_add_head(ips_free, i2_item); + } + if (!locked) + K_WUNLOCK(ips_free); +} + // TODO: discard them from RAM bool auths_add(PGconn *conn, char *poolinstance, char *username, char *workername, char *clientid, char *enonce1,