Browse Source

Merge branch 'master' of bitbucket.org:ckolivas/ckpool

master
Con Kolivas 9 years ago
parent
commit
d23dc06629
  1. 13
      configure.ac
  2. 12
      pool/base.php
  3. 51
      pool/db.php
  4. 2
      pool/inc.php
  5. 60
      pool/page.php
  6. 121
      pool/page_2fa.php
  7. 22
      pool/page_addrmgt.php
  8. 7
      pool/page_allwork.php
  9. 4
      pool/page_blocks.php
  10. 68
      pool/page_payout.php
  11. 50
      pool/page_reg.php
  12. 7
      pool/page_reset.php
  13. 45
      pool/page_settings.php
  14. 65
      pool/page_workers.php
  15. 5
      pool/prime.php
  16. 5
      sql/ckdb.sql
  17. 38
      sql/v1.0.0-v1.0.1.sql
  18. 4
      src/Makefile.am
  19. 2
      src/ckdb.c
  20. 131
      src/ckdb.h
  21. 330
      src/ckdb_cmd.c
  22. 239
      src/ckdb_crypt.c
  23. 331
      src/ckdb_data.c
  24. 189
      src/ckdb_dbio.c
  25. 55
      src/ckpool.c
  26. 22
      src/libckpool.c

13
configure.ac

@ -39,6 +39,7 @@ AC_CHECK_HEADERS(sys/types.h sys/socket.h sys/stat.h linux/un.h netdb.h)
AC_CHECK_HEADERS(stdint.h netinet/in.h netinet/tcp.h sys/ioctl.h getopt.h)
AC_CHECK_HEADERS(sys/epoll.h libpq-fe.h postgresql/libpq-fe.h grp.h)
AC_CHECK_HEADERS(gsl/gsl_math.h gsl/gsl_cdf.h)
AC_CHECK_HEADERS(openssl/x509.h openssl/hmac.h)
PTHREAD_LIBS="-lpthread"
MATH_LIBS="-lm"
@ -64,13 +65,17 @@ if test "x$ckdb" != "xno"; then
not found. Install it or disable support with --without-ckdb" && exit 1)
AC_CHECK_LIB([gslcblas], [main],[GSLCBLAS=-lgslcblas],echo "Error: Required library gslcblas
not found. Install it or disable support with --without-ckdb" && exit 1)
AC_CHECK_LIB([ssl], [main],[SSL=-lssl],echo "Error: Required library ssl
not found. Install it or disable support with --without-ckdb" && exit 1)
AC_CHECK_LIB([crypto], [main],[SSL=-lcrypto],echo "Error: Required library crypto
not found. Install it or disable support with --without-ckdb" && exit 1)
AC_DEFINE([USE_CKDB], [1], [Defined to 1 if ckdb support required])
PQ_LIBS="-lpq -lgsl -lgslcblas"
DB_LIBS="-lpq -lgsl -lgslcblas -lssl -lcrypto"
else
PQ_LIBS=""
DB_LIBS=""
fi
AM_CONDITIONAL([WANT_CKDB], [test "x$ckdb" != "xno"])
AC_SUBST(PQ_LIBS)
AC_SUBST(DB_LIBS)
AC_OUTPUT([Makefile] [src/Makefile])
@ -80,7 +85,7 @@ echo " CPPFLAGS.............: $CPPFLAGS"
echo " CFLAGS...............: $CFLAGS"
echo " LDFLAGS..............: $LDFLAGS"
echo " LDADD................: $PTHREAD_LIBS $MATH_LIBS $RT_LIBS $JANSSON_LIBS"
echo " db LDADD.............: $PQ_LIBS"
echo " db LDADD.............: $DB_LIBS"
echo
echo "Installation...........: make install (as root if needed, with 'su' or 'sudo')"
echo " prefix...............: $prefix"

12
pool/base.php

@ -323,12 +323,12 @@ session_start();
#
include_once('db.php');
#
function validUserPass($user, $pass)
function validUserPass($user, $pass, $twofa)
{
$rep = checkPass($user, $pass);
$rep = checkPass($user, $pass, $twofa);
if ($rep != null)
$ans = repDecode($rep);
usleep(100000); // Max 10x per second
usleep(500000); // Max twice per second
if ($rep != null && $ans['STATUS'] == 'ok')
{
$key = 'ckp'.rand(1000000,9999999);
@ -352,7 +352,7 @@ function logout()
}
}
#
function requestRegister()
function requestLoginRegReset()
{
$reg = getparam('Register', true);
$reg2 = getparam('Reset', false);
@ -397,7 +397,9 @@ function tryLogInOut()
return;
}
$valid = validUserPass($user, $pass);
$twofa = getparam('2fa', false);
$valid = validUserPass($user, $pass, $twofa);
if (!$valid)
$loginfailed = true;
}

51
pool/db.php

@ -166,23 +166,18 @@ function homeInfo($user)
if ($rep === false)
$ans = false;
else
{
$ans = repDecode($rep);
// if ($ans['lastblock'] == '?')
// {
// $ans['lastblock'] = 1401237522;
// $ans['lastblock'] = 1403819191;
// $ans['lastblock'] = 1407113822;
// }
}
return $ans;
}
#
function checkPass($user, $pass)
function checkPass($user, $pass, $twofa)
{
$passhash = myhash($pass);
$flds = array('username' => $user, 'passwordhash' => $passhash);
if (nuem($twofa))
$twofa = 0;
$flds = array('username' => $user, 'passwordhash' => $passhash,
'2fa' => $twofa);
$msg = msgEncode('chkpass', 'chkpass', $flds, $user);
$rep = sendsockreply('checkPass', $msg);
if (!$rep)
@ -190,11 +185,14 @@ function checkPass($user, $pass)
return $rep;
}
#
function setPass($user, $oldpass, $newpass)
function setPass($user, $oldpass, $newpass, $twofa)
{
$oldhash = myhash($oldpass);
$newhash = myhash($newpass);
$flds = array('username' => $user, 'oldhash' => $oldhash, 'newhash' => $newhash);
if (nuem($twofa))
$twofa = 0;
$flds = array('username' => $user, 'oldhash' => $oldhash,
'newhash' => $newhash, '2fa' => $twofa);
$msg = msgEncode('newpass', 'newpass', $flds, $user);
$rep = sendsockreply('setPass', $msg);
if (!$rep)
@ -202,10 +200,12 @@ function setPass($user, $oldpass, $newpass)
return repDecode($rep);
}
#
function resetPass($user, $newpass)
function resetPass($user, $newpass, $twofa)
{
$newhash = myhash($newpass);
$flds = array('username' => $user, 'newhash' => $newhash);
if (nuem($twofa))
$twofa = 0;
$flds = array('username' => $user, 'newhash' => $newhash, '2fa' => $twofa);
$msg = msgEncode('newpass', 'newpass', $flds, $user);
$rep = sendsockreply('resetPass', $msg);
if (!$rep)
@ -213,10 +213,24 @@ function resetPass($user, $newpass)
return repDecode($rep);
}
#
function get2fa($user, $action, $entropy, $value)
{
if ($value === null)
$value = '';
$flds = array('username' => $user, 'action' => $action,
'entropy' => $entropy, 'value' => $value);
$msg = msgEncode('2fa', '2fa', $flds, $user);
$rep = sendsockreply('get2fa', $msg);
if (!$rep)
dbdown();
return repDecode($rep);
}
#
function userReg($user, $email, $pass)
{
$passhash = myhash($pass);
$flds = array('username' => $user, 'emailaddress' => $email, 'passwordhash' => $passhash);
$flds = array('username' => $user, 'emailaddress' => $email,
'passwordhash' => $passhash);
$msg = msgEncode('adduser', 'reg', $flds, $user);
$rep = sendsockreply('userReg', $msg);
if (!$rep)
@ -224,7 +238,7 @@ function userReg($user, $email, $pass)
return repDecode($rep);
}
#
function userSettings($user, $email = null, $addr = null, $pass = null)
function userSettings($user, $email = null, $addr = null, $pass = null, $twofa = null)
{
$tmo = false;
$flds = array('username' => $user);
@ -246,7 +260,12 @@ function userSettings($user, $email = null, $addr = null, $pass = null)
$tmo = 3; # 3x the timeout
}
if ($pass != null)
{
$flds['passwordhash'] = myhash($pass);
if (nuem($twofa))
$twofa = 0;
$flds['2fa'] = $twofa;
}
$msg = msgEncode('usersettings', 'userset', $flds, $user);
$rep = sendsockreply('userSettings', $msg, $tmo);
if (!$rep)

2
pool/inc.php

@ -91,6 +91,8 @@ span.login {float:right;margin-left:8px;margin-right:24px;}
span.hil {color:blue;}
span.user {color:green;}
span.addr {color:brown;}
span.hdr {font-weight:bold;text-decoration:underline;}
span.nn {font-weight:bold;color:red;}
span.warn {color:orange;font-weight:bold;}
span.urg {color:red;font-weight:bold;}
span.err {color:red;font-weight:bold;font-size:120%;}

60
pool/page.php

@ -165,6 +165,7 @@ function pgtop($info, $dotop, $user, $douser)
$nlb = '?';
$pac = '0';
$per = '0';
$perset = false;
$uhr = '?GHs';
$u1hr = '';
if ($info !== false)
@ -244,12 +245,46 @@ function pgtop($info, $dotop, $user, $douser)
}
}
if (isset($info['blockshareinv']))
{
$shinv = $info['blockshareinv'];
$per = siprefmt($shinv, 1);
$perset = true;
if (isset($info['blockshareacc']))
{
$shacc = $info['blockshareacc'];
if (($shacc+$shinv) > 0)
{
$amt = 100.0 * $shinv / ($shacc + $shinv);
if (round($amt, 2) > 9.99)
$per .= ' ('.number_format($amt, 1).'%)';
else
$per .= ' ('.number_format($amt, 2).'%)';
}
}
}
if (isset($info['blockerr']))
{
$rej = $info['blockerr'];
$per = number_format($info['blockerr'], 0);
if (isset($info['blockacc']) && ($acc+$rej) > 0)
$per .= ' ('.number_format(100.0*$rej/($acc+$rej), 3).'%)';
if ($perset == false)
$per = '';
else
$per .= ' · ';
$inv = $info['blockerr'];
$per .= siprefmt($inv, 1);
if (isset($info['blockacc']))
{
$acc = $info['blockacc'];
if (($acc+$inv) > 0)
{
$amt = 100.0 * $inv / ($acc + $inv);
if (round($amt, 2) > 9.99)
$per .= ' ('.number_format($amt, 1).'%)';
else
$per .= ' ('.number_format($amt, 2).'%)';
}
}
}
if (isset($info['u_hashrate5m']))
@ -350,7 +385,7 @@ function pgtop($info, $dotop, $user, $douser)
$top .= "<td class=topdat>&nbsp;$phr</td></tr>";
$top .= '<tr><td class=topdes>Shares:&nbsp;</td>';
$top .= "<td class=topdat>&nbsp;$pac</td></tr>";
$top .= '<tr id=mini1><td class=topdes>Invalid:&nbsp;</td>';
$top .= '<tr id=mini1><td class=topdes>Invalids:&nbsp;</td>';
$top .= "<td class=topdat>&nbsp;$per</td></tr></table>";
$top .= '</td><td>';
$top .= '<table cellpadding=1 cellspacing=0 border=0 width=100%>';
@ -374,17 +409,10 @@ function pgtop($info, $dotop, $user, $douser)
list($who, $whoid) = validate();
if ($who == false)
{
$top .= makeForm('')."
<table cellpadding=0 cellspacing=0 border=0><tr><td>
<table cellpadding=0 cellspacing=0 border=0><tr>
<td>User:</td><td><input type=text name=User size=10 value=''></td>
</tr><tr>
<td>Pass:</td><td><input type=password name=Pass size=10 value=''></td>
</tr></table></td><td>
<table cellpadding=0 cellspacing=0 border=0><tr>
<td>&nbsp;<input type=submit name=Login value=Login></td></tr><tr>
<td>&nbsp;&nbsp;<input type=submit name=Register value='Register/Reset'></td></tr></table>
</td></tr></table></form>";
$top .= '<table cellpadding=0 cellspacing=0 border=0><tr><td>';
$top .= '<a href=https://' . $_SERVER['SERVER_NAME'];
$top .= '/index.php?Register=1>Login<br>Register</a>';
$top .= '</td></tr></table>';
}
else
{

121
pool/page_2fa.php

@ -0,0 +1,121 @@
<?php
#
function set_2fa($data, $user, $tfa, $ans, $err)
{
$pg = '<h1>Two Factor Authentication Settings</h1>';
if ($err !== null and $err != '')
$pg .= "<span class=err>$err<br><br></span>";
$pg .= '<table cellpadding=20 cellspacing=0 border=1>';
$pg .= '<tr class=dc><td><center>';
$pg .= makeForm('2fa');
$pg .= '<table cellpadding=5 cellspacing=0 border=0>';
$pg .= '<tr class=dc><td>';
switch ($tfa)
{
case '':
$pg .= '<tr class=dl><td>';
$pg .= "You don't have 2FA setup yet<br><br>";
$pg .= 'To use 2FA you need an App on your phone/tablet<br>';
$pg .= 'The free and recommended ones that have been tested here are:<br><br>';
$pg .= "Android: Google Play 'FreeOTP Authenticator' by Red Hat<br>";
$pg .= "Apple: App Store 'OTP Auth' by Roland Moers<br><br>";
$pg .= 'Click here to start setting up 2FA: ';
$pg .= '<input type=submit name=Setup value=Setup>';
$pg .= '</td></tr>';
break;
case 'test':
$pg .= '<tr class=dc><td>';
$pg .= '2FA is not yet enabled.<br>';
$pg .= 'Your 2FA key has been created but needs testing.<br><br>';
if (isset($ans['2fa_key']))
{
$key = $ans['2fa_key'];
$sfainfo = $ans['2fa_issuer'].': '.$ans['2fa_auth'].' '.
$ans['2fa_hash'].' '.$ans['2fa_time'].'s';
$who = substr($user, 0, 8);
$sfaurl = 'otpauth://'.$ans['2fa_auth'].'/'.$ans['2fa_issuer'].
':'.htmlspecialchars($who).'?secret='.$ans['2fa_key'].
'&algorithm='.$ans['2fa_hash'].'&issuer='.$ans['2fa_issuer'];
}
else
{
$key = 'unavailable';
$sfainfo = 'unavailable';
$sfaurl = 'unavailable';
}
$pg .= "Your 2FA Secret Key is: $key<br>";
$pg .= "2FA Settings are $sfainfo<br><br>";
$pg .= "2FA URL is <a href='$sfaurl'>Click</a><br><br>";
$pg .= '2FA Value: <input name=Value value="" size=10> ';
$pg .= '<input type=submit name=Test value=Test>';
$pg .= '</td></tr>';
break;
case 'ok':
$pg .= '<tr class=dc><td>';
$pg .= '2FA is enabled on your account.<br><br>';
$pg .= 'If you wish to replace your Secret Key with a new one:<br><br>';
$pg .= 'Current 2FA Value: <input name=Value value="" size=10> ';
$pg .= '<input type=submit name=New value=New><span class=st1>*</span><br><br>';
$pg .= '<span class=st1>*</span>WARNING: replacing the Secret Key will disable 2FA<br>';
$pg .= 'until you successfully test the new key.<br><br>';
$pg .= '</td></tr>';
break;
}
$pg .= '</table></form>';
$pg .= '</center></td></tr>';
$pg .= '</table>';
return $pg;
}
#
function do2fa($data, $user)
{
$err = '';
$setup = getparam('Setup', false);
if ($setup === 'Setup')
{
// rand() included as part of the entropy
$ans = get2fa($user, 'setup', rand(1073741824,2147483647), 0);
}
else
{
$value = getparam('Value', false);
$test = getparam('Test', false);
if ($test === 'Test' and $value !== null)
$ans = get2fa($user, 'test', 0, $value);
else
{
$nw = getparam('New', false);
if ($nw === 'New' and $value !== null)
$ans = get2fa($user, 'new', rand(1073741824,2147483647), $value);
else
$ans = get2fa($user, '', 0, 0);
}
}
if ($ans['STATUS'] != 'ok')
$err = 'DBERR';
else
{
if (isset($ans['2fa_error']))
$err = $ans['2fa_error'];
}
if (!isset($ans['2fa_status']))
$tfa = null;
else
$tfa = $ans['2fa_status'];
$pg = set_2fa($data, $user, $tfa, $ans, $err);
return $pg;
}
#
function show_2fa($info, $page, $menu, $name, $user)
{
gopage($info, NULL, 'do2fa', $page, $menu, $name, $user);
}
#
?>

22
pool/page_addrmgt.php

@ -72,13 +72,24 @@ function addrmgtuser($data, $user, $err)
else
$row = 'odd';
$pg .= "<tr class=$row>";
$pg .= '<td colspan=3 class=dc>';
$pg .= '<td class=dr>';
$pg .= 'Password: <input type=password name=pass size=20>';
$pg .= '&nbsp;<input type=submit name=OK value=Save></td></tr>';
}
$pg .= '<tr><td colspan=3 class=dc><font size=-1><span class=st1>*</span>';
$pg .= '</td><td colspan=2>&nbsp;</td></tr>';
if ((($offset++) % 2) == 0)
$row = 'even';
else
$row = 'odd';
$pg .= "<tr class=$row>";
$pg .= '<td class=dr>';
$pg .= '<span class=st1>*</span>2nd Authentication: <input type=password name=2fa size=10>';
$pg .= '</td><td colspan=2 class=dl><input type=submit name=OK value=Save></td></tr>';
$pg .= "<tr><td colspan=3 class=dc><font size=-1>";
$pg .= "<span class=st1>*</span>Leave blank if you haven't enabled it<br>";
$pg .= 'You must enter your password to save changes<br>';
$pg .= 'A ratio of 0, will remove the address from the payouts</font></td></tr>';
}
$pg .= "</table><input type=hidden name=rows value=$count id=rows></form>\n";
$pg .= "<script type='text/javascript'>\n";
@ -107,6 +118,7 @@ function doaddrmgt($data, $user)
$OK = getparam('OK', false);
$count = getparam('rows', false);
$pass = getparam('pass', false);
$twofa = getparam('2fa', false);
if ($OK == 'Save' && !nuem($count) && !nuem($pass))
{
if ($count > 0 && $count < 1000)
@ -119,7 +131,7 @@ function doaddrmgt($data, $user)
if (!nuem($addr) && !nuem($ratio))
$addrarr[] = array('addr' => $addr, 'ratio' => $ratio);
}
$ans = userSettings($user, null, $addrarr, $pass);
$ans = userSettings($user, null, $addrarr, $pass, $twofa);
if ($ans['STATUS'] != 'ok')
$err = $ans['ERROR'];
}

