alecpl
2011-08-01 363514e30bcc31bf4055d39c9d90044b0e63ff3a
commit | author | age
197601 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
5  | program/include/rcmail.php                                            |
6  |                                                                       |
e019f2 7  | This file is part of the Roundcube Webmail client                     |
63e992 8  | Copyright (C) 2008-2011, The Roundcube Dev Team                       |
197601 9  | Licensed under the GNU GPL                                            |
T 10  |                                                                       |
11  | PURPOSE:                                                              |
12  |   Application class providing core functions and holding              |
13  |   instances of all 'global' objects like db- and imap-connections     |
14  +-----------------------------------------------------------------------+
15  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
16  +-----------------------------------------------------------------------+
17
638fb8 18  $Id$
197601 19
T 20 */
21
22
23 /**
e019f2 24  * Application class of Roundcube Webmail
197601 25  * implemented as singleton
T 26  *
27  * @package Core
28  */
29 class rcmail
30 {
5c461b 31   /**
A 32    * Main tasks.
33    *
34    * @var array
35    */
677e1f 36   static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy');
A 37
5c461b 38   /**
A 39    * Singleton instace of rcmail
40    *
41    * @var rcmail
42    */
197601 43   static private $instance;
677e1f 44
5c461b 45   /**
A 46    * Stores instance of rcube_config.
47    *
48    * @var rcube_config
49    */
197601 50   public $config;
5c461b 51
A 52   /**
53    * Stores rcube_user instance.
54    *
55    * @var rcube_user
56    */
197601 57   public $user;
5c461b 58
A 59   /**
60    * Instace of database class.
61    *
62    * @var rcube_mdb2
63    */
197601 64   public $db;
5c461b 65
A 66   /**
76d401 67    * Instace of Memcache class.
T 68    *
69    * @var rcube_mdb2
70    */
71   public $memcache;
72
73   /**
5c461b 74    * Instace of rcube_session class.
A 75    *
76    * @var rcube_session
77    */
929a50 78   public $session;
5c461b 79
A 80   /**
81    * Instance of rcube_smtp class.
82    *
83    * @var rcube_smtp
84    */
2c3d81 85   public $smtp;
5c461b 86
A 87   /**
88    * Instance of rcube_imap class.
89    *
90    * @var rcube_imap
91    */
197601 92   public $imap;
5c461b 93
A 94   /**
95    * Instance of rcube_template class.
96    *
97    * @var rcube_template
98    */
197601 99   public $output;
5c461b 100
A 101   /**
102    * Instance of rcube_plugin_api.
103    *
104    * @var rcube_plugin_api
105    */
cc97ea 106   public $plugins;
5c461b 107
A 108   /**
109    * Current task.
110    *
111    * @var string
112    */
9b94eb 113   public $task;
5c461b 114
A 115   /**
116    * Current action.
117    *
118    * @var string
119    */
197601 120   public $action = '';
T 121   public $comm_path = './';
677e1f 122
197601 123   private $texts;
0501b6 124   private $address_books = array();
5cf5ee 125   private $caches = array();
68d2d5 126   private $action_map = array();
580adc 127   private $shutdown_functions = array();
677e1f 128
A 129
197601 130   /**
T 131    * This implements the 'singleton' design pattern
132    *
5c461b 133    * @return rcmail The one and only instance
197601 134    */
T 135   static function get_instance()
136   {
137     if (!self::$instance) {
138       self::$instance = new rcmail();
139       self::$instance->startup();  // init AFTER object was linked with self::$instance
140     }
141
142     return self::$instance;
143   }
b62a0d 144
A 145
197601 146   /**
T 147    * Private constructor
148    */
149   private function __construct()
150   {
151     // load configuration
152     $this->config = new rcube_config();
b62a0d 153
197601 154     register_shutdown_function(array($this, 'shutdown'));
T 155   }
b62a0d 156
A 157
197601 158   /**
T 159    * Initial startup function
160    * to register session, create database and imap connections
161    *
162    * @todo Remove global vars $DB, $USER
163    */
164   private function startup()
165   {
b77d0d 166     // initialize syslog
A 167     if ($this->config->get('log_driver') == 'syslog') {
168       $syslog_id = $this->config->get('syslog_id', 'roundcube');
169       $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
170       openlog($syslog_id, LOG_ODELAY, $syslog_facility);
171     }
cc97ea 172
197601 173     // connect to database
T 174     $GLOBALS['DB'] = $this->get_dbh();
175
929a50 176     // start session
A 177     $this->session_init();
197601 178
T 179     // create user object
180     $this->set_user(new rcube_user($_SESSION['user_id']));
929a50 181
A 182     // configure session (after user config merge!)
183     $this->session_configure();
197601 184
9b94eb 185     // set task and action properties
A 186     $this->set_task(get_input_value('_task', RCUBE_INPUT_GPC));
187     $this->action = asciiwords(get_input_value('_action', RCUBE_INPUT_GPC));
188
197601 189     // reset some session parameters when changing task
677e1f 190     if ($this->task != 'utils') {
A 191       if ($this->session && $_SESSION['task'] != $this->task)
192         $this->session->remove('page');
193       // set current task to session
194       $_SESSION['task'] = $this->task;
195     }
197601 196
48bc52 197     // init output class
A 198     if (!empty($_REQUEST['_remote']))
929a50 199       $GLOBALS['OUTPUT'] = $this->json_init();
48bc52 200     else
A 201       $GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed']));
202
cc97ea 203     // create plugin API and load plugins
T 204     $this->plugins = rcube_plugin_api::get_instance();
48bc52 205
A 206     // init plugins
207     $this->plugins->init();
197601 208   }
b62a0d 209
A 210
197601 211   /**
T 212    * Setter for application task
213    *
214    * @param string Task to set
215    */
216   public function set_task($task)
217   {
1c932d 218     $task = asciiwords($task);
9b94eb 219
A 220     if ($this->user && $this->user->ID)
c3be8e 221       $task = !$task ? 'mail' : $task;
9b94eb 222     else
A 223       $task = 'login';
224
225     $this->task = $task;
1c932d 226     $this->comm_path = $this->url(array('task' => $this->task));
b62a0d 227
197601 228     if ($this->output)
1c932d 229       $this->output->set_env('task', $this->task);
197601 230   }
b62a0d 231
A 232
197601 233   /**
T 234    * Setter for system user object
235    *
5c461b 236    * @param rcube_user Current user instance
197601 237    */
T 238   public function set_user($user)
239   {
240     if (is_object($user)) {
241       $this->user = $user;
242       $GLOBALS['USER'] = $this->user;
b62a0d 243
197601 244       // overwrite config with user preferences
b545d3 245       $this->config->set_user_prefs((array)$this->user->get_prefs());
197601 246     }
b62a0d 247
c8ae24 248     $_SESSION['language'] = $this->user->language = $this->language_prop($this->config->get('language', $_SESSION['language']));
531abb 249
197601 250     // set localization
e80f50 251     setlocale(LC_ALL, $_SESSION['language'] . '.utf8', 'en_US.utf8');
14de18 252
b62a0d 253     // workaround for http://bugs.php.net/bug.php?id=18556
A 254     if (in_array($_SESSION['language'], array('tr_TR', 'ku', 'az_AZ')))
255       setlocale(LC_CTYPE, 'en_US' . '.utf8');
197601 256   }
b62a0d 257
A 258
197601 259   /**
T 260    * Check the given string and return a valid language code
261    *
262    * @param string Language code
263    * @return string Valid language code
264    */
265   private function language_prop($lang)
266   {
267     static $rcube_languages, $rcube_language_aliases;
b62a0d 268
c8ae24 269     // user HTTP_ACCEPT_LANGUAGE if no language is specified
T 270     if (empty($lang) || $lang == 'auto') {
271        $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
272        $lang = str_replace('-', '_', $accept_langs[0]);
273      }
b62a0d 274
197601 275     if (empty($rcube_languages)) {
T 276       @include(INSTALL_PATH . 'program/localization/index.inc');
277     }
b62a0d 278
197601 279     // check if we have an alias for that language
T 280     if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
281       $lang = $rcube_language_aliases[$lang];
282     }
283     // try the first two chars
c3ab75 284     else if (!isset($rcube_languages[$lang])) {
7e78b2 285       $short = substr($lang, 0, 2);
b62a0d 286
235086 287       // check if we have an alias for the short language code
T 288       if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
289         $lang = $rcube_language_aliases[$short];
290       }
c3ab75 291       // expand 'nn' to 'nn_NN'
T 292       else if (!isset($rcube_languages[$short])) {
235086 293         $lang = $short.'_'.strtoupper($short);
T 294       }
197601 295     }
T 296
1854c4 297     if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
197601 298       $lang = 'en_US';
T 299     }
300
301     return $lang;
302   }
b62a0d 303
A 304
197601 305   /**
T 306    * Get the current database connection
307    *
5c461b 308    * @return rcube_mdb2  Database connection object
197601 309    */
T 310   public function get_dbh()
311   {
312     if (!$this->db) {
313       $config_all = $this->config->all();
314
9e8e5f 315       $this->db = new rcube_mdb2($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
197601 316       $this->db->sqlite_initials = INSTALL_PATH . 'SQL/sqlite.initial.sql';
T 317       $this->db->set_debug((bool)$config_all['sql_debug']);
318     }
319
320     return $this->db;
321   }
76d401 322   
T 323   
324   /**
325    * Get global handle for memcache access
326    *
327    * @return object Memcache
328    */
329   public function get_memcache()
330   {
331     if (!isset($this->memcache)) {
332       // no memcache support in PHP
333       if (!class_exists('Memcache')) {
334         $this->memcache = false;
335         return false;
336       }
7ad8e2 337
76d401 338       $this->memcache = new Memcache;
T 339       $mc_available = 0;
340       foreach ($this->config->get('memcache_hosts', array()) as $host) {
341         list($host, $port) = explode(':', $host);
342         if (!$port) $port = 11211;
343         // add server and attempt to connect if not already done yet
344         if ($this->memcache->addServer($host, $port) && !$mc_available)
345           $mc_available += intval($this->memcache->connect($host, $port));
346       }
7ad8e2 347
76d401 348       if (!$mc_available)
T 349         $this->memcache = false;
350     }
7ad8e2 351
76d401 352     return $this->memcache;
T 353   }
b62a0d 354
A 355
197601 356   /**
5cf5ee 357    * Initialize and get cache object
A 358    *
c9f4e9 359    * @param string $name   Cache identifier
A 360    * @param string $type   Cache type ('db', 'apc' or 'memcache')
361    * @param int    $ttl    Expiration time for cache items in seconds
362    * @param bool   $packed Enables/disables data serialization
5cf5ee 363    *
A 364    * @return rcube_cache Cache object
365    */
c9f4e9 366   public function get_cache($name, $type='db', $ttl=0, $packed=true)
5cf5ee 367   {
A 368     if (!isset($this->caches[$name])) {
c9f4e9 369       $this->caches[$name] = new rcube_cache($type, $_SESSION['user_id'], $name, $ttl, $packed);
5cf5ee 370     }
A 371
372     return $this->caches[$name];
373   }
374
375
376   /**
ade8e1 377    * Return instance of the internal address book class
T 378    *
3704b7 379    * @param string  Address book identifier
ade8e1 380    * @param boolean True if the address book needs to be writeable
5c461b 381    * @return rcube_contacts Address book object
ade8e1 382    */
T 383   public function get_address_book($id, $writeable = false)
384   {
b896b1 385     $contacts    = null;
ade8e1 386     $ldap_config = (array)$this->config->get('ldap_public');
b896b1 387     $abook_type  = strtolower($this->config->get('address_book_type'));
cc97ea 388
f03d89 389     // 'sql' is the alias for '0' used by autocomplete
A 390     if ($id == 'sql')
391         $id = '0';
392
0501b6 393     // use existing instance
b896b1 394     if (isset($this->address_books[$id]) && is_a($this->address_books[$id], 'rcube_addressbook') && (!$writeable || !$this->address_books[$id]->readonly)) {
0501b6 395       $contacts = $this->address_books[$id];
T 396     }
cc97ea 397     else if ($id && $ldap_config[$id]) {
010274 398       $contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['imap_host']));
cc97ea 399     }
T 400     else if ($id === '0') {
401       $contacts = new rcube_contacts($this->db, $this->user->ID);
ade8e1 402     }
b896b1 403     else {
A 404       $plugin = $this->plugins->exec_hook('addressbook_get', array('id' => $id, 'writeable' => $writeable));
405
406       // plugin returned instance of a rcube_addressbook
407       if ($plugin['instance'] instanceof rcube_addressbook) {
408         $contacts = $plugin['instance'];
409       }
5ed119 410       else if (!$id) {
A 411         if ($abook_type == 'ldap') {
412           // Use the first writable LDAP address book.
413           foreach ($ldap_config as $id => $prop) {
414             if (!$writeable || $prop['writable']) {
415               $contacts = new rcube_ldap($prop, $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['imap_host']));
416               break;
417             }
b896b1 418           }
ade8e1 419         }
5ed119 420         else { // $id == 'sql'
A 421           $contacts = new rcube_contacts($this->db, $this->user->ID);
422         }
ade8e1 423       }
5ed119 424     }
A 425
426     if (!$contacts) {
427       raise_error(array(
428         'code' => 600, 'type' => 'php',
429         'file' => __FILE__, 'line' => __LINE__,
430         'message' => "Addressbook source ($id) not found!"),
431         true, true);
ade8e1 432     }
457373 433
A 434     // add to the 'books' array for shutdown function
0501b6 435     if (!isset($this->address_books[$id]))
T 436       $this->address_books[$id] = $contacts;
b62a0d 437
ade8e1 438     return $contacts;
T 439   }
3704b7 440
A 441
442   /**
443    * Return address books list
444    *
445    * @param boolean True if the address book needs to be writeable
446    * @return array  Address books array
447    */
448   public function get_address_sources($writeable = false)
449   {
450     $abook_type = strtolower($this->config->get('address_book_type'));
7fdb9d 451     $ldap_config = $this->config->get('ldap_public');
A 452     $autocomplete = (array) $this->config->get('autocomplete_addressbooks');
3704b7 453     $list = array();
A 454
455     // We are using the DB address book
456     if ($abook_type != 'ldap') {
0501b6 457       if (!isset($this->address_books['0']))
T 458         $this->address_books['0'] = new rcube_contacts($this->db, $this->user->ID);
3704b7 459       $list['0'] = array(
0501b6 460         'id' => '0',
a61bbb 461         'name' => rcube_label('personaladrbook'),
0501b6 462         'groups' => $this->address_books['0']->groups,
8c263e 463         'readonly' => $this->address_books['0']->readonly,
a61bbb 464         'autocomplete' => in_array('sql', $autocomplete)
3704b7 465       );
A 466     }
467
7fdb9d 468     if ($ldap_config) {
A 469       $ldap_config = (array) $ldap_config;
3704b7 470       foreach ($ldap_config as $id => $prop)
A 471         $list[$id] = array(
a61bbb 472           'id' => $id,
T 473           'name' => $prop['name'],
6039aa 474           'groups' => is_array($prop['groups']),
a61bbb 475           'readonly' => !$prop['writable'],
T 476           'autocomplete' => in_array('sql', $autocomplete)
3704b7 477         );
A 478     }
479
e6ce00 480     $plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list));
3704b7 481     $list = $plugin['sources'];
A 482
0501b6 483     foreach ($list as $idx => $item) {
T 484       // register source for shutdown function
485       if (!is_object($this->address_books[$item['id']]))
486         $this->address_books[$item['id']] = $item;
487       // remove from list if not writeable as requested
488       if ($writeable && $item['readonly'])
c0297f 489           unset($list[$idx]);
3704b7 490     }
8c263e 491
3704b7 492     return $list;
A 493   }
b62a0d 494
A 495
ade8e1 496   /**
197601 497    * Init output object for GUI and add common scripts.
T 498    * This will instantiate a rcmail_template object and set
499    * environment vars according to the current session and configuration
0ece58 500    *
T 501    * @param boolean True if this request is loaded in a (i)frame
5c461b 502    * @return rcube_template Reference to HTML output object
197601 503    */
T 504   public function load_gui($framed = false)
505   {
506     // init output page
0ece58 507     if (!($this->output instanceof rcube_template))
T 508       $this->output = new rcube_template($this->task, $framed);
197601 509
95d90f 510     // set keep-alive/check-recent interval
bf67d6 511     if ($this->session && ($keep_alive = $this->session->get_keep_alive())) {
929a50 512       $this->output->set_env('keep_alive', $keep_alive);
95d90f 513     }
197601 514
T 515     if ($framed) {
516       $this->comm_path .= '&_framed=1';
517       $this->output->set_env('framed', true);
518     }
519
520     $this->output->set_env('task', $this->task);
521     $this->output->set_env('action', $this->action);
522     $this->output->set_env('comm_path', $this->comm_path);
79c45f 523     $this->output->set_charset(RCMAIL_CHARSET);
197601 524
7f5a84 525     // add some basic labels to client
74d421 526     $this->output->add_label('loading', 'servererror');
b62a0d 527
197601 528     return $this->output;
T 529   }
b62a0d 530
A 531
197601 532   /**
T 533    * Create an output object for JSON responses
0ece58 534    *
5c461b 535    * @return rcube_json_output Reference to JSON output object
197601 536    */
929a50 537   public function json_init()
197601 538   {
0ece58 539     if (!($this->output instanceof rcube_json_output))
T 540       $this->output = new rcube_json_output($this->task);
b62a0d 541
197601 542     return $this->output;
2c3d81 543   }
A 544
545
546   /**
547    * Create SMTP object and connect to server
548    *
549    * @param boolean True if connection should be established
550    */
551   public function smtp_init($connect = false)
552   {
553     $this->smtp = new rcube_smtp();
b62a0d 554
2c3d81 555     if ($connect)
A 556       $this->smtp->connect();
197601 557   }
b62a0d 558
A 559
197601 560   /**
T 561    * Create global IMAP object and connect to server
562    *
563    * @param boolean True if connection should be established
564    * @todo Remove global $IMAP
565    */
1854c4 566   public function imap_init($connect = false)
197601 567   {
47d8d3 568     // already initialized
T 569     if (is_object($this->imap))
570       return;
b62a0d 571
5cf5ee 572     $this->imap = new rcube_imap();
197601 573     $this->imap->debug_level = $this->config->get('debug_level');
T 574     $this->imap->skip_deleted = $this->config->get('skip_deleted');
575
576     // enable caching of imap data
5cf5ee 577     $imap_cache = $this->config->get('imap_cache');
A 578     $messages_cache = $this->config->get('messages_cache');
579     // for backward compatybility
580     if ($imap_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
581         $imap_cache     = 'db';
582         $messages_cache = true;
197601 583     }
5cf5ee 584     if ($imap_cache)
A 585         $this->imap->set_caching($imap_cache);
586     if ($messages_cache)
587         $this->imap->set_messages_caching(true);
197601 588
T 589     // set pagesize from config
590     $this->imap->set_pagesize($this->config->get('pagesize', 50));
b62a0d 591
600981 592     // Setting root and delimiter before establishing the connection
b62a0d 593     // can save time detecting them using NAMESPACE and LIST
230f94 594     $options = array(
bdab2c 595       'auth_method' => $this->config->get('imap_auth_type', 'check'),
a1fe6b 596       'auth_cid'    => $this->config->get('imap_auth_cid'),
A 597       'auth_pw'     => $this->config->get('imap_auth_pw'),
7f1da4 598       'debug'       => (bool) $this->config->get('imap_debug', 0),
f07d23 599       'force_caps'  => (bool) $this->config->get('imap_force_caps'),
A 600       'timeout'     => (int) $this->config->get('imap_timeout', 0),
230f94 601     );
76db10 602
230f94 603     $this->imap->set_options($options);
b62a0d 604
197601 605     // set global object for backward compatibility
T 606     $GLOBALS['IMAP'] = $this->imap;
48bc52 607
A 608     $hook = $this->plugins->exec_hook('imap_init', array('fetch_headers' => $this->imap->fetch_add_headers));
609     if ($hook['fetch_headers'])
610       $this->imap->fetch_add_headers = $hook['fetch_headers'];
b62a0d 611
47d8d3 612     // support this parameter for backward compatibility but log warning
T 613     if ($connect) {
1854c4 614       $this->imap_connect();
13ffa2 615       raise_error(array(
A 616         'code' => 800, 'type' => 'imap',
617         'file' => __FILE__, 'line' => __LINE__,
618         'message' => "rcube::imap_init(true) is deprecated, use rcube::imap_connect() instead"),
619         true, false);
47d8d3 620     }
1854c4 621   }
T 622
623
624   /**
625    * Connect to IMAP server with stored session data
626    *
627    * @return bool True on success, false on error
628    */
629   public function imap_connect()
630   {
48bc52 631     if (!$this->imap)
A 632       $this->imap_init();
b62a0d 633
59c216 634     if ($_SESSION['imap_host'] && !$this->imap->conn->connected()) {
A 635       if (!$this->imap->connect($_SESSION['imap_host'], $_SESSION['username'], $this->decrypt($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl'])) {
1854c4 636         if ($this->output)
0f0c17 637           $this->output->show_message($this->imap->get_error_code() == -1 ? 'imaperror' : 'sessionerror', 'error');
1854c4 638       }
59c216 639       else {
A 640         $this->set_imap_prop();
641         return $this->imap->conn;
642       }
1854c4 643     }
T 644
59c216 645     return false;
929a50 646   }
A 647
648
649   /**
650    * Create session object and start the session.
651    */
652   public function session_init()
653   {
bf67d6 654     // session started (Installer?)
A 655     if (session_id())
656       return;
657
929a50 658     // set session domain
A 659     if ($domain = $this->config->get('session_domain')) {
660       ini_set('session.cookie_domain', $domain);
661     }
662     // set session garbage collecting time according to session_lifetime
63e992 663     $lifetime = $this->config->get('session_lifetime', 0) * 60;
929a50 664     if ($lifetime) {
A 665       ini_set('session.gc_maxlifetime', $lifetime * 2);
666     }
667
668     ini_set('session.cookie_secure', rcube_https_check());
669     ini_set('session.name', 'roundcube_sessid');
670     ini_set('session.use_cookies', 1);
b62a0d 671     ini_set('session.use_only_cookies', 1);
929a50 672     ini_set('session.serialize_handler', 'php');
A 673
674     // use database for storing session data
63e992 675     $this->session = new rcube_session($this->get_dbh(), $this->config);
929a50 676
A 677     $this->session->register_gc_handler('rcmail_temp_gc');
678     if ($this->config->get('enable_caching'))
679       $this->session->register_gc_handler('rcmail_cache_gc');
680
681     // start PHP session (if not in CLI mode)
682     if ($_SERVER['REMOTE_ADDR'])
683       session_start();
684
685     // set initial session vars
cf2da2 686     if (!$_SESSION['user_id'])
929a50 687       $_SESSION['temp'] = true;
A 688   }
689
690
691   /**
692    * Configure session object internals
693    */
694   public function session_configure()
695   {
bf67d6 696     if (!$this->session)
A 697       return;
698
929a50 699     $lifetime = $this->config->get('session_lifetime', 0) * 60;
A 700
701     // set keep-alive/check-recent interval
702     if ($keep_alive = $this->config->get('keep_alive')) {
703       // be sure that it's less than session lifetime
704       if ($lifetime)
705         $keep_alive = min($keep_alive, $lifetime - 30);
706       $keep_alive = max(60, $keep_alive);
707       $this->session->set_keep_alive($keep_alive);
708     }
cf2da2 709     
T 710     $this->session->set_secret($this->config->get('des_key') . $_SERVER['HTTP_USER_AGENT']);
711     $this->session->set_ip_check($this->config->get('ip_check'));
197601 712   }
T 713
714
715   /**
716    * Perfom login to the IMAP server and to the webmail service.
717    * This will also create a new user entry if auto_create_user is configured.
718    *
719    * @param string IMAP user name
720    * @param string IMAP password
721    * @param string IMAP host
722    * @return boolean True on success, False on failure
723    */
724   function login($username, $pass, $host=NULL)
725   {
726     $user = NULL;
727     $config = $this->config->all();
728
729     if (!$host)
730       $host = $config['default_host'];
731
732     // Validate that selected host is in the list of configured hosts
733     if (is_array($config['default_host'])) {
734       $allowed = false;
735       foreach ($config['default_host'] as $key => $host_allowed) {
736         if (!is_numeric($key))
737           $host_allowed = $key;
738         if ($host == $host_allowed) {
739           $allowed = true;
740           break;
741         }
742       }
743       if (!$allowed)
744         return false;
745       }
bb8721 746     else if (!empty($config['default_host']) && $host != rcube_parse_host($config['default_host']))
197601 747       return false;
T 748
749     // parse $host URL
750     $a_host = parse_url($host);
751     if ($a_host['host']) {
752       $host = $a_host['host'];
753       $imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
e99991 754       if (!empty($a_host['port']))
f86e8f 755         $imap_port = $a_host['port'];
904809 756       else if ($imap_ssl && $imap_ssl != 'tls' && (!$config['default_port'] || $config['default_port'] == 143))
f86e8f 757         $imap_port = 993;
197601 758     }
b62a0d 759
f86e8f 760     $imap_port = $imap_port ? $imap_port : $config['default_port'];
197601 761
b62a0d 762     /* Modify username with domain if required
197601 763        Inspired by Marco <P0L0_notspam_binware.org>
T 764     */
765     // Check if we need to add domain
c16fab 766     if (!empty($config['username_domain']) && strpos($username, '@') === false) {
197601 767       if (is_array($config['username_domain']) && isset($config['username_domain'][$host]))
a76cbd 768         $username .= '@'.rcube_parse_host($config['username_domain'][$host], $host);
197601 769       else if (is_string($config['username_domain']))
a76cbd 770         $username .= '@'.rcube_parse_host($config['username_domain'], $host);
197601 771     }
T 772
e17553 773     // Convert username to lowercase. If IMAP backend
A 774     // is case-insensitive we need to store always the same username (#1487113)
775     if ($config['login_lc']) {
776       $username = mb_strtolower($username);
777     }
778
942069 779     // try to resolve email address from virtuser table
e17553 780     if (strpos($username, '@') && ($virtuser = rcube_user::email2user($username))) {
A 781       $username = $virtuser;
782     }
197601 783
f1adbf 784     // Here we need IDNA ASCII
A 785     // Only rcube_contacts class is using domain names in Unicode
e8d5bd 786     $host = rcube_idn_to_ascii($host);
f1adbf 787     if (strpos($username, '@')) {
8f94b1 788       // lowercase domain name
A 789       list($local, $domain) = explode('@', $username);
790       $username = $local . '@' . mb_strtolower($domain);
e8d5bd 791       $username = rcube_idn_to_ascii($username);
f1adbf 792     }
A 793
197601 794     // user already registered -> overwrite username
T 795     if ($user = rcube_user::query($username, $host))
796       $username = $user->data['username'];
797
48bc52 798     if (!$this->imap)
A 799       $this->imap_init();
800
6d94ab 801     // try IMAP login
T 802     if (!($imap_login = $this->imap->connect($host, $username, $pass, $imap_port, $imap_ssl))) {
f1adbf 803       // try with lowercase
6d94ab 804       $username_lc = mb_strtolower($username);
e17553 805       if ($username_lc != $username) {
A 806         // try to find user record again -> overwrite username
807         if (!$user && ($user = rcube_user::query($username_lc, $host)))
808           $username_lc = $user->data['username'];
809
810         if ($imap_login = $this->imap->connect($host, $username_lc, $pass, $imap_port, $imap_ssl))
811           $username = $username_lc;
812       }
6d94ab 813     }
T 814
197601 815     // exit if IMAP login failed
6d94ab 816     if (!$imap_login)
197601 817       return false;
T 818
b5846e 819     $this->set_imap_prop();
A 820
197601 821     // user already registered -> update user's record
T 822     if (is_object($user)) {
d08333 823       // fix some old settings according to namespace prefix
A 824       $this->fix_namespace_settings($user);
825
b5846e 826       // create default folders on first login
A 827       if (!$user->data['last_login'] && $config['create_default_folders'])
828         $this->imap->create_default_folders();
d08333 829       // update last login timestamp
197601 830       $user->touch();
T 831     }
832     // create new system user
833     else if ($config['auto_create_user']) {
834       if ($created = rcube_user::create($username, $host)) {
835         $user = $created;
b5846e 836         // create default folders on first login
A 837         if ($config['create_default_folders'])
838           $this->imap->create_default_folders();
197601 839       }
f879f4 840       else {
T 841         raise_error(array(
10eedb 842           'code' => 600, 'type' => 'php',
6d94ab 843           'file' => __FILE__, 'line' => __LINE__,
f879f4 844           'message' => "Failed to create a user record. Maybe aborted by a plugin?"
10eedb 845           ), true, false);
f879f4 846       }
197601 847     }
T 848     else {
849       raise_error(array(
10eedb 850         'code' => 600, 'type' => 'php',
A 851         'file' => __FILE__, 'line' => __LINE__,
197601 852         'message' => "Acces denied for new user $username. 'auto_create_user' is disabled"
T 853         ), true, false);
854     }
855
856     // login succeeded
857     if (is_object($user) && $user->ID) {
858       $this->set_user($user);
88ca38 859       $this->session_configure();
197601 860
T 861       // set session vars
862       $_SESSION['user_id']   = $user->ID;
863       $_SESSION['username']  = $user->data['username'];
864       $_SESSION['imap_host'] = $host;
865       $_SESSION['imap_port'] = $imap_port;
866       $_SESSION['imap_ssl']  = $imap_ssl;
2471d3 867       $_SESSION['password']  = $this->encrypt($pass);
197601 868       $_SESSION['login_time'] = mktime();
cf2da2 869       
b62a0d 870       if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_')
c8ae24 871         $_SESSION['timezone'] = floatval($_REQUEST['_timezone']);
197601 872
T 873       // force reloading complete list of subscribed mailboxes
ccc059 874       $this->imap->clear_cache('mailboxes', true);
197601 875
T 876       return true;
877     }
878
879     return false;
880   }
881
882
883   /**
884    * Set root dir and last stored mailbox
885    * This must be done AFTER connecting to the server!
886    */
887   public function set_imap_prop()
888   {
889     $this->imap->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
890
891     if ($default_folders = $this->config->get('default_imap_folders')) {
892       $this->imap->set_default_mailboxes($default_folders);
893     }
448409 894     if (isset($_SESSION['mbox'])) {
197601 895       $this->imap->set_mailbox($_SESSION['mbox']);
T 896     }
897     if (isset($_SESSION['page'])) {
898       $this->imap->set_page($_SESSION['page']);
899     }
900   }
901
1854c4 902
T 903   /**
904    * Auto-select IMAP host based on the posted login information
905    *
906    * @return string Selected IMAP host
907    */
908   public function autoselect_host()
909   {
910     $default_host = $this->config->get('default_host');
257f88 911     $host = null;
b62a0d 912
257f88 913     if (is_array($default_host)) {
T 914       $post_host = get_input_value('_host', RCUBE_INPUT_POST);
b62a0d 915
257f88 916       // direct match in default_host array
T 917       if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) {
918         $host = $post_host;
919       }
b62a0d 920
257f88 921       // try to select host by mail domain
1854c4 922       list($user, $domain) = explode('@', get_input_value('_user', RCUBE_INPUT_POST));
T 923       if (!empty($domain)) {
257f88 924         foreach ($default_host as $imap_host => $mail_domains) {
1854c4 925           if (is_array($mail_domains) && in_array($domain, $mail_domains)) {
T 926             $host = $imap_host;
927             break;
928           }
929         }
930       }
931
932       // take the first entry if $host is still an array
257f88 933       if (empty($host)) {
T 934         $host = array_shift($default_host);
935       }
936     }
937     else if (empty($default_host)) {
938       $host = get_input_value('_host', RCUBE_INPUT_POST);
1854c4 939     }
eec34e 940     else
bb8721 941       $host = rcube_parse_host($default_host);
1854c4 942
T 943     return $host;
944   }
945
946
947   /**
948    * Get localized text in the desired language
949    *
950    * @param mixed Named parameters array or label name
951    * @return string Localized text
952    */
cc97ea 953   public function gettext($attrib, $domain=null)
1854c4 954   {
T 955     // load localization files if not done yet
956     if (empty($this->texts))
957       $this->load_language();
b62a0d 958
1854c4 959     // extract attributes
T 960     if (is_string($attrib))
961       $attrib = array('name' => $attrib);
962
963     $nr = is_numeric($attrib['nr']) ? $attrib['nr'] : 1;
9898fe 964     $name = $attrib['name'] ? $attrib['name'] : '';
ae39c4 965     
T 966     // attrib contain text values: use them from now
967     if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us']))
968         $this->texts[$name] = $setval;
1854c4 969
cc97ea 970     // check for text with domain
9898fe 971     if ($domain && ($text_item = $this->texts[$domain.'.'.$name]))
cc97ea 972       ;
1854c4 973     // text does not exist
9898fe 974     else if (!($text_item = $this->texts[$name])) {
A 975       return "[$name]";
1854c4 976     }
T 977
b62a0d 978     // make text item array
1854c4 979     $a_text_item = is_array($text_item) ? $text_item : array('single' => $text_item);
T 980
981     // decide which text to use
982     if ($nr == 1) {
983       $text = $a_text_item['single'];
984     }
985     else if ($nr > 0) {
986       $text = $a_text_item['multiple'];
987     }
988     else if ($nr == 0) {
989       if ($a_text_item['none'])
990         $text = $a_text_item['none'];
991       else if ($a_text_item['single'])
992         $text = $a_text_item['single'];
993       else if ($a_text_item['multiple'])
994         $text = $a_text_item['multiple'];
995     }
996
997     // default text is single
998     if ($text == '') {
999       $text = $a_text_item['single'];
1000     }
1001
1002     // replace vars in text
1003     if (is_array($attrib['vars'])) {
1004       foreach ($attrib['vars'] as $var_key => $var_value)
9898fe 1005         $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
1854c4 1006     }
T 1007
1008     // format output
1009     if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst'])
1010       return ucfirst($text);
1011     else if ($attrib['uppercase'])
2aa2b3 1012       return mb_strtoupper($text);
1854c4 1013     else if ($attrib['lowercase'])
2aa2b3 1014       return mb_strtolower($text);
1854c4 1015
T 1016     return $text;
1017   }
1018
1019
1020   /**
07b95d 1021    * Check if the given text lable exists
T 1022    *
1023    * @param string Label name
1024    * @return boolean True if text exists (either in the current language or in en_US)
1025    */
1026   public function text_exists($name, $domain=null)
1027   {
1028     // load localization files if not done yet
1029     if (empty($this->texts))
1030       $this->load_language();
1031
1032     // check for text with domain first
1033     return ($domain && isset($this->texts[$domain.'.'.$name])) || isset($this->texts[$name]);
1034   }
1035
1036   /**
1854c4 1037    * Load a localization package
T 1038    *
1039    * @param string Language ID
1040    */
cc97ea 1041   public function load_language($lang = null, $add = array())
1854c4 1042   {
c8ae24 1043     $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
b62a0d 1044
1854c4 1045     // load localized texts
T 1046     if (empty($this->texts) || $lang != $_SESSION['language']) {
1047       $this->texts = array();
1048
7c9850 1049       // handle empty lines after closing PHP tag in localization files
A 1050       ob_start();
1051
1854c4 1052       // get english labels (these should be complete)
T 1053       @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
1054       @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
1055
1056       if (is_array($labels))
9d9f8d 1057         $this->texts = $labels;
1854c4 1058       if (is_array($messages))
9d9f8d 1059         $this->texts = array_merge($this->texts, $messages);
1854c4 1060
T 1061       // include user language files
1062       if ($lang != 'en' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
1063         include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
1064         include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
1065
1066         if (is_array($labels))
1067           $this->texts = array_merge($this->texts, $labels);
1068         if (is_array($messages))
1069           $this->texts = array_merge($this->texts, $messages);
1070       }
b62a0d 1071
7c9850 1072       ob_end_clean();
A 1073
1854c4 1074       $_SESSION['language'] = $lang;
T 1075     }
cc97ea 1076
T 1077     // append additional texts (from plugin)
1078     if (is_array($add) && !empty($add))
1079       $this->texts += $add;
1854c4 1080   }
T 1081
1082
1083   /**
1084    * Read directory program/localization and return a list of available languages
1085    *
1086    * @return array List of available localizations
1087    */
1088   public function list_languages()
1089   {
1090     static $sa_languages = array();
1091
1092     if (!sizeof($sa_languages)) {
1093       @include(INSTALL_PATH . 'program/localization/index.inc');
1094
1095       if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
1096         while (($name = readdir($dh)) !== false) {
2aa2b3 1097           if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name))
1854c4 1098             continue;
T 1099
1100           if ($label = $rcube_languages[$name])
7d5178 1101             $sa_languages[$name] = $label;
1854c4 1102         }
T 1103         closedir($dh);
1104       }
1105     }
1106
1107     return $sa_languages;
1108   }
1109
1110
1111   /**
1112    * Destroy session data and remove cookie
1113    */
1114   public function kill_session()
1115   {
e6ce00 1116     $this->plugins->exec_hook('session_destroy');
b62a0d 1117
cf2da2 1118     $this->session->kill();
T 1119     $_SESSION = array('language' => $this->user->language, 'temp' => true);
1854c4 1120     $this->user->reset();
T 1121   }
1122
1123
1124   /**
1125    * Do server side actions on logout
1126    */
1127   public function logout_actions()
1128   {
1129     $config = $this->config->all();
b62a0d 1130
A 1131     // on logout action we're not connected to imap server
1854c4 1132     if (($config['logout_purge'] && !empty($config['trash_mbox'])) || $config['logout_expunge']) {
cf2da2 1133       if (!$this->session->check_auth())
1854c4 1134         return;
T 1135
47d8d3 1136       $this->imap_connect();
1854c4 1137     }
T 1138
1139     if ($config['logout_purge'] && !empty($config['trash_mbox'])) {
1140       $this->imap->clear_mailbox($config['trash_mbox']);
1141     }
1142
1143     if ($config['logout_expunge']) {
1144       $this->imap->expunge('INBOX');
1145     }
40a186 1146
A 1147     // Try to save unsaved user preferences
1148     if (!empty($_SESSION['preferences'])) {
1149       $this->user->save_prefs(unserialize($_SESSION['preferences']));
1150     }
1854c4 1151   }
T 1152
1153
1154   /**
1155    * Function to be executed in script shutdown
1156    * Registered with register_shutdown_function()
1157    */
197601 1158   public function shutdown()
T 1159   {
580adc 1160     foreach ($this->shutdown_functions as $function)
A 1161       call_user_func($function);
1162
2c3d81 1163     if (is_object($this->smtp))
A 1164       $this->smtp->disconnect();
1165
0501b6 1166     foreach ($this->address_books as $book) {
T 1167       if (is_a($book, 'rcube_addressbook'))
457373 1168         $book->close();
0501b6 1169     }
197601 1170
5cf5ee 1171     foreach ($this->caches as $cache) {
A 1172         if (is_object($cache))
1173             $cache->close();
1174     }
1175
dd07e7 1176     if (is_object($this->imap))
A 1177       $this->imap->close();
1178
197601 1179     // before closing the database connection, write session data
609c5e 1180     if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
4591de 1181       $this->session->cleanup();
75da0b 1182       session_write_close();
4591de 1183     }
2b35c5 1184
A 1185     // write performance stats to logs/console
1186     if ($this->config->get('devel_mode')) {
1187       if (function_exists('memory_get_usage'))
1188         $mem = show_bytes(memory_get_usage());
1189       if (function_exists('memory_get_peak_usage'))
1190         $mem .= '/'.show_bytes(memory_get_peak_usage());
1191
1192       $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : '');
bf67d6 1193       if (defined('RCMAIL_START'))
A 1194         rcube_print_time(RCMAIL_START, $log);
1195       else
1196         console($log);
2b35c5 1197     }
197601 1198   }
b62a0d 1199
A 1200
1854c4 1201   /**
580adc 1202    * Registers shutdown function to be executed on shutdown.
A 1203    * The functions will be executed before destroying any
1204    * objects like smtp, imap, session, etc.
1205    *
1206    * @param callback Function callback
1207    */
1208   public function add_shutdown_function($function)
1209   {
1210     $this->shutdown_functions[] = $function;
1211   }
1212
1213
1214   /**
57f0c8 1215    * Generate a unique token to be used in a form request
T 1216    *
1217    * @return string The request token
1218    */
549933 1219   public function get_request_token()
57f0c8 1220   {
ec045b 1221     $sess_id = $_COOKIE[ini_get('session.name')];
c9f2c4 1222     if (!$sess_id) $sess_id = session_id();
ef27a6 1223     $plugin = $this->plugins->exec_hook('request_token', array('value' => md5('RT' . $this->task . $this->config->get('des_key') . $sess_id)));
T 1224     return $plugin['value'];
57f0c8 1225   }
b62a0d 1226
A 1227
57f0c8 1228   /**
T 1229    * Check if the current request contains a valid token
1230    *
549933 1231    * @param int Request method
57f0c8 1232    * @return boolean True if request token is valid false if not
T 1233    */
549933 1234   public function check_request($mode = RCUBE_INPUT_POST)
57f0c8 1235   {
T 1236     $token = get_input_value('_token', $mode);
ec045b 1237     $sess_id = $_COOKIE[ini_get('session.name')];
T 1238     return !empty($sess_id) && $token == $this->get_request_token();
57f0c8 1239   }
b62a0d 1240
A 1241
57f0c8 1242   /**
1854c4 1243    * Create unique authorization hash
T 1244    *
1245    * @param string Session ID
1246    * @param int Timestamp
1247    * @return string The generated auth hash
1248    */
1249   private function get_auth_hash($sess_id, $ts)
1250   {
1251     $auth_string = sprintf('rcmail*sess%sR%s*Chk:%s;%s',
1252       $sess_id,
1253       $ts,
1254       $this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] : '***.***.***.***',
1255       $_SERVER['HTTP_USER_AGENT']);
1256
1257     if (function_exists('sha1'))
1258       return sha1($auth_string);
1259     else
1260       return md5($auth_string);
1261   }
1262
2471d3 1263
1854c4 1264   /**
2471d3 1265    * Encrypt using 3DES
1854c4 1266    *
2471d3 1267    * @param string $clear clear text input
A 1268    * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
1269    * @param boolean $base64 whether or not to base64_encode() the result before returning
1270    *
1271    * @return string encrypted text
1854c4 1272    */
2471d3 1273   public function encrypt($clear, $key = 'des_key', $base64 = true)
1854c4 1274   {
713a66 1275     if (!$clear)
A 1276       return '';
2471d3 1277     /*-
A 1278      * Add a single canary byte to the end of the clear text, which
1279      * will help find out how much of padding will need to be removed
1280      * upon decryption; see http://php.net/mcrypt_generic#68082
1281      */
1282     $clear = pack("a*H2", $clear, "80");
b62a0d 1283
2471d3 1284     if (function_exists('mcrypt_module_open') &&
A 1285         ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
1286     {
564741 1287       $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
2471d3 1288       mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
A 1289       $cipher = $iv . mcrypt_generic($td, $clear);
1854c4 1290       mcrypt_generic_deinit($td);
T 1291       mcrypt_module_close($td);
1292     }
44155c 1293     else {
926948 1294       @include_once 'des.inc';
44155c 1295
A 1296       if (function_exists('des')) {
1297         $des_iv_size = 8;
564741 1298         $iv = $this->create_iv($des_iv_size);
44155c 1299         $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
A 1300       }
1301       else {
1302         raise_error(array(
1303           'code' => 500, 'type' => 'php',
1304           'file' => __FILE__, 'line' => __LINE__,
1305           'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
1306         ), true, true);
1307       }
1854c4 1308     }
44155c 1309
2471d3 1310     return $base64 ? base64_encode($cipher) : $cipher;
1854c4 1311   }
T 1312
1313   /**
2471d3 1314    * Decrypt 3DES-encrypted string
1854c4 1315    *
2471d3 1316    * @param string $cipher encrypted text
A 1317    * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
1318    * @param boolean $base64 whether or not input is base64-encoded
1319    *
1320    * @return string decrypted text
1854c4 1321    */
2471d3 1322   public function decrypt($cipher, $key = 'des_key', $base64 = true)
1854c4 1323   {
713a66 1324     if (!$cipher)
A 1325       return '';
b62a0d 1326
2471d3 1327     $cipher = $base64 ? base64_decode($cipher) : $cipher;
A 1328
1329     if (function_exists('mcrypt_module_open') &&
1330         ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
1331     {
9016a8 1332       $iv_size = mcrypt_enc_get_iv_size($td);
A 1333       $iv = substr($cipher, 0, $iv_size);
1334
1335       // session corruption? (#1485970)
1336       if (strlen($iv) < $iv_size)
1337         return '';
1338
1339       $cipher = substr($cipher, $iv_size);
2471d3 1340       mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
A 1341       $clear = mdecrypt_generic($td, $cipher);
1854c4 1342       mcrypt_generic_deinit($td);
T 1343       mcrypt_module_close($td);
1344     }
44155c 1345     else {
926948 1346       @include_once 'des.inc';
b62a0d 1347
44155c 1348       if (function_exists('des')) {
A 1349         $des_iv_size = 8;
1350         $iv = substr($cipher, 0, $des_iv_size);
1351         $cipher = substr($cipher, $des_iv_size);
1352         $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
1353       }
1354       else {
1355         raise_error(array(
1356           'code' => 500, 'type' => 'php',
1357           'file' => __FILE__, 'line' => __LINE__,
1358           'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
1359         ), true, true);
1360       }
1854c4 1361     }
44155c 1362
2471d3 1363     /*-
A 1364      * Trim PHP's padding and the canary byte; see note in
1365      * rcmail::encrypt() and http://php.net/mcrypt_generic#68082
1366      */
1367     $clear = substr(rtrim($clear, "\0"), 0, -1);
b62a0d 1368
2471d3 1369     return $clear;
1854c4 1370   }
c719f3 1371
T 1372   /**
564741 1373    * Generates encryption initialization vector (IV)
A 1374    *
1375    * @param int Vector size
1376    * @return string Vector string
1377    */
1378   private function create_iv($size)
1379   {
1380     // mcrypt_create_iv() can be slow when system lacks entrophy
1381     // we'll generate IV vector manually
1382     $iv = '';
1383     for ($i = 0; $i < $size; $i++)
1384         $iv .= chr(mt_rand(0, 255));
1385     return $iv;
1386   }
1387
1388   /**
e019f2 1389    * Build a valid URL to this instance of Roundcube
c719f3 1390    *
T 1391    * @param mixed Either a string with the action or url parameters as key-value pairs
1392    * @return string Valid application URL
1393    */
1394   public function url($p)
1395   {
1396     if (!is_array($p))
fde466 1397       $p = array('_action' => @func_get_arg(0));
b62a0d 1398
1c932d 1399     $task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task);
cc97ea 1400     $p['_task'] = $task;
1038a6 1401     unset($p['task']);
A 1402
cf1777 1403     $url = './';
T 1404     $delm = '?';
77406b 1405     foreach (array_reverse($p) as $key => $val) {
A 1406       if ($val !== '') {
cc97ea 1407         $par = $key[0] == '_' ? $key : '_'.$key;
cf1777 1408         $url .= $delm.urlencode($par).'='.urlencode($val);
T 1409         $delm = '&';
1410       }
1411     }
c719f3 1412     return $url;
T 1413   }
cefd1d 1414
T 1415
1416   /**
0501b6 1417    * Use imagemagick or GD lib to read image properties
T 1418    *
1419    * @param string Absolute file path
1420    * @return mixed Hash array with image props like type, width, height or False on error
1421    */
1422   public static function imageprops($filepath)
1423   {
1424     $rcmail = rcmail::get_instance();
1425     if ($cmd = $rcmail->config->get('im_identify_path', false)) {
1426       list(, $type, $size) = explode(' ', strtolower(rcmail::exec($cmd. ' 2>/dev/null {in}', array('in' => $filepath))));
1427       if ($size)
1428         list($width, $height) = explode('x', $size);
1429     }
1430     else if (function_exists('getimagesize')) {
1431       $imsize = @getimagesize($filepath);
1432       $width = $imsize[0];
1433       $height = $imsize[1];
1434       $type = preg_replace('!image/!', '', $imsize['mime']);
1435     }
1436
1437     return $type ? array('type' => $type, 'width' => $width, 'height' => $height) : false;
1438   }
1439
1440
1441   /**
1442    * Convert an image to a given size and type using imagemagick (ensures input is an image)
1443    *
1444    * @param $p['in']  Input filename (mandatory)
1445    * @param $p['out'] Output filename (mandatory)
1446    * @param $p['size']  Width x height of resulting image, e.g. "160x60"
1447    * @param $p['type']  Output file type, e.g. "jpg"
1448    * @param $p['-opts'] Custom command line options to ImageMagick convert
1449    * @return Success of convert as true/false
1450    */
1451   public static function imageconvert($p)
1452   {
1453     $result = false;
1454     $rcmail = rcmail::get_instance();
1455     $convert  = $rcmail->config->get('im_convert_path', false);
1456     $identify = $rcmail->config->get('im_identify_path', false);
4351f7 1457
0501b6 1458     // imagemagick is required for this
T 1459     if (!$convert)
1460         return false;
1461
1462     if (!(($imagetype = @exif_imagetype($p['in'])) && ($type = image_type_to_extension($imagetype, false))))
1463       list(, $type) = explode(' ', strtolower(rcmail::exec($identify . ' 2>/dev/null {in}', $p))); # for things like eps
1464
1465     $type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps"));
1466     $p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75);
1467     $p['-opts'] = array('-resize' => $p['size'].'>') + (array)$p['-opts'];
1468
1469     if (in_array($type, explode(',', $p['types']))) # Valid type?
1470       $result = rcmail::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {in} {type}:{out}', $p) === "";
1471
1472     return $result;
1473   }
1474
1475
1476   /**
1477    * Construct shell command, execute it and return output as string.
1478    * Keywords {keyword} are replaced with arguments
1479    *
1480    * @param $cmd Format string with {keywords} to be replaced
1481    * @param $values (zero, one or more arrays can be passed)
1482    * @return output of command. shell errors not detectable
1483    */
1484   public static function exec(/* $cmd, $values1 = array(), ... */)
1485   {
1486     $args = func_get_args();
1487     $cmd = array_shift($args);
1488     $values = $replacements = array();
1489
1490     // merge values into one array
1491     foreach ($args as $arg)
1492       $values += (array)$arg;
1493
1494     preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
1495     foreach ($matches as $tags) {
1496       list(, $tag, $option, $key) = $tags;
1497       $parts = array();
1498
1499       if ($option) {
1500         foreach ((array)$values["-$key"] as $key => $value) {
1501           if ($value === true || $value === false || $value === null)
1502             $parts[] = $value ? $key : "";
1503           else foreach ((array)$value as $val)
1504             $parts[] = "$key " . escapeshellarg($val);
1505         }
1506       }
1507       else {
1508         foreach ((array)$values[$key] as $value)
1509           $parts[] = escapeshellarg($value);
1510       }
1511
1512       $replacements[$tag] = join(" ", $parts);
1513     }
1514
1515     // use strtr behaviour of going through source string once
1516     $cmd = strtr($cmd, $replacements);
1517     
1518     return (string)shell_exec($cmd);
1519   }
1520
1521
1522   /**
cefd1d 1523    * Helper method to set a cookie with the current path and host settings
T 1524    *
1525    * @param string Cookie name
1526    * @param string Cookie value
1527    * @param string Expiration time
1528    */
1529   public static function setcookie($name, $value, $exp = 0)
1530   {
317a7d 1531     if (headers_sent())
A 1532       return;
1533
cefd1d 1534     $cookie = session_get_cookie_params();
2273d4 1535
cefd1d 1536     setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'],
c96c5a 1537       rcube_https_check(), true);
cefd1d 1538   }
68d2d5 1539
A 1540   /**
1541    * Registers action aliases for current task
1542    *
1543    * @param array $map Alias-to-filename hash array
1544    */
1545   public function register_action_map($map)
1546   {
1547     if (is_array($map)) {
1548       foreach ($map as $idx => $val) {
1549         $this->action_map[$idx] = $val;
1550       }
1551     }
1552   }
1553   
1554   /**
1555    * Returns current action filename
1556    *
1557    * @param array $map Alias-to-filename hash array
1558    */
1559   public function get_action_file()
1560   {
1561     if (!empty($this->action_map[$this->action])) {
1562       return $this->action_map[$this->action];
1563     }
1564
1565     return strtr($this->action, '-', '_') . '.inc';
1566   }
1567
d08333 1568   /**
A 1569    * Fixes some user preferences according to namespace handling change.
1570    * Old Roundcube versions were using folder names with removed namespace prefix.
1571    * Now we need to add the prefix on servers where personal namespace has prefix.
1572    *
1573    * @param rcube_user $user User object
1574    */
1575   private function fix_namespace_settings($user)
1576   {
1577     $prefix     = $this->imap->get_namespace('prefix');
1578     $prefix_len = strlen($prefix);
1579
1580     if (!$prefix_len)
1581       return;
1582
1583     $prefs = $user->get_prefs();
1584     if (empty($prefs) || $prefs['namespace_fixed'])
1585       return;
1586
1587     // Build namespace prefix regexp
1588     $ns     = $this->imap->get_namespace();
1589     $regexp = array();
1590
1591     foreach ($ns as $entry) {
1592       if (!empty($entry)) {
1593         foreach ($entry as $item) {
1594           if (strlen($item[0])) {
1595             $regexp[] = preg_quote($item[0], '/');
1596           }
1597         }
1598       }
1599     }
1600     $regexp = '/^('. implode('|', $regexp).')/';
1601
1602     // Fix preferences
1603     $opts = array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox', 'archive_mbox');
1604     foreach ($opts as $opt) {
1605       if ($value = $prefs[$opt]) {
1606         if ($value != 'INBOX' && !preg_match($regexp, $value)) {
1607           $prefs[$opt] = $prefix.$value;
1608         }
1609       }
1610     }
1611
1612     if (!empty($prefs['default_imap_folders'])) {
1613       foreach ($prefs['default_imap_folders'] as $idx => $name) {
1614         if ($name != 'INBOX' && !preg_match($regexp, $name)) {
1615           $prefs['default_imap_folders'][$idx] = $prefix.$name;
1616         }
1617       }
1618     }
1619
1620     if (!empty($prefs['search_mods'])) {
1621       $folders = array();
1622       foreach ($prefs['search_mods'] as $idx => $value) {
1623         if ($idx != 'INBOX' && $idx != '*' && !preg_match($regexp, $idx)) {
1624           $idx = $prefix.$idx;
1625         }
1626         $folders[$idx] = $value;
1627       }
1628       $prefs['search_mods'] = $folders;
1629     }
1630
1631     if (!empty($prefs['message_threading'])) {
1632       $folders = array();
1633       foreach ($prefs['message_threading'] as $idx => $value) {
1634         if ($idx != 'INBOX' && !preg_match($regexp, $idx)) {
1635           $idx = $prefix.$idx;
1636         }
1637         $folders[$prefix.$idx] = $value;
1638       }
1639       $prefs['message_threading'] = $folders;
1640     }
1641
1642     if (!empty($prefs['collapsed_folders'])) {
1643       $folders     = explode('&&', $prefs['collapsed_folders']);
1644       $count       = count($folders);
1645       $folders_str = '';
1646
1647       if ($count) {
1648           $folders[0]        = substr($folders[0], 1);
1649           $folders[$count-1] = substr($folders[$count-1], 0, -1);
1650       }
1651
1652       foreach ($folders as $value) {
1653         if ($value != 'INBOX' && !preg_match($regexp, $value)) {
1654           $value = $prefix.$value;
1655         }
1656         $folders_str .= '&'.$value.'&';
1657       }
1658       $prefs['collapsed_folders'] = $folders_str;
1659     }
1660
1661     $prefs['namespace_fixed'] = true;
1662
1663     // save updated preferences and reset imap settings (default folders)
1664     $user->save_prefs($prefs);
1665     $this->set_imap_prop();
1666   }
1667
197601 1668 }