diff --git a/index.php b/index.php index 03c3f4322..7adb0beb0 100644 --- a/index.php +++ b/index.php @@ -32,6 +32,7 @@ | | +-------------------------------------------------------------------------+ | Author: Thomas Bruederli | + | Author: Aleksander Machniak | +-------------------------------------------------------------------------+ */ @@ -49,34 +50,36 @@ ob_start(); // check if config files had errors if ($err_str = $RCMAIL->config->get_error()) { - rcmail::raise_error(array( - 'code' => 601, - 'type' => 'php', - 'message' => $err_str), false, true); + rcmail::raise_error(array( + 'code' => 601, + 'type' => 'php', + 'message' => $err_str), false, true); } // check DB connections and exit on failure if ($err_str = $RCMAIL->db->is_error()) { - rcmail::raise_error(array( - 'code' => 603, - 'type' => 'db', - 'message' => $err_str), FALSE, TRUE); + rcmail::raise_error(array( + 'code' => 603, + 'type' => 'db', + 'message' => $err_str), FALSE, TRUE); } // error steps if ($RCMAIL->action == 'error' && !empty($_GET['_code'])) { - rcmail::raise_error(array('code' => hexdec($_GET['_code'])), FALSE, TRUE); + rcmail::raise_error(array('code' => hexdec($_GET['_code'])), FALSE, TRUE); } // check if https is required (for login) and redirect if necessary if (empty($_SESSION['user_id']) && ($force_https = $RCMAIL->config->get('force_https', false))) { - $https_port = is_bool($force_https) ? 443 : $force_https; - if (!rcube_utils::https_check($https_port)) { - $host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']); - $host .= ($https_port != 443 ? ':' . $https_port : ''); - header('Location: https://' . $host . $_SERVER['REQUEST_URI']); - exit; - } + $https_port = is_bool($force_https) ? 443 : $force_https; + + if (!rcube_utils::https_check($https_port)) { + $host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']); + $host .= ($https_port != 443 ? ':' . $https_port : ''); + + header('Location: https://' . $host . $_SERVER['REQUEST_URI']); + exit; + } } // trigger startup plugin hook @@ -86,165 +89,176 @@ $RCMAIL->action = $startup['action']; // try to log in if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') { - $request_valid = $_SESSION['temp'] && $RCMAIL->check_request(rcube_utils::INPUT_POST, 'login'); - - // purge the session in case of new login when a session already exists - $RCMAIL->kill_session(); - - $auth = $RCMAIL->plugins->exec_hook('authenticate', array( - 'host' => $RCMAIL->autoselect_host(), - 'user' => trim(rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST)), - 'pass' => rcube_utils::get_input_value('_pass', rcube_utils::INPUT_POST, true, - $RCMAIL->config->get('password_charset', 'ISO-8859-1')), - 'cookiecheck' => true, - 'valid' => $request_valid, - )); - - // Login - if ($auth['valid'] && !$auth['abort'] && - $RCMAIL->login($auth['user'], $auth['pass'], $auth['host'], $auth['cookiecheck']) - ) { - // create new session ID, don't destroy the current session - // it was destroyed already by $RCMAIL->kill_session() above - $RCMAIL->session->remove('temp'); - $RCMAIL->session->regenerate_id(false); - - // send auth cookie if necessary - $RCMAIL->session->set_auth_cookie(); - - // log successful login - $RCMAIL->log_login(); - - // restore original request parameters - $query = array(); - if ($url = rcube_utils::get_input_value('_url', rcube_utils::INPUT_POST)) { - parse_str($url, $query); - - // prevent endless looping on login page - if ($query['_task'] == 'login') - unset($query['_task']); - - // prevent redirect to compose with specified ID (#1488226) - if ($query['_action'] == 'compose' && !empty($query['_id'])) - $query = array(); - } + $request_valid = $_SESSION['temp'] && $RCMAIL->check_request(rcube_utils::INPUT_POST, 'login'); - // allow plugins to control the redirect url after login success - $redir = $RCMAIL->plugins->exec_hook('login_after', $query + array('_task' => 'mail')); - unset($redir['abort'], $redir['_err']); + // purge the session in case of new login when a session already exists + $RCMAIL->kill_session(); - // send redirect - $OUTPUT->redirect($redir); - } - else { - if (!$auth['valid']) { - $error_code = RCMAIL::ERROR_INVALID_REQUEST; + $auth = $RCMAIL->plugins->exec_hook('authenticate', array( + 'host' => $RCMAIL->autoselect_host(), + 'user' => trim(rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST)), + 'pass' => rcube_utils::get_input_value('_pass', rcube_utils::INPUT_POST, true, + $RCMAIL->config->get('password_charset', 'ISO-8859-1')), + 'cookiecheck' => true, + 'valid' => $request_valid, + )); + + // Login + if ($auth['valid'] && !$auth['abort'] + && $RCMAIL->login($auth['user'], $auth['pass'], $auth['host'], $auth['cookiecheck']) + ) { + // create new session ID, don't destroy the current session + // it was destroyed already by $RCMAIL->kill_session() above + $RCMAIL->session->remove('temp'); + $RCMAIL->session->regenerate_id(false); + + // send auth cookie if necessary + $RCMAIL->session->set_auth_cookie(); + + // log successful login + $RCMAIL->log_login(); + + // restore original request parameters + $query = array(); + if ($url = rcube_utils::get_input_value('_url', rcube_utils::INPUT_POST)) { + parse_str($url, $query); + + // prevent endless looping on login page + if ($query['_task'] == 'login') { + unset($query['_task']); + } + + // prevent redirect to compose with specified ID (#1488226) + if ($query['_action'] == 'compose' && !empty($query['_id'])) { + $query = array(); + } + } + + // allow plugins to control the redirect url after login success + $redir = $RCMAIL->plugins->exec_hook('login_after', $query + array('_task' => 'mail')); + unset($redir['abort'], $redir['_err']); + + // send redirect + $OUTPUT->redirect($redir); } else { - $error_code = $auth['error'] ? $auth['error'] : $RCMAIL->login_error(); - } + if (!$auth['valid']) { + $error_code = RCMAIL::ERROR_INVALID_REQUEST; + } + else { + $error_code = $auth['error'] ? $auth['error'] : $RCMAIL->login_error(); + } - $error_labels = array( - RCMAIL::ERROR_STORAGE => 'storageerror', - RCMAIL::ERROR_COOKIES_DISABLED => 'cookiesdisabled', - RCMAIL::ERROR_INVALID_REQUEST => 'invalidrequest', - RCMAIL::ERROR_INVALID_HOST => 'invalidhost', - ); + $error_labels = array( + RCMAIL::ERROR_STORAGE => 'storageerror', + RCMAIL::ERROR_COOKIES_DISABLED => 'cookiesdisabled', + RCMAIL::ERROR_INVALID_REQUEST => 'invalidrequest', + RCMAIL::ERROR_INVALID_HOST => 'invalidhost', + ); - $error_message = $error_labels[$error_code] ? $error_labels[$error_code] : 'loginfailed'; + $error_message = $error_labels[$error_code] ? $error_labels[$error_code] : 'loginfailed'; - // log failed login - $RCMAIL->log_login($auth['user'], true, $error_code); + $OUTPUT->show_message($error_message, 'warning'); - $OUTPUT->show_message($error_message, 'warning'); - $RCMAIL->plugins->exec_hook('login_failed', array( - 'code' => $error_code, 'host' => $auth['host'], 'user' => $auth['user'])); - $RCMAIL->kill_session(); - } + // log failed login + $RCMAIL->log_login($auth['user'], true, $error_code); + + $RCMAIL->plugins->exec_hook('login_failed', array( + 'code' => $error_code, 'host' => $auth['host'], 'user' => $auth['user'])); + + $RCMAIL->kill_session(); + } } // end session (after optional referer check) -else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id']) && (!$RCMAIL->config->get('referer_check') || rcube_utils::check_referer())) { - $userdata = array( - 'user' => $_SESSION['username'], - 'host' => $_SESSION['storage_host'], - 'lang' => $RCMAIL->user->language, - ); - $OUTPUT->show_message('loggedout'); - $RCMAIL->logout_actions(); - $RCMAIL->kill_session(); - $RCMAIL->plugins->exec_hook('logout_after', $userdata); +else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id']) + && (!$RCMAIL->config->get('referer_check') || rcube_utils::check_referer()) +) { + $userdata = array( + 'user' => $_SESSION['username'], + 'host' => $_SESSION['storage_host'], + 'lang' => $RCMAIL->user->language, + ); + + $OUTPUT->show_message('loggedout'); + + $RCMAIL->logout_actions(); + $RCMAIL->kill_session(); + $RCMAIL->plugins->exec_hook('logout_after', $userdata); } // check session and auth cookie else if ($RCMAIL->task != 'login' && $_SESSION['user_id'] && $RCMAIL->action != 'send') { - if (!$RCMAIL->session->check_auth()) { - $RCMAIL->kill_session(); - $session_error = true; - } + if (!$RCMAIL->session->check_auth()) { + $RCMAIL->kill_session(); + $session_error = true; + } } // not logged in -> show login page if (empty($RCMAIL->user->ID)) { - // log session failures - $task = rcube_utils::get_input_value('_task', rcube_utils::INPUT_GPC); - if ($task && !in_array($task, array('login','logout')) && !$session_error && ($sess_id = $_COOKIE[ini_get('session.name')])) { - $RCMAIL->session->log("Aborted session " . $sess_id . "; no valid session data found"); - $session_error = true; - } - - // check if installer is still active - if ($RCMAIL->config->get('enable_installer') && is_readable('./installer/index.php')) { - $OUTPUT->add_footer(html::div(array('style' => "background:#ef9398; border:2px solid #dc5757; padding:0.5em; margin:2em auto; width:50em"), - html::tag('h2', array('style' => "margin-top:0.2em"), "Installer script is still accessible") . - html::p(null, "The install script of your Roundcube installation is still stored in its default location!") . - html::p(null, "Please remove the whole installer folder from the Roundcube directory because . - these files may expose sensitive configuration data like server passwords and encryption keys - to the public. Make sure you cannot access the installer script from your browser.") - ) - ); - } + // log session failures + $task = rcube_utils::get_input_value('_task', rcube_utils::INPUT_GPC); + + if ($task && !in_array($task, array('login','logout')) + && !$session_error && ($sess_id = $_COOKIE[ini_get('session.name')]) + ) { + $RCMAIL->session->log("Aborted session $sess_id; no valid session data found"); + $session_error = true; + } + + // check if installer is still active + if ($RCMAIL->config->get('enable_installer') && is_readable('./installer/index.php')) { + $OUTPUT->add_footer(html::div(array('style' => "background:#ef9398; border:2px solid #dc5757; padding:0.5em; margin:2em auto; width:50em"), + html::tag('h2', array('style' => "margin-top:0.2em"), "Installer script is still accessible") . + html::p(null, "The install script of your Roundcube installation is still stored in its default location!") . + html::p(null, "Please remove the whole installer folder from the Roundcube directory because . + these files may expose sensitive configuration data like server passwords and encryption keys + to the public. Make sure you cannot access the installer script from your browser.") + )); + } + + if ($session_error || $_REQUEST['_err'] == 'session') { + $OUTPUT->show_message('sessionerror', 'error', null, true, -1); + } - if ($session_error || $_REQUEST['_err'] == 'session') { - $OUTPUT->show_message('sessionerror', 'error', null, true, -1); - } + if ($OUTPUT->ajax_call || !empty($_REQUEST['_framed'])) { + $OUTPUT->command('session_error', $RCMAIL->url(array('_err' => 'session'))); + $OUTPUT->send('iframe'); + } - if ($OUTPUT->ajax_call || !empty($_REQUEST['_framed'])) { - $OUTPUT->command('session_error', $RCMAIL->url(array('_err' => 'session'))); - $OUTPUT->send('iframe'); - } + $plugin = $RCMAIL->plugins->exec_hook('unauthenticated', array('task' => 'login', 'error' => $session_error)); - $plugin = $RCMAIL->plugins->exec_hook('unauthenticated', array('task' => 'login', 'error' => $session_error)); + $RCMAIL->set_task($plugin['task']); - $RCMAIL->set_task($plugin['task']); - $OUTPUT->send($plugin['task']); + $OUTPUT->send($plugin['task']); } // CSRF prevention else { - // don't check for valid request tokens in these actions - $request_check_whitelist = array('login'=>1, 'spell'=>1, 'spell_html'=>1); - - if (!$request_check_whitelist[$RCMAIL->action]) { - // check client X-header to verify request origin - if ($OUTPUT->ajax_call) { - if (rcube_utils::request_header('X-Roundcube-Request') != $RCMAIL->get_request_token()) { - header('HTTP/1.1 403 Forbidden'); - die("Invalid Request"); - } - } - // check request token in POST form submissions - else if (!empty($_POST) && !$RCMAIL->check_request()) { - $OUTPUT->show_message('invalidrequest', 'error'); - $OUTPUT->send($RCMAIL->task); + // don't check for valid request tokens in these actions + $request_check_whitelist = array('login'=>1, 'spell'=>1, 'spell_html'=>1); + + if (!$request_check_whitelist[$RCMAIL->action]) { + // check client X-header to verify request origin + if ($OUTPUT->ajax_call) { + if (rcube_utils::request_header('X-Roundcube-Request') != $RCMAIL->get_request_token()) { + header('HTTP/1.1 403 Forbidden'); + die("Invalid Request"); + } + } + // check request token in POST form submissions + else if (!empty($_POST) && !$RCMAIL->check_request()) { + $OUTPUT->show_message('invalidrequest', 'error'); + $OUTPUT->send($RCMAIL->task); + } + + // check referer if configured + if ($RCMAIL->config->get('referer_check') && !rcube_utils::check_referer()) { + raise_error(array( + 'code' => 403, 'type' => 'php', + 'message' => "Referer check failed"), true, true); + } } - - // check referer if configured - if ($RCMAIL->config->get('referer_check') && !rcube_utils::check_referer()) { - raise_error(array( - 'code' => 403, 'type' => 'php', - 'message' => "Referer check failed"), true, true); - } - } } // we're ready, user is authenticated and the request is safe @@ -254,58 +268,57 @@ $RCMAIL->action = $plugin['action']; // handle special actions if ($RCMAIL->action == 'keep-alive') { - $OUTPUT->reset(); - $RCMAIL->plugins->exec_hook('keep_alive', array()); - $OUTPUT->send(); + $OUTPUT->reset(); + $RCMAIL->plugins->exec_hook('keep_alive', array()); + $OUTPUT->send(); } else if ($RCMAIL->action == 'save-pref') { - include INSTALL_PATH . 'program/steps/utils/save_pref.inc'; + include INSTALL_PATH . 'program/steps/utils/save_pref.inc'; } // include task specific functions -if (is_file($incfile = INSTALL_PATH . 'program/steps/'.$RCMAIL->task.'/func.inc')) - include_once $incfile; +if (is_file($incfile = INSTALL_PATH . 'program/steps/'.$RCMAIL->task.'/func.inc')) { + include_once $incfile; +} // allow 5 "redirects" to another action $redirects = 0; $incstep = null; while ($redirects < 5) { - // execute a plugin action - if ($RCMAIL->plugins->is_plugin_task($RCMAIL->task)) { - if (!$RCMAIL->action) $RCMAIL->action = 'index'; - $RCMAIL->plugins->exec_action($RCMAIL->task.'.'.$RCMAIL->action); - break; - } - else if (preg_match('/^plugin\./', $RCMAIL->action)) { - $RCMAIL->plugins->exec_action($RCMAIL->action); - break; - } - // try to include the step file - else if (($stepfile = $RCMAIL->get_action_file()) - && is_file($incfile = INSTALL_PATH . 'program/steps/'.$RCMAIL->task.'/'.$stepfile) - ) { - // include action file only once (in case it don't exit) - include_once $incfile; - $redirects++; - } - else { - break; - } + // execute a plugin action + if ($RCMAIL->plugins->is_plugin_task($RCMAIL->task)) { + if (!$RCMAIL->action) $RCMAIL->action = 'index'; + $RCMAIL->plugins->exec_action($RCMAIL->task.'.'.$RCMAIL->action); + break; + } + else if (preg_match('/^plugin\./', $RCMAIL->action)) { + $RCMAIL->plugins->exec_action($RCMAIL->action); + break; + } + // try to include the step file + else if (($stepfile = $RCMAIL->get_action_file()) + && is_file($incfile = INSTALL_PATH . 'program/steps/'.$RCMAIL->task.'/'.$stepfile) + ) { + // include action file only once (in case it don't exit) + include_once $incfile; + $redirects++; + } + else { + break; + } } if ($RCMAIL->action == 'refresh') { - $RCMAIL->plugins->exec_hook('refresh', array('last' => intval(rcube_utils::get_input_value('_last', rcube_utils::INPUT_GPC)))); + $RCMAIL->plugins->exec_hook('refresh', array('last' => intval(rcube_utils::get_input_value('_last', rcube_utils::INPUT_GPC)))); } // parse main template (default) $OUTPUT->send($RCMAIL->task); - // if we arrive here, something went wrong rcmail::raise_error(array( - 'code' => 404, - 'type' => 'php', - 'line' => __LINE__, - 'file' => __FILE__, - 'message' => "Invalid request"), true, true); - + 'code' => 404, + 'type' => 'php', + 'line' => __LINE__, + 'file' => __FILE__, + 'message' => "Invalid request"), true, true); diff --git a/program/include/clisetup.php b/program/include/clisetup.php index 07e318521..47a40719a 100644 --- a/program/include/clisetup.php +++ b/program/include/clisetup.php @@ -20,7 +20,7 @@ */ if (php_sapi_name() != 'cli') { - die('Not on the "shell" (php-cli).'); + die('Not on the "shell" (php-cli).'); } require_once INSTALL_PATH . 'program/include/iniset.php'; diff --git a/program/include/iniset.php b/program/include/iniset.php index 919cc7682..f6ad466da 100644 --- a/program/include/iniset.php +++ b/program/include/iniset.php @@ -84,4 +84,3 @@ function rcmail_autoload($classname) return false; } - diff --git a/program/include/rcmail.php b/program/include/rcmail.php index e85d82cde..a927b7946 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -5,8 +5,8 @@ | program/include/rcmail.php | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2008-2012, The Roundcube Dev Team | - | Copyright (C) 2011-2012, Kolab Systems AG | + | Copyright (C) 2008-2013, The Roundcube Dev Team | + | Copyright (C) 2011-2013, Kolab Systems AG | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -21,7 +21,6 @@ +-----------------------------------------------------------------------+ */ - /** * Application class of Roundcube Webmail * implemented as singleton @@ -30,616 +29,640 @@ */ class rcmail extends rcube { - /** - * Main tasks. - * - * @var array - */ - static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy'); - - /** - * Current task. - * - * @var string - */ - public $task; - - /** - * Current action. - * - * @var string - */ - public $action = ''; - public $comm_path = './'; - public $filename = ''; - - private $address_books = array(); - private $action_map = array(); - - - const ERROR_STORAGE = -2; - const ERROR_INVALID_REQUEST = 1; - const ERROR_INVALID_HOST = 2; - const ERROR_COOKIES_DISABLED = 3; - - - /** - * This implements the 'singleton' design pattern - * - * @param string Environment name to run (e.g. live, dev, test) - * @return rcmail The one and only instance - */ - static function get_instance($env = '') - { - if (!self::$instance || !is_a(self::$instance, 'rcmail')) { - self::$instance = new rcmail($env); - self::$instance->startup(); // init AFTER object was linked with self::$instance - } + /** + * Main tasks. + * + * @var array + */ + static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy'); - return self::$instance; - } - - - /** - * Initial startup function - * to register session, create database and imap connections - */ - protected function startup() - { - $this->init(self::INIT_WITH_DB | self::INIT_WITH_PLUGINS); - - // set filename if not index.php - if (($basename = basename($_SERVER['SCRIPT_FILENAME'])) && $basename != 'index.php') - $this->filename = $basename; - - // start session - $this->session_init(); - - // create user object - $this->set_user(new rcube_user($_SESSION['user_id'])); - - // set task and action properties - $this->set_task(rcube_utils::get_input_value('_task', rcube_utils::INPUT_GPC)); - $this->action = asciiwords(rcube_utils::get_input_value('_action', rcube_utils::INPUT_GPC)); - - // reset some session parameters when changing task - if ($this->task != 'utils') { - // we reset list page when switching to another task - // but only to the main task interface - empty action (#1489076) - // this will prevent from unintentional page reset on cross-task requests - if ($this->session && $_SESSION['task'] != $this->task && empty($this->action)) - $this->session->remove('page'); - // set current task to session - $_SESSION['task'] = $this->task; - } + /** + * Current task. + * + * @var string + */ + public $task; - // init output class - if (!empty($_REQUEST['_remote'])) - $GLOBALS['OUTPUT'] = $this->json_init(); - else - $GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed'])); - - // load plugins - $this->plugins->init($this, $this->task); - $this->plugins->load_plugins((array)$this->config->get('plugins', array()), array('filesystem_attachments', 'jqueryui')); - } - - - /** - * Setter for application task - * - * @param string Task to set - */ - public function set_task($task) - { - $task = asciiwords($task, true); - - if ($this->user && $this->user->ID) - $task = !$task ? 'mail' : $task; - else - $task = 'login'; - - $this->task = $task; - $this->comm_path = $this->url(array('task' => $this->task)); - - if ($this->output) - $this->output->set_env('task', $this->task); - } - - - /** - * Setter for system user object - * - * @param rcube_user Current user instance - */ - public function set_user($user) - { - if (is_object($user)) { - $this->user = $user; - - // overwrite config with user preferences - $this->config->set_user_prefs((array)$this->user->get_prefs()); - } + /** + * Current action. + * + * @var string + */ + public $action = ''; + public $comm_path = './'; + public $filename = ''; - $lang = $this->language_prop($this->config->get('language', $_SESSION['language'])); - $_SESSION['language'] = $this->user->language = $lang; + private $address_books = array(); + private $action_map = array(); - // set localization - setlocale(LC_ALL, $lang . '.utf8', $lang . '.UTF-8', 'en_US.utf8', 'en_US.UTF-8'); - // workaround for http://bugs.php.net/bug.php?id=18556 - if (version_compare(PHP_VERSION, '5.5.0', '<') && in_array($lang, array('tr_TR', 'ku', 'az_AZ'))) { - setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8'); - } - } - - - /** - * Return instance of the internal address book class - * - * @param string Address book identifier (-1 for default addressbook) - * @param boolean True if the address book needs to be writeable - * - * @return rcube_contacts Address book object - */ - public function get_address_book($id, $writeable = false) - { - $contacts = null; - $ldap_config = (array)$this->config->get('ldap_public'); - - // 'sql' is the alias for '0' used by autocomplete - if ($id == 'sql') - $id = '0'; - else if ($id == -1) { - $id = $this->config->get('default_addressbook'); - $default = true; - } + const ERROR_STORAGE = -2; + const ERROR_INVALID_REQUEST = 1; + const ERROR_INVALID_HOST = 2; + const ERROR_COOKIES_DISABLED = 3; - // use existing instance - if (isset($this->address_books[$id]) && ($this->address_books[$id] instanceof rcube_addressbook)) { - $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['storage_host'])); - } - else if ($id === '0') { - $contacts = new rcube_contacts($this->db, $this->get_user_id()); - } - else { - $plugin = $this->plugins->exec_hook('addressbook_get', array('id' => $id, 'writeable' => $writeable)); - // plugin returned instance of a rcube_addressbook - if ($plugin['instance'] instanceof rcube_addressbook) { - $contacts = $plugin['instance']; - } - } + /** + * This implements the 'singleton' design pattern + * + * @param string Environment name to run (e.g. live, dev, test) + * + * @return rcmail The one and only instance + */ + static function get_instance($env = '') + { + if (!self::$instance || !is_a(self::$instance, 'rcmail')) { + self::$instance = new rcmail($env); + // init AFTER object was linked with self::$instance + self::$instance->startup(); + } - // when user requested default writeable addressbook - // we need to check if default is writeable, if not we - // will return first writeable book (if any exist) - if ($contacts && $default && $contacts->readonly && $writeable) { - $contacts = null; + return self::$instance; } - // Get first addressbook from the list if configured default doesn't exist - // This can happen when user deleted the addressbook (e.g. Kolab folder) - if (!$contacts && (!$id || $default)) { - $source = reset($this->get_address_sources($writeable, !$default)); - if (!empty($source)) { - $contacts = $this->get_address_book($source['id']); - if ($contacts) { - $id = $source['id']; + /** + * Initial startup function + * to register session, create database and imap connections + */ + protected function startup() + { + $this->init(self::INIT_WITH_DB | self::INIT_WITH_PLUGINS); + + // set filename if not index.php + if (($basename = basename($_SERVER['SCRIPT_FILENAME'])) && $basename != 'index.php') { + $this->filename = $basename; } - } - } - if (!$contacts) { - // there's no default, just return - if ($default) { - return null; - } - - self::raise_error(array( - 'code' => 700, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Addressbook source ($id) not found!"), - true, true); - } + // start session + $this->session_init(); - // add to the 'books' array for shutdown function - $this->address_books[$id] = $contacts; + // create user object + $this->set_user(new rcube_user($_SESSION['user_id'])); - if ($writeable && $contacts->readonly) { - return null; - } + // set task and action properties + $this->set_task(rcube_utils::get_input_value('_task', rcube_utils::INPUT_GPC)); + $this->action = asciiwords(rcube_utils::get_input_value('_action', rcube_utils::INPUT_GPC)); - // set configured sort order - if ($sort_col = $this->config->get('addressbook_sort_col')) { - $contacts->set_sort_order($sort_col); - } + // reset some session parameters when changing task + if ($this->task != 'utils') { + // we reset list page when switching to another task + // but only to the main task interface - empty action (#1489076) + // this will prevent from unintentional page reset on cross-task requests + if ($this->session && $_SESSION['task'] != $this->task && empty($this->action)) { + $this->session->remove('page'); + } - return $contacts; - } - - - /** - * Return identifier of the address book object - * - * @param rcube_addressbook Addressbook source object - * - * @return string Source identifier - */ - public function get_address_book_id($object) - { - foreach ($this->address_books as $index => $book) { - if ($book === $object) { - return $index; - } - } - } - - - /** - * Return address books list - * - * @param boolean True if the address book needs to be writeable - * @param boolean True if the address book needs to be not hidden - * - * @return array Address books array - */ - public function get_address_sources($writeable = false, $skip_hidden = false) - { - $abook_type = (string) $this->config->get('address_book_type'); - $ldap_config = (array) $this->config->get('ldap_public'); - $autocomplete = (array) $this->config->get('autocomplete_addressbooks'); - $list = array(); - - // We are using the DB address book or a plugin address book - if (!empty($abook_type) && strtolower($abook_type) != 'ldap') { - if (!isset($this->address_books['0'])) - $this->address_books['0'] = new rcube_contacts($this->db, $this->get_user_id()); - $list['0'] = array( - 'id' => '0', - 'name' => $this->gettext('personaladrbook'), - 'groups' => $this->address_books['0']->groups, - 'readonly' => $this->address_books['0']->readonly, - 'autocomplete' => in_array('sql', $autocomplete), - 'undelete' => $this->address_books['0']->undelete && $this->config->get('undo_timeout'), - ); - } + // set current task to session + $_SESSION['task'] = $this->task; + } - if (!empty($ldap_config)) { - foreach ($ldap_config as $id => $prop) { - // handle misconfiguration - if (empty($prop) || !is_array($prop)) { - continue; - } - $list[$id] = array( - 'id' => $id, - 'name' => html::quote($prop['name']), - 'groups' => !empty($prop['groups']) || !empty($prop['group_filters']), - 'readonly' => !$prop['writable'], - 'hidden' => $prop['hidden'], - 'autocomplete' => in_array($id, $autocomplete) - ); - } - } + // init output class + if (!empty($_REQUEST['_remote'])) + $GLOBALS['OUTPUT'] = $this->json_init(); + else + $GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed'])); - $plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list)); - $list = $plugin['sources']; - - 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]); - } - // remove from list if hidden as requested - else if ($skip_hidden && $item['hidden']) { - unset($list[$idx]); - } + // load plugins + $this->plugins->init($this, $this->task); + $this->plugins->load_plugins((array)$this->config->get('plugins', array()), + array('filesystem_attachments', 'jqueryui')); } - return $list; - } - - /** - * Getter for compose responses. - * These are stored in local config and user preferences. - * - * @param boolean True to sort the list alphabetically - * @param boolean True if only this user's responses shall be listed - * @return array List of the current user's stored responses - */ - public function get_compose_responses($sorted = false, $user_only = false) - { - $responses = array(); - - if (!$user_only) { - foreach ($this->config->get('compose_responses_static', array()) as $response) { - if (empty($response['key'])) - $response['key'] = substr(md5($response['name']), 0, 16); - $response['static'] = true; - $response['class'] = 'readonly'; - $k = $sorted ? '0000-' . strtolower($response['name']) : $response['key']; - $responses[$k] = $response; - } - } + /** + * Setter for application task + * + * @param string Task to set + */ + public function set_task($task) + { + $task = asciiwords($task, true); - foreach ($this->config->get('compose_responses', array()) as $response) { - if (empty($response['key'])) - $response['key'] = substr(md5($response['name']), 0, 16); - $k = $sorted ? strtolower($response['name']) : $response['key']; - $responses[$k] = $response; - } + if ($this->user && $this->user->ID) + $task = !$task ? 'mail' : $task; + else + $task = 'login'; - // sort list by name - if ($sorted) { - ksort($responses, SORT_LOCALE_STRING); - } + $this->task = $task; + $this->comm_path = $this->url(array('task' => $this->task)); - return array_values($responses); - } - - - /** - * Init output object for GUI and add common scripts. - * This will instantiate a rcmail_output_html object and set - * environment vars according to the current session and configuration - * - * @param boolean True if this request is loaded in a (i)frame - * @return rcube_output Reference to HTML output object - */ - public function load_gui($framed = false) - { - // init output page - if (!($this->output instanceof rcmail_output_html)) - $this->output = new rcmail_output_html($this->task, $framed); - - // set refresh interval - $this->output->set_env('refresh_interval', $this->config->get('refresh_interval', 0)); - $this->output->set_env('session_lifetime', $this->config->get('session_lifetime', 0) * 60); - - if ($framed) { - $this->comm_path .= '&_framed=1'; - $this->output->set_env('framed', true); + if ($this->output) { + $this->output->set_env('task', $this->task); + } } - $this->output->set_env('task', $this->task); - $this->output->set_env('action', $this->action); - $this->output->set_env('comm_path', $this->comm_path); - $this->output->set_charset(RCUBE_CHARSET); - - if ($this->user && $this->user->ID) - $this->output->set_env('user_id', $this->user->get_hash()); - - // add some basic labels to client - $this->output->add_label('loading', 'servererror', 'requesttimedout', 'refreshing'); - - return $this->output; - } - - - /** - * Create an output object for JSON responses - * - * @return rcube_output Reference to JSON output object - */ - public function json_init() - { - if (!($this->output instanceof rcmail_output_json)) - $this->output = new rcmail_output_json($this->task); - - return $this->output; - } - - - /** - * Create session object and start the session. - */ - public function session_init() - { - parent::session_init(); - - // set initial session vars - if (!$_SESSION['user_id']) - $_SESSION['temp'] = true; - - // restore skin selection after logout - if ($_SESSION['temp'] && !empty($_SESSION['skin'])) - $this->config->set('skin', $_SESSION['skin']); - } - - - /** - * Perfom login to the mail server and to the webmail service. - * This will also create a new user entry if auto_create_user is configured. - * - * @param string Mail storage (IMAP) user name - * @param string Mail storage (IMAP) password - * @param string Mail storage (IMAP) host - * @param bool Enables cookie check - * - * @return boolean True on success, False on failure - */ - function login($username, $pass, $host = null, $cookiecheck = false) - { - $this->login_error = null; - - if (empty($username)) { - return false; - } + /** + * Setter for system user object + * + * @param rcube_user Current user instance + */ + public function set_user($user) + { + if (is_object($user)) { + $this->user = $user; - if ($cookiecheck && empty($_COOKIE)) { - $this->login_error = self::ERROR_COOKIES_DISABLED; - return false; - } + // overwrite config with user preferences + $this->config->set_user_prefs((array)$this->user->get_prefs()); + } - $config = $this->config->all(); - - if (!$host) - $host = $config['default_host']; - - // Validate that selected host is in the list of configured hosts - if (is_array($config['default_host'])) { - $allowed = false; - foreach ($config['default_host'] as $key => $host_allowed) { - if (!is_numeric($key)) - $host_allowed = $key; - if ($host == $host_allowed) { - $allowed = true; - break; - } - } - if (!$allowed) { - $host = null; - } - } - else if (!empty($config['default_host']) && $host != rcube_utils::parse_host($config['default_host'])) { - $host = null; - } + $lang = $this->language_prop($this->config->get('language', $_SESSION['language'])); + $_SESSION['language'] = $this->user->language = $lang; - if (!$host) { - $this->login_error = self::ERROR_INVALID_HOST; - return false; - } + // set localization + setlocale(LC_ALL, $lang . '.utf8', $lang . '.UTF-8', 'en_US.utf8', 'en_US.UTF-8'); - // parse $host URL - $a_host = parse_url($host); - if ($a_host['host']) { - $host = $a_host['host']; - $ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null; - if (!empty($a_host['port'])) - $port = $a_host['port']; - else if ($ssl && $ssl != 'tls' && (!$config['default_port'] || $config['default_port'] == 143)) - $port = 993; + // workaround for http://bugs.php.net/bug.php?id=18556 + if (version_compare(PHP_VERSION, '5.5.0', '<') && in_array($lang, array('tr_TR', 'ku', 'az_AZ'))) { + setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8'); + } } - if (!$port) { - $port = $config['default_port']; - } + /** + * Return instance of the internal address book class + * + * @param string Address book identifier (-1 for default addressbook) + * @param boolean True if the address book needs to be writeable + * + * @return rcube_contacts Address book object + */ + public function get_address_book($id, $writeable = false) + { + $contacts = null; + $ldap_config = (array)$this->config->get('ldap_public'); + + // 'sql' is the alias for '0' used by autocomplete + if ($id == 'sql') + $id = '0'; + else if ($id == -1) { + $id = $this->config->get('default_addressbook'); + $default = true; + } - // Check if we need to add/force domain to username - if (!empty($config['username_domain'])) { - $domain = is_array($config['username_domain']) ? $config['username_domain'][$host] : $config['username_domain']; + // use existing instance + if (isset($this->address_books[$id]) && ($this->address_books[$id] instanceof rcube_addressbook)) { + $contacts = $this->address_books[$id]; + } + else if ($id && $ldap_config[$id]) { + $domain = $this->config->mail_domain($_SESSION['storage_host']); + $contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $domain); + } + else if ($id === '0') { + $contacts = new rcube_contacts($this->db, $this->get_user_id()); + } + else { + $plugin = $this->plugins->exec_hook('addressbook_get', array('id' => $id, 'writeable' => $writeable)); - if ($domain = rcube_utils::parse_host((string)$domain, $host)) { - $pos = strpos($username, '@'); + // plugin returned instance of a rcube_addressbook + if ($plugin['instance'] instanceof rcube_addressbook) { + $contacts = $plugin['instance']; + } + } - // force configured domains - if (!empty($config['username_domain_forced']) && $pos !== false) { - $username = substr($username, 0, $pos) . '@' . $domain; + // when user requested default writeable addressbook + // we need to check if default is writeable, if not we + // will return first writeable book (if any exist) + if ($contacts && $default && $contacts->readonly && $writeable) { + $contacts = null; } - // just add domain if not specified - else if ($pos === false) { - $username .= '@' . $domain; + + // Get first addressbook from the list if configured default doesn't exist + // This can happen when user deleted the addressbook (e.g. Kolab folder) + if (!$contacts && (!$id || $default)) { + $source = reset($this->get_address_sources($writeable, !$default)); + if (!empty($source)) { + $contacts = $this->get_address_book($source['id']); + if ($contacts) { + $id = $source['id']; + } + } } - } - } - if (!isset($config['login_lc'])) { - $config['login_lc'] = 2; // default - } + if (!$contacts) { + // there's no default, just return + if ($default) { + return null; + } + + self::raise_error(array( + 'code' => 700, + 'file' => __FILE__, + 'line' => __LINE__, + 'message' => "Addressbook source ($id) not found!" + ), + true, true); + } + + // add to the 'books' array for shutdown function + $this->address_books[$id] = $contacts; + + if ($writeable && $contacts->readonly) { + return null; + } - // Convert username to lowercase. If storage backend - // is case-insensitive we need to store always the same username (#1487113) - if ($config['login_lc']) { - if ($config['login_lc'] == 2 || $config['login_lc'] === true) { - $username = mb_strtolower($username); - } - else if (strpos($username, '@')) { - // lowercase domain name - list($local, $domain) = explode('@', $username); - $username = $local . '@' . mb_strtolower($domain); - } + // set configured sort order + if ($sort_col = $this->config->get('addressbook_sort_col')) { + $contacts->set_sort_order($sort_col); + } + + return $contacts; } - // try to resolve email address from virtuser table - if (strpos($username, '@') && ($virtuser = rcube_user::email2user($username))) { - $username = $virtuser; + /** + * Return identifier of the address book object + * + * @param rcube_addressbook Addressbook source object + * + * @return string Source identifier + */ + public function get_address_book_id($object) + { + foreach ($this->address_books as $index => $book) { + if ($book === $object) { + return $index; + } + } } - // Here we need IDNA ASCII - // Only rcube_contacts class is using domain names in Unicode - $host = rcube_utils::idn_to_ascii($host); - $username = rcube_utils::idn_to_ascii($username); + /** + * Return address books list + * + * @param boolean True if the address book needs to be writeable + * @param boolean True if the address book needs to be not hidden + * + * @return array Address books array + */ + public function get_address_sources($writeable = false, $skip_hidden = false) + { + $abook_type = (string) $this->config->get('address_book_type'); + $ldap_config = (array) $this->config->get('ldap_public'); + $autocomplete = (array) $this->config->get('autocomplete_addressbooks'); + $list = array(); + + // We are using the DB address book or a plugin address book + if (!empty($abook_type) && strtolower($abook_type) != 'ldap') { + if (!isset($this->address_books['0'])) { + $this->address_books['0'] = new rcube_contacts($this->db, $this->get_user_id()); + } + + $list['0'] = array( + 'id' => '0', + 'name' => $this->gettext('personaladrbook'), + 'groups' => $this->address_books['0']->groups, + 'readonly' => $this->address_books['0']->readonly, + 'undelete' => $this->address_books['0']->undelete && $this->config->get('undo_timeout'), + 'autocomplete' => in_array('sql', $autocomplete), + ); + } + + if (!empty($ldap_config)) { + foreach ($ldap_config as $id => $prop) { + // handle misconfiguration + if (empty($prop) || !is_array($prop)) { + continue; + } + + $list[$id] = array( + 'id' => $id, + 'name' => html::quote($prop['name']), + 'groups' => !empty($prop['groups']) || !empty($prop['group_filters']), + 'readonly' => !$prop['writable'], + 'hidden' => $prop['hidden'], + 'autocomplete' => in_array($id, $autocomplete) + ); + } + } + + $plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list)); + $list = $plugin['sources']; + + 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]); + } + // remove from list if hidden as requested + else if ($skip_hidden && $item['hidden']) { + unset($list[$idx]); + } + } - // user already registered -> overwrite username - if ($user = rcube_user::query($username, $host)) { - $username = $user->data['username']; + return $list; } - $storage = $this->get_storage(); + /** + * Getter for compose responses. + * These are stored in local config and user preferences. + * + * @param boolean True to sort the list alphabetically + * @param boolean True if only this user's responses shall be listed + * + * @return array List of the current user's stored responses + */ + public function get_compose_responses($sorted = false, $user_only = false) + { + $responses = array(); + + if (!$user_only) { + foreach ($this->config->get('compose_responses_static', array()) as $response) { + if (empty($response['key'])) { + $response['key'] = substr(md5($response['name']), 0, 16); + } + + $response['static'] = true; + $response['class'] = 'readonly'; - // try to log in - if (!$storage->connect($host, $username, $pass, $port, $ssl)) { - return false; + $k = $sorted ? '0000-' . strtolower($response['name']) : $response['key']; + $responses[$k] = $response; + } + } + + foreach ($this->config->get('compose_responses', array()) as $response) { + if (empty($response['key'])) { + $response['key'] = substr(md5($response['name']), 0, 16); + } + + $k = $sorted ? strtolower($response['name']) : $response['key']; + $responses[$k] = $response; + } + + // sort list by name + if ($sorted) { + ksort($responses, SORT_LOCALE_STRING); + } + + return array_values($responses); } - // user already registered -> update user's record - if (is_object($user)) { - // update last login timestamp - $user->touch(); + /** + * Init output object for GUI and add common scripts. + * This will instantiate a rcmail_output_html object and set + * environment vars according to the current session and configuration + * + * @param boolean True if this request is loaded in a (i)frame + * + * @return rcube_output Reference to HTML output object + */ + public function load_gui($framed = false) + { + // init output page + if (!($this->output instanceof rcmail_output_html)) { + $this->output = new rcmail_output_html($this->task, $framed); + } + + // set refresh interval + $this->output->set_env('refresh_interval', $this->config->get('refresh_interval', 0)); + $this->output->set_env('session_lifetime', $this->config->get('session_lifetime', 0) * 60); + + if ($framed) { + $this->comm_path .= '&_framed=1'; + $this->output->set_env('framed', true); + } + + $this->output->set_env('task', $this->task); + $this->output->set_env('action', $this->action); + $this->output->set_env('comm_path', $this->comm_path); + $this->output->set_charset(RCUBE_CHARSET); + + if ($this->user && $this->user->ID) { + $this->output->set_env('user_id', $this->user->get_hash()); + } + + // add some basic labels to client + $this->output->add_label('loading', 'servererror', 'requesttimedout', 'refreshing'); + + return $this->output; } - // create new system user - else if ($config['auto_create_user']) { - if ($created = rcube_user::create($username, $host)) { - $user = $created; - } - else { - self::raise_error(array( - 'code' => 620, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Failed to create a user record. Maybe aborted by a plugin?" - ), true, false); - } + + /** + * Create an output object for JSON responses + * + * @return rcube_output Reference to JSON output object + */ + public function json_init() + { + if (!($this->output instanceof rcmail_output_json)) { + $this->output = new rcmail_output_json($this->task); + } + + return $this->output; } - else { - self::raise_error(array( - 'code' => 621, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Access denied for new user $username. 'auto_create_user' is disabled" - ), true, false); + + /** + * Create session object and start the session. + */ + public function session_init() + { + parent::session_init(); + + // set initial session vars + if (!$_SESSION['user_id']) { + $_SESSION['temp'] = true; + } + + // restore skin selection after logout + if ($_SESSION['temp'] && !empty($_SESSION['skin'])) { + $this->config->set('skin', $_SESSION['skin']); + } } - // login succeeded - if (is_object($user) && $user->ID) { - // Configure environment - $this->set_user($user); - $this->set_storage_prop(); + /** + * Perfom login to the mail server and to the webmail service. + * This will also create a new user entry if auto_create_user is configured. + * + * @param string Mail storage (IMAP) user name + * @param string Mail storage (IMAP) password + * @param string Mail storage (IMAP) host + * @param bool Enables cookie check + * + * @return boolean True on success, False on failure + */ + function login($username, $pass, $host = null, $cookiecheck = false) + { + $this->login_error = null; + + if (empty($username)) { + return false; + } - // fix some old settings according to namespace prefix - $this->fix_namespace_settings($user); + if ($cookiecheck && empty($_COOKIE)) { + $this->login_error = self::ERROR_COOKIES_DISABLED; + return false; + } - // create default folders on first login - if ($config['create_default_folders'] && (!empty($created) || empty($user->data['last_login']))) { - $storage->create_default_folders(); - } + $config = $this->config->all(); - // set session vars - $_SESSION['user_id'] = $user->ID; - $_SESSION['username'] = $user->data['username']; - $_SESSION['storage_host'] = $host; - $_SESSION['storage_port'] = $port; - $_SESSION['storage_ssl'] = $ssl; - $_SESSION['password'] = $this->encrypt($pass); - $_SESSION['login_time'] = time(); + if (!$host) { + $host = $config['default_host']; + } - if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_') - $_SESSION['timezone'] = rcube_utils::get_input_value('_timezone', rcube_utils::INPUT_GPC); + // Validate that selected host is in the list of configured hosts + if (is_array($config['default_host'])) { + $allowed = false; - // force reloading complete list of subscribed mailboxes - $storage->clear_cache('mailboxes', true); + foreach ($config['default_host'] as $key => $host_allowed) { + if (!is_numeric($key)) { + $host_allowed = $key; + } + if ($host == $host_allowed) { + $allowed = true; + break; + } + } - return true; - } + if (!$allowed) { + $host = null; + } + } + else if (!empty($config['default_host']) && $host != rcube_utils::parse_host($config['default_host'])) { + $host = null; + } + + if (!$host) { + $this->login_error = self::ERROR_INVALID_HOST; + return false; + } + + // parse $host URL + $a_host = parse_url($host); + if ($a_host['host']) { + $host = $a_host['host']; + $ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null; + + if (!empty($a_host['port'])) + $port = $a_host['port']; + else if ($ssl && $ssl != 'tls' && (!$config['default_port'] || $config['default_port'] == 143)) + $port = 993; + } + + if (!$port) { + $port = $config['default_port']; + } + + // Check if we need to add/force domain to username + if (!empty($config['username_domain'])) { + $domain = is_array($config['username_domain']) ? $config['username_domain'][$host] : $config['username_domain']; + + if ($domain = rcube_utils::parse_host((string)$domain, $host)) { + $pos = strpos($username, '@'); + + // force configured domains + if (!empty($config['username_domain_forced']) && $pos !== false) { + $username = substr($username, 0, $pos) . '@' . $domain; + } + // just add domain if not specified + else if ($pos === false) { + $username .= '@' . $domain; + } + } + } + + if (!isset($config['login_lc'])) { + $config['login_lc'] = 2; // default + } + + // Convert username to lowercase. If storage backend + // is case-insensitive we need to store always the same username (#1487113) + if ($config['login_lc']) { + if ($config['login_lc'] == 2 || $config['login_lc'] === true) { + $username = mb_strtolower($username); + } + else if (strpos($username, '@')) { + // lowercase domain name + list($local, $domain) = explode('@', $username); + $username = $local . '@' . mb_strtolower($domain); + } + } + + // try to resolve email address from virtuser table + if (strpos($username, '@') && ($virtuser = rcube_user::email2user($username))) { + $username = $virtuser; + } + + // Here we need IDNA ASCII + // Only rcube_contacts class is using domain names in Unicode + $host = rcube_utils::idn_to_ascii($host); + $username = rcube_utils::idn_to_ascii($username); - return false; - } + // user already registered -> overwrite username + if ($user = rcube_user::query($username, $host)) { + $username = $user->data['username']; + } + + $storage = $this->get_storage(); + + // try to log in + if (!$storage->connect($host, $username, $pass, $port, $ssl)) { + return false; + } + + // user already registered -> update user's record + if (is_object($user)) { + // update last login timestamp + $user->touch(); + } + // create new system user + else if ($config['auto_create_user']) { + if ($created = rcube_user::create($username, $host)) { + $user = $created; + } + else { + self::raise_error(array( + 'code' => 620, + 'file' => __FILE__, + 'line' => __LINE__, + 'message' => "Failed to create a user record. Maybe aborted by a plugin?" + ), + true, false); + } + } + else { + self::raise_error(array( + 'code' => 621, + 'file' => __FILE__, + 'line' => __LINE__, + 'message' => "Access denied for new user $username. 'auto_create_user' is disabled" + ), + true, false); + } + + // login succeeded + if (is_object($user) && $user->ID) { + // Configure environment + $this->set_user($user); + $this->set_storage_prop(); + + // fix some old settings according to namespace prefix + $this->fix_namespace_settings($user); + + // create default folders on first login + if ($config['create_default_folders'] && (!empty($created) || empty($user->data['last_login']))) { + $storage->create_default_folders(); + } + + // set session vars + $_SESSION['user_id'] = $user->ID; + $_SESSION['username'] = $user->data['username']; + $_SESSION['storage_host'] = $host; + $_SESSION['storage_port'] = $port; + $_SESSION['storage_ssl'] = $ssl; + $_SESSION['password'] = $this->encrypt($pass); + $_SESSION['login_time'] = time(); + + if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_') { + $_SESSION['timezone'] = rcube_utils::get_input_value('_timezone', rcube_utils::INPUT_GPC); + } + + // force reloading complete list of subscribed mailboxes + $storage->clear_cache('mailboxes', true); + return true; + } + + return false; + } /** * Returns error code of last login operation @@ -657,315 +680,317 @@ class rcmail extends rcube } } + /** + * Auto-select IMAP host based on the posted login information + * + * @return string Selected IMAP host + */ + public function autoselect_host() + { + $default_host = $this->config->get('default_host'); + $host = null; + + if (is_array($default_host)) { + $post_host = rcube_utils::get_input_value('_host', rcube_utils::INPUT_POST); + $post_user = rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST); - /** - * Auto-select IMAP host based on the posted login information - * - * @return string Selected IMAP host - */ - public function autoselect_host() - { - $default_host = $this->config->get('default_host'); - $host = null; - - if (is_array($default_host)) { - $post_host = rcube_utils::get_input_value('_host', rcube_utils::INPUT_POST); - $post_user = rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST); - - list(, $domain) = explode('@', $post_user); - - // direct match in default_host array - if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) { - $host = $post_host; - } - // try to select host by mail domain - else if (!empty($domain)) { - foreach ($default_host as $storage_host => $mail_domains) { - if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) { - $host = $storage_host; - break; - } - else if (stripos($storage_host, $domain) !== false || stripos(strval($mail_domains), $domain) !== false) { - $host = is_numeric($storage_host) ? $mail_domains : $storage_host; - break; - } + list(, $domain) = explode('@', $post_user); + + // direct match in default_host array + if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) { + $host = $post_host; + } + // try to select host by mail domain + else if (!empty($domain)) { + foreach ($default_host as $storage_host => $mail_domains) { + if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) { + $host = $storage_host; + break; + } + else if (stripos($storage_host, $domain) !== false || stripos(strval($mail_domains), $domain) !== false) { + $host = is_numeric($storage_host) ? $mail_domains : $storage_host; + break; + } + } + } + + // take the first entry if $host is still not set + if (empty($host)) { + list($key, $val) = each($default_host); + $host = is_numeric($key) ? $val : $key; + } + } + else if (empty($default_host)) { + $host = rcube_utils::get_input_value('_host', rcube_utils::INPUT_POST); + } + else { + $host = rcube_utils::parse_host($default_host); } - } - // take the first entry if $host is still not set - if (empty($host)) { - list($key, $val) = each($default_host); - $host = is_numeric($key) ? $val : $key; - } + return $host; } - else if (empty($default_host)) { - $host = rcube_utils::get_input_value('_host', rcube_utils::INPUT_POST); + + /** + * Destroy session data and remove cookie + */ + public function kill_session() + { + $this->plugins->exec_hook('session_destroy'); + + $this->session->kill(); + $_SESSION = array('language' => $this->user->language, 'temp' => true, 'skin' => $this->config->get('skin')); + $this->user->reset(); } - else - $host = rcube_utils::parse_host($default_host); - return $host; - } + /** + * Do server side actions on logout + */ + public function logout_actions() + { + $config = $this->config->all(); + $storage = $this->get_storage(); + + if ($config['logout_purge'] && !empty($config['trash_mbox'])) { + $storage->clear_folder($config['trash_mbox']); + } + if ($config['logout_expunge']) { + $storage->expunge_folder('INBOX'); + } - /** - * Destroy session data and remove cookie - */ - public function kill_session() - { - $this->plugins->exec_hook('session_destroy'); + // Try to save unsaved user preferences + if (!empty($_SESSION['preferences'])) { + $this->user->save_prefs(unserialize($_SESSION['preferences'])); + } + } - $this->session->kill(); - $_SESSION = array('language' => $this->user->language, 'temp' => true, 'skin' => $this->config->get('skin')); - $this->user->reset(); - } + /** + * Generate a unique token to be used in a form request + * + * @return string The request token + */ + public function get_request_token() + { + $sess_id = $_COOKIE[ini_get('session.name')]; + if (!$sess_id) { + $sess_id = session_id(); + } - /** - * Do server side actions on logout - */ - public function logout_actions() - { - $config = $this->config->all(); - $storage = $this->get_storage(); + $plugin = $this->plugins->exec_hook('request_token', array( + 'value' => md5('RT' . $this->get_user_id() . $this->config->get('des_key') . $sess_id))); - if ($config['logout_purge'] && !empty($config['trash_mbox'])) { - $storage->clear_folder($config['trash_mbox']); + return $plugin['value']; } - if ($config['logout_expunge']) { - $storage->expunge_folder('INBOX'); - } + /** + * Check if the current request contains a valid token + * + * @param int Request method + * + * @return boolean True if request token is valid false if not + */ + public function check_request($mode = rcube_utils::INPUT_POST) + { + $token = rcube_utils::get_input_value('_token', $mode); + $sess_id = $_COOKIE[ini_get('session.name')]; - // Try to save unsaved user preferences - if (!empty($_SESSION['preferences'])) { - $this->user->save_prefs(unserialize($_SESSION['preferences'])); - } - } - - - /** - * Generate a unique token to be used in a form request - * - * @return string The request token - */ - public function get_request_token() - { - $sess_id = $_COOKIE[ini_get('session.name')]; - if (!$sess_id) $sess_id = session_id(); - - $plugin = $this->plugins->exec_hook('request_token', array( - 'value' => md5('RT' . $this->get_user_id() . $this->config->get('des_key') . $sess_id))); - - return $plugin['value']; - } - - - /** - * Check if the current request contains a valid token - * - * @param int Request method - * @return boolean True if request token is valid false if not - */ - public function check_request($mode = rcube_utils::INPUT_POST) - { - $token = rcube_utils::get_input_value('_token', $mode); - $sess_id = $_COOKIE[ini_get('session.name')]; - return !empty($sess_id) && $token == $this->get_request_token(); - } - - - /** - * Build a valid URL to this instance of Roundcube - * - * @param mixed Either a string with the action or url parameters as key-value pairs - * - * @return string Valid application URL - */ - public function url($p) - { - if (!is_array($p)) { - if (strpos($p, 'http') === 0) - return $p; - - $p = array('_action' => @func_get_arg(0)); + return !empty($sess_id) && $token == $this->get_request_token(); } - $task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task); - $p['_task'] = $task; - unset($p['task']); - - $url = './' . $this->filename; - $delm = '?'; - foreach (array_reverse($p) as $key => $val) { - if ($val !== '' && $val !== null) { - $par = $key[0] == '_' ? $key : '_'.$key; - $url .= $delm.urlencode($par).'='.urlencode($val); - $delm = '&'; - } - } - return $url; - } + /** + * Build a valid URL to this instance of Roundcube + * + * @param mixed Either a string with the action or url parameters as key-value pairs + * + * @return string Valid application URL + */ + public function url($p) + { + if (!is_array($p)) { + if (strpos($p, 'http') === 0) { + return $p; + } + $p = array('_action' => @func_get_arg(0)); + } - /** - * Function to be executed in script shutdown - */ - public function shutdown() - { - parent::shutdown(); + $task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task); + $p['_task'] = $task; + unset($p['task']); - foreach ($this->address_books as $book) { - if (is_object($book) && is_a($book, 'rcube_addressbook')) - $book->close(); - } + $url = './' . $this->filename; + $delm = '?'; - // write performance stats to logs/console - if ($this->config->get('devel_mode')) { - if (function_exists('memory_get_usage')) - $mem = $this->show_bytes(memory_get_usage()); - if (function_exists('memory_get_peak_usage')) - $mem .= '/'.$this->show_bytes(memory_get_peak_usage()); - - $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : ''); - if (defined('RCMAIL_START')) - self::print_timer(RCMAIL_START, $log); - else - self::console($log); - } - } - - - /** - * 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]; + foreach (array_reverse($p) as $key => $val) { + if ($val !== '' && $val !== null) { + $par = $key[0] == '_' ? $key : '_'.$key; + $url .= $delm.urlencode($par).'='.urlencode($val); + $delm = '&'; + } + } + + return $url; } - return strtr($this->action, '-', '_') . '.inc'; - } - - - /** - * Fixes some user preferences according to namespace handling change. - * Old Roundcube versions were using folder names with removed namespace prefix. - * Now we need to add the prefix on servers where personal namespace has prefix. - * - * @param rcube_user $user User object - */ - private function fix_namespace_settings($user) - { - $prefix = $this->storage->get_namespace('prefix'); - $prefix_len = strlen($prefix); - - if (!$prefix_len) - return; - - $prefs = $this->config->all(); - if (!empty($prefs['namespace_fixed'])) - return; - - // Build namespace prefix regexp - $ns = $this->storage->get_namespace(); - $regexp = array(); - - foreach ($ns as $entry) { - if (!empty($entry)) { - foreach ($entry as $item) { - if (strlen($item[0])) { - $regexp[] = preg_quote($item[0], '/'); - } + /** + * Function to be executed in script shutdown + */ + public function shutdown() + { + parent::shutdown(); + + foreach ($this->address_books as $book) { + if (is_object($book) && is_a($book, 'rcube_addressbook')) + $book->close(); } - } - } - $regexp = '/^('. implode('|', $regexp).')/'; - // Fix preferences - $opts = array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox', 'archive_mbox'); - foreach ($opts as $opt) { - if ($value = $prefs[$opt]) { - if ($value != 'INBOX' && !preg_match($regexp, $value)) { - $prefs[$opt] = $prefix.$value; + // write performance stats to logs/console + if ($this->config->get('devel_mode')) { + if (function_exists('memory_get_usage')) + $mem = $this->show_bytes(memory_get_usage()); + if (function_exists('memory_get_peak_usage')) + $mem .= '/'.$this->show_bytes(memory_get_peak_usage()); + + $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : ''); + + if (defined('RCMAIL_START')) + self::print_timer(RCMAIL_START, $log); + else + self::console($log); } - } } - if (!empty($prefs['default_folders'])) { - foreach ($prefs['default_folders'] as $idx => $name) { - if ($name != 'INBOX' && !preg_match($regexp, $name)) { - $prefs['default_folders'][$idx] = $prefix.$name; + /** + * 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; + } } - } } - if (!empty($prefs['search_mods'])) { - $folders = array(); - foreach ($prefs['search_mods'] as $idx => $value) { - if ($idx != 'INBOX' && $idx != '*' && !preg_match($regexp, $idx)) { - $idx = $prefix.$idx; + /** + * 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]; } - $folders[$idx] = $value; - } - $prefs['search_mods'] = $folders; + + return strtr($this->action, '-', '_') . '.inc'; } - if (!empty($prefs['message_threading'])) { - $folders = array(); - foreach ($prefs['message_threading'] as $idx => $value) { - if ($idx != 'INBOX' && !preg_match($regexp, $idx)) { - $idx = $prefix.$idx; + /** + * Fixes some user preferences according to namespace handling change. + * Old Roundcube versions were using folder names with removed namespace prefix. + * Now we need to add the prefix on servers where personal namespace has prefix. + * + * @param rcube_user $user User object + */ + private function fix_namespace_settings($user) + { + $prefix = $this->storage->get_namespace('prefix'); + $prefix_len = strlen($prefix); + + if (!$prefix_len) + return; + + $prefs = $this->config->all(); + if (!empty($prefs['namespace_fixed'])) + return; + + // Build namespace prefix regexp + $ns = $this->storage->get_namespace(); + $regexp = array(); + + foreach ($ns as $entry) { + if (!empty($entry)) { + foreach ($entry as $item) { + if (strlen($item[0])) { + $regexp[] = preg_quote($item[0], '/'); + } + } + } + } + $regexp = '/^('. implode('|', $regexp).')/'; + + // Fix preferences + $opts = array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox', 'archive_mbox'); + foreach ($opts as $opt) { + if ($value = $prefs[$opt]) { + if ($value != 'INBOX' && !preg_match($regexp, $value)) { + $prefs[$opt] = $prefix.$value; + } + } } - $folders[$prefix.$idx] = $value; - } - $prefs['message_threading'] = $folders; - } - if (!empty($prefs['collapsed_folders'])) { - $folders = explode('&&', $prefs['collapsed_folders']); - $count = count($folders); - $folders_str = ''; + if (!empty($prefs['default_folders'])) { + foreach ($prefs['default_folders'] as $idx => $name) { + if ($name != 'INBOX' && !preg_match($regexp, $name)) { + $prefs['default_folders'][$idx] = $prefix.$name; + } + } + } - if ($count) { - $folders[0] = substr($folders[0], 1); - $folders[$count-1] = substr($folders[$count-1], 0, -1); - } + if (!empty($prefs['search_mods'])) { + $folders = array(); + foreach ($prefs['search_mods'] as $idx => $value) { + if ($idx != 'INBOX' && $idx != '*' && !preg_match($regexp, $idx)) { + $idx = $prefix.$idx; + } + $folders[$idx] = $value; + } - foreach ($folders as $value) { - if ($value != 'INBOX' && !preg_match($regexp, $value)) { - $value = $prefix.$value; + $prefs['search_mods'] = $folders; + } + + if (!empty($prefs['message_threading'])) { + $folders = array(); + foreach ($prefs['message_threading'] as $idx => $value) { + if ($idx != 'INBOX' && !preg_match($regexp, $idx)) { + $idx = $prefix.$idx; + } + $folders[$prefix.$idx] = $value; + } + + $prefs['message_threading'] = $folders; } - $folders_str .= '&'.$value.'&'; - } - $prefs['collapsed_folders'] = $folders_str; - } - $prefs['namespace_fixed'] = true; + if (!empty($prefs['collapsed_folders'])) { + $folders = explode('&&', $prefs['collapsed_folders']); + $count = count($folders); + $folders_str = ''; - // save updated preferences and reset imap settings (default folders) - $user->save_prefs($prefs); - $this->set_storage_prop(); - } + if ($count) { + $folders[0] = substr($folders[0], 1); + $folders[$count-1] = substr($folders[$count-1], 0, -1); + } + foreach ($folders as $value) { + if ($value != 'INBOX' && !preg_match($regexp, $value)) { + $value = $prefix.$value; + } + $folders_str .= '&'.$value.'&'; + } + + $prefs['collapsed_folders'] = $folders_str; + } + + $prefs['namespace_fixed'] = true; + + // save updated preferences and reset imap settings (default folders) + $user->save_prefs($prefs); + $this->set_storage_prop(); + } /** * Overwrite action variable @@ -978,7 +1003,6 @@ class rcmail extends rcube $this->output->set_env('action', $action); } - /** * Set environment variables for specified config options */ @@ -991,7 +1015,6 @@ class rcmail extends rcube } } - /** * Returns RFC2822 formatted current date in user's timezone * @@ -1011,7 +1034,6 @@ class rcmail extends rcube return $date->format('r'); } - /** * Write login data (name, ID, IP address) to the 'userlogins' log file. */ @@ -1036,14 +1058,13 @@ class rcmail extends rcube } $message = sprintf('Successful login for %s (ID: %d) from %s in session %s', - $user_name, $user_id, rcube_utils::remote_ip(), session_id()); + $user_name, $user_id, rcube_utils::remote_ip(), session_id()); } // log login self::write_log('userlogins', $message); } - /** * Create a HTML table based on the given data * @@ -1093,7 +1114,6 @@ class rcmail extends rcube return $table->show($attrib); } - /** * Convert the given date to a human readable form * This uses the date formatting properties from config @@ -1223,7 +1243,6 @@ class rcmail extends rcube return $out; } - /** * Return folders list in HTML * @@ -1316,7 +1335,6 @@ class rcmail extends rcube return $out; } - /** * Return folders list as html_select object * @@ -1360,7 +1378,6 @@ class rcmail extends rcube return $select; } - /** * Create a hierarchical array of the mailbox list */ @@ -1418,7 +1435,6 @@ class rcmail extends rcube } } - /** * Return html for a structured list <ul> for the mailbox tree */ @@ -1513,7 +1529,6 @@ class rcmail extends rcube return $out; } - /** * Return html for a flat list