From 3e398182213cb66057ff40b605296092bb9fe83d Mon Sep 17 00:00:00 2001 From: alecpl <alec@alec.pl> Date: Wed, 09 Mar 2011 05:30:15 -0500 Subject: [PATCH] - Add code for prevention from IMAP connection hangs when server closes socket unexpectedly --- program/include/rcube_imap_generic.php | 138 ++++++++++++++++++++++++++++++++++------------ 1 files changed, 102 insertions(+), 36 deletions(-) diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php index e8b1fd4..f21b6b0 100644 --- a/program/include/rcube_imap_generic.php +++ b/program/include/rcube_imap_generic.php @@ -213,31 +213,26 @@ { $line = ''; - if (!$this->fp) { - return NULL; - } - if (!$size) { $size = 1024; } do { - if (feof($this->fp)) { + if ($this->eof()) { return $line ? $line : NULL; } $buffer = fgets($this->fp, $size); if ($buffer === false) { - @fclose($this->fp); - $this->fp = null; + $this->closeSocket(); break; } if ($this->_debug) { $this->debug('S: '. rtrim($buffer)); } $line .= $buffer; - } while ($buffer[strlen($buffer)-1] != "\n"); + } while (substr($buffer, -1) != "\n"); return $line; } @@ -267,7 +262,7 @@ { $data = ''; $len = 0; - while ($len < $bytes && !feof($this->fp)) + while ($len < $bytes && !$this->eof()) { $d = fread($this->fp, $bytes-$len); if ($this->_debug) { @@ -312,8 +307,7 @@ } else if ($res == 'BAD') { $this->errornum = self::ERROR_BAD; } else if ($res == 'BYE') { - @fclose($this->fp); - $this->fp = null; + $this->closeSocket(); $this->errornum = self::ERROR_BYE; } @@ -339,6 +333,32 @@ return self::ERROR_UNKNOWN; } + private function eof() + { + if (!is_resource($this->fp)) { + return true; + } + + // If a connection opened by fsockopen() wasn't closed + // by the server, feof() will hang. + $start = microtime(true); + + if (feof($this->fp) || + ($this->prefs['timeout'] && (microtime(true) - $start > $this->prefs['timeout'])) + ) { + $this->closeSocket(); + return true; + } + + return false; + } + + private function closeSocket() + { + @fclose($this->fp); + $this->fp = null; + } + function setError($code, $msg='') { $this->errornum = $code; @@ -360,8 +380,7 @@ } if ($error && preg_match('/^\* (BYE|BAD) /i', $string, $m)) { if (strtoupper($m[1]) == 'BYE') { - @fclose($this->fp); - $this->fp = null; + $this->closeSocket(); } return true; } @@ -701,11 +720,12 @@ $host = $this->prefs['ssl_mode'] . '://' . $host; } + if ($this->prefs['timeout'] <= 0) { + $this->prefs['timeout'] = ini_get('default_socket_timeout'); + } + // Connect - if ($this->prefs['timeout'] > 0) - $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']); - else - $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr); + $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']); if (!$this->fp) { $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr)); @@ -759,6 +779,11 @@ } } + // Send ID info + if (!empty($this->prefs['ident']) && $this->getCapability('ID')) { + $this->id($this->prefs['ident']); + } + $auth_methods = array(); $result = null; @@ -777,6 +802,13 @@ 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 { // Prevent from sending credentials in plain text when connection is not secure @@ -786,32 +818,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) { + switch ($auth_method) { case 'CRAM_MD5': - $method = '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 @@ -840,8 +868,7 @@ $this->readReply(); } - @fclose($this->fp); - $this->fp = false; + $this->closeSocket(); } /** @@ -1157,6 +1184,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); @@ -1298,7 +1363,7 @@ $result[$id] = ''; } } else if ($mode == 2) { - if (preg_match('/\((UID|RFC822\.SIZE) ([0-9]+)/', $line, $matches)) { + if (preg_match('/(UID|RFC822\.SIZE) ([0-9]+)/', $line, $matches)) { $result[$id] = trim($matches[2]); } else { $result[$id] = 0; @@ -1631,7 +1696,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]; } @@ -3270,11 +3335,12 @@ else if ($string === '') { return '""'; } + // 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\x5C\x5D\x7F]+)/', $string) + preg_match('/([\x00-\x20\x28-\x29\x7B\x25\x2A\x22\x5B\x5C\x5D\x7F]+)/', $string) ) { - // string: special chars: SP, CTL, (, ), {, %, *, ", \, ] - return '"' . strtr($string, array('"'=>'\\"', '\\' => '\\\\')) . '"'; + return '"' . addcslashes($string, '\\"') . '"'; } // atom @@ -3283,7 +3349,7 @@ static function unEscape($string) { - return strtr($string, array('\\"'=>'"', '\\\\' => '\\')); + return stripslashes($string); } /** -- Gitblit v1.9.1