<?php
/*********************************************************************
*
* MultiOTP PHP CLI header - 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 ;-)
*
* If the name of this file is multiotp.php, it means that it is already
* the result of the merge of the two files multiotp.cli.header.php and
* multiotp.class.php
*
* The MultiOTP PHP CLI header is simply merged with the MultiOTP PHP
* class in order to provide an authentication command line script.
*
* This script can be used as an external authentication provider with at
* least the following RADIUS servers:
* - 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)
*
* For Windows, you can also use the multiotp.exe file provided, which is
* an embedded PHP interpreter together with the result of the merge.
*
*
* 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.cli.header.php $
* @link http://www.multiotp.net
* @link hide@address.com
* Language: PHP 4.4.4 or higher
*
*
* Command line usage
*
* Type multiotp -help to have the full description of the options,
* and have a look at the readme.txt file for enhanced explanations
*
*
* Return codes
*
* 0 OK: Token accepted
* 11 INFO: User successfully created or updated
* 12 INFO: User successfully deleted
* 13 INFO: User PIN code successfully changed
* 14 INFO: Token has been resynchronized successfully
* 15 INFO: XML tokens definition file successfully imported
* 19 INFO: Requested operation successfully done
* 21 ERROR: User doesn't exist
* 22 ERROR: User already exists
* 23 ERROR: Invalid algorithm
* 24 ERROR: User locked (too many tries)
* 25 ERROR: User delayed (too many tries, but still a hope in a few minutes)
* 26 ERROR: The time based token has already been used
* 27 ERROR: Resynchronization of the token has failed
* 28 ERROR: Unable to write the changes in the file
* 29 ERROR: Token doesn't exist
* 30 ERROR: At least one parameter is missing
* 31 ERROR: XML tokens definition file doesn't exist
* 32 ERROR: XML tokens definition file not successfully imported
* 99 ERROR: Authentication failed (and other possible unknown errors)
*
*
* Radius integration examples
*
* Example 1 (TekRADIUS or TekRADIUS LT under Windows)
*
* TekRADIUS supports a Default Username to be used when a matching user
* profile cannot be found for an incoming RADIUS authentication request.
* So a quick and easy way is to create in the TekRADIUS Manager a User
* named 'Default' that belongs to the existing 'Default' Group.
* Then add to this Default user the following attribute :
* Check External-Executable C:\multitop\multiotp.exe %ietf|1% %ietf|2%
*
*
* Example 2 (FreeRADIUS under Linux)
*
* Define a DEFAULT entry in the /etc/freeradius/users file like this:
* DEFAULT Auth-Type = Accept
* Exec-Program-Wait = "/usr/local/bin/multiotp.php %{User-Name} %{User-Password}",
* Fall-Through = Yes,
* Reply-Message = "Hello, %{User-Name}"
*
*
* 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 hide@address.com
*
*
* Users feedbacks and comments
*
* 2010-08-20 BirdNet, C. Christophi
* Documentation enhancement proposal for the TekRADIUS part, thanks !
*
*
* Change Log
*
* 2010-09-02 3.0.0 SysCo/al Adding tokens handling support, including importing XML tokens definition file
* Enhanced flat database file format (multiotp is still compatible with old formats)
* 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, define timezone if not defined (for embedded command line)
* If user doesn't exist, do not create the related flat file after a check
* 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 Adding more information in the help text
* 2010-07-19 2.0.0 SysCo/al New design using a class and a cli header stub
* 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
*
*********************************************************************/
// Trick to have mostly the correct timezone in embedded command line version
// and to avoid error messages when using time functions
if (function_exists("date_default_timezone_get"))
{
$actual_timezone = @date_default_timezone_get();
if (function_exists("date_default_timezone_set"))
{
@date_default_timezone_set($actual_timezone);
}
}
// Be sure that STDIN, STDOUT and STDERR are defined correctly for command line edition
if (!defined('STDIN'))
{
define(STDIN, fopen('php://stdin', 'r'));
}
if (!defined('STDOUT'))
{
define(STDOUT, fopen('php://stdout', 'w'));
}
if (!defined('STDERR'))
{
define(STDERR, fopen('php://stderr', 'w'));
}
// Create a new Multiotp object
// The log and users subfolders are set by default under the folder of the script
$multiotp = new Multiotp();
// Set a specific encryption key for the tokens and users files
$multiotp->SetEncryptionKey('DefaultCliEncryptionKey');
// Initialize some variables
$command = "check";
$display_help = FALSE;
$display_status = FALSE;
$prefix_pin = FALSE;
$crlf = chr(13).chr(10);
$result = 99; // Unknown error
$token_id_creation = FALSE;
// Extract all parameters
$param_count = 0;
$all_args = array();
for ($arg_loop=1; $arg_loop <= $_SERVER["argc"]-1; $arg_loop++)
{
if ("-check" == strtolower($_SERVER["argv"][$arg_loop]))
{
$command = "check";
}
elseif ("-create" == strtolower($_SERVER["argv"][$arg_loop]))
{
$command = "create";
}
elseif ("-debug" == strtolower($_SERVER["argv"][$arg_loop]))
{
$multiotp->EnableVerboseLog();
}
elseif ("-delete" == strtolower($_SERVER["argv"][$arg_loop]))
{
$command = "delete";
}
elseif ("-help" == strtolower($_SERVER["argv"][$arg_loop]))
{
$command = "help";
}
elseif ("-import-xml" == strtolower($_SERVER["argv"][$arg_loop]))
{
$command = "import-xml";
}
elseif ("-log" == strtolower($_SERVER["argv"][$arg_loop]))
{
$multiotp->EnableLog();
}
elseif ("-no-prefix-pin" == strtolower($_SERVER["argv"][$arg_loop]))
{
$prefix_pin = FALSE;
}
elseif ("-prefix-pin" == strtolower($_SERVER["argv"][$arg_loop]))
{
$prefix_pin = TRUE;
}
elseif ("-resync" == strtolower($_SERVER["argv"][$arg_loop]))
{
$command = "resync";
}
elseif ("-status" == strtolower($_SERVER["argv"][$arg_loop]))
{
$display_status = TRUE;
}
elseif ("-token-id" == strtolower($_SERVER["argv"][$arg_loop]))
{
$token_id_creation = TRUE;
}
elseif ("-update" == strtolower($_SERVER["argv"][$arg_loop]))
{
$command = "update";
}
elseif ("-update-pin" == strtolower($_SERVER["argv"][$arg_loop]))
{
$command = "update-pin";
}
elseif (("-version" == strtolower($_SERVER["argv"][$arg_loop])) || ("-v" == strtolower($_SERVER["argv"][$arg_loop])))
{
$command = "version";
}
else
{
$param_count++;
$all_args[$param_count] = $_SERVER["argv"][$arg_loop];
}
}
// Be sure that inexistant parameters are empty
for ($i = ($param_count+1); $i <= 20; $i++)
{
$all_args[$i] = "";
}
// if not enough parameters, display help
if (($param_count < 1) && ($command != "version"))
{ $command = "help";
}
switch ($command)
{
case "version":
echo "multiotp ".$multiotp->GetVersion()." (".$multiotp->GetDate().")".$crlf;
$result = 19;
break;
case "check";
if ($param_count < 2)
{
$result = 30; // ERROR: At least one parameter is missing
}
elseif (!$multiotp->ReadUserData($all_args[1]))
{
$result = 22; // ERROR: user doesn't exist.
}
else
{
$result = $multiotp->CheckToken($all_args[2]); // Result provided by the MultiOTP class
}
break;
case "create":
case "update":
if (("create" == $command) && $multiotp->ReadUserData($all_args[1], TRUE))
{
$result = 22; // ERROR: user already exists.
}
elseif (("update" == $command) && (!$multiotp->ReadUserData($all_args[1])))
{
$result = 21; // ERROR: user doesn't exist.
}
elseif ($param_count < 3)
{
$result = 30; // ERROR: At least one parameter is missing
}
else
{
$multiotp->SetUser($all_args[1]);
$multiotp->SetUserPrefixPin($prefix_pin?1:0);
if ($token_id_creation)
{
$key_id = $all_args[2];
if (!$multiotp->ReadTokenData($key_id))
{
$result = 29; // ERROR: token doesn't exist.
}
else
{
$multiotp->SetUserKeyId($key_id);
if (!$multiotp->SetUserAlgorithm($multiotp->GetTokenAlgorithm()))
{
$result = 23; // ERROR: invalid algorithm
}
else
{
$multiotp->SetUserTokenSeed($multiotp->GetTokenSeed());
$multiotp->SetUserTokenNumberOfDigits($multiotp->GetTokenNumberOfDigits());
$multiotp->SetUserTokenTimeInterval($multiotp->GetTokenTimeInterval());
$multiotp->SetUserTokenLastEvent($multiotp->GetTokenLastEvent());
$multiotp->SetUserPin($all_args[3]);
if ($multiotp->WriteUserData())
{
$result = 11; // INFO: user successfully created or updated
}
else
{
$result = 28; // ERROR: Unable to write the changes in the file
}
}
}
}
elseif (!$multiotp->SetUserAlgorithm($all_args[2]))
{
$result = 23; // ERROR: invalid algorithm
}
else
{
$multiotp->SetUserTokenSeed($all_args[3]);
if ($param_count < 4)
{
$result = 30; // ERROR: At least one parameter is missing
}
else
{
$multiotp->SetUserPin($all_args[4]);
if ("" == $all_args[5])
{
$all_args[5] = 6; // Default numnber of digits is set to 6
}
$multiotp->SetUserTokenNumberOfDigits($all_args[5]);
switch (strtoupper($all_args[2]))
{
// This is the time interval for mOTP
case "MOTP":
if ("" == $all_args[6])
{
$all_args[6] = 10; // Default windows value interval for mOTP
}
$multiotp->SetUserTokenTimeInterval($all_args[6]);
break;
// This is the time interval for TOTP
case "TOTP":
if ("" == $all_args[6])
{
$all_args[6] = 30; // Default windows value interval for TOTP
}
$multiotp->SetUserTokenTimeInterval($all_args[6]);
break;
// This is the next event for HOTP
case "HOTP":
default:
if ("" == $all_args[6])
{
$all_args[6] = 0; // Default next event
}
$multiotp->SetUserTokenLastEvent($all_args[6]-1);
// -1 because we are saving the last event in the user file database
break;
}
if ($multiotp->WriteUserData())
{
$result = 11; // INFO: user successfully created or updated
}
else
{
$result = 28; // ERROR: Unable to write the changes in the file
}
}
}
}
break;
case "delete":
$multiotp->SetUser($all_args[1]);
if (!$multiotp->DeleteUser())
{
$result = 21; // ERROR: user doesn't exist.
}
else
{
$result = 12; // INFO: user successfully deleted.
}
break;
case "resync":
if ($param_count < 3)
{
$result = 30; // ERROR: At least one parameter is missing
}
elseif (!$multiotp->ReadUserData($all_args[1]))
{
$result = 22; // ERROR: user doesn't exist.
}
else
{
$result = $multiotp->CheckToken($all_args[2], $all_args[3], $display_status); // Result provided by the MultiOTP class
}
break;
case "update-pin":
if ($param_count < 2)
{
$result = 30; // ERROR: At least one parameter is missing
}
elseif (!$multiotp->ReadUserData($all_args[1]))
{
$result = 21; // ERROR: user doesn't exist.
}
else
{
$multiotp->SetUserPin($all_args[2]);
if ($multiotp->WriteUserData())
{
$result = 13; // INFO: pin successfully changed
}
}
break;
case "import-xml":
if (!file_exists($all_args[1]))
{
$result = 31; // ERROR: XML tokens definition file doesn't exist.
}
else
{
if ($multiotp->ImportTokensFromXml($all_args[1]))
{
$result = 15; // INFO: XML tokens definition file successfully imported
}
else
{
$result = 32; // ERROR: XML tokens definition file not successfully imported.
}
}
break;
default: // help or others
echo "multiotp ".$multiotp->GetVersion()." (".$multiotp->GetDate().")".$crlf;
echo $multiotp->GetCopyright().$crlf;
echo $multiotp->GetWebsite().$crlf;
echo $crlf;
echo "multiotp will check if the token of a user is correct, based on a specified".$crlf;
echo "algorithm (currently Mobile-OTP (http://motp.sf.net), OATH/HOTP (RFC 4226) ".$crlf;
echo "and OATH/TOTP (HOTPTimeBased RFC 4226 extension) are implemented).".$crlf;
echo $crlf;
echo "If you are using the command line tool for Windows, be sure that the file".$crlf;
echo "multiotp.php is not present in the directory, otherwise conflict may appears.".$crlf;
echo $crlf;
echo "If a token is locked (return code 24), you can resync the token to unlock.".$crlf;
echo $crlf;
echo "It will return 0 for a correct token, or an error code (11-99) otherwise.".$crlf;
echo $crlf;
echo "Return codes:".$crlf;
reset($multiotp->_errors_text);
while(list($key, $value) = each($multiotp->_errors_text))
{
echo substr(' '.$key,-2)." ".$value.$crlf;
}
echo $crlf;
echo "Usage:".$crlf;
echo " multiotp [-log] -import-xml xml_tokens_definition_file.xml".$crlf;
echo " multiotp [-log] -create [-prefix-pin] user algo seed pin digits [pos|interval]".$crlf;
echo " multiotp [-log] -create -token-id [-prefix-pin] user token-id pin".$crlf;
echo " multiotp [-log] -resync [-status] user token1 token2 (two consecutive tokens)".$crlf;
echo " multiotp [-log] -update-pin user pin".$crlf;
echo " multiotp [-log] user token".$crlf;
echo " multiotp -delete user".$crlf;
echo $crlf;
echo " token-id: id of the previously imported token to attribute to the user".$crlf;
echo " user: name of the user (should be the account name)".$crlf;
echo " algo: available algorithms are mOTP, HOTP and TOTP".$crlf;
echo " seed: hexadecimal seed of the token".$crlf;
echo " pin: private pin code of the user".$crlf;
echo " digits: number of digits given by the token".$crlf;
echo " pos: for HOTP algorithm, position of the next awaited event".$crlf;
echo " interval: for mOTP and TOTP algorithms, token interval time in seconds".$crlf;
echo $crlf;
echo "Options:".$crlf;
echo " -help Display this help page".$crlf;
echo " -version Display the current version".$crlf;
echo " -prefix-pin The pin and the token must be typed merged by the user".$crlf;
echo " (if you pin is 1234 and your token displays 5556677,".$crlf;
echo " you will have to type 1234556677)".$crlf;
echo " -status Display a status bar during resynchronization".$crlf;
echo " -log Log operation in the log file (in the \log subdirectory)".$crlf;
echo " -debug Enhanced log information and code result on screen".$crlf;
echo $crlf;
echo "Examples:".$crlf;
echo " multiotp -log -debug -import-xml tokens.xml".$crlf;
echo " multiotp -log -create jimmy mOTP 004f5a158bca13984d349a7f23 1234 6 10".$crlf;
echo " multiotp -create -prefix-pin alan TOTP 3683453456769abc3452 2233 6 60".$crlf;
echo " multiotp -create -prefix-pin anna TOTP 56821bac24fbd2343393 4455 6 30".$crlf;
echo " multiotp -create -prefix-pin john HOTP 31323334353637383930 5678 6 137".$crlf;
echo " multiotp -create -token-id -prefix-pin rick 2010090201901 2345".$crlf;
echo " multiotp -resync john 5678456789 5678345231".$crlf;
echo " multiotp -resync -status anna 4455487352 4455983513".$crlf;
echo " multiotp -update-pin alan 4417".$crlf;
echo " multiotp -log -debug jimmy ea2315".$crlf;
echo " multiotp -log anna 546078".$crlf;
echo " multiotp john 5678124578".$crlf;
echo $crlf;
echo "When used with TekRADIUS (http://www.tekradius.com) the External-Executable".$crlf;
echo "must be called like this: C:\multitop\multiotp.exe %ietf|1% %ietf|2%".$crlf;
echo $crlf;
break;
}
if ($multiotp->GetVerboseFlag())
{
echo $result;
if (isset($multiotp->_errors_text[$result]))
{
echo " ".$multiotp->_errors_text[$result];
}
}
exit($result);
?>