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