PageRenderTime 65ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/program/lib/Roundcube/rcube.php

https://github.com/trimbakgopalghare/roundcubemail
PHP | 1657 lines | 930 code | 269 blank | 458 comment | 230 complexity | 0d76c27f9a2c10b3d77da03ed5e2f44f MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. +-----------------------------------------------------------------------+
  4. | This file is part of the Roundcube Webmail client |
  5. | Copyright (C) 2008-2014, The Roundcube Dev Team |
  6. | Copyright (C) 2011-2014, Kolab Systems AG |
  7. | |
  8. | Licensed under the GNU General Public License version 3 or |
  9. | any later version with exceptions for skins & plugins. |
  10. | See the README file for a full license statement. |
  11. | |
  12. | PURPOSE: |
  13. | Framework base class providing core functions and holding |
  14. | instances of all 'global' objects like db- and storage-connections |
  15. +-----------------------------------------------------------------------+
  16. | Author: Thomas Bruederli <roundcube@gmail.com> |
  17. +-----------------------------------------------------------------------+
  18. */
  19. /**
  20. * Base class of the Roundcube Framework
  21. * implemented as singleton
  22. *
  23. * @package Framework
  24. * @subpackage Core
  25. */
  26. class rcube
  27. {
  28. const INIT_WITH_DB = 1;
  29. const INIT_WITH_PLUGINS = 2;
  30. /**
  31. * Singleton instace of rcube
  32. *
  33. * @var rcube
  34. */
  35. static protected $instance;
  36. /**
  37. * Stores instance of rcube_config.
  38. *
  39. * @var rcube_config
  40. */
  41. public $config;
  42. /**
  43. * Instace of database class.
  44. *
  45. * @var rcube_db
  46. */
  47. public $db;
  48. /**
  49. * Instace of Memcache class.
  50. *
  51. * @var Memcache
  52. */
  53. public $memcache;
  54. /**
  55. * Instace of rcube_session class.
  56. *
  57. * @var rcube_session
  58. */
  59. public $session;
  60. /**
  61. * Instance of rcube_smtp class.
  62. *
  63. * @var rcube_smtp
  64. */
  65. public $smtp;
  66. /**
  67. * Instance of rcube_storage class.
  68. *
  69. * @var rcube_storage
  70. */
  71. public $storage;
  72. /**
  73. * Instance of rcube_output class.
  74. *
  75. * @var rcube_output
  76. */
  77. public $output;
  78. /**
  79. * Instance of rcube_plugin_api.
  80. *
  81. * @var rcube_plugin_api
  82. */
  83. public $plugins;
  84. /**
  85. * Instance of rcube_user class.
  86. *
  87. * @var rcube_user
  88. */
  89. public $user;
  90. /* private/protected vars */
  91. protected $texts;
  92. protected $caches = array();
  93. protected $shutdown_functions = array();
  94. /**
  95. * This implements the 'singleton' design pattern
  96. *
  97. * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants
  98. * @param string Environment name to run (e.g. live, dev, test)
  99. *
  100. * @return rcube The one and only instance
  101. */
  102. static function get_instance($mode = 0, $env = '')
  103. {
  104. if (!self::$instance) {
  105. self::$instance = new rcube($env);
  106. self::$instance->init($mode);
  107. }
  108. return self::$instance;
  109. }
  110. /**
  111. * Private constructor
  112. */
  113. protected function __construct($env = '')
  114. {
  115. // load configuration
  116. $this->config = new rcube_config($env);
  117. $this->plugins = new rcube_dummy_plugin_api;
  118. register_shutdown_function(array($this, 'shutdown'));
  119. }
  120. /**
  121. * Initial startup function
  122. */
  123. protected function init($mode = 0)
  124. {
  125. // initialize syslog
  126. if ($this->config->get('log_driver') == 'syslog') {
  127. $syslog_id = $this->config->get('syslog_id', 'roundcube');
  128. $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
  129. openlog($syslog_id, LOG_ODELAY, $syslog_facility);
  130. }
  131. // connect to database
  132. if ($mode & self::INIT_WITH_DB) {
  133. $this->get_dbh();
  134. }
  135. // create plugin API and load plugins
  136. if ($mode & self::INIT_WITH_PLUGINS) {
  137. $this->plugins = rcube_plugin_api::get_instance();
  138. }
  139. }
  140. /**
  141. * Get the current database connection
  142. *
  143. * @return rcube_db Database object
  144. */
  145. public function get_dbh()
  146. {
  147. if (!$this->db) {
  148. $config_all = $this->config->all();
  149. $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
  150. $this->db->set_debug((bool)$config_all['sql_debug']);
  151. }
  152. return $this->db;
  153. }
  154. /**
  155. * Get global handle for memcache access
  156. *
  157. * @return object Memcache
  158. */
  159. public function get_memcache()
  160. {
  161. if (!isset($this->memcache)) {
  162. // no memcache support in PHP
  163. if (!class_exists('Memcache')) {
  164. $this->memcache = false;
  165. return false;
  166. }
  167. $this->memcache = new Memcache;
  168. $this->mc_available = 0;
  169. // add all configured hosts to pool
  170. $pconnect = $this->config->get('memcache_pconnect', true);
  171. foreach ($this->config->get('memcache_hosts', array()) as $host) {
  172. if (substr($host, 0, 7) != 'unix://') {
  173. list($host, $port) = explode(':', $host);
  174. if (!$port) $port = 11211;
  175. }
  176. else {
  177. $port = 0;
  178. }
  179. $this->mc_available += intval($this->memcache->addServer(
  180. $host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
  181. }
  182. // test connection and failover (will result in $this->mc_available == 0 on complete failure)
  183. $this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist
  184. if (!$this->mc_available) {
  185. $this->memcache = false;
  186. }
  187. }
  188. return $this->memcache;
  189. }
  190. /**
  191. * Callback for memcache failure
  192. */
  193. public function memcache_failure($host, $port)
  194. {
  195. static $seen = array();
  196. // only report once
  197. if (!$seen["$host:$port"]++) {
  198. $this->mc_available--;
  199. self::raise_error(array(
  200. 'code' => 604, 'type' => 'db',
  201. 'line' => __LINE__, 'file' => __FILE__,
  202. 'message' => "Memcache failure on host $host:$port"),
  203. true, false);
  204. }
  205. }
  206. /**
  207. * Initialize and get cache object
  208. *
  209. * @param string $name Cache identifier
  210. * @param string $type Cache type ('db', 'apc' or 'memcache')
  211. * @param string $ttl Expiration time for cache items
  212. * @param bool $packed Enables/disables data serialization
  213. *
  214. * @return rcube_cache Cache object
  215. */
  216. public function get_cache($name, $type='db', $ttl=0, $packed=true)
  217. {
  218. if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) {
  219. $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed);
  220. }
  221. return $this->caches[$name];
  222. }
  223. /**
  224. * Initialize and get shared cache object
  225. *
  226. * @param string $name Cache identifier
  227. * @param bool $packed Enables/disables data serialization
  228. *
  229. * @return rcube_cache_shared Cache object
  230. */
  231. public function get_cache_shared($name, $packed=true)
  232. {
  233. $shared_name = "shared_$name";
  234. if (!array_key_exists($shared_name, $this->caches)) {
  235. $opt = strtolower($name) . '_cache';
  236. $type = $this->config->get($opt);
  237. $ttl = $this->config->get($opt . '_ttl');
  238. if (!$type) {
  239. // cache is disabled
  240. return $this->caches[$shared_name] = null;
  241. }
  242. if ($ttl === null) {
  243. $ttl = $this->config->get('shared_cache_ttl', '10d');
  244. }
  245. $this->caches[$shared_name] = new rcube_cache_shared($type, $name, $ttl, $packed);
  246. }
  247. return $this->caches[$shared_name];
  248. }
  249. /**
  250. * Create SMTP object and connect to server
  251. *
  252. * @param boolean True if connection should be established
  253. */
  254. public function smtp_init($connect = false)
  255. {
  256. $this->smtp = new rcube_smtp();
  257. if ($connect) {
  258. $this->smtp->connect();
  259. }
  260. }
  261. /**
  262. * Initialize and get storage object
  263. *
  264. * @return rcube_storage Storage object
  265. */
  266. public function get_storage()
  267. {
  268. // already initialized
  269. if (!is_object($this->storage)) {
  270. $this->storage_init();
  271. }
  272. return $this->storage;
  273. }
  274. /**
  275. * Initialize storage object
  276. */
  277. public function storage_init()
  278. {
  279. // already initialized
  280. if (is_object($this->storage)) {
  281. return;
  282. }
  283. $driver = $this->config->get('storage_driver', 'imap');
  284. $driver_class = "rcube_{$driver}";
  285. if (!class_exists($driver_class)) {
  286. self::raise_error(array(
  287. 'code' => 700, 'type' => 'php',
  288. 'file' => __FILE__, 'line' => __LINE__,
  289. 'message' => "Storage driver class ($driver) not found!"),
  290. true, true);
  291. }
  292. // Initialize storage object
  293. $this->storage = new $driver_class;
  294. // for backward compat. (deprecated, will be removed)
  295. $this->imap = $this->storage;
  296. // set class options
  297. $options = array(
  298. 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'),
  299. 'auth_cid' => $this->config->get("{$driver}_auth_cid"),
  300. 'auth_pw' => $this->config->get("{$driver}_auth_pw"),
  301. 'debug' => (bool) $this->config->get("{$driver}_debug"),
  302. 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"),
  303. 'disabled_caps' => $this->config->get("{$driver}_disabled_caps"),
  304. 'socket_options' => $this->config->get("{$driver}_conn_options"),
  305. 'timeout' => (int) $this->config->get("{$driver}_timeout"),
  306. 'skip_deleted' => (bool) $this->config->get('skip_deleted'),
  307. 'driver' => $driver,
  308. );
  309. if (!empty($_SESSION['storage_host'])) {
  310. $options['host'] = $_SESSION['storage_host'];
  311. $options['user'] = $_SESSION['username'];
  312. $options['port'] = $_SESSION['storage_port'];
  313. $options['ssl'] = $_SESSION['storage_ssl'];
  314. $options['password'] = $this->decrypt($_SESSION['password']);
  315. $_SESSION[$driver.'_host'] = $_SESSION['storage_host'];
  316. }
  317. $options = $this->plugins->exec_hook("storage_init", $options);
  318. // for backward compat. (deprecated, to be removed)
  319. $options = $this->plugins->exec_hook("imap_init", $options);
  320. $this->storage->set_options($options);
  321. $this->set_storage_prop();
  322. }
  323. /**
  324. * Set storage parameters.
  325. */
  326. protected function set_storage_prop()
  327. {
  328. $storage = $this->get_storage();
  329. // set pagesize from config
  330. $pagesize = $this->config->get('mail_pagesize');
  331. if (!$pagesize) {
  332. $pagesize = $this->config->get('pagesize', 50);
  333. }
  334. $storage->set_pagesize($pagesize);
  335. $storage->set_charset($this->config->get('default_charset', RCUBE_CHARSET));
  336. // enable caching of mail data
  337. $driver = $this->config->get('storage_driver', 'imap');
  338. $storage_cache = $this->config->get("{$driver}_cache");
  339. $messages_cache = $this->config->get('messages_cache');
  340. // for backward compatybility
  341. if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
  342. $storage_cache = 'db';
  343. $messages_cache = true;
  344. }
  345. if ($storage_cache) {
  346. $storage->set_caching($storage_cache);
  347. }
  348. if ($messages_cache) {
  349. $storage->set_messages_caching(true);
  350. }
  351. }
  352. /**
  353. * Set special folders type association.
  354. * This must be done AFTER connecting to the server!
  355. */
  356. protected function set_special_folders()
  357. {
  358. $storage = $this->get_storage();
  359. $folders = $storage->get_special_folders(true);
  360. $prefs = array();
  361. // check SPECIAL-USE flags on IMAP folders
  362. foreach ($folders as $type => $folder) {
  363. $idx = $type . '_mbox';
  364. if ($folder !== $this->config->get($idx)) {
  365. $prefs[$idx] = $folder;
  366. }
  367. }
  368. // Some special folders differ, update user preferences
  369. if (!empty($prefs) && $this->user) {
  370. $this->user->save_prefs($prefs);
  371. }
  372. // create default folders (on login)
  373. if ($this->config->get('create_default_folders')) {
  374. $storage->create_default_folders();
  375. }
  376. }
  377. /**
  378. * Create session object and start the session.
  379. */
  380. public function session_init()
  381. {
  382. // session started (Installer?)
  383. if (session_id()) {
  384. return;
  385. }
  386. $sess_name = $this->config->get('session_name');
  387. $sess_domain = $this->config->get('session_domain');
  388. $sess_path = $this->config->get('session_path');
  389. $lifetime = $this->config->get('session_lifetime', 0) * 60;
  390. $is_secure = $this->config->get('use_https') || rcube_utils::https_check();
  391. // set session domain
  392. if ($sess_domain) {
  393. ini_set('session.cookie_domain', $sess_domain);
  394. }
  395. // set session path
  396. if ($sess_path) {
  397. ini_set('session.cookie_path', $sess_path);
  398. }
  399. // set session garbage collecting time according to session_lifetime
  400. if ($lifetime) {
  401. ini_set('session.gc_maxlifetime', $lifetime * 2);
  402. }
  403. ini_set('session.cookie_secure', $is_secure);
  404. ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid');
  405. ini_set('session.use_cookies', 1);
  406. ini_set('session.use_only_cookies', 1);
  407. ini_set('session.cookie_httponly', 1);
  408. // use database for storing session data
  409. $this->session = new rcube_session($this->get_dbh(), $this->config);
  410. $this->session->register_gc_handler(array($this, 'gc'));
  411. $this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME']));
  412. $this->session->set_ip_check($this->config->get('ip_check'));
  413. if ($this->config->get('session_auth_name')) {
  414. $this->session->set_cookiename($this->config->get('session_auth_name'));
  415. }
  416. // start PHP session (if not in CLI mode)
  417. if ($_SERVER['REMOTE_ADDR']) {
  418. $this->session->start();
  419. }
  420. }
  421. /**
  422. * Garbage collector - cache/temp cleaner
  423. */
  424. public function gc()
  425. {
  426. rcube_cache::gc();
  427. rcube_cache_shared::gc();
  428. $this->get_storage()->cache_gc();
  429. $this->gc_temp();
  430. }
  431. /**
  432. * Garbage collector function for temp files.
  433. * Remove temp files older than two days
  434. */
  435. public function gc_temp()
  436. {
  437. $tmp = unslashify($this->config->get('temp_dir'));
  438. // expire in 48 hours by default
  439. $temp_dir_ttl = $this->config->get('temp_dir_ttl', '48h');
  440. $temp_dir_ttl = get_offset_sec($temp_dir_ttl);
  441. if ($temp_dir_ttl < 6*3600)
  442. $temp_dir_ttl = 6*3600; // 6 hours sensible lower bound.
  443. $expire = time() - $temp_dir_ttl;
  444. if ($tmp && ($dir = opendir($tmp))) {
  445. while (($fname = readdir($dir)) !== false) {
  446. if ($fname[0] == '.') {
  447. continue;
  448. }
  449. if (@filemtime($tmp.'/'.$fname) < $expire) {
  450. @unlink($tmp.'/'.$fname);
  451. }
  452. }
  453. closedir($dir);
  454. }
  455. }
  456. /**
  457. * Runs garbage collector with probability based on
  458. * session settings. This is intended for environments
  459. * without a session.
  460. */
  461. public function gc_run()
  462. {
  463. $probability = (int) ini_get('session.gc_probability');
  464. $divisor = (int) ini_get('session.gc_divisor');
  465. if ($divisor > 0 && $probability > 0) {
  466. $random = mt_rand(1, $divisor);
  467. if ($random <= $probability) {
  468. $this->gc();
  469. }
  470. }
  471. }
  472. /**
  473. * Get localized text in the desired language
  474. *
  475. * @param mixed $attrib Named parameters array or label name
  476. * @param string $domain Label domain (plugin) name
  477. *
  478. * @return string Localized text
  479. */
  480. public function gettext($attrib, $domain=null)
  481. {
  482. // load localization files if not done yet
  483. if (empty($this->texts)) {
  484. $this->load_language();
  485. }
  486. // extract attributes
  487. if (is_string($attrib)) {
  488. $attrib = array('name' => $attrib);
  489. }
  490. $name = $attrib['name'] ? $attrib['name'] : '';
  491. // attrib contain text values: use them from now
  492. if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us'])) {
  493. $this->texts[$name] = $setval;
  494. }
  495. // check for text with domain
  496. if ($domain && ($text = $this->texts[$domain.'.'.$name])) {
  497. }
  498. // text does not exist
  499. else if (!($text = $this->texts[$name])) {
  500. return "[$name]";
  501. }
  502. // replace vars in text
  503. if (is_array($attrib['vars'])) {
  504. foreach ($attrib['vars'] as $var_key => $var_value) {
  505. $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
  506. }
  507. }
  508. // format output
  509. if (($attrib['uppercase'] && strtolower($attrib['uppercase'] == 'first')) || $attrib['ucfirst']) {
  510. return ucfirst($text);
  511. }
  512. else if ($attrib['uppercase']) {
  513. return mb_strtoupper($text);
  514. }
  515. else if ($attrib['lowercase']) {
  516. return mb_strtolower($text);
  517. }
  518. return strtr($text, array('\n' => "\n"));
  519. }
  520. /**
  521. * Check if the given text label exists
  522. *
  523. * @param string $name Label name
  524. * @param string $domain Label domain (plugin) name or '*' for all domains
  525. * @param string $ref_domain Sets domain name if label is found
  526. *
  527. * @return boolean True if text exists (either in the current language or in en_US)
  528. */
  529. public function text_exists($name, $domain = null, &$ref_domain = null)
  530. {
  531. // load localization files if not done yet
  532. if (empty($this->texts)) {
  533. $this->load_language();
  534. }
  535. if (isset($this->texts[$name])) {
  536. $ref_domain = '';
  537. return true;
  538. }
  539. // any of loaded domains (plugins)
  540. if ($domain == '*') {
  541. foreach ($this->plugins->loaded_plugins() as $domain) {
  542. if (isset($this->texts[$domain.'.'.$name])) {
  543. $ref_domain = $domain;
  544. return true;
  545. }
  546. }
  547. }
  548. // specified domain
  549. else if ($domain) {
  550. $ref_domain = $domain;
  551. return isset($this->texts[$domain.'.'.$name]);
  552. }
  553. return false;
  554. }
  555. /**
  556. * Load a localization package
  557. *
  558. * @param string $lang Language ID
  559. * @param array $add Additional text labels/messages
  560. * @param array $merge Additional text labels/messages to merge
  561. */
  562. public function load_language($lang = null, $add = array(), $merge = array())
  563. {
  564. $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
  565. // load localized texts
  566. if (empty($this->texts) || $lang != $_SESSION['language']) {
  567. $this->texts = array();
  568. // handle empty lines after closing PHP tag in localization files
  569. ob_start();
  570. // get english labels (these should be complete)
  571. @include(RCUBE_LOCALIZATION_DIR . 'en_US/labels.inc');
  572. @include(RCUBE_LOCALIZATION_DIR . 'en_US/messages.inc');
  573. if (is_array($labels))
  574. $this->texts = $labels;
  575. if (is_array($messages))
  576. $this->texts = array_merge($this->texts, $messages);
  577. // include user language files
  578. if ($lang != 'en' && $lang != 'en_US' && is_dir(RCUBE_LOCALIZATION_DIR . $lang)) {
  579. include_once(RCUBE_LOCALIZATION_DIR . $lang . '/labels.inc');
  580. include_once(RCUBE_LOCALIZATION_DIR . $lang . '/messages.inc');
  581. if (is_array($labels))
  582. $this->texts = array_merge($this->texts, $labels);
  583. if (is_array($messages))
  584. $this->texts = array_merge($this->texts, $messages);
  585. }
  586. ob_end_clean();
  587. $_SESSION['language'] = $lang;
  588. }
  589. // append additional texts (from plugin)
  590. if (is_array($add) && !empty($add)) {
  591. $this->texts += $add;
  592. }
  593. // merge additional texts (from plugin)
  594. if (is_array($merge) && !empty($merge)) {
  595. $this->texts = array_merge($this->texts, $merge);
  596. }
  597. }
  598. /**
  599. * Check the given string and return a valid language code
  600. *
  601. * @param string Language code
  602. *
  603. * @return string Valid language code
  604. */
  605. protected function language_prop($lang)
  606. {
  607. static $rcube_languages, $rcube_language_aliases;
  608. // user HTTP_ACCEPT_LANGUAGE if no language is specified
  609. if (empty($lang) || $lang == 'auto') {
  610. $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
  611. $lang = $accept_langs[0];
  612. if (preg_match('/^([a-z]+)[_-]([a-z]+)$/i', $lang, $m)) {
  613. $lang = $m[1] . '_' . strtoupper($m[2]);
  614. }
  615. }
  616. if (empty($rcube_languages)) {
  617. @include(RCUBE_LOCALIZATION_DIR . 'index.inc');
  618. }
  619. // check if we have an alias for that language
  620. if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
  621. $lang = $rcube_language_aliases[$lang];
  622. }
  623. // try the first two chars
  624. else if (!isset($rcube_languages[$lang])) {
  625. $short = substr($lang, 0, 2);
  626. // check if we have an alias for the short language code
  627. if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
  628. $lang = $rcube_language_aliases[$short];
  629. }
  630. // expand 'nn' to 'nn_NN'
  631. else if (!isset($rcube_languages[$short])) {
  632. $lang = $short.'_'.strtoupper($short);
  633. }
  634. }
  635. if (!isset($rcube_languages[$lang]) || !is_dir(RCUBE_LOCALIZATION_DIR . $lang)) {
  636. $lang = 'en_US';
  637. }
  638. return $lang;
  639. }
  640. /**
  641. * Read directory program/localization and return a list of available languages
  642. *
  643. * @return array List of available localizations
  644. */
  645. public function list_languages()
  646. {
  647. static $sa_languages = array();
  648. if (!sizeof($sa_languages)) {
  649. @include(RCUBE_LOCALIZATION_DIR . 'index.inc');
  650. if ($dh = @opendir(RCUBE_LOCALIZATION_DIR)) {
  651. while (($name = readdir($dh)) !== false) {
  652. if ($name[0] == '.' || !is_dir(RCUBE_LOCALIZATION_DIR . $name)) {
  653. continue;
  654. }
  655. if ($label = $rcube_languages[$name]) {
  656. $sa_languages[$name] = $label;
  657. }
  658. }
  659. closedir($dh);
  660. }
  661. }
  662. return $sa_languages;
  663. }
  664. /**
  665. * Encrypt using 3DES
  666. *
  667. * @param string $clear clear text input
  668. * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
  669. * @param boolean $base64 whether or not to base64_encode() the result before returning
  670. *
  671. * @return string encrypted text
  672. */
  673. public function encrypt($clear, $key = 'des_key', $base64 = true)
  674. {
  675. if (!$clear) {
  676. return '';
  677. }
  678. /*-
  679. * Add a single canary byte to the end of the clear text, which
  680. * will help find out how much of padding will need to be removed
  681. * upon decryption; see http://php.net/mcrypt_generic#68082
  682. */
  683. $clear = pack("a*H2", $clear, "80");
  684. if (function_exists('mcrypt_module_open') &&
  685. ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
  686. ) {
  687. $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
  688. mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
  689. $cipher = $iv . mcrypt_generic($td, $clear);
  690. mcrypt_generic_deinit($td);
  691. mcrypt_module_close($td);
  692. }
  693. else {
  694. @include_once 'des.inc';
  695. if (function_exists('des')) {
  696. $des_iv_size = 8;
  697. $iv = $this->create_iv($des_iv_size);
  698. $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
  699. }
  700. else {
  701. self::raise_error(array(
  702. 'code' => 500, 'type' => 'php',
  703. 'file' => __FILE__, 'line' => __LINE__,
  704. 'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
  705. ), true, true);
  706. }
  707. }
  708. return $base64 ? base64_encode($cipher) : $cipher;
  709. }
  710. /**
  711. * Decrypt 3DES-encrypted string
  712. *
  713. * @param string $cipher encrypted text
  714. * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
  715. * @param boolean $base64 whether or not input is base64-encoded
  716. *
  717. * @return string decrypted text
  718. */
  719. public function decrypt($cipher, $key = 'des_key', $base64 = true)
  720. {
  721. if (!$cipher) {
  722. return '';
  723. }
  724. $cipher = $base64 ? base64_decode($cipher) : $cipher;
  725. if (function_exists('mcrypt_module_open') &&
  726. ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
  727. ) {
  728. $iv_size = mcrypt_enc_get_iv_size($td);
  729. $iv = substr($cipher, 0, $iv_size);
  730. // session corruption? (#1485970)
  731. if (strlen($iv) < $iv_size) {
  732. return '';
  733. }
  734. $cipher = substr($cipher, $iv_size);
  735. mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
  736. $clear = mdecrypt_generic($td, $cipher);
  737. mcrypt_generic_deinit($td);
  738. mcrypt_module_close($td);
  739. }
  740. else {
  741. @include_once 'des.inc';
  742. if (function_exists('des')) {
  743. $des_iv_size = 8;
  744. $iv = substr($cipher, 0, $des_iv_size);
  745. $cipher = substr($cipher, $des_iv_size);
  746. $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
  747. }
  748. else {
  749. self::raise_error(array(
  750. 'code' => 500, 'type' => 'php',
  751. 'file' => __FILE__, 'line' => __LINE__,
  752. 'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
  753. ), true, true);
  754. }
  755. }
  756. /*-
  757. * Trim PHP's padding and the canary byte; see note in
  758. * rcube::encrypt() and http://php.net/mcrypt_generic#68082
  759. */
  760. $clear = substr(rtrim($clear, "\0"), 0, -1);
  761. return $clear;
  762. }
  763. /**
  764. * Generates encryption initialization vector (IV)
  765. *
  766. * @param int Vector size
  767. *
  768. * @return string Vector string
  769. */
  770. private function create_iv($size)
  771. {
  772. // mcrypt_create_iv() can be slow when system lacks entrophy
  773. // we'll generate IV vector manually
  774. $iv = '';
  775. for ($i = 0; $i < $size; $i++) {
  776. $iv .= chr(mt_rand(0, 255));
  777. }
  778. return $iv;
  779. }
  780. /**
  781. * Build a valid URL to this instance of Roundcube
  782. *
  783. * @param mixed Either a string with the action or url parameters as key-value pairs
  784. * @return string Valid application URL
  785. */
  786. public function url($p)
  787. {
  788. // STUB: should be overloaded by the application
  789. return '';
  790. }
  791. /**
  792. * Function to be executed in script shutdown
  793. * Registered with register_shutdown_function()
  794. */
  795. public function shutdown()
  796. {
  797. foreach ($this->shutdown_functions as $function) {
  798. call_user_func($function);
  799. }
  800. // write session data as soon as possible and before
  801. // closing database connection, don't do this before
  802. // registered shutdown functions, they may need the session
  803. // Note: this will run registered gc handlers (ie. cache gc)
  804. if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
  805. $this->session->write_close();
  806. }
  807. if (is_object($this->smtp)) {
  808. $this->smtp->disconnect();
  809. }
  810. foreach ($this->caches as $cache) {
  811. if (is_object($cache)) {
  812. $cache->close();
  813. }
  814. }
  815. if (is_object($this->storage)) {
  816. $this->storage->close();
  817. }
  818. }
  819. /**
  820. * Registers shutdown function to be executed on shutdown.
  821. * The functions will be executed before destroying any
  822. * objects like smtp, imap, session, etc.
  823. *
  824. * @param callback Function callback
  825. */
  826. public function add_shutdown_function($function)
  827. {
  828. $this->shutdown_functions[] = $function;
  829. }
  830. /**
  831. * Quote a given string.
  832. * Shortcut function for rcube_utils::rep_specialchars_output()
  833. *
  834. * @return string HTML-quoted string
  835. */
  836. public static function Q($str, $mode = 'strict', $newlines = true)
  837. {
  838. return rcube_utils::rep_specialchars_output($str, 'html', $mode, $newlines);
  839. }
  840. /**
  841. * Quote a given string for javascript output.
  842. * Shortcut function for rcube_utils::rep_specialchars_output()
  843. *
  844. * @return string JS-quoted string
  845. */
  846. public static function JQ($str)
  847. {
  848. return rcube_utils::rep_specialchars_output($str, 'js');
  849. }
  850. /**
  851. * Construct shell command, execute it and return output as string.
  852. * Keywords {keyword} are replaced with arguments
  853. *
  854. * @param $cmd Format string with {keywords} to be replaced
  855. * @param $values (zero, one or more arrays can be passed)
  856. *
  857. * @return output of command. shell errors not detectable
  858. */
  859. public static function exec(/* $cmd, $values1 = array(), ... */)
  860. {
  861. $args = func_get_args();
  862. $cmd = array_shift($args);
  863. $values = $replacements = array();
  864. // merge values into one array
  865. foreach ($args as $arg) {
  866. $values += (array)$arg;
  867. }
  868. preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
  869. foreach ($matches as $tags) {
  870. list(, $tag, $option, $key) = $tags;
  871. $parts = array();
  872. if ($option) {
  873. foreach ((array)$values["-$key"] as $key => $value) {
  874. if ($value === true || $value === false || $value === null) {
  875. $parts[] = $value ? $key : "";
  876. }
  877. else {
  878. foreach ((array)$value as $val) {
  879. $parts[] = "$key " . escapeshellarg($val);
  880. }
  881. }
  882. }
  883. }
  884. else {
  885. foreach ((array)$values[$key] as $value) {
  886. $parts[] = escapeshellarg($value);
  887. }
  888. }
  889. $replacements[$tag] = join(" ", $parts);
  890. }
  891. // use strtr behaviour of going through source string once
  892. $cmd = strtr($cmd, $replacements);
  893. return (string)shell_exec($cmd);
  894. }
  895. /**
  896. * Print or write debug messages
  897. *
  898. * @param mixed Debug message or data
  899. */
  900. public static function console()
  901. {
  902. $args = func_get_args();
  903. if (class_exists('rcube', false)) {
  904. $rcube = self::get_instance();
  905. $plugin = $rcube->plugins->exec_hook('console', array('args' => $args));
  906. if ($plugin['abort']) {
  907. return;
  908. }
  909. $args = $plugin['args'];
  910. }
  911. $msg = array();
  912. foreach ($args as $arg) {
  913. $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
  914. }
  915. self::write_log('console', join(";\n", $msg));
  916. }
  917. /**
  918. * Append a line to a logfile in the logs directory.
  919. * Date will be added automatically to the line.
  920. *
  921. * @param $name name of log file
  922. * @param line Line to append
  923. */
  924. public static function write_log($name, $line)
  925. {
  926. if (!is_string($line)) {
  927. $line = var_export($line, true);
  928. }
  929. $date_format = self::$instance ? self::$instance->config->get('log_date_format') : null;
  930. $log_driver = self::$instance ? self::$instance->config->get('log_driver') : null;
  931. if (empty($date_format)) {
  932. $date_format = 'd-M-Y H:i:s O';
  933. }
  934. $date = date($date_format);
  935. // trigger logging hook
  936. if (is_object(self::$instance) && is_object(self::$instance->plugins)) {
  937. $log = self::$instance->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
  938. $name = $log['name'];
  939. $line = $log['line'];
  940. $date = $log['date'];
  941. if ($log['abort'])
  942. return true;
  943. }
  944. // add session ID to the log
  945. if ($sess = session_id()) {
  946. $line = '<' . substr($sess, 0, 8) . '> ' . $line;
  947. }
  948. if ($log_driver == 'syslog') {
  949. $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
  950. syslog($prio, $line);
  951. return true;
  952. }
  953. // log_driver == 'file' is assumed here
  954. $line = sprintf("[%s]: %s\n", $date, $line);
  955. $log_dir = null;
  956. // per-user logging is activated
  957. if (self::$instance && self::$instance->config->get('per_user_logging', false) && self::$instance->get_user_id()) {
  958. $log_dir = self::$instance->get_user_log_dir();
  959. if (empty($log_dir))
  960. return false;
  961. }
  962. else if (!empty($log['dir'])) {
  963. $log_dir = $log['dir'];
  964. }
  965. else if (self::$instance) {
  966. $log_dir = self::$instance->config->get('log_dir');
  967. }
  968. if (empty($log_dir)) {
  969. $log_dir = RCUBE_INSTALL_PATH . 'logs';
  970. }
  971. // try to open specific log file for writing
  972. $logfile = $log_dir.'/'.$name;
  973. if ($fp = @fopen($logfile, 'a')) {
  974. fwrite($fp, $line);
  975. fflush($fp);
  976. fclose($fp);
  977. return true;
  978. }
  979. trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
  980. return false;
  981. }
  982. /**
  983. * Throw system error (and show error page).
  984. *
  985. * @param array Named parameters
  986. * - code: Error code (required)
  987. * - type: Error type [php|db|imap|javascript] (required)
  988. * - message: Error message
  989. * - file: File where error occurred
  990. * - line: Line where error occurred
  991. * @param boolean True to log the error
  992. * @param boolean Terminate script execution
  993. */
  994. public static function raise_error($arg = array(), $log = false, $terminate = false)
  995. {
  996. // handle PHP exceptions
  997. if (is_object($arg) && is_a($arg, 'Exception')) {
  998. $arg = array(
  999. 'code' => $arg->getCode(),
  1000. 'line' => $arg->getLine(),
  1001. 'file' => $arg->getFile(),
  1002. 'message' => $arg->getMessage(),
  1003. );
  1004. }
  1005. else if (is_string($arg)) {
  1006. $arg = array('message' => $arg);
  1007. }
  1008. if (empty($arg['code'])) {
  1009. $arg['code'] = 500;
  1010. }
  1011. // installer
  1012. if (class_exists('rcmail_install', false)) {
  1013. $rci = rcmail_install::get_instance();
  1014. $rci->raise_error($arg);
  1015. return;
  1016. }
  1017. $cli = php_sapi_name() == 'cli';
  1018. if (($log || $terminate) && !$cli && $arg['message']) {
  1019. $arg['fatal'] = $terminate;
  1020. self::log_bug($arg);
  1021. }
  1022. // terminate script
  1023. if ($terminate) {
  1024. // display error page
  1025. if (is_object(self::$instance->output)) {
  1026. self::$instance->output->raise_error($arg['code'], $arg['message']);
  1027. }
  1028. else if ($cli) {
  1029. fwrite(STDERR, 'ERROR: ' . $arg['message']);
  1030. }
  1031. exit(1);
  1032. }
  1033. }
  1034. /**
  1035. * Report error according to configured debug_level
  1036. *
  1037. * @param array Named parameters
  1038. * @see self::raise_error()
  1039. */
  1040. public static function log_bug($arg_arr)
  1041. {
  1042. $program = strtoupper(!empty($arg_arr['type']) ? $arg_arr['type'] : 'php');
  1043. $level = self::get_instance()->config->get('debug_level');
  1044. // disable errors for ajax requests, write to log instead (#1487831)
  1045. if (($level & 4) && !empty($_REQUEST['_remote'])) {
  1046. $level = ($level ^ 4) | 1;
  1047. }
  1048. // write error to local log file
  1049. if (($level & 1) || !empty($arg_arr['fatal'])) {
  1050. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  1051. $post_query = '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']);
  1052. }
  1053. else {
  1054. $post_query = '';
  1055. }
  1056. $log_entry = sprintf("%s Error: %s%s (%s %s)",
  1057. $program,
  1058. $arg_arr['message'],
  1059. $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
  1060. $_SERVER['REQUEST_METHOD'],
  1061. $_SERVER['REQUEST_URI'] . $post_query);
  1062. if (!self::write_log('errors', $log_entry)) {
  1063. // send error to PHPs error handler if write_log didn't succeed
  1064. trigger_error($arg_arr['message'], E_USER_WARNING);
  1065. }
  1066. }
  1067. // report the bug to the global bug reporting system
  1068. if ($level & 2) {
  1069. // TODO: Send error via HTTP
  1070. }
  1071. // show error if debug_mode is on
  1072. if ($level & 4) {
  1073. print "<b>$program Error";
  1074. if (!empty($arg_arr['file']) && !empty($arg_arr['line'])) {
  1075. print " in $arg_arr[file] ($arg_arr[line])";
  1076. }
  1077. print ':</b>&nbsp;';
  1078. print nl2br($arg_arr['message']);
  1079. print '<br />';
  1080. flush();
  1081. }
  1082. }
  1083. /**
  1084. * Returns current time (with microseconds).
  1085. *
  1086. * @return float Current time in seconds since the Unix
  1087. */
  1088. public static function timer()
  1089. {
  1090. return microtime(true);
  1091. }
  1092. /**
  1093. * Logs time difference according to provided timer
  1094. *
  1095. * @param float $timer Timer (self::timer() result)
  1096. * @param string $label Log line prefix
  1097. * @param string $dest Log file name
  1098. *
  1099. * @see self::timer()
  1100. */
  1101. public static function print_timer($timer, $label = 'Timer', $dest = 'console')
  1102. {
  1103. static $print_count = 0;
  1104. $print_count++;
  1105. $now = self::timer();
  1106. $diff = $now - $timer;
  1107. if (empty($label)) {
  1108. $label = 'Timer '.$print_count;
  1109. }
  1110. self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
  1111. }
  1112. /**
  1113. * Setter for system user object
  1114. *
  1115. * @param rcube_user Current user instance
  1116. */
  1117. public function set_user($user)
  1118. {
  1119. if (is_object($user)) {
  1120. $this->user = $user;
  1121. // overwrite config with user preferences
  1122. $this->config->set_user_prefs((array)$this->user->get_prefs());
  1123. }
  1124. }
  1125. /**
  1126. * Getter for logged user ID.
  1127. *
  1128. * @return mixed User identifier
  1129. */
  1130. public function get_user_id()
  1131. {
  1132. if (is_object($this->user)) {
  1133. return $this->user->ID;
  1134. }
  1135. else if (isset($_SESSION['user_id'])) {
  1136. return $_SESSION['user_id'];
  1137. }
  1138. return null;
  1139. }
  1140. /**
  1141. * Getter for logged user name.
  1142. *
  1143. * @return string User name
  1144. */
  1145. public function get_user_name()
  1146. {
  1147. if (is_object($this->user)) {
  1148. return $this->user->get_username();
  1149. }
  1150. else if (isset($_SESSION['username'])) {
  1151. return $_SESSION['username'];
  1152. }
  1153. }
  1154. /**
  1155. * Getter for logged user email (derived from user name not identity).
  1156. *
  1157. * @return string User email address
  1158. */
  1159. public function get_user_email()
  1160. {
  1161. if (is_object($this->user)) {
  1162. return $this->user->get_username('mail');
  1163. }
  1164. }
  1165. /**
  1166. * Getter for logged user password.
  1167. *
  1168. * @return string User password
  1169. */
  1170. public function get_user_password()
  1171. {
  1172. if ($this->password) {
  1173. return $this->password;
  1174. }
  1175. else if ($_SESSION['password']) {
  1176. return $this->decrypt($_SESSION['password']);
  1177. }
  1178. }
  1179. /**
  1180. * Get the per-user log directory
  1181. */
  1182. protected function get_user_log_dir()
  1183. {
  1184. $log_dir = $this->config->get('log_dir', RCUBE_INSTALL_PATH . 'logs');
  1185. $user_name = $this->get_user_name();
  1186. $user_log_dir = $log_dir . '/' . $user_name;
  1187. return !empty($user_name) && is_writable($user_log_dir) ? $user_log_dir : false;
  1188. }
  1189. /**
  1190. * Getter for logged user language code.
  1191. *
  1192. * @return string User language code
  1193. */
  1194. public function get_user_language()
  1195. {
  1196. if (is_object($this->user)) {
  1197. return $this->user->language;
  1198. }
  1199. else if (isset($_SESSION['language'])) {
  1200. return $_SESSION['language'];
  1201. }
  1202. }
  1203. /**
  1204. * Unique Message-ID generator.
  1205. *
  1206. * @return string Message-ID
  1207. */
  1208. public function gen_message_id()
  1209. {
  1210. $local_part = md5(uniqid('rcube'.mt_rand(), true));
  1211. $domain_part = $this->user->get_username('domain');
  1212. // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
  1213. if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
  1214. foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) {
  1215. $host = preg_replace('/:[0-9]+$/', '', $host);
  1216. if ($host && preg_match('/\.[a-z]+$/i', $host)) {
  1217. $domain_part = $host;
  1218. }
  1219. }
  1220. }
  1221. return sprintf('<%s@%s>', $local_part, $domain_part);
  1222. }
  1223. /**
  1224. * Send the given message using the configured method.
  1225. *
  1226. * @param object $message Reference to Mail_MIME object
  1227. * @param string $from Sender address string
  1228. * @param array $mailto Array of recipient address strings
  1229. * @param array $error SMTP error array (reference)
  1230. * @param string $body_file Location of file with saved message body (reference),
  1231. * used when delay_file_io is enabled
  1232. * @param array $options SMTP options (e.g. DSN request)
  1233. *
  1234. * @return boolean Send status.
  1235. */
  1236. public function deliver_message(&$message, $from, $mailto, &$error, &$body_file = null, $options = null)
  1237. {
  1238. $plugin = $this->plugins->exec_hook('message_before_send', array(
  1239. 'message' => $message,
  1240. 'from' => $from,
  1241. 'mailto' => $mailto,
  1242. 'options' => $options,
  1243. ));
  1244. if ($plugin['abort']) {
  1245. if (!empty($plugin['error'])) {
  1246. $error = $plugin['error'];
  1247. }
  1248. if (!empty($plugin['body_file'])) {
  1249. $body_file = $plugin['body_file'];
  1250. }
  1251. return isset($plugin['result']) ? $plugin['result'] : false;
  1252. }
  1253. $from = $plugin['from'];
  1254. $mailto = $plugin['mailto'];
  1255. $options = $plugin['options'];
  1256. $message = $plugin['message'];
  1257. $headers = $message->headers();
  1258. // send thru SMTP server using custom SMTP library
  1259. if ($this->config->get('smtp_server')) {
  1260. // generate list of recipients
  1261. $a_recipients = array($mailto);
  1262. if (strlen($headers['Cc']))
  1263. $a_recipients[] = $headers['Cc'];
  1264. if (strlen($headers['Bcc']))
  1265. $a_recipients[] = $headers['Bcc'];
  1266. // clean Bcc from header for recipients
  1267. $send_headers = $headers;
  1268. unset($send_headers['Bcc']);
  1269. // here too, it because txtHeaders() below use $message->_headers not only $send_headers
  1270. unset($message->_headers['Bcc']);
  1271. $smtp_headers = $message->txtHeaders($send_headers, true);
  1272. if ($message->getParam('delay_file_io')) {
  1273. // use common temp dir
  1274. $temp_dir = $this->config->get('temp_dir');
  1275. $body_file = tempnam($temp_dir, 'rcmMsg');
  1276. if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
  1277. self::raise_error(array('code' => 650, 'type' => 'php',
  1278. 'file' => __FILE__, 'line' => __LINE__,
  1279. 'message' => "Could not create message: ".$mime_result->getMessage()),
  1280. TRUE, FALSE);
  1281. return false;
  1282. }
  1283. $msg_body = fopen($body_file, 'r');
  1284. }
  1285. else {
  1286. $msg_body = $message->get();
  1287. }
  1288. // send message
  1289. if (!is_object($this->smtp)) {
  1290. $this->smtp_init(true);
  1291. }
  1292. $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $options);
  1293. $response = $this->smtp->get_response();
  1294. $error = $this->smtp->get_error();
  1295. // log error
  1296. if (!$sent) {
  1297. self::raise_error(array('code' => 800, 'type' => 'smtp',
  1298. 'line' => __LINE__, 'file' => __FILE__,
  1299. 'message' => "SMTP error: ".join("\n", $response)), TRUE, FALSE);
  1300. }
  1301. }
  1302. // send mail using PHP's mail() function
  1303. else {
  1304. // unset some headers because they will be added by the mail() function
  1305. $headers_enc = $message->headers($headers);
  1306. $headers_php = $message->_headers;
  1307. unset($headers_php['To'], $headers_php['Subject']);
  1308. // reset stored headers and overwrite
  1309. $message->_headers = array();
  1310. $header_str = $message->txtHeaders($headers_php);
  1311. // #1485779
  1312. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  1313. if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
  1314. $headers_enc['To'] = implode(', ', $m[1]);
  1315. }
  1316. }
  1317. $msg_body = $message->get();
  1318. if (PEAR::isError($msg_body)) {
  1319. self::raise_error(array('code' => 650, 'type' => 'php',
  1320. 'file' => __FILE__, 'line' => __LINE__,
  1321. 'message' => "Could not create message: ".$msg_body->getMessage()),
  1322. TRUE, FALSE);
  1323. }
  1324. else {
  1325. $delim = $this->config->header_delimiter();
  1326. $to = $headers_enc['To'];
  1327. $subject = $headers_enc['Subject'];
  1328. $header_str = rtrim($header_str);
  1329. if ($delim != "\r\n") {
  1330. $header_str = str_replace("\r\n", $delim, $header_str);
  1331. $msg_body = str_replace("\r\n", $delim, $msg_body);
  1332. $to = str_replace("\r\n", $delim, $to);
  1333. $subject = str_replace("\r\n", $delim, $subject);
  1334. }
  1335. if (filter_var(ini_get('safe_mode'), FILTER_VALIDATE_BOOLEAN))
  1336. $sent = mail($to, $subject, $msg_body, $header_str);
  1337. else
  1338. $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
  1339. }
  1340. }
  1341. if ($sent) {
  1342. $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
  1343. // remove MDN headers after sending
  1344. unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
  1345. // get all recipients
  1346. if ($headers['Cc'])
  1347. $mailto .= $headers['Cc'];
  1348. if ($headers['Bcc'])
  1349. $mailto .= $headers['Bcc'];
  1350. if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
  1351. $mailto = implode(', ', array_unique($m[1]));
  1352. if ($this->config->get('smtp_log')) {
  1353. self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
  1354. $this->user->get_username(),
  1355. $_SERVER['REMOTE_ADDR'],
  1356. $mailto,
  1357. !empty($response) ? join('; ', $response) : ''));
  1358. }
  1359. }
  1360. else {
  1361. // allow plugins to catch sending errors with the same parameters as in …

Large files files are truncated, but you can click here to view the full file