diff --git a/include/class.ostsession.php b/include/class.ostsession.php index b300f16ec7a826443e3e1fb52530a7c88bd6486b..0b372f291594046e4d3082b4b6723c1a18c1ae04 100644 --- a/include/class.ostsession.php +++ b/include/class.ostsession.php @@ -15,11 +15,17 @@ **********************************************************************/ 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 osTicketSession($ttl=0){ $this->ttl = $ttl ?: ini_get('session.gc_maxlifetime') ?: SESSION_TTL; @@ -49,24 +55,40 @@ class osTicketSession { session_set_cookie_params($ttl, ROOT_PATH, $domain, osTicket::is_https()); - //Set handlers. - session_set_save_handler( - array(&$this, 'open'), - array(&$this, 'close'), - array(&$this, 'read'), - array(&$this, 'write'), - array(&$this, 'destroy'), - array(&$this, 'gc') - ); - - //Start the session. + 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 + $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->destroy($oldId); + $this->backend->destroy($oldId); } static function destroyCookie() { @@ -86,14 +108,57 @@ class osTicketSession { ini_get('session.cookie_httponly')); } - function open($save_path, $session_name){ - return (true); + /* 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) { + $this->ttl = $ttl; } - function close(){ - return (true); + function open($save_path, $session_name) { + return true; } + function close() { + return true; + } + + function getTTL() { + return $this->ttl; + } + + abstract function read($id); + abstract function write($id, $data); + abstract function destroy($id); + abstract function gc($maxlife); +} + +class DbSessionBackend +extends SessionBackend { + function read($id){ $this->isnew = false; if (!$this->data || $this->id != $id) { @@ -146,30 +211,100 @@ class osTicketSession { $sql='DELETE FROM '.SESSION_TABLE.' WHERE session_expire<NOW()'; db_query($sql); } +} - /* helper functions */ +class MemcacheSessionBackend +extends SessionBackend { + var $memcache; + var $servers = array(); - function getTTL(){ - return $this->ttl; + 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 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"; + function getKey($id) { + return sha1($id.SECRET_SALT); + } - $users=array(); - if(($res=db_query($sql)) && db_num_rows($res)) { - while(list($uid)=db_fetch_row($res)) - $users[] = $uid; + 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; + } } - return $users; + // No session data on record -- new session + $this->isnew = $data === false; + + return $data; } - /* ---------- static function ---------- */ - function start($ttl=0) { - return New osTicketSession($ttl); + function write($id, $data) { + if (defined('DISABLE_SESSION') && $this->isnew) + return; + + $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()); + } + } + + 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); + } + } + + 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 + } +} + ?> diff --git a/include/ost-sampleconfig.php b/include/ost-sampleconfig.php index a4624d1e706ce75f36c2e23a6ac1c0ec3b916eb3..0b26400698271c1d89ab46f55a209d501b5b1dae 100644 --- a/include/ost-sampleconfig.php +++ b/include/ost-sampleconfig.php @@ -106,4 +106,24 @@ define('TABLE_PREFIX','%CONFIG-PREFIX'); # ROOT_PATH *must* end with a forward-slash! # define('ROOT_PATH', '/support/'); + +# +# Session Storage Options +# --------------------------------------------------- +# Option: SESSION_BACKEND (default: db) +# +# osTicket supports Memcache as a session storage backend if the `memcache` +# pecl extesion is installed. This also requires MEMCACHE_SERVERS to be +# configured as well. +# +# MEMCACHE_SERVERS can be defined as a comma-separated list of host:port +# specifications. If more than one server is listed, the session is written +# to all of the servers for redundancy. +# +# Values: 'db' (default) +# 'memcache' (Use Memcache servers) +# 'system' (use PHP settings as configured (not recommended!)) +# +# define('SESSION_BACKEND', 'memcache'); +# define('MEMCACHE_SERVERS', 'server1:11211,server2:11211'); ?>