comparison program/lib/Roundcube/rcube.php @ 0:4681f974d28b

vanilla 1.3.3 distro, I hope
author Charlie Root
date Thu, 04 Jan 2018 15:52:31 -0500
parents
children 3a5f959af5ae
comparison
equal deleted inserted replaced
-1:000000000000 0:4681f974d28b
1 <?php
2
3 /**
4 +-----------------------------------------------------------------------+
5 | This file is part of the Roundcube Webmail client |
6 | Copyright (C) 2008-2014, The Roundcube Dev Team |
7 | Copyright (C) 2011-2014, Kolab Systems AG |
8 | |
9 | Licensed under the GNU General Public License version 3 or |
10 | any later version with exceptions for skins & plugins. |
11 | See the README file for a full license statement. |
12 | |
13 | PURPOSE: |
14 | Framework base class providing core functions and holding |
15 | instances of all 'global' objects like db- and storage-connections |
16 +-----------------------------------------------------------------------+
17 | Author: Thomas Bruederli <roundcube@gmail.com> |
18 +-----------------------------------------------------------------------+
19 */
20
21 /**
22 * Base class of the Roundcube Framework
23 * implemented as singleton
24 *
25 * @package Framework
26 * @subpackage Core
27 */
28 class rcube
29 {
30 // Init options
31 const INIT_WITH_DB = 1;
32 const INIT_WITH_PLUGINS = 2;
33
34 // Request status
35 const REQUEST_VALID = 0;
36 const REQUEST_ERROR_URL = 1;
37 const REQUEST_ERROR_TOKEN = 2;
38
39 const DEBUG_LINE_LENGTH = 4096;
40
41 /**
42 * Singleton instance of rcube
43 *
44 * @var rcube
45 */
46 static protected $instance;
47
48 /**
49 * Stores instance of rcube_config.
50 *
51 * @var rcube_config
52 */
53 public $config;
54
55 /**
56 * Instance of database class.
57 *
58 * @var rcube_db
59 */
60 public $db;
61
62 /**
63 * Instance of Memcache class.
64 *
65 * @var Memcache
66 */
67 public $memcache;
68
69 /**
70 * Instance of rcube_session class.
71 *
72 * @var rcube_session
73 */
74 public $session;
75
76 /**
77 * Instance of rcube_smtp class.
78 *
79 * @var rcube_smtp
80 */
81 public $smtp;
82
83 /**
84 * Instance of rcube_storage class.
85 *
86 * @var rcube_storage
87 */
88 public $storage;
89
90 /**
91 * Instance of rcube_output class.
92 *
93 * @var rcube_output
94 */
95 public $output;
96
97 /**
98 * Instance of rcube_plugin_api.
99 *
100 * @var rcube_plugin_api
101 */
102 public $plugins;
103
104 /**
105 * Instance of rcube_user class.
106 *
107 * @var rcube_user
108 */
109 public $user;
110
111 /**
112 * Request status
113 *
114 * @var int
115 */
116 public $request_status = 0;
117
118 /* private/protected vars */
119 protected $texts;
120 protected $caches = array();
121 protected $shutdown_functions = array();
122
123
124 /**
125 * This implements the 'singleton' design pattern
126 *
127 * @param integer $mode Options to initialize with this instance. See rcube::INIT_WITH_* constants
128 * @param string $env Environment name to run (e.g. live, dev, test)
129 *
130 * @return rcube The one and only instance
131 */
132 static function get_instance($mode = 0, $env = '')
133 {
134 if (!self::$instance) {
135 self::$instance = new rcube($env);
136 self::$instance->init($mode);
137 }
138
139 return self::$instance;
140 }
141
142 /**
143 * Private constructor
144 */
145 protected function __construct($env = '')
146 {
147 // load configuration
148 $this->config = new rcube_config($env);
149 $this->plugins = new rcube_dummy_plugin_api;
150
151 register_shutdown_function(array($this, 'shutdown'));
152 }
153
154 /**
155 * Initial startup function
156 */
157 protected function init($mode = 0)
158 {
159 // initialize syslog
160 if ($this->config->get('log_driver') == 'syslog') {
161 $syslog_id = $this->config->get('syslog_id', 'roundcube');
162 $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
163 openlog($syslog_id, LOG_ODELAY, $syslog_facility);
164 }
165
166 // connect to database
167 if ($mode & self::INIT_WITH_DB) {
168 $this->get_dbh();
169 }
170
171 // create plugin API and load plugins
172 if ($mode & self::INIT_WITH_PLUGINS) {
173 $this->plugins = rcube_plugin_api::get_instance();
174 }
175 }
176
177 /**
178 * Get the current database connection
179 *
180 * @return rcube_db Database object
181 */
182 public function get_dbh()
183 {
184 if (!$this->db) {
185 $this->db = rcube_db::factory(
186 $this->config->get('db_dsnw'),
187 $this->config->get('db_dsnr'),
188 $this->config->get('db_persistent')
189 );
190
191 $this->db->set_debug((bool)$this->config->get('sql_debug'));
192 }
193
194 return $this->db;
195 }
196
197 /**
198 * Get global handle for memcache access
199 *
200 * @return object Memcache
201 */
202 public function get_memcache()
203 {
204 if (!isset($this->memcache)) {
205 // no memcache support in PHP
206 if (!class_exists('Memcache')) {
207 $this->memcache = false;
208 return false;
209 }
210
211 $this->memcache = new Memcache;
212 $this->memcache_init();
213
214 // test connection and failover (will result in $this->mc_available == 0 on complete failure)
215 $this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist
216
217 if (!$this->mc_available) {
218 $this->memcache = false;
219 }
220 }
221
222 return $this->memcache;
223 }
224
225 /**
226 * Get global handle for memcache access
227 *
228 * @return object Memcache
229 */
230 protected function memcache_init()
231 {
232 $this->mc_available = 0;
233
234 // add all configured hosts to pool
235 $pconnect = $this->config->get('memcache_pconnect', true);
236 $timeout = $this->config->get('memcache_timeout', 1);
237 $retry_interval = $this->config->get('memcache_retry_interval', 15);
238
239 foreach ($this->config->get('memcache_hosts', array()) as $host) {
240 if (substr($host, 0, 7) != 'unix://') {
241 list($host, $port) = explode(':', $host);
242 if (!$port) $port = 11211;
243 }
244 else {
245 $port = 0;
246 }
247
248 $this->mc_available += intval($this->memcache->addServer(
249 $host, $port, $pconnect, 1, $timeout, $retry_interval, false, array($this, 'memcache_failure')));
250 }
251 }
252
253 /**
254 * Callback for memcache failure
255 */
256 public function memcache_failure($host, $port)
257 {
258 static $seen = array();
259
260 // only report once
261 if (!$seen["$host:$port"]++) {
262 $this->mc_available--;
263 self::raise_error(array(
264 'code' => 604, 'type' => 'db',
265 'line' => __LINE__, 'file' => __FILE__,
266 'message' => "Memcache failure on host $host:$port"),
267 true, false);
268 }
269 }
270
271 /**
272 * Initialize and get cache object
273 *
274 * @param string $name Cache identifier
275 * @param string $type Cache type ('db', 'apc' or 'memcache')
276 * @param string $ttl Expiration time for cache items
277 * @param bool $packed Enables/disables data serialization
278 *
279 * @return rcube_cache Cache object
280 */
281 public function get_cache($name, $type='db', $ttl=0, $packed=true)
282 {
283 if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) {
284 $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed);
285 }
286
287 return $this->caches[$name];
288 }
289
290 /**
291 * Initialize and get shared cache object
292 *
293 * @param string $name Cache identifier
294 * @param bool $packed Enables/disables data serialization
295 *
296 * @return rcube_cache_shared Cache object
297 */
298 public function get_cache_shared($name, $packed=true)
299 {
300 $shared_name = "shared_$name";
301
302 if (!array_key_exists($shared_name, $this->caches)) {
303 $opt = strtolower($name) . '_cache';
304 $type = $this->config->get($opt);
305 $ttl = $this->config->get($opt . '_ttl');
306
307 if (!$type) {
308 // cache is disabled
309 return $this->caches[$shared_name] = null;
310 }
311
312 if ($ttl === null) {
313 $ttl = $this->config->get('shared_cache_ttl', '10d');
314 }
315
316 $this->caches[$shared_name] = new rcube_cache_shared($type, $name, $ttl, $packed);
317 }
318
319 return $this->caches[$shared_name];
320 }
321
322 /**
323 * Create SMTP object and connect to server
324 *
325 * @param boolean $connect True if connection should be established
326 */
327 public function smtp_init($connect = false)
328 {
329 $this->smtp = new rcube_smtp();
330
331 if ($connect) {
332 $this->smtp->connect();
333 }
334 }
335
336 /**
337 * Initialize and get storage object
338 *
339 * @return rcube_storage Storage object
340 */
341 public function get_storage()
342 {
343 // already initialized
344 if (!is_object($this->storage)) {
345 $this->storage_init();
346 }
347
348 return $this->storage;
349 }
350
351 /**
352 * Initialize storage object
353 */
354 public function storage_init()
355 {
356 // already initialized
357 if (is_object($this->storage)) {
358 return;
359 }
360
361 $driver = $this->config->get('storage_driver', 'imap');
362 $driver_class = "rcube_{$driver}";
363
364 if (!class_exists($driver_class)) {
365 self::raise_error(array(
366 'code' => 700, 'type' => 'php',
367 'file' => __FILE__, 'line' => __LINE__,
368 'message' => "Storage driver class ($driver) not found!"),
369 true, true);
370 }
371
372 // Initialize storage object
373 $this->storage = new $driver_class;
374
375 // for backward compat. (deprecated, will be removed)
376 $this->imap = $this->storage;
377
378 // set class options
379 $options = array(
380 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'),
381 'auth_cid' => $this->config->get("{$driver}_auth_cid"),
382 'auth_pw' => $this->config->get("{$driver}_auth_pw"),
383 'debug' => (bool) $this->config->get("{$driver}_debug"),
384 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"),
385 'disabled_caps' => $this->config->get("{$driver}_disabled_caps"),
386 'socket_options' => $this->config->get("{$driver}_conn_options"),
387 'timeout' => (int) $this->config->get("{$driver}_timeout"),
388 'skip_deleted' => (bool) $this->config->get('skip_deleted'),
389 'driver' => $driver,
390 );
391
392 if (!empty($_SESSION['storage_host'])) {
393 $options['language'] = $_SESSION['language'];
394 $options['host'] = $_SESSION['storage_host'];
395 $options['user'] = $_SESSION['username'];
396 $options['port'] = $_SESSION['storage_port'];
397 $options['ssl'] = $_SESSION['storage_ssl'];
398 $options['password'] = $this->decrypt($_SESSION['password']);
399 $_SESSION[$driver.'_host'] = $_SESSION['storage_host'];
400 }
401
402 $options = $this->plugins->exec_hook("storage_init", $options);
403
404 // for backward compat. (deprecated, to be removed)
405 $options = $this->plugins->exec_hook("imap_init", $options);
406
407 $this->storage->set_options($options);
408 $this->set_storage_prop();
409
410 // subscribe to 'storage_connected' hook for session logging
411 if ($this->config->get('imap_log_session', false)) {
412 $this->plugins->register_hook('storage_connected', array($this, 'storage_log_session'));
413 }
414 }
415
416 /**
417 * Set storage parameters.
418 */
419 protected function set_storage_prop()
420 {
421 $storage = $this->get_storage();
422
423 // set pagesize from config
424 $pagesize = $this->config->get('mail_pagesize');
425 if (!$pagesize) {
426 $pagesize = $this->config->get('pagesize', 50);
427 }
428
429 $storage->set_pagesize($pagesize);
430 $storage->set_charset($this->config->get('default_charset', RCUBE_CHARSET));
431
432 // enable caching of mail data
433 $driver = $this->config->get('storage_driver', 'imap');
434 $storage_cache = $this->config->get("{$driver}_cache");
435 $messages_cache = $this->config->get('messages_cache');
436 // for backward compatybility
437 if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
438 $storage_cache = 'db';
439 $messages_cache = true;
440 }
441
442 if ($storage_cache) {
443 $storage->set_caching($storage_cache);
444 }
445 if ($messages_cache) {
446 $storage->set_messages_caching(true);
447 }
448 }
449
450 /**
451 * Set special folders type association.
452 * This must be done AFTER connecting to the server!
453 */
454 protected function set_special_folders()
455 {
456 $storage = $this->get_storage();
457 $folders = $storage->get_special_folders(true);
458 $prefs = array();
459
460 // check SPECIAL-USE flags on IMAP folders
461 foreach ($folders as $type => $folder) {
462 $idx = $type . '_mbox';
463 if ($folder !== $this->config->get($idx)) {
464 $prefs[$idx] = $folder;
465 }
466 }
467
468 // Some special folders differ, update user preferences
469 if (!empty($prefs) && $this->user) {
470 $this->user->save_prefs($prefs);
471 }
472
473 // create default folders (on login)
474 if ($this->config->get('create_default_folders')) {
475 $storage->create_default_folders();
476 }
477 }
478
479 /**
480 * Callback for IMAP connection events to log session identifiers
481 */
482 public function storage_log_session($args)
483 {
484 if (!empty($args['session']) && session_id()) {
485 $this->write_log('imap_session', $args['session']);
486 }
487 }
488
489 /**
490 * Create session object and start the session.
491 */
492 public function session_init()
493 {
494 // session started (Installer?)
495 if (session_id()) {
496 return;
497 }
498
499 $sess_name = $this->config->get('session_name');
500 $sess_domain = $this->config->get('session_domain');
501 $sess_path = $this->config->get('session_path');
502 $lifetime = $this->config->get('session_lifetime', 0) * 60;
503 $is_secure = $this->config->get('use_https') || rcube_utils::https_check();
504
505 // set session domain
506 if ($sess_domain) {
507 ini_set('session.cookie_domain', $sess_domain);
508 }
509 // set session path
510 if ($sess_path) {
511 ini_set('session.cookie_path', $sess_path);
512 }
513 // set session garbage collecting time according to session_lifetime
514 if ($lifetime) {
515 ini_set('session.gc_maxlifetime', $lifetime * 2);
516 }
517
518 // set session cookie lifetime so it never expires (#5961)
519 ini_set('session.cookie_lifetime', 0);
520 ini_set('session.cookie_secure', $is_secure);
521 ini_set('session.name', $sess_name ?: 'roundcube_sessid');
522 ini_set('session.use_cookies', 1);
523 ini_set('session.use_only_cookies', 1);
524 ini_set('session.cookie_httponly', 1);
525
526 // get session driver instance
527 $this->session = rcube_session::factory($this->config);
528 $this->session->register_gc_handler(array($this, 'gc'));
529
530 // start PHP session (if not in CLI mode)
531 if ($_SERVER['REMOTE_ADDR']) {
532 $this->session->start();
533 }
534 }
535
536 /**
537 * Garbage collector - cache/temp cleaner
538 */
539 public function gc()
540 {
541 rcube_cache::gc();
542 rcube_cache_shared::gc();
543 $this->get_storage()->cache_gc();
544
545 $this->gc_temp();
546 }
547
548 /**
549 * Garbage collector function for temp files.
550 * Remove temp files older than two days
551 */
552 public function gc_temp()
553 {
554 $tmp = unslashify($this->config->get('temp_dir'));
555
556 // expire in 48 hours by default
557 $temp_dir_ttl = $this->config->get('temp_dir_ttl', '48h');
558 $temp_dir_ttl = get_offset_sec($temp_dir_ttl);
559 if ($temp_dir_ttl < 6*3600)
560 $temp_dir_ttl = 6*3600; // 6 hours sensible lower bound.
561
562 $expire = time() - $temp_dir_ttl;
563
564 if ($tmp && ($dir = opendir($tmp))) {
565 while (($fname = readdir($dir)) !== false) {
566 if ($fname[0] == '.') {
567 continue;
568 }
569
570 if (@filemtime($tmp.'/'.$fname) < $expire) {
571 @unlink($tmp.'/'.$fname);
572 }
573 }
574
575 closedir($dir);
576 }
577 }
578
579 /**
580 * Runs garbage collector with probability based on
581 * session settings. This is intended for environments
582 * without a session.
583 */
584 public function gc_run()
585 {
586 $probability = (int) ini_get('session.gc_probability');
587 $divisor = (int) ini_get('session.gc_divisor');
588
589 if ($divisor > 0 && $probability > 0) {
590 $random = mt_rand(1, $divisor);
591 if ($random <= $probability) {
592 $this->gc();
593 }
594 }
595 }
596
597 /**
598 * Get localized text in the desired language
599 *
600 * @param mixed $attrib Named parameters array or label name
601 * @param string $domain Label domain (plugin) name
602 *
603 * @return string Localized text
604 */
605 public function gettext($attrib, $domain = null)
606 {
607 // load localization files if not done yet
608 if (empty($this->texts)) {
609 $this->load_language();
610 }
611
612 // extract attributes
613 if (is_string($attrib)) {
614 $attrib = array('name' => $attrib);
615 }
616
617 $name = (string) $attrib['name'];
618
619 // attrib contain text values: use them from now
620 if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us'])) {
621 $this->texts[$name] = $setval;
622 }
623
624 // check for text with domain
625 if ($domain && ($text = $this->texts[$domain.'.'.$name])) {
626 }
627 // text does not exist
628 else if (!($text = $this->texts[$name])) {
629 return "[$name]";
630 }
631
632 // replace vars in text
633 if (is_array($attrib['vars'])) {
634 foreach ($attrib['vars'] as $var_key => $var_value) {
635 $text = str_replace($var_key[0] != '$' ? '$'.$var_key : $var_key, $var_value, $text);
636 }
637 }
638
639 // replace \n with real line break
640 $text = strtr($text, array('\n' => "\n"));
641
642 // case folding
643 if (($attrib['uppercase'] && strtolower($attrib['uppercase']) == 'first') || $attrib['ucfirst']) {
644 $case_mode = MB_CASE_TITLE;
645 }
646 else if ($attrib['uppercase']) {
647 $case_mode = MB_CASE_UPPER;
648 }
649 else if ($attrib['lowercase']) {
650 $case_mode = MB_CASE_LOWER;
651 }
652
653 if (isset($case_mode)) {
654 $text = mb_convert_case($text, $case_mode);
655 }
656
657 return $text;
658 }
659
660 /**
661 * Check if the given text label exists
662 *
663 * @param string $name Label name
664 * @param string $domain Label domain (plugin) name or '*' for all domains
665 * @param string $ref_domain Sets domain name if label is found
666 *
667 * @return boolean True if text exists (either in the current language or in en_US)
668 */
669 public function text_exists($name, $domain = null, &$ref_domain = null)
670 {
671 // load localization files if not done yet
672 if (empty($this->texts)) {
673 $this->load_language();
674 }
675
676 if (isset($this->texts[$name])) {
677 $ref_domain = '';
678 return true;
679 }
680
681 // any of loaded domains (plugins)
682 if ($domain == '*') {
683 foreach ($this->plugins->loaded_plugins() as $domain) {
684 if (isset($this->texts[$domain.'.'.$name])) {
685 $ref_domain = $domain;
686 return true;
687 }
688 }
689 }
690 // specified domain
691 else if ($domain) {
692 $ref_domain = $domain;
693 return isset($this->texts[$domain.'.'.$name]);
694 }
695
696 return false;
697 }
698
699 /**
700 * Load a localization package
701 *
702 * @param string $lang Language ID
703 * @param array $add Additional text labels/messages
704 * @param array $merge Additional text labels/messages to merge
705 */
706 public function load_language($lang = null, $add = array(), $merge = array())
707 {
708 $lang = $this->language_prop($lang ?: $_SESSION['language']);
709
710 // load localized texts
711 if (empty($this->texts) || $lang != $_SESSION['language']) {
712 $this->texts = array();
713
714 // handle empty lines after closing PHP tag in localization files
715 ob_start();
716
717 // get english labels (these should be complete)
718 @include(RCUBE_LOCALIZATION_DIR . 'en_US/labels.inc');
719 @include(RCUBE_LOCALIZATION_DIR . 'en_US/messages.inc');
720
721 if (is_array($labels))
722 $this->texts = $labels;
723 if (is_array($messages))
724 $this->texts = array_merge($this->texts, $messages);
725
726 // include user language files
727 if ($lang != 'en' && $lang != 'en_US' && is_dir(RCUBE_LOCALIZATION_DIR . $lang)) {
728 include_once(RCUBE_LOCALIZATION_DIR . $lang . '/labels.inc');
729 include_once(RCUBE_LOCALIZATION_DIR . $lang . '/messages.inc');
730
731 if (is_array($labels))
732 $this->texts = array_merge($this->texts, $labels);
733 if (is_array($messages))
734 $this->texts = array_merge($this->texts, $messages);
735 }
736
737 ob_end_clean();
738
739 $_SESSION['language'] = $lang;
740 }
741
742 // append additional texts (from plugin)
743 if (is_array($add) && !empty($add)) {
744 $this->texts += $add;
745 }
746
747 // merge additional texts (from plugin)
748 if (is_array($merge) && !empty($merge)) {
749 $this->texts = array_merge($this->texts, $merge);
750 }
751 }
752
753 /**
754 * Check the given string and return a valid language code
755 *
756 * @param string $lang Language code
757 *
758 * @return string Valid language code
759 */
760 protected function language_prop($lang)
761 {
762 static $rcube_languages, $rcube_language_aliases;
763
764 // user HTTP_ACCEPT_LANGUAGE if no language is specified
765 if (empty($lang) || $lang == 'auto') {
766 $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
767 $lang = $accept_langs[0];
768
769 if (preg_match('/^([a-z]+)[_-]([a-z]+)$/i', $lang, $m)) {
770 $lang = $m[1] . '_' . strtoupper($m[2]);
771 }
772 }
773
774 if (empty($rcube_languages)) {
775 @include(RCUBE_LOCALIZATION_DIR . 'index.inc');
776 }
777
778 // check if we have an alias for that language
779 if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
780 $lang = $rcube_language_aliases[$lang];
781 }
782 // try the first two chars
783 else if (!isset($rcube_languages[$lang])) {
784 $short = substr($lang, 0, 2);
785
786 // check if we have an alias for the short language code
787 if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
788 $lang = $rcube_language_aliases[$short];
789 }
790 // expand 'nn' to 'nn_NN'
791 else if (!isset($rcube_languages[$short])) {
792 $lang = $short.'_'.strtoupper($short);
793 }
794 }
795
796 if (!isset($rcube_languages[$lang]) || !is_dir(RCUBE_LOCALIZATION_DIR . $lang)) {
797 $lang = 'en_US';
798 }
799
800 return $lang;
801 }
802
803 /**
804 * Read directory program/localization and return a list of available languages
805 *
806 * @return array List of available localizations
807 */
808 public function list_languages()
809 {
810 static $sa_languages = array();
811
812 if (!count($sa_languages)) {
813 @include(RCUBE_LOCALIZATION_DIR . 'index.inc');
814
815 if ($dh = @opendir(RCUBE_LOCALIZATION_DIR)) {
816 while (($name = readdir($dh)) !== false) {
817 if ($name[0] == '.' || !is_dir(RCUBE_LOCALIZATION_DIR . $name)) {
818 continue;
819 }
820
821 if ($label = $rcube_languages[$name]) {
822 $sa_languages[$name] = $label;
823 }
824 }
825 closedir($dh);
826 }
827 }
828
829 return $sa_languages;
830 }
831
832 /**
833 * Encrypt a string
834 *
835 * @param string $clear Clear text input
836 * @param string $key Encryption key to retrieve from the configuration, defaults to 'des_key'
837 * @param boolean $base64 Whether or not to base64_encode() the result before returning
838 *
839 * @return string Encrypted text
840 */
841 public function encrypt($clear, $key = 'des_key', $base64 = true)
842 {
843 if (!is_string($clear) || !strlen($clear)) {
844 return '';
845 }
846
847 $ckey = $this->config->get_crypto_key($key);
848 $method = $this->config->get_crypto_method();
849 $opts = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true;
850 $iv = rcube_utils::random_bytes(openssl_cipher_iv_length($method), true);
851 $cipher = $iv . openssl_encrypt($clear, $method, $ckey, $opts, $iv);
852
853 return $base64 ? base64_encode($cipher) : $cipher;
854 }
855
856 /**
857 * Decrypt a string
858 *
859 * @param string $cipher Encrypted text
860 * @param string $key Encryption key to retrieve from the configuration, defaults to 'des_key'
861 * @param boolean $base64 Whether or not input is base64-encoded
862 *
863 * @return string Decrypted text
864 */
865 public function decrypt($cipher, $key = 'des_key', $base64 = true)
866 {
867 if (!$cipher) {
868 return '';
869 }
870
871 $cipher = $base64 ? base64_decode($cipher) : $cipher;
872 $ckey = $this->config->get_crypto_key($key);
873 $method = $this->config->get_crypto_method();
874 $opts = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true;
875 $iv_size = openssl_cipher_iv_length($method);
876 $iv = substr($cipher, 0, $iv_size);
877
878 // session corruption? (#1485970)
879 if (strlen($iv) < $iv_size) {
880 return '';
881 }
882
883 $cipher = substr($cipher, $iv_size);
884 $clear = openssl_decrypt($cipher, $method, $ckey, $opts, $iv);
885
886 return $clear;
887 }
888
889 /**
890 * Returns session token for secure URLs
891 *
892 * @param bool $generate Generate token if not exists in session yet
893 *
894 * @return string|bool Token string, False when disabled
895 */
896 public function get_secure_url_token($generate = false)
897 {
898 if ($len = $this->config->get('use_secure_urls')) {
899 if (empty($_SESSION['secure_token']) && $generate) {
900 // generate x characters long token
901 $length = $len > 1 ? $len : 16;
902 $token = rcube_utils::random_bytes($length);
903
904 $plugin = $this->plugins->exec_hook('secure_token',
905 array('value' => $token, 'length' => $length));
906
907 $_SESSION['secure_token'] = $plugin['value'];
908 }
909
910 return $_SESSION['secure_token'];
911 }
912
913 return false;
914 }
915
916 /**
917 * Generate a unique token to be used in a form request
918 *
919 * @return string The request token
920 */
921 public function get_request_token()
922 {
923 if (empty($_SESSION['request_token'])) {
924 $plugin = $this->plugins->exec_hook('request_token', array(
925 'value' => rcube_utils::random_bytes(32)));
926
927 $_SESSION['request_token'] = $plugin['value'];
928 }
929
930 return $_SESSION['request_token'];
931 }
932
933 /**
934 * Check if the current request contains a valid token.
935 * Empty requests aren't checked until use_secure_urls is set.
936 *
937 * @param int $mode Request method
938 *
939 * @return boolean True if request token is valid false if not
940 */
941 public function check_request($mode = rcube_utils::INPUT_POST)
942 {
943 // check secure token in URL if enabled
944 if ($token = $this->get_secure_url_token()) {
945 foreach (explode('/', preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI'])) as $tok) {
946 if ($tok == $token) {
947 return true;
948 }
949 }
950
951 $this->request_status = self::REQUEST_ERROR_URL;
952
953 return false;
954 }
955
956 $sess_tok = $this->get_request_token();
957
958 // ajax requests
959 if (rcube_utils::request_header('X-Roundcube-Request') == $sess_tok) {
960 return true;
961 }
962
963 // skip empty requests
964 if (($mode == rcube_utils::INPUT_POST && empty($_POST))
965 || ($mode == rcube_utils::INPUT_GET && empty($_GET))
966 ) {
967 return true;
968 }
969
970 // default method of securing requests
971 $token = rcube_utils::get_input_value('_token', $mode);
972 $sess_id = $_COOKIE[ini_get('session.name')];
973
974 if (empty($sess_id) || $token != $sess_tok) {
975 $this->request_status = self::REQUEST_ERROR_TOKEN;
976 return false;
977 }
978
979 return true;
980 }
981
982 /**
983 * Build a valid URL to this instance of Roundcube
984 *
985 * @param mixed $p Either a string with the action or url parameters as key-value pairs
986 *
987 * @return string Valid application URL
988 */
989 public function url($p)
990 {
991 // STUB: should be overloaded by the application
992 return '';
993 }
994
995 /**
996 * Function to be executed in script shutdown
997 * Registered with register_shutdown_function()
998 */
999 public function shutdown()
1000 {
1001 foreach ($this->shutdown_functions as $function) {
1002 call_user_func($function);
1003 }
1004
1005 // write session data as soon as possible and before
1006 // closing database connection, don't do this before
1007 // registered shutdown functions, they may need the session
1008 // Note: this will run registered gc handlers (ie. cache gc)
1009 if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
1010 $this->session->write_close();
1011 }
1012
1013 if (is_object($this->smtp)) {
1014 $this->smtp->disconnect();
1015 }
1016
1017 foreach ($this->caches as $cache) {
1018 if (is_object($cache)) {
1019 $cache->close();
1020 }
1021 }
1022
1023 if (is_object($this->storage)) {
1024 $this->storage->close();
1025 }
1026
1027 if ($this->config->get('log_driver') == 'syslog') {
1028 closelog();
1029 }
1030 }
1031
1032 /**
1033 * Registers shutdown function to be executed on shutdown.
1034 * The functions will be executed before destroying any
1035 * objects like smtp, imap, session, etc.
1036 *
1037 * @param callback $function Function callback
1038 */
1039 public function add_shutdown_function($function)
1040 {
1041 $this->shutdown_functions[] = $function;
1042 }
1043
1044 /**
1045 * When you're going to sleep the script execution for a longer time
1046 * it is good to close all external connections (sql, memcache, SMTP, IMAP).
1047 *
1048 * No action is required on wake up, all connections will be
1049 * re-established automatically.
1050 */
1051 public function sleep()
1052 {
1053 foreach ($this->caches as $cache) {
1054 if (is_object($cache)) {
1055 $cache->close();
1056 }
1057 }
1058
1059 if ($this->storage) {
1060 $this->storage->close();
1061 }
1062
1063 if ($this->db) {
1064 $this->db->closeConnection();
1065 }
1066
1067 if ($this->memcache) {
1068 $this->memcache->close();
1069 // after close() need to re-init memcache
1070 $this->memcache_init();
1071 }
1072
1073 if ($this->smtp) {
1074 $this->smtp->disconnect();
1075 }
1076 }
1077
1078 /**
1079 * Quote a given string.
1080 * Shortcut function for rcube_utils::rep_specialchars_output()
1081 *
1082 * @return string HTML-quoted string
1083 */
1084 public static function Q($str, $mode = 'strict', $newlines = true)
1085 {
1086 return rcube_utils::rep_specialchars_output($str, 'html', $mode, $newlines);
1087 }
1088
1089 /**
1090 * Quote a given string for javascript output.
1091 * Shortcut function for rcube_utils::rep_specialchars_output()
1092 *
1093 * @return string JS-quoted string
1094 */
1095 public static function JQ($str)
1096 {
1097 return rcube_utils::rep_specialchars_output($str, 'js');
1098 }
1099
1100 /**
1101 * Construct shell command, execute it and return output as string.
1102 * Keywords {keyword} are replaced with arguments
1103 *
1104 * @param $cmd Format string with {keywords} to be replaced
1105 * @param $values (zero, one or more arrays can be passed)
1106 *
1107 * @return output of command. shell errors not detectable
1108 */
1109 public static function exec(/* $cmd, $values1 = array(), ... */)
1110 {
1111 $args = func_get_args();
1112 $cmd = array_shift($args);
1113 $values = $replacements = array();
1114
1115 // merge values into one array
1116 foreach ($args as $arg) {
1117 $values += (array)$arg;
1118 }
1119
1120 preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
1121 foreach ($matches as $tags) {
1122 list(, $tag, $option, $key) = $tags;
1123 $parts = array();
1124
1125 if ($option) {
1126 foreach ((array)$values["-$key"] as $key => $value) {
1127 if ($value === true || $value === false || $value === null) {
1128 $parts[] = $value ? $key : "";
1129 }
1130 else {
1131 foreach ((array)$value as $val) {
1132 $parts[] = "$key " . escapeshellarg($val);
1133 }
1134 }
1135 }
1136 }
1137 else {
1138 foreach ((array)$values[$key] as $value) {
1139 $parts[] = escapeshellarg($value);
1140 }
1141 }
1142
1143 $replacements[$tag] = join(" ", $parts);
1144 }
1145
1146 // use strtr behaviour of going through source string once
1147 $cmd = strtr($cmd, $replacements);
1148
1149 return (string)shell_exec($cmd);
1150 }
1151
1152 /**
1153 * Print or write debug messages
1154 *
1155 * @param mixed Debug message or data
1156 */
1157 public static function console()
1158 {
1159 $args = func_get_args();
1160
1161 if (class_exists('rcube', false)) {
1162 $rcube = self::get_instance();
1163 $plugin = $rcube->plugins->exec_hook('console', array('args' => $args));
1164 if ($plugin['abort']) {
1165 return;
1166 }
1167
1168 $args = $plugin['args'];
1169 }
1170
1171 $msg = array();
1172 foreach ($args as $arg) {
1173 $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
1174 }
1175
1176 self::write_log('console', join(";\n", $msg));
1177 }
1178
1179 /**
1180 * Append a line to a logfile in the logs directory.
1181 * Date will be added automatically to the line.
1182 *
1183 * @param string $name Name of the log file
1184 * @param mixed $line Line to append
1185 *
1186 * @return bool True on success, False on failure
1187 */
1188 public static function write_log($name, $line)
1189 {
1190 if (!is_string($line)) {
1191 $line = var_export($line, true);
1192 }
1193
1194 $date_format = $log_driver = $session_key = null;
1195 if (self::$instance) {
1196 $date_format = self::$instance->config->get('log_date_format');
1197 $log_driver = self::$instance->config->get('log_driver');
1198 $session_key = intval(self::$instance->config->get('log_session_id', 8));
1199 }
1200
1201 $date = rcube_utils::date_format($date_format);
1202
1203 // trigger logging hook
1204 if (is_object(self::$instance) && is_object(self::$instance->plugins)) {
1205 $log = self::$instance->plugins->exec_hook('write_log',
1206 array('name' => $name, 'date' => $date, 'line' => $line));
1207
1208 $name = $log['name'];
1209 $line = $log['line'];
1210 $date = $log['date'];
1211
1212 if ($log['abort']) {
1213 return true;
1214 }
1215 }
1216
1217 // add session ID to the log
1218 if ($session_key > 0 && ($sess = session_id())) {
1219 $line = '<' . substr($sess, 0, $session_key) . '> ' . $line;
1220 }
1221
1222 if ($log_driver == 'syslog') {
1223 $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1224 return syslog($prio, $line);
1225 }
1226
1227 // write message with file name when configured to log to STDOUT
1228 if ($log_driver == 'stdout') {
1229 $stdout = "php://stdout";
1230 $line = "$name: $line";
1231 return file_put_contents($stdout, $line, FILE_APPEND) !== false;
1232 }
1233
1234 // log_driver == 'file' is assumed here
1235
1236 $line = sprintf("[%s]: %s\n", $date, $line);
1237
1238 // per-user logging is activated
1239 if (self::$instance && self::$instance->config->get('per_user_logging') && self::$instance->get_user_id()) {
1240 $log_dir = self::$instance->get_user_log_dir();
1241 if (empty($log_dir) && !in_array($name, array('errors', 'userlogins', 'sendmail'))) {
1242 return false;
1243 }
1244 }
1245
1246 if (empty($log_dir)) {
1247 if (!empty($log['dir'])) {
1248 $log_dir = $log['dir'];
1249 }
1250 else if (self::$instance) {
1251 $log_dir = self::$instance->config->get('log_dir');
1252 }
1253 }
1254
1255 if (empty($log_dir)) {
1256 $log_dir = RCUBE_INSTALL_PATH . 'logs';
1257 }
1258
1259 return file_put_contents("$log_dir/$name", $line, FILE_APPEND) !== false;
1260 }
1261
1262 /**
1263 * Throw system error (and show error page).
1264 *
1265 * @param array $arg Named parameters
1266 * - code: Error code (required)
1267 * - type: Error type [php|db|imap|javascript]
1268 * - message: Error message
1269 * - file: File where error occurred
1270 * - line: Line where error occurred
1271 * @param boolean $log True to log the error
1272 * @param boolean $terminate Terminate script execution
1273 */
1274 public static function raise_error($arg = array(), $log = false, $terminate = false)
1275 {
1276 // handle PHP exceptions
1277 if (is_object($arg) && is_a($arg, 'Exception')) {
1278 $arg = array(
1279 'code' => $arg->getCode(),
1280 'line' => $arg->getLine(),
1281 'file' => $arg->getFile(),
1282 'message' => $arg->getMessage(),
1283 );
1284 }
1285 else if (is_string($arg)) {
1286 $arg = array('message' => $arg);
1287 }
1288
1289 if (empty($arg['code'])) {
1290 $arg['code'] = 500;
1291 }
1292
1293 $cli = php_sapi_name() == 'cli';
1294
1295 // installer
1296 if (!$cli && class_exists('rcmail_install', false)) {
1297 $rci = rcmail_install::get_instance();
1298 $rci->raise_error($arg);
1299 return;
1300 }
1301
1302 if (($log || $terminate) && !$cli && $arg['message']) {
1303 $arg['fatal'] = $terminate;
1304 self::log_bug($arg);
1305 }
1306
1307 // terminate script
1308 if ($terminate) {
1309 // display error page
1310 if (is_object(self::$instance->output)) {
1311 self::$instance->output->raise_error($arg['code'], $arg['message']);
1312 }
1313 else if ($cli) {
1314 fwrite(STDERR, 'ERROR: ' . $arg['message']);
1315 }
1316
1317 exit(1);
1318 }
1319 else if ($cli) {
1320 fwrite(STDERR, 'ERROR: ' . $arg['message']);
1321 }
1322 }
1323
1324 /**
1325 * Report error according to configured debug_level
1326 *
1327 * @param array $arg_arr Named parameters
1328 * @see self::raise_error()
1329 */
1330 public static function log_bug($arg_arr)
1331 {
1332 $program = strtoupper($arg_arr['type'] ?: 'php');
1333 $level = self::get_instance()->config->get('debug_level');
1334
1335 // disable errors for ajax requests, write to log instead (#1487831)
1336 if (($level & 4) && !empty($_REQUEST['_remote'])) {
1337 $level = ($level ^ 4) | 1;
1338 }
1339
1340 // write error to local log file
1341 if (($level & 1) || !empty($arg_arr['fatal'])) {
1342 if ($_SERVER['REQUEST_METHOD'] == 'POST') {
1343 foreach (array('_task', '_action') as $arg) {
1344 if ($_POST[$arg] && !$_GET[$arg]) {
1345 $post_query[$arg] = $_POST[$arg];
1346 }
1347 }
1348
1349 if (!empty($post_query)) {
1350 $post_query = (strpos($_SERVER['REQUEST_URI'], '?') != false ? '&' : '?')
1351 . http_build_query($post_query, '', '&');
1352 }
1353 }
1354
1355 $log_entry = sprintf("%s Error: %s%s (%s %s)",
1356 $program,
1357 $arg_arr['message'],
1358 $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
1359 $_SERVER['REQUEST_METHOD'],
1360 $_SERVER['REQUEST_URI'] . $post_query);
1361
1362 if (!self::write_log('errors', $log_entry)) {
1363 // send error to PHPs error handler if write_log didn't succeed
1364 trigger_error($arg_arr['message'], E_USER_WARNING);
1365 }
1366 }
1367
1368 // report the bug to the global bug reporting system
1369 if ($level & 2) {
1370 // TODO: Send error via HTTP
1371 }
1372
1373 // show error if debug_mode is on
1374 if ($level & 4) {
1375 print "<b>$program Error";
1376
1377 if (!empty($arg_arr['file']) && !empty($arg_arr['line'])) {
1378 print " in $arg_arr[file] ($arg_arr[line])";
1379 }
1380
1381 print ':</b>&nbsp;';
1382 print nl2br($arg_arr['message']);
1383 print '<br />';
1384 flush();
1385 }
1386 }
1387
1388 /**
1389 * Write debug info to the log
1390 *
1391 * @param string $engine Engine type - file name (memcache, apc)
1392 * @param string $data Data string to log
1393 * @param bool $result Operation result
1394 */
1395 public static function debug($engine, $data, $result = null)
1396 {
1397 static $debug_counter;
1398
1399 $line = '[' . (++$debug_counter[$engine]) . '] ' . $data;
1400
1401 if (($len = strlen($line)) > self::DEBUG_LINE_LENGTH) {
1402 $diff = $len - self::DEBUG_LINE_LENGTH;
1403 $line = substr($line, 0, self::DEBUG_LINE_LENGTH) . "... [truncated $diff bytes]";
1404 }
1405
1406 if ($result !== null) {
1407 $line .= ' [' . ($result ? 'TRUE' : 'FALSE') . ']';
1408 }
1409
1410 self::write_log($engine, $line);
1411 }
1412
1413 /**
1414 * Returns current time (with microseconds).
1415 *
1416 * @return float Current time in seconds since the Unix
1417 */
1418 public static function timer()
1419 {
1420 return microtime(true);
1421 }
1422
1423 /**
1424 * Logs time difference according to provided timer
1425 *
1426 * @param float $timer Timer (self::timer() result)
1427 * @param string $label Log line prefix
1428 * @param string $dest Log file name
1429 *
1430 * @see self::timer()
1431 */
1432 public static function print_timer($timer, $label = 'Timer', $dest = 'console')
1433 {
1434 static $print_count = 0;
1435
1436 $print_count++;
1437 $now = self::timer();
1438 $diff = $now - $timer;
1439
1440 if (empty($label)) {
1441 $label = 'Timer '.$print_count;
1442 }
1443
1444 self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1445 }
1446
1447 /**
1448 * Setter for system user object
1449 *
1450 * @param rcube_user Current user instance
1451 */
1452 public function set_user($user)
1453 {
1454 if (is_object($user)) {
1455 $this->user = $user;
1456
1457 // overwrite config with user preferences
1458 $this->config->set_user_prefs((array)$this->user->get_prefs());
1459 }
1460 }
1461
1462 /**
1463 * Getter for logged user ID.
1464 *
1465 * @return mixed User identifier
1466 */
1467 public function get_user_id()
1468 {
1469 if (is_object($this->user)) {
1470 return $this->user->ID;
1471 }
1472 else if (isset($_SESSION['user_id'])) {
1473 return $_SESSION['user_id'];
1474 }
1475
1476 return null;
1477 }
1478
1479 /**
1480 * Getter for logged user name.
1481 *
1482 * @return string User name
1483 */
1484 public function get_user_name()
1485 {
1486 if (is_object($this->user)) {
1487 return $this->user->get_username();
1488 }
1489 else if (isset($_SESSION['username'])) {
1490 return $_SESSION['username'];
1491 }
1492 }
1493
1494 /**
1495 * Getter for logged user email (derived from user name not identity).
1496 *
1497 * @return string User email address
1498 */
1499 public function get_user_email()
1500 {
1501 if (is_object($this->user)) {
1502 return $this->user->get_username('mail');
1503 }
1504 }
1505
1506 /**
1507 * Getter for logged user password.
1508 *
1509 * @return string User password
1510 */
1511 public function get_user_password()
1512 {
1513 if ($this->password) {
1514 return $this->password;
1515 }
1516 else if ($_SESSION['password']) {
1517 return $this->decrypt($_SESSION['password']);
1518 }
1519 }
1520
1521 /**
1522 * Get the per-user log directory
1523 */
1524 protected function get_user_log_dir()
1525 {
1526 $log_dir = $this->config->get('log_dir', RCUBE_INSTALL_PATH . 'logs');
1527 $user_name = $this->get_user_name();
1528 $user_log_dir = $log_dir . '/' . $user_name;
1529
1530 return !empty($user_name) && is_writable($user_log_dir) ? $user_log_dir : false;
1531 }
1532
1533 /**
1534 * Getter for logged user language code.
1535 *
1536 * @return string User language code
1537 */
1538 public function get_user_language()
1539 {
1540 if (is_object($this->user)) {
1541 return $this->user->language;
1542 }
1543 else if (isset($_SESSION['language'])) {
1544 return $_SESSION['language'];
1545 }
1546 }
1547
1548 /**
1549 * Unique Message-ID generator.
1550 *
1551 * @param string $sender Optional sender e-mail address
1552 *
1553 * @return string Message-ID
1554 */
1555 public function gen_message_id($sender = null)
1556 {
1557 $local_part = md5(uniqid('rcube'.mt_rand(), true));
1558 $domain_part = '';
1559
1560 if ($sender && preg_match('/@([^\s]+\.[a-z0-9-]+)/', $sender, $m)) {
1561 $domain_part = $m[1];
1562 }
1563 else {
1564 $domain_part = $this->user->get_username('domain');
1565 }
1566
1567 // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
1568 if (!preg_match('/\.[a-z0-9-]+$/i', $domain_part)) {
1569 foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) {
1570 $host = preg_replace('/:[0-9]+$/', '', $host);
1571 if ($host && preg_match('/\.[a-z]+$/i', $host)) {
1572 $domain_part = $host;
1573 break;
1574 }
1575 }
1576 }
1577
1578 return sprintf('<%s@%s>', $local_part, $domain_part);
1579 }
1580
1581 /**
1582 * Send the given message using the configured method.
1583 *
1584 * @param object $message Reference to Mail_MIME object
1585 * @param string $from Sender address string
1586 * @param array $mailto Array of recipient address strings
1587 * @param array $error SMTP error array (reference)
1588 * @param string $body_file Location of file with saved message body (reference),
1589 * used when delay_file_io is enabled
1590 * @param array $options SMTP options (e.g. DSN request)
1591 * @param bool $disconnect Close SMTP connection ASAP
1592 *
1593 * @return boolean Send status.
1594 */
1595 public function deliver_message(&$message, $from, $mailto, &$error,
1596 &$body_file = null, $options = null, $disconnect = false)
1597 {
1598 $plugin = $this->plugins->exec_hook('message_before_send', array(
1599 'message' => $message,
1600 'from' => $from,
1601 'mailto' => $mailto,
1602 'options' => $options,
1603 ));
1604
1605 if ($plugin['abort']) {
1606 if (!empty($plugin['error'])) {
1607 $error = $plugin['error'];
1608 }
1609 if (!empty($plugin['body_file'])) {
1610 $body_file = $plugin['body_file'];
1611 }
1612
1613 return isset($plugin['result']) ? $plugin['result'] : false;
1614 }
1615
1616 $from = $plugin['from'];
1617 $mailto = $plugin['mailto'];
1618 $options = $plugin['options'];
1619 $message = $plugin['message'];
1620 $headers = $message->headers();
1621
1622 // generate list of recipients
1623 $a_recipients = (array) $mailto;
1624
1625 if (strlen($headers['Cc'])) {
1626 $a_recipients[] = $headers['Cc'];
1627 }
1628 if (strlen($headers['Bcc'])) {
1629 $a_recipients[] = $headers['Bcc'];
1630 }
1631
1632 // remove Bcc header and get the whole head of the message as string
1633 $smtp_headers = $message->txtHeaders(array('Bcc' => null), true);
1634
1635 if ($message->getParam('delay_file_io')) {
1636 // use common temp dir
1637 $temp_dir = $this->config->get('temp_dir');
1638 $body_file = tempnam($temp_dir, 'rcmMsg');
1639 $mime_result = $message->saveMessageBody($body_file);
1640
1641 if (is_a($mime_result, 'PEAR_Error')) {
1642 self::raise_error(array('code' => 650, 'type' => 'php',
1643 'file' => __FILE__, 'line' => __LINE__,
1644 'message' => "Could not create message: ".$mime_result->getMessage()),
1645 true, false);
1646 return false;
1647 }
1648
1649 $msg_body = fopen($body_file, 'r');
1650 }
1651 else {
1652 $msg_body = $message->get();
1653 }
1654
1655 // initialize SMTP connection
1656 if (!is_object($this->smtp)) {
1657 $this->smtp_init(true);
1658 }
1659
1660 // send message
1661 $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $options);
1662 $response = $this->smtp->get_response();
1663 $error = $this->smtp->get_error();
1664
1665 if (!$sent) {
1666 self::raise_error(array('code' => 800, 'type' => 'smtp',
1667 'line' => __LINE__, 'file' => __FILE__,
1668 'message' => join("\n", $response)), true, false);
1669
1670 // allow plugins to catch sending errors with the same parameters as in 'message_before_send'
1671 $this->plugins->exec_hook('message_send_error', $plugin + array('error' => $error));
1672 }
1673 else {
1674 $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body, 'message' => $message));
1675
1676 // remove MDN headers after sending
1677 unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
1678
1679 if ($this->config->get('smtp_log')) {
1680 // get all recipient addresses
1681 $mailto = implode(',', $a_recipients);
1682 $mailto = rcube_mime::decode_address_list($mailto, null, false, null, true);
1683
1684 self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
1685 $this->user->get_username(),
1686 rcube_utils::remote_addr(),
1687 implode(', ', $mailto),
1688 !empty($response) ? join('; ', $response) : ''));
1689 }
1690 }
1691
1692 if (is_resource($msg_body)) {
1693 fclose($msg_body);
1694 }
1695
1696 if ($disconnect) {
1697 $this->smtp->disconnect();
1698 }
1699
1700 $message->headers($headers, true);
1701
1702 return $sent;
1703 }
1704 }
1705
1706
1707 /**
1708 * Lightweight plugin API class serving as a dummy if plugins are not enabled
1709 *
1710 * @package Framework
1711 * @subpackage Core
1712 */
1713 class rcube_dummy_plugin_api
1714 {
1715 /**
1716 * Triggers a plugin hook.
1717 * @see rcube_plugin_api::exec_hook()
1718 */
1719 public function exec_hook($hook, $args = array())
1720 {
1721 return $args;
1722 }
1723 }