source: subversion/trunk/roundcubemail/program/include/rcmail.php @ 3555

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