PageRenderTime 60ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/program/include/rcube.php

https://github.com/netconstructor/roundcubemail
PHP | 1227 lines | 667 code | 203 blank | 357 comment | 160 complexity | 350a5bf1f8494dccbb4c2b7b0001c8c9 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1
  1. <?php
  2. /*
  3. +-----------------------------------------------------------------------+
  4. | program/include/rcube.php |
  5. | |
  6. | This file is part of the Roundcube Webmail client |
  7. | Copyright (C) 2008-2012, The Roundcube Dev Team |
  8. | Copyright (C) 2011-2012, Kolab Systems AG |
  9. | |
  10. | Licensed under the GNU General Public License version 3 or |
  11. | any later version with exceptions for skins & plugins. |
  12. | See the README file for a full license statement. |
  13. | |
  14. | PURPOSE: |
  15. | Framework base class providing core functions and holding |
  16. | instances of all 'global' objects like db- and storage-connections |
  17. +-----------------------------------------------------------------------+
  18. | Author: Thomas Bruederli <roundcube@gmail.com> |
  19. +-----------------------------------------------------------------------+
  20. */
  21. /**
  22. * Base class of the Roundcube Framework
  23. * implemented as singleton
  24. *
  25. * @package Framework
  26. * @subpackage Core
  27. */
  28. class rcube
  29. {
  30. const INIT_WITH_DB = 1;
  31. const INIT_WITH_PLUGINS = 2;
  32. /**
  33. * Singleton instace of rcube
  34. *
  35. * @var rcmail
  36. */
  37. static protected $instance;
  38. /**
  39. * Stores instance of rcube_config.
  40. *
  41. * @var rcube_config
  42. */
  43. public $config;
  44. /**
  45. * Instace of database class.
  46. *
  47. * @var rcube_db
  48. */
  49. public $db;
  50. /**
  51. * Instace of Memcache class.
  52. *
  53. * @var Memcache
  54. */
  55. public $memcache;
  56. /**
  57. * Instace of rcube_session class.
  58. *
  59. * @var rcube_session
  60. */
  61. public $session;
  62. /**
  63. * Instance of rcube_smtp class.
  64. *
  65. * @var rcube_smtp
  66. */
  67. public $smtp;
  68. /**
  69. * Instance of rcube_storage class.
  70. *
  71. * @var rcube_storage
  72. */
  73. public $storage;
  74. /**
  75. * Instance of rcube_output class.
  76. *
  77. * @var rcube_output
  78. */
  79. public $output;
  80. /**
  81. * Instance of rcube_plugin_api.
  82. *
  83. * @var rcube_plugin_api
  84. */
  85. public $plugins;
  86. /* private/protected vars */
  87. protected $texts;
  88. protected $caches = array();
  89. protected $shutdown_functions = array();
  90. protected $expunge_cache = false;
  91. /**
  92. * This implements the 'singleton' design pattern
  93. *
  94. * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants
  95. *
  96. * @return rcube The one and only instance
  97. */
  98. static function get_instance($mode = 0)
  99. {
  100. if (!self::$instance) {
  101. self::$instance = new rcube();
  102. self::$instance->init($mode);
  103. }
  104. return self::$instance;
  105. }
  106. /**
  107. * Private constructor
  108. */
  109. protected function __construct()
  110. {
  111. // load configuration
  112. $this->config = new rcube_config;
  113. $this->plugins = new rcube_dummy_plugin_api;
  114. register_shutdown_function(array($this, 'shutdown'));
  115. }
  116. /**
  117. * Initial startup function
  118. */
  119. protected function init($mode = 0)
  120. {
  121. // initialize syslog
  122. if ($this->config->get('log_driver') == 'syslog') {
  123. $syslog_id = $this->config->get('syslog_id', 'roundcube');
  124. $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
  125. openlog($syslog_id, LOG_ODELAY, $syslog_facility);
  126. }
  127. // connect to database
  128. if ($mode & self::INIT_WITH_DB) {
  129. $this->get_dbh();
  130. }
  131. // create plugin API and load plugins
  132. if ($mode & self::INIT_WITH_PLUGINS) {
  133. $this->plugins = rcube_plugin_api::get_instance();
  134. }
  135. }
  136. /**
  137. * Get the current database connection
  138. *
  139. * @return rcube_db Database object
  140. */
  141. public function get_dbh()
  142. {
  143. if (!$this->db) {
  144. $config_all = $this->config->all();
  145. $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
  146. $this->db->set_debug((bool)$config_all['sql_debug']);
  147. }
  148. return $this->db;
  149. }
  150. /**
  151. * Get global handle for memcache access
  152. *
  153. * @return object Memcache
  154. */
  155. public function get_memcache()
  156. {
  157. if (!isset($this->memcache)) {
  158. // no memcache support in PHP
  159. if (!class_exists('Memcache')) {
  160. $this->memcache = false;
  161. return false;
  162. }
  163. $this->memcache = new Memcache;
  164. $this->mc_available = 0;
  165. // add all configured hosts to pool
  166. $pconnect = $this->config->get('memcache_pconnect', true);
  167. foreach ($this->config->get('memcache_hosts', array()) as $host) {
  168. if (substr($host, 0, 7) != 'unix://') {
  169. list($host, $port) = explode(':', $host);
  170. if (!$port) $port = 11211;
  171. }
  172. else {
  173. $port = 0;
  174. }
  175. $this->mc_available += intval($this->memcache->addServer(
  176. $host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
  177. }
  178. // test connection and failover (will result in $this->mc_available == 0 on complete failure)
  179. $this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist
  180. if (!$this->mc_available) {
  181. $this->memcache = false;
  182. }
  183. }
  184. return $this->memcache;
  185. }
  186. /**
  187. * Callback for memcache failure
  188. */
  189. public function memcache_failure($host, $port)
  190. {
  191. static $seen = array();
  192. // only report once
  193. if (!$seen["$host:$port"]++) {
  194. $this->mc_available--;
  195. self::raise_error(array(
  196. 'code' => 604, 'type' => 'db',
  197. 'line' => __LINE__, 'file' => __FILE__,
  198. 'message' => "Memcache failure on host $host:$port"),
  199. true, false);
  200. }
  201. }
  202. /**
  203. * Initialize and get cache object
  204. *
  205. * @param string $name Cache identifier
  206. * @param string $type Cache type ('db', 'apc' or 'memcache')
  207. * @param string $ttl Expiration time for cache items
  208. * @param bool $packed Enables/disables data serialization
  209. *
  210. * @return rcube_cache Cache object
  211. */
  212. public function get_cache($name, $type='db', $ttl=0, $packed=true)
  213. {
  214. if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) {
  215. $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed);
  216. }
  217. return $this->caches[$name];
  218. }
  219. /**
  220. * Create SMTP object and connect to server
  221. *
  222. * @param boolean True if connection should be established
  223. */
  224. public function smtp_init($connect = false)
  225. {
  226. $this->smtp = new rcube_smtp();
  227. if ($connect) {
  228. $this->smtp->connect();
  229. }
  230. }
  231. /**
  232. * Initialize and get storage object
  233. *
  234. * @return rcube_storage Storage object
  235. */
  236. public function get_storage()
  237. {
  238. // already initialized
  239. if (!is_object($this->storage)) {
  240. $this->storage_init();
  241. }
  242. return $this->storage;
  243. }
  244. /**
  245. * Initialize storage object
  246. */
  247. public function storage_init()
  248. {
  249. // already initialized
  250. if (is_object($this->storage)) {
  251. return;
  252. }
  253. $driver = $this->config->get('storage_driver', 'imap');
  254. $driver_class = "rcube_{$driver}";
  255. if (!class_exists($driver_class)) {
  256. self::raise_error(array(
  257. 'code' => 700, 'type' => 'php',
  258. 'file' => __FILE__, 'line' => __LINE__,
  259. 'message' => "Storage driver class ($driver) not found!"),
  260. true, true);
  261. }
  262. // Initialize storage object
  263. $this->storage = new $driver_class;
  264. // for backward compat. (deprecated, will be removed)
  265. $this->imap = $this->storage;
  266. // enable caching of mail data
  267. $storage_cache = $this->config->get("{$driver}_cache");
  268. $messages_cache = $this->config->get('messages_cache');
  269. // for backward compatybility
  270. if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
  271. $storage_cache = 'db';
  272. $messages_cache = true;
  273. }
  274. if ($storage_cache) {
  275. $this->storage->set_caching($storage_cache);
  276. }
  277. if ($messages_cache) {
  278. $this->storage->set_messages_caching(true);
  279. }
  280. // set pagesize from config
  281. $pagesize = $this->config->get('mail_pagesize');
  282. if (!$pagesize) {
  283. $pagesize = $this->config->get('pagesize', 50);
  284. }
  285. $this->storage->set_pagesize($pagesize);
  286. // set class options
  287. $options = array(
  288. 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'),
  289. 'auth_cid' => $this->config->get("{$driver}_auth_cid"),
  290. 'auth_pw' => $this->config->get("{$driver}_auth_pw"),
  291. 'debug' => (bool) $this->config->get("{$driver}_debug"),
  292. 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"),
  293. 'timeout' => (int) $this->config->get("{$driver}_timeout"),
  294. 'skip_deleted' => (bool) $this->config->get('skip_deleted'),
  295. 'driver' => $driver,
  296. );
  297. if (!empty($_SESSION['storage_host'])) {
  298. $options['host'] = $_SESSION['storage_host'];
  299. $options['user'] = $_SESSION['username'];
  300. $options['port'] = $_SESSION['storage_port'];
  301. $options['ssl'] = $_SESSION['storage_ssl'];
  302. $options['password'] = $this->decrypt($_SESSION['password']);
  303. $_SESSION[$driver.'_host'] = $_SESSION['storage_host'];
  304. }
  305. $options = $this->plugins->exec_hook("storage_init", $options);
  306. // for backward compat. (deprecated, to be removed)
  307. $options = $this->plugins->exec_hook("imap_init", $options);
  308. $this->storage->set_options($options);
  309. $this->set_storage_prop();
  310. }
  311. /**
  312. * Set storage parameters.
  313. * This must be done AFTER connecting to the server!
  314. */
  315. protected function set_storage_prop()
  316. {
  317. $storage = $this->get_storage();
  318. $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
  319. if ($default_folders = $this->config->get('default_folders')) {
  320. $storage->set_default_folders($default_folders);
  321. }
  322. if (isset($_SESSION['mbox'])) {
  323. $storage->set_folder($_SESSION['mbox']);
  324. }
  325. if (isset($_SESSION['page'])) {
  326. $storage->set_page($_SESSION['page']);
  327. }
  328. }
  329. /**
  330. * Create session object and start the session.
  331. */
  332. public function session_init()
  333. {
  334. // session started (Installer?)
  335. if (session_id()) {
  336. return;
  337. }
  338. $sess_name = $this->config->get('session_name');
  339. $sess_domain = $this->config->get('session_domain');
  340. $sess_path = $this->config->get('session_path');
  341. $lifetime = $this->config->get('session_lifetime', 0) * 60;
  342. // set session domain
  343. if ($sess_domain) {
  344. ini_set('session.cookie_domain', $sess_domain);
  345. }
  346. // set session path
  347. if ($sess_path) {
  348. ini_set('session.cookie_path', $sess_path);
  349. }
  350. // set session garbage collecting time according to session_lifetime
  351. if ($lifetime) {
  352. ini_set('session.gc_maxlifetime', $lifetime * 2);
  353. }
  354. ini_set('session.cookie_secure', rcube_utils::https_check());
  355. ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid');
  356. ini_set('session.use_cookies', 1);
  357. ini_set('session.use_only_cookies', 1);
  358. ini_set('session.serialize_handler', 'php');
  359. ini_set('session.cookie_httponly', 1);
  360. // use database for storing session data
  361. $this->session = new rcube_session($this->get_dbh(), $this->config);
  362. $this->session->register_gc_handler(array($this, 'temp_gc'));
  363. $this->session->register_gc_handler(array($this, 'cache_gc'));
  364. $this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME']));
  365. $this->session->set_ip_check($this->config->get('ip_check'));
  366. // start PHP session (if not in CLI mode)
  367. if ($_SERVER['REMOTE_ADDR']) {
  368. session_start();
  369. }
  370. }
  371. /**
  372. * Garbage collector function for temp files.
  373. * Remove temp files older than two days
  374. */
  375. public function temp_gc()
  376. {
  377. $tmp = unslashify($this->config->get('temp_dir'));
  378. $expire = time() - 172800; // expire in 48 hours
  379. if ($tmp && ($dir = opendir($tmp))) {
  380. while (($fname = readdir($dir)) !== false) {
  381. if ($fname{0} == '.') {
  382. continue;
  383. }
  384. if (filemtime($tmp.'/'.$fname) < $expire) {
  385. @unlink($tmp.'/'.$fname);
  386. }
  387. }
  388. closedir($dir);
  389. }
  390. }
  391. /**
  392. * Garbage collector for cache entries.
  393. * Set flag to expunge caches on shutdown
  394. */
  395. public function cache_gc()
  396. {
  397. // because this gc function is called before storage is initialized,
  398. // we just set a flag to expunge storage cache on shutdown.
  399. $this->expunge_cache = true;
  400. }
  401. /**
  402. * Get localized text in the desired language
  403. *
  404. * @param mixed $attrib Named parameters array or label name
  405. * @param string $domain Label domain (plugin) name
  406. *
  407. * @return string Localized text
  408. */
  409. public function gettext($attrib, $domain=null)
  410. {
  411. // load localization files if not done yet
  412. if (empty($this->texts)) {
  413. $this->load_language();
  414. }
  415. // extract attributes
  416. if (is_string($attrib)) {
  417. $attrib = array('name' => $attrib);
  418. }
  419. $name = $attrib['name'] ? $attrib['name'] : '';
  420. // attrib contain text values: use them from now
  421. if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us'])) {
  422. $this->texts[$name] = $setval;
  423. }
  424. // check for text with domain
  425. if ($domain && ($text = $this->texts[$domain.'.'.$name])) {
  426. }
  427. // text does not exist
  428. else if (!($text = $this->texts[$name])) {
  429. return "[$name]";
  430. }
  431. // replace vars in text
  432. if (is_array($attrib['vars'])) {
  433. foreach ($attrib['vars'] as $var_key => $var_value) {
  434. $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
  435. }
  436. }
  437. // format output
  438. if (($attrib['uppercase'] && strtolower($attrib['uppercase'] == 'first')) || $attrib['ucfirst']) {
  439. return ucfirst($text);
  440. }
  441. else if ($attrib['uppercase']) {
  442. return mb_strtoupper($text);
  443. }
  444. else if ($attrib['lowercase']) {
  445. return mb_strtolower($text);
  446. }
  447. return strtr($text, array('\n' => "\n"));
  448. }
  449. /**
  450. * Check if the given text label exists
  451. *
  452. * @param string $name Label name
  453. * @param string $domain Label domain (plugin) name or '*' for all domains
  454. * @param string $ref_domain Sets domain name if label is found
  455. *
  456. * @return boolean True if text exists (either in the current language or in en_US)
  457. */
  458. public function text_exists($name, $domain = null, &$ref_domain = null)
  459. {
  460. // load localization files if not done yet
  461. if (empty($this->texts)) {
  462. $this->load_language();
  463. }
  464. if (isset($this->texts[$name])) {
  465. $ref_domain = '';
  466. return true;
  467. }
  468. // any of loaded domains (plugins)
  469. if ($domain == '*') {
  470. foreach ($this->plugins->loaded_plugins() as $domain) {
  471. if (isset($this->texts[$domain.'.'.$name])) {
  472. $ref_domain = $domain;
  473. return true;
  474. }
  475. }
  476. }
  477. // specified domain
  478. else if ($domain) {
  479. $ref_domain = $domain;
  480. return isset($this->texts[$domain.'.'.$name]);
  481. }
  482. return false;
  483. }
  484. /**
  485. * Load a localization package
  486. *
  487. * @param string Language ID
  488. * @param array Additional text labels/messages
  489. */
  490. public function load_language($lang = null, $add = array())
  491. {
  492. $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
  493. // load localized texts
  494. if (empty($this->texts) || $lang != $_SESSION['language']) {
  495. $this->texts = array();
  496. // handle empty lines after closing PHP tag in localization files
  497. ob_start();
  498. // get english labels (these should be complete)
  499. @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
  500. @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
  501. if (is_array($labels))
  502. $this->texts = $labels;
  503. if (is_array($messages))
  504. $this->texts = array_merge($this->texts, $messages);
  505. // include user language files
  506. if ($lang != 'en' && $lang != 'en_US' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
  507. include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
  508. include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
  509. if (is_array($labels))
  510. $this->texts = array_merge($this->texts, $labels);
  511. if (is_array($messages))
  512. $this->texts = array_merge($this->texts, $messages);
  513. }
  514. ob_end_clean();
  515. $_SESSION['language'] = $lang;
  516. }
  517. // append additional texts (from plugin)
  518. if (is_array($add) && !empty($add)) {
  519. $this->texts += $add;
  520. }
  521. }
  522. /**
  523. * Check the given string and return a valid language code
  524. *
  525. * @param string Language code
  526. *
  527. * @return string Valid language code
  528. */
  529. protected function language_prop($lang)
  530. {
  531. static $rcube_languages, $rcube_language_aliases;
  532. // user HTTP_ACCEPT_LANGUAGE if no language is specified
  533. if (empty($lang) || $lang == 'auto') {
  534. $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
  535. $lang = str_replace('-', '_', $accept_langs[0]);
  536. }
  537. if (empty($rcube_languages)) {
  538. @include(INSTALL_PATH . 'program/localization/index.inc');
  539. }
  540. // check if we have an alias for that language
  541. if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
  542. $lang = $rcube_language_aliases[$lang];
  543. }
  544. // try the first two chars
  545. else if (!isset($rcube_languages[$lang])) {
  546. $short = substr($lang, 0, 2);
  547. // check if we have an alias for the short language code
  548. if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
  549. $lang = $rcube_language_aliases[$short];
  550. }
  551. // expand 'nn' to 'nn_NN'
  552. else if (!isset($rcube_languages[$short])) {
  553. $lang = $short.'_'.strtoupper($short);
  554. }
  555. }
  556. if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
  557. $lang = 'en_US';
  558. }
  559. return $lang;
  560. }
  561. /**
  562. * Read directory program/localization and return a list of available languages
  563. *
  564. * @return array List of available localizations
  565. */
  566. public function list_languages()
  567. {
  568. static $sa_languages = array();
  569. if (!sizeof($sa_languages)) {
  570. @include(INSTALL_PATH . 'program/localization/index.inc');
  571. if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
  572. while (($name = readdir($dh)) !== false) {
  573. if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name)) {
  574. continue;
  575. }
  576. if ($label = $rcube_languages[$name]) {
  577. $sa_languages[$name] = $label;
  578. }
  579. }
  580. closedir($dh);
  581. }
  582. }
  583. return $sa_languages;
  584. }
  585. /**
  586. * Encrypt using 3DES
  587. *
  588. * @param string $clear clear text input
  589. * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
  590. * @param boolean $base64 whether or not to base64_encode() the result before returning
  591. *
  592. * @return string encrypted text
  593. */
  594. public function encrypt($clear, $key = 'des_key', $base64 = true)
  595. {
  596. if (!$clear) {
  597. return '';
  598. }
  599. /*-
  600. * Add a single canary byte to the end of the clear text, which
  601. * will help find out how much of padding will need to be removed
  602. * upon decryption; see http://php.net/mcrypt_generic#68082
  603. */
  604. $clear = pack("a*H2", $clear, "80");
  605. if (function_exists('mcrypt_module_open') &&
  606. ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
  607. ) {
  608. $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
  609. mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
  610. $cipher = $iv . mcrypt_generic($td, $clear);
  611. mcrypt_generic_deinit($td);
  612. mcrypt_module_close($td);
  613. }
  614. else {
  615. @include_once 'des.inc';
  616. if (function_exists('des')) {
  617. $des_iv_size = 8;
  618. $iv = $this->create_iv($des_iv_size);
  619. $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
  620. }
  621. else {
  622. self::raise_error(array(
  623. 'code' => 500, 'type' => 'php',
  624. 'file' => __FILE__, 'line' => __LINE__,
  625. 'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
  626. ), true, true);
  627. }
  628. }
  629. return $base64 ? base64_encode($cipher) : $cipher;
  630. }
  631. /**
  632. * Decrypt 3DES-encrypted string
  633. *
  634. * @param string $cipher encrypted text
  635. * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
  636. * @param boolean $base64 whether or not input is base64-encoded
  637. *
  638. * @return string decrypted text
  639. */
  640. public function decrypt($cipher, $key = 'des_key', $base64 = true)
  641. {
  642. if (!$cipher) {
  643. return '';
  644. }
  645. $cipher = $base64 ? base64_decode($cipher) : $cipher;
  646. if (function_exists('mcrypt_module_open') &&
  647. ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
  648. ) {
  649. $iv_size = mcrypt_enc_get_iv_size($td);
  650. $iv = substr($cipher, 0, $iv_size);
  651. // session corruption? (#1485970)
  652. if (strlen($iv) < $iv_size) {
  653. return '';
  654. }
  655. $cipher = substr($cipher, $iv_size);
  656. mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
  657. $clear = mdecrypt_generic($td, $cipher);
  658. mcrypt_generic_deinit($td);
  659. mcrypt_module_close($td);
  660. }
  661. else {
  662. @include_once 'des.inc';
  663. if (function_exists('des')) {
  664. $des_iv_size = 8;
  665. $iv = substr($cipher, 0, $des_iv_size);
  666. $cipher = substr($cipher, $des_iv_size);
  667. $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
  668. }
  669. else {
  670. self::raise_error(array(
  671. 'code' => 500, 'type' => 'php',
  672. 'file' => __FILE__, 'line' => __LINE__,
  673. 'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
  674. ), true, true);
  675. }
  676. }
  677. /*-
  678. * Trim PHP's padding and the canary byte; see note in
  679. * rcube::encrypt() and http://php.net/mcrypt_generic#68082
  680. */
  681. $clear = substr(rtrim($clear, "\0"), 0, -1);
  682. return $clear;
  683. }
  684. /**
  685. * Generates encryption initialization vector (IV)
  686. *
  687. * @param int Vector size
  688. *
  689. * @return string Vector string
  690. */
  691. private function create_iv($size)
  692. {
  693. // mcrypt_create_iv() can be slow when system lacks entrophy
  694. // we'll generate IV vector manually
  695. $iv = '';
  696. for ($i = 0; $i < $size; $i++) {
  697. $iv .= chr(mt_rand(0, 255));
  698. }
  699. return $iv;
  700. }
  701. /**
  702. * Build a valid URL to this instance of Roundcube
  703. *
  704. * @param mixed Either a string with the action or url parameters as key-value pairs
  705. * @return string Valid application URL
  706. */
  707. public function url($p)
  708. {
  709. // STUB: should be overloaded by the application
  710. return '';
  711. }
  712. /**
  713. * Function to be executed in script shutdown
  714. * Registered with register_shutdown_function()
  715. */
  716. public function shutdown()
  717. {
  718. foreach ($this->shutdown_functions as $function) {
  719. call_user_func($function);
  720. }
  721. if (is_object($this->smtp)) {
  722. $this->smtp->disconnect();
  723. }
  724. foreach ($this->caches as $cache) {
  725. if (is_object($cache)) {
  726. $cache->close();
  727. }
  728. }
  729. if (is_object($this->storage)) {
  730. if ($this->expunge_cache) {
  731. $this->storage->expunge_cache();
  732. }
  733. $this->storage->close();
  734. }
  735. }
  736. /**
  737. * Registers shutdown function to be executed on shutdown.
  738. * The functions will be executed before destroying any
  739. * objects like smtp, imap, session, etc.
  740. *
  741. * @param callback Function callback
  742. */
  743. public function add_shutdown_function($function)
  744. {
  745. $this->shutdown_functions[] = $function;
  746. }
  747. /**
  748. * Construct shell command, execute it and return output as string.
  749. * Keywords {keyword} are replaced with arguments
  750. *
  751. * @param $cmd Format string with {keywords} to be replaced
  752. * @param $values (zero, one or more arrays can be passed)
  753. *
  754. * @return output of command. shell errors not detectable
  755. */
  756. public static function exec(/* $cmd, $values1 = array(), ... */)
  757. {
  758. $args = func_get_args();
  759. $cmd = array_shift($args);
  760. $values = $replacements = array();
  761. // merge values into one array
  762. foreach ($args as $arg) {
  763. $values += (array)$arg;
  764. }
  765. preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
  766. foreach ($matches as $tags) {
  767. list(, $tag, $option, $key) = $tags;
  768. $parts = array();
  769. if ($option) {
  770. foreach ((array)$values["-$key"] as $key => $value) {
  771. if ($value === true || $value === false || $value === null) {
  772. $parts[] = $value ? $key : "";
  773. }
  774. else {
  775. foreach ((array)$value as $val) {
  776. $parts[] = "$key " . escapeshellarg($val);
  777. }
  778. }
  779. }
  780. }
  781. else {
  782. foreach ((array)$values[$key] as $value) {
  783. $parts[] = escapeshellarg($value);
  784. }
  785. }
  786. $replacements[$tag] = join(" ", $parts);
  787. }
  788. // use strtr behaviour of going through source string once
  789. $cmd = strtr($cmd, $replacements);
  790. return (string)shell_exec($cmd);
  791. }
  792. /**
  793. * Print or write debug messages
  794. *
  795. * @param mixed Debug message or data
  796. */
  797. public static function console()
  798. {
  799. $args = func_get_args();
  800. if (class_exists('rcube', false)) {
  801. $rcube = self::get_instance();
  802. $plugin = $rcube->plugins->exec_hook('console', array('args' => $args));
  803. if ($plugin['abort']) {
  804. return;
  805. }
  806. $args = $plugin['args'];
  807. }
  808. $msg = array();
  809. foreach ($args as $arg) {
  810. $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
  811. }
  812. self::write_log('console', join(";\n", $msg));
  813. }
  814. /**
  815. * Append a line to a logfile in the logs directory.
  816. * Date will be added automatically to the line.
  817. *
  818. * @param $name name of log file
  819. * @param line Line to append
  820. */
  821. public static function write_log($name, $line)
  822. {
  823. if (!is_string($line)) {
  824. $line = var_export($line, true);
  825. }
  826. $date_format = self::$instance ? self::$instance->config->get('log_date_format') : null;
  827. $log_driver = self::$instance ? self::$instance->config->get('log_driver') : null;
  828. if (empty($date_format)) {
  829. $date_format = 'd-M-Y H:i:s O';
  830. }
  831. $date = date($date_format);
  832. // trigger logging hook
  833. if (is_object(self::$instance) && is_object(self::$instance->plugins)) {
  834. $log = self::$instance->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
  835. $name = $log['name'];
  836. $line = $log['line'];
  837. $date = $log['date'];
  838. if ($log['abort'])
  839. return true;
  840. }
  841. if ($log_driver == 'syslog') {
  842. $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
  843. syslog($prio, $line);
  844. return true;
  845. }
  846. // log_driver == 'file' is assumed here
  847. $line = sprintf("[%s]: %s\n", $date, $line);
  848. $log_dir = self::$instance ? self::$instance->config->get('log_dir') : null;
  849. if (empty($log_dir)) {
  850. $log_dir = INSTALL_PATH . 'logs';
  851. }
  852. // try to open specific log file for writing
  853. $logfile = $log_dir.'/'.$name;
  854. if ($fp = @fopen($logfile, 'a')) {
  855. fwrite($fp, $line);
  856. fflush($fp);
  857. fclose($fp);
  858. return true;
  859. }
  860. trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
  861. return false;
  862. }
  863. /**
  864. * Throw system error (and show error page).
  865. *
  866. * @param array Named parameters
  867. * - code: Error code (required)
  868. * - type: Error type [php|db|imap|javascript] (required)
  869. * - message: Error message
  870. * - file: File where error occured
  871. * - line: Line where error occured
  872. * @param boolean True to log the error
  873. * @param boolean Terminate script execution
  874. */
  875. public static function raise_error($arg = array(), $log = false, $terminate = false)
  876. {
  877. // handle PHP exceptions
  878. if (is_object($arg) && is_a($arg, 'Exception')) {
  879. $err = array(
  880. 'type' => 'php',
  881. 'code' => $arg->getCode(),
  882. 'line' => $arg->getLine(),
  883. 'file' => $arg->getFile(),
  884. 'message' => $arg->getMessage(),
  885. );
  886. $arg = $err;
  887. }
  888. // installer
  889. if (class_exists('rcube_install', false)) {
  890. $rci = rcube_install::get_instance();
  891. $rci->raise_error($arg);
  892. return;
  893. }
  894. if (($log || $terminate) && $arg['type'] && $arg['message']) {
  895. $arg['fatal'] = $terminate;
  896. self::log_bug($arg);
  897. }
  898. // display error page and terminate script
  899. if ($terminate && is_object(self::$instance->output)) {
  900. self::$instance->output->raise_error($arg['code'], $arg['message']);
  901. }
  902. }
  903. /**
  904. * Report error according to configured debug_level
  905. *
  906. * @param array Named parameters
  907. * @see self::raise_error()
  908. */
  909. public static function log_bug($arg_arr)
  910. {
  911. $program = strtoupper($arg_arr['type']);
  912. $level = self::get_instance()->config->get('debug_level');
  913. // disable errors for ajax requests, write to log instead (#1487831)
  914. if (($level & 4) && !empty($_REQUEST['_remote'])) {
  915. $level = ($level ^ 4) | 1;
  916. }
  917. // write error to local log file
  918. if (($level & 1) || !empty($arg_arr['fatal'])) {
  919. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  920. $post_query = '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']);
  921. }
  922. else {
  923. $post_query = '';
  924. }
  925. $log_entry = sprintf("%s Error: %s%s (%s %s)",
  926. $program,
  927. $arg_arr['message'],
  928. $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
  929. $_SERVER['REQUEST_METHOD'],
  930. $_SERVER['REQUEST_URI'] . $post_query);
  931. if (!self::write_log('errors', $log_entry)) {
  932. // send error to PHPs error handler if write_log didn't succeed
  933. trigger_error($arg_arr['message']);
  934. }
  935. }
  936. // report the bug to the global bug reporting system
  937. if ($level & 2) {
  938. // TODO: Send error via HTTP
  939. }
  940. // show error if debug_mode is on
  941. if ($level & 4) {
  942. print "<b>$program Error";
  943. if (!empty($arg_arr['file']) && !empty($arg_arr['line'])) {
  944. print " in $arg_arr[file] ($arg_arr[line])";
  945. }
  946. print ':</b>&nbsp;';
  947. print nl2br($arg_arr['message']);
  948. print '<br />';
  949. flush();
  950. }
  951. }
  952. /**
  953. * Returns current time (with microseconds).
  954. *
  955. * @return float Current time in seconds since the Unix
  956. */
  957. public static function timer()
  958. {
  959. return microtime(true);
  960. }
  961. /**
  962. * Logs time difference according to provided timer
  963. *
  964. * @param float $timer Timer (self::timer() result)
  965. * @param string $label Log line prefix
  966. * @param string $dest Log file name
  967. *
  968. * @see self::timer()
  969. */
  970. public static function print_timer($timer, $label = 'Timer', $dest = 'console')
  971. {
  972. static $print_count = 0;
  973. $print_count++;
  974. $now = self::timer();
  975. $diff = $now - $timer;
  976. if (empty($label)) {
  977. $label = 'Timer '.$print_count;
  978. }
  979. self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
  980. }
  981. /**
  982. * Getter for logged user ID.
  983. *
  984. * @return mixed User identifier
  985. */
  986. public function get_user_id()
  987. {
  988. if (is_object($this->user)) {
  989. return $this->user->ID;
  990. }
  991. else if (isset($_SESSION['user_id'])) {
  992. return $_SESSION['user_id'];
  993. }
  994. return null;
  995. }
  996. /**
  997. * Getter for logged user name.
  998. *
  999. * @return string User name
  1000. */
  1001. public function get_user_name()
  1002. {
  1003. if (is_object($this->user)) {
  1004. return $this->user->get_username();
  1005. }
  1006. return null;
  1007. }
  1008. }
  1009. /**
  1010. * Lightweight plugin API class serving as a dummy if plugins are not enabled
  1011. *
  1012. * @package Core
  1013. */
  1014. class rcube_dummy_plugin_api
  1015. {
  1016. /**
  1017. * Triggers a plugin hook.
  1018. * @see rcube_plugin_api::exec_hook()
  1019. */
  1020. public function exec_hook($hook, $args = array())
  1021. {
  1022. return $args;
  1023. }
  1024. }