<?php
/******************************************
* - 1st part of a simple Cross-site request forgery (CSRF) securitization package
    https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet
* - this is required by the requirer page, the page that needs access limited
* - optional over-rides, add these vars in the requirer to over-ride defaults set below:
    $login_required = false;        // can be set in the requirer if this script called from more than one other page
    $form_needed    = false;        // defaults to TRUE
* - minimum req'd, set and add these lines in the requirer above your doctype declaration:
        $v_dir = __DIR__ .DIRECTORY_SEPARATOR. 'incl';    // relative path from requirer to the v_file's folder (without trailing "/")
        require_once($v_dir."/v_require.php");            // below the $v_dir var but early in a requirer page needing verification
* - has access to vars from requirer, v_require, and $_SESSION
******************************************/

//*********** start user vars ************
$debug        false;
$v_username    "admin";    // set this if login required (case-sensitive)
$v_password    "4magic";    // set this if login required (case-sensitive)
//*********** end user vars *************

$err_dest    0;        // 0 php log, 
$fname        basename(__FILE__);
$fmsg        "$fname [".__LINE__."] Started ...".PHP_EOL;    # to be error_logged
$form_msg    "";                                # messages from script displayed to the user in the auth form

ini_set('session.use_cookies''1');
ini_set('session.use_only_cookies''0');
// next 2 for strict xhtml 1, but moot if session id manually added to form and more independent of restrictive php.ini
//ini_set('url_rewriter.tags', "fieldset=");    # <input> not allowed outside <fieldset>
//ini_set('session.use_trans_sid', '1');            # <input> will be auto added to tags by PHP if cookie not received
session_name(strtolower(session_name()));        # for strict xhtml 1, use existing session_name in lower case

if ($debug)
{
    
ini_set('display_errors''1');
    
error_reporting(E_ALL E_STRICT);
    
//$fmsg .= PHP_EOL.$bug->getGlobalsByString('inbound');
}

