Browse Source

ckdb/php - 2FA

master
kanoi 10 years ago
parent
commit
f6abd5333d
  1. 13
      configure.ac
  2. 29
      pool/db.php
  3. 121
      pool/page_2fa.php
  4. 2
      pool/page_addrmgt.php
  5. 2
      pool/page_reg.php
  6. 2
      pool/page_reset.php
  7. 4
      pool/page_settings.php
  8. 3
      pool/prime.php
  9. 4
      src/Makefile.am
  10. 2
      src/ckdb.c
  11. 78
      src/ckdb.h
  12. 260
      src/ckdb_cmd.c
  13. 239
      src/ckdb_crypt.c
  14. 175
      src/ckdb_data.c
  15. 142
      src/ckdb_dbio.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"

29
pool/db.php

@ -174,8 +174,8 @@ function homeInfo($user)
function checkPass($user, $pass, $twofa)
{
$passhash = myhash($pass);
if ($twofa === null)
$twofa = '';
if (nuem($twofa))
$twofa = 0;
$flds = array('username' => $user, 'passwordhash' => $passhash,
'2fa' => $twofa);
$msg = msgEncode('chkpass', 'chkpass', $flds, $user);
@ -189,8 +189,8 @@ function setPass($user, $oldpass, $newpass, $twofa)
{
$oldhash = myhash($oldpass);
$newhash = myhash($newpass);
if ($twofa === null)
$twofa = '';
if (nuem($twofa))
$twofa = 0;
$flds = array('username' => $user, 'oldhash' => $oldhash,
'newhash' => $newhash, '2fa' => $twofa);
$msg = msgEncode('newpass', 'newpass', $flds, $user);
@ -203,8 +203,8 @@ function setPass($user, $oldpass, $newpass, $twofa)
function resetPass($user, $newpass, $twofa)
{
$newhash = myhash($newpass);
if ($twofa === null)
$twofa = '';
if (nuem($twofa))
$twofa = 0;
$flds = array('username' => $user, 'newhash' => $newhash, '2fa' => $twofa);
$msg = msgEncode('newpass', 'newpass', $flds, $user);
$rep = sendsockreply('resetPass', $msg);
@ -213,6 +213,19 @@ function resetPass($user, $newpass, $twofa)
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);
@ -249,8 +262,8 @@ function userSettings($user, $email = null, $addr = null, $pass = null, $twofa =
if ($pass != null)
{
$flds['passwordhash'] = myhash($pass);
if ($twofa === null)
$twofa = '';
if (nuem($twofa))
$twofa = 0;
$flds['2fa'] = $twofa;
}
$msg = msgEncode('usersettings', 'userset', $flds, $user);

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: 'FreeOTP Authenticator' by Red Hat<br>";
$pg .= "Apple: '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);
}
#
?>

2
pool/page_addrmgt.php

@ -82,7 +82,7 @@ function addrmgtuser($data, $user, $err)
$row = 'odd';
$pg .= "<tr class=$row>";
$pg .= '<td class=dr>';
$pg .= '<span class=st1>*</span>2nd Authentication: <input type=password name=2fa size=20>';
$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>";

2
pool/page_reg.php

@ -30,7 +30,7 @@ function doregres($data, $u)
<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></td></tr>
<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>

2
pool/page_reset.php

@ -21,7 +21,7 @@ function allow_reset($error)
<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></td></tr>
<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>

4
pool/page_settings.php

@ -32,7 +32,7 @@ function settings($data, $user, $email, $addr, $err)
$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=20>';
$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>";
@ -94,7 +94,7 @@ function settings($data, $user, $email, $addr, $err)
$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=20>';
$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>";

3
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',

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:

78
src/ckdb.h

@ -55,7 +55,7 @@
#define DB_VLOCK "1"
#define DB_VERSION "1.0.1"
#define CKDB_VERSION DB_VERSION"-1.120"
#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,
@ -1037,16 +1038,33 @@ typedef struct users {
#define DATABITS_SEP ','
#define DATABITS_SEP_STR ","
// databits attributes
// These are generated at dbload time from userdata
// Google Auth 2FA
#define USER_GOOGLEAUTH_NAME "gauth"
#define USER_GOOGLEAUTH 0x1
/* 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;
@ -2119,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);
@ -2320,6 +2365,9 @@ extern bool users_update(PGconn *conn, K_ITEM *u_item, char *oldhash,
extern K_ITEM *users_add(PGconn *conn, char *username, char *emailaddress,
char *passwordhash, int64_t userbits, char *by,
char *code, char *inet, tv_t *cd, K_TREE *trf_root);
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,
@ -2488,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

260
src/ckdb_cmd.c

@ -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,
oldhash,
transfer_data(i_newhash),
NULL,
by, code, inet, now,
trf_root,
NULL);
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);
@ -6062,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;
}

175
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;
}
@ -1196,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)
{

142
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);
@ -657,24 +664,120 @@ unitem:
return NULL;
}
static void users_checkfor(USERS *row, char *name, int64_t bits)
// 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)
{
char *ptr;
ptr = strstr(row->userdata, name);
if (ptr) {
size_t len = strlen(name);
if ((ptr == row->userdata || *(ptr-1) == DATABITS_SEP) &&
*(ptr+len) == '=') {
row->databits |= bits;
}
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;
}
}
static void users_databits(USERS *row)
{
if (row->userdata && *(row->userdata))
users_checkfor(row, USER_GOOGLEAUTH_NAME, USER_GOOGLEAUTH);
// 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)
@ -766,6 +869,7 @@ 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;

Loading…
Cancel
Save