<?php
/**
 * @file  multiotp.server.php
 * @brief web service for the multiOTP class.
 *
 * multiOTP web service - Strong two-factor authentication PHP class
 * https://www.multiotp.net
 *
 * Visit http://forum.multiotp.net/ for additional support.
 *
 * Donation are always welcome! Please check https://www.multiotp.net
 * and you will find the magic button ;-)
 *
 * The multiOTP web service is simply merged with the multiOTP PHP class
 * in order to provide the server part of the client/server solution.
 *
 * This file can be used with any web server supporting PHP as
 * script language, like the following web servers:
 *  - nginx is a light one under Linux
 *    (http://nginx.org/)
 *  - Mongoose is a light one under Windows
 *    (https://code.google.com/archive/p/mongoose/downloads)
 *  - The Apache HTTP server is a very well known web server running under Linux and Windows
 *    (http://httpd.apache.org/)
 *
 *
 * PHP 7.4 or higher is supported.
 *
 * @author    Andre Liechti, SysCo systemes de communication sa, <info@multiotp.net>
 * @version   5.10.2.1
 * @date      2026-03-23
 * @since     2013-08-06
 * @copyright (c) 2013-2026 SysCo systemes de communication sa
 * @copyright GNU Lesser General Public License
 *
 *//*
 *
 * LICENCE
 *
 *   Copyright (c) 2010-2026 SysCo systemes de communication sa
 *   SysCo (tm) is a trademark of SysCo systemes de communication sa
 *   (http://www.sysco.ch)
 *   All rights reserved.
 * 
 *   This file is part of the multiOTP PHP class
 *
 *   multiOTP PHP class is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public License as
 *   published by the Free Software Foundation, either version 3 of the License,
 *   or (at your option) any later version.
 * 
 *   multiOTP PHP class is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 * 
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with MultiOTP PHP class.
 *   If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * Usage
 *
 *   You will have to send an XML formatted content in the field named data
 *    in a POSTed form.
 *
 *
 * Special issues
 *
 *   If you need specific developments concerning strong authentication,
 *   do not hesitate to contact us per email at info@multiotp.net.
 *
 *
 * Users feedbacks and comments
 *
 * 2017-02-09 Frank van der Aa, Vanboxtel BV (NL)
 *   Thanks for your debug about lockedlistarray[], the new
 *   GetDelayedUsersList() method and the delayed users display on the web GUI.
 *
 * 2013-07-25 Dominik Pretzsch from Last Squirrel IT
 *   After some discussions with Dominik, integration of the
 *    client/server support in the basic library
 *
 *
 * Change Log
 *
 *   2022-05-08 5.8.8.5 SysCo/al Scratchlist can be generated from the Web GUI
 *   2021-03-25 5.8.1.9 SysCo/al Cookie privacy (httponly and secure) are now handled in the application directly
 *                               Weak SSL ciphers disabled
 *   2021-02-12 5.8.1.0 SysCo/al Web GUI update
 *   2019-01-24 5.4.1.5 SysCo/al If any, clean specific NTP DHCP option at every reboot
 *   2019-01-07 5.4.1.1 SysCo/al Raspberry Pi 3B+ support
 *   2018-08-21 5.3.0.0 SysCo/al without2FA algorithm added
 *   2017-07-07 5.0.4.9 SysCo/al Code cleaning, possible web information added
 *   2017-05-29 5.0.4.5 SysCo/al Restore configuration added
 *                               Fixed configuration file directory under Windows
 *                               The file can now be included from another one that have already an instance
 *   2016-11-06 5.0.2.7 SysCo/al Better configuration file detection
 *   2016-11-04 5.0.2.6 SysCo/al Backup configuration added
 *   2016-10-16 5.0.2.5 SysCo/al New methods added for SOAP service
 *   2016-04-18 5.0.0.0 SysCo/al ForceNoDisplayLog() method called to avoid log on display
 *   2015-07-15 4.3.2.5 SysCo/al Admin password update has been fixed
 *   2015-06-10 4.3.2.3 SysCo/al Enhancements for the Dev(Talks): demo
 *   2015-06-09 4.3.2.2 SysCo/al More option available from the GUI, Dev Talks Edition
 *   2014-12-09 4.3.1.0 SysCo/al Speed improvement (apc options have been tuned)
 *   2014-11-04 4.3.0.0 SysCo/al Updated GUI
 *   2014-04-13 4.2.4.2 SysCo/al Version synchronization
 *   2014-03-30 4.2.4   SysCo/al Forum link added on the GUI, minor bug fixes
 *   2014-03-01 4.2.2   SysCo/al More options available from the GUI
 *   2014-01-20 4.1.1   SysCo/al Version synchronization
 *   2013-12-23 4.1.0   SysCo/al Adding basic web functionalities
 *   2013-08-30 4.0.7   SysCo/al Version synchronization
 *   2013-08-25 4.0.6   SysCo/al Enhanced default page
 *   2013-08-20 4.0.4   SysCo/al Initial release
 *
 *********************************************************************/

///////////////////////////////////////////////////////////////////////////
// For your convenience, the class file is directly integrated in this file
///////////////////////////////////////////////////////////////////////////
if (!class_exists('Multiotp')) {
  require_once('multiotp.class.php');
}

$multiotp_etc_dir  = '/etc/multiotp';
$config_folder     = $multiotp_etc_dir.'/config';
if (false === mb_strpos(getcwd(), '/')) {
  // if (!@file_exists($config_folder)) {
  $multiotp_etc_dir  = '';
  $config_folder = '';
}

if (!isset($multiotp)) {
  $multiotp = new Multiotp('DefaultCliEncryptionKey', false, '', $config_folder);
  $multiotp->SetHashSalt(_MULTIOTP_DEFAULT_HASH_SALT_); // Shared secret
}
$multiotp->ForceNoDisplayLog(); // No log on display as we are running a web server

if ('' != $multiotp_etc_dir) {
  $multiotp->SetLogFolder('/var/log/multiotp/');
  $multiotp->SetConfigFolder($multiotp_etc_dir.'/config/');
  $multiotp->SetDdnsFolder($multiotp_etc_dir.'/ddns/');
  $multiotp->SetDevicesFolder($multiotp_etc_dir.'/devices/');
  $multiotp->SetGroupsFolder($multiotp_etc_dir.'/groups/');
  $multiotp->SetTokensFolder($multiotp_etc_dir.'/tokens/');
  $multiotp->SetUsersFolder($multiotp_etc_dir.'/users/');
  $multiotp->SetCacheFolder('/tmp/cache/');
  $multiotp->SetLinuxFileMode('0666');
}
$multiotp->ReadConfigData();

/****************************************
 * WE REALLY DO NOT WANT TO BE CACHED !!!
 ****************************************/
header("Expires: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");

if (isset($_SERVER['HTTP_ORIGIN'])) {
  header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
} else {
  header("Access-Control-Allow-Origin: *");
}
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400');

// Access-Control headers are received during OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
  header('Access-Control-Allow-Headers: Content-Type,Prefer,X-Requested-With');
  exit(0);
}

$data = isset($_POST['data'])?$_POST['data']:'';
$method = mb_substr(isset($_GET['method'])?$_GET['method']:(isset($_POST['method'])?$_POST['method']:''),0,255);
$options = isset($_GET['options'])?$_GET['options']:(isset($_POST['options'])?$_POST['options']:'');
$postdata = file_get_contents("php://input");

$headers = getallheaders();
$prefer = (!empty($headers['Prefer']) ? $headers['Prefer'] : '');

