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