From 037af6890fe6fdb84a08d3c86083e847c90ec0ad Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 22 Oct 2013 08:17:26 -0400
Subject: [PATCH] Fix vulnerability in handling _session argument of utils/save-prefs (#1489382)

---
 program/lib/Roundcube/rcube_imap.php |  121 +++++++++++++++++++++-------------------
 1 files changed, 64 insertions(+), 57 deletions(-)

diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index 9054b6b..ca5e35f 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_imap.php                                        |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -14,13 +12,11 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   IMAP Storage Engine                                                 |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
-
 
 /**
  * Interface class for accessing an IMAP server
@@ -74,7 +70,7 @@
     protected $search_sort_field = '';
     protected $search_threads = false;
     protected $search_sorted = false;
-    protected $options = array('auth_method' => 'check');
+    protected $options = array('auth_type' => 'check');
     protected $caching = false;
     protected $messages_caching = false;
     protected $threading = false;
@@ -141,17 +137,17 @@
             $this->set_debug(true);
 
             $this->options['ident'] = array(
-                'name' => 'Roundcube Webmail',
-                'version' => RCMAIL_VERSION,
-                'php' => PHP_VERSION,
-                'os' => PHP_OS,
+                'name'    => 'Roundcube',
+                'version' => RCUBE_VERSION,
+                'php'     => PHP_VERSION,
+                'os'      => PHP_OS,
                 'command' => $_SERVER['REQUEST_URI'],
             );
         }
 
         $attempt = 0;
         do {
-            $data = rcube::get_instance()->plugins->exec_hook('imap_connect',
+            $data = rcube::get_instance()->plugins->exec_hook('storage_connect',
                 array_merge($this->options, array('host' => $host, 'user' => $user,
                     'attempt' => ++$attempt)));
 
@@ -402,10 +398,10 @@
     public function check_permflag($flag)
     {
         $flag       = strtoupper($flag);
-        $imap_flag  = $this->conn->flags[$flag];
         $perm_flags = $this->get_permflags($this->folder);
+        $imap_flag  = $this->conn->flags[$flag];
 
-        return in_array_nocase($imap_flag, $perm_flags);
+        return $imap_flag && !empty($perm_flags) && in_array_nocase($imap_flag, $perm_flags);
     }
 
 
@@ -421,17 +417,7 @@
         if (!strlen($folder)) {
             return array();
         }
-/*
-        Checking PERMANENTFLAGS is rather rare, so we disable caching of it
-        Re-think when we'll use it for more than only MDNSENT flag
 
-        $cache_key = 'mailboxes.permanentflags.' . $folder;
-        $permflags = $this->get_cache($cache_key);
-
-        if ($permflags !== null) {
-            return explode(' ', $permflags);
-        }
-*/
         if (!$this->check_connection()) {
             return array();
         }
@@ -446,10 +432,7 @@
         if (!is_array($permflags)) {
             $permflags = array();
         }
-/*
-        // Store permflags as string to limit cached object size
-        $this->update_cache($cache_key, implode(' ', $permflags));
-*/
+
         return $permflags;
     }
 
@@ -571,7 +554,7 @@
      * Get message count for a specific folder
      *
      * @param  string  $folder  Folder name
-     * @param  string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT]
+     * @param  string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
      * @param  boolean $force   Force reading from server and update cache
      * @param  boolean $status  Enables storing folder status info (max UID/count),
      *                          required for folder_status()
@@ -592,7 +575,7 @@
      * protected method for getting nr of messages
      *
      * @param string  $folder  Folder name
-     * @param string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT]
+     * @param string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
      * @param boolean $force   Force reading from server and update cache
      * @param boolean $status  Enables storing folder status info (max UID/count),
      *                         required for folder_status()
@@ -613,6 +596,10 @@
                 return $this->search_set->count();
             }
         }
+
+        // EXISTS is a special alias for ALL, it allows to get the number
+        // of all messages in a folder also when search is active and with
+        // any skip_deleted setting
 
         $a_folder_cache = $this->get_cache('messagecount');
 
