summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG1
-rw-r--r--index.php4
-rw-r--r--plugins/managesieve/managesieve.php4
-rw-r--r--plugins/new_user_dialog/new_user_dialog.php2
-rwxr-xr-xprogram/include/iniset.php9
-rw-r--r--program/include/main.inc10
-rw-r--r--program/include/rcmail.php110
-rw-r--r--program/include/rcube_plugin_api.php2
-rw-r--r--program/include/rcube_session.php330
-rwxr-xr-xprogram/include/rcube_template.php2
-rw-r--r--program/include/rcube_user.php5
-rw-r--r--program/steps/mail/func.inc6
12 files changed, 426 insertions, 59 deletions
diff --git a/CHANGELOG b/CHANGELOG
index c7627e6a5..63bd103ec 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
CHANGELOG RoundCube Webmail
===========================
+- Improve performance by avoiding unnecessary updates to the session table (#1486325)
- Fix invalid font tags which cause HTML message rendering problems (#1486521)
- Fix CVE-2010-0464: Disable DNS prefetching (#1486449)
- Fix Received headers to behave better with SpamAssassin (#1486513)
diff --git a/index.php b/index.php
index 8d3e10acd..fe7f6b5fa 100644
--- a/index.php
+++ b/index.php
@@ -96,8 +96,8 @@ if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') {
!empty($auth['host']) && !empty($auth['user']) &&
$RCMAIL->login($auth['user'], $auth['pass'], $auth['host'])) {
// create new session ID
- rcube_sess_unset('temp');
- rcube_sess_regenerate_id();
+ $RCMAIL->session->remove('temp');
+ $RCMAIL->session->regenerate_id();
// send auth cookie if necessary
$RCMAIL->authenticate_session();
diff --git a/plugins/managesieve/managesieve.php b/plugins/managesieve/managesieve.php
index 219c33489..e88fcff45 100644
--- a/plugins/managesieve/managesieve.php
+++ b/plugins/managesieve/managesieve.php
@@ -221,7 +221,7 @@ class managesieve extends rcube_plugin
if ($result === true) {
$this->rc->output->show_message('managesieve.setdeleted', 'confirmation');
$this->rc->output->command('managesieve_reload');
- rcube_sess_unset('managesieve_current');
+ $this->rc->session->remove('managesieve_current');
} else {
$this->rc->output->show_message('managesieve.setdeleteerror', 'error');
}
@@ -270,7 +270,7 @@ class managesieve extends rcube_plugin
if (!$error) {
$this->rc->output->show_message('managesieve.setcreated', 'confirmation');
$this->rc->output->command('parent.managesieve_reload', $name);
-// rcube_sess_unset('managesieve_current');
+// $this->rc->session->remove('managesieve_current');
} else {
$this->rc->output->show_message($error, 'error');
}
diff --git a/plugins/new_user_dialog/new_user_dialog.php b/plugins/new_user_dialog/new_user_dialog.php
index 11154ce29..006cb2711 100644
--- a/plugins/new_user_dialog/new_user_dialog.php
+++ b/plugins/new_user_dialog/new_user_dialog.php
@@ -98,7 +98,7 @@ class new_user_dialog extends rcube_plugin
// save data if not empty
if (!empty($save_data['name']) && !empty($save_data['email'])) {
$rcmail->user->update_identity($identity['identity_id'], $save_data);
- rcube_sess_unset('plugin.newuserdialog');
+ $rcmail->session->remove('plugin.newuserdialog');
}
$rcmail->output->redirect('');
diff --git a/program/include/iniset.php b/program/include/iniset.php
index 94d49d8aa..82ec49f19 100755
--- a/program/include/iniset.php
+++ b/program/include/iniset.php
@@ -52,15 +52,6 @@ if (set_include_path($include_path) === false) {
}
ini_set('error_reporting', E_ALL&~E_NOTICE);
-if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off') {
- ini_set('session.cookie_secure', 1);
-} else {
- ini_set('session.cookie_secure', 0);
-}
-ini_set('session.name', 'roundcube_sessid');
-ini_set('session.use_cookies', 1);
-ini_set('session.use_only_cookies', 1);
-ini_set('session.serialize_handler', 'php');
// increase maximum execution time for php scripts
// (does not work in safe mode)
diff --git a/program/include/main.inc b/program/include/main.inc
index 207614885..9cf6dd00c 100644
--- a/program/include/main.inc
+++ b/program/include/main.inc
@@ -1487,12 +1487,20 @@ function rcube_https_check($port=null, $use_https=true)
return true;
if ($port && $_SERVER['SERVER_PORT'] == $port)
return true;
- if ($use_https && $RCMAIL->config->get('use_https'))
+ if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
return true;
return false;
}
+// for backward compatibility
+function rcube_sess_unset($var_name=null)
+{
+ global $RCMAIL;
+
+ $RCMAIL->session->remove($var_name);
+}
+
/**
* E-mail address validation
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index b0d130e75..04c7151dc 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -35,6 +35,7 @@ class rcmail
public $config;
public $user;
public $db;
+ public $session;
public $smtp;
public $imap;
public $output;
@@ -82,8 +83,6 @@ class rcmail
*/
private function startup()
{
- $config_all = $this->config->all();
-
// initialize syslog
if ($this->config->get('log_driver') == 'syslog') {
$syslog_id = $this->config->get('syslog_id', 'roundcube');
@@ -94,45 +93,29 @@ class rcmail
// connect to database
$GLOBALS['DB'] = $this->get_dbh();
- // use database for storing session data
- include_once('include/session.inc');
-
- // set session domain
- if (!empty($config_all['session_domain'])) {
- ini_set('session.cookie_domain', $config_all['session_domain']);
- }
- // set session garbage collecting time according to session_lifetime
- if (!empty($config_all['session_lifetime'])) {
- ini_set('session.gc_maxlifetime', ($config_all['session_lifetime']) * 120);
- }
-
- // start PHP session (if not in CLI mode)
- if ($_SERVER['REMOTE_ADDR'])
- session_start();
-
- // set initial session vars
- if (!isset($_SESSION['auth_time'])) {
- $_SESSION['auth_time'] = time();
- $_SESSION['temp'] = true;
- }
+ // start session
+ $this->session_init();
// create user object
$this->set_user(new rcube_user($_SESSION['user_id']));
+ // configure session (after user config merge!)
+ $this->session_configure();
+
// set task and action properties
$this->set_task(get_input_value('_task', RCUBE_INPUT_GPC));
$this->action = asciiwords(get_input_value('_action', RCUBE_INPUT_GPC));
// reset some session parameters when changing task
if ($_SESSION['task'] != $this->task)
- rcube_sess_unset('page');
+ $this->session->remove('page');
// set current task to session
$_SESSION['task'] = $this->task;
// init output class
if (!empty($_REQUEST['_remote']))
- $GLOBALS['OUTPUT'] = $this->init_json();
+ $GLOBALS['OUTPUT'] = $this->json_init();
else
$GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed']));
@@ -314,11 +297,8 @@ class rcmail
$this->output = new rcube_template($this->task, $framed);
// set keep-alive/check-recent interval
- if ($keep_alive = $this->config->get('keep_alive')) {
- // be sure that it's less than session lifetime
- if ($session_lifetime = $this->config->get('session_lifetime'))
- $keep_alive = min($keep_alive, $session_lifetime * 60 - 30);
- $this->output->set_env('keep_alive', max(60, $keep_alive));
+ if ($keep_alive = $this->session->get_keep_alive()) {
+ $this->output->set_env('keep_alive', $keep_alive);
}
if ($framed) {
@@ -343,7 +323,7 @@ class rcmail
*
* @return object rcube_json_output Reference to JSON output object
*/
- public function init_json()
+ public function json_init()
{
if (!($this->output instanceof rcube_json_output))
$this->output = new rcube_json_output($this->task);
@@ -444,6 +424,65 @@ class rcmail
/**
+ * Create session object and start the session.
+ */
+ public function session_init()
+ {
+ $lifetime = $this->config->get('session_lifetime', 0) * 60;
+
+ // set session domain
+ if ($domain = $this->config->get('session_domain')) {
+ ini_set('session.cookie_domain', $domain);
+ }
+ // set session garbage collecting time according to session_lifetime
+ if ($lifetime) {
+ ini_set('session.gc_maxlifetime', $lifetime * 2);
+ }
+
+ ini_set('session.cookie_secure', rcube_https_check());
+ ini_set('session.name', 'roundcube_sessid');
+ ini_set('session.use_cookies', 1);
+ ini_set('session.use_only_cookies', 1);
+ ini_set('session.serialize_handler', 'php');
+
+ // use database for storing session data
+ $this->session = new rcube_session($this->get_dbh(), $lifetime);
+
+ $this->session->register_gc_handler('rcmail_temp_gc');
+ if ($this->config->get('enable_caching'))
+ $this->session->register_gc_handler('rcmail_cache_gc');
+
+ // start PHP session (if not in CLI mode)
+ if ($_SERVER['REMOTE_ADDR'])
+ session_start();
+
+ // set initial session vars
+ if (!isset($_SESSION['auth_time'])) {
+ $_SESSION['auth_time'] = time();
+ $_SESSION['temp'] = true;
+ }
+ }
+
+
+ /**
+ * Configure session object internals
+ */
+ public function session_configure()
+ {
+ $lifetime = $this->config->get('session_lifetime', 0) * 60;
+
+ // set keep-alive/check-recent interval
+ if ($keep_alive = $this->config->get('keep_alive')) {
+ // be sure that it's less than session lifetime
+ if ($lifetime)
+ $keep_alive = min($keep_alive, $lifetime - 30);
+ $keep_alive = max(60, $keep_alive);
+ $this->session->set_keep_alive($keep_alive);
+ }
+ }
+
+
+ /**
* Perfom login to the IMAP server and to the webmail service.
* This will also create a new user entry if auto_create_user is configured.
*
@@ -794,8 +833,6 @@ class rcmail
*/
function authenticate_session()
{
- global $SESS_CLIENT_IP, $SESS_CHANGED;
-
// advanced session authentication
if ($this->config->get('double_auth')) {
$now = time();
@@ -810,12 +847,13 @@ class rcmail
}
}
else {
- $valid = $this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] == $SESS_CLIENT_IP : true;
+ $valid = $this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] == $this->session->get_ip() : true;
}
// check session filetime
$lifetime = $this->config->get('session_lifetime');
- if (!empty($lifetime) && isset($SESS_CHANGED) && $SESS_CHANGED + $lifetime*60 < time()) {
+ $sess_ts = $this->session->get_ts();
+ if (!empty($lifetime) && !empty($sess_ts) && $sess_ts + $lifetime*60 < time()) {
$valid = false;
}
@@ -830,7 +868,7 @@ class rcmail
{
$this->plugins->exec_hook('kill_session');
- rcube_sess_unset();
+ $this->session->remove();
$_SESSION = array('language' => $this->user->language, 'auth_time' => time(), 'temp' => true);
rcmail::setcookie('sessauth', '-del-', time() - 60);
$this->user->reset();
diff --git a/program/include/rcube_plugin_api.php b/program/include/rcube_plugin_api.php
index c895578b9..6b6a0a263 100644
--- a/program/include/rcube_plugin_api.php
+++ b/program/include/rcube_plugin_api.php
@@ -69,7 +69,7 @@ class rcube_plugin_api
/**
* Load and init all enabled plugins
*
- * This has to be done after rcmail::load_gui() or rcmail::init_json()
+ * This has to be done after rcmail::load_gui() or rcmail::json_init()
* was called because plugins need to have access to rcmail->output
*/
public function init()
diff --git a/program/include/rcube_session.php b/program/include/rcube_session.php
new file mode 100644
index 000000000..da799be19
--- /dev/null
+++ b/program/include/rcube_session.php
@@ -0,0 +1,330 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_session.php |
+ | |
+ | This file is part of the RoundCube Webmail client |
+ | Copyright (C) 2005-2009, RoundCube Dev. - Switzerland |
+ | Licensed under the GNU GPL |
+ | |
+ | PURPOSE: |
+ | Provide database supported session management |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ | Author: Aleksander Machniak <alec@alec.pl> |
+ +-----------------------------------------------------------------------+
+
+ $Id: session.inc 2932 2009-09-07 12:51:21Z alec $
+
+*/
+
+class rcube_session
+{
+ private $db;
+ private $ip;
+ private $changed;
+ private $unsets = array();
+ private $gc_handlers = array();
+ private $start;
+ private $vars = false;
+ private $key;
+ private $keep_alive = 0;
+
+ /**
+ * Default constructor
+ */
+ public function __construct($db, $lifetime=60)
+ {
+ $this->db = $db;
+ $this->lifetime = $lifetime;
+ $this->start = microtime(true);
+
+ // set custom functions for PHP session management
+ session_set_save_handler(
+ array($this, 'open'),
+ array($this, 'close'),
+ array($this, 'read'),
+ array($this, 'write'),
+ array($this, 'destroy'),
+ array($this, 'gc'));
+ }
+
+
+ public function open($save_path, $session_name)
+ {
+ return true;
+ }
+
+
+ public function close()
+ {
+ return true;
+ }
+
+
+ // read session data
+ public function read($key)
+ {
+ $sql_result = $this->db->query(
+ sprintf("SELECT vars, ip, %s AS changed FROM %s WHERE sess_id = ?",
+ $this->db->unixtimestamp('changed'), get_table_name('session')),
+ $key);
+
+ if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
+ $this->changed = $sql_arr['changed'];
+ $this->vars = $sql_arr['vars'];
+ $this->ip = $sql_arr['ip'];
+ $this->key = $key;
+
+ if (!empty($sql_arr['vars']))
+ return $sql_arr['vars'];
+ }
+
+ return false;
+ }
+
+
+ // save session data
+ public function write($key, $vars)
+ {
+ $ts = microtime(true);
+ $now = $this->db->fromunixtime((int)$ts);
+
+ // use internal data from read() for fast requests (up to 0.5 sec.)
+ if ($key == $this->key && $ts - $this->start < 0.5) {
+ $oldvars = $this->vars;
+ } else { // else read data again from DB
+ $oldvars = $this->read($key);
+ }
+
+ if ($oldvars !== false) {
+ $a_oldvars = $this->unserialize($oldvars);
+ foreach ((array)$this->unsets as $k)
+ unset($a_oldvars[$k]);
+
+ $newvars = $this->serialize(array_merge(
+ (array)$a_oldvars, (array)$this->unserialize($vars)));
+
+ if ($this->keep_alive>0) {
+ $timeout = min($this->lifetime * 0.5,
+ $this->lifetime - $this->keep_alive);
+ } else {
+ $timeout = 0;
+ }
+
+ if (!($newvars === $oldvars) || ($ts - $this->changed > $timeout)) {
+ $this->db->query(
+ sprintf("UPDATE %s SET vars = ?, changed = %s WHERE sess_id = ?",
+ get_table_name('session'), $now),
+ $newvars, $key);
+ }
+ }
+ else {
+ $this->db->query(
+ sprintf("INSERT INTO %s (sess_id, vars, ip, created, changed) ".
+ "VALUES (?, ?, ?, %s, %s)",
+ get_table_name('session'), $now, $now),
+ $key, $vars, (string)$_SERVER['REMOTE_ADDR']);
+ }
+
+ $this->unsets = array();
+ return true;
+ }
+
+
+ // handler for session_destroy()
+ public function destroy($key)
+ {
+ $this->db->query(
+ sprintf("DELETE FROM %s WHERE sess_id = ?", get_table_name('session')),
+ $key);
+
+ return true;
+ }
+
+
+ // garbage collecting function
+ public function gc($maxlifetime)
+ {
+ // just delete all expired sessions
+ $this->db->query(
+ sprintf("DELETE FROM %s WHERE changed < %s",
+ get_table_name('session'), $this->db->fromunixtime(time() - $maxlifetime)));
+
+ foreach ($this->gc_handlers as $fct)
+ $fct();
+
+ return true;
+ }
+
+
+ // registering additional garbage collector functions
+ public function register_gc_handler($func_name)
+ {
+ if ($func_name && !in_array($func_name, $this->gc_handlers))
+ $this->gc_handlers[] = $func_name;
+ }
+
+
+ public function regenerate_id()
+ {
+ $randval = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+ for ($random = '', $i=1; $i <= 32; $i++) {
+ $random .= substr($randval, mt_rand(0,(strlen($randval) - 1)), 1);
+ }
+
+ // use md5 value for id or remove capitals from string $randval
+ $random = md5($random);
+
+ // delete old session record
+ $this->destroy(session_id());
+
+ session_id($random);
+
+ $cookie = session_get_cookie_params();
+ $lifetime = $cookie['lifetime'] ? time() + $cookie['lifetime'] : 0;
+
+ rcmail::setcookie(session_name(), $random, $lifetime);
+
+ return true;
+ }
+
+
+ // unset session variable
+ public function remove($var=NULL)
+ {
+ if (empty($var))
+ return $this->destroy(session_id());
+
+ $this->unsets[] = $var;
+ unset($_SESSION[$var]);
+
+ return true;
+ }
+
+
+ // serialize session data
+ private function serialize($vars)
+ {
+ $data = '';
+ if (is_array($vars))
+ foreach ($vars as $var=>$value)
+ $data .= $var.'|'.serialize($value);
+ else
+ $data = 'b:0;';
+ return $data;
+ }
+
+
+ // unserialize session data
+ // http://www.php.net/manual/en/function.session-decode.php#56106
+ private function unserialize($str)
+ {
+ $str = (string)$str;
+ $endptr = strlen($str);
+ $p = 0;
+
+ $serialized = '';
+ $items = 0;
+ $level = 0;
+
+ while ($p < $endptr) {
+ $q = $p;
+ while ($str[$q] != '|')
+ if (++$q >= $endptr) break 2;
+
+ if ($str[$p] == '!') {
+ $p++;
+ $has_value = false;
+ } else {
+ $has_value = true;
+ }
+
+ $name = substr($str, $p, $q - $p);
+ $q++;
+
+ $serialized .= 's:' . strlen($name) . ':"' . $name . '";';
+
+ if ($has_value) {
+ for (;;) {
+ $p = $q;
+ switch (strtolower($str[$q])) {
+ case 'n': /* null */
+ case 'b': /* boolean */
+ case 'i': /* integer */
+ case 'd': /* decimal */
+ do $q++;
+ while ( ($q < $endptr) && ($str[$q] != ';') );
+ $q++;
+ $serialized .= substr($str, $p, $q - $p);
+ if ($level == 0) break 2;
+ break;
+ case 'r': /* reference */
+ $q+= 2;
+ for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q++) $id .= $str[$q];
+ $q++;
+ $serialized .= 'R:' . ($id + 1) . ';'; /* increment pointer because of outer array */
+ if ($level == 0) break 2;
+ break;
+ case 's': /* string */
+ $q+=2;
+ for ($length=''; ($q < $endptr) && ($str[$q] != ':'); $q++) $length .= $str[$q];
+ $q+=2;
+ $q+= (int)$length + 2;
+ $serialized .= substr($str, $p, $q - $p);
+ if ($level == 0) break 2;
+ break;
+ case 'a': /* array */
+ case 'o': /* object */
+ do $q++;
+ while ( ($q < $endptr) && ($str[$q] != '{') );
+ $q++;
+ $level++;
+ $serialized .= substr($str, $p, $q - $p);
+ break;
+ case '}': /* end of array|object */
+ $q++;
+ $serialized .= substr($str, $p, $q - $p);
+ if (--$level == 0) break 2;
+ break;
+ default:
+ return false;
+ }
+ }
+ } else {
+ $serialized .= 'N;';
+ $q += 2;
+ }
+ $items++;
+ $p = $q;
+ }
+
+ return unserialize( 'a:' . $items . ':{' . $serialized . '}' );
+ }
+
+ public function set_keep_alive($keep_alive)
+ {
+ $this->keep_alive = $keep_alive;
+ }
+
+ public function get_keep_alive()
+ {
+ return $this->keep_alive;
+ }
+
+ // getter for private variables
+ public function get_ts()
+ {
+ return $this->changed;
+ }
+
+ // getter for private variables
+ public function get_ip()
+ {
+ return $this->ip;
+ }
+
+}
diff --git a/program/include/rcube_template.php b/program/include/rcube_template.php
index 5bd9136a6..5272ace20 100755
--- a/program/include/rcube_template.php
+++ b/program/include/rcube_template.php
@@ -986,7 +986,7 @@ class rcube_template extends rcube_html_page
return $username;
}
- // get e-mail address form default identity
+ // get e-mail address from default identity
if ($sql_arr = $this->app->user->get_identity()) {
$username = $sql_arr['email'];
}
diff --git a/program/include/rcube_user.php b/program/include/rcube_user.php
index 127efa746..ab44cdca2 100644
--- a/program/include/rcube_user.php
+++ b/program/include/rcube_user.php
@@ -47,7 +47,7 @@ class rcube_user
if ($id && !$sql_arr)
{
- $sql_result = $this->db->query("SELECT * FROM ".get_table_name('users')." WHERE user_id=?", $id);
+ $sql_result = $this->db->query("SELECT * FROM ".get_table_name('users')." WHERE user_id=?", $id);
$sql_arr = $this->db->fetch_assoc($sql_result);
}
@@ -154,8 +154,7 @@ class rcube_user
// get contacts from DB
$sql_result = $this->db->query(
"SELECT * FROM ".get_table_name('identities')."
- WHERE del<>1
- AND user_id=?
+ WHERE del<>1 AND user_id=?
$sql_add
ORDER BY ".$this->db->quoteIdentifier('standard')." DESC, name ASC, identity_id ASC",
$this->ID);
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index da21220ff..a8ef65139 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1325,9 +1325,9 @@ function rcmail_compose_cleanup()
if (!isset($_SESSION['compose']))
return;
- rcmail::get_instance()->plugins->exec_hook('cleanup_attachments',array());
-
- rcube_sess_unset('compose');
+ $rcmail = rcmail::get_instance();
+ $rcmail->plugins->exec_hook('cleanup_attachments',array());
+ $rcmail->session->remove('compose');
}