Aleksander Machniak
2014-06-16 109bcce470b789dac498d0fba8fb9673b988f867
Add config option to specify IMAP connection socket parameters - imap_conn_options (#1489948)
5 files modified
250 ■■■■■ changed files
CHANGELOG 1 ●●●● patch | view | raw | blame | history
config/defaults.inc.php 37 ●●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube.php 19 ●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_imap.php 12 ●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_imap_generic.php 181 ●●●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,6 +1,7 @@
CHANGELOG Roundcube Webmail
===========================
- Add config option to specify IMAP connection socket parameters - imap_conn_options (#1489948)
- Password: Add option to force new users to change their password (#1486884)
- Improve support for screen readers and assistive technology using WCAG 2.0 and WAI ARIA standards
- Enable basic keyboard navigation throughout the UI (#1487845)
config/defaults.inc.php
@@ -123,6 +123,27 @@
// best server supported one)
$config['imap_auth_type'] = null;
// IMAP socket context options
// See http://php.net/manual/en/context.ssl.php
// The example below enables server certificate validation
//$config['imap_conn_options'] = array(
//  'ssl'         => array(
//     'verify_peer'  => true,
//     'verify_depth' => 3,
//     'cafile'       => '/etc/openssl/certs/ca.crt',
//   ),
// );
$config['imap_conn_options'] = null;
// IMAP connection timeout, in seconds. Default: 0 (use default_socket_timeout)
$config['imap_timeout'] = 0;
// Optional IMAP authentication identifier to be used as authorization proxy
$config['imap_auth_cid'] = null;
// Optional IMAP authentication password to be used for imap_auth_cid
$config['imap_auth_pw'] = null;
// If you know your imap's folder delimiter, you can specify it here.
// Otherwise it will be determined automatically
$config['imap_delimiter'] = null;
@@ -159,15 +180,6 @@
// For example UW-IMAP server has broken ESEARCH.
// Note: Because the list is cached, re-login is required after change.
$config['imap_disabled_caps'] = array();
// IMAP connection timeout, in seconds. Default: 0 (use default_socket_timeout)
$config['imap_timeout'] = 0;
// Optional IMAP authentication identifier to be used as authorization proxy
$config['imap_auth_cid'] = null;
// Optional IMAP authentication password to be used for imap_auth_cid
$config['imap_auth_pw'] = null;
// Type of IMAP indexes cache. Supported values: 'db', 'apc' and 'memcache'.
$config['imap_cache'] = null;
@@ -244,13 +256,14 @@
// requires 'smtp_timeout' to be non zero.
// $config['smtp_conn_options'] = array(
//   'ssl'         => array(
//     'verify_peer'     => true,
//     'verify_depth     => 3,
//     'cafile'          => '/etc/openssl/certs/ca.crt',
//     'verify_peer'  => true,
//     'verify_depth' => 3,
//     'cafile'       => '/etc/openssl/certs/ca.crt',
//   ),
// );
$config['smtp_conn_options'] = null;
// ----------------------------------
// LDAP
// ----------------------------------
program/lib/Roundcube/rcube.php
@@ -357,15 +357,16 @@
        // set class options
        $options = array(
            'auth_type'   => $this->config->get("{$driver}_auth_type", 'check'),
            'auth_cid'    => $this->config->get("{$driver}_auth_cid"),
            'auth_pw'     => $this->config->get("{$driver}_auth_pw"),
            'debug'       => (bool) $this->config->get("{$driver}_debug"),
            'force_caps'  => (bool) $this->config->get("{$driver}_force_caps"),
            'disabled_caps' => $this->config->get("{$driver}_disabled_caps"),
            'timeout'     => (int) $this->config->get("{$driver}_timeout"),
            'skip_deleted' => (bool) $this->config->get('skip_deleted'),
            'driver'      => $driver,
            'auth_type'      => $this->config->get("{$driver}_auth_type", 'check'),
            'auth_cid'       => $this->config->get("{$driver}_auth_cid"),
            'auth_pw'        => $this->config->get("{$driver}_auth_pw"),
            'debug'          => (bool) $this->config->get("{$driver}_debug"),
            'force_caps'     => (bool) $this->config->get("{$driver}_force_caps"),
            'disabled_caps'  => $this->config->get("{$driver}_disabled_caps"),
            'socket_options' => $this->config->get("{$driver}_conn_options"),
            'timeout'        => (int) $this->config->get("{$driver}_timeout"),
            'skip_deleted'   => (bool) $this->config->get('skip_deleted'),
            'driver'         => $driver,
        );
        if (!empty($_SESSION['storage_host'])) {
program/lib/Roundcube/rcube_imap.php
@@ -110,13 +110,13 @@
    /**
     * Connect to an IMAP server
     *
     * @param  string   $host    Host to connect
     * @param  string   $user    Username for IMAP account
     * @param  string   $pass    Password for IMAP account
     * @param  integer  $port    Port to connect to
     * @param  string   $use_ssl SSL schema (either ssl or tls) or null if plain connection
     * @param string  $host    Host to connect
     * @param string  $user    Username for IMAP account
     * @param string  $pass    Password for IMAP account
     * @param integer $port    Port to connect to
     * @param string  $use_ssl SSL schema (either ssl or tls) or null if plain connection
     *
     * @return boolean  TRUE on success, FALSE on failure
     * @return boolean True on success, False on failure
     */
    public function connect($host, $user, $pass, $port=143, $use_ssl=null)
    {
program/lib/Roundcube/rcube_imap_generic.php
@@ -723,103 +723,30 @@
        // configure
        $this->set_prefs($options);
        $auth_method = $this->prefs['auth_type'];
        $result      = false;
        // initialize connection
        $this->error    = '';
        $this->errornum = self::ERROR_OK;
        $this->selected = null;
        $this->user     = $user;
        $this->host     = $host;
        $this->user     = $user;
        $this->logged   = false;
        $this->selected = null;
        // check input
        if (empty($host)) {
            $this->setError(self::ERROR_BAD, "Empty host");
            return false;
        }
        if (empty($user)) {
            $this->setError(self::ERROR_NO, "Empty user");
            return false;
        }
        if (empty($password)) {
            $this->setError(self::ERROR_NO, "Empty password");
            return false;
        }
        if (!$this->prefs['port']) {
            $this->prefs['port'] = 143;
        }
        // check for SSL
        if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') {
            $host = $this->prefs['ssl_mode'] . '://' . $host;
        }
        if ($this->prefs['timeout'] <= 0) {
            $this->prefs['timeout'] = max(0, intval(ini_get('default_socket_timeout')));
        }
        // Connect
        $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']);
        if (!$this->fp) {
            if (!$errstr) {
                $errstr = "Unknown reason (fsockopen() function disabled?)";
            }
            $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr));
        if (!$this->_connect($host)) {
            return false;
        }
        if ($this->prefs['timeout'] > 0) {
            stream_set_timeout($this->fp, $this->prefs['timeout']);
        }
        $line = trim(fgets($this->fp, 8192));
        if ($this->_debug) {
            // set connection identifier for debug output
            preg_match('/#([0-9]+)/', (string)$this->fp, $m);
            $this->resourceid = strtoupper(substr(md5($m[1].$this->user.microtime()), 0, 4));
            if ($line)
                $this->debug('S: '. $line);
        }
        // Connected to wrong port or connection error?
        if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) {
            if ($line)
                $error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line);
            else
                $error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']);
            $this->setError(self::ERROR_BAD, $error);
            $this->closeConnection();
            return false;
        }
        // RFC3501 [7.1] optional CAPABILITY response
        if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
            $this->parseCapability($matches[1], true);
        }
        // TLS connection
        if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) {
            $res = $this->execute('STARTTLS');
            if ($res[0] != self::ERROR_OK) {
                $this->closeConnection();
                return false;
            }
            if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
                $this->setError(self::ERROR_BAD, "Unable to negotiate TLS");
                $this->closeConnection();
                return false;
            }
            // Now we're secure, capabilities need to be reread
            $this->clearCapability();
        }
        // Send ID info
