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) --- plugins/managesieve/lib/Net/Sieve.php | 166 ++++++++++++++++++++++++++++++++++++++----------------- 1 files changed, 115 insertions(+), 51 deletions(-) diff --git a/plugins/managesieve/lib/Net/Sieve.php b/plugins/managesieve/lib/Net/Sieve.php index b2549ee..8a0a9b0 100644 --- a/plugins/managesieve/lib/Net/Sieve.php +++ b/plugins/managesieve/lib/Net/Sieve.php @@ -39,7 +39,7 @@ * @copyright 2002-2003 Richard Heyes * @copyright 2006-2008 Anish Mistry * @license http://www.opensource.org/licenses/bsd-license.php BSD - * @version SVN: $Id: Sieve.php 289313 2009-10-07 22:26:33Z yunosh $ + * @version SVN: $Id: Sieve.php 300898 2010-07-01 09:49:02Z yunosh $ * @link http://pear.php.net/package/Net_Sieve */ @@ -83,7 +83,7 @@ * @copyright 2002-2003 Richard Heyes * @copyright 2006-2008 Anish Mistry * @license http://www.opensource.org/licenses/bsd-license.php BSD - * @version Release: @package_version@ + * @version Release: 1.3.0 * @link http://pear.php.net/package/Net_Sieve * @link http://www.ietf.org/rfc/rfc3028.txt RFC 3028 (Sieve: A Mail * Filtering Language) @@ -107,7 +107,7 @@ * * @var array */ - var $_supportedSASLAuthMethods = array('DIGEST-MD5', 'CRAM-MD5'); + var $supportedSASLAuthMethods = array('DIGEST-MD5', 'CRAM-MD5'); /** * The socket handle. @@ -204,11 +204,13 @@ * @param boolean $useTLS Use TLS if available. * @param array $options Additional options for * stream_context_create(). + * @param mixed $handler A callback handler for the debug output. */ function Net_Sieve($user = null, $pass = null, $host = 'localhost', - $port = 2000, $logintype = '', $euser = '', $debug = false, - $bypassAuth = false, $useTLS = true, $options = null - ) { + $port = 2000, $logintype = '', $euser = '', + $debug = false, $bypassAuth = false, $useTLS = true, + $options = null, $handler = null) + { $this->_state = NET_SIEVE_STATE_DISCONNECTED; $this->_data['user'] = $user; $this->_data['pass'] = $pass; @@ -217,10 +219,10 @@ $this->_data['logintype'] = $logintype; $this->_data['euser'] = $euser; $this->_sock = new Net_Socket(); - $this->_debug = $debug; $this->_bypassAuth = $bypassAuth; $this->_useTLS = $useTLS; $this->_options = $options; + $this->setDebug($debug, $handler); /* Try to include the Auth_SASL package. If the package is not * available, we disable the authentication methods that depend upon @@ -294,6 +296,13 @@ */ function connect($host, $port, $options = null, $useTLS = true) { + $this->_data['host'] = $host; + $this->_data['port'] = $port; + $this->_useTLS = $useTLS; + if (!empty($options) && is_array($options)) { + $this->_options = array_merge($this->_options, $options); + } + if (NET_SIEVE_STATE_DISCONNECTED != $this->_state) { return PEAR::raiseError('Not currently in DISCONNECTED state', 1); } @@ -357,6 +366,12 @@ */ function login($user, $pass, $logintype = null, $euser = '', $bypassAuth = false) { + $this->_data['user'] = $user; + $this->_data['pass'] = $pass; + $this->_data['logintype'] = $logintype; + $this->_data['euser'] = $euser; + $this->_bypassAuth = $bypassAuth; + if (NET_SIEVE_STATE_AUTHORISATION != $this->_state) { return PEAR::raiseError('Not currently in AUTHORISATION state', 1); } @@ -473,7 +488,9 @@ if (NET_SIEVE_STATE_TRANSACTION != $this->_state) { return PEAR::raiseError('Not currently in TRANSACTION state', 1); } - if (PEAR::isError($res = $this->_doCmd(sprintf('HAVESPACE "%s" %d', $scriptname, $size)))) { + + $command = sprintf('HAVESPACE %s %d', $this->_escape($scriptname), $size); + if (PEAR::isError($res = $this->_doCmd($command))) { return $res; } return true; @@ -635,10 +652,10 @@ if (PEAR::isError($result = $this->_sendCmd('AUTHENTICATE "LOGIN"'))) { return $result; } - if (PEAR::isError($result = $this->_doCmd('"' . base64_encode($user) . '"'))) { + if (PEAR::isError($result = $this->_doCmd('"' . base64_encode($user) . '"', true))) { return $result; } - return $this->_doCmd('"' . base64_encode($pass) . '"'); + return $this->_doCmd('"' . base64_encode($pass) . '"', true); } /** @@ -687,10 +704,10 @@ return $response; } - if (PEAR::isError($result = $this->_sendStringResponse(base64_encode($param)))) { + if (PEAR::isError($result = $this->_sendStringResponse(base64_encode($response)))) { return $result; } - if (PEAR::isError($result = $this->_doCmd())) { + if (PEAR::isError($result = $this->_doCmd('', true))) { return $result; } if ($this->_toUpper(substr($result, 0, 2)) == 'OK') { @@ -738,7 +755,9 @@ if (NET_SIEVE_STATE_TRANSACTION != $this->_state) { return PEAR::raiseError('Not currently in AUTHORISATION state', 1); } - if (PEAR::isError($res = $this->_doCmd(sprintf('DELETESCRIPT "%s"', $scriptname)))) { + + $command = sprintf('DELETESCRIPT %s', $this->_escape($scriptname)); + if (PEAR::isError($res = $this->_doCmd($command))) { return $res; } return true; @@ -757,11 +776,12 @@ return PEAR::raiseError('Not currently in AUTHORISATION state', 1); } - if (PEAR::isError($res = $this->_doCmd(sprintf('GETSCRIPT "%s"', $scriptname)))) { + $command = sprintf('GETSCRIPT %s', $this->_escape($scriptname)); + if (PEAR::isError($res = $this->_doCmd($command))) { return $res; } - return preg_replace('/{[0-9]+}\r\n/', '', $res); + return preg_replace('/^{[0-9]+}\r\n/', '', $res); } /** @@ -777,9 +797,12 @@ if (NET_SIEVE_STATE_TRANSACTION != $this->_state) { return PEAR::raiseError('Not currently in AUTHORISATION state', 1); } - if (PEAR::isError($res = $this->_doCmd(sprintf('SETACTIVE "%s"', $scriptname)))) { + + $command = sprintf('SETACTIVE %s', $this->_escape($scriptname)); + if (PEAR::isError($res = $this->_doCmd($command))) { return $res; } + $this->_activeScript = $scriptname; return true; } @@ -806,9 +829,10 @@ $res = explode("\r\n", $res); foreach ($res as $value) { if (preg_match('/^"(.*)"( ACTIVE)?$/i', $value, $matches)) { - $scripts[] = $matches[1]; + $script_name = stripslashes($matches[1]); + $scripts[] = $script_name; if (!empty($matches[2])) { - $activescript = $matches[1]; + $activescript = $script_name; } } } @@ -831,8 +855,10 @@ } $stringLength = $this->_getLineLength($scriptdata); + $command = sprintf("PUTSCRIPT %s {%d+}\r\n%s", + $this->_escape($scriptname), $stringLength, $scriptdata); - if (PEAR::isError($res = $this->_doCmd(sprintf("PUTSCRIPT \"%s\" {%d+}\r\n%s", $scriptname, $stringLength, $scriptdata)))) { + if (PEAR::isError($res = $this->_doCmd($command))) { return $res; } @@ -979,6 +1005,28 @@ } /** + * Receives x bytes from the server. + * + * @param int $length Number of bytes to read + * + * @return string The server response. + */ + function _recvBytes($length) + { + $response = ''; + $response_length = 0; + + while ($response_length < $length) { + $response .= $this->_sock->read($length - $response_length); + $response_length = $this->_getLineLength($response); + } + + $this->_debug("S: " . rtrim($response)); + + return $response; + } + + /** * Send a command and retrieves a response from the server. * * @param string $cmd The command to send. @@ -1011,11 +1059,11 @@ if ('NO' == substr($uc_line, 0, 2)) { // Check for string literal error message. - if (preg_match('/^no {([0-9]+)\+?}/i', $line, $matches)) { - $line .= str_replace( - "\r\n", ' ', $this->_sock->read($matches[1] + 2) - ); - $this->_debug("S: $line"); + if (preg_match('/{([0-9]+)}$/i', $line, $matches)) { + $line = substr($line, 0, -(strlen($matches[1])+2)) + . str_replace( + "\r\n", ' ', $this->_recvBytes($matches[1] + 2) + ); } return PEAR::raiseError(trim($response . substr($line, 2)), 3); } @@ -1050,16 +1098,11 @@ return PEAR::raiseError(trim($response . $line), 6); } + // "\+?" is added in the regexp to workaround DBMail bug + // http://dbmail.org/mantis/view.php?id=963 if (preg_match('/^{([0-9]+)\+?}/i', $line, $matches)) { - // Matches String Responses. - $str_size = $matches[1] + 2; - $line = ''; - $line_length = 0; - while ($line_length < $str_size) { - $line .= $this->_sock->read($str_size - $line_length); - $line_length = $this->_getLineLength($line); - } - $this->_debug("S: $line"); + // Matches literal string responses. + $line = $this->_recvBytes($matches[1] + 2); if (!$auth) { // Receive the pending OK only if we aren't @@ -1099,33 +1142,30 @@ if (!isset($this->_capability['sasl'])) { return PEAR::raiseError('This server doesn\'t support any authentication methods. SASL problem?'); } - - $serverMethods = $this->_capability['sasl']; + if (!$this->_capability['sasl']) { + return PEAR::raiseError('This server doesn\'t support any authentication methods.'); + } if ($userMethod) { - $methods = array($userMethod); - } else { - $methods = $this->supportedAuthMethods; - } - - if (!$methods || !$serverMethods) { + if (in_array($userMethod, $this->_capability['sasl'])) { + return $userMethod; + } return PEAR::raiseError( - 'This server doesn\'t support any authentication methods.' - ); + sprintf('No supported authentication method found. The server supports these methods: %s, but we want to use: %s', + implode(', ', $this->_capability['sasl']), + $userMethod)); } - foreach ($methods as $method) { - if (in_array($method, $serverMethods)) { + foreach ($this->supportedAuthMethods as $method) { + if (in_array($method, $this->_capability['sasl'])) { return $method; } } return PEAR::raiseError( - 'No supported authentication method found. The server supports these methods: ' - . implode(',', $serverMethods) - . ', but we only support: ' - . implode(',', $this->supportedAuthMethods) - ); + sprintf('No supported authentication method found. The server supports these methods: %s, but we only support: %s', + implode(', ', $this->_capability['sasl']), + implode(', ', $this->supportedAuthMethods))); } /** @@ -1147,7 +1187,13 @@ // The server should be sending a CAPABILITY response after // negotiating TLS. Read it, and ignore if it doesn't. - $this->_doCmd(); + // Doesn't work with older timsieved versions + $regexp = '/^CYRUS TIMSIEVED V([0-9.]+)/'; + if (!preg_match($regexp, $this->_capability['implementation'], $matches) + || version_compare($matches[1], '2.3.10', '>=') + ) { + $this->_doCmd(); + } // RFC says we need to query the server capabilities again now that we // are under encryption. @@ -1193,6 +1239,24 @@ } /** + * Convert string into RFC's quoted-string or literal-c2s form + * + * @param string $string The string to convert. + * + * @return string Result string + */ + function _escape($string) + { + // Some implementations doesn't allow UTF-8 characters in quoted-string + // It's safe to use literal-c2s + if (preg_match('/[^\x01-\x09\x0B-\x0C\x0E-\x7F]/', $string)) { + return sprintf("{%d+}\r\n%s", $this->_getLineLength($string), $string); + } + + return '"' . addcslashes($string, '\\"') . '"'; + } + + /** * Write debug text to the current debug output handler. * * @param string $message Debug message text. -- Gitblit v1.9.1