From 7e3298753a9f93405ef44b46ba4db4ca98553b51 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Sat, 14 Nov 2015 04:08:07 -0500 Subject: [PATCH] Use ternary operator where aplicable --- program/lib/Roundcube/rcube.php | 695 +++++++++++++++++++++++++++++++++++---------------------- 1 files changed, 422 insertions(+), 273 deletions(-) diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index fbf9540..3b17d99 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -1,10 +1,10 @@ <?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-2014, The Roundcube Dev Team | + | Copyright (C) 2011-2014, Kolab Systems AG | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -18,7 +18,6 @@ +-----------------------------------------------------------------------+ */ - /** * Base class of the Roundcube Framework * implemented as singleton @@ -28,8 +27,16 @@ */ class rcube { - const INIT_WITH_DB = 1; + // Init options + const INIT_WITH_DB = 1; const INIT_WITH_PLUGINS = 2; + + // Request status + const REQUEST_VALID = 0; + const REQUEST_ERROR_URL = 1; + const REQUEST_ERROR_TOKEN = 2; + + const DEBUG_LINE_LENGTH = 4096; /** * Singleton instace of rcube @@ -94,6 +101,19 @@ */ public $plugins; + /** + * Instance of rcube_user class. + * + * @var rcube_user + */ + public $user; + + /** + * Request status + * + * @var int + */ + public $request_status = 0; /* private/protected vars */ protected $texts; @@ -104,8 +124,8 @@ /** * This implements the 'singleton' design pattern * - * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants - * @param string Environment name to run (e.g. live, dev, test) + * @param integer $mode Options to initialize with this instance. See rcube::INIT_WITH_* constants + * @param string $env Environment name to run (e.g. live, dev, test) * * @return rcube The one and only instance */ @@ -119,7 +139,6 @@ return self::$instance; } - /** * Private constructor */ @@ -131,7 +150,6 @@ register_shutdown_function(array($this, 'shutdown')); } - /** * Initial startup function @@ -156,7 +174,6 @@ } } - /** * Get the current database connection * @@ -165,14 +182,17 @@ public function get_dbh() { if (!$this->db) { - $config_all = $this->config->all(); - $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']); - $this->db->set_debug((bool)$config_all['sql_debug']); + $this->db = rcube_db::factory( + $this->config->get('db_dsnw'), + $this->config->get('db_dsnr'), + $this->config->get('db_persistent') + ); + + $this->db->set_debug((bool)$this->config->get('sql_debug')); } return $this->db; } - /** * Get global handle for memcache access @@ -192,7 +212,10 @@ $this->mc_available = 0; // add all configured hosts to pool - $pconnect = $this->config->get('memcache_pconnect', true); + $pconnect = $this->config->get('memcache_pconnect', true); + $timeout = $this->config->get('memcache_timeout', 1); + $retry_interval = $this->config->get('memcache_retry_interval', 15); + foreach ($this->config->get('memcache_hosts', array()) as $host) { if (substr($host, 0, 7) != 'unix://') { list($host, $port) = explode(':', $host); @@ -203,7 +226,7 @@ } $this->mc_available += intval($this->memcache->addServer( - $host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure'))); + $host, $port, $pconnect, 1, $timeout, $retry_interval, false, array($this, 'memcache_failure'))); } // test connection and failover (will result in $this->mc_available == 0 on complete failure) @@ -216,7 +239,6 @@ return $this->memcache; } - /** * Callback for memcache failure @@ -236,7 +258,6 @@ } } - /** * Initialize and get cache object * @@ -255,7 +276,6 @@ return $this->caches[$name]; } - /** * Initialize and get shared cache object @@ -289,11 +309,10 @@ return $this->caches[$shared_name]; } - /** * Create SMTP object and connect to server * - * @param boolean True if connection should be established + * @param boolean $connect True if connection should be established */ public function smtp_init($connect = false) { @@ -303,7 +322,6 @@ $this->smtp->connect(); } } - /** * Initialize and get storage object @@ -319,7 +337,6 @@ return $this->storage; } - /** * Initialize storage object @@ -348,40 +365,18 @@ // for backward compat. (deprecated, will be removed) $this->imap = $this->storage; - // enable caching of mail data - $storage_cache = $this->config->get("{$driver}_cache"); - $messages_cache = $this->config->get('messages_cache'); - // for backward compatybility - if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) { - $storage_cache = 'db'; - $messages_cache = true; - } - - if ($storage_cache) { - $this->storage->set_caching($storage_cache); - } - if ($messages_cache) { - $this->storage->set_messages_caching(true); - } - - // set pagesize from config - $pagesize = $this->config->get('mail_pagesize'); - if (!$pagesize) { - $pagesize = $this->config->get('pagesize', 50); - } - $this->storage->set_pagesize($pagesize); - // 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'])) { @@ -400,30 +395,85 @@ $this->storage->set_options($options); $this->set_storage_prop(); - } + // subscribe to 'storage_connected' hook for session logging + if ($this->config->get('imap_log_session', false)) { + $this->plugins->register_hook('storage_connected', array($this, 'storage_log_session')); + } + } /** * Set storage parameters. - * This must be done AFTER connecting to the server! */ protected function set_storage_prop() { $storage = $this->get_storage(); + // set pagesize from config + $pagesize = $this->config->get('mail_pagesize'); + if (!$pagesize) { + $pagesize = $this->config->get('pagesize', 50); + } + + $storage->set_pagesize($pagesize); $storage->set_charset($this->config->get('default_charset', RCUBE_CHARSET)); - if ($default_folders = $this->config->get('default_folders')) { - $storage->set_default_folders($default_folders); + // enable caching of mail data + $driver = $this->config->get('storage_driver', 'imap'); + $storage_cache = $this->config->get("{$driver}_cache"); + $messages_cache = $this->config->get('messages_cache'); + // for backward compatybility + if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) { + $storage_cache = 'db'; + $messages_cache = true; } - if (isset($_SESSION['mbox'])) { - $storage->set_folder($_SESSION['mbox']); + + if ($storage_cache) { + $storage->set_caching($storage_cache); } - if (isset($_SESSION['page'])) { - $storage->set_page($_SESSION['page']); + if ($messages_cache) { + $storage->set_messages_caching(true); } } + /** + * Set special folders type association. + * This must be done AFTER connecting to the server! + */ + protected function set_special_folders() + { + $storage = $this->get_storage(); + $folders = $storage->get_special_folders(true); + $prefs = array(); + + // check SPECIAL-USE flags on IMAP folders + foreach ($folders as $type => $folder) { + $idx = $type . '_mbox'; + if ($folder !== $this->config->get($idx)) { + $prefs[$idx] = $folder; + } + } + + // Some special folders differ, update user preferences + if (!empty($prefs) && $this->user) { + $this->user->save_prefs($prefs); + } + + // create default folders (on login) + if ($this->config->get('create_default_folders')) { + $storage->create_default_folders(); + } + } + + /** + * Callback for IMAP connection events to log session identifiers + */ + public function storage_log_session($args) + { + if (!empty($args['session']) && session_id()) { + $this->write_log('imap_session', $args['session']); + } + } /** * Create session object and start the session. @@ -455,28 +505,20 @@ } ini_set('session.cookie_secure', $is_secure); - ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid'); + ini_set('session.name', $sess_name ?: 'roundcube_sessid'); ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); ini_set('session.cookie_httponly', 1); - // use database for storing session data - $this->session = new rcube_session($this->get_dbh(), $this->config); - + // get session driver instance + $this->session = rcube_session::factory($this->config); $this->session->register_gc_handler(array($this, 'gc')); - $this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME'])); - $this->session->set_ip_check($this->config->get('ip_check')); - - if ($this->config->get('session_auth_name')) { - $this->session->set_cookiename($this->config->get('session_auth_name')); - } // start PHP session (if not in CLI mode) if ($_SERVER['REMOTE_ADDR']) { $this->session->start(); } } - /** * Garbage collector - cache/temp cleaner @@ -490,7 +532,6 @@ $this->gc_temp(); } - /** * Garbage collector function for temp files. * Remove temp files older than two days @@ -498,7 +539,14 @@ public function gc_temp() { $tmp = unslashify($this->config->get('temp_dir')); - $expire = time() - 172800; // expire in 48 hours + + // expire in 48 hours by default + $temp_dir_ttl = $this->config->get('temp_dir_ttl', '48h'); + $temp_dir_ttl = get_offset_sec($temp_dir_ttl); + if ($temp_dir_ttl < 6*3600) + $temp_dir_ttl = 6*3600; // 6 hours sensible lower bound. + + $expire = time() - $temp_dir_ttl; if ($tmp && ($dir = opendir($tmp))) { while (($fname = readdir($dir)) !== false) { @@ -514,7 +562,6 @@ closedir($dir); } } - /** * Runs garbage collector with probability based on @@ -533,7 +580,6 @@ } } } - /** * Get localized text in the desired language @@ -555,7 +601,7 @@ $attrib = array('name' => $attrib); } - $name = $attrib['name'] ? $attrib['name'] : ''; + $name = (string) $attrib['name']; // attrib contain text values: use them from now if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us'])) { @@ -573,7 +619,7 @@ // replace vars in text if (is_array($attrib['vars'])) { foreach ($attrib['vars'] as $var_key => $var_value) { - $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text); + $text = str_replace($var_key[0] != '$' ? '$'.$var_key : $var_key, $var_value, $text); } } @@ -590,7 +636,6 @@ return strtr($text, array('\n' => "\n")); } - /** * Check if the given text label exists @@ -631,16 +676,16 @@ return false; } - /** * Load a localization package * - * @param string Language ID - * @param array Additional text labels/messages + * @param string $lang Language ID + * @param array $add Additional text labels/messages + * @param array $merge Additional text labels/messages to merge */ - public function load_language($lang = null, $add = array()) + public function load_language($lang = null, $add = array(), $merge = array()) { - $lang = $this->language_prop(($lang ? $lang : $_SESSION['language'])); + $lang = $this->language_prop($lang ?: $_SESSION['language']); // load localized texts if (empty($this->texts) || $lang != $_SESSION['language']) { @@ -678,13 +723,17 @@ if (is_array($add) && !empty($add)) { $this->texts += $add; } - } + // merge additional texts (from plugin) + if (is_array($merge) && !empty($merge)) { + $this->texts = array_merge($this->texts, $merge); + } + } /** * Check the given string and return a valid language code * - * @param string Language code + * @param string $lang Language code * * @return string Valid language code */ @@ -695,7 +744,11 @@ // user HTTP_ACCEPT_LANGUAGE if no language is specified if (empty($lang) || $lang == 'auto') { $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); - $lang = str_replace('-', '_', $accept_langs[0]); + $lang = $accept_langs[0]; + + if (preg_match('/^([a-z]+)[_-]([a-z]+)$/i', $lang, $m)) { + $lang = $m[1] . '_' . strtoupper($m[2]); + } } if (empty($rcube_languages)) { @@ -727,7 +780,6 @@ return $lang; } - /** * Read directory program/localization and return a list of available languages * @@ -757,67 +809,38 @@ return $sa_languages; } - /** - * Encrypt using 3DES + * Encrypt a string * - * @param string $clear clear text input - * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key' - * @param boolean $base64 whether or not to base64_encode() the result before returning + * @param string $clear Clear text input + * @param string $key Encryption key to retrieve from the configuration, defaults to 'des_key' + * @param boolean $base64 Whether or not to base64_encode() the result before returning * - * @return string encrypted text + * @return string Encrypted text */ public function encrypt($clear, $key = 'des_key', $base64 = true) { - if (!$clear) { + if (!is_string($clear) || !strlen($clear)) { return ''; } - /*- - * Add a single canary byte to the end of the clear text, which - * will help find out how much of padding will need to be removed - * upon decryption; see http://php.net/mcrypt_generic#68082 - */ - $clear = pack("a*H2", $clear, "80"); - - if (function_exists('mcrypt_module_open') && - ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")) - ) { - $iv = $this->create_iv(mcrypt_enc_get_iv_size($td)); - mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); - $cipher = $iv . mcrypt_generic($td, $clear); - mcrypt_generic_deinit($td); - mcrypt_module_close($td); - } - else { - @include_once 'des.inc'; - - if (function_exists('des')) { - $des_iv_size = 8; - $iv = $this->create_iv($des_iv_size); - $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv); - } - else { - self::raise_error(array( - 'code' => 500, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available" - ), true, true); - } - } + $ckey = $this->config->get_crypto_key($key); + $method = $this->config->get_crypto_method(); + $opts = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true; + $iv = rcube_utils::random_bytes(openssl_cipher_iv_length($method), true); + $cipher = $iv . openssl_encrypt($clear, $method, $ckey, $opts, $iv); return $base64 ? base64_encode($cipher) : $cipher; } - /** - * Decrypt 3DES-encrypted string + * Decrypt a string * - * @param string $cipher encrypted text - * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key' - * @param boolean $base64 whether or not input is base64-encoded + * @param string $cipher Encrypted text + * @param string $key Encryption key to retrieve from the configuration, defaults to 'des_key' + * @param boolean $base64 Whether or not input is base64-encoded * - * @return string decrypted text + * @return string Decrypted text */ public function decrypt($cipher, $key = 'des_key', $base64 = true) { @@ -825,77 +848,122 @@ return ''; } - $cipher = $base64 ? base64_decode($cipher) : $cipher; + $cipher = $base64 ? base64_decode($cipher) : $cipher; + $ckey = $this->config->get_crypto_key($key); + $method = $this->config->get_crypto_method(); + $opts = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true; + $iv_size = openssl_cipher_iv_length($method); + $iv = substr($cipher, 0, $iv_size); - if (function_exists('mcrypt_module_open') && - ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")) - ) { - $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); - mcrypt_module_close($td); - } - else { - @include_once 'des.inc'; - - if (function_exists('des')) { - $des_iv_size = 8; - $iv = substr($cipher, 0, $des_iv_size); - $cipher = substr($cipher, $des_iv_size); - $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv); - } - else { - self::raise_error(array( - 'code' => 500, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available" - ), true, true); - } + // session corruption? (#1485970) + if (strlen($iv) < $iv_size) { + return ''; } - /*- - * Trim PHP's padding and the canary byte; see note in - * rcube::encrypt() and http://php.net/mcrypt_generic#68082 - */ - $clear = substr(rtrim($clear, "\0"), 0, -1); + $cipher = substr($cipher, $iv_size); + $clear = openssl_decrypt($cipher, $method, $ckey, $opts, $iv); return $clear; } - /** - * Generates encryption initialization vector (IV) + * Returns session token for secure URLs * - * @param int Vector size + * @param bool $generate Generate token if not exists in session yet * - * @return string Vector string + * @return string|bool Token string, False when disabled */ - private function create_iv($size) + public function get_secure_url_token($generate = false) { - // mcrypt_create_iv() can be slow when system lacks entrophy - // we'll generate IV vector manually - $iv = ''; - for ($i = 0; $i < $size; $i++) { - $iv .= chr(mt_rand(0, 255)); + if ($len = $this->config->get('use_secure_urls')) { + if (empty($_SESSION['secure_token']) && $generate) { + // generate x characters long token + $length = $len > 1 ? $len : 16; + $token = rcube_utils::random_bytes($length); + + $plugin = $this->plugins->exec_hook('secure_token', + array('value' => $token, 'length' => $length)); + + $_SESSION['secure_token'] = $plugin['value']; + } + + return $_SESSION['secure_token']; } - return $iv; + return false; } + /** + * Generate a unique token to be used in a form request + * + * @return string The request token + */ + public function get_request_token() + { + if (empty($_SESSION['request_token'])) { + $plugin = $this->plugins->exec_hook('request_token', array( + 'value' => rcube_utils::random_bytes(32))); + + $_SESSION['request_token'] = $plugin['value']; + } + + return $_SESSION['request_token']; + } + + /** + * Check if the current request contains a valid token. + * Empty requests aren't checked until use_secure_urls is set. + * + * @param int $mode Request method + * + * @return boolean True if request token is valid false if not + */ + public function check_request($mode = rcube_utils::INPUT_POST) + { + // check secure token in URL if enabled + if ($token = $this->get_secure_url_token()) { + foreach (explode('/', preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI'])) as $tok) { + if ($tok == $token) { + return true; + } + } + + $this->request_status = self::REQUEST_ERROR_URL; + + return false; + } + + $sess_tok = $this->get_request_token(); + + // ajax requests + if (rcube_utils::request_header('X-Roundcube-Request') == $sess_tok) { + return true; + } + + // skip empty requests + if (($mode == rcube_utils::INPUT_POST && empty($_POST)) + || ($mode == rcube_utils::INPUT_GET && empty($_GET)) + ) { + return true; + } + + // default method of securing requests + $token = rcube_utils::get_input_value('_token', $mode); + $sess_id = $_COOKIE[ini_get('session.name')]; + + if (empty($sess_id) || $token != $sess_tok) { + $this->request_status = self::REQUEST_ERROR_TOKEN; + return false; + } + + return true; + } /** * Build a valid URL to this instance of Roundcube * - * @param mixed Either a string with the action or url parameters as key-value pairs + * @param mixed $p Either a string with the action or url parameters as key-value pairs + * * @return string Valid application URL */ public function url($p) @@ -903,7 +971,6 @@ // STUB: should be overloaded by the application return ''; } - /** * Function to be executed in script shutdown @@ -938,7 +1005,6 @@ } } - /** * Registers shutdown function to be executed on shutdown. * The functions will be executed before destroying any @@ -951,7 +1017,6 @@ $this->shutdown_functions[] = $function; } - /** * Quote a given string. * Shortcut function for rcube_utils::rep_specialchars_output() @@ -962,7 +1027,6 @@ { return rcube_utils::rep_specialchars_output($str, 'html', $mode, $newlines); } - /** * Quote a given string for javascript output. @@ -975,12 +1039,11 @@ return rcube_utils::rep_specialchars_output($str, 'js'); } - /** * 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 $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 @@ -1028,7 +1091,6 @@ return (string)shell_exec($cmd); } - /** * Print or write debug messages * @@ -1039,12 +1101,13 @@ $args = func_get_args(); if (class_exists('rcube', false)) { - $rcube = self::get_instance(); + $rcube = self::get_instance(); $plugin = $rcube->plugins->exec_hook('console', array('args' => $args)); if ($plugin['abort']) { return; } - $args = $plugin['args']; + + $args = $plugin['args']; } $msg = array(); @@ -1055,13 +1118,12 @@ self::write_log('console', join(";\n", $msg)); } - /** * Append a line to a logfile in the logs directory. * Date will be added automatically to the line. * - * @param $name name of log file - * @param line Line to append + * @param string $name Name of the log file + * @param mixed $line Line to append */ public static function write_log($name, $line) { @@ -1069,14 +1131,14 @@ $line = var_export($line, true); } - $date_format = self::$instance ? self::$instance->config->get('log_date_format') : null; - $log_driver = self::$instance ? self::$instance->config->get('log_driver') : null; - - if (empty($date_format)) { - $date_format = 'd-M-Y H:i:s O'; + $date_format = $log_driver = $session_key = null; + if (self::$instance) { + $date_format = self::$instance->config->get('log_date_format'); + $log_driver = self::$instance->config->get('log_driver'); + $session_key = intval(self::$instance->config->get('log_session_id', 8)); } - $date = date($date_format); + $date = rcube_utils::date_format($date_format); // trigger logging hook if (is_object(self::$instance) && is_object(self::$instance->plugins)) { @@ -1088,6 +1150,11 @@ return true; } + // add session ID to the log + if ($session_key > 0 && ($sess = session_id())) { + $line = '<' . substr($sess, 0, $session_key) . '> ' . $line; + } + if ($log_driver == 'syslog') { $prio = $name == 'errors' ? LOG_ERR : LOG_INFO; syslog($prio, $line); @@ -1097,7 +1164,20 @@ // log_driver == 'file' is assumed here $line = sprintf("[%s]: %s\n", $date, $line); - $log_dir = self::$instance ? self::$instance->config->get('log_dir') : null; + $log_dir = null; + + // per-user logging is activated + if (self::$instance && self::$instance->config->get('per_user_logging', false) && self::$instance->get_user_id()) { + $log_dir = self::$instance->get_user_log_dir(); + if (empty($log_dir)) + return false; + } + else if (!empty($log['dir'])) { + $log_dir = $log['dir']; + } + else if (self::$instance) { + $log_dir = self::$instance->config->get('log_dir'); + } if (empty($log_dir)) { $log_dir = RCUBE_INSTALL_PATH . 'logs'; @@ -1117,25 +1197,23 @@ return false; } - /** * Throw system error (and show error page). * - * @param array Named parameters + * @param array $arg Named parameters * - code: Error code (required) * - type: Error type [php|db|imap|javascript] (required) * - message: Error message - * - file: File where error occured - * - line: Line where error occured - * @param boolean True to log the error - * @param boolean Terminate script execution + * - file: File where error occurred + * - line: Line where error occurred + * @param boolean $log True to log the error + * @param boolean $terminate Terminate script execution */ public static function raise_error($arg = array(), $log = false, $terminate = false) { // handle PHP exceptions if (is_object($arg) && is_a($arg, 'Exception')) { $arg = array( - 'type' => 'php', 'code' => $arg->getCode(), 'line' => $arg->getLine(), 'file' => $arg->getFile(), @@ -1143,7 +1221,7 @@ ); } else if (is_string($arg)) { - $arg = array('message' => $arg, 'type' => 'php'); + $arg = array('message' => $arg); } if (empty($arg['code'])) { @@ -1151,15 +1229,15 @@ } // installer - if (class_exists('rcube_install', false)) { - $rci = rcube_install::get_instance(); + if (class_exists('rcmail_install', false)) { + $rci = rcmail_install::get_instance(); $rci->raise_error($arg); return; } $cli = php_sapi_name() == 'cli'; - if (($log || $terminate) && !$cli && $arg['type'] && $arg['message']) { + if (($log || $terminate) && !$cli && $arg['message']) { $arg['fatal'] = $terminate; self::log_bug($arg); } @@ -1176,18 +1254,20 @@ exit(1); } + else if ($cli) { + fwrite(STDERR, 'ERROR: ' . $arg['message']); + } } - /** * Report error according to configured debug_level * - * @param array Named parameters + * @param array $arg_arr Named parameters * @see self::raise_error() */ public static function log_bug($arg_arr) { - $program = strtoupper($arg_arr['type']); + $program = strtoupper($arg_arr['type'] ?: 'php'); $level = self::get_instance()->config->get('debug_level'); // disable errors for ajax requests, write to log instead (#1487831) @@ -1237,6 +1317,30 @@ } } + /** + * Write debug info to the log + * + * @param string $engine Engine type - file name (memcache, apc) + * @param string $data Data string to log + * @param bool $result Operation result + */ + public static function debug($engine, $data, $result = null) + { + static $debug_counter; + + $line = '[' . (++$debug_counter[$engine]) . '] ' . $data; + + if (($len = strlen($line)) > self::DEBUG_LINE_LENGTH) { + $diff = $len - self::DEBUG_LINE_LENGTH; + $line = substr($line, 0, self::DEBUG_LINE_LENGTH) . "... [truncated $diff bytes]"; + } + + if ($result !== null) { + $line .= ' [' . ($result ? 'TRUE' : 'FALSE') . ']'; + } + + self::write_log($engine, $line); + } /** * Returns current time (with microseconds). @@ -1248,13 +1352,12 @@ return microtime(true); } - /** * Logs time difference according to provided timer * - * @param float $timer Timer (self::timer() result) - * @param string $label Log line prefix - * @param string $dest Log file name + * @param float $timer Timer (self::timer() result) + * @param string $label Log line prefix + * @param string $dest Log file name * * @see self::timer() */ @@ -1273,6 +1376,20 @@ self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff)); } + /** + * 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()); + } + } /** * Getter for logged user ID. @@ -1291,7 +1408,6 @@ return null; } - /** * Getter for logged user name. * @@ -1307,7 +1423,6 @@ } } - /** * Getter for logged user email (derived from user name not identity). * @@ -1319,7 +1434,6 @@ return $this->user->get_username('mail'); } } - /** * Getter for logged user password. @@ -1336,6 +1450,17 @@ } } + /** + * Get the per-user log directory + */ + protected function get_user_log_dir() + { + $log_dir = $this->config->get('log_dir', RCUBE_INSTALL_PATH . 'logs'); + $user_name = $this->get_user_name(); + $user_log_dir = $log_dir . '/' . $user_name; + + return !empty($user_name) && is_writable($user_log_dir) ? $user_log_dir : false; + } /** * Getter for logged user language code. @@ -1378,13 +1503,13 @@ /** * Send the given message using the configured method. * - * @param object $message Reference to Mail_MIME object - * @param string $from Sender address string - * @param array $mailto Array of recipient address strings - * @param array $error SMTP error array (reference) - * @param string $body_file Location of file with saved message body (reference), - * used when delay_file_io is enabled - * @param array $options SMTP options (e.g. DSN request) + * @param object $message Reference to Mail_MIME object + * @param string $from Sender address string + * @param array $mailto Array of recipient address strings + * @param array $error SMTP error array (reference) + * @param string $body_file Location of file with saved message body (reference), + * used when delay_file_io is enabled + * @param array $options SMTP options (e.g. DSN request) * * @return boolean Send status. */ @@ -1397,6 +1522,17 @@ 'options' => $options, )); + if ($plugin['abort']) { + if (!empty($plugin['error'])) { + $error = $plugin['error']; + } + if (!empty($plugin['body_file'])) { + $body_file = $plugin['body_file']; + } + + return isset($plugin['result']) ? $plugin['result'] : false; + } + $from = $plugin['from']; $mailto = $plugin['mailto']; $options = $plugin['options']; @@ -1406,32 +1542,30 @@ // send thru SMTP server using custom SMTP library if ($this->config->get('smtp_server')) { // generate list of recipients - $a_recipients = array($mailto); + $a_recipients = (array) $mailto; if (strlen($headers['Cc'])) $a_recipients[] = $headers['Cc']; if (strlen($headers['Bcc'])) $a_recipients[] = $headers['Bcc']; - // clean Bcc from header for recipients - $send_headers = $headers; - unset($send_headers['Bcc']); - // here too, it because txtHeaders() below use $message->_headers not only $send_headers - unset($message->_headers['Bcc']); - - $smtp_headers = $message->txtHeaders($send_headers, true); + // remove Bcc header and get the whole head of the message as string + $smtp_headers = $this->message_head($message, array('Bcc')); if ($message->getParam('delay_file_io')) { // use common temp dir - $temp_dir = $this->config->get('temp_dir'); - $body_file = tempnam($temp_dir, 'rcmMsg'); - if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) { + $temp_dir = $this->config->get('temp_dir'); + $body_file = tempnam($temp_dir, 'rcmMsg'); + $mime_result = $message->saveMessageBody($body_file); + + if (is_a($mime_result, 'PEAR_Error')) { self::raise_error(array('code' => 650, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Could not create message: ".$mime_result->getMessage()), - TRUE, FALSE); + true, false); return false; } + $msg_body = fopen($body_file, 'r'); } else { @@ -1451,39 +1585,33 @@ if (!$sent) { self::raise_error(array('code' => 800, 'type' => 'smtp', 'line' => __LINE__, 'file' => __FILE__, - 'message' => "SMTP error: ".join("\n", $response)), TRUE, FALSE); + 'message' => join("\n", $response)), true, false); } } // send mail using PHP's mail() function else { - // unset some headers because they will be added by the mail() function - $headers_enc = $message->headers($headers); - $headers_php = $message->_headers; - unset($headers_php['To'], $headers_php['Subject']); - - // reset stored headers and overwrite - $message->_headers = array(); - $header_str = $message->txtHeaders($headers_php); + // unset To,Subject headers because they will be added by the mail() function + $header_str = $this->message_head($message, array('To', 'Subject')); // #1485779 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) { - $headers_enc['To'] = implode(', ', $m[1]); + if (preg_match_all('/<([^@]+@[^>]+)>/', $headers['To'], $m)) { + $headers['To'] = implode(', ', $m[1]); } } $msg_body = $message->get(); - if (PEAR::isError($msg_body)) { + if (is_a($msg_body, 'PEAR_Error')) { self::raise_error(array('code' => 650, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Could not create message: ".$msg_body->getMessage()), - TRUE, FALSE); + true, false); } else { $delim = $this->config->header_delimiter(); - $to = $headers_enc['To']; - $subject = $headers_enc['Subject']; + $to = $headers['To']; + $subject = $headers['Subject']; $header_str = rtrim($header_str); if ($delim != "\r\n") { @@ -1506,40 +1634,61 @@ // remove MDN headers after sending unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']); - // get all recipients - if ($headers['Cc']) - $mailto .= $headers['Cc']; - if ($headers['Bcc']) - $mailto .= $headers['Bcc']; - if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m)) - $mailto = implode(', ', array_unique($m[1])); - if ($this->config->get('smtp_log')) { + // get all recipient addresses + if (is_array($mailto)) { + $mailto = implode(',', $mailto); + } + if ($headers['Cc']) { + $mailto .= ',' . $headers['Cc']; + } + if ($headers['Bcc']) { + $mailto .= ',' . $headers['Bcc']; + } + + $mailto = rcube_mime::decode_address_list($mailto, null, false, null, true); + self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s", $this->user->get_username(), - $_SERVER['REMOTE_ADDR'], - $mailto, + rcube_utils::remote_addr(), + implode(', ', $mailto), !empty($response) ? join('; ', $response) : '')); } + } + else { + // allow plugins to catch sending errors with the same parameters as in 'message_before_send' + $this->plugins->exec_hook('message_send_error', $plugin + array('error' => $error)); } if (is_resource($msg_body)) { fclose($msg_body); } - $message->_headers = array(); - $message->headers($headers); + $message->headers($headers, true); return $sent; } + /** + * Return message headers as a string + */ + protected function message_head($message, $unset = array()) + { + // requires Mail_mime >= 1.9.0 + $headers = array(); + foreach ((array) $unset as $header) { + $headers[$header] = null; + } + + return $message->txtHeaders($headers, true); + } } /** * Lightweight plugin API class serving as a dummy if plugins are not enabled * - * @package Framework + * @package Framework * @subpackage Core */ class rcube_dummy_plugin_api -- Gitblit v1.9.1