From ab0b51a1fef87bcc643c3aaf2e635c811b28ccd8 Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Tue, 15 Feb 2011 06:10:59 -0500
Subject: [PATCH] - Use only one from IMAP authentication methods to prevent login delays (1487784)

---
 program/include/rcube_imap_generic.php |  310 ++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 218 insertions(+), 92 deletions(-)

diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php
index 512e7e4..cc590e0 100644
--- a/program/include/rcube_imap_generic.php
+++ b/program/include/rcube_imap_generic.php
@@ -5,7 +5,7 @@
  | program/include/rcube_imap_generic.php                                |
  |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2005-2010, Roundcube Dev. - Switzerland                 |
+ | Copyright (C) 2005-2010, The Roundcube Dev Team                       |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -109,6 +109,8 @@
     private $prefs;
     private $cmd_tag;
     private $cmd_num = 0;
+    private $_debug = false;
+    private $_debug_handler = false;
 
     const ERROR_OK = 0;
     const ERROR_NO = -1;
@@ -142,8 +144,8 @@
         if (!$this->fp)
             return false;
 
-        if (!empty($this->prefs['debug_mode'])) {
-            write_log('imap', 'C: '. rtrim($string));
+        if ($this->_debug) {
+            $this->debug('C: '. rtrim($string));
         }
 
         $res = fwrite($this->fp, $string . ($endln ? "\r\n" : ''));
@@ -231,8 +233,8 @@
                 $this->fp = null;
                 break;
             }
-            if (!empty($this->prefs['debug_mode'])) {
-                write_log('imap', 'S: '. rtrim($buffer));
+            if ($this->_debug) {
+                $this->debug('S: '. rtrim($buffer));
             }
             $line .= $buffer;
         } while ($buffer[strlen($buffer)-1] != "\n");
