alecpl
2010-09-25 e019f2d0f2dc2fbfa345ab5d7ae85e67bfdd76b8
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                     |
A 8  | Copyright (C) 2008-2010, Roundcube Dev. - Switzerland                 |
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 {
677e1f 31   static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy');
A 32
197601 33   static private $instance;
677e1f 34
197601 35   public $config;
T 36   public $user;
37   public $db;
929a50 38   public $session;
2c3d81 39   public $smtp;
197601 40   public $imap;
T 41   public $output;
cc97ea 42   public $plugins;
9b94eb 43   public $task;
197601 44   public $action = '';
T 45   public $comm_path = './';
677e1f 46
197601 47   private $texts;
457373 48   private $books = array();
677e1f 49
A 50
197601 51   /**
T 52    * This implements the 'singleton' design pattern
53    *
115263 54    * @return object rcmail The one and only instance
197601 55    */
T 56   static function get_instance()
57   {
58     if (!self::$instance) {
59       self::$instance = new rcmail();
60       self::$instance->startup();  // init AFTER object was linked with self::$instance
61     }
62
63     return self::$instance;
64   }
b62a0d 65
A 66
197601 67   /**
T 68    * Private constructor
69    */
70   private function __construct()
71   {
72     // load configuration
73     $this->config = new rcube_config();
b62a0d 74
197601 75     register_shutdown_function(array($this, 'shutdown'));
T 76   }
b62a0d 77
A 78
197601 79   /**
T 80    * Initial startup function
81    * to register session, create database and imap connections
82    *
83    * @todo Remove global vars $DB, $USER
84    */
85   private function startup()
86   {
b77d0d 87     // initialize syslog
A 88     if ($this->config->get('log_driver') == 'syslog') {
89       $syslog_id = $this->config->get('syslog_id', 'roundcube');
90       $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
91       openlog($syslog_id, LOG_ODELAY, $syslog_facility);
92     }
cc97ea 93
197601 94     // connect to database
T 95     $GLOBALS['DB'] = $this->get_dbh();
96
929a50 97     // start session
A 98     $this->session_init();
197601 99
T 100     // create user object
101     $this->set_user(new rcube_user($_SESSION['user_id']));
929a50 102
A 103     // configure session (after user config merge!)
104     $this->session_configure();
197601 105
9b94eb 106     // set task and action properties
A 107     $this->set_task(get_input_value('_task', RCUBE_INPUT_GPC));
108     $this->action = asciiwords(get_input_value('_action', RCUBE_INPUT_GPC));
109
197601 110     // reset some session parameters when changing task
677e1f 111     if ($this->task != 'utils') {
A 112       if ($this->session && $_SESSION['task'] != $this->task)
113         $this->session->remove('page');
114       // set current task to session
115       $_SESSION['task'] = $this->task;
116     }
197601 117
48bc52 118     // init output class
A 119     if (!empty($_REQUEST['_remote']))
929a50 120       $GLOBALS['OUTPUT'] = $this->json_init();
48bc52 121     else
A 122       $GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed']));
123
cc97ea 124     // create plugin API and load plugins
T 125     $this->plugins = rcube_plugin_api::get_instance();
48bc52 126
A 127     // init plugins
128     $this->plugins->init();
197601 129   }
b62a0d 130
A 131
197601 132   /**
T 133    * Setter for application task
134    *
135    * @param string Task to set
136    */
137   public function set_task($task)
138   {
1c932d 139     $task = asciiwords($task);
9b94eb 140
A 141     if ($this->user && $this->user->ID)
142       $task = !$task || $task == 'login' ? 'mail' : $task;
143     else
144       $task = 'login';
145
146     $this->task = $task;
1c932d 147     $this->comm_path = $this->url(array('task' => $this->task));
b62a0d 148
197601 149     if ($this->output)
1c932d 150       $this->output->set_env('task', $this->task);
197601 151   }
b62a0d 152
A 153
197601 154   /**
T 155    * Setter for system user object
156    *
157    * @param object rcube_user Current user instance
158    */
159   public function set_user($user)
160   {
161     if (is_object($user)) {
162       $this->user = $user;
163       $GLOBALS['USER'] = $this->user;
b62a0d 164
197601 165       // overwrite config with user preferences
b545d3 166       $this->config->set_user_prefs((array)$this->user->get_prefs());
197601 167     }
b62a0d 168
c8ae24 169     $_SESSION['language'] = $this->user->language = $this->language_prop($this->config->get('language', $_SESSION['language']));
531abb 170
197601 171     // set localization
e80f50 172     setlocale(LC_ALL, $_SESSION['language'] . '.utf8', 'en_US.utf8');
14de18 173
b62a0d 174     // workaround for http://bugs.php.net/bug.php?id=18556
A 175     if (in_array($_SESSION['language'], array('tr_TR', 'ku', 'az_AZ')))
176       setlocale(LC_CTYPE, 'en_US' . '.utf8');
197601 177   }
b62a0d 178
A 179
197601 180   /**
T 181    * Check the given string and return a valid language code
182    *
183    * @param string Language code
184    * @return string Valid language code
185    */
186   private function language_prop($lang)
187   {
188     static $rcube_languages, $rcube_language_aliases;
b62a0d 189
c8ae24 190     // user HTTP_ACCEPT_LANGUAGE if no language is specified
T 191     if (empty($lang) || $lang == 'auto') {
192        $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
193        $lang = str_replace('-', '_', $accept_langs[0]);
194      }
b62a0d 195
197601 196     if (empty($rcube_languages)) {
T 197       @include(INSTALL_PATH . 'program/localization/index.inc');
198     }
b62a0d 199
197601 200     // check if we have an alias for that language
T 201     if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
202       $lang = $rcube_language_aliases[$lang];
203     }
204     // try the first two chars
c3ab75 205     else if (!isset($rcube_languages[$lang])) {
7e78b2 206       $short = substr($lang, 0, 2);
b62a0d 207
235086 208       // check if we have an alias for the short language code
T 209       if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
210         $lang = $rcube_language_aliases[$short];
211       }
c3ab75 212       // expand 'nn' to 'nn_NN'
T 213       else if (!isset($rcube_languages[$short])) {
235086 214         $lang = $short.'_'.strtoupper($short);
T 215       }
197601 216     }
T 217
1854c4 218     if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
197601 219       $lang = 'en_US';
T 220     }
221
222     return $lang;
223   }
b62a0d 224
A 225
197601 226   /**
T 227    * Get the current database connection
228    *
e93e54 229    * @return object rcube_mdb2  Database connection object
197601 230    */
T 231   public function get_dbh()
232   {
233     if (!$this->db) {
234       $config_all = $this->config->all();
235
9e8e5f 236       $this->db = new rcube_mdb2($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
197601 237       $this->db->sqlite_initials = INSTALL_PATH . 'SQL/sqlite.initial.sql';
T 238       $this->db->set_debug((bool)$config_all['sql_debug']);
239     }
240
241     return $this->db;
242   }
b62a0d 243
A 244
197601 245   /**
ade8e1 246    * Return instance of the internal address book class
T 247    *
3704b7 248    * @param string  Address book identifier
ade8e1 249    * @param boolean True if the address book needs to be writeable
T 250    * @return object rcube_contacts Address book object
251    */
252   public function get_address_book($id, $writeable = false)
253   {
254     $contacts = null;
255     $ldap_config = (array)$this->config->get('ldap_public');
256     $abook_type = strtolower($this->config->get('address_book_type'));
cc97ea 257
e6ce00 258     $plugin = $this->plugins->exec_hook('addressbook_get', array('id' => $id, 'writeable' => $writeable));
b62a0d 259
cc97ea 260     // plugin returned instance of a rcube_addressbook
T 261     if ($plugin['instance'] instanceof rcube_addressbook) {
262       $contacts = $plugin['instance'];
263     }
264     else if ($id && $ldap_config[$id]) {
010274 265       $contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['imap_host']));
cc97ea 266     }
T 267     else if ($id === '0') {
268       $contacts = new rcube_contacts($this->db, $this->user->ID);
ade8e1 269     }
T 270     else if ($abook_type == 'ldap') {
271       // Use the first writable LDAP address book.
272       foreach ($ldap_config as $id => $prop) {
273         if (!$writeable || $prop['writable']) {
010274 274           $contacts = new rcube_ldap($prop, $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['imap_host']));
ade8e1 275           break;
T 276         }
277       }
278     }
3704b7 279     else { // $id == 'sql'
ade8e1 280       $contacts = new rcube_contacts($this->db, $this->user->ID);
T 281     }
457373 282
A 283     // add to the 'books' array for shutdown function
284     if (!in_array($contacts, $this->books))
285       $this->books[] = $contacts;
b62a0d 286
ade8e1 287     return $contacts;
T 288   }
3704b7 289
A 290
291   /**
292    * Return address books list
293    *
294    * @param boolean True if the address book needs to be writeable
295    * @return array  Address books array
296    */
297   public function get_address_sources($writeable = false)
298   {
299     $abook_type = strtolower($this->config->get('address_book_type'));
7fdb9d 300     $ldap_config = $this->config->get('ldap_public');
A 301     $autocomplete = (array) $this->config->get('autocomplete_addressbooks');
3704b7 302     $list = array();
A 303
304     // We are using the DB address book
305     if ($abook_type != 'ldap') {
c0297f 306       $contacts = new rcube_contacts($this->db, null);
3704b7 307       $list['0'] = array(
A 308         'id' => 0,
a61bbb 309         'name' => rcube_label('personaladrbook'),
c0297f 310         'groups' => $contacts->groups,
3704b7 311         'readonly' => false,
a61bbb 312         'autocomplete' => in_array('sql', $autocomplete)
3704b7 313       );
A 314     }
315
7fdb9d 316     if ($ldap_config) {
A 317       $ldap_config = (array) $ldap_config;
3704b7 318       foreach ($ldap_config as $id => $prop)
A 319         $list[$id] = array(
a61bbb 320           'id' => $id,
T 321           'name' => $prop['name'],
322           'groups' => false,
323           'readonly' => !$prop['writable'],
324           'autocomplete' => in_array('sql', $autocomplete)
3704b7 325         );
A 326     }
327
e6ce00 328     $plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list));
3704b7 329     $list = $plugin['sources'];
A 330
331     if ($writeable && !empty($list)) {
332       foreach ($list as $idx => $item) {
333         if ($item['readonly']) {
c0297f 334           unset($list[$idx]);
3704b7 335         }
A 336       }
337     }
7fdb9d 338
3704b7 339     return $list;
A 340   }
b62a0d 341
A 342
ade8e1 343   /**
197601 344    * Init output object for GUI and add common scripts.
T 345    * This will instantiate a rcmail_template object and set
346    * environment vars according to the current session and configuration
0ece58 347    *
T 348    * @param boolean True if this request is loaded in a (i)frame
349    * @return object rcube_template Reference to HTML output object
197601 350    */
T 351   public function load_gui($framed = false)
352   {
353     // init output page
0ece58 354     if (!($this->output instanceof rcube_template))
T 355       $this->output = new rcube_template($this->task, $framed);
197601 356
95d90f 357     // set keep-alive/check-recent interval
bf67d6 358     if ($this->session && ($keep_alive = $this->session->get_keep_alive())) {
929a50 359       $this->output->set_env('keep_alive', $keep_alive);
95d90f 360     }
197601 361
T 362     if ($framed) {
363       $this->comm_path .= '&_framed=1';
364       $this->output->set_env('framed', true);
365     }
366
367     $this->output->set_env('task', $this->task);
368     $this->output->set_env('action', $this->action);
369     $this->output->set_env('comm_path', $this->comm_path);
79c45f 370     $this->output->set_charset(RCMAIL_CHARSET);
197601 371
T 372     // add some basic label to client
74d421 373     $this->output->add_label('loading', 'servererror');
b62a0d 374
197601 375     return $this->output;
T 376   }
b62a0d 377
A 378
197601 379   /**
T 380    * Create an output object for JSON responses
0ece58 381    *
T 382    * @return object rcube_json_output Reference to JSON output object
197601 383    */
929a50 384   public function json_init()
197601 385   {
0ece58 386     if (!($this->output instanceof rcube_json_output))
T 387       $this->output = new rcube_json_output($this->task);
b62a0d 388
197601 389     return $this->output;
2c3d81 390   }
A 391
392
393   /**
394    * Create SMTP object and connect to server
395    *
396    * @param boolean True if connection should be established
397    */
398   public function smtp_init($connect = false)
399   {
400     $this->smtp = new rcube_smtp();
b62a0d 401
2c3d81 402     if ($connect)
A 403       $this->smtp->connect();
197601 404   }
b62a0d 405
A 406
197601 407   /**
T 408    * Create global IMAP object and connect to server
409    *
410    * @param boolean True if connection should be established
411    * @todo Remove global $IMAP
412    */
1854c4 413   public function imap_init($connect = false)
197601 414   {
47d8d3 415     // already initialized
T 416     if (is_object($this->imap))
417       return;
b62a0d 418
197601 419     $this->imap = new rcube_imap($this->db);
T 420     $this->imap->debug_level = $this->config->get('debug_level');
421     $this->imap->skip_deleted = $this->config->get('skip_deleted');
422
423     // enable caching of imap data
424     if ($this->config->get('enable_caching')) {
425       $this->imap->set_caching(true);
426     }
427
428     // set pagesize from config
429     $this->imap->set_pagesize($this->config->get('pagesize', 50));
b62a0d 430
600981 431     // Setting root and delimiter before establishing the connection
b62a0d 432     // can save time detecting them using NAMESPACE and LIST
230f94 433     $options = array(
bdab2c 434       'auth_method' => $this->config->get('imap_auth_type', 'check'),
f07d23 435       'delimiter'   => isset($_SESSION['imap_delimiter']) ? $_SESSION['imap_delimiter'] : $this->config->get('imap_delimiter'),
A 436       'rootdir'     => isset($_SESSION['imap_root']) ? $_SESSION['imap_root'] : $this->config->get('imap_root'),
437       'debug_mode'  => (bool) $this->config->get('imap_debug', 0),
438       'force_caps'  => (bool) $this->config->get('imap_force_caps'),
439       'timeout'     => (int) $this->config->get('imap_timeout', 0),
230f94 440     );
76db10 441
230f94 442     $this->imap->set_options($options);
b62a0d 443
197601 444     // set global object for backward compatibility
T 445     $GLOBALS['IMAP'] = $this->imap;
48bc52 446
A 447     $hook = $this->plugins->exec_hook('imap_init', array('fetch_headers' => $this->imap->fetch_add_headers));
448     if ($hook['fetch_headers'])
449       $this->imap->fetch_add_headers = $hook['fetch_headers'];
b62a0d 450
47d8d3 451     // support this parameter for backward compatibility but log warning
T 452     if ($connect) {
1854c4 453       $this->imap_connect();
47d8d3 454       raise_error(array('code' => 800, 'type' => 'imap', 'file' => __FILE__,
T 455         'message' => "rcube::imap_init(true) is deprecated, use rcube::imap_connect() instead"), true, false);
456     }
1854c4 457   }
T 458
459
460   /**
461    * Connect to IMAP server with stored session data
462    *
463    * @return bool True on success, false on error
464    */
465   public function imap_connect()
466   {
48bc52 467     if (!$this->imap)
A 468       $this->imap_init();
b62a0d 469
59c216 470     if ($_SESSION['imap_host'] && !$this->imap->conn->connected()) {
A 471       if (!$this->imap->connect($_SESSION['imap_host'], $_SESSION['username'], $this->decrypt($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl'])) {
1854c4 472         if ($this->output)
T 473           $this->output->show_message($this->imap->error_code == -1 ? 'imaperror' : 'sessionerror', 'error');
474       }
59c216 475       else {
A 476         $this->set_imap_prop();
477         return $this->imap->conn;
478       }
1854c4 479     }
T 480
59c216 481     return false;
929a50 482   }
A 483
484
485   /**
486    * Create session object and start the session.
487    */
488   public function session_init()
489   {
bf67d6 490     // session started (Installer?)
A 491     if (session_id())
492       return;
493
929a50 494     $lifetime = $this->config->get('session_lifetime', 0) * 60;
A 495
496     // set session domain
497     if ($domain = $this->config->get('session_domain')) {
498       ini_set('session.cookie_domain', $domain);
499     }
500     // set session garbage collecting time according to session_lifetime
501     if ($lifetime) {
502       ini_set('session.gc_maxlifetime', $lifetime * 2);
503     }
504
505     ini_set('session.cookie_secure', rcube_https_check());
506     ini_set('session.name', 'roundcube_sessid');
507     ini_set('session.use_cookies', 1);
b62a0d 508     ini_set('session.use_only_cookies', 1);
929a50 509     ini_set('session.serialize_handler', 'php');
A 510
511     // use database for storing session data
512     $this->session = new rcube_session($this->get_dbh(), $lifetime);
513
514     $this->session->register_gc_handler('rcmail_temp_gc');
515     if ($this->config->get('enable_caching'))
516       $this->session->register_gc_handler('rcmail_cache_gc');
517
518     // start PHP session (if not in CLI mode)
519     if ($_SERVER['REMOTE_ADDR'])
520       session_start();
521
522     // set initial session vars
523     if (!isset($_SESSION['auth_time'])) {
524       $_SESSION['auth_time'] = time();
525       $_SESSION['temp'] = true;
526     }
527   }
528
529
530   /**
531    * Configure session object internals
532    */
533   public function session_configure()
534   {
bf67d6 535     if (!$this->session)
A 536       return;
537
929a50 538     $lifetime = $this->config->get('session_lifetime', 0) * 60;
A 539
540     // set keep-alive/check-recent interval
541     if ($keep_alive = $this->config->get('keep_alive')) {
542       // be sure that it's less than session lifetime
543       if ($lifetime)
544         $keep_alive = min($keep_alive, $lifetime - 30);
545       $keep_alive = max(60, $keep_alive);
546       $this->session->set_keep_alive($keep_alive);
547     }
197601 548   }
T 549
550
551   /**
552    * Perfom login to the IMAP server and to the webmail service.
553    * This will also create a new user entry if auto_create_user is configured.
554    *
555    * @param string IMAP user name
556    * @param string IMAP password
557    * @param string IMAP host
558    * @return boolean True on success, False on failure
559    */
560   function login($username, $pass, $host=NULL)
561   {
562     $user = NULL;
563     $config = $this->config->all();
564
565     if (!$host)
566       $host = $config['default_host'];
567
568     // Validate that selected host is in the list of configured hosts
569     if (is_array($config['default_host'])) {
570       $allowed = false;
571       foreach ($config['default_host'] as $key => $host_allowed) {
572         if (!is_numeric($key))
573           $host_allowed = $key;
574         if ($host == $host_allowed) {
575           $allowed = true;
576           break;
577         }
578       }
579       if (!$allowed)
580         return false;
581       }
bb8721 582     else if (!empty($config['default_host']) && $host != rcube_parse_host($config['default_host']))
197601 583       return false;
T 584
585     // parse $host URL
586     $a_host = parse_url($host);
587     if ($a_host['host']) {
588       $host = $a_host['host'];
589       $imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
f86e8f 590       if(!empty($a_host['port']))
A 591         $imap_port = $a_host['port'];
904809 592       else if ($imap_ssl && $imap_ssl != 'tls' && (!$config['default_port'] || $config['default_port'] == 143))
f86e8f 593         $imap_port = 993;
197601 594     }
b62a0d 595
f86e8f 596     $imap_port = $imap_port ? $imap_port : $config['default_port'];
197601 597
b62a0d 598     /* Modify username with domain if required
197601 599        Inspired by Marco <P0L0_notspam_binware.org>
T 600     */
601     // Check if we need to add domain
c16fab 602     if (!empty($config['username_domain']) && strpos($username, '@') === false) {
197601 603       if (is_array($config['username_domain']) && isset($config['username_domain'][$host]))
6a642d 604         $username .= '@'.rcube_parse_host($config['username_domain'][$host]);
197601 605       else if (is_string($config['username_domain']))
6a642d 606         $username .= '@'.rcube_parse_host($config['username_domain']);
197601 607     }
T 608
942069 609     // try to resolve email address from virtuser table
A 610     if (strpos($username, '@'))
611       if ($virtuser = rcube_user::email2user($username))
612         $username = $virtuser;
197601 613
T 614     // lowercase username if it's an e-mail address (#1484473)
615     if (strpos($username, '@'))
ee258c 616       $username = mb_strtolower($username);
197601 617
T 618     // user already registered -> overwrite username
619     if ($user = rcube_user::query($username, $host))
620       $username = $user->data['username'];
621
48bc52 622     if (!$this->imap)
A 623       $this->imap_init();
624
197601 625     // exit if IMAP login failed
230f94 626     if (!($imap_login  = $this->imap->connect($host, $username, $pass, $imap_port, $imap_ssl)))
197601 627       return false;
T 628
b5846e 629     $this->set_imap_prop();
A 630
197601 631     // user already registered -> update user's record
T 632     if (is_object($user)) {
b5846e 633       // create default folders on first login
A 634       if (!$user->data['last_login'] && $config['create_default_folders'])
635         $this->imap->create_default_folders();
197601 636       $user->touch();
T 637     }
638     // create new system user
639     else if ($config['auto_create_user']) {
640       if ($created = rcube_user::create($username, $host)) {
641         $user = $created;
b5846e 642         // create default folders on first login
A 643         if ($config['create_default_folders'])
644           $this->imap->create_default_folders();
197601 645       }
f879f4 646       else {
T 647         raise_error(array(
10eedb 648           'code' => 600, 'type' => 'php',
f0485a 649           'file' => __FILE__, 'line' => __LINE__,
f879f4 650           'message' => "Failed to create a user record. Maybe aborted by a plugin?"
10eedb 651           ), true, false);
f879f4 652       }
197601 653     }
T 654     else {
655       raise_error(array(
10eedb 656         'code' => 600, 'type' => 'php',
A 657         'file' => __FILE__, 'line' => __LINE__,
197601 658         'message' => "Acces denied for new user $username. 'auto_create_user' is disabled"
T 659         ), true, false);
660     }
661
662     // login succeeded
663     if (is_object($user) && $user->ID) {
664       $this->set_user($user);
665
666       // set session vars
667       $_SESSION['user_id']   = $user->ID;
668       $_SESSION['username']  = $user->data['username'];
669       $_SESSION['imap_host'] = $host;
670       $_SESSION['imap_port'] = $imap_port;
671       $_SESSION['imap_ssl']  = $imap_ssl;
2471d3 672       $_SESSION['password']  = $this->encrypt($pass);
197601 673       $_SESSION['login_time'] = mktime();
b62a0d 674
A 675       if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_')
c8ae24 676         $_SESSION['timezone'] = floatval($_REQUEST['_timezone']);
197601 677
T 678       // force reloading complete list of subscribed mailboxes
679       $this->imap->clear_cache('mailboxes');
680
681       return true;
682     }
683
684     return false;
685   }
686
687
688   /**
689    * Set root dir and last stored mailbox
690    * This must be done AFTER connecting to the server!
691    */
692   public function set_imap_prop()
693   {
694     $this->imap->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
695
696     if ($default_folders = $this->config->get('default_imap_folders')) {
697       $this->imap->set_default_mailboxes($default_folders);
698     }
699     if (!empty($_SESSION['mbox'])) {
700       $this->imap->set_mailbox($_SESSION['mbox']);
701     }
702     if (isset($_SESSION['page'])) {
703       $this->imap->set_page($_SESSION['page']);
704     }
b62a0d 705
230f94 706     // cache IMAP root and delimiter in session for performance reasons
T 707     $_SESSION['imap_root'] = $this->imap->root_dir;
708     $_SESSION['imap_delimiter'] = $this->imap->delimiter;
197601 709   }
T 710
1854c4 711
T 712   /**
713    * Auto-select IMAP host based on the posted login information
714    *
715    * @return string Selected IMAP host
716    */
717   public function autoselect_host()
718   {
719     $default_host = $this->config->get('default_host');
257f88 720     $host = null;
b62a0d 721
257f88 722     if (is_array($default_host)) {
T 723       $post_host = get_input_value('_host', RCUBE_INPUT_POST);
b62a0d 724
257f88 725       // direct match in default_host array
T 726       if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) {
727         $host = $post_host;
728       }
b62a0d 729
257f88 730       // try to select host by mail domain
1854c4 731       list($user, $domain) = explode('@', get_input_value('_user', RCUBE_INPUT_POST));
T 732       if (!empty($domain)) {
257f88 733         foreach ($default_host as $imap_host => $mail_domains) {
1854c4 734           if (is_array($mail_domains) && in_array($domain, $mail_domains)) {
T 735             $host = $imap_host;
736             break;
737           }
738         }
739       }
740
741       // take the first entry if $host is still an array
257f88 742       if (empty($host)) {
T 743         $host = array_shift($default_host);
744       }
745     }
746     else if (empty($default_host)) {
747       $host = get_input_value('_host', RCUBE_INPUT_POST);
1854c4 748     }
eec34e 749     else
bb8721 750       $host = rcube_parse_host($default_host);
1854c4 751
T 752     return $host;
753   }
754
755
756   /**
757    * Get localized text in the desired language
758    *
759    * @param mixed Named parameters array or label name
760    * @return string Localized text
761    */
cc97ea 762   public function gettext($attrib, $domain=null)
1854c4 763   {
T 764     // load localization files if not done yet
765     if (empty($this->texts))
766       $this->load_language();
b62a0d 767
1854c4 768     // extract attributes
T 769     if (is_string($attrib))
770       $attrib = array('name' => $attrib);
771
772     $nr = is_numeric($attrib['nr']) ? $attrib['nr'] : 1;
9898fe 773     $name = $attrib['name'] ? $attrib['name'] : '';
1854c4 774
cc97ea 775     // check for text with domain
9898fe 776     if ($domain && ($text_item = $this->texts[$domain.'.'.$name]))
cc97ea 777       ;
1854c4 778     // text does not exist
9898fe 779     else if (!($text_item = $this->texts[$name])) {
A 780       return "[$name]";
1854c4 781     }
T 782
b62a0d 783     // make text item array
1854c4 784     $a_text_item = is_array($text_item) ? $text_item : array('single' => $text_item);
T 785
786     // decide which text to use
787     if ($nr == 1) {
788       $text = $a_text_item['single'];
789     }
790     else if ($nr > 0) {
791       $text = $a_text_item['multiple'];
792     }
793     else if ($nr == 0) {
794       if ($a_text_item['none'])
795         $text = $a_text_item['none'];
796       else if ($a_text_item['single'])
797         $text = $a_text_item['single'];
798       else if ($a_text_item['multiple'])
799         $text = $a_text_item['multiple'];
800     }
801
802     // default text is single
803     if ($text == '') {
804       $text = $a_text_item['single'];
805     }
806
807     // replace vars in text
808     if (is_array($attrib['vars'])) {
809       foreach ($attrib['vars'] as $var_key => $var_value)
9898fe 810         $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
1854c4 811     }
T 812
813     // format output
814     if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst'])
815       return ucfirst($text);
816     else if ($attrib['uppercase'])
2aa2b3 817       return mb_strtoupper($text);
1854c4 818     else if ($attrib['lowercase'])
2aa2b3 819       return mb_strtolower($text);
1854c4 820
T 821     return $text;
822   }
823
824
825   /**
826    * Load a localization package
827    *
828    * @param string Language ID
829    */
cc97ea 830   public function load_language($lang = null, $add = array())
1854c4 831   {
c8ae24 832     $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
b62a0d 833
1854c4 834     // load localized texts
T 835     if (empty($this->texts) || $lang != $_SESSION['language']) {
836       $this->texts = array();
837
838       // get english labels (these should be complete)
839       @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
840       @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
841
842       if (is_array($labels))
9d9f8d 843         $this->texts = $labels;
1854c4 844       if (is_array($messages))
9d9f8d 845         $this->texts = array_merge($this->texts, $messages);
1854c4 846
T 847       // include user language files
848       if ($lang != 'en' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
849         include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
850         include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
851
852         if (is_array($labels))
853           $this->texts = array_merge($this->texts, $labels);
854         if (is_array($messages))
855           $this->texts = array_merge($this->texts, $messages);
856       }
b62a0d 857
1854c4 858       $_SESSION['language'] = $lang;
T 859     }
cc97ea 860
T 861     // append additional texts (from plugin)
862     if (is_array($add) && !empty($add))
863       $this->texts += $add;
1854c4 864   }
T 865
866
867   /**
868    * Read directory program/localization and return a list of available languages
869    *
870    * @return array List of available localizations
871    */
872   public function list_languages()
873   {
874     static $sa_languages = array();
875
876     if (!sizeof($sa_languages)) {
877       @include(INSTALL_PATH . 'program/localization/index.inc');
878
879       if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
880         while (($name = readdir($dh)) !== false) {
2aa2b3 881           if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name))
1854c4 882             continue;
T 883
884           if ($label = $rcube_languages[$name])
7d5178 885             $sa_languages[$name] = $label;
1854c4 886         }
T 887         closedir($dh);
888       }
889     }
890
891     return $sa_languages;
892   }
893
894
895   /**
896    * Check the auth hash sent by the client against the local session credentials
897    *
898    * @return boolean True if valid, False if not
899    */
900   function authenticate_session()
901   {
902     // advanced session authentication
903     if ($this->config->get('double_auth')) {
904       $now = time();
905       $valid = ($_COOKIE['sessauth'] == $this->get_auth_hash(session_id(), $_SESSION['auth_time']) ||
906                 $_COOKIE['sessauth'] == $this->get_auth_hash(session_id(), $_SESSION['last_auth']));
907
908       // renew auth cookie every 5 minutes (only for GET requests)
909       if (!$valid || ($_SERVER['REQUEST_METHOD']!='POST' && $now - $_SESSION['auth_time'] > 300)) {
910         $_SESSION['last_auth'] = $_SESSION['auth_time'];
911         $_SESSION['auth_time'] = $now;
cefd1d 912         rcmail::setcookie('sessauth', $this->get_auth_hash(session_id(), $now), 0);
1854c4 913       }
T 914     }
915     else {
929a50 916       $valid = $this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] == $this->session->get_ip() : true;
1854c4 917     }
T 918
919     // check session filetime
920     $lifetime = $this->config->get('session_lifetime');
929a50 921     $sess_ts = $this->session->get_ts();
A 922     if (!empty($lifetime) && !empty($sess_ts) && $sess_ts + $lifetime*60 < time()) {
1854c4 923       $valid = false;
T 924     }
925
926     return $valid;
927   }
928
929
930   /**
931    * Destroy session data and remove cookie
932    */
933   public function kill_session()
934   {
e6ce00 935     $this->plugins->exec_hook('session_destroy');
b62a0d 936
929a50 937     $this->session->remove();
c8ae24 938     $_SESSION = array('language' => $this->user->language, 'auth_time' => time(), 'temp' => true);
cefd1d 939     rcmail::setcookie('sessauth', '-del-', time() - 60);
1854c4 940     $this->user->reset();
T 941   }
942
943
944   /**
945    * Do server side actions on logout
946    */
947   public function logout_actions()
948   {
949     $config = $this->config->all();
b62a0d 950
A 951     // on logout action we're not connected to imap server
1854c4 952     if (($config['logout_purge'] && !empty($config['trash_mbox'])) || $config['logout_expunge']) {
T 953       if (!$this->authenticate_session())
954         return;
955
47d8d3 956       $this->imap_connect();
1854c4 957     }
T 958
959     if ($config['logout_purge'] && !empty($config['trash_mbox'])) {
960       $this->imap->clear_mailbox($config['trash_mbox']);
961     }
962
963     if ($config['logout_expunge']) {
964       $this->imap->expunge('INBOX');
965     }
966   }
967
968
969   /**
970    * Function to be executed in script shutdown
971    * Registered with register_shutdown_function()
972    */
197601 973   public function shutdown()
T 974   {
59c216 975     if (is_object($this->imap))
197601 976       $this->imap->close();
T 977
2c3d81 978     if (is_object($this->smtp))
A 979       $this->smtp->disconnect();
980
457373 981     foreach ($this->books as $book)
A 982       if (is_object($book))
983         $book->close();
197601 984
T 985     // before closing the database connection, write session data
75da0b 986     if ($_SERVER['REMOTE_ADDR'])
A 987       session_write_close();
2b35c5 988
A 989     // write performance stats to logs/console
990     if ($this->config->get('devel_mode')) {
991       if (function_exists('memory_get_usage'))
992         $mem = show_bytes(memory_get_usage());
993       if (function_exists('memory_get_peak_usage'))
994         $mem .= '/'.show_bytes(memory_get_peak_usage());
995
996       $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : '');
bf67d6 997       if (defined('RCMAIL_START'))
A 998         rcube_print_time(RCMAIL_START, $log);
999       else
1000         console($log);
2b35c5 1001     }
197601 1002   }
b62a0d 1003
A 1004
1854c4 1005   /**
57f0c8 1006    * Generate a unique token to be used in a form request
T 1007    *
1008    * @return string The request token
1009    */
549933 1010   public function get_request_token()
57f0c8 1011   {
549933 1012     $key = $this->task;
b62a0d 1013
549933 1014     if (!$_SESSION['request_tokens'][$key])
b48d9b 1015       $_SESSION['request_tokens'][$key] = md5(uniqid($key . mt_rand(), true));
b62a0d 1016
549933 1017     return $_SESSION['request_tokens'][$key];
57f0c8 1018   }
b62a0d 1019
A 1020
57f0c8 1021   /**
T 1022    * Check if the current request contains a valid token
1023    *
549933 1024    * @param int Request method
57f0c8 1025    * @return boolean True if request token is valid false if not
T 1026    */
549933 1027   public function check_request($mode = RCUBE_INPUT_POST)
57f0c8 1028   {
T 1029     $token = get_input_value('_token', $mode);
549933 1030     return !empty($token) && $_SESSION['request_tokens'][$this->task] == $token;
57f0c8 1031   }
b62a0d 1032
A 1033
57f0c8 1034   /**
1854c4 1035    * Create unique authorization hash
T 1036    *
1037    * @param string Session ID
1038    * @param int Timestamp
1039    * @return string The generated auth hash
1040    */
1041   private function get_auth_hash($sess_id, $ts)
1042   {
1043     $auth_string = sprintf('rcmail*sess%sR%s*Chk:%s;%s',
1044       $sess_id,
1045       $ts,
1046       $this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] : '***.***.***.***',
1047       $_SERVER['HTTP_USER_AGENT']);
1048
1049     if (function_exists('sha1'))
1050       return sha1($auth_string);
1051     else
1052       return md5($auth_string);
1053   }
1054
2471d3 1055
1854c4 1056   /**
2471d3 1057    * Encrypt using 3DES
1854c4 1058    *
2471d3 1059    * @param string $clear clear text input
A 1060    * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
1061    * @param boolean $base64 whether or not to base64_encode() the result before returning
1062    *
1063    * @return string encrypted text
1854c4 1064    */
2471d3 1065   public function encrypt($clear, $key = 'des_key', $base64 = true)
1854c4 1066   {
713a66 1067     if (!$clear)
A 1068       return '';
2471d3 1069     /*-
A 1070      * Add a single canary byte to the end of the clear text, which
1071      * will help find out how much of padding will need to be removed
1072      * upon decryption; see http://php.net/mcrypt_generic#68082
1073      */
1074     $clear = pack("a*H2", $clear, "80");
b62a0d 1075
2471d3 1076     if (function_exists('mcrypt_module_open') &&
A 1077         ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
1078     {
564741 1079       $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
2471d3 1080       mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
A 1081       $cipher = $iv . mcrypt_generic($td, $clear);
1854c4 1082       mcrypt_generic_deinit($td);
T 1083       mcrypt_module_close($td);
1084     }
44155c 1085     else {
A 1086       @include_once('lib/des.inc');
1087
1088       if (function_exists('des')) {
1089         $des_iv_size = 8;
564741 1090         $iv = $this->create_iv($des_iv_size);
44155c 1091         $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
A 1092       }
1093       else {
1094         raise_error(array(
1095           'code' => 500, 'type' => 'php',
1096           'file' => __FILE__, 'line' => __LINE__,
1097           'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
1098         ), true, true);
1099       }
1854c4 1100     }
44155c 1101
2471d3 1102     return $base64 ? base64_encode($cipher) : $cipher;
1854c4 1103   }
T 1104
1105   /**
2471d3 1106    * Decrypt 3DES-encrypted string
1854c4 1107    *
2471d3 1108    * @param string $cipher encrypted text
A 1109    * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
1110    * @param boolean $base64 whether or not input is base64-encoded
1111    *
1112    * @return string decrypted text
1854c4 1113    */
2471d3 1114   public function decrypt($cipher, $key = 'des_key', $base64 = true)
1854c4 1115   {
713a66 1116     if (!$cipher)
A 1117       return '';
b62a0d 1118
2471d3 1119     $cipher = $base64 ? base64_decode($cipher) : $cipher;
A 1120
1121     if (function_exists('mcrypt_module_open') &&
1122         ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
1123     {
1124       $iv = substr($cipher, 0, mcrypt_enc_get_iv_size($td));
1125       $cipher = substr($cipher, mcrypt_enc_get_iv_size($td));
1126       mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
1127       $clear = mdecrypt_generic($td, $cipher);
1854c4 1128       mcrypt_generic_deinit($td);
T 1129       mcrypt_module_close($td);
1130     }
44155c 1131     else {
A 1132       @include_once('lib/des.inc');
b62a0d 1133
44155c 1134       if (function_exists('des')) {
A 1135         $des_iv_size = 8;
1136         $iv = substr($cipher, 0, $des_iv_size);
1137         $cipher = substr($cipher, $des_iv_size);
1138         $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
1139       }
1140       else {
1141         raise_error(array(
1142           'code' => 500, 'type' => 'php',
1143           'file' => __FILE__, 'line' => __LINE__,
1144           'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
1145         ), true, true);
1146       }
1854c4 1147     }
44155c 1148
2471d3 1149     /*-
A 1150      * Trim PHP's padding and the canary byte; see note in
1151      * rcmail::encrypt() and http://php.net/mcrypt_generic#68082
1152      */
1153     $clear = substr(rtrim($clear, "\0"), 0, -1);
b62a0d 1154
2471d3 1155     return $clear;
1854c4 1156   }
c719f3 1157
T 1158   /**
564741 1159    * Generates encryption initialization vector (IV)
A 1160    *
1161    * @param int Vector size
1162    * @return string Vector string
1163    */
1164   private function create_iv($size)
1165   {
1166     // mcrypt_create_iv() can be slow when system lacks entrophy
1167     // we'll generate IV vector manually
1168     $iv = '';
1169     for ($i = 0; $i < $size; $i++)
1170         $iv .= chr(mt_rand(0, 255));
1171     return $iv;
1172   }
1173
1174   /**
e019f2 1175    * Build a valid URL to this instance of Roundcube
c719f3 1176    *
T 1177    * @param mixed Either a string with the action or url parameters as key-value pairs
1178    * @return string Valid application URL
1179    */
1180   public function url($p)
1181   {
1182     if (!is_array($p))
fde466 1183       $p = array('_action' => @func_get_arg(0));
b62a0d 1184
1c932d 1185     $task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task);
cc97ea 1186     $p['_task'] = $task;
1038a6 1187     unset($p['task']);
A 1188
cf1777 1189     $url = './';
T 1190     $delm = '?';
cc97ea 1191     foreach (array_reverse($p) as $key => $val)
cf1777 1192     {
T 1193       if (!empty($val)) {
cc97ea 1194         $par = $key[0] == '_' ? $key : '_'.$key;
cf1777 1195         $url .= $delm.urlencode($par).'='.urlencode($val);
T 1196         $delm = '&';
1197       }
1198     }
c719f3 1199     return $url;
T 1200   }
cefd1d 1201
T 1202
1203   /**
1204    * Helper method to set a cookie with the current path and host settings
1205    *
1206    * @param string Cookie name
1207    * @param string Cookie value
1208    * @param string Expiration time
1209    */
1210   public static function setcookie($name, $value, $exp = 0)
1211   {
317a7d 1212     if (headers_sent())
A 1213       return;
1214
cefd1d 1215     $cookie = session_get_cookie_params();
2273d4 1216
cefd1d 1217     setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'],
c96c5a 1218       rcube_https_check(), true);
cefd1d 1219   }
197601 1220 }
T 1221
1222