diff --git a/pool/base.php b/pool/base.php index 4430a175..5f2eb774 100644 --- a/pool/base.php +++ b/pool/base.php @@ -6,6 +6,9 @@ global $dbg, $dbgstr; $dbg = false; $dbgstr = ''; # +global $alrts; +$alrts = array(); +# function adddbg($str) { global $dbg, $dbgstr; diff --git a/pool/db.php b/pool/db.php index c845d5e9..97af40d2 100644 --- a/pool/db.php +++ b/pool/db.php @@ -1,7 +1,6 @@ "; + $top .= $alert_marker; if ($loginfailed === true) $top .= '
Login Failed
'; if (isset($info['u_nopayaddr'])) @@ -507,7 +508,7 @@ function pgmenu($menus) return $ret; } # -function pgbody($info, $page, $menu, $dotop, $user, $douser) +function pgbody($alert_marker, $info, $page, $menu, $dotop, $user, $douser) { $body = ''; @@ -590,8 +595,8 @@ function gopage($info, $data, $pagefun, $page, $menu, $name, $user, $ispage = tr // unset($_SESSION['logkey']); $head = pghead($css_marker, $script_marker, $name); - $body = pgbody($info, $page, $menu, $dotop, $user, $douser); - $foot = pgfoot($info); + $body = pgbody($alert_marker, $info, $page, $menu, $dotop, $user, $douser); + $foot = pgfoot($elapsed_marker, $info); if ($dbg === true) $pg = str_replace($dbg_marker, cvtdbg(), $pg); @@ -603,6 +608,11 @@ function gopage($info, $data, $pagefun, $page, $menu, $name, $user, $ispage = tr $head = str_replace($script_marker, $page_scripts, $head); + $alertstr = ''; + foreach ($alrts as $str => $num) + $alertstr .= "
$str
"; + $body = str_replace($alert_marker, $alertstr, $body); + $all = $head; $all .= trm_force($body); $all .= trm($pg); @@ -612,7 +622,7 @@ function gopage($info, $data, $pagefun, $page, $menu, $name, $user, $ispage = tr else $elapsed = microtime(true) - $stt; - $foot = trm_force(str_replace('Z/', number_format($elapsed, 4), $foot)); + $foot = trm_force(str_replace($elapsed_marker, number_format($elapsed, 4).'s', $foot)); usleep(100000); diff --git a/pool/page_api.php b/pool/page_api.php index 7e43d28a..7a228201 100644 --- a/pool/page_api.php +++ b/pool/page_api.php @@ -23,6 +23,7 @@ function show_api($info, $page, $menu, $name, $user) $ans = getAtts($u, 'KAPIKey.str'); if ($ans['STATUS'] != 'ok') no_api($jfu); + # TODO: pass $api to ckdb to produce an invalid Key event if (!isset($ans['KAPIKey.str'])) no_api($jfu); if ($ans['KAPIKey.str'] != $api) diff --git a/pool/page_reg.php b/pool/page_reg.php index 5b50894f..bbdf2363 100644 --- a/pool/page_reg.php +++ b/pool/page_reg.php @@ -1,6 +1,5 @@ mvalue; add_to_ktree_nolock(msgline->trf_root, t_item); k_add_head_nolock(msgline->trf_store, t_item); } @@ -2991,6 +3052,30 @@ static enum cmd_values breakdown(K_ITEM **ml_item, char *buf, tv_t *now, } } free(cmdptr); + + if (!seqall && ip) { + bool alert = false; + K_WLOCK(ips_free); + i_item = find_ips(IPS_GROUP_BAN, ip); + if (i_item) { + DATA_IPS(ips, i_item); + // Has the ban expired? + if ((int)tvdiff(now, &(ips->createdate)) > ips->lifetime) { + remove_from_ktree(ips_root, i_item); + k_unlink_item(ips_store, i_item); + if (ips->description) { + LIST_MEM_SUB(ips_free, ips->description); + FREENULL(ips->description); + } + k_add_head(ips_free, i_item); + } else + alert = true; + } + K_WUNLOCK(ips_free); + if (alert) + return CMD_ALERT; + } + return ckdb_cmds[msgline->which_cmds].cmd_val; nogood: if (t_item) { @@ -4113,7 +4198,7 @@ static void *socketer(__maybe_unused void *arg) proc_instance_t *pi = (proc_instance_t *)arg; pthread_t clis_pt, blis_pt; unixsock_t *us = &pi->us; - char *end, *ans = NULL, *rep = NULL, *buf = NULL; + char *end, *ans = NULL, *rep = NULL, *buf = NULL, *tmp; enum cmd_values cmdnum; int sockd; K_ITEM *wq_item = NULL, *ml_item = NULL; @@ -4184,6 +4269,15 @@ static void *socketer(__maybe_unused void *arg) now.tv_sec); send_unix_msg(sockd, reply); break; + case CMD_ALERT: + snprintf(reply, sizeof(reply), + "%s.%ld.failed.ERR", + msgline->id, + now.tv_sec); + tmp = reply_event(EVENTID_MAX, reply); + send_unix_msg(sockd, tmp); + FREENULL(tmp); + break; case CMD_TERMINATE: LOGWARNING("Listener received" " terminate message," @@ -4506,6 +4600,7 @@ static void reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) switch (cmdnum) { // Ignore case CMD_REPLY: + case CMD_ALERT: break; // Shouldn't be there case CMD_TERMINATE: @@ -5659,6 +5754,8 @@ static void check_restore_dir(char *name) } static struct option long_options[] = { + // script to call when alerts happen + { "alert", required_argument, 0, 'c' }, { "config", required_argument, 0, 'c' }, { "dbname", required_argument, 0, 'd' }, { "free", required_argument, 0, 'f' }, @@ -5715,8 +5812,11 @@ int main(int argc, char **argv) memset(&ckp, 0, sizeof(ckp)); ckp.loglevel = LOG_NOTICE; - while ((c = getopt_long(argc, argv, "c:d:ghi:kl:mM:n:p:P:r:R:s:S:t:u:U:vw:yY:", long_options, &i)) != -1) { + 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': + ckdb_alert_cmd = strdup(optarg); + break; case 'c': ckp.config = strdup(optarg); break; diff --git a/src/ckdb.h b/src/ckdb.h index 1af2ed49..419dc45c 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.923" +#define CKDB_VERSION DB_VERSION"-1.930" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -353,6 +353,8 @@ extern cklock_t last_lock; #define STR_SHAREERRORS "shareerror" #define STR_AGEWORKINFO "ageworkinfo" +extern char *ckdb_alert_cmd; + extern char *btc_server; extern char *btc_auth; extern int btc_timeout; @@ -627,6 +629,7 @@ enum cmd_values { CMD_UNSET, CMD_DUPSEQ, // Ignore, we've already got it CMD_REPLY, // Means something was wrong - send back reply + CMD_ALERT, // Means reply with the buf passed CMD_TERMINATE, CMD_PING, CMD_VERSION, @@ -804,6 +807,16 @@ enum cmd_values { _row->expirydate.tv_usec = default_expiry.tv_usec; \ } while (0) +#define HISTORYDATEDEFAULT(_row, _cd) do { \ + _row->createdate.tv_sec = (_cd)->tv_sec; \ + _row->createdate.tv_usec = (_cd)->tv_usec; \ + STRNCPY(_row->createby, by_default); \ + STRNCPY(_row->createcode, (char *)__func__); \ + STRNCPY(_row->createinet, inet_default); \ + _row->expirydate.tv_sec = default_expiry.tv_sec; \ + _row->expirydate.tv_usec = default_expiry.tv_usec; \ + } while (0) + /* Override _row defaults if transfer fields are present * We don't care about the reply so it can be small */ #define HISTORYDATETRANSFER(_root, _row) do { \ @@ -815,17 +828,17 @@ enum cmd_values { __item = optional_name(_root, BYTRF, 1, NULL, __reply, __siz); \ if (__item) { \ DATA_TRANSFER(__transfer, __item); \ - STRNCPY(_row->createby, __transfer->mvalue); \ + STRNCPY((_row)->createby, __transfer->mvalue); \ } \ __item = optional_name(_root, CODETRF, 1, NULL, __reply, __siz); \ if (__item) { \ DATA_TRANSFER(__transfer, __item); \ - STRNCPY(_row->createcode, __transfer->mvalue); \ + STRNCPY((_row)->createcode, __transfer->mvalue); \ } \ __item = optional_name(_root, INETTRF, 1, NULL, __reply, __siz); \ if (__item) { \ DATA_TRANSFER(__transfer, __item); \ - STRNCPY(_row->createinet, __transfer->mvalue); \ + STRNCPY((_row)->createinet, __transfer->mvalue); \ } \ } \ } while (0) @@ -1935,25 +1948,109 @@ extern K_LIST *process_pplns_free; * UserAtts can also at the user level */ #define SHIFTS_SETTING_NAME "ShiftsPageSize" -/* -// EVENTLOG -typedef struct eventlog { - int64_t eventlogid; - char poolinstance[TXT_BIG+1]; - char eventlogcode[TXT_SML+1]; - char *eventlogdescription; +// IPS +typedef struct ips { + char group[TXT_SML+1]; + char ip[TXT_MED+1]; + int lifetime; + bool log; + char *description; HISTORYDATECONTROLFIELDS; -} EVENTLOG; - -#define ALLOC_EVENTLOG 100 -#define LIMIT_EVENTLOG 0 -#define INIT_EVENTLOG(_item) INIT_GENERIC(_item, eventlog) -#define DATA_EVENTLOG(_var, _item) DATA_GENERIC(_var, _item, eventlog, true) - -extern K_TREE *eventlog_root; -extern K_LIST *eventlog_free; -extern K_STORE *eventlog_store; -*/ +} IPS; + +#define ALLOC_IPS 16 +#define LIMIT_IPS 0 +#define INIT_IPS(_item) INIT_GENERIC(_item, ips) +#define DATA_IPS(_var, _item) DATA_GENERIC(_var, _item, ips, true) +#define DATA_IPS_NULL(_var, _item) DATA_GENERIC(_var, _item, ips, false) + +extern K_TREE *ips_root; +extern K_LIST *ips_free; +extern K_STORE *ips_store; + +#define IPS_GROUP_OK "OK" +#define IPS_GROUP_BAD "BAD" +#define IPS_GROUP_BAN "BAN" + +// EVENTS RAM only +typedef struct events { + int id; + // class C truncated version of createinet or full IP for IPv6 + char ipc[TXT_MED+1]; + // check for repeated use of the same hash + char hash[TXT_BIG+1]; + // How many trees the item is still in + int trees; + // who: createby, createinet, createdate + HISTORYDATECONTROLFIELDS; +} EVENTS; + +#define ALLOC_EVENTS 1000 +#define LIMIT_EVENTS 0 +#define INIT_EVENTS(_item) INIT_GENERIC(_item, events) +#define DATA_EVENTS(_var, _item) DATA_GENERIC(_var, _item, events, true) +#define DATA_EVENTS_NULL(_var, _item) DATA_GENERIC(_var, _item, events, false) + +extern K_TREE *events_user_root; +extern K_TREE *events_ip_root; +extern K_TREE *events_ipc_root; +extern K_TREE *events_hash_root; +extern K_LIST *events_free; +extern K_STORE *events_store; + +// Any password failure +#define EVENTID_PASSFAIL 0 +// Add/Change address +#define EVENTID_CREADDR 1 +// Create an account +#define EVENTID_CREACC 2 +// API unkatts +#define EVENTID_UNKATTS 3 +// 2FA rubbish +#define EVENTID_INV2FA 4 +// 2FA incorrect +#define EVENTID_WRONG2FA 5 +// Attempt change to an invalid BTC address (that required btcd checking) +#define EVENTID_INVBTC 6 +// Attempt change to an incorrect BTC address (that failed the ckdb test) +#define EVENTID_INCBTC 7 +// Attempt change to another used BTC address +#define EVENTID_BTCUSED 8 +// Auto create account +#define EVENTID_AUTOACC 9 +// Unknown auth username +#define EVENTID_INVAUTH 10 +// Unknown chkpass username +#define EVENTID_INVUSER 11 +#define EVENTID_MAX 11 + +#define EVENT_OK -1 + +/* 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 + * i.e. a single user will not fail the test result for ip */ +typedef struct event_limits { + int id; + int user_low_time; + // how many in above limit = ok (+1 = alert) + int user_low_time_limit; + int user_hi_time; + // how many in above limit = ok (+1 = alert) + int user_hi_time_limit; + int ip_low_time; + // how many in above limit = ok (+1 = alert) + int ip_low_time_limit; + int ip_hi_time; + // how many in above limit = ok (+1 = alert) + int ip_hi_time_limit; + // expire events + int lifetime; +} EVENT_LIMITS; + +extern EVENT_LIMITS e_limits[]; +// Has a fixed limit of 1 event allowed +extern int event_limits_hash_lifetime; // AUTHS authorise.id.json={...} typedef struct auths { @@ -2650,6 +2747,20 @@ extern K_ITEM *find_payoutid(int64_t payoutid); extern K_ITEM *find_payouts_wid(int64_t workinfoidend, K_TREE_CTX *ctx); extern double payout_stats(PAYOUTS *payouts, char *statname); extern bool process_pplns(int32_t height, char *blockhash, tv_t *now); +extern cmp_t cmp_ips(K_ITEM *a, K_ITEM *b); +extern K_ITEM *find_ips(char *group, char *ip); +extern cmp_t cmp_events_user(K_ITEM *a, K_ITEM *b); +extern cmp_t cmp_events_ip(K_ITEM *a, K_ITEM *b); +extern cmp_t cmp_events_ipc(K_ITEM *a, K_ITEM *b); +extern cmp_t cmp_events_hash(K_ITEM *a, K_ITEM *b); +extern K_ITEM *last_events_user(int id, char *user, K_TREE_CTX *ctx); +extern K_ITEM *last_events_ip(int id, char *ip, K_TREE_CTX *ctx); +extern K_ITEM *last_events_ipc(int id, char *ipc, K_TREE_CTX *ctx); +extern K_ITEM *last_events_hash(int id, char *hash, K_TREE_CTX *ctx); +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_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); @@ -2755,7 +2866,8 @@ extern int64_t nextid(PGconn *conn, char *idname, int64_t increment, tv_t *cd, char *by, char *code, char *inet); extern bool users_update(PGconn *conn, K_ITEM *u_item, char *oldhash, char *newhash, char *email, char *by, char *code, - char *inet, tv_t *cd, K_TREE *trf_root, char *status); + char *inet, tv_t *cd, K_TREE *trf_root, char *status, + int *event); extern K_ITEM *users_add(PGconn *conn, char *username, char *emailaddress, char *passwordhash, int64_t userbits, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root); @@ -2857,11 +2969,15 @@ 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 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, char *workername, char *clientid, char *enonce1, char *useragent, char *preauth, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root, - bool addressuser, USERS **users, WORKERS **workers); + bool addressuser, USERS **users, WORKERS **workers, + int *event); extern bool poolstats_add(PGconn *conn, bool store, char *poolinstance, char *elapsed, char *users, char *workers, char *hashrate, char *hashrate5m, diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index 624daf35..a11595dc 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -39,6 +39,7 @@ static char *cmd_adduser(PGconn *conn, char *cmd, char *id, tv_t *now, char *by, char reply[1024] = ""; size_t siz = sizeof(reply); K_ITEM *i_username, *i_emailaddress, *i_passwordhash, *u_item = NULL; + int event = EVENT_OK; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); @@ -66,15 +67,18 @@ static char *cmd_adduser(PGconn *conn, char *cmd, char *id, tv_t *now, char *by, if (!i_passwordhash) return strdup(reply); - u_item = users_add(conn, transfer_data(i_username), - transfer_data(i_emailaddress), - transfer_data(i_passwordhash), 0, - by, code, inet, now, trf_root); + event = events_add(EVENTID_CREACC, trf_root); + if (event == EVENT_OK) { + u_item = users_add(conn, transfer_data(i_username), + transfer_data(i_emailaddress), + transfer_data(i_passwordhash), 0, + by, code, inet, now, trf_root); + } } if (!u_item) { LOGERR("%s() %s.failed.DBE", __func__, id); - return strdup("failed.DBE"); + return reply_event(event, "failed.DBE"); } LOGDEBUG("%s.ok.added %s", id, transfer_data(i_username)); snprintf(reply, siz, "ok.added %s", transfer_data(i_username)); @@ -88,6 +92,7 @@ static char *cmd_newpass(__maybe_unused PGconn *conn, char *cmd, char *id, K_ITEM *i_username, *i_oldhash, *i_newhash, *i_2fa, *u_item; char reply[1024] = ""; size_t siz = sizeof(reply); + int event = EVENT_OK; bool ok = true; char *oldhash; int32_t value; @@ -112,8 +117,10 @@ static char *cmd_newpass(__maybe_unused PGconn *conn, char *cmd, char *id, } i_2fa = require_name(trf_root, "2fa", 1, (char *)intpatt, reply, siz); - if (!i_2fa) - return strdup(reply); + if (!i_2fa) { + event = events_add(EVENTID_INV2FA, trf_root); + return reply_event(event, reply); + } if (ok) { i_newhash = require_name(trf_root, "newhash", @@ -131,6 +138,8 @@ static char *cmd_newpass(__maybe_unused PGconn *conn, char *cmd, char *id, if (USER_TOTP_ENA(users)) { value = (int32_t)atoi(transfer_data(i_2fa)); ok = check_2fa(users, value); + if (!ok) + event = events_add(EVENTID_WRONG2FA, trf_root); } if (ok) { ok = users_update(NULL, @@ -140,7 +149,7 @@ static char *cmd_newpass(__maybe_unused PGconn *conn, char *cmd, char *id, NULL, by, code, inet, now, trf_root, - NULL); + NULL, &event); } } else ok = false; @@ -148,7 +157,7 @@ static char *cmd_newpass(__maybe_unused PGconn *conn, char *cmd, char *id, if (!ok) { LOGERR("%s.failed.%s", id, transfer_data(i_username)); - return strdup("failed."); + return reply_event(event, "failed."); } LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); return strdup("ok."); @@ -162,6 +171,7 @@ static char *cmd_chkpass(__maybe_unused PGconn *conn, char *cmd, char *id, K_ITEM *i_username, *i_passwordhash, *i_2fa, *u_item; char reply[1024] = ""; size_t siz = sizeof(reply); + int event = EVENT_OK; USERS *users; bool ok; @@ -177,27 +187,34 @@ static char *cmd_chkpass(__maybe_unused PGconn *conn, char *cmd, char *id, return strdup(reply); i_2fa = require_name(trf_root, "2fa", 1, (char *)intpatt, reply, siz); - if (!i_2fa) - return strdup(reply); + if (!i_2fa) { + event = events_add(EVENTID_INV2FA, trf_root); + return reply_event(event, reply); + } K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); K_RUNLOCK(users_free); - if (!u_item) + if (!u_item) { + event = events_add(EVENTID_INVUSER, trf_root); ok = false; - else { + } else { DATA_USERS(users, u_item); ok = check_hash(users, transfer_data(i_passwordhash)); + if (!ok) + event = events_add(EVENTID_PASSFAIL, trf_root); if (ok && USER_TOTP_ENA(users)) { uint32_t value = (int32_t)atoi(transfer_data(i_2fa)); ok = check_2fa(users, value); + if (!ok) + event = events_add(EVENTID_WRONG2FA, trf_root); } } if (!ok) { LOGERR("%s.failed.%s", id, transfer_data(i_username)); - return strdup("failed."); + return reply_event(event, "failed."); } LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); return strdup("ok."); @@ -210,6 +227,7 @@ static char *cmd_2fa(__maybe_unused PGconn *conn, char *cmd, char *id, K_ITEM *i_username, *i_action, *i_entropy, *i_value, *u_item, *u_new; char reply[1024] = ""; size_t siz = sizeof(reply); + int event = EVENT_OK; size_t len, off; char tmp[1024]; int32_t entropy, value; @@ -241,8 +259,10 @@ static char *cmd_2fa(__maybe_unused PGconn *conn, char *cmd, char *id, // Field always expected, use 0 if not required i_value = require_name(trf_root, "value", 1, (char *)intpatt, reply, siz); - if (!i_value) - return strdup(reply); + if (!i_value) { + event = events_add(EVENTID_INV2FA, trf_root); + return reply_event(event, reply); + } K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); @@ -328,6 +348,7 @@ static char *cmd_2fa(__maybe_unused PGconn *conn, char *cmd, char *id, goto dame; value = (int32_t)atoi(transfer_data(i_value)); if (!check_2fa(users, value)) { + event = events_add(EVENTID_WRONG2FA, trf_root); sfa_error = "Invalid code"; // Report sfa_error to web ok = true; @@ -350,6 +371,7 @@ static char *cmd_2fa(__maybe_unused PGconn *conn, char *cmd, char *id, // remove requires value value = (int32_t)atoi(transfer_data(i_value)); if (!check_2fa(users, value)) { + event = events_add(EVENTID_WRONG2FA, trf_root); sfa_error = "Invalid code"; // Report sfa_error to web ok = true; @@ -428,7 +450,7 @@ dame: // Only db/php/code errors should get here LOGERR("%s.failed.%s-%s", id, transfer_data(i_username), action); FREENULL(buf); - return strdup("failed."); + return reply_event(event, "failed."); } snprintf(tmp, sizeof(tmp), "2fa_status=%s%c2fa_error=%s%c2fa_msg=%s", @@ -436,7 +458,7 @@ dame: sfa_msg); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.%s-%s.%s", id, transfer_data(i_username), action, buf); - return buf; + return reply_event_free(event, buf); } static char *cmd_userset(PGconn *conn, char *cmd, char *id, @@ -451,6 +473,7 @@ static char *cmd_userset(PGconn *conn, char *cmd, char *id, char *email, *address, *payname; char reply[1024] = ""; size_t siz = sizeof(reply); + int event = EVENT_OK; char tmp[1024]; PAYMENTADDRESSES *row, *pa; K_STORE *pa_store = NULL; @@ -479,6 +502,7 @@ static char *cmd_userset(PGconn *conn, char *cmd, char *id, K_RUNLOCK(users_free); if (!u_item) { + event = events_add(EVENTID_UNKATTS, trf_root); reason = "Unknown user"; goto struckout; } else { @@ -548,11 +572,13 @@ static char *cmd_userset(PGconn *conn, char *cmd, char *id, i_2fa = require_name(trf_root, "2fa", 1, (char *)intpatt, reply, siz); if (!i_2fa) { + event = events_add(EVENTID_INV2FA, trf_root); reason = "Invalid data"; goto struckout; } if (!check_hash(users, transfer_data(i_passwordhash))) { + event = events_add(EVENTID_PASSFAIL, trf_root); reason = "Incorrect password"; goto struckout; } @@ -560,6 +586,7 @@ static char *cmd_userset(PGconn *conn, char *cmd, char *id, if (USER_TOTP_ENA(users)) { uint32_t value = (int32_t)atoi(transfer_data(i_2fa)); if (!check_2fa(users, value)) { + event = events_add(EVENTID_WRONG2FA, trf_root); reason = "Invalid data"; goto struckout; } @@ -624,6 +651,8 @@ static char *cmd_userset(PGconn *conn, char *cmd, char *id, reply, siz); if (!i_address) { K_WUNLOCK(paymentaddresses_free); + event = events_add(EVENTID_INCBTC, + trf_root); reason = "Invalid address"; goto struckout; } @@ -679,10 +708,14 @@ static char *cmd_userset(PGconn *conn, char *cmd, char *id, * payout address */ DATA_PAYMENTADDRESSES(pa, old_pa_item); if (pa->userid != users->userid) { + event = events_add(EVENTID_BTCUSED, + trf_root); reason = "Unavailable BTC address"; goto struckout; } } else if (!btc_valid_address(row->payaddress)) { + event = events_add(EVENTID_INVBTC, + trf_root); reason = "Invalid BTC address"; goto struckout; } @@ -696,7 +729,7 @@ static char *cmd_userset(PGconn *conn, char *cmd, char *id, email, by, code, inet, now, trf_root, - NULL); + NULL, &event); if (!ok) { reason = "email error"; goto struckout; @@ -728,16 +761,22 @@ struckout: pa_store = NULL; } if (reason) { + char *user, *st = NULL; snprintf(reply, siz, "ERR.%s", reason); - LOGERR("%s.%s.%s", cmd, id, reply); - return strdup(reply); + if (i_username) + user = st = safe_text(transfer_data(i_username)); + else + user = EMPTY; + LOGERR("%s.%s.%s (%s)", cmd, id, reply, user); + FREENULL(st); + return reply_event(event, reply); } APPEND_REALLOC_INIT(ret, off, len); APPEND_REALLOC(ret, off, len, "ok."); APPEND_REALLOC(ret, off, len, answer); free(answer); LOGDEBUG("%s.%s", id, ret); - return ret; + return reply_event_free(event, ret); } static char *cmd_workerset(PGconn *conn, char *cmd, char *id, tv_t *now, @@ -2894,6 +2933,7 @@ static char *cmd_auth_do(PGconn *conn, char *cmd, char *id, char *by, K_TREE_CTX ctx[1]; char reply[1024] = ""; size_t siz = sizeof(reply); + int event = EVENT_OK; K_ITEM *i_poolinstance, *i_username, *i_workername, *i_clientid; K_ITEM *i_enonce1, *i_useragent, *i_preauth, *u_item, *oc_item, *w_item; USERS *users = NULL; @@ -2902,7 +2942,7 @@ static char *cmd_auth_do(PGconn *conn, char *cmd, char *id, char *by, OPTIONCONTROL *optioncontrol; size_t len, off; char *buf; - bool ok, first; + bool ok = true, first; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); @@ -2958,26 +2998,33 @@ static char *cmd_auth_do(PGconn *conn, char *cmd, char *id, char *by, u_item = find_users(username); K_RUNLOCK(users_free); if (!u_item) { - DATA_OPTIONCONTROL(optioncontrol, oc_item); - u_item = users_add(conn, username, EMPTY, - optioncontrol->optionvalue, 0, - by, code, inet, cd, trf_root); + event = events_add(EVENTID_AUTOACC, trf_root); + if (event == EVENT_OK) { + DATA_OPTIONCONTROL(optioncontrol, oc_item); + u_item = users_add(conn, username, EMPTY, + optioncontrol->optionvalue, + 0, by, code, inet, cd, + trf_root); + } else + ok = false; } } - ok = auths_add(conn, transfer_data(i_poolinstance), - username, - transfer_data(i_workername), - transfer_data(i_clientid), - transfer_data(i_enonce1), - transfer_data(i_useragent), - transfer_data(i_preauth), - by, code, inet, cd, trf_root, false, - &users, &workers); + if (ok) { + ok = auths_add(conn, transfer_data(i_poolinstance), + username, + transfer_data(i_workername), + transfer_data(i_clientid), + transfer_data(i_enonce1), + transfer_data(i_useragent), + transfer_data(i_preauth), + by, code, inet, cd, trf_root, false, + &users, &workers, &event); + } if (!ok) { LOGDEBUG("%s() %s.failed.DBE", __func__, id); - return strdup("failed.DBE"); + return reply_event(event, "failed.DBE"); } // Only flag a successful auth @@ -3042,6 +3089,7 @@ static char *cmd_addrauth_do(PGconn *conn, char *cmd, char *id, char *by, K_TREE_CTX ctx[1]; char reply[1024] = ""; size_t siz = sizeof(reply); + int event = EVENT_OK; K_ITEM *i_poolinstance, *i_username, *i_workername, *i_clientid; K_ITEM *i_enonce1, *i_useragent, *i_preauth, *w_item; USERS *users = NULL; @@ -3105,11 +3153,11 @@ static char *cmd_addrauth_do(PGconn *conn, char *cmd, char *id, char *by, transfer_data(i_useragent), transfer_data(i_preauth), by, code, inet, cd, trf_root, true, - &users, &workers); + &users, &workers, &event); if (!ok) { LOGDEBUG("%s() %s.failed.DBE", __func__, id); - return strdup("failed.DBE"); + return reply_event(event, "failed.DBE"); } // Only flag a successful auth @@ -3352,7 +3400,7 @@ static char *cmd_homepage(__maybe_unused PGconn *conn, char *cmd, char *id, pool.shareinv, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); - // TODO: assumes only one poolinstance (for now) + // TODO: DB only has one poolinstance with -i K_RLOCK(poolstats_free); p_item = last_in_ktree(poolstats_root, ctx); K_RUNLOCK(poolstats_free); @@ -3524,6 +3572,7 @@ static char *cmd_getatts(__maybe_unused PGconn *conn, char *cmd, char *id, K_ITEM *i_username, *i_attlist, *u_item, *ua_item; char reply[1024] = ""; size_t siz = sizeof(reply); + int event = EVENT_OK; char tmp[1024]; USERATTS *useratts; USERS *users; @@ -3538,6 +3587,7 @@ static char *cmd_getatts(__maybe_unused PGconn *conn, char *cmd, char *id, i_username = require_name(trf_root, "username", MIN_USERNAME, (char *)userpatt, reply, siz); if (!i_username) { + // Shouldn't happen except with a code problem no event required reason = "Missing username"; goto nuts; } @@ -3547,6 +3597,8 @@ static char *cmd_getatts(__maybe_unused PGconn *conn, char *cmd, char *id, K_RUNLOCK(users_free); if (!u_item) { + // page_api.php without a valid username + event = events_add(EVENTID_UNKATTS, trf_root); reason = "Unknown user"; goto nuts; } else { @@ -3639,7 +3691,7 @@ nuts: free(answer); snprintf(reply, siz, "ERR.%s", reason); LOGERR("%s.%s.%s", cmd, id, reply); - return strdup(reply); + return reply_event(event, reply); } snprintf(reply, siz, "ok.%s", answer); LOGDEBUG("%s.%s", id, answer); @@ -5871,7 +5923,7 @@ static char *cmd_userstatus(PGconn *conn, char *cmd, char *id, tv_t *now, char * NULL, by, code, inet, now, trf_root, - status); + status, NULL); } if (!ok) { diff --git a/src/ckdb_data.c b/src/ckdb_data.c index 8ece1ba7..8c90d4b0 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -425,7 +425,7 @@ void _txt_to_tv(char *nam, char *fld, tv_t *data, size_t siz, WHERE_FFL_ARGS) _txt_to_data(TYPE_TV, nam, fld, (void *)data, siz, WHERE_FFL_PASS); } -// Convert msg S,nS to tv_t +// Convert msg S[,nS] to tv_t void _txt_to_ctv(char *nam, char *fld, tv_t *data, size_t siz, WHERE_FFL_ARGS) { _txt_to_data(TYPE_CTV, nam, fld, (void *)data, siz, WHERE_FFL_PASS); @@ -4273,6 +4273,540 @@ oku: return ok; } +// order by group asc,ip asc,expirydate desc +cmp_t cmp_ips(K_ITEM *a, K_ITEM *b) +{ + IPS *ia, *ib; + DATA_IPS(ia, a); + DATA_IPS(ib, b); + cmp_t c = CMP_STR(ia->group, ib->group); + if (c == 0) { + c = CMP_STR(ia->ip, ib->ip); + if (c == 0) + c = CMP_TV(ib->expirydate, ia->expirydate); + } + return c; +} + +// Must be R or W locked before call +K_ITEM *find_ips(char *group, char *ip) +{ + K_TREE_CTX ctx[1]; + K_ITEM look; + IPS ips; + + STRNCPY(ips.group, group); + STRNCPY(ips.ip, ip); + ips.expirydate.tv_sec = default_expiry.tv_sec; + ips.expirydate.tv_usec = default_expiry.tv_usec; + + INIT_IPS(&look); + look.data = (void *)(&ips); + return find_in_ktree(ips_root, &look, ctx); +} + +// order by createby asc,id asc,expirydate desc, createdate asc +cmp_t cmp_events_user(K_ITEM *a, K_ITEM *b) +{ + EVENTS *ea, *eb; + DATA_EVENTS(ea, a); + DATA_EVENTS(eb, b); + cmp_t c = CMP_STR(ea->createby, eb->createby); + if (c == 0) { + c = CMP_INT(ea->id, eb->id); + if (c == 0) { + c = CMP_TV(eb->expirydate, ea->expirydate); + if (c == 0) + c = CMP_TV(ea->createdate, eb->createdate); + } + } + return c; +} + +// order by createinet asc,id asc,expirydate desc, createdate asc +cmp_t cmp_events_ip(K_ITEM *a, K_ITEM *b) +{ + EVENTS *ea, *eb; + DATA_EVENTS(ea, a); + DATA_EVENTS(eb, b); + cmp_t c = CMP_STR(ea->createinet, eb->createinet); + if (c == 0) { + c = CMP_INT(ea->id, eb->id); + if (c == 0) { + c = CMP_TV(eb->expirydate, ea->expirydate); + if (c == 0) + c = CMP_TV(ea->createdate, eb->createdate); + } + } + return c; +} + +// order by ipc asc,id asc,expirydate desc, createdate asc +cmp_t cmp_events_ipc(K_ITEM *a, K_ITEM *b) +{ + EVENTS *ea, *eb; + DATA_EVENTS(ea, a); + DATA_EVENTS(eb, b); + cmp_t c = CMP_STR(ea->ipc, eb->ipc); + if (c == 0) { + c = CMP_INT(ea->id, eb->id); + if (c == 0) { + c = CMP_TV(eb->expirydate, ea->expirydate); + if (c == 0) + c = CMP_TV(ea->createdate, eb->createdate); + } + } + return c; +} + +// order by hash asc,id asc,expirydate desc, createdate asc +cmp_t cmp_events_hash(K_ITEM *a, K_ITEM *b) +{ + EVENTS *ea, *eb; + DATA_EVENTS(ea, a); + DATA_EVENTS(eb, b); + cmp_t c = CMP_STR(ea->hash, eb->hash); + if (c == 0) { + c = CMP_INT(ea->id, eb->id); + if (c == 0) { + c = CMP_TV(eb->expirydate, ea->expirydate); + if (c == 0) + c = CMP_TV(ea->createdate, eb->createdate); + } + } + return c; +} + +K_ITEM *last_events_user(int id, char *user, K_TREE_CTX *ctx) +{ + K_TREE_CTX ctx0[1]; + EVENTS events; + K_ITEM look; + + if (ctx == NULL) + ctx = ctx0; + + events.id = id; + STRNCPY(events.createby, user); + copy_tv(&(events.expirydate), &default_expiry); + copy_tv(&(events.createdate), &date_eot); + + INIT_EVENTS(&look); + look.data = (void *)(&events); + return find_before_in_ktree(events_user_root, &look, ctx); +} + +K_ITEM *last_events_ip(int id, char *ip, K_TREE_CTX *ctx) +{ + K_TREE_CTX ctx0[1]; + EVENTS events; + K_ITEM look; + + if (ctx == NULL) + ctx = ctx0; + + events.id = id; + STRNCPY(events.createinet, ip); + copy_tv(&(events.expirydate), &default_expiry); + copy_tv(&(events.createdate), &date_eot); + + INIT_EVENTS(&look); + look.data = (void *)(&events); + return find_before_in_ktree(events_ip_root, &look, ctx); +} + +K_ITEM *last_events_ipc(int id, char *ipc, K_TREE_CTX *ctx) +{ + K_TREE_CTX ctx0[1]; + EVENTS events; + K_ITEM look; + + if (ctx == NULL) + ctx = ctx0; + + events.id = id; + STRNCPY(events.ipc, ipc); + copy_tv(&(events.expirydate), &default_expiry); + copy_tv(&(events.createdate), &date_eot); + + INIT_EVENTS(&look); + look.data = (void *)(&events); + return find_before_in_ktree(events_ipc_root, &look, ctx); +} + +K_ITEM *last_events_hash(int id, char *hash, K_TREE_CTX *ctx) +{ + K_TREE_CTX ctx0[1]; + EVENTS events; + K_ITEM look; + + if (ctx == NULL) + ctx = ctx0; + + events.id = id; + STRNCPY(events.hash, hash); + copy_tv(&(events.expirydate), &default_expiry); + copy_tv(&(events.createdate), &date_eot); + + INIT_EVENTS(&look); + look.data = (void *)(&events); + return find_before_in_ktree(events_hash_root, &look, ctx); +} + +enum event_cause { + CAUSE_NONE, + CAUSE_USER_LO, + CAUSE_USER_HI, + CAUSE_IP_LO, + CAUSE_IP_HI, + CAUSE_IPC_LO, + CAUSE_IPC_HI, + CAUSE_HASH +}; + +static const char *cause_none = "None"; +static const char *cause_user_lo = "User Low"; +static const char *cause_user_hi = "User High"; +static const char *cause_ip_lo = "IP Low"; +static const char *cause_ip_hi = "IP High"; +static const char *cause_ipc_lo = "IP C-Class Low"; +static const char *cause_ipc_hi = "IP C-Class Hi"; +static const char *cause_hash = "Hash"; +static const char *cause_unknown = "Unknown?"; + +static const char *cause_str(enum event_cause cause) +{ + switch (cause) { + case CAUSE_NONE: + return cause_none; + case CAUSE_USER_LO: + return cause_user_lo; + case CAUSE_USER_HI: + return cause_user_hi; + case CAUSE_IP_LO: + return cause_ip_lo; + case CAUSE_IP_HI: + return cause_ip_hi; + case CAUSE_IPC_LO: + return cause_ipc_lo; + case CAUSE_IPC_HI: + return cause_ipc_hi; + case CAUSE_HASH: + return cause_hash; + default: + return cause_unknown; + } +} + +// return EVENT_OK or timeout seconds +int check_events(EVENTS *events) +{ + bool alert = false, user1, user2; + char createby[TXT_SML+1], *st = NULL; + enum event_cause cause = CAUSE_NONE; + K_ITEM *i_item, *e_item = NULL, *tmp_item, *u_item; + K_TREE_CTX ctx[1]; + EVENTS *e = NULL; + int count, secs; + int tyme, limit, lifetime; + pid_t pid; + tv_t now; + + // No way to send an alert, so don't test + if (!ckdb_alert_cmd) + return EVENT_OK; + + K_RLOCK(ips_free); + i_item = find_ips(IPS_GROUP_OK, events->createinet); + if (!i_item) + i_item = find_ips(IPS_GROUP_OK, events->ipc); + K_RUNLOCK(ips_free); + if (i_item) + return EVENT_OK; + + // All tests below always run all full checks to clean up old events + setnow(&now); + K_WLOCK(events_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); + 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; + } + } + } + } + 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; + while (e_item && e->id == events->id && + strcmp(e->createby, events->createby) == 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_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); + } + 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; + } + 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; + } + e_item = prev_in_ktree(ctx); + } + } + // 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; + } + 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; + } + e_item = prev_in_ktree(ctx); + } + /* 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; + } + 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; + } + e_item = prev_in_ktree(ctx); + } + /* 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_WUNLOCK(events_free); + if (alert) { + LOGERR("%s() ALERT ID:%d Lim:%d Time:%d Life:%d %s '%s' '%s'", + __func__, + events->id, limit, tyme, lifetime, + events->createinet, + st = safe_text_nonull(events->createby), + cause_str(cause)); + FREENULL(st); + ips_add(IPS_GROUP_BAN, events->createinet, + (char *)cause_str(cause), true, false, lifetime); + pid = fork(); + if (pid < 0) { + LOGERR("%s() ALERT failed to fork (%d)", + __func__, errno); + } else { + if (pid == 0) { + char buf1[16], buf2[16], buf3[16], buf4[16]; + snprintf(buf1, sizeof(buf1), "%d", events->id); + snprintf(buf2, sizeof(buf2), "%d", limit); + 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, + buf2, buf3, buf4, events->createinet, + st, cause_str(cause), NULL); + LOGERR("%s() ALERT fork failed to execute (%d)", + __func__, errno); + FREENULL(st); + exit(0); + } + } + 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); + +char *_reply_event(int event, char *buf, bool fre) +{ + size_t len; + char *reply; + + if (event == EVENT_OK) { + if (fre) + return buf; + else { + reply = strdup(buf); + if (!reply) + quithere(1, "strdup OOM"); + return reply; + } + } + + len = strlen(buf); + len += 1 + lurtsiz; + len += toomanysiz + 1; + reply = malloc(len); + if (!reply) + quithere(1, "malloc (%d) OOM", (int)len); + snprintf(reply, len, "%s%c%s%s", buf, FLDSEP, lurt, toomany); + if (fre) + free(buf); + return reply; +} + // 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 1eae592a..eed647f2 100644 --- a/src/ckdb_dbio.c +++ b/src/ckdb_dbio.c @@ -389,10 +389,10 @@ cleanup: return lastid; } -// status was added to the end so type checking intercepts new mistakes bool users_update(PGconn *conn, K_ITEM *u_item, char *oldhash, char *newhash, char *email, char *by, char *code, - char *inet, tv_t *cd, K_TREE *trf_root, char *status) + char *inet, tv_t *cd, K_TREE *trf_root, char *status, + int *event) { ExecStatusType rescode; bool conned = false; @@ -414,8 +414,11 @@ bool users_update(PGconn *conn, K_ITEM *u_item, char *oldhash, DATA_USERS(users, u_item); // i.e. if oldhash == EMPTY, just overwrite with new - if (hash && oldhash != EMPTY && !check_hash(users, oldhash)) + if (hash && oldhash != EMPTY && !check_hash(users, oldhash)) { + if (event) + *event = events_add(EVENTID_PASSFAIL, trf_root); return false; + } K_WLOCK(users_free); item = k_unlink_head(users_free); @@ -6125,12 +6128,180 @@ bool payouts_fill(PGconn *conn) return ok; } +void ips_add(char *group, char *ip, char *des, bool log, bool cclass, int life) +{ + K_ITEM *i_item, *i2_item; + IPS *ips, *ips2; + char *dot; + tv_t now; + bool ok; + + setnow(&now); + 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); + } + 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) +{ + K_ITEM look, *e_item, *i_passwordhash, *i_webtime, *i_username; + K_TREE_CTX ctx[1]; + EVENTS events, *d_events; + char reply[1024] = ""; + size_t siz = sizeof(reply); + + LOGDEBUG("%s(): add", __func__); + + bzero(&events, sizeof(events)); + events.id = id; + events.expirydate.tv_sec = default_expiry.tv_sec; + events.expirydate.tv_usec = default_expiry.tv_usec; + + // Default to now if not specified + setnow(&(events.createdate)); + + if (by) + STRNCPY(events.createby, by); + + if (inet) + STRNCPY(events.createinet, inet); + + if (cd) + copy_tv(&(events.createdate), cd); + + // trf_root values overrides parameters + HISTORYDATETRANSFER(trf_root, &events); + + // username overrides createby + i_username = optional_name(trf_root, "username", 1, NULL, + reply, siz); + if (i_username) + STRNCPY(events.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), + events.createdate); + } + + /* We don't care if it's valid or not, though php should have + * already ensured it's valid */ + i_passwordhash = optional_name(trf_root, "passwordhash", 1, NULL, + reply, siz); + if (i_passwordhash) + STRNCPY(events.hash, transfer_data(i_passwordhash)); + + if (events.createinet[0]) { + char *dot; + STRNCPY(events.ipc, events.createinet); + dot = strchr(events.ipc, '.'); + if (dot) { + dot = strchr(dot+1, '.'); + if (dot) { + dot = strchr(dot+1, '.'); + if (dot) + *dot = '\0'; + } + } + } + + // Only store an event that had actual usable data + if (!events.createby[0] && !events.createinet[0] && + !events.ipc[0] && !events.hash[0]) + return EVENT_OK; + + INIT_EVENTS(&look); + look.data = (void *)(&events); + + // All processing under lock - since we must be able to delete events + K_WLOCK(events_free); + // keep looping incrementing the usec time until it's not a duplicate + while (find_in_ktree(events_user_root, &look, ctx) || + find_in_ktree(events_ip_root, &look, ctx) || + find_in_ktree(events_ipc_root, &look, ctx) || + find_in_ktree(events_hash_root, &look, ctx)) { + if (++events.createdate.tv_usec >= 1000000) { + events.createdate.tv_usec -= 1000000; + events.createdate.tv_sec++; + } + } + e_item = k_unlink_head(events_free); + DATA_EVENTS(d_events, e_item); + COPY_DATA(d_events, &events); + k_add_head(events_store, e_item); + if (d_events->createby[0]) { + add_to_ktree(events_user_root, e_item); + d_events->trees++; + } + if (d_events->createinet[0]) { + add_to_ktree(events_ip_root, e_item); + d_events->trees++; + } + // Don't bother if it's the same as IP + if (d_events->ipc[0] && + strcmp(d_events->ipc, d_events->createinet) != 0) { + add_to_ktree(events_ipc_root, e_item); + d_events->trees++; + } + if (d_events->hash[0]) { + add_to_ktree(events_hash_root, e_item); + d_events->trees++; + } + K_WUNLOCK(events_free); + + return check_events(&events); +} + // TODO: discard them from RAM bool auths_add(PGconn *conn, char *poolinstance, char *username, char *workername, char *clientid, char *enonce1, char *useragent, char *preauth, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root, - bool addressuser, USERS **users, WORKERS **workers) + bool addressuser, USERS **users, WORKERS **workers, + int *event) { K_TREE_CTX ctx[1]; K_ITEM *a_item, *u_item, *w_item; @@ -6161,6 +6332,7 @@ bool auths_add(PGconn *conn, char *poolinstance, char *username, __func__, st = safe_text_nonull(username)); FREENULL(st); + *event = events_add(EVENTID_INVAUTH, trf_root); } if (!u_item) goto unitem;