diff --git a/Dockerfile b/Dockerfile index a04834c..4fcc994 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,15 +15,16 @@ # Please check http://www.multiOTP.net/ and you will find the magic button ;-) # # @author Andre Liechti, SysCo systemes de communication sa, -# @version 5.8.1.2 (apt-offline removed) -# @date 2021-03-24 +# @version 5.8.1.9 +# @date 2021-03-25 # @since 2013-11-29 -# @copyright (c) 2013-2018 by SysCo systemes de communication sa +# @copyright (c) 2013-2021 SysCo systemes de communication sa # @copyright GNU Lesser General Public License # # docker build . # docker run --mount source=multiotp-data,target=/etc/multiotp -p 80:80 -p 443:443 -p 1812:1812/udp -p 1813:1813/udp -d xxxxxxxxxxxx # +# 2021-03-25 5.8.1.9 SysCo/al Remove apt-offline, which is not used # 2020-08-31 5.8.0.0 SysCo/al Debian Buster 10.5 support # 2019-10-22 5.6.1.3 SysCo/al Debian 10 support # 2019-01-07 5.4.1.1 SysCo/al Debian 9 support @@ -40,7 +41,7 @@ MAINTAINER Andre Liechti LABEL Description="multiOTP open source, running on Debian ${DEBIAN} with PHP${PHPVERSION}." \ License="LGPL-3.0" \ Usage="docker run --mount source=[SOURCE PERSISTENT VOLUME],target=/etc/multiotp -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.8.1.1" + Version="5.8.1.9" ARG DEBIAN_FRONTEND=noninteractive @@ -101,7 +102,7 @@ COPY raspberry/boot-part/multiotp-tree /boot/multiotp-tree/ # (if you want to build an image with the latest # available version instead of the local one) # -# RUN wget -q http://download.multiotp.net/multiotp.zip -O /tmp/multiotp.zip && \ +# RUN wget -q https://download.multiotp.net/multiotp.zip -O /tmp/multiotp.zip && \ # unzip -q -o /tmp/multiotp.zip -d /tmp/multiotp # # RUN mv /tmp/multiotp/raspberry/boot-part/* /boot && \ diff --git a/README.md b/README.md index db55d2c..7e35e78 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ multiOTP open source is OATH certified for HOTP/TOTP (c) 2010-2021 SysCo systemes de communication sa http://www.multiOTP.net/ -Current build: 5.8.1.1 (2021-03-14) +Current build: 5.8.1.9 (2021-03-25) Binary download: https://download.multiotp.net/ (including virtual appliance image) @@ -311,6 +311,15 @@ WHAT'S NEW IN THE RELEASES CHANGE LOG OF RELEASED VERSIONS =============================== ``` +2021-03-25 5.8.1.9 FIX: Cookie privacy (httponly and secure) backported to previous virtual appliances + ENH: Cookie privacy (httponly and secure) are now handled in the application directly + ENH: Weak SSL ciphers disabled + ENH: Better Docker support + ENH: Better log handling +2021-03-21 5.8.1.2 ENH: Test (1 == GetUserPrefixPin()) replaced by IsUserPrefixPin() +2021-03-14 5.8.1.1 FIX: In some cases, the HOTP/TOTP was not well computed +2021-03-21 5.8.1.2 FIX: Dockerfile corrected, apt-offline removed + ENH: Enhanced log file handling 2021-03-14 5.8.1.1 FIX: In some cases, the HOTP/TOTP was not well computed 2021-02-12 5.8.1.0 ENH: Enhanced Web GUI accounts list (green=AD/LDAP synced, orange = delayed, red=locked) 2020-12-11 5.8.0.7 ENH: -sync-delete-retention-days= option is set by default to 30 days @@ -1679,7 +1688,7 @@ MULTIOTP COMMAND LINE TOOL ========================== ``` -multiOTP 5.8.1.1 (2021-03-14) +multiOTP 5.8.1.9 (2021-03-25) (c) 2010-2021 SysCo systemes de communication sa http://www.multiOTP.net (you can try the [Donate] button ;-) @@ -1724,7 +1733,7 @@ Return codes: 15 INFO: Tokens definition file successfully imported 16 INFO: QRcode successfully created 17 INFO: UrlLink successfully created -18 INFO: SMS code request received +18 INFO: Static code request received 19 INFO: Requested operation successfully done 20 ERROR: User blacklisted 21 ERROR: User doesn't exist @@ -1759,10 +1768,15 @@ Return codes: 62 ERROR: SMS provider not supported 63 ERROR: This SMS code has expired 64 ERROR: Cannot resent an SMS code right now +65 ERROR: SMS code request not allowed +66 ERROR: Email code request not allowed +67 ERROR: No information on where to send Email code +68 ERROR: Email code request received, but an error occurred during transmission 69 ERROR: Failed to send email 70 ERROR: Server authentication error 71 ERROR: Server request is not correctly formatted 72 ERROR: Server answer is not correctly formatted +73 ERROR: Email SMTP server not defined 79 ERROR: AD/LDAP connection error 80 ERROR: Server cache error 81 ERROR: Cache too old for this user, account autolocked @@ -2020,7 +2034,8 @@ Backup/restore commands: Other information commands: multiotp -phpinfo : print the current PHP version - multiotp -showlog : print the log file + multiotp -showlog : print the log entries + multiotp -clearlog : clear the log entries multiotp -tokenslist : print the list of the tokens multiotp -userslist : print the list of the users multiotp -lockeduserslist : print the list of the locked users @@ -2179,8 +2194,8 @@ Visit https://forum.multiotp.net/ for additional support ``` ``` -Hash verification for multiotp_5.8.1.1.zip -SHA256:9cd03e212323964cd8c9fc2a132a01792d9cc5186c02125d0f06aef957801711 -SHA1:f45b31f5cd7fe596ff7ff8090316b1fbbd611016 -MD5:5d0b90c902edc5f21df5e528001835b3 +Hash verification for multiotp_5.8.1.9.zip +SHA256:f07fdc9420a2700f5f3627a4f6e8e50fca64ae485214a2fa25ba7a5e738b2fd1 +SHA1:87159d78fb582b20f8d796bd7549e1ba78232fbf +MD5:3b7d16b66ceb83be0c5800c74ac2a7bc ``` diff --git a/check.multiotp.class.php b/check.multiotp.class.php index f996fa0..39526cb 100644 --- a/check.multiotp.class.php +++ b/check.multiotp.class.php @@ -22,8 +22,8 @@ * PHP 5.3.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.8.1.1 - * @date 2021-03-14 + * @version 5.8.1.9 + * @date 2021-03-25 * @since 2013-07-10 * @copyright (c) 2013-2021 SysCo systemes de communication sa * @copyright GNU Lesser General Public License @@ -134,6 +134,8 @@ $GLOBALS['noresume'] = $_GET['noresume']; } +$test_mail = isset($GLOBALS['test_mail'])?$GLOBALS['test_mail']:''; + if (!function_exists('echo_full')) { function echo_full($to_display) { if (!$GLOBALS['minima']) { @@ -498,7 +500,7 @@ function scrollToObject(object_div) echo_full($crlf); - //==================================================================== + //==================================================================== // Delete the user test_user if it exists echo_full($i_on); echo_full("Deleting the test_user".$crlf); @@ -514,7 +516,7 @@ function scrollToObject(object_div) echo_full($crlf); - //==================================================================== + //==================================================================== // Delete the user test_user twice if it exists echo_full($i_on); echo_full("Deleting the test_user (twice)".$crlf); @@ -530,7 +532,7 @@ function scrollToObject(object_div) echo_full($crlf); - //==================================================================== + //==================================================================== // Delete the user test_totp if it exists echo_full($i_on); echo_full("Deleting the test_totp".$crlf); @@ -546,7 +548,7 @@ function scrollToObject(object_div) echo_full($crlf); - //==================================================================== + //==================================================================== //==================================================================== // Delete the token test_token if it exists echo_full($i_on); @@ -563,7 +565,7 @@ function scrollToObject(object_div) echo_full($crlf); - //==================================================================== + //==================================================================== //==================================================================== // Delete the token test_token_totp if it exists echo_full($i_on); @@ -773,6 +775,26 @@ function scrollToObject(object_div) echo_full($crlf); + //================================================ + // TEST: Generate Email token for the current user + if ('' != $test_mail) { + $tests++; + echo_full($b_on."Generate Email token for user test_user".$b_off.$crlf); + $multiotp->SetUser('test_user'); + $multiotp->SetEmailCodeAllowed(1); + $multiotp->SetUserEmail($test_mail); + $multiotp->WriteUserData(); + $token_result = $multiotp->GenerateEmailToken(); + if (18 == $token_result) { + echo_full("- ".$ok_on.'OK!'.$ok_off." Email token successfully generated".$crlf); + $successes++; + } else { + echo_full("- ".$ko_on.'KO!'.$ko_off." Email token generation failed, error $token_result.".$crlf); + } + echo_full($crlf); + } + + //==================================================================== // Delete the user test_user8 if it exists echo_full($i_on); diff --git a/checkmultiotp.cmd b/checkmultiotp.cmd index 9b85e02..d46f55d 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.8.1.1 -REM @date 2021-03-14 +REM @version 5.8.1.9 +REM @date 2021-03-25 REM @since 2010-07-10 REM @copyright (c) 2010-2021 SysCo systemes de communication sa REM @copyright GNU Lesser General Public License @@ -250,9 +250,9 @@ SET /A TOTAL_TESTS=TOTAL_TESTS+1 ECHO. ECHO Test replay rejection for user test_user %_multiotp% -keep-local -log test_user "ThisIsALongNonDigitPinCode!755224" -IF NOT ERRORLEVEL 1 ECHO - KO! Replayed token *WRONGLY* accepted -IF NOT ERRORLEVEL 1 ECHO - KO! Replayed token *WRONGLY* accepted (%_backend%) >>"%TEMP%\multiotp_error.log" -IF NOT ERRORLEVEL 1 GOTO ErrorReplay +IF NOT ERRORLEVEL 26 ECHO - KO! Replayed token *WRONGLY* accepted +IF NOT ERRORLEVEL 26 ECHO - KO! Replayed token *WRONGLY* accepted (%_backend%) >>"%TEMP%\multiotp_error.log" +IF NOT ERRORLEVEL 26 GOTO ErrorReplay ECHO - OK! Token of the user test_user successfully REJECTED (replay) SET /A SUCCESSES=SUCCESSES+1 :ErrorReplay @@ -310,11 +310,11 @@ ECHO. ECHO Authenticate test_user with replayed token 162583 with prefix using MS-CHAPv2 REM user test_user and password "ThisIsALongNonDigitPinCode!162583" %_multiotp% -keep-local -log test_user -ms-chap-challenge=0xc5356d83125a36b655c59a05b2245d68 -ms-chap2-response=0x00006cea45ad4f3e3a6af414cc09619aeb1e00000000000000004dd32ee9f3b898cf4fcd665ba167a303ce2c1266e7a26f10 -IF NOT ERRORLEVEL 1 ECHO - KO! Replayed token of the user test_user wrongly accepted -IF NOT ERRORLEVEL 1 ECHO - KO! Replayed token of the user test_user wrongly accepted (%_backend%) >>"%TEMP%\multiotp_error.log" -IF NOT ERRORLEVEL 1 GOTO ErrorReplayedMsChapV2 -IF ERRORLEVEL 1 ECHO - OK! Replayed Token of the test_user successfully REJECTED -IF ERRORLEVEL 1 SET /A SUCCESSES=SUCCESSES+1 +IF NOT ERRORLEVEL 26 ECHO - KO! Replayed token of the user test_user wrongly accepted +IF NOT ERRORLEVEL 26 ECHO - KO! Replayed token of the user test_user wrongly accepted (%_backend%) >>"%TEMP%\multiotp_error.log" +IF NOT ERRORLEVEL 26 GOTO ErrorReplayedMsChapV2 +IF ERRORLEVEL 26 ECHO - OK! Replayed Token of the test_user successfully REJECTED +IF ERRORLEVEL 26 SET /A SUCCESSES=SUCCESSES+1 :ErrorReplayedMsChapV2 SET /A TOTAL_TESTS=TOTAL_TESTS+1 diff --git a/contrib/MultiotpTools.php b/contrib/MultiotpTools.php index 078eac6..e38f94e 100644 --- a/contrib/MultiotpTools.php +++ b/contrib/MultiotpTools.php @@ -70,6 +70,26 @@ function pcre_fnmatch($pattern, $string, $flags = 0) { } +/*********************************************************************** + * Name: bytes_nice_format + * Short description: nice format for a size in bytes + * + * Creation 2021-03-14 + * Update 2021-03-14 + * @version 1.0.0 + * @author Adapted from https://www.php.net/manual/en/function.disk-free-space.php#103382 + * + * @param int $bytes size in bytes + * @return string nice size in a string + ***********************************************************************/ +function bytes_nice_format($bytes) { + $size_prefix = array( 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ); + $base = 1024; + $class = min((int)log($bytes , $base) , count($size_prefix) - 1); + return sprintf('%1.2f' , $bytes / pow($base,$class)) . ' ' . $size_prefix[$class]; +} + + /*********************************************************************** * Name: bcmod * Short description: description: Patch for bcmod @@ -845,12 +865,17 @@ function rmrf($dir) { } -/** - * Based on http://snipplr.com/view/57982/convert-html-to-text/ - * by kendsnyder (2011-08-18) +/*********************************************************************** + * Name: html2text + * Short description: Convert html to text + * Based on http://snipplr.com/view/57982/convert-html-to-text/ * - * Enhanced by SysCo/al - */ + * Creation 2011-08-18 kendsnyder + * Update 2021-03-23 + * @version 2.0.0 + * @author SysCo/al + ***********************************************************************/ + if (!function_exists('html2text')) { function html2text($value) @@ -878,8 +903,7 @@ function html2text($value) '@&(cent|#162);@i', // Cent '@&(pound|#163);@i', // Pound '@&(copy|#169);@i', // Copyright - '@&(reg|#174);@i', // Registered - '@&#(d+);@e'); // Evaluate as php + '@&(reg|#174);@i'); // Registered $Replace = array ('', // Strip out javascript '', // Strip out style '', // Strip out title @@ -895,9 +919,11 @@ function html2text($value) chr(162), // Cent chr(163), // Pound chr(169), // Copyright - chr(174), // Registered - 'chr()'); // Evaluate as php + chr(174)); // Registered $Document = preg_replace($Rules, $Replace, $Document); + + $Document = preg_replace_callback('@&#(d+);@i', function ($match) { return (((intval($match) >= 1) && (intval($match) <= 255)) ? chr(intval($match)) : ''); }, $Document); + $Document = preg_replace('@[\r\n]@', '', $Document); $Document = str_replace('*CRLF*',chr(13).chr(10),$Document); $Document = preg_replace('@[\r\n][ ]+@', chr(13).chr(10), $Document); diff --git a/launcher/ReadMe.txt b/launcher/ReadMe.txt index 53c805c..32f84a3 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.8.1.1 -@date 2021-03-14 +@version 5.8.1.9 +@date 2021-03-25 @since 2016-12-08 @copyright (c) 2010-2021 SysCo systemes de communication sa @copyright GNU Lesser General Public License diff --git a/launcher/launcher.cpp b/launcher/launcher.cpp index 21aed82..dd943dd 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.8.1.1 - * @date 2021-03-14 + * @version 5.8.1.9 + * @date 2021-03-25 * @since 2016-12-08 * @copyright (c) 2010-2021 SysCo systemes de communication sa * @copyright GNU Lesser General Public License @@ -68,8 +68,8 @@ #include #define SOFTWARE "LAUNCHPHPMULTIOTP" -#define VER_NUMBER "5.8.1.1" -#define VER_DATE "2021-03-14" +#define VER_NUMBER "5.8.1.9" +#define VER_DATE "2021-03-25" int _tmain(int argc, _TCHAR* argv[]) { diff --git a/multiotp.class.php b/multiotp.class.php index 298445a..e022a46 100644 --- a/multiotp.class.php +++ b/multiotp.class.php @@ -72,8 +72,8 @@ * PHP 5.3.0 or higher is supported. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.8.1.1 - * @date 2021-03-14 + * @version 5.8.1.9 + * @date 2021-03-25 * @since 2010-06-08 * @copyright (c) 2010-2021 SysCo systemes de communication sa * @copyright GNU Lesser General Public License @@ -513,6 +513,12 @@ * * Change Log * + * 2021-03-25 5.8.1.9 SysCo/al FIX: Cookie privacy (httponly and secure) backported to previous virtual appliances + * ENH: Cookie privacy (httponly and secure) are now handled in the application directly + * ENH: Weak SSL ciphers disabled + * ENH: Better Docker support + * ENH: Better log handling + * 2021-03-21 5.8.1.2 SysCo/al ENH: Test (1 == GetUserPrefixPin()) replaced by IsUserPrefixPin() * 2021-03-14 5.8.1.1 SysCo/al FIX: In some cases, the HOTP/TOTP was not well computed * 2021-02-12 5.8.1.0 SysCo/al ENH: Enhanced Web GUI accounts list (green=AD/LDAP synced, orange = delayed, red=locked) * 2020-12-11 5.8.0.7 SysCo/al ENH: -sync-delete-retention-days= option is set by default to 30 days @@ -874,8 +880,8 @@ class Multiotp * @brief Main class definition of the multiOTP project. * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.8.1.1 - * @date 2021-03-14 + * @version 5.8.1.9 + * @date 2021-03-25 * @since 2010-07-18 */ { @@ -969,8 +975,8 @@ class Multiotp * @retval void * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 5.8.1.1 - * @date 2021-03-14 + * @version 5.8.1.9 + * @date 2021-03-25 * @since 2010-07-18 */ function __construct( @@ -994,11 +1000,11 @@ function __construct( if (!isset($this->_class)) { $this->_class = base64_decode('bXVsdGlPVFA='); } if (!isset($this->_version)) { - $temp_version = '@version 5.8.1.1'; // You should add a suffix for your changes (for example 5.0.3.2-andy-2016-10-XX) + $temp_version = '@version 5.8.1.9'; // You should add a suffix for your changes (for example 5.0.3.2-andy-2016-10-XX) $this->_version = trim(mb_substr($temp_version, 8)); } if (!isset($this->_date)) { - $temp_date = '@date 2021-03-14'; // You should update the date with the date of your changes + $temp_date = '@date 2021-03-25'; // You should update the date with the date of your changes $this->_date = trim(mb_substr($temp_date, 8)); } if (!isset($this->_copyright)) { $this->_copyright = base64_decode('KGMpIDIwMTAtMjAyMSBTeXNDbyBzeXN0ZW1lcyBkZSBjb21tdW5pY2F0aW9uIHNh'); } @@ -1133,6 +1139,8 @@ function __construct( 'display_log' => "int(1) DEFAULT 0", 'domain_name' => "TEXT DEFAULT ''", 'email_admin_address' => "TEXT DEFAULT ''", + 'email_code_allowed' => "int(1) DEFAULT 0", + 'email_code_timeout' => "int(10) DEFAULT 600", 'encode_file_id' => "int(1) DEFAULT 0", 'encryption_key_full_path' => "TEXT DEFAULT ''", // Locking delay in seconds between two trials after "max_delayed_failures" failures @@ -1215,6 +1223,7 @@ function __construct( 'server_url' => "TEXT DEFAULT ''", 'sms_api_id' => "TEXT DEFAULT ''", 'sms_basic_auth' => "int(1) DEFAULT 0", + 'sms_code_allowed' => "int(1) DEFAULT 1", 'sms_content_encoding' => "TEXT DEFAULT ''", 'sms_content_success' => "TEXT DEFAULT ''", 'sms_digits' => "int(10) DEFAULT 6", @@ -1315,6 +1324,7 @@ function __construct( $this->_sql_tables_ignore['groups'] = "**"; $this->_sql_tables_schema['log'] = array( + 'log_id' => "varchar(100) DEFAULT ''", 'category' => "varchar(255) DEFAULT ''", 'create_host' => "varchar(255) DEFAULT ''", 'create_time' => "int(10) DEFAULT 0", @@ -1324,6 +1334,7 @@ function __construct( 'last_sync_update_host' => "varchar(255) DEFAULT ''", 'last_update' => "int(10) DEFAULT 0", 'last_update_host' => "varchar(255) DEFAULT ''", + 'local_only' => "int(1) DEFAULT 0", 'logentry' => "text", 'note' => "varchar(255) DEFAULT ''", 'severity' => "varchar(255) DEFAULT ''", @@ -2100,6 +2111,9 @@ function TouchFolder( ) { $touch_suffix_array = $this->GetTouchSuffixArray(); if (('' != $this->GetTouchFolder()) && (0 < count($touch_suffix_array)) && (!(('data' == mb_strtolower($type_fn,'UTF-8')) && ('stat' == mb_strtolower($item_fn,'UTF-8'))))) { + if (('slavelog' == mb_strtolower($type_fn,'UTF-8')) && ('unique_id' == mb_strtolower($item_fn,'UTF-8'))) { + $touch_suffix_array = array($id_fn); + } if ($this->GetVerboseFlag()) { $this->WriteLog("Debug: *Touch element $type_fn $item_fn $id_fn", FALSE, FALSE, 8888, 'System', ''); } @@ -2212,7 +2226,7 @@ function ResetErrorsArray() $this->_errors_text[15] = "INFO: Tokens definition file successfully imported"; $this->_errors_text[16] = "INFO: QRcode successfully created"; $this->_errors_text[17] = "INFO: UrlLink successfully created"; - $this->_errors_text[18] = "INFO: SMS code request received"; + $this->_errors_text[18] = "INFO: Static code request received"; $this->_errors_text[19] = "INFO: Requested operation successfully done"; $this->_errors_text[20] = "ERROR: User blacklisted"; @@ -2252,11 +2266,16 @@ function ResetErrorsArray() $this->_errors_text[62] = "ERROR: SMS provider not supported"; $this->_errors_text[63] = "ERROR: This SMS code has expired"; $this->_errors_text[64] = "ERROR: Cannot resent an SMS code right now"; + $this->_errors_text[65] = "ERROR: SMS code request not allowed"; + $this->_errors_text[66] = "ERROR: Email code request not allowed"; + $this->_errors_text[67] = "ERROR: No information on where to send Email code"; + $this->_errors_text[68] = "ERROR: Email code request received, but an error occurred during transmission"; $this->_errors_text[69] = "ERROR: Failed to send email"; $this->_errors_text[70] = "ERROR: Server authentication error"; $this->_errors_text[71] = "ERROR: Server request is not correctly formatted"; $this->_errors_text[72] = "ERROR: Server answer is not correctly formatted"; + $this->_errors_text[73] = "ERROR: Email SMTP server not defined"; $this->_errors_text[79] = "ERROR: AD/LDAP connection error"; $this->_errors_text[80] = "ERROR: Server cache error"; @@ -2646,7 +2665,12 @@ function WriteData( } if ($this->GetVerboseFlag()) { if ($file_created) { - $this->WriteLog("Info: *File created: ".$folder.$filename, FALSE, FALSE, 8888, 'System', ''); + $this->WriteLog(array('text' => "Info: *File created: ".$folder.$filename, + 'error_code' => 8888, + 'category' => 'System', + 'user' => '', + 'local_only' => 1) + ); } } } @@ -2706,7 +2730,12 @@ function WriteData( $sQi_Columns .= "`{$key}`,"; // Columns for INSERT query $sQi_Values .= "'{$value}',"; // Values for INSERT query } elseif ((!$in_the_schema) && (!$not_in_the_schema) && ('unique_id' != $key) && $this->GetVerboseFlag()) { - $this->WriteLog("Warning: *The key ".$key." is not in the $table table schema", FALSE, FALSE, 8888, 'System', ''); + $this->WriteLog(array('text' => "Warning: *The key ".$key." is not in the $table table schema", + 'error_code' => 8888, + 'category' => 'System', + 'user' => '', + 'local_only' => 1) + ); } } $num_rows = 0; @@ -2717,12 +2746,24 @@ function WriteData( if (is_object($this->_mysqli)) { if (!($result = @$this->_mysqli->query($sQuery))) { - $this->WriteLog("Error: SQL query error ".trim($this->_mysqli->error)." ".$sQuery, TRUE, FALSE, 40, 'System', '', 3); + $this->WriteLog(array('text' => "Error: SQL query error ".trim($this->_mysqli->error)." ".$sQuery, + 'file_only' => TRUE, + 'error_code' => 40, + 'category' => 'System', + 'user' => '', + 'overwrite_severity' => 3) + ); } else { $num_rows = $result->num_rows; } } elseif (!($result = @mysql_query($sQuery, $this->_mysql_database_link))) { - $this->WriteLog("Error: SQL query error ($sQuery) : ".mysql_error(), TRUE, FALSE, 40, 'System', '', 3); + $this->WriteLog(array('text' => "Error: SQL query error ($sQuery) : ".mysql_error(), + 'file_only' => TRUE, + 'error_code' => 40, + 'category' => 'System', + 'user' => '', + 'overwrite_severity' => 3) + ); } else { $num_rows = mysql_num_rows($result); } @@ -2734,11 +2775,23 @@ function WriteData( } if (is_object($this->_mysqli)) { if (!($rResult = @$this->_mysqli->query($sQuery))) { - $this->WriteLog("Error: SQL query error ".trim($this->_mysqli->error)." ".$sQuery, TRUE, FALSE, 40, 'System', '', 3); + $this->WriteLog(array('text' => "Error: SQL query error ".trim($this->_mysqli->error)." ".$sQuery, + 'file_only' => TRUE, + 'error_code' => 40, + 'category' => 'System', + 'user' => '', + 'overwrite_severity' => 3) + ); $result = FALSE; } } elseif (!($rResult = @mysql_query($sQuery, $this->_mysql_database_link))) { - $this->WriteLog("Error: SQL query error ($sQuery) : ".mysql_error(), TRUE, FALSE, 40, 'System', '', 3); + $this->WriteLog(array('text' => "Error: SQL query error ($sQuery) : ".mysql_error(), + 'file_only' => TRUE, + 'error_code' => 40, + 'category' => 'System', + 'user' => '', + 'overwrite_severity' => 3) + ); $result = FALSE; } } else { @@ -2869,10 +2922,20 @@ function WriteData( if (!$backup_format) { if ($item_created && $result) { if ($automatically) { - $this->WriteLog("Info: ".$item_info." automatically created", FALSE, FALSE, 19, 'System', ''); + $this->WriteLog(array('text' => "Info: ".$item_info." automatically created", + 'error_code' => 19, + 'category' => 'System', + 'user' => '', + 'local_only' => 1) + ); } else { - $this->WriteLog("Info: ".$item_info." manually created", FALSE, FALSE, 19, 'System', ''); + $this->WriteLog(array('text' => "Info: ".$item_info." manually created", + 'error_code' => 19, + 'category' => 'System', + 'user' => '', + 'local_only' => 1) + ); } } } @@ -3776,8 +3839,8 @@ function GetLogFolder() * @retval void * * @author Andre Liechti, SysCo systemes de communication sa, - * @version 4.1.0 - * @date 2014-01-03 + * @version 5.8.1 + * @date 2021-03-21 * @since 2010-12-19 * * Severity values: @@ -3791,15 +3854,40 @@ function GetLogFolder() * 7 Debug: debug-level messages */ function WriteLog( - $info, - $file_only = FALSE, - $hide_on_display = FALSE, - $error_code = 9999, - $category = '*DEFAULT*', - $user = '*DEFAULT*', - $overwrite_severity = -1, - $no_syslog = FALSE - ) { + $text_array = array('text' => ''), + $file_only_param = FALSE, + $hide_on_display_param = FALSE, + $error_code_param = 9999, + $category_param = '*DEFAULT*', + $user_param = '*DEFAULT*', + $overwrite_severity_param = -1, + $no_syslog_param = FALSE + ) { + if (is_array($text_array)) { + $text = isset($text_array['text'])?$text_array['text']:''; + $file_only = isset($text_array['file_only'])?(TRUE === $text_array['file_only']):FALSE; + $hide_on_display = isset($text_array['hide_on_display'])?(TRUE === $text_array['hide_on_display']):FALSE; + $error_code = isset($text_array['error_code'])?intval($text_array['error_code']):9999; + $category = isset($text_array['category'])?$text_array['category']:'*DEFAULT*'; + $user = isset($text_array['user'])?$text_array['user']:'*DEFAULT*'; + $overwrite_severity = isset($text_array['overwrite_severity'])?$text_array['overwrite_severity']:-1; + $no_syslog = isset($text_array['no_syslog'])?(TRUE === $text_array['no_syslog']):FALSE; + $local_only = isset($text_array['local_only'])?intval($text_array['local_only']):0; + } else { // backward compatibility + $text = $text_array; + $file_only = $file_only_param; + $hide_on_display = $hide_on_display_param; + $error_code = $error_code_param; + $category = $category_param; + $user = $user_param; + $overwrite_severity = $overwrite_severity_param; + $no_syslog = $no_syslog_param; + // New parameters, only available in the array + $local_only = 0; + } + + $log_id = substr(bigdec2hex((time()-mktime(1,1,1,1,1,2000)).mt_rand(10000,99999)).'@'.$this->GetCreateHost(), 0, 100); + if ('*DEFAULT*' != $user) { $user_log = $user; } else { @@ -3873,35 +3961,35 @@ function WriteLog( $severity_txt = 'error'; } - $post_info = ""; - $pre_info = ""; + $post_text = ""; + $pre_text = ""; if ("" != ($this->GetSourceIp().$this->GetSourceMac())) { - $post_info.= "from "; + $post_text.= "from "; if ("" != $this->GetSourceIp()) { - $post_info.= $this->GetSourceIp().' '; + $post_text.= $this->GetSourceIp().' '; } if ("" != $this->GetSourceMac()) { - $post_info.= '['.$this->GetSourceMac().'] '; + $post_text.= '['.$this->GetSourceMac().'] '; } } if ("" != ($this->GetCallingIp().$this->GetCallingMac())) { - $post_info.= "for "; + $post_text.= "for "; if ("" != $this->GetCallingIp()) { - $post_info.= $this->GetCallingIp().' '; + $post_text.= $this->GetCallingIp().' '; } if ("" != $this->GetCallingMac()) { - $post_info.= '['.$this->GetCallingMac().'] '; + $post_text.= '['.$this->GetCallingMac().'] '; } } - $log_info = trim(trim($pre_info).' '.$info.' '.trim($post_info)); + $log_text = trim(trim($pre_text).' '.$text.' '.trim($post_text)); - // Cleaning the log info, just to be sure that we don't have tabs (\t) + // Cleaning the log text, just to be sure that we don't have tabs (\t) // in them, and also that the CRLF, CR or LF is the good done (\n) - $log_info = str_replace(chr(13).chr(10), "<>", $log_info); - $log_info = str_replace(chr(13), "<>", $log_info); - $log_info = str_replace(chr(10), "<>", $log_info); - $log_info = str_replace("<>", "\n", $log_info); - $log_info = str_replace("\t", " ", $log_info); + $log_text = str_replace(chr(13).chr(10), "<>", $log_text); + $log_text = str_replace(chr(13), "<>", $log_text); + $log_text = str_replace(chr(10), "<>", $log_text); + $log_text = str_replace("<>", "\n", $log_text); + $log_text = str_replace("\t", " ", $log_text); $log_time = time(); $log_datetime = date("Y-m-d H:i:s", $log_time); @@ -3909,10 +3997,10 @@ function WriteLog( // In the logfile, we don't want to have several lines for one entry, // therefore we are replacing the "\n" with "; " (or
if we want to debug in HTML mode - $logfile_content = $log_datetime."\t".$severity_txt."\t".$user_log."\t".$category_log."\t".str_replace("\n", $this->IsDebugViaHtml()?"
":"; ", $log_info); + $logfile_content = $log_datetime."\t".$severity_txt."\t".$user_log."\t".$category_log."\t".str_replace("\n", $this->IsDebugViaHtml()?"
":"; ", $log_text)."\t".$local_only."\t".$this->GetCreateHost(); if (($this->GetDisplayLogFlag()) && (!$hide_on_display) && (!$this->GetNoDisplayLogFlag())) { - $display_text = "\nLOG ".$log_datetime.' '.$severity_txt.' '.(("" == $user_log)?"":'(user '.$user_log.') ').$category_log.' '.$log_info."\n"; + $display_text = "\nLOG ".$log_datetime.' '.$severity_txt.' '.(("" == $user_log)?"":'(user '.$user_log.') ').$category_log.' '.$log_text."\n"; if ($this->IsDebugViaHtml()) { $display_text = str_replace("\n","
\n", htmlentities($display_text)); } @@ -3937,7 +4025,7 @@ function WriteLog( $syslog_hostname = $this->GetSystemName(); $syslog_process = 'multiOTP'; $syslog_ip_from = $this->GetLocalIpAddress(); - $syslog_content = str_replace("\n", "; ", $log_info); + $syslog_content = str_replace("\n", "; ", $log_text); $syslog_fqdn = $this->GetSystemName().(("" != $this->GetDomainName())?'.'.$this->GetDomainName():""); // Do an asynchronous SysLog if possible (Linux) @@ -3992,9 +4080,10 @@ function WriteLog( $log_severity_escaped = escape_mysql_string($severity_txt); $log_user_escaped = escape_mysql_string($user_log); $log_category_escaped = escape_mysql_string($category_log); - $log_info_escaped = escape_mysql_string(mb_substr($log_info,0,255)); + $log_text_escaped = escape_mysql_string(mb_substr($log_text,0,255)); + $log_create_host = escape_mysql_string($this->GetCreateHost()); - $sQuery = "INSERT INTO `".$this->_config_data['sql_log_table']."` (`datetime`,`severity`,`user`,`category`,`logentry`) VALUES ('".$log_datetime."','".$log_severity_escaped."','".$log_user_escaped."','".$log_category_escaped."','".$log_info_escaped."')"; + $sQuery = "INSERT INTO `".$this->_config_data['sql_log_table']."` (`datetime`,`severity`,`user`,`category`,`logentry`,`local_only`, `create_host`, `create_time`) VALUES ('".$log_datetime."','".$log_severity_escaped."','".$log_user_escaped."','".$log_category_escaped."','".$log_text_escaped."',".intval($local_only).",'".$log_create_host."', ".$log_time.")"; if (is_object($this->_mysqli)) { if (!($rResult = $this->_mysqli->query($sQuery))) { @@ -4010,9 +4099,10 @@ function WriteLog( $log_severity_escaped = pg_escape_string($severity_txt); $log_user_escaped = pg_escape_string($user_log); $log_category_escaped = pg_escape_string($category_log); - $log_info_escaped = pg_escape_string(mb_substr($log_info,0,255)); + $log_text_escaped = pg_escape_string(mb_substr($log_text,0,255)); + $log_create_host = pg_escape_string($this->GetCreateHost()); - $sQuery = "INSERT INTO \"".$this->_config_data['sql_schema']."\".\"".$this->_config_data['sql_log_table']."\" (\"datetime\",\"severity\",\"user\",\"category\",\"logentry\") VALUES ('".$log_datetime."','".$log_severity_escaped."','".$log_user_escaped."','".$log_category_escaped."','".$log_info_escaped."')"; + $sQuery = "INSERT INTO \"".$this->_config_data['sql_schema']."\".\"".$this->_config_data['sql_log_table']."\" (\"datetime\",\"severity\",\"user\",\"category\",\"logentry\",\"local_only\",\"create_host\", \"create_time\") VALUES ('".$log_datetime."','".$log_severity_escaped."','".$log_user_escaped."','".$log_category_escaped."','".$log_text_escaped."',".intval($local_only).",'".$log_create_host."', ".$log_time.")"; if (!($rResult = pg_query($this->_pgsql_database_link, $sQuery))) { $this->WriteLog("Error: SQL query error ($sQuery) : ".pg_last_error(), TRUE, FALSE, 40, 'System', '', 3); @@ -4169,6 +4259,11 @@ function ClearLog($days = 0) unlink($this->GetLogFolder().$this->GetLogFileName()); } } + foreach (glob($this->GetLogFolder().$this->GetLogFileName().".*") as $filename) { + if ((filemtime($filename) + ($days * 86400)) < time()) { + unlink($filename); + } + } return $result; } @@ -5041,6 +5136,11 @@ function GetDebugOption() { } + function IsDebugOption() { + return (1 == intval($this->_config_data['debug'])); + } + + function SetDeveloperMode( $value ) { @@ -6714,6 +6814,45 @@ function GetDefaultAlgorithm() return $this->_config_data['default_algorithm']; } + + function SetSmsCodeAllowed( + $value + ) { + $this->_config_data['sms_code_allowed'] = ((intval($value) > 0)?1:0); + } + + + function GetSmsCodeAllowed() + { + return intval($this->_config_data['sms_code_allowed']); + } + + + function IsSmsCodeAllowed() + { + return (1 == intval($this->_config_data['sms_code_allowed'])); + } + + + function SetEmailCodeAllowed( + $value + ) { + $this->_config_data['email_code_allowed'] = ((intval($value) > 0)?1:0); + } + + + function GetEmailCodeAllowed() + { + return intval($this->_config_data['email_code_allowed']); + } + + + function IsEmailCodeAllowed() + { + return (1 == intval($this->_config_data['email_code_allowed'])); + } + + function SetDefaultRequestLdapPwd( $value ) { @@ -7342,6 +7481,19 @@ function GetSmsTimeout() } + function SetEmailCodeTimeout( + $value + ) { + $this->_config_data['email_code_timeout'] = intval($value); + } + + + function GetEmailCodeTimeout() + { + return $this->_config_data['email_code_timeout']; + } + + function SetConfigAttribute( $attribute, $value @@ -8710,7 +8862,7 @@ function CreateUser($user_raw, $automatically = FALSE, $sync_process = FALSE ) { - // A user cannot be created with a leading backslash + // A user cannot be created with one (or more) leading backslash $user = str_replace("\\", "", $user_raw); $result = FALSE; if ('' != trim($user)) { @@ -8819,7 +8971,7 @@ function CreateUserFromToken($user_raw, $description = '', $group = '' ) { - // A user cannot be created with a leading backslash + // A user cannot be created with one (or more) leading backslash $user = str_replace("\\", "", $user_raw); if (intval($prefix_pin_needed) < 0) @@ -9369,7 +9521,7 @@ function FastCreateUser($user_raw, $dialin_ip_address = '', $sync_process = false ) { - // A user cannot be created with a leading backslash + // A user cannot be created with one (or more) leading backslash $user = str_replace("\\", "", $user_raw); $result = FALSE; @@ -14454,7 +14606,7 @@ function GetUserInfo( $result.= " Locked: ".((1 == $this->GetUserLocked()) ? 'yes' : 'no').$crlf; $result.= " Activated: ".((1 == $this->GetUserActivated()) ? 'yes' : 'no').$crlf; $result.= " AD/LDAP synchronized: ".((1 == $this->GetUserSynchronized()) ? 'yes' : 'no').$crlf; - $result.= " Prefix pin needed: ".((1 == $this->GetUserPrefixPin()) ? 'yes' : 'no').$crlf; + $result.= " Prefix pin needed: ".(($this->IsUserPrefixPin()) ? 'yes' : 'no').$crlf; $result.= " Description: ".$this->GetUserDescription().$crlf; $result.= " Email: ".$this->GetUserEmail().$crlf; $result.= " Mobile phone: ".$this->GetUserSms().$crlf; @@ -14982,7 +15134,7 @@ function SyncLdapUsers( 'LDAP', // Synchronized channel $this->GetLdapDomainControllers(), // Synchronized server $ldap_synchronized_dn, - -1, // Set to default value if the user is created automatically + -1, // LDAP prefix set to default value if the user is created automatically $ldap_language, (("---" != $ldap_framedipaddress) ? $ldap_framedipaddress : "") ); @@ -15692,9 +15844,9 @@ function SendSms( $exec_cmd = str_replace('%to', $sms_number, $exec_cmd); $exec_cmd = str_replace('%msg', encode_utf8_if_needed($sms_message_to_send), $exec_cmd); exec($exec_cmd, $output); - $result = 18; // INFO: SMS code request received + $result = 18; // INFO: Static code request received if ($write_log) { - $this->WriteLog("Info: SMS code request received for ".$real_user.(("" != $source_tag)?" for $source_tag":"")." and sent via ".$exec_cmd, FALSE, FALSE, $result, 'SMS', $real_user); + $this->WriteLog("Info: Static code request received for ".$real_user.(("" != $source_tag)?" for $source_tag":"")." and sent via ".$exec_cmd, FALSE, FALSE, $result, 'Authentication', $real_user); } } else { $sms_message = new MultiotpSms(array('provider' => $sms_provider, @@ -15718,9 +15870,9 @@ function SendSms( )); if ($sms_message->sendSMS()) { - $result = 18; // INFO: SMS code request received + $result = 18; // INFO: Static code request received if ($write_log) { - $this->WriteLog("Info: SMS code request received for ".$real_user.(("" != $source_tag)?" for $source_tag":"")." and sent via ".$sms_provider." to ".$sms_number, FALSE, FALSE, $result, 'SMS', $real_user); + $this->WriteLog("Info: Static code request received for ".$real_user.(("" != $source_tag)?" for $source_tag":"")." and sent via ".$sms_provider." to ".$sms_number, FALSE, FALSE, $result, 'Authentication', $real_user); } if ($this->GetVerboseFlag()) { $this->WriteLog("Debug: *Detailed Sent Info successful to $sms_provider: ".$sms_message->getLastSendInfo(), FALSE, FALSE, 8888, 'Debug', ''); @@ -15745,6 +15897,7 @@ function GenerateSmsToken( $user = '' ) { $result = 99; + If ($this->IsSmsCodeAllowed()) { $now_epoch = time(); if ('' != $user) { $this->SetUser($user); @@ -15772,9 +15925,224 @@ function GenerateSmsToken( $result = $this->SendSms($sms_number, $sms_message_to_send, $user); } else { $result = 60; // ERROR: no information on where to send SMS code - $this->WriteLog("Error: no information on where to send SMS code for ".$real_user, FALSE, FALSE, $result, 'SMS', $real_user); + $this->WriteLog("Error: no information on where to send SMS code for ".$user, FALSE, FALSE, $result, 'SMS', $user); } $this->WriteUserData(); + } else { + $result = 65; + $this->WriteLog(array('text' => "ERROR: SMS code request not allowed", + 'error_code' => $result, + 'category' => 'System', + 'user' => '') + ); + } + return $result; + } + + + // Generate Email token, and store them in the static token + // (which was initially the sms token) + function GenerateEmailToken( + $user = '' + ) { + $result = 99; + If ($this->IsEmailCodeAllowed()) { + $now_epoch = time(); + if ('' != $user) { + $this->SetUser($user); + } else { + $user = $this->GetUser(); + } + $email = $this->GetUserEmail(); + if ('' != $email) { + $steps = $now_epoch; + $digits = $this->GetSmsDigits(); + $seed_bin = hex2bin(md5('EmaiL'.$this->GetEncryptionKey().$this->GetUserTokenSeed().$user.$now_epoch)); + $token = $this->GenerateOathHotp($seed_bin, $steps, $digits); + $this->SetUserSmsOtp($token); + $this->SetUserSmsValidity($now_epoch + $this->GetEmailCodeTimeout()); + + $nice_token = $this->ConvertToNiceToken($token); + $subject = "OTP: $nice_token"; + $content = $subject."
\n({MultiotpDateTime format=Y-m-d H:i:s})"; + + $result = ($this->SendEmail(array('user' => $user, + 'subject' => $subject, + 'content' => $content + ) + ) ? 18 : 68); + } else { + $result = 67; // ERROR: No information on where to send Email code + $this->WriteLog(array('text' => "Error: no information on where to send Email code for ".$user, + 'error_code' => $result, + 'category' => 'Authentication', + 'user' => $user) + ); + } + $this->WriteUserData(); + } else { + $result = 66; + $this->WriteLog(array('text' => "ERROR: Email code request not allowed", + 'error_code' => $result, + 'category' => 'System', + 'user' => '') + ); + } + return $result; + } + + + function SendEmail( + $send_email_array = array() + ) { + $user = isset($send_email_array['user'])?$send_email_array['user']:''; + $email = isset($send_email_array['email'])?$send_email_array['email']:''; + $from_email = isset($send_email_array['from_email'])?$send_email_array['from_email']:''; + $from_name = isset($send_email_array['from_name'])?$send_email_array['from_name']:''; + $returnpath = isset($send_email_array['returnpath'])?$send_email_array['returnpath']:''; + $subject = isset($send_email_array['subject'])?$send_email_array['subject']:''; + $content = isset($send_email_array['content'])?$send_email_array['content']:''; + $template = isset($send_email_array['template'])?$send_email_array['template']:''; + $var = isset($send_email_array['var'])?$send_email_array['var']:''; + $x_sender = isset($send_email_array['x-sender'])?$send_email_array['x-sender']:''; + $priority = isset($send_email_array['priority'])?$send_email_array['priority']:''; + $language = isset($send_email_array['language'])?$send_email_array['language']:''; + + $result = FALSE; + $actual_user = ""; + + $specific_language = $this->GetLanguage(); + + if ('' == $email) { + $actual_user = ('' != $user)?$user:$this->GetUser(); + if ('' != $actual_user) { + $this->ReadUserData($actual_user); + $email = trim($this->GetUserEmail()); + $specific_language = $this->GetUserLanguage(); + } + } + + if ('' != $language) { + $specific_language = $language(); + } else { + $language = $this->GetLanguage(); + } + + if ('' != $email) { + if ('' == $from_email) { + $from_email = $this->GetSmtpSender(); + } + if ('' == $from_email) { + $from_email = $email; + } + + if ('' == $from_name) { + $from_name = trim($this->GetSmtpSenderName()); + } + if ('' == $from_name) { + $from_name = $this->GetSystemName().(('' != $this->GetDomainName())?'.'.$this->GetDomainName():''); + } + + if ('' == $returnpath) { + $returnpath = $from_email; + } + + if ('' == $content) { + $content = $subject; + } + + if ('' == $x_sender) { + $x_sender = $this->GetClassName(); + } + + if ('' == $priority) { + $priority = 'High'; + } + + //Get template file + if ('' != $template) { + $file_to_show = $this->GetConfigFolder().$template.'.html'; + if(!file_exists($file_to_show)) { + $file_to_show = $this->GetTemplatesFolder().$template.'.html'; + } + if(file_exists($file_to_show)) { + $content = (file_get_contents($file_to_show)); + } + } + + $content = encode_utf8_if_needed($content); + + // Check if a specific language exists in the tags + if (false === mb_stripos($content, '{IfMultiotpLanguage="'.$specific_language.'"')) { + $specific_language = $language; + if (false === mb_stripos($content, '{IfMultiotpLanguage="'.$specific_language.'"')) { + $specific_language = 'en'; + } + } + // Clean other languages info + $content = preg_replace('//i', '', $content); + $content = preg_replace('//i', '', $content); + $content = preg_replace('//i', ' -- {/IfMultiotpLanguage="other"} {ML} -->', $content); + $content = preg_replace('//i', '",$content); + foreach($content_slice as $one_slice) { + $comment_pos = mb_strpos($one_slice,'",$content); + foreach($content_slice as $one_slice) { + $comment_pos = mb_strpos($one_slice,'/i', '', $content); + $content = preg_replace('//i', '', $content); + $content = preg_replace('//i', ' -- {/IfMultiotpLanguage="other"} {ML} -->', $content); + $content = preg_replace('//i', '",$content); + foreach($content_slice as $one_slice) { + $comment_pos = mb_strpos($one_slice,'",$content); + foreach($content_slice as $one_slice) { + $comment_pos = mb_strpos($one_slice,'