From b282870139f965cfcb9fd4fac8aea62523efafec Mon Sep 17 00:00:00 2001 From: Michael M Slusarz Date: Mon, 23 Feb 2009 02:07:14 -0700 Subject: [PATCH] Import Horde_SessionHandler:: from CVS HEAD. --- .../SessionHandler/lib/Horde/SessionHandler.php | 359 +++++++++++++++++++++ .../lib/Horde/SessionHandler/ldap.php | 139 ++++++++ .../lib/Horde/SessionHandler/memcache.php | 358 ++++++++++++++++++++ .../lib/Horde/SessionHandler/mysql.php | 305 +++++++++++++++++ .../lib/Horde/SessionHandler/none.php | 67 ++++ .../lib/Horde/SessionHandler/oci8.php | 278 ++++++++++++++++ .../lib/Horde/SessionHandler/pgsql.php | 285 ++++++++++++++++ .../lib/Horde/SessionHandler/sql.php | 298 +++++++++++++++++ framework/SessionHandler/package.xml | 154 +++++++++ .../Horde/SessionHandler/horde-active-sessions.php | 67 ++++ 10 files changed, 2310 insertions(+) create mode 100644 framework/SessionHandler/lib/Horde/SessionHandler.php create mode 100644 framework/SessionHandler/lib/Horde/SessionHandler/ldap.php create mode 100644 framework/SessionHandler/lib/Horde/SessionHandler/memcache.php create mode 100644 framework/SessionHandler/lib/Horde/SessionHandler/mysql.php create mode 100644 framework/SessionHandler/lib/Horde/SessionHandler/none.php create mode 100644 framework/SessionHandler/lib/Horde/SessionHandler/oci8.php create mode 100644 framework/SessionHandler/lib/Horde/SessionHandler/pgsql.php create mode 100644 framework/SessionHandler/lib/Horde/SessionHandler/sql.php create mode 100644 framework/SessionHandler/package.xml create mode 100755 framework/SessionHandler/scripts/Horde/SessionHandler/horde-active-sessions.php diff --git a/framework/SessionHandler/lib/Horde/SessionHandler.php b/framework/SessionHandler/lib/Horde/SessionHandler.php new file mode 100644 index 000000000..8f543b979 --- /dev/null +++ b/framework/SessionHandler/lib/Horde/SessionHandler.php @@ -0,0 +1,359 @@ + + * 'memcache' - (boolean) Use memcache to cache session data? + * + * + * Copyright 2002-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Mike Cochrane + * @author Michael Slusarz + * @package Horde_SessionHandler + */ +class Horde_SessionHandler +{ + /** + * Singleton instances. + * + * @var array + */ + static protected $_instances = array(); + + /** + * Hash containing connection parameters. + * + * @var array + */ + protected $_params = array(); + + /** + * Initial session data signature. + * + * @var string + */ + protected $_sig; + + /** + * Force saving the session data? + * + * @var boolean + */ + protected $_force = false; + + /** + * Has a connection been made to the backend? + * + * @var boolean + */ + protected $_connected = false; + + /** + * Attempts to return a concrete Horde_SessionHandler instance based on + * $driver. + * + * @param string $driver The type of concrete subclass to return. + * @param array $params A hash containing any additional configuration or + * connection parameters a subclass might need. + * + * @return Horde_SessionHandler The newly created concrete instance. + * @throws Horde_Exception + */ + static public function factory($driver, $params = array()) + { + $driver = basename($driver); + $persistent_params = array(); + + if ($driver == 'memcached') { + // Trap for old driver name. + $driver = 'memcache'; + } elseif (($driver != 'memcache') && !empty($params['memcache'])) { + unset($params['memcache']); + $persistent_params = array('persistent_driver' => $driver, 'persistent_params' => $params); + $driver = 'memcache'; + $params = array(); + } + + $class = 'Horde_SessionHandler_' . $driver; + + if (class_exists($class)) { + if (empty($params)) { + $params = Horde::getDriverConfig('sessionhandler', $driver); + } + return new $class(array_merge($params, $persistent_params)); + } + + throw new Horde_Exception('Class definition of ' . $class . ' not found.'); + } + + /** + * Attempts to return a reference to a concrete Horde_SessionHandler + * instance based on $driver. It will only create a new instance + * if no Horde_SessionHandler instance with the same parameters + * currently exists. + * + * This method must be invoked as: + * $var = &Horde_SessionHandler::singleton() + * + * @param string $driver See Horde_SessionHandler::factory(). + * @param array $params See Horde_SessionHandler::factory(). + * + * @return Horde_SessionHandler The singleton instance. + * @throws Horde_Exception + */ + static public function singleton($driver, $params = array()) + { + ksort($params); + $signature = hash('md5', serialize(array($driver, $params))); + if (empty(self::$_instances[$signature])) { + self::$_instances[$signature] = self::factory($driver, $params); + } + + return self::$_instances[$signature]; + } + + /** + * Constructor. + * + * @param array $params A hash containing connection parameters. + * + * @throws Horde_Exception + */ + protected function __construct($params = array()) + { + $this->_params = $params; + } + + /** + * Destructor. + * + * Used to determine if we need to write the session to avoid a session + * timeout, even though the session is unchanged. + * Theory: On initial login, set the current time plus half of the max + * lifetime in the session. Then check this timestamp before saving. + * If we exceed, force a write of the session and set a new timestamp. + * Why half the maxlifetime? It guarantees that if we are accessing the + * server via a periodic mechanism (think folder refreshing in IMP) that + * we will catch this refresh. + */ + public function __destruct() + { + $curr_time = time(); + + if (!isset($_SESSION['__sessionhandler']) || + ($curr_time >= $_SESSION['__sessionhandler'])) { + $_SESSION['__sessionhandler'] = $curr_time + (ini_get('session.gc_maxlifetime') / 2); + $this->_force = true; + } + } + + /** + * Open the backend. + * + * @param string $save_path The path to the session object. + * @param string $session_name The name of the session. + * + * @return boolean True on success, false otherwise. + */ + public function open($save_path, $session_name) + { + if (!$this->_connected) { + try { + $this->_open($save_path, $session_name); + } catch (Horde_Exception $e) { + Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + $this->_connected = true; + } + + return true; + } + + /** + * Open the backend. + * + * @param string $save_path The path to the session object. + * @param string $session_name The name of the session. + * + * @throws Horde_Exception + */ + protected function _open($save_path = null, $session_name = null) + { + } + + /** + * Close the backend. + * + * @return boolean True on success, false otherwise. + */ + public function close() + { + try { + $this->_close(); + } catch (Horde_Exception $e) { + Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + $this->_connected = false; + return true; + } + + /** + * Close the backend. + * + * @throws Horde_Exception + */ + protected function _close() + { + } + + /** + * Read the data for a particular session identifier from the backend. + * This method should only be called internally by PHP via + * session_set_save_handler(). + * + * @param string $id The session identifier. + * + * @return string The session data. + */ + public function read($id) + { + $result = $this->_read($id); + $this->_sig = md5($result); + return $result; + } + + /** + * Read the data for a particular session identifier from the backend. + * + * @param string $id The session identifier. + * + * @return string The session data. + */ + protected function _read($id) + { + return ''; + } + + /** + * Write session data to the backend. + * This method should only be called internally by PHP via + * session_set_save_handler(). + * + * @param string $id The session identifier. + * @param string $session_data The session data. + * + * @return boolean True on success, false otherwise. + */ + public function write($id, $session_data) + { + if (!$this->_force && ($this->_sig == md5($session_data))) { + Horde::logMessage('Session data unchanged (id = ' . $id . ')', __FILE__, __LINE__, PEAR_LOG_DEBUG); + return true; + } + + return $this->_write($id, $session_data); + } + + /** + * Write session data to the backend. + * + * @param string $id The session identifier. + * @param string $session_data The session data. + * + * @return boolean True on success, false otherwise. + */ + protected function _write($id, $session_data) + { + return false; + } + + /** + * Destroy the data for a particular session identifier in the backend. + * This method should only be called internally by PHP via + * session_set_save_handler(). + * + * @param string $id The session identifier. + * + * @return boolean True on success, false otherwise. + */ + public function destroy($id) + { + return false; + } + + /** + * Garbage collect stale sessions from the backend. + * This method should only be called internally by PHP via + * session_set_save_handler(). + * + * @param integer $maxlifetime The maximum age of a session. + * + * @return boolean True on success, false otherwise. + */ + public function gc($maxlifetime = 300) + { + return false; + } + + /** + * Get session data read-only. + * + * @param string $id The session identifier. + * + * @return string The session data. + */ + protected function _readOnly($id) + { + return $this->read($id); + } + + /** + * Get a list of the valid session identifiers. + * + * @return array A list of valid session identifiers. + * @throws Horde_Exception + */ + public function getSessionIDs() + { + throw new Horde_Exception(_("Not supported.")); + } + + /** + * Returns a list of authenticated users and data about their session. + * + * @return array For authenticated users, the sessionid as a key and the + * information returned from Auth::readSessionData() as + * values. + * @throws Horde_Exception + */ + public function getSessionsInfo() + { + $sessions = $this->getSessionIDs(); + + $info = array(); + + foreach ($sessions as $id) { + try { + $data = $this->_readOnly($id); + } catch (Horde_Exception $e) { + continue; + } + $data = Auth::readSessionData($data, true); + if ($data !== false) { + $info[$id] = $data; + } + } + + return $info; + } + +} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/ldap.php b/framework/SessionHandler/lib/Horde/SessionHandler/ldap.php new file mode 100644 index 000000000..4aed3b523 --- /dev/null +++ b/framework/SessionHandler/lib/Horde/SessionHandler/ldap.php @@ -0,0 +1,139 @@ + + * 'hostspec' - (string) The hostname of the ldap server. + * 'port' - (integer) The port number of the ldap server. + * 'dn' - (string) The bind DN. + * 'password' - (string) The bind password. + * + * + * This code is adapted from the comments at + * http://www.php.net/session-set-save-handler. + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @package Horde_SessionHandler + */ +class Horde_SessionHandler_ldap extends Horde_SessionHandler +{ + /** + * Handle for the current database connection. + * + * @var resource + */ + protected $_conn; + + /** + * Open the backend. + * + * @param string $save_path The path to the session object. + * @param string $session_name The name of the session. + * + * @throws Horde_Exception + */ + protected function _open($save_path = null, $session_name = null) + { + $this->_conn = @ldap_connect($this->_params['hostspec'], $this->_params['port']); + + // Set protocol version if necessary. + if (isset($this->_params['version'])) { + if (!@ldap_set_option($this->_ds, LDAP_OPT_PROTOCOL_VERSION, $this->_params['version'])) { + throw new Horde_Exception(sprintf('Set LDAP protocol version to %d failed: [%d] %s', $this->_params['version'], ldap_errno($conn), ldap_error($conn))); + } + } + + if (!@ldap_bind($this->_conn, $this->_params['dn'], $this->_params['password'])) { + throw new Horde_Exception('Could not bind to LDAP server.'); + } + } + + /** + * Close the backend. + */ + protected function _close() + { + if (!@ldap_close($this->_conn)) { + throw new Horde_Exception('Could not unbind from LDAP server.'); + } + } + + /** + * Read the data for a particular session identifier from the backend. + * + * @param string $id The session identifier. + * + * @return string The session data. + */ + protected function _read($id) + { + $sr = @ldap_search($this->_conn, $this->_params['dn'], "(cn=$id)"); + $info = @ldap_get_entries($this->_conn, $sr); + return ($info['count'] > 0) ? $info[0]['session'][0] : ''; + } + + /** + * Write session data to the backend. + * + * @param string $id The session identifier. + * @param string $session_data The session data. + * + * @return boolean True on success, false otherwise. + */ + protected function _write($id, $session_data) + { + $update = array('objectClass' => array('phpsession', 'top'), + 'session' => $session_data); + $dn = "cn=$id," . $this->_params['dn']; + @ldap_delete($this->_conn, $dn); + return @ldap_add($this->_conn, $dn, $update); + } + + /** + * Destroy the data for a particular session identifier in the backend. + * + * @param string $id The session identifier. + * + * @return boolean True on success, false otherwise. + */ + public function destroy($id) + { + $dn = "cn=$id," . $this->_params['dn']; + return @ldap_delete($this->_conn, $dn); + } + + /** + * Garbage collect stale sessions from the backend. + * + * @param integer $maxlifetime The maximum age of a session. + * + * @return boolean True on success, false otherwise. + */ + public function gc($maxlifetime = 300) + { + $sr = @ldap_search($this->_conn, $this->_params['dn'], + '(objectClass=phpsession)', array('+', 'cn')); + $info = @ldap_get_entries($this->_conn, $sr); + if ($info['count'] > 0) { + for ($i = 0; $i < $info['count']; $i++) { + $id = $info[$i]['cn'][0]; + $dn = "cn=$id," . $this->_params['dn']; + $ldapstamp = $info[$i]['modifytimestamp'][0]; + $year = substr($ldapstamp, 0, 4); + $month = substr($ldapstamp, 4, 2); + $day = substr($ldapstamp, 6, 2); + $hour = substr($ldapstamp, 8, 2); + $minute = substr($ldapstamp, 10, 2); + $modified = gmmktime($hour, $minute, 0, $month, $day, $year); + if (time() - $modified >= $maxlifetime) { + @ldap_delete($this->_conn, $dn); + } + } + } + + return true; + } + +} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/memcache.php b/framework/SessionHandler/lib/Horde/SessionHandler/memcache.php new file mode 100644 index 000000000..7307516d4 --- /dev/null +++ b/framework/SessionHandler/lib/Horde/SessionHandler/memcache.php @@ -0,0 +1,358 @@ + + * 'persistent_driver' - (string) If set, uses this backend to store session + * data persistently. + * 'persistent_params' - (array) If using a persistent backend, the params + * to use for the persistent backend. + * 'track' - (boolean) Track active sessions? + * 'track_lifetime' - (integer) The number of seconds after which tracked + * sessions will be treated as expired. + * + * + * Copyright 2005-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Rong-En Fan + * @author Michael Slusarz + * @package Horde_SessionHandler + */ +class Horde_SessionHandler_memcache extends Horde_SessionHandler +{ + /** + * Horde_Memcache object. + * + * @var Horde_Memcache + */ + protected $_memcache; + + /** + * Current session ID. + * + * @var string + */ + protected $_id; + + /** + * Persistent backend driver. + * + * @var Horde_SessionHandler + */ + protected $_persistent; + + /** + * Do read-only get? + * + * @var boolean + */ + protected $_readonly = false; + + /** + * The ID used for session tracking. + * + * @var string + */ + protected $_trackID = 'horde_memcache_sessions_track'; + + /** + * Constructor. + * + * @param array $params A hash containing connection parameters. + * + * @throws Horde_Exception + */ + protected function __construct($params = array()) + { + if (!empty($params['persistent_driver'])) { + try { + $this->_persistent = &self::singleton($params['persistent_driver'], empty($params['persistent_params']) ? null : $params['persistent_params']); + } catch (Horde_Exception $e) { + throw new Horde_Exception('Horde is unable to correctly start the persistent session handler.'); + } + } + + parent::__construct($params); + + // If using a persistent backend, don't track sessions in memcache + if (isset($this->_persistent)) { + $this->_params['track'] = false; + } + + if (empty($this->_params['track_lifetime'])) { + $this->_params['track_lifetime'] = ini_get('session.gc_maxlifetime'); + } + } + + /** + * Destructor. + */ + public function __destruct() + { + if (!empty($this->_params['track']) && (rand(0, 999) == 0)) { + $this->_trackGC(); + } + + parent::__destruct(); + } + + /** + * Open the backend. + * + * @param string $save_path The path to the session object. + * @param string $session_name The name of the session. + * + * @throws Horde_Exception + */ + protected function _open($save_path = null, $session_name = null) + { + $this->_memcache = &Horde_Memcache::singleton(); + if (is_a($this->_memcache, 'PEAR_Error')) { + throw new Horde_Exception($this->_memcache); + } + + if (isset($this->_persistent)) { + if (!$this->_persistent->open($save_path, $session_name)) { + throw new Horde_Exception('Could not open persistent backend.'); + } + } + } + + /** + * Close the backend. + * + * @throws Horde_Exception + */ + protected function _close() + { + if (isset($this->_id)) { + $this->_memcache->unlock($this->_id); + } + if (isset($this->_persistent)) { + $this->_persistent->close(); + } + } + + /** + * Read the data for a particular session identifier. + * + * @param string $id The session identifier. + * + * @return string The session data. + */ + protected function _read($id) + { + if (!$this->_readonly) { + $this->_memcache->lock($id); + } + $result = $this->_memcache->get($id); + + if ($result === false) { + if (!$this->_readonly) { + $this->_memcache->unlock($id); + } + + if (isset($this->_persistent)) { + $result = $this->_persistent->read($id); + } + + if ($result === false) { + Horde::logMessage('Error retrieving session data (id = ' . $id . ')', __FILE__, __LINE__, PEAR_LOG_DEBUG); + return false; + } + + $this->_persistent->write($id, $session_data); + } + + if (!$this->_readonly) { + $this->_id = $id; + } + + Horde::logMessage('Read session data (id = ' . $id . ')', __FILE__, __LINE__, PEAR_LOG_DEBUG); + return $result; + } + + /** + * Write session data to the backend. + * + * @param string $id The session identifier. + * @param string $session_data The session data. + * + * @return boolean True on success, false otherwise. + */ + protected function _write($id, $session_data) + { + if (!empty($this->_params['track'])) { + // Do a replace - the only time it should fail is if we are + // writing a session for the first time. If that is the case, + // update the session tracker. + $res = $this->_memcache->replace($id, $session_data); + $track = !$res; + } else { + $res = $track = false; + } + + if (!$res && + !$this->_memcache->set($id, $session_data)) { + Horde::logMessage('Error writing session data (id = ' . $id . ')', __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + if (isset($this->_persistent)) { + $result = $this->_persistent->write($id, $session_data); + } + + if ($track) { + $this->_memcache->lock($this->_trackID); + $ids = $this->_memcache->get($this->_trackID); + if ($ids === false) { + $ids = array(); + } + + $ids[$id] = time(); + $this->_memcache->set($this->_trackID, $ids); + $this->_memcache->unlock($this->_trackID); + } + + Horde::logMessage('Wrote session data (id = ' . $id . ')', __FILE__, __LINE__, PEAR_LOG_DEBUG); + return true; + } + + /** + * Destroy the data for a particular session identifier. + * + * @param string $id The session identifier. + * + * @return boolean True on success, false otherwise. + */ + public function destroy($id) + { + $result = $this->_memcache->delete($id); + $this->_memcache->unlock($id); + + if ($result !== false && + isset($this->_persistent)) { + $result = $this->_persistent->destroy($id); + } + + if ($result !== false) { + Horde::logMessage('Failed to delete session (id = ' . $id . ')', __FILE__, __LINE__, PEAR_LOG_DEBUG); + return false; + } + + if (!empty($this->_params['track'])) { + $this->_memcache->lock($this->_trackID); + $ids = $this->_memcache->get($this->_trackID); + if ($ids !== false) { + unset($ids[$id]); + $this->_memcache->set($this->_trackID, $ids); + } + $this->_memcache->unlock($this->_trackID); + } + + Horde::logMessage('Deleted session data (id = ' . $id . ')', __FILE__, __LINE__, PEAR_LOG_DEBUG); + return true; + } + + /** + * Garbage collect stale sessions from the backend. + * + * @param integer $maxlifetime The maximum age of a session. + * + * @return boolean True on success, false otherwise. + */ + public function gc($maxlifetime = 300) + { + $result = true; + + if (isset($this->_persistent)) { + $result = $this->_persistent->gc($maxlifetime); + } + + // Memcache does its own garbage collection. + return $result; + } + + /** + * Get a list of (possibly) valid session identifiers. + * + * @return array A list of session identifiers. + * @throws Horde_Exception + */ + public function getSessionIDs() + { + if (isset($this->_persistent)) { + return $this->_persistent->getSessionIDs(); + } + + try { + $this->_open(); + + if (empty($this->_params['track'])) { + throw new Horde_Exception(_("Memcache session tracking not enabled.")); + } + } catch (Horde_Exception $e) { + if (isset($this->_persistent)) { + return $this->_persistent->getSessionIDs(); + } + throw $e; + } + + $this->_trackGC(); + + $ids = $this->_memcache->get($this->_trackID); + return ($ids === false) ? array() : array_keys($ids); + } + + /** + * Get session data read-only. + * + * @param string $id The session identifier. + * + * @return string The session data. + */ + protected function _readOnly($id) + { + $this->_readonly = true; + $result = $this->_memcache->get($id); + $this->_readonly = false; + return $result; + } + + /** + * Do garbage collection for session tracking information. + */ + protected function _trackGC() + { + $this->_memcache->lock($this->_trackID); + $ids = $this->_memcache->get($this->_trackID); + if (empty($ids)) { + $this->_memcache->unlock($this->_trackID); + return; + } + + $tstamp = time() - $this->_params['track_lifetime']; + $alter = false; + + foreach ($ids as $key => $val) { + if ($tstamp > $val) { + unset($ids[$key]); + $alter = true; + } + } + + if ($alter) { + $this->_memcache->set($this->_trackID, $ids); + } + + $this->_memcache->unlock($this->_trackID); + } + +} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/mysql.php b/framework/SessionHandler/lib/Horde/SessionHandler/mysql.php new file mode 100644 index 000000000..9a5aa0133 --- /dev/null +++ b/framework/SessionHandler/lib/Horde/SessionHandler/mysql.php @@ -0,0 +1,305 @@ + + * 'hostspec' - (string) The hostname of the database server. + * 'protocol' - (string) The communication protocol ('tcp', 'unix', etc.). + * 'username' - (string) The username with which to connect to the + * database. + * 'password' - (string) The password associated with 'username'. + * 'database' - (string) The name of the database. + * 'table' - (string) The name of the sessiondata table in 'database'. + * 'rowlocking' - (boolean) Whether to use row-level locking and + * transactions (InnoDB) or table-level locking (MyISAM). + * + * + * Required for some configurations:
+ *   'port' - (integer) The port on which to connect to the database.
+ * 
+ * + * Optional parameters:
+ *   'persistent' - (boolean) Use persistent DB connections?
+ * 
+ * + * The table structure can be found in: + * horde/scripts/sql/horde_sessionhandler.sql. + * + * Copyright 2002-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Mike Cochrame + * @author Chuck Hagenbuch + * @author Jan Schneider + * @package Horde_SessionHandler + */ +class Horde_SessionHandler_mysql extends Horde_SessionHandler +{ + /** + * Handle for the current database connection. + * + * @var resource + */ + protected $_db; + + /** + * Attempts to open a connection to the SQL server. + * + * @param string $save_path The path to the session object. + * @param string $session_name The name of the session. + * + * @throws Horde_Exception + */ + protected function _open($save_path = null, $session_name = null) + { + Horde::assertDriverConfig($this->_params, 'sessionhandler', + array('hostspec', 'username', 'database'), + 'session handler MySQL'); + + if (empty($this->_params['password'])) { + $this->_params['password'] = ''; + } + + if (empty($this->_params['table'])) { + $this->_params['table'] = 'horde_sessionhandler'; + } + + $connect = empty($this->_params['persistent']) + ? 'mysql_connect' + : 'mysql_pconnect'; + + if (!$this->_db = @$connect($this->_params['hostspec'] . (!empty($this->_params['port']) ? ':' . $this->_params['port'] : ''), + $this->_params['username'], + $this->_params['password'])) { + throw new Horde_Exception('Could not connect to database for SQL Horde_SessionHandler.'); + } + + if (!@mysql_select_db($this->_params['database'], $this->_db)) { + throw new Horde_Exception(sprintf('Could not connect to database %s for SQL Horde_SessionHandler.', $this->_params['database'])); + } + } + + /** + * Close the backend. + * + * @throws Horde_Exception + */ + protected function _close() + { + /* Disconnect from database. */ + if (!@mysql_close($this->_db)) { + throw new Horde_Exception('Could not disconnect from database.'); + } + } + + /** + * Read the data for a particular session identifier from the backend. + * + * @param string $id The session identifier. + * + * @return string The session data. + */ + protected function _read($id) + { + /* Select db */ + if (!@mysql_select_db($this->_params['database'], $this->_db)) { + return ''; + } + + $query = sprintf('SELECT session_data FROM %s WHERE session_id = %s', + $this->_params['table'], + $this->_quote($id)); + + if (!empty($this->_params['rowlocking'])) { + /* Start a transaction. */ + $result = @mysql_query('START TRANSACTION', $this->_db); + $query .= ' FOR UPDATE'; + } else { + $result = @mysql_query('LOCK TABLES ' . $this->_params['table'] . ' WRITE', $this->_db); + } + if (!$result) { + return ''; + } + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_mysql::_read(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $result = @mysql_query($query, $this->_db); + if (!$result) { + Horde::logMessage('Error retrieving session data (id = ' . $id . '): ' . mysql_error($this->_db), __FILE__, __LINE__, PEAR_LOG_ERR); + return ''; + } + + return @mysql_result($result, 0, 0); + } + + /** + * Write session data to the backend. + * + * @param string $id The session identifier. + * @param string $session_data The session data. + * + * @return boolean True on success, false otherwise. + */ + protected function _write($id, $session_data) + { + /* Select db */ + if (!@mysql_select_db($this->_params['database'], $this->_db)) { + return ''; + } + + /* Build the SQL query. */ + $query = sprintf('REPLACE INTO %s (session_id, session_data, session_lastmodified)' . + ' VALUES (%s, %s, %s)', + $this->_params['table'], + $this->_quote($id), + $this->_quote($session_data), + time()); + + $result = @mysql_query($query, $this->_db); + if (!$result) { + $error = mysql_error($this->_db); + } + if (empty($this->_params['rowlocking'])) { + @mysql_query('UNLOCK TABLES ' . $this->_params['table'], $this->_db); + } + if (!$result) { + @mysql_query('ROLLBACK', $this->_db); + Horde::logMessage('Error writing session data: ' . $error, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + @mysql_query('COMMIT', $this->_db); + + return true; + } + + /** + * Destroy the data for a particular session identifier in the backend. + * + * @param string $id The session identifier. + * + * @return boolean True on success, false otherwise. + */ + public function destroy($id) + { + /* Select db */ + if (!@mysql_select_db($this->_params['database'], $this->_db)) { + return ''; + } + + /* Build the SQL query. */ + $query = sprintf('DELETE FROM %s WHERE session_id = %s', + $this->_params['table'], $this->_quote($id)); + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_mysql::destroy(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + /* Execute the query. */ + $result = @mysql_query($query, $this->_db); + if (!$result) { + $error = mysql_error($this->_db); + } + if (empty($this->_params['rowlocking'])) { + @mysql_query('UNLOCK TABLES ' . $this->_params['table'], $this->_db); + } + if (!$result) { + @mysql_query('ROLLBACK', $this->_db); + Horde::logMessage('Failed to delete session (id = ' . $id . '): ' . $error, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + @mysql_query('COMMIT', $this->_db); + + return true; + } + + /** + * Garbage collect stale sessions from the backend. + * + * @param integer $maxlifetime The maximum age of a session. + * + * @return boolean True on success, false otherwise. + */ + public function gc($maxlifetime = 300) + { + /* Select db */ + if (!@mysql_select_db($this->_params['database'], $this->_db)) { + return ''; + } + + /* Build the SQL query. */ + $query = sprintf('DELETE FROM %s WHERE session_lastmodified < %s', + $this->_params['table'], (int)(time() - $maxlifetime)); + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_mysql::gc(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + /* Execute the query. */ + $result = @mysql_query($query, $this->_db); + if (!$result) { + Horde::logMessage('Error garbage collecting old sessions: ' . mysql_error($this->_db), __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + return @mysql_affected_rows($this->_db); + } + + /** + * Get a list of the valid session identifiers. + * + * @return array A list of valid session identifiers. + * @throws Horde_Exception + */ + public function getSessionIDs() + { + /* Make sure we have a valid database connection. */ + $this->_open(); + + $query = sprintf('SELECT session_id FROM %s' . + ' WHERE session_lastmodified >= %s', + $this->_params['table'], + time() - ini_get('session.gc_maxlifetime')); + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_mysql::getSessionIDs(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $result = @mysql_query($query, $this->_db); + if (!$result) { + throw new Horde_Exception('Error getting session IDs: ' . mysql_error($this->_db)); + } + + $sessions = array(); + + while ($row = mysql_fetch_row($result)) { + $sessions[] = $row[0]; + } + + return $sessions; + } + + /** + * Escape a mysql string. + * + * @param string $value The string to quote. + * + * @return string The quoted string. + */ + protected function _quote($value) + { + switch (strtolower(gettype($value))) { + case 'null': + return 'NULL'; + + case 'integer': + return $value; + + case 'string': + default: + return "'" . @mysql_real_escape_string($value, $this->_db) . "'"; + } + } + +} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/none.php b/framework/SessionHandler/lib/Horde/SessionHandler/none.php new file mode 100644 index 000000000..c845cc95d --- /dev/null +++ b/framework/SessionHandler/lib/Horde/SessionHandler/none.php @@ -0,0 +1,67 @@ + + * None. + * + * Optional parameters:
+ *   None.
+ * + * Copyright 2005-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Matt Selsky + * @package Horde_SessionHandler + */ +class Horde_SessionHandler_none extends Horde_SessionHandler +{ + /** + * Read the data for a particular session identifier from the backend. + * + * @param string $id The session identifier. + * + * @return string The session data. + */ + protected function _read($id) + { + $file = session_save_path() . DIRECTORY_SEPARATOR . 'sess_' . $id; + $session_data = @file_get_contents($file); + if ($session_data === false) { + Horde::logMessage('Unable to read file: ' . $file, __FILE__, __LINE__, PEAR_LOG_ERR); + $session_data = ''; + } + + return $session_data; + } + + /** + * Get a list of the valid session identifiers. + * + * @return array A list of valid session identifiers. + */ + public function getSessionIDs() + { + $sessions = array(); + + $path = session_save_path(); + $d = @dir(empty($path) ? Util::getTempDir() : $path); + if (!$d) { + return $sessions; + } + + while (($entry = $d->read()) !== false) { + /* Make sure we're dealing with files that start with + * sess_. */ + if (is_file($d->path . DIRECTORY_SEPARATOR . $entry) && + !strncmp($entry, 'sess_', strlen('sess_'))) { + $sessions[] = substr($entry, strlen('sess_')); + } + } + + return $sessions; + } + +} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/oci8.php b/framework/SessionHandler/lib/Horde/SessionHandler/oci8.php new file mode 100644 index 000000000..21c52ff6d --- /dev/null +++ b/framework/SessionHandler/lib/Horde/SessionHandler/oci8.php @@ -0,0 +1,278 @@ + + * 'hostspec' - (string) The hostname of the database server. + * 'username' - (string) The username with which to connect to the database. + * 'password' - (string) The password associated with 'username'. + * 'database' - (string) The name of the database. + * 'table' - (string) The name of the sessiondata table in 'database'. + * + * + * Required for some configurations:
+ *   'port' - (integer) The port on which to connect to the database.
+ * 
+ * + * Optional parameters:
+ *   'persistent' - (boolean) Use persistent DB connections?
+ * 
+ + * The table structure can be found in: + * horde/scripts/sql/horde_sessionhandler.oci8.sql. + * + * Copyright 2003-2009 Liam Hoekenga + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Liam Hoekenga + * @package Horde_SessionHandler + */ +class Horde_SessionHandler_oci8 extends Horde_SessionHandler +{ + /** + * Handle for the current database connection. + * + * @var resource + */ + protected $_db; + + /** + * Attempts to open a connection to the SQL server. + * + * @param string $save_path The path to the session object. + * @param string $session_name The name of the session. + * + * @throws Horde_Exception + */ + protected function _open($save_path = false, $session_name = false) + { + Horde::assertDriverConfig($this->_params, 'sessionhandler', + array('hostspec', 'username', 'password'), + 'session handler Oracle'); + + if (!isset($this->_params['table'])) { + $this->_params['table'] = 'horde_sessionhandler'; + } + + if (function_exists('oci_connect')) { + $connect = empty($this->_params['persistent']) + ? 'oci_connect' + : 'oci_pconnect'; + } else { + $connect = empty($this->_params['persistent']) + ? 'OCILogon' + : 'OCIPLogon'; + } + + if (!is_resource($this->_db = @$connect($this->_params['username'], + $this->_params['password'], + $this->_params['hostspec']))) { + throw new Horde_Exception('Could not connect to database for SQL Horde_SessionHandler.'); + } + } + + /** + * Close the backend. + * + * @throws Horde_Exception + */ + protected function _close() + { + if (!OCILogOff($this->_db)) { + throw new Horde_Exception('Could not disconnect from databse.'); + } + } + + /** + * Read the data for a particular session identifier from the backend. + * + * @param string $id The session identifier. + * + * @return string The session data. + */ + protected function _read($id) + { + $select_query = sprintf('SELECT session_data FROM %s WHERE session_id = %s FOR UPDATE', + $this->_params['table'], $this->_quote($id)); + + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_oci8::_read(): query = "%s"', $select_query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $select_statement = OCIParse($this->_db, $select_query); + OCIExecute($select_statement, OCI_DEFAULT); + if (OCIFetchInto($select_statement, $result)) { + $value = $result[0]->load(); + } else { + $value = ''; + } + + OCIFreeStatement($select_statement); + + return $value; + } + + /** + * Write session data to the backend. + * + * @param string $id The session identifier. + * @param string $session_data The session data. + * + * @return boolean True on success, false otherwise. + */ + protected function _write($id, $session_data) + { + $select_query = sprintf('SELECT session_data FROM %s WHERE session_id = %s FOR UPDATE', + $this->_params['table'], $this->_quote($id)); + + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_oci8::_write(): query = "%s"', $select_query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $select_statement = OCIParse($this->_db, $select_query); + OCIExecute($select_statement, OCI_DEFAULT); + if (OCIFetchInto($select_statement, $result)) { + /* Discard the existing LOB contents. */ + if (!$result[0]->truncate()) { + OCIRollback($this->_db); + return false; + } + + /* Save the session data. */ + if ($result[0]->save($session_data)) { + OCICommit($this->_db); + OCIFreeStatement($select_statement); + } else { + OCIRollback($this->_db); + return false; + } + } else { + $insert_query = sprintf('INSERT INTO %s (session_id, session_lastmodified, session_data) VALUES (%s, %s, EMPTY_BLOB()) RETURNING session_data INTO :blob', + $this->_params['table'], + $this->_quote($id), + $this->_quote(time())); + + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_oci8::_read(): query = "%s"', $insert_query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $insert_statement = OCIParse($this->_db, $insert_query); + $lob = OCINewDescriptor($this->_db); + OCIBindByName($insert_statement, ':blob', $lob, -1, SQLT_BLOB); + OCIExecute($insert_statement, OCI_DEFAULT); + if (!$lob->save($session_data)) { + OCIRollback($this->_db); + return false; + } + OCICommit($this->_db); + OCIFreeStatement($insert_statement); + } + + return true; + } + + /** + * Destroy the data for a particular session identifier in the backend. + * + * @param string $id The session identifier. + * + * @return boolean True on success, false otherwise. + */ + public function destroy($id) + { + /* Build the SQL query. */ + $query = sprintf('DELETE FROM %s WHERE session_id = %s', + $this->_params['table'], $this->_quote($id)); + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_oci8::destroy(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + /* Execute the query. */ + $statement = OCIParse($this->_db, $query); + $result = OCIExecute($statement); + if (!$result) { + OCIFreeStatement($statement); + Horde::logMessage('Failed to delete session (id = ' . $id . ')', __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + OCIFreeStatement($statement); + + return true; + } + + /** + * Garbage collect stale sessions from the backend. + * + * @param integer $maxlifetime The maximum age of a session. + * + * @return boolean True on success, false otherwise. + */ + public function gc($maxlifetime = 1) + { + /* Build the SQL query. */ + $query = sprintf('DELETE FROM %s WHERE session_lastmodified < %s', + $this->_params['table'], $this->_quote(time() - $maxlifetime)); + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_oci8::gc(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + /* Execute the query. */ + $statement = OCIParse($this->_db, $query); + $result = OCIExecute($statement); + if (!$result) { + OCIFreeStatement($statement); + Horde::logMessage('Error garbage collecting old sessions', __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + OCIFreeStatement($statement); + + return true; + } + + /** + * Get a list of the valid session identifiers. + * + * @return array A list of valid session identifiers. + * @throws Horde_Exception + */ + public function getSessionIDs() + { + /* Make sure we have a valid database connection. */ + $this->open(); + + /* Session timeout, don't rely on garbage collection */ + $query = sprintf('SELECT session_id FROM %s ' . + 'WHERE session_lastmodified >= %s', + $this->_params['table'], + time() - ini_get('session.gc_maxlifetime')); + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_oci8::getSessionIDs(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + /* Execute query */ + $statement = OCIParse($this->_db, $query); + OCIExecute($statement); + + $sessions = array(); + while (OCIFetchInto($statement, $row)) { + $sessions[] = $row[0]; + } + + OCIFreeStatement($statement); + + return $sessions; + } + + /** + * Escape a string for insertion. Stolen from PEAR::DB. + * + * @param string $value The string to quote. + * + * @return string The quoted string. + */ + protected function _quote($value) + { + return is_null($value) + ? 'NULL' + : "'" . str_replace("'", "''", $value) . "'"; + } + +} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/pgsql.php b/framework/SessionHandler/lib/Horde/SessionHandler/pgsql.php new file mode 100644 index 000000000..e9de42718 --- /dev/null +++ b/framework/SessionHandler/lib/Horde/SessionHandler/pgsql.php @@ -0,0 +1,285 @@ + + * 'database' - (string) The name of the database. + * 'password' - (string) The password associated with 'username'. + * 'protocol' - (string) The communication protocol ('tcp', 'unix'). + * 'username' - (string) The username with which to connect to the database. + * + * Required for some configurations (i.e. 'protocol' = 'tcp'):
+ *   'hostspec' - (string) The hostname of the database server.
+ *   'port'     - (integer) The port on which to connect to the database.
+ * 
+ * + * Optional parameters:
+ *   'persistent' - (boolean) Use persistent DB connections?
+ *                  Default: NO
+ *   'table'      - (string) The name of the sessiondata table in 'database'.
+ *                  Default: 'horde_sessionhandler'
+ * + + * The table structure can be found in: + * horde/scripts/sql/horde_sessionhandler.pgsql.sql. + * + * Contributors:
+ *  Jason Carlson           Return an empty string on failed reads
+ *  pat@pcprogrammer.com    Perform update in a single transaction
+ *  Jonathan Crompton       Lock row for life of session
+ * + * @author Jon Parise + * @package Horde_SessionHandler + */ +class Horde_SessionHandler_pgsql extends Horde_SessionHandler +{ + /** + * Handle for the current database connection. + * + * @var resource + */ + protected $_db; + + /** + * Attempts to open a connection to the SQL server. + * + * @param string $save_path The path to the session object. + * @param string $session_name The name of the session. + * + * @throws Horde_Exception + */ + protected function _open($save_path = null, $session_name = null) + { + Horde::assertDriverConfig($this->_params, 'sessionhandler', + array('hostspec', 'username', 'database', 'password'), + 'session handler pgsql'); + + if (empty($this->_params['table'])) { + $this->_params['table'] = 'horde_sessionhandler'; + } + + $connect = empty($this->_params['persistent']) ? + 'pg_connect' :'pg_pconnect'; + + $paramstr = ''; + if (isset($this->_params['protocol']) && + $this->_params['protocol'] == 'tcp') { + $paramstr .= ' host=' . $this->_params['hostspec']; + if (isset($this->_params['port'])) { + $paramstr .= ' port=' . $this->_params['port']; + } + } + $paramstr .= ' dbname=' . $this->_params['database'] . + ' user=' . $this->_params['username'] . + ' password=' . $this->_params['password']; + + if (!$this->_db = @$connect($paramstr)) { + throw new Horde_Exception(sprintf('Could not connect to database %s for SQL Horde_SessionHandler.', $this->_params['database'])); + } + } + + /** + * Close the backend. + * + * @throws Horde_Exception + */ + protected function _close() + { + /* Disconnect from database. */ + if (!@pg_close($this->_db)) { + throw new Horde_Exception('Cound not disconnect from database.'); + } + } + + /** + * Read the data for a particular session identifier from the backend. + * + * @param string $id The session identifier. + * + * @return string The session data. + */ + protected function _read($id) + { + @pg_query($this->_db, 'BEGIN;'); + + $query = sprintf('SELECT session_data FROM %s WHERE session_id = %s ' . + 'FOR UPDATE;', + $this->_params['table'], + $this->_quote($id)); + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_pgsql::' . '_read(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $result = @pg_query($this->_db, $query); + $data = pg_fetch_result($result, 0, 'session_data'); + pg_free_result($result); + + return pack('H*', $data); + } + + /** + * Write session data to the backend. + * + * @param string $id The session identifier. + * @param string $session_data The session data. + * + * @return boolean True on success, false otherwise. + */ + protected function _write($id, $session_data) + { + $query = sprintf('SELECT session_data FROM %s WHERE session_id = %s ' . + 'FOR UPDATE', + $this->_params['table'], + $this->_quote($id)); + $result = @pg_query($this->_db, $query); + $rows = pg_num_rows($result); + pg_free_result($result); + + if ($rows == 0) { + $query = sprintf('INSERT INTO %s (session_id, ' . + 'session_lastmodified, session_data) ' . + 'VALUES (%s, %s, %s);', + $this->_params['table'], + $this->_quote($id), + time(), + $this->_quote(bin2hex($session_data))); + } else { + $query = sprintf('UPDATE %s SET session_lastmodified = %s, ' . + 'session_data = %s WHERE session_id = %s;', + $this->_params['table'], + time(), + $this->_quote(bin2hex($session_data)), + $this->_quote($id)); + } + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_pgsql::' . '_write(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $result = @pg_query($this->_db, $query); + $rows = pg_affected_rows($result); + pg_free_result($result); + + @pg_query($this->_db, 'COMMIT;'); + + if ($rows != 1) { + Horde::logMessage('Error writing session data', __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + return true; + } + + /** + * Destroy the data for a particular session identifier in the backend. + * + * @param string $id The session identifier. + * + * @return boolean True on success, false otherwise. + */ + public function destroy($id) + { + /* Build the SQL query. */ + $query = sprintf('DELETE FROM %s WHERE session_id = %s;', + $this->_params['table'], $this->_quote($id)); + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_pgsql::' . 'destroy(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + /* Execute the query. */ + $result = @pg_query($this->_db, $query); + + @pg_query($this->_db, 'COMMIT;'); + + if (!$result) { + pg_free_result($result); + Horde::logMessage('Failed to delete session (id = ' . $id . ')', __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + pg_free_result($result); + return true; + } + + /** + * Garbage collect stale sessions from the backend. + * + * @param integer $maxlifetime The maximum age of a session. + * + * @return boolean True on success, false otherwise. + */ + public function gc($maxlifetime = 300) + { + /* Build the SQL query. */ + $query = sprintf('DELETE FROM %s WHERE session_lastmodified < %s', + $this->_params['table'], + $this->_quote(time() - $maxlifetime)); + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_pgsql::' . 'gc(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + /* Execute the query. */ + $result = @pg_query($this->_db, $query); + if (!$result) { + Horde::logMessage('Error garbage collecting old sessions', __FILE__, __LINE__, PEAR_LOG_ERR); + } + + pg_free_result($result); + + return $result; + } + + /** + * Get a list of the valid session identifiers. + * + * @return array A list of valid session identifiers. + * @throws Horde_Exception + */ + public function getSessionIDs() + { + /* Make sure we have a valid database connection. */ + $this->_open(); + + /* Build the SQL query. */ + $query = sprintf('SELECT session_id FROM %s ' . + 'WHERE session_lastmodified >= %s', + $this->_params['table'], + time() - ini_get('session.gc_maxlifetime')); + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_pgsql::' . 'getSessionIDs(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + /* Execute the query. */ + $result = @pg_query($this->_db, $query); + if (!$result) { + pg_free_result($result); + throw new Horde_Exception('Error getting session IDs'); + } + + $sessions = array(); + while ($row = pg_fetch_row($result)) { + $sessions[] = $row[0]; + } + + pg_free_result($result); + + return $sessions; + } + + /** + * Escape a string for insertion into the database. + * + * @param string $value The string to quote. + * + * @return string The quoted string. + */ + protected function _quote($value) + { + return "'" . addslashes($value) . "'"; + } + +} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/sql.php b/framework/SessionHandler/lib/Horde/SessionHandler/sql.php new file mode 100644 index 000000000..d9cd80e42 --- /dev/null +++ b/framework/SessionHandler/lib/Horde/SessionHandler/sql.php @@ -0,0 +1,298 @@ + + * 'phptype' - (string) The database type (e.g. 'pgsql', 'mysql', etc.). + * 'hostspec' - (string) The hostname of the database server. + * 'protocol' - (string) The communication protocol ('tcp', 'unix', etc.). + * 'username' - (string) The username with which to connect to the database. + * 'password' - (string) The password associated with 'username'. + * 'database' - (string) The name of the database. + * 'options' - (array) Additional options to pass to the database. + * 'tty' - (string) The TTY on which to connect to the database. + * 'port' - (integer) The port on which to connect to the database. + * + * + * Optional parameters:
+ *   'table'      - (string) The name of the sessiondata table in 'database'.
+ *   'persistent' - (boolean) Use persistent DB connections?
+ * 
+ * + * Optional values when using separate reading and writing servers, for example + * in replication settings:
+ *   'splitread' - (boolean) Whether to implement the separation or not.
+ *   'read'      - (array) Array containing the parameters which are
+ *                 different for the writer database connection, currently
+ *                 supports only 'hostspec' and 'port' parameters.
+ * 
+ * + * The table structure can be found in: + * horde/scripts/sql/horde_sessionhandler.sql. + * + * Copyright 2002-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Mike Cochrane + * @package Horde_SessionHandler + */ +class Horde_SessionHandler_sql extends Horde_SessionHandler +{ + /** + * Handle for the current database connection. + * + * @var DB + */ + protected $_db; + + /** + * Handle for the current database connection, used for writing. Defaults + * to the same handle as $_db if a separate write database is not required. + * + * @var DB + */ + protected $_write_db; + + /** + * Attempts to open a connection to the SQL server. + * + * @param string $save_path The path to the session object. + * @param string $session_name The name of the session. + * + * @throws Horde_Exception + */ + protected function _open($save_path = null, $session_name = null) + { + Horde::assertDriverConfig($this->_params, 'sessionhandler', + array('phptype'), + 'session handler SQL'); + + if (!isset($this->_params['database'])) { + $this->_params['database'] = ''; + } + if (!isset($this->_params['username'])) { + $this->_params['username'] = ''; + } + if (!isset($this->_params['hostspec'])) { + $this->_params['hostspec'] = ''; + } + if (empty($this->_params['table'])) { + $this->_params['table'] = 'horde_sessionhandler'; + } + + /* Connect to the SQL server using the supplied parameters. */ + $this->_write_db = &DB::connect($this->_params, + array('persistent' => !empty($this->_params['persistent']), + 'ssl' => !empty($this->_params['ssl']))); + if (is_a($this->_write_db, 'PEAR_Error')) { + throw new Horde_Exception($this->_write_db); + } + + $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); + + /* Check if we need to set up the read DB connection + * seperately. */ + if (!empty($this->_params['splitread'])) { + $params = array_merge($this->_params, $this->_params['read']); + $this->_db = &DB::connect($params, + array('persistent' => !empty($params['persistent']), + 'ssl' => !empty($this->_params['ssl']))); + if (is_a($this->_db, 'PEAR_Error')) { + throw new Horde_Exception($this->_db); + } + $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); + } else { + /* Default to the same DB handle for reads. */ + $this->_db =& $this->_write_db; + } + } + + /** + * Close the backend. + * + * @throws Horde_Exception + */ + protected function _close() + { + /* Close any open transactions. */ + $this->_db->commit(); + $this->_db->autoCommit(true); + $this->_write_db->commit(); + $this->_write_db->autoCommit(true); + + @$this->_write_db->disconnect(); + @$this->_db->disconnect(); + } + + /** + * Read the data for a particular session identifier from the backend. + * + * @param string $id The session identifier. + * + * @return string The session data. + */ + protected function _read($id) + { + /* Begin a transaction. */ + $result = $this->_write_db->autocommit(false); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return ''; + } + + /* Execute the query. */ + $result = Horde_SQL::readBlob($this->_write_db, $this->_params['table'], 'session_data', array('session_id' => $id)); + + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return ''; + } + + return $result; + } + + /** + * Write session data to the backend. + * + * @param string $id The session identifier. + * @param string $session_data The session data. + * + * @return boolean True on success, false otherwise. + */ + protected function _write($id, $session_data) + { + /* Build the SQL query. */ + $query = sprintf('SELECT session_id FROM %s WHERE session_id = ?', + $this->_params['table']); + $values = array($id); + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_sql::write(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + /* Execute the query. */ + $result = $this->_write_db->getOne($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + if ($result) { + $result = Horde_SQL::updateBlob($this->_write_db, $this->_params['table'], 'session_data', + $session_data, array('session_id' => $id), + array('session_lastmodified' => time())); + } else { + $result = Horde_SQL::insertBlob($this->_write_db, $this->_params['table'], 'session_data', + $session_data, array('session_id' => $id, + 'session_lastmodified' => time())); + } + + if (is_a($result, 'PEAR_Error')) { + $this->_write_db->rollback(); + $this->_write_db->autoCommit(true); + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + $result = $this->_write_db->commit(); + if (is_a($result, 'PEAR_Error')) { + $this->_write_db->autoCommit(true); + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + $this->_write_db->autoCommit(true); + return true; + } + + /** + * Destroy the data for a particular session identifier in the backend. + * + * @param string $id The session identifier. + * + * @return boolean True on success, false otherwise. + */ + public function destroy($id) + { + /* Build the SQL query. */ + $query = sprintf('DELETE FROM %s WHERE session_id = ?', + $this->_params['table']); + $values = array($id); + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_sql::destroy(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + /* Execute the query. */ + $result = $this->_write_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + $result = $this->_write_db->commit(); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + return true; + } + + /** + * Garbage collect stale sessions from the backend. + * + * @param integer $maxlifetime The maximum age of a session. + * + * @return boolean True on success, false otherwise. + */ + public function gc($maxlifetime = 300) + { + /* Build the SQL query. */ + $query = sprintf('DELETE FROM %s WHERE session_lastmodified < ?', + $this->_params['table']); + $values = array(time() - $maxlifetime); + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_sql::gc(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + /* Execute the query. */ + $result = $this->_write_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + return true; + } + + /** + * Get a list of the valid session identifiers. + * + * @return array A list of valid session identifiers. + * @throws Horde_Exception + */ + public function getSessionIDs() + { + $this->open(); + + /* Build the SQL query. */ + $query = 'SELECT session_id FROM ' . $this->_params['table'] . + ' WHERE session_lastmodified => ?'; + $values = array(time() - ini_get('session.gc_maxlifetime')); + + /* Log the query at a DEBUG log level. */ + Horde::logMessage(sprintf('SQL Query by Horde_SessionHandler_sql::getSessionIDs(): query = "%s"', $query), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + /* Execute the query. */ + $result = $this->_db->getCol($query, 0, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + return $result; + } + +} diff --git a/framework/SessionHandler/package.xml b/framework/SessionHandler/package.xml new file mode 100644 index 000000000..cb58dffcc --- /dev/null +++ b/framework/SessionHandler/package.xml @@ -0,0 +1,154 @@ + + + SessionHandler + pear.horde.org + Horde Session Handler API + Horde_SessionHandler:: defines an API for implementing custom + session handlers for PHP. + + + Chuck Hagenbuch + chuck + chuck@horde.org + yes + + + Jan Schneider + jan + jan@horde.org + yes + + + Michael Slusarz + slusarz + slusarz@horde.org + yes + + 2009-02-23 + + 0.1.0 + 0.1.0 + + + beta + beta + + LGPL + * Initial Horde 4 package. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.2.0 + + + 1.5.0 + + + + + Horde_Memcache + pear.horde.org + + + Horde_SQL + pear.horde.org + + + + + + + + + + + + + + + + + + 2006-05-08 + + + 0.0.3 + 0.0.3 + + + alpha + alpha + + LGPL + * Fixed garbage collection checking for SQL backends. +* Memcache driver no longer uses value in session.gc_maxlifetime as the lifetime value. +* Add memcache caching support to all other backends. +* Only write to backend if session data has changed +* Locking in memcache driver implemented via memcache itself +* Support LDAPv3 in the LDAP backend (Bug #5864) +* Converted to package.xml 2.0 for pear.horde.org + + + + + 0.0.2 + 0.0.2 + + + alpha + alpha + + 2004-01-01 + LGPL + * Renamed memcached.php to memcache.php for consistency with other drivers and applications. +* Add support for separate read and write DB servers for the sql driver. +* Add support for locking in the memcache driver (Bug #2913). + + + + + 0.0.1 + 0.0.1 + + + alpha + alpha + + 2003-07-03 + LGPL + Initial release as a PEAR package + + + + diff --git a/framework/SessionHandler/scripts/Horde/SessionHandler/horde-active-sessions.php b/framework/SessionHandler/scripts/Horde/SessionHandler/horde-active-sessions.php new file mode 100755 index 000000000..191f69c84 --- /dev/null +++ b/framework/SessionHandler/scripts/Horde/SessionHandler/horde-active-sessions.php @@ -0,0 +1,67 @@ +#!@php_bin@ +init(); + +require_once HORDE_BASE . '/lib/base.php'; + +/* Make sure there's no compression. */ +@ob_end_clean(); + +/* Check for sessionhandler object. */ +if (empty($GLOBALS['horde_sessionhandler'])) { + Horde::fatal(PEAR::raiseError('Horde is unable to load the session handler'), __FILE__, __LINE__, false); +} + +$type = !empty($conf['sessionhandler']['type']) ? + $conf['sessionhandler']['type'] : 'builtin'; +if ($type == 'external') { + Horde::fatal(PEAR::raiseError('Session counting is not supported in the \'external\' SessionHandler at this time.'), __FILE__, __LINE__, false); +} + +$sessions = $GLOBALS['horde_sessionhandler']->getSessionsInfo(); +if (is_a($sessions, 'PEAR_Error')) { + Horde::fatal($sessions, __FILE__, __LINE__, false); +} + +if (($argc < 2) || (($argv[1] != '-l') && ($argv[1] != '-ll'))) { + $cli->writeln(count($sessions)); +} else { + foreach ($sessions as $data) { + if ($argv[1] == '-ll') { + $cli->writeln($data['userid'] . ' [' . date('r', $data['timestamp']) . ']'); + } else { + $cli->writeln($data['userid']); + } + } + $cli->writeln($cli->green('Total Sessions: ' . count($sessions))); +} -- 2.11.0