1
0
mirror of https://github.com/Yubico/yubikey-val.git synced 2024-11-29 00:24:13 +01:00

Merge pull request #59 from Yubico/enhance_data_validation

Enhanced data validation to address YSA-2020-01
This commit is contained in:
Ryan Allen 2020-03-03 07:50:48 -08:00 committed by GitHub
commit 19345b76ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 2417 additions and 2306 deletions

View File

@ -30,18 +30,18 @@
$verbose = 0; $verbose = 0;
if (isset($argv[1])) { if (isset($argv[1])) {
if ($argv[1] == "-h" || $argv[1] == "--help") { if ($argv[1] == "-h" || $argv[1] == "--help") {
print "Usage: " . $argv[0] . " [-h|--help] [-v]\n"; print "Usage: " . $argv[0] . " [-h|--help] [-v]\n";
exit(1); exit(1);
} }
if ($argv[1] && $argv[1] != "-v") { if ($argv[1] && $argv[1] != "-v") {
print $argv[0] . ": invalid option -- '" . $argv[0] . "'\n"; print $argv[0] . ": invalid option -- '" . $argv[0] . "'\n";
print "Try `" . $argv[0] . " --help' for more information.\n"; print "Try `" . $argv[0] . " --help' for more information.\n";
exit(1); exit(1);
} }
$verbose = $argv[1] == "-v"; $verbose = $argv[1] == "-v";
} }
set_include_path(implode(PATH_SEPARATOR, array( set_include_path(implode(PATH_SEPARATOR, array(
@ -59,30 +59,30 @@ $myLog = new Log($logname);
$db = Db::GetDatabaseHandle($baseParams, $logname); $db = Db::GetDatabaseHandle($baseParams, $logname);
if (!$db->connect()) { if (!$db->connect()) {
$myLog->log(LOG_WARNING, "Could not connect to database"); $myLog->log(LOG_WARNING, "Could not connect to database");
exit(1); exit(1);
} }
$everything = ""; $everything = "";
$result=$db->customQuery("SELECT id, active, secret ". $result=$db->customQuery("SELECT id, active, secret ".
"FROM clients ". "FROM clients ".
"ORDER BY id"); "ORDER BY id");
while($row = $db->fetchArray($result)) { while($row = $db->fetchArray($result)) {
$active = $row['active']; $active = $row['active'];
if ($active == "") { if ($active == "") {
# For some reason PostgreSQL returns empty strings for false values?! # For some reason PostgreSQL returns empty strings for false values?!
$active = "0"; $active = "0";
} }
$everything .= $everything .=
$row['id'] . "\t" . $active . "\t" . $row['id'] . "\t" . $active . "\t" .
$row['secret'] . "\n"; $row['secret'] . "\n";
} }
$db->closeCursor($result); $db->closeCursor($result);
$hash = sha1 ($everything); $hash = sha1 ($everything);
if ($verbose) { if ($verbose) {
print $everything; print $everything;
} }
print substr ($hash, 0, 10) . "\n"; print substr ($hash, 0, 10) . "\n";

View File

@ -30,18 +30,18 @@
$verbose = 0; $verbose = 0;
if (isset($argv[1])) { if (isset($argv[1])) {
if ($argv[1] == "-h" || $argv[1] == "--help") { if ($argv[1] == "-h" || $argv[1] == "--help") {
print "Usage: " . $argv[0] . " [-h|--help] [-v]\n"; print "Usage: " . $argv[0] . " [-h|--help] [-v]\n";
exit(1); exit(1);
} }
if ($argv[1] && $argv[1] != "-v") { if ($argv[1] && $argv[1] != "-v") {
print $argv[0] . ": invalid option -- '" . $argv[0] . "'\n"; print $argv[0] . ": invalid option -- '" . $argv[0] . "'\n";
print "Try `" . $argv[0] . " --help' for more information.\n"; print "Try `" . $argv[0] . " --help' for more information.\n";
exit(1); exit(1);
} }
$verbose = $argv[1] == "-v"; $verbose = $argv[1] == "-v";
} }
set_include_path(implode(PATH_SEPARATOR, array( set_include_path(implode(PATH_SEPARATOR, array(
@ -59,24 +59,24 @@ $myLog = new Log($logname);
$db = Db::GetDatabaseHandle($baseParams, $logname); $db = Db::GetDatabaseHandle($baseParams, $logname);
if (!$db->connect()) { if (!$db->connect()) {
$myLog->log(LOG_WARNING, "Could not connect to database"); $myLog->log(LOG_WARNING, "Could not connect to database");
exit(1); exit(1);
} }
$everything = ""; $everything = "";
$result=$db->customQuery("SELECT yk_publicname, yk_counter, yk_use ". $result=$db->customQuery("SELECT yk_publicname, yk_counter, yk_use ".
"FROM yubikeys WHERE active = false ". "FROM yubikeys WHERE active = false ".
"ORDER BY yk_publicname"); "ORDER BY yk_publicname");
while($row = $result->fetch(PDO::FETCH_ASSOC)) { while($row = $result->fetch(PDO::FETCH_ASSOC)) {
$everything .= $everything .=
$row['yk_publicname'] . "\t" . $row['yk_counter'] . "\t" . $row['yk_use'] . $row['yk_publicname'] . "\t" . $row['yk_counter'] . "\t" . $row['yk_use'] .
"\n"; "\n";
} }
$hash = sha1 ($everything); $hash = sha1 ($everything);
if ($verbose) { if ($verbose) {
print $everything; print $everything;
} }
print substr ($hash, 0, 10) . "\n"; print substr ($hash, 0, 10) . "\n";

View File

@ -45,69 +45,112 @@ define('TS_REL_TOLERANCE', 0.3);
define('TS_ABS_TOLERANCE', 20); define('TS_ABS_TOLERANCE', 20);
define('TOKEN_LEN', 32); define('TOKEN_LEN', 32);
define('OTP_MAX_LEN', 48); // TOKEN_LEN plus public identity of 0..16 define('PUBID_MAX_LEN', 16);
define('OTP_MAX_LEN', TOKEN_LEN + PUBID_MAX_LEN);
define('NONCE_MIN_LEN', 16);
define('NONCE_MAX_LEN', 40);
define('INT32_LEN', 10);
function logdie ($logger, $str) function logdie ($logger, $str)
{ {
$logger->log(LOG_INFO, $str); $logger->log(LOG_INFO, $str);
die($str . "\n"); die($str . "\n");
} }
function getHttpVal ($key, $default, $a) function getHttpVal ($key, $default, $a)
{ {
if (array_key_exists($key, $a)) if (array_key_exists($key, $a))
{ {
$val = $a[$key]; $val = $a[$key];
} }
else else
{ {
$val = $default; $val = $default;
} }
$val = trim($val); $val = trim($val);
$val = str_replace('\\', '', $val); $val = str_replace('\\', '', $val);
return $val; return $val;
}
// Verifies if a given string is modhex
function is_modhex($s) {
if (preg_match('/^[cbdefghijklnrtuv]+$/', $s) === 0) {
return false;
} else {
return true;
}
}
// Verifies if a given string is a valid OTP
function is_otp($otp) {
if ($otp == "") {
return false;
}
$otp_len = strlen($otp);
return $otp_len >= TOKEN_LEN && $otp_len <= OTP_MAX_LEN && is_modhex($otp);
}
// Verifies if a given string is a valid public id
function is_pubid($id) {
$id_len = strlen($id);
return $id_len >= 0 && $id_len <= PUBID_MAX_LEN && is_modhex($id);
}
// Verifies a given string is a valid nonce
function is_nonce($nonce) {
return strlen($nonce) >= NONCE_MIN_LEN
&& strlen($nonce) <= NONCE_MAX_LEN
&& ctype_alnum($nonce);
}
// Verifies if a given string is a valid client id
function is_clientid($id) {
if ($id == "0") {
return false;
}
return strlen($id) <= INT32_LEN && ctype_digit($id);
} }
// Sign a http query string in the array of key-value pairs // Sign a http query string in the array of key-value pairs
// return b64 encoded hmac hash // return b64 encoded hmac hash
function sign($a, $apiKey, $logger) function sign($a, $apiKey, $logger)
{ {
ksort($a); ksort($a);
$qs = http_build_query($a); $qs = http_build_query($a);
$qs = urldecode($qs); $qs = urldecode($qs);
$qs = utf8_encode($qs); $qs = utf8_encode($qs);
// base64 encoded binary digest // base64 encoded binary digest
$hmac = hash_hmac('sha1', $qs, $apiKey, TRUE); $hmac = hash_hmac('sha1', $qs, $apiKey, TRUE);
$hmac = base64_encode($hmac); $hmac = base64_encode($hmac);
$logger->log(LOG_DEBUG, "SIGN: $qs H=$hmac"); $logger->log(LOG_DEBUG, "SIGN: $qs H=$hmac");
return $hmac; return $hmac;
} }
function curl_settings($logger, $ident, $ch, $url, $timeout, $opts) function curl_settings($logger, $ident, $ch, $url, $timeout, $opts)
{ {
$logger->log(LOG_DEBUG, "$ident adding URL : $url"); $logger->log(LOG_DEBUG, "$ident adding URL : $url");
curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_USERAGENT, 'YK-VAL'); curl_setopt($ch, CURLOPT_USERAGENT, 'YK-VAL');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_FAILONERROR, TRUE); curl_setopt($ch, CURLOPT_FAILONERROR, TRUE);
if (is_array($opts) === FALSE) if (is_array($opts) === FALSE)
{ {
$logger->log(LOG_WARN, $ident . 'curl options must be an array'); $logger->log(LOG_WARN, $ident . 'curl options must be an array');
return; return;
} }
foreach ($opts as $key => $val) foreach ($opts as $key => $val)
if (curl_setopt($ch, $key, $val) === FALSE) if (curl_setopt($ch, $key, $val) === FALSE)
$logger->log(LOG_WARN, "$ident failed to set " . curl_opt_name($key)); $logger->log(LOG_WARN, "$ident failed to set " . curl_opt_name($key));
} }
// returns the string name of a curl constant, // returns the string name of a curl constant,
@ -117,16 +160,16 @@ function curl_settings($logger, $ident, $ch, $url, $timeout, $opts)
// curl_opt_name(CURLOPT_BLABLA) returns "curl option" // curl_opt_name(CURLOPT_BLABLA) returns "curl option"
function curl_opt_name($opt) function curl_opt_name($opt)
{ {
$consts = get_defined_constants(true); $consts = get_defined_constants(true);
$consts = $consts['curl']; $consts = $consts['curl'];
$name = array_search($opt, $consts, TRUE); $name = array_search($opt, $consts, TRUE);
// array_search may return either on failure... // array_search may return either on failure...
if ($name === FALSE || $name === NULL) if ($name === FALSE || $name === NULL)
return 'curl option'; return 'curl option';
return $name; return $name;
} }
// This function takes a list of URLs. It will return the content of // This function takes a list of URLs. It will return the content of
@ -138,164 +181,164 @@ function curl_opt_name($opt)
// (defaults to ^OK) is returned, or if all URLs failed, false. // (defaults to ^OK) is returned, or if all URLs failed, false.
function retrieveURLasync($ident, $urls, $logger, $ans_req=1, $match="^OK", $returl=False, $timeout=10, $curlopts) function retrieveURLasync($ident, $urls, $logger, $ans_req=1, $match="^OK", $returl=False, $timeout=10, $curlopts)
{ {
$mh = curl_multi_init(); $mh = curl_multi_init();
$ch = array(); $ch = array();
foreach ($urls as $url) foreach ($urls as $url)
{ {
$handle = curl_init(); $handle = curl_init();
curl_settings($logger, $ident, $handle, $url, $timeout, $curlopts); curl_settings($logger, $ident, $handle, $url, $timeout, $curlopts);
curl_multi_add_handle($mh, $handle); curl_multi_add_handle($mh, $handle);
$ch[(int) $handle] = $handle; $ch[(int) $handle] = $handle;
} }
$ans_arr = array(); $ans_arr = array();
do do
{ {
while (curl_multi_exec($mh, $active) == CURLM_CALL_MULTI_PERFORM); while (curl_multi_exec($mh, $active) == CURLM_CALL_MULTI_PERFORM);
while ($info = curl_multi_info_read($mh)) while ($info = curl_multi_info_read($mh))
{ {
$logger->log(LOG_DEBUG, "$ident curl multi info : ", $info); $logger->log(LOG_DEBUG, "$ident curl multi info : ", $info);
if ($info['result'] == CURLE_OK) if ($info['result'] == CURLE_OK)
{ {
$str = curl_multi_getcontent($info['handle']); $str = curl_multi_getcontent($info['handle']);
$logger->log(LOG_DEBUG, "$ident curl multi content : $str"); $logger->log(LOG_DEBUG, "$ident curl multi content : $str");
if (preg_match("/$match/", $str)) if (preg_match("/$match/", $str))
{ {
$logger->log(LOG_DEBUG, "$ident response matches $match"); $logger->log(LOG_DEBUG, "$ident response matches $match");
$error = curl_error($info['handle']); $error = curl_error($info['handle']);
$errno = curl_errno($info['handle']); $errno = curl_errno($info['handle']);
$cinfo = curl_getinfo($info['handle']); $cinfo = curl_getinfo($info['handle']);
$logger->log(LOG_INFO, "$ident errno/error: $errno/$error", $cinfo); $logger->log(LOG_INFO, "$ident errno/error: $errno/$error", $cinfo);
if ($returl) if ($returl)
$ans_arr[] = "url=" . $cinfo['url'] . "\n" . $str; $ans_arr[] = "url=" . $cinfo['url'] . "\n" . $str;
else else
$ans_arr[] = $str; $ans_arr[] = $str;
} }
if (count($ans_arr) >= $ans_req) if (count($ans_arr) >= $ans_req)
{ {
foreach ($ch as $h) foreach ($ch as $h)
{ {
curl_multi_remove_handle($mh, $h); curl_multi_remove_handle($mh, $h);
curl_close($h); curl_close($h);
} }
curl_multi_close($mh); curl_multi_close($mh);
return $ans_arr; return $ans_arr;
} }
curl_multi_remove_handle($mh, $info['handle']); curl_multi_remove_handle($mh, $info['handle']);
curl_close($info['handle']); curl_close($info['handle']);
unset($ch[(int) $info['handle']]); unset($ch[(int) $info['handle']]);
} }
curl_multi_select($mh); curl_multi_select($mh);
} }
} }
while($active); while($active);
foreach ($ch as $h) foreach ($ch as $h)
{ {
curl_multi_remove_handle($mh, $h); curl_multi_remove_handle($mh, $h);
curl_close($h); curl_close($h);
} }
curl_multi_close($mh); curl_multi_close($mh);
if (count($ans_arr) > 0) if (count($ans_arr) > 0)
return $ans_arr; return $ans_arr;
return false; return false;
} }
function KSMdecryptOTP($urls, $logger, $curlopts) function KSMdecryptOTP($urls, $logger, $curlopts)
{ {
$response = retrieveURLasync('YK-KSM', $urls, $logger, $ans_req=1, $match='^OK', $returl=False, $timeout=10, $curlopts); $response = retrieveURLasync('YK-KSM', $urls, $logger, $ans_req=1, $match='^OK', $returl=False, $timeout=10, $curlopts);
if ($response === FALSE) if ($response === FALSE)
return false; return false;
$response = array_shift($response); $response = array_shift($response);
$logger->log(LOG_DEBUG, "YK-KSM response: $response"); $logger->log(LOG_DEBUG, "YK-KSM response: $response");
$ret = array(); $ret = array();
if (sscanf($response, if (sscanf($response,
'OK counter=%04x low=%04x high=%02x use=%02x', 'OK counter=%04x low=%04x high=%02x use=%02x',
$ret['session_counter'], $ret['session_counter'],
$ret['low'], $ret['low'],
$ret['high'], $ret['high'],
$ret['session_use']) !== 4) $ret['session_use']) !== 4)
{ {
return false; return false;
} }
return $ret; return $ret;
} }
function sendResp($status, $logger, $apiKey = '', $extra = null) function sendResp($status, $logger, $apiKey = '', $extra = null)
{ {
if ($logger->request !== NULL) if ($logger->request !== NULL)
$logger->request->set('status', $status); $logger->request->set('status', $status);
$a['status'] = $status; $a['status'] = $status;
// 2008-11-21T06:11:55Z0711 // 2008-11-21T06:11:55Z0711
$t = substr(microtime(false), 2, 3); $t = substr(microtime(false), 2, 3);
$t = gmdate('Y-m-d\TH:i:s\Z0') . $t; $t = gmdate('Y-m-d\TH:i:s\Z0') . $t;
$a['t'] = $t; $a['t'] = $t;
if ($extra) if ($extra)
foreach ($extra as $param => $value) foreach ($extra as $param => $value)
$a[$param] = $value; $a[$param] = $value;
$h = sign($a, $apiKey, $logger); $h = sign($a, $apiKey, $logger);
$str = ""; $str = "";
$str .= "h=" . $h . "\r\n"; $str .= "h=" . $h . "\r\n";
$str .= "t=" . $a['t'] . "\r\n"; $str .= "t=" . $a['t'] . "\r\n";
if ($extra) if ($extra)
foreach ($extra as $param => $value) foreach ($extra as $param => $value)
$str .= $param . "=" . $value . "\r\n"; $str .= $param . "=" . $value . "\r\n";
$str .= "status=" . $a['status'] . "\r\n"; $str .= "status=" . $a['status'] . "\r\n";
$str .= "\r\n"; $str .= "\r\n";
$logger->log(LOG_INFO, "Response: " . $str . " (at " . gmdate("c") . " " . microtime() . ")"); $logger->log(LOG_INFO, "Response: " . $str . " (at " . gmdate("c") . " " . microtime() . ")");
if ($logger->request !== NULL) if ($logger->request !== NULL)
$logger->request->write(); $logger->request->write();
echo $str; echo $str;
exit; exit;
} }
// backport from PHP 5.6 // backport from PHP 5.6
if (function_exists('hash_equals') === FALSE) if (function_exists('hash_equals') === FALSE)
{ {
function hash_equals($a, $b) function hash_equals($a, $b)
{ {
// hashes are a (known) fixed length, // hashes are a (known) fixed length,
// so this doesn't leak anything. // so this doesn't leak anything.
if (strlen($a) != strlen($b)) if (strlen($a) != strlen($b))
return false; return false;
$result = 0; $result = 0;
for ($i = 0; $i < strlen($a); $i++) for ($i = 0; $i < strlen($a); $i++)
$result |= ord($a[$i]) ^ ord($b[$i]); $result |= ord($a[$i]) ^ ord($b[$i]);
return (0 === $result); return (0 === $result);
} }
} }
/** /**
@ -306,33 +349,33 @@ if (function_exists('hash_equals') === FALSE)
*/ */
function total_time ($url) function total_time ($url)
{ {
$opts = array( $opts = array(
CURLOPT_URL => $url, CURLOPT_URL => $url,
CURLOPT_TIMEOUT => 3, CURLOPT_TIMEOUT => 3,
CURLOPT_FORBID_REUSE => TRUE, CURLOPT_FORBID_REUSE => TRUE,
CURLOPT_FRESH_CONNECT => TRUE, CURLOPT_FRESH_CONNECT => TRUE,
CURLOPT_RETURNTRANSFER => TRUE, CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_USERAGENT => 'ykval-munin-vallatency/1.0', CURLOPT_USERAGENT => 'ykval-munin-vallatency/1.0',
); );
if (($ch = curl_init()) === FALSE) if (($ch = curl_init()) === FALSE)
return false; return false;
if (curl_setopt_array($ch, $opts) === FALSE) if (curl_setopt_array($ch, $opts) === FALSE)
return false; return false;
// we don't care about the actual response // we don't care about the actual response
if (curl_exec($ch) === FALSE) if (curl_exec($ch) === FALSE)
return false; return false;
$total_time = curl_getinfo($ch, CURLINFO_TOTAL_TIME); $total_time = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
curl_close($ch); curl_close($ch);
if (is_float($total_time) === FALSE) if (is_float($total_time) === FALSE)
return false; return false;
return $total_time; return $total_time;
} }
/** /**
@ -343,38 +386,38 @@ function total_time ($url)
*/ */
function endpoints ($urls) function endpoints ($urls)
{ {
$endpoints = array(); $endpoints = array();
foreach ($urls as $url) foreach ($urls as $url)
{ {
// internal munin name must be a-zA-Z0-9_, // internal munin name must be a-zA-Z0-9_,
// so sha1 hex should be fine. // so sha1 hex should be fine.
// //
// munin also truncates at some length, // munin also truncates at some length,
// so we just take the first few characters of the hashsum. // so we just take the first few characters of the hashsum.
$internal = substr(sha1($url), 0, 20); $internal = substr(sha1($url), 0, 20);
// actual label name shown for graph values // actual label name shown for graph values
if (($label = hostport($url)) === FALSE) if (($label = hostport($url)) === FALSE)
{ {
return false; return false;
} }
$endpoints[] = array($internal, $label, $url); $endpoints[] = array($internal, $label, $url);
} }
// check for truncated sha1 collisions (or actual duplicate URLs!) // check for truncated sha1 collisions (or actual duplicate URLs!)
$internal = array(); $internal = array();
foreach($endpoints as $endpoint) foreach($endpoints as $endpoint)
{ {
$internal[] = $endpoint[0]; $internal[] = $endpoint[0];
} }
if (count(array_unique($internal)) !== count($endpoints)) if (count(array_unique($internal)) !== count($endpoints))
return false; return false;
return $endpoints; return $endpoints;
} }
/** /**
@ -387,22 +430,22 @@ function endpoints ($urls)
*/ */
function hostport ($url) function hostport ($url)
{ {
if (($url = parse_url($url)) === FALSE) if (($url = parse_url($url)) === FALSE)
return false; return false;
if (array_key_exists('host', $url) === FALSE || $url['host'] === NULL) if (array_key_exists('host', $url) === FALSE || $url['host'] === NULL)
return false; return false;
if (array_key_exists('port', $url) === TRUE && $url['port'] !== NULL) if (array_key_exists('port', $url) === TRUE && $url['port'] !== NULL)
return $url['host'].':'.$url['port']; return $url['host'].':'.$url['port'];
if (array_key_exists('scheme', $url) === TRUE if (array_key_exists('scheme', $url) === TRUE
&& strtolower($url['scheme']) === 'http') && strtolower($url['scheme']) === 'http')
return $url['host'].':80'; return $url['host'].':80';
if (array_key_exists('scheme', $url) === TRUE if (array_key_exists('scheme', $url) === TRUE
&& strtolower($url['scheme']) === 'https') && strtolower($url['scheme']) === 'https')
return $url['host'].':443'; return $url['host'].':443';
return $url['host']; return $url['host'];
} }

View File

@ -36,17 +36,17 @@ $dbfile = '/etc/yubico/val/config-db.php';
if (file_exists($dbfile) && @is_readable($dbfile)) if (file_exists($dbfile) && @is_readable($dbfile))
{ {
require_once $dbfile; require_once $dbfile;
} }
else else
{ {
// FIXME hostname // FIXME hostname
// FIXME port // FIXME port
// 'oci:oracledb' for Oracle DB (with OCI library) // 'oci:oracledb' for Oracle DB (with OCI library)
$dbtype = 'mysql'; $dbtype = 'mysql';
$dbuser = 'ykval_verifier'; $dbuser = 'ykval_verifier';
$dbpass = 'yourpassword'; $dbpass = 'yourpassword';
$dbname = 'ykval'; $dbname = 'ykval';
} }
// for the validation interface. // for the validation interface.
@ -58,9 +58,9 @@ $baseParams['__YKVAL_DB_OPTIONS__'] = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EX
// for the validation server sync // for the validation server sync
$baseParams['__YKVAL_SYNC_POOL__'] = array( $baseParams['__YKVAL_SYNC_POOL__'] = array(
// "https://api2.example.com/wsapi/2.0/sync", // "https://api2.example.com/wsapi/2.0/sync",
// "https://api3.example.com/wsapi/2.0/sync", // "https://api3.example.com/wsapi/2.0/sync",
// "https://api4.example.com/wsapi/2.0/sync", // "https://api4.example.com/wsapi/2.0/sync",
); );
/** /**
@ -70,12 +70,12 @@ $baseParams['__YKVAL_SYNC_POOL__'] = array(
* Both IPv4 and IPv6 are supported. * Both IPv4 and IPv6 are supported.
*/ */
$baseParams['__YKVAL_ALLOWED_SYNC_POOL__'] = array( $baseParams['__YKVAL_ALLOWED_SYNC_POOL__'] = array(
// "1.2.3.4", // "1.2.3.4",
// "2.3.4.5", // "2.3.4.5",
// "3.4.5.6", // "3.4.5.6",
// "fc00:aaaa::", // "fc00:aaaa::",
// "fc00:bbbb::", // "fc00:bbbb::",
// "fc00:cccc::", // "fc00:cccc::",
); );
// An array of IP addresses allowed to issue YubiKey activation/deactivation // An array of IP addresses allowed to issue YubiKey activation/deactivation
@ -103,33 +103,33 @@ $baseParams['__YKVAL_SYNC_DEFAULT_TIMEOUT__'] = 1;
// A key -> value array with curl options to set // A key -> value array with curl options to set
// when calling URLs defined in __YKVAL_SYNC_POOL__ // when calling URLs defined in __YKVAL_SYNC_POOL__
$baseParams['__YKVAL_SYNC_CURL_OPTS__'] = array( $baseParams['__YKVAL_SYNC_CURL_OPTS__'] = array(
//CURLOPT_PROTOCOLS => CURLPROTO_HTTP, //CURLOPT_PROTOCOLS => CURLPROTO_HTTP,
); );
// A key -> value array with curl options to set // A key -> value array with curl options to set
// when calling URLs returned by otp2ksmurls() // when calling URLs returned by otp2ksmurls()
$baseParams['__YKVAL_KSM_CURL_OPTS__'] = array( $baseParams['__YKVAL_KSM_CURL_OPTS__'] = array(
//CURLOPT_PROTOCOLS => CURLPROTO_HTTP, //CURLOPT_PROTOCOLS => CURLPROTO_HTTP,
); );
// Returns an array of YK-KSM URLs for decrypting $otp for $client. // Returns an array of YK-KSM URLs for decrypting $otp for $client.
// The URLs must be fully qualified, i.e., containing the OTP itself. // The URLs must be fully qualified, i.e., containing the OTP itself.
function otp2ksmurls ($otp, $client) function otp2ksmurls ($otp, $client)
{ {
//if ($client == 42) { //if ($client == 42) {
// return array("https://another-ykksm.example.com/wsapi/decrypt?otp=$otp"); // return array("https://another-ykksm.example.com/wsapi/decrypt?otp=$otp");
//} //}
//if (preg_match("/^dteffujehknh/", $otp)) { //if (preg_match("/^dteffujehknh/", $otp)) {
// return array("https://different-ykksm.example.com/wsapi/decrypt?otp=$otp"); // return array("https://different-ykksm.example.com/wsapi/decrypt?otp=$otp");
//} //}
return array( return array(
// "https://ykksm1.example.com/wsapi/decrypt?otp=$otp", // "https://ykksm1.example.com/wsapi/decrypt?otp=$otp",
// "https://ykksm2.example.com/wsapi/decrypt?otp=$otp", // "https://ykksm2.example.com/wsapi/decrypt?otp=$otp",
"http://127.0.0.1:80/wsapi/decrypt?otp=$otp", "http://127.0.0.1:80/wsapi/decrypt?otp=$otp",
"http://127.0.0.1:8002/wsapi/decrypt?otp=$otp", "http://127.0.0.1:8002/wsapi/decrypt?otp=$otp",
); );
} }
/** /**

View File

@ -36,204 +36,224 @@ require_once('ykval-db.php');
class DbImpl extends Db class DbImpl extends Db
{ {
/** /**
* Constructor * Constructor
* *
* @param string $host Database host * @param string $host Database host
* @param string $user Database user * @param string $user Database user
* @param string $pwd Database password * @param string $pwd Database password
* @param string $name Database table name * @param string $name Database table name
* @return void * @return void
* *
*/ */
public function __construct($db_dsn, $db_username, $db_password, $db_options, $name='ykval-db') public function __construct($db_dsn, $db_username, $db_password, $db_options, $name='ykval-db')
{ {
$this->db_dsn=$db_dsn; $this->db_dsn=$db_dsn;
$this->db_username=$db_username; $this->db_username=$db_username;
$this->db_password=$db_password; $this->db_password=$db_password;
$this->db_options=$db_options; $this->db_options=$db_options;
if(substr($db_dsn, 0, 4) == 'oci:') { if(substr($db_dsn, 0, 4) == 'oci:') {
# "oci:" prefix needs to be removed before passing db_dsn to OCI # "oci:" prefix needs to be removed before passing db_dsn to OCI
$this->db_dsn = substr($this->db_dsn, 4); $this->db_dsn = substr($this->db_dsn, 4);
}
$this->myLog=new Log($name);
} }
$this->myLog=new Log($name); /**
} * function to connect to database defined in config.php
*
/** * @return boolean True on success, otherwise false.
* function to connect to database defined in config.php *
* */
* @return boolean True on success, otherwise false. public function connect(){
* $this->dbh = oci_connect($this->db_username, $this->db_password, $this->db_dsn);
*/ if (!$this->dbh) {
public function connect(){ $error = oci_error();
$this->dbh = oci_connect($this->db_username, $this->db_password, $this->db_dsn); $this->myLog->log(LOG_CRIT, "Database connection error: " . $error["message"]);
if (!$this->dbh) { $this->dbh=Null;
$error = oci_error(); return false;
$this->myLog->log(LOG_CRIT, "Database connection error: " . $error["message"]); }
$this->dbh=Null; return true;
return false;
}
return true;
}
protected function query($query, $returnresult=false) {
if(!$this->isConnected()) {
$this->connect();
}
if($this->isConnected()) {
$this->myLog->log(LOG_DEBUG, 'DB query is: ' . $query);
# OCI mode
$result = oci_parse($this->dbh, $query);
if(!oci_execute($result)) {
$this->myLog->log(LOG_INFO, 'Database query error: ' . preg_replace('/\n/',' ',print_r(oci_error($result), true)));
$this->dbh = Null;
return false;
}
$this->result = $result;
if ($returnresult) return $this->result;
else return true;
} else {
$this->myLog->log(LOG_CRIT, 'No database connection');
return false;
}
}
/**
* function to get a row from the query result
* Once all rows have been fetch, function closeCursor needs to be called
*
* @param object $result Query result object or null to use the current one
* @return array a query row
*
*/
public function fetchArray($result=null){
if(!$result) $result = $this->result;
if(!$result) return null;
$res = oci_fetch_array($result, OCI_ASSOC);
return array_change_key_case($res, CASE_LOWER);
}
/**
* function to close the cursor after having fetched rows
*
* @param object $result Query result object or null to use the current one
*
*/
public function closeCursor($result=null){
}
/**
* main function used to get rows by multiple key=>value pairs from Db table.
*
* @param string $table Database table to update row in
* @param array $where Array with column=>values to select rows by
* @param int $nr Number of rows to collect. NULL=>inifinity. Default=NULL.
* @param int $rev rev=1 indicates order should be reversed. Default=NULL.
* @param string distinct Select rows with distinct columns, Default=NULL
* @return mixed Array with values from Db row or 2d-array with multiple rows
*
*/
public function findByMultiple($table, $where, $nr=null, $rev=null, $distinct=null)
{
$value=""; /* quiet the PHP Notice */
$match=null; /* quiet the PHP Notice */
$query="SELECT";
if($nr!=null){
# LIMIT doesn't exist in Oracle, so we encapsulate the query to be
# able to filter a given number of rows afterwars (after ordering)
$query.= " * FROM (SELECT";
} }
if ($distinct!=null) { protected function query($query, $returnresult=false) {
$query.= " DISTINCT " . $distinct; if(!$this->isConnected()) {
} else { $this->connect();
$query.= " *"; }
} if($this->isConnected()) {
$query.= " FROM " . $table; $this->myLog->log(LOG_DEBUG, 'DB query is: ' . $query);
if ($where!=null){ # OCI mode
foreach ($where as $key=>$value) { $result = oci_parse($this->dbh, $query);
if ($key!=null) { if(!oci_execute($result)) {
if ($value!=null) $match.= " ". $key . " = '" . $value . "' and"; $this->myLog->log(LOG_INFO, 'Database query error: ' . preg_replace('/\n/',' ',print_r(oci_error($result), true)));
else $match.= " ". $key . " is NULL and"; $this->dbh = Null;
} return false;
} }
if ($match!=null) $query .= " WHERE" . $match; $this->result = $result;
$query=rtrim($query, "and"); if ($returnresult) return $this->result;
$query=rtrim($query); else return true;
} } else {
if ($rev==1) $query.= " ORDER BY id DESC"; $this->myLog->log(LOG_CRIT, 'No database connection');
if ($nr!=null) { return false;
$query .= ") WHERE rownum < " . ($nr+1); }
} }
$result = $this->query($query, true); /**
if (!$result) return false; * function to get a row from the query result
* Once all rows have been fetch, function closeCursor needs to be called
*
* @param object $result Query result object or null to use the current one
* @return array a query row
*
*/
public function fetchArray($result=null){
if(!$result) $result = $this->result;
if(!$result) return null;
if ($nr==1) { $res = oci_fetch_array($result, OCI_ASSOC);
$row = $this->fetchArray($result); return array_change_key_case($res, CASE_LOWER);
$this->closeCursor($result);
return $row;
} }
else {
$collection=array(); /**
while($row = $this->fetchArray($result)){ * Function to close the cursor after having fetched rows
$collection[]=$row; *
} * @param object $result Query result object or null to use the current one
$this->closeCursor($result); *
return $collection; */
public function closeCursor($result=null){
} }
}
/** /**
* main function used to delete rows by multiple key=>value pairs from Db table. * main function used to get rows by multiple key=>value pairs from Db table.
* *
* @param string $table Database table to delete row in * @param string $table Database table to update row in
* @param array $where Array with column=>values to select rows by * @param array $where Array with column=>values to select rows by
* @param int $nr Number of rows to collect. NULL=>inifinity. Default=NULL. * @param int $nr Number of rows to collect. NULL=>inifinity. Default=NULL.
* @param int $rev rev=1 indicates order should be reversed. Default=NULL. * @param int $rev rev=1 indicates order should be reversed. Default=NULL.
* @param string distinct Select rows with distinct columns, Default=NULL * @param string distinct Select rows with distinct columns, Default=NULL
* @return boolean True on success, otherwise false. * @return mixed Array with values from Db row or 2d-array with multiple rows
* *
*/ */
public function deleteByMultiple($table, $where, $nr=null, $rev=null) public function findByMultiple($table, $where, $nr=null, $rev=null, $distinct=null)
{ {
$query="DELETE"; $value=""; /* quiet the PHP Notice */
$query.= " FROM " . $table; $match=null; /* quiet the PHP Notice */
$query .= " WHERE id IN (SELECT id FROM " . $table; $query="SELECT";
if ($where!=null){
$query.= " WHERE"; if($nr!=null){
foreach ($where as $key=>$value) { # LIMIT doesn't exist in Oracle, so we encapsulate the query to be
$query.= " ". $key . " = '" . $value . "' and"; # able to filter a given number of rows afterwars (after ordering)
} $query.= " * FROM (SELECT";
$query=rtrim($query, "and"); }
$query=rtrim($query);
if ($distinct!=null) {
$query.= " DISTINCT " . $distinct;
} else {
$query.= " *";
}
$query.= " FROM " . $table;
if ($where!=null){
foreach ($where as $key=>$value) {
if ($key != 'server' && !(ctype_alnum($value) || is_null($value)))
{
$this->myLog->log(LOG_WARNING, "findByMultiple: attempted to use non-alphanumeric in WHERE: " . $table . "." . $key . " = " . $value);
return false;
}
elseif ($key == 'server' && !filter_var($value, FILTER_VALIDATE_URL))
{
$this->myLog->log(LOG_WARNING, "findByMultiple: attempted use invalid URL in WHERE: " . $table . "." . $key . " = " . $value);
return false;
}
if ($key!=null) {
if ($value!=null) $match.= " ". $key . " = '" . $value . "' and";
else $match.= " ". $key . " is NULL and";
}
}
if ($match!=null) $query .= " WHERE" . $match;
$query=rtrim($query, "and");
$query=rtrim($query);
}
if ($rev==1) $query.= " ORDER BY id DESC";
if ($nr!=null) {
$query .= ") WHERE rownum < " . ($nr+1);
}
$result = $this->query($query, true);
if (!$result) return false;
if ($nr==1) {
$row = $this->fetchArray($result);
$this->closeCursor($result);
return $row;
}
else {
$collection=array();
while($row = $this->fetchArray($result)){
$collection[]=$row;
}
$this->closeCursor($result);
return $collection;
}
} }
if ($rev==1) $query.= " ORDER BY id DESC";
$query .= ")"; /**
if ($nr!=null) $query.= " and rownum < " . ($nr+1); * main function used to delete rows by multiple key=>value pairs from Db table.
*
* @param string $table Database table to delete row in
* @param array $where Array with column=>values to select rows by
* @param int $nr Number of rows to collect. NULL=>inifinity. Default=NULL.
* @param int $rev rev=1 indicates order should be reversed. Default=NULL.
* @param string distinct Select rows with distinct columns, Default=NULL
* @return boolean True on success, otherwise false.
*
*/
public function deleteByMultiple($table, $where, $nr=null, $rev=null)
{
$query="DELETE";
$query.= " FROM " . $table;
$query .= " WHERE id IN (SELECT id FROM " . $table;
if ($where!=null){
$query.= " WHERE";
foreach ($where as $key=>$value) {
if ($key != 'server' && !ctype_alnum($value))
{
$this->myLog->log(LOG_WARNING, "deleteByMultiple: attempted to write non-alphanumeric to the database: " . $value);
return false;
}
elseif ($key == 'server' && !filter_var($value, FILTER_VALIDATE_URL))
{
$this->myLog->log(LOG_WARNING, "deleteByMultiple: attempted to write invalid URL to the database: " . $value);
return false;
}
$query.= " ". $key . " = '" . $value . "' and";
}
$query=rtrim($query, "and");
$query=rtrim($query);
}
if ($rev==1) $query.= " ORDER BY id DESC";
return $this->query($query, false); $query .= ")";
} if ($nr!=null) $query.= " and rownum < " . ($nr+1);
/** return $this->query($query, false);
* Function to get the number of rows }
*
* @param object $result Query result object or null to use the current one /**
* @return int number of rows affected by last statement or 0 if database connection is not functional. * Function to get the number of rows
* *
*/ * @param object $result Query result object or null to use the current one
public function rowCount($result=null) * @return int number of rows affected by last statement or 0 if database connection is not functional.
{ *
if(!$result) $result = $this->result; */
if($result) { public function rowCount($result=null)
return oci_num_rows($result); {
} else { if(!$result) $result = $this->result;
return 0; if($result) {
return oci_num_rows($result);
} else {
return 0;
}
} }
}
} }

View File

@ -36,212 +36,228 @@ require_once('ykval-db.php');
class DbImpl extends Db class DbImpl extends Db
{ {
/**
* Constructor
*
* @param string $host Database host
* @param string $user Database user
* @param string $pwd Database password
* @param string $name Database table name
* @return void
*
*/
public function __construct($db_dsn, $db_username, $db_password, $db_options, $name='ykval-db')
{
$this->db_dsn=$db_dsn;
$this->db_username=$db_username;
$this->db_password=$db_password;
$this->db_options=$db_options;
$this->result = null;
$this->myLog=new Log($name);
/**
* Constructor
*
* @param string $host Database host
* @param string $user Database user
* @param string $pwd Database password
* @param string $name Database table name
* @return void
*
*/
public function __construct($db_dsn, $db_username, $db_password, $db_options, $name='ykval-db')
{
$this->db_dsn=$db_dsn;
$this->db_username=$db_username;
$this->db_password=$db_password;
$this->db_options=$db_options;
$this->result = null;
$this->myLog=new Log($name);
}
/**
* function to connect to database defined in config.php
*
* @return boolean True on success, otherwise false.
*
*/
public function connect(){
try {
$this->dbh = new PDO($this->db_dsn, $this->db_username, $this->db_password, $this->db_options);
} catch (PDOException $e) {
$this->myLog->log(LOG_CRIT, "Database connection error: " . $e->getMessage());
$this->dbh=Null;
return false;
} }
return true;
}
protected function query($query, $returnresult=false) { /**
if(!$this->isConnected()) { * function to connect to database defined in config.php
$this->connect(); *
* @return boolean True on success, otherwise false.
*
*/
public function connect(){
try {
$this->dbh = new PDO($this->db_dsn, $this->db_username, $this->db_password, $this->db_options);
} catch (PDOException $e) {
$this->myLog->log(LOG_CRIT, "Database connection error: " . $e->getMessage());
$this->dbh=Null;
return false;
}
return true;
} }
if($this->isConnected()) {
$this->myLog->log(LOG_DEBUG, 'DB query is: ' . $query);
try { protected function query($query, $returnresult=false) {
$this->result = $this->dbh->query($query); if(!$this->isConnected()) {
} catch (PDOException $e) { $this->connect();
$this->myLog->log(LOG_INFO, 'Database query error: ' . preg_replace('/\n/',' ',print_r($this->dbh->errorInfo(), true))); }
$this->dbh = Null; if($this->isConnected()) {
return false; $this->myLog->log(LOG_DEBUG, 'DB query is: ' . $query);
}
if ($returnresult) return $this->result; try {
else return true; $this->result = $this->dbh->query($query);
} else { } catch (PDOException $e) {
$this->myLog->log(LOG_CRIT, 'No database connection'); $this->myLog->log(LOG_INFO, 'Database query error: ' . preg_replace('/\n/',' ',print_r($this->dbh->errorInfo(), true)));
return false; $this->dbh = Null;
return false;
}
if ($returnresult) return $this->result;
else return true;
} else {
$this->myLog->log(LOG_CRIT, 'No database connection');
return false;
}
} }
}
/**
* function to get a row from the query result
* Once all rows have been fetch, function closeCursor needs to be called
*
* @param object $result Query result object or null to use the current one
* @return array a query row
*
*/
public function fetchArray($result=null){
if(!$result) $result = $this->result;
if(!$result) return null;
/** return $result->fetch(PDO::FETCH_ASSOC);
* function to get a row from the query result
* Once all rows have been fetch, function closeCursor needs to be called
*
* @param object $result Query result object or null to use the current one
* @return array a query row
*
*/
public function fetchArray($result=null){
if(!$result) $result = $this->result;
if(!$result) return null;
return $result->fetch(PDO::FETCH_ASSOC);
}
/**
* function to close the cursor after having fetched rows
*
* @param object $result Query result object or null to use the current one
*
*/
public function closeCursor($result=null){
if(!$result) $result = $this->result;
if($result) $result->closeCursor();
}
/**
* Main function used to get rows by multiple key=>value pairs from Db table.
*
* @param string $table Database table to update row in
* @param array $where Array with column=>values to select rows by
* @param int $nr Number of rows to collect. NULL=>inifinity. Default=NULL.
* @param int $rev rev=1 indicates order should be reversed. Default=NULL.
* @param string distinct Select rows with distinct columns, Default=NULL
*
* @return mixed Array with values from Db row or 2d-array with multiple rows
*/
public function findByMultiple($table, $where, $nr=NULL, $rev=NULL, $distinct=NULL)
{
$value = '';
$match = NULL;
$query = 'SELECT';
if ($distinct != NULL)
$query.= " DISTINCT " . $distinct;
else
$query.= " *";
$query.= " FROM " . $table;
if ($where != NULL)
{
foreach ($where as $key => $value)
{
if ($key != NULL)
{
if ($value != NULL)
$match .= " ". $key . " = '" . $value . "' and";
else
$match .= " ". $key . " is NULL and";
}
}
if ($match != NULL)
$query .= " WHERE" . $match;
$query = rtrim($query, "and");
$query = rtrim($query);
}
if ($rev == 1)
$query.= " ORDER BY id DESC";
if ($nr != NULL)
$query.= " LIMIT " . $nr;
$result = $this->query($query, true);
if (!$result)
return false;
if ($nr == 1)
{
$row = $this->fetchArray($result);
$this->closeCursor($result);
return $row;
}
$collection = array();
while($row = $this->fetchArray($result))
$collection[] = $row;
$this->closeCursor($result);
return $collection;
}
/**
* main function used to delete rows by multiple key=>value pairs from Db table.
*
* @param string $table Database table to delete row in
* @param array $where Array with column=>values to select rows by
* @param int $nr Number of rows to collect. NULL=>inifinity. Default=NULL.
* @param int $rev rev=1 indicates order should be reversed. Default=NULL.
* @param string distinct Select rows with distinct columns, Default=NULL
* @return boolean True on success, otherwise false.
*
*/
public function deleteByMultiple($table, $where, $nr=null, $rev=null)
{
$query="DELETE";
$query.= " FROM " . $table;
if ($where!=null){
$query.= " WHERE";
foreach ($where as $key=>$value) {
$query.= " ". $key . " = '" . $value . "' and";
}
$query=rtrim($query, "and");
$query=rtrim($query);
} }
if ($rev==1) $query.= " ORDER BY id DESC";
if ($nr!=null) $query.= " LIMIT " . $nr;
return $this->query($query, false);
}
/** /**
* Function to get the number of rows * function to close the cursor after having fetched rows
* *
* @param object $result Query result object or null to use the current one * @param object $result Query result object or null to use the current one
* @return int number of rows affected by last statement or 0 if database connection is not functional. *
* */
*/ public function closeCursor($result=null){
public function rowCount($result=null) if(!$result) $result = $this->result;
{ if($result) $result->closeCursor();
if(!$result) $result = $this->result; }
if($result) {
$count=$result->rowCount(); /**
$result->closeCursor(); * Main function used to get rows by multiple key=>value pairs from Db table.
return $count; *
} else { * @param string $table Database table to update row in
return 0; * @param array $where Array with column=>values to select rows by
* @param int $nr Number of rows to collect. NULL=>inifinity. Default=NULL.
* @param int $rev rev=1 indicates order should be reversed. Default=NULL.
* @param string distinct Select rows with distinct columns, Default=NULL
*
* @return mixed Array with values from Db row or 2d-array with multiple rows
*/
public function findByMultiple($table, $where, $nr=NULL, $rev=NULL, $distinct=NULL)
{
$value = '';
$match = NULL;
$query = 'SELECT';
if ($distinct != NULL)
$query.= " DISTINCT " . $distinct;
else
$query.= " *";
$query.= " FROM " . $table;
if ($where != NULL)
{
foreach ($where as $key => $value)
{
if ($key != 'server' && !(ctype_alnum($value) || is_null($value)))
{
$this->myLog->log(LOG_WARNING, "findByMultiple: attempted to use non-alphanumeric in WHERE: " . $table . "." . $key . " = " . $value);
return false;
}
elseif ($key == 'server' && !filter_var($value, FILTER_VALIDATE_URL))
{
$this->myLog->log(LOG_WARNING, "findByMultiple: attempted to use invalid URL in WHERE: " . $table . "." . $key . " = " . $value);
return false;
}
if ($key != NULL)
{
if ($value != NULL)
$match .= " ". $key . " = '" . $value . "' and";
else
$match .= " ". $key . " is NULL and";
}
}
if ($match != NULL)
$query .= " WHERE" . $match;
$query = rtrim($query, "and");
$query = rtrim($query);
}
if ($rev == 1)
$query.= " ORDER BY id DESC";
if ($nr != NULL)
$query.= " LIMIT " . $nr;
$result = $this->query($query, true);
if (!$result)
return false;
if ($nr == 1)
{
$row = $this->fetchArray($result);
$this->closeCursor($result);
return $row;
}
$collection = array();
while($row = $this->fetchArray($result))
$collection[] = $row;
$this->closeCursor($result);
return $collection;
}
/**
* main function used to delete rows by multiple key=>value pairs from Db table.
*
* @param string $table Database table to delete row in
* @param array $where Array with column=>values to select rows by
* @param int $nr Number of rows to collect. NULL=>inifinity. Default=NULL.
* @param int $rev rev=1 indicates order should be reversed. Default=NULL.
* @param string distinct Select rows with distinct columns, Default=NULL
* @return boolean True on success, otherwise false.
*
*/
public function deleteByMultiple($table, $where, $nr=null, $rev=null)
{
$query="DELETE";
$query.= " FROM " . $table;
if ($where!=null){
$query.= " WHERE";
foreach ($where as $key=>$value) {
if ($key != 'server' && !ctype_alnum($value)) {
$this->myLog->log(LOG_WARNING, "deleteByMultiple: attempted to write non-alphanumeric to the database: " . $value);
return false;
}
elseif ($key == 'server' && !filter_var($value, FILTER_VALIDATE_URL))
{
$this->myLog->log(LOG_WARNING, "deleteByMultiple: attempted to write invalid URL to the database: " . $value);
return false;
}
$query.= " ". $key . " = '" . $value . "' and";
}
$query=rtrim($query, "and");
$query=rtrim($query);
}
if ($rev==1) $query.= " ORDER BY id DESC";
if ($nr!=null) $query.= " LIMIT " . $nr;
return $this->query($query, false);
}
/**
* Function to get the number of rows
*
* @param object $result Query result object or null to use the current one
* @return int number of rows affected by last statement or 0 if database connection is not functional.
*
*/
public function rowCount($result=null)
{
if(!$result) $result = $this->result;
if($result) {
$count=$result->rowCount();
$result->closeCursor();
return $count;
} else {
return 0;
}
} }
}
} }

View File

@ -35,231 +35,274 @@ require_once('ykval-log.php');
abstract class Db abstract class Db
{ {
/** /**
* static function to determine database type and instantiate the correct subclass * static function to determine database type and instantiate the correct subclass
* *
* */ * */
public static function GetDatabaseHandle($baseParams, $logname) public static function GetDatabaseHandle($baseParams, $logname)
{ {
if(substr($baseParams['__YKVAL_DB_DSN__'], 0, 3) == 'oci') { if(substr($baseParams['__YKVAL_DB_DSN__'], 0, 3) == 'oci') {
require_once 'ykval-db-oci.php'; require_once 'ykval-db-oci.php';
} else { } else {
require_once 'ykval-db-pdo.php'; require_once 'ykval-db-pdo.php';
} }
return new DbImpl($baseParams['__YKVAL_DB_DSN__'], return new DbImpl($baseParams['__YKVAL_DB_DSN__'],
$baseParams['__YKVAL_DB_USER__'], $baseParams['__YKVAL_DB_USER__'],
$baseParams['__YKVAL_DB_PW__'], $baseParams['__YKVAL_DB_PW__'],
$baseParams['__YKVAL_DB_OPTIONS__'], $baseParams['__YKVAL_DB_OPTIONS__'],
$logname . ':db'); $logname . ':db');
}
function addField($name, $value)
{
$this->myLog->addField($name, $value);
}
/**
* function to convert Db timestamps to unixtime(s)
*
* @param string $updated Database timestamp
* @return int Timestamp in unixtime format
*
*/
public function timestampToTime($updated)
{
$stamp=strptime($updated, '%F %H:%M:%S');
return mktime($stamp[tm_hour], $stamp[tm_min], $stamp[tm_sec], $stamp[tm_mon]+1, $stamp[tm_mday], $stamp[tm_year]);
}
/**
* function to compute delta (s) between 2 Db timestamps
*
* @param string $first Database timestamp 1
* @param string $second Database timestamp 2
* @return int Deltatime (s)
*
*/
public function timestampDeltaTime($first, $second)
{
return Db::timestampToTime($second) - Db::timestampToTime($first);
}
/**
* function to disconnect from database
*
* @return boolean True on success, otherwise false.
*
*/
public function disconnect()
{
$this->dbh=NULL;
}
/**
* function to check if database is connected
*
* @return boolean True if connected, otherwise false.
*
*/
public function isConnected()
{
if ($this->dbh!=NULL) return True;
else return False;
}
/**
* function to update row in database by a where condition
*
* @param string $table Database table to update row in
* @param int $id Id on row to update
* @param array $values Array with key=>values to update
* @return boolean True on success, otherwise false.
*
*/
public function updateBy($table, $k, $v, $values)
{
$query = "";
foreach ($values as $key=>$value){
if (!is_null($value)) $query .= ' ' . $key . "='" . $value . "',";
else $query .= ' ' . $key . '=NULL,';
}
if (! $query) {
$this->myLog->log(LOG_DEBUG, "no values to set in query. Not updating DB");
return true;
} }
$query = rtrim($query, ",") . " WHERE " . $k . " = '" . $v . "'"; function addField($name, $value)
// Insert UPDATE statement at beginning {
$query = "UPDATE " . $table . " SET " . $query; $this->myLog->addField($name, $value);
return $this->query($query, false);
}
/**
* function to update row in database
*
* @param string $table Database table to update row in
* @param int $id Id on row to update
* @param array $values Array with key=>values to update
* @return boolean True on success, otherwise false.
*
*/
public function update($table, $id, $values)
{
return $this->updateBy($table, 'id', $id, $values);
}
/**
* function to update row in database based on a condition
*
* @param string $table Database table to update row in
* @param string $k Column to select row on
* @param string $v Value to select row on
* @param array $values Array with key=>values to update
* @param string $condition conditional statement
* @return boolean True on success, otherwise false.
*
*/
public function conditionalUpdateBy($table, $k, $v, $values, $condition)
{
$query = ""; /* quiet the PHP Notice */
foreach ($values as $key=>$value){
$query = $query . " " . $key . "='" . $value . "',";
}
if (! $query) {
$this->myLog->log(LOG_DEBUG, "no values to set in query. Not updating DB");
return true;
} }
$query = rtrim($query, ",") . " WHERE " . $k . " = '" . $v . "' and " . $condition; /**
// Insert UPDATE statement at beginning * function to convert Db timestamps to unixtime(s)
$query = "UPDATE " . $table . " SET " . $query; *
* @param string $updated Database timestamp
* @return int Timestamp in unixtime format
*
*/
public function timestampToTime($updated)
{
$stamp=strptime($updated, '%F %H:%M:%S');
return mktime($stamp[tm_hour], $stamp[tm_min], $stamp[tm_sec], $stamp[tm_mon]+1, $stamp[tm_mday], $stamp[tm_year]);
return $this->query($query, false);
}
/**
* Function to update row in database based on a condition.
* An ID value is passed to select the appropriate column
*
* @param string $table Database table to update row in
* @param int $id Id on row to update
* @param array $values Array with key=>values to update
* @param string $condition conditional statement
* @return boolean True on success, otherwise false.
*
*/
public function conditionalUpdate($table, $id, $values, $condition)
{
return $this->conditionalUpdateBy($table, 'id', $id, $values, $condition);
}
/**
* function to insert new row in database
*
* @param string $table Database table to update row in
* @param array $values Array with key=>values to update
* @return boolean True on success, otherwise false.
*
*/
public function save($table, $values)
{
$query= 'INSERT INTO ' . $table . " (";
foreach ($values as $key=>$value){
if (!is_null($value)) $query = $query . $key . ",";
} }
$query = rtrim($query, ",") . ') VALUES (';
foreach ($values as $key=>$value){ /**
if (!is_null($value)) $query = $query . "'" . $value . "',"; * function to compute delta (s) between 2 Db timestamps
*
* @param string $first Database timestamp 1
* @param string $second Database timestamp 2
* @return int Deltatime (s)
*
*/
public function timestampDeltaTime($first, $second)
{
return Db::timestampToTime($second) - Db::timestampToTime($first);
} }
$query = rtrim($query, ",");
$query = $query . ")";
return $this->query($query, false);
}
/**
* helper function to collect last row[s] in database
*
* @param string $table Database table to update row in
* @param int $nr Number of rows to collect. NULL=>inifinity. DEFAULT=1.
* @return mixed Array with values from Db row or 2d-array with multiple rows
or false on failure.
*
*/
public function last($table, $nr=1)
{
return Db::findBy($table, null, null, $nr, 1);
}
/** /**
* main function used to get rows from Db table. * function to disconnect from database
* *
* @param string $table Database table to update row in * @return boolean True on success, otherwise false.
* @param string $key Column to select rows by *
* @param string $value Value to select rows by */
* @param int $nr Number of rows to collect. NULL=>inifinity. Default=NULL. public function disconnect()
* @param int $rev rev=1 indicates order should be reversed. Default=NULL. {
* @return mixed Array with values from Db row or 2d-array with multiple rows $this->dbh=NULL;
* }
*/
public function findBy($table, $key, $value, $nr=null, $rev=null)
{
return $this->findByMultiple($table, array($key=>$value), $nr, $rev);
}
/** /**
* Function to do a custom query on database connection * function to check if database is connected
* *
* @param string $query Database query * @return boolean True if connected, otherwise false.
* @return mixed *
* */
*/ public function isConnected()
public function customQuery($query) {
{ if ($this->dbh!=NULL) return True;
return $this->query($query, true); else return False;
} }
/**
* function to update row in database by a where condition
*
* @param string $table Database table to update row in
* @param int $id Id on row to update
* @param array $values Array with key=>values to update
* @return boolean True on success, otherwise false.
*
*/
public function updateBy($table, $k, $v, $values)
{
if (!ctype_alnum($v) && !filter_var($v, FILTER_VALIDATE_URL)) {
$this->myLog->log(LOG_WARNING, "updateBy: attempted to use an invalid value: " . $v);
return false;
}
$query = "";
foreach ($values as $key=>$value) {
if ($key != 'server' && !(ctype_alnum($value) || is_null($value))) {
$this->myLog->log(LOG_WARNING, "updateBy: attempted to write non-alphanumeric to the database: " . $table . "." . $key . " <= " . $value);
return false;
}
elseif ($key == 'server' && !filter_var($value, FILTER_VALIDATE_URL))
{
$this->myLog->log(LOG_WARNING, "updateBy: attempted to write invalid URL to the database: " . $table . "." . $key . " <= " . $value);
return false;
}
if (!is_null($value)) $query .= ' ' . $key . "='" . $value . "',";
else $query .= ' ' . $key . '=NULL,';
}
if (! $query) {
$this->myLog->log(LOG_DEBUG, "no values to set in query. Not updating DB");
return true;
}
$query = rtrim($query, ",") . " WHERE " . $k . " = '" . $v . "'";
// Insert UPDATE statement at beginning
$query = "UPDATE " . $table . " SET " . $query;
return $this->query($query, false);
}
/**
* function to update row in database
*
* @param string $table Database table to update row in
* @param int $id Id on row to update
* @param array $values Array with key=>values to update
* @return boolean True on success, otherwise false.
*
*/
public function update($table, $id, $values)
{
return $this->updateBy($table, 'id', $id, $values);
}
/**
* function to update row in database based on a condition
*
* @param string $table Database table to update row in
* @param string $k Column to select row on
* @param string $v Value to select row on
* @param array $values Array with key=>values to update
* @param string $condition conditional statement
* @return boolean True on success, otherwise false.
*
*/
public function conditionalUpdateBy($table, $k, $v, $values, $condition)
{
if (!ctype_alnum($v) || preg_match('/^[a-zA-Z0-9><=()_ ]*$/', $condition) == 0)
{
$this->myLog->log(LOG_WARNING, "conditionalUpdateBy: attempted to use non-alphanumeric value: " . $v . " - " . $condition);
return false;
}
$query = ""; /* quiet the PHP Notice */
foreach ($values as $key=>$value){
if ($key != 'server' && !is_int($value) && !ctype_alnum($value)) {
$this->myLog->log(LOG_WARNING, "conditionalUpdateBy: attempted to write non-alphanumeric to the database: " . $value);
return false;
}
elseif ($key == 'server' && !filter_var($value, FILTER_VALIDATE_URL))
{
$this->myLog->log(LOG_WARNING, "conditionalUpdateBy: attempted to write invalid URL to the database: " . $value);
return false;
}
$query = $query . " " . $key . "='" . $value . "',";
}
if (! $query) {
$this->myLog->log(LOG_DEBUG, "no values to set in query. Not updating DB");
return true;
}
$query = rtrim($query, ",") . " WHERE " . $k . " = '" . $v . "' and " . $condition;
// Insert UPDATE statement at beginning
$query = "UPDATE " . $table . " SET " . $query;
return $this->query($query, false);
}
/**
* Function to update row in database based on a condition.
* An ID value is passed to select the appropriate column
*
* @param string $table Database table to update row in
* @param int $id Id on row to update
* @param array $values Array with key=>values to update
* @param string $condition conditional statement
* @return boolean True on success, otherwise false.
*
*/
public function conditionalUpdate($table, $id, $values, $condition)
{
return $this->conditionalUpdateBy($table, 'id', $id, $values, $condition);
}
/**
* function to insert new row in database
*
* @param string $table Database table to update row in
* @param array $values Array with key=>values to update
* @return boolean True on success, otherwise false.
*
*/
public function save($table, $values)
{
$query= 'INSERT INTO ' . $table . " (";
foreach ($values as $key=>$value){
if ($key == 'server') {
$v = filter_var($value, FILTER_VALIDATE_URL);
if (!$v) {
$this->myLog->log(LOG_WARNING, "save: bad server URL provided: " . $value);
return false;
}
$value = $v;
} else if ($key == 'info') {
if (preg_match('/[a-zA-Z0-9&_=,]+/', $value) == 0) {
$this->myLog->log(LOG_WARNING, "save: bad info string provided: " . $value);
return false;
}
} else {
if ($value != '' && !is_int($value) && !ctype_alnum($value)) {
$this->myLog->log(LOG_WARNING, "save: attempted to write non-alphanumeric to the database: " . $value);
return false;
}
}
if (!is_null($value)) $query = $query . $key . ",";
}
$query = rtrim($query, ",") . ') VALUES (';
foreach ($values as $key=>$value){
if (!is_null($value)) $query = $query . "'" . $value . "',";
}
$query = rtrim($query, ",");
$query = $query . ")";
return $this->query($query, false);
}
/**
* helper function to collect last row[s] in database
*
* @param string $table Database table to update row in
* @param int $nr Number of rows to collect. NULL=>inifinity. DEFAULT=1.
* @return mixed Array with values from Db row or 2d-array with multiple rows
or false on failure.
*
*/
public function last($table, $nr=1)
{
return Db::findBy($table, null, null, $nr, 1);
}
/**
* main function used to get rows from Db table.
*
* @param string $table Database table to update row in
* @param string $key Column to select rows by
* @param string $value Value to select rows by
* @param int $nr Number of rows to collect. NULL=>inifinity. Default=NULL.
* @param int $rev rev=1 indicates order should be reversed. Default=NULL.
* @return mixed Array with values from Db row or 2d-array with multiple rows
*
*/
public function findBy($table, $key, $value, $nr=null, $rev=null)
{
return $this->findByMultiple($table, array($key=>$value), $nr, $rev);
}
/**
* Function to do a custom query on database connection
*
* @param string $query Database query
* @return mixed
*
*/
public function customQuery($query)
{
return $this->query($query, true);
}
} }

View File

@ -29,9 +29,9 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set_include_path(implode(PATH_SEPARATOR, array( set_include_path(implode(PATH_SEPARATOR, array(
get_include_path(), get_include_path(),
'/usr/share/yubikey-val', '/usr/share/yubikey-val',
'/etc/yubico/val', '/etc/yubico/val',
))); )));
require_once 'ykval-config.php'; require_once 'ykval-config.php';
@ -44,24 +44,24 @@ $myLog = new Log($logname);
$db = Db::GetDatabaseHandle($baseParams, $logname); $db = Db::GetDatabaseHandle($baseParams, $logname);
if (!$db->connect()) { if (!$db->connect()) {
$myLog->log(LOG_WARNING, "Could not connect to database"); $myLog->log(LOG_WARNING, "Could not connect to database");
exit(1); exit(1);
} }
$result=$db->customQuery("SELECT active, created, modified, yk_publicname, yk_counter, yk_use, yk_low, yk_high, nonce, notes FROM yubikeys ORDER BY yk_publicname"); $result=$db->customQuery("SELECT active, created, modified, yk_publicname, yk_counter, yk_use, yk_low, yk_high, nonce, notes FROM yubikeys ORDER BY yk_publicname");
while($row = $db->fetchArray($result)){ while($row = $db->fetchArray($result)){
echo (int)$row['active'] . echo (int)$row['active'] .
"," . $row['created'] . "," . $row['created'] .
"," . $row['modified'] . "," . $row['modified'] .
"," . $row['yk_publicname'] . "," . $row['yk_publicname'] .
"," . $row['yk_counter'] . "," . $row['yk_counter'] .
"," . $row['yk_use'] . "," . $row['yk_use'] .
"," . $row['yk_low'] . "," . $row['yk_low'] .
"," . $row['yk_high'] . "," . $row['yk_high'] .
"," . $row['nonce'] . "," . $row['nonce'] .
"," . $row['notes'] . "," . $row['notes'] .
"\n"; "\n";
} }
$db->closeCursor($result); $db->closeCursor($result);
$db->disconnect(); $db->disconnect();

View File

@ -44,20 +44,20 @@ $myLog = new Log($logname);
$db = Db::GetDatabaseHandle($baseParams, $logname); $db = Db::GetDatabaseHandle($baseParams, $logname);
if (!$db->connect()) { if (!$db->connect()) {
$myLog->log(LOG_WARNING, "Could not connect to database"); $myLog->log(LOG_WARNING, "Could not connect to database");
exit(1); exit(1);
} }
$result = $db->customQuery("select id, active, created, secret, email, notes, otp from clients order by id"); $result = $db->customQuery("select id, active, created, secret, email, notes, otp from clients order by id");
while($row = $db->fetchArray($result)) { while($row = $db->fetchArray($result)) {
echo $row['id'] . echo $row['id'] .
"," . (int)$row['active'] . "," . (int)$row['active'] .
"," . $row['created'] . "," . $row['created'] .
"," . $row['secret'] . "," . $row['secret'] .
"," . $row['email'] . "," . $row['email'] .
"," . $row['notes'] . "," . $row['notes'] .
"," . $row['otp'] . "," . $row['otp'] .
"\n"; "\n";
} }
$db->closeCursor($result); $db->closeCursor($result);

View File

@ -37,18 +37,18 @@ set_include_path(implode(PATH_SEPARATOR, array(
$options = getopt("h", array("help", "email::", "notes::", "otp::", "urandom")); $options = getopt("h", array("help", "email::", "notes::", "otp::", "urandom"));
if(array_key_exists('h', $options) || array_key_exists('help', $options)) { if(array_key_exists('h', $options) || array_key_exists('help', $options)) {
echo "Usage: ".$argv[0]." [ OPTIONS ] [ num_clients ]\n"; echo "Usage: ".$argv[0]." [ OPTIONS ] [ num_clients ]\n";
echo " Unless num_clients is defined, one client will be generated.\n\n"; echo " Unless num_clients is defined, one client will be generated.\n\n";
echo "Options supported by ".$argv[0]."\n"; echo "Options supported by ".$argv[0]."\n";
echo " --urandom\n"; echo " --urandom\n";
echo " Use /dev/urandom instead of /dev/random as entropy source\n"; echo " Use /dev/urandom instead of /dev/random as entropy source\n";
echo " --email=EMAIL\n"; echo " --email=EMAIL\n";
echo " Sets the e-mail field of the created clients\n"; echo " Sets the e-mail field of the created clients\n";
echo " --notes=NOTES\n"; echo " --notes=NOTES\n";
echo " Sets the notes field of the created clients\n"; echo " Sets the notes field of the created clients\n";
echo " --otp=OTP\n"; echo " --otp=OTP\n";
echo " Sets the otp field of the created clients\n"; echo " Sets the otp field of the created clients\n";
exit(1); exit(1);
} }
require_once 'ykval-config.php'; require_once 'ykval-config.php';
@ -60,65 +60,65 @@ $myLog = new Log($logname);
$db = Db::GetDatabaseHandle($baseParams, $logname); $db = Db::GetDatabaseHandle($baseParams, $logname);
if(!$db->connect()) { if(!$db->connect()) {
$myLog->log(LOG_WARNING, "Could not connect to database"); $myLog->log(LOG_WARNING, "Could not connect to database");
error_log("Could not connect to database"); error_log("Could not connect to database");
exit(1); exit(1);
} }
$count=1; $count=1;
if($last_arg = intval(array_pop($argv))) { if($last_arg = intval(array_pop($argv))) {
$count = $last_arg; $count = $last_arg;
} }
$result = $db->customQuery("SELECT id FROM clients ORDER BY id DESC LIMIT 1"); $result = $db->customQuery("SELECT id FROM clients ORDER BY id DESC LIMIT 1");
$row = $db->fetchArray($result); $row = $db->fetchArray($result);
$db->closeCursor($result); $db->closeCursor($result);
if($row) { if($row) {
$next_id = $row['id']+1; $next_id = $row['id']+1;
} else { } else {
$next_id = 1; $next_id = 1;
} }
$random = array_key_exists('urandom', $options) ? "/dev/urandom" : "/dev/random"; $random = array_key_exists('urandom', $options) ? "/dev/urandom" : "/dev/random";
$fh = fopen($random, "r"); $fh = fopen($random, "r");
if(!$fh) { if(!$fh) {
die("cannot open ".$random); die("cannot open ".$random);
} }
for ($i=0; $i<$count; $i++) { for ($i=0; $i<$count; $i++) {
$client_id = $next_id++; $client_id = $next_id++;
if (!($rnd = fread ($fh, 20))) { if (!($rnd = fread ($fh, 20))) {
die("cannot read from ".$random); die("cannot read from ".$random);
} }
$secret = base64_encode($rnd); $secret = base64_encode($rnd);
$params = array( $params = array(
"id" => $client_id, "id" => $client_id,
"active" => 1, "active" => 1,
"created" => time(), "created" => time(),
"secret" => $secret, "secret" => $secret,
"email" => array_key_exists('email', $options) ? $options['email'] : '', "email" => array_key_exists('email', $options) ? $options['email'] : '',
"notes" => array_key_exists('notes', $options) ? $options['notes'] : '', "notes" => array_key_exists('notes', $options) ? $options['notes'] : '',
"otp" => array_key_exists('otp', $options) ? $options['otp'] : '' "otp" => array_key_exists('otp', $options) ? $options['otp'] : ''
); );
$query="INSERT INTO clients " . $query="INSERT INTO clients " .
"(id,active,created,secret,email,notes,otp) VALUES " . "(id,active,created,secret,email,notes,otp) VALUES " .
"('" . $params["id"] . "', " . "('" . $params["id"] . "', " .
"'" . $params["active"] . "', " . "'" . $params["active"] . "', " .
"'" . $params['created'] . "'," . "'" . $params['created'] . "'," .
"'" . $params['secret'] . "'," . "'" . $params['secret'] . "'," .
"'" . $params['email'] . "'," . "'" . $params['email'] . "'," .
"'" . $params['notes'] . "'," . "'" . $params['notes'] . "'," .
"'" . $params['otp'] . "')"; "'" . $params['otp'] . "')";
if(!$db->customQuery($query)){ if(!$db->customQuery($query)){
$myLog->log(LOG_ERR, "Failed to insert new client with query " . $query); $myLog->log(LOG_ERR, "Failed to insert new client with query " . $query);
error_log("Failed to insert new client with query " . $query); error_log("Failed to insert new client with query " . $query);
exit(1); exit(1);
} }
echo $client_id.",".$secret."\n"; echo $client_id.",".$secret."\n";
} }
fclose($fh); fclose($fh);

View File

@ -44,80 +44,80 @@ $myLog = new Log($logname);
$db = Db::GetDatabaseHandle($baseParams, $logname); $db = Db::GetDatabaseHandle($baseParams, $logname);
if (!$db->connect()) { if (!$db->connect()) {
$myLog->log(LOG_WARNING, "Could not connect to database"); $myLog->log(LOG_WARNING, "Could not connect to database");
error_log("Could not connect to database"); error_log("Could not connect to database");
exit(1); exit(1);
} }
while ($res=fgetcsv(STDIN, 0, ",")) { while ($res=fgetcsv(STDIN, 0, ",")) {
if ($res[0]===null || strpos($res[0], '#')===0) if ($res[0]===null || strpos($res[0], '#')===0)
continue; continue;
$params=array( $params=array(
"active"=>$res[0], "active"=>$res[0],
"created"=>$res[1], "created"=>$res[1],
"modified"=>$res[2], "modified"=>$res[2],
"yk_publicname"=>$res[3], "yk_publicname"=>$res[3],
"yk_counter"=>$res[4], "yk_counter"=>$res[4],
"yk_use"=>$res[5], "yk_use"=>$res[5],
"yk_low"=>$res[6], "yk_low"=>$res[6],
"yk_high"=>$res[7], "yk_high"=>$res[7],
"nonce"=>$res[8], "nonce"=>$res[8],
"notes"=>$res[9] "notes"=>$res[9]
); );
# FIXME # FIXME
# INSERT first, if duplicate error, UPDATE. # INSERT first, if duplicate error, UPDATE.
$query="SELECT * FROM yubikeys WHERE yk_publicname='" . $params['yk_publicname'] . "'"; $query="SELECT * FROM yubikeys WHERE yk_publicname='" . $params['yk_publicname'] . "'";
$result=$db->customQuery($query); $result=$db->customQuery($query);
if ($db->rowCount($result)) { if ($db->rowCount($result)) {
$query="UPDATE yubikeys SET " . $query="UPDATE yubikeys SET " .
"active='" . $params["active"] . "' " . "active='" . $params["active"] . "' " .
",created='" . $params["created"] . "' " . ",created='" . $params["created"] . "' " .
",modified='" . $params["modified"] . "' " . ",modified='" . $params["modified"] . "' " .
",yk_counter='" . $params["yk_counter"] . "' " . ",yk_counter='" . $params["yk_counter"] . "' " .
",yk_use='" . $params["yk_use"] . "' " . ",yk_use='" . $params["yk_use"] . "' " .
",yk_low='" . $params["yk_low"] . "' " . ",yk_low='" . $params["yk_low"] . "' " .
",yk_high='" . $params["yk_high"] . "' " . ",yk_high='" . $params["yk_high"] . "' " .
",nonce='" . $params["nonce"] . "' " . ",nonce='" . $params["nonce"] . "' " .
",notes='" . $params["notes"] . "' " . ",notes='" . $params["notes"] . "' " .
"WHERE yk_publicname='" . $params['yk_publicname'] . "' AND " . "WHERE yk_publicname='" . $params['yk_publicname'] . "' AND " .
"(".$params['yk_counter'].">yk_counter or (".$params['yk_counter']."=yk_counter and " . "(".$params['yk_counter'].">yk_counter or (".$params['yk_counter']."=yk_counter and " .
$params['yk_use'] . ">yk_use))"; $params['yk_use'] . ">yk_use))";
if(!$db->customQuery($query)) { if(!$db->customQuery($query)) {
$myLog->log(LOG_ERR, "Failed to update yk_publicname with query " . $query); $myLog->log(LOG_ERR, "Failed to update yk_publicname with query " . $query);
error_log("Failed to update yk_publicname with query " . $query); error_log("Failed to update yk_publicname with query " . $query);
exit(1); exit(1);
}
} }
} else {
else { // We didn't have the yk_publicname in database so we need to do insert instead
// We didn't have the yk_publicname in database so we need to do insert instead $query="INSERT INTO yubikeys " .
$query="INSERT INTO yubikeys " . "(active,created,modified,yk_publicname,yk_counter,yk_use,yk_low,yk_high,nonce,notes) VALUES " .
"(active,created,modified,yk_publicname,yk_counter,yk_use,yk_low,yk_high,nonce,notes) VALUES " . "('" . $params["active"] . "', " .
"('" . $params["active"] . "', " . "'" . $params['created'] . "'," .
"'" . $params['created'] . "'," . "'" . $params['modified'] . "'," .
"'" . $params['modified'] . "'," . "'" . $params['yk_publicname'] . "'," .
"'" . $params['yk_publicname'] . "'," . "'" . $params['yk_counter'] . "'," .
"'" . $params['yk_counter'] . "'," . "'" . $params['yk_use'] . "'," .
"'" . $params['yk_use'] . "'," . "'" . $params['yk_low'] . "'," .
"'" . $params['yk_low'] . "'," . "'" . $params['yk_high'] . "'," .
"'" . $params['yk_high'] . "'," . "'" . $params['nonce'] . "'," .
"'" . $params['nonce'] . "'," . "'" . $params['notes'] . "')";
"'" . $params['notes'] . "')";
if (!$db->customQuery($query)) { if (!$db->customQuery($query)) {
$myLog->log(LOG_ERR, "Failed to insert new yk_publicname with query " . $query); $myLog->log(LOG_ERR, "Failed to insert new yk_publicname with query " . $query);
error_log("Failed to insert new yk_publicname with query " . $query); error_log("Failed to insert new yk_publicname with query " . $query);
exit(1); exit(1);
}
} }
}
$db->closeCursor($result); $db->closeCursor($result);
} }
$myLog->log(LOG_NOTICE, "Successfully imported yubikeys to database"); $myLog->log(LOG_NOTICE, "Successfully imported yubikeys to database");

View File

@ -44,46 +44,46 @@ $myLog = new Log($logname);
$db = Db::GetDatabaseHandle($baseParams, $logname); $db = Db::GetDatabaseHandle($baseParams, $logname);
if (!$db->connect()) { if (!$db->connect()) {
$myLog->log(LOG_WARNING, "Could not connect to database"); $myLog->log(LOG_WARNING, "Could not connect to database");
error_log("Could not connect to database"); error_log("Could not connect to database");
exit(1); exit(1);
} }
while ($res=fgetcsv(STDIN, 0, ",")) { while ($res=fgetcsv(STDIN, 0, ",")) {
if($res[0]===null || strpos($res[0], '#')===0) continue; if($res[0]===null || strpos($res[0], '#')===0) continue;
$params=array("id"=>$res[0], $params=array("id"=>$res[0],
"active"=>$res[1], "active"=>$res[1],
"created"=>$res[2], "created"=>$res[2],
"secret"=>$res[3], "secret"=>$res[3],
"email"=>$res[4], "email"=>$res[4],
"notes"=>$res[5], "notes"=>$res[5],
"otp"=>$res[6]); "otp"=>$res[6]);
$query="SELECT * FROM clients WHERE id='" . $params['id'] . "'"; $query="SELECT * FROM clients WHERE id='" . $params['id'] . "'";
$result=$db->customQuery($query); $result=$db->customQuery($query);
if($db->rowCount($result) == 0) { if($db->rowCount($result) == 0) {
// We didn't have the id in database so we need to do insert instead // We didn't have the id in database so we need to do insert instead
$query="INSERT INTO clients " . $query="INSERT INTO clients " .
"(id,active,created,secret,email,notes,otp) VALUES " . "(id,active,created,secret,email,notes,otp) VALUES " .
"('" . $params["id"] . "', " . "('" . $params["id"] . "', " .
"'" . $params["active"] . "', " . "'" . $params["active"] . "', " .
"'" . $params['created'] . "'," . "'" . $params['created'] . "'," .
"'" . $params['secret'] . "'," . "'" . $params['secret'] . "'," .
"'" . $params['email'] . "'," . "'" . $params['email'] . "'," .
"'" . $params['notes'] . "'," . "'" . $params['notes'] . "'," .
"'" . $params['otp'] . "')"; "'" . $params['otp'] . "')";
if(!$db->customQuery($query)){ if(!$db->customQuery($query)){
$myLog->log(LOG_ERR, "Failed to insert new client with query " . $query); $myLog->log(LOG_ERR, "Failed to insert new client with query " . $query);
error_log("Failed to insert new client with query " . $query); error_log("Failed to insert new client with query " . $query);
exit(1); exit(1);
}
} }
} $db->closeCursor($result);
$db->closeCursor($result); }
}
$myLog->log(LOG_NOTICE, "Successfully imported clients to database"); $myLog->log(LOG_NOTICE, "Successfully imported clients to database");

View File

@ -31,153 +31,153 @@ require_once 'ykval-common.php';
class LogVerify class LogVerify
{ {
public $format = NULL; public $format = NULL;
private $fields = array( private $fields = array(
'time_start' => NULL, 'time_start' => NULL,
'time_end' => NULL, 'time_end' => NULL,
'time_taken' => NULL, 'time_taken' => NULL,
'ip' => NULL, 'ip' => NULL,
'client' => NULL, 'client' => NULL,
'public_id' => NULL, 'public_id' => NULL,
'otp' => NULL, 'otp' => NULL,
'status' => NULL, 'status' => NULL,
'nonce' => NULL, 'nonce' => NULL,
'signed' => NULL, 'signed' => NULL,
'counter' => NULL, 'counter' => NULL,
'low' => NULL, 'low' => NULL,
'high' => NULL, 'high' => NULL,
'use' => NULL, 'use' => NULL,
'tls' => NULL, 'tls' => NULL,
'protocol' => NULL, 'protocol' => NULL,
'sl' => NULL, 'sl' => NULL,
'timeout' => NULL, 'timeout' => NULL,
); );
/** /**
* Set field value. * Set field value.
* *
* @param $name string * @param $name string
* @param $value mixed * @param $value mixed
* @return bool * @return bool
*/ */
public function set($name, $value) public function set($name, $value)
{ {
// not settable from outside // not settable from outside
if ($name === 'time_end' || $name === 'time_taken') if ($name === 'time_end' || $name === 'time_taken')
return false; return false;
if (array_key_exists($name, $this->fields) === FALSE) if (array_key_exists($name, $this->fields) === FALSE)
return false; return false;
$this->fields[$name] = $value; $this->fields[$name] = $value;
return true; return true;
} }
/** /**
* Write verify request log line to syslog. * Write verify request log line to syslog.
* *
* @return bool * @return bool
*/ */
public function write() public function write()
{ {
if ($this->format === NULL) if ($this->format === NULL)
return false; return false;
$values = array(); $values = array();
foreach ($this->sanitized() as $key => $val) foreach ($this->sanitized() as $key => $val)
{ {
$values['%'.$key.'%'] = $val; $values['%'.$key.'%'] = $val;
} }
$message = strtr($this->format, $values); $message = strtr($this->format, $values);
if (!is_string($message)) if (!is_string($message))
return false; return false;
return syslog(LOG_INFO, $message); return syslog(LOG_INFO, $message);
} }
/** /**
* Sanitize untrusted values from clients before writing them to syslog. * Sanitize untrusted values from clients before writing them to syslog.
* *
* P.S. signed, status, time_start, tls are assumed safe, * P.S. signed, status, time_start, tls are assumed safe,
* since they are set internally. * since they are set internally.
* *
* @return array sanitized $this->fields * @return array sanitized $this->fields
*/ */
private function sanitized() private function sanitized()
{ {
$a = $this->fields; $a = $this->fields;
if (preg_match('/^[cbdefghijklnrtuv]+$/', $a['public_id']) !== 1 if (preg_match('/^[cbdefghijklnrtuv]+$/', $a['public_id']) !== 1
|| strlen($a['public_id']) < 1 || strlen($a['public_id']) < 1
|| strlen($a['public_id']) > (OTP_MAX_LEN - TOKEN_LEN)) || strlen($a['public_id']) > (OTP_MAX_LEN - TOKEN_LEN))
{ {
$a['public_id'] = '-'; $a['public_id'] = '-';
} }
if (preg_match('/^[cbdefghijklnrtuv]+$/', $a['otp']) !== 1 if (preg_match('/^[cbdefghijklnrtuv]+$/', $a['otp']) !== 1
|| strlen($a['otp']) < TOKEN_LEN || strlen($a['otp']) < TOKEN_LEN
|| strlen($a['otp']) > OTP_MAX_LEN) || strlen($a['otp']) > OTP_MAX_LEN)
{ {
$a['otp'] = '-'; $a['otp'] = '-';
} }
if (preg_match('/^[0-9]+$/', $a['client']) !== 1) if (preg_match('/^[0-9]+$/', $a['client']) !== 1)
$a['client'] = '-'; $a['client'] = '-';
if (filter_var($a['ip'], FILTER_VALIDATE_IP) === FALSE) if (filter_var($a['ip'], FILTER_VALIDATE_IP) === FALSE)
$a['ip'] = '-'; $a['ip'] = '-';
if (is_int($a['counter']) === FALSE) if (is_int($a['counter']) === FALSE)
$a['counter'] = '-'; $a['counter'] = '-';
if (is_int($a['low']) === FALSE) if (is_int($a['low']) === FALSE)
$a['low'] = '-'; $a['low'] = '-';
if (is_int($a['high']) === FALSE) if (is_int($a['high']) === FALSE)
$a['high'] = '-'; $a['high'] = '-';
if (is_int($a['use']) === FALSE) if (is_int($a['use']) === FALSE)
$a['use'] = '-'; $a['use'] = '-';
if (preg_match('/^[a-zA-Z0-9]{16,40}$/', $a['nonce']) !== 1) if (preg_match('/^[a-zA-Z0-9]{16,40}$/', $a['nonce']) !== 1)
$a['nonce'] = '-'; $a['nonce'] = '-';
if (is_float($a['protocol']) === TRUE) if (is_float($a['protocol']) === TRUE)
$a['protocol'] = sprintf('%.1f', $a['protocol']); $a['protocol'] = sprintf('%.1f', $a['protocol']);
else else
$a['protocol'] = '-'; $a['protocol'] = '-';
if ( $a['sl'] !== 'fast' if ( $a['sl'] !== 'fast'
&& $a['sl'] !== 'secure' && $a['sl'] !== 'secure'
&& (preg_match('/^[0-9]{1,3}$/', $a['sl']) !== 1 || (((int) $a['sl']) > 100))) && (preg_match('/^[0-9]{1,3}$/', $a['sl']) !== 1 || (((int) $a['sl']) > 100)))
{ {
$a['sl'] = '-'; $a['sl'] = '-';
} }
if (preg_match('/^[0-9]+$/', $a['timeout']) !== 1) if (preg_match('/^[0-9]+$/', $a['timeout']) !== 1)
$a['timeout'] = '-'; $a['timeout'] = '-';
$start = explode(' ', $a['time_start']); $start = explode(' ', $a['time_start']);
$start_msec = $start[0]; $start_msec = $start[0];
$start_sec = $start[1]; $start_sec = $start[1];
$start = bcadd($start_sec, $start_msec, 8); $start = bcadd($start_sec, $start_msec, 8);
unset($start_sec, $start_msec); unset($start_sec, $start_msec);
$end = explode(' ', microtime()); $end = explode(' ', microtime());
$end_msec = $end[0]; $end_msec = $end[0];
$end_sec = $end[1]; $end_sec = $end[1];
$end = bcadd($end_sec, $end_msec, 8); $end = bcadd($end_sec, $end_msec, 8);
unset($end_sec, $end_msec); unset($end_sec, $end_msec);
$taken = bcsub($end, $start, 8); $taken = bcsub($end, $start, 8);
$a['time_start'] = $start; $a['time_start'] = $start;
$a['time_end'] = $end; $a['time_end'] = $end;
$a['time_taken'] = $taken; $a['time_taken'] = $taken;
return $a; return $a;
} }
} }

View File

@ -29,58 +29,58 @@
class Log class Log
{ {
// request logger object // request logger object
public $request = NULL; public $request = NULL;
private $log_levels = array( private $log_levels = array(
LOG_EMERG => 'LOG_EMERG', LOG_EMERG => 'LOG_EMERG',
LOG_ALERT => 'LOG_ALERT', LOG_ALERT => 'LOG_ALERT',
LOG_CRIT => 'LOG_CRIT', LOG_CRIT => 'LOG_CRIT',
LOG_ERR => 'LOG_ERR', LOG_ERR => 'LOG_ERR',
LOG_WARNING => 'LOG_WARNING', LOG_WARNING => 'LOG_WARNING',
LOG_NOTICE => 'LOG_NOTICE', LOG_NOTICE => 'LOG_NOTICE',
LOG_INFO => 'LOG_INFO', LOG_INFO => 'LOG_INFO',
LOG_DEBUG => 'LOG_DEBUG', LOG_DEBUG => 'LOG_DEBUG',
); );
private $fields = array(); private $fields = array();
public function __construct ($name = 'ykval') public function __construct ($name = 'ykval')
{ {
$this->name = $name; $this->name = $name;
openlog('ykval', LOG_PID, LOG_LOCAL0); openlog('ykval', LOG_PID, LOG_LOCAL0);
} }
public function addField ($name, $value) public function addField ($name, $value)
{ {
$this->fields[$name] = $value; $this->fields[$name] = $value;
} }
public function log ($priority, $message, $extra = NULL) public function log ($priority, $message, $extra = NULL)
{ {
$prefix = ''; $prefix = '';
foreach ($this->fields as $val) foreach ($this->fields as $val)
$prefix .= "[$val] "; $prefix .= "[$val] ";
$suffix = ''; $suffix = '';
if (is_array($extra)) { if (is_array($extra)) {
foreach($extra as $key => $value) { foreach($extra as $key => $value) {
if (is_array($value)) { if (is_array($value)) {
$value = implode(':', $value); $value = implode(':', $value);
} }
$suffix .= " $key=$value "; $suffix .= " $key=$value ";
} }
} }
$message = $prefix . $message . $suffix; $message = $prefix . $message . $suffix;
$message = implode(':', array( $message = implode(':', array(
$this->log_levels[$priority], $this->log_levels[$priority],
$this->name, $this->name,
$message $message
)); ));
syslog($priority, $message); syslog($priority, $message);
} }
} }

View File

@ -1,6 +1,5 @@
#!/usr/bin/php #!/usr/bin/php
<?php <?php
# Copyright (c) 2010-2015 Yubico AB # Copyright (c) 2010-2015 Yubico AB
# All rights reserved. # All rights reserved.
# #
@ -29,9 +28,9 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set_include_path(implode(PATH_SEPARATOR, array( set_include_path(implode(PATH_SEPARATOR, array(
get_include_path(), get_include_path(),
'/usr/share/yubikey-val', '/usr/share/yubikey-val',
'/etc/yubico/val', '/etc/yubico/val',
))); )));
require_once 'ykval-config.php'; require_once 'ykval-config.php';
@ -43,48 +42,48 @@ $urls = otp2ksmurls('ccccccccfnkjtvvijktfrvvginedlbvudjhjnggndtck', 16);
if (($endpoints = endpoints($urls)) === FALSE) if (($endpoints = endpoints($urls)) === FALSE)
{ {
echo "Cannot parse URLs from ksm url list\n"; echo "Cannot parse URLs from ksm url list\n";
exit(1); exit(1);
} }
if ($argc == 2 && strcmp($argv[1], 'autoconf') == 0) if ($argc == 2 && strcmp($argv[1], 'autoconf') == 0)
{ {
echo "yes\n"; echo "yes\n";
exit(0); exit(0);
} }
if ($argc == 2 && strcmp($argv[1], 'config') == 0) if ($argc == 2 && strcmp($argv[1], 'config') == 0)
{ {
echo "multigraph ykval_ksmlatency\n"; echo "multigraph ykval_ksmlatency\n";
echo "graph_title KSM latency\n"; echo "graph_title KSM latency\n";
echo "graph_vlabel Average KSM Decrypt Latency (seconds)\n"; echo "graph_vlabel Average KSM Decrypt Latency (seconds)\n";
echo "graph_category ykval\n"; echo "graph_category ykval\n";
echo "graph_width 400\n"; echo "graph_width 400\n";
foreach ($endpoints as $endpoint) foreach ($endpoints as $endpoint)
{ {
list ($internal, $label, $url) = $endpoint; list ($internal, $label, $url) = $endpoint;
echo "${internal}_avgwait.label ${label}\n"; echo "${internal}_avgwait.label ${label}\n";
echo "${internal}_avgwait.type GAUGE\n"; echo "${internal}_avgwait.type GAUGE\n";
echo "${internal}_avgwait.info Average wait time for KSM decrypt\n"; echo "${internal}_avgwait.info Average wait time for KSM decrypt\n";
echo "${internal}_avgwait.min 0\n"; echo "${internal}_avgwait.min 0\n";
echo "${internal}_avgwait.draw LINE1\n"; echo "${internal}_avgwait.draw LINE1\n";
} }
exit(0); exit(0);
} }
echo "multigraph ykval_ksmlatency\n"; echo "multigraph ykval_ksmlatency\n";
foreach ($endpoints as $endpoint) foreach ($endpoints as $endpoint)
{ {
list ($internal, $label, $url) = $endpoint; list ($internal, $label, $url) = $endpoint;
if (($total_time = total_time($url)) === FALSE) if (($total_time = total_time($url)) === FALSE)
$total_time = 'error'; $total_time = 'error';
echo "${internal}_avgwait.value ${total_time}\n"; echo "${internal}_avgwait.value ${total_time}\n";
} }
exit(0); exit(0);

View File

@ -29,52 +29,51 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set_include_path(implode(PATH_SEPARATOR, array( set_include_path(implode(PATH_SEPARATOR, array(
get_include_path(), get_include_path(),
'/usr/share/yubikey-val', '/usr/share/yubikey-val',
'/etc/yubico/val', '/etc/yubico/val',
))); )));
require_once 'ykval-config.php'; require_once 'ykval-config.php';
require_once 'ykval-common.php'; require_once 'ykval-common.php';
require_once 'ykval-synclib.php'; require_once 'ykval-synclib.php';
$urls = $baseParams['__YKVAL_SYNC_POOL__']; $urls = $baseParams['__YKVAL_SYNC_POOL__'];
if ($argc == 2 && strcmp($argv[1], 'autoconf') == 0) if ($argc == 2 && strcmp($argv[1], 'autoconf') == 0)
{ {
if (is_array($urls) && count($urls) > 0) if (is_array($urls) && count($urls) > 0)
{ {
echo "yes\n"; echo "yes\n";
exit(0); exit(0);
} }
echo "no (sync pool not configured)\n"; echo "no (sync pool not configured)\n";
exit(0); exit(0);
} }
if (($endpoints = endpoints($urls)) === FALSE) if (($endpoints = endpoints($urls)) === FALSE)
{ {
echo "Cannot parse URLs from sync pool list\n"; echo "Cannot parse URLs from sync pool list\n";
exit(1); exit(1);
} }
if ($argc == 2 && strcmp($argv[1], 'config') == 0) if ($argc == 2 && strcmp($argv[1], 'config') == 0)
{ {
echo "graph_title YK-VAL queue size\n"; echo "graph_title YK-VAL queue size\n";
echo "graph_vlabel sync requests in queue\n"; echo "graph_vlabel sync requests in queue\n";
echo "graph_category ykval\n"; echo "graph_category ykval\n";
foreach ($endpoints as $endpoint) foreach ($endpoints as $endpoint)
{ {
list ($internal, $label, $url) = $endpoint; list ($internal, $label, $url) = $endpoint;
echo "${internal}_queuelength.label sync ${label}\n"; echo "${internal}_queuelength.label sync ${label}\n";
echo "${internal}_queuelength.draw AREASTACK\n"; echo "${internal}_queuelength.draw AREASTACK\n";
echo "${internal}_queuelength.type GAUGE\n"; echo "${internal}_queuelength.type GAUGE\n";
} }
exit(0); exit(0);
} }
$sync = new SyncLib('ykval-synclib:munin'); $sync = new SyncLib('ykval-synclib:munin');
@ -82,14 +81,14 @@ $queuelength = $sync->getQueueLengthByServer();
foreach ($endpoints as $endpoint) foreach ($endpoints as $endpoint)
{ {
list ($internal, $label, $url) = $endpoint; list ($internal, $label, $url) = $endpoint;
$count = 0; $count = 0;
if (array_key_exists($url, $queuelength)) if (array_key_exists($url, $queuelength))
$count = $queuelength[$url]; $count = $queuelength[$url];
echo "${internal}_queuelength.value $count\n"; echo "${internal}_queuelength.value $count\n";
} }
exit(0); exit(0);

View File

@ -29,67 +29,66 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set_include_path(implode(PATH_SEPARATOR, array( set_include_path(implode(PATH_SEPARATOR, array(
get_include_path(), get_include_path(),
'/usr/share/yubikey-val', '/usr/share/yubikey-val',
'/etc/yubico/val', '/etc/yubico/val',
))); )));
require_once 'ykval-config.php'; require_once 'ykval-config.php';
require_once 'ykval-common.php'; require_once 'ykval-common.php';
$urls = $baseParams['__YKVAL_SYNC_POOL__']; $urls = $baseParams['__YKVAL_SYNC_POOL__'];
if ($argc == 2 && strcmp($argv[1], 'autoconf') == 0) if ($argc == 2 && strcmp($argv[1], 'autoconf') == 0)
{ {
if (is_array($urls) && count($urls) > 0) if (is_array($urls) && count($urls) > 0)
{ {
echo "yes\n"; echo "yes\n";
exit(0); exit(0);
} }
echo "no (sync pool not configured)\n"; echo "no (sync pool not configured)\n";
exit(0); exit(0);
} }
if (($endpoints = endpoints($urls)) === FALSE) if (($endpoints = endpoints($urls)) === FALSE)
{ {
echo "Cannot parse URLs from sync pool list\n"; echo "Cannot parse URLs from sync pool list\n";
exit(1); exit(1);
} }
if ($argc == 2 && strcmp($argv[1], 'config') == 0) if ($argc == 2 && strcmp($argv[1], 'config') == 0)
{ {
echo "multigraph ykval_vallatency\n"; echo "multigraph ykval_vallatency\n";
echo "graph_title VAL latency\n"; echo "graph_title VAL latency\n";
echo "graph_vlabel Average VAL Latency (seconds)\n"; echo "graph_vlabel Average VAL Latency (seconds)\n";
echo "graph_category ykval\n"; echo "graph_category ykval\n";
echo "graph_width 400\n"; echo "graph_width 400\n";
foreach ($endpoints as $endpoint) foreach ($endpoints as $endpoint)
{ {
list($internal, $label, $url) = $endpoint; list($internal, $label, $url) = $endpoint;
echo "${internal}_avgwait.label ${label}\n"; echo "${internal}_avgwait.label ${label}\n";
echo "${internal}_avgwait.type GAUGE\n"; echo "${internal}_avgwait.type GAUGE\n";
echo "${internal}_avgwait.info Average VAL round-trip latency\n"; echo "${internal}_avgwait.info Average VAL round-trip latency\n";
echo "${internal}_avgwait.min 0\n"; echo "${internal}_avgwait.min 0\n";
echo "${internal}_avgwait.draw LINE1\n"; echo "${internal}_avgwait.draw LINE1\n";
} }
exit(0); exit(0);
} }
echo "multigraph ykval_vallatency\n"; echo "multigraph ykval_vallatency\n";
foreach ($endpoints as $endpoint) foreach ($endpoints as $endpoint)
{ {
list ($internal, $label, $url) = $endpoint; list ($internal, $label, $url) = $endpoint;
if (($total_time = total_time($url)) === FALSE) if (($total_time = total_time($url)) === FALSE)
$total_time = 'error'; $total_time = 'error';
echo "${internal}_avgwait.value ${total_time}\n"; echo "${internal}_avgwait.value ${total_time}\n";
} }
exit(0); exit(0);

View File

@ -29,9 +29,9 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set_include_path(implode(PATH_SEPARATOR, array( set_include_path(implode(PATH_SEPARATOR, array(
get_include_path(), get_include_path(),
'/usr/share/yubikey-val', '/usr/share/yubikey-val',
'/etc/yubico/val', '/etc/yubico/val',
))); )));
require_once 'ykval-config.php'; require_once 'ykval-config.php';
@ -39,62 +39,62 @@ require_once 'ykval-db.php';
if ($argc == 2 && strcmp($argv[1], "autoconf") == 0) if ($argc == 2 && strcmp($argv[1], "autoconf") == 0)
{ {
print "yes\n"; print "yes\n";
exit(0); exit(0);
} }
if ($argc == 2 && strcmp($argv[1], "config") == 0) if ($argc == 2 && strcmp($argv[1], "config") == 0)
{ {
echo "graph_title YK-VAL YubiKey stats\n"; echo "graph_title YK-VAL YubiKey stats\n";
echo "graph_vlabel Known YubiKeys\n"; echo "graph_vlabel Known YubiKeys\n";
echo "graph_category ykval\n"; echo "graph_category ykval\n";
echo "yubikeys_enabled.label Enabled YubiKeys\n"; echo "yubikeys_enabled.label Enabled YubiKeys\n";
echo "yubikeys_enabled.draw AREA\n"; echo "yubikeys_enabled.draw AREA\n";
echo "yubikeys_disabled.label Disabled YubiKeys\n"; echo "yubikeys_disabled.label Disabled YubiKeys\n";
echo "yubikeys_disabled.draw STACK\n"; echo "yubikeys_disabled.draw STACK\n";
echo "yubikeys_1month.label YubiKeys seen last month\n"; echo "yubikeys_1month.label YubiKeys seen last month\n";
echo "yubikeys_1month.draw LINE2\n"; echo "yubikeys_1month.draw LINE2\n";
echo "clients_enabled.label Enabled validation clients\n"; echo "clients_enabled.label Enabled validation clients\n";
echo "clients_enabled.draw LINE2\n"; echo "clients_enabled.draw LINE2\n";
echo "clients_disabled.label Disabled validation clients\n"; echo "clients_disabled.label Disabled validation clients\n";
echo "clients_disabled.draw LINE2\n"; echo "clients_disabled.draw LINE2\n";
exit(0); exit(0);
} }
$db = Db::GetDatabaseHandle($baseParams, 'ykval-munin-yubikeystats'); $db = Db::GetDatabaseHandle($baseParams, 'ykval-munin-yubikeystats');
if (!$db->connect()) if (!$db->connect())
logdie($myLog, 'ERROR Database connect error (1)'); logdie($myLog, 'ERROR Database connect error (1)');
function get_count($db, $table, $conditions) function get_count($db, $table, $conditions)
{ {
$res = $db->customQuery("SELECT count(1) as count FROM $table WHERE $conditions"); $res = $db->customQuery("SELECT count(1) as count FROM $table WHERE $conditions");
if ($res) if ($res)
{ {
$r = $res->fetch(PDO::FETCH_ASSOC); $r = $res->fetch(PDO::FETCH_ASSOC);
return $r['count']; return $r['count'];
} }
return Null; return Null;
} }
if ($count = get_count($db, 'yubikeys', 'active=true')) if ($count = get_count($db, 'yubikeys', 'active=true'))
echo "yubikeys_enabled.value $count\n"; echo "yubikeys_enabled.value $count\n";
if ($count = get_count($db, 'yubikeys', 'active=false')) if ($count = get_count($db, 'yubikeys', 'active=false'))
echo "yubikeys_disabled.value $count\n"; echo "yubikeys_disabled.value $count\n";
if ($count = get_count($db, 'yubikeys', 'modified >= ' . (time() - (31 * 86400)))) if ($count = get_count($db, 'yubikeys', 'modified >= ' . (time() - (31 * 86400))))
echo "yubikeys_1month.value $count\n"; echo "yubikeys_1month.value $count\n";
if ($count = get_count($db, 'clients', 'active=true')) if ($count = get_count($db, 'clients', 'active=true'))
echo "clients_enabled.value $count\n"; echo "clients_enabled.value $count\n";
if ($count = get_count($db, 'clients', 'active=false')) if ($count = get_count($db, 'clients', 'active=false'))
echo "clients_disabled.value $count\n"; echo "clients_disabled.value $count\n";

View File

@ -29,9 +29,9 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set_include_path(implode(PATH_SEPARATOR, array( set_include_path(implode(PATH_SEPARATOR, array(
get_include_path(), get_include_path(),
'/usr/share/yubikey-val', '/usr/share/yubikey-val',
'/etc/yubico/val', '/etc/yubico/val',
))); )));
require_once 'ykval-synclib.php'; require_once 'ykval-synclib.php';
@ -39,8 +39,8 @@ require_once 'ykval-config.php';
require_once 'ykval-log.php'; require_once 'ykval-log.php';
if ($argc != 3) { if ($argc != 3) {
print "warning and critical levels have to be given on commandline\n"; print "warning and critical levels have to be given on commandline\n";
exit (3); exit (3);
} }
$warning = $argv[1]; $warning = $argv[1];
@ -53,12 +53,12 @@ $len = $sync->getQueueLength ();
$message = "Queue length is $len"; $message = "Queue length is $len";
if($len > $critical) { if($len > $critical) {
print("CRITICAL: $message\n"); print("CRITICAL: $message\n");
exit (2); exit (2);
} elseif($len > $warning) { } elseif($len > $warning) {
print("WARNING: $message\n"); print("WARNING: $message\n");
exit (1); exit (1);
} else { } else {
print("OK: $message\n"); print("OK: $message\n");
exit (0); exit (0);
} }

View File

@ -56,7 +56,7 @@ if ($sl->getNumberOfServers() === 0 && $sl->getQueueLength() === 0)
do { do {
$start = time(); $start = time();
$sl->reSync($baseParams['__YKVAL_SYNC_OLD_LIMIT__'], $sl->reSync($baseParams['__YKVAL_SYNC_OLD_LIMIT__'],
$baseParams['__YKVAL_SYNC_RESYNC_TIMEOUT__']); $baseParams['__YKVAL_SYNC_RESYNC_TIMEOUT__']);
$duration = time() - $start; $duration = time() - $start;
$sleep_time = max($baseParams['__YKVAL_SYNC_INTERVAL__'] - $duration, 0); $sleep_time = max($baseParams['__YKVAL_SYNC_INTERVAL__'] - $duration, 0);
} while(sleep($sleep_time)==0); } while(sleep($sleep_time)==0);

View File

@ -39,39 +39,39 @@ $myLog = new Log('ykval-resync');
$myLog->addField('ip', $_SERVER['REMOTE_ADDR']); $myLog->addField('ip', $_SERVER['REMOTE_ADDR']);
if (!in_array ($_SERVER["REMOTE_ADDR"], $baseParams['__YKRESYNC_IPS__'])) { if (!in_array ($_SERVER["REMOTE_ADDR"], $baseParams['__YKRESYNC_IPS__'])) {
logdie($myLog, "ERROR Authorization failed (logged ". $_SERVER["REMOTE_ADDR"] .")"); logdie($myLog, "ERROR Authorization failed (logged ". $_SERVER["REMOTE_ADDR"] .")");
} }
# Parse input # Parse input
$yk = $_REQUEST["yk"]; $yk = $_REQUEST["yk"];
if (!$yk) { if (!$yk) {
logdie($myLog, "ERROR Missing parameter"); logdie($myLog, "ERROR Missing parameter");
} }
if (!($yk == "all" || preg_match("/^([cbdefghijklnrtuv]{0,16})$/", $yk))) { if (!($yk == "all" || is_pubid($yk))) {
logdie($myLog, "ERROR Unknown yk value: $yk"); logdie($myLog, "ERROR Unknown yk value: $yk");
} }
$myLog->addField('yk', $yk); $myLog->addField('yk', $yk);
# Connect to db # Connect to db
$db = Db::GetDatabaseHandle($baseParams, 'ykval-resync'); $db = Db::GetDatabaseHandle($baseParams, 'ykval-resync');
if (!$db->connect()) { if (!$db->connect()) {
logdie($myLog, 'ERROR Database connect error (1)'); logdie($myLog, 'ERROR Database connect error (1)');
} }
if($yk == "all") { if($yk == "all") {
# Get all keys # Get all keys
$res = $db->customQuery("SELECT yk_publicname FROM yubikeys WHERE active = true"); $res = $db->customQuery("SELECT yk_publicname FROM yubikeys WHERE active = true");
while($r = $db->fetchArray($res)) { while($r = $db->fetchArray($res)) {
$yubikeys[] = $r['yk_publicname']; $yubikeys[] = $r['yk_publicname'];
} }
$db->closeCursor($res); $db->closeCursor($res);
} else { } else {
# Check if key exists # Check if key exists
$r = $db->findBy('yubikeys', 'yk_publicname', $yk, 1); $r = $db->findBy('yubikeys', 'yk_publicname', $yk, 1);
if (!$r) { if (!$r) {
logdie($myLog, "ERROR Unknown yubikey: $yk"); logdie($myLog, "ERROR Unknown yubikey: $yk");
} }
$yubikeys = array($yk); $yubikeys = array($yk);
} }
/* Initialize the sync library. */ /* Initialize the sync library. */
@ -80,21 +80,21 @@ $sync->addField('ip', $_SERVER['REMOTE_ADDR']);
$sync->addField('yk', $yk); $sync->addField('yk', $yk);
if (! $sync->isConnected()) { if (! $sync->isConnected()) {
logdie($myLog, 'ERROR Database connect error (2)'); logdie($myLog, 'ERROR Database connect error (2)');
} }
foreach($yubikeys as $key) { foreach($yubikeys as $key) {
if (($localParams = $sync->getLocalParams($key)) === FALSE) { if (($localParams = $sync->getLocalParams($key)) === FALSE) {
logdie($myLog, 'ERROR Invalid Yubikey ' . $key); logdie($myLog, 'ERROR Invalid Yubikey ' . $key);
} }
$localParams['otp'] = $key . str_repeat('c', 32); // Fake an OTP, only used for logging. $localParams['otp'] = $key . str_repeat('c', 32); // Fake an OTP, only used for logging.
$myLog->log(LOG_DEBUG, "Auth data:", $localParams); $myLog->log(LOG_DEBUG, "Auth data:", $localParams);
/* Queue sync request */ /* Queue sync request */
if (!$sync->queue($localParams, $localParams)) { if (!$sync->queue($localParams, $localParams)) {
logdie($myLog, 'ERROR Failed resync'); logdie($myLog, 'ERROR Failed resync');
} }
} }
# We are done # We are done

View File

@ -38,38 +38,38 @@ $myLog = new Log('ykval-revoke');
$myLog->addField('ip', $_SERVER['REMOTE_ADDR']); $myLog->addField('ip', $_SERVER['REMOTE_ADDR']);
if (!in_array ($_SERVER["REMOTE_ADDR"], $baseParams['__YKREV_IPS__'])) { if (!in_array ($_SERVER["REMOTE_ADDR"], $baseParams['__YKREV_IPS__'])) {
logdie($myLog, "ERROR Authorization failed (logged ". $_SERVER["REMOTE_ADDR"] .")"); logdie($myLog, "ERROR Authorization failed (logged ". $_SERVER["REMOTE_ADDR"] .")");
} }
# Parse input # Parse input
$yk = $_REQUEST["yk"]; $yk = $_REQUEST["yk"];
$do = $_REQUEST["do"]; $do = $_REQUEST["do"];
if (!$yk || !$do) { if (!$yk || !$do) {
logdie($myLog, "ERROR Missing parameter"); logdie($myLog, "ERROR Missing parameter");
} }
if (!preg_match("/^([cbdefghijklnrtuv]{0,16})$/", $yk)) { if (!is_pubid($yk)) {
logdie($myLog, "ERROR Unknown yk value: $yk"); logdie($myLog, "ERROR Unknown yk value: $yk");
} }
if ($do != "enable" && $do != "disable") { if ($do != "enable" && $do != "disable") {
logdie($myLog, "ERROR Unknown do value: $do"); logdie($myLog, "ERROR Unknown do value: $do");
} }
# Connect to db # Connect to db
$db = Db::GetDatabaseHandle($baseParams, 'ykval-revoke'); $db = Db::GetDatabaseHandle($baseParams, 'ykval-revoke');
if (!$db->connect()) { if (!$db->connect()) {
logdie($myLog, "ERROR Database connect error"); logdie($myLog, "ERROR Database connect error");
} }
# Check if key exists # Check if key exists
$r = $db->findBy('yubikeys', 'yk_publicname', $yk, 1); $r = $db->findBy('yubikeys', 'yk_publicname', $yk, 1);
if (!$r) { if (!$r) {
logdie($myLog, "ERROR Unknown yubikey: $yk"); logdie($myLog, "ERROR Unknown yubikey: $yk");
} }
# Enable/Disable the yubikey # Enable/Disable the yubikey
if (!$db->updateBy('yubikeys', 'yk_publicname', $yk, if (!$db->updateBy('yubikeys', 'yk_publicname', $yk,
array('active'=>($do == "enable" ? "1" : "0")))) { array('active'=>($do == "enable" ? "1" : "0")))) {
logdie($myLog, "ERROR Could not $do for $yk"); logdie($myLog, "ERROR Could not $do for $yk");
} }
# We are done # We are done

View File

@ -43,43 +43,43 @@ $myLog->log(LOG_DEBUG, "Received request from $ipaddr");
if (empty($_SERVER['QUERY_STRING'])) if (empty($_SERVER['QUERY_STRING']))
{ {
sendResp(S_MISSING_PARAMETER, $myLog); sendResp(S_MISSING_PARAMETER, $myLog);
} }
// verify request sent by whitelisted address // verify request sent by whitelisted address
if (in_array($ipaddr, $allowed, TRUE) === FALSE) if (in_array($ipaddr, $allowed, TRUE) === FALSE)
{ {
$myLog->log(LOG_NOTICE, "Operation not allowed from IP $ipaddr"); $myLog->log(LOG_NOTICE, "Operation not allowed from IP $ipaddr");
$myLog->log(LOG_DEBUG, "Remote IP $ipaddr not listed in allowed sync pool : " . implode(', ', $allowed)); $myLog->log(LOG_DEBUG, "Remote IP $ipaddr not listed in allowed sync pool : " . implode(', ', $allowed));
sendResp(S_OPERATION_NOT_ALLOWED, $myLog); sendResp(S_OPERATION_NOT_ALLOWED, $myLog);
} }
// define requirements on protocol // define requirements on protocol
$syncParams = array( $syncParams = array(
'modified' => NULL, 'modified' => NULL,
'otp' => NULL, 'otp' => NULL,
'nonce' => NULL, 'nonce' => NULL,
'yk_publicname' => NULL, 'yk_publicname' => NULL,
'yk_counter' => NULL, 'yk_counter' => NULL,
'yk_use' => NULL, 'yk_use' => NULL,
'yk_high' => NULL, 'yk_high' => NULL,
'yk_low' => NULL 'yk_low' => NULL
); );
// extract values from HTTP request // extract values from HTTP request
$tmp_log = 'Received '; $tmp_log = 'Received ';
foreach ($syncParams as $param => $value) foreach ($syncParams as $param => $value)
{ {
$value = getHttpVal($param, NULL, $_GET); $value = getHttpVal($param, NULL, $_GET);
if ($value == NULL) if ($value == NULL)
{ {
$myLog->log(LOG_NOTICE, "Received request with parameter[s] ($param) missing value"); $myLog->log(LOG_NOTICE, "Received request with parameter[s] ($param) missing value");
sendResp(S_MISSING_PARAMETER, $myLog); sendResp(S_MISSING_PARAMETER, $myLog);
} }
$syncParams[$param] = $value; $syncParams[$param] = $value;
$tmp_log .= "$param=$value "; $tmp_log .= "$param=$value ";
} }
$myLog->log(LOG_INFO, $tmp_log); $myLog->log(LOG_INFO, $tmp_log);
@ -89,35 +89,52 @@ $sync->addField('ip', $ipaddr);
if (! $sync->isConnected()) if (! $sync->isConnected())
{ {
sendResp(S_BACKEND_ERROR, $myLog); sendResp(S_BACKEND_ERROR, $myLog);
} }
// at this point we should have the otp so let's add it to the logging module // at this point we should have the otp so let's add it to the logging module
$myLog->addField('otp', $syncParams['otp']); $myLog->addField('otp', $syncParams['otp']);
$sync->addField('otp', $syncParams['otp']);
// Verify correctness of numeric input parameters
// verify correctness of input parameters
foreach (array('modified','yk_counter', 'yk_use', 'yk_high', 'yk_low') as $param) foreach (array('modified','yk_counter', 'yk_use', 'yk_high', 'yk_low') as $param)
{ {
// -1 is valid except for modified // -1 is valid except for modified
if ($param !== 'modified' && $syncParams[$param] === '-1') if ($param !== 'modified' && $syncParams[$param] === '-1')
continue; continue;
// [0-9]+ // [0-9]+
if ($syncParams[$param] !== '' && ctype_digit($syncParams[$param])) if ($syncParams[$param] !== '' && ctype_digit($syncParams[$param]))
continue; continue;
$myLog->log(LOG_NOTICE, "Input parameters $param not correct"); $myLog->log(LOG_NOTICE, "Input parameters $param not correct");
sendResp(S_MISSING_PARAMETER, $myLog); sendResp(S_MISSING_PARAMETER, $myLog);
}
// Verify correctness of OTP input
if (!is_otp($syncParams['otp'])) {
$myLog->log(LOG_NOTICE, "Input parameter " . $syncParams['otp'] . " not correct");
sendResp(S_MISSING_PARAMETER, $myLog);
}
// Verify correctness of pubid input
if (!is_pubid($syncParams['yk_publicname'])) {
$myLog->log(LOG_NOTICE, "Input parameter " . $syncParams['yk_publicname'] . " not correct");
sendResp(S_MISSING_PARAMETER, $myLog);
}
// Verify correctness of nonce input
if (!is_nonce($syncParams['nonce'])) {
$myLog->log(LOG_NOTICE, "Input parameter " . $syncParams['nonce'] . " not correct");
sendResp(S_MISSING_PARAMETER, $myLog);
} }
// get local counter data // get local counter data
$sync->addField('otp', $syncParams['otp']);
$yk_publicname = $syncParams['yk_publicname']; $yk_publicname = $syncParams['yk_publicname'];
if (($localParams = $sync->getLocalParams($yk_publicname)) === FALSE) if (($localParams = $sync->getLocalParams($yk_publicname)) === FALSE)
{ {
$myLog->log(LOG_NOTICE, "Invalid Yubikey $yk_publicname"); $myLog->log(LOG_NOTICE, "Invalid Yubikey $yk_publicname");
sendResp(S_BACKEND_ERROR, $myLog); sendResp(S_BACKEND_ERROR, $myLog);
} }
// conditional update local database // conditional update local database
@ -128,60 +145,60 @@ $myLog->log(LOG_DEBUG, 'Sync request params ', $syncParams);
if ($sync->countersHigherThan($localParams, $syncParams)) if ($sync->countersHigherThan($localParams, $syncParams))
{ {
$myLog->log(LOG_WARNING, 'Remote server out of sync.'); $myLog->log(LOG_WARNING, 'Remote server out of sync.');
} }
if ($sync->countersEqual($localParams, $syncParams)) if ($sync->countersEqual($localParams, $syncParams))
{ {
if ($syncParams['modified'] == $localParams['modified'] if ($syncParams['modified'] == $localParams['modified']
&& $syncParams['nonce'] == $localParams['nonce']) && $syncParams['nonce'] == $localParams['nonce'])
{ {
/** /**
* This is not an error. When the remote server received an OTP to verify, it would * This is not an error. When the remote server received an OTP to verify, it would
* have sent out sync requests immediately. When the required number of responses had * have sent out sync requests immediately. When the required number of responses had
* been received, the current implementation discards all additional responses (to * been received, the current implementation discards all additional responses (to
* return the result to the client as soon as possible). If our response sent last * return the result to the client as soon as possible). If our response sent last
* time was discarded, we will end up here when the background ykval-queue processes * time was discarded, we will end up here when the background ykval-queue processes
* the sync request again. * the sync request again.
*/ */
$myLog->log(LOG_INFO, 'Sync request unnecessarily sent'); $myLog->log(LOG_INFO, 'Sync request unnecessarily sent');
} }
if ($syncParams['modified'] != $localParams['modified'] if ($syncParams['modified'] != $localParams['modified']
&& $syncParams['nonce'] == $localParams['nonce']) && $syncParams['nonce'] == $localParams['nonce'])
{ {
$deltaModified = $syncParams['modified'] - $localParams['modified']; $deltaModified = $syncParams['modified'] - $localParams['modified'];
if ($deltaModified < -1 || $deltaModified > 1) if ($deltaModified < -1 || $deltaModified > 1)
{ {
$myLog->log(LOG_WARNING, "We might have a replay. 2 events at different times have generated the same counters. The time difference is $deltaModified seconds"); $myLog->log(LOG_WARNING, "We might have a replay. 2 events at different times have generated the same counters. The time difference is $deltaModified seconds");
} }
} }
if ($syncParams['nonce'] != $localParams['nonce']) if ($syncParams['nonce'] != $localParams['nonce'])
{ {
$myLog->log(LOG_WARNING, 'Remote server has received a request to validate an already validated OTP'); $myLog->log(LOG_WARNING, 'Remote server has received a request to validate an already validated OTP');
} }
} }
if ($localParams['active'] != 1) if ($localParams['active'] != 1)
{ {
/** /**
* The remote server has accepted an OTP from a YubiKey which we would not. * The remote server has accepted an OTP from a YubiKey which we would not.
* We still needed to update our counters with the counters from the OTP though. * We still needed to update our counters with the counters from the OTP though.
*/ */
$myLog->log(LOG_WARNING, "Received sync-request for de-activated Yubikey $yk_publicname - check database synchronization!!!"); $myLog->log(LOG_WARNING, "Received sync-request for de-activated Yubikey $yk_publicname - check database synchronization!!!");
sendResp(S_BAD_OTP, $myLog); sendResp(S_BAD_OTP, $myLog);
} }
$extra = array( $extra = array(
'modified' => $localParams['modified'], 'modified' => $localParams['modified'],
'nonce' => $localParams['nonce'], 'nonce' => $localParams['nonce'],
'yk_publicname' => $yk_publicname, 'yk_publicname' => $yk_publicname,
'yk_counter' => $localParams['yk_counter'], 'yk_counter' => $localParams['yk_counter'],
'yk_use' => $localParams['yk_use'], 'yk_use' => $localParams['yk_use'],
'yk_high' => $localParams['yk_high'], 'yk_high' => $localParams['yk_high'],
'yk_low' => $localParams['yk_low'] 'yk_low' => $localParams['yk_low']
); );
sendResp(S_OK, $myLog, '', $extra); sendResp(S_OK, $myLog, '', $extra);

View File

@ -29,23 +29,23 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if($argc != 3 || !( $argv[2] == 'all' || preg_match('/^[cbdefghijklnrtuv]{0,16}$/', $argv[2]))) { if($argc != 3 || !( $argv[2] == 'all' || preg_match('/^[cbdefghijklnrtuv]{0,16}$/', $argv[2]))) {
echo "Usage: ".$argv[0]." URL KEY\n\n"; echo "Usage: ".$argv[0]." URL KEY\n\n";
echo "URL: URL of resync service.\n\n"; echo "URL: URL of resync service.\n\n";
echo "KEY: Public identifier of a YubiKey to sync, or \"all\".\n\n"; echo "KEY: Public identifier of a YubiKey to sync, or \"all\".\n\n";
echo "Usage example:\n\n"; echo "Usage example:\n\n";
echo $argv[0]." http://example.com/wsapi/2.0/resync ccccccccccdn\n"; echo $argv[0]." http://example.com/wsapi/2.0/resync ccccccccccdn\n";
exit(1); exit(1);
} }
$host = $argv[1]; $host = $argv[1];
$yk = $argv[2]; $yk = $argv[2];
if(!preg_match('~^https?://~', $host)) { if(!preg_match('~^https?://~', $host)) {
#Allow just an IP or host #Allow just an IP or host
if(strpos($host, "/") == False) { if(strpos($host, "/") == False) {
$host = $host."/wsapi/2.0/resync"; $host = $host."/wsapi/2.0/resync";
} }
$host = "http://".$host; $host = "http://".$host;
} }
$url = $host."?yk=".$yk; $url = $host."?yk=".$yk;
@ -59,7 +59,7 @@ curl_setopt($ch, CURLOPT_FAILONERROR, true);
$response = curl_exec($ch); $response = curl_exec($ch);
if($response == False) { if($response == False) {
die("error"); die("error");
} }
echo $response; echo $response;

View File

@ -33,651 +33,651 @@ require_once 'ykval-db.php';
require_once 'ykval-log.php'; require_once 'ykval-log.php';
function _get($arr, $key) { function _get($arr, $key) {
return array_key_exists($key, $arr) ? $arr[$key] : "?"; return array_key_exists($key, $arr) ? $arr[$key] : "?";
} }
class SyncLib class SyncLib
{ {
public $syncServers = array(); public $syncServers = array();
public $dbConn = null; public $dbConn = null;
public $curlopts = array(); public $curlopts = array();
public function __construct($logname='ykval-synclib') public function __construct($logname='ykval-synclib')
{ {
$this->myLog = new Log($logname); $this->myLog = new Log($logname);
global $baseParams; global $baseParams;
$this->syncServers = $baseParams['__YKVAL_SYNC_POOL__']; $this->syncServers = $baseParams['__YKVAL_SYNC_POOL__'];
$this->db = Db::GetDatabaseHandle($baseParams, $logname); $this->db = Db::GetDatabaseHandle($baseParams, $logname);
$this->isConnected=$this->db->connect(); $this->isConnected=$this->db->connect();
$this->server_nonce=md5(uniqid(rand())); $this->server_nonce=md5(uniqid(rand()));
if (array_key_exists('__YKVAL_SYNC_CURL_OPTS__', $baseParams)) if (array_key_exists('__YKVAL_SYNC_CURL_OPTS__', $baseParams))
{ {
$this->curlopts = $baseParams['__YKVAL_SYNC_CURL_OPTS__']; $this->curlopts = $baseParams['__YKVAL_SYNC_CURL_OPTS__'];
} }
} }
public function addField($name, $value) public function addField($name, $value)
{ {
$this->myLog->addField($name, $value); $this->myLog->addField($name, $value);
$this->db->addField($name, $value); $this->db->addField($name, $value);
} }
public function isConnected() public function isConnected()
{ {
return $this->isConnected; return $this->isConnected;
} }
public function getNumberOfServers() public function getNumberOfServers()
{ {
return count($this->syncServers); return count($this->syncServers);
} }
public function getNumberOfValidAnswers() public function getNumberOfValidAnswers()
{ {
if (isset($this->valid_answers)) if (isset($this->valid_answers))
return $this->valid_answers; return $this->valid_answers;
return 0; return 0;
} }
public function getNumberOfAnswers() public function getNumberOfAnswers()
{ {
if (isset($this->answers)) if (isset($this->answers))
return $this->answers; return $this->answers;
return 0; return 0;
} }
public function getClientData($client) public function getClientData($client)
{ {
$res = $this->db->customQuery("SELECT id, secret FROM clients WHERE active='1' AND id='" . $client . "'"); $res = $this->db->customQuery("SELECT id, secret FROM clients WHERE active='1' AND id='" . $client . "'");
$r = $this->db->fetchArray($res); $r = $this->db->fetchArray($res);
$this->db->closeCursor($res); $this->db->closeCursor($res);
if ($r) if ($r)
return $r; return $r;
return false; return false;
} }
public function getQueueLength() public function getQueueLength()
{ {
return count($this->db->findBy('queue', null, null, null)); return count($this->db->findBy('queue', null, null, null));
} }
public function getQueueLengthByServer() public function getQueueLengthByServer()
{ {
$counters = array(); $counters = array();
foreach ($this->syncServers as $server) foreach ($this->syncServers as $server)
{ {
$counters[$server] = 0; $counters[$server] = 0;
} }
$result = $this->db->customQuery('SELECT server, COUNT(server) as count FROM queue GROUP BY server'); $result = $this->db->customQuery('SELECT server, COUNT(server) as count FROM queue GROUP BY server');
while ($row = $this->db->fetchArray($result)) while ($row = $this->db->fetchArray($result))
{ {
$counters[$row['server']] = $row['count']; $counters[$row['server']] = $row['count'];
} }
$this->db->closeCursor($result); $this->db->closeCursor($result);
return $counters; return $counters;
} }
public function queue($otpParams, $localParams) public function queue($otpParams, $localParams)
{ {
$info = $this->createInfoString($otpParams, $localParams); $info = $this->createInfoString($otpParams, $localParams);
$this->otpParams = $otpParams; $this->otpParams = $otpParams;
$this->localParams = $localParams; $this->localParams = $localParams;
$queued = time(); $queued = time();
$result = true; $result = true;
foreach ($this->syncServers as $server) foreach ($this->syncServers as $server)
{ {
$arr = array( $arr = array(
'queued' => $queued, 'queued' => $queued,
'modified' => $otpParams['modified'], 'modified' => $otpParams['modified'],
'otp' => $otpParams['otp'], 'otp' => $otpParams['otp'],
'server' => $server, 'server' => $server,
'server_nonce' => $this->server_nonce, 'server_nonce' => $this->server_nonce,
'info' => $info 'info' => $info
); );
if (! $this->db->save('queue', $arr)) if (! $this->db->save('queue', $arr))
$result = false; $result = false;
} }
return $result; return $result;
} }
public function log($priority, $msg, $params=NULL) public function log($priority, $msg, $params=NULL)
{ {
if ($params) if ($params)
$msg .= ' modified=' . _get($params, 'modified') . $msg .= ' modified=' . _get($params, 'modified') .
' nonce=' . _get($params, 'nonce') . ' nonce=' . _get($params, 'nonce') .
' yk_publicname=' . _get($params, 'yk_publicname') . ' yk_publicname=' . _get($params, 'yk_publicname') .
' yk_counter=' . _get($params, 'yk_counter') . ' yk_counter=' . _get($params, 'yk_counter') .
' yk_use=' . _get($params, 'yk_use') . ' yk_use=' . _get($params, 'yk_use') .
' yk_high=' . _get($params, 'yk_high') . ' yk_high=' . _get($params, 'yk_high') .
' yk_low=' . _get($params, 'yk_low'); ' yk_low=' . _get($params, 'yk_low');
if ($this->myLog) if ($this->myLog)
$this->myLog->log($priority, $msg); $this->myLog->log($priority, $msg);
else else
error_log("Warning: myLog uninitialized in ykval-synclib.php. Message is " . $msg); error_log("Warning: myLog uninitialized in ykval-synclib.php. Message is " . $msg);
} }
public function getLocalParams($yk_publicname) public function getLocalParams($yk_publicname)
{ {
$this->log(LOG_DEBUG, "searching for yk_publicname $yk_publicname in local db"); $this->log(LOG_DEBUG, "searching for yk_publicname $yk_publicname in local db");
$res = $this->db->findBy('yubikeys', 'yk_publicname', $yk_publicname, 1); $res = $this->db->findBy('yubikeys', 'yk_publicname', $yk_publicname, 1);
if (!$res) if (!$res)
{ {
$this->log(LOG_NOTICE, "Discovered new identity $yk_publicname"); $this->log(LOG_NOTICE, "Discovered new identity $yk_publicname");
$this->db->save('yubikeys', array( $this->db->save('yubikeys', array(
'active' => 1, 'active' => 1,
'created' => time(), 'created' => time(),
'modified' => -1, 'modified' => -1,
'yk_publicname' => $yk_publicname, 'yk_publicname' => $yk_publicname,
'yk_counter' => -1, 'yk_counter' => -1,
'yk_use' => -1, 'yk_use' => -1,
'yk_low' => -1, 'yk_low' => -1,
'yk_high' => -1, 'yk_high' => -1,
'nonce' => '0000000000000000', 'nonce' => '0000000000000000',
'notes' => '' 'notes' => ''
)); ));
$res = $this->db->findBy('yubikeys', 'yk_publicname', $yk_publicname, 1); $res = $this->db->findBy('yubikeys', 'yk_publicname', $yk_publicname, 1);
} }
if ($res) if ($res)
{ {
$localParams = array( $localParams = array(
'modified' => $res['modified'], 'modified' => $res['modified'],
'nonce' => $res['nonce'], 'nonce' => $res['nonce'],
'active' => $res['active'], 'active' => $res['active'],
'yk_publicname' => $yk_publicname, 'yk_publicname' => $yk_publicname,
'yk_counter' => $res['yk_counter'], 'yk_counter' => $res['yk_counter'],
'yk_use' => $res['yk_use'], 'yk_use' => $res['yk_use'],
'yk_high' => $res['yk_high'], 'yk_high' => $res['yk_high'],
'yk_low' => $res['yk_low'] 'yk_low' => $res['yk_low']
); );
$this->log(LOG_INFO, "yubikey found in db ", $localParams); $this->log(LOG_INFO, "yubikey found in db ", $localParams);
return $localParams; return $localParams;
} }
$this->log(LOG_NOTICE, "params for yk_publicname $yk_publicname not found in database"); $this->log(LOG_NOTICE, "params for yk_publicname $yk_publicname not found in database");
return false; return false;
} }
public function updateDbCounters($params) public function updateDbCounters($params)
{ {
if (!isset($params['yk_publicname'])) if (!isset($params['yk_publicname']))
return false; return false;
$arr = array( $arr = array(
'modified' => $params['modified'], 'modified' => $params['modified'],
'yk_counter' => $params['yk_counter'], 'yk_counter' => $params['yk_counter'],
'yk_use' => $params['yk_use'], 'yk_use' => $params['yk_use'],
'yk_low' => $params['yk_low'], 'yk_low' => $params['yk_low'],
'yk_high' => $params['yk_high'], 'yk_high' => $params['yk_high'],
'nonce' => $params['nonce'] 'nonce' => $params['nonce']
); );
$condition = '('.$params['yk_counter'].'>yk_counter or ('.$params['yk_counter'].'=yk_counter and ' . $params['yk_use'] . '>yk_use))'; $condition = '('.$params['yk_counter'].'>yk_counter or ('.$params['yk_counter'].'=yk_counter and ' . $params['yk_use'] . '>yk_use))';
if (! $this->db->conditionalUpdateBy('yubikeys', 'yk_publicname', $params['yk_publicname'], $arr, $condition)) if (! $this->db->conditionalUpdateBy('yubikeys', 'yk_publicname', $params['yk_publicname'], $arr, $condition))
{ {
$this->log(LOG_CRIT, 'failed to update internal DB with new counters'); $this->log(LOG_CRIT, 'failed to update internal DB with new counters');
return false; return false;
} }
if ($this->db->rowCount() > 0) if ($this->db->rowCount() > 0)
$this->log(LOG_INFO, 'updated database ', $params); $this->log(LOG_INFO, 'updated database ', $params);
else else
$this->log(LOG_INFO, 'database not updated', $params); $this->log(LOG_INFO, 'database not updated', $params);
return true; return true;
} }
public function countersHigherThan($p1, $p2) public function countersHigherThan($p1, $p2)
{ {
if ($p1['yk_counter'] > $p2['yk_counter']) if ($p1['yk_counter'] > $p2['yk_counter'])
return true; return true;
if ($p1['yk_counter'] == $p2['yk_counter'] && $p1['yk_use'] > $p2['yk_use']) if ($p1['yk_counter'] == $p2['yk_counter'] && $p1['yk_use'] > $p2['yk_use'])
return true; return true;
return false; return false;
} }
public function countersHigherThanOrEqual($p1, $p2) public function countersHigherThanOrEqual($p1, $p2)
{ {
if ($p1['yk_counter'] > $p2['yk_counter']) if ($p1['yk_counter'] > $p2['yk_counter'])
return true; return true;
if ($p1['yk_counter'] == $p2['yk_counter'] && $p1['yk_use'] >= $p2['yk_use']) if ($p1['yk_counter'] == $p2['yk_counter'] && $p1['yk_use'] >= $p2['yk_use'])
return true; return true;
return false; return false;
} }
public function countersEqual($p1, $p2) public function countersEqual($p1, $p2)
{ {
return ($p1['yk_counter'] == $p2['yk_counter'] && $p1['yk_use'] == $p2['yk_use']); return ($p1['yk_counter'] == $p2['yk_counter'] && $p1['yk_use'] == $p2['yk_use']);
} }
// queue daemon // queue daemon
public function reSync($older_than, $timeout) public function reSync($older_than, $timeout)
{ {
$this->log(LOG_DEBUG, 'starting resync'); $this->log(LOG_DEBUG, 'starting resync');
/* Loop over all unique servers in queue */ /* Loop over all unique servers in queue */
$queued_limit = time()-$older_than; $queued_limit = time()-$older_than;
$server_res = $this->db->customQuery("select distinct server from queue WHERE queued < " . $queued_limit . " or queued is null"); $server_res = $this->db->customQuery("select distinct server from queue WHERE queued < " . $queued_limit . " or queued is null");
$server_list = array(); $server_list = array();
$mh = curl_multi_init(); $mh = curl_multi_init();
$ch = array(); $ch = array();
$entries = array(); $entries = array();
$handles = 0; $handles = 0;
$num_per_server = 4; $num_per_server = 4;
$curlopts = $this->curlopts; $curlopts = $this->curlopts;
while ($my_server = $this->db->fetchArray($server_res)) while ($my_server = $this->db->fetchArray($server_res))
{ {
$server = $my_server['server']; $server = $my_server['server'];
$this->log(LOG_DEBUG, "Processing queue for server " . $server); $this->log(LOG_DEBUG, "Processing queue for server " . $server);
$res = $this->db->customQuery("select * from queue WHERE (queued < " . $queued_limit . " or queued is null) and server='" . $server . "' LIMIT 1000"); $res = $this->db->customQuery("select * from queue WHERE (queued < " . $queued_limit . " or queued is null) and server='" . $server . "' LIMIT 1000");
$list = array(); $list = array();
while ($entry = $this->db->fetchArray($res)) while ($entry = $this->db->fetchArray($res))
{ {
$list[] = $entry; $list[] = $entry;
} }
$server_list[$server] = $list; $server_list[$server] = $list;
$this->db->closeCursor($res); $this->db->closeCursor($res);
} }
$this->db->closeCursor($server_res); $this->db->closeCursor($server_res);
/* add up to n entries for each server we're going to sync */ /* add up to n entries for each server we're going to sync */
foreach ($server_list as $server) { foreach ($server_list as $server) {
$items = array_slice($server, 0, $num_per_server); $items = array_slice($server, 0, $num_per_server);
$counter = 0; $counter = 0;
foreach ($items as $entry) { foreach ($items as $entry) {
$label = "{$entry['server']}:$counter"; $label = "{$entry['server']}:$counter";
$handle = curl_init(); $handle = curl_init();
$ch[$label] = $handle; $ch[$label] = $handle;
$counter++; $counter++;
$this->log(LOG_INFO, "server=" . $entry['server'] . ", server_nonce=" . $entry['server_nonce'] . ", info=" . $entry['info']); $this->log(LOG_INFO, "server=" . $entry['server'] . ", server_nonce=" . $entry['server_nonce'] . ", info=" . $entry['info']);
$url = $this->buildSyncUrl($entry); $url = $this->buildSyncUrl($entry);
$curlopts[CURLOPT_PRIVATE] = $label; $curlopts[CURLOPT_PRIVATE] = $label;
curl_settings($this, 'YK-VAL resync', $handle, $url, $timeout, $curlopts); curl_settings($this, 'YK-VAL resync', $handle, $url, $timeout, $curlopts);
$entries[$label] = $entry; $entries[$label] = $entry;
curl_multi_add_handle($mh, $handle); curl_multi_add_handle($mh, $handle);
$handles++; $handles++;
} }
$empty = array(); $empty = array();
array_splice($server, 0, $num_per_server, $empty); array_splice($server, 0, $num_per_server, $empty);
if(count($server) == 0) { if(count($server) == 0) {
unset($server_list[$entry['server']]); unset($server_list[$entry['server']]);
} }
} }
while($handles > 0) { while($handles > 0) {
while (curl_multi_exec($mh, $active) == CURLM_CALL_MULTI_PERFORM); while (curl_multi_exec($mh, $active) == CURLM_CALL_MULTI_PERFORM);
while ($info = curl_multi_info_read($mh)) { while ($info = curl_multi_info_read($mh)) {
$handle = $info['handle']; $handle = $info['handle'];
$server = strtok(curl_getinfo($handle, CURLINFO_EFFECTIVE_URL), "?"); $server = strtok(curl_getinfo($handle, CURLINFO_EFFECTIVE_URL), "?");
$label = curl_getinfo($handle, CURLINFO_PRIVATE); $label = curl_getinfo($handle, CURLINFO_PRIVATE);
$entry = $entries[$label]; $entry = $entries[$label];
$this->log(LOG_DEBUG, "handle indicated to be for $server."); $this->log(LOG_DEBUG, "handle indicated to be for $server.");
curl_multi_remove_handle($mh, $handle); curl_multi_remove_handle($mh, $handle);
$handles--; $handles--;
if ($info['result'] === CURLE_OK) { if ($info['result'] === CURLE_OK) {
$response = curl_multi_getcontent($handle); $response = curl_multi_getcontent($handle);
if (preg_match('/status=OK/', $response)) if (preg_match('/status=OK/', $response))
{ {
$resParams = $this->parseParamsFromMultiLineString($response); $resParams = $this->parseParamsFromMultiLineString($response);
$this->log(LOG_DEBUG, 'response contains ', $resParams); $this->log(LOG_DEBUG, 'response contains ', $resParams);
/* Update database counters */ /* Update database counters */
$this->updateDbCounters($resParams); $this->updateDbCounters($resParams);
/* Retrieve info from entry info string */ /* Retrieve info from entry info string */
/* This is the counter values we had in our database *before* processing the current OTP. */ /* This is the counter values we had in our database *before* processing the current OTP. */
$validationParams = $this->localParamsFromInfoString($entry['info']); $validationParams = $this->localParamsFromInfoString($entry['info']);
/* This is the data from the current OTP. */ /* This is the data from the current OTP. */
$otpParams = $this->otpParamsFromInfoString($entry['info']); $otpParams = $this->otpParamsFromInfoString($entry['info']);
/* Fetch current information from our database */ /* Fetch current information from our database */
$localParams = $this->getLocalParams($otpParams['yk_publicname']); $localParams = $this->getLocalParams($otpParams['yk_publicname']);
$this->log(LOG_DEBUG, 'validation params: ', $validationParams); $this->log(LOG_DEBUG, 'validation params: ', $validationParams);
$this->log(LOG_DEBUG, 'OTP params: ', $otpParams); $this->log(LOG_DEBUG, 'OTP params: ', $otpParams);
/* Check for warnings */ /* Check for warnings */
if ($this->countersHigherThan($validationParams, $resParams)) if ($this->countersHigherThan($validationParams, $resParams))
{ {
$this->log(LOG_NOTICE, 'Remote server out of sync compared to counters at validation request time. '); $this->log(LOG_NOTICE, 'Remote server out of sync compared to counters at validation request time. ');
} }
if ($this->countersHigherThan($resParams, $validationParams)) if ($this->countersHigherThan($resParams, $validationParams))
{ {
if ($this->countersEqual($resParams, $otpParams)) if ($this->countersEqual($resParams, $otpParams))
{ {
$this->log(LOG_INFO, 'Remote server had received the current counter values already. '); $this->log(LOG_INFO, 'Remote server had received the current counter values already. ');
} }
else else
{ {
$this->log(LOG_NOTICE, 'Local server out of sync compared to counters at validation request time. '); $this->log(LOG_NOTICE, 'Local server out of sync compared to counters at validation request time. ');
} }
} }
if ($this->countersHigherThan($localParams, $resParams)) if ($this->countersHigherThan($localParams, $resParams))
{ {
$this->log(LOG_WARNING, 'Remote server out of sync compared to current local counters. '); $this->log(LOG_WARNING, 'Remote server out of sync compared to current local counters. ');
} }
if ($this->countersHigherThan($resParams, $localParams)) if ($this->countersHigherThan($resParams, $localParams))
{ {
$this->log(LOG_WARNING, 'Local server out of sync compared to current local counters. Local server updated. '); $this->log(LOG_WARNING, 'Local server out of sync compared to current local counters. Local server updated. ');
} }
if ($this->countersHigherThan($resParams, $otpParams)) if ($this->countersHigherThan($resParams, $otpParams))
{ {
$this->log(LOG_ERR, 'Remote server has higher counters than OTP. This response would have marked the OTP as invalid. '); $this->log(LOG_ERR, 'Remote server has higher counters than OTP. This response would have marked the OTP as invalid. ');
} }
elseif ($this->countersEqual($resParams, $otpParams) && $resParams['nonce'] != $otpParams['nonce']) elseif ($this->countersEqual($resParams, $otpParams) && $resParams['nonce'] != $otpParams['nonce'])
{ {
$this->log(LOG_ERR, 'Remote server has equal counters as OTP and nonce differs. This response would have marked the OTP as invalid.'); $this->log(LOG_ERR, 'Remote server has equal counters as OTP and nonce differs. This response would have marked the OTP as invalid.');
} }
/* Deletion */ /* Deletion */
$this->log(LOG_DEBUG, 'deleting queue entry with modified=' . $entry['modified'] . $this->log(LOG_DEBUG, 'deleting queue entry with modified=' . $entry['modified'] .
' server_nonce=' . $entry['server_nonce'] . ' server_nonce=' . $entry['server_nonce'] .
' server=' . $entry['server']); ' server=' . $entry['server']);
$this->db->deleteByMultiple('queue', array( $this->db->deleteByMultiple('queue', array(
'modified' => $entry['modified'], 'modified' => $entry['modified'],
'server_nonce' => $entry['server_nonce'], 'server_nonce' => $entry['server_nonce'],
'server' => $entry['server'] 'server' => $entry['server']
)); ));
} }
else if (preg_match('/status=BAD_OTP/', $response)) else if (preg_match('/status=BAD_OTP/', $response))
{ {
$this->log(LOG_WARNING, 'Remote server says BAD_OTP, pointless to try again, removing from queue.'); $this->log(LOG_WARNING, 'Remote server says BAD_OTP, pointless to try again, removing from queue.');
$this->db->deleteByMultiple('queue', array( $this->db->deleteByMultiple('queue', array(
'modified' => $entry['modified'], 'modified' => $entry['modified'],
'server_nonce' => $entry['server_nonce'], 'server_nonce' => $entry['server_nonce'],
'server' => $entry['server'] 'server' => $entry['server']
)); ));
} }
else else
{ {
$this->log(LOG_ERR, 'Remote server refused our sync request. Check remote server logs.'); $this->log(LOG_ERR, 'Remote server refused our sync request. Check remote server logs.');
} }
if (array_key_exists($server, $server_list)) { if (array_key_exists($server, $server_list)) {
$entry = array_shift($server_list[$server]); $entry = array_shift($server_list[$server]);
if(count($server_list[$server]) == 0) { if(count($server_list[$server]) == 0) {
$this->log(LOG_DEBUG, "All entries for $server synced."); $this->log(LOG_DEBUG, "All entries for $server synced.");
unset($server_list[$server]); unset($server_list[$server]);
} }
$this->log(LOG_INFO, "server=" . $entry['server'] . ", server_nonce=" . $entry['server_nonce'] . ", info=" . $entry['info']); $this->log(LOG_INFO, "server=" . $entry['server'] . ", server_nonce=" . $entry['server_nonce'] . ", info=" . $entry['info']);
$url = $this->buildSyncUrl($entry); $url = $this->buildSyncUrl($entry);
$curlopts[CURLOPT_PRIVATE] = $label; $curlopts[CURLOPT_PRIVATE] = $label;
curl_settings($this, 'YK-VAL resync', $handle, $url, $timeout, $curlopts); curl_settings($this, 'YK-VAL resync', $handle, $url, $timeout, $curlopts);
$entries[$label] = $entry; $entries[$label] = $entry;
curl_multi_add_handle($mh, $handle); curl_multi_add_handle($mh, $handle);
$handles++; $handles++;
} }
} else { } else {
$this->log(LOG_NOTICE, 'Timeout. Stopping queue resync for server ' . $entry['server']); $this->log(LOG_NOTICE, 'Timeout. Stopping queue resync for server ' . $entry['server']);
unset($server_list[$server]); unset($server_list[$server]);
} }
} }
} }
foreach ($ch as $handle) { foreach ($ch as $handle) {
curl_close($handle); curl_close($handle);
} }
curl_multi_close($mh); curl_multi_close($mh);
return true; return true;
} }
// blocks verify requests // blocks verify requests
public function sync($ans_req, $timeout=1) public function sync($ans_req, $timeout=1)
{ {
// construct URLs // construct URLs
$urls = array(); $urls = array();
$res = $this->db->findByMultiple('queue', array( $res = $this->db->findByMultiple('queue', array(
'modified' => $this->otpParams['modified'], 'modified' => $this->otpParams['modified'],
'server_nonce' => $this->server_nonce 'server_nonce' => $this->server_nonce
)); ));
foreach ($res as $row) foreach ($res as $row)
{ {
$urls[] = $this->buildSyncUrl($row); $urls[] = $this->buildSyncUrl($row);
} }
// send out requests // send out requests
$ans_arr = retrieveURLasync('YK-VAL sync', $urls, $this->myLog, $ans_req, $match='status=OK', $returl=True, $timeout, $this->curlopts); $ans_arr = retrieveURLasync('YK-VAL sync', $urls, $this->myLog, $ans_req, $match='status=OK', $returl=True, $timeout, $this->curlopts);
if ($ans_arr === FALSE) if ($ans_arr === FALSE)
{ {
$this->log(LOG_WARNING, 'No responses from validation server pool'); $this->log(LOG_WARNING, 'No responses from validation server pool');
$ans_arr = array(); $ans_arr = array();
} }
// parse responses // parse responses
$localParams = $this->localParams; $localParams = $this->localParams;
$this->answers = count($ans_arr); $this->answers = count($ans_arr);
$this->valid_answers = 0; $this->valid_answers = 0;
foreach ($ans_arr as $answer) foreach ($ans_arr as $answer)
{ {
// parse out parameters from each response // parse out parameters from each response
$resParams=$this->parseParamsFromMultiLineString($answer); $resParams=$this->parseParamsFromMultiLineString($answer);
$this->log(LOG_DEBUG, 'local db contains ', $localParams); $this->log(LOG_DEBUG, 'local db contains ', $localParams);
$this->log(LOG_DEBUG, 'response contains ', $resParams); $this->log(LOG_DEBUG, 'response contains ', $resParams);
$this->log(LOG_DEBUG, 'OTP contains ', $this->otpParams); $this->log(LOG_DEBUG, 'OTP contains ', $this->otpParams);
// update internal DB (conditional) // update internal DB (conditional)
$this->updateDbCounters($resParams); $this->updateDbCounters($resParams);
/** /**
* Check for warnings * Check for warnings
* *
* See https://developers.yubico.com/yubikey-val/doc/ServerReplicationProtocol.html * See https://developers.yubico.com/yubikey-val/doc/ServerReplicationProtocol.html
* *
* NOTE: We use localParams for validationParams comparison since they are actually the * NOTE: We use localParams for validationParams comparison since they are actually the
* same in this situation and we have them at hand. * same in this situation and we have them at hand.
*/ */
if ($this->countersHigherThan($localParams, $resParams)) if ($this->countersHigherThan($localParams, $resParams))
{ {
$this->log(LOG_NOTICE, 'Remote server out of sync'); $this->log(LOG_NOTICE, 'Remote server out of sync');
} }
if ($this->countersHigherThan($resParams, $localParams)) if ($this->countersHigherThan($resParams, $localParams))
{ {
$this->log(LOG_NOTICE, 'Local server out of sync'); $this->log(LOG_NOTICE, 'Local server out of sync');
} }
if ($this->countersEqual($resParams, $localParams) && $resParams['nonce'] != $localParams['nonce']) if ($this->countersEqual($resParams, $localParams) && $resParams['nonce'] != $localParams['nonce'])
{ {
$this->log(LOG_NOTICE, 'Servers out of sync. Nonce differs. '); $this->log(LOG_NOTICE, 'Servers out of sync. Nonce differs. ');
} }
if ($this->countersEqual($resParams, $localParams) && $resParams['modified'] != $localParams['modified']) if ($this->countersEqual($resParams, $localParams) && $resParams['modified'] != $localParams['modified'])
{ {
$this->log(LOG_NOTICE, 'Servers out of sync. Modified differs. '); $this->log(LOG_NOTICE, 'Servers out of sync. Modified differs. ');
} }
if ($this->countersHigherThan($resParams, $this->otpParams)) if ($this->countersHigherThan($resParams, $this->otpParams))
{ {
$this->log(LOG_WARNING, 'OTP is replayed. Sync response counters higher than OTP counters.'); $this->log(LOG_WARNING, 'OTP is replayed. Sync response counters higher than OTP counters.');
} }
elseif ($this->countersEqual($resParams, $this->otpParams) && $resParams['nonce'] != $this->otpParams['nonce']) elseif ($this->countersEqual($resParams, $this->otpParams) && $resParams['nonce'] != $this->otpParams['nonce'])
{ {
$this->log(LOG_WARNING, 'OTP is replayed. Sync response counters equal to OTP counters and nonce differs.'); $this->log(LOG_WARNING, 'OTP is replayed. Sync response counters equal to OTP counters and nonce differs.');
} }
else else
{ {
// the answer is ok since a REPLAY was not indicated // the answer is ok since a REPLAY was not indicated
$this->valid_answers++; $this->valid_answers++;
} }
// delete entry from table // delete entry from table
$this->deleteQueueEntry($answer); $this->deleteQueueEntry($answer);
} }
/** /**
* NULL queued_time for remaining entries in queue, to allow * NULL queued_time for remaining entries in queue, to allow
* daemon to take care of them as soon as possible. * daemon to take care of them as soon as possible.
*/ */
$this->db->updateBy('queue', 'server_nonce', $this->server_nonce, array('queued'=>NULL)); $this->db->updateBy('queue', 'server_nonce', $this->server_nonce, array('queued'=>NULL));
/** /**
* Return true if valid answers equals required answers. * Return true if valid answers equals required answers.
* Since we only obtain the required amount of answers from * Since we only obtain the required amount of answers from
* retrieveAsync this indicates that all answers were actually valid. * retrieveAsync this indicates that all answers were actually valid.
* Otherwise, return false. * Otherwise, return false.
*/ */
if ($this->valid_answers == $ans_req) if ($this->valid_answers == $ans_req)
return true; return true;
return false; return false;
} }
private function createInfoString($otpParams, $localParams) private function createInfoString($otpParams, $localParams)
{ {
# FIXME &local_counter # FIXME &local_counter
return 'yk_publicname=' . $otpParams['yk_publicname'] . return 'yk_publicname=' . $otpParams['yk_publicname'] .
'&yk_counter=' . $otpParams['yk_counter'] . '&yk_counter=' . $otpParams['yk_counter'] .
'&yk_use=' . $otpParams['yk_use'] . '&yk_use=' . $otpParams['yk_use'] .
'&yk_high=' . $otpParams['yk_high'] . '&yk_high=' . $otpParams['yk_high'] .
'&yk_low=' . $otpParams['yk_low'] . '&yk_low=' . $otpParams['yk_low'] .
'&nonce=' . $otpParams['nonce'] . '&nonce=' . $otpParams['nonce'] .
',local_counter=' . $localParams['yk_counter'] . ',local_counter=' . $localParams['yk_counter'] .
'&local_use=' . $localParams['yk_use']; '&local_use=' . $localParams['yk_use'];
} }
private function otpParamsFromInfoString($info) private function otpParamsFromInfoString($info)
{ {
$out = explode(',', $info); $out = explode(',', $info);
parse_str($out[0], $params); parse_str($out[0], $params);
return $params; return $params;
} }
private function otpPartFromInfoString($info) private function otpPartFromInfoString($info)
{ {
$out = explode(',', $info); $out = explode(',', $info);
return $out[0]; return $out[0];
} }
private function localParamsFromInfoString($info) private function localParamsFromInfoString($info)
{ {
$out = explode(',', $info); $out = explode(',', $info);
parse_str($out[1], $params); parse_str($out[1], $params);
return array( return array(
'yk_counter' => $params['local_counter'], 'yk_counter' => $params['local_counter'],
'yk_use' => $params['local_use'] 'yk_use' => $params['local_use']
); );
} }
private function parseParamsFromMultiLineString($str) private function parseParamsFromMultiLineString($str)
{ {
$i = preg_match("/^modified=(-1|[0-9]+)/m", $str, $out); $i = preg_match("/^modified=(-1|[0-9]+)/m", $str, $out);
if ($i != 1) { if ($i != 1) {
$this->log(LOG_ALERT, "cannot parse modified value: $str"); $this->log(LOG_ALERT, "cannot parse modified value: $str");
} }
$resParams['modified']=$out[1]; $resParams['modified']=$out[1];
$i = preg_match("/^yk_publicname=([cbdefghijklnrtuv]+)/m", $str, $out); $i = preg_match("/^yk_publicname=([cbdefghijklnrtuv]+)/m", $str, $out);
if ($i != 1) { if ($i != 1) {
$this->log(LOG_ALERT, "cannot parse publicname value: $str"); $this->log(LOG_ALERT, "cannot parse publicname value: $str");
} }
$resParams['yk_publicname']=$out[1]; $resParams['yk_publicname']=$out[1];
$i = preg_match("/^yk_counter=(-1|[0-9]+)/m", $str, $out); $i = preg_match("/^yk_counter=(-1|[0-9]+)/m", $str, $out);
if ($i != 1) { if ($i != 1) {
$this->log(LOG_ALERT, "cannot parse counter value: $str"); $this->log(LOG_ALERT, "cannot parse counter value: $str");
} }
$resParams['yk_counter']=$out[1]; $resParams['yk_counter']=$out[1];
$i = preg_match("/^yk_use=(-1|[0-9]+)/m", $str, $out); $i = preg_match("/^yk_use=(-1|[0-9]+)/m", $str, $out);
if ($i != 1) { if ($i != 1) {
$this->log(LOG_ALERT, "cannot parse use value: $str"); $this->log(LOG_ALERT, "cannot parse use value: $str");
} }
$resParams['yk_use']=$out[1]; $resParams['yk_use']=$out[1];
$i = preg_match("/^yk_high=(-1|[0-9]+)/m", $str, $out); $i = preg_match("/^yk_high=(-1|[0-9]+)/m", $str, $out);
if ($i != 1) { if ($i != 1) {
$this->log(LOG_ALERT, "cannot parse high value: $str"); $this->log(LOG_ALERT, "cannot parse high value: $str");
} }
$resParams['yk_high']=$out[1]; $resParams['yk_high']=$out[1];
$i = preg_match("/^yk_low=(-1|[0-9]+)/m", $str, $out); $i = preg_match("/^yk_low=(-1|[0-9]+)/m", $str, $out);
if ($i != 1) { if ($i != 1) {
$this->log(LOG_ALERT, "cannot parse low value: $str"); $this->log(LOG_ALERT, "cannot parse low value: $str");
} }
$resParams['yk_low']=$out[1]; $resParams['yk_low']=$out[1];
$i = preg_match("/^nonce=([[:alnum:]]+)/m", $str, $out); $i = preg_match("/^nonce=([[:alnum:]]+)/m", $str, $out);
if ($i != 1) { if ($i != 1) {
$this->log(LOG_ALERT, "cannot parse nonce value: $str"); $this->log(LOG_ALERT, "cannot parse nonce value: $str");
} }
$resParams['nonce']=$out[1]; $resParams['nonce']=$out[1];
return $resParams; return $resParams;
} }
private function deleteQueueEntry($answer) private function deleteQueueEntry($answer)
{ {
preg_match('/url=(.*)\?/', $answer, $out); preg_match('/url=(.*)\?/', $answer, $out);
$server = $out[1]; $server = $out[1];
$this->log(LOG_INFO, "deleting server=" . $server . $this->log(LOG_INFO, "deleting server=" . $server .
" modified=" . $this->otpParams['modified'] . " modified=" . $this->otpParams['modified'] .
" server_nonce=" . $this->server_nonce); " server_nonce=" . $this->server_nonce);
$this->db->deleteByMultiple('queue', array( $this->db->deleteByMultiple('queue', array(
'modified' => $this->otpParams['modified'], 'modified' => $this->otpParams['modified'],
'server_nonce' => $this->server_nonce, 'server_nonce' => $this->server_nonce,
'server' => $server 'server' => $server
)); ));
} }
private function buildSyncUrl($entry) private function buildSyncUrl($entry)
{ {
return $entry['server'] . return $entry['server'] .
"?otp=" . $entry['otp'] . "?otp=" . $entry['otp'] .
"&modified=" . $entry['modified'] . "&modified=" . $entry['modified'] .
"&" . $this->otpPartFromInfoString($entry['info']); "&" . $this->otpPartFromInfoString($entry['info']);
} }
} }

View File

@ -39,7 +39,7 @@ header('content-type: text/plain');
$ipaddr = $_SERVER['REMOTE_ADDR']; $ipaddr = $_SERVER['REMOTE_ADDR'];
$https = (array_key_exists('HTTPS', $_SERVER) === TRUE $https = (array_key_exists('HTTPS', $_SERVER) === TRUE
&& strtolower($_SERVER['HTTPS']) !== 'off' ? TRUE : FALSE); && strtolower($_SERVER['HTTPS']) !== 'off' ? TRUE : FALSE);
$myLog = new Log('ykval-verify'); $myLog = new Log('ykval-verify');
$myLog->addField('ip', $ipaddr); $myLog->addField('ip', $ipaddr);
@ -47,9 +47,9 @@ $myLog->addField('ip', $ipaddr);
$myLog->request = new LogVerify(); $myLog->request = new LogVerify();
if (array_key_exists('__YKVAL_VERIFY_LOGFORMAT__', $baseParams) if (array_key_exists('__YKVAL_VERIFY_LOGFORMAT__', $baseParams)
&& is_string($baseParams['__YKVAL_VERIFY_LOGFORMAT__'])) && is_string($baseParams['__YKVAL_VERIFY_LOGFORMAT__']))
{ {
$myLog->request->format = $baseParams['__YKVAL_VERIFY_LOGFORMAT__']; $myLog->request->format = $baseParams['__YKVAL_VERIFY_LOGFORMAT__'];
} }
$myLog->request->set('ip', $ipaddr); $myLog->request->set('ip', $ipaddr);
@ -60,24 +60,24 @@ unset($time_start);
if ($_GET) if ($_GET)
{ {
$request = $_GET; $request = $_GET;
$message = 'Request: ' . $_SERVER['QUERY_STRING']; $message = 'Request: ' . $_SERVER['QUERY_STRING'];
} }
else if ($_POST) else if ($_POST)
{ {
$request = $_POST; $request = $_POST;
$kv = array(); $kv = array();
foreach ($request as $key => $value) foreach ($request as $key => $value)
{ {
$kv[] = "$key=$value"; $kv[] = "$key=$value";
} }
$message = 'POST: ' . join('&', $kv); $message = 'POST: ' . join('&', $kv);
unset($kv); unset($kv);
} }
else else
{ {
$request = array(); $request = array();
$message = ''; $message = '';
} }
$message .= ' (at ' . date('c') . ' ' . microtime() . ') HTTP' . ($https ? 'S' : ''); $message .= ' (at ' . date('c') . ' ' . microtime() . ') HTTP' . ($https ? 'S' : '');
$myLog->log(LOG_INFO, $message); $myLog->log(LOG_INFO, $message);
@ -87,11 +87,11 @@ unset($message);
/* Detect protocol version */ /* Detect protocol version */
if (preg_match('/\/wsapi\/([0-9]+)\.([0-9]+)\//', $_SERVER['REQUEST_URI'], $out)) if (preg_match('/\/wsapi\/([0-9]+)\.([0-9]+)\//', $_SERVER['REQUEST_URI'], $out))
{ {
$protocol_version = $out[1] + $out[2] * 0.1; $protocol_version = $out[1] + $out[2] * 0.1;
} }
else else
{ {
$protocol_version = 1.0; $protocol_version = 1.0;
} }
$myLog->request->set('protocol', $protocol_version); $myLog->request->set('protocol', $protocol_version);
@ -108,10 +108,10 @@ $otp = getHttpVal('otp', '', $request);
$otp = strtolower($otp); $otp = strtolower($otp);
if (preg_match('/^[jxe.uidchtnbpygk]+$/', $otp)) if (preg_match('/^[jxe.uidchtnbpygk]+$/', $otp))
{ {
$new_otp = strtr($otp, 'jxe.uidchtnbpygk', 'cbdefghijklnrtuv'); $new_otp = strtr($otp, 'jxe.uidchtnbpygk', 'cbdefghijklnrtuv');
$myLog->log(LOG_INFO, "Dvorak OTP converting $otp to $new_otp"); $myLog->log(LOG_INFO, "Dvorak OTP converting $otp to $new_otp");
$otp = $new_otp; $otp = $new_otp;
unset($new_otp); unset($new_otp);
} }
$myLog->request->set('signed', ($h === '' ? '-' : 'signed')); $myLog->request->set('signed', ($h === '' ? '-' : 'signed'));
@ -126,7 +126,7 @@ $extra = array();
if ($protocol_version >= 2.0) if ($protocol_version >= 2.0)
{ {
$extra['otp'] = $otp; $extra['otp'] = $otp;
} }
/** /**
@ -137,23 +137,23 @@ $myLog->addField('otp', $otp);
if ($protocol_version >= 2.0) if ($protocol_version >= 2.0)
{ {
$sl = getHttpVal('sl', '', $request); $sl = getHttpVal('sl', '', $request);
$timeout = getHttpVal('timeout', '', $request); $timeout = getHttpVal('timeout', '', $request);
$nonce = getHttpVal('nonce', '', $request); $nonce = getHttpVal('nonce', '', $request);
$myLog->request->set('sl', $sl); $myLog->request->set('sl', $sl);
$myLog->request->set('timeout', $timeout); $myLog->request->set('timeout', $timeout);
$myLog->request->set('nonce', $nonce); $myLog->request->set('nonce', $nonce);
/* Nonce is required from protocol 2.0 */ /* Nonce is required from protocol 2.0 */
if (!$nonce) if (!$nonce)
{ {
$myLog->log(LOG_NOTICE, 'Nonce is missing and protocol version >= 2.0'); $myLog->log(LOG_NOTICE, 'Nonce is missing and protocol version >= 2.0');
sendResp(S_MISSING_PARAMETER, $myLog); sendResp(S_MISSING_PARAMETER, $myLog);
} }
/* Add nonce to response parameters */ /* Add nonce to response parameters */
$extra['nonce'] = $nonce; $extra['nonce'] = $nonce;
} }
@ -170,70 +170,45 @@ if ($protocol_version >= 2.0)
*/ */
/* Change default protocol "strings" to numeric values */ /* Change default protocol "strings" to numeric values */
if (isset($sl) && strcasecmp($sl, 'fast') == 0) if (isset($sl) && $sl != '') {
{ if (strcasecmp($sl, 'fast') == 0) {
$sl = $baseParams['__YKVAL_SYNC_FAST_LEVEL__']; $sl = $baseParams['__YKVAL_SYNC_FAST_LEVEL__'];
} } else if (strcasecmp($sl, 'secure') == 0) {
if (isset($sl) && strcasecmp($sl, 'secure') == 0) $sl = $baseParams['__YKVAL_SYNC_SECURE_LEVEL__'];
{ } else {
$sl = $baseParams['__YKVAL_SYNC_SECURE_LEVEL__']; $sl = intval($sl); // non-numbers return zero
} if ($sl < 1) {
if (!isset($sl) || $sl == '') $myLog->log(LOG_NOTICE, 'SL is provided but not correct');
{ sendResp(S_MISSING_PARAMETER, $myLog);
$sl = $baseParams['__YKVAL_SYNC_DEFAULT_LEVEL__']; }
} }
if ($sl && (preg_match("/^[0-9]+$/", $sl)==0 || ($sl<0 || $sl>100))) } else {
{ $sl = $baseParams['__YKVAL_SYNC_DEFAULT_LEVEL__'];
$myLog->log(LOG_NOTICE, 'SL is provided but not correct');
sendResp(S_MISSING_PARAMETER, $myLog);
} }
if (!isset($timeout) || $timeout == '') if (!isset($timeout) || $timeout == '') {
{ $timeout = $baseParams['__YKVAL_SYNC_DEFAULT_TIMEOUT__'];
$timeout = $baseParams['__YKVAL_SYNC_DEFAULT_TIMEOUT__']; } else if (!ctype_digit($timeout)) {
} $myLog->log(LOG_NOTICE, 'timeout is provided but not correct');
if ($timeout && preg_match("/^[0-9]+$/", $timeout) == 0) sendResp(S_MISSING_PARAMETER, $myLog);
{
$myLog->log(LOG_NOTICE, 'timeout is provided but not correct');
sendResp(S_MISSING_PARAMETER, $myLog);
} }
if ($otp == '') if (!is_otp($otp))
{ {
$myLog->log(LOG_NOTICE, 'OTP is missing'); $myLog->log(LOG_NOTICE, "Invalid OTP: $otp");
sendResp(S_MISSING_PARAMETER, $myLog); sendResp(S_BAD_OTP, $myLog);
}
if (strlen($otp) < TOKEN_LEN || strlen($otp) > OTP_MAX_LEN)
{
$myLog->log(LOG_NOTICE, "Incorrect OTP length: $otp");
sendResp(S_BAD_OTP, $myLog);
}
if (preg_match('/^[cbdefghijklnrtuv]+$/', $otp) == 0)
{
$myLog->log(LOG_NOTICE, "Invalid OTP: $otp");
sendResp(S_BAD_OTP, $myLog);
} }
if (preg_match("/^[0-9]+$/", $client) == 0) if (!is_clientid($client))
{ {
$myLog->log(LOG_NOTICE, 'id provided in request must be an integer'); $myLog->log(LOG_NOTICE, 'Client ID is missing or invalid');
sendResp(S_MISSING_PARAMETER, $myLog); sendResp(S_MISSING_PARAMETER, $myLog);
}
if ($client === '0')
{
$myLog->log(LOG_NOTICE, 'Client ID is missing');
sendResp(S_MISSING_PARAMETER, $myLog);
} }
if (isset($nonce) && preg_match("/^[A-Za-z0-9]+$/", $nonce) == 0) if (isset($nonce) && !is_nonce($nonce))
{ {
$myLog->log(LOG_NOTICE, 'NONCE is provided but not correct'); $myLog->log(LOG_NOTICE, 'NONCE is provided but not correct');
sendResp(S_MISSING_PARAMETER, $myLog); sendResp(S_MISSING_PARAMETER, $myLog);
}
if (isset($nonce) && (strlen($nonce) < 16 || strlen($nonce) > 40))
{
$myLog->log(LOG_NOTICE, 'Nonce too short or too long');
sendResp(S_MISSING_PARAMETER, $myLog);
} }
/** /**
@ -252,13 +227,13 @@ $sync->addField('otp', $otp);
if (! $sync->isConnected()) if (! $sync->isConnected())
{ {
sendResp(S_BACKEND_ERROR, $myLog); sendResp(S_BACKEND_ERROR, $myLog);
} }
if (($cd = $sync->getClientData($client)) === FALSE) if (($cd = $sync->getClientData($client)) === FALSE)
{ {
$myLog->log(LOG_NOTICE, "Invalid client id $client"); $myLog->log(LOG_NOTICE, "Invalid client id $client");
sendResp(S_NO_SUCH_CLIENT, $myLog); sendResp(S_NO_SUCH_CLIENT, $myLog);
} }
$myLog->log(LOG_DEBUG, 'Client data:', $cd); $myLog->log(LOG_DEBUG, 'Client data:', $cd);
@ -272,16 +247,16 @@ unset($cd);
if ($h != '') if ($h != '')
{ {
// Create the signature using the API key // Create the signature using the API key
unset($request['h']); unset($request['h']);
$hmac = sign($request, $apiKey, $myLog); $hmac = sign($request, $apiKey, $myLog);
if (hash_equals($hmac, $h) === FALSE) if (hash_equals($hmac, $h) === FALSE)
{ {
$myLog->log(LOG_DEBUG, "client hmac=$h, server hmac=$hmac"); $myLog->log(LOG_DEBUG, "client hmac=$h, server hmac=$hmac");
sendResp(S_BAD_SIGNATURE, $myLog, $apiKey); sendResp(S_BAD_SIGNATURE, $myLog, $apiKey);
} }
} }
/** /**
@ -290,33 +265,33 @@ if ($h != '')
*/ */
if ($protocol_version < 2.0) if ($protocol_version < 2.0)
{ {
// we need to create a nonce manually here // we need to create a nonce manually here
$nonce = md5(uniqid(rand())); $nonce = md5(uniqid(rand()));
$myLog->log(LOG_INFO, "protocol version below 2.0. Created nonce $nonce"); $myLog->log(LOG_INFO, "protocol version below 2.0. Created nonce $nonce");
} }
// which YK-KSM should we talk to? // which YK-KSM should we talk to?
$urls = otp2ksmurls($otp, $client); $urls = otp2ksmurls($otp, $client);
if (!is_array($urls)) if (!is_array($urls))
{ {
sendResp(S_BACKEND_ERROR, $myLog, $apiKey); sendResp(S_BACKEND_ERROR, $myLog, $apiKey);
} }
// decode OTP from input // decode OTP from input
$curlopts = array(); $curlopts = array();
if (array_key_exists('__YKVAL_KSM_CURL_OPTS__', $baseParams)) if (array_key_exists('__YKVAL_KSM_CURL_OPTS__', $baseParams))
{ {
$curlopts = $baseParams['__YKVAL_KSM_CURL_OPTS__']; $curlopts = $baseParams['__YKVAL_KSM_CURL_OPTS__'];
} }
if (($otpinfo = KSMdecryptOTP($urls, $myLog, $curlopts)) === FALSE) if (($otpinfo = KSMdecryptOTP($urls, $myLog, $curlopts)) === FALSE)
{ {
/** /**
* FIXME * FIXME
* *
* Return S_BACKEND_ERROR if there are connection issues, * Return S_BACKEND_ERROR if there are connection issues,
* e.g. misconfigured otp2ksmurls. * e.g. misconfigured otp2ksmurls.
*/ */
sendResp(S_BAD_OTP, $myLog, $apiKey); sendResp(S_BAD_OTP, $myLog, $apiKey);
} }
$myLog->request->set('counter', $otpinfo['session_counter']); $myLog->request->set('counter', $otpinfo['session_counter']);
$myLog->request->set('use', $otpinfo['session_use']); $myLog->request->set('use', $otpinfo['session_use']);
@ -329,28 +304,28 @@ $public_id = substr($otp, 0, strlen ($otp) - TOKEN_LEN);
$myLog->request->set('public_id', $public_id); $myLog->request->set('public_id', $public_id);
if (($localParams = $sync->getLocalParams($public_id)) === FALSE) if (($localParams = $sync->getLocalParams($public_id)) === FALSE)
{ {
$myLog->log(LOG_NOTICE, "Invalid Yubikey $public_id"); $myLog->log(LOG_NOTICE, "Invalid Yubikey $public_id");
sendResp(S_BACKEND_ERROR, $myLog, $apiKey); sendResp(S_BACKEND_ERROR, $myLog, $apiKey);
} }
$myLog->log(LOG_DEBUG, 'Auth data:', $localParams); $myLog->log(LOG_DEBUG, 'Auth data:', $localParams);
if ($localParams['active'] != 1) if ($localParams['active'] != 1)
{ {
$myLog->log(LOG_NOTICE, "De-activated Yubikey $public_id"); $myLog->log(LOG_NOTICE, "De-activated Yubikey $public_id");
sendResp(S_BAD_OTP, $myLog, $apiKey); sendResp(S_BAD_OTP, $myLog, $apiKey);
} }
/* Build OTP params */ /* Build OTP params */
$otpParams = array( $otpParams = array(
'modified' => time(), 'modified' => time(),
'otp' => $otp, 'otp' => $otp,
'nonce' => $nonce, 'nonce' => $nonce,
'yk_publicname' => $public_id, 'yk_publicname' => $public_id,
'yk_counter' => $otpinfo['session_counter'], 'yk_counter' => $otpinfo['session_counter'],
'yk_use' => $otpinfo['session_use'], 'yk_use' => $otpinfo['session_use'],
'yk_high' => $otpinfo['high'], 'yk_high' => $otpinfo['high'],
'yk_low' => $otpinfo['low'] 'yk_low' => $otpinfo['low']
); );
unset($otpinfo); unset($otpinfo);
@ -358,117 +333,117 @@ unset($otpinfo);
/* First check if OTP is seen with the same nonce, in such case we have an replayed request */ /* First check if OTP is seen with the same nonce, in such case we have an replayed request */
if ($sync->countersEqual($localParams, $otpParams) && $localParams['nonce'] == $otpParams['nonce']) if ($sync->countersEqual($localParams, $otpParams) && $localParams['nonce'] == $otpParams['nonce'])
{ {
$myLog->log(LOG_WARNING, 'Replayed request'); $myLog->log(LOG_WARNING, 'Replayed request');
sendResp(S_REPLAYED_REQUEST, $myLog, $apiKey, $extra); sendResp(S_REPLAYED_REQUEST, $myLog, $apiKey, $extra);
} }
/* Check the OTP counters against local db */ /* Check the OTP counters against local db */
if ($sync->countersHigherThanOrEqual($localParams, $otpParams)) if ($sync->countersHigherThanOrEqual($localParams, $otpParams))
{ {
$sync->log(LOG_WARNING, 'replayed OTP: Local counters higher'); $sync->log(LOG_WARNING, 'replayed OTP: Local counters higher');
$sync->log(LOG_WARNING, 'replayed OTP: Local counters ', $localParams); $sync->log(LOG_WARNING, 'replayed OTP: Local counters ', $localParams);
$sync->log(LOG_WARNING, 'replayed OTP: Otp counters ', $otpParams); $sync->log(LOG_WARNING, 'replayed OTP: Otp counters ', $otpParams);
sendResp(S_REPLAYED_OTP, $myLog, $apiKey, $extra); sendResp(S_REPLAYED_OTP, $myLog, $apiKey, $extra);
} }
/* Valid OTP, update database. */ /* Valid OTP, update database. */
if (!$sync->updateDbCounters($otpParams)) if (!$sync->updateDbCounters($otpParams))
{ {
$myLog->log(LOG_CRIT, 'Failed to update yubikey counters in database'); $myLog->log(LOG_CRIT, 'Failed to update yubikey counters in database');
sendResp(S_BACKEND_ERROR, $myLog, $apiKey); sendResp(S_BACKEND_ERROR, $myLog, $apiKey);
} }
/* Queue sync requests */ /* Queue sync requests */
if (!$sync->queue($otpParams, $localParams)) if (!$sync->queue($otpParams, $localParams))
{ {
$myLog->log(LOG_CRIT, 'failed to queue sync requests'); $myLog->log(LOG_CRIT, 'failed to queue sync requests');
sendResp(S_BACKEND_ERROR, $myLog, $apiKey); sendResp(S_BACKEND_ERROR, $myLog, $apiKey);
} }
$nr_servers = $sync->getNumberOfServers(); $nr_servers = $sync->getNumberOfServers();
$req_answers = ceil($nr_servers * $sl / 100.0); $req_answers = ceil($nr_servers * $sl / 100.0);
if ($req_answers > 0) if ($req_answers > 0)
{ {
$syncres = $sync->sync($req_answers, $timeout); $syncres = $sync->sync($req_answers, $timeout);
$nr_answers = $sync->getNumberOfAnswers(); $nr_answers = $sync->getNumberOfAnswers();
$nr_valid_answers = $sync->getNumberOfValidAnswers(); $nr_valid_answers = $sync->getNumberOfValidAnswers();
$sl_success_rate = floor(100.0 * $nr_valid_answers / $nr_servers); $sl_success_rate = floor(100.0 * $nr_valid_answers / $nr_servers);
} }
else else
{ {
$syncres = true; $syncres = true;
$nr_answers = 0; $nr_answers = 0;
$nr_valid_answers = 0; $nr_valid_answers = 0;
$sl_success_rate = 0; $sl_success_rate = 0;
} }
$myLog->log(LOG_INFO, '', array( $myLog->log(LOG_INFO, '', array(
'synclevel' => $sl, 'synclevel' => $sl,
'nr servers' => $nr_servers, 'nr servers' => $nr_servers,
'req answers' => $req_answers, 'req answers' => $req_answers,
'answers' => $nr_answers, 'answers' => $nr_answers,
'valid answers' => $nr_valid_answers, 'valid answers' => $nr_valid_answers,
'sl success rate' => $sl_success_rate, 'sl success rate' => $sl_success_rate,
'timeout' => $timeout, 'timeout' => $timeout,
)); ));
if ($syncres == False) if ($syncres == False)
{ {
/* sync returned false, indicating that /* sync returned false, indicating that
either at least 1 answer marked OTP as invalid or either at least 1 answer marked OTP as invalid or
there were not enough answers */ there were not enough answers */
$myLog->log(LOG_WARNING, 'Sync failed'); $myLog->log(LOG_WARNING, 'Sync failed');
if ($nr_valid_answers != $nr_answers) if ($nr_valid_answers != $nr_answers)
sendResp(S_REPLAYED_OTP, $myLog, $apiKey, $extra); sendResp(S_REPLAYED_OTP, $myLog, $apiKey, $extra);
$extra['sl'] = $sl_success_rate; $extra['sl'] = $sl_success_rate;
sendResp(S_NOT_ENOUGH_ANSWERS, $myLog, $apiKey, $extra); sendResp(S_NOT_ENOUGH_ANSWERS, $myLog, $apiKey, $extra);
} }
if ($otpParams['yk_counter'] == $localParams['yk_counter'] && $otpParams['yk_use'] > $localParams['yk_use']) if ($otpParams['yk_counter'] == $localParams['yk_counter'] && $otpParams['yk_use'] > $localParams['yk_use'])
{ {
$ts = ($otpParams['yk_high'] << 16) + $otpParams['yk_low']; $ts = ($otpParams['yk_high'] << 16) + $otpParams['yk_low'];
$seenTs = ($localParams['yk_high'] << 16) + $localParams['yk_low']; $seenTs = ($localParams['yk_high'] << 16) + $localParams['yk_low'];
$tsDiff = $ts - $seenTs; $tsDiff = $ts - $seenTs;
$tsDelta = $tsDiff * TS_SEC; $tsDelta = $tsDiff * TS_SEC;
$now = time(); $now = time();
$elapsed = $now - $localParams['modified']; $elapsed = $now - $localParams['modified'];
$deviation = abs($elapsed - $tsDelta); $deviation = abs($elapsed - $tsDelta);
// Time delta server might verify multiple OTPS in a row. In such case validation server doesn't // Time delta server might verify multiple OTPS in a row. In such case validation server doesn't
// have time to tick a whole second and we need to avoid division by zero. // have time to tick a whole second and we need to avoid division by zero.
if ($elapsed != 0) if ($elapsed != 0)
{ {
$percent = $deviation/$elapsed; $percent = $deviation/$elapsed;
} }
else else
{ {
$percent = 1; $percent = 1;
} }
$myLog->log(LOG_INFO, 'Timestamp', array( $myLog->log(LOG_INFO, 'Timestamp', array(
'seen' => $seenTs, 'seen' => $seenTs,
'this' => $ts, 'this' => $ts,
'delta' => $tsDiff, 'delta' => $tsDiff,
'secs' => $tsDelta, 'secs' => $tsDelta,
'accessed' => sprintf('%s (%s)', $localParams['modified'], date('Y-m-d H:i:s', $localParams['modified'])), 'accessed' => sprintf('%s (%s)', $localParams['modified'], date('Y-m-d H:i:s', $localParams['modified'])),
'now' => sprintf('%s (%s)', $now, date('Y-m-d H:i:s', $now)), 'now' => sprintf('%s (%s)', $now, date('Y-m-d H:i:s', $now)),
'elapsed' => $elapsed, 'elapsed' => $elapsed,
'deviation' => sprintf('%s secs or %s%%', $deviation, round(100 * $percent)), 'deviation' => sprintf('%s secs or %s%%', $deviation, round(100 * $percent)),
)); ));
if ($deviation > TS_ABS_TOLERANCE && $percent > TS_REL_TOLERANCE) if ($deviation > TS_ABS_TOLERANCE && $percent > TS_REL_TOLERANCE)
{ {
$myLog->log(LOG_NOTICE, 'OTP failed phishing test'); $myLog->log(LOG_NOTICE, 'OTP failed phishing test');
// FIXME // FIXME
// This was wrapped around if (0). should we nuke or enable? // This was wrapped around if (0). should we nuke or enable?
// sendResp(S_DELAYED_OTP, $myLog, $apiKey, $extra); // sendResp(S_DELAYED_OTP, $myLog, $apiKey, $extra);
} }
} }
/** /**
@ -477,14 +452,14 @@ if ($otpParams['yk_counter'] == $localParams['yk_counter'] && $otpParams['yk_use
if ($protocol_version >= 2.0) if ($protocol_version >= 2.0)
{ {
$extra['sl'] = $sl_success_rate; $extra['sl'] = $sl_success_rate;
} }
if ($timestamp == 1) if ($timestamp == 1)
{ {
$extra['timestamp'] = ($otpParams['yk_high'] << 16) + $otpParams['yk_low']; $extra['timestamp'] = ($otpParams['yk_high'] << 16) + $otpParams['yk_low'];
$extra['sessioncounter'] = $otpParams['yk_counter']; $extra['sessioncounter'] = $otpParams['yk_counter'];
$extra['sessionuse'] = $otpParams['yk_use']; $extra['sessionuse'] = $otpParams['yk_use'];
} }
sendResp(S_OK, $myLog, $apiKey, $extra); sendResp(S_OK, $myLog, $apiKey, $extra);