@@ -827,6 +754,7 @@
            $this->id($this->prefs['ident']);
        }
        $auth_method  = $this->prefs['auth_type'];
        $auth_methods = array();
        $result       = null;
@@ -901,6 +829,103 @@
    }
    /**
     * Connects to IMAP server.
     *
     * @param string $host Server hostname or IP
     *
     * @return bool True on success, False on failure
     */
    protected function _connect($host)
    {
        // initialize connection
        $this->error    = '';
        $this->errornum = self::ERROR_OK;
        if (!$this->prefs['port']) {
            $this->prefs['port'] = 143;
        }
        // check for SSL
        if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') {
            $host = $this->prefs['ssl_mode'] . '://' . $host;
        }
        if ($this->prefs['timeout'] <= 0) {
            $this->prefs['timeout'] = max(0, intval(ini_get('default_socket_timeout')));
        }
        if (!empty($this->prefs['socket_options'])) {
            $context  = stream_context_create($this->prefs['socket_options']);
            $this->fp = stream_socket_client($host . ':' . $this->prefs['port'], $errno, $errstr,
                $this->prefs['timeout'], STREAM_CLIENT_CONNECT, $context);
        }
        else {
            $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 ?: "Unknown reason"));
            return false;
        }
        if ($this->prefs['timeout'] > 0) {
            stream_set_timeout($this->fp, $this->prefs['timeout']);
        }
        $line = trim(fgets($this->fp, 8192));
        if ($this->_debug) {
            // set connection identifier for debug output
            preg_match('/#([0-9]+)/', (string) $this->fp, $m);
            $this->resourceid = strtoupper(substr(md5($m[1].$this->user.microtime()), 0, 4));
            if ($line) {
                $this->debug('S: '. $line);
            }
        }
        // Connected to wrong port or connection error?
        if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) {
            if ($line)
                $error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line);
            else
                $error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']);
            $this->setError(self::ERROR_BAD, $error);
            $this->closeConnection();
            return false;
        }
        // RFC3501 [7.1] optional CAPABILITY response
        if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
            $this->parseCapability($matches[1], true);
        }
        // TLS connection
        if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) {
            $res = $this->execute('STARTTLS');
            if ($res[0] != self::ERROR_OK) {
                $this->closeConnection();
                return false;
            }
            if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
                $this->setError(self::ERROR_BAD, "Unable to negotiate TLS");
                $this->closeConnection();
                return false;
            }
            // Now we're secure, capabilities need to be reread
            $this->clearCapability();
        }
        return true;
    }
    /**
     * Initializes environment
     */
    protected function set_prefs($prefs)