/* * Copyright 2003-2014 Andrew Smith * Copyright 2014 Con Kolivas * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. See COPYING for more details. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBPQ_FE_H #include #elif defined (HAVE_POSTGRESQL_LIBPQ_FE_H) #include #endif #include "ckpool.h" #include "libckpool.h" #include "klist.h" #include "ktree.h" // TODO: a lot of the tree access isn't locked // will need to be if threading is required static char *db_user; static char *db_pass; // size limit on the command string #define ID_SIZ 31 #define TXT_BIG 256 #define TXT_MED 128 #define TXT_SML 64 #define TXT_FLAG 1 #define FLDSEP 0x02 // Ensure long long and int64_t are both 8 bytes (and thus also the same) #define ASSERT1(condition) __maybe_unused static char sizeof_longlong_must_be_8[(condition)?1:-1] #define ASSERT2(condition) __maybe_unused static char sizeof_int64_t_must_be_8[(condition)?1:-1] ASSERT1(sizeof(long long) == 8); ASSERT2(sizeof(int64_t) == 8); #define PGLOG(__LOG, __str, __rescode, __conn) do { \ char *__ptr, *__buf = strdup(PQerrorMessage(__conn)); \ __ptr = __buf + strlen(__buf) - 1; \ while (__ptr >= __buf && (*__ptr == '\n' || *__ptr == '\r')) \ *(__ptr--) = '\0'; \ while (--__ptr >= __buf) \ if (*__ptr == '\n' || *__ptr == '\r' || *__ptr == '\t') \ *__ptr = ' '; \ __LOG("%s(): %s failed (%d) '%s'", __func__, \ __str, (int)rescode, __buf); \ free(__buf); \ } while (0) #define PGLOGERR(_str, _rescode, _conn) PGLOG(LOGERR, _str, _rescode, _conn) /* N.B. STRNCPY() truncates, whereas txt_to_str() aborts ckdb if src > trg * If copying data from the DB, code should always use txt_to_str() since * data should never be lost/truncated if it came from the DB - * that simply implies a code bug or a database change that must be fixed */ #define STRNCPY(trg, src) do { \ strncpy((char *)(trg), (char *)(src), sizeof(trg)); \ trg[sizeof(trg) - 1] = '\0'; \ } while (0) #define STRNCPYSIZ(trg, src, siz) do { \ strncpy((char *)(trg), (char *)(src), siz); \ trg[siz - 1] = '\0'; \ } while (0) #define APPEND_REALLOC(_dst, _dstoff, _dstsiz, _src) do { \ size_t _srclen = strlen(_src); \ if ((_dstoff) + _srclen >= (_dstsiz)) { \ _dstsiz += 1024; \ _dst = realloc(_dst, _dstsiz); \ if (!(_dst)) \ quithere(1, "realloc (%d) OOM", (int)_dstsiz); \ } \ strcpy((_dst)+(_dstoff), _src); \ _dstoff += _srclen; \ } while(0) enum data_type { TYPE_STR, TYPE_BIGINT, TYPE_INT, TYPE_TV, TYPE_BLOB, TYPE_DOUBLE }; #define TXT_TO_STR(__nam, __fld, __data) txt_to_str(__nam, __fld, (__data), sizeof(__data)) #define TXT_TO_BIGINT(__nam, __fld, __data) txt_to_bigint(__nam, __fld, &(__data), sizeof(__data)) #define TXT_TO_INT(__nam, __fld, __data) txt_to_int(__nam, __fld, &(__data), sizeof(__data)) #define TXT_TO_TV(__nam, __fld, __data) txt_to_tv(__nam, __fld, &(__data), sizeof(__data)) #define TXT_TO_BLOB(__nam, __fld, __data) txt_to_blob(__nam, __fld, __data) #define TXT_TO_DOUBLE(__nam, __fld, __data) txt_to_double(__nam, __fld, &(__data), sizeof(__data)) #define PQ_GET_FLD(__res, __row, __name, __fld, __ok) do { \ int __col = PQfnumber(__res, __name); \ if (__col == -1) { \ LOGERR("%s(): Unknown field '%s' row %d", __func__, __name, __row); \ __ok = false; \ } else \ __fld = PQgetvalue(__res, __row, __col); \ } while (0) // HISTORY FIELDS #define HISTORYDATECONTROL ",createdate,createby,createcode,createinet,expirydate" #define HISTORYDATECOUNT 5 #define HISTORYDATEFLDS(_res, _row, _data, _ok) do { \ char *_fld; \ PQ_GET_FLD(_res, _row, "createdate", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_TV("createdate", _fld, (_data)->createdate); \ PQ_GET_FLD(_res, _row, "createby", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createby", _fld, (_data)->createby); \ PQ_GET_FLD(_res, _row, "createcode", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createcode", _fld, (_data)->createcode); \ PQ_GET_FLD(_res, _row, "createinet", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createinet", _fld, (_data)->createinet); \ PQ_GET_FLD(_res, _row, "expirydate", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_TV("expirydate", _fld, (_data)->expirydate); \ } while (0) #define HISTORYDATECONTROLFIELDS \ tv_t createdate; \ char createby[TXT_SML+1]; \ char createcode[TXT_MED+1]; \ char createinet[TXT_MED+1]; \ tv_t expirydate #define HISTORYDATEPARAMS(_params, _his_pos, _row) do { \ _params[_his_pos++] = tv_to_buf(&(_row->createdate), NULL, 0); \ _params[_his_pos++] = str_to_buf(_row->createby, NULL, 0); \ _params[_his_pos++] = str_to_buf(_row->createcode, NULL, 0); \ _params[_his_pos++] = str_to_buf(_row->createinet, NULL, 0); \ _params[_his_pos++] = tv_to_buf(&(_row->expirydate), NULL, 0); \ } while (0) // 6-Jun-6666 06:06:06+00 #define DEFAULT_EXPIRY 148204965966L // 1-Jun-6666 00:00:00+00 #define COMPARE_EXPIRY 148204512000L static const tv_t default_expiry = { DEFAULT_EXPIRY, 0L }; #define HISTORYDATEINIT(_row, _by, _code, _inet) do { \ setnow(&(_row->createdate)); \ STRNCPY(_row->createby, _by); \ STRNCPY(_row->createcode, _code); \ STRNCPY(_row->createinet, _inet); \ _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 #define HISTORYDATETRANSFER(_row) do { \ K_ITEM *item; \ item = optional_name("createdate", 10, NULL); \ if (item) { \ long sec, usec; \ int n; \ n = sscanf(DATA_TRANSFER(item)->data, "%ld,%ld", &sec, &usec); \ if (n > 0) { \ _row->createdate.tv_sec = (time_t)sec; \ if (n > 1) \ _row->createdate.tv_usec = usec; \ else \ _row->createdate.tv_usec = 0L; \ } \ } \ item = optional_name("createby", 1, NULL); \ if (item) \ STRNCPY(_row->createby, DATA_TRANSFER(item)->data); \ item = optional_name("createcode", 1, NULL); \ if (item) \ STRNCPY(_row->createcode, DATA_TRANSFER(item)->data); \ item = optional_name("createinet", 1, NULL); \ if (item) \ STRNCPY(_row->createinet, DATA_TRANSFER(item)->data); \ } while (0) // MODIFY FIELDS #define MODIFYDATECONTROL ",createdate,createby,createcode,createinet" \ ",modifydate,modifyby,modifycode,modifyinet" #define MODIFYDATECOUNT 8 #define MODIFYDATEFLDS(_res, _row, _data, _ok) do { \ char *_fld; \ PQ_GET_FLD(_res, _row, "createdate", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_TV("createdate", _fld, (_data)->createdate); \ PQ_GET_FLD(_res, _row, "createby", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createby", _fld, (_data)->createby); \ PQ_GET_FLD(_res, _row, "createcode", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createcode", _fld, (_data)->createcode); \ PQ_GET_FLD(_res, _row, "createinet", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createinet", _fld, (_data)->createinet); \ PQ_GET_FLD(_res, _row, "modifydate", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_TV("modifydate", _fld, (_data)->modifydate); \ PQ_GET_FLD(_res, _row, "modifyby", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("modifyby", _fld, (_data)->modifyby); \ PQ_GET_FLD(_res, _row, "modifycode", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("modifycode", _fld, (_data)->modifycode); \ PQ_GET_FLD(_res, _row, "modifyinet", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("modifyinet", _fld, (_data)->modifyinet); \ } while (0) #define MODIFYDATECONTROLFIELDS \ tv_t createdate; \ char createby[TXT_SML+1]; \ char createcode[TXT_MED+1]; \ char createinet[TXT_MED+1]; \ tv_t modifydate; \ char modifyby[TXT_SML+1]; \ char modifycode[TXT_MED+1]; \ char modifyinet[TXT_MED+1] #define MODIFYDATEPARAMS(_params, _mod_pos, _row) do { \ _params[_mod_pos++] = tv_to_buf(&(_row->createdate), NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->createby, NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->createcode, NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->createinet, NULL, 0); \ _params[_mod_pos++] = tv_to_buf(&(_row->modifydate), NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->modifyby, NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->modifycode, NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->modifyinet, NULL, 0); \ } while (0) #define MODIFYDATEINIT(_row, _by, _code, _inet) do { \ setnow(&(_row->createdate)); \ STRNCPY(_row->createby, _by); \ STRNCPY(_row->createcode, _code); \ STRNCPY(_row->createinet, _inet); \ _row->modifydate.tv_sec = 0; \ _row->modifydate.tv_usec = 0; \ _row->modifyby[0] = '\0'; \ _row->modifycode[0] = '\0'; \ _row->modifyinet[0] = '\0'; \ } while (0) // For easy parameter constant strings #define PQPARAM1 "$1" #define PQPARAM2 "$1,$2" #define PQPARAM3 "$1,$2,$3" #define PQPARAM4 "$1,$2,$3,$4" #define PQPARAM5 "$1,$2,$3,$4,$5" #define PQPARAM6 "$1,$2,$3,$4,$5,$6" #define PQPARAM7 "$1,$2,$3,$4,$5,$6,$7" #define PQPARAM8 "$1,$2,$3,$4,$5,$6,$7,$8" #define PQPARAM9 "$1,$2,$3,$4,$5,$6,$7,$8,$9" #define PQPARAM10 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10" #define PQPARAM11 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11" #define PQPARAM12 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12" #define PQPARAM13 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13" #define PQPARAM14 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14" #define PQPARAM15 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15" #define PQPARAM16 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16" #define PQPARAM17 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17" #define PARCHK(_par, _params) do { \ if (_par != (int)(sizeof(_params)/sizeof(_params[0]))) { \ quithere(1, "params[] usage (%d) != size (%d)", \ _par, (int)(sizeof(_params)/sizeof(_params[0]))); \ } \ } while (0) static const char *userpatt = "^[!-~]*$"; // no spaces static const char *mailpatt = "^[A-Za-z0-9_-][A-Za-z0-9_\\.-]*@[A-Za-z0-9][A-Za-z0-9\\.]*[A-Za-z0-9]$"; static const char *idpatt = "^[_A-Za-z][_A-Za-z0-9]*$"; static const char *intpatt = "^[0-9][0-9]*$"; static const char *hashpatt = "^[A-Fa-f0-9]*$"; #define JSON_TRANSFER "json=" #define JSON_TRANSFER_LEN (sizeof(JSON_TRANSFER)-1) // JSON Methods #define METHOD_WORKINFO "workinfo" #define METHOD_SHARES "shares" #define METHOD_SHAREERRORS "shareerror" #define METHOD_AUTH "authorise" // LOGFILE codes - should be in libckpool.h ... with the file logging code #define CODE_WORKINFO "W" #define CODE_SHARES "S" #define CODE_SHAREERRORSS "E" // TRANSFER #define NAME_SIZE 63 #define VALUE_SIZE 1023 typedef struct transfer { char name[NAME_SIZE+1]; char value[VALUE_SIZE+1]; char *data; } TRANSFER; #define ALLOC_TRANSFER 1024 #define LIMIT_TRANSFER 0 #define DATA_TRANSFER(_item) ((TRANSFER *)(_item->data)) static K_TREE *transfer_root; static K_LIST *transfer_list; static K_STORE *transfer_store; // USERS typedef struct users { int64_t userid; char username[TXT_BIG+1]; char emailaddress[TXT_BIG+1]; tv_t joineddate; char passwordhash[TXT_BIG+1]; char secondaryuserid[TXT_SML+1]; HISTORYDATECONTROLFIELDS; } USERS; #define ALLOC_USERS 1024 #define LIMIT_USERS 0 #define DATA_USERS(_item) ((USERS *)(_item->data)) static K_TREE *users_root; static K_TREE *userid_root; static K_LIST *users_list; static K_STORE *users_store; // WORKERS typedef struct workers { int64_t workerid; int64_t userid; char workername[TXT_BIG+1]; // includes username int32_t difficultydefault; char idlenotificationenabled[TXT_FLAG+1]; int32_t idlenotificationtime; HISTORYDATECONTROLFIELDS; } WORKERS; #define ALLOC_WORKERS 1024 #define LIMIT_WORKERS 0 #define DATA_WORKERS(_item) ((WORKERS *)(_item->data)) static K_TREE *workers_root; static K_LIST *workers_list; static K_STORE *workers_store; /* unused yet // PAYMENTADDRESSES typedef struct paymentaddresses { int64_t paymentaddressid; int64_t userid; char payaddress[TXT_BIG+1]; int32_t payratio; HISTORYDATECONTROLFIELDS; } PAYMENTADDRESSES; #define ALLOC_PAYMENTADDRESSES 1024 #define LIMIT_PAYMENTADDRESSES 0 #define DATA_PAYMENTADDRESSES(_item) ((PAYMENTADDRESSES *)(_item->data)) static K_TREE *paymentaddresses_root; static K_LIST *paymentaddresses_list; static K_STORE *paymentaddresses_store; */ // PAYMENTS typedef struct payments { int64_t paymentid; int64_t userid; tv_t paydate; char payaddress[TXT_BIG+1]; char originaltxn[TXT_BIG+1]; int64_t amount; char committxn[TXT_BIG+1]; char commitblockhash[TXT_BIG+1]; HISTORYDATECONTROLFIELDS; } PAYMENTS; #define ALLOC_PAYMENTS 1024 #define LIMIT_PAYMENTS 0 #define DATA_PAYMENTS(_item) ((PAYMENTS *)(_item->data)) static K_TREE *payments_root; static K_LIST *payments_list; static K_STORE *payments_store; /* unused yet // ACCOUNTBALANCE typedef struct accountbalance { int64_t userid; int64_t confirmedpaid; int64_t confirmedunpaid; int64_t pendingconfirm; int32_t heightupdate; HISTORYDATECONTROLFIELDS; } ACCOUNTBALANCE; #define ALLOC_ACCOUNTBALANCE 1024 #define LIMIT_ACCOUNTBALANCE 0 #define DATA_ACCOUNTBALANCE(_item) ((ACCOUNTBALANCE *)(_item->data)) static K_TREE *accountbalance_root; static K_LIST *accountbalance_list; static K_STORE *accountbalance_store; // ACCOUNTADJUSTMENT typedef struct accountbalance { int64_t userid; char authority[TXT_BIG+1]; char *reason; int64_t amount; HISTORYDATECONTROLFIELDS; } ACCOUNTADJUSTMENT; #define ALLOC_ACCOUNTADJUSTMENT 100 #define LIMIT_ACCOUNTADJUSTMENT 0 #define DATA_ACCOUNTADJUSTMENT(_item) ((ACCOUNTADJUSTMENT *)(_item->data)) static K_TREE *accountbalance_root; static K_LIST *accountbalance_list; static K_STORE *accountbalance_store; */ // IDCONTROL typedef struct idcontrol { char idname[TXT_SML+1]; int64_t lastid; MODIFYDATECONTROLFIELDS; } IDCONTROL; #define ALLOC_IDCONTROL 16 #define LIMIT_IDCONTROL 0 #define DATA_IDCONTROL(_item) ((IDCONTROL *)(_item->data)) // These are only used for db access - not stored in memory //static K_TREE *idcontrol_root; static K_LIST *idcontrol_list; static K_STORE *idcontrol_store; /* unused yet // OPTIONCONTROL typedef struct optioncontrol { char optionname[TXT_SML+1]; char *optionvalue; tv_t activationdate; int32_t activationheight; HISTORYDATECONTROLFIELDS; } OPTIONCONTROL; #define ALLOC_OPTIONCONTROL 64 #define LIMIT_OPTIONCONTROL 0 #define DATA_OPTIONCONTROL(_item) ((OPTIONCONTROL *)(_item->data)) static K_TREE *optioncontrol_root; static K_LIST *optioncontrol_list; static K_STORE *optioncontrol_store; */ // TODO: aging/discarding workinfo,shares // WORKINFO id.sharelog.json={...} typedef struct workinfo { int64_t workinfoid; char poolinstance[TXT_BIG+1]; char *transactiontree; char *merklehash; char prevhash[TXT_BIG+1]; char coinbase1[TXT_BIG+1]; char coinbase2[TXT_BIG+1]; char version[TXT_SML+1]; char bits[TXT_SML+1]; char ntime[TXT_SML+1]; int64_t reward; HISTORYDATECONTROLFIELDS; } WORKINFO; // ~10 hrs #define ALLOC_WORKINFO 1400 #define LIMIT_WORKINFO 0 #define DATA_WORKINFO(_item) ((WORKINFO *)(_item->data)) static K_TREE *workinfo_root; static K_LIST *workinfo_list; static K_STORE *workinfo_store; // SHARES id.sharelog.json={...} typedef struct shares { int64_t workinfoid; int64_t userid; char workername[TXT_BIG+1]; int32_t clientid; char enonce1[TXT_SML+1]; char nonce2[TXT_BIG+1]; char nonce[TXT_SML+1]; double diff; double sdiff; int32_t errn; char error[TXT_SML+1]; char secondaryuserid[TXT_SML+1]; HISTORYDATECONTROLFIELDS; } SHARES; #define ALLOC_SHARES 10000 #define LIMIT_SHARES 0 #define DATA_SHARES(_item) ((SHARES *)(_item->data)) static K_TREE *shares_root; static K_LIST *shares_list; static K_STORE *shares_store; // SHAREERRORS id.sharelog.json={...} typedef struct shareerrorss { int64_t workinfoid; int64_t userid; char workername[TXT_BIG+1]; int32_t clientid; int32_t errn; char error[TXT_SML+1]; char secondaryuserid[TXT_SML+1]; HISTORYDATECONTROLFIELDS; } SHAREERRORS; #define ALLOC_SHAREERRORS 10000 #define LIMIT_SHAREERRORS 0 #define DATA_SHAREERRORS(_item) ((SHAREERRORS *)(_item->data)) static K_TREE *shareerrors_root; static K_LIST *shareerrors_list; static K_STORE *shareerrors_store; /* // SHARESUMMARY typedef struct sharesummary { int64_t userid; char workername[TXT_BIG+1]; int64_t workinfoid; int64_t diff_acc; int64_t diff_sta; int64_t diff_dup; int64_t diff_low; int64_t diff_rej; int64_t share_acc; int64_t share_sta; int64_t share_dup; int64_t share_low; int64_t share_rej; tv_t first_share; tv_t last_share; char complete[TXT_FLAG+1]; MODIFYDATECONTROLFIELDS; } SHARESUMMARY; #define ALLOC_SHARESUMMARY 10000 #define LIMIT_SHARESUMMARY 0 #define DATA_SHARESUMMARY(_item) ((SHARESUMMARY *)(_item->data)) static K_TREE *sharesummary_root; static K_LIST *sharesummary_list; static K_STORE *sharesummary_store; // BLOCKSUMMARY typedef struct blocksummary { int32_t height; char blockhash[TXT_BIG+1]; int64_t userid; char workername[TXT_BIG+1]; int64_t diff_acc; int64_t diff_sta; int64_t diff_dup; int64_t diff_low; int64_t diff_rej; int64_t share_acc; int64_t share_sta; int64_t share_dup; int64_t share_low; int64_t share_rej; tv_t first_share; tv_t last_share; char complete[TXT_FLAG+1]; MODIFYDATECONTROLFIELDS; } BLOCKSUMMARY; #define ALLOC_BLOCKSUMMARY 10000 #define LIMIT_BLOCKSUMMARY 0 #define DATA_BLOCKSUMMARY(_item) ((BLOCKSUMMARY *)(_item->data)) static K_TREE *blocksummary_root; static K_LIST *blocksummary_list; static K_STORE *blocksummary_store; // BLOCKS typedef struct blocks { int32_t height; char blockhash[TXT_BIG+1]; int64_t workinfoid; int64_t userid; char workername[TXT_BIG+1]; int32_t clientid; char enonce1[TXT_SML+1]; char nonce2[TXT_BIG+1]; char nonce[TXT_SML+1]; int64_t reward; char confirmed[TXT_FLAG+1]; HISTORYDATECONTROLFIELDS; } BLOCKS; #define ALLOC_BLOCKS 10000 #define LIMIT_BLOCKS 0 #define DATA_BLOCKS ((BLOCKS *)(_item->data)) static K_TREE *blocks_root; static K_LIST *blocks_list; static K_STORE *blocks_store; // MININGPAYOUTS typedef struct miningpayouts { int64_t miningpayoutid; int64_t userid; int32_t height; char blockhash[TXT_BIG+1]; int64_t amount; HISTORYDATECONTROLFIELDS; } MININGPAYOUTS; #define ALLOC_MININGPAYOUTS 1000 #define LIMIT_MININGPAYOUTS 0 #define DATA_MININGPAYOUTS(_item) ((MININGPAYOUTS *)(_item->data)) static K_TREE *miningpayouts_root; static K_LIST *miningpayouts_list; static K_STORE *miningpayouts_store; // EVENTLOG typedef struct eventlog { int64_t eventlogid; char eventlogcode[TXT_SML+1]; char *eventlogdescription; HISTORYDATECONTROLFIELDS; } EVENTLOG; #define ALLOC_EVENTLOG 100 #define LIMIT_EVENTLOG 0 #define DATA_EVENTLOG(_item) ((EVENTLOG *)(_item->data)) static K_TREE *eventlog_root; static K_LIST *eventlog_list; static K_STORE *eventlog_store; */ // AUTHS typedef struct auths { int64_t authid; int64_t userid; char workername[TXT_BIG+1]; int32_t clientid; char enonce1[TXT_SML+1]; char useragent[TXT_BIG+1]; HISTORYDATECONTROLFIELDS; } AUTHS; #define ALLOC_AUTHS 1000 #define LIMIT_AUTHS 0 #define DATA_AUTHS(_item) ((AUTHS *)(_item->data)) static K_TREE *auths_root; static K_LIST *auths_list; static K_STORE *auths_store; /* // POOLSTATS // TODO: not in DB yet - design incomplete // poll pool(s) every 10min? typedef struct poolstats { char poolinstance[TXT_BIG+1]; int32_t users; int32_t workers; int64_t hashrate; // ... etc int64_t hashrate5m; int64_t hashrate1hr; int64_t hashrate24hr; HISTORYDATECONTROLFIELDS; } POOLSTATS; #define ALLOC_POOLSTATS 10000 #define LIMIT_POOLSTATS 0 #define DATA_POOLSTATS(_item) ((POOLSTATS *)(_item->data)) static K_TREE *poolstats_root; static K_LIST *poolstats_list; static K_STORE *poolstats_store; */ static void setnow(tv_t *now) { ts_t spec; spec.tv_sec = 0; spec.tv_nsec = 0; clock_gettime(CLOCK_REALTIME, &spec); now->tv_sec = spec.tv_sec; now->tv_usec = spec.tv_nsec / 1000; } static double cmp_transfer(K_ITEM *a, K_ITEM *b) { double c = (double)strcmp(DATA_TRANSFER(a)->name, DATA_TRANSFER(b)->name); return c; } static K_ITEM *find_transfer(char *name) { TRANSFER transfer; K_TREE_CTX ctx[1]; K_ITEM look; STRNCPY(transfer.name, name); look.data = (void *)(&transfer); return find_in_ktree(transfer_root, &look, cmp_transfer, ctx); } static K_ITEM *optional_name(char *name, int len, char *patt) { K_ITEM *item; char *value; regex_t re; int ret; item = find_transfer(name); if (!item) return NULL; value = DATA_TRANSFER(item)->data; if (!*value || (int)strlen(value) < len) return NULL; if (patt) { if (regcomp(&re, patt, REG_NOSUB) != 0) return NULL; ret = regexec(&re, value, (size_t)0, NULL, 0); regfree(&re); if (ret != 0) return NULL; } return item; } static K_ITEM *require_name(char *name, int len, char *patt, char *reply, size_t siz) { K_ITEM *item; char *value; regex_t re; int ret; item = find_transfer(name); if (!item) { snprintf(reply, siz, "failed.missing %s", name); return NULL; } value = DATA_TRANSFER(item)->data; if (!*value || (int)strlen(value) < len) { snprintf(reply, siz, "failed.short %s", name); return NULL; } if (patt) { if (regcomp(&re, patt, REG_NOSUB) != 0) { snprintf(reply, siz, "failed.REC %s", name); return NULL; } ret = regexec(&re, value, (size_t)0, NULL, 0); regfree(&re); if (ret != 0) { snprintf(reply, siz, "failed.invalid %s", name); return NULL; } } return item; } static void txt_to_data(enum data_type typ, char *nam, char *fld, void *data, size_t siz) { char *tmp; switch (typ) { case TYPE_STR: // A database field being bigger than local storage is a fatal error if (siz < (strlen(fld)+1)) { quithere(1, "Field %s structure size %d is smaller than db %d", nam, (int)siz, (int)strlen(fld)+1); } strcpy((char *)data, fld); break; case TYPE_BIGINT: if (siz != sizeof(int64_t)) { quithere(1, "Field %s bigint incorrect structure size %d - should be %d", nam, (int)siz, (int)sizeof(int64_t)); } *((long long *)data) = atoll(fld); break; case TYPE_INT: if (siz != sizeof(int32_t)) { quithere(1, "Field %s int incorrect structure size %d - should be %d", nam, (int)siz, (int)sizeof(int32_t)); } *((int32_t *)data) = atoi(fld); break; case TYPE_TV: if (siz != sizeof(tv_t)) { quithere(1, "Field %s timeval incorrect structure size %d - should be %d", nam, (int)siz, (int)sizeof(tv_t)); } unsigned int yyyy, mm, dd, HH, MM, SS, uS = 0, tz; struct tm tm; time_t tim; int n; n = sscanf(fld, "%u-%u-%u %u:%u:%u+%u", &yyyy, &mm, &dd, &HH, &MM, &SS, &tz); if (n != 7) { // allow uS n = sscanf(fld, "%u-%u-%u %u:%u:%u.%u+%u", &yyyy, &mm, &dd, &HH, &MM, &SS, &uS, &tz); if (n != 8) { quithere(1, "Field %s timeval unhandled date '%s' (%d)", nam, fld, n); } } tm.tm_sec = (int)SS; tm.tm_min = (int)MM; tm.tm_hour = (int)HH; tm.tm_mday = (int)dd; tm.tm_mon = (int)mm - 1; tm.tm_year = (int)yyyy - 1900; tm.tm_isdst = -1; tim = mktime(&tm); // Fix TZ offsets errors if (tim > COMPARE_EXPIRY) { ((tv_t *)data)->tv_sec = default_expiry.tv_sec; ((tv_t *)data)->tv_usec = default_expiry.tv_usec; } else { ((tv_t *)data)->tv_sec = tim; ((tv_t *)data)->tv_usec = uS; } break; case TYPE_BLOB: tmp = strdup(fld); if (!tmp) quithere(1, "Field %s (%d) OOM", nam, (int)strlen(fld)); *((char **)data) = tmp; break; case TYPE_DOUBLE: if (siz != sizeof(double)) { quithere(1, "Field %s int incorrect structure size %d - should be %d", nam, (int)siz, (int)sizeof(double)); } *((double *)data) = atof(fld); break; default: quithere(1, "Unknown field %s (%d) to convert", nam, (int)typ); break; } } // N.B. STRNCPY* macros truncate, whereas this aborts ckdb if src > trg static void txt_to_str(char *nam, char *fld, char data[], size_t siz) { txt_to_data(TYPE_STR, nam, fld, (void *)data, siz); } static void txt_to_bigint(char *nam, char *fld, int64_t *data, size_t siz) { txt_to_data(TYPE_BIGINT, nam, fld, (void *)data, siz); } static void txt_to_int(char *nam, char *fld, int32_t *data, size_t siz) { txt_to_data(TYPE_INT, nam, fld, (void *)data, siz); } static void txt_to_tv(char *nam, char *fld, tv_t *data, size_t siz) { txt_to_data(TYPE_TV, nam, fld, (void *)data, siz); } static void txt_to_blob(char *nam, char *fld, char *data) { txt_to_data(TYPE_BLOB, nam, fld, (void *)(&data), 0); } static void txt_to_double(char *nam, char *fld, double *data, size_t siz) { txt_to_data(TYPE_DOUBLE, nam, fld, (void *)data, siz); } static char *data_to_buf(enum data_type typ, void *data, char *buf, size_t siz) { struct tm tm; char *buf2; if (!buf) { switch (typ) { case TYPE_STR: case TYPE_BLOB: siz = strlen((char *)data) + 1; break; case TYPE_BIGINT: case TYPE_INT: case TYPE_TV: case TYPE_DOUBLE: siz = 64; // More than big enough break; default: quithere(1, "Unknown field (%d) to convert", (int)typ); break; } buf = malloc(siz); if (!buf) quithere(1, "OOM (%d)", (int)siz); } switch (typ) { case TYPE_STR: case TYPE_BLOB: snprintf(buf, siz, "%s", (char *)data); break; case TYPE_BIGINT: snprintf(buf, siz, "%"PRId64, *((uint64_t *)data)); break; case TYPE_INT: snprintf(buf, siz, "%"PRId32, *((uint32_t *)data)); break; case TYPE_TV: buf2 = malloc(siz); if (!buf2) quithere(1, "OOM (%d)", (int)siz); localtime_r(&(((struct timeval *)data)->tv_sec), &tm); strftime(buf2, siz, "%Y-%m-%d %H:%M:%S", &tm); snprintf(buf, siz, "%s.%06ld", buf2, (((struct timeval *)data)->tv_usec)); free(buf2); break; case TYPE_DOUBLE: snprintf(buf, siz, "%f", *((double *)data)); break; } return buf; } static char *str_to_buf(char data[], char *buf, size_t siz) { return data_to_buf(TYPE_STR, (void *)data, buf, siz); } static char *bigint_to_buf(int64_t data, char *buf, size_t siz) { return data_to_buf(TYPE_BIGINT, (void *)(&data), buf, siz); } static char *int_to_buf(int32_t data, char *buf, size_t siz) { return data_to_buf(TYPE_INT, (void *)(&data), buf, siz); } static char *tv_to_buf(tv_t *data, char *buf, size_t siz) { return data_to_buf(TYPE_TV, (void *)data, buf, siz); } /* unused yet static char *blob_to_buf(char *data, char *buf, size_t siz) { return data_to_buf(TYPE_BLOB, (void *)data, buf, siz); } static char *double_to_buf(double data, char *buf, size_t siz) { return data_to_buf(TYPE_DOUBLE, (void *)(&data), buf, siz); } */ static PGconn *dbconnect() { char conninfo[128]; PGconn *conn; snprintf(conninfo, sizeof(conninfo), "host=127.0.0.1 dbname=ckdb user=%s", db_user); conn = PQconnectdb(conninfo); if (PQstatus(conn) != CONNECTION_OK) quithere(1, "ERR: Failed to connect to db '%s'", PQerrorMessage(conn)); return conn; } static int64_t nextid(PGconn *conn, char *idname, int64_t increment, char *by, char *code, char *inet) { ExecStatusType rescode; PGresult *res; char qry[1024]; char *params[5]; int par; int64_t lastid; char *field; tv_t now; bool ok; int n; lastid = 0; snprintf(qry, sizeof(qry), "select lastid from idcontrol " "where idname='%s' for update", idname); res = PQexec(conn, qry); rescode = PQresultStatus(res); if (rescode != PGRES_TUPLES_OK) { PGLOGERR("Select", rescode, conn); goto cleanup; } n = PQnfields(res); if (n != 1) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, 1, n); goto cleanup; } n = PQntuples(res); if (n < 1) { LOGERR("%s(): No matching idname='%s'", __func__, idname); goto cleanup; } ok = true; PQ_GET_FLD(res, 0, "lastid", field, ok); if (!ok) goto cleanup; TXT_TO_BIGINT("lastid", field, lastid); PQclear(res); lastid += increment; snprintf(qry, sizeof(qry), "update idcontrol set " "lastid=$1, modifydate=$2, modifyby=$3, " "modifycode=$4, modifyinet=$5 " "where idname='%s'", idname); setnow(&now); par = 0; params[par++] = bigint_to_buf(lastid, NULL, 0); params[par++] = tv_to_buf(&now, NULL, 0); params[par++] = str_to_buf(by, NULL, 0); params[par++] = str_to_buf(code, NULL, 0); params[par++] = str_to_buf(inet, NULL, 0); PARCHK(par, params); res = PQexecParams(conn, qry, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (rescode != PGRES_COMMAND_OK) { PGLOGERR("Update", rescode, conn); lastid = 0; } for (n = 0; n < par; n++) free(params[n]); cleanup: PQclear(res); return lastid; } // default tree order by username asc,expirydate desc static double cmp_users(K_ITEM *a, K_ITEM *b) { double c = strcmp(DATA_USERS(a)->username, DATA_USERS(b)->username); if (c == 0.0) c = tvdiff(&(DATA_USERS(b)->expirydate), &(DATA_USERS(a)->expirydate)); return c; } // order by userid asc,expirydate desc static double cmp_userid(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_USERS(a)->userid) - (double)(DATA_USERS(b)->userid); if (c == 0.0) c = tvdiff(&(DATA_USERS(b)->expirydate), &(DATA_USERS(a)->expirydate)); return c; } static K_ITEM *find_users(char *username) { USERS users; K_TREE_CTX ctx[1]; K_ITEM look; STRNCPY(users.username, username); users.expirydate.tv_sec = default_expiry.tv_sec; users.expirydate.tv_usec = default_expiry.tv_usec; look.data = (void *)(&users); return find_in_ktree(users_root, &look, cmp_users, ctx); } /* unused static K_ITEM *find_userid(int64_t userid) { USERS users; K_TREE_CTX ctx[1]; K_ITEM look; users.userid = userid; users.expirydate.tv_sec = default_expiry.tv_sec; users.expirydate.tv_usec = default_expiry.tv_usec; look.data = (void *)(&users); return find_in_ktree(userid_root, &look, cmp_userid, ctx); } */ static bool users_add(PGconn *conn, char *username, char *emailaddress, char *passwordhash, char *by, char *code, char *inet) { ExecStatusType rescode; PGresult *res; K_ITEM *item; int n; USERS *row; char *ins; char tohash[64]; uint64_t hash; __maybe_unused uint64_t tmp; bool ok = false; char *params[11]; int par; LOGDEBUG("%s(): add", __func__); K_WLOCK(users_list); item = k_unlink_head(users_list); K_WUNLOCK(users_list); row = DATA_USERS(item); row->userid = nextid(conn, "userid", (int64_t)(666 + (rand() % 334)), by, code, inet); if (row->userid == 0) goto unitem; // TODO: pre-check the username exists? STRNCPY(row->username, username); STRNCPY(row->emailaddress, emailaddress); STRNCPY(row->passwordhash, passwordhash); snprintf(tohash, sizeof(tohash), "%s&#%s", username, emailaddress); HASH_BER(tohash, strlen(tohash), 1, hash, tmp); __bin2hex(row->secondaryuserid, (void *)(&hash), sizeof(hash)); HISTORYDATEINIT(row, by, code, inet); // copy createdate row->joineddate.tv_sec = row->createdate.tv_sec; row->joineddate.tv_usec = row->createdate.tv_usec; par = 0; params[par++] = bigint_to_buf(row->userid, NULL, 0); params[par++] = str_to_buf(row->username, NULL, 0); params[par++] = str_to_buf(row->emailaddress, NULL, 0); params[par++] = tv_to_buf(&(row->joineddate), NULL, 0); params[par++] = str_to_buf(row->passwordhash, NULL, 0); params[par++] = str_to_buf(row->secondaryuserid, NULL, 0); HISTORYDATEPARAMS(params, par, row); PARCHK(par, params); ins = "insert into users " "(userid,username,emailaddress,joineddate,passwordhash," "secondaryuserid" HISTORYDATECONTROL ") values (" PQPARAM11 ")"; res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (rescode != PGRES_COMMAND_OK) { PGLOGERR("Insert", rescode, conn); goto unparam; } ok = true; unparam: PQclear(res); for (n = 0; n < par; n++) free(params[n]); unitem: K_WLOCK(users_list); if (!ok) k_add_head(users_list, item); else { users_root = add_to_ktree(users_root, item, cmp_users); userid_root = add_to_ktree(userid_root, item, cmp_userid); k_add_head(users_store, item); } K_WUNLOCK(users_list); return ok; } static bool users_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; K_ITEM *item; int n, i; USERS *row; char *field; char *sel; int fields = 6; bool ok; LOGDEBUG("%s(): select", __func__); sel = "select " "userid,username,emailaddress,joineddate,passwordhash," "secondaryuserid" HISTORYDATECONTROL " from users"; res = PQexec(conn, sel); rescode = PQresultStatus(res); if (rescode != PGRES_TUPLES_OK) { PGLOGERR("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != (fields + HISTORYDATECOUNT)) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, fields + HISTORYDATECOUNT, n); PQclear(res); return false; } n = PQntuples(res); LOGDEBUG("%s(): tree build count %d", __func__, n); ok = true; K_WLOCK(users_list); for (i = 0; i < n; i++) { item = k_unlink_head(users_list); row = DATA_USERS(item); PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); PQ_GET_FLD(res, i, "username", field, ok); if (!ok) break; TXT_TO_STR("username", field, row->username); PQ_GET_FLD(res, i, "emailaddress", field, ok); if (!ok) break; TXT_TO_STR("emailaddress", field, row->emailaddress); PQ_GET_FLD(res, i, "joineddate", field, ok); if (!ok) break; TXT_TO_TV("joineddate", field, row->joineddate); PQ_GET_FLD(res, i, "passwordhash", field, ok); if (!ok) break; TXT_TO_STR("passwordhash", field, row->passwordhash); PQ_GET_FLD(res, i, "secondaryuserid", field, ok); if (!ok) break; TXT_TO_STR("secondaryuserid", field, row->secondaryuserid); HISTORYDATEFLDS(res, i, row, ok); if (!ok) break; users_root = add_to_ktree(users_root, item, cmp_users); userid_root = add_to_ktree(userid_root, item, cmp_userid); k_add_head(users_store, item); } if (!ok) k_add_head(users_list, item); K_WUNLOCK(users_list); PQclear(res); if (ok) LOGDEBUG("%s(): built", __func__); return true; } void users_reload() { PGconn *conn = dbconnect(); K_WLOCK(users_list); users_root = free_ktree(users_root, NULL); userid_root = free_ktree(userid_root, NULL); k_list_transfer_to_head(users_store, users_list); K_WUNLOCK(users_list); users_fill(conn); PQfinish(conn); } // order by userid asc,workername asc,expirydate desc static double cmp_workers(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_WORKERS(a)->userid) - (double)(DATA_WORKERS(b)->userid); if (c == 0.0) { c = strcmp(DATA_WORKERS(a)->workername, DATA_WORKERS(b)->workername); if (c == 0.0) c = tvdiff(&(DATA_WORKERS(b)->expirydate), &(DATA_WORKERS(a)->expirydate)); } return c; } static K_ITEM *find_workers(int64_t userid, char *workername) { WORKERS workers; K_TREE_CTX ctx[1]; K_ITEM look; workers.userid = userid; STRNCPY(workers.workername, workername); workers.expirydate.tv_sec = default_expiry.tv_sec; workers.expirydate.tv_usec = default_expiry.tv_usec; look.data = (void *)(&workers); return find_in_ktree(workers_root, &look, cmp_workers, ctx); } static bool workers_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; K_ITEM *item; int n, i; WORKERS *row; char *field; char *sel; int fields = 6; bool ok; LOGDEBUG("%s(): select", __func__); sel = "select " "userid,workername,difficultydefault," "idlenotificationenabled,idlenotificationtime" HISTORYDATECONTROL ",workerid from workers"; res = PQexec(conn, sel); rescode = PQresultStatus(res); if (rescode != PGRES_TUPLES_OK) { PGLOGERR("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != (fields + HISTORYDATECOUNT)) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, fields + HISTORYDATECOUNT, n); PQclear(res); return false; } n = PQntuples(res); LOGDEBUG("%s(): tree build count %d", __func__, n); ok = true; K_WLOCK(workers_list); for (i = 0; i < n; i++) { item = k_unlink_head(workers_list); row = DATA_WORKERS(item); PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); PQ_GET_FLD(res, i, "workername", field, ok); if (!ok) break; TXT_TO_STR("workername", field, row->workername); PQ_GET_FLD(res, i, "difficultydefault", field, ok); if (!ok) break; TXT_TO_INT("difficultydefault", field, row->difficultydefault); PQ_GET_FLD(res, i, "idlenotificationenabled", field, ok); if (!ok) break; TXT_TO_STR("idlenotificationenabled", field, row->idlenotificationenabled); PQ_GET_FLD(res, i, "idlenotificationtime", field, ok); if (!ok) break; TXT_TO_INT("idlenotificationtime", field, row->idlenotificationtime); HISTORYDATEFLDS(res, i, row, ok); if (!ok) break; PQ_GET_FLD(res, i, "workerid", field, ok); if (!ok) break; TXT_TO_BIGINT("workerid", field, row->workerid); workers_root = add_to_ktree(workers_root, item, cmp_workers); k_add_head(workers_store, item); } if (!ok) k_add_head(workers_list, item); K_WUNLOCK(workers_list); PQclear(res); if (ok) LOGDEBUG("%s(): built", __func__); return true; } void workers_reload() { PGconn *conn = dbconnect(); K_WLOCK(workers_list); workers_root = free_ktree(workers_root, NULL); k_list_transfer_to_head(workers_store, workers_list); K_WUNLOCK(workers_list); workers_fill(conn); PQfinish(conn); } // order by userid asc,paydate asc,payaddress asc,expirydate desc static double cmp_payments(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_PAYMENTS(a)->userid) - (double)(DATA_PAYMENTS(b)->userid); if (c == 0.0) { c = tvdiff(&(DATA_PAYMENTS(a)->paydate), &(DATA_PAYMENTS(b)->paydate)); if (c == 0.0) { c = strcmp(DATA_PAYMENTS(a)->payaddress, DATA_PAYMENTS(b)->payaddress); if (c == 0.0) c = tvdiff(&(DATA_PAYMENTS(b)->expirydate), &(DATA_PAYMENTS(a)->expirydate)); } } return c; } static bool payments_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; K_ITEM *item; int n, i; PAYMENTS *row; char *params[1]; int par; char *field; char *sel; int fields = 8; bool ok; LOGDEBUG("%s(): select", __func__); // TODO: handle selecting a subset, eg 20 per web page sel = "select " "userid,paydate,payaddress,originaltxn,amount,committxn,commitblockhash" HISTORYDATECONTROL ",paymentid from payments where expirydate=$1"; par = 0; params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); PARCHK(par, params); res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (rescode != PGRES_TUPLES_OK) { PGLOGERR("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != (fields + HISTORYDATECOUNT)) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, fields + HISTORYDATECOUNT, n); PQclear(res); return false; } n = PQntuples(res); LOGDEBUG("%s(): tree build count %d", __func__, n); ok = true; K_WLOCK(payments_list); for (i = 0; i < n; i++) { item = k_unlink_head(payments_list); row = DATA_PAYMENTS(item); PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); PQ_GET_FLD(res, i, "paydate", field, ok); if (!ok) break; TXT_TO_TV("paydate", field, row->paydate); PQ_GET_FLD(res, i, "payaddress", field, ok); if (!ok) break; TXT_TO_STR("payaddress", field, row->payaddress); PQ_GET_FLD(res, i, "originaltxn", field, ok); if (!ok) break; TXT_TO_STR("originaltxn", field, row->originaltxn); PQ_GET_FLD(res, i, "amount", field, ok); if (!ok) break; TXT_TO_BIGINT("amount", field, row->amount); PQ_GET_FLD(res, i, "committxn", field, ok); if (!ok) break; TXT_TO_STR("committxn", field, row->committxn); PQ_GET_FLD(res, i, "commitblockhash", field, ok); if (!ok) break; TXT_TO_STR("commitblockhash", field, row->commitblockhash); HISTORYDATEFLDS(res, i, row, ok); if (!ok) break; PQ_GET_FLD(res, i, "paymentid", field, ok); if (!ok) break; TXT_TO_BIGINT("paymentid", field, row->paymentid); payments_root = add_to_ktree(payments_root, item, cmp_payments); k_add_head(payments_store, item); } if (!ok) k_add_head(payments_list, item); K_WUNLOCK(payments_list); PQclear(res); if (ok) LOGDEBUG("%s(): built", __func__); return true; } void payments_reload() { PGconn *conn = dbconnect(); K_WLOCK(payments_list); payments_root = free_ktree(payments_root, NULL); k_list_transfer_to_head(payments_store, payments_list); K_WUNLOCK(payments_list); payments_fill(conn); PQfinish(conn); } // order by workinfoid asc, expirydate asc static double cmp_workinfo(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_WORKINFO(a)->workinfoid) - (double)(DATA_WORKINFO(b)->workinfoid); if (c == 0) c = tvdiff(&(DATA_WORKINFO(b)->expirydate), &(DATA_WORKINFO(a)->expirydate)); return c; } static K_ITEM *find_workinfo(int64_t workinfoid) { WORKINFO workinfo; K_TREE_CTX ctx[1]; K_ITEM look; workinfo.workinfoid = workinfoid; look.data = (void *)(&workinfo); return find_in_ktree(workinfo_root, &look, cmp_workinfo, ctx); } static int64_t workinfo_add(PGconn *conn, char *workinfoidstr, char *poolinstance, char *transactiontree, char *merklehash, char *prevhash, char *coinbase1, char *coinbase2, char *version, char *bits, char *ntime, char *reward, char *by, char *code, char *inet) { ExecStatusType rescode; PGresult *res; K_ITEM *item; int n; int64_t workinfoid = -1; WORKINFO *row; char *ins; char *params[16]; int par; LOGDEBUG("%s(): add", __func__); K_WLOCK(workinfo_list); item = k_unlink_head(workinfo_list); K_WUNLOCK(workinfo_list); row = DATA_WORKINFO(item); TXT_TO_BIGINT("workinfoid", workinfoidstr, row->workinfoid); STRNCPY(row->poolinstance, poolinstance); row->transactiontree = strdup(transactiontree); row->merklehash = strdup(merklehash); STRNCPY(row->prevhash, prevhash); STRNCPY(row->coinbase1, coinbase1); STRNCPY(row->coinbase2, coinbase2); STRNCPY(row->version, version); STRNCPY(row->bits, bits); STRNCPY(row->ntime, ntime); TXT_TO_BIGINT("reward", reward, row->reward); HISTORYDATEINIT(row, by, code, inet); HISTORYDATETRANSFER(row); par = 0; params[par++] = bigint_to_buf(row->workinfoid, NULL, 0); params[par++] = str_to_buf(row->transactiontree, NULL, 0); params[par++] = str_to_buf(row->merklehash, NULL, 0); params[par++] = str_to_buf(row->prevhash, NULL, 0); params[par++] = str_to_buf(row->coinbase1, NULL, 0); params[par++] = str_to_buf(row->coinbase2, NULL, 0); params[par++] = str_to_buf(row->version, NULL, 0); params[par++] = str_to_buf(row->bits, NULL, 0); params[par++] = str_to_buf(row->ntime, NULL, 0); params[par++] = bigint_to_buf(row->reward, NULL, 0); HISTORYDATEPARAMS(params, par, row); PARCHK(par, params); ins = "insert into workinfo " "(workinfoid,transactiontree,merklehash,prevhash," "coinbase1,coinbase2,version,bits,ntime,reward" HISTORYDATECONTROL ") values (" PQPARAM16 ")"; res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (rescode != PGRES_COMMAND_OK) { PGLOGERR("Insert", rescode, conn); goto unparam; } workinfoid = row->workinfoid; unparam: PQclear(res); for (n = 0; n < par; n++) free(params[n]); K_WLOCK(workinfo_list); if (workinfoid == -1) { free(row->transactiontree); free(row->merklehash); k_add_head(workinfo_list, item); } else { workinfo_root = add_to_ktree(workinfo_root, item, cmp_workinfo); k_add_head(workinfo_store, item); } K_WUNLOCK(workinfo_list); return workinfoid; } static bool workinfo_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; K_ITEM *item; int n, i; WORKINFO *row; char *params[1]; int par; char *field; char *sel; int fields = 8; bool ok; LOGDEBUG("%s(): select", __func__); // TODO: select the data based on sharesummary since old data isn't needed // however, the ageing rules for workinfo will decide that also // keep the last block + current? sel = "select " "workinfoid,poolinstance,transactiontree,merklehash,prevhash," "coinbase1,coinbase2,version,bits,ntime,reward" HISTORYDATECONTROL " from workinfo where expirydate=$1"; par = 0; params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); PARCHK(par, params); res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (rescode != PGRES_TUPLES_OK) { PGLOGERR("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != (fields + HISTORYDATECOUNT)) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, fields + HISTORYDATECOUNT, n); PQclear(res); return false; } n = PQntuples(res); LOGDEBUG("%s(): tree build count %d", __func__, n); ok = true; K_WLOCK(workinfo_list); for (i = 0; i < n; i++) { item = k_unlink_head(workinfo_list); row = DATA_WORKINFO(item); PQ_GET_FLD(res, i, "workinfoid", field, ok); if (!ok) break; TXT_TO_BIGINT("workinfoid", field, row->workinfoid); PQ_GET_FLD(res, i, "poolinstance", field, ok); if (!ok) break; TXT_TO_STR("poolinstance", field, row->poolinstance); PQ_GET_FLD(res, i, "transactiontree", field, ok); if (!ok) break; TXT_TO_BLOB("transactiontree", field, row->transactiontree); PQ_GET_FLD(res, i, "merklehash", field, ok); if (!ok) break; TXT_TO_BLOB("merklehash", field, row->merklehash); PQ_GET_FLD(res, i, "prevhash", field, ok); if (!ok) break; TXT_TO_STR("prevhash", field, row->prevhash); PQ_GET_FLD(res, i, "coinbase1", field, ok); if (!ok) break; TXT_TO_STR("coinbase1", field, row->coinbase1); PQ_GET_FLD(res, i, "coinbase2", field, ok); if (!ok) break; TXT_TO_STR("coinbase2", field, row->coinbase2); PQ_GET_FLD(res, i, "version", field, ok); if (!ok) break; TXT_TO_STR("version", field, row->version); PQ_GET_FLD(res, i, "bits", field, ok); if (!ok) break; TXT_TO_STR("bits", field, row->bits); PQ_GET_FLD(res, i, "ntime", field, ok); if (!ok) break; TXT_TO_STR("ntime", field, row->ntime); PQ_GET_FLD(res, i, "reward", field, ok); if (!ok) break; TXT_TO_BIGINT("reward", field, row->reward); HISTORYDATEFLDS(res, i, row, ok); if (!ok) break; workinfo_root = add_to_ktree(workinfo_root, item, cmp_workinfo); k_add_head(workinfo_store, item); } if (!ok) k_add_head(workinfo_list, item); K_WUNLOCK(workinfo_list); PQclear(res); if (ok) LOGDEBUG("%s(): built", __func__); return true; } void workinfo_reload() { // TODO: ??? a bad idea? /* PGconn *conn = dbconnect(); K_WLOCK(workinfo_list); workinfo_root = free_ktree(workinfo_root, ???); free transactiontree and merklehash k_list_transfer_to_head(workinfo_store, workinfo_list); K_WUNLOCK(workinfo_list); workinfo_fill(conn); PQfinish(conn); */ } // order by workinfoid asc, userid asc, createdate asc, nonce asc, expirydate desc static double cmp_shares(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_SHARES(a)->workinfoid) - (double)(DATA_SHARES(b)->workinfoid); if (c == 0) { c = (double)(DATA_SHARES(b)->userid) - (double)(DATA_SHARES(a)->userid); if (c == 0) { c = tvdiff(&(DATA_SHARES(b)->createdate), &(DATA_SHARES(a)->createdate)); if (c == 0) { c = strcmp(DATA_SHARES(a)->nonce, DATA_SHARES(b)->nonce); if (c == 0) c = tvdiff(&(DATA_SHARES(b)->expirydate), &(DATA_SHARES(a)->expirydate)); } } } return c; } // Memory (and log file) only static bool shares_add(char *workinfoid, char *username, char *workername, char *clientid, char *enonce1, char *nonce2, char *nonce, char *diff, char *sdiff, char *secondaryuserid, char *by, char *code, char *inet) { K_ITEM *s_item, *u_item, *w_item; SHARES *shares; bool ok = false; LOGDEBUG("%s(): add", __func__); K_WLOCK(shares_list); s_item = k_unlink_head(shares_list); K_WUNLOCK(shares_list); shares = DATA_SHARES(s_item); // TODO: allow BTC address later? u_item = find_users(username); if (!u_item) goto unitem; shares->userid = DATA_USERS(u_item)->userid; TXT_TO_BIGINT("workinfoid", workinfoid, shares->workinfoid); STRNCPY(shares->workername, workername); TXT_TO_INT("clientid", clientid, shares->clientid); STRNCPY(shares->enonce1, enonce1); STRNCPY(shares->nonce2, nonce2); STRNCPY(shares->nonce, nonce); TXT_TO_DOUBLE("diff", diff, shares->diff); TXT_TO_DOUBLE("sdiff", sdiff, shares->sdiff); STRNCPY(shares->secondaryuserid, secondaryuserid); HISTORYDATEINIT(shares, by, code, inet); HISTORYDATETRANSFER(shares); w_item = find_workinfo(shares->workinfoid); if (!w_item) goto unitem; w_item = find_workers(shares->userid, shares->workername); if (!w_item) goto unitem; // TODO: update stats - log file load will have to do all these checks also ok = true; unitem: K_WLOCK(shares_list); if (!ok) k_add_head(shares_list, s_item); else { shares_root = add_to_ktree(shares_root, s_item, cmp_shares); k_add_head(shares_store, s_item); } K_WUNLOCK(shares_list); return ok; } static bool shares_fill() { // TODO: reload shares from workinfo from log file // and verify workinfo while doing that return true; } // order by workinfoid asc, userid asc, createdate asc, nonce asc, expirydate desc static double cmp_shareerrors(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_SHAREERRORS(a)->workinfoid) - (double)(DATA_SHAREERRORS(b)->workinfoid); if (c == 0) { c = (double)(DATA_SHAREERRORS(b)->userid) - (double)(DATA_SHAREERRORS(a)->userid); if (c == 0) { c = tvdiff(&(DATA_SHAREERRORS(b)->createdate), &(DATA_SHAREERRORS(a)->createdate)); if (c == 0) c = tvdiff(&(DATA_SHAREERRORS(b)->expirydate), &(DATA_SHAREERRORS(a)->expirydate)); } } return c; } // Memory (and log file) only static bool shareerrors_add(char *workinfoid, char *username, char *workername, char *clientid, char *errn, char *error, char *secondaryuserid, char *by, char *code, char *inet) { K_ITEM *s_item, *u_item, *w_item; SHAREERRORS *shareerrors; bool ok = false; LOGDEBUG("%s(): add", __func__); K_WLOCK(shareerrors_list); s_item = k_unlink_head(shareerrors_list); K_WUNLOCK(shareerrors_list); shareerrors = DATA_SHAREERRORS(s_item); // TODO: allow BTC address later? u_item = find_users(username); if (!u_item) goto unitem; shareerrors->userid = DATA_USERS(u_item)->userid; TXT_TO_BIGINT("workinfoid", workinfoid, shareerrors->workinfoid); STRNCPY(shareerrors->workername, workername); TXT_TO_INT("clientid", clientid, shareerrors->clientid); TXT_TO_INT("errno", errn, shareerrors->errn); STRNCPY(shareerrors->error, error); STRNCPY(shareerrors->secondaryuserid, secondaryuserid); HISTORYDATEINIT(shareerrors, by, code, inet); HISTORYDATETRANSFER(shareerrors); w_item = find_workinfo(shareerrors->workinfoid); if (!w_item) goto unitem; w_item = find_workers(shareerrors->userid, shareerrors->workername); if (!w_item) goto unitem; // TODO: update stats - log file load will have to do all these checks also ok = true; unitem: K_WLOCK(shareerrors_list); if (!ok) k_add_head(shareerrors_list, s_item); else { shareerrors_root = add_to_ktree(shareerrors_root, s_item, cmp_shareerrors); k_add_head(shareerrors_store, s_item); } K_WUNLOCK(shareerrors_list); return ok; } static bool shareerrors_fill() { // TODO: reload shareerrors from workinfo from log file // and verify workinfo while doing that return true; } static double cmp_auths(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_SHAREERRORS(a)->workinfoid) - (double)(DATA_SHAREERRORS(b)->workinfoid); if (c == 0) { c = (double)(DATA_SHAREERRORS(b)->userid) - (double)(DATA_SHAREERRORS(a)->userid); if (c == 0) { c = tvdiff(&(DATA_SHAREERRORS(b)->createdate), &(DATA_SHAREERRORS(a)->createdate)); if (c == 0) c = tvdiff(&(DATA_SHAREERRORS(b)->expirydate), &(DATA_SHAREERRORS(a)->expirydate)); } } return c; } static char *auths_add(PGconn *conn, char *username, char *workername, char *clientid, char *enonce1, char *useragent, char *by, char *code, char *inet) { ExecStatusType rescode; PGresult *res; K_ITEM *a_item, *u_item, *w_item; int n; AUTHS *row; char *ins; char *secuserid = NULL; char *params[11]; int par; LOGDEBUG("%s(): add", __func__); K_WLOCK(auths_list); a_item = k_unlink_head(auths_list); K_WUNLOCK(auths_list); row = DATA_AUTHS(a_item); u_item = find_users(username); if (!u_item) goto unitem; row->userid = DATA_USERS(u_item)->userid; w_item = find_workers(row->userid, workername); if (!w_item) goto unitem; row->authid = nextid(conn, "authid", (int64_t)1, by, code, inet); if (row->authid == 0) goto unitem; STRNCPY(row->workername, workername); TXT_TO_INT("clientid", clientid, row->clientid); STRNCPY(row->enonce1, enonce1); STRNCPY(row->useragent, useragent); HISTORYDATEINIT(row, by, code, inet); HISTORYDATETRANSFER(row); row->authid = nextid(conn, "authid", (int64_t)1, by, code, inet); if (row->authid == 0) goto unitem; par = 0; params[par++] = bigint_to_buf(row->authid, NULL, 0); params[par++] = bigint_to_buf(row->userid, NULL, 0); params[par++] = str_to_buf(row->workername, NULL, 0); params[par++] = int_to_buf(row->clientid, NULL, 0); params[par++] = str_to_buf(row->enonce1, NULL, 0); params[par++] = str_to_buf(row->useragent, NULL, 0); HISTORYDATEPARAMS(params, par, row); PARCHK(par, params); ins = "insert into auths " "(authid,userid,workername,clientid,enonce1,useragent" HISTORYDATECONTROL ") values (" PQPARAM11 ")"; res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (rescode != PGRES_COMMAND_OK) { PGLOGERR("Insert", rescode, conn); goto unparam; } secuserid = DATA_USERS(u_item)->secondaryuserid; unparam: PQclear(res); for (n = 0; n < par; n++) free(params[n]); unitem: K_WLOCK(auths_list); if (!secuserid) k_add_head(auths_list, a_item); else { auths_root = add_to_ktree(auths_root, a_item, cmp_auths); k_add_head(auths_store, a_item); } K_WUNLOCK(auths_list); return secuserid; } static bool auths_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; K_ITEM *item; int n, i; AUTHS *row; char *params[1]; int par; char *field; char *sel; int fields = 11; bool ok; LOGDEBUG("%s(): select", __func__); // TODO: keep last x - since a user may login and mine for 100 days sel = "select " "authid,userid,workername,clientid,enonce1,useragent" HISTORYDATECONTROL " from auths where expirydate=$1"; par = 0; params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); PARCHK(par, params); res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (rescode != PGRES_TUPLES_OK) { PGLOGERR("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != (fields + HISTORYDATECOUNT)) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, fields + HISTORYDATECOUNT, n); PQclear(res); return false; } n = PQntuples(res); LOGDEBUG("%s(): tree build count %d", __func__, n); ok = true; K_WLOCK(auths_list); for (i = 0; i < n; i++) { item = k_unlink_head(auths_list); row = DATA_AUTHS(item); PQ_GET_FLD(res, i, "authid", field, ok); if (!ok) break; TXT_TO_BIGINT("authid", field, row->authid); PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); PQ_GET_FLD(res, i, "workername", field, ok); if (!ok) break; TXT_TO_STR("workername", field, row->workername); PQ_GET_FLD(res, i, "clientid", field, ok); if (!ok) break; TXT_TO_INT("clientid", field, row->clientid); PQ_GET_FLD(res, i, "enonce1", field, ok); if (!ok) break; TXT_TO_BLOB("enonce1", field, row->enonce1); PQ_GET_FLD(res, i, "useragent", field, ok); if (!ok) break; TXT_TO_STR("useragent", field, row->useragent); HISTORYDATEFLDS(res, i, row, ok); if (!ok) break; auths_root = add_to_ktree(auths_root, item, cmp_auths); k_add_head(auths_store, item); } if (!ok) k_add_head(auths_list, item); K_WUNLOCK(auths_list); PQclear(res); if (ok) LOGDEBUG("%s(): built", __func__); return true; } void auths_reload() { PGconn *conn = dbconnect(); K_WLOCK(auths_list); auths_root = free_ktree(auths_root, NULL); k_list_transfer_to_head(auths_store, auths_list); K_WUNLOCK(auths_list); auths_fill(conn); PQfinish(conn); } static void getdata() { PGconn *conn = dbconnect(); users_fill(conn); workers_fill(conn); payments_fill(conn); workinfo_fill(conn); shares_fill(); shareerrors_fill(); auths_fill(conn); PQfinish(conn); } static PGconn *dbquit(PGconn *conn) { if (conn != NULL) PQfinish(conn); return NULL; } /* Open the file in path, check if there is a pid in there that still exists * and if not, write the pid into that file. */ static bool write_pid(ckpool_t *ckp, const char *path, pid_t pid) { struct stat statbuf; FILE *fp; int ret; if (!stat(path, &statbuf)) { int oldpid; LOGWARNING("File %s exists", path); fp = fopen(path, "r"); if (!fp) { LOGEMERG("Failed to open file %s", path); return false; } ret = fscanf(fp, "%d", &oldpid); fclose(fp); if (ret == 1 && !(kill(oldpid, 0))) { if (!ckp->killold) { LOGEMERG("Process %s pid %d still exists, start ckpool with -k if you wish to kill it", path, oldpid); return false; } if (kill(oldpid, 9)) { LOGEMERG("Unable to kill old process %s pid %d", path, oldpid); return false; } LOGWARNING("Killing off old process %s pid %d", path, oldpid); } } fp = fopen(path, "w"); if (!fp) { LOGERR("Failed to open file %s", path); return false; } fprintf(fp, "%d", pid); fclose(fp); return true; } static void create_process_unixsock(proc_instance_t *pi) { unixsock_t *us = &pi->us; us->path = strdup(pi->ckp->socket_dir); realloc_strcat(&us->path, pi->sockname); LOGDEBUG("Opening %s", us->path); us->sockd = open_unix_server(us->path); if (unlikely(us->sockd < 0)) quit(1, "Failed to open %s socket", pi->sockname); } static void write_namepid(proc_instance_t *pi) { char s[256]; pi->pid = getpid(); sprintf(s, "%s%s.pid", pi->ckp->socket_dir, pi->processname); if (!write_pid(pi->ckp, s, pi->pid)) quit(1, "Failed to write %s pid %d", pi->processname, pi->pid); } static void rm_namepid(proc_instance_t *pi) { char s[256]; sprintf(s, "%s%s.pid", pi->ckp->socket_dir, pi->processname); unlink(s); } static void clean_up(ckpool_t *ckp) { rm_namepid(&ckp->main); dealloc(ckp->socket_dir); fclose(ckp->logfp); } static void setup_data() { transfer_list = k_new_list("Transfer", sizeof(TRANSFER), ALLOC_TRANSFER, LIMIT_TRANSFER, true); transfer_store = k_new_store(transfer_list); transfer_root = new_ktree(); users_list = k_new_list("Users", sizeof(USERS), ALLOC_USERS, LIMIT_USERS, true); users_store = k_new_store(users_list); users_root = new_ktree(); workers_list = k_new_list("Workers", sizeof(WORKERS), ALLOC_WORKERS, LIMIT_WORKERS, true); workers_store = k_new_store(workers_list); workers_root = new_ktree(); payments_list = k_new_list("Payments", sizeof(PAYMENTS), ALLOC_PAYMENTS, LIMIT_PAYMENTS, true); payments_store = k_new_store(payments_list); payments_root = new_ktree(); idcontrol_list = k_new_list("IDControl", sizeof(IDCONTROL), ALLOC_IDCONTROL, LIMIT_IDCONTROL, true); idcontrol_store = k_new_store(idcontrol_list); workinfo_list = k_new_list("WorkInfo", sizeof(WORKINFO), ALLOC_WORKINFO, LIMIT_WORKINFO, true); workinfo_store = k_new_store(workinfo_list); workinfo_root = new_ktree(); shares_list = k_new_list("Shares", sizeof(SHARES), ALLOC_SHARES, LIMIT_SHARES, true); shares_store = k_new_store(shares_list); shares_root = new_ktree(); shareerrors_list = k_new_list("Shareerrors", sizeof(SHAREERRORS), ALLOC_SHAREERRORS, LIMIT_SHAREERRORS, true); shareerrors_store = k_new_store(shareerrors_list); shareerrors_root = new_ktree(); auths_list = k_new_list("Auths", sizeof(AUTHS), ALLOC_AUTHS, LIMIT_AUTHS, true); auths_store = k_new_store(auths_list); auths_root = new_ktree(); getdata(); } static char *cmd_adduser(char *id, char *by, char *code, char *inet) { char reply[1024] = ""; size_t siz = sizeof(reply); K_ITEM *i_username, *i_emailaddress, *i_passwordhash; PGconn *conn; bool ok; i_username = require_name("username", 3, (char *)userpatt, reply, siz); if (!i_username) return strdup(reply); i_emailaddress = require_name("emailaddress", 7, (char *)mailpatt, reply, siz); if (!i_emailaddress) return strdup(reply); i_passwordhash = require_name("passwordhash", 64, (char *)hashpatt, reply, siz); if (!i_passwordhash) return strdup(reply); conn = dbconnect(); ok = users_add(conn, DATA_TRANSFER(i_username)->data, DATA_TRANSFER(i_emailaddress)->data, DATA_TRANSFER(i_passwordhash)->data, by, code, inet); PQfinish(conn); if (!ok) { STRNCPY(reply, "failed.DBE"); return strdup(reply); } LOGDEBUG("%s.added.%s", id, DATA_TRANSFER(i_username)->data); snprintf(reply, siz, "added.%s", DATA_TRANSFER(i_username)->data); return strdup(reply); } static char *cmd_chkpass(char *id, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet) { K_ITEM *i_username, *i_passwordhash, *u_item; char reply[1024] = ""; size_t siz = sizeof(reply); bool ok; i_username = require_name("username", 3, (char *)userpatt, reply, siz); if (!i_username) return strdup(reply); i_passwordhash = require_name("passwordhash", 64, (char *)hashpatt, reply, siz); if (!i_passwordhash) return strdup(reply); u_item = find_users(DATA_TRANSFER(i_username)->data); if (!u_item) ok = false; else { if (strcasecmp(DATA_TRANSFER(i_passwordhash)->data, DATA_USERS(u_item)->passwordhash) == 0) ok = true; else ok = false; } if (!ok) return strdup("bad"); LOGDEBUG("%s.login.%s", id, DATA_TRANSFER(i_username)->data); return strdup("ok"); } static char *cmd_poolstats(char *id, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet) { char reply[1024] = ""; size_t siz = sizeof(reply); LOGDEBUG("%s.stats.ok", id); printf("%s.stats", id); snprintf(reply, siz, "stats.ok"); return strdup(reply); } static char *cmd_newid(char *id, char *by, char *code, char *inet) { char reply[1024] = ""; size_t siz = sizeof(reply); K_ITEM *i_idname, *i_idvalue, *look; IDCONTROL *row; char *params[10]; int par; bool ok = false; ExecStatusType rescode; PGresult *res; PGconn *conn; char *ins; int n; LOGDEBUG("%s(): add", __func__); i_idname = require_name("idname", 3, (char *)idpatt, reply, siz); if (!i_idname) return strdup(reply); i_idvalue = require_name("idvalue", 1, (char *)intpatt, reply, siz); if (!i_idvalue) return strdup(reply); K_WLOCK(idcontrol_list); look = k_unlink_head(idcontrol_list); K_WUNLOCK(idcontrol_list); row = DATA_IDCONTROL(look); STRNCPY(row->idname, DATA_TRANSFER(i_idname)->data); TXT_TO_BIGINT("idvalue", DATA_TRANSFER(i_idvalue)->data, row->lastid); MODIFYDATEINIT(row, by, code, inet); par = 0; params[par++] = str_to_buf(row->idname, NULL, 0); params[par++] = bigint_to_buf(row->lastid, NULL, 0); MODIFYDATEPARAMS(params, par, row); PARCHK(par, params); ins = "insert into idcontrol " "(idname,lastid" MODIFYDATECONTROL ") values (" PQPARAM10 ")"; conn = dbconnect(); res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (rescode != PGRES_COMMAND_OK) { PGLOGERR("Insert", rescode, conn); goto foil; } ok = true; foil: PQclear(res); PQfinish(conn); for (n = 0; n < par; n++) free(params[n]); K_WLOCK(idcontrol_list); k_add_head(idcontrol_list, look); K_WUNLOCK(idcontrol_list); if (!ok) { snprintf(reply, siz, "failed.DBE"); return strdup(reply); } LOGDEBUG("%s.added.%s", id, DATA_TRANSFER(i_idname)->data); snprintf(reply, siz, "added.%s", DATA_TRANSFER(i_idname)->data); return strdup(reply); } static char *cmd_payments(char *id, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet) { K_ITEM *i_username, *look, *u_item, *p_item; K_TREE_CTX ctx[1]; PAYMENTS *row; char reply[1024] = ""; char tmp[1024]; size_t siz = sizeof(reply); char *buf; size_t len, off; int rows; i_username = require_name("username", 3, (char *)userpatt, reply, siz); if (!i_username) return strdup(reply); u_item = find_users(DATA_TRANSFER(i_username)->data); if (!u_item) return strdup("bad"); K_WLOCK(payments_list); look = k_unlink_head(payments_list); K_WUNLOCK(payments_list); row = DATA_PAYMENTS(look); row->userid = DATA_USERS(u_item)->userid; row->paydate.tv_sec = 0; row->paydate.tv_usec = 0; p_item = find_after_in_ktree(payments_root, look, cmp_payments, ctx); len = 1024; buf = malloc(len); if (!buf) quithere(1, "malloc buf (%d) OOM", (int)len); strcpy(buf, "ok."); off = strlen(buf); rows = 0; while (p_item && DATA_PAYMENTS(p_item)->userid == DATA_USERS(u_item)->userid) { tv_to_buf(&(DATA_PAYMENTS(p_item)->paydate), reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "paydate%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); str_to_buf(DATA_PAYMENTS(p_item)->payaddress, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "payaddress%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(DATA_PAYMENTS(p_item)->amount, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "amount%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; p_item = next_in_ktree(ctx); } snprintf(tmp, sizeof(tmp), "rows=%d", rows); APPEND_REALLOC(buf, off, len, tmp); K_WLOCK(payments_list); k_add_head(payments_list, look); K_WUNLOCK(payments_list); LOGDEBUG("%s.payments.%s", id, DATA_TRANSFER(i_username)->data); return buf; } static char *cmd_sharelog(char *id, char *by, char *code, char *inet) { char reply[1024] = ""; size_t siz = sizeof(reply); K_ITEM *i_method; PGconn *conn; // log to logfile with processing success/failure code i_method = require_name("method", 1, NULL, reply, siz); if (!i_method) return strdup(reply); if (strcasecmp(DATA_TRANSFER(i_method)->data, METHOD_WORKINFO) == 0) { K_ITEM *i_workinfoid, *i_poolinstance, *i_transactiontree, *i_merklehash; K_ITEM *i_prevhash, *i_coinbase1, *i_coinbase2, *i_version, *i_bits; K_ITEM *i_ntime, *i_reward; int64_t workinfoid; i_workinfoid = require_name("workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); i_poolinstance = require_name("poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) return strdup(reply); i_transactiontree = require_name("transactiontree", 1, NULL, reply, siz); if (!i_transactiontree) return strdup(reply); i_merklehash = require_name("merklehash", 1, NULL, reply, siz); if (!i_merklehash) return strdup(reply); i_prevhash = require_name("prevhash", 1, NULL, reply, siz); if (!i_prevhash) return strdup(reply); i_coinbase1 = require_name("coinbase1", 1, NULL, reply, siz); if (!i_coinbase1) return strdup(reply); i_coinbase2 = require_name("coinbase2", 1, NULL, reply, siz); if (!i_coinbase2) return strdup(reply); i_version = require_name("version", 1, NULL, reply, siz); if (!i_version) return strdup(reply); i_bits = require_name("bits", 1, NULL, reply, siz); if (!i_bits) return strdup(reply); i_ntime = require_name("ntime", 1, NULL, reply, siz); if (!i_ntime) return strdup(reply); i_reward = require_name("reward", 1, NULL, reply, siz); if (!i_reward) return strdup(reply); conn = dbconnect(); workinfoid = workinfo_add(conn, DATA_TRANSFER(i_workinfoid)->data, DATA_TRANSFER(i_poolinstance)->data, DATA_TRANSFER(i_transactiontree)->data, DATA_TRANSFER(i_merklehash)->data, DATA_TRANSFER(i_prevhash)->data, DATA_TRANSFER(i_coinbase1)->data, DATA_TRANSFER(i_coinbase2)->data, DATA_TRANSFER(i_version)->data, DATA_TRANSFER(i_bits)->data, DATA_TRANSFER(i_ntime)->data, DATA_TRANSFER(i_reward)->data, by, code, inet); PQfinish(conn); if (workinfoid == -1) { STRNCPY(reply, "bad.DBE"); return strdup(reply); } LOGDEBUG("added.%s.%"PRId64, DATA_TRANSFER(i_method)->data, workinfoid); snprintf(reply, siz, "%s.added.%"PRId64, id, workinfoid); return strdup(reply); } else if (strcasecmp(DATA_TRANSFER(i_method)->data, METHOD_SHARES) == 0) { K_ITEM *i_workinfoid, *i_username, *i_workername, *i_clientid, *i_enonce1; K_ITEM *i_nonce2, *i_nonce, *i_diff, *i_sdiff, *i_secondaryuserid; bool ok; i_workinfoid = require_name("workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); i_username = require_name("username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); i_workername = require_name("workername", 1, NULL, reply, siz); if (!i_workername) return strdup(reply); i_clientid = require_name("clientid", 1, NULL, reply, siz); if (!i_clientid) return strdup(reply); i_enonce1 = require_name("enonce1", 1, NULL, reply, siz); if (!i_enonce1) return strdup(reply); i_nonce2 = require_name("nonce2", 1, NULL, reply, siz); if (!i_nonce2) return strdup(reply); i_nonce = require_name("nonce", 1, NULL, reply, siz); if (!i_nonce) return strdup(reply); i_diff = require_name("diff", 1, NULL, reply, siz); if (!i_diff) return strdup(reply); i_sdiff = require_name("sdiff", 1, NULL, reply, siz); if (!i_sdiff) return strdup(reply); i_secondaryuserid = require_name("secondaryuserid", 1, NULL, reply, siz); if (!i_secondaryuserid) return strdup(reply); ok = shares_add(DATA_TRANSFER(i_workinfoid)->data, DATA_TRANSFER(i_username)->data, DATA_TRANSFER(i_workername)->data, DATA_TRANSFER(i_clientid)->data, DATA_TRANSFER(i_enonce1)->data, DATA_TRANSFER(i_nonce2)->data, DATA_TRANSFER(i_nonce)->data, DATA_TRANSFER(i_diff)->data, DATA_TRANSFER(i_sdiff)->data, DATA_TRANSFER(i_secondaryuserid)->data, by, code, inet); if (!ok) { STRNCPY(reply, "bad.DATA"); return strdup(reply); } LOGDEBUG("added.%s.%s", DATA_TRANSFER(i_method)->data, DATA_TRANSFER(i_nonce)->data); snprintf(reply, siz, "%s.added.%s", id, DATA_TRANSFER(i_nonce)->data); return strdup(reply); } else if (strcasecmp(DATA_TRANSFER(i_method)->data, METHOD_SHAREERRORS) == 0) { K_ITEM *i_workinfoid, *i_username, *i_workername, *i_clientid, *i_errn; K_ITEM *i_error, *i_secondaryuserid; bool ok; i_workinfoid = require_name("workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); i_username = require_name("username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); i_workername = require_name("workername", 1, NULL, reply, siz); if (!i_workername) return strdup(reply); i_clientid = require_name("clientid", 1, NULL, reply, siz); if (!i_clientid) return strdup(reply); i_errn = require_name("errno", 1, NULL, reply, siz); if (!i_errn) return strdup(reply); i_error = require_name("error", 1, NULL, reply, siz); if (!i_error) return strdup(reply); i_secondaryuserid = require_name("secondaryuserid", 1, NULL, reply, siz); if (!i_secondaryuserid) return strdup(reply); ok = shareerrors_add(DATA_TRANSFER(i_workinfoid)->data, DATA_TRANSFER(i_username)->data, DATA_TRANSFER(i_workername)->data, DATA_TRANSFER(i_clientid)->data, DATA_TRANSFER(i_errn)->data, DATA_TRANSFER(i_error)->data, DATA_TRANSFER(i_secondaryuserid)->data, by, code, inet); if (!ok) { STRNCPY(reply, "bad.DATA"); return strdup(reply); } LOGDEBUG("added.%s.%s", DATA_TRANSFER(i_method)->data, DATA_TRANSFER(i_username)->data); snprintf(reply, siz, "%s.added.%s", id, DATA_TRANSFER(i_username)->data); return strdup(reply); } STRNCPY(reply, "bad.method"); return strdup(reply); } static char *cmd_auth(char *id, char *by, char *code, char *inet) { char reply[1024] = ""; size_t siz = sizeof(reply); K_ITEM *i_method; PGconn *conn; i_method = require_name("method", 1, NULL, reply, siz); if (!i_method) return strdup(reply); if (strcasecmp(DATA_TRANSFER(i_method)->data, METHOD_AUTH) == 0) { K_ITEM *i_username, *i_workername, *i_clientid, *i_enonce1, *i_useragent; char *secuserid; i_username = require_name("username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); i_workername = require_name("workername", 1, NULL, reply, siz); if (!i_workername) return strdup(reply); i_clientid = require_name("clientid", 1, NULL, reply, siz); if (!i_clientid) return strdup(reply); i_enonce1 = require_name("enonce1", 1, NULL, reply, siz); if (!i_enonce1) return strdup(reply); i_useragent = require_name("useragent", 1, NULL, reply, siz); if (!i_useragent) return strdup(reply); conn = dbconnect(); secuserid = auths_add(conn, DATA_TRANSFER(i_username)->data, DATA_TRANSFER(i_workername)->data, DATA_TRANSFER(i_clientid)->data, DATA_TRANSFER(i_enonce1)->data, DATA_TRANSFER(i_useragent)->data, by, code, inet); PQfinish(conn); if (!secuserid) { STRNCPY(reply, "bad.DBE"); return strdup(reply); } LOGDEBUG("added.%s.%s", DATA_TRANSFER(i_method)->data, secuserid); snprintf(reply, siz, "%s.added.%s", id, secuserid); return strdup(reply); } STRNCPY(reply, "bad.method"); return strdup(reply); } enum cmd_values { CMD_UNSET, CMD_REPLY, // Means something was wrong - send back reply CMD_SHUTDOWN, CMD_PING, CMD_LOGSHARE, CMD_AUTH, CMD_ADDUSER, CMD_CHKPASS, CMD_POOLSTAT, CMD_NEWID, CMD_PAYMENTS, CMD_END }; #define ACCESS_POOL "p" #define ACCESS_SYSTEM "s" #define ACCESS_WEB "w" #define ACCESS_PROXY "x" static struct CMDS { enum cmd_values cmd_val; char *cmd_str; char *(*func)(char *, char *, char *, char *); char *access; } cmds[] = { { CMD_SHUTDOWN, "shutdown", NULL, ACCESS_SYSTEM }, { CMD_PING, "ping", NULL, ACCESS_SYSTEM ACCESS_WEB }, { CMD_LOGSHARE, "sharelog", cmd_sharelog, ACCESS_POOL }, // Workinfo and Shares { CMD_AUTH, "authorise", cmd_auth, ACCESS_POOL }, { CMD_ADDUSER, "adduser", cmd_adduser, ACCESS_WEB }, { CMD_CHKPASS, "chkpass", cmd_chkpass, ACCESS_WEB }, { CMD_POOLSTAT, "poolstats", cmd_poolstats, ACCESS_WEB }, { CMD_NEWID, "newid", cmd_newid, ACCESS_SYSTEM }, { CMD_PAYMENTS, "payments", cmd_payments, ACCESS_WEB }, { CMD_END, NULL, NULL, NULL } }; // TODO: size limits? static enum cmd_values breakdown(char *buf, int *which_cmds, char *id) { K_TREE_CTX ctx[1]; K_ITEM *item; char *copy, *cmd, *data, *next, *eq; *which_cmds = CMD_UNSET; copy = strdup(buf); cmd = strchr(copy, '.'); if (!cmd || !*cmd) { STRNCPYSIZ(id, copy, ID_SIZ); LOGINFO("Listener received invalid message: '%s'", buf); free(copy); return CMD_REPLY; } *(cmd++) = '\0'; STRNCPYSIZ(id, copy, ID_SIZ); data = strchr(cmd, '.'); if (data) *(data++) = '\0'; for (*which_cmds = 0; cmds[*which_cmds].cmd_val != CMD_END; (*which_cmds)++) { if (strcasecmp(cmd, cmds[*which_cmds].cmd_str) == 0) break; } if (cmds[*which_cmds].cmd_val == CMD_END) { LOGINFO("Listener received unknown command: '%s'", buf); free(copy); return CMD_REPLY; } next = data; if (strncmp(next, JSON_TRANSFER, JSON_TRANSFER_LEN) == 0) { json_t *json_data; json_error_t err_val; void *json_iter; const char *json_key, *json_str; json_t *json_value; size_t siz; next += JSON_TRANSFER_LEN; json_data = json_loads(next, JSON_DISABLE_EOF_CHECK, &err_val); if (!json_data) { LOGINFO("Json decode error from command: '%s'", cmd); free(copy); return CMD_REPLY; } json_iter = json_object_iter(json_data); K_WLOCK(transfer_list); while (json_iter) { json_key = json_object_iter_key(json_iter); json_value = json_object_iter_value(json_iter); if (json_is_string(json_value) || json_is_integer(json_value)) { item = k_unlink_head(transfer_list); STRNCPY(DATA_TRANSFER(item)->name, json_key); if (json_is_string(json_value)) { json_str = json_string_value(json_value); siz = strlen(json_str); if (siz >= sizeof(DATA_TRANSFER(item)->value)) DATA_TRANSFER(item)->data = strdup(json_str); else { STRNCPY(DATA_TRANSFER(item)->value, json_str); DATA_TRANSFER(item)->data = DATA_TRANSFER(item)->value; } } else { snprintf(DATA_TRANSFER(item)->value, sizeof(DATA_TRANSFER(item)->value), "%"PRId64, (int64_t)json_integer_value(json_value)); DATA_TRANSFER(item)->data = DATA_TRANSFER(item)->value; } if (find_in_ktree(transfer_root, item, cmp_transfer, ctx)) { if (DATA_TRANSFER(item)->data != DATA_TRANSFER(item)->value) free(DATA_TRANSFER(item)->data); k_add_head(transfer_list, item); } else { transfer_root = add_to_ktree(transfer_root, item, cmp_transfer); k_add_head(transfer_store, item); } } json_iter = json_object_iter_next(json_data, json_iter); } K_WUNLOCK(transfer_list); json_decref(json_data); } else { K_WLOCK(transfer_list); while (next && *next) { data = next; next = strchr(data, 0x02); if (next) *(next++) = '\0'; eq = strchr(data, '='); if (!eq) eq = ""; else *(eq++) = '\0'; item = k_unlink_head(transfer_list); STRNCPY(DATA_TRANSFER(item)->name, data); STRNCPY(DATA_TRANSFER(item)->value, eq); DATA_TRANSFER(item)->data = DATA_TRANSFER(item)->value; if (find_in_ktree(transfer_root, item, cmp_transfer, ctx)) { if (DATA_TRANSFER(item)->data != DATA_TRANSFER(item)->value) free(DATA_TRANSFER(item)->data); k_add_head(transfer_list, item); } else { transfer_root = add_to_ktree(transfer_root, item, cmp_transfer); k_add_head(transfer_store, item); } } K_WUNLOCK(transfer_list); } free(copy); return cmds[*which_cmds].cmd_val; } // TODO: equivalent of api_allow static void *listener(void *arg) { proc_instance_t *pi = (proc_instance_t *)arg; unixsock_t *us = &pi->us; char *end, *ans, *rep, *buf = NULL; char id[ID_SIZ+1], reply[1024+1]; enum cmd_values cmd; int sockd, which_cmds; K_ITEM *item; size_t siz; tv_t now; rename_proc(pi->sockname); setup_data(); while (true) { dealloc(buf); sockd = accept(us->sockd, NULL, NULL); if (sockd < 0) { LOGERR("Failed to accept on socket in listener"); break; } cmd = CMD_UNSET; buf = recv_unix_msg(sockd); // Once we've read the message setnow(&now); if (buf) { end = buf + strlen(buf) - 1; // strip trailing \n and \r while (end >= buf && (*end == '\n' || *end == '\r')) *(end--) = '\0'; } if (!buf || !*buf) { // An empty message wont get a reply if (!buf) LOGWARNING("Failed to get message in listener"); else LOGWARNING("Empty message in listener"); } else { cmd = breakdown(buf, &which_cmds, id); switch (cmd) { case CMD_REPLY: snprintf(reply, sizeof(reply), "%s.%ld.?", id, now.tv_sec); send_unix_msg(sockd, reply); break; case CMD_SHUTDOWN: LOGWARNING("Listener received shutdown message, terminating ckdb"); snprintf(reply, sizeof(reply), "%s.%d.exiting", id, (int)now.tv_sec); send_unix_msg(sockd, reply); break; case CMD_PING: LOGDEBUG("Listener received ping request"); snprintf(reply, sizeof(reply), "%s.%ld.pong", id, now.tv_sec); send_unix_msg(sockd, reply); break; default: // TODO: optionally get by/code/inet from transfer here instead? ans = cmds[which_cmds].func(id, (char *)"code", (char *)__func__, (char *)"127.0.0.1"); siz = strlen(ans) + strlen(id) + 32; rep = malloc(siz); snprintf(rep, siz, "%s.%ld.%s", id, now.tv_sec, ans); free(ans); ans = NULL; send_unix_msg(sockd, rep); free(rep); rep = NULL; break; } } close(sockd); if (cmd == CMD_SHUTDOWN) break; K_WLOCK(transfer_list); transfer_root = free_ktree(transfer_root, NULL); item = transfer_store->head; while (item) { if (DATA_TRANSFER(item)->data != DATA_TRANSFER(item)->value) free(DATA_TRANSFER(item)->data); item = item->next; } k_list_transfer_to_head(transfer_store, transfer_list); K_WUNLOCK(transfer_list); } dealloc(buf); close_unix_socket(us->sockd, us->path); return NULL; } int main(int argc, char **argv) { struct sigaction handler; char buf[512]; ckpool_t ckp; int c, ret; char *kill; memset(&ckp, 0, sizeof(ckp)); ckp.loglevel = LOG_NOTICE; while ((c = getopt(argc, argv, "c:kl:n:p:s:u:")) != -1) { switch(c) { case 'c': ckp.config = optarg; break; case 'k': ckp.killold = true; break; case 'n': ckp.name = strdup(optarg); break; case 'l': ckp.loglevel = atoi(optarg); if (ckp.loglevel < LOG_EMERG || ckp.loglevel > LOG_DEBUG) { quit(1, "Invalid loglevel (range %d - %d): %d", LOG_EMERG, LOG_DEBUG, ckp.loglevel); } break; case 's': ckp.socket_dir = strdup(optarg); break; case 'u': db_user = strdup(optarg); kill = optarg; while (*kill) *(kill++) = ' '; break; case 'p': db_pass = strdup(optarg); kill = optarg; if (*kill) *(kill++) = ' '; while (*kill) *(kill++) = '\0'; break; } } // if (!db_pass) // zzz if (!db_user) db_user = "postgres"; if (!ckp.name) ckp.name = "ckdb"; snprintf(buf, 15, "%s", ckp.name); prctl(PR_SET_NAME, buf, 0, 0, 0); memset(buf, 0, 15); if (!ckp.config) { ckp.config = strdup(ckp.name); realloc_strcat(&ckp.config, ".conf"); } if (!ckp.socket_dir) { // ckp.socket_dir = strdup("/tmp/"); ckp.socket_dir = strdup("/opt/"); realloc_strcat(&ckp.socket_dir, ckp.name); } trail_slash(&ckp.socket_dir); /* Ignore sigpipe */ signal(SIGPIPE, SIG_IGN); ret = mkdir(ckp.socket_dir, 0700); if (ret && errno != EEXIST) quit(1, "Failed to make directory %s", ckp.socket_dir); // parse_config(&ckp); if (!ckp.logdir) ckp.logdir = strdup("logs"); /* Create the log directory */ trail_slash(&ckp.logdir); ret = mkdir(ckp.logdir, 0700); if (ret && errno != EEXIST) quit(1, "Failed to make log directory %s", ckp.logdir); /* Create the logfile */ sprintf(buf, "%s%s.log", ckp.logdir, ckp.name); ckp.logfp = fopen(buf, "a"); if (!ckp.logfp) quit(1, "Failed to open log file %s", buf); ckp.logfd = fileno(ckp.logfp); ckp.main.ckp = &ckp; ckp.main.processname = strdup("main"); ckp.main.sockname = strdup("listener"); write_namepid(&ckp.main); create_process_unixsock(&ckp.main); srand((unsigned int)time(NULL)); create_pthread(&ckp.pth_listener, listener, &ckp.main); handler.sa_flags = 0; sigemptyset(&handler.sa_mask); sigaction(SIGTERM, &handler, NULL); sigaction(SIGINT, &handler, NULL); /* Shutdown from here if the listener is sent a shutdown message */ join_pthread(ckp.pth_listener); clean_up(&ckp); return 0; }