From 409b64934605d583ddc569570762da8adcdd5930 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Fri, 14 Nov 2014 02:48:56 -0500 Subject: [PATCH] Fix lack of delimiter for recipient addresses in smtp_log (#1490150) --- program/lib/Roundcube/rcube.php | 586 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 files changed, 491 insertions(+), 95 deletions(-) diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index b681f05..689823f 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -3,8 +3,8 @@ /* +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | - | Copyright (C) 2008-2012, The Roundcube Dev Team | - | Copyright (C) 2011-2012, Kolab Systems AG | + | Copyright (C) 2008-2014, The Roundcube Dev Team | + | Copyright (C) 2011-2014, Kolab Systems AG | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -94,25 +94,32 @@ */ public $plugins; + /** + * Instance of rcube_user class. + * + * @var rcube_user + */ + public $user; + /* private/protected vars */ protected $texts; protected $caches = array(); protected $shutdown_functions = array(); - protected $expunge_cache = false; /** * This implements the 'singleton' design pattern * * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants + * @param string Environment name to run (e.g. live, dev, test) * * @return rcube The one and only instance */ - static function get_instance($mode = 0) + static function get_instance($mode = 0, $env = '') { if (!self::$instance) { - self::$instance = new rcube(); + self::$instance = new rcube($env); self::$instance->init($mode); } @@ -123,10 +130,10 @@ /** * Private constructor */ - protected function __construct() + protected function __construct($env = '') { // load configuration - $this->config = new rcube_config; + $this->config = new rcube_config($env); $this->plugins = new rcube_dummy_plugin_api; register_shutdown_function(array($this, 'shutdown')); @@ -165,9 +172,13 @@ public function get_dbh() { if (!$this->db) { - $config_all = $this->config->all(); - $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']); - $this->db->set_debug((bool)$config_all['sql_debug']); + $this->db = rcube_db::factory( + $this->config->get('db_dsnw'), + $this->config->get('db_dsnr'), + $this->config->get('db_persistent') + ); + + $this->db->set_debug((bool)$this->config->get('sql_debug')); } return $this->db; @@ -258,6 +269,39 @@ /** + * Initialize and get shared cache object + * + * @param string $name Cache identifier + * @param bool $packed Enables/disables data serialization + * + * @return rcube_cache_shared Cache object + */ + public function get_cache_shared($name, $packed=true) + { + $shared_name = "shared_$name"; + + if (!array_key_exists($shared_name, $this->caches)) { + $opt = strtolower($name) . '_cache'; + $type = $this->config->get($opt); + $ttl = $this->config->get($opt . '_ttl'); + + if (!$type) { + // cache is disabled + return $this->caches[$shared_name] = null; + } + + if ($ttl === null) { + $ttl = $this->config->get('shared_cache_ttl', '10d'); + } + + $this->caches[$shared_name] = new rcube_cache_shared($type, $name, $ttl, $packed); + } + + return $this->caches[$shared_name]; + } + + + /** * Create SMTP object and connect to server * * @param boolean True if connection should be established @@ -315,39 +359,18 @@ // for backward compat. (deprecated, will be removed) $this->imap = $this->storage; - // enable caching of mail data - $storage_cache = $this->config->get("{$driver}_cache"); - $messages_cache = $this->config->get('messages_cache'); - // for backward compatybility - if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) { - $storage_cache = 'db'; - $messages_cache = true; - } - - if ($storage_cache) { - $this->storage->set_caching($storage_cache); - } - if ($messages_cache) { - $this->storage->set_messages_caching(true); - } - - // set pagesize from config - $pagesize = $this->config->get('mail_pagesize'); - if (!$pagesize) { - $pagesize = $this->config->get('pagesize', 50); - } - $this->storage->set_pagesize($pagesize); - // set class options $options = array( - 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'), - 'auth_cid' => $this->config->get("{$driver}_auth_cid"), - 'auth_pw' => $this->config->get("{$driver}_auth_pw"), - 'debug' => (bool) $this->config->get("{$driver}_debug"), - 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"), - 'timeout' => (int) $this->config->get("{$driver}_timeout"), - 'skip_deleted' => (bool) $this->config->get('skip_deleted'), - 'driver' => $driver, + 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'), + 'auth_cid' => $this->config->get("{$driver}_auth_cid"), + 'auth_pw' => $this->config->get("{$driver}_auth_pw"), + 'debug' => (bool) $this->config->get("{$driver}_debug"), + 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"), + 'disabled_caps' => $this->config->get("{$driver}_disabled_caps"), + 'socket_options' => $this->config->get("{$driver}_conn_options"), + 'timeout' => (int) $this->config->get("{$driver}_timeout"), + 'skip_deleted' => (bool) $this->config->get('skip_deleted'), + 'driver' => $driver, ); if (!empty($_SESSION['storage_host'])) { @@ -366,30 +389,87 @@ $this->storage->set_options($options); $this->set_storage_prop(); - } + // subscribe to 'storage_connected' hook for session logging + if ($this->config->get('imap_log_session', false)) { + $this->plugins->register_hook('storage_connected', array($this, 'storage_log_session')); + } + } /** * Set storage parameters. - * This must be done AFTER connecting to the server! */ protected function set_storage_prop() { $storage = $this->get_storage(); + // set pagesize from config + $pagesize = $this->config->get('mail_pagesize'); + if (!$pagesize) { + $pagesize = $this->config->get('pagesize', 50); + } + + $storage->set_pagesize($pagesize); $storage->set_charset($this->config->get('default_charset', RCUBE_CHARSET)); - if ($default_folders = $this->config->get('default_folders')) { - $storage->set_default_folders($default_folders); + // enable caching of mail data + $driver = $this->config->get('storage_driver', 'imap'); + $storage_cache = $this->config->get("{$driver}_cache"); + $messages_cache = $this->config->get('messages_cache'); + // for backward compatybility + if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) { + $storage_cache = 'db'; + $messages_cache = true; } - if (isset($_SESSION['mbox'])) { - $storage->set_folder($_SESSION['mbox']); + + if ($storage_cache) { + $storage->set_caching($storage_cache); } - if (isset($_SESSION['page'])) { - $storage->set_page($_SESSION['page']); + if ($messages_cache) { + $storage->set_messages_caching(true); } } + + /** + * Set special folders type association. + * This must be done AFTER connecting to the server! + */ + protected function set_special_folders() + { + $storage = $this->get_storage(); + $folders = $storage->get_special_folders(true); + $prefs = array(); + + // check SPECIAL-USE flags on IMAP folders + foreach ($folders as $type => $folder) { + $idx = $type . '_mbox'; + if ($folder !== $this->config->get($idx)) { + $prefs[$idx] = $folder; + } + } + + // Some special folders differ, update user preferences + if (!empty($prefs) && $this->user) { + $this->user->save_prefs($prefs); + } + + // create default folders (on login) + if ($this->config->get('create_default_folders')) { + $storage->create_default_folders(); + } + } + + + /** + * Callback for IMAP connection events to log session identifiers + */ + public function storage_log_session($args) + { + if (!empty($args['session']) && session_id()) { + $this->write_log('imap_session', $args['session']); + } + } /** * Create session object and start the session. @@ -424,22 +504,36 @@ ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid'); ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); - ini_set('session.serialize_handler', 'php'); ini_set('session.cookie_httponly', 1); // use database for storing session data $this->session = new rcube_session($this->get_dbh(), $this->config); - $this->session->register_gc_handler(array($this, 'temp_gc')); - $this->session->register_gc_handler(array($this, 'cache_gc')); - + $this->session->register_gc_handler(array($this, 'gc')); $this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME'])); $this->session->set_ip_check($this->config->get('ip_check')); + if ($this->config->get('session_auth_name')) { + $this->session->set_cookiename($this->config->get('session_auth_name')); + } + // start PHP session (if not in CLI mode) if ($_SERVER['REMOTE_ADDR']) { - session_start(); + $this->session->start(); } + } + + + /** + * Garbage collector - cache/temp cleaner + */ + public function gc() + { + rcube_cache::gc(); + rcube_cache_shared::gc(); + $this->get_storage()->cache_gc(); + + $this->gc_temp(); } @@ -447,18 +541,25 @@ * Garbage collector function for temp files. * Remove temp files older than two days */ - public function temp_gc() + public function gc_temp() { $tmp = unslashify($this->config->get('temp_dir')); - $expire = time() - 172800; // expire in 48 hours + + // expire in 48 hours by default + $temp_dir_ttl = $this->config->get('temp_dir_ttl', '48h'); + $temp_dir_ttl = get_offset_sec($temp_dir_ttl); + if ($temp_dir_ttl < 6*3600) + $temp_dir_ttl = 6*3600; // 6 hours sensible lower bound. + + $expire = time() - $temp_dir_ttl; if ($tmp && ($dir = opendir($tmp))) { while (($fname = readdir($dir)) !== false) { - if ($fname{0} == '.') { + if ($fname[0] == '.') { continue; } - if (filemtime($tmp.'/'.$fname) < $expire) { + if (@filemtime($tmp.'/'.$fname) < $expire) { @unlink($tmp.'/'.$fname); } } @@ -469,14 +570,21 @@ /** - * Garbage collector for cache entries. - * Set flag to expunge caches on shutdown + * Runs garbage collector with probability based on + * session settings. This is intended for environments + * without a session. */ - public function cache_gc() + public function gc_run() { - // because this gc function is called before storage is initialized, - // we just set a flag to expunge storage cache on shutdown. - $this->expunge_cache = true; + $probability = (int) ini_get('session.gc_probability'); + $divisor = (int) ini_get('session.gc_divisor'); + + if ($divisor > 0 && $probability > 0) { + $random = mt_rand(1, $divisor); + if ($random <= $probability) { + $this->gc(); + } + } } @@ -580,10 +688,11 @@ /** * Load a localization package * - * @param string Language ID - * @param array Additional text labels/messages + * @param string $lang Language ID + * @param array $add Additional text labels/messages + * @param array $merge Additional text labels/messages to merge */ - public function load_language($lang = null, $add = array()) + public function load_language($lang = null, $add = array(), $merge = array()) { $lang = $this->language_prop(($lang ? $lang : $_SESSION['language'])); @@ -623,6 +732,11 @@ if (is_array($add) && !empty($add)) { $this->texts += $add; } + + // merge additional texts (from plugin) + if (is_array($merge) && !empty($merge)) { + $this->texts = array_merge($this->texts, $merge); + } } @@ -640,7 +754,11 @@ // user HTTP_ACCEPT_LANGUAGE if no language is specified if (empty($lang) || $lang == 'auto') { $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); - $lang = str_replace('-', '_', $accept_langs[0]); + $lang = $accept_langs[0]; + + if (preg_match('/^([a-z]+)[_-]([a-z]+)$/i', $lang, $m)) { + $lang = $m[1] . '_' . strtoupper($m[2]); + } } if (empty($rcube_languages)) { @@ -724,12 +842,19 @@ * upon decryption; see http://php.net/mcrypt_generic#68082 */ $clear = pack("a*H2", $clear, "80"); + $ckey = $this->config->get_crypto_key($key); - if (function_exists('mcrypt_module_open') && + if (function_exists('openssl_encrypt')) { + $method = 'DES-EDE3-CBC'; + $opts = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true; + $iv = $this->create_iv(openssl_cipher_iv_length($method)); + $cipher = $iv . openssl_encrypt($clear, $method, $ckey, $opts, $iv); + } + else if (function_exists('mcrypt_module_open') && ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")) ) { $iv = $this->create_iv(mcrypt_enc_get_iv_size($td)); - mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); + mcrypt_generic_init($td, $ckey, $iv); $cipher = $iv . mcrypt_generic($td, $clear); mcrypt_generic_deinit($td); mcrypt_module_close($td); @@ -740,13 +865,13 @@ if (function_exists('des')) { $des_iv_size = 8; $iv = $this->create_iv($des_iv_size); - $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv); + $cipher = $iv . des($ckey, $clear, 1, 1, $iv); } else { self::raise_error(array( 'code' => 500, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available" + 'message' => "Could not perform encryption; make sure OpenSSL or Mcrypt or lib/des.inc is available" ), true, true); } } @@ -771,12 +896,13 @@ } $cipher = $base64 ? base64_decode($cipher) : $cipher; + $ckey = $this->config->get_crypto_key($key); - if (function_exists('mcrypt_module_open') && - ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")) - ) { - $iv_size = mcrypt_enc_get_iv_size($td); - $iv = substr($cipher, 0, $iv_size); + if (function_exists('openssl_decrypt')) { + $method = 'DES-EDE3-CBC'; + $opts = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true; + $iv_size = openssl_cipher_iv_length($method); + $iv = substr($cipher, 0, $iv_size); // session corruption? (#1485970) if (strlen($iv) < $iv_size) { @@ -784,7 +910,21 @@ } $cipher = substr($cipher, $iv_size); - mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); + $clear = openssl_decrypt($cipher, $method, $ckey, $opts, $iv); + } + else if (function_exists('mcrypt_module_open') && + ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")) + ) { + $iv_size = mcrypt_enc_get_iv_size($td); + $iv = substr($cipher, 0, $iv_size); + + // session corruption? (#1485970) + if (strlen($iv) < $iv_size) { + return ''; + } + + $cipher = substr($cipher, $iv_size); + mcrypt_generic_init($td, $ckey, $iv); $clear = mdecrypt_generic($td, $cipher); mcrypt_generic_deinit($td); mcrypt_module_close($td); @@ -794,15 +934,15 @@ if (function_exists('des')) { $des_iv_size = 8; - $iv = substr($cipher, 0, $des_iv_size); + $iv = substr($cipher, 0, $des_iv_size); $cipher = substr($cipher, $des_iv_size); - $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv); + $clear = des($ckey, $cipher, 0, 1, $iv); } else { self::raise_error(array( 'code' => 500, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available" + 'message' => "Could not perform decryption; make sure OpenSSL or Mcrypt or lib/des.inc is available" ), true, true); } } @@ -860,6 +1000,14 @@ call_user_func($function); } + // write session data as soon as possible and before + // closing database connection, don't do this before + // registered shutdown functions, they may need the session + // Note: this will run registered gc handlers (ie. cache gc) + if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) { + $this->session->write_close(); + } + if (is_object($this->smtp)) { $this->smtp->disconnect(); } @@ -871,9 +1019,6 @@ } if (is_object($this->storage)) { - if ($this->expunge_cache) { - $this->storage->expunge_cache(); - } $this->storage->close(); } } @@ -1009,8 +1154,12 @@ $line = var_export($line, true); } - $date_format = self::$instance ? self::$instance->config->get('log_date_format') : null; - $log_driver = self::$instance ? self::$instance->config->get('log_driver') : null; + $date_format = $log_driver = $session_key = null; + if (self::$instance) { + $date_format = self::$instance->config->get('log_date_format'); + $log_driver = self::$instance->config->get('log_driver'); + $session_key = intval(self::$instance->config->get('log_session_id', 8)); + } if (empty($date_format)) { $date_format = 'd-M-Y H:i:s O'; @@ -1028,6 +1177,11 @@ return true; } + // add session ID to the log + if ($session_key > 0 && ($sess = session_id())) { + $line = '<' . substr($sess, 0, $session_key) . '> ' . $line; + } + if ($log_driver == 'syslog') { $prio = $name == 'errors' ? LOG_ERR : LOG_INFO; syslog($prio, $line); @@ -1037,7 +1191,20 @@ // log_driver == 'file' is assumed here $line = sprintf("[%s]: %s\n", $date, $line); - $log_dir = self::$instance ? self::$instance->config->get('log_dir') : null; + $log_dir = null; + + // per-user logging is activated + if (self::$instance && self::$instance->config->get('per_user_logging', false) && self::$instance->get_user_id()) { + $log_dir = self::$instance->get_user_log_dir(); + if (empty($log_dir)) + return false; + } + else if (!empty($log['dir'])) { + $log_dir = $log['dir']; + } + else if (self::$instance) { + $log_dir = self::$instance->config->get('log_dir'); + } if (empty($log_dir)) { $log_dir = RCUBE_INSTALL_PATH . 'logs'; @@ -1065,8 +1232,8 @@ * - code: Error code (required) * - type: Error type [php|db|imap|javascript] (required) * - message: Error message - * - file: File where error occured - * - line: Line where error occured + * - file: File where error occurred + * - line: Line where error occurred * @param boolean True to log the error * @param boolean Terminate script execution */ @@ -1075,7 +1242,6 @@ // handle PHP exceptions if (is_object($arg) && is_a($arg, 'Exception')) { $arg = array( - 'type' => 'php', 'code' => $arg->getCode(), 'line' => $arg->getLine(), 'file' => $arg->getFile(), @@ -1083,7 +1249,7 @@ ); } else if (is_string($arg)) { - $arg = array('message' => $arg, 'type' => 'php'); + $arg = array('message' => $arg); } if (empty($arg['code'])) { @@ -1091,15 +1257,15 @@ } // installer - if (class_exists('rcube_install', false)) { - $rci = rcube_install::get_instance(); + if (class_exists('rcmail_install', false)) { + $rci = rcmail_install::get_instance(); $rci->raise_error($arg); return; } $cli = php_sapi_name() == 'cli'; - if (($log || $terminate) && !$cli && $arg['type'] && $arg['message']) { + if (($log || $terminate) && !$cli && $arg['message']) { $arg['fatal'] = $terminate; self::log_bug($arg); } @@ -1116,6 +1282,9 @@ exit(1); } + else if ($cli) { + fwrite(STDERR, 'ERROR: ' . $arg['message']); + } } @@ -1127,7 +1296,7 @@ */ public static function log_bug($arg_arr) { - $program = strtoupper($arg_arr['type']); + $program = strtoupper(!empty($arg_arr['type']) ? $arg_arr['type'] : 'php'); $level = self::get_instance()->config->get('debug_level'); // disable errors for ajax requests, write to log instead (#1487831) @@ -1213,6 +1382,20 @@ self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff)); } + /** + * Setter for system user object + * + * @param rcube_user Current user instance + */ + public function set_user($user) + { + if (is_object($user)) { + $this->user = $user; + + // overwrite config with user preferences + $this->config->set_user_prefs((array)$this->user->get_prefs()); + } + } /** * Getter for logged user ID. @@ -1276,6 +1459,17 @@ } } + /** + * Get the per-user log directory + */ + protected function get_user_log_dir() + { + $log_dir = $this->config->get('log_dir', RCUBE_INSTALL_PATH . 'logs'); + $user_name = $this->get_user_name(); + $user_log_dir = $log_dir . '/' . $user_name; + + return !empty($user_name) && is_writable($user_log_dir) ? $user_log_dir : false; + } /** * Getter for logged user language code. @@ -1291,6 +1485,208 @@ return $_SESSION['language']; } } + + /** + * Unique Message-ID generator. + * + * @return string Message-ID + */ + public function gen_message_id() + { + $local_part = md5(uniqid('rcube'.mt_rand(), true)); + $domain_part = $this->user->get_username('domain'); + + // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924) + if (!preg_match('/\.[a-z]+$/i', $domain_part)) { + foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) { + $host = preg_replace('/:[0-9]+$/', '', $host); + if ($host && preg_match('/\.[a-z]+$/i', $host)) { + $domain_part = $host; + } + } + } + + return sprintf('<%s@%s>', $local_part, $domain_part); + } + + /** + * Send the given message using the configured method. + * + * @param object $message Reference to Mail_MIME object + * @param string $from Sender address string + * @param array $mailto Array of recipient address strings + * @param array $error SMTP error array (reference) + * @param string $body_file Location of file with saved message body (reference), + * used when delay_file_io is enabled + * @param array $options SMTP options (e.g. DSN request) + * + * @return boolean Send status. + */ + public function deliver_message(&$message, $from, $mailto, &$error, &$body_file = null, $options = null) + { + $plugin = $this->plugins->exec_hook('message_before_send', array( + 'message' => $message, + 'from' => $from, + 'mailto' => $mailto, + 'options' => $options, + )); + + if ($plugin['abort']) { + if (!empty($plugin['error'])) { + $error = $plugin['error']; + } + if (!empty($plugin['body_file'])) { + $body_file = $plugin['body_file']; + } + + return isset($plugin['result']) ? $plugin['result'] : false; + } + + $from = $plugin['from']; + $mailto = $plugin['mailto']; + $options = $plugin['options']; + $message = $plugin['message']; + $headers = $message->headers(); + + // send thru SMTP server using custom SMTP library + if ($this->config->get('smtp_server')) { + // generate list of recipients + $a_recipients = (array) $mailto; + + if (strlen($headers['Cc'])) + $a_recipients[] = $headers['Cc']; + if (strlen($headers['Bcc'])) + $a_recipients[] = $headers['Bcc']; + + // clean Bcc from header for recipients + $send_headers = $headers; + unset($send_headers['Bcc']); + // here too, it because txtHeaders() below use $message->_headers not only $send_headers + unset($message->_headers['Bcc']); + + $smtp_headers = $message->txtHeaders($send_headers, true); + + if ($message->getParam('delay_file_io')) { + // use common temp dir + $temp_dir = $this->config->get('temp_dir'); + $body_file = tempnam($temp_dir, 'rcmMsg'); + if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) { + self::raise_error(array('code' => 650, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Could not create message: ".$mime_result->getMessage()), + TRUE, FALSE); + return false; + } + $msg_body = fopen($body_file, 'r'); + } + else { + $msg_body = $message->get(); + } + + // send message + if (!is_object($this->smtp)) { + $this->smtp_init(true); + } + + $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $options); + $response = $this->smtp->get_response(); + $error = $this->smtp->get_error(); + + // log error + if (!$sent) { + self::raise_error(array('code' => 800, 'type' => 'smtp', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "SMTP error: ".join("\n", $response)), TRUE, FALSE); + } + } + // send mail using PHP's mail() function + else { + // unset some headers because they will be added by the mail() function + $headers_enc = $message->headers($headers); + $headers_php = $message->_headers; + unset($headers_php['To'], $headers_php['Subject']); + + // reset stored headers and overwrite + $message->_headers = array(); + $header_str = $message->txtHeaders($headers_php); + + // #1485779 + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) { + $headers_enc['To'] = implode(', ', $m[1]); + } + } + + $msg_body = $message->get(); + + if (PEAR::isError($msg_body)) { + self::raise_error(array('code' => 650, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Could not create message: ".$msg_body->getMessage()), + TRUE, FALSE); + } + else { + $delim = $this->config->header_delimiter(); + $to = $headers_enc['To']; + $subject = $headers_enc['Subject']; + $header_str = rtrim($header_str); + + if ($delim != "\r\n") { + $header_str = str_replace("\r\n", $delim, $header_str); + $msg_body = str_replace("\r\n", $delim, $msg_body); + $to = str_replace("\r\n", $delim, $to); + $subject = str_replace("\r\n", $delim, $subject); + } + + if (filter_var(ini_get('safe_mode'), FILTER_VALIDATE_BOOLEAN)) + $sent = mail($to, $subject, $msg_body, $header_str); + else + $sent = mail($to, $subject, $msg_body, $header_str, "-f$from"); + } + } + + if ($sent) { + $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body)); + + // remove MDN headers after sending + unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']); + + if ($this->config->get('smtp_log')) { + // get all recipient addresses + if (is_array($mailto)) { + $mailto = implode(',', $mailto); + } + if ($headers['Cc']) { + $mailto .= ',' . $headers['Cc']; + } + if ($headers['Bcc']) { + $mailto .= ',' . $headers['Bcc']; + } + + $mailto = rcube_mime::decode_address_list($mailto, null, false, null, true); + + self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s", + $this->user->get_username(), + rcube_utils::remote_addr(), + implode(', ', $mailto), + !empty($response) ? join('; ', $response) : '')); + } + } + else { + // allow plugins to catch sending errors with the same parameters as in 'message_before_send' + $this->plugins->exec_hook('message_send_error', $plugin + array('error' => $error)); + } + + if (is_resource($msg_body)) { + fclose($msg_body); + } + + $message->_headers = array(); + $message->headers($headers); + + return $sent; + } + } -- Gitblit v1.9.1