From e8d5bdc84ecfdf6fe5008655215a258bbdf0c521 Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Thu, 03 Feb 2011 08:58:07 -0500
Subject: [PATCH] - Fix IDNA support when IDN/INTL modules are in use (#1487742)
---
program/include/rcmail.php | 398 +++++++++++++++++++++++++++++++++++++++++++-------------
1 files changed, 306 insertions(+), 92 deletions(-)
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index f0cbdbf..8c80fe2 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -5,7 +5,7 @@
| program/include/rcmail.php |
| |
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2008-2010, Roundcube Dev. - Switzerland |
+ | Copyright (C) 2008-2010, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
@@ -28,30 +28,100 @@
*/
class rcmail
{
+ /**
+ * Main tasks.
+ *
+ * @var array
+ */
static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy');
+ /**
+ * Singleton instace of rcmail
+ *
+ * @var rcmail
+ */
static private $instance;
+ /**
+ * Stores instance of rcube_config.
+ *
+ * @var rcube_config
+ */
public $config;
+
+ /**
+ * Stores rcube_user instance.
+ *
+ * @var rcube_user
+ */
public $user;
+
+ /**
+ * Instace of database class.
+ *
+ * @var rcube_mdb2
+ */
public $db;
+
+ /**
+ * Instace of rcube_session class.
+ *
+ * @var rcube_session
+ */
public $session;
+
+ /**
+ * Instance of rcube_smtp class.
+ *
+ * @var rcube_smtp
+ */
public $smtp;
+
+ /**
+ * Instance of rcube_imap class.
+ *
+ * @var rcube_imap
+ */
public $imap;
+
+ /**
+ * Instance of rcube_template class.
+ *
+ * @var rcube_template
+ */
public $output;
+
+ /**
+ * Instance of rcube_plugin_api.
+ *
+ * @var rcube_plugin_api
+ */
public $plugins;
+
+ /**
+ * Current task.
+ *
+ * @var string
+ */
public $task;
+
+ /**
+ * Current action.
+ *
+ * @var string
+ */
public $action = '';
public $comm_path = './';
private $texts;
- private $books = array();
+ private $address_books = array();
+ private $action_map = array();
/**
* This implements the 'singleton' design pattern
*
- * @return object rcmail The one and only instance
+ * @return rcmail The one and only instance
*/
static function get_instance()
{
@@ -139,7 +209,7 @@
$task = asciiwords($task);
if ($this->user && $this->user->ID)
- $task = !$task || $task == 'login' ? 'mail' : $task;
+ $task = !$task ? 'mail' : $task;
else
$task = 'login';
@@ -154,7 +224,7 @@
/**
* Setter for system user object
*
- * @param object rcube_user Current user instance
+ * @param rcube_user Current user instance
*/
public function set_user($user)
{
@@ -226,7 +296,7 @@
/**
* Get the current database connection
*
- * @return object rcube_mdb2 Database connection object
+ * @return rcube_mdb2 Database connection object
*/
public function get_dbh()
{
@@ -247,7 +317,7 @@
*
* @param string Address book identifier
* @param boolean True if the address book needs to be writeable
- * @return object rcube_contacts Address book object
+ * @return rcube_contacts Address book object
*/
public function get_address_book($id, $writeable = false)
{
@@ -260,6 +330,10 @@
// plugin returned instance of a rcube_addressbook
if ($plugin['instance'] instanceof rcube_addressbook) {
$contacts = $plugin['instance'];
+ }
+ // use existing instance
+ else if (isset($this->address_books[$id]) && is_a($this->address_books[$id], 'rcube_addressbook') && (!$writeable || !$this->address_books[$id]->readonly)) {
+ $contacts = $this->address_books[$id];
}
else if ($id && $ldap_config[$id]) {
$contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['imap_host']));
@@ -281,8 +355,8 @@
}
// add to the 'books' array for shutdown function
- if (!in_array($contacts, $this->books))
- $this->books[] = $contacts;
+ if (!isset($this->address_books[$id]))
+ $this->address_books[$id] = $contacts;
return $contacts;
}
@@ -303,11 +377,12 @@
// We are using the DB address book
if ($abook_type != 'ldap') {
- $contacts = new rcube_contacts($this->db, null);
+ if (!isset($this->address_books['0']))
+ $this->address_books['0'] = new rcube_contacts($this->db, $this->user->ID);
$list['0'] = array(
- 'id' => 0,
+ 'id' => '0',
'name' => rcube_label('personaladrbook'),
- 'groups' => $contacts->groups,
+ 'groups' => $this->address_books['0']->groups,
'readonly' => false,
'autocomplete' => in_array('sql', $autocomplete)
);
@@ -319,7 +394,7 @@
$list[$id] = array(
'id' => $id,
'name' => $prop['name'],
- 'groups' => false,
+ 'groups' => is_array($prop['groups']),
'readonly' => !$prop['writable'],
'autocomplete' => in_array('sql', $autocomplete)
);
@@ -328,14 +403,15 @@
$plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list));
$list = $plugin['sources'];
- if ($writeable && !empty($list)) {
- foreach ($list as $idx => $item) {
- if ($item['readonly']) {
+ foreach ($list as $idx => $item) {
+ // register source for shutdown function
+ if (!is_object($this->address_books[$item['id']]))
+ $this->address_books[$item['id']] = $item;
+ // remove from list if not writeable as requested
+ if ($writeable && $item['readonly'])
unset($list[$idx]);
- }
- }
}
-
+
return $list;
}
@@ -346,7 +422,7 @@
* environment vars according to the current session and configuration
*
* @param boolean True if this request is loaded in a (i)frame
- * @return object rcube_template Reference to HTML output object
+ * @return rcube_template Reference to HTML output object
*/
public function load_gui($framed = false)
{
@@ -379,7 +455,7 @@
/**
* Create an output object for JSON responses
*
- * @return object rcube_json_output Reference to JSON output object
+ * @return rcube_json_output Reference to JSON output object
*/
public function json_init()
{
@@ -432,9 +508,9 @@
// can save time detecting them using NAMESPACE and LIST
$options = array(
'auth_method' => $this->config->get('imap_auth_type', 'check'),
- 'delimiter' => isset($_SESSION['imap_delimiter']) ? $_SESSION['imap_delimiter'] : $this->config->get('imap_delimiter'),
- 'rootdir' => isset($_SESSION['imap_root']) ? $_SESSION['imap_root'] : $this->config->get('imap_root'),
- 'debug_mode' => (bool) $this->config->get('imap_debug', 0),
+ 'auth_cid' => $this->config->get('imap_auth_cid'),
+ 'auth_pw' => $this->config->get('imap_auth_pw'),
+ 'debug' => (bool) $this->config->get('imap_debug', 0),
'force_caps' => (bool) $this->config->get('imap_force_caps'),
'timeout' => (int) $this->config->get('imap_timeout', 0),
);
@@ -451,8 +527,11 @@
// support this parameter for backward compatibility but log warning
if ($connect) {
$this->imap_connect();
- raise_error(array('code' => 800, 'type' => 'imap', 'file' => __FILE__,
- 'message' => "rcube::imap_init(true) is deprecated, use rcube::imap_connect() instead"), true, false);
+ raise_error(array(
+ 'code' => 800, 'type' => 'imap',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "rcube::imap_init(true) is deprecated, use rcube::imap_connect() instead"),
+ true, false);
}
}
@@ -470,7 +549,7 @@
if ($_SESSION['imap_host'] && !$this->imap->conn->connected()) {
if (!$this->imap->connect($_SESSION['imap_host'], $_SESSION['username'], $this->decrypt($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl'])) {
if ($this->output)
- $this->output->show_message($this->imap->error_code == -1 ? 'imaperror' : 'sessionerror', 'error');
+ $this->output->show_message($this->imap->get_error_code() == -1 ? 'imaperror' : 'sessionerror', 'error');
}
else {
$this->set_imap_prop();
@@ -520,10 +599,8 @@
session_start();
// set initial session vars
- if (!isset($_SESSION['auth_time'])) {
- $_SESSION['auth_time'] = time();
+ if (!$_SESSION['user_id'])
$_SESSION['temp'] = true;
- }
}
@@ -545,6 +622,9 @@
$keep_alive = max(60, $keep_alive);
$this->session->set_keep_alive($keep_alive);
}
+
+ $this->session->set_secret($this->config->get('des_key') . $_SERVER['HTTP_USER_AGENT']);
+ $this->session->set_ip_check($this->config->get('ip_check'));
}
@@ -587,7 +667,7 @@
if ($a_host['host']) {
$host = $a_host['host'];
$imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
- if(!empty($a_host['port']))
+ if (!empty($a_host['port']))
$imap_port = $a_host['port'];
else if ($imap_ssl && $imap_ssl != 'tls' && (!$config['default_port'] || $config['default_port'] == 143))
$imap_port = 993;
@@ -601,19 +681,31 @@
// Check if we need to add domain
if (!empty($config['username_domain']) && strpos($username, '@') === false) {
if (is_array($config['username_domain']) && isset($config['username_domain'][$host]))
- $username .= '@'.rcube_parse_host($config['username_domain'][$host]);
+ $username .= '@'.rcube_parse_host($config['username_domain'][$host], $host);
else if (is_string($config['username_domain']))
- $username .= '@'.rcube_parse_host($config['username_domain']);
+ $username .= '@'.rcube_parse_host($config['username_domain'], $host);
+ }
+
+ // Convert username to lowercase. If IMAP backend
+ // is case-insensitive we need to store always the same username (#1487113)
+ if ($config['login_lc']) {
+ $username = mb_strtolower($username);
}
// try to resolve email address from virtuser table
- if (strpos($username, '@'))
- if ($virtuser = rcube_user::email2user($username))
- $username = $virtuser;
+ if (strpos($username, '@') && ($virtuser = rcube_user::email2user($username))) {
+ $username = $virtuser;
+ }
- // lowercase username if it's an e-mail address (#1484473)
- if (strpos($username, '@'))
- $username = mb_strtolower($username);
+ // Here we need IDNA ASCII
+ // Only rcube_contacts class is using domain names in Unicode
+ $host = rcube_idn_to_ascii($host);
+ if (strpos($username, '@')) {
+ // lowercase domain name
+ list($local, $domain) = explode('@', $username);
+ $username = $local . '@' . mb_strtolower($domain);
+ $username = rcube_idn_to_ascii($username);
+ }
// user already registered -> overwrite username
if ($user = rcube_user::query($username, $host))
@@ -622,8 +714,22 @@
if (!$this->imap)
$this->imap_init();
+ // try IMAP login
+ if (!($imap_login = $this->imap->connect($host, $username, $pass, $imap_port, $imap_ssl))) {
+ // try with lowercase
+ $username_lc = mb_strtolower($username);
+ if ($username_lc != $username) {
+ // try to find user record again -> overwrite username
+ if (!$user && ($user = rcube_user::query($username_lc, $host)))
+ $username_lc = $user->data['username'];
+
+ if ($imap_login = $this->imap->connect($host, $username_lc, $pass, $imap_port, $imap_ssl))
+ $username = $username_lc;
+ }
+ }
+
// exit if IMAP login failed
- if (!($imap_login = $this->imap->connect($host, $username, $pass, $imap_port, $imap_ssl)))
+ if (!$imap_login)
return false;
$this->set_imap_prop();
@@ -646,7 +752,7 @@
else {
raise_error(array(
'code' => 600, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
+ 'file' => __FILE__, 'line' => __LINE__,
'message' => "Failed to create a user record. Maybe aborted by a plugin?"
), true, false);
}
@@ -662,6 +768,7 @@
// login succeeded
if (is_object($user) && $user->ID) {
$this->set_user($user);
+ $this->session_configure();
// set session vars
$_SESSION['user_id'] = $user->ID;
@@ -671,7 +778,7 @@
$_SESSION['imap_ssl'] = $imap_ssl;
$_SESSION['password'] = $this->encrypt($pass);
$_SESSION['login_time'] = mktime();
-
+
if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_')
$_SESSION['timezone'] = floatval($_REQUEST['_timezone']);
@@ -696,16 +803,12 @@
if ($default_folders = $this->config->get('default_imap_folders')) {
$this->imap->set_default_mailboxes($default_folders);
}
- if (!empty($_SESSION['mbox'])) {
+ if (isset($_SESSION['mbox'])) {
$this->imap->set_mailbox($_SESSION['mbox']);
}
if (isset($_SESSION['page'])) {
$this->imap->set_page($_SESSION['page']);
}
-
- // cache IMAP root and delimiter in session for performance reasons
- $_SESSION['imap_root'] = $this->imap->root_dir;
- $_SESSION['imap_delimiter'] = $this->imap->delimiter;
}
@@ -835,6 +938,9 @@
if (empty($this->texts) || $lang != $_SESSION['language']) {
$this->texts = array();
+ // handle empty lines after closing PHP tag in localization files
+ ob_start();
+
// get english labels (these should be complete)
@include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
@include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
@@ -854,6 +960,8 @@
if (is_array($messages))
$this->texts = array_merge($this->texts, $messages);
}
+
+ ob_end_clean();
$_SESSION['language'] = $lang;
}
@@ -893,50 +1001,14 @@
/**
- * Check the auth hash sent by the client against the local session credentials
- *
- * @return boolean True if valid, False if not
- */
- function authenticate_session()
- {
- // advanced session authentication
- if ($this->config->get('double_auth')) {
- $now = time();
- $valid = ($_COOKIE['sessauth'] == $this->get_auth_hash(session_id(), $_SESSION['auth_time']) ||
- $_COOKIE['sessauth'] == $this->get_auth_hash(session_id(), $_SESSION['last_auth']));
-
- // renew auth cookie every 5 minutes (only for GET requests)
- if (!$valid || ($_SERVER['REQUEST_METHOD']!='POST' && $now - $_SESSION['auth_time'] > 300)) {
- $_SESSION['last_auth'] = $_SESSION['auth_time'];
- $_SESSION['auth_time'] = $now;
- rcmail::setcookie('sessauth', $this->get_auth_hash(session_id(), $now), 0);
- }
- }
- else {
- $valid = $this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] == $this->session->get_ip() : true;
- }
-
- // check session filetime
- $lifetime = $this->config->get('session_lifetime');
- $sess_ts = $this->session->get_ts();
- if (!empty($lifetime) && !empty($sess_ts) && $sess_ts + $lifetime*60 < time()) {
- $valid = false;
- }
-
- return $valid;
- }
-
-
- /**
* Destroy session data and remove cookie
*/
public function kill_session()
{
$this->plugins->exec_hook('session_destroy');
- $this->session->remove();
- $_SESSION = array('language' => $this->user->language, 'auth_time' => time(), 'temp' => true);
- rcmail::setcookie('sessauth', '-del-', time() - 60);
+ $this->session->kill();
+ $_SESSION = array('language' => $this->user->language, 'temp' => true);
$this->user->reset();
}
@@ -950,7 +1022,7 @@
// on logout action we're not connected to imap server
if (($config['logout_purge'] && !empty($config['trash_mbox'])) || $config['logout_expunge']) {
- if (!$this->authenticate_session())
+ if (!$this->session->check_auth())
return;
$this->imap_connect();
@@ -978,9 +1050,12 @@
if (is_object($this->smtp))
$this->smtp->disconnect();
- foreach ($this->books as $book)
- if (is_object($book))
+ foreach ($this->address_books as $book) {
+ if (!is_object($book)) // maybe an address book instance wasn't fetched using get_address_book() yet
+ $book = $this->get_address_book($book['id']);
+ if (is_a($book, 'rcube_addressbook'))
$book->close();
+ }
// before closing the database connection, write session data
if ($_SERVER['REMOTE_ADDR'])
@@ -1121,8 +1196,14 @@
if (function_exists('mcrypt_module_open') &&
($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
{
- $iv = substr($cipher, 0, mcrypt_enc_get_iv_size($td));
- $cipher = substr($cipher, mcrypt_enc_get_iv_size($td));
+ $iv_size = mcrypt_enc_get_iv_size($td);
+ $iv = substr($cipher, 0, $iv_size);
+
+ // session corruption? (#1485970)
+ if (strlen($iv) < $iv_size)
+ return '';
+
+ $cipher = substr($cipher, $iv_size);
mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
$clear = mdecrypt_generic($td, $cipher);
mcrypt_generic_deinit($td);
@@ -1201,6 +1282,112 @@
/**
+ * Use imagemagick or GD lib to read image properties
+ *
+ * @param string Absolute file path
+ * @return mixed Hash array with image props like type, width, height or False on error
+ */
+ public static function imageprops($filepath)
+ {
+ $rcmail = rcmail::get_instance();
+ if ($cmd = $rcmail->config->get('im_identify_path', false)) {
+ list(, $type, $size) = explode(' ', strtolower(rcmail::exec($cmd. ' 2>/dev/null {in}', array('in' => $filepath))));
+ if ($size)
+ list($width, $height) = explode('x', $size);
+ }
+ else if (function_exists('getimagesize')) {
+ $imsize = @getimagesize($filepath);
+ $width = $imsize[0];
+ $height = $imsize[1];
+ $type = preg_replace('!image/!', '', $imsize['mime']);
+ }
+
+ return $type ? array('type' => $type, 'width' => $width, 'height' => $height) : false;
+ }
+
+
+ /**
+ * Convert an image to a given size and type using imagemagick (ensures input is an image)
+ *
+ * @param $p['in'] Input filename (mandatory)
+ * @param $p['out'] Output filename (mandatory)
+ * @param $p['size'] Width x height of resulting image, e.g. "160x60"
+ * @param $p['type'] Output file type, e.g. "jpg"
+ * @param $p['-opts'] Custom command line options to ImageMagick convert
+ * @return Success of convert as true/false
+ */
+ public static function imageconvert($p)
+ {
+ $result = false;
+ $rcmail = rcmail::get_instance();
+ $convert = $rcmail->config->get('im_convert_path', false);
+ $identify = $rcmail->config->get('im_identify_path', false);
+
+ // imagemagick is required for this
+ if (!$convert)
+ return false;
+
+ if (!(($imagetype = @exif_imagetype($p['in'])) && ($type = image_type_to_extension($imagetype, false))))
+ list(, $type) = explode(' ', strtolower(rcmail::exec($identify . ' 2>/dev/null {in}', $p))); # for things like eps
+
+ $type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps"));
+ $p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75);
+ $p['-opts'] = array('-resize' => $p['size'].'>') + (array)$p['-opts'];
+
+ if (in_array($type, explode(',', $p['types']))) # Valid type?
+ $result = rcmail::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {in} {type}:{out}', $p) === "";
+
+ return $result;
+ }
+
+
+ /**
+ * Construct shell command, execute it and return output as string.
+ * Keywords {keyword} are replaced with arguments
+ *
+ * @param $cmd Format string with {keywords} to be replaced
+ * @param $values (zero, one or more arrays can be passed)
+ * @return output of command. shell errors not detectable
+ */
+ public static function exec(/* $cmd, $values1 = array(), ... */)
+ {
+ $args = func_get_args();
+ $cmd = array_shift($args);
+ $values = $replacements = array();
+
+ // merge values into one array
+ foreach ($args as $arg)
+ $values += (array)$arg;
+
+ preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
+ foreach ($matches as $tags) {
+ list(, $tag, $option, $key) = $tags;
+ $parts = array();
+
+ if ($option) {
+ foreach ((array)$values["-$key"] as $key => $value) {
+ if ($value === true || $value === false || $value === null)
+ $parts[] = $value ? $key : "";
+ else foreach ((array)$value as $val)
+ $parts[] = "$key " . escapeshellarg($val);
+ }
+ }
+ else {
+ foreach ((array)$values[$key] as $value)
+ $parts[] = escapeshellarg($value);
+ }
+
+ $replacements[$tag] = join(" ", $parts);
+ }
+
+ // use strtr behaviour of going through source string once
+ $cmd = strtr($cmd, $replacements);
+
+ return (string)shell_exec($cmd);
+ }
+
+
+ /**
* Helper method to set a cookie with the current path and host settings
*
* @param string Cookie name
@@ -1217,6 +1404,33 @@
setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'],
rcube_https_check(), true);
}
+
+ /**
+ * Registers action aliases for current task
+ *
+ * @param array $map Alias-to-filename hash array
+ */
+ public function register_action_map($map)
+ {
+ if (is_array($map)) {
+ foreach ($map as $idx => $val) {
+ $this->action_map[$idx] = $val;
+ }
+ }
+ }
+
+ /**
+ * Returns current action filename
+ *
+ * @param array $map Alias-to-filename hash array
+ */
+ public function get_action_file()
+ {
+ if (!empty($this->action_map[$this->action])) {
+ return $this->action_map[$this->action];
+ }
+
+ return strtr($this->action, '-', '_') . '.inc';
+ }
+
}
-
-
--
Gitblit v1.9.1