@@ -268,8 +270,8 @@
         while ($len < $bytes && !feof($this->fp))
         {
             $d = fread($this->fp, $bytes-$len);
-            if (!empty($this->prefs['debug_mode'])) {
-                write_log('imap', 'S: '. $d);
+            if ($this->_debug) {
+                $this->debug('S: '. $d);
             }
             $data .= $d;
             $data_len = strlen($data);
@@ -369,10 +371,43 @@
         return false;
     }
 
-    function getCapability($name)
+    private function hasCapability($name)
     {
+        if (empty($this->capability) || $name == '') {
+            return false;
+        }
+
         if (in_array($name, $this->capability)) {
             return true;
+        }
+        else if (strpos($name, '=')) {
+            return false;
+        }
+
+        $result = array();
+        foreach ($this->capability as $cap) {
+            $entry = explode('=', $cap);
+            if ($entry[0] == $name) {
+                $result[] = $entry[1];
+            }
+        }
+
+        return !empty($result) ? $result : false;
+    }
+
+    /**
+     * Capabilities checker
+     *
+     * @param string $name Capability name
+     *
+     * @return mixed Capability values array for key=value pairs, true/false for others
+     */
+    function getCapability($name)
+    {
+        $result = $this->hasCapability($name);
+
+        if (!empty($result)) {
+            return $result;
         }
         else if ($this->capability_readed) {
             return false;
@@ -388,11 +423,7 @@
 
         $this->capability_readed = true;
 
-        if (in_array($name, $this->capability)) {
-            return true;
-        }
-
-        return false;
+        return $this->hasCapability($name);
     }
 
     function clearCapability()
@@ -686,8 +717,8 @@
 
         $line = trim(fgets($this->fp, 8192));
 
-        if ($this->prefs['debug_mode'] && $line) {
-            write_log('imap', 'S: '. $line);
+        if ($this->_debug && $line) {
+            $this->debug('S: '. $line);
         }
 
         // Connected to wrong port or connection error?
@@ -728,23 +759,35 @@
             }
         }
 
+        // Send ID info
+        if (!empty($this->prefs['ident']) && $this->getCapability('ID')) {
+            $this->id($this->prefs['ident']);
+        }
+
         $auth_methods = array();
         $result       = null;
 
         // check for supported auth methods
         if ($auth_method == 'CHECK') {
-            if ($this->getCapability('AUTH=DIGEST-MD5')) {
-                $auth_methods[] = 'DIGEST-MD5';
-            }
-            if ($this->getCapability('AUTH=CRAM-MD5') || $this->getCapability('AUTH=CRAM_MD5')) {
-                $auth_methods[] = 'CRAM-MD5';
-            }
-            if ($this->getCapability('AUTH=PLAIN')) {
-                $auth_methods[] = 'PLAIN';
+            if ($auth_caps = $this->getCapability('AUTH')) {
+                $auth_methods = $auth_caps;
             }
             // RFC 2595 (LOGINDISABLED) LOGIN disabled when connection is not secure
-            if (!$this->getCapability('LOGINDISABLED')) {
+            $login_disabled = $this->getCapability('LOGINDISABLED');
+            if (($key = array_search('LOGIN', $auth_methods)) !== false) {
+                if ($login_disabled) {
+                    unset($auth_methods[$key]);
+                }
+            }
+            else if (!$login_disabled) {
                 $auth_methods[] = 'LOGIN';
+            }
+
+            // Use best (for security) supported authentication method
+            foreach (array('DIGEST-MD5', 'CRAM-MD5', 'CRAM_MD5', 'PLAIN', 'LOGIN') as $auth_method) {
+                if (in_array($auth_method, $auth_methods)) {
+                    break;
+                }
             }
         }
         else {
@@ -755,30 +798,28 @@
                 return false;
             }
             // replace AUTH with CRAM-MD5 for backward compat.
-            $auth_methods[] = $auth_method == 'AUTH' ? 'CRAM-MD5' : $auth_method;
+            if ($auth_method == 'AUTH') {
+                $auth_method = 'CRAM-MD5';
+            }
         }
 
         // pre-login capabilities can be not complete
         $this->capability_readed = false;
 
         // Authenticate
-        foreach ($auth_methods as $method) {
-            switch ($method) {
-            case 'DIGEST-MD5':
+        switch ($auth_method) {
+            case 'CRAM_MD5':
+                $auth_method = 'CRAM-MD5';
             case 'CRAM-MD5':
+            case 'DIGEST-MD5':
             case 'PLAIN':
-                $result = $this->authenticate($user, $password, $method);
+                $result = $this->authenticate($user, $password, $auth_method);
                 break;
             case 'LOGIN':
                 $result = $this->login($user, $password);
                 break;
             default:
-                $this->setError(self::ERROR_BAD, "Configuration error. Unknown auth method: $method");
-            }
-
-            if (is_resource($result)) {
-                break;
-            }
+                $this->setError(self::ERROR_BAD, "Configuration error. Unknown auth method: $auth_method");
         }
 
         // Connected and authenticated
@@ -1124,6 +1165,44 @@
         return false;
     }
 
+    /**
+     * Executes ID command (RFC2971)
+     *
+     * @param array $items Client identification information key/value hash
+     *
+     * @return array Server identification information key/value hash
+     * @access public
+     * @since 0.6
+     */
+    function id($items=array())
+    {
+        if (is_array($items) && !empty($items)) {
+            foreach ($items as $key => $value) {
+                $args[] = $this->escape($key);
+                $args[] = $this->escape($value);
+            }
+        }
+
+        list($code, $response) = $this->execute('ID', array(
+            !empty($args) ? '(' . implode(' ', (array) $args) . ')' : $this->escape(null)
+        ));
+
+
+        if ($code == self::ERROR_OK && preg_match('/\* ID /i', $response)) {
+            $response = substr($response, 5); // remove prefix "* ID "
+            $items    = $this->tokenizeResponse($response);
+            $result   = null;
+
+            for ($i=0, $len=count($items); $i<$len; $i += 2) {
+                $result[$items[$i]] = $items[$i+1];
+            }
+
+            return $result;
+        }
+
+        return false;
+    }
+
     function sort($mailbox, $field, $add='', $is_uid=FALSE, $encoding = 'US-ASCII')
     {
         $field = strtoupper($field);
@@ -1461,7 +1540,7 @@
                 // INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...)
                 // BODY[HEADER.FIELDS ...
 
-                if (preg_match('/^\* [0-9]+ FETCH \((.*) BODY/s', $line, $matches)) {
+                if (preg_match('/^\* [0-9]+ FETCH \((.*) BODY/sU', $line, $matches)) {
                     $str = $matches[1];
 
                     // swap parents with quotes, then explode
@@ -1498,7 +1577,7 @@
 
                     // BODYSTRUCTURE
                     if ($bodystr) {
-                        while (!preg_match('/ BODYSTRUCTURE (.*) BODY\[HEADER.FIELDS/s', $line, $m)) {
+                        while (!preg_match('/ BODYSTRUCTURE (.*) BODY\[HEADER.FIELDS/sU', $line, $m)) {
                             $line2 = $this->readLine(1024);
                             $line .= $this->multLine($line2, true);
                         }
@@ -1598,7 +1677,7 @@
                         break;
                         case 'content-type':
                             $ctype_parts = preg_split('/[; ]/', $string);
-                            $result[$id]->ctype = array_shift($ctype_parts);
+                            $result[$id]->ctype = strtolower(array_shift($ctype_parts));
                             if (preg_match('/charset\s*=\s*"?([a-z0-9\-\.\_]+)"?/i', $string, $regs)) {
                                 $result[$id]->charset = $regs[1];
                             }
@@ -2604,13 +2683,13 @@
      */
     function getACL($mailbox)
     {
-        list($code, $response) = $this->execute('GETACL', $this->escape($mailbox));
+        list($code, $response) = $this->execute('GETACL', array($this->escape($mailbox)));
 
         if ($code == self::ERROR_OK && preg_match('/^\* ACL /i', $response)) {
             // Parse server response (remove "* ACL ")
             $response = substr($response, 6);
             $ret  = $this->tokenizeResponse($response);
-            $mbox = array_unshift($ret);
+            $mbox = array_shift($ret);
             $size = count($ret);
 
             // Create user-rights hash array
@@ -2677,7 +2756,7 @@
      */
     function myRights($mailbox)
     {
-        list($code, $response) = $this->execute('MYRIGHTS', array($this->escape(mailbox)));
+        list($code, $response) = $this->execute('MYRIGHTS', array($this->escape($mailbox)));
 
         if ($code == self::ERROR_OK && preg_match('/^\* MYRIGHTS /i', $response)) {
             // Parse server response (remove "* MYRIGHTS ")
@@ -2804,37 +2883,46 @@
         list($code, $response) = $this->execute('GETMETADATA', array(
             $this->escape($mailbox), $optlist));
 
-        if ($code == self::ERROR_OK && preg_match('/^\* METADATA /i', $response)) {
-            // Parse server response (remove "* METADATA ")
-            $response = substr($response, 11);
-            $ret_mbox = $this->tokenizeResponse($response, 1);
-            $data     = $this->tokenizeResponse($response);
+        if ($code == self::ERROR_OK) {
+            $result = array();
+            $data   = $this->tokenizeResponse($response);
 
             // The METADATA response can contain multiple entries in a single
             // response or multiple responses for each entry or group of entries
             if (!empty($data) && ($size = count($data))) {
                 for ($i=0; $i<$size; $i++) {
-                    if (is_array($data[$i])) {
+                    if (isset($mbox) && is_array($data[$i])) {
                         $size_sub = count($data[$i]);
                         for ($x=0; $x<$size_sub; $x++) {
-                            $data[$data[$i][$x]] = $data[$i][++$x];
+                            $result[$mbox][$data[$i][$x]] = $data[$i][++$x];
                         }
                         unset($data[$i]);
                     }
-                    else if ($data[$i] == '*' && $data[$i+1] == 'METADATA') {
-                        unset($data[$i]);   // "*"
-                        unset($data[++$i]); // "METADATA"
-                        unset($data[++$i]); // Mailbox
+                    else if ($data[$i] == '*') {
+                        if ($data[$i+1] == 'METADATA') {
+                            $mbox = $data[$i+2];
+                            unset($data[$i]);   // "*"
+                            unset($data[++$i]); // "METADATA"
+                            unset($data[++$i]); // Mailbox
+                        }
+                        // get rid of other untagged responses
+                        else {
+                            unset($mbox);
+                            unset($data[$i]);
+                        }
                     }
-                    else {
-                        $data[$data[$i]] = $data[++$i];
+                    else if (isset($mbox)) {
+                        $result[$mbox][$data[$i]] = $data[++$i];
                         unset($data[$i]);
                         unset($data[$i-1]);
+                    }
+                    else {
+                        unset($data[$i]);
                     }
                 }
             }
 
-            return $data;
+            return $result;
         }
 
         return NULL;
@@ -2870,8 +2958,9 @@
                 $value = sprintf("{%d}\r\n%s", strlen($value), $value);
             }
 
+            // ANNOTATEMORE drafts before version 08 require quoted parameters
             $entries[] = sprintf('%s (%s %s)',
-                $this->escape($name), $this->escape($attr), $value);
+                $this->escape($name, true), $this->escape($attr, true), $value);
         }
 
         $entries = implode(' ', $entries);
@@ -2921,8 +3010,9 @@
             $entries = array($entries);
         }
         // create entries string
+        // ANNOTATEMORE drafts before version 08 require quoted parameters
         foreach ($entries as $idx => $name) {
-            $entries[$idx] = $this->escape($name);
+            $entries[$idx] = $this->escape($name, true);
         }
         $entries = '(' . implode(' ', $entries) . ')';
 
@@ -2931,50 +3021,65 @@
         }
         // create entries string
         foreach ($attribs as $idx => $name) {
-            $attribs[$idx] = $this->escape($name);
+            $attribs[$idx] = $this->escape($name, true);
         }
         $attribs = '(' . implode(' ', $attribs) . ')';
 
         list($code, $response) = $this->execute('GETANNOTATION', array(
             $this->escape($mailbox), $entries, $attribs));
 
-        if ($code == self::ERROR_OK && preg_match('/^\* ANNOTATION /i', $response)) {
-            // Parse server response (remove "* ANNOTATION ")
-            $response = substr($response, 13);
-            $ret_mbox = $this->tokenizeResponse($response, 1);
-            $data     = $this->tokenizeResponse($response);
-            $res      = array();
+        if ($code == self::ERROR_OK) {
+            $result = array();
+            $data   = $this->tokenizeResponse($response);
 
             // Here we returns only data compatible with METADATA result format
             if (!empty($data) && ($size = count($data))) {
                 for ($i=0; $i<$size; $i++) {
-                    $entry = $data[$i++];
-                    if (is_array($entry)) {
+                    $entry = $data[$i];
+                    if (isset($mbox) && is_array($entry)) {
                         $attribs = $entry;
                         $entry   = $last_entry;
                     }
-                    else
-                        $attribs = $data[$i++];
+                    else if ($entry == '*') {
+                        if ($data[$i+1] == 'ANNOTATION') {
+                            $mbox = $data[$i+2];
+                            unset($data[$i]);   // "*"
+                            unset($data[++$i]); // "ANNOTATION"
+                            unset($data[++$i]); // Mailbox
+                        }
+                        // get rid of other untagged responses
+                        else {
+                            unset($mbox);
+                            unset($data[$i]);
+                        }
+                        continue;
+                    }
+                    else if (isset($mbox)) {
+                        $attribs = $data[++$i];
+                    }
+                    else {
+                        unset($data[$i]);
+                        continue;
+                    }
 
                     if (!empty($attribs)) {
                         for ($x=0, $len=count($attribs); $x<$len;) {
                             $attr  = $attribs[$x++];
                             $value = $attribs[$x++];
                             if ($attr == 'value.priv') {
-                                $res['/private' . $entry] = $value;
+                                $result[$mbox]['/private' . $entry] = $value;
                             }
                             else if ($attr == 'value.shared') {
-                                $res['/shared' . $entry] = $value;
+                                $result[$mbox]['/shared' . $entry] = $value;
                             }
                         }
                     }
                     $last_entry = $entry;
-                    unset($data[$i-1]);
-                    unset($data[$i-2]);
+                    unset($data[$i]);
                 }
             }
 
-            return $res;
+            return $result;
         }
 
         return NULL;
@@ -3164,21 +3269,7 @@
      */
     private function strToTime($date)
     {
-        // support non-standard "GMTXXXX" literal
-        $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
-        // if date parsing fails, we have a date in non-rfc format.
-        // remove token from the end and try again
-        while ((($ts = @strtotime($date))===false) || ($ts < 0)) {
-            $d = explode(' ', $date);
-            array_pop($d);
-            if (!$d) {
-                break;
-            }
-            $date = implode(' ', $d);
-        }
-
-        $ts = (int) $ts;
-
+        $ts = (int) rcube_strtotime($date);
         return $ts < 0 ? 0 : $ts;
     }
 
@@ -3211,12 +3302,13 @@
     /**
      * Escapes a string when it contains special characters (RFC3501)
      *
-     * @param string $string IMAP string
+     * @param string  $string       IMAP string
+     * @param boolean $force_quotes Forces string quoting
      *
      * @return string Escaped string
      * @todo String literals, lists
      */
-    static function escape($string)
+    static function escape($string, $force_quotes=false)
     {
         if ($string === null) {
             return 'NIL';
@@ -3224,8 +3316,11 @@
         else if ($string === '') {
             return '""';
         }
-        else if (preg_match('/([\x00-\x20\x28-\x29\x7B\x25\x2A\x22\x5C\x5D\x7F]+)/', $string)) {
-            // string: special chars: SP, CTL, (, ), {, %, *, ", \, ]
+        // need quoted-string? find special chars: SP, CTL, (, ), {, %, *, ", \, ]
+        // plus [ character as a workaround for DBMail's bug (#1487766)
+        else if ($force_quotes ||
+            preg_match('/([\x00-\x20\x28-\x29\x7B\x25\x2A\x22\x5B\x5C\x5D\x7F]+)/', $string)
+        ) {
             return '"' . strtr($string, array('"'=>'\\"', '\\' => '\\\\')) . '"';
         }
 
@@ -3238,4 +3333,35 @@
         return strtr($string, array('\\"'=>'"', '\\\\' => '\\'));
     }
 
+    /**
+     * Set the value of the debugging flag.
+     *
+     * @param   boolean $debug      New value for the debugging flag.
+     *
+     * @access  public
+     * @since   0.5-stable
+     */
+    function setDebug($debug, $handler = null)
+    {
+        $this->_debug = $debug;
+        $this->_debug_handler = $handler;
+    }
+
+    /**
+     * Write the given debug text to the current debug output handler.
+     *
+     * @param   string  $message    Debug mesage text.
+     *
+     * @access  private
+     * @since   0.5-stable
+     */
+    private function debug($message)
+    {
+        if ($this->_debug_handler) {
+            call_user_func_array($this->_debug_handler, array(&$this, $message));
+        } else {
+            echo "DEBUG: $message\n";
+        }
+    }
+
 }

--
Gitblit v1.9.1