--- /dev/null
+<?php
+/**
+ * Horde_SessionHandler:: defines an API for implementing custom PHP session
+ * handlers.
+ *
+ * Optional parameters:<pre>
+ * 'memcache' - (boolean) Use memcache to cache session data?
+ * </pre>
+ *
+ * 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 <mike@graftonhall.co.nz>
+ * @author Michael Slusarz <slusarz@curecanti.org>
+ * @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;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Horde_SessionHandler:: implementation for LDAP directories.
+ *
+ * Required parameters:<pre>
+ * '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.
+ * </pre>
+ *
+ * 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;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Horde_SessionHandler:: implementation for memcache.
+ *
+ * NOTE FOR WINDOWS USERS w/PHP 4: Due to limitations in PHP 4, you should not
+ * use the memcache driver. Either upgrade to PHP 5 or use a different
+ * session handler.
+ *
+ * Optional parameters:<pre>
+ * '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.
+ * </pre>
+ *
+ * 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 <rafan@infor.org>
+ * @author Michael Slusarz <slusarz@curecanti.org>
+ * @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);
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Horde_SessionHandler:: implementation for MySQL (native).
+ *
+ * Required parameters:<pre>
+ * '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).
+ * </pre>
+ *
+ * Required for some configurations:<pre>
+ * 'port' - (integer) The port on which to connect to the database.
+ * </pre>
+ *
+ * Optional parameters:<pre>
+ * 'persistent' - (boolean) Use persistent DB connections?
+ * </pre>
+ *
+ * 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 <mike@graftonhall.co.nz>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @author Jan Schneider <jan@horde.org>
+ * @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) . "'";
+ }
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Horde_SessionHandler implementation for PHP's built-in session handler.
+ *
+ * Required parameters:<pre>
+ * None.</pre>
+ *
+ * Optional parameters:<pre>
+ * None.</pre>
+ *
+ * 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 <selsky@columbia.edu>
+ * @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;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Horde_SessionHandler:: implementation for Oracle 8i (native).
+ *
+ * Required parameters:<pre>
+ * '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'.
+ * </pre>
+ *
+ * Required for some configurations:<pre>
+ * 'port' - (integer) The port on which to connect to the database.
+ * </pre>
+ *
+ * Optional parameters:<pre>
+ * 'persistent' - (boolean) Use persistent DB connections?
+ * </pre>
+
+ * The table structure can be found in:
+ * horde/scripts/sql/horde_sessionhandler.oci8.sql.
+ *
+ * Copyright 2003-2009 Liam Hoekenga <liamr@umich.edu>
+ *
+ * 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 <liamr@umich.edu>
+ * @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) . "'";
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Horde_SessionHandler implementation for PostgreSQL (native).
+ *
+ * Copyright 1999-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.
+ *
+ * Required parameters:<pre>
+ * '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'):<pre>
+ * 'hostspec' - (string) The hostname of the database server.
+ * 'port' - (integer) The port on which to connect to the database.
+ * </pre>
+ *
+ * Optional parameters:<pre>
+ * 'persistent' - (boolean) Use persistent DB connections?
+ * Default: NO
+ * 'table' - (string) The name of the sessiondata table in 'database'.
+ * Default: 'horde_sessionhandler'</pre>
+ * </pre>
+
+ * The table structure can be found in:
+ * horde/scripts/sql/horde_sessionhandler.pgsql.sql.
+ *
+ * Contributors:<pre>
+ * 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</pre>
+ *
+ * @author Jon Parise <jon@csh.rit.edu>
+ * @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) . "'";
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Horde_SessionHandler implementation for PHP's PEAR database abstraction
+ * layer.
+ *
+ * Required parameters:<pre>
+ * '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.
+ * </pre>
+ *
+ * Optional parameters:<pre>
+ * 'table' - (string) The name of the sessiondata table in 'database'.
+ * 'persistent' - (boolean) Use persistent DB connections?
+ * </pre>
+ *
+ * Optional values when using separate reading and writing servers, for example
+ * in replication settings:<pre>
+ * '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.
+ * </pre>
+ *
+ * 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 <mike@graftonhall.co.nz>
+ * @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;
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>SessionHandler</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde Session Handler API</summary>
+ <description>Horde_SessionHandler:: defines an API for implementing custom
+ session handlers for PHP.
+ </description>
+ <lead>
+ <name>Chuck Hagenbuch</name>
+ <user>chuck</user>
+ <email>chuck@horde.org</email>
+ <active>yes</active>
+ </lead>
+ <lead>
+ <name>Jan Schneider</name>
+ <user>jan</user>
+ <email>jan@horde.org</email>
+ <active>yes</active>
+ </lead>
+ <lead>
+ <name>Michael Slusarz</name>
+ <user>slusarz</user>
+ <email>slusarz@horde.org</email>
+ <active>yes</active>
+ </lead>
+ <date>2009-02-23</date>
+ <version>
+ <release>0.1.0</release>
+ <api>0.1.0</api>
+ </version>
+ <stability>
+ <release>beta</release>
+ <api>beta</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* Initial Horde 4 package.
+ </notes>
+ <contents>
+ <dir name="/">
+ <dir name="scripts">
+ <dir name="Horde">
+ <dir name="SessionHandler">
+ <file name="horde-active-sessions.php" role="script">
+ <tasks:replace from="@php_bin@" to="php_bin" type="pear-config"/>
+ </file>
+ </dir> <!-- /scripts/Horde/SessionHandler -->
+ </dir> <!-- /scripts/Horde -->
+ </dir> <!-- /scripts -->
+ <dir name="lib">
+ <dir name="Horde">
+ <dir name="SessionHandler">
+ <file name="ldap.php" role="php" />
+ <file name="memcache.php" role="php" />
+ <file name="mysql.php" role="php" />
+ <file name="none.php" role="php" />
+ <file name="oci8.php" role="php" />
+ <file name="pgsql.php" role="php" />
+ <file name="sql.php" role="php" />
+ </dir> <!-- /lib/Horde/SessionHandler -->
+ <file name="SessionHandler.php" role="php" />
+ </dir> <!-- /lib/Horde -->
+ </dir> <!-- /lib -->
+ </dir> <!-- / -->
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.2.0</min>
+ </php>
+ <pearinstaller>
+ <min>1.5.0</min>
+ </pearinstaller>
+ </required>
+ <optional>
+ <package>
+ <name>Horde_Memcache</name>
+ <channel>pear.horde.org</channel>
+ </package>
+ <package>
+ <name>Horde_SQL</name>
+ <channel>pear.horde.org</channel>
+ </package>
+ </optional>
+ </dependencies>
+ <phprelease>
+ <filelist>
+ <install name="lib/Horde/SessionHandler/ldap.php" as="Horde/SessionHandler/ldap.php" />
+ <install name="lib/Horde/SessionHandler/memcache.php" as="Horde/SessionHandler/memcache.php" />
+ <install name="lib/Horde/SessionHandler/mysql.php" as="Horde/SessionHandler/mysql.php" />
+ <install name="lib/Horde/SessionHandler/none.php" as="Horde/SessionHandler/none.php" />
+ <install name="lib/Horde/SessionHandler/oci8.php" as="Horde/SessionHandler/pgsql.php" />
+ <install name="lib/Horde/SessionHandler/sql.php" as="Horde/SessionHandler/sql.php" />
+ <install name="lib/Horde/SessionHandler.php" as="Horde/SessionHandler.php" />
+ <install name="scripts/Horde/SessionHandler/horde-active-sessions.php" as="scripts/horde-active-sessions.php" />
+ </filelist>
+ </phprelease>
+ <changelog>
+ <release>
+ <date>2006-05-08</date>
+ <time>23:16:14</time>
+ <version>
+ <release>0.0.3</release>
+ <api>0.0.3</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* 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
+ </notes>
+ </release>
+ <release>
+ <version>
+ <release>0.0.2</release>
+ <api>0.0.2</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <date>2004-01-01</date>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* 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).
+ </notes>
+ </release>
+ <release>
+ <version>
+ <release>0.0.1</release>
+ <api>0.0.1</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <date>2003-07-03</date>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>Initial release as a PEAR package
+ </notes>
+ </release>
+ </changelog>
+</package>
--- /dev/null
+#!@php_bin@
+<?php
+/**
+ * This script counts the number of active authenticated user sessions.
+ *
+ * Command line options:
+ * '-l' List the username of active authenticated users
+ * '-ll' List the username and login time of active authenticated users
+ *
+ * $Horde: framework/SessionHandler/scripts/horde-active-sessions.php,v 1.7 2008/09/22 03:50:58 slusarz Exp $
+ *
+ * @package Horde_SessionHandler
+ */
+
+// No auth.
+@define('AUTH_HANDLER', true);
+
+// Find the base file path of Horde.
+@define('HORDE_BASE', dirname(__FILE__) . '/..');
+
+// Do CLI checks and environment setup first.
+require_once HORDE_BASE . '/lib/core.php';
+require_once 'Horde/CLI.php';
+
+// Make sure no one runs this from the web.
+if (!Horde_CLI::runningFromCLI()) {
+ exit("Must be run from the command line\n");
+}
+
+// Load the CLI environment - make sure there's no time limit, init some
+// variables, etc.
+$cli = &Horde_CLI::singleton();
+$cli->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)));
+}