File: multiotp.server.php

Recommend this page to a friend!
  Classes of André Liechti  >  multiOTP PHP class  >  multiotp.server.php  >  Download  
File: multiotp.server.php
Role: Application script
Content type: text/plain
Description: server component of the client/server feature
Class: multiOTP PHP class
Authenticate and manage OTP strong user tokens
Author: By
Last change: New release 5.4.1.7
ENH: New QRcode library used (without external files dependency)
ENH: New Raspberry images support for Raspberry Pi 1B/1B+/2B/3B/3B+
New release 5.4.1.6
FIX: If any, clean specific NTP DHCP option at every reboot
ENH: Modifications for Debian 9.x (stretch) binary images support
ENH: Raspberry Pi 3B+ support
ENH: Import of PSKC definition files with binary decoding key file
ENH: Added Swisscom LA REST, Afilnet, Clickatell2, eCall, Nexmo, NowSMS, SMSEagle and custom SMS provider support
New release 5.4.0.1
FIX: Values of SetUserCacheLevel(), GetUserCacheLevel(), SetUserCacheLifetime() and GetUserCacheLifetime() are not correctly initialized
ENH: Enigma Virtual Box updated to version 9.10 (to create the special all-in-one-file)
ENH: PHP 7.1.22 used in the one single file (only PHP < 7.2 is still compatible with Windows 7/2008)
ENH: Compatibility mode to Windows 7 automatically added for radiusd.exe during radius service installation
ENH: PHP display error flag is now set to off by default in the webservice under Windows
New release 5.3.0.3
FIX: Better without2FA algorithm support
New release 5.3.0.2
FIX: Restore configuration has been fixed in the command line edition
ENH: Cache-level and cache-lifetime can be set separately for each user
ENH: In client/server mode, only unencrypted user attributes are sent back to a successful client request
ENH: Enhanced monitoring
New release 5.3.0.0
FIX: stream_timeout is no more pushed to 20 seconds in PostHttpDataXmlRequest if we are in Credential Provider mode
FIX: RemoveTokenFromUser() method corrected. Token administrative information corrected, new software token created for the user
ENH: Multiple semicolon separated "Users DN" supported for AD/LDAP synchronization
ENH: Additional debug messages for disabled users during synchronization
ENH: Enigma Virtual Box updated to version 9.00 (to create the special all-in-one-file)
ENH: PHP 7.2.8 used in the one single file
ENH: without2FA algorithm now available (useful to do 2FA only for some accounts and not for others)
New release 5.2.0.2
ENH: Enhanced AD/LDAP support for huge Microsoft Active Directory
ENH: Base DN and Users DN are now two different parameters (Users DN optional)
New release 5.1.1.2
FIX: typo in the source code of the command line option for ldap-pwd and prefix-pin
ENH: Dockerfile available
FIX: Enigma Virtual Box updated to version 8.10 (to create the special all-in-one-file)
FIX: [Receive an OTP by SMS] link is now fixed for Windows 10
ENH: Credential Provider registry entries are now always used when calling multiOTP.exe
FIX: To avoid virus false positive alert, multiOTP.exe is NO more packaged in one single file
using Enigma, a php folder is now included in the multiOTP folder
FIX: multiOTPOptions registry entry is now useless
ENH: Credential Provider registry entries are used if available
Expired AD/LDAP password support
multiOTP Credential Provider (for Windows) improvements (user@domain.name UPN support, default domain name supported and displayed, SMS request link)
"force_no_prefix_pin" option for devices (for example if the device is a computer with multiOTP credential Provider and AD/LDAP synced password)
Better unicode handling, multibyte fonctions used when needed (mb_strtolower(), ...)
Better FreeRADIUS 3.x documentation
New radius tag prefix configuration option
New multiple groups device option
Some notice corrections (if the array element doesn't exist)
A user cannot be created with a leading backslash (fixed in FastCreateUser and CreateUserFromToken)
The proposed mOTP generator for Android/iOS is now OTP Authenticator
New QRCode provisioning format for mOTP (compatible with OTP Authenticator)
NirSoft nircmd.exe tool removed from the distribution (false virus detection)
Multiple URLs separator for client/server config is still ";", but [space] and "," are accepted
New developer mode for some specific detailed logs during development process only
New methods: SetLdapTlsReqcert, GetLdapTlsReqcert, SetLdapTlsCipherSuite, GetLdapTlsCipherSuite to change config parameters, instead of hard coded parameters (for SSL/TLS LDAP connection)
Fixed too much detailed information in the log when trying to detect a token serial number for self-registration
Fixed SSL/TLS LDAP failed connection for PHP 7.x (GnuTLS TLS1.2 restriction removed for PHP 7.x)
Fixed a typo in the ReadCacheData method for PostgreSQL support (thanks Frank for the feedback)
Fixed default folder detection for the multiotp.exe file
Important, under Linux, the config, devices, groups, tokens and users folders are now always located in /etc/multiotp/. Please be sure to make the move when you are upgrading
Cleaned some ugly PHP warnings when the backend is not initialized
Date: 1 year ago
Size: 75,045 bytes
 

Contents

Download
<?php
/**
 * @file  multiotp.server.php
 * @brief web service for the multiOTP class.
 *
 * multiOTP web service - Strong two-factor authentication PHP class
 * http://www.multiotp.net
 *
 * Visit http://forum.multiotp.net/ for additional support.
 *
 * Donation are always welcome! Please check http://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 5.3.0 or higher is supported.
 *
 * @author    Andre Liechti, SysCo systemes de communication sa, <info@multiotp.net>
 * @version   5.4.1.7
 * @date      2019-01-30
 * @since     2013-08-06
 * @copyright (c) 2013-2019 SysCo systemes de communication sa
 * @copyright GNU Lesser General Public License
 *
 *//*
 *
 * LICENCE
 *
 *   Copyright (c) 2010-2019 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
 *
 *   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->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->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();

$data = isset($_POST['data'])?$_POST['data']:'';
$method = 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");

if (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 = 'http://www.multiotp.net/wsdl/multiotp/';
    $soap_endpoint_url = false;
    $soap_schema_target_namespace = 'http://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 {
    session_start();
    $multiotp->SetHashSalt('AjaxH@shS@lt'); // Shared secret
    $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'])?trim($_POST['restore_config_password']):'')))) {

                  // Clean Devices
                  foreach (explode("\t", $multiotp->GetDevicesList()) as $one_device) {
                    if ('' != 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 ('' != 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 ('' != 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'])?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'])?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        ":'');

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

        $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;
            }
            /* 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 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 counter = 0;

                var remotecall = eval(RemoteCall('GetUsersList'));
                var userslist = 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 ('' != userslist)
                {
                    var usersarray = userslist.split("\t");

                    if ('false' == userslist)
                    {
                        userslist = 'not authorized';
                    }
                    else
                    {
                        var lockedlistarray = lockedlist.split("\t");
                        var delayedlistarray = delayedlist.split("\t");
                        
                        userslist = '';
                        for (var i = 0; i < usersarray.length; i++)
                        {
                            userslist = userslist + '<button type="button" onclick="DeleteUser(\''+usersarray[i]+'\');">Delete</button>';
                            userslist = userslist + '<button type="button" onclick="PrintQrCode(\''+usersarray[i]+'\');">Print</button>';
                            userslist = userslist + '<button type="button" onclick="ResyncUser(\''+usersarray[i]+'\');">Resync</button>';
                            
                            userslist = userslist + ' ' + usersarray[i];
                            
                            for (var j = 0; j < lockedlistarray.length; j++) {
                                if (usersarray[i] == lockedlistarray[j]) {
                                    userslist = userslist + ' (<a href="#" onclick="UnlockUser(\''+usersarray[i]+'\');">unlock</a>)';
                                    break;
                                }
                            }
                            
                            for (var j = 0; j < delayedlistarray.length; j++) {
                                var delayinfo = delayedlistarray[j].split('|');

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

                            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
        $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="http://www.multiOTP.net/">http://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" onfocus="this.blur();" name="user" id="user" length="20" value="admin" /> (default is admin)
            <span id="log_info"></span><span id="logged"></span>
            <br />
            Password: <input type="password" onfocus="this.value='';" name="password" id="password" length="20" value="1234" /> (default is 1234)
            &nbsp;
            <span id="login"><button type="button" onclick="Login();" >Login</button></span>
        </form>
        </div>
        <div id="logout_section">
        <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 = trim(fgets($infoweb_handler));
                fclose($infoweb_handler);
            }
        }
        if (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="http://www.multiotp.com/">http://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 = 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))
        {
            case mb_strtoupper("GetRandomSalt"):
                $ajax_result = $multiotp->GetRandomSalt();
                break;
            case mb_strtoupper("Login"):
                $result = FALSE;
                $username = substr(isset($options_array[0])?$options_array[0]:'',0,255);
                $password = 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 = 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))
                    {
                        case mb_strtoupper("DeleteUser"):
                            $ajax_result = $multiotp->DeleteUser($options_array[0]);
                            break;
                        case mb_strtoupper("DeleteToken"):
                            $ajax_result = $multiotp->DeleteToken($options_array[0]);
                            break;
                        case mb_strtoupper("FastCreateUser"):
                            $user              = trim((isset($options_array[0])?$options_array[0]:''));
                            $email             = trim((isset($options_array[1])?$options_array[1]:''));
                            $sms               = 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      = 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"):
                            $ajax_result = $multiotp->GetFullVersionInfo();
                            break;
                        case mb_strtoupper("GetTokensList"):
                            $ajax_result = $multiotp->GetTokensList();
                            break;
                        case mb_strtoupper("GetUsersList"):
                            $ajax_result = $multiotp->GetUsersList();
                            break;
                        case mb_strtoupper("GetDelayedUsersList"):
                            $ajax_result = $multiotp->GetDelayedUsersList();
                            break;
                        case mb_strtoupper("GetLockedUsersList"):
                            $ajax_result = $multiotp->GetLockedUsersList();
                            break;
                        case mb_strtoupper("PrintQrCode"):
                            echo $multiotp->GenerateHtmlQrCode($options_array[0]);
                            $ajax_result = '';
                            break;
                        case mb_strtoupper("ResyncUser"):
                            $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"):
                            $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"):
                            $tmp  = '/tmp';
                            if (!file_exists($tmp)) {
                                $tmp = $multiotp->ConvertToWindowsPathIfNeeded($multiotp->GetScriptFolder()."../_temp");
                            }
                            $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"):
                            if ($multiotp->IsDemoMode())
                            {
                                $result = "false";
                            }
                            else
                            {
                                $ajax_result = $multiotp->SetAdminPasswordHash($options_array[0]);
                                $multiotp->WriteConfigData();
                            }
                            break;
                        case mb_strtoupper("UnlockUser"):
                            $ajax_result = $multiotp->UnlockUser($options_array[0]);
                            break;
                        case mb_strtoupper("UserLoggedIn"):
                            $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(); 
    }
}

?>

For more information send a message to info at phpclasses dot org.