]> git.donarmstrong.com Git - roundcube.git/blob - program/include/rcmail.php
5323b649f54ff371759e2869afdfde888573d6d7
[roundcube.git] / program / include / rcmail.php
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-2011, The Roundcube Dev Team                       |
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: rcmail.php 5235 2011-09-19 06:43:57Z alec $
19
20 */
21
22
23 /**
24  * Application class of Roundcube Webmail
25  * implemented as singleton
26  *
27  * @package Core
28  */
29 class rcmail
30 {
31   /**
32    * Main tasks.
33    *
34    * @var array
35    */
36   static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy');
37
38   /**
39    * Singleton instace of rcmail
40    *
41    * @var rcmail
42    */
43   static private $instance;
44
45   /**
46    * Stores instance of rcube_config.
47    *
48    * @var rcube_config
49    */
50   public $config;
51
52   /**
53    * Stores rcube_user instance.
54    *
55    * @var rcube_user
56    */
57   public $user;
58
59   /**
60    * Instace of database class.
61    *
62    * @var rcube_mdb2
63    */
64   public $db;
65
66   /**
67    * Instace of Memcache class.
68    *
69    * @var rcube_mdb2
70    */
71   public $memcache;
72
73   /**
74    * Instace of rcube_session class.
75    *
76    * @var rcube_session
77    */
78   public $session;
79
80   /**
81    * Instance of rcube_smtp class.
82    *
83    * @var rcube_smtp
84    */
85   public $smtp;
86
87   /**
88    * Instance of rcube_imap class.
89    *
90    * @var rcube_imap
91    */
92   public $imap;
93
94   /**
95    * Instance of rcube_template class.
96    *
97    * @var rcube_template
98    */
99   public $output;
100
101   /**
102    * Instance of rcube_plugin_api.
103    *
104    * @var rcube_plugin_api
105    */
106   public $plugins;
107
108   /**
109    * Current task.
110    *
111    * @var string
112    */
113   public $task;
114
115   /**
116    * Current action.
117    *
118    * @var string
119    */
120   public $action = '';
121   public $comm_path = './';
122
123   private $texts;
124   private $address_books = array();
125   private $caches = array();
126   private $action_map = array();
127   private $shutdown_functions = array();
128
129
130   /**
131    * This implements the 'singleton' design pattern
132    *
133    * @return rcmail The one and only instance
134    */
135   static function get_instance()
136   {
137     if (!self::$instance) {
138       self::$instance = new rcmail();
139       self::$instance->startup();  // init AFTER object was linked with self::$instance
140     }
141
142     return self::$instance;
143   }
144
145
146   /**
147    * Private constructor
148    */
149   private function __construct()
150   {
151     // load configuration
152     $this->config = new rcube_config();
153
154     register_shutdown_function(array($this, 'shutdown'));
155   }
156
157
158   /**
159    * Initial startup function
160    * to register session, create database and imap connections
161    *
162    * @todo Remove global vars $DB, $USER
163    */
164   private function startup()
165   {
166     // initialize syslog
167     if ($this->config->get('log_driver') == 'syslog') {
168       $syslog_id = $this->config->get('syslog_id', 'roundcube');
169       $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
170       openlog($syslog_id, LOG_ODELAY, $syslog_facility);
171     }
172
173     // connect to database
174     $GLOBALS['DB'] = $this->get_dbh();
175
176     // start session
177     $this->session_init();
178
179     // create user object
180     $this->set_user(new rcube_user($_SESSION['user_id']));
181
182     // configure session (after user config merge!)
183     $this->session_configure();
184
185     // set task and action properties
186     $this->set_task(get_input_value('_task', RCUBE_INPUT_GPC));
187     $this->action = asciiwords(get_input_value('_action', RCUBE_INPUT_GPC));
188
189     // reset some session parameters when changing task
190     if ($this->task != 'utils') {
191       if ($this->session && $_SESSION['task'] != $this->task)
192         $this->session->remove('page');
193       // set current task to session
194       $_SESSION['task'] = $this->task;
195     }
196
197     // init output class
198     if (!empty($_REQUEST['_remote']))
199       $GLOBALS['OUTPUT'] = $this->json_init();
200     else
201       $GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed']));
202
203     // create plugin API and load plugins
204     $this->plugins = rcube_plugin_api::get_instance();
205
206     // init plugins
207     $this->plugins->init();
208   }
209
210
211   /**
212    * Setter for application task
213    *
214    * @param string Task to set
215    */
216   public function set_task($task)
217   {
218     $task = asciiwords($task);
219
220     if ($this->user && $this->user->ID)
221       $task = !$task ? 'mail' : $task;
222     else
223       $task = 'login';
224
225     $this->task = $task;
226     $this->comm_path = $this->url(array('task' => $this->task));
227
228     if ($this->output)
229       $this->output->set_env('task', $this->task);
230   }
231
232
233   /**
234    * Setter for system user object
235    *
236    * @param rcube_user Current user instance
237    */
238   public function set_user($user)
239   {
240     if (is_object($user)) {
241       $this->user = $user;
242       $GLOBALS['USER'] = $this->user;
243
244       // overwrite config with user preferences
245       $this->config->set_user_prefs((array)$this->user->get_prefs());
246     }
247
248     $_SESSION['language'] = $this->user->language = $this->language_prop($this->config->get('language', $_SESSION['language']));
249
250     // set localization
251     setlocale(LC_ALL, $_SESSION['language'] . '.utf8', 'en_US.utf8');
252
253     // workaround for http://bugs.php.net/bug.php?id=18556
254     if (in_array($_SESSION['language'], array('tr_TR', 'ku', 'az_AZ')))
255       setlocale(LC_CTYPE, 'en_US' . '.utf8');
256   }
257
258
259   /**
260    * Check the given string and return a valid language code
261    *
262    * @param string Language code
263    * @return string Valid language code
264    */
265   private function language_prop($lang)
266   {
267     static $rcube_languages, $rcube_language_aliases;
268
269     // user HTTP_ACCEPT_LANGUAGE if no language is specified
270     if (empty($lang) || $lang == 'auto') {
271        $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
272        $lang = str_replace('-', '_', $accept_langs[0]);
273      }
274
275     if (empty($rcube_languages)) {
276       @include(INSTALL_PATH . 'program/localization/index.inc');
277     }
278
279     // check if we have an alias for that language
280     if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
281       $lang = $rcube_language_aliases[$lang];
282     }
283     // try the first two chars
284     else if (!isset($rcube_languages[$lang])) {
285       $short = substr($lang, 0, 2);
286
287       // check if we have an alias for the short language code
288       if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
289         $lang = $rcube_language_aliases[$short];
290       }
291       // expand 'nn' to 'nn_NN'
292       else if (!isset($rcube_languages[$short])) {
293         $lang = $short.'_'.strtoupper($short);
294       }
295     }
296
297     if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
298       $lang = 'en_US';
299     }
300
301     return $lang;
302   }
303
304
305   /**
306    * Get the current database connection
307    *
308    * @return rcube_mdb2  Database connection object
309    */
310   public function get_dbh()
311   {
312     if (!$this->db) {
313       $config_all = $this->config->all();
314
315       $this->db = new rcube_mdb2($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
316       $this->db->sqlite_initials = INSTALL_PATH . 'SQL/sqlite.initial.sql';
317       $this->db->set_debug((bool)$config_all['sql_debug']);
318     }
319
320     return $this->db;
321   }
322   
323   
324   /**
325    * Get global handle for memcache access
326    *
327    * @return object Memcache
328    */
329   public function get_memcache()
330   {
331     if (!isset($this->memcache)) {
332       // no memcache support in PHP
333       if (!class_exists('Memcache')) {
334         $this->memcache = false;
335         return false;
336       }
337
338       $this->memcache = new Memcache;
339       $this->mc_available = 0;
340       
341       // add alll configured hosts to pool
342       $pconnect = $this->config->get('memcache_pconnect', true);
343       foreach ($this->config->get('memcache_hosts', array()) as $host) {
344         list($host, $port) = explode(':', $host);
345         if (!$port) $port = 11211;
346         $this->mc_available += intval($this->memcache->addServer($host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
347       }
348       
349       // test connection and failover (will result in $this->mc_available == 0 on complete failure)
350       $this->memcache->increment('__CONNECTIONTEST__', 1);  // NOP if key doesn't exist
351
352       if (!$this->mc_available)
353         $this->memcache = false;
354     }
355
356     return $this->memcache;
357   }
358   
359   /**
360    * Callback for memcache failure
361    */
362   public function memcache_failure($host, $port)
363   {
364     static $seen = array();
365     
366     // only report once
367     if (!$seen["$host:$port"]++) {
368       $this->mc_available--;
369       raise_error(array('code' => 604, 'type' => 'db',
370         'line' => __LINE__, 'file' => __FILE__,
371         'message' => "Memcache failure on host $host:$port"),
372         true, false);
373     }
374   }
375
376
377   /**
378    * Initialize and get cache object
379    *
380    * @param string $name   Cache identifier
381    * @param string $type   Cache type ('db', 'apc' or 'memcache')
382    * @param int    $ttl    Expiration time for cache items in seconds
383    * @param bool   $packed Enables/disables data serialization
384    *
385    * @return rcube_cache Cache object
386    */
387   public function get_cache($name, $type='db', $ttl=0, $packed=true)
388   {
389     if (!isset($this->caches[$name])) {
390       $this->caches[$name] = new rcube_cache($type, $_SESSION['user_id'], $name, $ttl, $packed);
391     }
392
393     return $this->caches[$name];
394   }
395
396
397   /**
398    * Return instance of the internal address book class
399    *
400    * @param string  Address book identifier
401    * @param boolean True if the address book needs to be writeable
402    *
403    * @return rcube_contacts Address book object
404    */
405   public function get_address_book($id, $writeable = false)
406   {
407     $contacts    = null;
408     $ldap_config = (array)$this->config->get('ldap_public');
409     $abook_type  = strtolower($this->config->get('address_book_type'));
410
411     // 'sql' is the alias for '0' used by autocomplete
412     if ($id == 'sql')
413         $id = '0';
414
415     // use existing instance
416     if (isset($this->address_books[$id]) && is_object($this->address_books[$id])
417       && is_a($this->address_books[$id], 'rcube_addressbook')
418       && (!$writeable || !$this->address_books[$id]->readonly)
419     ) {
420       $contacts = $this->address_books[$id];
421     }
422     else if ($id && $ldap_config[$id]) {
423       $contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['imap_host']));
424     }
425     else if ($id === '0') {
426       $contacts = new rcube_contacts($this->db, $this->user->ID);
427     }
428     else {
429       $plugin = $this->plugins->exec_hook('addressbook_get', array('id' => $id, 'writeable' => $writeable));
430
431       // plugin returned instance of a rcube_addressbook
432       if ($plugin['instance'] instanceof rcube_addressbook) {
433         $contacts = $plugin['instance'];
434       }
435       // get first source from the list
436       else if (!$id) {
437         $source = reset($this->get_address_sources($writeable));
438         if (!empty($source)) {
439           $contacts = $this->get_address_book($source['id']);
440           if ($contacts)
441             $id = $source['id'];
442         }
443       }
444     }
445
446     if (!$contacts) {
447       raise_error(array(
448         'code' => 700, 'type' => 'php',
449         'file' => __FILE__, 'line' => __LINE__,
450         'message' => "Addressbook source ($id) not found!"),
451         true, true);
452     }
453
454     // add to the 'books' array for shutdown function
455     if (!isset($this->address_books[$id]))
456       $this->address_books[$id] = $contacts;
457
458     return $contacts;
459   }
460
461
462   /**
463    * Return address books list
464    *
465    * @param boolean True if the address book needs to be writeable
466    *
467    * @return array  Address books array
468    */
469   public function get_address_sources($writeable = false)
470   {
471     $abook_type = strtolower($this->config->get('address_book_type'));
472     $ldap_config = $this->config->get('ldap_public');
473     $autocomplete = (array) $this->config->get('autocomplete_addressbooks');
474     $list = array();
475
476     // We are using the DB address book
477     if ($abook_type != 'ldap') {
478       if (!isset($this->address_books['0']))
479         $this->address_books['0'] = new rcube_contacts($this->db, $this->user->ID);
480       $list['0'] = array(
481         'id'       => '0',
482         'name'     => rcube_label('personaladrbook'),
483         'groups'   => $this->address_books['0']->groups,
484         'readonly' => $this->address_books['0']->readonly,
485         'autocomplete' => in_array('sql', $autocomplete)
486       );
487     }
488
489     if ($ldap_config) {
490       $ldap_config = (array) $ldap_config;
491       foreach ($ldap_config as $id => $prop)
492         $list[$id] = array(
493           'id'       => $id,
494           'name'     => $prop['name'],
495           'groups'   => is_array($prop['groups']),
496           'readonly' => !$prop['writable'],
497           'hidden'   => $prop['hidden'],
498           'autocomplete' => in_array($id, $autocomplete)
499         );
500     }
501
502     $plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list));
503     $list = $plugin['sources'];
504
505     foreach ($list as $idx => $item) {
506       // register source for shutdown function
507       if (!is_object($this->address_books[$item['id']]))
508         $this->address_books[$item['id']] = $item;
509       // remove from list if not writeable as requested
510       if ($writeable && $item['readonly'])
511           unset($list[$idx]);
512     }
513
514     return $list;
515   }
516
517
518   /**
519    * Init output object for GUI and add common scripts.
520    * This will instantiate a rcmail_template object and set
521    * environment vars according to the current session and configuration
522    *
523    * @param boolean True if this request is loaded in a (i)frame
524    * @return rcube_template Reference to HTML output object
525    */
526   public function load_gui($framed = false)
527   {
528     // init output page
529     if (!($this->output instanceof rcube_template))
530       $this->output = new rcube_template($this->task, $framed);
531
532     // set keep-alive/check-recent interval
533     if ($this->session && ($keep_alive = $this->session->get_keep_alive())) {
534       $this->output->set_env('keep_alive', $keep_alive);
535     }
536
537     if ($framed) {
538       $this->comm_path .= '&_framed=1';
539       $this->output->set_env('framed', true);
540     }
541
542     $this->output->set_env('task', $this->task);
543     $this->output->set_env('action', $this->action);
544     $this->output->set_env('comm_path', $this->comm_path);
545     $this->output->set_charset(RCMAIL_CHARSET);
546
547     // add some basic labels to client
548     $this->output->add_label('loading', 'servererror');
549
550     return $this->output;
551   }
552
553
554   /**
555    * Create an output object for JSON responses
556    *
557    * @return rcube_json_output Reference to JSON output object
558    */
559   public function json_init()
560   {
561     if (!($this->output instanceof rcube_json_output))
562       $this->output = new rcube_json_output($this->task);
563
564     return $this->output;
565   }
566
567
568   /**
569    * Create SMTP object and connect to server
570    *
571    * @param boolean True if connection should be established
572    */
573   public function smtp_init($connect = false)
574   {
575     $this->smtp = new rcube_smtp();
576
577     if ($connect)
578       $this->smtp->connect();
579   }
580
581
582   /**
583    * Create global IMAP object and connect to server
584    *
585    * @param boolean True if connection should be established
586    * @todo Remove global $IMAP
587    */
588   public function imap_init($connect = false)
589   {
590     // already initialized
591     if (is_object($this->imap))
592       return;
593
594     $this->imap = new rcube_imap();
595     $this->imap->debug_level = $this->config->get('debug_level');
596     $this->imap->skip_deleted = $this->config->get('skip_deleted');
597
598     // enable caching of imap data
599     $imap_cache = $this->config->get('imap_cache');
600     $messages_cache = $this->config->get('messages_cache');
601     // for backward compatybility
602     if ($imap_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
603         $imap_cache     = 'db';
604         $messages_cache = true;
605     }
606     if ($imap_cache)
607         $this->imap->set_caching($imap_cache);
608     if ($messages_cache)
609         $this->imap->set_messages_caching(true);
610
611     // set pagesize from config
612     $this->imap->set_pagesize($this->config->get('pagesize', 50));
613
614     // Setting root and delimiter before establishing the connection
615     // can save time detecting them using NAMESPACE and LIST
616     $options = array(
617       'auth_method' => $this->config->get('imap_auth_type', 'check'),
618       'auth_cid'    => $this->config->get('imap_auth_cid'),
619       'auth_pw'     => $this->config->get('imap_auth_pw'),
620       'debug'       => (bool) $this->config->get('imap_debug', 0),
621       'force_caps'  => (bool) $this->config->get('imap_force_caps'),
622       'timeout'     => (int) $this->config->get('imap_timeout', 0),
623     );
624
625     $this->imap->set_options($options);
626
627     // set global object for backward compatibility
628     $GLOBALS['IMAP'] = $this->imap;
629
630     $hook = $this->plugins->exec_hook('imap_init', array('fetch_headers' => $this->imap->fetch_add_headers));
631     if ($hook['fetch_headers'])
632       $this->imap->fetch_add_headers = $hook['fetch_headers'];
633
634     // support this parameter for backward compatibility but log warning
635     if ($connect) {
636       $this->imap_connect();
637       raise_error(array(
638         'code' => 800, 'type' => 'imap',
639         'file' => __FILE__, 'line' => __LINE__,
640         'message' => "rcube::imap_init(true) is deprecated, use rcube::imap_connect() instead"),
641         true, false);
642     }
643   }
644
645
646   /**
647    * Connect to IMAP server with stored session data
648    *
649    * @return bool True on success, false on error
650    */
651   public function imap_connect()
652   {
653     if (!$this->imap)
654       $this->imap_init();
655
656     if ($_SESSION['imap_host'] && !$this->imap->conn->connected()) {
657       if (!$this->imap->connect($_SESSION['imap_host'], $_SESSION['username'], $this->decrypt($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl'])) {
658         if ($this->output)
659           $this->output->show_message($this->imap->get_error_code() == -1 ? 'imaperror' : 'sessionerror', 'error');
660       }
661       else {
662         $this->set_imap_prop();
663         return $this->imap->conn;
664       }
665     }
666
667     return false;
668   }
669
670
671   /**
672    * Create session object and start the session.
673    */
674   public function session_init()
675   {
676     // session started (Installer?)
677     if (session_id())
678       return;
679
680     // set session domain
681     if ($domain = $this->config->get('session_domain')) {
682       ini_set('session.cookie_domain', $domain);
683     }
684     // set session garbage collecting time according to session_lifetime
685     $lifetime = $this->config->get('session_lifetime', 0) * 60;
686     if ($lifetime) {
687       ini_set('session.gc_maxlifetime', $lifetime * 2);
688     }
689
690     ini_set('session.cookie_secure', rcube_https_check());
691     ini_set('session.name', 'roundcube_sessid');
692     ini_set('session.use_cookies', 1);
693     ini_set('session.use_only_cookies', 1);
694     ini_set('session.serialize_handler', 'php');
695
696     // use database for storing session data
697     $this->session = new rcube_session($this->get_dbh(), $this->config);
698
699     $this->session->register_gc_handler('rcmail_temp_gc');
700     if ($this->config->get('enable_caching'))
701       $this->session->register_gc_handler('rcmail_cache_gc');
702
703     // start PHP session (if not in CLI mode)
704     if ($_SERVER['REMOTE_ADDR'])
705       session_start();
706
707     // set initial session vars
708     if (!$_SESSION['user_id'])
709       $_SESSION['temp'] = true;
710   }
711
712
713   /**
714    * Configure session object internals
715    */
716   public function session_configure()
717   {
718     if (!$this->session)
719       return;
720
721     $lifetime = $this->config->get('session_lifetime', 0) * 60;
722
723     // set keep-alive/check-recent interval
724     if ($keep_alive = $this->config->get('keep_alive')) {
725       // be sure that it's less than session lifetime
726       if ($lifetime)
727         $keep_alive = min($keep_alive, $lifetime - 30);
728       $keep_alive = max(60, $keep_alive);
729       $this->session->set_keep_alive($keep_alive);
730     }
731     
732     $this->session->set_secret($this->config->get('des_key') . $_SERVER['HTTP_USER_AGENT']);
733     $this->session->set_ip_check($this->config->get('ip_check'));
734   }
735
736
737   /**
738    * Perfom login to the IMAP server and to the webmail service.
739    * This will also create a new user entry if auto_create_user is configured.
740    *
741    * @param string IMAP user name
742    * @param string IMAP password
743    * @param string IMAP host
744    * @return boolean True on success, False on failure
745    */
746   function login($username, $pass, $host=NULL)
747   {
748     $user = NULL;
749     $config = $this->config->all();
750
751     if (!$host)
752       $host = $config['default_host'];
753
754     // Validate that selected host is in the list of configured hosts
755     if (is_array($config['default_host'])) {
756       $allowed = false;
757       foreach ($config['default_host'] as $key => $host_allowed) {
758         if (!is_numeric($key))
759           $host_allowed = $key;
760         if ($host == $host_allowed) {
761           $allowed = true;
762           break;
763         }
764       }
765       if (!$allowed)
766         return false;
767       }
768     else if (!empty($config['default_host']) && $host != rcube_parse_host($config['default_host']))
769       return false;
770
771     // parse $host URL
772     $a_host = parse_url($host);
773     if ($a_host['host']) {
774       $host = $a_host['host'];
775       $imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
776       if (!empty($a_host['port']))
777         $imap_port = $a_host['port'];
778       else if ($imap_ssl && $imap_ssl != 'tls' && (!$config['default_port'] || $config['default_port'] == 143))
779         $imap_port = 993;
780     }
781
782     $imap_port = $imap_port ? $imap_port : $config['default_port'];
783
784     /* Modify username with domain if required
785        Inspired by Marco <P0L0_notspam_binware.org>
786     */
787     // Check if we need to add domain
788     if (!empty($config['username_domain']) && strpos($username, '@') === false) {
789       if (is_array($config['username_domain']) && isset($config['username_domain'][$host]))
790         $username .= '@'.rcube_parse_host($config['username_domain'][$host], $host);
791       else if (is_string($config['username_domain']))
792         $username .= '@'.rcube_parse_host($config['username_domain'], $host);
793     }
794
795     // Convert username to lowercase. If IMAP backend
796     // is case-insensitive we need to store always the same username (#1487113)
797     if ($config['login_lc']) {
798       $username = mb_strtolower($username);
799     }
800
801     // try to resolve email address from virtuser table
802     if (strpos($username, '@') && ($virtuser = rcube_user::email2user($username))) {
803       $username = $virtuser;
804     }
805
806     // Here we need IDNA ASCII
807     // Only rcube_contacts class is using domain names in Unicode
808     $host = rcube_idn_to_ascii($host);
809     if (strpos($username, '@')) {
810       // lowercase domain name
811       list($local, $domain) = explode('@', $username);
812       $username = $local . '@' . mb_strtolower($domain);
813       $username = rcube_idn_to_ascii($username);
814     }
815
816     // user already registered -> overwrite username
817     if ($user = rcube_user::query($username, $host))
818       $username = $user->data['username'];
819
820     if (!$this->imap)
821       $this->imap_init();
822
823     // try IMAP login
824     if (!($imap_login = $this->imap->connect($host, $username, $pass, $imap_port, $imap_ssl))) {
825       // try with lowercase
826       $username_lc = mb_strtolower($username);
827       if ($username_lc != $username) {
828         // try to find user record again -> overwrite username
829         if (!$user && ($user = rcube_user::query($username_lc, $host)))
830           $username_lc = $user->data['username'];
831
832         if ($imap_login = $this->imap->connect($host, $username_lc, $pass, $imap_port, $imap_ssl))
833           $username = $username_lc;
834       }
835     }
836
837     // exit if IMAP login failed
838     if (!$imap_login)
839       return false;
840
841     $this->set_imap_prop();
842
843     // user already registered -> update user's record
844     if (is_object($user)) {
845       // fix some old settings according to namespace prefix
846       $this->fix_namespace_settings($user);
847
848       // create default folders on first login
849       if (!$user->data['last_login'] && $config['create_default_folders'])
850         $this->imap->create_default_folders();
851       // update last login timestamp
852       $user->touch();
853     }
854     // create new system user
855     else if ($config['auto_create_user']) {
856       if ($created = rcube_user::create($username, $host)) {
857         $user = $created;
858
859         // fix default settings according to namespace prefix
860         $this->fix_namespace_settings($user);
861
862         // create default folders on first login
863         if ($config['create_default_folders'])
864           $this->imap->create_default_folders();
865       }
866       else {
867         raise_error(array(
868           'code' => 620, 'type' => 'php',
869           'file' => __FILE__, 'line' => __LINE__,
870           'message' => "Failed to create a user record. Maybe aborted by a plugin?"
871           ), true, false);
872       }
873     }
874     else {
875       raise_error(array(
876         'code' => 621, 'type' => 'php',
877         'file' => __FILE__, 'line' => __LINE__,
878         'message' => "Access denied for new user $username. 'auto_create_user' is disabled"
879         ), true, false);
880     }
881
882     // login succeeded
883     if (is_object($user) && $user->ID) {
884       $this->set_user($user);
885       $this->session_configure();
886
887       // set session vars
888       $_SESSION['user_id']   = $user->ID;
889       $_SESSION['username']  = $user->data['username'];
890       $_SESSION['imap_host'] = $host;
891       $_SESSION['imap_port'] = $imap_port;
892       $_SESSION['imap_ssl']  = $imap_ssl;
893       $_SESSION['password']  = $this->encrypt($pass);
894       $_SESSION['login_time'] = mktime();
895       
896       if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_')
897         $_SESSION['timezone'] = floatval($_REQUEST['_timezone']);
898
899       // force reloading complete list of subscribed mailboxes
900       $this->imap->clear_cache('mailboxes', true);
901
902       return true;
903     }
904
905     return false;
906   }
907
908
909   /**
910    * Set root dir and last stored mailbox
911    * This must be done AFTER connecting to the server!
912    */
913   public function set_imap_prop()
914   {
915     $this->imap->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
916
917     if ($default_folders = $this->config->get('default_imap_folders')) {
918       $this->imap->set_default_mailboxes($default_folders);
919     }
920     if (isset($_SESSION['mbox'])) {
921       $this->imap->set_mailbox($_SESSION['mbox']);
922     }
923     if (isset($_SESSION['page'])) {
924       $this->imap->set_page($_SESSION['page']);
925     }
926   }
927
928
929   /**
930    * Auto-select IMAP host based on the posted login information
931    *
932    * @return string Selected IMAP host
933    */
934   public function autoselect_host()
935   {
936     $default_host = $this->config->get('default_host');
937     $host = null;
938
939     if (is_array($default_host)) {
940       $post_host = get_input_value('_host', RCUBE_INPUT_POST);
941
942       // direct match in default_host array
943       if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) {
944         $host = $post_host;
945       }
946
947       // try to select host by mail domain
948       list($user, $domain) = explode('@', get_input_value('_user', RCUBE_INPUT_POST));
949       if (!empty($domain)) {
950         foreach ($default_host as $imap_host => $mail_domains) {
951           if (is_array($mail_domains) && in_array($domain, $mail_domains)) {
952             $host = $imap_host;
953             break;
954           }
955         }
956       }
957
958       // take the first entry if $host is still an array
959       if (empty($host)) {
960         $host = array_shift($default_host);
961       }
962     }
963     else if (empty($default_host)) {
964       $host = get_input_value('_host', RCUBE_INPUT_POST);
965     }
966     else
967       $host = rcube_parse_host($default_host);
968
969     return $host;
970   }
971
972
973   /**
974    * Get localized text in the desired language
975    *
976    * @param mixed Named parameters array or label name
977    * @return string Localized text
978    */
979   public function gettext($attrib, $domain=null)
980   {
981     // load localization files if not done yet
982     if (empty($this->texts))
983       $this->load_language();
984
985     // extract attributes
986     if (is_string($attrib))
987       $attrib = array('name' => $attrib);
988
989     $nr = is_numeric($attrib['nr']) ? $attrib['nr'] : 1;
990     $name = $attrib['name'] ? $attrib['name'] : '';
991     
992     // attrib contain text values: use them from now
993     if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us']))
994         $this->texts[$name] = $setval;
995
996     // check for text with domain
997     if ($domain && ($text_item = $this->texts[$domain.'.'.$name]))
998       ;
999     // text does not exist
1000     else if (!($text_item = $this->texts[$name])) {
1001       return "[$name]";
1002     }
1003
1004     // make text item array
1005     $a_text_item = is_array($text_item) ? $text_item : array('single' => $text_item);
1006
1007     // decide which text to use
1008     if ($nr == 1) {
1009       $text = $a_text_item['single'];
1010     }
1011     else if ($nr > 0) {
1012       $text = $a_text_item['multiple'];
1013     }
1014     else if ($nr == 0) {
1015       if ($a_text_item['none'])
1016         $text = $a_text_item['none'];
1017       else if ($a_text_item['single'])
1018         $text = $a_text_item['single'];
1019       else if ($a_text_item['multiple'])
1020         $text = $a_text_item['multiple'];
1021     }
1022
1023     // default text is single
1024     if ($text == '') {
1025       $text = $a_text_item['single'];
1026     }
1027
1028     // replace vars in text
1029     if (is_array($attrib['vars'])) {
1030       foreach ($attrib['vars'] as $var_key => $var_value)
1031         $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
1032     }
1033
1034     // format output
1035     if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst'])
1036       return ucfirst($text);
1037     else if ($attrib['uppercase'])
1038       return mb_strtoupper($text);
1039     else if ($attrib['lowercase'])
1040       return mb_strtolower($text);
1041
1042     return $text;
1043   }
1044
1045
1046   /**
1047    * Check if the given text lable exists
1048    *
1049    * @param string Label name
1050    * @return boolean True if text exists (either in the current language or in en_US)
1051    */
1052   public function text_exists($name, $domain=null)
1053   {
1054     // load localization files if not done yet
1055     if (empty($this->texts))
1056       $this->load_language();
1057
1058     // check for text with domain first
1059     return ($domain && isset($this->texts[$domain.'.'.$name])) || isset($this->texts[$name]);
1060   }
1061
1062   /**
1063    * Load a localization package
1064    *
1065    * @param string Language ID
1066    */
1067   public function load_language($lang = null, $add = array())
1068   {
1069     $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
1070
1071     // load localized texts
1072     if (empty($this->texts) || $lang != $_SESSION['language']) {
1073       $this->texts = array();
1074
1075       // handle empty lines after closing PHP tag in localization files
1076       ob_start();
1077
1078       // get english labels (these should be complete)
1079       @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
1080       @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
1081
1082       if (is_array($labels))
1083         $this->texts = $labels;
1084       if (is_array($messages))
1085         $this->texts = array_merge($this->texts, $messages);
1086
1087       // include user language files
1088       if ($lang != 'en' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
1089         include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
1090         include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
1091
1092         if (is_array($labels))
1093           $this->texts = array_merge($this->texts, $labels);
1094         if (is_array($messages))
1095           $this->texts = array_merge($this->texts, $messages);
1096       }
1097
1098       ob_end_clean();
1099
1100       $_SESSION['language'] = $lang;
1101     }
1102
1103     // append additional texts (from plugin)
1104     if (is_array($add) && !empty($add))
1105       $this->texts += $add;
1106   }
1107
1108
1109   /**
1110    * Read directory program/localization and return a list of available languages
1111    *
1112    * @return array List of available localizations
1113    */
1114   public function list_languages()
1115   {
1116     static $sa_languages = array();
1117
1118     if (!sizeof($sa_languages)) {
1119       @include(INSTALL_PATH . 'program/localization/index.inc');
1120
1121       if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
1122         while (($name = readdir($dh)) !== false) {
1123           if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name))
1124             continue;
1125
1126           if ($label = $rcube_languages[$name])
1127             $sa_languages[$name] = $label;
1128         }
1129         closedir($dh);
1130       }
1131     }
1132
1133     return $sa_languages;
1134   }
1135
1136
1137   /**
1138    * Destroy session data and remove cookie
1139    */
1140   public function kill_session()
1141   {
1142     $this->plugins->exec_hook('session_destroy');
1143
1144     $this->session->kill();
1145     $_SESSION = array('language' => $this->user->language, 'temp' => true);
1146     $this->user->reset();
1147   }
1148
1149
1150   /**
1151    * Do server side actions on logout
1152    */
1153   public function logout_actions()
1154   {
1155     $config = $this->config->all();
1156
1157     // on logout action we're not connected to imap server
1158     if (($config['logout_purge'] && !empty($config['trash_mbox'])) || $config['logout_expunge']) {
1159       if (!$this->session->check_auth())
1160         return;
1161
1162       $this->imap_connect();
1163     }
1164
1165     if ($config['logout_purge'] && !empty($config['trash_mbox'])) {
1166       $this->imap->clear_mailbox($config['trash_mbox']);
1167     }
1168
1169     if ($config['logout_expunge']) {
1170       $this->imap->expunge('INBOX');
1171     }
1172
1173     // Try to save unsaved user preferences
1174     if (!empty($_SESSION['preferences'])) {
1175       $this->user->save_prefs(unserialize($_SESSION['preferences']));
1176     }
1177   }
1178
1179
1180   /**
1181    * Function to be executed in script shutdown
1182    * Registered with register_shutdown_function()
1183    */
1184   public function shutdown()
1185   {
1186     foreach ($this->shutdown_functions as $function)
1187       call_user_func($function);
1188
1189     if (is_object($this->smtp))
1190       $this->smtp->disconnect();
1191
1192     foreach ($this->address_books as $book) {
1193       if (is_object($book) && is_a($book, 'rcube_addressbook'))
1194         $book->close();
1195     }
1196
1197     foreach ($this->caches as $cache) {
1198         if (is_object($cache))
1199             $cache->close();
1200     }
1201
1202     if (is_object($this->imap))
1203       $this->imap->close();
1204
1205     // before closing the database connection, write session data
1206     if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
1207       $this->session->cleanup();
1208       session_write_close();
1209     }
1210
1211     // write performance stats to logs/console
1212     if ($this->config->get('devel_mode')) {
1213       if (function_exists('memory_get_usage'))
1214         $mem = show_bytes(memory_get_usage());
1215       if (function_exists('memory_get_peak_usage'))
1216         $mem .= '/'.show_bytes(memory_get_peak_usage());
1217
1218       $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : '');
1219       if (defined('RCMAIL_START'))
1220         rcube_print_time(RCMAIL_START, $log);
1221       else
1222         console($log);
1223     }
1224   }
1225
1226
1227   /**
1228    * Registers shutdown function to be executed on shutdown.
1229    * The functions will be executed before destroying any
1230    * objects like smtp, imap, session, etc.
1231    *
1232    * @param callback Function callback
1233    */
1234   public function add_shutdown_function($function)
1235   {
1236     $this->shutdown_functions[] = $function;
1237   }
1238
1239
1240   /**
1241    * Generate a unique token to be used in a form request
1242    *
1243    * @return string The request token
1244    */
1245   public function get_request_token()
1246   {
1247     $sess_id = $_COOKIE[ini_get('session.name')];
1248     if (!$sess_id) $sess_id = session_id();
1249     $plugin = $this->plugins->exec_hook('request_token', array('value' => md5('RT' . $this->task . $this->config->get('des_key') . $sess_id)));
1250     return $plugin['value'];
1251   }
1252
1253
1254   /**
1255    * Check if the current request contains a valid token
1256    *
1257    * @param int Request method
1258    * @return boolean True if request token is valid false if not
1259    */
1260   public function check_request($mode = RCUBE_INPUT_POST)
1261   {
1262     $token = get_input_value('_token', $mode);
1263     $sess_id = $_COOKIE[ini_get('session.name')];
1264     return !empty($sess_id) && $token == $this->get_request_token();
1265   }
1266
1267
1268   /**
1269    * Create unique authorization hash
1270    *
1271    * @param string Session ID
1272    * @param int Timestamp
1273    * @return string The generated auth hash
1274    */
1275   private function get_auth_hash($sess_id, $ts)
1276   {
1277     $auth_string = sprintf('rcmail*sess%sR%s*Chk:%s;%s',
1278       $sess_id,
1279       $ts,
1280       $this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] : '***.***.***.***',
1281       $_SERVER['HTTP_USER_AGENT']);
1282
1283     if (function_exists('sha1'))
1284       return sha1($auth_string);
1285     else
1286       return md5($auth_string);
1287   }
1288
1289
1290   /**
1291    * Encrypt using 3DES
1292    *
1293    * @param string $clear clear text input
1294    * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
1295    * @param boolean $base64 whether or not to base64_encode() the result before returning
1296    *
1297    * @return string encrypted text
1298    */
1299   public function encrypt($clear, $key = 'des_key', $base64 = true)
1300   {
1301     if (!$clear)
1302       return '';
1303     /*-
1304      * Add a single canary byte to the end of the clear text, which
1305      * will help find out how much of padding will need to be removed
1306      * upon decryption; see http://php.net/mcrypt_generic#68082
1307      */
1308     $clear = pack("a*H2", $clear, "80");
1309
1310     if (function_exists('mcrypt_module_open') &&
1311         ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
1312     {
1313       $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
1314       mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
1315       $cipher = $iv . mcrypt_generic($td, $clear);
1316       mcrypt_generic_deinit($td);
1317       mcrypt_module_close($td);
1318     }
1319     else {
1320       // @include_once 'des.inc'; (not shipped with this distribution)
1321
1322       if (function_exists('des')) {
1323         $des_iv_size = 8;
1324         $iv = $this->create_iv($des_iv_size);
1325         $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
1326       }
1327       else {
1328         raise_error(array(
1329           'code' => 500, 'type' => 'php',
1330           'file' => __FILE__, 'line' => __LINE__,
1331           'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
1332         ), true, true);
1333       }
1334     }
1335
1336     return $base64 ? base64_encode($cipher) : $cipher;
1337   }
1338
1339   /**
1340    * Decrypt 3DES-encrypted string
1341    *
1342    * @param string $cipher encrypted text
1343    * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
1344    * @param boolean $base64 whether or not input is base64-encoded
1345    *
1346    * @return string decrypted text
1347    */
1348   public function decrypt($cipher, $key = 'des_key', $base64 = true)
1349   {
1350     if (!$cipher)
1351       return '';
1352
1353     $cipher = $base64 ? base64_decode($cipher) : $cipher;
1354
1355     if (function_exists('mcrypt_module_open') &&
1356         ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
1357     {
1358       $iv_size = mcrypt_enc_get_iv_size($td);
1359       $iv = substr($cipher, 0, $iv_size);
1360
1361       // session corruption? (#1485970)
1362       if (strlen($iv) < $iv_size)
1363         return '';
1364
1365       $cipher = substr($cipher, $iv_size);
1366       mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
1367       $clear = mdecrypt_generic($td, $cipher);
1368       mcrypt_generic_deinit($td);
1369       mcrypt_module_close($td);
1370     }
1371     else {
1372       // @include_once 'des.inc'; (not shipped with this distribution)
1373
1374       if (function_exists('des')) {
1375         $des_iv_size = 8;
1376         $iv = substr($cipher, 0, $des_iv_size);
1377         $cipher = substr($cipher, $des_iv_size);
1378         $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
1379       }
1380       else {
1381         raise_error(array(
1382           'code' => 500, 'type' => 'php',
1383           'file' => __FILE__, 'line' => __LINE__,
1384           'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
1385         ), true, true);
1386       }
1387     }
1388
1389     /*-
1390      * Trim PHP's padding and the canary byte; see note in
1391      * rcmail::encrypt() and http://php.net/mcrypt_generic#68082
1392      */
1393     $clear = substr(rtrim($clear, "\0"), 0, -1);
1394
1395     return $clear;
1396   }
1397
1398   /**
1399    * Generates encryption initialization vector (IV)
1400    *
1401    * @param int Vector size
1402    * @return string Vector string
1403    */
1404   private function create_iv($size)
1405   {
1406     // mcrypt_create_iv() can be slow when system lacks entrophy
1407     // we'll generate IV vector manually
1408     $iv = '';
1409     for ($i = 0; $i < $size; $i++)
1410         $iv .= chr(mt_rand(0, 255));
1411     return $iv;
1412   }
1413
1414   /**
1415    * Build a valid URL to this instance of Roundcube
1416    *
1417    * @param mixed Either a string with the action or url parameters as key-value pairs
1418    * @return string Valid application URL
1419    */
1420   public function url($p)
1421   {
1422     if (!is_array($p))
1423       $p = array('_action' => @func_get_arg(0));
1424
1425     $task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task);
1426     $p['_task'] = $task;
1427     unset($p['task']);
1428
1429     $url = './';
1430     $delm = '?';
1431     foreach (array_reverse($p) as $key => $val) {
1432       if ($val !== '') {
1433         $par = $key[0] == '_' ? $key : '_'.$key;
1434         $url .= $delm.urlencode($par).'='.urlencode($val);
1435         $delm = '&';
1436       }
1437     }
1438     return $url;
1439   }
1440
1441
1442   /**
1443    * Use imagemagick or GD lib to read image properties
1444    *
1445    * @param string Absolute file path
1446    * @return mixed Hash array with image props like type, width, height or False on error
1447    */
1448   public static function imageprops($filepath)
1449   {
1450     $rcmail = rcmail::get_instance();
1451     if ($cmd = $rcmail->config->get('im_identify_path', false)) {
1452       list(, $type, $size) = explode(' ', strtolower(rcmail::exec($cmd. ' 2>/dev/null {in}', array('in' => $filepath))));
1453       if ($size)
1454         list($width, $height) = explode('x', $size);
1455     }
1456     else if (function_exists('getimagesize')) {
1457       $imsize = @getimagesize($filepath);
1458       $width = $imsize[0];
1459       $height = $imsize[1];
1460       $type = preg_replace('!image/!', '', $imsize['mime']);
1461     }
1462
1463     return $type ? array('type' => $type, 'width' => $width, 'height' => $height) : false;
1464   }
1465
1466
1467   /**
1468    * Convert an image to a given size and type using imagemagick (ensures input is an image)
1469    *
1470    * @param $p['in']  Input filename (mandatory)
1471    * @param $p['out'] Output filename (mandatory)
1472    * @param $p['size']  Width x height of resulting image, e.g. "160x60"
1473    * @param $p['type']  Output file type, e.g. "jpg"
1474    * @param $p['-opts'] Custom command line options to ImageMagick convert
1475    * @return Success of convert as true/false
1476    */
1477   public static function imageconvert($p)
1478   {
1479     $result = false;
1480     $rcmail = rcmail::get_instance();
1481     $convert  = $rcmail->config->get('im_convert_path', false);
1482     $identify = $rcmail->config->get('im_identify_path', false);
1483
1484     // imagemagick is required for this
1485     if (!$convert)
1486         return false;
1487
1488     if (!(($imagetype = @exif_imagetype($p['in'])) && ($type = image_type_to_extension($imagetype, false))))
1489       list(, $type) = explode(' ', strtolower(rcmail::exec($identify . ' 2>/dev/null {in}', $p))); # for things like eps
1490
1491     $type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps"));
1492     $p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75);
1493     $p['-opts'] = array('-resize' => $p['size'].'>') + (array)$p['-opts'];
1494
1495     if (in_array($type, explode(',', $p['types']))) # Valid type?
1496       $result = rcmail::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {in} {type}:{out}', $p) === "";
1497
1498     return $result;
1499   }
1500
1501
1502   /**
1503    * Construct shell command, execute it and return output as string.
1504    * Keywords {keyword} are replaced with arguments
1505    *
1506    * @param $cmd Format string with {keywords} to be replaced
1507    * @param $values (zero, one or more arrays can be passed)
1508    * @return output of command. shell errors not detectable
1509    */
1510   public static function exec(/* $cmd, $values1 = array(), ... */)
1511   {
1512     $args = func_get_args();
1513     $cmd = array_shift($args);
1514     $values = $replacements = array();
1515
1516     // merge values into one array
1517     foreach ($args as $arg)
1518       $values += (array)$arg;
1519
1520     preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
1521     foreach ($matches as $tags) {
1522       list(, $tag, $option, $key) = $tags;
1523       $parts = array();
1524
1525       if ($option) {
1526         foreach ((array)$values["-$key"] as $key => $value) {
1527           if ($value === true || $value === false || $value === null)
1528             $parts[] = $value ? $key : "";
1529           else foreach ((array)$value as $val)
1530             $parts[] = "$key " . escapeshellarg($val);
1531         }
1532       }
1533       else {
1534         foreach ((array)$values[$key] as $value)
1535           $parts[] = escapeshellarg($value);
1536       }
1537
1538       $replacements[$tag] = join(" ", $parts);
1539     }
1540
1541     // use strtr behaviour of going through source string once
1542     $cmd = strtr($cmd, $replacements);
1543     
1544     return (string)shell_exec($cmd);
1545   }
1546
1547
1548   /**
1549    * Helper method to set a cookie with the current path and host settings
1550    *
1551    * @param string Cookie name
1552    * @param string Cookie value
1553    * @param string Expiration time
1554    */
1555   public static function setcookie($name, $value, $exp = 0)
1556   {
1557     if (headers_sent())
1558       return;
1559
1560     $cookie = session_get_cookie_params();
1561
1562     setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'],
1563       rcube_https_check(), true);
1564   }
1565
1566   /**
1567    * Registers action aliases for current task
1568    *
1569    * @param array $map Alias-to-filename hash array
1570    */
1571   public function register_action_map($map)
1572   {
1573     if (is_array($map)) {
1574       foreach ($map as $idx => $val) {
1575         $this->action_map[$idx] = $val;
1576       }
1577     }
1578   }
1579   
1580   /**
1581    * Returns current action filename
1582    *
1583    * @param array $map Alias-to-filename hash array
1584    */
1585   public function get_action_file()
1586   {
1587     if (!empty($this->action_map[$this->action])) {
1588       return $this->action_map[$this->action];
1589     }
1590
1591     return strtr($this->action, '-', '_') . '.inc';
1592   }
1593
1594   /**
1595    * Fixes some user preferences according to namespace handling change.
1596    * Old Roundcube versions were using folder names with removed namespace prefix.
1597    * Now we need to add the prefix on servers where personal namespace has prefix.
1598    *
1599    * @param rcube_user $user User object
1600    */
1601   private function fix_namespace_settings($user)
1602   {
1603     $prefix     = $this->imap->get_namespace('prefix');
1604     $prefix_len = strlen($prefix);
1605
1606     if (!$prefix_len)
1607       return;
1608
1609     $prefs = $user->get_prefs();
1610     if (empty($prefs) || $prefs['namespace_fixed'])
1611       return;
1612
1613     // Build namespace prefix regexp
1614     $ns     = $this->imap->get_namespace();
1615     $regexp = array();
1616
1617     foreach ($ns as $entry) {
1618       if (!empty($entry)) {
1619         foreach ($entry as $item) {
1620           if (strlen($item[0])) {
1621             $regexp[] = preg_quote($item[0], '/');
1622           }
1623         }
1624       }
1625     }
1626     $regexp = '/^('. implode('|', $regexp).')/';
1627
1628     // Fix preferences
1629     $opts = array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox', 'archive_mbox');
1630     foreach ($opts as $opt) {
1631       if ($value = $prefs[$opt]) {
1632         if ($value != 'INBOX' && !preg_match($regexp, $value)) {
1633           $prefs[$opt] = $prefix.$value;
1634         }
1635       }
1636     }
1637
1638     if (!empty($prefs['default_imap_folders'])) {
1639       foreach ($prefs['default_imap_folders'] as $idx => $name) {
1640         if ($name != 'INBOX' && !preg_match($regexp, $name)) {
1641           $prefs['default_imap_folders'][$idx] = $prefix.$name;
1642         }
1643       }
1644     }
1645
1646     if (!empty($prefs['search_mods'])) {
1647       $folders = array();
1648       foreach ($prefs['search_mods'] as $idx => $value) {
1649         if ($idx != 'INBOX' && $idx != '*' && !preg_match($regexp, $idx)) {
1650           $idx = $prefix.$idx;
1651         }
1652         $folders[$idx] = $value;
1653       }
1654       $prefs['search_mods'] = $folders;
1655     }
1656
1657     if (!empty($prefs['message_threading'])) {
1658       $folders = array();
1659       foreach ($prefs['message_threading'] as $idx => $value) {
1660         if ($idx != 'INBOX' && !preg_match($regexp, $idx)) {
1661           $idx = $prefix.$idx;
1662         }
1663         $folders[$prefix.$idx] = $value;
1664       }
1665       $prefs['message_threading'] = $folders;
1666     }
1667
1668     if (!empty($prefs['collapsed_folders'])) {
1669       $folders     = explode('&&', $prefs['collapsed_folders']);
1670       $count       = count($folders);
1671       $folders_str = '';
1672
1673       if ($count) {
1674           $folders[0]        = substr($folders[0], 1);
1675           $folders[$count-1] = substr($folders[$count-1], 0, -1);
1676       }
1677
1678       foreach ($folders as $value) {
1679         if ($value != 'INBOX' && !preg_match($regexp, $value)) {
1680           $value = $prefix.$value;
1681         }
1682         $folders_str .= '&'.$value.'&';
1683       }
1684       $prefs['collapsed_folders'] = $folders_str;
1685     }
1686
1687     $prefs['namespace_fixed'] = true;
1688
1689     // save updated preferences and reset imap settings (default folders)
1690     $user->save_prefs($prefs);
1691     $this->set_imap_prop();
1692   }
1693
1694 }