From 617b4f699f2e47991c50e05528b1f9ecbc3c3d9c Mon Sep 17 00:00:00 2001
From: svncommit <devs@roundcube.net>
Date: Tue, 12 May 2009 09:26:07 -0400
Subject: [PATCH] Minimize chance of race condition in session handling (#1485659, #1484678)

---
 program/include/session.inc |  207 ++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 145 insertions(+), 62 deletions(-)

diff --git a/program/include/session.inc b/program/include/session.inc
index d6486a4..2d537ed 100644
--- a/program/include/session.inc
+++ b/program/include/session.inc
@@ -5,7 +5,7 @@
  | program/include/session.inc                                           |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005-2008, RoundCube Dev. - Switzerland                 |
+ | Copyright (C) 2005-2009, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -39,10 +39,6 @@
   
   $DB = rcmail::get_instance()->get_dbh();
   
-  if ($DB->is_error()) {
-    return false;
-  }
-  
   $sql_result = $DB->query(
     "SELECT vars, ip, " . $DB->unixtimestamp('changed') . " AS changed
      FROM " . get_table_name('session') . "
@@ -66,28 +62,21 @@
 {
   $DB = rcmail::get_instance()->get_dbh();
   
-  if ($DB->is_error()) {
-    return false;
-  }
+  $now = $DB->fromunixtime(time());
 
-  $sql_result = $DB->query(
-    "SELECT 1 FROM " . get_table_name('session') . "
-     WHERE  sess_id=?",
-    $key);
-
-  if ($DB->num_rows($sql_result)) {
+  if ($oldvars = rcube_sess_read($key)) {
     $DB->query(
       "UPDATE " . get_table_name('session') . "
-       SET    vars=?, changed=" . $DB->now() . "
+       SET    vars=?, changed= " . $now . "
        WHERE  sess_id=?",
-      $vars,
+      rcube_sess_serialize(array_merge(rcube_sess_unserialize($oldvars), rcube_sess_unserialize($vars))),
       $key);
   }
   else {
     $DB->query(
       "INSERT INTO " . get_table_name('session') . "
        (sess_id, vars, ip, created, changed)
-       VALUES (?, ?, ?, ".$DB->now().", ".$DB->now().")",
+       VALUES (?, ?, ?, " . $now . ", " . $now .")",
       $key,
       $vars,
       (string)$_SERVER['REMOTE_ADDR']);
@@ -97,21 +86,146 @@
 }
 
 
+// unset session variable
+function rcube_sess_unset($var)
+{
+  $DB = rcmail::get_instance()->get_dbh();
+
+  if ($DB->is_error()) {
+    return false;
+  }
+
+  $now = $DB->fromunixtime(time());
+
+  $sql_result = $DB->query(
+    "SELECT vars
+     FROM " . get_table_name('session') . "
+     WHERE  sess_id=?",
+     session_id());
+
+  if ($sql_arr = $DB->fetch_assoc($sql_result)) {
+    $vars = rcube_sess_unserialize($sql_arr['vars']);
+    if (isset($vars[$var])) {
+      unset($vars[$var]);
+      $DB->query(
+        "UPDATE " . get_table_name('session') . "
+         SET    vars=?, changed= " . $now . "
+         WHERE  sess_id=?",
+         rcube_sess_serialize($vars),
+         session_id());
+    }
+  }   
+
+  return true;
+}
+
+
+// serialize session data
+function rcube_sess_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
+function rcube_sess_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 . '}' );
+}
+
+
 // handler for session_destroy()
 function rcube_sess_destroy($key)
 {
   $rcmail = rcmail::get_instance();
   $DB = $rcmail->get_dbh();
   
-  if ($DB->is_error()) {
-    return false;
-  }
-
-  // delete session entries in cache table
-  if ($rcmail->config->get('enable_caching')) {
-    $DB->query("DELETE FROM " . get_table_name('cache') . " WHERE session_id=?", $key);
-  }
-              
   $DB->query("DELETE FROM " . get_table_name('session') . " WHERE sess_id=?", $key);
 
   return true;
@@ -124,43 +238,12 @@
   $rcmail = rcmail::get_instance();
   $DB = $rcmail->get_dbh();
 
-  if ($DB->is_error()) {
-    return false;
-  }
+  // just delete all expired sessions
+  $DB->query("DELETE FROM " . get_table_name('session') . "
+    WHERE changed < " . $DB->fromunixtime(time() - $maxlifetime));
 
-  if ($rcmail->config->get('enable_caching')) {
-    // get all expired sessions
-    $sql_result = $DB->query(
-	"SELECT sess_id
-        FROM " . get_table_name('session') . "
-        WHERE " . $DB->unixtimestamp($DB->now())."-".$DB->unixtimestamp('changed') . " > ?",
-	$maxlifetime);
-                                   
-    $exp_sessions = array();
-    while ($sql_arr = $DB->fetch_assoc($sql_result)) {
-      $exp_sessions[] = $sql_arr['sess_id'];
-    }
-
-    if (sizeof($exp_sessions)) {
-      $exp_sessions = "'" . join("','", $exp_sessions) . "'";
-      // delete session cache records
-      $DB->query("DELETE FROM " . get_table_name('cache') . "
-            WHERE session_id IN (" . $exp_sessions . ")");
-
-      // delete session records
-      $DB->query("DELETE FROM " . get_table_name('session') . "
-            WHERE sess_id IN (" . $exp_sessions . ")");
-    }
-
-    // also run message cache GC
-    rcmail_message_cache_gc();
-  
-  } else {
-    // just delete all expired sessions
-    $DB->query("DELETE FROM " . get_table_name('session') . "
-        WHERE " . $DB->unixtimestamp($DB->now())."-".$DB->unixtimestamp('changed') . " > ?",
-	$maxlifetime);
-  }
+  if ($rcmail->config->get('enable_caching'))
+    rcmail_cache_gc();
 
   rcmail_temp_gc();
 

--
Gitblit v1.9.1