@@ -644,7 +631,7 @@
             $count = $this->conn->countRecent($folder);
         }
         // use SEARCH for message counting
-        else if (!empty($this->options['skip_deleted'])) {
+        else if ($mode != 'EXISTS' && !empty($this->options['skip_deleted'])) {
             $search_str = "ALL UNDELETED";
             $keys       = array('COUNT');
 
@@ -683,8 +670,8 @@
             }
             else {
                 $count = $this->conn->countMessages($folder);
-                if ($status) {
-                    $this->set_folder_stats($folder,'cnt', $count);
+                if ($status && $mode == 'ALL') {
+                    $this->set_folder_stats($folder, 'cnt', $count);
                     $this->set_folder_stats($folder, 'maxuid', $count ? $this->id2uid($count, $folder) : 0);
                 }
             }
@@ -981,7 +968,7 @@
             // use memory less expensive (and quick) method for big result set
             $index = clone $this->index('', $this->sort_field, $this->sort_order);
             // get messages uids for one page...
-            $index->slice($start_msg, min($cnt-$from, $this->page_size));
+            $index->slice($from, min($cnt-$from, $this->page_size));
 
             if ($slice) {
                 $index->slice(-$slice, $slice);
@@ -1336,17 +1323,16 @@
         // THREAD=REFERENCES:     sorting by sent date of root message
         // THREAD=REFS:           sorting by the most recent date in each thread
 
-        if ($this->sort_field && ($this->sort_field != 'date' || $this->get_capability('THREAD') != 'REFS')) {
-            $index = $this->index_direct($this->folder, $this->sort_field, $this->sort_order, false);
+        if ($this->threading != 'REFS' || ($this->sort_field && $this->sort_field != 'date')) {
+            $sortby = $this->sort_field ? $this->sort_field : 'date';
+            $index  = $this->index_direct($this->folder, $sortby, $this->sort_order, false);
 
             if (!$index->is_empty()) {
                 $threads->sort($index);
             }
         }
-        else {
-            if ($this->sort_order != $threads->get_parameters('ORDER')) {
-                $threads->revert();
-            }
+        else if ($this->sort_order != $threads->get_parameters('ORDER')) {
+            $threads->revert();
         }
     }
 
@@ -1634,9 +1620,15 @@
         // Example of structure for malformed MIME message:
         // ("text" "plain" NIL NIL NIL "7bit" 2154 70 NIL NIL NIL)
         if ($headers->ctype && !is_array($structure[0]) && $headers->ctype != 'text/plain'
-            && strtolower($structure[0].'/'.$structure[1]) == 'text/plain') {
+            && strtolower($structure[0].'/'.$structure[1]) == 'text/plain'
+        ) {
+            // A special known case "Content-type: text" (#1488968)
+            if ($headers->ctype == 'text') {
+                $structure[1]   = 'plain';
+                $headers->ctype = 'text/plain';
+            }
             // we can handle single-part messages, by simple fix in structure (#1486898)
-            if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) {
+            else if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) {
                 $structure[0] = $m[1];
                 $structure[1] = $m[2];
             }
@@ -1660,11 +1652,21 @@
             $struct = $this->structure_part($structure, 0, '', $headers);
         }
 
-        // don't trust given content-type
-        if (empty($struct->parts) && !empty($headers->ctype)) {
-            $struct->mime_id = '1';
-            $struct->mimetype = strtolower($headers->ctype);
-            list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
+        // some workarounds on simple messages...
+        if (empty($struct->parts)) {
+            // ...don't trust given content-type
+            if (!empty($headers->ctype)) {
+                $struct->mime_id  = '1';
+                $struct->mimetype = strtolower($headers->ctype);
+                list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
+            }
+
+            // ...and charset (there's a case described in #1488968 where invalid content-type
+            // results in invalid charset in BODYSTRUCTURE)
+            if (!empty($headers->charset) && $headers->charset != $struct->ctype_parameters['charset']) {
+                $struct->charset                     = $headers->charset;
+                $struct->ctype_parameters['charset'] = $headers->charset;
+            }
         }
 
         $headers->structure = $struct;
@@ -2051,10 +2053,11 @@
      * @param  mixed              $print  True to print part, ressource to write part contents in
      * @param  resource           $fp     File pointer to save the message part
      * @param  boolean            $skip_charset_conv Disables charset conversion
+     * @param  int                $max_bytes  Only read this number of bytes
      *
      * @return string Message/part body if not printed
      */
-    public function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false)
+    public function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false, $max_bytes=0)
     {
         if (!$this->check_connection()) {
             return null;
@@ -2074,7 +2077,7 @@
 
         if ($o_part && $o_part->size) {
             $body = $this->conn->handlePartBody($this->folder, $uid, true,
-                $part ? $part : 'TEXT', $o_part->encoding, $print, $fp, $o_part->ctype_primary == 'text');
+                $part ? $part : 'TEXT', $o_part->encoding, $print, $fp, $o_part->ctype_primary == 'text', $max_bytes);
         }
 
         if ($fp || $print) {
@@ -2188,10 +2191,10 @@
             $result = $this->conn->flag($folder, $uids, $flag);
         }
 
-        if ($result) {
+        if ($result && !$skip_cache) {
             // reload message headers if cached
-            // @TODO: update flags instead removing from cache
-            if (!$skip_cache && ($mcache = $this->get_mcache_engine())) {
+            // update flags instead removing from cache
+            if ($mcache = $this->get_mcache_engine()) {
                 $status = strpos($flag, 'UN') !== 0;
                 $mflag  = preg_replace('/^UN/', '', $flag);
                 $mcache->change_flag($folder, $all_mode ? null : explode(',', $uids),
@@ -2203,8 +2206,12 @@
                 $this->clear_messagecount($folder, 'SEEN');
                 $this->clear_messagecount($folder, 'UNSEEN');
             }
-            else if ($flag == 'DELETED') {
+            else if ($flag == 'DELETED' || $flag == 'UNDELETED') {
                 $this->clear_messagecount($folder, 'DELETED');
+                // remove cached messages
+                if ($this->options['skip_deleted']) {
+                    $this->clear_message_cache($folder, $all_mode ? null : explode(',', $uids));
+                }
             }
         }
 
@@ -2221,10 +2228,11 @@
      * @param boolean $is_file True if $message is a filename
      * @param array   $flags   Message flags
      * @param mixed   $date    Message internal date
+     * @param bool    $binary  Enables BINARY append
      *
      * @return int|bool Appended message UID or True on success, False on error
      */
-    public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null)
+    public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null, $binary = false)
     {
         if (!strlen($folder)) {
             $folder = $this->folder;
@@ -2242,10 +2250,10 @@
         $date = $this->date_format($date);
 
         if ($is_file) {
-            $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date);
+            $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date, $binary);
         }
         else {
-            $saved = $this->conn->append($folder, $message, $flags, $date);
+            $saved = $this->conn->append($folder, $message, $flags, $date, $binary);
         }
 
         if ($saved) {
@@ -2704,7 +2712,7 @@
 
         // filter folders list according to rights requirements
         if ($rights && $this->get_capability('ACL')) {
-            $a_folders = $this->filter_rights($a_folders, $rights);
+            $a_mboxes = $this->filter_rights($a_mboxes, $rights);
         }
 
         // filter folders and sort them
@@ -3349,7 +3357,6 @@
     {
         if (!empty($this->options['fetch_headers'])) {
             $headers = explode(' ', $this->options['fetch_headers']);
-            $headers = array_map('strtoupper', $headers);
         }
         else {
             $headers = array();
@@ -3359,7 +3366,7 @@
             $headers = array_merge($headers, $this->all_headers);
         }
 
-        return implode(' ', array_unique($headers));
+        return $headers;
     }
 
 

--
Gitblit v1.9.1