if (
session_id() === "")                        # in case the requirer has already started a session
{
  
session_start();
  if (
$debug$fmsg .= "\t [".__LINE__."] No session so started one".PHP_EOL;
}

if (
$debug$fmsg .= "\t [".__LINE__."] Session name & id: ".session_name()." = ".session_id().PHP_EOL;

// INCORPORATE CAPTCHA IMAGE OVER-RIDES FROM REQUIRER, used by v_image (only accepts $_GET['phpsessid'])

if ( isset($v_img_width) )        $_SESSION['v_img_width'] = $v_img_width;
if ( isset(
$v_img_height) )        $_SESSION['v_img_height'] = $v_img_height;
if ( isset(
$v_img_color_bg) )    $_SESSION['v_img_color_bg'] = $v_img_color_bg;
if ( isset(
$v_img_color_txt) )    $_SESSION['v_img_color_txt'] = $v_img_color_txt;


// DEFAULTS FOR THIS VERIFICATION SCRIPT, OVER-RIDE THESE IN REQUIRER

// number of verification characters the user must type, over-rides a default set in v_image.php
$vCodeLen $_SESSION['vCodeLen'] = isset($vCodeLen) ? $vCodeLen 3;

// default time limit in seconds to submit the verification form
$timeLimit = isset($timeLimit) ? $timeLimit 60;

// default we use our form. Ooverride this in your requirer page to use your own form
$form_needed = isset($form_needed) ? $form_needed TRUE;

// default uses password authentication, override in requirer if not needed or using your own
$login_required = isset($login_required) ? $login_required false;


// LOGOUT REQUEST VIA URL, ?will not work with use_only_cookies?
if (isset($_GET['action']) && $_GET['action'] === "logout")
{
  unset(
$_SESSION['verified']);
  if ( isset(
$_COOKIE[session_name()]) ) setcookie(session_name(), ''time()-42000'/');
  
header("Location: ".$_SERVER["PHP_SELF"]);
  exit;
}


function 
verify ()
{
  global 
$form_msg$fmsg$vCodeLen$timeLimit$login_required$v_username$v_password$debug;

  
// FORM SHARED SECRET, validate 32 byte hexdecimal MD5 secret
  
if (!isset($_SESSION['secret']) || strlen($_POST['secret']) !== 32 || !ctype_xdigit($_POST['secret']) 
      || 
$_POST['secret'] !== $_SESSION['secret'] )
  {
    
$form_msg .= ' Form mismatched. Please try again.';
    if (
$debug)
    {
        if (!isset(
$_SESSION['secret'])) $fmsg .= "[".__LINE__."] \$_SESSION['secret'] NOT set".PHP_EOL;
        else 
$fmsg .= "[".__LINE__."] Secrets \$_SESSION (upper) & \$_POST (lower) DIFFERENT".PHP_EOL.$_SESSION['secret']." !== ".PHP_EOL.$_POST['secret'].PHP_EOL;
    }
    return 
false;
  }

  
// TIME LIMIT
  
if ( !isset($_SESSION['time_start']) || $_SERVER['REQUEST_TIME'] - $_SESSION['time_start'] > $timeLimit)
  {
    
$form_msg .= ' Form has expired. Please try again.';
    if (
$debug)
    {
        if ( !isset(
$_SESSION['time_start']) ) $fmsg .= "[".__LINE__."] \$_SESSION['time_start'] NOT set".PHP_EOL;
        else
        {    
$fmsg .= "\t [".__LINE__."] Time limit of $timeLimit seconds exceeded by ".($_SERVER['REQUEST_TIME'] - $_SESSION['time_start'] - $timeLimit)." seconds.".PHP_EOL;
            
$fmsg .= "\t     [".__LINE__."] \$_SERVER['REQUEST_TIME'] ".$_SERVER['REQUEST_TIME']." - \$_SESSION['time_start'] ".$_SESSION['time_start']." = ".($_SERVER['REQUEST_TIME'] - $_SESSION['time_start']).PHP_EOL;
        }
    }
    return 
false;
  }

  
// LOGIN HERE
  
if ($login_required)
  {
    if (!isset(
$_POST['username']) || $v_username !== $_POST['username']
     || !isset(
$_POST['password']) || $v_password !== $_POST['password'])
    {
      
$form_msg .= ' Username or password incorrect. Please try again.';
      return 
false;
    }
  }

  
// VERIFICATION CODE
  
if (!$debug)
  {
    if ( !isset(
$_POST['verify_code']) || strlen($_POST['verify_code']) !== $vCodeLen 
        
|| !ctype_alnum($_POST['verify_code']) 
        || 
md5(strtolower($_POST['verify_code'])) !== $_SESSION['v_code_hash'] )
    {
      
$form_msg .= ' Verification code mismatch. Please try again.';
      return 
false;
    }
  }
  else
  {
    if ( !isset(
$_POST['verify_code'])){
      
$form_msg .= ' Verification code not set.';
      return 
false;
    }
    if ( 
strlen($_POST['verify_code']) !== $vCodeLen ){
      
$form_msg .= " strlen not equal $vCodeLen.";
      return 
false;
    }
    if ( !
ctype_alnum($_POST['verify_code']) ){
      
$form_msg .= ' ctype not alpha-numberic.';
      return 
false;
    }
    if ( 
md5(strtolower($_POST['verify_code'])) !== $_SESSION['v_code_hash'] )
    {
      
$form_msg .= ' Verification code mismatch. Please try again.';
      
$fmsg .= "[".__LINE__."] \$_POST['verify_code'] = ".$_POST['verify_code'].PHP_EOL;
      
$fmsg .= "[".__LINE__."] ".md5(strtolower($_POST['verify_code']))." md5(strtolower(\$_POST['verify_code'] = '".$_POST['verify_code']."')) !== ".PHP_EOL;
      
$fmsg .= "[".__LINE__."] ".$_SESSION['v_code_hash']." \$_SESSION['v_code_hash']".PHP_EOL;
      return 
false;
    }
  }

  
$_SESSION['v_code_hash'] = '';
  return 
true;

}    
# end verify()


// VERIFICATION NEEDED AND POST ATTEMPTED
if ( !isset($_SESSION['verified']) && isset($_POST['secret']) )
{
    if (
verify()) $_SESSION['verified'] = TRUE;
}


if (! isset (
$_SESSION['verified']) && session_status() == PHP_SESSION_ACTIVE)        // checking
{
    
// for FireFox non-localhost image caching of v_image, we need to create a new session id each visit

    
session_regenerate_id();        // keeps existing $_SESSION array

    // Generate the form secret each page request until verified, used by user's form or the default
    
$_SESSION['secret'] = md5(uniqid(rand(), true));

    if (
$debug)    error_log($fmsg$err_dest);

    if (
$form_needed)            // stop here if we need to display a form
    
{
    require_once 
"v_form.php";
    exit;
    }
}
?>