<?php
/*********************************************************************
*
* MultiOTP PHP class - Strong two-factor authentication PHP class
* http://www.multiotp.net
*
* Donation are always welcome! Please check http://www.multiotp.net
* and you will find the magic button ;-)
*
* The MultiOTP class is a strong authentication class in pure PHP
* that supports the following algorithms:
* - mOTP (http://motp.sourceforge.net)
* - OATH/HOTP RFC 4226 (http://www.ietf.org/rfc/rfc4226.txt)
* - OATH/TOTP HOTPTimeBased RFC 4226 extension
*
* This class can be used as is in your own PHP project, but it can also be
* used easily as an external authentication provider with at least the
* following RADIUS servers (using the multiotp command line script):
* - TekRADIUS, a free Radius server for Windows with MS-SQL backend
* (http:/www.tekradius.com)
* - TekRADIUS LT, a free Radius server for Windows with SQLite backend
* (http:/www.tekradius.com)
* - FreeRADIUS, a free Radius server implementation for Linux
* and *nix environments (http://freeradius.org)
*
*
* LICENCE
*
* Copyright (c) 2010, SysCo systemes de communication sa
* SysCo (tm) is a trademark of SysCo systemes de communication sa
* (http://www.sysco.ch)
* All rights reserved.
*
* This file is part of the MultiOTP PHP class
*
* MultiOTP PHP class is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* MultiOTP PHP class is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with MultiOTP PHP class.
* If not, see <http://www.gnu.org/licenses/>.
*
*
* @author: SysCo/al
* @since CreationDate: 2010-06-08
* @copyright (c) 2010 by SysCo systemes de communication sa
* @version $LastChangedRevision: 3.0.0 $
* @version $LastChangedDate: 2010-09-02 $
* @version $LastChangedBy: SysCo/al $
* @link $HeadURL: multiotp.class.php $
* @link http://www.multiotp.net
* @link developer@sysco.ch
* Language: PHP 4.4.4 or higher
*
*
* Usage
*
* require_once('multiotp.class.php');
* $multiotp = new Multiotp();
* $multiotp->SetUser('user);
* $result = $multiotp->CheckToken('token');
*
*
* Examples
*
* Example 1
* <?php
* require_once('multiotp.class.php');
* $multiotp = new Multiotp();
* $multiotp->SetUser('user');
* if ($multiotp->CheckToken('token'))
* {
* echo "Authentication accepted.";
* }
* else
* {
* echo "Authentication rejected.";
* }
* ?>
*
* Example 2
* <?php
* require_once('multiotp.class.php');
* $multiotp = new Multiotp();
* // Set a specific encryption key for the tokens and users files
* $multiotp->SetEncryptionKey('MyEncryptionKey');
* // Set specific attributes to encrypt in the flat files
* $multiotp->SetAttributesToEncrypt('*user_pin*token_seed*token_serial*');
* $multiotp->SetUser('user');
* if ($multiotp->CheckToken('token'))
* {
* echo "Authentication accepted.";
* }
* else
* {
* echo "Authentication rejected.";
* }
* ?>
*
* For examples on how to integrate it with radius servers, please have a look
* to the readme.txt file or read the header of the multiotp.cli.header.php file.
*
*
* External files created
*
* Users database files in the subfolder called users
* Tokens database files in the subfolder called tokens
*
*
* External file needed
*
* Users database files in the subfolder called users
* Tokens database files in the subfolder called tokens
*
*
* Special issues
*
* If you need specific developements concerning strong authentication,
* do not hesistate to contact us per email at developer@sysco.ch.
*
*
* Other related ressources
*
* Mobile-OTP: Strong Two-Factor Authentication with Mobile Phones:
* http://motp.sourceforge.net
*
* The Initiative for Open Authentication:
* http://www.openauthentication.org
*
* TekRADIUS, a free RADIUS server for windows, available in two versions (MS-SQL and SQLite):
* http://www.tekradius.com
*
* FreeRADIUS, a free Radius server implementation for Linux and *nix environments:
* http://www.freeradius.org
*
* Additional Portable Symmetric Key Container (PSKC) Algorithm Profiles
* http://tools.ietf.org/html/draft-hoyer-keyprov-pskc-algorithm-profiles-00
*
*
* Users feedbacks and comments
*
* 2010-08-20 BirdNet, C. Christophi
* Documentation enhancement proposal for the TekRADIUS part, thanks !
*
* 2010-07-19 SysCo/al
* Well, as requested by some users, the new "class" design is done, enjoy !
*
*
* Todos
*
* Add more comments in the main class file
* Add more information in the log
* Add more verbose information in the log
*
*
* Change Log
*
* 2010-09-02 3.0.0 SysCo/al Adding tokens handling support, including importing XML tokens definition file
* (http://tools.ietf.org/html/draft-hoyer-keyprov-pskc-algorithm-profiles-00)
* Enhanced flat database file format (multiotp is still compatible with old versions)
* Internal method SetDataReadFlag renamed to SetUserDataReadFlag
* Internal method GetDataReadFlag renamed to GetUserDataReadFlag
* 2010-08-21 2.0.4 SysCo/al Enhancement in order to use an alternate php "compiler" for Windows command line
* Documentation enhancement
* 2010-08-18 2.0.3 SysCo/al Minor notice fix
* 2010-07-21 2.0.2 SysCo/al Fix to create correctly the folders "uaers" and "log" if needed
* 2010-07-19 2.0.1 SysCo/al Foreach was not working well in "compiled" Windows command line
* 2010-07-19 2.0.0 SysCo/al New design using a class, mOTP support, cleaning of the code
* 2010-06-15 1.1.5 SysCo/al Adding OATH/TOTP support
* 2010-06-15 1.1.4 SysCo/al Project renamed to multiotp to avoid overlapping
* 2010-06-08 1.1.3 SysCo/al Typo in script folder detection
* 2010-06-08 1.1.2 SysCo/al Typo in variable name
* 2010-06-08 1.1.1 SysCo/al Status bar during resynchronization
* 2010-06-08 1.1.0 SysCo/al Fix in the example, distribution not compressed
* 2010-06-07 1.0.0 SysCo/al Initial implementation
*
*********************************************************************/
/*********************************************************************
*
* Name: Multiotp
* MultiOTP PHP class
*
* Creation 2010-07-18
* Update 2010-09-02
* @package multiotp
* @version 3.0.0
* @author SysCo/al
*
*********************************************************************/
class Multiotp
{
var $_version; // Current version of the library
var $_date; // Current date of the library
var $_copyright; // Copyright message of the library
var $_website; // Website of the library
var $_valid_algorithms; // String containing valid algorithms to be used
var $_attributes_to_encrypt; // Attributes to encrypt in the flat files
var $_encryption_key; // Symetric encryption key for the users files and the tokens files
var $_errors_text; // An array containing errors text description
var $_user; // Current user, case insensitive
var $_user_data; // An array with all the user related info
var $_user_data_read_flag; // Indicate if the user data has been read from the database file
var $_users_folder; // Folder where users definition files are stored
var $_token; // Current token, case insensitive
var $_token_data; // An array with all the user related info
var $_token_data_read_flag; // Indicate if the user data has been read from the database file
var $_tokens_folder; // Folder where users definition files are stored
var $_log_folder; // Folder where log file is written
var $_log_file_name; // Name of the log file
var $_log_flag; // Enable or disable the log
var $_log_header_written; // Internal flag to know if the header was already written or not in the log file
var $_log_verbose_flag; // Enable or disable the verbose mode for the log
var $_max_event_window; // Maximum event window to be accepted
var $_max_event_resync_window; // Maximum event window to be accepted for resync
var $_max_time_window; // Maximum time window to be accepted, in seconds (+/-)
var $_max_time_resync_window; // Maximum time window to be accepted for resync (+/-)
var $_max_delayed_failures; // Number of consecutive failures before delaying the next request
var $_failure_delayed_time; // Number of seconds to wait before releasing the failure delay
var $_max_block_failures; // Number of consecutive failures before blocking the token. A blocked token needs a resync.
/*********************************************************************
*
* Name: Multiotp
* Short description: Multiotp class constructor
*
* Creation 2010-07-18
* Update 2010-09-02
* @package multiotp
* @version 3.0.0
* @author SysCo/al
* @return void
*********************************************************************/
function Multiotp()
{
$this->_version = "3.0.0";
$this->_date = "2010-09-02";
$this->_copyright = "(c) 2010 SysCo systemes de communication sa";
$this->_website = "http://www.multiotp.net";
$this->_log_header_written = FALSE; // Flag to know if the header has already been written in the log file or not
$this->_valid_algorithms = '*mOTP*HOTP*TOTP*';
$this->_attributes_to_encrypt = '*user_pin*token_seed*';
$this->_encryption_key = 'MuLtIoTpEnCrYpTiOn';
$this->_log_file_name = "multiotp.log";
$this->_log_flag = FALSE;
$this->_log_verbose_flag = FALSE;
$this->_max_event_window = 100; // Number of events accepted for event based algorithm(s) token
$this->_max_event_resync_window = 10000; // NUmber of events accepted to sync event based algorithm(s) token
$this->_max_time_window = 8000; // a little bit more than +/- 2 hours
$this->_max_time_resync_window = 90000; // more than +/- one day
$this->_max_delayed_failures = 3; // Number of authorized failures before locking for a fixed delay
$this->_failure_delayed_time = 300; // Locking delay between two trials after "_max_delayed_failures" failures
$this->_max_block_failures = 6; // Number of login trial failures that will block the user
$this->_user = ""; // Name of the current user to authenticate
$this->_user_data_read_flag = FALSE; // Flag to know if the data concerning the current user has been read
$this->_users_folder = ""; // Folders which contain the users flat files
$this->_log_folder = ""; // Folder which contains the log file
// Initialize the user array
// User pin
$this->_user_data['user_pin'] = '';
// Algorithm used by the token
$this->_user_data['algorithm'] = '';
// Time interval in seconds for a time based token
$this->_user_data['time_interval'] = 0;
// Number of digits returned by the token
$this->_user_data['number_of_digits'] = 6;
// Request the pin as a prefix of the rturned token value
$this->_user_data['request_prefix_pin'] = 0;
// Last successful login
$this->_user_data['last_login'] = 0;
// Last successful event
$this->_user_data['last_event'] = -1;
// Last error login
$this->_user_data['last_error'] = 0;
// Delta time in seconds for a time based token
$this->_user_data['delta_time'] = 0;
// Key identification number, if any
$this->_user_data['key_id'] = '';
// Token seed, default set to the RFC test seed
$this->_user_data['token_seed'] = '3132333435363738393031323334353637383930';
// Login error counter
$this->_user_data['error_counter'] = 0;
// Token locked
$this->_user_data['locked'] = 0;
// Initialize the errors text array
$this->_errors_text[0] = "OK: Token accepted";
$this->_errors_text[11] = "INFO: User successfully created or updated";
$this->_errors_text[12] = "INFO: User successfully deleted";
$this->_errors_text[13] = "INFO: User PIN code successfully changed";
$this->_errors_text[14] = "INFO: Token has been resynchronized successfully";
$this->_errors_text[15] = "INFO: XML tokens definition file successfully imported";
$this->_errors_text[19] = "INFO: Requested operation successfully done";
$this->_errors_text[21] = "ERROR: User doesn't exist";
$this->_errors_text[22] = "ERROR: User already exists";
$this->_errors_text[23] = "ERROR: Invalid algorithm";
$this->_errors_text[24] = "ERROR: User locked (too many tries)";
$this->_errors_text[25] = "ERROR: User delayed (too many tries, but still a hope in a few minutes)";
$this->_errors_text[26] = "ERROR: The time based token has already been used";
$this->_errors_text[27] = "ERROR: Resynchronization of the token has failed";
$this->_errors_text[28] = "ERROR: Unable to write the changes in the file";
$this->_errors_text[29] = "ERROR: Token doesn't exist";
$this->_errors_text[30] = "ERROR: At least one parameter is missing";
$this->_errors_text[31] = "ERROR: XML tokens definition file doesn't exist";
$this->_errors_text[32] = "ERROR: XML tokens definition file not successfully imported";
$this->_errors_text[99] = "ERROR: Authentication failed (and other possible unknown errors)";
}
/*********************************************************************
*
* Name: ShowStatus
* Short description: Show a progress status bar in the console
*
* Creation 2010
* Source: http://brian.moonspot.net/status_bar.php.txt
* @author Copyright (c) 2010, dealnews.com, Inc. - All rights reserved.
*
* @param int $done how many items are completed
* @param int $total how many items are to be done total
* @param int $size optional size of the status bar
* @return void
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of dealnews.com, Inc. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* Usage
*
* for($x=1;$x<=100;$x++)
* {
* ShowStatus($x, 100);
* usleep(100000);
* }
*
* @param int $done how many items are completed
* @param int $total how many items are to be done total
* @param int $size optional size of the status bar
* @return void
*
*********************************************************************/
function ShowStatus($done, $total, $size=30)
{
static $start_time;
// if we go over our bound, just ignore it
if($done > $total) return;
if(empty($start_time)) $start_time=time();
$now = time();
$perc=(double)($done/$total);
$bar=floor($perc*$size);
$status_bar="\r[";
$status_bar.=str_repeat("=", $bar);
if($bar<$size)
{
$status_bar.=">";
// $status_bar.=str_repeat(" ", $size-$bar);
$status_bar.=str_repeat("-", $size-$bar);
}
else
{
$status_bar.="=";
}
$disp=number_format($perc*100, 0);
$status_bar.="] $disp% $done/$total";
$rate = ($now-$start_time)/$done;
$left = $total - $done;
$eta = round($rate * $left, 2);
$elapsed = $now - $start_time;
// $status_bar.= " remaining: ".number_format($eta)." sec. elapsed: ".number_format($elapsed)." sec.";
echo "$status_bar ";
flush();
// when done, send a newline
if($done == $total)
{
echo "\n";
}
}
// Defining this custom function if hash_hmac is not available in the actual configuration
function HashHmac($algo, $data, $key, $raw_output = false)
{
$algo = strtolower($algo);
$pack = 'H'.strlen($algo('test'));
$size = 64;
$opad = str_repeat(chr(0x5C), $size);
$ipad = str_repeat(chr(0x36), $size);
if (strlen($key) > $size)
{
$key = str_pad(pack($pack, $algo($key)), $size, chr(0x00));
}
else
{
$key = str_pad($key, $size, chr(0x00));
}
for ($i = 0; $i < strlen($key) - 1; $i++)
{
$opad[$i] = $opad[$i] ^ $key[$i];
$ipad[$i] = $ipad[$i] ^ $key[$i];
}
$output = $algo($opad.pack($pack, $algo($ipad.$data)));
return ($raw_output) ? pack($pack, $output) : $output;
}
// Defining this custom function if str_split is not available in the actual configuration
function StrSplit($string, $length = 1)
{
if ($length <= 0)
{
trigger_error(__FUNCTION__."(): The the length of each segment must be greater then zero:", E_USER_WARNING);
return false;
}
$splitted = array();
$str_length = strlen($string);
$i = 0;
if ($length == 1)
{
while ($str_length--)
{
$splitted[$i] = $string[$i++];
}
}
else
{
$j = $i;
while ($str_length > 0)
{
$splitted[$j++] = substr($string, $i, $length);
$str_length -= $length;
$i += $length;
}
}
return $splitted;
}
function GetVersion()
{
return $this->_version;
}
function GetDate()
{
return $this->_date;
}
function GetCopyright()
{
return $this->_copyright;
}
function GetWebsite()
{
return $this->_website;
}
function SetMaxTimeWindow($time_window)
{
$this->_max_time_window = intval($time_window);
}
function GetMaxTimeWindow()
{
return $this->_max_time_window;
}
function SetMaxTimeResyncWindow($time_resync_window)
{
$this->_max_time_resync_window = intval($time_resync_window);
}
function GetMaxTimeResyncWindow()
{
return $this->_max_time_resync_window;
}
function SetMaxEventWindow($event_window)
{
$this->_max_time_window = intval($event_window);
}
function GetMaxEventWindow()
{
return $this->_max_event_window;
}
function SetMaxEventResyncWindow($event_resync_window)
{
$this->_max_event_resync_window = intval($event_resync_window);
}
function GetMaxEventResyncWindow()
{
return $this->_max_event_resync_window;
}
function SetMaxBlockFailures($max_failures)
{
$this->_max_block_failures = $max_failures;
}
function GetMaxBlockFailures()
{
return $this->_max_block_failures;
}
/*********************************************************************
*
* Name: ComputeMotp
* Short description: Compute the mOTP result
*
* Creation 2010-06-07
* Update 2010-07-19
* @package multiotp
* @version 2.0.0
* @author SysCo/al
*
* @param string $seed_and_pin Key used to compute the mOTP result
* @param int $timestep Timestep used to calculate the token
* @param int $token_size Token size
* @return string mOTP result
*
*********************************************************************/
function ComputeMotp($seed_and_pin, $timestep, $token_size)
{
return substr(md5($timestep.$seed_and_pin),0,$token_size);
}
/*********************************************************************
*
* Name: ComputeOathHotp
* Short description: Compute the OATH defined hash
*
* Creation 2010-06-07
* Update 2010-07-19
* @package multiotp
* @version 2.0.0
* @author SysCo/al
*
* @param string $key Key used to compute the OATH hash
* @param int $counter Counter position
* @return string Full OATH hash
*
*********************************************************************/
function ComputeOathHotp($key, $counter)
{
// Counter
//the counter value can be more than one byte long, so we need to go multiple times
$cur_counter = array(0,0,0,0,0,0,0,0);
for($i=7;$i>=0;$i--)
{
$cur_counter[$i] = pack ('C*', $counter);
$counter = $counter >> 8;
}
$bin_counter = implode($cur_counter);
// Pad to 8 chars
if (strlen ($bin_counter) < 8)
{
$bin_counter = str_repeat(chr(0), 8 - strlen($bin_counter)) . $bin_counter;
}
// HMAC hash
$hash = $this->HashHmac('sha1', $bin_counter, $key);
return $hash;
}
/*********************************************************************
*
* Name: ComputeOathTruncate
* Short description: Truncate the result as defined by the OATH
*
* Creation 2010-06-07
* Update 2010-07-19
* @package multiotp
* @version 2.0.0
* @author SysCo/al
*
* @param string $hash Full OATH hash to be truncated
* @param int $length Length of the result token
* @return string Truncated OATH hash
*
*********************************************************************/
function ComputeOathTruncate($hash, $length = 6)
{
// Convert to decimal
foreach($this->StrSplit($hash,2) as $hex)
{
$hmac_result[]=hexdec($hex);
}
// Find offset
$offset = $hmac_result[19] & 0xf;
// Algorithm from RFC
return
substr(str_repeat('0',$length).((
(($hmac_result[$offset+0] & 0x7f) << 24 ) |
(($hmac_result[$offset+1] & 0xff) << 16 ) |
(($hmac_result[$offset+2] & 0xff) << 8 ) |
($hmac_result[$offset+3] & 0xff)
) % pow(10,$length)),-$length); // & 0x7FFFFFFF before the pow()
}
/*********************************************************************
*
* Name: ConvertHex2Bin
* Short description: Convert hexadecimal string to binary content
*
* Creation 2010-06-07
* Update 2010-07-19
* @package multiotp
* @version 2.0.0
* @author SysCo/al
*
* @param string $hexdata Full string in hex format to convert
* @return string Converted binary content
*
*********************************************************************/
function ConvertHex2Bin($hexdata)
{
$bindata = "";
for ($i=0;$i<strlen($hexdata);$i+=2)
{
$bindata.=chr(hexdec(substr($hexdata,$i,2)));
}
return $bindata;
}
function SetEncryptionKey($key)
{
$this->_encryption_key = $key;
}
function Encrypt($key, $value)
{
$result = '';
if (strlen($this->_encryption_key) > 0)
{
for ($i=0; $i < strlen($value); $i++)
{
$encrypt_char = ord(substr($this->_encryption_key,$i % strlen($this->_encryption_key),1));
$key_char = ord(substr($key,$i % strlen($key),1));
$result .= chr($encrypt_char^$key_char^ord(substr($value,$i,1)));
}
$result = base64_encode($result);
}
else
{
$result = $value;
}
return $result;
}
function Decrypt($key, $value)
{
$result = '';
if (strlen($this->_encryption_key) > 0)
{
$value_to_decrypt = base64_decode($value);
for ($i=0; $i < strlen($value_to_decrypt); $i++)
{
$encrypt_char = ord(substr($this->_encryption_key,$i % strlen($this->_encryption_key),1));
$key_char = ord(substr($key,$i % strlen($key),1));
$result .= chr($encrypt_char^$key_char^ord(substr($value_to_decrypt,$i,1)));
}
}
else
{
$result = $value;
}
return $result;
}
function SetMaxDelayedFailures($failures)
{
$this->_max_delayed_failures = $failures;
}
function GetMaxDelayedFailures()
{
return $this->_max_delayed_failures;
}
function SetMaxDelayedTime($seconds)
{
$this->_failure_delayed_time = $seconds;
}
function GetMaxDelayedTime()
{
return $this->_failure_delayed_time;
}
function SetUser($user)
{
$this->_user = $user;
$this->SetUserDataReadFlag(FALSE);
}
function GetUser()
{
return $this->_user;
}
function SetUserDataReadFlag($flag)
{
$this->_user_data_read_flag = $flag;
}
function GetUserDataReadFlag()
{
return $this->_user_data_read_flag;
}
function SetUserPrefixPin($value)
{
$this->_user_data['request_prefix_pin'] = $value;
}
function GetUserPrefixPin()
{
return $this->_user_data['request_prefix_pin'];
}
function SetUserAlgorithm($algorithm)
{
$result = FALSE;
if (FALSE === strpos(strtoupper($this->_valid_algorithms), strtoupper('*'.$algorithm.'*')))
{
$this->WriteLog("Error: ".$algorithm." algorithm is unknown");
}
else
{
$this->_user_data['algorithm'] = $algorithm;
$result = TRUE;
}
return $result;
}
function GetUserAlgorithm()
{
return $this->_user_data['algorithm'];
}
function SetUserTokenSeed($seed)
{
$this->_user_data['token_seed'] = $seed;
}
function GetUserTokenSeed()
{
return $this->_user_data['token_seed'];
}
function SetUserPin($pin)
{
$this->_user_data['user_pin'] = $pin;
}
function GetUserPin()
{
return $this->_user_data['user_pin'];
}
function SetUserTokenDeltaTime($delta_time)
{
$this->_user_data['delta_time'] = $delta_time;
}
function GetUserTokenDeltaTime()
{
return $this->_user_data['delta_time'];
}
function SetUserKeyId($key_id)
{
$this->_user_data['key_id'] = $key_id;
}
function GetUserKeyId()
{
return $this->_user_data['key_id'];
}
function SetUserTokenNumberOfDigits($number_of_digits)
{
$this->_user_data['number_of_digits'] = $number_of_digits;
}
function GetUserTokenNumberOfDigits()
{
return $this->_user_data['number_of_digits'];
}
function SetUserTokenTimeInterval($interval)
{
if (intval($interval) > 0)
{
$this->_user_data['time_interval'] = intval($interval);
}
}
function GetUserTokenTimeInterval()
{
return $this->_user_data['time_interval'];
}
function SetUserTokenLastEvent($last_event)
{
$this->_user_data['last_event'] = $last_event;
}
function GetUserTokenLastEvent()
{
return $this->_user_data['last_event'];
}
function SetUserTokenLastLogin($time)
{
$this->_user_data['last_login'] = $time;
}
function GetUserTokenLastLogin()
{
return $this->_user_data['last_login'];
}
function SetUserTokenLastError($time)
{
$this->_user_data['last_error'] = $time;
}
function GetUserTokenLastError()
{
return $this->_user_data['last_error'];
}
function SetUserLocked($locked)
{
$this->_user_data['locked'] = $locked;
}
function GetUserLocked()
{
return $this->_user_data['locked'];
}
function SetUserErrorCounter($counter)
{
$this->_user_data['error_counter'] = $counter;
}
function GetUserErrorCounter()
{
return $this->_user_data['error_counter'];
}
function SetToken($token)
{
$this->_token = $token;
$this->SetTokenDataReadFlag(FALSE);
}
function GetToken()
{
return $this->_token;
}
function SetTokenDataReadFlag($flag)
{
$this->_token_data_read_flag = $flag;
}
function GetTokenDataReadFlag()
{
return $this->_token_data_read_flag;
}
function GetScriptFolder()
{
// Detect the current folder, change Windows notation to universal notation if needed
$current_folder = $this->ConvertToUnixPath(getcwd());
$current_script_folder = $this->ConvertToUnixPath($_SERVER["argv"][0]);
if ("" == (trim($current_script_folder)))
{
$current_script_folder = $_SERVER['SCRIPT_FILENAME'];
}
if (FALSE === strpos($current_script_folder,"/"))
{
$current_script_folder_detected = dirname($current_folder."/fake.file");
}
else
{
$current_script_folder_detected = dirname($current_script_folder);
}
if (substr($current_script_folder_detected,-1) != "/")
{
$current_script_folder_detected.="/";
}
return $this->ConvertToWindowsPathIfNeeded($current_script_folder_detected);
}
function ConvertToUnixPath($path)
{
return str_replace("\\","/",$path);
}
function ConvertToWindowsPathIfNeeded($path)
{
$result = $path;
if (FALSE !== strpos($result,":"))
{
$result = str_replace("/","\\",$result);
}
return $result;
}
function SetLogFolder($folder)
{
$new_folder = $this->ConvertToUnixPath($folder);
if (substr($new_folder,-1) != "/")
{
$new_folder.="/";
}
$new_folder = $this->ConvertToWindowsPathIfNeeded($new_folder);
$this->_log_folder = $new_folder;
if (!file_exists($new_folder))
{
@mkdir($new_folder);
}
}
function GetLogFolder()
{
if ("" == $this->_log_folder)
{
$this->SetLogFolder($this->GetScriptFolder()."log/");
}
return $this->ConvertToWindowsPathIfNeeded($this->_log_folder);
}
function WriteLog($log_info)
{
if ($this->_log_flag)
{
if (!file_exists($this->GetLogFolder()))
{
@mkdir($this->_log_flag);
}
$log_file_handle = fopen($this->GetLogFolder().$this->_log_file_name,"ab+");
if (!$this->_log_header_written)
{
fwrite($log_file_handle,str_repeat("=",40)."\n");
fwrite($log_file_handle,'multiotp '.$this->GetVersion()."\n");
$this->_log_header_written = TRUE;
}
fwrite($log_file_handle,date("Y-m-d H:i:s")." ".$log_info."\n");
fclose($log_file_handle);
}
}
function EnableLog()
{
$this->_log_flag = TRUE;
if ("" == $this->_log_folder)
{
$this->SetLogFolder($this->GetScriptFolder()."log/");
}
}
function DisableLog()
{
$this->_log_flag = FALSE;
}
function EnableVerboseLog()
{
$this->EnableLog();
$this->_log_verbose_flag = TRUE;
}
function DisableVerboseLog()
{
$this->_log_verbose_flag = FALSE;
}
function GetVerboseFlag()
{
return $this->_log_verbose_flag;
}
function SetAttributesToEncrypt($attributes_to_encrypt)
{
$this->_attributes_to_encrypt = $attributes_to_encrypt;
}
function GetAttributesToEncrypt()
{
return $this->_attributes_to_encrypt;
}
function SetUsersFolder($folder)
{
$new_folder = $this->ConvertToUnixPath($folder);
if (substr($new_folder,-1) != "/")
{
$new_folder.="/";
}
$new_folder = $this->ConvertToWindowsPathIfNeeded($new_folder);
$this->_users_folder = $new_folder;
if (!file_exists($new_folder))
{
if (!@mkdir($new_folder))
{
$this->WriteLog("Error: unable to create the missing users folder ".$new_folder);
}
}
}
function GetUsersFolder()
{
if ("" == $this->_users_folder)
{
$this->SetUsersFolder($this->GetScriptFolder()."users/");
}
return $this->ConvertToWindowsPathIfNeeded($this->_users_folder);
}
function SetTokenManufacturer($manufacturer)
{
$this->_token_data['manufacturer'] = $manufacturer;
}
function GetTokenManufacturer()
{
return $this->_token_data['manufacturer'];
}
function SetTokenSerialNumber($token_serial)
{
$this->_token_data['token_serial'] = $token_serial;
}
function GetTokenSerialNumber()
{
return $this->_token_data['token_serial'];
}
function SetTokenIssuer($issuer)
{
$this->_token_data['issuer'] = $issuer;
}
function GetTokenIssuer()
{
return $this->_token_data['issuer'];
}
function SetTokenKeyAlgorithm($key_algorithm)
{
$this->_token_data['key_algorithm'] = $key_algorithm;
}
function GetTokenKeyAlgorithm()
{
return $this->_token_data['key_algorithm'];
}
function SetTokenAlgorithm($algorithm)
{
$this->_token_data['algorithm'] = $algorithm;
}
function GetTokenAlgorithm()
{
return $this->_token_data['algorithm'];
}
function SetTokenOtp($otp)
{
$this->_token_data['otp'] = $otp;
}
function GetTokenOtp()
{
return $this->_token_data['otp'];
}
function SetTokenFormat($format)
{
$this->_token_data['format'] = $format;
}
function GetTokenFormat()
{
return $this->_token_data['format'];
}
function SetTokenNumberOfDigits($number_of_digits)
{
$this->_token_data['number_of_digits'] = $number_of_digits;
}
function GetTokenNumberOfDigits()
{
return $this->_token_data['number_of_digits'];
}
function SetTokenLastEvent($last_event)
{
$this->_token_data['last_event'] = $last_event;
}
function GetTokenLastEvent()
{
return $this->_token_data['last_event'];
}
function SetTokenDeltaTime($delta_time)
{
$this->_token_data['delta_time'] = $delta_time;
}
function GetTokenDeltaTime()
{
return $this->_token_data['delta_time'];
}
function SetTokenTimeInterval($time_interval)
{
$this->_token_data['time_interval'] = $time_interval;
}
function GetTokenTimeInterval()
{
return $this->_token_data['time_interval'];
}
function SetTokenSeed($token_seed)
{
$this->_token_data['token_seed'] = $token_seed;
}
function GetTokenSeed()
{
return $this->_token_data['token_seed'];
}
function SetTokensFolder($folder)
{
$new_folder = $this->ConvertToUnixPath($folder);
if (substr($new_folder,-1) != "/")
{
$new_folder.="/";
}
$new_folder = $this->ConvertToWindowsPathIfNeeded($new_folder);
$this->_tokens_folder = $new_folder;
if (!file_exists($new_folder))
{
if (!@mkdir($new_folder))
{
$this->WriteLog("Error: unable to create the missing tokens folder ".$new_folder);
}
}
}
function GetTokensFolder()
{
if ("" == $this->_tokens_folder)
{
$this->SetTokensFolder($this->GetScriptFolder()."tokens/");
}
return $this->ConvertToWindowsPathIfNeeded($this->_tokens_folder);
}
function DeleteUser()
{
$result = FALSE;
$user_filename = strtolower($this->_user).'.db';
if (!file_exists($this->GetUsersFolder().$user_filename))
{
$this->WriteLog("Error: unable to delete user ".$this->_user.", database file ".$this->GetUsersFolder().$user_filename." does not exist");
}
else
{
$result = unlink($this->GetUsersFolder().$user_filename);
if ($result)
{
$this->WriteLog("Information: user ".$this->_user." successfully deleted");
}
else
{
$this->WriteLog("Error: unable to delete user ".$this->_user);
}
}
return $result;
}
function ReadUserData($user = "", $create = FALSE)
{
if ("" != $user)
{
$this->SetUser($user);
}
$result = FALSE;
$user_filename = strtolower($this->GetUser()).'.db';
if (!file_exists($this->GetUsersFolder().$user_filename))
{
if (!$create)
{
$this->WriteLog("Error: database file ".$this->GetUsersFolder().$user_filename." for user ".$this->_user." does not exist");
}
}
else
{
$user_file_handler = fopen($this->GetUsersFolder().$user_filename, "rt");
$first_line = trim(fgets($user_file_handler));
// First version format support
if (FALSE === strpos(strtolower($first_line),"multiotp-database-format"))
{
$this->_user_data['algorithm'] = $first_line;
$this->_user_data['token_seed'] = trim(fgets($user_file_handler));
$this->_user_data['user_pin'] = trim(fgets($user_file_handler));
$this->_user_data['number_of_digits'] = trim(fgets($user_file_handler));
$this->_user_data['last_event'] = intval(trim(fgets($user_file_handler)) - 1);
$this->_user_data['request_prefix_pin'] = intval(trim(fgets($user_file_handler)));
$this->_user_data['last_login'] = intval(trim(fgets($user_file_handler)));
$this->_user_data['error_counter'] = intval(trim(fgets($user_file_handler)));
$this->_user_data['locked'] = intval(trim(fgets($user_file_handler)));
}
else
{
while (!feof($user_file_handler))
{
$v3 = (FALSE !== strpos(strtolower($first_line),"multiotp-database-format-v3"));
$line = trim(fgets($user_file_handler));
$line_array = explode("=",$line,2);
if ($v3) // v3 format, only tags followed by := instead od = are encrypted
{
if (":" == substr($line_array[0], -1))
{
$line_array[0] = substr($line_array[0], 0, strlen($line_array[0]) -1);
$line_array[1] = $this->Decrypt($line_array[0],$line_array[1]);
}
}
else // v2 format, only defined tags are encrypted
{
if (FALSE !== strpos(strtolower($this->_attributes_to_encrypt), strtolower('*'.$line_array[0].'*')))
{
$line_array[1] = $this->Decrypt($line_array[0],$line_array[1]);
}
}
if ("" != trim($line_array[0]))
{
$this->_user_data[strtolower($line_array[0])] = $line_array[1];
}
}
}
fclose($user_file_handler);
$result = TRUE;
}
$this->SetUserDataReadFlag($result);
return $result;
}
function WriteUserData()
{
$result = FALSE;
$user_filename = strtolower($this->_user).'.db';
if (!($user_file_handler = fopen($this->GetUsersFolder().$user_filename, "wt")))
{
$this->WriteLog("Error: database file for user ".$this->_user." cannot be written");
}
else
{
fwrite($user_file_handler,"multiotp-database-format-v3"."\n");
// foreach ($this->_user_data as $key => $value) // this is not working well in CLI mode
reset($this->_user_data);
while(list($key, $value) = each($this->_user_data))
{
if ("" != trim($key))
{
$line = strtolower($key);
if (FALSE !== strpos(strtolower($this->_attributes_to_encrypt), strtolower('*'.$key.'*')))
{
$value = $this->Encrypt($key,$value);
$line = $line.":";
}
$line = $line."=".$value;
fwrite($user_file_handler,$line."\n");
}
}
$result = TRUE;
fclose($user_file_handler);
}
return $result;
}
function ReadTokenData($token = "", $create = FALSE)
{
if ("" != $token)
{
$this->SetToken($token);
}
$result = FALSE;
$token_filename = strtolower($this->GetToken()).'.db';
if (!file_exists($this->GetTokensFolder().$token_filename))
{
if (!$create)
{
$this->WriteLog("Error: database file ".$this->GetTokensFolder().$token_filename." for token ".$this->_token." does not exist");
}
}
else
{
$token_file_handler = fopen($this->GetTokensFolder().$token_filename, "rt");
$first_line = trim(fgets($token_file_handler));
while (!feof($token_file_handler))
{
$line = trim(fgets($token_file_handler));
$line_array = explode("=",$line,2);
if (":" == substr($line_array[0], -1))
{
$line_array[0] = substr($line_array[0], 0, strlen($line_array[0]) -1);
$line_array[1] = $this->Decrypt($line_array[0],$line_array[1]);
}
if ("" != trim($line_array[0]))
{
$this->_token_data[strtolower($line_array[0])] = $line_array[1];
}
}
fclose($token_file_handler);
$result = TRUE;
}
$this->SetTokenDataReadFlag($result);
return $result;
}
function WriteTokenData()
{
$result = FALSE;
$token_filename = strtolower($this->_token).'.db';
if (!($token_file_handler = fopen($this->GetTokensFolder().$token_filename, "wt")))
{
$this->WriteLog("Error: database file for token ".$this->_token." cannot be written");
}
else
{
fwrite($token_file_handler,"multiotp-database-format-v3"."\n");
// foreach ($this->_token_data as $key => $value) // this is not working well in CLI mode
reset($this->_token_data);
while(list($key, $value) = each($this->_token_data))
{
if ("" != trim($key))
{
$line = strtolower($key);
if (FALSE !== strpos(strtolower($this->_attributes_to_encrypt), strtolower('*'.$key.'*')))
{
$value = $this->Encrypt($key,$value);
$line = $line.":";
}
$line = $line."=".$value;
fwrite($token_file_handler,$line."\n");
}
}
$result = TRUE;
fclose($token_file_handler);
}
return $result;
}
/*********************************************************************
*
* Name: CheckToken
* Short description: Check the token and give the result, with resync options
*
* Creation 2010-06-07
* Update 2010-07-19
* @package multiotp
* @version 2.0.0
* @author SysCo/al
*
* @param string $input Token to check
* @param string $input_sync Second token to check for resync
* @return int Error code (0 = successful authentication, 1n = info, >= 20 = error)
*
*********************************************************************/
function CheckToken($input = "", $input_sync = "", $display_status = FALSE)
{
if (!$this->ReadUserData($this->GetUser()))
{
$result = 21; // ERROR: user doesn't exist.
$this->WriteLog("Error: user ".$this->GetUser()." doesn't exist");
}
else
{
$result = 99; // Unknown error
$now_epoch = time();
if ((1 == $this->GetUserLocked()) && ("" == $input_sync))
{
$result = 24; // ERROR: user locked;
$this->WriteLog("Error: user ".$this->GetUser()." locked");
}
elseif(($this->GetUserErrorCounter() >= $this->GetMaxDelayedFailures()) && $now_epoch < ($this->GetMaxDelayedTime()+$this->GetMaxDelayedTime()))
{
$result = 25; // ERROR: user delayed;
$this->WriteLog("Error: user ".$this->GetUser()." delayed");
}
else
{
$pin = $this->GetUserPin();
$need_prefix = (1 == $this->GetUserPrefixPin());
$seed = $this->GetUserTokenSeed();
$seed_bin = $this->ConvertHex2Bin($seed);
$delta_time = $this->GetUserTokenDeltaTime();
$interval = $this->GetUserTokenTimeInterval();
if (0 >= $interval)
{
$interval = 1;
}
$last_event = $this->GetUserTokenLastEvent();
$last_login = $this->GetUserTokenLastLogin();
$digits = $this->GetUserTokenNumberOfDigits();
$error_counter = $this->GetUserErrorCounter();
$now_steps = intval($now_epoch / $interval);
$time_window = $this->GetMaxTimeWindow();
$step_window = intval($time_window / $interval);
$event_window = $this->GetMaxEventWindow();
$time_sync_window = $this->GetMaxTimeResyncWindow();
$step_sync_window = intval($time_sync_window / $interval);
$event_sync_window = $this->GetMaxEventResyncWindow();
$last_login_step = intval($last_login / $interval);
$delta_step = $delta_time / $interval;
switch (strtoupper($this->GetUserAlgorithm()))
{
case "MOTP":
if ("" == $input_sync)
{
$max_steps = 2 * $step_window;
}
else
{
$max_steps = 2 * $step_sync_window;
}
$check_step = 0;
do
{
$additional_step = (1 - (2 * ($check_step % 2))) * intval($check_step/2);
$calculated_token = $this->ComputeMotp($seed.$pin, $now_steps+$additional_step+$delta_step, $digits);
if ($need_prefix)
{
$calculated_token = $pin.$calculated_token;
}
if ($input == $calculated_token)
{
if ("" == $input_sync)
{
if (($now_steps+$additional_step+$delta_step) > $last_login_step)
{
$this->SetUserTokenLastLogin(($now_steps+$additional_step+$delta_step) * $interval);
$this->SetUserTokenDeltaTime(($additional_step+$delta_step) * $interval);
$this->SetUserErrorCounter(0);
$result = 0; // OK: This is the correct token
$this->WriteLog("OK: user ".$this->GetUser()." successfully logged in");
}
else
{
$this->SetUserErrorCounter($error_counter+1);
$this->SetUserTokenLastError($now_epoch);
$result = 26; // ERROR: this token has already been used
$this->WriteLog("Error: token of user ".$this->GetUser()." already used");
}
}
else
{
$calculated_token = $this->ComputeMotp($seed.$pin, $now_steps+$additional_step+$delta_step+1, $digits);
if ($need_prefix)
{
$calculated_token = $pin.$calculated_token;
}
if ($input_sync == $calculated_token)
{
$this->SetUserTokenLastLogin(($now_steps+$additional_step+$delta_step+1) * $interval);
$this->SetUserTokenDeltaTime(($additional_step+$delta_step+1) * $interval);
$this->SetUserErrorCounter(0);
$this->SetUserLocked(0);
$result = 14; // INFO: token is now synchronized
$this->WriteLog("Info: token for user ".$this->GetUser()." is now resynchronized with a delta of ".(($additional_step+$delta_step+1) * $interval). " seconds");
}
else
{
$result = 27; // ERROR: resync failed
$this->WriteLog("Error: resync for user ".$this->GetUser()." has failed");
}
}
}
else
{
$check_step++;
if ($display_status)
{
$this->ShowStatus($check_step, $max_steps);
}
}
}
while (($check_step < $max_steps) && (99 == $result));
if ($display_status)
{
echo "\n\r";
}
if (99 == $result)
{
$this->SetUserErrorCounter($error_counter+1);
$this->SetUserTokenLastError($now_epoch);
$this->WriteLog("Error: authentication failed for user ".$this->GetUser());
}
break;
case "HOTP";
if ("" == $input_sync)
{
$max_steps = $event_window;
}
else
{
$max_steps = $event_sync_window;
}
$check_step = 1;
do
{
$calculated_token = $this->ComputeOathTruncate($this->ComputeOathHotp($seed_bin,$last_event+$check_step),$digits);
if ($need_prefix)
{
$calculated_token = $pin.$calculated_token;
}
if ($input == $calculated_token)
{
if ("" == $input_sync)
{
$this->SetUserTokenLastLogin($now_epoch);
$this->SetUserTokenLastEvent($last_event+$check_step);
$this->SetUserErrorCounter(0);
$result = 0; // OK: This is the correct token
$this->WriteLog("OK: user ".$this->GetUser()." successfully logged in");
}
else
{
$calculated_token = $this->ComputeOathTruncate($this->ComputeOathHotp($seed_bin,$last_event+$check_step+1),$digits);
if ($need_prefix)
{
$calculated_token = $pin.$calculated_token;
}
if ($input_sync == $calculated_token)
{
$this->SetUserTokenLastLogin($now_epoch);
$this->SetUserTokenLastEvent($last_event+$check_step+1);
$this->SetUserErrorCounter(0);
$this->SetUserLocked(0);
$result = 14; // INFO: token is now synchronized
$this->WriteLog("Info: token for user ".$this->GetUser()." is now resynchronized with the last event ".($last_event+$check_step+1));
}
else
{
$result = 27; // ERROR: resync failed
$this->WriteLog("Error: resync for user ".$this->GetUser()." has failed");
}
}
}
else
{
$check_step++;
if ($display_status)
{
$this->ShowStatus($check_step, $max_steps);
}
}
}
while (($check_step < $max_steps) && (99 == $result));
if ($display_status)
{
echo "\n\r";
}
if (99 == $result)
{
$this->SetUserErrorCounter($error_counter+1);
$this->SetUserTokenLastError($now_epoch);
$this->WriteLog("Error: authentication failed for user ".$this->GetUser());
}
break;
case "TOTP";
if ("" == $input_sync)
{
$max_steps = 2 * $step_window;
}
else
{
$max_steps = 2 * $step_sync_window;
}
$check_step = 0;
do
{
$additional_step = (1 - (2 * ($check_step % 2))) * intval($check_step/2);
$calculated_token = $this->ComputeOathTruncate($this->ComputeOathHotp($seed_bin,$now_steps+$additional_step+$delta_step),$digits);
if ($need_prefix)
{
$calculated_token = $pin.$calculated_token;
}
if ($input == $calculated_token)
{
if ("" == $input_sync)
{
if (($now_steps+$additional_step+$delta_step) > $last_login_step)
{
$this->SetUserTokenLastLogin(($now_steps+$additional_step+$delta_step) * $interval);
$this->SetUserTokenDeltaTime(($additional_step+$delta_step) * $interval);
$this->SetUserErrorCounter(0);
$result = 0; // OK: This is the correct token
$this->WriteLog("OK: user ".$this->GetUser()." successfully logged in");
}
else
{
$this->SetUserErrorCounter($error_counter+1);
$this->SetUserTokenLastError($now_epoch);
$result = 26; // ERROR: this token has already been used
$this->WriteLog("Error: token of user ".$this->GetUser()." already used");
}
}
else
{
$calculated_token = $this->ComputeOathTruncate($this->ComputeOathHotp($seed_bin,$now_steps+$additional_step+$delta_step+1),$digits);
if ($need_prefix)
{
$calculated_token = $pin.$calculated_token;
}
if ($input_sync == $calculated_token)
{
$this->SetUserTokenLastLogin(($now_steps+$additional_step+$delta_step+1) * $interval);
$this->SetUserTokenDeltaTime(($additional_step+$delta_step+1) * $interval);
$this->SetUserErrorCounter(0);
$this->SetUserLocked(0);
$result = 14; // INFO: token is now synchronized
$this->WriteLog("Info: token for user ".$this->GetUser()." is now resynchronized with a delta of ".(($additional_step+$delta_step+1) * $interval). " seconds");
}
else
{
$result = 27; // ERROR: resync failed
$this->WriteLog("Error: resync for user ".$this->GetUser()." has failed");
}
}
}
else
{
$check_step++;
if ($display_status)
{
$this->ShowStatus($check_step, $max_steps);
}
}
}
while (($check_step < $max_steps) && (99 == $result));
if ($display_status)
{
echo "\n\r";
}
if (99 == $result)
{
$this->SetUserErrorCounter($error_counter+1);
$this->SetUserTokenLastError($now_epoch);
$this->WriteLog("Error: authentication failed for user ".$this->GetUser());
}
break;
default:
$result = 23;
$this->WriteLog("Error: ".$this->GetUserAlgorithm()." algorithm is unknown");
}
}
}
if ($this->GetUserErrorCounter() >= $this->GetMaxBlockFailures())
{
$this->SetUserLocked(1);
}
$this->WriteUserData();
return $result;
}
function ImportTokensFromXml($xml_file)
{
$result = TRUE;
if (!file_exists($xml_file))
{
$this->WriteLog("Error: XML tokens definition file ".$xml_file." doesn't exist");
$result = FALSE;
}
else
{
// http://tools.ietf.org/html/draft-hoyer-keyprov-pskc-algorithm-profiles-00
//Get the XML document loaded into a variable
$sXmlData = @file_get_contents($xml_file);
//Set up the parser object
$xml = new MultiotpXmlParser($sXmlData);
//Parse it !
$xml->Parse();
// Array of key types
$key_types = array();
// Array of devices
$devices = array();
if (isset($xml->document->keyproperties))
{
foreach ($xml->document->keyproperties as $keyproperty)
{
$id = (isset($keyproperty->tagAttrs['xml:id'])?$keyproperty->tagAttrs['xml:id']:"");
if ('' != $id)
{
$key_types[$id]['id'] = $id;
$key_types[$id]['issuer'] = (isset($keyproperty->issuer[0]->tagData)?$keyproperty->issuer[0]->tagData:"");
$key_types[$id]['keyalgorithm'] = (isset($keyproperty->tagAttrs['keyalgorithm'])?$keyproperty->tagAttrs['keyalgorithm']:'');
$pos = strrpos($key_types[$id]['keyalgorithm'], "#");
$key_types[$id]['algorithm'] = (($pos === false)?'':strtoupper(substr($key_types[$id]['keyalgorithm'], $pos+1)));
$key_types[$id]['otp'] = (isset($keyproperty->usage[0]->tagAttrs['otp'])?$keyproperty->usage[0]->tagAttrs['otp']:'');
$key_types[$id]['format'] = (isset($keyproperty->usage[0]->responseformat[0]->tagAttrs['format'])?$keyproperty->usage[0]->responseformat[0]->tagAttrs['format']:'');
$key_types[$id]['length'] = (isset($keyproperty->usage[0]->responseformat[0]->tagAttrs['length'])?$keyproperty->usage[0]->responseformat[0]->tagAttrs['length']:-1);
$key_types[$id]['counter'] = (isset($keyproperty->data[0]->counter[0]->plainvalue[0]->tagData)?$keyproperty->data[0]->counter[0]->plainvalue[0]->tagData:-1);
$key_types[$id]['time'] = (isset($keyproperty->data[0]->time[0]->plainvalue[0]->tagData)?$keyproperty->data[0]->time[0]->plainvalue[0]->tagData:-1);
$key_types[$id]['timeinterval'] = (isset($keyproperty->data[0]->timeinterval[0]->plainvalue[0]->tagData)?$keyproperty->data[0]->timeinterval[0]->plainvalue[0]->tagData:-1);
}
}
}
if (isset($xml->document->device))
{
foreach ($xml->document->device as $device)
{
$keyid = (isset($device->key[0]->tagAttrs['keyid'])?$device->key[0]->tagAttrs['keyid']:'');
if ('' != $keyid)
{
$keyproperties = '';
$manufacturer = '';
$serialno = '';
$issuer = '';
$keyalgorithm = '';
$algorithm = '';
$otp = '';
$format = '';
$length = 0;
$counter = -1;
$time = 0;
$timeinterval = 0;
$secret = '';
if (isset($device->key[0]->tagAttrs['keyproperties']))
{
$keyproperties = $device->key[0]->tagAttrs['keyproperties'];
if (isset($key_types[$keyproperties]))
{
reset($key_types[$keyproperties]);
while(list($key, $value) = each($key_types[$keyproperties]))
{
$$key = $value;
}
}
}
$manufacturer = (isset($device->deviceinfo[0]->manufacturer[0]->tagData)?$device->deviceinfo[0]->manufacturer[0]->tagData:$manufacturer);
$serialno = (isset($device->deviceinfo[0]->serialno[0]->tagData)?$device->deviceinfo[0]->serialno[0]->tagData:$serialno);
$issuer = (isset($device->key[0]->issuer[0]->tagData)?$device->key[0]->issuer[0]->tagData:$issuer);
if (isset($device->key[0]->tagAttrs['keyalgorithm']))
{
$keyalgorithm = $device->key[0]->tagAttrs['keyalgorithm'];
$pos = strrpos($keyalgorithm, "#");
$algorithm = (($pos === false)?$algorithm:strtoupper(substr($keyalgorithm, $pos+1)));
}
$otp = (isset($device->key[0]->usage[0]->tagAttrs['otp'])?$device->key[0]->usage[0]->tagAttrs['otp']:$otp);
$format = (isset($device->key[0]->usage[0]->responseformat[0]->tagAttrs['format'])?$device->key[0]->usage[0]->responseformat[0]->tagAttrs['format']:$format);
$length = (isset($device->key[0]->usage[0]->responseformat[0]->tagAttrs['length'])?$device->key[0]->usage[0]->responseformat[0]->tagAttrs['length']:$length);
$counter = (isset($device->key[0]->data[0]->counter[0])?$device->key[0]->data[0]->counter[0]->plainvalue[0]->tagData:$counter);
$time = (isset($device->key[0]->data[0]->time[0])?$device->key[0]->data[0]->time[0]->plainvalue[0]->tagData:$time);
$timeinterval = (isset($device->key[0]->data[0]->timeinterval[0])?$device->key[0]->data[0]->timeinterval[0]->plainvalue[0]->tagData:$timeinterval);
if (isset($device->key[0]->data[0]->secret[0]->plainvalue[0]->tagData))
{
$secret = bin2hex(base64_decode($device->key[0]->data[0]->secret[0]->plainvalue[0]->tagData));
}
$this->SetToken($keyid);
$this->_token_data['manufacturer'] = $manufacturer;
$this->_token_data['token_serial'] = $serialno;
$this->_token_data['issuer'] = $issuer;
$this->_token_data['key_algorithm'] = $keyalgorithm;
$this->_token_data['algorithm'] = $algorithm;
$this->_token_data['otp'] = $otp;
$this->_token_data['format'] = $format;
$this->_token_data['number_of_digits'] = $length;
if ($counter >= 0)
{
$this->_token_data['last_event'] = $counter-1;
}
else
{
$this->_token_data['last_event'] = 0;
}
$this->_token_data['delta_time'] = $time;
$this->_token_data['time_interval'] = $timeinterval;
$this->_token_data['token_seed'] = $secret;
$result = $this->WriteTokenData() && $result;
$this->WriteLog("Information: Token with keyid ".$keyid." successfully imported");
if ($this->_log_verbose_flag)
{
reset($this->_token_data);
while(list($key, $value) = each($this->_token_data))
{
if ('' != $value)
{
$this->WriteLog(" Token ".$keyid." - ".$key.": ".$value);
}
}
}
}
}
}
}
return $result;
}
}
/*********************************************************************
*
* XML Parser Class (php4)
* Parses an XML document into an object structure much like the SimpleXML extension.
*
* Name: MultiotpXmlParser (original name: XMLParser)
*
* @author: MT Shahzad - http://mts.sw3solutions.com
* Source: http://www.geosourcecode.com/post241.html
*
*********************************************************************/
class MultiotpXmlParser
{
/**
* The XML parser
*
* @var resource
*/
var $parser;
/**
* The XML document
*
* @var string
*/
var $xml;
/**
* Document tag
*
* @var object
*/
var $document;
/**
* Current object depth
*
* @var array
*/
var $stack;
/**
* Constructor. Loads XML document.
*
* @param string $xml The string of the XML document
* @return MultiotpXmlParser
*/
function MultiotpXmlParser($xml = '')
{
//Load XML document
$this->xml = $xml;
// Set stack to an array
$this->stack = array();
}
/**
* Initiates and runs PHP's XML parser
*/
function Parse()
{
//Create the parser resource
$this->parser = xml_parser_create();
//Set the handlers
xml_set_object($this->parser, $this);
xml_set_element_handler($this->parser, 'StartElement', 'EndElement');
xml_set_character_data_handler($this->parser, 'CharacterData');
//Error handling
if (!xml_parse($this->parser, $this->xml))
$this->HandleError(xml_get_error_code($this->parser), xml_get_current_line_number($this->parser), xml_get_current_column_number($this->parser));
//Free the parser
xml_parser_free($this->parser);
}
/**
* Handles an XML parsing error
*
* @param int $code XML Error Code
* @param int $line Line on which the error happened
* @param int $col Column on which the error happened
*/
function HandleError($code, $line, $col)
{
trigger_error('XML Parsing Error at '.$line.':'.$col.'. Error '.$code.': '.xml_error_string($code));
}
/**
* Gets the XML output of the PHP structure within $this->document
*
* @return string
*/
function GenerateXML()
{
return $this->document->GetXML();
}
/**
* Gets the reference to the current direct parent
*
* @return object
*/
function GetStackLocation()
{
$return = '';
foreach($this->stack as $stack)
$return .= $stack.'->';
return rtrim($return, '->');
}
/**
* Handler function for the start of a tag
*
* @param resource $parser
* @param string $name
* @param array $attrs
*/
function StartElement($parser, $name, $attrs = array())
{
//Make the name of the tag lower case
$name = strtolower($name);
//Check to see if tag is root-level
if (count($this->stack) == 0)
{
//If so, set the document as the current tag
$this->document = new XMLTag($name, $attrs);
//And start out the stack with the document tag
$this->stack = array('document');
}
//If it isn't root level, use the stack to find the parent
else
{
//Get the name which points to the current direct parent, relative to $this
$parent = $this->GetStackLocation();
//Add the child
eval('$this->'.$parent.'->AddChild($name, $attrs, '.count($this->stack).');');
//Update the stack
eval('$this->stack[] = $name.\'[\'.(count($this->'.$parent.'->'.$name.') - 1).\']\';');
}
}
/**
* Handler function for the end of a tag
*
* @param resource $parser
* @param string $name
*/
function EndElement($parser, $name)
{
//Update stack by removing the end value from it as the parent
array_pop($this->stack);
}
/**
* Handler function for the character data within a tag
*
* @param resource $parser
* @param string $data
*/
function CharacterData($parser, $data)
{
//Get the reference to the current parent object
$tag = $this->GetStackLocation();
//Assign data to it
eval('$this->'.$tag.'->tagData .= trim($data);');
}
}
/**
* XML Tag Object (php4)
*
* This object stores all of the direct children of itself in the $children array. They are also stored by
* type as arrays. So, if, for example, this tag had 2 <font> tags as children, there would be a class member
* called $font created as an array. $font[0] would be the first font tag, and $font[1] would be the second.
*
* To loop through all of the direct children of this object, the $children member should be used.
*
* To loop through all of the direct children of a specific tag for this object, it is probably easier
* to use the arrays of the specific tag names, as explained above.
*/
class XMLTag
{
/**
* Array with the attributes of this XML tag
*
* @var array
*/
var $tagAttrs;
/**
* The name of the tag
*
* @var string
*/
var $tagName;
/**
* The data the tag contains
*
* So, if the tag doesn't contain child tags, and just contains a string, it would go here
*
* @var string
*/
var $tagData;
/**
* Array of references to the objects of all direct children of this XML object
*
* @var array
*/
var $tagChildren;
/**
* The number of parents this XML object has (number of levels from this tag to the root tag)
*
* Used presently only to set the number of tabs when outputting XML
*
* @var int
*/
var $tagParents;
/**
* Constructor, sets up all the default values
*
* @param string $name
* @param array $attrs
* @param int $parents
* @return XMLTag
*/
function XMLTag($name, $attrs = array(), $parents = 0)
{
//Make the keys of the attr array lower case, and store the value
$this->tagAttrs = array_change_key_case($attrs, CASE_LOWER);
//Make the name lower case and store the value
$this->tagName = strtolower($name);
//Set the number of parents
$this->tagParents = $parents;
//Set the types for children and data
$this->tagChildren = array();
$this->tagData = '';
}
/**
* Adds a direct child to this object
*
* @param string $name
* @param array $attrs
* @param int $parents
*/
function AddChild($name, $attrs, $parents)
{
//If there is no array already set for the tag name being added,
//create an empty array for it
if(!isset($this->$name))
$this->$name = array();
//If the tag has the same name as a member in XMLTag, or somehow the
//array wasn't properly created, output a more informative error than
//PHP otherwise would.
if(!is_array($this->$name))
{
trigger_error('You have used a reserved name as the name of an XML tag. Please consult the documentation (http://www.thousandmonkeys.net/xml_doc.php) and rename the tag named '.$name.' to something other than a reserved name.', E_USER_ERROR);
return;
}
//Create the child object itself
$child = new XMLTag($name, $attrs, $parents);
//Add the reference of it to the end of an array member named for the tag's name
$this->{$name}[] =& $child;
//Add the reference to the children array member
$this->tagChildren[] =& $child;
}
/**
* Returns the string of the XML document which would be generated from this object
*
* This function works recursively, so it gets the XML of itself and all of its children, which
* in turn gets the XML of all their children, which in turn gets the XML of all thier children,
* and so on. So, if you call GetXML from the document root object, it will return a string for
* the XML of the entire document.
*
* This function does not, however, return a DTD or an XML version/encoding tag. That should be
* handled by MultiotpXmlParser::GetXML()
*
* @return string
*/
function GetXML()
{
//Start a new line, indent by the number indicated in $this->parents, add a <, and add the name of the tag
$out = "\n".str_repeat("\t", $this->tagParents).'<'.$this->tagName;
//For each attribute, add attr="value"
foreach($this->tagAttrs as $attr => $value)
$out .= ' '.$attr.'="'.$value.'"';
//If there are no children and it contains no data, end it off with a />
if(empty($this->tagChildren) && empty($this->tagData))
$out .= " />";
//Otherwise...
else
{
//If there are children
if(!empty($this->tagChildren))
{
//Close off the start tag
$out .= '>';
//For each child, call the GetXML function (this will ensure that all children are added recursively)
foreach($this->tagChildren as $child)
$out .= $child->GetXML();
//Add the newline and indentation to go along with the close tag
$out .= "\n".str_repeat("\t", $this->tagParents);
}
//If there is data, close off the start tag and add the data
elseif(!empty($this->tagData))
$out .= '>'.$this->tagData;
//Add the end tag
$out .= '</'.$this->tagName.'>';
}
//Return the final output
return $out;
}
}
?> |