Mercurial > hg > rc2
comparison program/lib/Roundcube/rcube.php @ 0:4681f974d28b
vanilla 1.3.3 distro, I hope
| author | Charlie Root |
|---|---|
| date | Thu, 04 Jan 2018 15:52:31 -0500 |
| parents | |
| children | 3a5f959af5ae |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:4681f974d28b |
|---|---|
| 1 <?php | |
| 2 | |
| 3 /** | |
| 4 +-----------------------------------------------------------------------+ | |
| 5 | This file is part of the Roundcube Webmail client | | |
| 6 | Copyright (C) 2008-2014, The Roundcube Dev Team | | |
| 7 | Copyright (C) 2011-2014, Kolab Systems AG | | |
| 8 | | | |
| 9 | Licensed under the GNU General Public License version 3 or | | |
| 10 | any later version with exceptions for skins & plugins. | | |
| 11 | See the README file for a full license statement. | | |
| 12 | | | |
| 13 | PURPOSE: | | |
| 14 | Framework base class providing core functions and holding | | |
| 15 | instances of all 'global' objects like db- and storage-connections | | |
| 16 +-----------------------------------------------------------------------+ | |
| 17 | Author: Thomas Bruederli <roundcube@gmail.com> | | |
| 18 +-----------------------------------------------------------------------+ | |
| 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 // Init options | |
| 31 const INIT_WITH_DB = 1; | |
| 32 const INIT_WITH_PLUGINS = 2; | |
| 33 | |
| 34 // Request status | |
| 35 const REQUEST_VALID = 0; | |
| 36 const REQUEST_ERROR_URL = 1; | |
| 37 const REQUEST_ERROR_TOKEN = 2; | |
| 38 | |
| 39 const DEBUG_LINE_LENGTH = 4096; | |
| 40 | |
| 41 /** | |
| 42 * Singleton instance of rcube | |
| 43 * | |
| 44 * @var rcube | |
| 45 */ | |
| 46 static protected $instance; | |
| 47 | |
| 48 /** | |
| 49 * Stores instance of rcube_config. | |
| 50 * | |
| 51 * @var rcube_config | |
| 52 */ | |
| 53 public $config; | |
| 54 | |
| 55 /** | |
| 56 * Instance of database class. | |
| 57 * | |
| 58 * @var rcube_db | |
| 59 */ | |
| 60 public $db; | |
| 61 | |
| 62 /** | |
| 63 * Instance of Memcache class. | |
| 64 * | |
| 65 * @var Memcache | |
| 66 */ | |
| 67 public $memcache; | |
| 68 | |
| 69 /** | |
| 70 * Instance of rcube_session class. | |
| 71 * | |
| 72 * @var rcube_session | |
| 73 */ | |
| 74 public $session; | |
| 75 | |
| 76 /** | |
| 77 * Instance of rcube_smtp class. | |
| 78 * | |
| 79 * @var rcube_smtp | |
| 80 */ | |
| 81 public $smtp; | |
| 82 | |
| 83 /** | |
| 84 * Instance of rcube_storage class. | |
| 85 * | |
| 86 * @var rcube_storage | |
| 87 */ | |
| 88 public $storage; | |
| 89 | |
| 90 /** | |
| 91 * Instance of rcube_output class. | |
| 92 * | |
| 93 * @var rcube_output | |
| 94 */ | |
| 95 public $output; | |
| 96 | |
| 97 /** | |
| 98 * Instance of rcube_plugin_api. | |
| 99 * | |
| 100 * @var rcube_plugin_api | |
| 101 */ | |
| 102 public $plugins; | |
| 103 | |
| 104 /** | |
| 105 * Instance of rcube_user class. | |
| 106 * | |
| 107 * @var rcube_user | |
| 108 */ | |
| 109 public $user; | |
| 110 | |
| 111 /** | |
| 112 * Request status | |
| 113 * | |
| 114 * @var int | |
| 115 */ | |
| 116 public $request_status = 0; | |
| 117 | |
| 118 /* private/protected vars */ | |
| 119 protected $texts; | |
| 120 protected $caches = array(); | |
| 121 protected $shutdown_functions = array(); | |
| 122 | |
| 123 | |
| 124 /** | |
| 125 * This implements the 'singleton' design pattern | |
| 126 * | |
| 127 * @param integer $mode Options to initialize with this instance. See rcube::INIT_WITH_* constants | |
| 128 * @param string $env Environment name to run (e.g. live, dev, test) | |
| 129 * | |
| 130 * @return rcube The one and only instance | |
| 131 */ | |
| 132 static function get_instance($mode = 0, $env = '') | |
| 133 { | |
| 134 if (!self::$instance) { | |
| 135 self::$instance = new rcube($env); | |
| 136 self::$instance->init($mode); | |
| 137 } | |
| 138 | |
| 139 return self::$instance; | |
| 140 } | |
| 141 | |
| 142 /** | |
| 143 * Private constructor | |
| 144 */ | |
| 145 protected function __construct($env = '') | |
| 146 { | |
| 147 // load configuration | |
| 148 $this->config = new rcube_config($env); | |
| 149 $this->plugins = new rcube_dummy_plugin_api; | |
| 150 | |
| 151 register_shutdown_function(array($this, 'shutdown')); | |
| 152 } | |
| 153 | |
| 154 /** | |
| 155 * Initial startup function | |
| 156 */ | |
| 157 protected function init($mode = 0) | |
| 158 { | |
| 159 // initialize syslog | |
| 160 if ($this->config->get('log_driver') == 'syslog') { | |
| 161 $syslog_id = $this->config->get('syslog_id', 'roundcube'); | |
| 162 $syslog_facility = $this->config->get('syslog_facility', LOG_USER); | |
| 163 openlog($syslog_id, LOG_ODELAY, $syslog_facility); | |
| 164 } | |
| 165 | |
| 166 // connect to database | |
| 167 if ($mode & self::INIT_WITH_DB) { | |
| 168 $this->get_dbh(); | |
| 169 } | |
| 170 | |
| 171 // create plugin API and load plugins | |
| 172 if ($mode & self::INIT_WITH_PLUGINS) { | |
| 173 $this->plugins = rcube_plugin_api::get_instance(); | |
| 174 } | |
| 175 } | |
| 176 | |
| 177 /** | |
| 178 * Get the current database connection | |
| 179 * | |
| 180 * @return rcube_db Database object | |
| 181 */ | |
| 182 public function get_dbh() | |
| 183 { | |
| 184 if (!$this->db) { | |
| 185 $this->db = rcube_db::factory( | |
| 186 $this->config->get('db_dsnw'), | |
| 187 $this->config->get('db_dsnr'), | |
| 188 $this->config->get('db_persistent') | |
| 189 ); | |
| 190 | |
| 191 $this->db->set_debug((bool)$this->config->get('sql_debug')); | |
| 192 } | |
| 193 | |
| 194 return $this->db; | |
| 195 } | |
| 196 | |
| 197 /** | |
| 198 * Get global handle for memcache access | |
| 199 * | |
| 200 * @return object Memcache | |
| 201 */ | |
| 202 public function get_memcache() | |
| 203 { | |
| 204 if (!isset($this->memcache)) { | |
| 205 // no memcache support in PHP | |
| 206 if (!class_exists('Memcache')) { | |
| 207 $this->memcache = false; | |
| 208 return false; | |
| 209 } | |
| 210 | |
| 211 $this->memcache = new Memcache; | |
| 212 $this->memcache_init(); | |
| 213 | |
| 214 // test connection and failover (will result in $this->mc_available == 0 on complete failure) | |
| 215 $this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist | |
| 216 | |
| 217 if (!$this->mc_available) { | |
| 218 $this->memcache = false; | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 return $this->memcache; | |
| 223 } | |
| 224 | |
| 225 /** | |
| 226 * Get global handle for memcache access | |
| 227 * | |
| 228 * @return object Memcache | |
| 229 */ | |
| 230 protected function memcache_init() | |
| 231 { | |
| 232 $this->mc_available = 0; | |
| 233 | |
| 234 // add all configured hosts to pool | |
| 235 $pconnect = $this->config->get('memcache_pconnect', true); | |
| 236 $timeout = $this->config->get('memcache_timeout', 1); | |
| 237 $retry_interval = $this->config->get('memcache_retry_interval', 15); | |
| 238 | |
| 239 foreach ($this->config->get('memcache_hosts', array()) as $host) { | |
| 240 if (substr($host, 0, 7) != 'unix://') { | |
| 241 list($host, $port) = explode(':', $host); | |
| 242 if (!$port) $port = 11211; | |
| 243 } | |
| 244 else { | |
| 245 $port = 0; | |
| 246 } | |
| 247 | |
| 248 $this->mc_available += intval($this->memcache->addServer( | |
| 249 $host, $port, $pconnect, 1, $timeout, $retry_interval, false, array($this, 'memcache_failure'))); | |
| 250 } | |
| 251 } | |
| 252 | |
| 253 /** | |
| 254 * Callback for memcache failure | |
| 255 */ | |
| 256 public function memcache_failure($host, $port) | |
| 257 { | |
| 258 static $seen = array(); | |
| 259 | |
| 260 // only report once | |
| 261 if (!$seen["$host:$port"]++) { | |
| 262 $this->mc_available--; | |
| 263 self::raise_error(array( | |
| 264 'code' => 604, 'type' => 'db', | |
| 265 'line' => __LINE__, 'file' => __FILE__, | |
| 266 'message' => "Memcache failure on host $host:$port"), | |
| 267 true, false); | |
| 268 } | |
| 269 } | |
| 270 | |
| 271 /** | |
| 272 * Initialize and get cache object | |
| 273 * | |
| 274 * @param string $name Cache identifier | |
| 275 * @param string $type Cache type ('db', 'apc' or 'memcache') | |
| 276 * @param string $ttl Expiration time for cache items | |
| 277 * @param bool $packed Enables/disables data serialization | |
| 278 * | |
| 279 * @return rcube_cache Cache object | |
| 280 */ | |
| 281 public function get_cache($name, $type='db', $ttl=0, $packed=true) | |
| 282 { | |
| 283 if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) { | |
| 284 $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed); | |
| 285 } | |
| 286 | |
| 287 return $this->caches[$name]; | |
| 288 } | |
| 289 | |
| 290 /** | |
| 291 * Initialize and get shared cache object | |
| 292 * | |
| 293 * @param string $name Cache identifier | |
| 294 * @param bool $packed Enables/disables data serialization | |
| 295 * | |
| 296 * @return rcube_cache_shared Cache object | |
| 297 */ | |
| 298 public function get_cache_shared($name, $packed=true) | |
| 299 { | |
| 300 $shared_name = "shared_$name"; | |
| 301 | |
| 302 if (!array_key_exists($shared_name, $this->caches)) { | |
| 303 $opt = strtolower($name) . '_cache'; | |
| 304 $type = $this->config->get($opt); | |
| 305 $ttl = $this->config->get($opt . '_ttl'); | |
| 306 | |
| 307 if (!$type) { | |
| 308 // cache is disabled | |
| 309 return $this->caches[$shared_name] = null; | |
| 310 } | |
| 311 | |
| 312 if ($ttl === null) { | |
| 313 $ttl = $this->config->get('shared_cache_ttl', '10d'); | |
| 314 } | |
| 315 | |
| 316 $this->caches[$shared_name] = new rcube_cache_shared($type, $name, $ttl, $packed); | |
| 317 } | |
| 318 | |
| 319 return $this->caches[$shared_name]; | |
| 320 } | |
| 321 | |
| 322 /** | |
| 323 * Create SMTP object and connect to server | |
| 324 * | |
| 325 * @param boolean $connect True if connection should be established | |
| 326 */ | |
| 327 public function smtp_init($connect = false) | |
| 328 { | |
| 329 $this->smtp = new rcube_smtp(); | |
| 330 | |
| 331 if ($connect) { | |
| 332 $this->smtp->connect(); | |
| 333 } | |
| 334 } | |
| 335 | |
| 336 /** | |
| 337 * Initialize and get storage object | |
| 338 * | |
| 339 * @return rcube_storage Storage object | |
| 340 */ | |
| 341 public function get_storage() | |
| 342 { | |
| 343 // already initialized | |
| 344 if (!is_object($this->storage)) { | |
| 345 $this->storage_init(); | |
| 346 } | |
| 347 | |
| 348 return $this->storage; | |
| 349 } | |
| 350 | |
| 351 /** | |
| 352 * Initialize storage object | |
| 353 */ | |
| 354 public function storage_init() | |
| 355 { | |
| 356 // already initialized | |
| 357 if (is_object($this->storage)) { | |
| 358 return; | |
| 359 } | |
| 360 | |
| 361 $driver = $this->config->get('storage_driver', 'imap'); | |
| 362 $driver_class = "rcube_{$driver}"; | |
| 363 | |
| 364 if (!class_exists($driver_class)) { | |
| 365 self::raise_error(array( | |
| 366 'code' => 700, 'type' => 'php', | |
| 367 'file' => __FILE__, 'line' => __LINE__, | |
| 368 'message' => "Storage driver class ($driver) not found!"), | |
| 369 true, true); | |
| 370 } | |
| 371 | |
| 372 // Initialize storage object | |
| 373 $this->storage = new $driver_class; | |
| 374 | |
| 375 // for backward compat. (deprecated, will be removed) | |
| 376 $this->imap = $this->storage; | |
| 377 | |
| 378 // set class options | |
| 379 $options = array( | |
| 380 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'), | |
| 381 'auth_cid' => $this->config->get("{$driver}_auth_cid"), | |
| 382 'auth_pw' => $this->config->get("{$driver}_auth_pw"), | |
| 383 'debug' => (bool) $this->config->get("{$driver}_debug"), | |
| 384 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"), | |
| 385 'disabled_caps' => $this->config->get("{$driver}_disabled_caps"), | |
| 386 'socket_options' => $this->config->get("{$driver}_conn_options"), | |
| 387 'timeout' => (int) $this->config->get("{$driver}_timeout"), | |
| 388 'skip_deleted' => (bool) $this->config->get('skip_deleted'), | |
| 389 'driver' => $driver, | |
| 390 ); | |
| 391 | |
| 392 if (!empty($_SESSION['storage_host'])) { | |
| 393 $options['language'] = $_SESSION['language']; | |
| 394 $options['host'] = $_SESSION['storage_host']; | |
| 395 $options['user'] = $_SESSION['username']; | |
| 396 $options['port'] = $_SESSION['storage_port']; | |
| 397 $options['ssl'] = $_SESSION['storage_ssl']; | |
| 398 $options['password'] = $this->decrypt($_SESSION['password']); | |
| 399 $_SESSION[$driver.'_host'] = $_SESSION['storage_host']; | |
| 400 } | |
| 401 | |
| 402 $options = $this->plugins->exec_hook("storage_init", $options); | |
| 403 | |
| 404 // for backward compat. (deprecated, to be removed) | |
| 405 $options = $this->plugins->exec_hook("imap_init", $options); | |
| 406 | |
| 407 $this->storage->set_options($options); | |
| 408 $this->set_storage_prop(); | |
| 409 | |
| 410 // subscribe to 'storage_connected' hook for session logging | |
| 411 if ($this->config->get('imap_log_session', false)) { | |
| 412 $this->plugins->register_hook('storage_connected', array($this, 'storage_log_session')); | |
| 413 } | |
| 414 } | |
| 415 | |
| 416 /** | |
| 417 * Set storage parameters. | |
| 418 */ | |
| 419 protected function set_storage_prop() | |
| 420 { | |
| 421 $storage = $this->get_storage(); | |
| 422 | |
| 423 // set pagesize from config | |
| 424 $pagesize = $this->config->get('mail_pagesize'); | |
| 425 if (!$pagesize) { | |
| 426 $pagesize = $this->config->get('pagesize', 50); | |
| 427 } | |
| 428 | |
| 429 $storage->set_pagesize($pagesize); | |
| 430 $storage->set_charset($this->config->get('default_charset', RCUBE_CHARSET)); | |
| 431 | |
| 432 // enable caching of mail data | |
| 433 $driver = $this->config->get('storage_driver', 'imap'); | |
| 434 $storage_cache = $this->config->get("{$driver}_cache"); | |
| 435 $messages_cache = $this->config->get('messages_cache'); | |
| 436 // for backward compatybility | |
| 437 if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) { | |
| 438 $storage_cache = 'db'; | |
| 439 $messages_cache = true; | |
| 440 } | |
| 441 | |
| 442 if ($storage_cache) { | |
| 443 $storage->set_caching($storage_cache); | |
| 444 } | |
| 445 if ($messages_cache) { | |
| 446 $storage->set_messages_caching(true); | |
| 447 } | |
| 448 } | |
| 449 | |
| 450 /** | |
| 451 * Set special folders type association. | |
| 452 * This must be done AFTER connecting to the server! | |
| 453 */ | |
| 454 protected function set_special_folders() | |
| 455 { | |
| 456 $storage = $this->get_storage(); | |
| 457 $folders = $storage->get_special_folders(true); | |
| 458 $prefs = array(); | |
| 459 | |
| 460 // check SPECIAL-USE flags on IMAP folders | |
| 461 foreach ($folders as $type => $folder) { | |
| 462 $idx = $type . '_mbox'; | |
| 463 if ($folder !== $this->config->get($idx)) { | |
| 464 $prefs[$idx] = $folder; | |
| 465 } | |
| 466 } | |
| 467 | |
| 468 // Some special folders differ, update user preferences | |
| 469 if (!empty($prefs) && $this->user) { | |
| 470 $this->user->save_prefs($prefs); | |
| 471 } | |
| 472 | |
| 473 // create default folders (on login) | |
| 474 if ($this->config->get('create_default_folders')) { | |
| 475 $storage->create_default_folders(); | |
| 476 } | |
| 477 } | |
| 478 | |
| 479 /** | |
| 480 * Callback for IMAP connection events to log session identifiers | |
| 481 */ | |
| 482 public function storage_log_session($args) | |
| 483 { | |
| 484 if (!empty($args['session']) && session_id()) { | |
| 485 $this->write_log('imap_session', $args['session']); | |
| 486 } | |
| 487 } | |
| 488 | |
| 489 /** | |
| 490 * Create session object and start the session. | |
| 491 */ | |
| 492 public function session_init() | |
| 493 { | |
| 494 // session started (Installer?) | |
| 495 if (session_id()) { | |
| 496 return; | |
| 497 } | |
| 498 | |
| 499 $sess_name = $this->config->get('session_name'); | |
| 500 $sess_domain = $this->config->get('session_domain'); | |
| 501 $sess_path = $this->config->get('session_path'); | |
| 502 $lifetime = $this->config->get('session_lifetime', 0) * 60; | |
| 503 $is_secure = $this->config->get('use_https') || rcube_utils::https_check(); | |
| 504 | |
| 505 // set session domain | |
| 506 if ($sess_domain) { | |
| 507 ini_set('session.cookie_domain', $sess_domain); | |
| 508 } | |
| 509 // set session path | |
| 510 if ($sess_path) { | |
| 511 ini_set('session.cookie_path', $sess_path); | |
| 512 } | |
| 513 // set session garbage collecting time according to session_lifetime | |
| 514 if ($lifetime) { | |
| 515 ini_set('session.gc_maxlifetime', $lifetime * 2); | |
| 516 } | |
| 517 | |
| 518 // set session cookie lifetime so it never expires (#5961) | |
| 519 ini_set('session.cookie_lifetime', 0); | |
| 520 ini_set('session.cookie_secure', $is_secure); | |
| 521 ini_set('session.name', $sess_name ?: 'roundcube_sessid'); | |
| 522 ini_set('session.use_cookies', 1); | |
| 523 ini_set('session.use_only_cookies', 1); | |
| 524 ini_set('session.cookie_httponly', 1); | |
| 525 | |
| 526 // get session driver instance | |
| 527 $this->session = rcube_session::factory($this->config); | |
| 528 $this->session->register_gc_handler(array($this, 'gc')); | |
| 529 | |
| 530 // start PHP session (if not in CLI mode) | |
| 531 if ($_SERVER['REMOTE_ADDR']) { | |
| 532 $this->session->start(); | |
| 533 } | |
| 534 } | |
| 535 | |
| 536 /** | |
| 537 * Garbage collector - cache/temp cleaner | |
| 538 */ | |
| 539 public function gc() | |
| 540 { | |
| 541 rcube_cache::gc(); | |
| 542 rcube_cache_shared::gc(); | |
| 543 $this->get_storage()->cache_gc(); | |
| 544 | |
| 545 $this->gc_temp(); | |
| 546 } | |
| 547 | |
| 548 /** | |
| 549 * Garbage collector function for temp files. | |
| 550 * Remove temp files older than two days | |
| 551 */ | |
| 552 public function gc_temp() | |
| 553 { | |
| 554 $tmp = unslashify($this->config->get('temp_dir')); | |
| 555 | |
| 556 // expire in 48 hours by default | |
| 557 $temp_dir_ttl = $this->config->get('temp_dir_ttl', '48h'); | |
| 558 $temp_dir_ttl = get_offset_sec($temp_dir_ttl); | |
| 559 if ($temp_dir_ttl < 6*3600) | |
| 560 $temp_dir_ttl = 6*3600; // 6 hours sensible lower bound. | |
| 561 | |
| 562 $expire = time() - $temp_dir_ttl; | |
| 563 | |
| 564 if ($tmp && ($dir = opendir($tmp))) { | |
| 565 while (($fname = readdir($dir)) !== false) { | |
| 566 if ($fname[0] == '.') { | |
| 567 continue; | |
| 568 } | |
| 569 | |
| 570 if (@filemtime($tmp.'/'.$fname) < $expire) { | |
| 571 @unlink($tmp.'/'.$fname); | |
| 572 } | |
| 573 } | |
| 574 | |
| 575 closedir($dir); | |
| 576 } | |
| 577 } | |
| 578 | |
| 579 /** | |
| 580 * Runs garbage collector with probability based on | |
| 581 * session settings. This is intended for environments | |
| 582 * without a session. | |
| 583 */ | |
| 584 public function gc_run() | |
| 585 { | |
| 586 $probability = (int) ini_get('session.gc_probability'); | |
| 587 $divisor = (int) ini_get('session.gc_divisor'); | |
| 588 | |
| 589 if ($divisor > 0 && $probability > 0) { | |
| 590 $random = mt_rand(1, $divisor); | |
| 591 if ($random <= $probability) { | |
| 592 $this->gc(); | |
| 593 } | |
| 594 } | |
| 595 } | |
| 596 | |
| 597 /** | |
| 598 * Get localized text in the desired language | |
| 599 * | |
| 600 * @param mixed $attrib Named parameters array or label name | |
| 601 * @param string $domain Label domain (plugin) name | |
| 602 * | |
| 603 * @return string Localized text | |
| 604 */ | |
| 605 public function gettext($attrib, $domain = null) | |
| 606 { | |
| 607 // load localization files if not done yet | |
| 608 if (empty($this->texts)) { | |
| 609 $this->load_language(); | |
| 610 } | |
| 611 | |
| 612 // extract attributes | |
| 613 if (is_string($attrib)) { | |
| 614 $attrib = array('name' => $attrib); | |
| 615 } | |
| 616 | |
| 617 $name = (string) $attrib['name']; | |
| 618 | |
| 619 // attrib contain text values: use them from now | |
| 620 if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us'])) { | |
| 621 $this->texts[$name] = $setval; | |
| 622 } | |
| 623 | |
| 624 // check for text with domain | |
| 625 if ($domain && ($text = $this->texts[$domain.'.'.$name])) { | |
| 626 } | |
| 627 // text does not exist | |
| 628 else if (!($text = $this->texts[$name])) { | |
| 629 return "[$name]"; | |
| 630 } | |
| 631 | |
| 632 // replace vars in text | |
| 633 if (is_array($attrib['vars'])) { | |
| 634 foreach ($attrib['vars'] as $var_key => $var_value) { | |
| 635 $text = str_replace($var_key[0] != '$' ? '$'.$var_key : $var_key, $var_value, $text); | |
| 636 } | |
| 637 } | |
| 638 | |
| 639 // replace \n with real line break | |
| 640 $text = strtr($text, array('\n' => "\n")); | |
| 641 | |
| 642 // case folding | |
| 643 if (($attrib['uppercase'] && strtolower($attrib['uppercase']) == 'first') || $attrib['ucfirst']) { | |
| 644 $case_mode = MB_CASE_TITLE; | |
| 645 } | |
| 646 else if ($attrib['uppercase']) { | |
| 647 $case_mode = MB_CASE_UPPER; | |
| 648 } | |
| 649 else if ($attrib['lowercase']) { | |
| 650 $case_mode = MB_CASE_LOWER; | |
| 651 } | |
| 652 | |
| 653 if (isset($case_mode)) { | |
| 654 $text = mb_convert_case($text, $case_mode); | |
| 655 } | |
| 656 | |
| 657 return $text; | |
| 658 } | |
| 659 | |
| 660 /** | |
| 661 * Check if the given text label exists | |
| 662 * | |
| 663 * @param string $name Label name | |
| 664 * @param string $domain Label domain (plugin) name or '*' for all domains | |
| 665 * @param string $ref_domain Sets domain name if label is found | |
| 666 * | |
| 667 * @return boolean True if text exists (either in the current language or in en_US) | |
| 668 */ | |
| 669 public function text_exists($name, $domain = null, &$ref_domain = null) | |
| 670 { | |
| 671 // load localization files if not done yet | |
| 672 if (empty($this->texts)) { | |
| 673 $this->load_language(); | |
| 674 } | |
| 675 | |
| 676 if (isset($this->texts[$name])) { | |
| 677 $ref_domain = ''; | |
| 678 return true; | |
| 679 } | |
| 680 | |
| 681 // any of loaded domains (plugins) | |
| 682 if ($domain == '*') { | |
| 683 foreach ($this->plugins->loaded_plugins() as $domain) { | |
| 684 if (isset($this->texts[$domain.'.'.$name])) { | |
| 685 $ref_domain = $domain; | |
| 686 return true; | |
| 687 } | |
| 688 } | |
| 689 } | |
| 690 // specified domain | |
| 691 else if ($domain) { | |
| 692 $ref_domain = $domain; | |
| 693 return isset($this->texts[$domain.'.'.$name]); | |
| 694 } | |
| 695 | |
| 696 return false; | |
| 697 } | |
| 698 | |
| 699 /** | |
| 700 * Load a localization package | |
| 701 * | |
| 702 * @param string $lang Language ID | |
| 703 * @param array $add Additional text labels/messages | |
| 704 * @param array $merge Additional text labels/messages to merge | |
| 705 */ | |
| 706 public function load_language($lang = null, $add = array(), $merge = array()) | |
| 707 { | |
| 708 $lang = $this->language_prop($lang ?: $_SESSION['language']); | |
| 709 | |
| 710 // load localized texts | |
| 711 if (empty($this->texts) || $lang != $_SESSION['language']) { | |
| 712 $this->texts = array(); | |
| 713 | |
| 714 // handle empty lines after closing PHP tag in localization files | |
| 715 ob_start(); | |
| 716 | |
| 717 // get english labels (these should be complete) | |
| 718 @include(RCUBE_LOCALIZATION_DIR . 'en_US/labels.inc'); | |
| 719 @include(RCUBE_LOCALIZATION_DIR . 'en_US/messages.inc'); | |
| 720 | |
| 721 if (is_array($labels)) | |
| 722 $this->texts = $labels; | |
| 723 if (is_array($messages)) | |
| 724 $this->texts = array_merge($this->texts, $messages); | |
| 725 | |
| 726 // include user language files | |
| 727 if ($lang != 'en' && $lang != 'en_US' && is_dir(RCUBE_LOCALIZATION_DIR . $lang)) { | |
| 728 include_once(RCUBE_LOCALIZATION_DIR . $lang . '/labels.inc'); | |
| 729 include_once(RCUBE_LOCALIZATION_DIR . $lang . '/messages.inc'); | |
| 730 | |
| 731 if (is_array($labels)) | |
| 732 $this->texts = array_merge($this->texts, $labels); | |
| 733 if (is_array($messages)) | |
| 734 $this->texts = array_merge($this->texts, $messages); | |
| 735 } | |
| 736 | |
| 737 ob_end_clean(); | |
| 738 | |
| 739 $_SESSION['language'] = $lang; | |
| 740 } | |
| 741 | |
| 742 // append additional texts (from plugin) | |
| 743 if (is_array($add) && !empty($add)) { | |
| 744 $this->texts += $add; | |
| 745 } | |
| 746 | |
| 747 // merge additional texts (from plugin) | |
| 748 if (is_array($merge) && !empty($merge)) { | |
| 749 $this->texts = array_merge($this->texts, $merge); | |
| 750 } | |
| 751 } | |
| 752 | |
| 753 /** | |
| 754 * Check the given string and return a valid language code | |
| 755 * | |
| 756 * @param string $lang Language code | |
| 757 * | |
| 758 * @return string Valid language code | |
| 759 */ | |
| 760 protected function language_prop($lang) | |
| 761 { | |
| 762 static $rcube_languages, $rcube_language_aliases; | |
| 763 | |
| 764 // user HTTP_ACCEPT_LANGUAGE if no language is specified | |
| 765 if (empty($lang) || $lang == 'auto') { | |
| 766 $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); | |
| 767 $lang = $accept_langs[0]; | |
| 768 | |
| 769 if (preg_match('/^([a-z]+)[_-]([a-z]+)$/i', $lang, $m)) { | |
| 770 $lang = $m[1] . '_' . strtoupper($m[2]); | |
| 771 } | |
| 772 } | |
| 773 | |
| 774 if (empty($rcube_languages)) { | |
| 775 @include(RCUBE_LOCALIZATION_DIR . 'index.inc'); | |
| 776 } | |
| 777 | |
| 778 // check if we have an alias for that language | |
| 779 if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) { | |
| 780 $lang = $rcube_language_aliases[$lang]; | |
| 781 } | |
| 782 // try the first two chars | |
| 783 else if (!isset($rcube_languages[$lang])) { | |
| 784 $short = substr($lang, 0, 2); | |
| 785 | |
| 786 // check if we have an alias for the short language code | |
| 787 if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) { | |
| 788 $lang = $rcube_language_aliases[$short]; | |
| 789 } | |
| 790 // expand 'nn' to 'nn_NN' | |
| 791 else if (!isset($rcube_languages[$short])) { | |
| 792 $lang = $short.'_'.strtoupper($short); | |
| 793 } | |
| 794 } | |
| 795 | |
| 796 if (!isset($rcube_languages[$lang]) || !is_dir(RCUBE_LOCALIZATION_DIR . $lang)) { | |
| 797 $lang = 'en_US'; | |
| 798 } | |
| 799 | |
| 800 return $lang; | |
| 801 } | |
| 802 | |
| 803 /** | |
| 804 * Read directory program/localization and return a list of available languages | |
| 805 * | |
| 806 * @return array List of available localizations | |
| 807 */ | |
| 808 public function list_languages() | |
| 809 { | |
| 810 static $sa_languages = array(); | |
| 811 | |
| 812 if (!count($sa_languages)) { | |
| 813 @include(RCUBE_LOCALIZATION_DIR . 'index.inc'); | |
| 814 | |
| 815 if ($dh = @opendir(RCUBE_LOCALIZATION_DIR)) { | |
| 816 while (($name = readdir($dh)) !== false) { | |
| 817 if ($name[0] == '.' || !is_dir(RCUBE_LOCALIZATION_DIR . $name)) { | |
| 818 continue; | |
| 819 } | |
| 820 | |
| 821 if ($label = $rcube_languages[$name]) { | |
| 822 $sa_languages[$name] = $label; | |
| 823 } | |
| 824 } | |
| 825 closedir($dh); | |
| 826 } | |
| 827 } | |
| 828 | |
| 829 return $sa_languages; | |
| 830 } | |
| 831 | |
| 832 /** | |
| 833 * Encrypt a string | |
| 834 * | |
| 835 * @param string $clear Clear text input | |
| 836 * @param string $key Encryption key to retrieve from the configuration, defaults to 'des_key' | |
| 837 * @param boolean $base64 Whether or not to base64_encode() the result before returning | |
| 838 * | |
| 839 * @return string Encrypted text | |
| 840 */ | |
| 841 public function encrypt($clear, $key = 'des_key', $base64 = true) | |
| 842 { | |
| 843 if (!is_string($clear) || !strlen($clear)) { | |
| 844 return ''; | |
| 845 } | |
| 846 | |
| 847 $ckey = $this->config->get_crypto_key($key); | |
| 848 $method = $this->config->get_crypto_method(); | |
| 849 $opts = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true; | |
| 850 $iv = rcube_utils::random_bytes(openssl_cipher_iv_length($method), true); | |
| 851 $cipher = $iv . openssl_encrypt($clear, $method, $ckey, $opts, $iv); | |
| 852 | |
| 853 return $base64 ? base64_encode($cipher) : $cipher; | |
| 854 } | |
| 855 | |
| 856 /** | |
| 857 * Decrypt a string | |
| 858 * | |
| 859 * @param string $cipher Encrypted text | |
| 860 * @param string $key Encryption key to retrieve from the configuration, defaults to 'des_key' | |
| 861 * @param boolean $base64 Whether or not input is base64-encoded | |
| 862 * | |
| 863 * @return string Decrypted text | |
| 864 */ | |
| 865 public function decrypt($cipher, $key = 'des_key', $base64 = true) | |
| 866 { | |
| 867 if (!$cipher) { | |
| 868 return ''; | |
| 869 } | |
| 870 | |
| 871 $cipher = $base64 ? base64_decode($cipher) : $cipher; | |
| 872 $ckey = $this->config->get_crypto_key($key); | |
| 873 $method = $this->config->get_crypto_method(); | |
| 874 $opts = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true; | |
| 875 $iv_size = openssl_cipher_iv_length($method); | |
| 876 $iv = substr($cipher, 0, $iv_size); | |
| 877 | |
| 878 // session corruption? (#1485970) | |
| 879 if (strlen($iv) < $iv_size) { | |
| 880 return ''; | |
| 881 } | |
| 882 | |
| 883 $cipher = substr($cipher, $iv_size); | |
| 884 $clear = openssl_decrypt($cipher, $method, $ckey, $opts, $iv); | |
| 885 | |
| 886 return $clear; | |
| 887 } | |
| 888 | |
| 889 /** | |
| 890 * Returns session token for secure URLs | |
| 891 * | |
| 892 * @param bool $generate Generate token if not exists in session yet | |
| 893 * | |
| 894 * @return string|bool Token string, False when disabled | |
| 895 */ | |
| 896 public function get_secure_url_token($generate = false) | |
| 897 { | |
| 898 if ($len = $this->config->get('use_secure_urls')) { | |
| 899 if (empty($_SESSION['secure_token']) && $generate) { | |
| 900 // generate x characters long token | |
| 901 $length = $len > 1 ? $len : 16; | |
| 902 $token = rcube_utils::random_bytes($length); | |
| 903 | |
| 904 $plugin = $this->plugins->exec_hook('secure_token', | |
| 905 array('value' => $token, 'length' => $length)); | |
| 906 | |
| 907 $_SESSION['secure_token'] = $plugin['value']; | |
| 908 } | |
| 909 | |
| 910 return $_SESSION['secure_token']; | |
| 911 } | |
| 912 | |
| 913 return false; | |
| 914 } | |
| 915 | |
| 916 /** | |
| 917 * Generate a unique token to be used in a form request | |
| 918 * | |
| 919 * @return string The request token | |
| 920 */ | |
| 921 public function get_request_token() | |
| 922 { | |
| 923 if (empty($_SESSION['request_token'])) { | |
| 924 $plugin = $this->plugins->exec_hook('request_token', array( | |
| 925 'value' => rcube_utils::random_bytes(32))); | |
| 926 | |
| 927 $_SESSION['request_token'] = $plugin['value']; | |
| 928 } | |
| 929 | |
| 930 return $_SESSION['request_token']; | |
| 931 } | |
| 932 | |
| 933 /** | |
| 934 * Check if the current request contains a valid token. | |
| 935 * Empty requests aren't checked until use_secure_urls is set. | |
| 936 * | |
| 937 * @param int $mode Request method | |
| 938 * | |
| 939 * @return boolean True if request token is valid false if not | |
| 940 */ | |
| 941 public function check_request($mode = rcube_utils::INPUT_POST) | |
| 942 { | |
| 943 // check secure token in URL if enabled | |
| 944 if ($token = $this->get_secure_url_token()) { | |
| 945 foreach (explode('/', preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI'])) as $tok) { | |
| 946 if ($tok == $token) { | |
| 947 return true; | |
| 948 } | |
| 949 } | |
| 950 | |
| 951 $this->request_status = self::REQUEST_ERROR_URL; | |
| 952 | |
| 953 return false; | |
| 954 } | |
| 955 | |
| 956 $sess_tok = $this->get_request_token(); | |
| 957 | |
| 958 // ajax requests | |
| 959 if (rcube_utils::request_header('X-Roundcube-Request') == $sess_tok) { | |
| 960 return true; | |
| 961 } | |
| 962 | |
| 963 // skip empty requests | |
| 964 if (($mode == rcube_utils::INPUT_POST && empty($_POST)) | |
| 965 || ($mode == rcube_utils::INPUT_GET && empty($_GET)) | |
| 966 ) { | |
| 967 return true; | |
| 968 } | |
| 969 | |
| 970 // default method of securing requests | |
| 971 $token = rcube_utils::get_input_value('_token', $mode); | |
| 972 $sess_id = $_COOKIE[ini_get('session.name')]; | |
| 973 | |
| 974 if (empty($sess_id) || $token != $sess_tok) { | |
| 975 $this->request_status = self::REQUEST_ERROR_TOKEN; | |
| 976 return false; | |
| 977 } | |
| 978 | |
| 979 return true; | |
| 980 } | |
| 981 | |
| 982 /** | |
| 983 * Build a valid URL to this instance of Roundcube | |
| 984 * | |
| 985 * @param mixed $p Either a string with the action or url parameters as key-value pairs | |
| 986 * | |
| 987 * @return string Valid application URL | |
| 988 */ | |
| 989 public function url($p) | |
| 990 { | |
| 991 // STUB: should be overloaded by the application | |
| 992 return ''; | |
| 993 } | |
| 994 | |
| 995 /** | |
| 996 * Function to be executed in script shutdown | |
| 997 * Registered with register_shutdown_function() | |
| 998 */ | |
| 999 public function shutdown() | |
| 1000 { | |
| 1001 foreach ($this->shutdown_functions as $function) { | |
| 1002 call_user_func($function); | |
| 1003 } | |
| 1004 | |
| 1005 // write session data as soon as possible and before | |
| 1006 // closing database connection, don't do this before | |
| 1007 // registered shutdown functions, they may need the session | |
| 1008 // Note: this will run registered gc handlers (ie. cache gc) | |
| 1009 if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) { | |
| 1010 $this->session->write_close(); | |
| 1011 } | |
| 1012 | |
| 1013 if (is_object($this->smtp)) { | |
| 1014 $this->smtp->disconnect(); | |
| 1015 } | |
| 1016 | |
| 1017 foreach ($this->caches as $cache) { | |
| 1018 if (is_object($cache)) { | |
| 1019 $cache->close(); | |
| 1020 } | |
| 1021 } | |
| 1022 | |
| 1023 if (is_object($this->storage)) { | |
| 1024 $this->storage->close(); | |
| 1025 } | |
| 1026 | |
| 1027 if ($this->config->get('log_driver') == 'syslog') { | |
| 1028 closelog(); | |
| 1029 } | |
| 1030 } | |
| 1031 | |
| 1032 /** | |
| 1033 * Registers shutdown function to be executed on shutdown. | |
| 1034 * The functions will be executed before destroying any | |
| 1035 * objects like smtp, imap, session, etc. | |
| 1036 * | |
| 1037 * @param callback $function Function callback | |
| 1038 */ | |
| 1039 public function add_shutdown_function($function) | |
| 1040 { | |
| 1041 $this->shutdown_functions[] = $function; | |
| 1042 } | |
| 1043 | |
| 1044 /** | |
| 1045 * When you're going to sleep the script execution for a longer time | |
| 1046 * it is good to close all external connections (sql, memcache, SMTP, IMAP). | |
| 1047 * | |
| 1048 * No action is required on wake up, all connections will be | |
| 1049 * re-established automatically. | |
| 1050 */ | |
| 1051 public function sleep() | |
| 1052 { | |
| 1053 foreach ($this->caches as $cache) { | |
| 1054 if (is_object($cache)) { | |
| 1055 $cache->close(); | |
| 1056 } | |
| 1057 } | |
| 1058 | |
| 1059 if ($this->storage) { | |
| 1060 $this->storage->close(); | |
| 1061 } | |
| 1062 | |
| 1063 if ($this->db) { | |
| 1064 $this->db->closeConnection(); | |
| 1065 } | |
| 1066 | |
| 1067 if ($this->memcache) { | |
| 1068 $this->memcache->close(); | |
| 1069 // after close() need to re-init memcache | |
| 1070 $this->memcache_init(); | |
| 1071 } | |
| 1072 | |
| 1073 if ($this->smtp) { | |
| 1074 $this->smtp->disconnect(); | |
| 1075 } | |
| 1076 } | |
| 1077 | |
| 1078 /** | |
| 1079 * Quote a given string. | |
| 1080 * Shortcut function for rcube_utils::rep_specialchars_output() | |
| 1081 * | |
| 1082 * @return string HTML-quoted string | |
| 1083 */ | |
| 1084 public static function Q($str, $mode = 'strict', $newlines = true) | |
| 1085 { | |
| 1086 return rcube_utils::rep_specialchars_output($str, 'html', $mode, $newlines); | |
| 1087 } | |
| 1088 | |
| 1089 /** | |
| 1090 * Quote a given string for javascript output. | |
| 1091 * Shortcut function for rcube_utils::rep_specialchars_output() | |
| 1092 * | |
| 1093 * @return string JS-quoted string | |
| 1094 */ | |
| 1095 public static function JQ($str) | |
| 1096 { | |
| 1097 return rcube_utils::rep_specialchars_output($str, 'js'); | |
| 1098 } | |
| 1099 | |
| 1100 /** | |
| 1101 * Construct shell command, execute it and return output as string. | |
| 1102 * Keywords {keyword} are replaced with arguments | |
| 1103 * | |
| 1104 * @param $cmd Format string with {keywords} to be replaced | |
| 1105 * @param $values (zero, one or more arrays can be passed) | |
| 1106 * | |
| 1107 * @return output of command. shell errors not detectable | |
| 1108 */ | |
| 1109 public static function exec(/* $cmd, $values1 = array(), ... */) | |
| 1110 { | |
| 1111 $args = func_get_args(); | |
| 1112 $cmd = array_shift($args); | |
| 1113 $values = $replacements = array(); | |
| 1114 | |
| 1115 // merge values into one array | |
| 1116 foreach ($args as $arg) { | |
| 1117 $values += (array)$arg; | |
| 1118 } | |
| 1119 | |
| 1120 preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER); | |
| 1121 foreach ($matches as $tags) { | |
| 1122 list(, $tag, $option, $key) = $tags; | |
| 1123 $parts = array(); | |
| 1124 | |
| 1125 if ($option) { | |
| 1126 foreach ((array)$values["-$key"] as $key => $value) { | |
| 1127 if ($value === true || $value === false || $value === null) { | |
| 1128 $parts[] = $value ? $key : ""; | |
| 1129 } | |
| 1130 else { | |
| 1131 foreach ((array)$value as $val) { | |
| 1132 $parts[] = "$key " . escapeshellarg($val); | |
| 1133 } | |
| 1134 } | |
| 1135 } | |
| 1136 } | |
| 1137 else { | |
| 1138 foreach ((array)$values[$key] as $value) { | |
| 1139 $parts[] = escapeshellarg($value); | |
| 1140 } | |
| 1141 } | |
| 1142 | |
| 1143 $replacements[$tag] = join(" ", $parts); | |
| 1144 } | |
| 1145 | |
| 1146 // use strtr behaviour of going through source string once | |
| 1147 $cmd = strtr($cmd, $replacements); | |
| 1148 | |
| 1149 return (string)shell_exec($cmd); | |
| 1150 } | |
| 1151 | |
| 1152 /** | |
| 1153 * Print or write debug messages | |
| 1154 * | |
| 1155 * @param mixed Debug message or data | |
| 1156 */ | |
| 1157 public static function console() | |
| 1158 { | |
| 1159 $args = func_get_args(); | |
| 1160 | |
| 1161 if (class_exists('rcube', false)) { | |
| 1162 $rcube = self::get_instance(); | |
| 1163 $plugin = $rcube->plugins->exec_hook('console', array('args' => $args)); | |
| 1164 if ($plugin['abort']) { | |
| 1165 return; | |
| 1166 } | |
| 1167 | |
| 1168 $args = $plugin['args']; | |
| 1169 } | |
| 1170 | |
| 1171 $msg = array(); | |
| 1172 foreach ($args as $arg) { | |
| 1173 $msg[] = !is_string($arg) ? var_export($arg, true) : $arg; | |
| 1174 } | |
| 1175 | |
| 1176 self::write_log('console', join(";\n", $msg)); | |
| 1177 } | |
| 1178 | |
| 1179 /** | |
| 1180 * Append a line to a logfile in the logs directory. | |
| 1181 * Date will be added automatically to the line. | |
| 1182 * | |
| 1183 * @param string $name Name of the log file | |
| 1184 * @param mixed $line Line to append | |
| 1185 * | |
| 1186 * @return bool True on success, False on failure | |
| 1187 */ | |
| 1188 public static function write_log($name, $line) | |
| 1189 { | |
| 1190 if (!is_string($line)) { | |
| 1191 $line = var_export($line, true); | |
| 1192 } | |
| 1193 | |
| 1194 $date_format = $log_driver = $session_key = null; | |
| 1195 if (self::$instance) { | |
| 1196 $date_format = self::$instance->config->get('log_date_format'); | |
| 1197 $log_driver = self::$instance->config->get('log_driver'); | |
| 1198 $session_key = intval(self::$instance->config->get('log_session_id', 8)); | |
| 1199 } | |
| 1200 | |
| 1201 $date = rcube_utils::date_format($date_format); | |
| 1202 | |
| 1203 // trigger logging hook | |
| 1204 if (is_object(self::$instance) && is_object(self::$instance->plugins)) { | |
| 1205 $log = self::$instance->plugins->exec_hook('write_log', | |
| 1206 array('name' => $name, 'date' => $date, 'line' => $line)); | |
| 1207 | |
| 1208 $name = $log['name']; | |
| 1209 $line = $log['line']; | |
| 1210 $date = $log['date']; | |
| 1211 | |
| 1212 if ($log['abort']) { | |
| 1213 return true; | |
| 1214 } | |
| 1215 } | |
| 1216 | |
| 1217 // add session ID to the log | |
| 1218 if ($session_key > 0 && ($sess = session_id())) { | |
| 1219 $line = '<' . substr($sess, 0, $session_key) . '> ' . $line; | |
| 1220 } | |
| 1221 | |
| 1222 if ($log_driver == 'syslog') { | |
| 1223 $prio = $name == 'errors' ? LOG_ERR : LOG_INFO; | |
| 1224 return syslog($prio, $line); | |
| 1225 } | |
| 1226 | |
| 1227 // write message with file name when configured to log to STDOUT | |
| 1228 if ($log_driver == 'stdout') { | |
| 1229 $stdout = "php://stdout"; | |
| 1230 $line = "$name: $line"; | |
| 1231 return file_put_contents($stdout, $line, FILE_APPEND) !== false; | |
| 1232 } | |
| 1233 | |
| 1234 // log_driver == 'file' is assumed here | |
| 1235 | |
| 1236 $line = sprintf("[%s]: %s\n", $date, $line); | |
| 1237 | |
| 1238 // per-user logging is activated | |
| 1239 if (self::$instance && self::$instance->config->get('per_user_logging') && self::$instance->get_user_id()) { | |
| 1240 $log_dir = self::$instance->get_user_log_dir(); | |
| 1241 if (empty($log_dir) && !in_array($name, array('errors', 'userlogins', 'sendmail'))) { | |
| 1242 return false; | |
| 1243 } | |
| 1244 } | |
| 1245 | |
| 1246 if (empty($log_dir)) { | |
| 1247 if (!empty($log['dir'])) { | |
| 1248 $log_dir = $log['dir']; | |
| 1249 } | |
| 1250 else if (self::$instance) { | |
| 1251 $log_dir = self::$instance->config->get('log_dir'); | |
| 1252 } | |
| 1253 } | |
| 1254 | |
| 1255 if (empty($log_dir)) { | |
| 1256 $log_dir = RCUBE_INSTALL_PATH . 'logs'; | |
| 1257 } | |
| 1258 | |
| 1259 return file_put_contents("$log_dir/$name", $line, FILE_APPEND) !== false; | |
| 1260 } | |
| 1261 | |
| 1262 /** | |
| 1263 * Throw system error (and show error page). | |
| 1264 * | |
| 1265 * @param array $arg Named parameters | |
| 1266 * - code: Error code (required) | |
| 1267 * - type: Error type [php|db|imap|javascript] | |
| 1268 * - message: Error message | |
| 1269 * - file: File where error occurred | |
| 1270 * - line: Line where error occurred | |
| 1271 * @param boolean $log True to log the error | |
| 1272 * @param boolean $terminate Terminate script execution | |
| 1273 */ | |
| 1274 public static function raise_error($arg = array(), $log = false, $terminate = false) | |
| 1275 { | |
| 1276 // handle PHP exceptions | |
| 1277 if (is_object($arg) && is_a($arg, 'Exception')) { | |
| 1278 $arg = array( | |
| 1279 'code' => $arg->getCode(), | |
| 1280 'line' => $arg->getLine(), | |
| 1281 'file' => $arg->getFile(), | |
| 1282 'message' => $arg->getMessage(), | |
| 1283 ); | |
| 1284 } | |
| 1285 else if (is_string($arg)) { | |
| 1286 $arg = array('message' => $arg); | |
| 1287 } | |
| 1288 | |
| 1289 if (empty($arg['code'])) { | |
| 1290 $arg['code'] = 500; | |
| 1291 } | |
| 1292 | |
| 1293 $cli = php_sapi_name() == 'cli'; | |
| 1294 | |
| 1295 // installer | |
| 1296 if (!$cli && class_exists('rcmail_install', false)) { | |
| 1297 $rci = rcmail_install::get_instance(); | |
| 1298 $rci->raise_error($arg); | |
| 1299 return; | |
| 1300 } | |
| 1301 | |
| 1302 if (($log || $terminate) && !$cli && $arg['message']) { | |
| 1303 $arg['fatal'] = $terminate; | |
| 1304 self::log_bug($arg); | |
| 1305 } | |
| 1306 | |
| 1307 // terminate script | |
| 1308 if ($terminate) { | |
| 1309 // display error page | |
| 1310 if (is_object(self::$instance->output)) { | |
| 1311 self::$instance->output->raise_error($arg['code'], $arg['message']); | |
| 1312 } | |
| 1313 else if ($cli) { | |
| 1314 fwrite(STDERR, 'ERROR: ' . $arg['message']); | |
| 1315 } | |
| 1316 | |
| 1317 exit(1); | |
| 1318 } | |
| 1319 else if ($cli) { | |
| 1320 fwrite(STDERR, 'ERROR: ' . $arg['message']); | |
| 1321 } | |
| 1322 } | |
| 1323 | |
| 1324 /** | |
| 1325 * Report error according to configured debug_level | |
| 1326 * | |
| 1327 * @param array $arg_arr Named parameters | |
| 1328 * @see self::raise_error() | |
| 1329 */ | |
| 1330 public static function log_bug($arg_arr) | |
| 1331 { | |
| 1332 $program = strtoupper($arg_arr['type'] ?: 'php'); | |
| 1333 $level = self::get_instance()->config->get('debug_level'); | |
| 1334 | |
| 1335 // disable errors for ajax requests, write to log instead (#1487831) | |
| 1336 if (($level & 4) && !empty($_REQUEST['_remote'])) { | |
| 1337 $level = ($level ^ 4) | 1; | |
| 1338 } | |
| 1339 | |
| 1340 // write error to local log file | |
| 1341 if (($level & 1) || !empty($arg_arr['fatal'])) { | |
| 1342 if ($_SERVER['REQUEST_METHOD'] == 'POST') { | |
| 1343 foreach (array('_task', '_action') as $arg) { | |
| 1344 if ($_POST[$arg] && !$_GET[$arg]) { | |
| 1345 $post_query[$arg] = $_POST[$arg]; | |
| 1346 } | |
| 1347 } | |
| 1348 | |
| 1349 if (!empty($post_query)) { | |
| 1350 $post_query = (strpos($_SERVER['REQUEST_URI'], '?') != false ? '&' : '?') | |
| 1351 . http_build_query($post_query, '', '&'); | |
| 1352 } | |
| 1353 } | |
| 1354 | |
| 1355 $log_entry = sprintf("%s Error: %s%s (%s %s)", | |
| 1356 $program, | |
| 1357 $arg_arr['message'], | |
| 1358 $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '', | |
| 1359 $_SERVER['REQUEST_METHOD'], | |
| 1360 $_SERVER['REQUEST_URI'] . $post_query); | |
| 1361 | |
| 1362 if (!self::write_log('errors', $log_entry)) { | |
| 1363 // send error to PHPs error handler if write_log didn't succeed | |
| 1364 trigger_error($arg_arr['message'], E_USER_WARNING); | |
| 1365 } | |
| 1366 } | |
| 1367 | |
| 1368 // report the bug to the global bug reporting system | |
| 1369 if ($level & 2) { | |
| 1370 // TODO: Send error via HTTP | |
| 1371 } | |
| 1372 | |
| 1373 // show error if debug_mode is on | |
| 1374 if ($level & 4) { | |
| 1375 print "<b>$program Error"; | |
| 1376 | |
| 1377 if (!empty($arg_arr['file']) && !empty($arg_arr['line'])) { | |
| 1378 print " in $arg_arr[file] ($arg_arr[line])"; | |
| 1379 } | |
| 1380 | |
| 1381 print ':</b> '; | |
| 1382 print nl2br($arg_arr['message']); | |
| 1383 print '<br />'; | |
| 1384 flush(); | |
| 1385 } | |
| 1386 } | |
| 1387 | |
| 1388 /** | |
| 1389 * Write debug info to the log | |
| 1390 * | |
| 1391 * @param string $engine Engine type - file name (memcache, apc) | |
| 1392 * @param string $data Data string to log | |
| 1393 * @param bool $result Operation result | |
| 1394 */ | |
| 1395 public static function debug($engine, $data, $result = null) | |
| 1396 { | |
| 1397 static $debug_counter; | |
| 1398 | |
| 1399 $line = '[' . (++$debug_counter[$engine]) . '] ' . $data; | |
| 1400 | |
| 1401 if (($len = strlen($line)) > self::DEBUG_LINE_LENGTH) { | |
| 1402 $diff = $len - self::DEBUG_LINE_LENGTH; | |
| 1403 $line = substr($line, 0, self::DEBUG_LINE_LENGTH) . "... [truncated $diff bytes]"; | |
| 1404 } | |
| 1405 | |
| 1406 if ($result !== null) { | |
| 1407 $line .= ' [' . ($result ? 'TRUE' : 'FALSE') . ']'; | |
| 1408 } | |
| 1409 | |
| 1410 self::write_log($engine, $line); | |
| 1411 } | |
| 1412 | |
| 1413 /** | |
| 1414 * Returns current time (with microseconds). | |
| 1415 * | |
| 1416 * @return float Current time in seconds since the Unix | |
| 1417 */ | |
| 1418 public static function timer() | |
| 1419 { | |
| 1420 return microtime(true); | |
| 1421 } | |
| 1422 | |
| 1423 /** | |
| 1424 * Logs time difference according to provided timer | |
| 1425 * | |
| 1426 * @param float $timer Timer (self::timer() result) | |
| 1427 * @param string $label Log line prefix | |
| 1428 * @param string $dest Log file name | |
| 1429 * | |
| 1430 * @see self::timer() | |
| 1431 */ | |
| 1432 public static function print_timer($timer, $label = 'Timer', $dest = 'console') | |
| 1433 { | |
| 1434 static $print_count = 0; | |
| 1435 | |
| 1436 $print_count++; | |
| 1437 $now = self::timer(); | |
| 1438 $diff = $now - $timer; | |
| 1439 | |
| 1440 if (empty($label)) { | |
| 1441 $label = 'Timer '.$print_count; | |
| 1442 } | |
| 1443 | |
| 1444 self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff)); | |
| 1445 } | |
| 1446 | |
| 1447 /** | |
| 1448 * Setter for system user object | |
| 1449 * | |
| 1450 * @param rcube_user Current user instance | |
| 1451 */ | |
| 1452 public function set_user($user) | |
| 1453 { | |
| 1454 if (is_object($user)) { | |
| 1455 $this->user = $user; | |
| 1456 | |
| 1457 // overwrite config with user preferences | |
| 1458 $this->config->set_user_prefs((array)$this->user->get_prefs()); | |
| 1459 } | |
| 1460 } | |
| 1461 | |
| 1462 /** | |
| 1463 * Getter for logged user ID. | |
| 1464 * | |
| 1465 * @return mixed User identifier | |
| 1466 */ | |
| 1467 public function get_user_id() | |
| 1468 { | |
| 1469 if (is_object($this->user)) { | |
| 1470 return $this->user->ID; | |
| 1471 } | |
| 1472 else if (isset($_SESSION['user_id'])) { | |
| 1473 return $_SESSION['user_id']; | |
| 1474 } | |
| 1475 | |
| 1476 return null; | |
| 1477 } | |
| 1478 | |
| 1479 /** | |
| 1480 * Getter for logged user name. | |
| 1481 * | |
| 1482 * @return string User name | |
| 1483 */ | |
| 1484 public function get_user_name() | |
| 1485 { | |
| 1486 if (is_object($this->user)) { | |
| 1487 return $this->user->get_username(); | |
| 1488 } | |
| 1489 else if (isset($_SESSION['username'])) { | |
| 1490 return $_SESSION['username']; | |
| 1491 } | |
| 1492 } | |
| 1493 | |
| 1494 /** | |
| 1495 * Getter for logged user email (derived from user name not identity). | |
| 1496 * | |
| 1497 * @return string User email address | |
| 1498 */ | |
| 1499 public function get_user_email() | |
| 1500 { | |
| 1501 if (is_object($this->user)) { | |
| 1502 return $this->user->get_username('mail'); | |
| 1503 } | |
| 1504 } | |
| 1505 | |
| 1506 /** | |
| 1507 * Getter for logged user password. | |
| 1508 * | |
| 1509 * @return string User password | |
| 1510 */ | |
| 1511 public function get_user_password() | |
| 1512 { | |
| 1513 if ($this->password) { | |
| 1514 return $this->password; | |
| 1515 } | |
| 1516 else if ($_SESSION['password']) { | |
| 1517 return $this->decrypt($_SESSION['password']); | |
| 1518 } | |
| 1519 } | |
| 1520 | |
| 1521 /** | |
| 1522 * Get the per-user log directory | |
| 1523 */ | |
| 1524 protected function get_user_log_dir() | |
| 1525 { | |
| 1526 $log_dir = $this->config->get('log_dir', RCUBE_INSTALL_PATH . 'logs'); | |
| 1527 $user_name = $this->get_user_name(); | |
| 1528 $user_log_dir = $log_dir . '/' . $user_name; | |
| 1529 | |
| 1530 return !empty($user_name) && is_writable($user_log_dir) ? $user_log_dir : false; | |
| 1531 } | |
| 1532 | |
| 1533 /** | |
| 1534 * Getter for logged user language code. | |
| 1535 * | |
| 1536 * @return string User language code | |
| 1537 */ | |
| 1538 public function get_user_language() | |
| 1539 { | |
| 1540 if (is_object($this->user)) { | |
| 1541 return $this->user->language; | |
| 1542 } | |
| 1543 else if (isset($_SESSION['language'])) { | |
| 1544 return $_SESSION['language']; | |
| 1545 } | |
| 1546 } | |
| 1547 | |
| 1548 /** | |
| 1549 * Unique Message-ID generator. | |
| 1550 * | |
| 1551 * @param string $sender Optional sender e-mail address | |
| 1552 * | |
| 1553 * @return string Message-ID | |
| 1554 */ | |
| 1555 public function gen_message_id($sender = null) | |
| 1556 { | |
| 1557 $local_part = md5(uniqid('rcube'.mt_rand(), true)); | |
| 1558 $domain_part = ''; | |
| 1559 | |
| 1560 if ($sender && preg_match('/@([^\s]+\.[a-z0-9-]+)/', $sender, $m)) { | |
| 1561 $domain_part = $m[1]; | |
| 1562 } | |
| 1563 else { | |
| 1564 $domain_part = $this->user->get_username('domain'); | |
| 1565 } | |
| 1566 | |
| 1567 // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924) | |
| 1568 if (!preg_match('/\.[a-z0-9-]+$/i', $domain_part)) { | |
| 1569 foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) { | |
| 1570 $host = preg_replace('/:[0-9]+$/', '', $host); | |
| 1571 if ($host && preg_match('/\.[a-z]+$/i', $host)) { | |
| 1572 $domain_part = $host; | |
| 1573 break; | |
| 1574 } | |
| 1575 } | |
| 1576 } | |
| 1577 | |
| 1578 return sprintf('<%s@%s>', $local_part, $domain_part); | |
| 1579 } | |
| 1580 | |
| 1581 /** | |
| 1582 * Send the given message using the configured method. | |
| 1583 * | |
| 1584 * @param object $message Reference to Mail_MIME object | |
| 1585 * @param string $from Sender address string | |
| 1586 * @param array $mailto Array of recipient address strings | |
| 1587 * @param array $error SMTP error array (reference) | |
| 1588 * @param string $body_file Location of file with saved message body (reference), | |
| 1589 * used when delay_file_io is enabled | |
| 1590 * @param array $options SMTP options (e.g. DSN request) | |
| 1591 * @param bool $disconnect Close SMTP connection ASAP | |
| 1592 * | |
| 1593 * @return boolean Send status. | |
| 1594 */ | |
| 1595 public function deliver_message(&$message, $from, $mailto, &$error, | |
| 1596 &$body_file = null, $options = null, $disconnect = false) | |
| 1597 { | |
| 1598 $plugin = $this->plugins->exec_hook('message_before_send', array( | |
| 1599 'message' => $message, | |
| 1600 'from' => $from, | |
| 1601 'mailto' => $mailto, | |
| 1602 'options' => $options, | |
| 1603 )); | |
| 1604 | |
| 1605 if ($plugin['abort']) { | |
| 1606 if (!empty($plugin['error'])) { | |
| 1607 $error = $plugin['error']; | |
| 1608 } | |
| 1609 if (!empty($plugin['body_file'])) { | |
| 1610 $body_file = $plugin['body_file']; | |
| 1611 } | |
| 1612 | |
| 1613 return isset($plugin['result']) ? $plugin['result'] : false; | |
| 1614 } | |
| 1615 | |
| 1616 $from = $plugin['from']; | |
| 1617 $mailto = $plugin['mailto']; | |
| 1618 $options = $plugin['options']; | |
| 1619 $message = $plugin['message']; | |
| 1620 $headers = $message->headers(); | |
| 1621 | |
| 1622 // generate list of recipients | |
| 1623 $a_recipients = (array) $mailto; | |
| 1624 | |
| 1625 if (strlen($headers['Cc'])) { | |
| 1626 $a_recipients[] = $headers['Cc']; | |
| 1627 } | |
| 1628 if (strlen($headers['Bcc'])) { | |
| 1629 $a_recipients[] = $headers['Bcc']; | |
| 1630 } | |
| 1631 | |
| 1632 // remove Bcc header and get the whole head of the message as string | |
| 1633 $smtp_headers = $message->txtHeaders(array('Bcc' => null), true); | |
| 1634 | |
| 1635 if ($message->getParam('delay_file_io')) { | |
| 1636 // use common temp dir | |
| 1637 $temp_dir = $this->config->get('temp_dir'); | |
| 1638 $body_file = tempnam($temp_dir, 'rcmMsg'); | |
| 1639 $mime_result = $message->saveMessageBody($body_file); | |
| 1640 | |
| 1641 if (is_a($mime_result, 'PEAR_Error')) { | |
| 1642 self::raise_error(array('code' => 650, 'type' => 'php', | |
| 1643 'file' => __FILE__, 'line' => __LINE__, | |
| 1644 'message' => "Could not create message: ".$mime_result->getMessage()), | |
| 1645 true, false); | |
| 1646 return false; | |
| 1647 } | |
| 1648 | |
| 1649 $msg_body = fopen($body_file, 'r'); | |
| 1650 } | |
| 1651 else { | |
| 1652 $msg_body = $message->get(); | |
| 1653 } | |
| 1654 | |
| 1655 // initialize SMTP connection | |
| 1656 if (!is_object($this->smtp)) { | |
| 1657 $this->smtp_init(true); | |
| 1658 } | |
| 1659 | |
| 1660 // send message | |
| 1661 $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $options); | |
| 1662 $response = $this->smtp->get_response(); | |
| 1663 $error = $this->smtp->get_error(); | |
| 1664 | |
| 1665 if (!$sent) { | |
| 1666 self::raise_error(array('code' => 800, 'type' => 'smtp', | |
| 1667 'line' => __LINE__, 'file' => __FILE__, | |
| 1668 'message' => join("\n", $response)), true, false); | |
| 1669 | |
| 1670 // allow plugins to catch sending errors with the same parameters as in 'message_before_send' | |
| 1671 $this->plugins->exec_hook('message_send_error', $plugin + array('error' => $error)); | |
| 1672 } | |
| 1673 else { | |
| 1674 $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body, 'message' => $message)); | |
| 1675 | |
| 1676 // remove MDN headers after sending | |
| 1677 unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']); | |
| 1678 | |
| 1679 if ($this->config->get('smtp_log')) { | |
| 1680 // get all recipient addresses | |
| 1681 $mailto = implode(',', $a_recipients); | |
| 1682 $mailto = rcube_mime::decode_address_list($mailto, null, false, null, true); | |
| 1683 | |
| 1684 self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s", | |
| 1685 $this->user->get_username(), | |
| 1686 rcube_utils::remote_addr(), | |
| 1687 implode(', ', $mailto), | |
| 1688 !empty($response) ? join('; ', $response) : '')); | |
| 1689 } | |
| 1690 } | |
| 1691 | |
| 1692 if (is_resource($msg_body)) { | |
| 1693 fclose($msg_body); | |
| 1694 } | |
| 1695 | |
| 1696 if ($disconnect) { | |
| 1697 $this->smtp->disconnect(); | |
| 1698 } | |
| 1699 | |
| 1700 $message->headers($headers, true); | |
| 1701 | |
| 1702 return $sent; | |
| 1703 } | |
| 1704 } | |
| 1705 | |
| 1706 | |
| 1707 /** | |
| 1708 * Lightweight plugin API class serving as a dummy if plugins are not enabled | |
| 1709 * | |
| 1710 * @package Framework | |
| 1711 * @subpackage Core | |
| 1712 */ | |
| 1713 class rcube_dummy_plugin_api | |
| 1714 { | |
| 1715 /** | |
| 1716 * Triggers a plugin hook. | |
| 1717 * @see rcube_plugin_api::exec_hook() | |
| 1718 */ | |
| 1719 public function exec_hook($hook, $args = array()) | |
| 1720 { | |
| 1721 return $args; | |
| 1722 } | |
| 1723 } |