if ($prefer == "api=vuejs") {
 /*****************
  *****************
  *** API/VUEJS ***
  *****************
  *****************/
  $multiotp->ApiVueJsPreAuth();
  $multiotp->ApiVueJs($postdata);
exit();
} elseif (FALSE !== mb_strpos($data,'<multiOTP')) {
  $multiotp->XmlServer($data);
  exit();
} elseif ((FALSE !== mb_strpos($postdata,'<SOAP-ENV')) || (isset($_GET['soap'])) || (isset($_GET['wsdl']))) {
    /*******************
     *******************
     *** SOAP SERVER ***
     *******************
     ******************/
     
    // Instantiate the SOAP server
    $soap_server = new soap_server();

    $soap_service_name = "multiotp";
    $soap_tns_namespace = 'https://www.multiotp.net/wsdl/multiotp/';
    $soap_endpoint_url = false;
    $soap_schema_target_namespace = 'https://www.multiotp.net/wsdl/multiotp/';
    $soap_openotp_namespace = 'urn:openotp'; // urn:openotp

    // Create the WSDL 
    $soap_server->configureWSDL($soap_service_name, $soap_tns_namespace, $soap_endpoint_url);

    // Set the schema target namespace
    // $soap_server->wsdl->schemaTargetNamespace = $soap_schema_target_namespace;


    //Register openotpNormalLogin method
    $soap_server->register(
        'openotpNormalLogin', // method name
        array('username'     => 'xsd:string', // input parameters
              'domain'       => 'xsd:string',
              'ldapPassword' => 'xsd:string',
              'otpPassword'  => 'xsd:string',
              'client'       => 'xsd:string',
              'source'       => 'xsd:string',
              'settings'     => 'xsd:string',
              'options'      => 'xsd:string'),
        array('code'         => 'xsd:integer', // return value(s)
              'message'      => 'xsd:string',
              'session'      => 'xsd:string',
              'data'         => 'xsd:string',
              'timeout'      => 'xsd:integer',
              'otpChallenge' => 'xsd:string',
              'u2fChallenge' => 'xsd:string'),
        $soap_openotp_namespace, // namespace
        false, // soapaction: (use default)
        'rpc', // style: rpc or document
        'encoded', // use: encoded or literal
        'This method is used to send an authentication request.'); // description: documentation for the method
        
    // Defined method
    function openotpNormalLogin($username, $domain, $ldapPassword, $otpPassword, $client, $source, $settings, $options) {
        global $multiotp;
        return $multiotp->SoapOpenotpNormalLogin($username, $domain, $ldapPassword, $otpPassword, $client, $source, $settings, $options);
    }

    //Register openotpSimpleLogin method
    $soap_server->register(
        'openotpSimpleLogin',
        array('username'     => 'xsd:string',
              'domain'       => 'xsd:string',
              'anyPassword'  => 'xsd:string',
              'client'       => 'xsd:string',
              'source'       => 'xsd:string',
              'settings'     => 'xsd:string'),
        array('code'         => 'xsd:integer',
              'message'      => 'xsd:string',
              'session'      => 'xsd:string',
              'data'         => 'xsd:string',
              'timeout'      => 'xsd:integer'),
        $soap_openotp_namespace,
        false,
        'rpc',
        'encoded',
        'This method is similar to openotpNormalLogin with only one generic password attribute.');
        
    // Defined method
    function openotpSimpleLogin($username, $domain, $anyPassword, $client, $source, $settings) {
        global $multiotp;
        return $multiotp->SoapOpenotpSimpleLogin($username, $domain, $anyPassword, $client, $source, $settings);
    }


    //Register openotpLogin method
    $soap_server->register(
        'openotpLogin',
        array('username'     => 'xsd:string', // input parameters
              'domain'       => 'xsd:string',
              'ldapPassword' => 'xsd:string',
              'otpPassword'  => 'xsd:string',
              'client'       => 'xsd:string',
              'source'       => 'xsd:string',
              'settings'     => 'xsd:string',
              'options'      => 'xsd:string'),
        array('code'         => 'xsd:integer',
              'message'      => 'xsd:string',
              'session'      => 'xsd:string',
              'data'         => 'xsd:string',
              'timeout'      => 'xsd:integer'),
        $soap_openotp_namespace,
        false,
        'rpc',
        'encoded',
        'This method is an alias of openotpNormalLogin. It ensures backward compatibility.');
        
    // Defined method
    function openotpLogin($username, $domain, $ldapPassword, $otpPassword, $client, $source, $settings, $options) {
        global $multiotp;
        return $multiotp->SoapOpenotpNormalLogin($username, $domain, $ldapPassword, $otpPassword, $client, $source, $settings, $options);
    }


    //Register openotpChallenge method
    $soap_server->register(
        'openotpChallenge',
        array('username'     => 'xsd:string',
              'domain'       => 'xsd:string',
              'session'      => 'xsd:string',
              'otpPassword'  => 'xsd:string',
              'u2fResponse'  => 'xsd:string'),
        array('code'         => 'xsd:integer',
              'message'      => 'xsd:string',
              'data'         => 'xsd:string'),
        $soap_openotp_namespace,
        false,
        'rpc',
        'encoded',
        'This method is used when the openotpLogin returned a challenge (code 2). This is the second request to be sent containing the user one-time password.');
        
    // Defined method
    function openotpChallenge($username, $domain, $session, $otpPassword) {
        global $multiotp;
        return $multiotp->SoapOpenotpChallenge($username, $domain, $session, $otpPassword);
    }


    //Register openotpStatus method
    $soap_server->register(
        'openotpStatus',
        array(),
        array('status'  => 'xsd:boolean',
              'message' => 'xsd:string'),
        $soap_openotp_namespace,
        false,
        'rpc',
        'encoded',
        'openotpStatus call');


    // Defined method
    function openotpStatus() {
        global $multiotp;
        return $multiotp->SoapOpenotpStatus();
    }

    $soap_server->service($postdata);
    exit();
} else {
    // Secure and httponly cookie parameters forced in the application
    $params = session_get_cookie_params();
    $cookie_secure = (!$multiotp->IsDebugOption()) && (!$multiotp->IsDeveloperMode());
    $cookie_httponly = true;
    session_set_cookie_params($params["lifetime"],
              $params["path"], $params["domain"],
              $cookie_secure, $cookie_httponly
             );
    session_start();
    $hash_salt = $multiotp->GetHashSalt();

    /****************************************
     * WE REALLY DO NOT WANT TO BE CACHED !!!
     ****************************************/
    header("Expires: " . gmdate("D, d M Y H:i:s") . " GMT");
    header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
    header("Cache-Control: no-store, no-cache, must-revalidate");
    header("Cache-Control: post-check=0, pre-check=0", false);
    header("Pragma: no-cache");

    $multiotp->SendWeeklyAnonymousStat();

    if (isset($_FILES['upgrade_file']['tmp_name'])) {
      if ((isset($_SESSION['logged']) && $_SESSION['logged'])) {
        if (file_exists($_FILES['upgrade_file']['tmp_name']) && (UPLOAD_ERR_OK == $_FILES["upgrade_file"]["error"])) {
          $upgrade_file = $multiotp->ConvertToWindowsPathIfNeeded(sys_get_temp_dir()."/".date("YmdHis")."-".md5($_FILES['upgrade_file']['tmp_name']).".cfg");
          move_uploaded_file($_FILES['upgrade_file']['tmp_name'], $upgrade_file);
        }
      }
    }
    
    if (isset($_FILES['config_file']['tmp_name'])) {
        if ((isset($_SESSION['logged']) && $_SESSION['logged'])) {
            if (file_exists($_FILES['config_file']['tmp_name']) && (UPLOAD_ERR_OK == $_FILES["config_file"]["error"])) {
              $config_file = $multiotp->GetConfigFolder().date("YmdHis")."-".md5($_FILES['config_file']['tmp_name']).".cfg";
              if (move_uploaded_file($_FILES['config_file']['tmp_name'], $config_file)) {

                if ($multiotp->RestoreConfiguration(array('backup_file' => $config_file, 'restore_key' => (isset($_POST['restore_config_password'])?nullable_trim($_POST['restore_config_password']):'')))) {

                  // Clean Devices
                  foreach (explode("\t", $multiotp->GetDevicesList()) as $one_device) {
                    if ('' != nullable_trim($one_device)) {
                      $multiotp->DeleteDevice($one_device);
                    }
                  }
                  $actual_folder = $multiotp->GetDevicesFolder();
                  $actual_filter = "*.db";
                  if (($actual_dir = opendir($actual_folder)) !== FALSE) {
                    while(($actual_file_name = readdir($actual_dir)) !== FALSE) {
                      if (fnmatch($actual_filter, $actual_file_name)) {
                        $actual_file = $actual_folder.$actual_file_name;
                        unlink($actual_file);
                      }
                    }
                  }

                  // Clean Groups
                  foreach (explode("\t", $multiotp->GetGroupsList()) as $one_group) {
                    if ('' != nullable_trim($one_group)) {
                      $multiotp->DeleteGroup($one_group);
                    }
                  }
                  $actual_folder = $multiotp->GetGroupsFolder();
                  $actual_filter = "*.db";
                  if (($actual_dir = opendir($actual_folder)) !== FALSE) {
                    while(($actual_file_name = readdir($actual_dir)) !== FALSE) {
                      if (fnmatch($actual_filter, $actual_file_name)) {
                        $actual_file = $actual_folder.$actual_file_name;
                        unlink($actual_file);
                      }
                    }
                  }

                  // Clean Tokens
                  foreach (explode("\t", $multiotp->GetTokensList()) as $one_token) {
                    if ('' != nullable_trim($one_token)) {
                      $multiotp->DeleteToken($one_token);
                    }
                  }
                  $actual_folder = $multiotp->GetTokensFolder();
                  $actual_filter = "*.db";
                  if (($actual_dir = opendir($actual_folder)) !== FALSE) {
                    while(($actual_file_name = readdir($actual_dir)) !== FALSE) {
                      if (fnmatch($actual_filter, $actual_file_name)) {
                        $actual_file = $actual_folder.$actual_file_name;
                        unlink($actual_file);
                      }
                    }
                  }

                  // Clean Users
                  $user_array = $multiotp->GetNextUserArray(TRUE);
                  while (FALSE !== $user_array) {
                    if (isset($user_array['user'])) {
                      $multiotp->DeleteUser($user_array['user']);
                    }
                    $user_array = $multiotp->GetNextUserArray();
                  }
                  $actual_folder = $multiotp->GetUsersFolder();
                  $actual_filter = "*.db";
                  if (($actual_dir = opendir($actual_folder)) !== FALSE) {
                    while(($actual_file_name = readdir($actual_dir)) !== FALSE) {
                      if (fnmatch($actual_filter, $actual_file_name)) {
                        $actual_file = $actual_folder.$actual_file_name;
                        unlink($actual_file);
                      }
                    }
                  }
                  
                  $multiotp->RestoreConfiguration(array('backup_file' => $config_file, 'restore_key' => (isset($_POST['restore_config_password'])?nullable_trim($_POST['restore_config_password']):'')));
                  
                  $_SESSION = array();
                  $_SESSION['logged'] = FALSE;
                  $ajax_result = "false";
                  // $multiotp->ReadConfigData();
                  header('Location: '.(isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
                  exit();
                } // End of the restore is ok
              } // if move_uploaded_file
              if (file_exists($config_file)) {
                unlink($config_file);
              }
            }
        }
    }

    if (isset($_FILES['token_file']['tmp_name'])) {
        if ((isset($_SESSION['logged']) && $_SESSION['logged'])) {
            if (file_exists($_FILES['token_file']['tmp_name']) && (UPLOAD_ERR_OK == $_FILES["token_file"]["error"])) {
                $multiotp->ImportTokensFile($_FILES['token_file']['tmp_name'], $_FILES['token_file']['name'], isset($_POST['token_password'])?nullable_trim($_POST['token_password']):'');
            }
        }
        echo "DONE";
    } elseif ('' == $method) {
        /*********************
         * Basic web server *
         *********************/
         
        $actual_date     = date('Y-m-d H:i:s');
        $class_name      = $multiotp->GetClassName();
        $class_version   = $multiotp->GetVersion();
        $class_date      = $multiotp->GetDate();
        $rpi_serial      = $multiotp->GetRaspberryPiSerialNumber();
        $rpi_info        = (('' != $rpi_serial)?"<br />\n        Raspberry Pi serial number: ".$rpi_serial."\n        ":'');
        $server_software = (isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'].', ' : '')."PHP/".phpversion();

        $prefix_required0_checked = '';
        $prefix_required1_checked = '';
        
        if ($multiotp->IsDefaultRequestPrefixPin())
        {
            $prefix_required1_checked = ' checked="checked" ';
        }
        else
        {
            $prefix_required0_checked = ' checked="checked" ';
        }

        if ($multiotp->CheckAdminPassword("1234")) {
          $default_user_info = "(default is admin)";
          $default_password_info = "(default is 1234)";
        } else {
          $default_user_info = "";
          $default_password_info = "";
        }

        $webpage = <<<EOWEBPAGE
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
    <head>
        <title>
            multiOTP web administration console
        </title>
        <style>
            body {
                font-family: Verdana, Helvetica, Arial;
                color: black;
                font-size: 10pt;
                font-weight: normal;
                text-decoration: none;
            }
            h2 {
                font-size: 12pt;
                font-weight: bold;
                margin-top: 0em;
                margin-bottom: 0em;
            }
            h3 {
                font-size: 11pt;
                font-weight: bold;
                margin-top: 0.5em;
                margin-bottom: 0.1em;
            }
            p {
                margin-top: 0em;
                margin-bottom: 0em;
            }
            th {
                text-align: right;
            }
            .info {
                font-style: italic;
                font-weight: normal;
            }
            .section_title {
                display: block;
                font-weight: bold;
            }
            .section_title a {
                color: black;
                text-decoration: none;
            }
            
            /*************************/
            /* Custom colors - BEGIN */
            body {
                background-color: black;
                color: white;
                font-family: Verdana, Helvetica, Arial;
                font-size: 10pt;
                font-weight: normal;
                text-decoration: none;
            }
            .custom_logo_white {
                color: white;
                font-weight: bold;
                font-size: 20px;
            }
            .custom_logo_red {
                color: red;
                font-weight: bold;
                font-size: 20px;
            }
            a {
                color: white;
                font-weight: bold;
                text-decoration: none;
            }
            a:hover {
                color: red;
            }
            .section_title a {
                color: white;
                text-decoration: none;
            }
            .synced {
                color: #80ff80;
            }
            .locked {
                color: #ff4040;
            }
            .delayed {
                color: #ff8000;
            }
            /* Custom colors - END */
            /***********************/


        </style>
        <meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
        <script>
            // Relative url page in order to execute the method remotely
            var url_page = location.protocol + '//' + location.host + location.pathname;

            // Shared secret
            var hash_salt = '$hash_salt';

            var selected_color = 'red';
            var unselected_color = 'black';
            var selected_background_color = '#e0e0e0';
            var unselected_background_color = '';
            var succeeded_color = '#80ff80';
            var failed_color = 'red';

            /*************************/
            /* Custom colors - BEGIN */

            var selected_color = 'red';
            var unselected_color = 'white';
            var selected_background_color = '#303030';
            var unselected_background_color = '';

            /* Custom colors - END */
            /***********************/
            
            function ChangeAdminPassword()
            {
                var newpassword = document.getElementById('newpassword').value;
                var newpassword2 = document.getElementById('newpassword2').value;
                document.getElementById('newpassword').value = '';
                document.getElementById('newpassword2').value = '';
                
                if ('' == newpassword)
                {
                    alert('Password is empty!');
                }
                else if (newpassword != newpassword2)
                {
                    alert('Passwords are not equal!');
                }
                else
                {
                    var hash_password = md5(hash_salt+newpassword+hash_salt);
                    RemoteCall('SetAdminPasswordHash', hash_password);
                    Logout();
                }
            }

            function Add()
            {
                var newuser = document.getElementById('newuser').value;
                var newemail = document.getElementById('newemail').value;
                var newsms = document.getElementById('newsms').value;
                var prefix_required = (document.getElementById('prefix_required1').checked?'1':'0');
                var algorithm = document.getElementById('algorithm').value;
                var token_serial = document.getElementById('token_serial').value;
                var pin = document.getElementById('pin').value;
                if (IsLoggedIn())
                {
                    RemoteCall('FastCreateUser', newuser+"\t"+newemail+"\t"+newsms+"\t"+prefix_required+"\t"+algorithm+"\t"+pin+"\t"+token_serial);
                    UpdatePage();
                }
                document.getElementById('newuser').value = '';
                document.getElementById('newemail').value = '';
                document.getElementById('newsms').value = '';
                document.getElementById('newuser').focus();
            }

            function DeleteToken(one_token)
            {
                if ('' != one_token)
                {
                    if (confirm('Are you sure you want to delete the token ' + one_token + '?'))
                    {
                        RemoteCall('DeleteToken', one_token);
                        UpdatePage();
                    }
                }
            }
            
            function DeleteUser(one_user)
            {
                if ('' != one_user)
                {
                    if (confirm('Are you sure you want to delete the user ' + one_user + '?'))
                    {
                        RemoteCall('DeleteUser', one_user);
                        UpdatePage();
                    }
                }
            }
            
            function ResyncUserNow(resync_user, resync_otp1, resync_otp2)
            {
                var resynced = ('true' == eval(RemoteCall('ResyncUser', resync_user+"\t"+resync_otp1+"\t"+resync_otp2)));
                if (resynced)
                {
                    Toggle('resync', 'none');
                    document.getElementById('resync_user').value = '';
                    document.getElementById('resync_otp1').value = '';
                    document.getElementById('resync_otp2').value = '';
                }
                else
                {
                    document.getElementById('resync_otp1').select();
                    document.getElementById('resync_otp1').focus();
                }
            }

            function CheckUserNow(check_user_user, check_user_otp)
            {
                var result = eval(RemoteCall('CheckToken', check_user_user+"\t"+check_user_otp));
                UpdatePage();
                if ('true' == result)
                {
                    document.getElementById('check_user_result').innerHTML = '<span style="color:'+succeeded_color+';">succeeded</span>';
                    document.getElementById('check_user_user').value = '';
                    document.getElementById('check_user_otp').value = '';
                }
                else
                {
                    document.getElementById('check_user_result').innerHTML = '<span style="color:'+failed_color+';">failed ('+result+')</span>';
                    document.getElementById('check_user_user').select();
                    document.getElementById('check_user_user').focus();
                    document.getElementById('check_user_otp').value = '';
                }
            }

            function BackupConfigNow(backup_config_password)
            {
                if (backup_config_password != '') {
                    var http_params = "method=BackupConfig"+"&options="+encodeURIComponent(backup_config_password);
                    var full_url = url_page +'?'+http_params;
                    window.open(full_url,'_blank');
                    document.getElementById('backup_config_password').value = '';
                }
            }

            function IsLoggedIn()
            {
                var logged_in = ('true' == eval(RemoteCall('UserLoggedIn')));
                return logged_in;
            }

            function Login()
            {
                var random_salt = eval(RemoteCall('GetRandomSalt'));

                var user = document.getElementById('user').value;
                var password = document.getElementById('password').value;
                document.getElementById('password').value = '';

                var hash_password = md5(random_salt+md5(hash_salt+password+hash_salt)+random_salt);
                RemoteCall('Login', user+"\t"+hash_password);
                if (UpdatePage())
                {
                    Toggle('add_user', 'block');
                    document.getElementById('newuser').focus();
                }
            }

            function Logout()
            {
                RemoteCall("Login", "");
                Toggle('add_user', 'block');
                UpdatePage();
            }

            function PrintQrCode(one_user)
            {
                if ('' != one_user)
                {
                    var http_params = "method=PrintQrCode"+"&options="+encodeURIComponent(one_user);
                    var full_url = url_page +'?'+http_params;
                    window.open(full_url,'_blank');
                }
            }

            function PrintScratchlist(one_user)
            {
                if ('' != one_user)
                {
                    var http_params = "method=PrintScratchlist"+"&options="+encodeURIComponent(one_user);
                    var full_url = url_page +'?'+http_params;
                    window.open(full_url,'_blank');
                }
            }

            function ResyncUser(one_user)
            {
                document.getElementById('resync_user').value = one_user;
                Toggle('resync', 'block');
                document.getElementById('resync_otp1').focus();
            }


            function RebootDevice()
            {
                var result = GetHttp('RebootDevice','');
            }

            function RemoteCall(my_method, my_options, my_id, async, form_method)
            /******************************************************************************************
             * RemoteCall is a sample function that make an (a)synchronous GET or POST request to launch
             *   a remote command and return the result as an HTML content in the defined id if defined.
             *
             * @param   string   my_method   remote method to call
             * @param   string   my_options  options (separated by \t) for the called method
             * @param   string   my_id       id of the div/span to update asynchronously with the result
             * @param   boolean  async       define the asynchronous mode
             * @param   string   form_method method used to send the request (GET or POST)
             * @return  none
             ******************************************************************************************/
            {
                my_method = typeof my_method !== 'undefined' ? my_method : '';
                my_options = typeof my_options !== 'undefined' ? my_options : '';
                my_id = typeof my_id !== 'undefined' ? my_id : '';
                async = typeof async !== 'undefined' ? async : false;
                form_method = typeof form_method !== 'undefined' ? form_method : 'GET';

                async = false;

                var result = '';
                
                if ('POST' != form_method)
                {
                    form_method = 'GET';
                }

                var xmlhttp;
                if (window.XMLHttpRequest)
                { // code for IE7+, Firefox, Chrome, Opera, Safari
                    xmlhttp=new XMLHttpRequest();
                }
                else
                { // code for IE6, IE5
                    xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
                }
                
                if (true == async)
                {
                    xmlhttp.onreadystatechange=function()
                    {
                        if (xmlhttp.readyState==4 && xmlhttp.status==200)
                        {
                            result = xmlhttp.responseText;
                            if ('' != my_id)
                            {
                                document.getElementById(my_id).innerHTML=eval(result);
                            }
                        }
                    }
                }

                var http_params = "method="+my_method+"&options="+encodeURIComponent(my_options);
                
                var full_url = url_page;
                var post_params = null;

                if ('GET' == form_method)
                {
                    full_url = url_page +'?'+http_params;
                }
                else
                {
                    post_params = http_params;
                }

                xmlhttp.open(form_method,full_url,false);
                if ('POST' == form_method)
                {
                    xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                    xmlhttp.setRequestHeader("Content-length", post_params.length);
                    xmlhttp.setRequestHeader("Connection", "close");
                }
                xmlhttp.send(post_params);

                if (false == async)
                {
                    if (xmlhttp.status === 200)
                    {
                        result = xmlhttp.responseText;
                        if ('' != my_id)
                        {
                            document.getElementById(my_id).innerHTML=eval(result);
                        }
                    }
                    return (result);
                }
            }

            function Toggle(my_section, set_value)
            {
                var all_sections = ['add_user', 'change_password', 'import_tokens', 'resync', 'check_user', 'backup_config', 'restore_config', 'list_tokens'];

                // Flush the check section
                document.getElementById('check_user_user').value = '';
                document.getElementById('check_user_otp').value = '';
                document.getElementById('check_user_result').innerHTML = '';

                set_value = typeof set_value !== 'undefined' ? set_value : '';
                
                if ('all' == set_value)
                {
                    all_sections.forEach(function(one_section)
                    {
                        document.getElementById(one_section+'_section').style.display = 'block';
                        document.getElementById(one_section+'_title').style.backgroundColor = selected_background_color;
                        document.getElementById(one_section+'_text').style.color=selected_color;
                        section_title = document.getElementById(one_section+'_toggle').innerHTML;
                        if ('[+]' == section_title)
                        {
                            document.getElementById(one_section+'_toggle').innerHTML = '[-]';
                        }
                    });
                }
                else if ((('none' == document.getElementById(my_section+'_section').style.display) || ('block' == set_value)) && ('none' != set_value))
                {
                    all_sections.forEach(function(one_section)
                    {
                        if (one_section != my_section)
                        {
                            document.getElementById(one_section+'_section').style.display = 'none';
                            document.getElementById(one_section+'_title').style.backgroundColor = unselected_background_color;
                            document.getElementById(one_section+'_text').style.color=unselected_color;
                            section_title = document.getElementById(one_section+'_toggle').innerHTML;
                            if ('[-]' == section_title)
                            {
                                document.getElementById(one_section+'_toggle').innerHTML = '[+]';
                            }
                        }
                    });
                    document.getElementById(my_section+'_section').style.display = 'block';
                    document.getElementById(my_section+'_title').style.backgroundColor = selected_background_color;
                    document.getElementById(my_section+'_text').style.color=selected_color;
                    section_title = document.getElementById(my_section+'_toggle').innerHTML;
                    if ('[+]' == section_title)
                    {
                        document.getElementById(my_section+'_toggle').innerHTML = '[-]';
                    }
                    if ('add_user' == my_section)
                    {
                        document.getElementById('newuser').focus();
                    }
                    else if ('change_password' == my_section)
                    {
                        document.getElementById('newpassword').focus();
                    }
                    else if ('import_tokens' == my_section)
                    {
                        document.getElementById('token_file').value = '';
                        var ifrm = document.getElementById('hidden_frame');
                        ifrm = (ifrm.contentWindow) ? ifrm.contentWindow : (ifrm.contentDocument.document) ? ifrm.contentDocument.document : ifrm.contentDocument;
                        ifrm.document.open();
                        ifrm.document.write('Waiting...');
                        ifrm.document.close();
                    }
                    else if ('resync' == my_section) {
                        document.getElementById('resync_user').select();
                        document.getElementById('resync_user').focus();
                        document.getElementById('resync_otp1').value = '';
                        document.getElementById('resync_otp2').value = '';
                    }
                    else if ('check_user' == my_section)
                    {
                        document.getElementById('check_user_user').select();
                        document.getElementById('check_user_user').focus();
                    }
                }
                else
                {
                    document.getElementById(my_section+'_section').style.display = 'none';
                    document.getElementById(my_section+'_title').style.backgroundColor = unselected_background_color;
                    document.getElementById(my_section+'_text').style.color=unselected_color;
                    section_title = document.getElementById(my_section+'_toggle').innerHTML;
                    if ('[-]' == section_title)
                    {
                        document.getElementById(my_section+'_toggle').innerHTML = '[+]';
                    }
                }
            }

            function UnlockUser(one_user)
            {
                if ('' != one_user)
                {
                    RemoteCall('UnlockUser', one_user);
                    UpdatePage();
                }
            }

            function UpdatePage()
            {
                var logged_in = IsLoggedIn();
                document.getElementById('logged').innerHTML = (logged_in?'User authenticated':'User NOT authenticated');
                document.getElementById('logout_section').style.display=(logged_in?'block':'none');
                document.getElementById('login_section').style.display=(logged_in?'none':'block');
                document.getElementById('authenticated_section').style.display=(logged_in?'block':'none');
                if (!logged_in)
                {
                    document.getElementById('login_title').style.backgroundColor = selected_background_color;
                    document.getElementById('login_text').style.color=selected_color;
                }

                document.getElementById('token_type').style.display = 'table-row';
                document.getElementById('pin').value = '';
                
                UpdateTokensList();
                UpdateUsersList();
                
                return logged_in;
            }
            
            function UpdateTokensList()
            {
                // Tokens
                var tokens_counter = 0;

                var remotecall = eval(RemoteCall('GetTokensList'));
                var tokenslist = typeof remotecall !== 'undefined' ? remotecall.trim() : '';
 
                /*
                var remotecall = eval(RemoteCall('GetLockedTokensList'));
                var lockedlist = typeof remotecall !== 'undefined' ? remotecall.trim() : '';
                */
                var lockedlist = '';
 
                if ('' != tokenslist)
                {
                    var tokensarray = tokenslist.split("\t");

                    if ('false' == tokenslist)
                    {
                        tokenslist = 'not authorized';
                        tokensarray = [];
                    }
                    else
                    {
                        var lockedlistarray = lockedlist.split("\t");
                        
                        tokenslist = '';
                        for (var i = 0; i < tokensarray.length; i++)
                        {
                            tokenslist = tokenslist + '<button type="button" onclick="DeleteToken(\''+tokensarray[i]+'\');">Delete</button>';
                            
                            tokenslist = tokenslist + ' ' + tokensarray[i];
                            
                            /*
                            for (var j = 0; j < lockedlistarray.length; j++)
                            {
                                if (tokensarray[i] == lockedlistarray[j])
                                {
                                    tokenslist = tokenslist + ' (<a href="#" onclick="UnlockToken(\''+tokensarray[i]+'\');">unlock</a>)';
                                    break;
                                }
                            }
                            */
                            
                            tokenslist = tokenslist + '<br />';
                            
                            tokens_counter++;
                        }
                    }

                    select = document.getElementById("token_serial");
                    select.options.length = 1;
                    for (var i = 0; i < tokensarray.length; i++)
                    {
                        select.options[1 + i] = new Option(tokensarray[i], tokensarray[i]);
                        /*
                        var opt= document.getElementById('token_serial').options[1 + i];
                        opt.value = tokensarray[i];
                        opt.text  = tokensarray[i];
                        */
                    }                    
                }
                document.getElementById('tokenslist').innerHTML = tokenslist;

                tokenscounter_txt = '';
                tokenstitle_txt = 'List of hardware token';
                if (tokens_counter > 0)
                {
                    tokenscounter_txt = '('+tokens_counter+' token';
                    if (tokens_counter > 1)
                    {
                        tokenscounter_txt = tokenscounter_txt + 's';
                        tokenstitle_txt = tokenstitle_txt + 's';
                    }
                    tokenscounter_txt = tokenscounter_txt + ')';
                }
                document.getElementById('tokenscounter').innerHTML = tokenscounter_txt;
                document.getElementById('tokenstitle').innerHTML = tokenstitle_txt;
            }
            
            function UpdateUsersList()
            {
                // Users
                var userslist = 'No user yet...';
                var counter = 0;

                var remotecall = eval(RemoteCall('GetEnhancedUsersList'));
                var enhanceduserslist = typeof remotecall !== 'undefined' ? remotecall.trim() : '';

                var remotecall = eval(RemoteCall('GetLockedUsersList'));
                var lockedlist = typeof remotecall !== 'undefined' ? remotecall.trim() : '';
 
                var remotecall = eval(RemoteCall('GetDelayedUsersList'));
                var delayedlist = typeof remotecall !== 'undefined' ? remotecall.trim() : '';
                
                if ('' != enhanceduserslist)
                {
                    var enhancedusersarray = enhanceduserslist.split("\t");

                    if ('false' == enhanceduserslist)
                    {
                        userslist = 'not authorized';
                    }
                    else
                    {
                        var lockedlistarray = lockedlist.split("\t");
                        var delayedlistarray = delayedlist.split("\t");
                        
                        userslist = '';
                        for (var i = 0; i < enhancedusersarray.length; i++)
                        {
                            var usersinfo = enhancedusersarray[i].split('|');
                            // usersinfo[0] = username
                            // usersinfo[1] = s1|s0 (AD/LDAP synchronized or not)
                            
                            userclass = "";

                            synced = ("s1" == usersinfo[1]);
                            if (synced) {
                                userclass="synced";
                            }

                            locked = false;
                            for (var j = 0; j < lockedlistarray.length; j++) {
                                if (usersinfo[0] == lockedlistarray[j]) {
                                    locked = true;
                                    userclass="locked";
                                    break;
                                }
                            }

                            delayed = false;
                            delayed_time = 0;
                            for (var j = 0; j < delayedlistarray.length; j++) {
                                var delayinfo = delayedlistarray[j].split('|');

                                if (usersinfo[0] == delayinfo[0]) {
                                    delayed_time = delayinfo[1];
                                    delayed = true;
                                    userclass="delayed";
                                    break;
                                }
                            }

                            userslist = userslist + '<button type="button" onclick="DeleteUser(\''+usersinfo[0]+'\');">Delete</button>';
                            userslist = userslist + '<button type="button" onclick="PrintQrCode(\''+usersinfo[0]+'\');">Print</button>';
                            userslist = userslist + '<button type="button" onclick="PrintScratchlist(\''+usersinfo[0]+'\');">Scratchlist</button>';
                            userslist = userslist + '<button type="button" onclick="ResyncUser(\''+usersinfo[0]+'\');">Resync</button>';

                            userslist = userslist + ' ';

                            if (userclass != '') {
                                userslist = userslist + '<span class="' + userclass + '">';
                            }

                            userslist = userslist + usersinfo[0];
                            
                            if (synced) {
                              userslist = userslist + ' ' + '[AD/LDAP]';
                            }

                            if (locked) {
                                userslist = userslist + ' (locked, <a href="#" onclick="UnlockUser(\''+usersinfo[0]+'\');">unlock</a>)';
                            }
                            
                            if (delayed) {
                                var delay_end = new Date(delayed_time * 1000 );
                                userslist = userslist + ' (delayed until ' + delay_end.toLocaleDateString() + ' ' + delay_end.toLocaleTimeString() + ', <a href="#" onclick="UnlockUser(\''+usersinfo[0]+'\');">unlock</a>)';
                            }

                            if (userclass != '') {
                                userslist = userslist + '</span>';
                            }

                            userslist = userslist + '<br />';
                            
                            counter++;
                        }
                    }
                }
                document.getElementById('userslist').innerHTML = userslist;

                userscounter_txt = '';
                userstitle_txt = 'List of users';
                if (counter > 0)
                {
                    userscounter_txt = '('+counter+' user';
                    if (counter > 1)
                    {
                        userscounter_txt = userscounter_txt + 's';
                        // userstitle_txt = userstitle_txt + 's';
                    }
                    userscounter_txt = userscounter_txt + ')';
                }
                document.getElementById('userscounter').innerHTML = userscounter_txt;
                document.getElementById('userstitle').innerHTML = userstitle_txt;
            }

        </script><script type='text/javascript' src='md5.js'></script>
    </head>
    <body onload=" if (UpdatePage()) { Toggle('add_user', 'block'); document.getElementById('newuser').focus(); }">
        <h2>multi<i>OTP</i> web administration console</h2>
        the open source strong authentication library
        <br />
        $class_name $class_version $class_date
        <br />
        <!-- div id="custom_info">
            <br />
            <span class="custom_logo_white">Dev</span><span class="custom_logo_red">(</span><span class="custom_logo_white">Talks</span><span class="custom_logo_red">)</span><span class="custom_logo_white">: Edition</span>
            <br />
            <br />
        </div -->
Web service is ready $actual_date, $server_software
$rpi_info<hr />
        <div id="login_section">
        <form>
            <div id="package_info_section">
                This package is the result of a *bunch* of work. If you find this package useful, <a target="_blank" href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=B78FJAH6RBNZ2">[Donation]</a> are always welcome to support this project.
                <br />
                Please check <a target="_blank" href="https://www.multiotp.net/">https://www.multiotp.net/</a> and you will find the magic button ;-)
				<br />
				Visit <a target="_blank" href="http://forum.multiotp.net/">http://forum.multiotp.net/</a> for additional support.
                <hr />
                \-_infoweb_-/
            </div>
            <hr />
            <div class="section_title" id="login_title"><span id="login_text">Login</span></div>
            Username: <input type=text" name="user" id="user" length="20" value="" /> $default_user_info
            <br />
            Password: <input type="password" onfocus="this.value='';" name="password" id="password" length="20" value="" /> $default_password_info
            <br />
            <span id="login"><button type="button" onclick="Login();" >Login</button></span>
        </form>
        </div>
        <div id="logout_section">
        <div id="log_info"></span><span id="logged"></div>
        <form>
            <span id="logout"><button type="button" onclick="Logout();">Logout</button></span>
        </form>
        <hr />
        </div>
        <div id="authenticated_section">
            <iframe name="hidden_frame" id="hidden_frame" style="display:none;"></iframe>
            <div class="section_title" id="change_password_title"><a href="#" onclick="Toggle('change_password');"><span id="change_password_toggle">[+]</span> <span id="change_password_text">Change admin password</span></a></div>
            <div id="change_password_section" style="display:none;">
            <form>
                <table>
                    <tr>
                        <th>
                            New admin password:
                        </th>
                        <td>
                            <input type="password" name="newpassword" id="newpassword" length="20" value="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                             Retype new admin password:
                        </th>
                        <td>
                            <input type="password" name="newpassword2" id="newpassword2" length="20" value="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                        </th>
                        <td>
                            <button type="button" onclick="ChangeAdminPassword();">Apply</button>
                        </td>
                    </tr>
                </table>
            </form>
            <hr />
            </div>
            <div class="section_title" id="import_tokens_title"><a href="#" onclick="Toggle('import_tokens');"><span id="import_tokens_toggle">[+]</span> <span id="import_tokens_text">Import new hardware tokens</span></a></div>
            <div id="import_tokens_section" style="display:none;">
            <form target="hidden_frame" enctype="multipart/form-data" method="post" action="">
                <table>
                    <tr>
                        <th>
                            Import tokens definition file (OATH PSKC, Yubico, etc.):
                        </th>
                        <td>
                            <input type="file" name="token_file" id="token_file" />
                        </td>
                    <tr>
                    <tr>
                        <th>
                            Password (if any):
                        </th>
                        <td>
                            <input type="password" name="token_password" id="token_password" />
                        </td>
                    <tr>
                    </tr>
                        <th>
                        </th>
                        <td>
                            <input type="submit" value="Import" onClick="Toggle('import_tokens', 'none'); setTimeout(function(){UpdateTokensList()},2500);" />
                        </td>
                    </tr>
                </table>
            </form>
            <hr />
            </div>
            <div class="section_title" id="list_tokens_title"><a href="#" onclick="Toggle('list_tokens');"><span id="list_tokens_toggle">[+]</span> <span id="list_tokens_text"><span id="tokenstitle">List of hardware tokens</span></span></a> <span class="info" id="tokenscounter"></span></div>
            <div id="list_tokens_section" style="display:none;">
                <br />
                <div id="tokenslist"></div>
            <hr />
            </div>
            <div class="section_title" id="add_user_title"><a href="#" onclick="Toggle('add_user');"><span id="add_user_toggle">[-]</span> <span id="add_user_text">Add a new user</span></a></div>
            <div id="add_user_section" style="display:block;">
            <form>
                <table>
                    <tr>
                        <th>
                            Username:
                        </th>
                        <td>
                            <input type=text" name="newuser" id="newuser" length="20" value="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Email address:
                        </th>
                        <td>
                            <input type=text" name="newemail" id="newemail" length="30" value="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Mobile phone (SMS):
                        </th>
                        <td>
                            <input type=text" name="newsms" id="newsms" length="30" value="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                            With prefix PIN:
                        </th>
                        <td>
                            <input type="radio" name="prefix_required" id="prefix_required1" $prefix_required1_checked value="1">yes</input>
                            <input type="radio" name="prefix_required" id="prefix_required0" $prefix_required0_checked value="0">no</input>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Specific prefix PIN:
                        </th>
                        <td>
                            <input type=text" name="pin" id="pin" length="30" value="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Select a token:
                        </th>
                        <td>
                            <select name="token_serial" id="token_serial" onchange="if ('' != this.value) { document.getElementById('token_type').style.display = 'none' } else { document.getElementById('token_type').style.display = 'table-row' };">
                                <option value="" selected="selected">software</option>
                            </select>
                        </td>
                    </tr>
                    <tr id="token_type" style="display:table-row;">
                        <th>
                            Token type:
                        </th>
                        <td>
                            <select name="algorithm" id="algorithm">
                                <option value="TOTP" selected="selected">TOTP</option>
                                <option value="HOTP">HOTP</option>
                                <option value="MOTP">MOTP</option>
                                <option value="without2FA">without2FA</option>
                            </select>
                        </td>
                    </tr>
                    <tr>
                        <th>
                        </th>
                        <td>
                            <button type="button" onclick="Add();">Add this user</button>
                        </td>
                    </tr>
                </table>
            </form>
            <hr />
            </div>
            <div class="section_title" id="resync_title"><a href="#" onclick="Toggle('resync');"><span id="resync_toggle">[+]</span> <span id="resync_text">Resync a user</span></a></div>
            <div id="resync_section" style="display:none;">
            <form>
                <table>
                    <tr>
                        <th>
                            User to resync:
                        </th>
                        <td>
                            <input type="text" name="resync_user" id="resync_user" length="20" value="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                             First OTP:
                        </th>
                        <td>
                            <input type="text" name="resync_otp1" id="resync_otp1" length="20" value="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                             Second OTP:
                        </th>
                        <td>
                            <input type="text" name="resync_otp2" id="resync_otp2" length="20" value="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                        </th>
                        <td>
                            <button type="button" onclick="ResyncUserNow(document.getElementById('resync_user').value, document.getElementById('resync_otp1').value, document.getElementById('resync_otp2').value);">Resync now</button>
                        </td>
                    </tr>
                </table>
            </form>
            </div>
            <div class="section_title" id="check_user_title"><a href="#" onclick="Toggle('check_user');"><span id="check_user_toggle">[+]</span> <span id="check_user_text">Check a user</span></a></div>
            <div id="check_user_section" style="display:none;">
            <form>
                <table>
                    <tr>
                        <th>
                            User to check:
                        </th>
                        <td>
                            <input type="text" name="check_user_user" id="check_user_user" length="50" value="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                             OTP (with prefix if needed):
                        </th>
                        <td>
                            <input type="text" name="check_user_otp" id="check_user_otp" length="50" value="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                        </th>
                        <td>
                            <button type="button" onclick="CheckUserNow(document.getElementById('check_user_user').value, document.getElementById('check_user_otp').value);">Check now</button>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Test result: 
                        </th>
                        <td>
                            <span id="check_user_result"></span>
                        </td>
                    </tr>
                </table>
            </form>
            </div>
            <div class="section_title" id="backup_config_title"><a href="#" onclick="Toggle('backup_config');"><span id="backup_config_toggle">[+]</span> <span id="backup_config_text">Backup the configuration</span></a></div>
            <div id="backup_config_section" style="display:none;">
            <form>
                <table>
                    <tr>
                        <th>
                            Backup password (cannot be empty):
                        </th>
                        <td>
                            <input type="text" name="backup_config_password" id="backup_config_password" length="50" value="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                        </th>
                        <td>
                            <button type="button" onclick="BackupConfigNow(document.getElementById('backup_config_password').value);">Backup configuration now</button>
                        </td>
                    </tr>
                </table>
            </form>
            </div>
            <div class="section_title" id="restore_config_title"><a href="#" onclick="Toggle('restore_config');"><span id="restore_config_toggle">[+]</span> <span id="restore_config_text">Restore the configuration</span></a></div>
            <div id="restore_config_section" style="display:none;">
            <form target="_self" enctype="multipart/form-data" method="post" action="">
                <table>
                    <tr>
                        <th>
                            Configuration file (*.cfg):
                        </th>
                        <td>
                            <input type="file" name="config_file" id="config_file" />
                        </td>
                    <tr>
                    <tr>
                        <th>
                            Restore password (cannot be empty):
                        </th>
                        <td>
                            <input type="password" name="restore_config_password" id="restore_config_password" />
                        </td>
                    <tr>
                    </tr>
                        <th>
                        </th>
                        <td>
                            <input type="submit" value="Restore configuration now" />
                        </td>
                    </tr>
                </table>
            </form>
            <iframe name="upload_frame" id="upload_frame" style="display:none;"></iframe>
            <hr />
            </div>
            <hr />
            <div class="section_title" id="list_users_title"><span id="list_users_toggle"></span><span id="list_users_text"><span id="userstitle">List of users</span></span> <span class="info" id="userscounter"></span></div>
            <div id="list_users_section" style="display:block;">
                <br />
                <div id="userslist"></div>
            </div>
        </div>
    </body>
</html>
EOWEBPAGE;
        $infoweb = "";
        $infoweb_filename = "infoweb.html";
        if (file_exists($multiotp->GetConfigFolder().$infoweb_filename)) {
            if ($infoweb_handler = @fopen($multiotp->GetConfigFolder().$infoweb_filename, "rt")) {
                $infoweb = nullable_trim(fgets($infoweb_handler));
                fclose($infoweb_handler);
            }
        }
        if (nullable_trim($infoweb == "")) {
            $infoweb = <<<EOI
<i>
    Are you interested in additional features like automatic syncronization of AD/LDAP users, provisioning PDF automatic email distribution, API automation, HA in master-slave mode and many others, everything through an easy and fast web interface ?
    <br />
    Check out our commercial editions here: <a target="_blank" href="https://www.multiOTP.com/">https://www.multiOTP.com/</a>
</i>
EOI;
        }
        $webpage = str_replace('\-_infoweb_-/', $infoweb, $webpage);
        echo $webpage;
    } else {
        /*********************
         * Basic Ajax server *
         *********************/
        $method = preg_replace('/[^(x20-\x7F)]*/','', $method);

        // This filtering is not working for full UTF-8 support
        // $options = preg_replace('/[^(\x09\x20-\x7E\xA0-\xFF)]*/','', $options);


        $options = preg_replace('/[^(\x09\x20-\xFF)]*/','', $options);
        $options_array = explode("\t",$options);
    
        // Set the default password to 1234 if no password is set
        if ('' == $multiotp->GetConfigAttribute('admin_password_hash'))
        {
            $multiotp->SetAdminPassword('1234');
            $multiotp->WriteConfigData();
        }
    
        if (!isset($_SESSION['logged']))
        {
            $_SESSION['logged'] = FALSE;
        }

        if (!isset($_SESSION['random_salt']))
        {
            $random_salt = mb_substr(md5(time()."@".rand(100000,999999)),0,12);
            $_SESSION['random_salt'] = $random_salt;
        }
        $multiotp->SetRandomSalt($_SESSION['random_salt']);
        
        $ajax_result = "false";

        switch (mb_strtoupper($method,'UTF-8'))
        {
            case mb_strtoupper("GetRandomSalt",'UTF-8'):
                $ajax_result = $multiotp->GetRandomSalt();
                break;
            case mb_strtoupper("Login",'UTF-8'):
                $result = FALSE;
                $username = mb_substr(isset($options_array[0])?$options_array[0]:'',0,255);
                $password = mb_substr(isset($options_array[1])?$options_array[1]:'',0,255);
                if ('admin' == $username) {
                    $result = $multiotp->CheckAdminPasswordHashWithRandomSalt($password);
                }

                if ($result) {
                    $_SESSION['logged'] = TRUE;
                    /* And we change the random_salt to avoid a second login with the same previous hash */
                    $random_salt = mb_substr(md5(time()."@".rand(100000,999999)),0,12);
                    $_SESSION['random_salt'] = $random_salt;
                    $multiotp->SetRandomSalt($random_salt);
                    $ajax_result = "true";
                } else {
                    $_SESSION = array();
                    $_SESSION['logged'] = FALSE;
                    $ajax_result = "false";
                }
                break;
            default:
                if ((isset($_SESSION['logged']) && $_SESSION['logged']))
                {
                    /*******************************************************
                     * The next methods are allowed only if we are logged in
                     *******************************************************/
                    switch (mb_strtoupper($method,'UTF-8'))
                    {
                        case mb_strtoupper("DeleteUser",'UTF-8'):
                            $ajax_result = $multiotp->DeleteUser($options_array[0]);
                            break;
                        case mb_strtoupper("DeleteToken",'UTF-8'):
                            $ajax_result = $multiotp->DeleteToken($options_array[0]);
                            break;
                        case mb_strtoupper("FastCreateUser",'UTF-8'):
                            $user              = nullable_trim((isset($options_array[0])?$options_array[0]:''));
                            $email             = nullable_trim((isset($options_array[1])?$options_array[1]:''));
                            $sms               = nullable_trim((isset($options_array[2])?$options_array[2]:''));
                            $prefix_pin_needed = intval(isset($options_array[3])?$options_array[3]:$multiotp->GetDefaultRequestPrefixPin());
                            $algorithm         = (isset($options_array[4])?$options_array[4]:"totp");
                            $pin               = (isset($options_array[5])?$options_array[5]:'');
                            $token_serial      = nullable_trim((isset($options_array[6])?$options_array[6]:''));
                            if ('' != $token_serial) {
                                $ajax_result = $multiotp->CreateUserFromToken($user, $token_serial, $email, $sms, $pin, $prefix_pin_needed);
                            }
                            else {
                                $ajax_result = $multiotp->FastCreateUser($user, $email, $sms, $prefix_pin_needed, $algorithm, 1, '', '*DEFAULT*', 0, $pin);
                            }
                            if (isset($options_array[3])) {
                                $multiotp->SetDefaultRequestPrefixPin(intval($options_array[3]));
                                $multiotp->WriteConfigData();
                            }
                            break;
                        case mb_strtoupper("GetFullVersionInfo",'UTF-8'):
                            $ajax_result = $multiotp->GetFullVersionInfo();
                            break;
                        case mb_strtoupper("GetTokensList",'UTF-8'):
                            $ajax_result = $multiotp->GetTokensList();
                            break;
                        case mb_strtoupper("GetUsersList",'UTF-8'):
                            $ajax_result = $multiotp->GetUsersList();
                            break;
                        case mb_strtoupper("GetDelayedUsersList",'UTF-8'):
                            $ajax_result = $multiotp->GetDelayedUsersList();
                            break;
                        case mb_strtoupper("GetLockedUsersList",'UTF-8'):
                            $ajax_result = $multiotp->GetLockedUsersList();
                            break;
                        case mb_strtoupper("GetEnhancedUsersList",'UTF-8'):
                            $ajax_result = $multiotp->GetEnhancedUsersList();
                            break;
                        case mb_strtoupper("PrintQrCode",'UTF-8'):
                            echo $multiotp->GenerateHtmlQrCode($options_array[0]);
                            $ajax_result = '';
                            break;
                        case mb_strtoupper("PrintScratchlist",'UTF-8'):
                            echo $multiotp->GenerateHtmlScratchlist($options_array[0]);
                            $ajax_result = '';
                            break;
                        case mb_strtoupper("ResyncUser",'UTF-8'):
                            $ajax_result = "false";
                            if ($multiotp->ReadUserData($options_array[0])) {
                                $result = $multiotp->CheckToken($options_array[1], $options_array[2]);
                                if (14 == $result) { // 14 Token has been resynchronized successfully
                                    $ajax_result = "true";
                                }
                            }
                            break;
                        case mb_strtoupper("CheckToken",'UTF-8'):
                            $ajax_result = '21 '.$multiotp->GetErrorText(21);
                            if ($multiotp->ReadUserData($options_array[0]))
                            {
                                $result = $multiotp->CheckToken($options_array[1]);
                                if (0 == $result)
                                {
                                    $ajax_result = "true";
                                }
                                else
                                {
                                    $ajax_result = $result.' '.$multiotp->GetErrorText($result);
                                }
                            }
                            break;
                        case mb_strtoupper("BackupConfig",'UTF-8'):
                            $tmp  = '/tmp';
                            if (!file_exists($tmp)) {
                              $tmp = $multiotp->ConvertToWindowsPathIfNeeded(sys_get_temp_dir());
                            }
                            $backup_file_name = "multiotp-".date('Y-m-d-His').".cfg";
                            $backup_config_file = "$tmp/$backup_file_name";
                            if (file_exists($backup_config_file)) {
                                unlink($backup_config_file);
                            }

                            $result = $multiotp->BackupConfiguration(array("backup_file"       => $backup_config_file,
                                                                           "encryption_key"    => $options_array[0],
                                                                           "flush_attributes"  => array("admin_password_hash"),
                                                                           "return_content"    => FALSE));
                            if ($result) {
                                // $size = filesize($backup_config_file);
                                header("Expires: " . gmdate("D, d M Y H:i:s") . " GMT");
                                header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
                                header("Cache-Control: no-store, no-cache, must-revalidate");
                                header("Cache-Control: post-check=0, pre-check=0", false);
                                header("Pragma: no-cache");
                                header("Content-Type: application/force-download");
                                header("Content-Type: application/octet-stream");
                                header("Content-Type: application/download\n"); 
                                header("Content-Disposition: attachment; filename=\"".basename($backup_config_file)."\""); 
                                header("Content-Transfer-Encoding: binary");
                                // header("Content-Length: $size");
                                $fn=fopen($backup_config_file, "rb");
                                while(!feof($fn)) {
                                    @set_time_limit(0);
                                    echo fread($fn, 1024*8);
                                    flush();
                                }
                                fclose($fn);
                                $ajax_result = '';
                            } else {
                                echo "*** BACKUP ERROR ***";
                            }
                            break;
                        case mb_strtoupper("SetAdminPasswordHash",'UTF-8'):
                            if ($multiotp->IsDemoMode())
                            {
                                $result = "false";
                            }
                            else
                            {
                                $ajax_result = $multiotp->SetAdminPasswordHash($options_array[0]);
                                $multiotp->WriteConfigData();
                            }
                            break;
                        case mb_strtoupper("UnlockUser",'UTF-8'):
                            $ajax_result = $multiotp->UnlockUser($options_array[0]);
                            break;
                        case mb_strtoupper("UserLoggedIn",'UTF-8'):
                            $ajax_result = "true"; //User is logged if code arrives here!
                            break;
                        default:
                            $ajax_result = "false";
                            break;
                    }
                }
                else
                {
                    $ajax_result = "false";
                }
                break;
        }
        if ('' != $ajax_result)
        {
            echo json_encode($ajax_result);
        }
        @ob_flush();
        flush(); 
    }
}

?>