7
pool/page_allwork.php

@ -10,6 +10,7 @@ function doallwork($data, $user)
$totshare = 0;
$totdiff = 0;
$totshrate = 0;
$totinvalid = 0;
$totrate = 0;
$offset = 0;
@ -26,13 +27,13 @@ function doallwork($data, $user)
{
$pg .= workuser($data, $ans['username:'.$i],
$offset, $totshare, $totdiff,
$totinvalid, $totrate,
$totshrate, $totinvalid, $totrate,
$blockacc, $blockreward, 3600);
}
}
$pg .= worktotal($offset, $totshare, $totdiff, $totinvalid, $totrate,
$blockacc, $blockreward);
$pg .= worktotal($offset, $totshare, $totdiff, $totshrate, $totinvalid,
$totrate, $blockacc, $blockreward);
$pg .= "</table>\n";

4
pool/page_blocks.php

@ -204,7 +204,7 @@ function doblocks($data, $user)
{
$pct = 100.0 * $diffratio;
list($fg, $bg) = pctcolour($pct);
$bpct = "<font color=$fg>".number_format($pct, 2).'%</font>';
$bpct = "<font color=$fg>$approx".number_format($pct, 3).'%</font>';
$bg = " bgcolor=$bg";
$blktot += $diffacc;
if ($stat != 'Orphan')
@ -230,7 +230,7 @@ function doblocks($data, $user)
$pg .= "<td class=dl$ex>".utcd($ans['firstcreatedate:'.$i]).'</td>';
$pg .= "<td class=dr$ex>$stat</td>";
$pg .= "<td class=dr>$stara$approx$acc</td>";
$pg .= "<td class=dr$bg>$approx$bpct</td>";
$pg .= "<td class=dr$bg>$bpct</td>";
$pg .= "<td class=dr>$cdfdsp</td>";
$pg .= "</tr>\n";
}

68
pool/page_payout.php

@ -2,20 +2,60 @@
#
function dopayout($data, $user)
{
$pg = '<h1>Payouts</h1>';
$pg .= '<table width=75% cellpadding=0 cellspacing=0 border=0>';
$pg .= '<tr><td class=dc>';
$pg .= 'We use PPLNS (pay per last N shares)<br><br>';
$pg .= 'The N value used for PPLNS is 5 times the network difficulty';
$pg .= ' when a block is found,<br>';
$pg .= 'but includes the full shift at the start and end of the range,<br>';
$pg .= 'so it usually will be a bit more than 5N.<br><br>';
$pg .= 'Shifts are ~50min long, however, when we find any pool blocks<br>';
$pg .= 'the current shift ends at the point the block was found.<br>';
$pg .= 'A ckpool restart will also start a new shift.<br><br>';
$pg .= 'Transaction fees are included in the miner payout.<br>';
$pg .= 'Pool fee is 0.9% of the total.<br>';
$pg .= '</td></tr></table>';
$t = '<span class=nn>5</span>';
$ot = '<span class=nn>1/5</span>';
$n = '<span class=nn>5Nd</span>';
$n1 = '<span class=nn>N</span>';
$n1d = '<span class=nn>Nd</span>';
$bc = '+101 Confirms';
$bm = 'Matured';
$nd = 0;
if (isset($data['info']['currndiff']))
$nd = $data['info']['currndiff'];
$nv = number_format($nd, 1);
$nv5 = number_format(5.0*$nd, 1);
$pg = "<h1>Payouts</h1>
<table width=75% cellpadding=0 cellspacing=0 border=0><tr><td>
<span class=hdr>What payout method does the pool use?</span><br><br>
We use <b>PPL${n1}S</b> (<b>P</b>ay <b>P</b>er <b>L</b>ast $n1 <b>S</b>hares)<br><br>
<b>PPL${n1}S</b> means that when a block is found, the block reward is shared among the last $n1 shares that miners sent to the pool, up to when the block was found.<br>
The $n1 value the pool uses is $t times the network difficulty when the block is found - '$n'.<br><br>
<span class=hdr>How much of each block does the pool reward?</span><br><br>
Transaction fees are included in the miner reward.<br>
Pool fee is 0.9% of the total.<br><br>
<span class=hdr>How do the payments work?</span><br><br>
The $n means the pool rewards $t times the expected number of shares, each time a block is found.<br>
So each share will be paid appoximately $ot of it's expected value, in each block it gets a reward,<br>
but each share is also expected, on average, to be rewarded $t times in blocks found after the share is submitted to the pool.<br>
i.e. if pool luck was always 100% then each share is expected to be rewarded $t times.<br><br>
If pool luck is better than 100%, then the average share reward will be better than $t times.<br>
If pool luck is lower than 100%, then the average share reward will be less than $t times.<br><br>
<span class=hdr>What's a shift?</span></br><br>
When your miner sends shares to the pool, the shares are not stored individually, but rather summarised into shifts.<br>
Shifts are ~50min or less in length.<br>
Aproximately every 30s, the pool generates new work and sends that to all the miners.<br>
The pool also sends new work every time a block is found on the Bitcoin network.<br>
A shift summarises all the shares submitted to the pool for 100 work changes.<br>
However, when we find pool blocks, the current shift ends at the work in which the block was found.<br>
A ckpool restart will also end the current shift and start a new one.<br><br>
<span class=hdr>So, what's the $n value?</span><br><br>
The current Bitcoin network value for $n1d is $nv and thus $n is <b>$nv5</b><br>
Bitcoin adjusts the $n1d value every 2016 blocks, which is about every 2 weeks.<br><br>
When a block is found, the reward process counts back shifts until the total share difficulty included is $n.<br>
Since shares are summarised into shifts, it will include the full shift at the end of the range counting backwards,<br>
so it usually will be a bit more than $n.<br><br>
<span class=hdr>When are payments sent out?</span><br><br>
The block 'Status' must first reach '$bc' on the Blocks page, and then is flagged as '$bm', before the reward is distributed.<br>
The block reward is sent out manually soon after that.<br><br>
</td></tr></table>";
return $pg;
}
#

50
pool/page_reg.php

