diff --git a/Dockerfile b/Dockerfile index 54a7109..db71eeb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,8 +15,8 @@ # Please check https://www\.multiOTP.net/ and you will find the magic button ;-) # # @author Andre Liechti, SysCo systemes de communication sa, -# @version 5.9.7.0 -# @date 2023-11-23 +# @version 5.9.7.1 +# @date 2023-12-03 # @since 2013-11-29 # @copyright (c) 2013-2023 SysCo systemes de communication sa # @copyright GNU Lesser General Public License @@ -47,7 +47,7 @@ MAINTAINER Andre Liechti LABEL Description="multiOTP open source, running on Debian ${DEBIAN} with PHP${PHPVERSION}." \ License="LGPL-3.0" \ Usage="docker run -v [PATH/TO/MULTIOTP/DATA/VOLUME]:/etc/multiotp -v [PATH/TO/FREERADIUS/CONFIG/VOLUME]:/etc/freeradius -v [PATH/TO/MULTIOTP/LOG/VOLUME]:/var/log/multiotp -v [PATH/TO/FREERADIUS/LOG/VOLUME]:/var/log/freeradius -p [HOST WWW PORT NUMBER]:80 -p [HOST SSL PORT NUMBER]:443 -p [HOST RADIUS-AUTH PORT NUMBER]:1812/udp -p [HOST RADIUS-ACCNT PORT NUMBER]:1813/udp -d multiotp-open-source" \ - Version="5.9.7.0" + Version="5.9.7.1" ARG DEBIAN_FRONTEND=noninteractive diff --git a/README.md b/README.md index 9733824..672371b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ multiOTP open source is OATH certified for HOTP/TOTP (c) 2010-2023 SysCo systemes de communication sa https://www.multiotp.net/ -Current build: 5.9.7.0 (2023-11-23) +Current build: 5.9.7.1 (2023-12-03) Binary download: https://download.multiotp.net/ (including virtual appliance image) @@ -158,6 +158,15 @@ WHAT'S NEW IN THIS 5.9.x RELEASE CHANGE LOG OF RELEASED VERSIONS =============================== ``` +2023-12-03 5.9.7.1 FIX: Command line number of parameters detection corrected + ENH: It's now possible to define the number of digits for new created PIN + (multiotp -config default-pin-digits=n) + ENH: It's now possible to generate the HTML provisioning file by command line + (multiotp -htmlinfo username /full/path/to/username.html or + multiotp -htmlinfo /full/path/to/folder/ to generate files for all users) + ENH: Embedded Windows nginx edition updated to version 1.25.3 + ENH: Embedded Windows internal tools updated (wget 1.21.4 and fart 1.99d) + ENH: Embedded Windows freeradius is now launched using NSSM (instead of SRVANY) 2023-11-23 5.9.7.0 FIX: Better Windows nginx configuration support (path backslashes replaced by slashes) ENH: Embedded Windows nginx edition updated to version 1.24.0 ENH: Embedded Windows PHP edition updated to version 8.2.13 @@ -1897,7 +1906,7 @@ MULTIOTP COMMAND LINE TOOL ========================== ``` -multiOTP 5.9.7.0 (2023-11-23) +multiOTP 5.9.7.1 (2023-12-03) (c) 2010-2023 SysCo systemes de communication sa http://www.multiOTP.net (you can try the [Donate] button ;-) @@ -1973,6 +1982,7 @@ Return codes: 43 ERROR: SQL entry cannot be updated 50 ERROR: QRcode not created 51 ERROR: UrlLink not created (no provisionable client for this protocol) +52 ERROR: HTML info not created 58 ERROR: File is missing 59 ERROR: Bad restore configuration password 60 ERROR: No information on where to send SMS code @@ -2053,6 +2063,8 @@ Usage: multiotp -qrcode user png_file_name.png (only for TOTP and HOTP) multiotp -urllink user (only for TOTP and HOTP, generate provisioning URL) + multiotp -htmlinfo user htlm_file_name.html (create file for one user) or + multiotp -htmlinfo htlm_file_folder (to create all files) multiotp -scratchlist user (generate & display scratch passwords for the user) @@ -2085,6 +2097,7 @@ Usage: (code result are also displayed on the console) debug-prefix: add a prefix when using the debug mode (for example 'Reply-Message := ' for FreeRADIUS) + default-pin-digits: [4-32] set the default amount of PIN digits default-request-prefix-pin: [0|1] prefix PIN enabled/disabled by default default-request-ldap-pwd: [0|1] LDAP/AD password enabled/disabled by default display-log: [0|1] enable/disable log display on the console @@ -2140,6 +2153,7 @@ Usage: sms-api-id: SMS API id (if any, give your REST/XML API id) with exec as provider, define the script to call (available variables: %from, %to, %msg) + sms-digits: [6-32] set the default amount of SMS digits sms-ip: IP address of the SMS server (for inhouse server) sms-challenge-enabled: [0|1] enable/disable SMS challenge sms-message: SMS message to display before the OTP @@ -2420,8 +2434,8 @@ Visit https://forum.multiotp.net/ for additional support ``` ``` -Hash verification for multiotp_5.9.7.0.zip -SHA256:de7e4f9bb09586b095262b68a8f907a30b32f06f88665eb57f9174e52d8e0e76 -SHA1:d53d1f73a44a0b608f1c70c7b70ccb7fcd60a72c -MD5:1ff145782ee27acc36ed0db379a0fcab +Hash verification for multiotp_5.9.7.1.zip +SHA256:1c20cc31c707245c9fd55a71c7e4d9204eae6a20eaba87099ffc62caf19ca59b +SHA1:e3ab1db6c0b62cc2afb2866117b13fe2506a1727 +MD5:0cc3b0df1d5b760c53f7beade835817f ``` diff --git a/check.multiotp.class.php b/check.multiotp.class.php index a23afe5..5e35d47 100644 --- a/check.multiotp.class.php +++ b/check.multiotp.class.php @@ -22,8 +22,8 @@ * PHP 5.4.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2013-07-10 * @copyright (c) 2013-2023 SysCo systemes de communication sa * @copyright GNU Lesser General Public License diff --git a/checkmultiotp.cmd b/checkmultiotp.cmd index 5d42b05..1d22173 100644 --- a/checkmultiotp.cmd +++ b/checkmultiotp.cmd @@ -11,8 +11,8 @@ REM REM Windows batch file for Windows 2K/XP/2003/7/2008/8/2012/10/2019 REM REM @author Andre Liechti, SysCo systemes de communication sa, -REM @version 5.9.7.0 -REM @date 2023-11-23 +REM @version 5.9.7.1 +REM @date 2023-12-03 REM @since 2010-07-10 REM @copyright (c) 2010-2023 SysCo systemes de communication sa REM @copyright GNU Lesser General Public License @@ -493,7 +493,7 @@ IF ERRORLEVEL 1 ECHO - KO! multiOTP web service is not responding correctly on h IF ERRORLEVEL 1 TYPE "%TEMP%\multiOTPwebservice.check" IF ERRORLEVEL 1 TYPE "%TEMP%\multiOTPwebservice.check" (%_backend%) >>"%TEMP%\multiotp_error.log" SET /A TOTAL_TESTS=TOTAL_TESTS+1 -DEL "%TEMP%\multiOTPwebservice.check" /Q + DEL "%TEMP%\multiOTPwebservice.check" /Q DEL "%TEMP%\multiOTPwebservice.ready" /Q ECHO. diff --git a/launcher/ReadMe.txt b/launcher/ReadMe.txt index d7a860a..a00cc42 100644 --- a/launcher/ReadMe.txt +++ b/launcher/ReadMe.txt @@ -15,8 +15,8 @@ The multiOTP C++ launcher is simply used to launch PHP and run multiotp.windows.php with the provided arguments. @author Andre Liechti, SysCo systemes de communication sa, -@version 5.9.7.0 -@date 2023-11-23 +@version 5.9.7.1 +@date 2023-12-03 @since 2016-12-08 @copyright (c) 2010-2023 SysCo systemes de communication sa @copyright GNU Lesser General Public License diff --git a/launcher/launcher.cpp b/launcher/launcher.cpp index 2827932..168b20d 100644 --- a/launcher/launcher.cpp +++ b/launcher/launcher.cpp @@ -14,8 +14,8 @@ * and run multiotp.windows.php with the provided arguments. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2016-12-08 * @copyright (c) 2010-2023 SysCo systemes de communication sa * @copyright GNU Lesser General Public License @@ -68,8 +68,8 @@ #include #define SOFTWARE "LAUNCHPHPMULTIOTP" -#define VER_NUMBER "5.9.7.0" -#define VER_DATE "2023-11-23" +#define VER_NUMBER "5.9.7.1" +#define VER_DATE "2023-12-03" void replaceAll(std::string& str, const std::string& from, const std::string& to) { if (from.empty()) diff --git a/multiotp.class.php b/multiotp.class.php index bf15e03..9009301 100644 --- a/multiotp.class.php +++ b/multiotp.class.php @@ -72,8 +72,8 @@ * PHP 5.4.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-06-08 * @copyright (c) 2010-2023 SysCo systemes de communication sa * @copyright GNU Lesser General Public License @@ -277,8 +277,8 @@ class Multiotp * @brief Main class definition of the multiOTP project. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-07-18 */ { @@ -393,8 +393,8 @@ class Multiotp * @retval void * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-07-18 */ function __construct( @@ -418,11 +418,11 @@ function __construct( if (!isset($this->_class)) { $this->_class = base64_decode('bXVsdGlPVFA='); } if (!isset($this->_version)) { - $temp_version = '@version 5.9.7.0'; // You should add a suffix for your changes (for example 5.0.3.2-andy-2016-10-XX) + $temp_version = '@version 5.9.7.1'; // You should add a suffix for your changes (for example 5.0.3.2-andy-2016-10-XX) $this->_version = nullable_trim(mb_substr($temp_version, 8)); } if (!isset($this->_date)) { - $temp_date = '@date 2023-11-23'; // You should update the date with the date of your changes + $temp_date = '@date 2023-12-03'; // You should update the date with the date of your changes $this->_date = nullable_trim(mb_substr($temp_date, 8)); } if (!isset($this->_copyright)) { $this->_copyright = base64_decode('KGMpIDIwMTAtMjAyMyBTeXNDbyBzeXN0ZW1lcyBkZSBjb21tdW5pY2F0aW9uIHNh'); } @@ -550,6 +550,7 @@ function __construct( 'debug' => "int(1) DEFAULT 0", 'default_algorithm' => "TEXT DEFAULT 'totp'", 'default_dialin_ip_mask' => "TEXT DEFAULT ''", + 'default_pin_digits' => "int(10) DEFAULT 4", 'default_user_group' => "TEXT DEFAULT ''", 'default_request_ldap_pwd' => "int(1) DEFAULT 1", 'default_request_prefix_pin' => "int(1) DEFAULT 1", @@ -1714,6 +1715,7 @@ function ResetErrorsArray() $this->_errors_text[50] = "ERROR: QRcode not created"; $this->_errors_text[51] = "ERROR: UrlLink not created (no provisionable client for this protocol)"; + $this->_errors_text[52] = "ERROR: HTML info not created"; $this->_errors_text[58] = "ERROR: File is missing"; $this->_errors_text[59] = "ERROR: Bad restore configuration password"; @@ -7210,15 +7212,27 @@ function GetSmsMessage() function SetSmsDigits( - $value + $value ) { - $this->_config_data['sms_digits'] = intval($value); + $digits = intval($value); + if ($digits < 6) { + $digits = 6; + } elseif ($digits > 32) { + $digits = 32; + } + $this->_config_data['sms_digits'] = intval($digits); } function GetSmsDigits() { - return $this->_config_data['sms_digits']; + $value = intval($this->_config_data['sms_digits']); + if ($value < 4) { + $value = 4; + } elseif ($value > 32) { + $value = 32; + } + return intval($value); } @@ -7261,6 +7275,80 @@ function GetEmailCodeTimeout() } + function SetDefaultPinDigits( + $value + ) { + $digits = intval($value); + if ($digits < 4) { + $digits = 4; + } elseif ($digits > 32) { + $digits = 32; + } + $this->_config_data['default_pin_digits'] = intval($digits); + } + + + function GetDefaultPinDigits() + { + $value = intval($this->_config_data['default_pin_digits']); + if ($value < 4) { + $value = 4; + } elseif ($value > 32) { + $value = 32; + } + return intval($value); + } + + + function GeneratePin( + $value = 0 + ) { + $digits = intval($value); + if ($digits < 1) { + $digits = $this->GetDefaultPinDigits(); + } elseif ($digits < 4) { + $digits = 4; + } elseif ($digits > 32) { + $digits = 32; + } + do { + $regenerate = false; + $result = ''; + $last_digit = ''; + $same_counter = 0; + $increment_counter = 0; + $decrement_counter = 0; + for ($i = 0; $i < $digits; $i++) { + $digit = strval(mt_rand(0,9)); + if (strval($digit) == strval($last_digit)) { + $same_counter++; + $increment_counter = 0; + $decrement_counter = 0; + } elseif (intval($digit-1) == intval($last_digit)) { + $same_counter = 0; + $increment_counter++; + $decrement_counter = 0; + } elseif (intval($digit+1) == intval($last_digit)) { + $same_counter = 0; + $increment_counter = 0; + $decrement_counter++; + } else { + $same_counter = 0; + $increment_counter = 0; + $decrement_counter = 0; + } + if (($same_counter >= 2) || ($increment_counter >= 3) || ($decrement_counter >= 3)) { + $regenerate = true; + break; + } + $last_digit = $digit; + $result.= $digit; + } + } while ($regenerate); + return $result; + } + + function SetConfigAttribute( $attribute, $value @@ -8659,26 +8747,26 @@ function CreateUser($user_raw, $the_pin = $pin; if ('' == $the_pin) { - $the_pin = mt_rand(1000,9999); + $the_pin = $this->GeneratePin(); } $this->SetUserTokenNumberOfDigits($number_of_digits); $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,20).mb_substr(md5(mt_rand(100000,999999).date("YmdHis")),0,20):$seed); if (('hotp' == mb_strtolower($algorithm,'UTF-8')) || ('yubicootp' == mb_strtolower($algorithm,'UTF-8'))) { - $next_event = ((-1 == $time_interval_or_next_event)?0:$time_interval_or_next_event); - $time_interval = 0; + $next_event = ((-1 == $time_interval_or_next_event)?0:$time_interval_or_next_event); + $time_interval = 0; } elseif (('totp' == mb_strtolower($algorithm,'UTF-8')) || ('motp' == mb_strtolower($algorithm,'UTF-8'))) { - $next_event = 0; - $time_interval = ((-1 == $time_interval_or_next_event)?30:$time_interval_or_next_event); - if ('motp' == mb_strtolower($algorithm,'UTF-8')) { - // $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,16):$seed); - $time_interval = 10; - if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { - $the_pin = mt_rand(1000,9999); - } - $the_pin = mb_substr($the_pin, 0, 4); + $next_event = 0; + $time_interval = ((-1 == $time_interval_or_next_event)?30:$time_interval_or_next_event); + if ('motp' == mb_strtolower($algorithm,'UTF-8')) { + // $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,16):$seed); + $time_interval = 10; + if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { + $the_pin = $this->GeneratePin(4); // PIN of 4 digits is mandatory for motp } + $the_pin = mb_substr($the_pin, 0, 4); // PIN of 4 digits is mandatory for motp + } } else { // without2FA or unknown $next_event = 0; $time_interval = 0; @@ -8789,9 +8877,8 @@ function CreateUserFromToken($user_raw, $this->SetUserTokenLastEvent($this->GetTokenLastEvent()); $the_pin = $pin; - if ('' == $the_pin) - { - $the_pin = mt_rand(1000,9999); + if ('' == $the_pin) { + $the_pin = $this->GeneratePin(); } $this_email = nullable_trim($email); @@ -9481,7 +9568,7 @@ function FastCreateUser($user_raw, $this->SetUserTokenAlgoSuite(''); // Default algorithm suite (HMAC-SHA1) $the_pin = $pin; if ('' == $the_pin) { - $the_pin = mt_rand(1000,9999); + $the_pin = $this->GeneratePin(); } $this->SetUserPrefixPin($prefix_required); $this->SetUserTokenNumberOfDigits(6); @@ -9489,18 +9576,17 @@ function FastCreateUser($user_raw, $seed = mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,20).mb_substr(md5(mt_rand(100000,999999).date("YmdHis")),0,20); - if ('totp' == mb_strtolower($algorithm,'UTF-8')) - { - $time_interval = 30; + if ('totp' == mb_strtolower($algorithm,'UTF-8')) { + $time_interval = 30; } elseif ('motp' == mb_strtolower($algorithm,'UTF-8')) { - $seed = mb_substr($seed,0,16); - $time_interval = 10; - if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) - { - $the_pin = mt_rand(1000,9999); - } + $seed = mb_substr($seed,0,16); + $time_interval = 10; + if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { + $the_pin = $this->GeneratePin(4); // PIN of 4 digits is mandatory for motp + } + $the_pin = mb_substr($the_pin, 0, 4); // PIN of 4 digits is mandatory for motp } else { - $time_interval = 0; + $time_interval = 0; } $this->SetUserPin($the_pin); @@ -16273,10 +16359,7 @@ function GenerateSmsToken( $sms_number = $this->CleanPhoneNumber($this->GetUserSms()); if ('' != $sms_number) { $sms_message_prefix = nullable_trim($this->GetSmsMessage()); - $sms_now_steps = $now_epoch; - $sms_digits = $this->GetSmsDigits(); - $sms_seed_bin = hex2bin(md5('sMs'.$this->GetEncryptionKey().$this->GetUserTokenSeed().$user.$now_epoch)); - $sms_token = $this->GenerateOathHotp($sms_seed_bin,$sms_now_steps,$sms_digits); + $sms_token = $this->GeneratePin($this->GetSmsDigits()); $this->SetUserSmsOtp($sms_token); $this->SetUserSmsValidity($now_epoch + $this->GetSmsTimeout()); @@ -16322,10 +16405,7 @@ function GenerateEmailToken( } $email = $this->GetUserEmail(); if ('' != $email) { - $steps = $now_epoch; - $digits = $this->GetEmailDigits(); - $seed_bin = hex2bin(md5('EmaiL'.$this->GetEncryptionKey().$this->GetUserTokenSeed().$user.$now_epoch)); - $token = $this->GenerateOathHotp($seed_bin, $steps, $digits); + $token = $this->GeneratePin($this->GetEmailDigits()); $this->SetUserEmailOtp($token); $this->SetUserEmailValidity($now_epoch + $this->GetEmailCodeTimeout()); @@ -17162,7 +17242,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for SMS: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17263,7 +17343,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for Email: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17361,7 +17441,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for scratch password: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17612,7 +17692,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for motp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17721,7 +17801,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for hotp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17886,7 +17966,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for totp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17980,7 +18060,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for totp: $code_confirmed", false, false, 19, 'Debug', ''); } } diff --git a/multiotp.cli.header.php b/multiotp.cli.header.php index 9f63d3d..4e8738d 100644 --- a/multiotp.cli.header.php +++ b/multiotp.cli.header.php @@ -35,8 +35,8 @@ * PHP 5.4.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-06-08 * @copyright (c) 2010-2023 SysCo systemes de communication sa * @copyright GNU Lesser General Public License @@ -510,6 +510,8 @@ function clean_quotes( $command = "purge-ldap-cache-folder"; } elseif ("-qrcode" == mb_strtolower($current_arg,'UTF-8')) { $command = "qrcode"; + } elseif ("-htmlinfo" == mb_strtolower($current_arg,'UTF-8')) { + $command = "htmlinfo"; } elseif ("-requiresms" == mb_strtolower($current_arg,'UTF-8')) { $command = "requiresms"; } elseif ("-remove-token" == mb_strtolower($current_arg,'UTF-8')) { @@ -706,11 +708,6 @@ function clean_quotes( } -// Be sure that non-existent parameters are empty -for ($i = ($param_count+1); $i <= $all_args_size; $i++) { - $all_args[$i] = ''; -} - // if not enough parameters, display error message // and indicate how to display the help page if (($param_count < 1) && @@ -947,6 +944,11 @@ function clean_quotes( $all_args[$i] = ''; } + // Be sure that non-existent parameters are empty + for ($i = ($param_count+1); $i <= $all_args_size; $i++) { + $all_args[$i] = ''; + } + switch ($command) { case "mysql": if ($param_count < 1) { @@ -1092,33 +1094,37 @@ function clean_quotes( } break; case "call-method"; + $call_result = ''; if (method_exists($multiotp, $call_method)) { - if ('' != $all_args[7]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5], $all_args[6], $all_args[7]); - } elseif ('' != $all_args[6]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5], $all_args[6]); - } elseif ('' != $all_args[5]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5]); - } elseif ('' != $all_args[4]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4]); - } elseif ('' != $all_args[3]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3]); - } elseif ('' != $all_args[2]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2]); - } elseif ('' != $all_args[1]) { - $call_result = $multiotp->$call_method($all_args[1]); - } else { - $call_result = $multiotp->$call_method(); - } - if ($multiotp->GetVerboseFlag()) { - $multiotp->WriteLog('Debug: *Method '.$call_method.' returned the following result: '.print_r($call_result, true), false, false, 8888, 'Debug', ''); - } - $result = 19; + if ('' != $all_args[7]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5], $all_args[6], $all_args[7]); + } elseif ('' != $all_args[6]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5], $all_args[6]); + } elseif ('' != $all_args[5]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5]); + } elseif ('' != $all_args[4]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4]); + } elseif ('' != $all_args[3]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3]); + } elseif ('' != $all_args[2]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2]); + } elseif ('' != $all_args[1]) { + $call_result = $multiotp->$call_method($all_args[1]); + } else { + $call_result = $multiotp->$call_method(); + } + if ($multiotp->GetVerboseFlag()) { + $multiotp->WriteLog('Debug: *Method '.$call_method.' returned the following result: '.print_r($call_result, true), false, false, 8888, 'Debug', ''); + } + $result = 19; } else { - if ($multiotp->GetVerboseFlag()) { - $multiotp->WriteLog("Debug: *Method $call_method doesn't exist", false, false, 8888, 'Debug', ''); - } - $result = 99; + if ($multiotp->GetVerboseFlag()) { + $multiotp->WriteLog("Debug: *Method $call_method doesn't exist", false, false, 8888, 'Debug', ''); + } + $result = 99; + } + if ((!is_bool($call_result)) && (!is_null($call_result)) && (!empty($call_result))) { + echo $call_result; } break; case "check"; @@ -1201,87 +1207,93 @@ function clean_quotes( case "create": case "update": if (("create" == $command) && $multiotp->ReadUserData($all_args[1], true, true)) { - $result = 22; // ERROR: user already exists. + $result = 22; // ERROR: user already exists. } elseif (("update" == $command) && (!$multiotp->ReadUserData($all_args[1], false, true))) { - $result = 21; // ERROR: user doesn't exist. - } elseif ($param_count < 3) { - $result = 30; // ERROR: At least one parameter is missing + $result = 21; // ERROR: user doesn't exist. + } elseif ($param_count < 2) { + $result = 30; // ERROR: At least one parameter is missing + } elseif (($param_count < 3) && ("WITHOUT2FA" != mb_strtoupper($all_args[2],'UTF-8'))) { + $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); - $multiotp->SetUserTokenSerialNumber($multiotp->GetTokenSerialNumber()); - 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->SetUserTokenAlgoSuite($multiotp->GetTokenAlgoSuite()); - - $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 + $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->SetUserTokenSeed($all_args[3]); + $multiotp->SetUserKeyId($key_id); + $multiotp->SetUserTokenSerialNumber($multiotp->GetTokenSerialNumber()); + 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->SetUserTokenAlgoSuite($multiotp->GetTokenAlgoSuite()); + + $multiotp->SetUserPin($all_args[3]); - if ($param_count < 4) { - $result = 30; // ERROR: At least one parameter is missing + if ($multiotp->WriteUserData()) { + $result = 11; // INFO: user successfully created or updated } else { - $multiotp->SetUserPin($all_args[4]); - if ('' == $all_args[5]) { - $all_args[5] = 6; // Default number of digits is set to 6 - } - $multiotp->SetUserTokenNumberOfDigits($all_args[5]); - switch (mb_strtoupper($all_args[2],'UTF-8')) - { - // 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 - } + $result = 28; // ERROR: Unable to write the changes in the file } + } } + } elseif (!$multiotp->SetUserAlgorithm($all_args[2])) { + $result = 23; // ERROR: invalid algorithm + } elseif ("WITHOUT2FA" == mb_strtoupper($all_args[2],'UTF-8')) { + if ($multiotp->WriteUserData()) { + $result = 11; // INFO: user successfully created or updated + } else { + $result = 28; // ERROR: Unable to write the changes in the file + } + } 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 number of digits is set to 6 + } + $multiotp->SetUserTokenNumberOfDigits($all_args[5]); + switch (mb_strtoupper($all_args[2],'UTF-8')) { + // 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": @@ -1564,6 +1576,10 @@ function clean_quotes( $verbose_prefix = $multiotp->GetVerboseLogPrefix(); $write_config_data = true; break; + case 'default-pin-digits': + $multiotp->SetDefaultPinDigits(intval($actual_array[1])); + $write_config_data = true; + break; case 'default-request-prefix-pin': $multiotp->SetDefaultRequestPrefixPin(intval($actual_array[1])); $write_config_data = true; @@ -1704,6 +1720,10 @@ function clean_quotes( $multiotp->SetSmsApiId($actual_array[1]); $write_config_data = true; break; + case 'sms-digits': + $multiotp->SetSmsDigits(intval($actual_array[1])); + $write_config_data = true; + break; case 'sms-message': $multiotp->SetSmsMessage($actual_array[1]); $write_config_data = true; @@ -2020,6 +2040,40 @@ function clean_quotes( } } break; + case "htmlinfo": + if ($param_count < 2) { + if ($param_count < 1) { + $result = 30; // ERROR: At least one parameter is missing + } elseif (is_dir($all_args[1])) { + $user_list = nullable_trim($multiotp->GetUsersList()); + $users_array = explode("\t", $user_list); + if (("" != $user_list) && (count($users_array) > 0)) { + foreach ($users_array as $user) { + $content = $multiotp->GenerateHtmlQrCode($user); + if (FALSE !== file_put_contents(realpath($all_args[1]) . DIRECTORY_SEPARATOR . $user . '.html', $content)) { + $result = 16; // INFO: HTML info successfully created. + } else { + $result = 52; // INFO: HTML info not created. + break; + } + } + } else { + $result = 21; // ERROR: user doesn't exist. + } + } else { + $result = 30; // ERROR: At least one parameter is missing + } + } elseif (!$multiotp->CheckUserExists($all_args[1])) { + $result = 21; // ERROR: user doesn't exist. + } else { + $content = $multiotp->GenerateHtmlQrCode($all_args[1]); + if (FALSE !== file_put_contents($all_args[2], $content)) { + $result = 16; // INFO: HTML info successfully created. + } else { + $result = 52; // INFO: HTML info not created. + } + } + break; case "urllink": if ($param_count < 1) { $result = 30; // ERROR: At least one parameter is missing @@ -2317,6 +2371,8 @@ function clean_quotes( echo $crlf; echo " multiotp -qrcode user png_file_name.png (only for TOTP and HOTP)".$crlf; echo " multiotp -urllink user (only for TOTP and HOTP, generate provisioning URL)".$crlf; + echo " multiotp -htmlinfo user htlm_file_name.html (create file for one user) or ".$crlf; + echo " multiotp -htmlinfo htlm_file_folder (to create all files)".$crlf; echo $crlf; echo " multiotp -scratchlist user (generate & display scratch passwords for the user)".$crlf; echo $crlf; @@ -2350,6 +2406,7 @@ function clean_quotes( echo " (code result are also displayed on the console)".$crlf; echo " debug-prefix: add a prefix when using the debug mode".$crlf; echo " (for example 'Reply-Message := ' for FreeRADIUS)".$crlf; + echo " default-pin-digits: [4-32] set the default amount of PIN digits".$crlf; echo " default-request-prefix-pin: [0|1] prefix PIN enabled/disabled by default".$crlf; echo " default-request-ldap-pwd: [0|1] LDAP/AD password enabled/disabled by default".$crlf; echo " display-log: [0|1] enable/disable log display on the console".$crlf; @@ -2405,6 +2462,7 @@ function clean_quotes( echo " sms-api-id: SMS API id (if any, give your REST/XML API id)".$crlf; echo " with exec as provider, define the script to call".$crlf; echo " (available variables: %from, %to, %msg)".$crlf; + echo " sms-digits: [6-32] set the default amount of SMS digits".$crlf; echo " sms-ip: IP address of the SMS server (for inhouse server)".$crlf; echo " sms-challenge-enabled: [0|1] enable/disable SMS challenge".$crlf; echo " sms-message: SMS message to display before the OTP".$crlf; diff --git a/multiotp.cli.proxy.php b/multiotp.cli.proxy.php index 0c988a0..03429a4 100644 --- a/multiotp.cli.proxy.php +++ b/multiotp.cli.proxy.php @@ -15,8 +15,8 @@ * PHP 5.4.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-06-08 * @copyright (c) 2010-2023 SysCo systemes de communication sa * @copyright GNU Lesser General Public License diff --git a/multiotp.php b/multiotp.php index 8df1bb6..5141745 100644 --- a/multiotp.php +++ b/multiotp.php @@ -37,8 +37,8 @@ * PHP 5.4.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-06-08 * @copyright (c) 2010-2023 SysCo systemes de communication sa * @copyright GNU Lesser General Public License @@ -277,8 +277,8 @@ * PHP 5.4.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-06-08 * @copyright (c) 2010-2023 SysCo systemes de communication sa * @copyright GNU Lesser General Public License @@ -482,8 +482,8 @@ class Multiotp * @brief Main class definition of the multiOTP project. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-07-18 */ { @@ -598,8 +598,8 @@ class Multiotp * @retval void * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-07-18 */ function __construct( @@ -623,11 +623,11 @@ function __construct( if (!isset($this->_class)) { $this->_class = base64_decode('bXVsdGlPVFA='); } if (!isset($this->_version)) { - $temp_version = '@version 5.9.7.0'; // You should add a suffix for your changes (for example 5.0.3.2-andy-2016-10-XX) + $temp_version = '@version 5.9.7.1'; // You should add a suffix for your changes (for example 5.0.3.2-andy-2016-10-XX) $this->_version = nullable_trim(mb_substr($temp_version, 8)); } if (!isset($this->_date)) { - $temp_date = '@date 2023-11-23'; // You should update the date with the date of your changes + $temp_date = '@date 2023-12-03'; // You should update the date with the date of your changes $this->_date = nullable_trim(mb_substr($temp_date, 8)); } if (!isset($this->_copyright)) { $this->_copyright = base64_decode('KGMpIDIwMTAtMjAyMyBTeXNDbyBzeXN0ZW1lcyBkZSBjb21tdW5pY2F0aW9uIHNh'); } @@ -755,6 +755,7 @@ function __construct( 'debug' => "int(1) DEFAULT 0", 'default_algorithm' => "TEXT DEFAULT 'totp'", 'default_dialin_ip_mask' => "TEXT DEFAULT ''", + 'default_pin_digits' => "int(10) DEFAULT 4", 'default_user_group' => "TEXT DEFAULT ''", 'default_request_ldap_pwd' => "int(1) DEFAULT 1", 'default_request_prefix_pin' => "int(1) DEFAULT 1", @@ -1919,6 +1920,7 @@ function ResetErrorsArray() $this->_errors_text[50] = "ERROR: QRcode not created"; $this->_errors_text[51] = "ERROR: UrlLink not created (no provisionable client for this protocol)"; + $this->_errors_text[52] = "ERROR: HTML info not created"; $this->_errors_text[58] = "ERROR: File is missing"; $this->_errors_text[59] = "ERROR: Bad restore configuration password"; @@ -7415,15 +7417,27 @@ function GetSmsMessage() function SetSmsDigits( - $value + $value ) { - $this->_config_data['sms_digits'] = intval($value); + $digits = intval($value); + if ($digits < 6) { + $digits = 6; + } elseif ($digits > 32) { + $digits = 32; + } + $this->_config_data['sms_digits'] = intval($digits); } function GetSmsDigits() { - return $this->_config_data['sms_digits']; + $value = intval($this->_config_data['sms_digits']); + if ($value < 4) { + $value = 4; + } elseif ($value > 32) { + $value = 32; + } + return intval($value); } @@ -7466,6 +7480,80 @@ function GetEmailCodeTimeout() } + function SetDefaultPinDigits( + $value + ) { + $digits = intval($value); + if ($digits < 4) { + $digits = 4; + } elseif ($digits > 32) { + $digits = 32; + } + $this->_config_data['default_pin_digits'] = intval($digits); + } + + + function GetDefaultPinDigits() + { + $value = intval($this->_config_data['default_pin_digits']); + if ($value < 4) { + $value = 4; + } elseif ($value > 32) { + $value = 32; + } + return intval($value); + } + + + function GeneratePin( + $value = 0 + ) { + $digits = intval($value); + if ($digits < 1) { + $digits = $this->GetDefaultPinDigits(); + } elseif ($digits < 4) { + $digits = 4; + } elseif ($digits > 32) { + $digits = 32; + } + do { + $regenerate = false; + $result = ''; + $last_digit = ''; + $same_counter = 0; + $increment_counter = 0; + $decrement_counter = 0; + for ($i = 0; $i < $digits; $i++) { + $digit = strval(mt_rand(0,9)); + if (strval($digit) == strval($last_digit)) { + $same_counter++; + $increment_counter = 0; + $decrement_counter = 0; + } elseif (intval($digit-1) == intval($last_digit)) { + $same_counter = 0; + $increment_counter++; + $decrement_counter = 0; + } elseif (intval($digit+1) == intval($last_digit)) { + $same_counter = 0; + $increment_counter = 0; + $decrement_counter++; + } else { + $same_counter = 0; + $increment_counter = 0; + $decrement_counter = 0; + } + if (($same_counter >= 2) || ($increment_counter >= 3) || ($decrement_counter >= 3)) { + $regenerate = true; + break; + } + $last_digit = $digit; + $result.= $digit; + } + } while ($regenerate); + return $result; + } + + function SetConfigAttribute( $attribute, $value @@ -8864,26 +8952,26 @@ function CreateUser($user_raw, $the_pin = $pin; if ('' == $the_pin) { - $the_pin = mt_rand(1000,9999); + $the_pin = $this->GeneratePin(); } $this->SetUserTokenNumberOfDigits($number_of_digits); $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,20).mb_substr(md5(mt_rand(100000,999999).date("YmdHis")),0,20):$seed); if (('hotp' == mb_strtolower($algorithm,'UTF-8')) || ('yubicootp' == mb_strtolower($algorithm,'UTF-8'))) { - $next_event = ((-1 == $time_interval_or_next_event)?0:$time_interval_or_next_event); - $time_interval = 0; + $next_event = ((-1 == $time_interval_or_next_event)?0:$time_interval_or_next_event); + $time_interval = 0; } elseif (('totp' == mb_strtolower($algorithm,'UTF-8')) || ('motp' == mb_strtolower($algorithm,'UTF-8'))) { - $next_event = 0; - $time_interval = ((-1 == $time_interval_or_next_event)?30:$time_interval_or_next_event); - if ('motp' == mb_strtolower($algorithm,'UTF-8')) { - // $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,16):$seed); - $time_interval = 10; - if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { - $the_pin = mt_rand(1000,9999); - } - $the_pin = mb_substr($the_pin, 0, 4); + $next_event = 0; + $time_interval = ((-1 == $time_interval_or_next_event)?30:$time_interval_or_next_event); + if ('motp' == mb_strtolower($algorithm,'UTF-8')) { + // $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,16):$seed); + $time_interval = 10; + if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { + $the_pin = $this->GeneratePin(4); // PIN of 4 digits is mandatory for motp } + $the_pin = mb_substr($the_pin, 0, 4); // PIN of 4 digits is mandatory for motp + } } else { // without2FA or unknown $next_event = 0; $time_interval = 0; @@ -8994,9 +9082,8 @@ function CreateUserFromToken($user_raw, $this->SetUserTokenLastEvent($this->GetTokenLastEvent()); $the_pin = $pin; - if ('' == $the_pin) - { - $the_pin = mt_rand(1000,9999); + if ('' == $the_pin) { + $the_pin = $this->GeneratePin(); } $this_email = nullable_trim($email); @@ -9686,7 +9773,7 @@ function FastCreateUser($user_raw, $this->SetUserTokenAlgoSuite(''); // Default algorithm suite (HMAC-SHA1) $the_pin = $pin; if ('' == $the_pin) { - $the_pin = mt_rand(1000,9999); + $the_pin = $this->GeneratePin(); } $this->SetUserPrefixPin($prefix_required); $this->SetUserTokenNumberOfDigits(6); @@ -9694,18 +9781,17 @@ function FastCreateUser($user_raw, $seed = mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,20).mb_substr(md5(mt_rand(100000,999999).date("YmdHis")),0,20); - if ('totp' == mb_strtolower($algorithm,'UTF-8')) - { - $time_interval = 30; + if ('totp' == mb_strtolower($algorithm,'UTF-8')) { + $time_interval = 30; } elseif ('motp' == mb_strtolower($algorithm,'UTF-8')) { - $seed = mb_substr($seed,0,16); - $time_interval = 10; - if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) - { - $the_pin = mt_rand(1000,9999); - } + $seed = mb_substr($seed,0,16); + $time_interval = 10; + if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { + $the_pin = $this->GeneratePin(4); // PIN of 4 digits is mandatory for motp + } + $the_pin = mb_substr($the_pin, 0, 4); // PIN of 4 digits is mandatory for motp } else { - $time_interval = 0; + $time_interval = 0; } $this->SetUserPin($the_pin); @@ -16478,10 +16564,7 @@ function GenerateSmsToken( $sms_number = $this->CleanPhoneNumber($this->GetUserSms()); if ('' != $sms_number) { $sms_message_prefix = nullable_trim($this->GetSmsMessage()); - $sms_now_steps = $now_epoch; - $sms_digits = $this->GetSmsDigits(); - $sms_seed_bin = hex2bin(md5('sMs'.$this->GetEncryptionKey().$this->GetUserTokenSeed().$user.$now_epoch)); - $sms_token = $this->GenerateOathHotp($sms_seed_bin,$sms_now_steps,$sms_digits); + $sms_token = $this->GeneratePin($this->GetSmsDigits()); $this->SetUserSmsOtp($sms_token); $this->SetUserSmsValidity($now_epoch + $this->GetSmsTimeout()); @@ -16527,10 +16610,7 @@ function GenerateEmailToken( } $email = $this->GetUserEmail(); if ('' != $email) { - $steps = $now_epoch; - $digits = $this->GetEmailDigits(); - $seed_bin = hex2bin(md5('EmaiL'.$this->GetEncryptionKey().$this->GetUserTokenSeed().$user.$now_epoch)); - $token = $this->GenerateOathHotp($seed_bin, $steps, $digits); + $token = $this->GeneratePin($this->GetEmailDigits()); $this->SetUserEmailOtp($token); $this->SetUserEmailValidity($now_epoch + $this->GetEmailCodeTimeout()); @@ -17367,7 +17447,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for SMS: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17468,7 +17548,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for Email: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17566,7 +17646,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for scratch password: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17817,7 +17897,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for motp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17926,7 +18006,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for hotp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -18091,7 +18171,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for totp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -18185,7 +18265,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for totp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -74739,6 +74819,8 @@ function clean_quotes( $command = "purge-ldap-cache-folder"; } elseif ("-qrcode" == mb_strtolower($current_arg,'UTF-8')) { $command = "qrcode"; + } elseif ("-htmlinfo" == mb_strtolower($current_arg,'UTF-8')) { + $command = "htmlinfo"; } elseif ("-requiresms" == mb_strtolower($current_arg,'UTF-8')) { $command = "requiresms"; } elseif ("-remove-token" == mb_strtolower($current_arg,'UTF-8')) { @@ -74935,11 +75017,6 @@ function clean_quotes( } -// Be sure that non-existent parameters are empty -for ($i = ($param_count+1); $i <= $all_args_size; $i++) { - $all_args[$i] = ''; -} - // if not enough parameters, display error message // and indicate how to display the help page if (($param_count < 1) && @@ -75176,6 +75253,11 @@ function clean_quotes( $all_args[$i] = ''; } + // Be sure that non-existent parameters are empty + for ($i = ($param_count+1); $i <= $all_args_size; $i++) { + $all_args[$i] = ''; + } + switch ($command) { case "mysql": if ($param_count < 1) { @@ -75321,33 +75403,37 @@ function clean_quotes( } break; case "call-method"; + $call_result = ''; if (method_exists($multiotp, $call_method)) { - if ('' != $all_args[7]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5], $all_args[6], $all_args[7]); - } elseif ('' != $all_args[6]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5], $all_args[6]); - } elseif ('' != $all_args[5]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5]); - } elseif ('' != $all_args[4]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4]); - } elseif ('' != $all_args[3]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3]); - } elseif ('' != $all_args[2]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2]); - } elseif ('' != $all_args[1]) { - $call_result = $multiotp->$call_method($all_args[1]); - } else { - $call_result = $multiotp->$call_method(); - } - if ($multiotp->GetVerboseFlag()) { - $multiotp->WriteLog('Debug: *Method '.$call_method.' returned the following result: '.print_r($call_result, true), false, false, 8888, 'Debug', ''); - } - $result = 19; + if ('' != $all_args[7]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5], $all_args[6], $all_args[7]); + } elseif ('' != $all_args[6]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5], $all_args[6]); + } elseif ('' != $all_args[5]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5]); + } elseif ('' != $all_args[4]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4]); + } elseif ('' != $all_args[3]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3]); + } elseif ('' != $all_args[2]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2]); + } elseif ('' != $all_args[1]) { + $call_result = $multiotp->$call_method($all_args[1]); + } else { + $call_result = $multiotp->$call_method(); + } + if ($multiotp->GetVerboseFlag()) { + $multiotp->WriteLog('Debug: *Method '.$call_method.' returned the following result: '.print_r($call_result, true), false, false, 8888, 'Debug', ''); + } + $result = 19; } else { - if ($multiotp->GetVerboseFlag()) { - $multiotp->WriteLog("Debug: *Method $call_method doesn't exist", false, false, 8888, 'Debug', ''); - } - $result = 99; + if ($multiotp->GetVerboseFlag()) { + $multiotp->WriteLog("Debug: *Method $call_method doesn't exist", false, false, 8888, 'Debug', ''); + } + $result = 99; + } + if ((!is_bool($call_result)) && (!is_null($call_result)) && (!empty($call_result))) { + echo $call_result; } break; case "check"; @@ -75430,87 +75516,93 @@ function clean_quotes( case "create": case "update": if (("create" == $command) && $multiotp->ReadUserData($all_args[1], true, true)) { - $result = 22; // ERROR: user already exists. + $result = 22; // ERROR: user already exists. } elseif (("update" == $command) && (!$multiotp->ReadUserData($all_args[1], false, true))) { - $result = 21; // ERROR: user doesn't exist. - } elseif ($param_count < 3) { - $result = 30; // ERROR: At least one parameter is missing + $result = 21; // ERROR: user doesn't exist. + } elseif ($param_count < 2) { + $result = 30; // ERROR: At least one parameter is missing + } elseif (($param_count < 3) && ("WITHOUT2FA" != mb_strtoupper($all_args[2],'UTF-8'))) { + $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); - $multiotp->SetUserTokenSerialNumber($multiotp->GetTokenSerialNumber()); - 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->SetUserTokenAlgoSuite($multiotp->GetTokenAlgoSuite()); - - $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 + $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->SetUserTokenSeed($all_args[3]); + $multiotp->SetUserKeyId($key_id); + $multiotp->SetUserTokenSerialNumber($multiotp->GetTokenSerialNumber()); + 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->SetUserTokenAlgoSuite($multiotp->GetTokenAlgoSuite()); + + $multiotp->SetUserPin($all_args[3]); - if ($param_count < 4) { - $result = 30; // ERROR: At least one parameter is missing + if ($multiotp->WriteUserData()) { + $result = 11; // INFO: user successfully created or updated } else { - $multiotp->SetUserPin($all_args[4]); - if ('' == $all_args[5]) { - $all_args[5] = 6; // Default number of digits is set to 6 - } - $multiotp->SetUserTokenNumberOfDigits($all_args[5]); - switch (mb_strtoupper($all_args[2],'UTF-8')) - { - // 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 - } + $result = 28; // ERROR: Unable to write the changes in the file } + } + } + } elseif (!$multiotp->SetUserAlgorithm($all_args[2])) { + $result = 23; // ERROR: invalid algorithm + } elseif ("WITHOUT2FA" == mb_strtoupper($all_args[2],'UTF-8')) { + if ($multiotp->WriteUserData()) { + $result = 11; // INFO: user successfully created or updated + } else { + $result = 28; // ERROR: Unable to write the changes in the file } + } 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 number of digits is set to 6 + } + $multiotp->SetUserTokenNumberOfDigits($all_args[5]); + switch (mb_strtoupper($all_args[2],'UTF-8')) { + // 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": @@ -75793,6 +75885,10 @@ function clean_quotes( $verbose_prefix = $multiotp->GetVerboseLogPrefix(); $write_config_data = true; break; + case 'default-pin-digits': + $multiotp->SetDefaultPinDigits(intval($actual_array[1])); + $write_config_data = true; + break; case 'default-request-prefix-pin': $multiotp->SetDefaultRequestPrefixPin(intval($actual_array[1])); $write_config_data = true; @@ -75933,6 +76029,10 @@ function clean_quotes( $multiotp->SetSmsApiId($actual_array[1]); $write_config_data = true; break; + case 'sms-digits': + $multiotp->SetSmsDigits(intval($actual_array[1])); + $write_config_data = true; + break; case 'sms-message': $multiotp->SetSmsMessage($actual_array[1]); $write_config_data = true; @@ -76249,6 +76349,40 @@ function clean_quotes( } } break; + case "htmlinfo": + if ($param_count < 2) { + if ($param_count < 1) { + $result = 30; // ERROR: At least one parameter is missing + } elseif (is_dir($all_args[1])) { + $user_list = nullable_trim($multiotp->GetUsersList()); + $users_array = explode("\t", $user_list); + if (("" != $user_list) && (count($users_array) > 0)) { + foreach ($users_array as $user) { + $content = $multiotp->GenerateHtmlQrCode($user); + if (FALSE !== file_put_contents(realpath($all_args[1]) . DIRECTORY_SEPARATOR . $user . '.html', $content)) { + $result = 16; // INFO: HTML info successfully created. + } else { + $result = 52; // INFO: HTML info not created. + break; + } + } + } else { + $result = 21; // ERROR: user doesn't exist. + } + } else { + $result = 30; // ERROR: At least one parameter is missing + } + } elseif (!$multiotp->CheckUserExists($all_args[1])) { + $result = 21; // ERROR: user doesn't exist. + } else { + $content = $multiotp->GenerateHtmlQrCode($all_args[1]); + if (FALSE !== file_put_contents($all_args[2], $content)) { + $result = 16; // INFO: HTML info successfully created. + } else { + $result = 52; // INFO: HTML info not created. + } + } + break; case "urllink": if ($param_count < 1) { $result = 30; // ERROR: At least one parameter is missing @@ -76546,6 +76680,8 @@ function clean_quotes( echo $crlf; echo " multiotp -qrcode user png_file_name.png (only for TOTP and HOTP)".$crlf; echo " multiotp -urllink user (only for TOTP and HOTP, generate provisioning URL)".$crlf; + echo " multiotp -htmlinfo user htlm_file_name.html (create file for one user) or ".$crlf; + echo " multiotp -htmlinfo htlm_file_folder (to create all files)".$crlf; echo $crlf; echo " multiotp -scratchlist user (generate & display scratch passwords for the user)".$crlf; echo $crlf; @@ -76579,6 +76715,7 @@ function clean_quotes( echo " (code result are also displayed on the console)".$crlf; echo " debug-prefix: add a prefix when using the debug mode".$crlf; echo " (for example 'Reply-Message := ' for FreeRADIUS)".$crlf; + echo " default-pin-digits: [4-32] set the default amount of PIN digits".$crlf; echo " default-request-prefix-pin: [0|1] prefix PIN enabled/disabled by default".$crlf; echo " default-request-ldap-pwd: [0|1] LDAP/AD password enabled/disabled by default".$crlf; echo " display-log: [0|1] enable/disable log display on the console".$crlf; @@ -76634,6 +76771,7 @@ function clean_quotes( echo " sms-api-id: SMS API id (if any, give your REST/XML API id)".$crlf; echo " with exec as provider, define the script to call".$crlf; echo " (available variables: %from, %to, %msg)".$crlf; + echo " sms-digits: [6-32] set the default amount of SMS digits".$crlf; echo " sms-ip: IP address of the SMS server (for inhouse server)".$crlf; echo " sms-challenge-enabled: [0|1] enable/disable SMS challenge".$crlf; echo " sms-message: SMS message to display before the OTP".$crlf; diff --git a/multiotp.server.php b/multiotp.server.php index 4eb236e..0d386be 100644 --- a/multiotp.server.php +++ b/multiotp.server.php @@ -27,8 +27,8 @@ * PHP 5.4.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2013-08-06 * @copyright (c) 2013-2023 SysCo systemes de communication sa * @copyright GNU Lesser General Public License diff --git a/radius_debug.cmd b/radius_debug.cmd index db29511..9b3f91a 100644 --- a/radius_debug.cmd +++ b/radius_debug.cmd @@ -9,8 +9,8 @@ REM REM Windows batch file for Windows 2K/XP/2003/7/2008/8/2012/10 REM REM @author Andre Liechti, SysCo systemes de communication sa, -REM @version 5.9.7.0 -REM @date 2023-11-23 +REM @version 5.9.7.1 +REM @date 2023-12-03 REM @since 2014-04-22 REM @copyright (c) 2014-2023 SysCo systemes de communication sa REM @copyright GNU Lesser General Public License diff --git a/radius_install.cmd b/radius_install.cmd index 21f7673..5069604 100644 --- a/radius_install.cmd +++ b/radius_install.cmd @@ -9,8 +9,8 @@ REM REM Windows batch file for Windows 2K/XP/2003/7/2008/8/2012/10 REM REM @author Andre Liechti, SysCo systemes de communication sa, -REM @version 5.9.7.0 -REM @date 2023-11-23 +REM @version 5.9.7.1 +REM @date 2023-12-03 REM @since 2013-08-20 REM @copyright (c) 2013-2023 SysCo systemes de communication sa REM @copyright GNU Lesser General Public License diff --git a/radius_uninstall.cmd b/radius_uninstall.cmd index 2ec94a3..5c19b24 100644 --- a/radius_uninstall.cmd +++ b/radius_uninstall.cmd @@ -9,8 +9,8 @@ REM REM Windows batch file for Windows 2K/XP/2003/7/2008/8/2012/10 REM REM @author Andre Liechti, SysCo systemes de communication sa, -REM @version 5.9.7.0 -REM @date 2023-11-23 +REM @version 5.9.7.1 +REM @date 2023-12-03 REM @since 2013-08-20 REM @copyright (c) 2013-2023 SysCo systemes de communication sa REM @copyright GNU Lesser General Public License diff --git a/raspberry/boot-part/install.sh b/raspberry/boot-part/install.sh index 09f4964..a80cec8 100644 --- a/raspberry/boot-part/install.sh +++ b/raspberry/boot-part/install.sh @@ -16,8 +16,8 @@ # Please check https://www.multiotp.net/ and you will find the magic button ;-) # # @author Andre Liechti, SysCo systemes de communication sa, -# @version 5.9.7.0 -# @date 2023-11-23 +# @version 5.9.7.1 +# @date 2023-12-03 # @since 2013-11-29 # @copyright (c) 2013-2022 by SysCo systemes de communication sa # @copyright GNU Lesser General Public License @@ -71,7 +71,7 @@ SSH_ROOT_LOGIN="1" DEFAULT_IP="192.168.1.44" REBOOT_AT_THE_END="1" -TEMPVERSION="@version 5.9.7.0" +TEMPVERSION="@version 5.9.7.1" MULTIOTPVERSION="$(echo -e "${TEMPVERSION:8}" | tr -d '[[:space:]]')" IFS='.' read -ra MULTIOTPVERSIONARRAY <<< "$MULTIOTPVERSION" MULTIOTPMAJORVERSION=${MULTIOTPVERSIONARRAY[0]} diff --git a/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/index.php b/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/index.php index 8db463a..cf2a77b 100644 --- a/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/index.php +++ b/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/index.php @@ -27,8 +27,8 @@ * PHP 5.4.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2013-08-06 * @copyright (c) 2013-2023 SysCo systemes de communication sa * @copyright GNU Lesser General Public License @@ -192,8 +192,8 @@ * PHP 5.4.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-06-08 * @copyright (c) 2010-2023 SysCo systemes de communication sa * @copyright GNU Lesser General Public License @@ -397,8 +397,8 @@ class Multiotp * @brief Main class definition of the multiOTP project. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-07-18 */ { @@ -513,8 +513,8 @@ class Multiotp * @retval void * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-07-18 */ function __construct( @@ -538,11 +538,11 @@ function __construct( if (!isset($this->_class)) { $this->_class = base64_decode('bXVsdGlPVFA='); } if (!isset($this->_version)) { - $temp_version = '@version 5.9.7.0'; // You should add a suffix for your changes (for example 5.0.3.2-andy-2016-10-XX) + $temp_version = '@version 5.9.7.1'; // You should add a suffix for your changes (for example 5.0.3.2-andy-2016-10-XX) $this->_version = nullable_trim(mb_substr($temp_version, 8)); } if (!isset($this->_date)) { - $temp_date = '@date 2023-11-23'; // You should update the date with the date of your changes + $temp_date = '@date 2023-12-03'; // You should update the date with the date of your changes $this->_date = nullable_trim(mb_substr($temp_date, 8)); } if (!isset($this->_copyright)) { $this->_copyright = base64_decode('KGMpIDIwMTAtMjAyMyBTeXNDbyBzeXN0ZW1lcyBkZSBjb21tdW5pY2F0aW9uIHNh'); } @@ -670,6 +670,7 @@ function __construct( 'debug' => "int(1) DEFAULT 0", 'default_algorithm' => "TEXT DEFAULT 'totp'", 'default_dialin_ip_mask' => "TEXT DEFAULT ''", + 'default_pin_digits' => "int(10) DEFAULT 4", 'default_user_group' => "TEXT DEFAULT ''", 'default_request_ldap_pwd' => "int(1) DEFAULT 1", 'default_request_prefix_pin' => "int(1) DEFAULT 1", @@ -1834,6 +1835,7 @@ function ResetErrorsArray() $this->_errors_text[50] = "ERROR: QRcode not created"; $this->_errors_text[51] = "ERROR: UrlLink not created (no provisionable client for this protocol)"; + $this->_errors_text[52] = "ERROR: HTML info not created"; $this->_errors_text[58] = "ERROR: File is missing"; $this->_errors_text[59] = "ERROR: Bad restore configuration password"; @@ -7330,15 +7332,27 @@ function GetSmsMessage() function SetSmsDigits( - $value + $value ) { - $this->_config_data['sms_digits'] = intval($value); + $digits = intval($value); + if ($digits < 6) { + $digits = 6; + } elseif ($digits > 32) { + $digits = 32; + } + $this->_config_data['sms_digits'] = intval($digits); } function GetSmsDigits() { - return $this->_config_data['sms_digits']; + $value = intval($this->_config_data['sms_digits']); + if ($value < 4) { + $value = 4; + } elseif ($value > 32) { + $value = 32; + } + return intval($value); } @@ -7381,6 +7395,80 @@ function GetEmailCodeTimeout() } + function SetDefaultPinDigits( + $value + ) { + $digits = intval($value); + if ($digits < 4) { + $digits = 4; + } elseif ($digits > 32) { + $digits = 32; + } + $this->_config_data['default_pin_digits'] = intval($digits); + } + + + function GetDefaultPinDigits() + { + $value = intval($this->_config_data['default_pin_digits']); + if ($value < 4) { + $value = 4; + } elseif ($value > 32) { + $value = 32; + } + return intval($value); + } + + + function GeneratePin( + $value = 0 + ) { + $digits = intval($value); + if ($digits < 1) { + $digits = $this->GetDefaultPinDigits(); + } elseif ($digits < 4) { + $digits = 4; + } elseif ($digits > 32) { + $digits = 32; + } + do { + $regenerate = false; + $result = ''; + $last_digit = ''; + $same_counter = 0; + $increment_counter = 0; + $decrement_counter = 0; + for ($i = 0; $i < $digits; $i++) { + $digit = strval(mt_rand(0,9)); + if (strval($digit) == strval($last_digit)) { + $same_counter++; + $increment_counter = 0; + $decrement_counter = 0; + } elseif (intval($digit-1) == intval($last_digit)) { + $same_counter = 0; + $increment_counter++; + $decrement_counter = 0; + } elseif (intval($digit+1) == intval($last_digit)) { + $same_counter = 0; + $increment_counter = 0; + $decrement_counter++; + } else { + $same_counter = 0; + $increment_counter = 0; + $decrement_counter = 0; + } + if (($same_counter >= 2) || ($increment_counter >= 3) || ($decrement_counter >= 3)) { + $regenerate = true; + break; + } + $last_digit = $digit; + $result.= $digit; + } + } while ($regenerate); + return $result; + } + + function SetConfigAttribute( $attribute, $value @@ -8779,26 +8867,26 @@ function CreateUser($user_raw, $the_pin = $pin; if ('' == $the_pin) { - $the_pin = mt_rand(1000,9999); + $the_pin = $this->GeneratePin(); } $this->SetUserTokenNumberOfDigits($number_of_digits); $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,20).mb_substr(md5(mt_rand(100000,999999).date("YmdHis")),0,20):$seed); if (('hotp' == mb_strtolower($algorithm,'UTF-8')) || ('yubicootp' == mb_strtolower($algorithm,'UTF-8'))) { - $next_event = ((-1 == $time_interval_or_next_event)?0:$time_interval_or_next_event); - $time_interval = 0; + $next_event = ((-1 == $time_interval_or_next_event)?0:$time_interval_or_next_event); + $time_interval = 0; } elseif (('totp' == mb_strtolower($algorithm,'UTF-8')) || ('motp' == mb_strtolower($algorithm,'UTF-8'))) { - $next_event = 0; - $time_interval = ((-1 == $time_interval_or_next_event)?30:$time_interval_or_next_event); - if ('motp' == mb_strtolower($algorithm,'UTF-8')) { - // $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,16):$seed); - $time_interval = 10; - if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { - $the_pin = mt_rand(1000,9999); - } - $the_pin = mb_substr($the_pin, 0, 4); + $next_event = 0; + $time_interval = ((-1 == $time_interval_or_next_event)?30:$time_interval_or_next_event); + if ('motp' == mb_strtolower($algorithm,'UTF-8')) { + // $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,16):$seed); + $time_interval = 10; + if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { + $the_pin = $this->GeneratePin(4); // PIN of 4 digits is mandatory for motp } + $the_pin = mb_substr($the_pin, 0, 4); // PIN of 4 digits is mandatory for motp + } } else { // without2FA or unknown $next_event = 0; $time_interval = 0; @@ -8909,9 +8997,8 @@ function CreateUserFromToken($user_raw, $this->SetUserTokenLastEvent($this->GetTokenLastEvent()); $the_pin = $pin; - if ('' == $the_pin) - { - $the_pin = mt_rand(1000,9999); + if ('' == $the_pin) { + $the_pin = $this->GeneratePin(); } $this_email = nullable_trim($email); @@ -9601,7 +9688,7 @@ function FastCreateUser($user_raw, $this->SetUserTokenAlgoSuite(''); // Default algorithm suite (HMAC-SHA1) $the_pin = $pin; if ('' == $the_pin) { - $the_pin = mt_rand(1000,9999); + $the_pin = $this->GeneratePin(); } $this->SetUserPrefixPin($prefix_required); $this->SetUserTokenNumberOfDigits(6); @@ -9609,18 +9696,17 @@ function FastCreateUser($user_raw, $seed = mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,20).mb_substr(md5(mt_rand(100000,999999).date("YmdHis")),0,20); - if ('totp' == mb_strtolower($algorithm,'UTF-8')) - { - $time_interval = 30; + if ('totp' == mb_strtolower($algorithm,'UTF-8')) { + $time_interval = 30; } elseif ('motp' == mb_strtolower($algorithm,'UTF-8')) { - $seed = mb_substr($seed,0,16); - $time_interval = 10; - if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) - { - $the_pin = mt_rand(1000,9999); - } + $seed = mb_substr($seed,0,16); + $time_interval = 10; + if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { + $the_pin = $this->GeneratePin(4); // PIN of 4 digits is mandatory for motp + } + $the_pin = mb_substr($the_pin, 0, 4); // PIN of 4 digits is mandatory for motp } else { - $time_interval = 0; + $time_interval = 0; } $this->SetUserPin($the_pin); @@ -16393,10 +16479,7 @@ function GenerateSmsToken( $sms_number = $this->CleanPhoneNumber($this->GetUserSms()); if ('' != $sms_number) { $sms_message_prefix = nullable_trim($this->GetSmsMessage()); - $sms_now_steps = $now_epoch; - $sms_digits = $this->GetSmsDigits(); - $sms_seed_bin = hex2bin(md5('sMs'.$this->GetEncryptionKey().$this->GetUserTokenSeed().$user.$now_epoch)); - $sms_token = $this->GenerateOathHotp($sms_seed_bin,$sms_now_steps,$sms_digits); + $sms_token = $this->GeneratePin($this->GetSmsDigits()); $this->SetUserSmsOtp($sms_token); $this->SetUserSmsValidity($now_epoch + $this->GetSmsTimeout()); @@ -16442,10 +16525,7 @@ function GenerateEmailToken( } $email = $this->GetUserEmail(); if ('' != $email) { - $steps = $now_epoch; - $digits = $this->GetEmailDigits(); - $seed_bin = hex2bin(md5('EmaiL'.$this->GetEncryptionKey().$this->GetUserTokenSeed().$user.$now_epoch)); - $token = $this->GenerateOathHotp($seed_bin, $steps, $digits); + $token = $this->GeneratePin($this->GetEmailDigits()); $this->SetUserEmailOtp($token); $this->SetUserEmailValidity($now_epoch + $this->GetEmailCodeTimeout()); @@ -17282,7 +17362,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for SMS: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17383,7 +17463,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for Email: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17481,7 +17561,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for scratch password: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17732,7 +17812,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for motp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17841,7 +17921,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for hotp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -18006,7 +18086,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for totp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -18100,7 +18180,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for totp: $code_confirmed", false, false, 19, 'Debug', ''); } } diff --git a/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/multiotp.class.php b/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/multiotp.class.php index 815f2c7..fce12f7 100644 --- a/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/multiotp.class.php +++ b/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/multiotp.class.php @@ -72,8 +72,8 @@ * PHP 5.4.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-06-08 * @copyright (c) 2010-2023 SysCo systemes de communication sa * @copyright GNU Lesser General Public License @@ -277,8 +277,8 @@ class Multiotp * @brief Main class definition of the multiOTP project. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-07-18 */ { @@ -393,8 +393,8 @@ class Multiotp * @retval void * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-07-18 */ function __construct( @@ -418,11 +418,11 @@ function __construct( if (!isset($this->_class)) { $this->_class = base64_decode('bXVsdGlPVFA='); } if (!isset($this->_version)) { - $temp_version = '@version 5.9.7.0'; // You should add a suffix for your changes (for example 5.0.3.2-andy-2016-10-XX) + $temp_version = '@version 5.9.7.1'; // You should add a suffix for your changes (for example 5.0.3.2-andy-2016-10-XX) $this->_version = nullable_trim(mb_substr($temp_version, 8)); } if (!isset($this->_date)) { - $temp_date = '@date 2023-11-23'; // You should update the date with the date of your changes + $temp_date = '@date 2023-12-03'; // You should update the date with the date of your changes $this->_date = nullable_trim(mb_substr($temp_date, 8)); } if (!isset($this->_copyright)) { $this->_copyright = base64_decode('KGMpIDIwMTAtMjAyMyBTeXNDbyBzeXN0ZW1lcyBkZSBjb21tdW5pY2F0aW9uIHNh'); } @@ -550,6 +550,7 @@ function __construct( 'debug' => "int(1) DEFAULT 0", 'default_algorithm' => "TEXT DEFAULT 'totp'", 'default_dialin_ip_mask' => "TEXT DEFAULT ''", + 'default_pin_digits' => "int(10) DEFAULT 4", 'default_user_group' => "TEXT DEFAULT ''", 'default_request_ldap_pwd' => "int(1) DEFAULT 1", 'default_request_prefix_pin' => "int(1) DEFAULT 1", @@ -1714,6 +1715,7 @@ function ResetErrorsArray() $this->_errors_text[50] = "ERROR: QRcode not created"; $this->_errors_text[51] = "ERROR: UrlLink not created (no provisionable client for this protocol)"; + $this->_errors_text[52] = "ERROR: HTML info not created"; $this->_errors_text[58] = "ERROR: File is missing"; $this->_errors_text[59] = "ERROR: Bad restore configuration password"; @@ -7210,15 +7212,27 @@ function GetSmsMessage() function SetSmsDigits( - $value + $value ) { - $this->_config_data['sms_digits'] = intval($value); + $digits = intval($value); + if ($digits < 6) { + $digits = 6; + } elseif ($digits > 32) { + $digits = 32; + } + $this->_config_data['sms_digits'] = intval($digits); } function GetSmsDigits() { - return $this->_config_data['sms_digits']; + $value = intval($this->_config_data['sms_digits']); + if ($value < 4) { + $value = 4; + } elseif ($value > 32) { + $value = 32; + } + return intval($value); } @@ -7261,6 +7275,80 @@ function GetEmailCodeTimeout() } + function SetDefaultPinDigits( + $value + ) { + $digits = intval($value); + if ($digits < 4) { + $digits = 4; + } elseif ($digits > 32) { + $digits = 32; + } + $this->_config_data['default_pin_digits'] = intval($digits); + } + + + function GetDefaultPinDigits() + { + $value = intval($this->_config_data['default_pin_digits']); + if ($value < 4) { + $value = 4; + } elseif ($value > 32) { + $value = 32; + } + return intval($value); + } + + + function GeneratePin( + $value = 0 + ) { + $digits = intval($value); + if ($digits < 1) { + $digits = $this->GetDefaultPinDigits(); + } elseif ($digits < 4) { + $digits = 4; + } elseif ($digits > 32) { + $digits = 32; + } + do { + $regenerate = false; + $result = ''; + $last_digit = ''; + $same_counter = 0; + $increment_counter = 0; + $decrement_counter = 0; + for ($i = 0; $i < $digits; $i++) { + $digit = strval(mt_rand(0,9)); + if (strval($digit) == strval($last_digit)) { + $same_counter++; + $increment_counter = 0; + $decrement_counter = 0; + } elseif (intval($digit-1) == intval($last_digit)) { + $same_counter = 0; + $increment_counter++; + $decrement_counter = 0; + } elseif (intval($digit+1) == intval($last_digit)) { + $same_counter = 0; + $increment_counter = 0; + $decrement_counter++; + } else { + $same_counter = 0; + $increment_counter = 0; + $decrement_counter = 0; + } + if (($same_counter >= 2) || ($increment_counter >= 3) || ($decrement_counter >= 3)) { + $regenerate = true; + break; + } + $last_digit = $digit; + $result.= $digit; + } + } while ($regenerate); + return $result; + } + + function SetConfigAttribute( $attribute, $value @@ -8659,26 +8747,26 @@ function CreateUser($user_raw, $the_pin = $pin; if ('' == $the_pin) { - $the_pin = mt_rand(1000,9999); + $the_pin = $this->GeneratePin(); } $this->SetUserTokenNumberOfDigits($number_of_digits); $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,20).mb_substr(md5(mt_rand(100000,999999).date("YmdHis")),0,20):$seed); if (('hotp' == mb_strtolower($algorithm,'UTF-8')) || ('yubicootp' == mb_strtolower($algorithm,'UTF-8'))) { - $next_event = ((-1 == $time_interval_or_next_event)?0:$time_interval_or_next_event); - $time_interval = 0; + $next_event = ((-1 == $time_interval_or_next_event)?0:$time_interval_or_next_event); + $time_interval = 0; } elseif (('totp' == mb_strtolower($algorithm,'UTF-8')) || ('motp' == mb_strtolower($algorithm,'UTF-8'))) { - $next_event = 0; - $time_interval = ((-1 == $time_interval_or_next_event)?30:$time_interval_or_next_event); - if ('motp' == mb_strtolower($algorithm,'UTF-8')) { - // $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,16):$seed); - $time_interval = 10; - if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { - $the_pin = mt_rand(1000,9999); - } - $the_pin = mb_substr($the_pin, 0, 4); + $next_event = 0; + $time_interval = ((-1 == $time_interval_or_next_event)?30:$time_interval_or_next_event); + if ('motp' == mb_strtolower($algorithm,'UTF-8')) { + // $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,16):$seed); + $time_interval = 10; + if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { + $the_pin = $this->GeneratePin(4); // PIN of 4 digits is mandatory for motp } + $the_pin = mb_substr($the_pin, 0, 4); // PIN of 4 digits is mandatory for motp + } } else { // without2FA or unknown $next_event = 0; $time_interval = 0; @@ -8789,9 +8877,8 @@ function CreateUserFromToken($user_raw, $this->SetUserTokenLastEvent($this->GetTokenLastEvent()); $the_pin = $pin; - if ('' == $the_pin) - { - $the_pin = mt_rand(1000,9999); + if ('' == $the_pin) { + $the_pin = $this->GeneratePin(); } $this_email = nullable_trim($email); @@ -9481,7 +9568,7 @@ function FastCreateUser($user_raw, $this->SetUserTokenAlgoSuite(''); // Default algorithm suite (HMAC-SHA1) $the_pin = $pin; if ('' == $the_pin) { - $the_pin = mt_rand(1000,9999); + $the_pin = $this->GeneratePin(); } $this->SetUserPrefixPin($prefix_required); $this->SetUserTokenNumberOfDigits(6); @@ -9489,18 +9576,17 @@ function FastCreateUser($user_raw, $seed = mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,20).mb_substr(md5(mt_rand(100000,999999).date("YmdHis")),0,20); - if ('totp' == mb_strtolower($algorithm,'UTF-8')) - { - $time_interval = 30; + if ('totp' == mb_strtolower($algorithm,'UTF-8')) { + $time_interval = 30; } elseif ('motp' == mb_strtolower($algorithm,'UTF-8')) { - $seed = mb_substr($seed,0,16); - $time_interval = 10; - if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) - { - $the_pin = mt_rand(1000,9999); - } + $seed = mb_substr($seed,0,16); + $time_interval = 10; + if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { + $the_pin = $this->GeneratePin(4); // PIN of 4 digits is mandatory for motp + } + $the_pin = mb_substr($the_pin, 0, 4); // PIN of 4 digits is mandatory for motp } else { - $time_interval = 0; + $time_interval = 0; } $this->SetUserPin($the_pin); @@ -16273,10 +16359,7 @@ function GenerateSmsToken( $sms_number = $this->CleanPhoneNumber($this->GetUserSms()); if ('' != $sms_number) { $sms_message_prefix = nullable_trim($this->GetSmsMessage()); - $sms_now_steps = $now_epoch; - $sms_digits = $this->GetSmsDigits(); - $sms_seed_bin = hex2bin(md5('sMs'.$this->GetEncryptionKey().$this->GetUserTokenSeed().$user.$now_epoch)); - $sms_token = $this->GenerateOathHotp($sms_seed_bin,$sms_now_steps,$sms_digits); + $sms_token = $this->GeneratePin($this->GetSmsDigits()); $this->SetUserSmsOtp($sms_token); $this->SetUserSmsValidity($now_epoch + $this->GetSmsTimeout()); @@ -16322,10 +16405,7 @@ function GenerateEmailToken( } $email = $this->GetUserEmail(); if ('' != $email) { - $steps = $now_epoch; - $digits = $this->GetEmailDigits(); - $seed_bin = hex2bin(md5('EmaiL'.$this->GetEncryptionKey().$this->GetUserTokenSeed().$user.$now_epoch)); - $token = $this->GenerateOathHotp($seed_bin, $steps, $digits); + $token = $this->GeneratePin($this->GetEmailDigits()); $this->SetUserEmailOtp($token); $this->SetUserEmailValidity($now_epoch + $this->GetEmailCodeTimeout()); @@ -17162,7 +17242,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for SMS: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17263,7 +17343,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for Email: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17361,7 +17441,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for scratch password: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17612,7 +17692,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for motp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17721,7 +17801,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for hotp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17886,7 +17966,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for totp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17980,7 +18060,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for totp: $code_confirmed", false, false, 19, 'Debug', ''); } } diff --git a/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/multiotp.php b/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/multiotp.php index 1d87958..dcff8af 100644 --- a/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/multiotp.php +++ b/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/multiotp.php @@ -17,8 +17,8 @@ * PHP 5.4.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-06-08 * @copyright (c) 2010-2023 SysCo systemes de communication sa * @copyright GNU Lesser General Public License diff --git a/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/multiotp.proxy.php b/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/multiotp.proxy.php index 31e336b..6011c34 100644 --- a/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/multiotp.proxy.php +++ b/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/multiotp.proxy.php @@ -36,8 +36,8 @@ * PHP 5.4.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-06-08 * @copyright (c) 2010-2023 SysCo systemes de communication sa * @copyright GNU Lesser General Public License @@ -276,8 +276,8 @@ * PHP 5.4.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-06-08 * @copyright (c) 2010-2023 SysCo systemes de communication sa * @copyright GNU Lesser General Public License @@ -481,8 +481,8 @@ class Multiotp * @brief Main class definition of the multiOTP project. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-07-18 */ { @@ -597,8 +597,8 @@ class Multiotp * @retval void * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.9.7.0 - * @date 2023-11-23 + * @version 5.9.7.1 + * @date 2023-12-03 * @since 2010-07-18 */ function __construct( @@ -622,11 +622,11 @@ function __construct( if (!isset($this->_class)) { $this->_class = base64_decode('bXVsdGlPVFA='); } if (!isset($this->_version)) { - $temp_version = '@version 5.9.7.0'; // You should add a suffix for your changes (for example 5.0.3.2-andy-2016-10-XX) + $temp_version = '@version 5.9.7.1'; // You should add a suffix for your changes (for example 5.0.3.2-andy-2016-10-XX) $this->_version = nullable_trim(mb_substr($temp_version, 8)); } if (!isset($this->_date)) { - $temp_date = '@date 2023-11-23'; // You should update the date with the date of your changes + $temp_date = '@date 2023-12-03'; // You should update the date with the date of your changes $this->_date = nullable_trim(mb_substr($temp_date, 8)); } if (!isset($this->_copyright)) { $this->_copyright = base64_decode('KGMpIDIwMTAtMjAyMyBTeXNDbyBzeXN0ZW1lcyBkZSBjb21tdW5pY2F0aW9uIHNh'); } @@ -754,6 +754,7 @@ function __construct( 'debug' => "int(1) DEFAULT 0", 'default_algorithm' => "TEXT DEFAULT 'totp'", 'default_dialin_ip_mask' => "TEXT DEFAULT ''", + 'default_pin_digits' => "int(10) DEFAULT 4", 'default_user_group' => "TEXT DEFAULT ''", 'default_request_ldap_pwd' => "int(1) DEFAULT 1", 'default_request_prefix_pin' => "int(1) DEFAULT 1", @@ -1918,6 +1919,7 @@ function ResetErrorsArray() $this->_errors_text[50] = "ERROR: QRcode not created"; $this->_errors_text[51] = "ERROR: UrlLink not created (no provisionable client for this protocol)"; + $this->_errors_text[52] = "ERROR: HTML info not created"; $this->_errors_text[58] = "ERROR: File is missing"; $this->_errors_text[59] = "ERROR: Bad restore configuration password"; @@ -7414,15 +7416,27 @@ function GetSmsMessage() function SetSmsDigits( - $value + $value ) { - $this->_config_data['sms_digits'] = intval($value); + $digits = intval($value); + if ($digits < 6) { + $digits = 6; + } elseif ($digits > 32) { + $digits = 32; + } + $this->_config_data['sms_digits'] = intval($digits); } function GetSmsDigits() { - return $this->_config_data['sms_digits']; + $value = intval($this->_config_data['sms_digits']); + if ($value < 4) { + $value = 4; + } elseif ($value > 32) { + $value = 32; + } + return intval($value); } @@ -7465,6 +7479,80 @@ function GetEmailCodeTimeout() } + function SetDefaultPinDigits( + $value + ) { + $digits = intval($value); + if ($digits < 4) { + $digits = 4; + } elseif ($digits > 32) { + $digits = 32; + } + $this->_config_data['default_pin_digits'] = intval($digits); + } + + + function GetDefaultPinDigits() + { + $value = intval($this->_config_data['default_pin_digits']); + if ($value < 4) { + $value = 4; + } elseif ($value > 32) { + $value = 32; + } + return intval($value); + } + + + function GeneratePin( + $value = 0 + ) { + $digits = intval($value); + if ($digits < 1) { + $digits = $this->GetDefaultPinDigits(); + } elseif ($digits < 4) { + $digits = 4; + } elseif ($digits > 32) { + $digits = 32; + } + do { + $regenerate = false; + $result = ''; + $last_digit = ''; + $same_counter = 0; + $increment_counter = 0; + $decrement_counter = 0; + for ($i = 0; $i < $digits; $i++) { + $digit = strval(mt_rand(0,9)); + if (strval($digit) == strval($last_digit)) { + $same_counter++; + $increment_counter = 0; + $decrement_counter = 0; + } elseif (intval($digit-1) == intval($last_digit)) { + $same_counter = 0; + $increment_counter++; + $decrement_counter = 0; + } elseif (intval($digit+1) == intval($last_digit)) { + $same_counter = 0; + $increment_counter = 0; + $decrement_counter++; + } else { + $same_counter = 0; + $increment_counter = 0; + $decrement_counter = 0; + } + if (($same_counter >= 2) || ($increment_counter >= 3) || ($decrement_counter >= 3)) { + $regenerate = true; + break; + } + $last_digit = $digit; + $result.= $digit; + } + } while ($regenerate); + return $result; + } + + function SetConfigAttribute( $attribute, $value @@ -8863,26 +8951,26 @@ function CreateUser($user_raw, $the_pin = $pin; if ('' == $the_pin) { - $the_pin = mt_rand(1000,9999); + $the_pin = $this->GeneratePin(); } $this->SetUserTokenNumberOfDigits($number_of_digits); $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,20).mb_substr(md5(mt_rand(100000,999999).date("YmdHis")),0,20):$seed); if (('hotp' == mb_strtolower($algorithm,'UTF-8')) || ('yubicootp' == mb_strtolower($algorithm,'UTF-8'))) { - $next_event = ((-1 == $time_interval_or_next_event)?0:$time_interval_or_next_event); - $time_interval = 0; + $next_event = ((-1 == $time_interval_or_next_event)?0:$time_interval_or_next_event); + $time_interval = 0; } elseif (('totp' == mb_strtolower($algorithm,'UTF-8')) || ('motp' == mb_strtolower($algorithm,'UTF-8'))) { - $next_event = 0; - $time_interval = ((-1 == $time_interval_or_next_event)?30:$time_interval_or_next_event); - if ('motp' == mb_strtolower($algorithm,'UTF-8')) { - // $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,16):$seed); - $time_interval = 10; - if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { - $the_pin = mt_rand(1000,9999); - } - $the_pin = mb_substr($the_pin, 0, 4); + $next_event = 0; + $time_interval = ((-1 == $time_interval_or_next_event)?30:$time_interval_or_next_event); + if ('motp' == mb_strtolower($algorithm,'UTF-8')) { + // $the_seed = (('' == $seed)?mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,16):$seed); + $time_interval = 10; + if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { + $the_pin = $this->GeneratePin(4); // PIN of 4 digits is mandatory for motp } + $the_pin = mb_substr($the_pin, 0, 4); // PIN of 4 digits is mandatory for motp + } } else { // without2FA or unknown $next_event = 0; $time_interval = 0; @@ -8993,9 +9081,8 @@ function CreateUserFromToken($user_raw, $this->SetUserTokenLastEvent($this->GetTokenLastEvent()); $the_pin = $pin; - if ('' == $the_pin) - { - $the_pin = mt_rand(1000,9999); + if ('' == $the_pin) { + $the_pin = $this->GeneratePin(); } $this_email = nullable_trim($email); @@ -9685,7 +9772,7 @@ function FastCreateUser($user_raw, $this->SetUserTokenAlgoSuite(''); // Default algorithm suite (HMAC-SHA1) $the_pin = $pin; if ('' == $the_pin) { - $the_pin = mt_rand(1000,9999); + $the_pin = $this->GeneratePin(); } $this->SetUserPrefixPin($prefix_required); $this->SetUserTokenNumberOfDigits(6); @@ -9693,18 +9780,17 @@ function FastCreateUser($user_raw, $seed = mb_substr(md5(date("YmdHis").mt_rand(100000,999999)),0,20).mb_substr(md5(mt_rand(100000,999999).date("YmdHis")),0,20); - if ('totp' == mb_strtolower($algorithm,'UTF-8')) - { - $time_interval = 30; + if ('totp' == mb_strtolower($algorithm,'UTF-8')) { + $time_interval = 30; } elseif ('motp' == mb_strtolower($algorithm,'UTF-8')) { - $seed = mb_substr($seed,0,16); - $time_interval = 10; - if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) - { - $the_pin = mt_rand(1000,9999); - } + $seed = mb_substr($seed,0,16); + $time_interval = 10; + if ((mb_strlen($the_pin) < 4) || (0 == intval($the_pin))) { + $the_pin = $this->GeneratePin(4); // PIN of 4 digits is mandatory for motp + } + $the_pin = mb_substr($the_pin, 0, 4); // PIN of 4 digits is mandatory for motp } else { - $time_interval = 0; + $time_interval = 0; } $this->SetUserPin($the_pin); @@ -16477,10 +16563,7 @@ function GenerateSmsToken( $sms_number = $this->CleanPhoneNumber($this->GetUserSms()); if ('' != $sms_number) { $sms_message_prefix = nullable_trim($this->GetSmsMessage()); - $sms_now_steps = $now_epoch; - $sms_digits = $this->GetSmsDigits(); - $sms_seed_bin = hex2bin(md5('sMs'.$this->GetEncryptionKey().$this->GetUserTokenSeed().$user.$now_epoch)); - $sms_token = $this->GenerateOathHotp($sms_seed_bin,$sms_now_steps,$sms_digits); + $sms_token = $this->GeneratePin($this->GetSmsDigits()); $this->SetUserSmsOtp($sms_token); $this->SetUserSmsValidity($now_epoch + $this->GetSmsTimeout()); @@ -16526,10 +16609,7 @@ function GenerateEmailToken( } $email = $this->GetUserEmail(); if ('' != $email) { - $steps = $now_epoch; - $digits = $this->GetEmailDigits(); - $seed_bin = hex2bin(md5('EmaiL'.$this->GetEncryptionKey().$this->GetUserTokenSeed().$user.$now_epoch)); - $token = $this->GenerateOathHotp($seed_bin, $steps, $digits); + $token = $this->GeneratePin($this->GetEmailDigits()); $this->SetUserEmailOtp($token); $this->SetUserEmailValidity($now_epoch + $this->GetEmailCodeTimeout()); @@ -17366,7 +17446,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for SMS: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17467,7 +17547,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for Email: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17565,7 +17645,7 @@ function CheckToken( } elseif ('' != $this->GetMsChap2Response()) { $clear_code_confirmed = $code_confirmed; $code_confirmed = $this->CalculateMsChap2Response($real_user, $code_confirmed); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for scratch password: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17816,7 +17896,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for motp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -17925,7 +18005,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for hotp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -18090,7 +18170,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for totp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -18184,7 +18264,7 @@ function CheckToken( $clear_code_confirmed = $code_confirmed; $code_confirmed_without_pin = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed_without_pin),'UTF-8'); $code_confirmed = mb_strtolower($this->CalculateMsChap2Response($real_user, $code_confirmed),'UTF-8'); - if ($this->GetVerboseFlag()) { + if ($this->IsDeveloperMode()) { $this->WriteLog("Debug: *CalculateMsChap2Response($real_user, $clear_code_confirmed) for totp: $code_confirmed", false, false, 19, 'Debug', ''); } } @@ -74738,6 +74818,8 @@ function clean_quotes( $command = "purge-ldap-cache-folder"; } elseif ("-qrcode" == mb_strtolower($current_arg,'UTF-8')) { $command = "qrcode"; + } elseif ("-htmlinfo" == mb_strtolower($current_arg,'UTF-8')) { + $command = "htmlinfo"; } elseif ("-requiresms" == mb_strtolower($current_arg,'UTF-8')) { $command = "requiresms"; } elseif ("-remove-token" == mb_strtolower($current_arg,'UTF-8')) { @@ -74934,11 +75016,6 @@ function clean_quotes( } -// Be sure that non-existent parameters are empty -for ($i = ($param_count+1); $i <= $all_args_size; $i++) { - $all_args[$i] = ''; -} - // if not enough parameters, display error message // and indicate how to display the help page if (($param_count < 1) && @@ -75175,6 +75252,11 @@ function clean_quotes( $all_args[$i] = ''; } + // Be sure that non-existent parameters are empty + for ($i = ($param_count+1); $i <= $all_args_size; $i++) { + $all_args[$i] = ''; + } + switch ($command) { case "mysql": if ($param_count < 1) { @@ -75320,33 +75402,37 @@ function clean_quotes( } break; case "call-method"; + $call_result = ''; if (method_exists($multiotp, $call_method)) { - if ('' != $all_args[7]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5], $all_args[6], $all_args[7]); - } elseif ('' != $all_args[6]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5], $all_args[6]); - } elseif ('' != $all_args[5]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5]); - } elseif ('' != $all_args[4]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4]); - } elseif ('' != $all_args[3]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3]); - } elseif ('' != $all_args[2]) { - $call_result = $multiotp->$call_method($all_args[1], $all_args[2]); - } elseif ('' != $all_args[1]) { - $call_result = $multiotp->$call_method($all_args[1]); - } else { - $call_result = $multiotp->$call_method(); - } - if ($multiotp->GetVerboseFlag()) { - $multiotp->WriteLog('Debug: *Method '.$call_method.' returned the following result: '.print_r($call_result, true), false, false, 8888, 'Debug', ''); - } - $result = 19; + if ('' != $all_args[7]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5], $all_args[6], $all_args[7]); + } elseif ('' != $all_args[6]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5], $all_args[6]); + } elseif ('' != $all_args[5]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4], $all_args[5]); + } elseif ('' != $all_args[4]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3], $all_args[4]); + } elseif ('' != $all_args[3]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2], $all_args[3]); + } elseif ('' != $all_args[2]) { + $call_result = $multiotp->$call_method($all_args[1], $all_args[2]); + } elseif ('' != $all_args[1]) { + $call_result = $multiotp->$call_method($all_args[1]); + } else { + $call_result = $multiotp->$call_method(); + } + if ($multiotp->GetVerboseFlag()) { + $multiotp->WriteLog('Debug: *Method '.$call_method.' returned the following result: '.print_r($call_result, true), false, false, 8888, 'Debug', ''); + } + $result = 19; } else { - if ($multiotp->GetVerboseFlag()) { - $multiotp->WriteLog("Debug: *Method $call_method doesn't exist", false, false, 8888, 'Debug', ''); - } - $result = 99; + if ($multiotp->GetVerboseFlag()) { + $multiotp->WriteLog("Debug: *Method $call_method doesn't exist", false, false, 8888, 'Debug', ''); + } + $result = 99; + } + if ((!is_bool($call_result)) && (!is_null($call_result)) && (!empty($call_result))) { + echo $call_result; } break; case "check"; @@ -75429,87 +75515,93 @@ function clean_quotes( case "create": case "update": if (("create" == $command) && $multiotp->ReadUserData($all_args[1], true, true)) { - $result = 22; // ERROR: user already exists. + $result = 22; // ERROR: user already exists. } elseif (("update" == $command) && (!$multiotp->ReadUserData($all_args[1], false, true))) { - $result = 21; // ERROR: user doesn't exist. - } elseif ($param_count < 3) { - $result = 30; // ERROR: At least one parameter is missing + $result = 21; // ERROR: user doesn't exist. + } elseif ($param_count < 2) { + $result = 30; // ERROR: At least one parameter is missing + } elseif (($param_count < 3) && ("WITHOUT2FA" != mb_strtoupper($all_args[2],'UTF-8'))) { + $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); - $multiotp->SetUserTokenSerialNumber($multiotp->GetTokenSerialNumber()); - 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->SetUserTokenAlgoSuite($multiotp->GetTokenAlgoSuite()); - - $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 + $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->SetUserTokenSeed($all_args[3]); + $multiotp->SetUserKeyId($key_id); + $multiotp->SetUserTokenSerialNumber($multiotp->GetTokenSerialNumber()); + 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->SetUserTokenAlgoSuite($multiotp->GetTokenAlgoSuite()); + + $multiotp->SetUserPin($all_args[3]); - if ($param_count < 4) { - $result = 30; // ERROR: At least one parameter is missing + if ($multiotp->WriteUserData()) { + $result = 11; // INFO: user successfully created or updated } else { - $multiotp->SetUserPin($all_args[4]); - if ('' == $all_args[5]) { - $all_args[5] = 6; // Default number of digits is set to 6 - } - $multiotp->SetUserTokenNumberOfDigits($all_args[5]); - switch (mb_strtoupper($all_args[2],'UTF-8')) - { - // 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 - } + $result = 28; // ERROR: Unable to write the changes in the file } + } + } + } elseif (!$multiotp->SetUserAlgorithm($all_args[2])) { + $result = 23; // ERROR: invalid algorithm + } elseif ("WITHOUT2FA" == mb_strtoupper($all_args[2],'UTF-8')) { + if ($multiotp->WriteUserData()) { + $result = 11; // INFO: user successfully created or updated + } else { + $result = 28; // ERROR: Unable to write the changes in the file } + } 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 number of digits is set to 6 + } + $multiotp->SetUserTokenNumberOfDigits($all_args[5]); + switch (mb_strtoupper($all_args[2],'UTF-8')) { + // 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": @@ -75792,6 +75884,10 @@ function clean_quotes( $verbose_prefix = $multiotp->GetVerboseLogPrefix(); $write_config_data = true; break; + case 'default-pin-digits': + $multiotp->SetDefaultPinDigits(intval($actual_array[1])); + $write_config_data = true; + break; case 'default-request-prefix-pin': $multiotp->SetDefaultRequestPrefixPin(intval($actual_array[1])); $write_config_data = true; @@ -75932,6 +76028,10 @@ function clean_quotes( $multiotp->SetSmsApiId($actual_array[1]); $write_config_data = true; break; + case 'sms-digits': + $multiotp->SetSmsDigits(intval($actual_array[1])); + $write_config_data = true; + break; case 'sms-message': $multiotp->SetSmsMessage($actual_array[1]); $write_config_data = true; @@ -76248,6 +76348,40 @@ function clean_quotes( } } break; + case "htmlinfo": + if ($param_count < 2) { + if ($param_count < 1) { + $result = 30; // ERROR: At least one parameter is missing + } elseif (is_dir($all_args[1])) { + $user_list = nullable_trim($multiotp->GetUsersList()); + $users_array = explode("\t", $user_list); + if (("" != $user_list) && (count($users_array) > 0)) { + foreach ($users_array as $user) { + $content = $multiotp->GenerateHtmlQrCode($user); + if (FALSE !== file_put_contents(realpath($all_args[1]) . DIRECTORY_SEPARATOR . $user . '.html', $content)) { + $result = 16; // INFO: HTML info successfully created. + } else { + $result = 52; // INFO: HTML info not created. + break; + } + } + } else { + $result = 21; // ERROR: user doesn't exist. + } + } else { + $result = 30; // ERROR: At least one parameter is missing + } + } elseif (!$multiotp->CheckUserExists($all_args[1])) { + $result = 21; // ERROR: user doesn't exist. + } else { + $content = $multiotp->GenerateHtmlQrCode($all_args[1]); + if (FALSE !== file_put_contents($all_args[2], $content)) { + $result = 16; // INFO: HTML info successfully created. + } else { + $result = 52; // INFO: HTML info not created. + } + } + break; case "urllink": if ($param_count < 1) { $result = 30; // ERROR: At least one parameter is missing @@ -76545,6 +76679,8 @@ function clean_quotes( echo $crlf; echo " multiotp -qrcode user png_file_name.png (only for TOTP and HOTP)".$crlf; echo " multiotp -urllink user (only for TOTP and HOTP, generate provisioning URL)".$crlf; + echo " multiotp -htmlinfo user htlm_file_name.html (create file for one user) or ".$crlf; + echo " multiotp -htmlinfo htlm_file_folder (to create all files)".$crlf; echo $crlf; echo " multiotp -scratchlist user (generate & display scratch passwords for the user)".$crlf; echo $crlf; @@ -76578,6 +76714,7 @@ function clean_quotes( echo " (code result are also displayed on the console)".$crlf; echo " debug-prefix: add a prefix when using the debug mode".$crlf; echo " (for example 'Reply-Message := ' for FreeRADIUS)".$crlf; + echo " default-pin-digits: [4-32] set the default amount of PIN digits".$crlf; echo " default-request-prefix-pin: [0|1] prefix PIN enabled/disabled by default".$crlf; echo " default-request-ldap-pwd: [0|1] LDAP/AD password enabled/disabled by default".$crlf; echo " display-log: [0|1] enable/disable log display on the console".$crlf; @@ -76633,6 +76770,7 @@ function clean_quotes( echo " sms-api-id: SMS API id (if any, give your REST/XML API id)".$crlf; echo " with exec as provider, define the script to call".$crlf; echo " (available variables: %from, %to, %msg)".$crlf; + echo " sms-digits: [6-32] set the default amount of SMS digits".$crlf; echo " sms-ip: IP address of the SMS server (for inhouse server)".$crlf; echo " sms-challenge-enabled: [0|1] enable/disable SMS challenge".$crlf; echo " sms-message: SMS message to display before the OTP".$crlf; diff --git a/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/readme_5.9.7.0.txt b/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/readme_5.9.7.1.txt similarity index 97% rename from raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/readme_5.9.7.0.txt rename to raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/readme_5.9.7.1.txt index 94c1cea..a1acba4 100644 --- a/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/readme_5.9.7.0.txt +++ b/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/readme_5.9.7.1.txt @@ -6,7 +6,7 @@ multiOTP open source is OATH certified for HOTP/TOTP (c) 2010-2023 SysCo systemes de communication sa https://www.multiotp.net/ -Current build: 5.9.7.0 (2023-11-23) +Current build: 5.9.7.1 (2023-12-03) Binary download: https://download.multiotp.net/ (including virtual appliance image) @@ -158,6 +158,15 @@ WHAT'S NEW IN THIS 5.9.x RELEASE CHANGE LOG OF RELEASED VERSIONS =============================== ``` +2023-12-03 5.9.7.1 FIX: Command line number of parameters detection corrected + ENH: It's now possible to define the number of digits for new created PIN + (multiotp -config default-pin-digits=n) + ENH: It's now possible to generate the HTML provisioning file by command line + (multiotp -htmlinfo username /full/path/to/username.html or + multiotp -htmlinfo /full/path/to/folder/ to generate files for all users) + ENH: Embedded Windows nginx edition updated to version 1.25.3 + ENH: Embedded Windows internal tools updated (wget 1.21.4 and fart 1.99d) + ENH: Embedded Windows freeradius is now launched using NSSM (instead of SRVANY) 2023-11-23 5.9.7.0 FIX: Better Windows nginx configuration support (path backslashes replaced by slashes) ENH: Embedded Windows nginx edition updated to version 1.24.0 ENH: Embedded Windows PHP edition updated to version 8.2.13 @@ -1897,7 +1906,7 @@ MULTIOTP COMMAND LINE TOOL ========================== ``` -multiOTP 5.9.7.0 (2023-11-23) +multiOTP 5.9.7.1 (2023-12-03) (c) 2010-2023 SysCo systemes de communication sa http://www.multiOTP.net (you can try the [Donate] button ;-) @@ -1973,6 +1982,7 @@ Return codes: 43 ERROR: SQL entry cannot be updated 50 ERROR: QRcode not created 51 ERROR: UrlLink not created (no provisionable client for this protocol) +52 ERROR: HTML info not created 58 ERROR: File is missing 59 ERROR: Bad restore configuration password 60 ERROR: No information on where to send SMS code @@ -2053,6 +2063,8 @@ Usage: multiotp -qrcode user png_file_name.png (only for TOTP and HOTP) multiotp -urllink user (only for TOTP and HOTP, generate provisioning URL) + multiotp -htmlinfo user htlm_file_name.html (create file for one user) or + multiotp -htmlinfo htlm_file_folder (to create all files) multiotp -scratchlist user (generate & display scratch passwords for the user) @@ -2085,6 +2097,7 @@ Usage: (code result are also displayed on the console) debug-prefix: add a prefix when using the debug mode (for example 'Reply-Message := ' for FreeRADIUS) + default-pin-digits: [4-32] set the default amount of PIN digits default-request-prefix-pin: [0|1] prefix PIN enabled/disabled by default default-request-ldap-pwd: [0|1] LDAP/AD password enabled/disabled by default display-log: [0|1] enable/disable log display on the console @@ -2140,6 +2153,7 @@ Usage: sms-api-id: SMS API id (if any, give your REST/XML API id) with exec as provider, define the script to call (available variables: %from, %to, %msg) + sms-digits: [6-32] set the default amount of SMS digits sms-ip: IP address of the SMS server (for inhouse server) sms-challenge-enabled: [0|1] enable/disable SMS challenge sms-message: SMS message to display before the OTP diff --git a/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/scripts/multiotp-service.sh b/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/scripts/multiotp-service.sh index ba24b78..b0797a9 100644 --- a/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/scripts/multiotp-service.sh +++ b/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/scripts/multiotp-service.sh @@ -16,8 +16,8 @@ # Please check https://www.multiotp.net/ and you will find the magic button ;-) # # @author Andre Liechti, SysCo systemes de communication sa, -# @version 5.9.7.0 -# @date 2023-11-23 +# @version 5.9.7.1 +# @date 2023-12-03 # @since 2013-11-29 # @copyright (c) 2013-2021 by SysCo systemes de communication sa # @copyright GNU Lesser General Public License diff --git a/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/scripts/multiotp.pl b/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/scripts/multiotp.pl index 8f1c222..ea182a1 100644 --- a/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/scripts/multiotp.pl +++ b/raspberry/boot-part/multiotp-tree/usr/local/bin/multiotp/scripts/multiotp.pl @@ -15,8 +15,8 @@ # Please check https://www\.multiOTP.net/ and you will find the magic button ;-) # # @author SysCo/yj, SysCo/al, SysCo systemes de communication sa, -# @version 5.9.7.0 -# @date 2023-11-23 +# @version 5.9.7.1 +# @date 2023-12-03 # @since 2014-08-14 # @copyright (c) 2014-2023 SysCo systemes de communication sa # @copyright (c) 2002 by Boian Jordanov diff --git a/raspberry/boot-part/newvm.sh b/raspberry/boot-part/newvm.sh index 8e2415f..491a5d4 100644 --- a/raspberry/boot-part/newvm.sh +++ b/raspberry/boot-part/newvm.sh @@ -8,8 +8,8 @@ # https://www.multiotp.net/ # # @author Andre Liechti, SysCo systemes de communication sa, -# @version 5.9.7.0 -# @date 2023-11-23 +# @version 5.9.7.1 +# @date 2023-12-03 # @since 2013-09-22 # @copyright (c) 2013-2023 SysCo systemes de communication sa # @copyright GNU Lesser General Public License @@ -40,7 +40,7 @@ # 2013-09-22 4.0.9.0 SysCo/al Initial release ########################################################################## -TEMPVERSION="@version 5.9.7.0" +TEMPVERSION="@version 5.9.7.1" MULTIOTPVERSION="$(echo -e "${TEMPVERSION:8}" | tr -d '[[:space:]]')" IFS='.' read -ra MULTIOTPVERSIONARRAY <<< "$MULTIOTPVERSION" MULTIOTPMAJORVERSION=${MULTIOTPVERSIONARRAY[0]} diff --git a/raspberry/how-to-build-a-raspberry-strong-authentication-server.txt b/raspberry/how-to-build-a-raspberry-strong-authentication-server.txt index 6358071..bea3705 100644 --- a/raspberry/how-to-build-a-raspberry-strong-authentication-server.txt +++ b/raspberry/how-to-build-a-raspberry-strong-authentication-server.txt @@ -4,7 +4,7 @@ How to build a Raspberry Pi RADIUS strong two factors authentication server in s (c) 2010-2023 SysCo systemes de communication sa https://www.multiotp.net/ -Current build: 5.9.7.0 (2023-11-23) +Current build: 5.9.7.1 (2023-12-03) Supported Raspberry Pi hardware: 1B/1B+/2B/3B/3B+/4B diff --git a/raspberry/multiotp-open-source-raspberry-img.txt b/raspberry/multiotp-open-source-raspberry-img.txt index c88dda0..3ddcc28 100644 --- a/raspberry/multiotp-open-source-raspberry-img.txt +++ b/raspberry/multiotp-open-source-raspberry-img.txt @@ -5,7 +5,7 @@ multiOTP open source is a strong two-factor authentication device (c) 2010-2023 SysCo systemes de communication sa https://www.multiotp.net/ -Current build: 5.9.7.0 (2023-11-23) +Current build: 5.9.7.1 (2023-12-03) Check the readme file of the multiOTP open source edition for more information. diff --git a/scripts/multiotp-service.sh b/scripts/multiotp-service.sh index ba24b78..b0797a9 100644 --- a/scripts/multiotp-service.sh +++ b/scripts/multiotp-service.sh @@ -16,8 +16,8 @@ # Please check https://www.multiotp.net/ and you will find the magic button ;-) # # @author Andre Liechti, SysCo systemes de communication sa, -# @version 5.9.7.0 -# @date 2023-11-23 +# @version 5.9.7.1 +# @date 2023-12-03 # @since 2013-11-29 # @copyright (c) 2013-2021 by SysCo systemes de communication sa # @copyright GNU Lesser General Public License diff --git a/scripts/multiotp.pl b/scripts/multiotp.pl index 8f1c222..ea182a1 100644 --- a/scripts/multiotp.pl +++ b/scripts/multiotp.pl @@ -15,8 +15,8 @@ # Please check https://www\.multiOTP.net/ and you will find the magic button ;-) # # @author SysCo/yj, SysCo/al, SysCo systemes de communication sa, -# @version 5.9.7.0 -# @date 2023-11-23 +# @version 5.9.7.1 +# @date 2023-12-03 # @since 2014-08-14 # @copyright (c) 2014-2023 SysCo systemes de communication sa # @copyright (c) 2002 by Boian Jordanov diff --git a/webservice_install.cmd b/webservice_install.cmd index 4efa72a..58abec8 100644 --- a/webservice_install.cmd +++ b/webservice_install.cmd @@ -9,8 +9,8 @@ REM REM Windows batch file for Windows 2K/XP/2003/7/2008/8/2012/10 REM REM @author Andre Liechti, SysCo systemes de communication sa, -REM @version 5.9.7.0 -REM @date 2023-11-23 +REM @version 5.9.7.1 +REM @date 2023-12-03 REM @since 2013-08-09 REM @copyright (c) 2013-2023 SysCo systemes de communication sa REM @copyright GNU Lesser General Public License diff --git a/webservice_uninstall.cmd b/webservice_uninstall.cmd index 1ff4021..17f032c 100644 --- a/webservice_uninstall.cmd +++ b/webservice_uninstall.cmd @@ -9,8 +9,8 @@ REM REM Windows batch file for Windows 2K/XP/2003/7/2008/8/2012/10 REM REM @author Andre Liechti, SysCo systemes de communication sa, -REM @version 5.9.7.0 -REM @date 2023-11-23 +REM @version 5.9.7.1 +REM @date 2023-12-03 REM @since 2013-08-09 REM @copyright (c) 2013-2023 SysCo systemes de communication sa REM @copyright GNU Lesser General Public License