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

Last change on this file since 3994 was 3994, checked in by thomasb, 3 years ago

Only lower-case user name if first login attempt failed (#1486393) + fix test

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