|
@@ -5,7 +5,7 @@
|
|
* @category Auth
|
|
* @category Auth
|
|
* @package Auth_Yubico
|
|
* @package Auth_Yubico
|
|
* @author Simon Josefsson <simon@yubico.com>, Olov Danielson <olov@yubico.com>
|
|
* @author Simon Josefsson <simon@yubico.com>, Olov Danielson <olov@yubico.com>
|
|
- * @copyright 2007-2015 Yubico AB
|
|
|
|
|
|
+ * @copyright 2007-2020 Yubico AB
|
|
* @license https://opensource.org/licenses/bsd-license.php New BSD License
|
|
* @license https://opensource.org/licenses/bsd-license.php New BSD License
|
|
* @version 2.0
|
|
* @version 2.0
|
|
* @link https://www.yubico.com/
|
|
* @link https://www.yubico.com/
|
|
@@ -50,12 +50,6 @@ class Auth_Yubico
|
|
*/
|
|
*/
|
|
var $_key;
|
|
var $_key;
|
|
|
|
|
|
- /**
|
|
|
|
- * URL part of validation server
|
|
|
|
- * @var string
|
|
|
|
- */
|
|
|
|
- var $_url;
|
|
|
|
-
|
|
|
|
/**
|
|
/**
|
|
* List with URL part of validation servers
|
|
* List with URL part of validation servers
|
|
* @var array
|
|
* @var array
|
|
@@ -80,12 +74,24 @@ class Auth_Yubico
|
|
*/
|
|
*/
|
|
var $_response;
|
|
var $_response;
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Number of times we retried in our last validation
|
|
|
|
+ * @var int
|
|
|
|
+ */
|
|
|
|
+ var $_retries;
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Flag whether to verify HTTPS server certificates or not.
|
|
* Flag whether to verify HTTPS server certificates or not.
|
|
* @var boolean
|
|
* @var boolean
|
|
*/
|
|
*/
|
|
var $_httpsverify;
|
|
var $_httpsverify;
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Maximum number of times we will retry transient HTTP errors
|
|
|
|
+ * @var int
|
|
|
|
+ */
|
|
|
|
+ var $_max_retries;
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Constructor
|
|
* Constructor
|
|
*
|
|
*
|
|
@@ -98,39 +104,37 @@ class Auth_Yubico
|
|
* default true)
|
|
* default true)
|
|
* @access public
|
|
* @access public
|
|
*/
|
|
*/
|
|
- public function __construct($id, $key = '', $https = 0, $httpsverify = 1)
|
|
|
|
|
|
+ public function __construct($id, $key = '', $https = 0, $httpsverify = 1, $max_retries = 3)
|
|
{
|
|
{
|
|
$this->_id = $id;
|
|
$this->_id = $id;
|
|
$this->_key = base64_decode($key);
|
|
$this->_key = base64_decode($key);
|
|
$this->_httpsverify = $httpsverify;
|
|
$this->_httpsverify = $httpsverify;
|
|
|
|
+ $this->_max_retries = $max_retries;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
* Specify to use a different URL part for verification.
|
|
* Specify to use a different URL part for verification.
|
|
- * The default is "api.yubico.com/wsapi/verify".
|
|
|
|
|
|
+ * The default is "https://api.yubico.com/wsapi/2.0/verify".
|
|
*
|
|
*
|
|
* @param string $url New server URL part to use
|
|
* @param string $url New server URL part to use
|
|
* @access public
|
|
* @access public
|
|
|
|
+ * @deprecated
|
|
*/
|
|
*/
|
|
function setURLpart($url)
|
|
function setURLpart($url)
|
|
{
|
|
{
|
|
- $this->_url = $url;
|
|
|
|
|
|
+ $this->_url_list = array($url);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
* Get next URL part from list to use for validation.
|
|
* Get next URL part from list to use for validation.
|
|
*
|
|
*
|
|
- * @return mixed string with URL part of false if no more URLs in list
|
|
|
|
|
|
+ * @return mixed string with URL part or false if no more URLs in list
|
|
* @access public
|
|
* @access public
|
|
*/
|
|
*/
|
|
function getNextURLpart()
|
|
function getNextURLpart()
|
|
{
|
|
{
|
|
if ($this->_url_list) $url_list=$this->_url_list;
|
|
if ($this->_url_list) $url_list=$this->_url_list;
|
|
- else $url_list=array('https://api.yubico.com/wsapi/2.0/verify',
|
|
|
|
- 'https://api2.yubico.com/wsapi/2.0/verify',
|
|
|
|
- 'https://api3.yubico.com/wsapi/2.0/verify',
|
|
|
|
- 'https://api4.yubico.com/wsapi/2.0/verify',
|
|
|
|
- 'https://api5.yubico.com/wsapi/2.0/verify');
|
|
|
|
|
|
+ else $url_list=array('https://api.yubico.com/wsapi/2.0/verify');
|
|
|
|
|
|
if ($this->_url_index>=count($url_list)) return false;
|
|
if ($this->_url_index>=count($url_list)) return false;
|
|
else return $url_list[$this->_url_index++];
|
|
else return $url_list[$this->_url_index++];
|
|
@@ -178,6 +182,17 @@ class Auth_Yubico
|
|
return $this->_response;
|
|
return $this->_response;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Return the number of retries that were used in the last validation
|
|
|
|
+ *
|
|
|
|
+ * @return int Number of retries
|
|
|
|
+ * @access public
|
|
|
|
+ */
|
|
|
|
+ function getRetries()
|
|
|
|
+ {
|
|
|
|
+ return $this->_retries;
|
|
|
|
+ }
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Parse input string into password, yubikey prefix,
|
|
* Parse input string into password, yubikey prefix,
|
|
* ciphertext, and OTP.
|
|
* ciphertext, and OTP.
|
|
@@ -238,6 +253,26 @@ class Auth_Yubico
|
|
return $param_array;
|
|
return $param_array;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ function _make_curl_handle($query, $timeout=null)
|
|
|
|
+ {
|
|
|
|
+ flush();
|
|
|
|
+ $handle = curl_init($query);
|
|
|
|
+ curl_setopt($handle, CURLOPT_USERAGENT, "PEAR Auth_Yubico");
|
|
|
|
+ curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1);
|
|
|
|
+ if (!$this->_httpsverify) {
|
|
|
|
+ curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, 0);
|
|
|
|
+ curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, 0);
|
|
|
|
+ }
|
|
|
|
+ curl_setopt($handle, CURLOPT_FAILONERROR, true);
|
|
|
|
+ /* If timeout is set, we better apply it here as well
|
|
|
|
+ * in case the validation server fails to follow it. */
|
|
|
|
+ if ($timeout) {
|
|
|
|
+ curl_setopt($handle, CURLOPT_TIMEOUT, $timeout);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return $handle;
|
|
|
|
+ }
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Verify Yubico OTP against multiple URLs
|
|
* Verify Yubico OTP against multiple URLs
|
|
* Protocol specification 2.0 is used to construct validation requests
|
|
* Protocol specification 2.0 is used to construct validation requests
|
|
@@ -252,12 +287,19 @@ class Auth_Yubico
|
|
* and 100 or "fast" or "secure".
|
|
* and 100 or "fast" or "secure".
|
|
* @param int $timeout Max number of seconds to wait
|
|
* @param int $timeout Max number of seconds to wait
|
|
* for responses
|
|
* for responses
|
|
|
|
+ * @param int $max_retries Max number of times we will retry on
|
|
|
|
+ * transient errors.
|
|
* @return mixed PEAR error on error, true otherwise
|
|
* @return mixed PEAR error on error, true otherwise
|
|
* @access public
|
|
* @access public
|
|
*/
|
|
*/
|
|
function verify($token, $use_timestamp=null, $wait_for_all=False,
|
|
function verify($token, $use_timestamp=null, $wait_for_all=False,
|
|
- $sl=null, $timeout=null)
|
|
|
|
|
|
+ $sl=null, $timeout=null, $max_retries=null)
|
|
{
|
|
{
|
|
|
|
+ /* If maximum retries is not set, default from instance */
|
|
|
|
+ if (is_null($max_retries)) {
|
|
|
|
+ $max_retries = $this->_max_retries;
|
|
|
|
+ }
|
|
|
|
+
|
|
/* Construct parameters string */
|
|
/* Construct parameters string */
|
|
$ret = $this->parsePasswordOTP($token);
|
|
$ret = $this->parsePasswordOTP($token);
|
|
if (!$ret) {
|
|
if (!$ret) {
|
|
@@ -284,10 +326,13 @@ class Auth_Yubico
|
|
}
|
|
}
|
|
|
|
|
|
/* Generate and prepare request. */
|
|
/* Generate and prepare request. */
|
|
- $this->_lastquery=null;
|
|
|
|
|
|
+ $this->_lastquery = null;
|
|
|
|
+ $this->_retries = 0;
|
|
$this->URLreset();
|
|
$this->URLreset();
|
|
|
|
+
|
|
$mh = curl_multi_init();
|
|
$mh = curl_multi_init();
|
|
$ch = array();
|
|
$ch = array();
|
|
|
|
+ $retries = array();
|
|
while($URLpart=$this->getNextURLpart())
|
|
while($URLpart=$this->getNextURLpart())
|
|
{
|
|
{
|
|
$query = $URLpart . "?" . $parameters;
|
|
$query = $URLpart . "?" . $parameters;
|
|
@@ -295,21 +340,11 @@ class Auth_Yubico
|
|
if ($this->_lastquery) { $this->_lastquery .= " "; }
|
|
if ($this->_lastquery) { $this->_lastquery .= " "; }
|
|
$this->_lastquery .= $query;
|
|
$this->_lastquery .= $query;
|
|
|
|
|
|
- $handle = curl_init($query);
|
|
|
|
- curl_setopt($handle, CURLOPT_USERAGENT, "PEAR Auth_Yubico");
|
|
|
|
- curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1);
|
|
|
|
- if (!$this->_httpsverify) {
|
|
|
|
- curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, 0);
|
|
|
|
- curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, 0);
|
|
|
|
- }
|
|
|
|
- curl_setopt($handle, CURLOPT_FAILONERROR, true);
|
|
|
|
- /* If timeout is set, we better apply it here as well
|
|
|
|
- in case the validation server fails to follow it.
|
|
|
|
- */
|
|
|
|
- if ($timeout) curl_setopt($handle, CURLOPT_TIMEOUT, $timeout);
|
|
|
|
|
|
+ $handle = $this->_make_curl_handle($query, $timeout);
|
|
curl_multi_add_handle($mh, $handle);
|
|
curl_multi_add_handle($mh, $handle);
|
|
|
|
|
|
$ch[(int)$handle] = $handle;
|
|
$ch[(int)$handle] = $handle;
|
|
|
|
+ $retries[$query] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/* Execute and read request. */
|
|
/* Execute and read request. */
|
|
@@ -319,19 +354,20 @@ class Auth_Yubico
|
|
do {
|
|
do {
|
|
/* Let curl do its work. */
|
|
/* Let curl do its work. */
|
|
while (($mrc = curl_multi_exec($mh, $active))
|
|
while (($mrc = curl_multi_exec($mh, $active))
|
|
- == CURLM_CALL_MULTI_PERFORM)
|
|
|
|
- ;
|
|
|
|
|
|
+ == CURLM_CALL_MULTI_PERFORM) {
|
|
|
|
+ curl_multi_select($mh);
|
|
|
|
+ }
|
|
|
|
|
|
while ($info = curl_multi_info_read($mh)) {
|
|
while ($info = curl_multi_info_read($mh)) {
|
|
|
|
+ $cinfo = curl_getinfo ($info['handle']);
|
|
if ($info['result'] == CURLE_OK) {
|
|
if ($info['result'] == CURLE_OK) {
|
|
-
|
|
|
|
/* We have a complete response from one server. */
|
|
/* We have a complete response from one server. */
|
|
|
|
|
|
$str = curl_multi_getcontent($info['handle']);
|
|
$str = curl_multi_getcontent($info['handle']);
|
|
- $cinfo = curl_getinfo ($info['handle']);
|
|
|
|
|
|
|
|
if ($wait_for_all) { # Better debug info
|
|
if ($wait_for_all) { # Better debug info
|
|
- $this->_response .= 'URL=' . $cinfo['url'] ."\n"
|
|
|
|
|
|
+ $this->_response .= 'URL=' . $cinfo['url'] . ' HTTP_CODE='
|
|
|
|
+ . $cinfo['http_code'] . "\n"
|
|
. $str . "\n";
|
|
. $str . "\n";
|
|
}
|
|
}
|
|
|
|
|
|
@@ -401,7 +437,7 @@ class Auth_Yubico
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- if (!$wait_for_all && ($valid || $replay))
|
|
|
|
|
|
+ if (!$wait_for_all && ($valid || $replay))
|
|
{
|
|
{
|
|
/* We have status=OK or status=REPLAYED_OTP, return. */
|
|
/* We have status=OK or status=REPLAYED_OTP, return. */
|
|
foreach ($ch as $h) {
|
|
foreach ($ch as $h) {
|
|
@@ -413,12 +449,35 @@ class Auth_Yubico
|
|
if ($valid) return true;
|
|
if ($valid) return true;
|
|
return PEAR::raiseError($status);
|
|
return PEAR::raiseError($status);
|
|
}
|
|
}
|
|
-
|
|
|
|
- curl_multi_remove_handle($mh, $info['handle']);
|
|
|
|
- curl_close($info['handle']);
|
|
|
|
- unset ($ch[(int)$info['handle']]);
|
|
|
|
|
|
+ } else {
|
|
|
|
+ /* Some kind of error, but def. not a 200 response */
|
|
|
|
+ /* No status= in response body */
|
|
|
|
+ $http_status_code = (int)$cinfo['http_code'];
|
|
|
|
+ $query = $cinfo['url'];
|
|
|
|
+ if ($http_status_code == 400 ||
|
|
|
|
+ ($http_status_code >= 500 && $http_status_code < 600)) {
|
|
|
|
+ /* maybe retry */
|
|
|
|
+ if ($retries[$query] < $max_retries) {
|
|
|
|
+ $retries[$query]++; // for this server
|
|
|
|
+ $this->_retries++; // for this validation attempt
|
|
|
|
+
|
|
|
|
+ $newhandle = $this->_make_curl_handle($query, $timeout);
|
|
|
|
+
|
|
|
|
+ curl_multi_add_handle($mh, $newhandle);
|
|
|
|
+ $ch[(int)$newhandle] = $newhandle;
|
|
|
|
+
|
|
|
|
+ // Loop back up to curl_multi_exec, even if this
|
|
|
|
+ // was the last handle and curl_multi_exec _was_
|
|
|
|
+ // no longer active, it's active again now we've
|
|
|
|
+ // added a retry.
|
|
|
|
+ $active = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- curl_multi_select($mh);
|
|
|
|
|
|
+ /* Done with this handle */
|
|
|
|
+ curl_multi_remove_handle($mh, $info['handle']);
|
|
|
|
+ curl_close($info['handle']);
|
|
|
|
+ unset ($ch[(int)$info['handle']]);
|
|
}
|
|
}
|
|
} while ($active);
|
|
} while ($active);
|
|
|
|
|