| | |
| | | /* |
| | | +-----------------------------------------------------------------------+ |
| | | | 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. | |
| | |
| | | */ |
| | | 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; |
| | | |
| | | /** |
| | | * Singleton instace of rcube |
| | |
| | | */ |
| | | 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; |
| | | protected $caches = array(); |
| | | protected $shutdown_functions = array(); |
| | | protected $expunge_cache = false; |
| | | |
| | | |
| | | /** |
| | | * 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) |
| | | * |
| | | * @return rcube The one and only instance |
| | | */ |
| | | static function get_instance($mode = 0) |
| | | static function get_instance($mode = 0, $env = '') |
| | | { |
| | | if (!self::$instance) { |
| | | self::$instance = new rcube(); |
| | | self::$instance = new rcube($env); |
| | | self::$instance->init($mode); |
| | | } |
| | | |
| | |
| | | /** |
| | | * Private constructor |
| | | */ |
| | | protected function __construct() |
| | | protected function __construct($env = '') |
| | | { |
| | | // load configuration |
| | | $this->config = new rcube_config; |
| | | $this->config = new rcube_config($env); |
| | | $this->plugins = new rcube_dummy_plugin_api; |
| | | |
| | | register_shutdown_function(array($this, 'shutdown')); |
| | |
| | | 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; |
| | |
| | | $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); |
| | |
| | | } |
| | | |
| | | $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) |
| | |
| | | } |
| | | |
| | | return $this->caches[$name]; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Initialize and get shared cache object |
| | | * |
| | | * @param string $name Cache identifier |
| | | * @param bool $packed Enables/disables data serialization |
| | | * |
| | | * @return rcube_cache_shared Cache object |
| | | */ |
| | | public function get_cache_shared($name, $packed=true) |
| | | { |
| | | $shared_name = "shared_$name"; |
| | | |
| | | if (!array_key_exists($shared_name, $this->caches)) { |
| | | $opt = strtolower($name) . '_cache'; |
| | | $type = $this->config->get($opt); |
| | | $ttl = $this->config->get($opt . '_ttl'); |
| | | |
| | | if (!$type) { |
| | | // cache is disabled |
| | | return $this->caches[$shared_name] = null; |
| | | } |
| | | |
| | | if ($ttl === null) { |
| | | $ttl = $this->config->get('shared_cache_ttl', '10d'); |
| | | } |
| | | |
| | | $this->caches[$shared_name] = new rcube_cache_shared($type, $name, $ttl, $packed); |
| | | } |
| | | |
| | | return $this->caches[$shared_name]; |
| | | } |
| | | |
| | | |
| | |
| | | // 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"), |
| | | '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'])) { |
| | |
| | | |
| | | $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. |
| | |
| | | $sess_domain = $this->config->get('session_domain'); |
| | | $sess_path = $this->config->get('session_path'); |
| | | $lifetime = $this->config->get('session_lifetime', 0) * 60; |
| | | $is_secure = $this->config->get('use_https') || rcube_utils::https_check(); |
| | | |
| | | // set session domain |
| | | if ($sess_domain) { |
| | |
| | | ini_set('session.gc_maxlifetime', $lifetime * 2); |
| | | } |
| | | |
| | | ini_set('session.cookie_secure', rcube_utils::https_check()); |
| | | ini_set('session.cookie_secure', $is_secure); |
| | | ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid'); |
| | | ini_set('session.use_cookies', 1); |
| | | ini_set('session.use_only_cookies', 1); |
| | | ini_set('session.serialize_handler', 'php'); |
| | | ini_set('session.cookie_httponly', 1); |
| | | |
| | | // use database for storing session data |
| | | $this->session = new rcube_session($this->get_dbh(), $this->config); |
| | | |
| | | $this->session->register_gc_handler(array($this, 'temp_gc')); |
| | | $this->session->register_gc_handler(array($this, 'cache_gc')); |
| | | $path = $_SERVER['SCRIPT_NAME']; |
| | | if (strpos($path, '://')) { |
| | | $path = parse_url($path, PHP_URL_PATH); // #1490582 |
| | | } |
| | | |
| | | $this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME'])); |
| | | $this->session->register_gc_handler(array($this, 'gc')); |
| | | $this->session->set_secret($this->config->get('des_key') . dirname($path)); |
| | | $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']) { |
| | | session_start(); |
| | | $this->session->start(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Garbage collector - cache/temp cleaner |
| | | */ |
| | | public function gc() |
| | | { |
| | | rcube_cache::gc(); |
| | | rcube_cache_shared::gc(); |
| | | $this->get_storage()->cache_gc(); |
| | | |
| | | $this->gc_temp(); |
| | | } |
| | | |
| | | |
| | |
| | | * Garbage collector function for temp files. |
| | | * Remove temp files older than two days |
| | | */ |
| | | public function temp_gc() |
| | | 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) { |
| | | if ($fname{0} == '.') { |
| | | if ($fname[0] == '.') { |
| | | continue; |
| | | } |
| | | |
| | | if (filemtime($tmp.'/'.$fname) < $expire) { |
| | | if (@filemtime($tmp.'/'.$fname) < $expire) { |
| | | @unlink($tmp.'/'.$fname); |
| | | } |
| | | } |
| | |
| | | |
| | | |
| | | /** |
| | | * Garbage collector for cache entries. |
| | | * Set flag to expunge caches on shutdown |
| | | * Runs garbage collector with probability based on |
| | | * session settings. This is intended for environments |
| | | * without a session. |
| | | */ |
| | | public function cache_gc() |
| | | public function gc_run() |
| | | { |
| | | // because this gc function is called before storage is initialized, |
| | | // we just set a flag to expunge storage cache on shutdown. |
| | | $this->expunge_cache = true; |
| | | $probability = (int) ini_get('session.gc_probability'); |
| | | $divisor = (int) ini_get('session.gc_divisor'); |
| | | |
| | | if ($divisor > 0 && $probability > 0) { |
| | | $random = mt_rand(1, $divisor); |
| | | if ($random <= $probability) { |
| | | $this->gc(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | |
| | | /** |
| | | * 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'])); |
| | | |
| | |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | |
| | |
| | | // 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)) { |
| | |
| | | * upon decryption; see http://php.net/mcrypt_generic#68082 |
| | | */ |
| | | $clear = pack("a*H2", $clear, "80"); |
| | | $ckey = $this->config->get_crypto_key($key); |
| | | |
| | | if (function_exists('mcrypt_module_open') && |
| | | if (function_exists('openssl_encrypt')) { |
| | | $method = 'DES-EDE3-CBC'; |
| | | $opts = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true; |
| | | $iv = $this->create_iv(openssl_cipher_iv_length($method)); |
| | | $cipher = $iv . openssl_encrypt($clear, $method, $ckey, $opts, $iv); |
| | | } |
| | | else 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); |
| | | mcrypt_generic_init($td, $ckey, $iv); |
| | | $cipher = $iv . mcrypt_generic($td, $clear); |
| | | mcrypt_generic_deinit($td); |
| | | mcrypt_module_close($td); |
| | |
| | | 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); |
| | | $cipher = $iv . des($ckey, $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" |
| | | 'message' => "Could not perform encryption; make sure OpenSSL or Mcrypt or lib/des.inc is available" |
| | | ), true, true); |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | $cipher = $base64 ? base64_decode($cipher) : $cipher; |
| | | $ckey = $this->config->get_crypto_key($key); |
| | | |
| | | 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); |
| | | if (function_exists('openssl_decrypt')) { |
| | | $method = 'DES-EDE3-CBC'; |
| | | $opts = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true; |
| | | $iv_size = openssl_cipher_iv_length($method); |
| | | $iv = substr($cipher, 0, $iv_size); |
| | | |
| | | // session corruption? (#1485970) |
| | | if (strlen($iv) < $iv_size) { |
| | |
| | | } |
| | | |
| | | $cipher = substr($cipher, $iv_size); |
| | | mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); |
| | | $clear = openssl_decrypt($cipher, $method, $ckey, $opts, $iv); |
| | | } |
| | | else 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, $ckey, $iv); |
| | | $clear = mdecrypt_generic($td, $cipher); |
| | | mcrypt_generic_deinit($td); |
| | | mcrypt_module_close($td); |
| | |
| | | |
| | | if (function_exists('des')) { |
| | | $des_iv_size = 8; |
| | | $iv = substr($cipher, 0, $des_iv_size); |
| | | $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); |
| | | $clear = des($ckey, $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" |
| | | 'message' => "Could not perform decryption; make sure OpenSSL or Mcrypt or lib/des.inc is available" |
| | | ), true, true); |
| | | } |
| | | } |
| | |
| | | |
| | | |
| | | /** |
| | | * Returns session token for secure URLs |
| | | * |
| | | * @param bool $generate Generate token if not exists in session yet |
| | | * |
| | | * @return string|bool Token string, False when disabled |
| | | */ |
| | | public function get_secure_url_token($generate = false) |
| | | { |
| | | 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 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 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 |
| | |
| | | call_user_func($function); |
| | | } |
| | | |
| | | // write session data as soon as possible and before |
| | | // closing database connection, don't do this before |
| | | // registered shutdown functions, they may need the session |
| | | // Note: this will run registered gc handlers (ie. cache gc) |
| | | if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) { |
| | | $this->session->write_close(); |
| | | } |
| | | |
| | | if (is_object($this->smtp)) { |
| | | $this->smtp->disconnect(); |
| | | } |
| | |
| | | } |
| | | |
| | | if (is_object($this->storage)) { |
| | | if ($this->expunge_cache) { |
| | | $this->storage->expunge_cache(); |
| | | } |
| | | $this->storage->close(); |
| | | } |
| | | } |
| | |
| | | $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; |
| | | $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)); |
| | | } |
| | | |
| | | if (empty($date_format)) { |
| | | $date_format = 'd-M-Y H:i:s O'; |
| | |
| | | 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); |
| | |
| | | // 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'; |
| | |
| | | * - 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 |
| | | * - file: File where error occurred |
| | | * - line: Line where error occurred |
| | | * @param boolean True to log the error |
| | | * @param boolean Terminate script execution |
| | | */ |
| | |
| | | { |
| | | // handle PHP exceptions |
| | | if (is_object($arg) && is_a($arg, 'Exception')) { |
| | | $err = array( |
| | | 'type' => 'php', |
| | | $arg = array( |
| | | 'code' => $arg->getCode(), |
| | | 'line' => $arg->getLine(), |
| | | 'file' => $arg->getFile(), |
| | | 'message' => $arg->getMessage(), |
| | | ); |
| | | $arg = $err; |
| | | } |
| | | else if (is_string($arg)) { |
| | | $arg = array('message' => $arg); |
| | | } |
| | | |
| | | if (empty($arg['code'])) { |
| | | $arg['code'] = 500; |
| | | } |
| | | |
| | | // 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; |
| | | } |
| | | |
| | | if (($log || $terminate) && $arg['type'] && $arg['message']) { |
| | | $cli = php_sapi_name() == 'cli'; |
| | | |
| | | if (($log || $terminate) && !$cli && $arg['message']) { |
| | | $arg['fatal'] = $terminate; |
| | | self::log_bug($arg); |
| | | } |
| | | |
| | | // display error page and terminate script |
| | | if ($terminate && is_object(self::$instance->output)) { |
| | | self::$instance->output->raise_error($arg['code'], $arg['message']); |
| | | // terminate script |
| | | if ($terminate) { |
| | | // display error page |
| | | if (is_object(self::$instance->output)) { |
| | | self::$instance->output->raise_error($arg['code'], $arg['message']); |
| | | } |
| | | else if ($cli) { |
| | | fwrite(STDERR, 'ERROR: ' . $arg['message']); |
| | | } |
| | | |
| | | exit(1); |
| | | } |
| | | else if ($cli) { |
| | | fwrite(STDERR, 'ERROR: ' . $arg['message']); |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | public static function log_bug($arg_arr) |
| | | { |
| | | $program = strtoupper($arg_arr['type']); |
| | | $program = strtoupper(!empty($arg_arr['type']) ? $arg_arr['type'] : 'php'); |
| | | $level = self::get_instance()->config->get('debug_level'); |
| | | |
| | | // disable errors for ajax requests, write to log instead (#1487831) |
| | |
| | | |
| | | if (!self::write_log('errors', $log_entry)) { |
| | | // send error to PHPs error handler if write_log didn't succeed |
| | | trigger_error($arg_arr['message']); |
| | | trigger_error($arg_arr['message'], E_USER_WARNING); |
| | | } |
| | | } |
| | | |
| | |
| | | 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. |
| | |
| | | return $this->decrypt($_SESSION['password']); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 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. |
| | | * |
| | | * @return string User language code |
| | | */ |
| | | public function get_user_language() |
| | | { |
| | | if (is_object($this->user)) { |
| | | return $this->user->language; |
| | | } |
| | | else if (isset($_SESSION['language'])) { |
| | | return $_SESSION['language']; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Unique Message-ID generator. |
| | | * |
| | | * @return string Message-ID |
| | | */ |
| | | public function gen_message_id() |
| | | { |
| | | $local_part = md5(uniqid('rcube'.mt_rand(), true)); |
| | | $domain_part = $this->user->get_username('domain'); |
| | | |
| | | // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924) |
| | | if (!preg_match('/\.[a-z]+$/i', $domain_part)) { |
| | | foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) { |
| | | $host = preg_replace('/:[0-9]+$/', '', $host); |
| | | if ($host && preg_match('/\.[a-z]+$/i', $host)) { |
| | | $domain_part = $host; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return sprintf('<%s@%s>', $local_part, $domain_part); |
| | | } |
| | | |
| | | /** |
| | | * 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) |
| | | * |
| | | * @return boolean Send status. |
| | | */ |
| | | public function deliver_message(&$message, $from, $mailto, &$error, &$body_file = null, $options = null) |
| | | { |
| | | $plugin = $this->plugins->exec_hook('message_before_send', array( |
| | | 'message' => $message, |
| | | 'from' => $from, |
| | | 'mailto' => $mailto, |
| | | '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']; |
| | | $message = $plugin['message']; |
| | | $headers = $message->headers(); |
| | | |
| | | // send thru SMTP server using custom SMTP library |
| | | if ($this->config->get('smtp_server')) { |
| | | // generate list of recipients |
| | | $a_recipients = (array) $mailto; |
| | | |
| | | if (strlen($headers['Cc'])) |
| | | $a_recipients[] = $headers['Cc']; |
| | | if (strlen($headers['Bcc'])) |
| | | $a_recipients[] = $headers['Bcc']; |
| | | |
| | | // 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'); |
| | | $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); |
| | | return false; |
| | | } |
| | | |
| | | $msg_body = fopen($body_file, 'r'); |
| | | } |
| | | else { |
| | | $msg_body = $message->get(); |
| | | } |
| | | |
| | | // send message |
| | | if (!is_object($this->smtp)) { |
| | | $this->smtp_init(true); |
| | | } |
| | | |
| | | $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $options); |
| | | $response = $this->smtp->get_response(); |
| | | $error = $this->smtp->get_error(); |
| | | |
| | | // log error |
| | | if (!$sent) { |
| | | self::raise_error(array('code' => 800, 'type' => 'smtp', |
| | | 'line' => __LINE__, 'file' => __FILE__, |
| | | 'message' => join("\n", $response)), true, false); |
| | | } |
| | | } |
| | | // send mail using PHP's mail() function |
| | | else { |
| | | // 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['To'], $m)) { |
| | | $headers['To'] = implode(', ', $m[1]); |
| | | } |
| | | } |
| | | |
| | | $msg_body = $message->get(); |
| | | |
| | | 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); |
| | | } |
| | | else { |
| | | $delim = $this->config->header_delimiter(); |
| | | $to = $headers['To']; |
| | | $subject = $headers['Subject']; |
| | | $header_str = rtrim($header_str); |
| | | |
| | | if ($delim != "\r\n") { |
| | | $header_str = str_replace("\r\n", $delim, $header_str); |
| | | $msg_body = str_replace("\r\n", $delim, $msg_body); |
| | | $to = str_replace("\r\n", $delim, $to); |
| | | $subject = str_replace("\r\n", $delim, $subject); |
| | | } |
| | | |
| | | if (filter_var(ini_get('safe_mode'), FILTER_VALIDATE_BOOLEAN)) |
| | | $sent = mail($to, $subject, $msg_body, $header_str); |
| | | else |
| | | $sent = mail($to, $subject, $msg_body, $header_str, "-f$from"); |
| | | } |
| | | } |
| | | |
| | | if ($sent) { |
| | | $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body)); |
| | | |
| | | // remove MDN headers after sending |
| | | unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']); |
| | | |
| | | 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(), |
| | | 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($headers, true); |
| | | |
| | | return $sent; |
| | | } |
| | | |
| | | /** |
| | | * Return message headers as a string |
| | | */ |
| | | protected function message_head($message, $unset = array()) |
| | | { |
| | | // Mail_mime >= 1.9.0 |
| | | if (method_exists($message, 'isMultipart')) { |
| | | foreach ($unset as $header) { |
| | | $headers[$header] = null; |
| | | } |
| | | } |
| | | else { |
| | | $headers = $message->headers(); |
| | | foreach ($unset as $header) { |
| | | unset($headers[$header]); |
| | | } |
| | | $message->_headers = array(); |
| | | } |
| | | |
| | | return $message->txtHeaders($headers, true); |
| | | } |
| | | } |
| | | |
| | | |