@ -15,7 +15,30 @@ function doregres($data, $u)
else
$mail = '';
$pg = '<br><br><table cellpadding=5 cellspacing=0 border=1><tr><td class=dc>';
$pg = '<br><br><h1>Choose one:</h1>';
$pg .= '<table cellpadding=5 cellspacing=0 border=1><tr><td class=dc>';
$pg .= '<h1>Login</h1>';
if (isset($data['data']['error']))
$pg .= "<br><b>".$data['data']['error']." - please try again</b><br><br>";
$pg .= makeForm('');
$pg .= "
<table>
<tr><td class=dr>Username:</td>
<td class=dl><input name=User value=''></td></tr>
<tr><td class=dr>Password:</td>
<td class=dl><input type=password name=Pass value=''></td></tr>
<tr><td class=dr><span class=st1>*</span>2nd Authentication:</td>
<td class=dl><input type=password name=2fa size=10></td></tr>
<tr><td colspan=2 class=dc><font size=-1><span class=st1>*</span>
Leave blank if you haven't enabled it</font></td></tr>
<tr><td>&nbsp;</td>
<td class=dl><input type=submit name=Login value=Login></td></tr>
</table>
</form>";
$pg.= '</td></tr><tr><td class=dc>';
$pg .= '<h1>Register</h1>';
if (isset($data['data']['error']))
@ -28,14 +51,14 @@ function doregres($data, $u)
<tr><td class=dr>Email:</td>
<td class=dl><input name=mail value=\"$mail\"></td></tr>
<tr><td class=dr>Password:</td>
<td class=dl><input type=password name=pass></td></tr>
<td class=dl><input type=password name=pass value=''></td></tr>
<tr><td class=dr>Retype Password:</td>
<td class=dl><input type=password name=pass2></td></tr>
<td class=dl><input type=password name=pass2 value=''></td></tr>
<tr><td>&nbsp;</td>
<td class=dl><input type=submit name=Register value=Register></td></tr>
<tr><td colspan=2 class=dc><br><font size=-1><span class=st1>*</span>
All fields are required</font></td></tr>
<tr><td colspan=2 class=dc><br>". passrequires() . "</font></td></tr>
All fields are required<br>Your Username can't be a BTC address</font></td></tr>
<tr><td colspan=2 class=dc><br>". passrequires() . "</td></tr>
</table>
</form>";
@ -51,10 +74,9 @@ function doregres($data, $u)
<td class=dl><input name=mail value=''></td></tr>
<tr><td>&nbsp;</td>
<td class=dl><input type=submit name=Reset value=Reset></td></tr>
<tr><td colspan=2 class=dc><br><font size=-1><span class=st1>*</span>
All fields are required</font></td></tr>
<tr><td colspan=2 class=dc><br><font size=-1>
An Email will be sent to you, to let you reset your password</font></td></tr>
If you enter the details correctly,<br>
an Email will be sent to you to let you reset your password</font></td></tr>
</table>
</form>";
@ -103,6 +125,12 @@ function try_reg($info, $page, $menu, $name, $u)
$ok = false;
else
{
if (stripos($mail, 'hotmail') !== false)
{
$ok = false;
$data['error'] = "hotmail not allowed";
}
if (safepass($pass) !== true)
{
$ok = false;
@ -184,9 +212,6 @@ function try_reset($info, $page, $menu, $name, $u)
$user = getparam('user', false);
$mail = trim(getparam('mail', false));
// Slow this right down
usleep(500000);
$data = array();
if (!nuem($user))
@ -208,6 +233,9 @@ function try_reset($info, $page, $menu, $name, $u)
#
function show_reg($info, $page, $menu, $name, $u)
{
// Slow this right down
usleep(1000000);
$reg = getparam('Register', false);
if ($reg !== NULL)
try_reg($info, $page, $menu, $name, $u);

7
pool/page_reset.php

@ -20,10 +20,12 @@ function allow_reset($error)
<td class=dl><input type=password name=pass></td></tr>
<tr><td class=dr>Retype Password:</td>
<td class=dl><input type=password name=pass2></td></tr>
<tr><td class=dr><span class=st1>*</span>2nd Authentication:</td>
<td class=dl><input type=password name=2fa size=10></td></tr>
<tr><td colspan=2 class=dc><br><font size=-1><span class=st1>*</span>
Leave blank if you haven't enabled it</font></td></tr>
<tr><td>&nbsp;</td>
<td class=dl><input type=submit name=Update value=Update></td></tr>
<tr><td colspan=2 class=dc><br><font size=-1><span class=st1>*</span>
All fields are required</font></td></tr>
</table>
</form>";
@ -61,6 +63,7 @@ function dbreset()
$pass = getparam('pass', true);
$pass2 = getparam('pass2', true);
$twofa = getparam('2fa', true);
if (nuem($pass) || nuem($pass2))
return allow_reset('Enter both passwords');

45
pool/page_settings.php

@ -29,6 +29,14 @@ function settings($data, $user, $email, $addr, $err)
$pg .= '</td><td class=dl>';
$pg .= '<input type=password name=pass size=20>';
$pg .= '</td></tr>';
$pg .= '<tr class=dc><td class=dr nowrap>';
$pg .= '<span class=st1>*</span>2nd Authentication:';
$pg .= '</td><td class=dl>';
$pg .= '<input type=password name=2fa size=10>';
$pg .= '</td></tr>';
$pg .= '<tr class=dc><td colspan=2 class=dc><font size=-1>';
$pg .= "<span class=st1>*</span>Leave blank if you haven't enabled it</font>";
$pg .= '</td></tr>';
$pg .= '<tr class=dc><td class=dr colspan=2>';
$pg .= 'Change: <input type=submit name=Change value=EMail>';
$pg .= '</td></tr>';
@ -54,6 +62,14 @@ function settings($data, $user, $email, $addr, $err)
$pg .= '</td><td class=dl>';
$pg .= '<input type=password name=pass size=20>';
$pg .= '</td></tr>';
$pg .= '<tr class=dc><td class=dr nowrap>';
$pg .= '<span class=st1>*</span>2nd Authentication:';
$pg .= '</td><td class=dl>';
$pg .= '<input type=password name=2fa size=10>';
$pg .= '</td></tr>';
$pg .= '<tr class=dc><td colspan=2 class=dc><font size=-1>';
$pg .= "<span class=st1>*</span>Leave blank if you haven't enabled it</font>";
$pg .= '</td></tr>';
$pg .= '<tr class=dc><td class=dr colspan=2>';
$pg .= 'Change: <input type=submit name=Change value=Address>';
$pg .= '</td></tr>';
@ -83,6 +99,14 @@ function settings($data, $user, $email, $addr, $err)
$pg .= '</td><td class=dl>';
$pg .= '<input type=password name=pass2 size=20>';
$pg .= '</td></tr>';
$pg .= '<tr class=dc><td class=dr nowrap>';
$pg .= '<span class=st1>*</span>2nd Authentication:';
$pg .= '</td><td class=dl>';
$pg .= '<input type=password name=2fa size=10>';
$pg .= '</td></tr>';
$pg .= '<tr class=dc><td colspan=2 class=dc><font size=-1>';
$pg .= "<span class=st1>*</span>Leave blank if you haven't enabled it</font>";
$pg .= '</td></tr>';
$pg .= '<tr class=dc><td class=dr colspan=2>';
$pg .= 'Change: <input type=submit name=Change value=Password>';
$pg .= '</td></tr>';
@ -103,10 +127,16 @@ function dosettings($data, $user)
{
case 'EMail':
$email = getparam('email', false);
if (stripos($email, 'hotmail') !== false)
$err = 'hotmail not allowed';
else
{
$pass = getparam('pass', false);
$ans = userSettings($user, $email, null, $pass);
$twofa = getparam('2fa', false);
$ans = userSettings($user, $email, null, $pass, $twofa);
$err = 'EMail changed';
$check = true;
}
break;
case 'Address':
if (!isset($data['info']['u_multiaddr']))
@ -114,7 +144,8 @@ function dosettings($data, $user)
$addr = getparam('baddr', false);
$addrarr = array(array('addr' => $addr));
$pass = getparam('pass', false);
$ans = userSettings($user, null, $addrarr, $pass);
$twofa = getparam('2fa', false);
$ans = userSettings($user, null, $addrarr, $pass, $twofa);
$err = 'Payout address changed';
$check = true;
}
@ -123,16 +154,14 @@ function dosettings($data, $user)
$oldpass = getparam('oldpass', false);
$pass1 = getparam('pass1', false);
$pass2 = getparam('pass2', false);
$twofa = getparam('2fa', false);
if (!safepass($pass1))
{
$err = "Password is unsafe - requires 6 or more characters, including<br>" .
"at least one of each uppercase, lowercase and digits, but not Tab";
}
$err = 'Unsafe password. ' . passrequires();
elseif ($pass1 != $pass2)
$err = "Passwords don't match";
else
{
$ans = setPass($user, $oldpass, $pass1);
$ans = setPass($user, $oldpass, $pass1, $twofa);
$err = 'Password changed';
$check = true;
}
@ -189,7 +218,7 @@ function dosettings($data, $user)
$old = $_SESSION['old_set_email'];
else
$old = null;
emailAddressChanged($email, zeip(), $emailinfo, $old);
# emailAddressChanged($email, zeip(), $emailinfo, $old);
break;
case 'Address':
payoutAddressChanged($email, zeip(), $emailinfo);

65
pool/page_workers.php

@ -10,9 +10,10 @@ function worktitle($data, $user)
$pg .= "<td class=dr><span class=nb><$r id=srtlst data-sf=n2>:Last</span> Share</td>";
$pg .= '<td class=dr>Shares</td>';
$pg .= "<td class=dr><span class=nb><$r id=srtdiff data-sf=r4>:Diff</span></td>";
$pg .= '<td class=dr>Invalid</td>';
$pg .= "<td class=dr><span class=nb><$r id=srtshrate data-sf=r5>:Share Rate</span></td>";
$pg .= "<td class=dr><span class=nb><$r id=srtinv data-sf=r6>:Invalid</span></td>";
$pg .= '<td class=dr>Block %</td>';
$pg .= "<td class=dr><span class=nb><$r id=srtrate data-sf=r7>:Hash</span> Rate</td>";
$pg .= "<td class=dr><span class=nb><$r id=srtrate data-sf=r8>:Hash</span> Rate</td>";
$pg .= "</tr>\n";
return $pg;
}
@ -23,7 +24,7 @@ function workhashorder($a, $b)
}
#
function workuser($data, $user, &$offset, &$totshare, &$totdiff,
&$totinvalid, &$totrate, &$blockacc,
&$totshrate, &$totinvalid, &$totrate, &$blockacc,
&$blockreward, $old = false, $srt = false,
$one = false, &$title)
{
@ -63,6 +64,8 @@ function workuser($data, $user, &$offset, &$totshare, &$totdiff,
'w_diffacc' => $ans['w_diffacc:'.$i],
'w_diffinv' => $ans['w_diffinv:'.$i],
'w_lastdiff' => $ans['w_lastdiff:'.$i],
'w_active_diffacc' => $ans['w_active_diffacc:'.$i],
'w_active_start' => $ans['w_active_start:'.$i],
'w_uhr' => $uhr);
}
@ -92,25 +95,49 @@ function workuser($data, $user, &$offset, &$totshare, &$totdiff,
$shareacc = number_format($all[$i]['w_shareacc'], 0);
$totshare += $all[$i]['w_shareacc'];
$diffacc = number_format($all[$i]['w_diffacc'], 0);
$ds = round($all[$i]['w_diffacc']);
$totdiff += $all[$i]['w_diffacc'];
$dacc = $all[$i]['w_diffacc'];
$diffacc = number_format($dacc, 0);
$ds = round($dacc);
$totdiff += $dacc;
$pg .= "<td class=dr>$shareacc</td>";
$pg .= "<td class=dr data-srt=$ds>$diffacc</td>";
$dtot = $all[$i]['w_diffacc'] + $all[$i]['w_diffinv'];
$acthr = '0';
$acthrv = 0;
$actstt = $all[$i]['w_active_start'];
if ($actstt > 0)
{
$elapsed = $ans['STAMP'] - $actstt;
if ($elapsed > 0)
{
$acthrv = $all[$i]['w_active_diffacc'] *
pow(2,32) / $elapsed;
$acthr = dsprate($acthrv);
$totshrate += $acthrv;
}
}
$pg .= "<td class=dr data-srt=$acthrv>$acthr</td>";
$dinv = $all[$i]['w_diffinv'];
$dtot = $dacc + $dinv;
if ($dtot > 0)
$rej = number_format(100.0 * $all[$i]['w_diffinv'] / $dtot, 3);
{
$rejf = $dinv / $dtot;
$rej = number_format(100.0 * $rejf, 3);
}
else
{
$rejf = 0;
$rej = '0';
$totinvalid += $all[$i]['w_diffinv'];
}
$totinvalid += $dinv;
$pg .= "<td class=dr>$rej%</td>";
$pg .= "<td class=dr data-srt=$rejf>$rej%</td>";
if ($blockacc <= 0)
$blkpct = '&nbsp;';
else
$blkpct = number_format(100.0 * $all[$i]['w_diffacc'] / $blockacc, 3) . '%';
$blkpct = number_format(100.0 * $dacc / $blockacc, 3) . '%';
$pg .= "<td class=dr>$blkpct</td>";
@ -136,9 +163,11 @@ function workuser($data, $user, &$offset, &$totshare, &$totdiff,
return $pg;
}
#
function worktotal($offset, $totshare, $totdiff, $totinvalid, $totrate, $blockacc, $blockreward)
function worktotal($offset, $totshare, $totdiff, $totshrate, $totinvalid,
$totrate, $blockacc, $blockreward)
{
$pg = '';
$totshrate = dsprate($totshrate);
$totrate = dsprate($totrate);
if (($offset % 2) == 0)
$row = 'even';
@ -149,6 +178,7 @@ function worktotal($offset, $totshare, $totdiff, $totinvalid, $totrate, $blockac
$pg .= "<td class=dr>$shareacc</td>";
$diffacc = number_format($totdiff, 0);
$pg .= "<td class=dr>$diffacc</td>";
$pg .= "<td class=dr>$totshrate</td>";
$dtot = $totdiff + $totinvalid;
if ($dtot > 0)
$rej = number_format(100.0 * $totinvalid / $dtot, 3);
@ -172,6 +202,7 @@ function doworker($data, $user)
$totshare = 0;
$totdiff = 0;
$totshrate = 0;
$totinvalid = 0;
$totrate = 0;
$offset = 0;
@ -179,11 +210,11 @@ function doworker($data, $user)
$blockreward = 0;
$pg .= worktitle($data, $user);
$pg .= workuser($data, $user, $offset, $totshare, $totdiff, $totinvalid,
$totrate, $blockacc, $blockreward, false, true, true,
$title);
$pg .= worktotal($offset, $totshare, $totdiff, $totinvalid, $totrate,
$blockacc, $blockreward);
$pg .= workuser($data, $user, $offset, $totshare, $totdiff, $totshrate,
$totinvalid, $totrate, $blockacc, $blockreward,
false, true, true, $title);
$pg .= worktotal($offset, $totshare, $totdiff, $totshrate, $totinvalid,
$totrate, $blockacc, $blockreward);
if (false && $blockacc > 0 && $blockreward > 0)
{

5
pool/prime.php

@ -73,7 +73,8 @@ function check()
'Rewards' => 'mpayouts',
'Payments' => 'payments',
'Settings' => 'settings',
'User Settings' => 'userset'
'User Settings' => 'userset',
'2FA Settings' => '2fa'
),
'Workers' => array(
'Shifts' => 'shifts',
@ -105,7 +106,7 @@ function check()
showPage(NULL, 'reset', $dmenu, '', $who);
else
{
if (requestRegister() == true)
if (requestLoginRegReset() == true)
showPage(NULL, 'reg', $dmenu, '', $who);
else
{

5
sql/ckdb.sql

@ -21,6 +21,8 @@ CREATE TABLE users (
passwordhash character varying(256) NOT NULL,
secondaryuserid character varying(64) NOT NULL,
salt character varying(256) DEFAULT ''::character varying NOT NULL,
userdata text DEFAULT ''::text NOT NULL,
userbits bigint NOT NULL,
createdate timestamp with time zone NOT NULL,
createby character varying(64) DEFAULT ''::character varying NOT NULL,
createcode character varying(128) DEFAULT ''::character varying NOT NULL,
@ -57,6 +59,7 @@ CREATE TABLE workers (
difficultydefault integer DEFAULT 0 NOT NULL, -- 0 means default
idlenotificationenabled char DEFAULT 'n'::character varying NOT NULL,
idlenotificationtime integer DEFAULT 10 NOT NULL,
workerbits bigint NOT NULL,
createdate timestamp with time zone NOT NULL,
createby character varying(64) DEFAULT ''::character varying NOT NULL,
createcode character varying(128) DEFAULT ''::character varying NOT NULL,
@ -460,4 +463,4 @@ CREATE TABLE version (
PRIMARY KEY (vlock)
);
insert into version (vlock,version) values (1,'1.0.0');
insert into version (vlock,version) values (1,'1.0.1');

38
sql/v1.0.0-v1.0.1.sql

@ -0,0 +1,38 @@
SET SESSION AUTHORIZATION 'postgres';
BEGIN transaction;
DO $$
DECLARE ver TEXT;
BEGIN
UPDATE version set version='1.0.1' where vlock=1 and version='1.0.0';
IF found THEN
RETURN;
END IF;
SELECT version into ver from version
WHERE vlock=1;
RAISE EXCEPTION 'Wrong DB version - expect "1.0.0" - found "%"', ver;
END $$;
ALTER TABLE ONLY users
ADD COLUMN userdata text DEFAULT ''::text NOT NULL,
ADD COLUMN userbits bigint NOT NULL DEFAULT 0;
ALTER TABLE ONLY users
ALTER COLUMN userbits DROP DEFAULT;
-- match based on ckdb_data.c like_address()
UPDATE users set userbits=1 where username ~ '[13][A-HJ-NP-Za-km-z1-9]{15,}';
ALTER TABLE ONLY workers
ADD COLUMN workerbits bigint NOT NULL DEFAULT 0;
ALTER TABLE ONLY workers
ALTER COLUMN workerbits DROP DEFAULT;
END transaction;

4
src/Makefile.am

@ -22,6 +22,6 @@ notifier_LDADD = libckpool.la @JANSSON_LIBS@
if WANT_CKDB
bin_PROGRAMS += ckdb
ckdb_SOURCES = ckdb.c ckdb_cmd.c ckdb_data.c ckdb_dbio.c ckdb_btc.c \
ckdb.h klist.c ktree.c klist.h ktree.h
ckdb_LDADD = libckpool.la @JANSSON_LIBS@ @PQ_LIBS@ @MATH_LIBS@
ckdb_crypt.c ckdb.h klist.c ktree.c klist.h ktree.h
ckdb_LDADD = libckpool.la @JANSSON_LIBS@ @DB_LIBS@ @MATH_LIBS@
endif

2
src/ckdb.c

@ -3935,6 +3935,7 @@ static void *socketer(__maybe_unused void *arg)
fflush(global_ckp->logfp);
break;
case CMD_CHKPASS:
case CMD_2FA:
case CMD_ADDUSER:
case CMD_NEWPASS:
case CMD_USERSET:
@ -4262,6 +4263,7 @@ static void reload_line(PGconn *conn, char *filename, uint64_t count, char *buf)
case CMD_ADDUSER:
case CMD_NEWPASS:
case CMD_CHKPASS:
case CMD_2FA:
case CMD_USERSET:
case CMD_WORKERSET:
case CMD_BLOCKLIST:

131
src/ckdb.h

@ -54,8 +54,8 @@
*/
#define DB_VLOCK "1"
#define DB_VERSION "1.0.0"
#define CKDB_VERSION DB_VERSION"-1.112"
#define DB_VERSION "1.0.1"
#define CKDB_VERSION DB_VERSION"-1.200"
#define WHERE_FFL " - from %s %s() line %d"
#define WHERE_FFL_HERE __FILE__, __func__, __LINE__
@ -369,6 +369,7 @@ enum cmd_values {
CMD_HEARTBEAT,
CMD_NEWPASS,
CMD_CHKPASS,
CMD_2FA,
CMD_USERSET,
CMD_WORKERSET,
CMD_POOLSTAT,
@ -1017,6 +1018,9 @@ typedef struct users {
char passwordhash[TXT_BIG+1];
char secondaryuserid[TXT_SML+1];
char salt[TXT_BIG+1];
char *userdata;
int64_t databits; // non-DB field, Bitmask of userdata content
int64_t userbits; // Bitmask of user attributes
HISTORYDATECONTROLFIELDS;
} USERS;
@ -1031,6 +1035,36 @@ typedef struct users {
#define SALTSIZHEX 32
#define SALTSIZBIN 16
#define DATABITS_SEP ','
#define DATABITS_SEP_STR ","
/* databits attributes
* These are generated at dbload time from userdata
* and when the userdata is changed */
// TOTP Auth 2FA
#define USER_TOTPAUTH_NAME "totpauth"
#define USER_TOTPAUTH 0x1
// 2FA Key untested
#define USER_TEST2FA_NAME "test2fa"
#define USER_TEST2FA 0x2
#define USER_TOTP_ENA(_users) \
(((_users)->databits & (USER_TOTPAUTH | USER_TEST2FA)) == USER_TOTPAUTH)
// userbits attributes
// Address account, not a username account
#define USER_ADDRESS 0x1
// 16 x base 32 (5 bits) = 10 bytes (8 bits)
#define TOTPAUTH_KEYSIZE 10
#define TOTPAUTH_DSP_KEYSIZE 16
// Optioncontrol name
#define TOTPAUTH_ISSUER "taissuer"
// Currently only:
#define TOTPAUTH_AUTH "totp"
#define TOTPAUTH_HASH "SHA256"
#define TOTPAUTH_TIME 30
extern K_TREE *users_root;
extern K_TREE *userid_root;
extern K_LIST *users_free;
@ -1073,6 +1107,7 @@ typedef struct workers {
int32_t difficultydefault;
char idlenotificationenabled[TXT_FLAG+1];
int32_t idlenotificationtime;
int64_t workerbits; // Bitmask of worker attributes
HISTORYDATECONTROLFIELDS;
} WORKERS;
@ -1086,6 +1121,8 @@ extern K_TREE *workers_root;
extern K_LIST *workers_free;
extern K_STORE *workers_store;
// Currently no workerbits attributes
#define DIFFICULTYDEFAULT_MIN 10
#define DIFFICULTYDEFAULT_MAX 0x7fffffff
// 0 means it's not set
@ -1735,18 +1772,32 @@ typedef struct workerstatus {
tv_t last_stats;
tv_t last_idle;
// Below gets reset on each block
double diffacc;
double diffinv; // Non-acc
double diffsta;
double diffdup;
double diffhi;
double diffrej;
double shareacc;
double shareinv; // Non-acc
double sharesta;
double sharedup;
double sharehi;
double sharerej;
double block_diffacc;
double block_diffinv; // Non-acc
double block_diffsta;
double block_diffdup;
double block_diffhi;
double block_diffrej;
double block_shareacc;
double block_shareinv; // Non-acc
double block_sharesta;
double block_sharedup;
double block_sharehi;
double block_sharerej;
// Below gets reset on each idle
double active_diffacc;
double active_diffinv; // Non-acc
double active_diffsta;
double active_diffdup;
double active_diffhi;
double active_diffrej;
double active_shareacc;
double active_shareinv; // Non-acc
double active_sharesta;
double active_sharedup;
double active_sharehi;
double active_sharerej;
tv_t active_start;
} WORKERSTATUS;
#define ALLOC_WORKERSTATUS 1000
@ -2086,8 +2137,35 @@ extern cmp_t cmp_userid(K_ITEM *a, K_ITEM *b);
extern K_ITEM *find_users(char *username);
extern K_ITEM *find_userid(int64_t userid);
extern void make_salt(USERS *users);
extern void password_hash(char *username, char *passwordhash, char *salt, char *result, size_t siz);
extern void password_hash(char *username, char *passwordhash, char *salt,
char *result, size_t siz);
extern bool check_hash(USERS *users, char *passwordhash);
extern void users_databits(USERS *users);
#define users_userdata_get_hex(_users, _name, _bit, _hexlen) \
_users_userdata_get_hex(_users, _name, _bit, _hexlen, WHERE_FFL_HERE)
extern char *_users_userdata_get_hex(USERS *users, char *name, int64_t bit,
size_t *hexlen, WHERE_FFL_ARGS);
#define users_userdata_get_bin(_users, _name, _bit, _binlen) \
_users_userdata_get_bin(_users, _name, _bit, _binlen, WHERE_FFL_HERE)
extern unsigned char *_users_userdata_get_bin(USERS *users, char *name,
int64_t bit, size_t *binlen,
WHERE_FFL_ARGS);
#define users_userdata_del(_users, _name, _bit) \
_users_userdata_del(_users, _name, _bit, WHERE_FFL_HERE)
extern void _users_userdata_del(USERS *users, char *name, int64_t bit,
WHERE_FFL_ARGS);
// If we want to store a simple string, no point encoding it
#define users_userdata_add_txt(_users, _name, _bit, _hex) \
_users_userdata_add_hex(_users, _name, _bit, _hex, WHERE_FFL_HERE)
#define users_userdata_add_hex(_users, _name, _bit, _hex) \
_users_userdata_add_hex(_users, _name, _bit, _hex, WHERE_FFL_HERE)
extern void _users_userdata_add_hex(USERS *users, char *name, int64_t bit,
char *hex, WHERE_FFL_ARGS);
#define users_userdata_add_bin(_users, _name, _bit, _bin, _len) \
_users_userdata_add_bin(_users, _name, _bit, _bin, _len, WHERE_FFL_HERE)
extern void _users_userdata_add_bin(USERS *users, char *name, int64_t bit,
unsigned char *bin, size_t len,
WHERE_FFL_ARGS);
extern cmp_t cmp_useratts(K_ITEM *a, K_ITEM *b);
extern K_ITEM *find_useratts(int64_t userid, char *attname);
extern cmp_t cmp_workers(K_ITEM *a, K_ITEM *b);
@ -2285,8 +2363,11 @@ 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);
extern K_ITEM *users_add(PGconn *conn, char *username, char *emailaddress,
char *passwordhash, char *by, char *code, char *inet,
tv_t *cd, K_TREE *trf_root);
char *passwordhash, int64_t userbits, char *by,
char *code, char *inet, tv_t *cd, K_TREE *trf_root);
extern bool users_replace(PGconn *conn, K_ITEM *u_item, K_ITEM *old_u_item,
char *by, char *code, char *inet, tv_t *cd,
K_TREE *trf_root);
extern bool users_fill(PGconn *conn);
extern bool useratts_item_add(PGconn *conn, K_ITEM *ua_item, tv_t *cd, bool begun);
extern K_ITEM *useratts_add(PGconn *conn, char *username, char *attname,
@ -2455,4 +2536,20 @@ extern struct CMDS ckdb_cmds[];
extern bool btc_valid_address(char *addr);
extern void btc_blockstatus(BLOCKS *blocks);
// ***
// *** ckdb_crypt.c
// ***
#define tob32(_users, _bin, _len, _name, _olen) \
_tob32(_users, _bin, _len, _name, _olen, WHERE_FFL_HERE)
extern char *_tob32(USERS *users, unsigned char *bin, size_t len, char *name,
size_t olen, WHERE_FFL_ARGS);
extern bool gen_data(USERS *users, unsigned char *buf, size_t len,
int32_t entropy);
extern K_ITEM *gen_2fa_key(K_ITEM *old_u_item, int32_t entropy, char *by,
char *code, char *inet, tv_t *cd, K_TREE *trf_root);
extern bool check_2fa(USERS *users, int32_t value);
extern bool tst_2fa(K_ITEM *old_u_item, int32_t value, char *by, char *code,
char *inet, tv_t *cd, K_TREE *trf_root);
#endif

330
src/ckdb_cmd.c

@ -67,7 +67,7 @@ static char *cmd_adduser(PGconn *conn, char *cmd, char *id, tv_t *now, char *by,
u_item = users_add(conn, transfer_data(i_username),
transfer_data(i_emailaddress),
transfer_data(i_passwordhash),
transfer_data(i_passwordhash), 0,
by, code, inet, now, trf_root);
}
@ -84,11 +84,13 @@ static char *cmd_newpass(__maybe_unused PGconn *conn, char *cmd, char *id,
tv_t *now, char *by, char *code, char *inet,
__maybe_unused tv_t *cd, K_TREE *trf_root)
{
K_ITEM *i_username, *i_oldhash, *i_newhash, *u_item;
K_ITEM *i_username, *i_oldhash, *i_newhash, *i_2fa, *u_item;
char reply[1024] = "";
size_t siz = sizeof(reply);
bool ok = true;
char *oldhash;
int32_t value;
USERS *users;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
@ -108,6 +110,10 @@ static char *cmd_newpass(__maybe_unused PGconn *conn, char *cmd, char *id,
oldhash = EMPTY;
}
i_2fa = require_name(trf_root, "2fa", 1, (char *)intpatt, reply, siz);
if (!i_2fa)
return strdup(reply);
if (ok) {
i_newhash = require_name(trf_root, "newhash",
64, (char *)hashpatt,
@ -120,13 +126,21 @@ static char *cmd_newpass(__maybe_unused PGconn *conn, char *cmd, char *id,
K_RUNLOCK(users_free);
if (u_item) {
ok = users_update(NULL, u_item,
DATA_USERS(users, u_item);
if (USER_TOTP_ENA(users)) {
value = (int32_t)atoi(transfer_data(i_2fa));
ok = check_2fa(users, value);
}
if (ok) {
ok = users_update(NULL,
u_item,
oldhash,
transfer_data(i_newhash),
NULL,
by, code, inet, now,
trf_root,
NULL);
}
} else
ok = false;
}
@ -144,7 +158,7 @@ static char *cmd_chkpass(__maybe_unused PGconn *conn, char *cmd, char *id,
__maybe_unused char *code, __maybe_unused char *inet,
__maybe_unused tv_t *notcd, K_TREE *trf_root)
{
K_ITEM *i_username, *i_passwordhash, *u_item;
K_ITEM *i_username, *i_passwordhash, *i_2fa, *u_item;
char reply[1024] = "";
size_t siz = sizeof(reply);
USERS *users;
@ -160,6 +174,10 @@ static char *cmd_chkpass(__maybe_unused PGconn *conn, char *cmd, char *id,
if (!i_passwordhash)
return strdup(reply);
i_2fa = require_name(trf_root, "2fa", 1, (char *)intpatt, reply, siz);
if (!i_2fa)
return strdup(reply);
K_RLOCK(users_free);
u_item = find_users(transfer_data(i_username));
K_RUNLOCK(users_free);
@ -169,6 +187,10 @@ static char *cmd_chkpass(__maybe_unused PGconn *conn, char *cmd, char *id,
else {
DATA_USERS(users, u_item);
ok = check_hash(users, transfer_data(i_passwordhash));
if (ok && USER_TOTP_ENA(users)) {
uint32_t value = (int32_t)atoi(transfer_data(i_2fa));
ok = check_2fa(users, value);
}
}
if (!ok) {
@ -179,13 +201,212 @@ static char *cmd_chkpass(__maybe_unused PGconn *conn, char *cmd, char *id,
return strdup("ok.");
}
static char *cmd_2fa(__maybe_unused PGconn *conn, char *cmd, char *id,
tv_t *now, char *by, char *code, char *inet,
__maybe_unused tv_t *notcd, K_TREE *trf_root)
{
K_ITEM *i_username, *i_action, *i_entropy, *i_value, *u_item, *u_new;
char reply[1024] = "";
size_t siz = sizeof(reply);
size_t len, off;
char tmp[1024];
int32_t entropy, value;
USERS *users;
char *action, *buf = NULL, *st = NULL;
char *sfa_status = EMPTY, *sfa_error = EMPTY;
bool ok = false, key = false;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_username = require_name(trf_root, "username", 3, (char *)userpatt,
reply, siz);
if (!i_username)
return strdup(reply);
// Field always expected, blank means to report the status
i_action = require_name(trf_root, "action", 0, NULL, reply, siz);
if (!i_action)
return strdup(reply);
action = transfer_data(i_action);
/* Field always expected with a value,
* but the value is only used when generating a Secret Key */
i_entropy = require_name(trf_root, "entropy", 1, (char *)intpatt,
reply, siz);
if (!i_entropy)
return strdup(reply);
// 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);
K_RLOCK(users_free);
u_item = find_users(transfer_data(i_username));
K_RUNLOCK(users_free);
if (u_item) {
DATA_USERS(users, u_item);
APPEND_REALLOC_INIT(buf, off, len);
APPEND_REALLOC(buf, off, len, "ok.");
switch (users->databits & (USER_TOTPAUTH | USER_TEST2FA)) {
case 0:
break;
case USER_TOTPAUTH:
sfa_status = "ok";
break;
case (USER_TOTPAUTH | USER_TEST2FA):
sfa_status = "test";
key = true;
break;
default:
// USER_TEST2FA only <- currently invalid
LOGERR("%s() users databits invalid for "
"'%s/%"PRId64,
__func__,
st = safe_text_nonull(users->username),
users->databits);
goto dame;
}
if (!*action) {
ok = true;
} else if (strcmp(action, "setup") == 0) {
// Can't setup if anything is already present -> new
if (users->databits & (USER_TOTPAUTH | USER_TEST2FA))
goto dame;
entropy = (int32_t)atoi(transfer_data(i_entropy));
u_new = gen_2fa_key(u_item, entropy, by, code, inet,
now, trf_root);
if (u_new) {
ok = true;
sfa_status = "test";
key = true;
u_item = u_new;
DATA_USERS(users, u_item);
}
} else if (strcmp(action, "test") == 0) {
// Can't test if it's not ready to test
if ((users->databits & (USER_TOTPAUTH | USER_TEST2FA))
!= (USER_TOTPAUTH | USER_TEST2FA))
goto dame;
value = (int32_t)atoi(transfer_data(i_value));
ok = tst_2fa(u_item, value, by, code, inet, now,
trf_root);
if (!ok)
sfa_error = "Invalid code";
else {
key = false;
sfa_status = "ok";
}
// Report sfa_error to web
ok = true;
} else if (strcmp(action, "new") == 0) {
// Can't new if 2FA isn't already present -> setup
if ((users->databits & USER_TOTPAUTH) == 0)
goto dame;
value = (int32_t)atoi(transfer_data(i_value));
if (!check_2fa(users, value)) {
sfa_error = "Invalid code";
// Report sfa_error to web
ok = true;
} else {
entropy = (int32_t)atoi(transfer_data(i_entropy));
u_new = gen_2fa_key(u_item, entropy, by, code,
inet, now, trf_root);
if (u_new) {
ok = true;
sfa_status = "test";
key = true;
u_item = u_new;
DATA_USERS(users, u_item);
}
}
}
if (key) {
char *keystr, *issuer = "Kano";
char cd_buf[DATE_BUFSIZ];
unsigned char *bin;
OPTIONCONTROL *oc;
K_ITEM *oc_item;
size_t binlen;
bin = users_userdata_get_bin(users,
USER_TOTPAUTH_NAME,
USER_TOTPAUTH,
&binlen);
if (binlen != TOTPAUTH_KEYSIZE) {
LOGERR("%s() invalid key for '%s/%s "
"len(%d) != %d",
__func__,
st = safe_text_nonull(users->username),
USER_TOTPAUTH_NAME, (int)binlen,
TOTPAUTH_KEYSIZE);
FREENULL(st);
}
if (bin && binlen == TOTPAUTH_KEYSIZE) {
keystr = tob32(users, bin, binlen,
USER_TOTPAUTH_NAME,
TOTPAUTH_DSP_KEYSIZE);
snprintf(tmp, sizeof(tmp), "2fa_key=%s%c",
keystr, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
FREENULL(keystr);
K_RLOCK(optioncontrol_free);
oc_item = find_optioncontrol(TOTPAUTH_ISSUER,
now,
OPTIONCONTROL_HEIGHT);
K_RUNLOCK(optioncontrol_free);
if (oc_item) {
DATA_OPTIONCONTROL(oc, oc_item);
issuer = oc->optionvalue;
} else {
tv_to_buf(now, cd_buf, sizeof(cd_buf));
LOGEMERG("%s(): missing optioncontrol "
"%s (%s/%d)",
__func__, TOTPAUTH_ISSUER,
cd_buf, OPTIONCONTROL_HEIGHT);
}
// TODO: add issuer to optioncontrol
snprintf(tmp, sizeof(tmp),
"2fa_auth=%s%c2fa_hash=%s%c"
"2fa_time=%d%c2fa_issuer=%s%c",
TOTPAUTH_AUTH, FLDSEP,
TOTPAUTH_HASH, FLDSEP,
TOTPAUTH_TIME, FLDSEP,
issuer, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
}
FREENULL(bin);
}
}
if (!ok) {
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.");
}
snprintf(tmp, sizeof(tmp), "2fa_status=%s%c2fa_error=%s",
sfa_status, FLDSEP, sfa_error);
APPEND_REALLOC(buf, off, len, tmp);
LOGDEBUG("%s.%s-%s.%s", id, transfer_data(i_username), action, buf);
return buf;
}
static char *cmd_userset(PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *now, __maybe_unused char *by,
__maybe_unused char *code, __maybe_unused char *inet,
__maybe_unused tv_t *notcd, K_TREE *trf_root)
{
K_ITEM *i_username, *i_passwordhash, *i_rows, *i_address, *i_ratio;
K_ITEM *i_email, *u_item, *pa_item, *old_pa_item;
K_ITEM *i_username, *i_passwordhash, *i_2fa, *i_rows, *i_address;
K_ITEM *i_ratio, *i_email, *u_item, *pa_item, *old_pa_item;
char *email, *address;
char reply[1024] = "";
size_t siz = sizeof(reply);
@ -263,10 +484,26 @@ static char *cmd_userset(PGconn *conn, char *cmd, char *id,
"PaymentAddresses", FLDSEP, "");
APPEND_REALLOC(answer, off, len, tmp);
} else {
i_2fa = require_name(trf_root, "2fa", 1, (char *)intpatt,
reply, siz);
if (!i_2fa) {
reason = "Invalid data";
goto struckout;
}
if (!check_hash(users, transfer_data(i_passwordhash))) {
reason = "Incorrect password";
goto struckout;
}
if (USER_TOTP_ENA(users)) {
uint32_t value = (int32_t)atoi(transfer_data(i_2fa));
if (!check_2fa(users, value)) {
reason = "Invalid data";
goto struckout;
}
}
i_email = optional_name(trf_root, "email",
1, (char *)mailpatt,
reply, siz);
@ -1395,18 +1632,18 @@ static char *cmd_percent(char *cmd, char *id, tv_t *now, USERS *users)
ws_item = get_workerstatus(users->userid, workers->workername);
if (ws_item) {
DATA_WORKERSTATUS(workerstatus, ws_item);
t_diffacc += workerstatus->diffacc;
t_diffinv += workerstatus->diffinv;
t_diffsta += workerstatus->diffsta;
t_diffdup += workerstatus->diffdup;
t_diffhi += workerstatus->diffhi;
t_diffrej += workerstatus->diffrej;
t_shareacc += workerstatus->shareacc;
t_shareinv += workerstatus->shareinv;
t_sharesta += workerstatus->sharesta;
t_sharedup += workerstatus->sharedup;
t_sharehi += workerstatus->sharehi;
t_sharerej += workerstatus->sharerej;
t_diffacc += workerstatus->block_diffacc;
t_diffinv += workerstatus->block_diffinv;
t_diffsta += workerstatus->block_diffsta;
t_diffdup += workerstatus->block_diffdup;
t_diffhi += workerstatus->block_diffhi;
t_diffrej += workerstatus->block_diffrej;
t_shareacc += workerstatus->block_shareacc;
t_shareinv += workerstatus->block_shareinv;
t_sharesta += workerstatus->block_sharesta;
t_sharedup += workerstatus->block_sharedup;
t_sharehi += workerstatus->block_sharehi;
t_sharerej += workerstatus->block_sharerej;
}
/* TODO: workers_root userid+worker is ordered
@ -1692,6 +1929,8 @@ static char *cmd_workers(__maybe_unused PGconn *conn, char *cmd, char *id,
double w_shareacc, w_shareinv;
double w_sharesta, w_sharedup;
double w_sharehi, w_sharerej;
double w_active_diffacc;
tv_t w_active_start;
w_hashrate5m = w_hashrate1hr =
w_hashrate24hr = 0.0;
@ -1699,30 +1938,34 @@ static char *cmd_workers(__maybe_unused PGconn *conn, char *cmd, char *id,
if (!ws_item) {
w_lastshare.tv_sec = 0;
w_lastdiff = w_diffacc = w_diffinv =
w_diffsta = w_diffdup =
w_diffhi = w_diffrej =
w_shareacc = w_shareinv =
w_sharesta = w_sharedup =
w_sharehi = w_sharerej = 0;
w_lastdiff = w_diffacc =
w_diffinv = w_diffsta =
w_diffdup = w_diffhi =
w_diffrej = w_shareacc =
w_shareinv = w_sharesta =
w_sharedup = w_sharehi =
w_sharerej = w_active_diffacc = 0;
w_active_start.tv_sec = 0;
} else {
DATA_WORKERSTATUS(workerstatus, ws_item);
// It's bad to read possibly changing data
K_RLOCK(workerstatus_free);
w_lastshare.tv_sec = workerstatus->last_share.tv_sec;
w_lastdiff = workerstatus->last_diff;
w_diffacc = workerstatus->diffacc;
w_diffinv = workerstatus->diffinv;
w_diffsta = workerstatus->diffsta;
w_diffdup = workerstatus->diffdup;
w_diffhi = workerstatus->diffhi;
w_diffrej = workerstatus->diffrej;
w_shareacc = workerstatus->shareacc;
w_shareinv = workerstatus->shareinv;
w_sharesta = workerstatus->sharesta;
w_sharedup = workerstatus->sharedup;
w_sharehi = workerstatus->sharehi;
w_sharerej = workerstatus->sharerej;
w_diffacc = workerstatus->block_diffacc;
w_diffinv = workerstatus->block_diffinv;
w_diffsta = workerstatus->block_diffsta;
w_diffdup = workerstatus->block_diffdup;
w_diffhi = workerstatus->block_diffhi;
w_diffrej = workerstatus->block_diffrej;
w_shareacc = workerstatus->block_shareacc;
w_shareinv = workerstatus->block_shareinv;
w_sharesta = workerstatus->block_sharesta;
w_sharedup = workerstatus->block_sharedup;
w_sharehi = workerstatus->block_sharehi;
w_sharerej = workerstatus->block_sharerej;
w_active_diffacc = workerstatus->active_diffacc;
w_active_start.tv_sec = workerstatus->active_start.tv_sec;
K_RUNLOCK(workerstatus_free);
}
@ -1815,6 +2058,15 @@ static char *cmd_workers(__maybe_unused PGconn *conn, char *cmd, char *id,
double_to_buf(w_sharerej, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "w_sharerej:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(w_active_diffacc, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "w_active_diffacc:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
int_to_buf((int)(w_active_start.tv_sec), reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "w_active_start:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
}
rows++;
}
@ -1832,7 +2084,8 @@ static char *cmd_workers(__maybe_unused PGconn *conn, char *cmd, char *id,
"w_lastdiff,w_diffacc,w_diffinv,"
"w_diffsta,w_diffdup,w_diffhi,w_diffrej,"
"w_shareacc,w_shareinv,"
"w_sharesta,w_sharedup,w_sharehi,w_sharerej" : "",
"w_sharesta,w_sharedup,w_sharehi,w_sharerej,"
"w_active_diffacc,w_active_start" : "",
FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
@ -2460,7 +2713,7 @@ static char *cmd_auth_do(PGconn *conn, char *cmd, char *id, char *by,
if (!u_item) {
DATA_OPTIONCONTROL(optioncontrol, oc_item);
u_item = users_add(conn, username, EMPTY,
optioncontrol->optionvalue,
optioncontrol->optionvalue, 0,
by, code, inet, cd, trf_root);
}
}
@ -6046,6 +6299,7 @@ struct CMDS ckdb_cmds[] = {
{ CMD_ADDUSER, "adduser", false, false, cmd_adduser, SEQ_NONE, ACCESS_WEB },
{ CMD_NEWPASS, "newpass", false, false, cmd_newpass, SEQ_NONE, ACCESS_WEB },
{ CMD_CHKPASS, "chkpass", false, false, cmd_chkpass, SEQ_NONE, ACCESS_WEB },
{ CMD_2FA, "2fa", false, false, cmd_2fa, SEQ_NONE, ACCESS_WEB },
{ CMD_USERSET, "usersettings", false, false, cmd_userset, SEQ_NONE, ACCESS_WEB },
{ CMD_WORKERSET,"workerset", false, false, cmd_workerset, SEQ_NONE, ACCESS_WEB },
{ CMD_POOLSTAT, "poolstats", false, true, cmd_poolstats, SEQ_POOLSTATS, ACCESS_POOL },

239
src/ckdb_crypt.c

@ -0,0 +1,239 @@
/*
* Copyright 2015 Andrew Smith
*
* 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 <openssl/x509.h>
#include <openssl/hmac.h>
#include "ckdb.h"
#if (SHA256SIZBIN != SHA256_DIGEST_LENGTH)
#error "SHA256SIZBIN must = OpenSSL SHA256_DIGEST_LENGTH"
#endif
static char b32code[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', '2', '3', '4', '5', '6', '7'
};
#define ASSERT32(condition) __maybe_unused static char b32code_length_must_be_32[(condition)?1:-1]
ASSERT32(sizeof(b32code) == 32);
// bin is bigendian, return buf is bigendian
char *_tob32(USERS *users, unsigned char *bin, size_t len, char *name,
size_t olen, WHERE_FFL_ARGS)
{
size_t osiz = (len * 8 + 4) / 5;
char *buf, *ptr, *st = NULL;
int i, j, bits, ch;
if (osiz != olen) {
LOGEMERG("%s() of '%s' data for '%s' invalid olen=%d != osiz=%d"
WHERE_FFL,
__func__, name, safe_text_nonull(users->username),
(int)olen, (int)osiz, WHERE_FFL_PASS);
FREENULL(st);
olen = osiz;
}
buf = malloc(olen+1);
ptr = buf + olen;
*ptr = '\0';
i = 0;
while (--ptr >= buf) {
j = i / 8;
bits = (31 << (i % 8));
ch = (bin[len-j-1] & bits) >> (i % 8);
if (bits > 255)
ch |= (bin[len-j-2] & (bits >> 8)) << (8 - (i % 8));
// Shouldn't ever happen
if (ch < 0 || ch > 31) {
char *binstr = bin2hex(bin, len);
LOGEMERG("%s() failure of '%s' data for '%s' invalid "
"ch=%d, i=%d j=%d bits=%d bin=0x%s len=%d "
"olen=%d" WHERE_FFL,
__func__, name,
safe_text_nonull(users->username), ch, i, j,
bits, binstr, (int)len, (int)olen,
WHERE_FFL_PASS);
FREENULL(st);
FREENULL(binstr);
ch = 0;
}
*ptr = b32code[ch];
i += 5;
}
return buf;
}
bool gen_data(__maybe_unused USERS *users, unsigned char *buf, size_t len,
int32_t entropy)
{
unsigned char *ptr;
ssize_t ret, want, got;
int i;
int fil = open("/dev/random", O_RDONLY);
if (fil == -1)
return false;
want = (ssize_t)len;
got = 0;
while (got < want) {
ret = read(fil, buf+got, want-got);
if (ret < 0) {
close(fil);
return false;
}
got += ret;
}
close(fil);
ptr = (unsigned char *)&entropy;
for (i = 0; i < (int)sizeof(entropy) && (i + sizeof(entropy)) < len; i++)
buf[i+sizeof(entropy)] ^= *(ptr + i);
return true;
}
K_ITEM *gen_2fa_key(K_ITEM *old_u_item, int32_t entropy, char *by, char *code,
char *inet, tv_t *cd, K_TREE *trf_root)
{
unsigned char key[TOTPAUTH_KEYSIZE];
K_ITEM *u_item = NULL;
USERS *old_users, *users;
bool ok;
DATA_USERS(old_users, old_u_item);
ok = gen_data(old_users, key, sizeof(key), entropy);
if (ok) {
K_WLOCK(users_free);
u_item = k_unlink_head(users_free);
K_WUNLOCK(users_free);
DATA_USERS(users, u_item);
memcpy(users, old_users, sizeof(*users));
if (users->userdata != EMPTY) {
users->userdata = strdup(users->userdata);
if (!users->userdata)
quithere(1, "strdup OOM");
}
users_userdata_add_bin(users, USER_TOTPAUTH_NAME,
USER_TOTPAUTH, key, sizeof(key));
users_userdata_add_txt(users, USER_TEST2FA_NAME,
USER_TEST2FA, "Y");
ok = users_replace(NULL, u_item, old_u_item, by, code, inet, cd,
trf_root);
if (!ok) {
// u_item was cleaned up in user_replace()
u_item = NULL;
}
}
return u_item;
}
bool check_2fa(USERS *users, int32_t value)
{
char *st = NULL, *tmp1 = NULL, *tmp2 = NULL, *tmp3 = NULL;
unsigned char tim[sizeof(int64_t)], *bin, *hash;
unsigned int reslen;
size_t binlen;
HMAC_CTX ctx;
int64_t now;
int32_t otp;
int i, offset;
now = (int64_t)time(NULL) / TOTPAUTH_TIME;
bin = users_userdata_get_bin(users, USER_TOTPAUTH_NAME,
USER_TOTPAUTH, &binlen);
if (binlen != TOTPAUTH_KEYSIZE) {
LOGERR("%s() invalid key for '%s/%s "
"len(%d) != %d",
__func__,
st = safe_text_nonull(users->username),
USER_TOTPAUTH_NAME, (int)binlen,
TOTPAUTH_KEYSIZE);
FREENULL(st);
return false;
}
for (i = 0; i < (int)sizeof(int64_t); i++)
tim[i] = (now >> 8 * ((sizeof(int64_t) - 1) - i)) & 0xff;
LOGDEBUG("%s() '%s/%s tim=%"PRId64"=%s key=%s=%s", __func__,
st = safe_text_nonull(users->username),
USER_TOTPAUTH_NAME, now,
tmp1 = (char *)bin2hex(&tim, sizeof(tim)),
tmp2 = (char *)bin2hex(bin, TOTPAUTH_KEYSIZE),
tmp3 = tob32(users, bin, binlen,USER_TOTPAUTH_NAME, TOTPAUTH_DSP_KEYSIZE));
FREENULL(tmp3);
FREENULL(tmp2);
FREENULL(tmp1);
FREENULL(st);
hash = malloc(SHA256_DIGEST_LENGTH);
if (!hash)
quithere(1, "malloc OOM");
HMAC_CTX_init(&ctx);
HMAC_Init_ex(&ctx, bin, binlen, EVP_sha256(), NULL);
HMAC_Update(&ctx, (unsigned char *)&tim, sizeof(tim));
HMAC_Final(&ctx, hash, &reslen);
LOGDEBUG("%s() '%s/%s hash=%s", __func__,
st = safe_text_nonull(users->username),
USER_TOTPAUTH_NAME,
tmp1 = (char *)bin2hex(hash, SHA256_DIGEST_LENGTH));
FREENULL(tmp1);
FREENULL(st);
offset = hash[reslen-1] & 0xf;
otp = ((hash[offset] & 0x7f) << 24) | ((hash[offset+1] & 0xff) << 16) |
((hash[offset+2] & 0xff) << 8) | (hash[offset+3] & 0xff);
otp %= 1000000;
LOGDEBUG("%s() '%s/%s offset=%d otp=%"PRId32" value=%"PRId32,
__func__, st = safe_text_nonull(users->username),
USER_TOTPAUTH_NAME, offset, otp, value);
FREENULL(st);
FREENULL(hash);
if (otp == value)
return true;
else
return false;
}
bool tst_2fa(K_ITEM *old_u_item, int32_t value, char *by, char *code,
char *inet, tv_t *cd, K_TREE *trf_root)
{
K_ITEM *u_item;
USERS *old_users, *users;
bool ok;
DATA_USERS(old_users, old_u_item);
ok = check_2fa(old_users, value);
if (ok) {
K_WLOCK(users_free);
u_item = k_unlink_head(users_free);
K_WUNLOCK(users_free);
DATA_USERS(users, u_item);
memcpy(users, old_users, sizeof(*users));
if (users->userdata != EMPTY) {
users->userdata = strdup(users->userdata);
if (!users->userdata)
quithere(1, "strdup OOM");
}
users_userdata_del(users, USER_TEST2FA_NAME, USER_TEST2FA);
ok = users_replace(NULL, u_item, old_u_item, by, code, inet, cd,
trf_root);
// if !ok : u_item was cleaned up in user_replace()
}
return ok;
}

331
src/ckdb_data.c

@ -155,7 +155,7 @@ char *_safe_text(char *txt, bool shownull)
if (!txt) {
buf = strdup("(Null)");
if (!buf)
quithere(1, "malloc OOM");
quithere(1, "strdup OOM");
return buf;
}
@ -871,6 +871,18 @@ K_ITEM *_find_create_workerstatus(int64_t userid, char *workername,
return ws_item;
}
// workerstatus must be locked
static void zero_on_idle(tv_t *when, WORKERSTATUS *workerstatus)
{
copy_tv(&(workerstatus->active_start), when);
workerstatus->active_diffacc = workerstatus->active_diffinv =
workerstatus->active_diffsta = workerstatus->active_diffdup =
workerstatus->active_diffhi = workerstatus->active_diffrej =
workerstatus->active_shareacc = workerstatus->active_shareinv =
workerstatus->active_sharesta = workerstatus->active_sharedup =
workerstatus->active_sharehi = workerstatus->active_sharerej = 0.0;
}
/* All data is loaded, now update workerstatus fields
TODO: combine set_block_share_counters() with this? */
void workerstatus_ready()
@ -939,6 +951,8 @@ void _workerstatus_update(AUTHS *auths, SHARES *shares,
K_WLOCK(workerstatus_free);
if (tv_newer(&(row->last_auth), &(auths->createdate)))
copy_tv(&(row->last_auth), &(auths->createdate));
if (row->active_start.tv_sec == 0)
copy_tv(&(row->active_start), &(auths->createdate));
K_WUNLOCK(workerstatus_free);
}
}
@ -960,34 +974,54 @@ void _workerstatus_update(AUTHS *auths, SHARES *shares,
copy_tv(&(row->last_share), &(shares->createdate));
row->last_diff = shares->diff;
}
if (row->active_start.tv_sec == 0)
copy_tv(&(row->active_start), &(shares->createdate));
switch (shares->errn) {
case SE_NONE:
row->diffacc += shares->diff;
row->shareacc++;
row->block_diffacc += shares->diff;
row->block_shareacc++;
row->active_diffacc += shares->diff;
row->active_shareacc++;
break;
case SE_STALE:
row->diffinv += shares->diff;
row->shareinv++;
row->diffsta += shares->diff;
row->sharesta++;
row->block_diffinv += shares->diff;
row->block_shareinv++;
row->block_diffsta += shares->diff;
row->block_sharesta++;
row->active_diffinv += shares->diff;
row->active_shareinv++;
row->active_diffsta += shares->diff;
row->active_sharesta++;
break;
case SE_DUPE:
row->diffinv += shares->diff;
row->shareinv++;
row->diffdup += shares->diff;
row->sharedup++;
row->block_diffinv += shares->diff;
row->block_shareinv++;
row->block_diffdup += shares->diff;
row->block_sharedup++;
row->active_diffinv += shares->diff;
row->active_shareinv++;
row->active_diffdup += shares->diff;
row->active_sharedup++;
break;
case SE_HIGH_DIFF:
row->diffinv += shares->diff;
row->shareinv++;
row->diffhi += shares->diff;
row->sharehi++;
row->block_diffinv += shares->diff;
row->block_shareinv++;
row->block_diffhi += shares->diff;
row->block_sharehi++;
row->active_diffinv += shares->diff;
row->active_shareinv++;
row->active_diffhi += shares->diff;
row->active_sharehi++;
break;
default:
row->diffinv += shares->diff;
row->shareinv++;
row->diffrej += shares->diff;
row->sharerej++;
row->block_diffinv += shares->diff;
row->block_shareinv++;
row->block_diffrej += shares->diff;
row->block_sharerej++;
row->active_diffinv += shares->diff;
row->active_shareinv++;
row->active_diffrej += shares->diff;
row->active_sharerej++;
break;
}
K_WUNLOCK(workerstatus_free);
@ -1001,8 +1035,10 @@ void _workerstatus_update(AUTHS *auths, SHARES *shares,
DATA_WORKERSTATUS(row, item);
K_WLOCK(workerstatus_free);
if (userstats->idle) {
if (tv_newer(&(row->last_idle), &(userstats->statsdate)))
if (tv_newer(&(row->last_idle), &(userstats->statsdate))) {
copy_tv(&(row->last_idle), &(userstats->statsdate));
zero_on_idle(&(userstats->statsdate), row);
}
} else {
if (tv_newer(&(row->last_stats), &(userstats->statsdate)))
copy_tv(&(row->last_stats), &(userstats->statsdate));
@ -1160,6 +1196,179 @@ bool check_hash(USERS *users, char *passwordhash)
return (strcasecmp(passwordhash, users->passwordhash) == 0);
}
static void users_checkfor(USERS *users, char *name, int64_t bits)
{
char *ptr;
ptr = strstr(users->userdata, name);
if (ptr) {
size_t len = strlen(name);
if ((ptr == users->userdata || *(ptr-1) == DATABITS_SEP) &&
*(ptr+len) == '=') {
users->databits |= bits;
}
}
}
void users_databits(USERS *users)
{
users->databits = 0;
if (users->userdata && *(users->userdata))
{
users_checkfor(users, USER_TOTPAUTH_NAME, USER_TOTPAUTH);
users_checkfor(users, USER_TEST2FA_NAME, USER_TEST2FA);
}
}
// Returns the hex text string (and length) in a malloced buffer
char *_users_userdata_get_hex(USERS *users, char *name, int64_t bit,
size_t *hexlen, WHERE_FFL_ARGS)
{
char *ptr, *tmp, *end, *st = NULL, *val = NULL;
*hexlen = 0;
if (users->userdata && (users->databits & bit)) {
ptr = strstr(users->userdata, name);
// Should always be true
if (!ptr) {
LOGEMERG("%s() users userdata/databits mismatch for "
"%s/%"PRId64 WHERE_FFL,
__func__,
st = safe_text_nonull(users->username),
users->databits, WHERE_FFL_PASS);
FREENULL(st);
} else {
tmp = ptr + strlen(name) + 1;
if ((ptr == users->userdata || *(ptr-1) == DATABITS_SEP) &&
*(tmp-1) == '=') {
end = strchr(tmp, DATABITS_SEP);
if (end)
*hexlen = end - tmp;
else
*hexlen = strlen(tmp);
val = malloc(*hexlen + 1);
if (!val)
quithere(1, "malloc OOM");
memcpy(val, tmp, *hexlen);
val[*hexlen] = '\0';
}
}
}
return val;
}
// Returns binary malloced string (and length) or NULL if not found
unsigned char *_users_userdata_get_bin(USERS *users, char *name, int64_t bit,
size_t *binlen, WHERE_FFL_ARGS)
{
unsigned char *val = NULL;
size_t hexlen;
char *hex;
*binlen = 0;
hex = _users_userdata_get_hex(users, name, bit, &hexlen,
WHERE_FFL_PASS);
if (hex) {
/* avoid calling malloc twice, hex is 2x required
* and overlap is OK with _hex2bin code */
hexlen >>= 1;
if (hex2bin(hex, hex, hexlen)) {
val = (unsigned char *)hex;
*binlen = hexlen;
} else
FREENULL(hex);
}
return val;
}
/* WARNING - users->userdata and users->databits are updated */
void _users_userdata_del(USERS *users, char *name, int64_t bit, WHERE_FFL_ARGS)
{
char *ptr, *tmp, *st = NULL, *end;
if (users->userdata && (users->databits & bit)) {
ptr = strstr(users->userdata, name);
// Should always be true
if (!ptr) {
LOGEMERG("%s() users userdata/databits mismatch for "
"%s/%"PRId64 WHERE_FFL,
__func__,
st = safe_text_nonull(users->username),
users->databits, WHERE_FFL_PASS);
FREENULL(st);
} else {
tmp = ptr + strlen(name) + 1;
if ((ptr == users->userdata || *(ptr-1) == DATABITS_SEP) &&
*(tmp-1) == '=') {
// overwrite the memory since it will be smaller
end = strchr(tmp, DATABITS_SEP);
if (!end) {
// chop off the end
if (ptr == users->userdata) {
// now empty
*ptr = '\0';
} else {
// remove from DATABITS_SEP
*(ptr-1) = '\0';
}
} else {
// overlap
memmove(ptr, end+1, strlen(end+1)+1);
}
users->databits &= ~bit;
}
}
}
}
/* hex should be null terminated hex text
* WARNING - users->userdata and users->databits are updated */
void _users_userdata_add_hex(USERS *users, char *name, int64_t bit, char *hex,
WHERE_FFL_ARGS)
{
char *ptr;
if (users->userdata && (users->databits & bit)) {
// TODO: if it's the same size or smaller, don't reallocate
_users_userdata_del(users, name, bit, WHERE_FFL_PASS);
}
if (users->userdata == EMPTY)
users->userdata = NULL;
else if (users->userdata && !(*(users->userdata)))
FREENULL(users->userdata);
if (users->userdata) {
size_t len = strlen(users->userdata) + 1 +
strlen(name) + 1 + strlen(hex) + 1;
ptr = malloc(len);
if (!(ptr))
quithere(1, "malloc OOM");
snprintf(ptr, len,
"%s%c%s=%s",
users->userdata, DATABITS_SEP, name, hex);
FREENULL(users->userdata);
users->userdata = ptr;
} else {
size_t len = strlen(name) + 1 + strlen(hex) + 1;
users->userdata = malloc(len);
if (!(users->userdata))
quithere(1, "malloc OOM");
snprintf(users->userdata, len, "%s=%s", name, hex);
}
users->databits |= bit;
}
/* value is considered binary data of length len
* WARNING - users->userdata and users->databits are updated */
void _users_userdata_add_bin(USERS *users, char *name, int64_t bit,
unsigned char *bin, size_t len, WHERE_FFL_ARGS)
{
char *hex = bin2hex((const void *)bin, len);
_users_userdata_add_hex(users, name, bit, hex, WHERE_FFL_PASS);
FREENULL(hex);
}
// default tree order by userid asc,attname asc,expirydate desc
cmp_t cmp_useratts(K_ITEM *a, K_ITEM *b)
{
@ -2436,12 +2645,12 @@ void zero_on_new_block()
ws_item = first_in_ktree(workerstatus_root, ctx);
while (ws_item) {
DATA_WORKERSTATUS(workerstatus, ws_item);
workerstatus->diffacc = workerstatus->diffinv =
workerstatus->diffsta = workerstatus->diffdup =
workerstatus->diffhi = workerstatus->diffrej =
workerstatus->shareacc = workerstatus->shareinv =
workerstatus->sharesta = workerstatus->sharedup =
workerstatus->sharehi = workerstatus->sharerej = 0.0;
workerstatus->block_diffacc = workerstatus->block_diffinv =
workerstatus->block_diffsta = workerstatus->block_diffdup =
workerstatus->block_diffhi = workerstatus->block_diffrej =
workerstatus->block_shareacc = workerstatus->block_shareinv =
workerstatus->block_sharesta = workerstatus->block_sharedup =
workerstatus->block_sharehi = workerstatus->block_sharerej = 0.0;
ws_item = next_in_ktree(ctx);
}
K_WUNLOCK(workerstatus_free);
@ -2511,20 +2720,25 @@ void set_block_share_counters()
pool.diffacc += sharesummary->diffacc;
pool.diffinv += sharesummary->diffsta + sharesummary->diffdup +
sharesummary->diffhi + sharesummary->diffrej;
workerstatus->diffacc += sharesummary->diffacc;
workerstatus->diffinv += sharesummary->diffsta + sharesummary->diffdup +
sharesummary->diffhi + sharesummary->diffrej;
workerstatus->diffsta += sharesummary->diffsta;
workerstatus->diffdup += sharesummary->diffdup;
workerstatus->diffhi += sharesummary->diffhi;
workerstatus->diffrej += sharesummary->diffrej;
workerstatus->shareacc += sharesummary->shareacc;
workerstatus->shareinv += sharesummary->sharesta + sharesummary->sharedup +
sharesummary->sharehi + sharesummary->sharerej;
workerstatus->sharesta += sharesummary->sharesta;
workerstatus->sharedup += sharesummary->sharedup;
workerstatus->sharehi += sharesummary->sharehi;
workerstatus->sharerej += sharesummary->sharerej;
// Block stats only
workerstatus->block_diffacc += sharesummary->diffacc;
workerstatus->block_diffinv += sharesummary->diffsta +
sharesummary->diffdup +
sharesummary->diffhi +
sharesummary->diffrej;
workerstatus->block_diffsta += sharesummary->diffsta;
workerstatus->block_diffdup += sharesummary->diffdup;
workerstatus->block_diffhi += sharesummary->diffhi;
workerstatus->block_diffrej += sharesummary->diffrej;
workerstatus->block_shareacc += sharesummary->shareacc;
workerstatus->block_shareinv += sharesummary->sharesta +
sharesummary->sharedup +
sharesummary->sharehi +
sharesummary->sharerej;
workerstatus->block_sharesta += sharesummary->sharesta;
workerstatus->block_sharedup += sharesummary->sharedup;
workerstatus->block_sharehi += sharesummary->sharehi;
workerstatus->block_sharerej += sharesummary->sharerej;
ss_item = prev_in_ktree(ctx);
}
@ -2589,20 +2803,25 @@ void set_block_share_counters()
pool.diffacc += markersummary->diffacc;
pool.diffinv += markersummary->diffsta + markersummary->diffdup +
markersummary->diffhi + markersummary->diffrej;
workerstatus->diffacc += markersummary->diffacc;
workerstatus->diffinv += markersummary->diffsta + markersummary->diffdup +
markersummary->diffhi + markersummary->diffrej;
workerstatus->diffsta += markersummary->diffsta;
workerstatus->diffdup += markersummary->diffdup;
workerstatus->diffhi += markersummary->diffhi;
workerstatus->diffrej += markersummary->diffrej;
workerstatus->shareacc += markersummary->shareacc;
workerstatus->shareinv += markersummary->sharesta + markersummary->sharedup +
markersummary->sharehi + markersummary->sharerej;
workerstatus->sharesta += markersummary->sharesta;
workerstatus->sharedup += markersummary->sharedup;
workerstatus->sharehi += markersummary->sharehi;
workerstatus->sharerej += markersummary->sharerej;
// Block stats only
workerstatus->block_diffacc += markersummary->diffacc;
workerstatus->block_diffinv += markersummary->diffsta +
markersummary->diffdup +
markersummary->diffhi +
markersummary->diffrej;
workerstatus->block_diffsta += markersummary->diffsta;
workerstatus->block_diffdup += markersummary->diffdup;
workerstatus->block_diffhi += markersummary->diffhi;
workerstatus->block_diffrej += markersummary->diffrej;
workerstatus->block_shareacc += markersummary->shareacc;
workerstatus->block_shareinv += markersummary->sharesta +
markersummary->sharedup +
markersummary->sharehi +
markersummary->sharerej;
workerstatus->block_sharesta += markersummary->sharesta;
workerstatus->block_sharedup += markersummary->sharedup;
workerstatus->block_sharehi += markersummary->sharehi;
workerstatus->block_sharerej += markersummary->sharerej;
ms_item = prev_in_ktree(ctx_ms);
}
@ -2925,7 +3144,7 @@ double payout_stats(PAYOUTS *payouts, char *statname)
pos += len+1;
// They should only contain +ve numbers
if (*pos && isdigit(*pos)) {
tab = strchr(pos, '\t');
tab = strchr(pos, FLDSEP);
if (!tab)
numlen = strlen(pos);
else
@ -2938,7 +3157,7 @@ double payout_stats(PAYOUTS *payouts, char *statname)
}
break;
}
pos = strchr(pos, '\t');
pos = strchr(pos, FLDSEP);
if (pos)
pos++;
}

189
src/ckdb_dbio.c

@ -428,6 +428,11 @@ bool users_update(PGconn *conn, K_ITEM *u_item, char *oldhash,
STRNCPY(row->emailaddress, email);
if (status)
STRNCPY(row->status, status);
if (row->userdata != EMPTY) {
row->userdata = strdup(users->userdata);
if (!row->userdata)
quithere(1, "strdup OOM");
}
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
@ -478,10 +483,10 @@ bool users_update(PGconn *conn, K_ITEM *u_item, char *oldhash,
ins = "insert into users "
"(userid,username,status,emailaddress,joineddate,"
"passwordhash,secondaryuserid,salt"
"passwordhash,secondaryuserid,salt,userdata,userbits"
HISTORYDATECONTROL ") select "
"userid,username,$3,$4,joineddate,"
"$5,secondaryuserid,$6,"
"$5,secondaryuserid,$6,userdata,userbits,"
"$7,$8,$9,$10,$11 from users where "
"userid=$1 and "EDDB"=$2";
@ -508,9 +513,11 @@ unparam:
free(params[n]);
K_WLOCK(users_free);
if (!ok)
if (!ok) {
if (row->userdata != EMPTY)
FREENULL(row->userdata);
k_add_head(users_free, item);
else {
} else {
users_root = remove_from_ktree(users_root, u_item, cmp_users);
userid_root = remove_from_ktree(userid_root, u_item, cmp_userid);
copy_tv(&(users->expirydate), cd);
@ -527,8 +534,8 @@ unparam:
}
K_ITEM *users_add(PGconn *conn, char *username, char *emailaddress,
char *passwordhash, char *by, char *code, char *inet,
tv_t *cd, K_TREE *trf_root)
char *passwordhash, int64_t userbits, char *by,
char *code, char *inet, tv_t *cd, K_TREE *trf_root)
{
ExecStatusType rescode;
bool conned = false;
@ -540,7 +547,7 @@ K_ITEM *users_add(PGconn *conn, char *username, char *emailaddress,
uint64_t hash;
__maybe_unused uint64_t tmp;
bool dup, ok = false;
char *params[8 + HISTORYDATECOUNT];
char *params[10 + HISTORYDATECOUNT];
int n, par = 0;
LOGDEBUG("%s(): add", __func__);
@ -592,6 +599,9 @@ K_ITEM *users_add(PGconn *conn, char *username, char *emailaddress,
row->passwordhash, sizeof(row->passwordhash));
}
row->userdata = EMPTY;
row->userbits = userbits;
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
@ -608,13 +618,15 @@ K_ITEM *users_add(PGconn *conn, char *username, char *emailaddress,
params[par++] = str_to_buf(row->passwordhash, NULL, 0);
params[par++] = str_to_buf(row->secondaryuserid, NULL, 0);
params[par++] = str_to_buf(row->salt, NULL, 0);
params[par++] = str_to_buf(row->userdata, NULL, 0);
params[par++] = bigint_to_buf(row->userbits, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into users "
"(userid,username,status,emailaddress,joineddate,passwordhash,"
"secondaryuserid,salt"
HISTORYDATECONTROL ") values (" PQPARAM13 ")";
"secondaryuserid,salt,userdata,userbits"
HISTORYDATECONTROL ") values (" PQPARAM15 ")";
if (!conn) {
conn = dbconnect();
@ -652,6 +664,122 @@ unitem:
return NULL;
}
// Replace the current users record with u_item, and expire the old one
bool users_replace(PGconn *conn, K_ITEM *u_item, K_ITEM *old_u_item, char *by,
char *code, char *inet, tv_t *cd, K_TREE *trf_root)
{
ExecStatusType rescode;
bool conned = false;
PGresult *res;
bool ok = false;
USERS *users, *old_users;
char *upd, *ins;
char *params[10 + HISTORYDATECOUNT];
int n, par = 0;
LOGDEBUG("%s(): replace", __func__);
DATA_USERS(users, u_item);
DATA_USERS(old_users, old_u_item);
HISTORYDATEINIT(users, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, users);
upd = "update users set "EDDB"=$1 where userid=$2 and "EDDB"=$3";
par = 0;
params[par++] = tv_to_buf(cd, NULL, 0);
params[par++] = bigint_to_buf(old_users->userid, NULL, 0);
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0);
PARCHKVAL(par, 3, params);
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
// Beginning of a write txn
res = PQexec(conn, "Begin", CKPQ_WRITE);
rescode = PQresultStatus(res);
PQclear(res);
if (!PGOK(rescode)) {
PGLOGERR("Begin", rescode, conn);
goto unparam;
}
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
PQclear(res);
if (!PGOK(rescode)) {
PGLOGERR("Update", rescode, conn);
goto rollback;
}
for (n = 0; n < par; n++)
free(params[n]);
par = 0;
params[par++] = bigint_to_buf(users->userid, NULL, 0);
params[par++] = str_to_buf(users->username, NULL, 0);
params[par++] = str_to_buf(users->status, NULL, 0);
params[par++] = str_to_buf(users->emailaddress, NULL, 0);
params[par++] = tv_to_buf(&(users->joineddate), NULL, 0);
params[par++] = str_to_buf(users->passwordhash, NULL, 0);
params[par++] = str_to_buf(users->secondaryuserid, NULL, 0);
params[par++] = str_to_buf(users->salt, NULL, 0);
params[par++] = str_to_buf(users->userdata, NULL, 0);
params[par++] = bigint_to_buf(users->userbits, NULL, 0);
HISTORYDATEPARAMS(params, par, users);
PARCHKVAL(par, 10 + HISTORYDATECOUNT, params);
ins = "insert into users "
"(userid,username,status,emailaddress,joineddate,"
"passwordhash,secondaryuserid,salt,userdata,userbits"
HISTORYDATECONTROL ") values (" PQPARAM15 ")";
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
PQclear(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
goto rollback;
}
ok = true;
rollback:
if (ok)
res = PQexec(conn, "Commit", CKPQ_WRITE);
else
res = PQexec(conn, "Rollback", CKPQ_WRITE);
PQclear(res);
unparam:
if (conned)
PQfinish(conn);
for (n = 0; n < par; n++)
free(params[n]);
K_WLOCK(users_free);
if (!ok) {
// cleanup done here
if (users->userdata != EMPTY)
FREENULL(users->userdata);
k_add_head(users_free, u_item);
} else {
users_root = remove_from_ktree(users_root, old_u_item, cmp_users);
userid_root = remove_from_ktree(userid_root, old_u_item, cmp_userid);
copy_tv(&(old_users->expirydate), cd);
users_root = add_to_ktree(users_root, old_u_item, cmp_users);
userid_root = add_to_ktree(userid_root, old_u_item, cmp_userid);
users_root = add_to_ktree(users_root, u_item, cmp_users);
userid_root = add_to_ktree(userid_root, u_item, cmp_userid);
k_add_head(users_store, u_item);
}
K_WUNLOCK(users_free);
return ok;
}
bool users_fill(PGconn *conn)
{
ExecStatusType rescode;
@ -661,14 +789,14 @@ bool users_fill(PGconn *conn)
USERS *row;
char *field;
char *sel;
int fields = 8;
int fields = 10;
bool ok;
LOGDEBUG("%s(): select", __func__);
sel = "select "
"userid,username,status,emailaddress,joineddate,"
"passwordhash,secondaryuserid,salt"
"passwordhash,secondaryuserid,salt,userdata,userbits"
HISTORYDATECONTROL
" from users";
res = PQexec(conn, sel, CKPQ_READ);
@ -741,6 +869,19 @@ bool users_fill(PGconn *conn)
break;
TXT_TO_STR("salt", field, row->salt);
// TODO: good case for invariant
PQ_GET_FLD(res, i, "userdata", field, ok);
if (!ok)
break;
TXT_TO_PTR("userdata", field, row->userdata);
LIST_MEM_ADD(users_free, row->userdata);
users_databits(row);
PQ_GET_FLD(res, i, "userbits", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("userbits", field, row->userbits);
HISTORYDATEFLDS(res, i, row, ok);
if (!ok)
break;
@ -1159,7 +1300,7 @@ K_ITEM *workers_add(PGconn *conn, int64_t userid, char *workername,
K_ITEM *item, *ret = NULL;
WORKERS *row;
char *ins;
char *params[6 + HISTORYDATECOUNT];
char *params[7 + HISTORYDATECOUNT];
int n, par = 0;
int32_t diffdef;
int32_t nottime;
@ -1222,6 +1363,8 @@ K_ITEM *workers_add(PGconn *conn, int64_t userid, char *workername,
if (row->idlenotificationtime == IDLENOTIFICATIONTIME_DEF)
row->idlenotificationenabled[0] = IDLENOTIFICATIONDISABLED[0];
row->workerbits = 0;
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
@ -1232,13 +1375,14 @@ K_ITEM *workers_add(PGconn *conn, int64_t userid, char *workername,
params[par++] = int_to_buf(row->difficultydefault, NULL, 0);
params[par++] = str_to_buf(row->idlenotificationenabled, NULL, 0);
params[par++] = int_to_buf(row->idlenotificationtime, NULL, 0);
params[par++] = bigint_to_buf(row->workerbits, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into workers "
"(workerid,userid,workername,difficultydefault,"
"idlenotificationenabled,idlenotificationtime"
HISTORYDATECONTROL ") values (" PQPARAM11 ")";
"idlenotificationenabled,idlenotificationtime,workerbits"
HISTORYDATECONTROL ") values (" PQPARAM12 ")";
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
@ -1371,8 +1515,8 @@ bool workers_update(PGconn *conn, K_ITEM *item, char *difficultydefault,
ins = "insert into workers "
"(workerid,userid,workername,difficultydefault,"
"idlenotificationenabled,idlenotificationtime"
HISTORYDATECONTROL ") values (" PQPARAM11 ")";
"idlenotificationenabled,idlenotificationtime,workerbits"
HISTORYDATECONTROL ") values (" PQPARAM12 ")";
par = 0;
params[par++] = bigint_to_buf(row->workerid, NULL, 0);
@ -1381,6 +1525,7 @@ bool workers_update(PGconn *conn, K_ITEM *item, char *difficultydefault,
params[par++] = int_to_buf(row->difficultydefault, NULL, 0);
params[par++] = str_to_buf(row->idlenotificationenabled, NULL, 0);
params[par++] = int_to_buf(row->idlenotificationtime, NULL, 0);
params[par++] = bigint_to_buf(row->workerbits, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHK(par, params);
@ -1418,14 +1563,14 @@ bool workers_fill(PGconn *conn)
WORKERS *row;
char *field;
char *sel;
int fields = 6;
int fields = 7;
bool ok;
LOGDEBUG("%s(): select", __func__);
sel = "select "
"userid,workername,difficultydefault,"
"idlenotificationenabled,idlenotificationtime"
"idlenotificationenabled,idlenotificationtime,workerbits"
HISTORYDATECONTROL
",workerid from workers";
res = PQexec(conn, sel, CKPQ_READ);
@ -1483,6 +1628,11 @@ bool workers_fill(PGconn *conn)
break;
TXT_TO_INT("idlenotificationtime", field, row->idlenotificationtime);
PQ_GET_FLD(res, i, "workerbits", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("workerbits", field, row->workerbits);
HISTORYDATEFLDS(res, i, row, ok);
if (!ok)
break;
@ -5495,7 +5645,8 @@ bool auths_add(PGconn *conn, char *poolinstance, char *username,
if (!u_item) {
if (addressuser) {
u_item = users_add(conn, username, EMPTY, EMPTY,
by, code, inet, cd, trf_root);
USER_ADDRESS, by, code, inet, cd,
trf_root);
} else {
LOGDEBUG("%s(): unknown user '%s'",
__func__,

55
src/ckpool.c

@ -727,37 +727,47 @@ char *_ckdb_msg_call(const ckpool_t *ckp, const char *msg, const char *file, co
return buf;
}
static const char *rpc_method(const char *rpc_req)
{
const char *ptr = strchr(rpc_req, ':');
if (ptr)
return ptr+1;
return rpc_req;
}
json_t *json_rpc_call(connsock_t *cs, const char *rpc_req)
{
float timeout = RPC_TIMEOUT;
char *http_req = NULL;
json_error_t err_val;
json_t *val = NULL;
tv_t stt_tv, fin_tv;
double elapsed;
int len, ret;
if (unlikely(cs->fd < 0)) {
LOGWARNING("FD %d invalid in json_rpc_call", cs->fd);
LOGWARNING("FD %d invalid in %s", cs->fd, __func__);
goto out;
}
if (unlikely(!cs->url)) {
LOGWARNING("No URL in json_rpc_call");
LOGWARNING("No URL in %s", __func__);
goto out;
}
if (unlikely(!cs->port)) {
LOGWARNING("No port in json_rpc_call");
LOGWARNING("No port in %s", __func__);
goto out;
}
if (unlikely(!cs->auth)) {
LOGWARNING("No auth in json_rpc_call");
LOGWARNING("No auth in %s", __func__);
goto out;
}
if (unlikely(!rpc_req)) {
LOGWARNING("Null rpc_req passed to json_rpc_call");
LOGWARNING("Null rpc_req passed to %s", __func__);
goto out;
}
len = strlen(rpc_req);
if (unlikely(!len)) {
LOGWARNING("Zero length rpc_req passed to json_rpc_call");
LOGWARNING("Zero length rpc_req passed to %s", __func__);
goto out;
}
http_req = ckalloc(len + 256); // Leave room for headers
@ -770,31 +780,52 @@ json_t *json_rpc_call(connsock_t *cs, const char *rpc_req)
cs->auth, cs->url, cs->port, len, rpc_req);
len = strlen(http_req);
tv_time(&stt_tv);
ret = write_socket(cs->fd, http_req, len);
if (ret != len) {
LOGWARNING("Failed to write to socket in json_rpc_call");
tv_time(&fin_tv);
elapsed = tvdiff(&fin_tv, &stt_tv);
LOGWARNING("Failed to write to socket in %s (%.10s...) %.3fs",
__func__, rpc_method(rpc_req), elapsed);
goto out_empty;
}
ret = read_socket_line(cs, &timeout);
if (ret < 1) {
LOGWARNING("Failed to read socket line in json_rpc_call");
tv_time(&fin_tv);
elapsed = tvdiff(&fin_tv, &stt_tv);
LOGWARNING("Failed to read socket line in %s (%.10s...) %.3fs",
__func__, rpc_method(rpc_req), elapsed);
goto out_empty;
}
if (strncasecmp(cs->buf, "HTTP/1.1 200 OK", 15)) {
LOGWARNING("HTTP response not ok: %s", cs->buf);
tv_time(&fin_tv);
elapsed = tvdiff(&fin_tv, &stt_tv);
LOGWARNING("HTTP response to (%.10s...) %.3fs not ok: %s",
rpc_method(rpc_req), elapsed, cs->buf);
goto out_empty;
}
do {
ret = read_socket_line(cs, &timeout);
if (ret < 1) {
LOGWARNING("Failed to read http socket lines in json_rpc_call");
tv_time(&fin_tv);
elapsed = tvdiff(&fin_tv, &stt_tv);
LOGWARNING("Failed to read http socket lines in %s (%.10s...) %.3fs",
__func__, rpc_method(rpc_req), elapsed);
goto out_empty;
}
} while (strncmp(cs->buf, "{", 1));
tv_time(&fin_tv);
elapsed = tvdiff(&fin_tv, &stt_tv);
if (elapsed > 5.0) {
LOGWARNING("HTTP socket read+write took %.3fs in %s (%.10s...)",
elapsed, __func__, rpc_method(rpc_req));
}
val = json_loads(cs->buf, 0, &err_val);
if (!val)
LOGWARNING("JSON decode failed(%d): %s", err_val.line, err_val.text);
if (!val) {
LOGWARNING("JSON decode (%.10s...) failed(%d): %s",
rpc_method(rpc_req), err_val.line, err_val.text);
}
out_empty:
empty_socket(cs->fd);
empty_buffer(cs);

22
src/libckpool.c

@ -948,17 +948,19 @@ char *_recv_unix_msg(int sockd, int timeout1, int timeout2, const char *file, co
{
char *buf = NULL;
uint32_t msglen;
int ret;
int ret, ern;
ret = wait_read_select(sockd, timeout1);
if (unlikely(ret < 1)) {
LOGERR("Select1 failed in recv_unix_msg");
ern = errno;
LOGERR("Select1 failed in recv_unix_msg (%d)", ern);
goto out;
}
/* Get message length */
ret = read_length(sockd, &msglen, 4);
if (unlikely(ret < 4)) {
LOGERR("Failed to read 4 byte length in recv_unix_msg");
ern = errno;
LOGERR("Failed to read 4 byte length in recv_unix_msg (%d?)", ern);
goto out;
}
msglen = le32toh(msglen);
@ -968,13 +970,15 @@ char *_recv_unix_msg(int sockd, int timeout1, int timeout2, const char *file, co
}
ret = wait_read_select(sockd, timeout2);
if (unlikely(ret < 1)) {
LOGERR("Select2 failed in recv_unix_msg");
ern = errno;
LOGERR("Select2 failed in recv_unix_msg (%d)", ern);
goto out;
}
buf = ckzalloc(msglen + 1);
ret = read_length(sockd, buf, msglen);
if (unlikely(ret < (int)msglen)) {
LOGERR("Failed to read %u bytes in recv_unix_msg", msglen);
ern = errno;
LOGERR("Failed to read %u bytes in recv_unix_msg (%d?)", msglen, ern);
dealloc(buf);
}
out:
@ -1005,7 +1009,7 @@ out:
int _write_length(int sockd, const void *buf, int len, const char *file, const char *func, const int line)
{
int ret, ofs = 0;
int ret, ofs = 0, ern;
if (unlikely(len < 1)) {
LOGWARNING("Invalid write length of %d requested in write_length from %s %s:%d",
@ -1013,6 +1017,7 @@ int _write_length(int sockd, const void *buf, int len, const char *file, const c
return -1;
}
if (unlikely(sockd < 0)) {
ern = errno;
LOGWARNING("Attempt to write to invalidated sock in write_length from %s %s:%d",
file, func, line);
return -1;
@ -1020,8 +1025,9 @@ int _write_length(int sockd, const void *buf, int len, const char *file, const c
while (len) {
ret = write(sockd, buf + ofs, len);
if (unlikely(ret < 0)) {
LOGERR("Failed to write %d bytes in write_length from %s %s:%d",
len, file, func, line);
ern = errno;
LOGERR("Failed to write %d bytes in write_length (%d) from %s %s:%d",
len, ern, file, func, line);
return -1;
}
ofs += ret;

Loading…
Cancel
Save