e107_handlers/session_handler.php
<?php
/*
* e107 website system
*
* Copyright (C) 2008-2012 e107 Inc (e107.org)
* Released under the terms and conditions of the
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
*
* Session handler
*
* $URL$
* $Id$
*/
if (!defined('e107_INIT'))
{
exit;
}
/**
* @package e107
* @subpackage e107_handlers
* @version $Id$
* @author SecretR
*
* Dependencies:
* - direct: language handler
* - indirect: system preferences (required by language handler)
*
* What could break it?
* If session is started before the first system session call (see class2.php
* 'Start: Set User Language' phase), session config will not be applied!
* This could happen if included $CLASS2_INCLUDE script (see class2.php)
* calls session_start(). However, sessions will not be broken, just not secured
* as per e_SECURITY_LEVEL setting.
*
* Security levels:
* - SECURITY_LEVEL_NONE [0]: security disabled - no token checks, all session validation settings dsiabled
* - SECURITY_LEVEL_BALANCED [5]: ValidateRemoteAddr, ValidateHttpXForwardedFor are on,
* session token is created/checked, but not regenerated on every page load
* - SECURITY_LEVEL_HIGH [7]: Same as above but ValidateHttpVia, ValidateHttpUserAgent are on.
* - SECURITY_LEVEL_PARANOID [9]: Same as SECURITY_LEVEL_HIGH except session token is regenerated on
* every page load. 'httponly' is on, which means JS is unable to retrieve session cookie, this may cause
* troubles with some browsers.
* - SECURITY_LEVEL_INSANE [10]: Same as SECURITY_LEVEL_HIGH plus session id is regenerated at the end
* of every page request.
*
* Session objects are created by namespace:
* $_SESSION['e107'] is default namesapce auto created with
* <code><?php e107::getSession();</code>
* Session handler is validating corresponding session COOKIE
* (named as current session name, keeping the session id)
* on regular basis (session lifetime/4). If validation
* fails, corresponding cookie is destroyed (not the session itself).
*
* Initial system Session is started after language detection (see class2.php) to
* ensure proper session handling for sites using language sub-domains (e.g. fr.site.com)
*
* Some important system session data will be kept outside of the object for now (e.g. user validation data)
*
*/
class e_session
{
/**
* No protection, label 'Looking for trouble'
* @var integer
*/
const SECURITY_LEVEL_NONE = 0;
const SECURITY_LEVEL_LOW = 3;
/**
* Default system protection, balanced for best user experience,
* label 'Safe mode - Balanced'
* @var integer
*/
const SECURITY_LEVEL_BALANCED = 5;
/**
* Adds more system security, but there is a chance (minimal) to break stuff,
* label 'High Security'
* @var integer
*/
const SECURITY_LEVEL_HIGH = 7;
/**
* High system protection, session id is regenerated on every page request,
* label 'Paranoid'
* @var integer
*/
const SECURITY_LEVEL_PARANOID = 9;
/**
* Highest system protection, session id and token values are regenerated on every page request,
* label 'Insane'
* @var int unknown_type
*/
const SECURITY_LEVEL_INSANE = 10;
/**
* Session save path
* @var string
*/
protected $_sessionSavePath = false;
/**
* Session save method
* @var string files|db
*/
protected $_sessionSaveMethod = 'files';//'files';
/**
* Session cache limiter, ignored if empty
* php.net/manual/en/function.session-cache-limiter.php
* @var string public|private_no_expire|private|nocache
*/
protected $_sessionCacheLimiter = '';
protected $_namespace;
protected $_name;
protected static $_sessionStarted = false; // Fixes lost $_SESSION value problem.
/**
* Validation options
* @var boolean
*/
protected $_sessionValidateRemoteAddr = true;
protected $_sessionValidateHttpVia = true;
protected $_sessionValidateHttpXForwardedFor = true;
protected $_sessionValidateHttpUserAgent = true;
/**
* Skip validation
* @var array
*/
protected $_sessionValidateRemoteAddrSkip = array();
protected $_sessionValidateHttpViaSkip = array();
protected $_sessionValidateHttpXForwardedForSkip = array();
protected $_sessionValidateHttpUserAgentSkip = array();
/**
* Default session options
* @var array
*/
protected $_options = array(
'lifetime' => 3600 , // 1 hour
'path' => '',
'domain' => '',
'secure' => false,
'httponly' => true,
);
/**
* Session data
* @var array
*/
protected $_data = array();
/**
* Set session options
* @param string $key
* @param mixed $value
* @return e_session
*/
public function setOption($key, $value)
{
$this->setOptions(array($key => $value));
return $this;
}
/**
* @return array
*/
public function getOptions()
{
return $this->_options;
}
/**
* Get session option
* @param string $key
* @param mixed $default
* @return mixed value
*/
public function getOption($key, $default = null)
{
return (isset($this->_options[$key]) ? $this->_options[$key] : $default);
}
/**
* Set default settings/options based on the current security level
* NOTE: new prefs 'session_save_path', 'session_save_method', 'session_lifetime' introduced,
* still not added to preference administration
* @return e_session
*/
public function setDefaultSystemConfig()
{
if ($this->getSessionId()) return $this;
$config = array(
'ValidateRemoteAddr' => (e_SECURITY_LEVEL >= self::SECURITY_LEVEL_BALANCED),
'ValidateHttpVia' => (e_SECURITY_LEVEL >= self::SECURITY_LEVEL_HIGH),
'ValidateHttpXForwardedFor' => (e_SECURITY_LEVEL >= self::SECURITY_LEVEL_LOW),
'ValidateHttpUserAgent' => (e_SECURITY_LEVEL >= self::SECURITY_LEVEL_HIGH),
);
$options = array(
// 'httponly' => (e_SECURITY_LEVEL >= self::SECURITY_LEVEL_PARANOID),
'httponly' => true,
);
if (!defined('E107_INSTALL'))
{
$systemSaveMethod = ini_get('session.save_handler');
$saveMethod = (!empty($systemSaveMethod)) ? $systemSaveMethod : 'files';
$config['SavePath'] = e107::getPref('session_save_path', false); // FIXME - new pref
$config['SaveMethod'] = e107::getPref('session_save_method', $saveMethod);
$options['lifetime'] = (int) e107::getPref('session_lifetime', 86400);
$options['path'] = e107::getPref('session_cookie_path', ''); // FIXME - new pref
$options['secure'] = e107::getPref('ssl_enabled', false); //
e107::getDebug()->log("Session Save Method: ".$config['SaveMethod']);
if (!empty($options['secure']))
{
ini_set('session.cookie_secure', 1);
}
ini_set('session.gc_maxlifetime', $options['lifetime']);
}
if (defined('SESSION_SAVE_PATH')) // safer than a pref.
{
$config['SavePath'] = e_BASE . SESSION_SAVE_PATH;
}
$hashes = hash_algos();
// if ((e_SECURITY_LEVEL >= self::SECURITY_LEVEL_BALANCED) && in_array('sha512', $hashes))
{
// ini_set('session.hash_function', 'sha512'); Removed in PHP 7.1
// ini_set('session.hash_bits_per_character', 5); Removed in PHP 7.1
}
$this->fixSessionFileGarbageCollection();
$this->setConfig($config)
->setOptions($options);
return $this;
}
/**
* Modify PHP ini at runtime to enable session file garbage collection
*
* Takes no action if the garbage collector is already enabled.
*
* @see https://github.com/e107inc/e107/issues/4113
* @return void
*/
private function fixSessionFileGarbageCollection()
{
$gc_probability = ini_get('session.gc_probability');
if ($gc_probability > 0) return;
ini_set('session.gc_probability', 1);
ini_set('session.gc_divisor', 100);
}
/**
* Retrieve value from current session namespace
* Equals to $_SESSION[NAMESPACE][$key]
* @param string $key
* @param boolean $clear unset key
* @return mixed
*/
public function get($key, $clear = false)
{
$ret = isset($this->_data[$key]) ? $this->_data[$key] : null;
if($clear) $this->clear($key);
return $ret;
}
/**
* Retrieve value from current session namespace
* If key is null, returns all current session namespace data
*
* @param string|null $key
* @param boolean $clear
* @return mixed
*/
public function getData($key = null, $clear = false)
{
if(null === $key)
{
$ret = $this->_data;
if($clear) $this->clearData();
return $ret;
}
return $this->get($key, $clear);
}
/**
* Set value in current session namespace
* Equals to $_SESSION[NAMESPACE][$key] = $value
* @param string $key Also accepts multi-dimensinal format. key1/key2
* @param mixed $value
* @return e_session
*/
public function set($key, $value)
{
if(strpos($key,'/') !== false) // multi-dimensional
{
$keyArr = explode('/',$key);
$count = count($keyArr);
if($count === 2)
{
list($k1, $k2) = $keyArr;
$this->_data[$k1][$k2] = $value;
}
elseif($count === 3)
{
list($k1, $k2, $k3) = $keyArr;
$this->_data[$k1][$k2][$k3] = $value;
}
}
else
{
$this->_data[$key] = $value;
}
return $this;
}
/**
* Set value in current session namespace
* If $key is array, the whole namespace array will be replaced with it,
* $value will be ignored
* @param string|null $key
* @param mixed $value
* @return e_session
*/
public function setData($key, $value = null)
{
if(is_array($key))
{
$this->_data = $key;
return $this;
}
return $this->set($key, $value);
}
/**
* Check if given key is set in current session namespace
* Equals to isset($_SESSION[NAMESPACE][$key])
* @param string $key
* @return boolean
*/
public function is($key)
{
return isset($this->_data[$key]);
}
/**
* Check if given key is set and not empty in current session namespace
* Equals to !empty($_SESSION[NAMESPACE][$key]) check
* @param string $key
* @return boolean
*/
public function has($key)
{
return (isset($this->_data[$key]) && $this->_data[$key]);
}
/**
* Checks if current session namespace contains any data
* Equals to !empty($_SESSION[NAMESPACE]) check
* @return boolean
*/
public function hasData()
{
return !empty($this->_data);
}
/**
* Unset member of current session namespace array
* Equals to unset($_SESSION[NAMESPACE][$key])
* @param string $key
* @return e_session
*/
public function clear($key=null)
{
if($key == null) // clear all under this namespace.
{
$this->_data = array(); // must be set to array() not unset.
return $this;
}
if(strpos($key,'/') !== false) // multi-dimensional
{
$keyArr = explode('/',$key);
$count = count($keyArr);
if($count === 2)
{
list($k1, $k2) = $keyArr;
unset($this->_data[$k1][$k2]);
}
elseif($count === 3)
{
list($k1, $k2, $k3) = $keyArr;
unset($this->_data[$k1][$k2][$k3]);
}
}
else
{
unset($this->_data[$key]);
}
return $this;
}
/**
* Reset current session namespace to empty array
* @return e_session
*/
public function clearData()
{
$this->_data = array();
return $this;
}
/**
* Set protected class vars, prefixed with _session
* @param array $config
* @return e_session
*/
public function setConfig($config)
{
foreach ($config as $k => $v)
{
$key = '_session'.$k;
if (isset($this->$key)) $this->$key = $v;
}
return $this;
}
/**
* Get registered namespace key
* @return string
*/
public function getNamespaceKey()
{
return $this->_namespace;
}
/**
* Reset session options
* @param array $options
* @return e_session
*/
public function setOptions($options)
{
if (empty($options) || !is_array($options)) return $this;
foreach ($options as $k => $v)
{
switch ($k)
{
case 'lifetime':
$v = intval($v);
break;
case 'path':
case 'domain':
$v = (string) $v;
break;
case 'secure':
case 'httponly':
$v = $v ? true : false;
break;
default:
$v = null;
break;
}
if($v !== null)
{
$this->_options[$k] = $v;
}
}
return $this;
}
/**
* @param $namespace
* @param $sessionName
* @return void
*/
public function init($namespace, $sessionName = null)
{
$this->start($sessionName);
if (!isset($_SESSION[$namespace]))
{
$_SESSION[$namespace] = array();
}
$this->_data =& $_SESSION[$namespace];
$this->_namespace = $namespace;
$this->validate();
$this->validateSessionCookie();
}
/**
* Conigure and start session
*
* @param string $sessionName optional session name
* @return e_session
*/
public function start($sessionName = null)
{
if (isset($_SESSION) && (self::$_sessionStarted === true))
{
return $this;
}
if (false !== $this->_sessionSavePath && is_writable($this->_sessionSavePath))
{
session_save_path($this->_sessionSavePath);
}
switch ($this->_sessionSaveMethod)
{
case 'db':
// ini_set('session.save_handler', 'user');
$session = new e_session_db;
session_set_save_handler(
[$session, 'open'],
[$session, 'close'],
[$session, 'read'],
[$session, 'write'],
[$session, 'destroy'],
[$session, 'gc']
);
$session->setSaveHandler();
break;
default:
if(!isset($_SESSION))
{
session_module_name($this->_sessionSaveMethod);
}
break;
}
if (empty($this->_options['domain']))
{
// MULTILANG_SUBDOMAIN set during initial language detection in language handler
$doma = ((deftrue('e_SUBDOMAIN') || deftrue('MULTILANG_SUBDOMAIN')) && e_DOMAIN != FALSE) ? ".".e_DOMAIN : FALSE; // from v1.x
$this->_options['domain'] = $doma;
}
if (empty($this->_options['path']))
{
if(defined('e_MULTISITE_MATCH')) // multisite support.
{
$this->_options['path'] = '/';
}
else
{
$this->_options['path'] = defined('e_HTTP') ? e_HTTP : '/';
}
}
// session name before options - problems reported on php.net
if (!empty($sessionName))
{
$this->setSessionName($sessionName);
}
// set session cookie params
session_set_cookie_params($this->_options['lifetime'],
$this->_options['path'],
$this->_options['domain'],
$this->_options['secure'],
$this->_options['httponly']);
if ($this->_sessionCacheLimiter)
{
session_cache_limiter((string) $this->_sessionCacheLimiter); //XXX Remove and have e_headers class handle it?
}
session_start();
self::$_sessionStarted = true;
return $this;
}
/**
* Set session ID
* @param string $sid
* @return e_session
*/
public function setSessionId($sid = null)
{
// comma and minus allowed since 5.0
if (!empty($sid) && preg_match('#^[0-9a-zA-Z,-]+$#', $sid))
{
session_id($sid);
}
return $this;
}
/**
* Retrieve current session id
* @return string
*/
public function getSessionId()
{
return session_id();
}
/**
* Retrieve current session save method.
* @return string
*/
public function getSaveMethod()
{
return $this->_sessionSaveMethod;
}
/**
* Set new session name
* @param string $name alphanumeric characters only
* @return false old session name or false on error
*/
public function setSessionName($name)
{
if (!empty($name) && preg_match('#^[0-9a-z_]+$#i', $name))
{
$this->_name = $name;
// return session_name($name);
}
return false;
}
/**
* Retrieve current session name
* @return string
*/
public function getSessionName()
{
return session_name();
}
/**
* Reset session cookie lifetime
* We reset session cookie on every (session_lifetime / 4) seconds
* It's done by all session handler instances, they all share
* one and the same '_cookie_session_validate' variable (global session namespace)
* @return e_session
*/
public function validateSessionCookie()
{
if (!$this->_options['lifetime'])
{
return $this;
}
if (empty($_SESSION['_cookie_session_validate']))
{
$time = time() + round($this->_options['lifetime'] / 4);
$_SESSION['_cookie_session_validate'] = $time;
}
elseif ($_SESSION['_cookie_session_validate'] < time())
{
if (!headers_sent())
{
cookie(session_name(), session_id(), time() + $this->_options['lifetime'], $this->_options['path'], $this->_options['domain'], $this->_options['secure']);
$time = time() + round($this->_options['lifetime'] / 4);
$_SESSION['_cookie_session_validate'] = $time;
}
}
return $this;
}
/**
* Delete session cookie
* @return e_session
*/
public function cookieDelete()
{
cookie(session_name(), null, null, $this->_options['path'], $this->_options['domain'], $this->_options['secure']);
return $this;
}
/**
* Validate current session
* @return e_session
*/
public function validate()
{
if (!isset($this->_data['_session_validate_data']))
{
$this->_data['_session_validate_data'] = $this->getValidateData();
}
elseif (!$this->_validate())
{
$sessionData = $this->_data['_session_validate_data'];
$validateData = $this->getValidateData();
$details = 'USER INFORMATION: '.(isset($_COOKIE[e_COOKIE]) ? $_COOKIE[e_COOKIE] : (isset($_SESSION[e_COOKIE]) ? $_SESSION[e_COOKIE] : 'n/a'))."\n";
$details .= "HOST: ".$_SERVER['HTTP_HOST']."\n";
$details .= "REQUEST_URI: ".$_SERVER['REQUEST_URI']."\n";
$details .= "SESSION OPTIONS: ".print_r($this->_options, true)."\n";
$details .= "SESSION NAMESPACE: ".$this->_namespace."\n";
$details .= "SESSION VALIDATION DATA SAVED: ".print_r($sessionData, true)."\n";
$details .= "SESSION VALIDATION DATA CURRENT: ".print_r($validateData, true)."\n";
$details .= "CURRENT NAMESPACE SESSION DATA:\n";
$this->clear('_session_validate_data'); // already logged
$details .= print_r($this->_data, true);
$this->close(false);
$details .= "SESSION GLOBAL DATA:\n";
$details .= print_r($_SESSION, true);
// delete cookie, destroy session
$this->cookieDelete()->destroy();
// TODO event trigger
// e107::getAdminLog()->add('Session validation failed!', $details, E_LOG_FATAL);
// TODO session exception, handle it proper on live site
// throw new Exception('');
// just for now
$msg = 'Session validation failed! <a href="'.strip_tags($_SERVER['REQUEST_URI']).'">Go Back</a>';
// die($msg); //FIXME not functioning as intended.
}
return $this;
}
/**
* Validate current session based on config options
*
* @return bool
*/
protected function _validate()
{
$sessionData = $this->_data['_session_validate_data'];
$validateData = $this->getValidateData();
$keyvar = '_sessionValidate';
foreach ($validateData as $vkey => $value)
{
$var = $keyvar.$vkey;
$varskip = $var.'Skip';
if ($this->$var && $sessionData[$vkey] != $value && !in_array($value, $this->$varskip))
{
return false;
}
}
return true;
}
/**
* Retrieve data for validator
* @return array
*/
public function getValidateData()
{
$data = array(
'RemoteAddr' => '',
'HttpVia' => '',
'HttpXForwardedFor' => '',
'HttpUserAgent' => ''
);
// collect ip data
if (isset($_SERVER['REMOTE_ADDR']))
{
$data['RemoteAddr'] = (string) $_SERVER['REMOTE_ADDR'];
}
if (isset($_ENV['HTTP_VIA']))
{
$data['HttpVia'] = (string) $_ENV['HTTP_VIA'];
}
if (isset($_ENV['HTTP_X_FORWARDED_FOR']))
{
$data['HttpXForwardedFor'] = (string) $_ENV['HTTP_X_FORWARDED_FOR'];
}
// collect user agent data
if (isset($_SERVER['HTTP_USER_AGENT']))
{
$data['HttpUserAgent'] = (string) $_SERVER['HTTP_USER_AGENT'];
}
return $data;
}
/**
* Retrieve (create if doesn't exist) XSF protection token
* @param boolean $in_form if true (default) - value for forms, else raw session value
* @return string
*/
public function getFormToken($in_form = true)
{
if(!$this->has('__form_token') && !defined('e_TOKEN_DISABLE')) // TODO FIXME: SEF URL of Error page causes e-token refresh.
{
$this->set('__form_token', uniqid(md5(rand()), true));
if(deftrue('e_DEBUG_SESSION')) // XXX enable to troubleshoot "Unauthorized Access!" issues.
{
$message = date('r')."\t\t".e_REQUEST_URI."\n";
file_put_contents(__DIR__.'/session.log', $message, FILE_APPEND);
}
}
return ($in_form ? md5($this->get('__form_token')) : $this->get('__form_token'));
}
/**
* Regenerate form token value
* TODO - save old token
* @return e_session
*/
protected function _regenerateFormToken()
{
$this->set('__form_token', uniqid(md5(rand()), true));
return $this;
}
/**
* Do a check against passed token
* @param string $token
* @return boolean
*/
public function checkFormToken($token)
{
$utoken = $this->getFormToken(false);
return ($token === md5($utoken));
}
/**
* Clear and Unset current namespace, unregister session singleton
* e107::getSession('namespace') if needed.
* @param boolean $unregister if true (default) - unregister Singleton, destroy namespace,
* else alias of self::clearData()
* @return void
*/
public function close($unregister = true)
{
$this->clearData();
if($unregister)
{
unset($_SESSION[$this->_namespace]);
e107::setRegistry('core/e107/session/'.$this->_namespace, null);
}
}
/**
* Save session data to disk, end session.
* Sessions can't be used after this point.
* Method should be called before every header redirect.
* @return void
*/
public function end()
{
session_write_close();
}
/**
* Destroy all session data
* @return e_session
*/
public function destroy()
{
$this->cookieDelete()->close();
//unset($_SESSION);
// cleanup
cookie(e_COOKIE, null, null); // remove user auth cookie
// unset($_SESSION['_cookie_session_validate']);
session_destroy();
return $this;
}
/**
* @return void
*/
public function replaceRegistry()
{
e107::setRegistry('core/e107/session/'.$this->_namespace, $this, true);
}
}
/**
*
*/
class e_core_session extends e_session
{
/**
* Constructor
* 3rd party code and/or other system areas are
* able to extend the base e_session class and
* add more or override the implemented functionality, has their own
* namespace, add more session security etc.
* @param array $data session config data
*/
public function __construct($data = array())
{
// default system configuration
$this->setDefaultSystemConfig();
$namespace = 'e107sess'; // Quick Fix for Fatal Error "Cannot use object of type e107 as array" on line 550
$name = (isset($data['name']) && !empty($data['name']) ? $data['name'] : deftrue('e_COOKIE', 'e107')).'SID';
if(isset($data['namespace']) && !empty($data['namespace'])) $namespace = $data['namespace'];
// create $_SESSION['e107'] namespace by default
$this->init($namespace, $name);
}
/**
* Session shutdown - called at the top of footer_default.php by default
* @return void
*/
public function shutdown()
{
if(!session_id()) // someone closed the session?
{
$this->init($this->_namespace, $this->_name); // restart
}
// give 3rd party code a way to prevent token re-generation
if(e_SECURITY_LEVEL >= e_session::SECURITY_LEVEL_PARANOID && !deftrue('e_TOKEN_FREEZE'))
{
if(e_SECURITY_LEVEL == e_session::SECURITY_LEVEL_INSANE)
{
// regenerate SID
$oldSID = session_id(); // old SID
$oldSData = $_SESSION; // old session data
session_regenerate_id(false); // true don't work on php4 - so time to move on people!
$newSID = session_id(); // new SID
// Clean
session_id($oldSID); // switch to the old session
session_destroy(); // destroy it
// set new ID, reopen the session, set saved data
session_id($newSID);
session_start();
$_SESSION = $oldSData;
}
$this->set('__form_token_regenerate', time()); // check() needs it to re-create token on the next request
}
// write session data
$this->end();
}
/**
* @param $status
* @param $type
* @return void|null
*/
private function log($status, $type=E_LOG_FATAL)
{
if(!deftrue('e_DEBUG_SESSION'))
{
return null;
}
// $details = "USER: ".USERNAME."\n";
$details = "HOST: ".$_SERVER['HTTP_HOST']."\n";
$details .= "REQUEST_URI: ".$_SERVER['REQUEST_URI']."\n";
$details .= ($_POST['e-token']) ? "e-token (POST): ".$_POST['e-token']."\n" : "";
$details .= ($_GET['e-token']) ? "e-token (GET): ".$_GET['e-token']."\n" : "";
$details .= ($_POST['e_token']) ? "AJAX e_token (POST): ".$_POST['e_token']."\n" : "";
/*
$utoken = $this->getFormToken(false);
$details .= "raw token: ".$utoken."\n";
$details .= "checkFormToken (e-token should match this): ".md5($utoken)."\n";
$details .= "md5(e-token): ".md5($_POST['e-token'])."\n";*/
/*
$regenerate = $this->get('__form_token_regenerate');
$details .= "Regenerate after: ".date('r', $regenerate)." (".$regenerate.")\n";
*/
$details .= "has __form_token: ";
$hasToken = $this->has('__form_token');
$details .= empty($hasToken) ? 'false' : 'true';
$details .= "\n";
$details .= "_SESSION:\n";
$details .= print_r($_SESSION,true);
/* if($pref['plug_installed'])
{
$details .= "\nPlugins:\n";
$details .= print_r($pref['plug_installed'],true);
}*/
$details .= $status."\n\n---------------------------------\n\n";
$log = e107::getLog();
$log->addDebug($details);
if(deftrue('e_DEBUG_SESSION'))
{
$log->toFile('Unauthorized_access','Unauthorized access Log', true);
}
$log->add($status, $details, $type);
}
/**
* Core CSF protection, see class2.php
* Could be adopted by plugins for their own (different) protection logic
* @param boolean $die
* @return boolean
*/
public function check($die = true)
{
// define('e_TOKEN_NAME', 'e107_token_'.md5($_SERVER['HTTP_HOST'].e_HTTP));
// TODO e-token required for all system forms?
// only if not disabled and not in 'cli' mod
if(e_SECURITY_LEVEL < e_session::SECURITY_LEVEL_LOW || e107::getE107('cli')) return true;
if($this->getSessionId())
{
if((isset($_POST['e-token']) && !$this->checkFormToken($_POST['e-token']))
|| (isset($_GET['e-token']) && !$this->checkFormToken($_GET['e-token']))
|| (isset($_POST['e_token']) && !$this->checkFormToken($_POST['e_token']))) // '-' is not allowed in jquery. b
{
$this->log('Unauthorized access!');
// do not redirect, prevent dead loop, save server resources
if($die == true)
{
die('Unauthorized access!');
}
return false;
}
$this->log('Session Token Okay!', defset('E_LOG_NOTICE', 1));
}
if(!defined('e_TOKEN'))
{
// FREEZE token regeneration if minimal, ajax or iframe (ajax and iframe not implemented yet) request
$_toFreeze = (e107::getE107('minimal') || e107::getE107('ajax') || e107::getE107('iframe'));
if(!defined('e_TOKEN_FREEZE') && $_toFreeze)
{
define('e_TOKEN_FREEZE', true);
}
// __form_token_regenerate set in footer, so if footer is not called, token will be never regenerated!
if(e_SECURITY_LEVEL == e_session::SECURITY_LEVEL_INSANE && !deftrue('e_TOKEN_FREEZE') && $this->has('__form_token_regenerate'))
{
$this->_regenerateFormToken()
->clear('__form_token_regenerate');
}
define('e_TOKEN', $this->getFormToken());
}
return true;
}
/**
* Manually Reset the Token.
* @see e107forum::ajaxQuickReply();
*/
public function reset()
{
$this->_regenerateFormToken()->clear('__form_token_regenerate');
}
/**
* Make sure there is unique challenge string for CHAP login
* @see class2.php
* @return e_core_session
@TODO: Remove debug code
*/
public function challenge()
{
if (!$this->is('challenge')) // TODO: Eliminate need for this
{
$this->set('challenge', sha1(time().rand().$this->getSessionId())); // New challenge for next time
}
if ($this->is('challenge'))
{
$this->set('prevprevchallenge', $this->get('prevchallenge')); // Purely for debug
$this->set('prevchallenge', $this->get('challenge')); // Need to check user login against this
}
else
{
$this->set('prevchallenge', ''); // Dummy value
$this->set('prevprevchallenge', ''); // Dummy value
}
//$this->set('challenge', sha1(time().rand().$this->getSessionId())); // Temporarily disabled
// FIXME - session id will be regenerated if e_SECURITY_LEVEL is 'paranoid|insane' - generate (might be OK as long as values retained)
//$extra_text = 'C: '.$this->get('challenge').' PC: '.$this->get('prevchallenge').' PPC: '.$this->get('prevprevchallenge');
//$logfp = fopen(e_LOG.'authlog.txt', 'a+'); fwrite($logfp, strftime('%H:%M:%S').' CHAP start: '.$extra_text."\n"); fclose($logfp);
// could go, see _validate()
$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
$ubrowser = md5('E107'.$user_agent);
if (!$this->is('ubrowser'))
{
$this->set('ubrowser', $ubrowser);
}
return $this;
}
}
/**
* Database session handler
*
* @todo PHP 8.1 support with {@see SessionHandlerInterface}
*/
class e_session_db #implements SessionHandlerInterface
{
/**
* @var e_db
*/
protected $_db = null;
/**
* Table name
* @var string
*/
protected $_table = 'session';
/**
* @var integer
*/
protected $_lifetime = null;
public function __construct()
{
$this->_db = e107::getDb('session');
}
/**
*
*/
public function __destruct()
{
session_write_close();
}
/**
* @return string
*/
public function getTable()
{
return $this->_table;
}
/**
* @param string $table
* @return e_session_db
*/
public function setTable($table)
{
$this->_table = $table;
return $this;
}
/**
* @return integer
*/
public function getLifetime()
{
if(null === $this->_lifetime)
{
$this->_lifetime = ini_get('session.gc_maxlifetime');
if(!$this->_lifetime)
{
$this->_lifetime = 3600;
}
}
return (integer) $this->_lifetime;
}
/**
* @param integer $seconds
* @return e_session_db
*/
public function setLifetime($seconds = null)
{
$this->_lifetime = $seconds;
return $this;
}
/**
* Set session save handler
* @return e_session_db
*/
public function setSaveHandler()
{
session_set_save_handler(
array($this, 'open'),
array($this, 'close'),
array($this, 'read'),
array($this, 'write'),
array($this, 'destroy'),
array($this, 'gc')
);
return $this;
}
/**
* Open session, parameters are ignored (see e_session handler)
* @param string $save_path
* @param string $sess_name
* @return boolean
*/
public function open($save_path, $sess_name)
{
return true;
}
/**
* Close session
* @return boolean
*/
public function close()
{
$this->gc($this->getLifetime());
return true;
}
/**
* Get session data
* @param string $session_id
* @return string
*/
public function read($session_id)
{
$data = false;
$check = $this->_db->select($this->getTable(), 'session_data', "session_id='".$this->_sanitize($session_id)."' AND session_expires>".time());
if($check)
{
$tmp = $this->_db->fetch();
$data = base64_decode($tmp['session_data']);
}
elseif(false !== $check)
{
$data = '';
}
return $data;
}
/**
* Write session data
* @param string $session_id
* @param string $session_data
* @return boolean
*/
public function write($session_id, $session_data)
{
$data = array(
'data' => array(
'session_expires' => time() + $this->getLifetime(),
'session_data' => base64_encode($session_data),
'session_user' => defset('USERID'),
),
'_FIELD_TYPES' => array(
'session_id' => 'str',
'session_expires' => 'int',
'session_user' => 'int',
'session_data' => 'str'
),
'_DEFAULT' => 'str'
);
if(!($session_id = $this->_sanitize($session_id)))
{
return false;
}
$check = $this->_db->select($this->getTable(), 'session_id', "`session_id`='{$session_id}'");
if($check)
{
$data['WHERE'] = "`session_id`='{$session_id}'";
if(false !== $this->_db->update($this->getTable(), $data))
{
return true;
}
}
else
{
$data['data']['session_id'] = $session_id;
if($this->_db->insert($this->getTable(), $data))
{
return true;
}
}
return false;
}
/**
* Destroy session
* @param string $session_id
* @return boolean
*/
public function destroy($session_id)
{
$session_id = $this->_sanitize($session_id);
$this->_db->delete($this->getTable(), "`session_id`='{$session_id}'");
return true;
}
/**
* Garbage collection
* @param integer $session_maxlf ignored - see write()
* @return boolean
*/
public function gc($session_maxlf)
{
$this->_db->delete($this->getTable(), '`session_expires`<'.time());
return true;
}
/**
* Allow only well formed session id string
* @param string $session_id
* @return string
*/
protected function _sanitize($session_id)
{
return preg_replace('#[^0-9a-zA-Z,-]#', '', $session_id);
}
}