<?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;
    }
}
?>