diff --git a/e107_handlers/phpmailer/class.phpmailer.php b/e107_handlers/phpmailer/class.phpmailer.php index 1b31ec14c..12a958757 100644 --- a/e107_handlers/phpmailer/class.phpmailer.php +++ b/e107_handlers/phpmailer/class.phpmailer.php @@ -31,7 +31,7 @@ class PHPMailer * The PHPMailer Version number. * @var string */ - public $Version = '5.2.23'; + public $Version = '5.2.27'; /** * Email priority. @@ -440,9 +440,9 @@ class PHPMailer * * Parameters: * boolean $result result of the send action - * string $to email address of the recipient - * string $cc cc email addresses - * string $bcc bcc email addresses + * array $to email addresses of the recipients + * array $cc cc email addresses + * array $bcc bcc email addresses * string $subject the subject * string $body the email body * string $from email address of sender @@ -659,6 +659,8 @@ class PHPMailer if ($exceptions !== null) { $this->exceptions = (boolean)$exceptions; } + //Pick an appropriate debug output format automatically + $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html'); } /** @@ -1294,9 +1296,12 @@ class PHPMailer // Sign with DKIM if enabled if (!empty($this->DKIM_domain) - && !empty($this->DKIM_selector) - && (!empty($this->DKIM_private_string) - || (!empty($this->DKIM_private) && file_exists($this->DKIM_private)) + and !empty($this->DKIM_selector) + and (!empty($this->DKIM_private_string) + or (!empty($this->DKIM_private) + and self::isPermittedPath($this->DKIM_private) + and file_exists($this->DKIM_private) + ) ) ) { $header_dkim = $this->DKIM_Add( @@ -1461,6 +1466,18 @@ class PHPMailer return true; } + /** + * Check whether a file path is of a permitted type. + * Used to reject URLs and phar files from functions that access local file paths, + * such as addAttachment. + * @param string $path A relative or absolute path to a file. + * @return bool + */ + protected static function isPermittedPath($path) + { + return !preg_match('#^[a-z]+://#i', $path); + } + /** * Send mail using the PHP mail() function. * @param string $header The message headers @@ -1622,8 +1639,13 @@ class PHPMailer foreach ($hosts as $hostentry) { $hostinfo = array(); - if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) { + if (!preg_match( + '/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*|\[[a-fA-F0-9:]+\]):?([0-9]*)$/', + trim($hostentry), + $hostinfo + )) { // Not a valid host entry + $this->edebug('Ignoring invalid host: ' . $hostentry); continue; } // $hostinfo[2]: optional ssl or tls prefix @@ -1742,6 +1764,7 @@ class PHPMailer 'dk' => 'da', 'no' => 'nb', 'se' => 'sv', + 'sr' => 'rs' ); if (isset($renamed_langcodes[$langcode])) { @@ -1783,7 +1806,7 @@ class PHPMailer // There is no English translation file if ($langcode != 'en') { // Make sure language file path is readable - if (!is_readable($lang_file)) { + if (!self::isPermittedPath($lang_file) or !is_readable($lang_file)) { $foundlang = false; } else { // Overwrite language-specific strings. @@ -2024,10 +2047,7 @@ class PHPMailer { $result = ''; - if ($this->MessageDate == '') { - $this->MessageDate = self::rfcDate(); - } - $result .= $this->headerLine('Date', $this->MessageDate); + $result .= $this->headerLine('Date', $this->MessageDate == '' ? self::rfcDate() : $this->MessageDate); // To be created automatically by mail() if ($this->SingleTo) { @@ -2494,6 +2514,8 @@ class PHPMailer * Add an attachment from a path on the filesystem. * Never use a user-supplied path to a file! * Returns false if the file could not be found or read. + * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client. + * If you need to do that, fetch the resource yourself and pass it in via a local file or string. * @param string $path Path to the attachment. * @param string $name Overrides the attachment name. * @param string $encoding File encoding (see $Encoding). @@ -2505,7 +2527,7 @@ class PHPMailer public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment') { try { - if (!@is_file($path)) { + if (!self::isPermittedPath($path) or !@is_file($path)) { throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE); } @@ -2686,7 +2708,7 @@ class PHPMailer protected function encodeFile($path, $encoding = 'base64') { try { - if (!is_readable($path)) { + if (!self::isPermittedPath($path) or !file_exists($path)) { throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE); } $magic_quotes = get_magic_quotes_runtime(); @@ -3030,7 +3052,7 @@ class PHPMailer */ public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline') { - if (!@is_file($path)) { + if (!self::isPermittedPath($path) or !@is_file($path)) { $this->setError($this->lang('file_access') . $path); return false; } @@ -4033,7 +4055,7 @@ class phpmailerException extends Exception */ public function errorMessage() { - $errorMsg = '' . $this->getMessage() . "
\n"; + $errorMsg = '' . htmlspecialchars($this->getMessage()) . "
\n"; return $errorMsg; } } diff --git a/e107_handlers/phpmailer/class.pop3.php b/e107_handlers/phpmailer/class.pop3.php index c464f90c6..5a458e5d5 100644 --- a/e107_handlers/phpmailer/class.pop3.php +++ b/e107_handlers/phpmailer/class.pop3.php @@ -34,7 +34,7 @@ class POP3 * @var string * @access public */ - public $Version = '5.2.23'; + public $Version = '5.2.27'; /** * Default POP3 port number. diff --git a/e107_handlers/phpmailer/class.smtp.php b/e107_handlers/phpmailer/class.smtp.php index 01cee8209..118cb20f6 100644 --- a/e107_handlers/phpmailer/class.smtp.php +++ b/e107_handlers/phpmailer/class.smtp.php @@ -30,7 +30,7 @@ class SMTP * The PHPMailer SMTP version number. * @var string */ - const VERSION = '5.2.23'; + const VERSION = '5.2.27'; /** * SMTP line break constant. @@ -81,7 +81,7 @@ class SMTP * @deprecated Use the `VERSION` constant instead * @see SMTP::VERSION */ - public $Version = '5.2.23'; + public $Version = '5.2.27'; /** * SMTP server port number. @@ -151,9 +151,8 @@ class SMTP public $Timelimit = 300; /** - * @var array patterns to extract smtp transaction id from smtp reply - * Only first capture group will be use, use non-capturing group to deal with it - * Extend this class to override this property to fulfil your needs. + * @var array Patterns to extract an SMTP transaction id from reply to a DATA command. + * The first capture group in each regex will be used as the ID. */ protected $smtp_transaction_id_patterns = array( 'exim' => '/[0-9]{3} OK id=(.*)/', @@ -161,6 +160,12 @@ class SMTP 'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/' ); + /** + * @var string The last transaction ID issued in response to a DATA command, + * if one was detected + */ + protected $last_smtp_transaction_id; + /** * The socket for the server connection. * @var resource @@ -227,7 +232,7 @@ class SMTP break; case 'html': //Cleans up output a bit for a better looking, HTML-safe output - echo htmlentities( + echo gmdate('Y-m-d H:i:s') . ' ' . htmlentities( preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, 'UTF-8' @@ -709,6 +714,7 @@ class SMTP $savetimelimit = $this->Timelimit; $this->Timelimit = $this->Timelimit * 2; $result = $this->sendCommand('DATA END', '.', 250); + $this->recordLastTransactionID(); //Restore timelimit $this->Timelimit = $savetimelimit; return $result; @@ -989,7 +995,10 @@ class SMTP public function client_send($data) { $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); - return fwrite($this->smtp_conn, $data); + set_error_handler(array($this, 'errorHandler')); + $result = fwrite($this->smtp_conn, $data); + restore_error_handler(); + return $result; } /** @@ -1089,8 +1098,10 @@ class SMTP $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); $data .= $str; - // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen - if ((isset($str[3]) and $str[3] == ' ')) { + // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled), + // or 4th character is a space, we are done reading, break the loop, + // string array access is a micro-optimisation over strlen + if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) { break; } // Timed-out? Log and break @@ -1226,26 +1237,40 @@ class SMTP } /** - * Will return the ID of the last smtp transaction based on a list of patterns provided - * in SMTP::$smtp_transaction_id_patterns. + * Extract and return the ID of the last SMTP transaction based on + * a list of patterns provided in SMTP::$smtp_transaction_id_patterns. + * Relies on the host providing the ID in response to a DATA command. * If no reply has been received yet, it will return null. - * If no pattern has been matched, it will return false. + * If no pattern was matched, it will return false. * @return bool|null|string */ - public function getLastTransactionID() + protected function recordLastTransactionID() { $reply = $this->getLastReply(); if (empty($reply)) { - return null; - } - - foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) { - if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) { - return $matches[1]; + $this->last_smtp_transaction_id = null; + } else { + $this->last_smtp_transaction_id = false; + foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) { + if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) { + $this->last_smtp_transaction_id = $matches[1]; + } } } - return false; + return $this->last_smtp_transaction_id; + } + + /** + * Get the queue/transaction ID of the last SMTP transaction + * If no reply has been received yet, it will return null. + * If no pattern was matched, it will return false. + * @return bool|null|string + * @see recordLastTransactionID() + */ + public function getLastTransactionID() + { + return $this->last_smtp_transaction_id; } }