You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
345 lines
9.8 KiB
345 lines
9.8 KiB
<?php
|
|
/*********************************************************************
|
|
class.ostsession.php
|
|
|
|
Custom osTicket session handler.
|
|
|
|
Peter Rotich <peter@osticket.com>
|
|
Copyright (c) 2006-2013 osTicket
|
|
http://www.osticket.com
|
|
|
|
Released under the GNU General Public License WITHOUT ANY WARRANTY.
|
|
See LICENSE.TXT for details.
|
|
|
|
vim: expandtab sw=4 ts=4 sts=4:
|
|
**********************************************************************/
|
|
|
|
class osTicketSession {
|
|
static $backends = array(
|
|
'db' => 'DbSessionBackend',
|
|
'memcache' => 'MemcacheSessionBackend',
|
|
'system' => 'FallbackSessionBackend',
|
|
);
|
|
|
|
var $ttl = SESSION_TTL;
|
|
var $data = '';
|
|
var $data_hash = '';
|
|
var $id = '';
|
|
var $backend;
|
|
|
|
function __construct($ttl=0){
|
|
$this->ttl = $ttl ?: ini_get('session.gc_maxlifetime') ?: SESSION_TTL;
|
|
|
|
// Set osTicket specific session name.
|
|
session_name('OSTSESSID');
|
|
|
|
// Forced cleanup on shutdown
|
|
register_shutdown_function('session_write_close');
|
|
|
|
// Set session cleanup time to match TTL
|
|
ini_set('session.gc_maxlifetime', $ttl);
|
|
|
|
if (OsticketConfig::getDBVersion())
|
|
return session_start();
|
|
|
|
# Cookies
|
|
// Avoid setting a cookie domain without a dot, thanks
|
|
// http://stackoverflow.com/a/1188145
|
|
$domain = null;
|
|
if (isset($_SERVER['HTTP_HOST'])
|
|
&& strpos($_SERVER['HTTP_HOST'], '.') !== false
|
|
&& !Validator::is_ip($_SERVER['HTTP_HOST']))
|
|
// Remote port specification, as it will make an invalid domain
|
|
list($domain) = explode(':', $_SERVER['HTTP_HOST']);
|
|
|
|
session_set_cookie_params($ttl, ROOT_PATH, $domain,
|
|
osTicket::is_https(), true);
|
|
|
|
if (!defined('SESSION_BACKEND'))
|
|
define('SESSION_BACKEND', 'db');
|
|
|
|
try {
|
|
$bk = SESSION_BACKEND;
|
|
if (!class_exists(self::$backends[$bk]))
|
|
$bk = 'db';
|
|
$this->backend = new self::$backends[$bk]($this->ttl);
|
|
}
|
|
catch (Exception $x) {
|
|
// Use the database for sessions
|
|
trigger_error($x->getMessage(), E_USER_WARNING);
|
|
$this->backend = new self::$backends['db']($this->ttl);
|
|
}
|
|
|
|
if ($this->backend instanceof SessionBackend) {
|
|
// Set handlers.
|
|
session_set_save_handler(
|
|
array($this->backend, 'open'),
|
|
array($this->backend, 'close'),
|
|
array($this->backend, 'read'),
|
|
array($this->backend, 'write'),
|
|
array($this->backend, 'destroy'),
|
|
array($this->backend, 'gc')
|
|
);
|
|
}
|
|
|
|
// Start the session.
|
|
session_start();
|
|
}
|
|
|
|
function regenerate_id(){
|
|
$oldId = session_id();
|
|
session_regenerate_id();
|
|
$this->backend->destroy($oldId);
|
|
}
|
|
|
|
static function destroyCookie() {
|
|
setcookie(session_name(), 'deleted', 1,
|
|
ini_get('session.cookie_path'),
|
|
ini_get('session.cookie_domain'),
|
|
ini_get('session.cookie_secure'),
|
|
ini_get('session.cookie_httponly'));
|
|
}
|
|
|
|
static function renewCookie($baseTime=false, $window=false) {
|
|
setcookie(session_name(), session_id(),
|
|
($baseTime ?: time()) + ($window ?: SESSION_TTL),
|
|
ini_get('session.cookie_path'),
|
|
ini_get('session.cookie_domain'),
|
|
ini_get('session.cookie_secure'),
|
|
ini_get('session.cookie_httponly'));
|
|
}
|
|
|
|
/* helper functions */
|
|
|
|
function get_online_users($sec=0){
|
|
$sql='SELECT user_id FROM '.SESSION_TABLE.' WHERE user_id>0 AND session_expire>NOW()';
|
|
if($sec)
|
|
$sql.=" AND TIME_TO_SEC(TIMEDIFF(NOW(),session_updated))<$sec";
|
|
|
|
$users=array();
|
|
if(($res=db_query($sql)) && db_num_rows($res)) {
|
|
while(list($uid)=db_fetch_row($res))
|
|
$users[] = $uid;
|
|
}
|
|
|
|
return $users;
|
|
}
|
|
|
|
/* ---------- static function ---------- */
|
|
static function start($ttl=0) {
|
|
return new static($ttl);
|
|
}
|
|
}
|
|
|
|
abstract class SessionBackend {
|
|
var $isnew = false;
|
|
var $ttl;
|
|
|
|
function __construct($ttl=SESSION_TTL) {
|
|
$this->ttl = $ttl;
|
|
}
|
|
|
|
function open($save_path, $session_name) {
|
|
return true;
|
|
}
|
|
|
|
function close() {
|
|
return true;
|
|
}
|
|
|
|
function getTTL() {
|
|
return $this->ttl;
|
|
}
|
|
|
|
function write($id, $data) {
|
|
// Last chance session update
|
|
$i = new ArrayObject(array('touched' => false));
|
|
Signal::send('session.close', null, $i);
|
|
return $this->update($id, $i['touched'] ? session_encode() : $data);
|
|
}
|
|
|
|
function cleanup() {
|
|
$this->gc(0);
|
|
}
|
|
|
|
abstract function read($id);
|
|
abstract function update($id, $data);
|
|
abstract function destroy($id);
|
|
abstract function gc($maxlife);
|
|
}
|
|
|
|
class SessionData
|
|
extends VerySimpleModel {
|
|
static $meta = array(
|
|
'table' => SESSION_TABLE,
|
|
'pk' => array('session_id'),
|
|
);
|
|
}
|
|
|
|
class DbSessionBackend
|
|
extends SessionBackend {
|
|
var $data = null;
|
|
|
|
function read($id) {
|
|
try {
|
|
$this->data = SessionData::objects()
|
|
->filter(['session_id' => $id])
|
|
->annotate(array('is_expired' =>
|
|
new SqlExpr(new Q(array('session_expire__lt' => SqlFunction::NOW())))))
|
|
->one();
|
|
|
|
if ($this->data->is_expired > 0) {
|
|
// session_expire is in the past. Pretend it is expired and
|
|
// reset the data. This will assist with CSRF issues
|
|
$this->data->session_data='';
|
|
}
|
|
$this->id = $id;
|
|
}
|
|
catch (DoesNotExist $e) {
|
|
$this->data = new SessionData(['session_id' => $id, 'session_data' => '']);
|
|
}
|
|
catch (OrmException $e) {
|
|
return false;
|
|
}
|
|
return $this->data->session_data;
|
|
}
|
|
|
|
function update($id, $data){
|
|
global $thisstaff;
|
|
|
|
if (defined('DISABLE_SESSION') && $this->data->__new__)
|
|
return true;
|
|
|
|
$ttl = $this && method_exists($this, 'getTTL')
|
|
? $this->getTTL() : SESSION_TTL;
|
|
|
|
// Create a session data obj if not loaded.
|
|
if (!isset($this->data))
|
|
$this->data = new SessionData(['session_id' => $id]);
|
|
|
|
$this->data->session_data = $data;
|
|
$this->data->session_expire =
|
|
SqlFunction::NOW()->plus(SqlInterval::SECOND($ttl));
|
|
$this->data->user_id = $thisstaff ? $thisstaff->getId() : 0;
|
|
$this->data->user_ip = $_SERVER['REMOTE_ADDR'];
|
|
$this->data->user_agent = $_SERVER['HTTP_USER_AGENT'];
|
|
|
|
return $this->data->save();
|
|
}
|
|
|
|
function destroy($id){
|
|
return SessionData::objects()->filter(['session_id' => $id])->delete() ? true : false;
|
|
}
|
|
|
|
function cleanup() {
|
|
self::gc(0);
|
|
}
|
|
|
|
function gc($maxlife){
|
|
SessionData::objects()->filter([
|
|
'session_expire__lte' => SqlFunction::NOW()
|
|
])->delete();
|
|
}
|
|
}
|
|
|
|
class MemcacheSessionBackend
|
|
extends SessionBackend {
|
|
var $memcache;
|
|
var $servers = array();
|
|
|
|
function __construct($ttl) {
|
|
parent::__construct($ttl);
|
|
|
|
if (!extension_loaded('memcache'))
|
|
throw new Exception('Memcached extension is missing');
|
|
if (!defined('MEMCACHE_SERVERS'))
|
|
throw new Exception('MEMCACHE_SERVERS must be defined');
|
|
|
|
$servers = explode(',', MEMCACHE_SERVERS);
|
|
$this->memcache = new Memcache();
|
|
|
|
foreach ($servers as $S) {
|
|
@list($host, $port) = explode(':', $S);
|
|
if (strpos($host, '/') !== false)
|
|
// Use port '0' for unix sockets
|
|
$port = 0;
|
|
else
|
|
$port = $port ?: ini_get('memcache.default_port') ?: 11211;
|
|
$this->servers[] = array(trim($host), (int) trim($port));
|
|
// FIXME: Crash or warn if invalid $host or $port
|
|
}
|
|
}
|
|
|
|
function getKey($id) {
|
|
return sha1($id.SECRET_SALT);
|
|
}
|
|
|
|
function read($id) {
|
|
$key = $this->getKey($id);
|
|
|
|
// Try distributed read first
|
|
foreach ($this->servers as $S) {
|
|
list($host, $port) = $S;
|
|
$this->memcache->addServer($host, $port);
|
|
}
|
|
$data = $this->memcache->get($key);
|
|
|
|
// Read from other servers on failure
|
|
if ($data === false && count($this->servers) > 1) {
|
|
foreach ($this->servers as $S) {
|
|
list($host, $port) = $S;
|
|
$this->memcache->pconnect($host, $port);
|
|
if ($data = $this->memcache->get($key))
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
// No session data on record -- new session
|
|
$this->isnew = $data === false;
|
|
|
|
return $data ?: '';
|
|
}
|
|
|
|
function update($id, $data) {
|
|
if (defined('DISABLE_SESSION') && $this->isnew)
|
|
return true;
|
|
|
|
$key = $this->getKey($id);
|
|
foreach ($this->servers as $S) {
|
|
list($host, $port) = $S;
|
|
$this->memcache->pconnect($host, $port);
|
|
if (!$this->memcache->replace($key, $data, 0, $this->getTTL()));
|
|
$this->memcache->set($key, $data, 0, $this->getTTL());
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
function destroy($id) {
|
|
$key = $this->getKey($id);
|
|
foreach ($this->servers as $S) {
|
|
list($host, $port) = $S;
|
|
$this->memcache->pconnect($host, $port);
|
|
$this->memcache->replace($key, '', 0, 1);
|
|
$this->memcache->delete($key, 0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function gc($maxlife) {
|
|
// Memcache does this automatically
|
|
}
|
|
}
|
|
|
|
class FallbackSessionBackend {
|
|
// Use default PHP settings, with some edits for best experience
|
|
function __construct() {
|
|
// FIXME: Consider extra possible security tweaks such as adjusting
|
|
// the session.save_path
|
|
}
|
|
}
|
|
|
|
?>
|