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