update phpMailer to 5.2.21

Signed-off-by: Michael Kaufmann (d00p) <d00p@froxlor.org>
This commit is contained in:
Michael Kaufmann (d00p)
2016-12-29 10:54:25 +01:00
parent 7a603596c5
commit 437446c49d
3 changed files with 5284 additions and 5126 deletions

View File

@@ -31,7 +31,7 @@ class PHPMailer
* The PHPMailer Version number. * The PHPMailer Version number.
* @var string * @var string
*/ */
public $Version = '5.2.16'; public $Version = '5.2.21';
/** /**
* Email priority. * Email priority.
@@ -201,6 +201,9 @@ class PHPMailer
/** /**
* An ID to be used in the Message-ID header. * An ID to be used in the Message-ID header.
* If empty, a unique id will be generated. * If empty, a unique id will be generated.
* You can set your own, but it must be in the format "<id@domain>",
* as defined in RFC5322 section 3.6.4 or it will be ignored.
* @see https://tools.ietf.org/html/rfc5322#section-3.6.4
* @var string * @var string
*/ */
public $MessageID = ''; public $MessageID = '';
@@ -420,6 +423,13 @@ class PHPMailer
*/ */
public $DKIM_private = ''; public $DKIM_private = '';
/**
* DKIM private key string.
* If set, takes precedence over `$DKIM_private`.
* @var string
*/
public $DKIM_private_string = '';
/** /**
* Callback Action function name. * Callback Action function name.
* *
@@ -681,16 +691,16 @@ class PHPMailer
} else { } else {
$subject = $this->encodeHeader($this->secureHeader($subject)); $subject = $this->encodeHeader($this->secureHeader($subject));
} }
//Can't use additional_parameters in safe_mode
//Can't use additional_parameters in safe_mode, calling mail() with null params breaks
//@link http://php.net/manual/en/function.mail.php //@link http://php.net/manual/en/function.mail.php
if (ini_get('safe_mode') or !$this->UseSendmailOptions) { if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
$result = @mail($to, $subject, $body, $header); $result = @mail($to, $subject, $body, $header);
} else { } else {
$result = @mail($to, $subject, $body, $header, $params); $result = @mail($to, $subject, $body, $header, $params);
} }
return $result; return $result;
} }
/** /**
* Output debugging info via user-defined method. * Output debugging info via user-defined method.
* Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug). * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
@@ -1284,9 +1294,11 @@ class PHPMailer
// Sign with DKIM if enabled // Sign with DKIM if enabled
if (!empty($this->DKIM_domain) if (!empty($this->DKIM_domain)
&& !empty($this->DKIM_private)
&& !empty($this->DKIM_selector) && !empty($this->DKIM_selector)
&& file_exists($this->DKIM_private)) { && (!empty($this->DKIM_private_string)
|| (!empty($this->DKIM_private) && file_exists($this->DKIM_private))
)
) {
$header_dkim = $this->DKIM_Add( $header_dkim = $this->DKIM_Add(
$this->MIMEHeader . $this->mailHeader, $this->MIMEHeader . $this->mailHeader,
$this->encodeHeader($this->secureHeader($this->Subject)), $this->encodeHeader($this->secureHeader($this->Subject)),
@@ -1352,19 +1364,24 @@ class PHPMailer
*/ */
protected function sendmailSend($header, $body) protected function sendmailSend($header, $body)
{ {
if ($this->Sender != '') { // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
if (!empty($this->Sender) and self::isShellSafe($this->Sender)) {
if ($this->Mailer == 'qmail') { if ($this->Mailer == 'qmail') {
$sendmail = sprintf('%s -f%s', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); $sendmailFmt = '%s -f%s';
} else { } else {
$sendmail = sprintf('%s -oi -f%s -t', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); $sendmailFmt = '%s -oi -f%s -t';
} }
} else { } else {
if ($this->Mailer == 'qmail') { if ($this->Mailer == 'qmail') {
$sendmail = sprintf('%s', escapeshellcmd($this->Sendmail)); $sendmailFmt = '%s';
} else { } else {
$sendmail = sprintf('%s -oi -t', escapeshellcmd($this->Sendmail)); $sendmailFmt = '%s -oi -t';
} }
} }
// TODO: If possible, this should be changed to escapeshellarg. Needs thorough testing.
$sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
if ($this->SingleTo) { if ($this->SingleTo) {
foreach ($this->SingleToArray as $toAddr) { foreach ($this->SingleToArray as $toAddr) {
if (!@$mail = popen($sendmail, 'w')) { if (!@$mail = popen($sendmail, 'w')) {
@@ -1410,6 +1427,40 @@ class PHPMailer
return true; return true;
} }
/**
* Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
*
* Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
* @param string $string The string to be validated
* @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
* @access protected
* @return boolean
*/
protected static function isShellSafe($string)
{
// Future-proof
if (escapeshellcmd($string) !== $string
or !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))
) {
return false;
}
$length = strlen($string);
for ($i = 0; $i < $length; $i++) {
$c = $string[$i];
// All other characters have a special meaning in at least one common shell, including = and +.
// Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
// Note that this does permit non-Latin alphanumeric characters based on the current locale.
if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
return false;
}
}
return true;
}
/** /**
* Send mail using the PHP mail() function. * Send mail using the PHP mail() function.
* @param string $header The message headers * @param string $header The message headers
@@ -1429,10 +1480,13 @@ class PHPMailer
$params = null; $params = null;
//This sets the SMTP envelope sender which gets turned into a return-path header by the receiver //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
if (!empty($this->Sender)) { if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
// CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
if (self::isShellSafe($this->Sender)) {
$params = sprintf('-f%s', $this->Sender); $params = sprintf('-f%s', $this->Sender);
} }
if ($this->Sender != '' and !ini_get('safe_mode')) { }
if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
$old_from = ini_get('sendmail_from'); $old_from = ini_get('sendmail_from');
ini_set('sendmail_from', $this->Sender); ini_set('sendmail_from', $this->Sender);
} }
@@ -1486,10 +1540,10 @@ class PHPMailer
if (!$this->smtpConnect($this->SMTPOptions)) { if (!$this->smtpConnect($this->SMTPOptions)) {
throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
} }
if ('' == $this->Sender) { if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
$smtp_from = $this->From;
} else {
$smtp_from = $this->Sender; $smtp_from = $this->Sender;
} else {
$smtp_from = $this->From;
} }
if (!$this->smtp->mail($smtp_from)) { if (!$this->smtp->mail($smtp_from)) {
$this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
@@ -1681,6 +1735,19 @@ class PHPMailer
*/ */
public function setLanguage($langcode = 'en', $lang_path = '') public function setLanguage($langcode = 'en', $lang_path = '')
{ {
// Backwards compatibility for renamed language codes
$renamed_langcodes = array(
'br' => 'pt_br',
'cz' => 'cs',
'dk' => 'da',
'no' => 'nb',
'se' => 'sv',
);
if (isset($renamed_langcodes[$langcode])) {
$langcode = $renamed_langcodes[$langcode];
}
// Define full set of translatable strings in English // Define full set of translatable strings in English
$PHPMAILER_LANG = array( $PHPMAILER_LANG = array(
'authenticate' => 'SMTP Error: Could not authenticate.', 'authenticate' => 'SMTP Error: Could not authenticate.',
@@ -1707,6 +1774,10 @@ class PHPMailer
// Calculate an absolute path so it can work if CWD is not here // Calculate an absolute path so it can work if CWD is not here
$lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR; $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
} }
//Validate $langcode
if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
$langcode = 'en';
}
$foundlang = true; $foundlang = true;
$lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
// There is no English translation file // There is no English translation file
@@ -2000,6 +2071,8 @@ class PHPMailer
$result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject))); $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
} }
// Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
// https://tools.ietf.org/html/rfc5322#section-3.6.4
if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) { if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
$this->lastMessageID = $this->MessageID; $this->lastMessageID = $this->MessageID;
} else { } else {
@@ -2105,6 +2178,14 @@ class PHPMailer
return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody; return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody;
} }
/**
* Create unique ID
* @return string
*/
protected function generateId() {
return md5(uniqid(time()));
}
/** /**
* Assemble the message body. * Assemble the message body.
* Returns an empty string on failure. * Returns an empty string on failure.
@@ -2116,7 +2197,7 @@ class PHPMailer
{ {
$body = ''; $body = '';
//Create unique IDs and preset boundaries //Create unique IDs and preset boundaries
$this->uniqueid = md5(uniqid(time())); $this->uniqueid = $this->generateId();
$this->boundary[1] = 'b1_' . $this->uniqueid; $this->boundary[1] = 'b1_' . $this->uniqueid;
$this->boundary[2] = 'b2_' . $this->uniqueid; $this->boundary[2] = 'b2_' . $this->uniqueid;
$this->boundary[3] = 'b3_' . $this->uniqueid; $this->boundary[3] = 'b3_' . $this->uniqueid;
@@ -3296,16 +3377,18 @@ class PHPMailer
} }
/** /**
* Create a message from an HTML string. * Create a message body from an HTML string.
* Automatically makes modifications for inline images and backgrounds * Automatically inlines images and creates a plain-text version by converting the HTML,
* and creates a plain-text version by converting the HTML. * overwriting any existing values in Body and AltBody.
* Overwrites any existing values in $this->Body and $this->AltBody * $basedir is used when handling relative image paths, e.g. <img src="images/a.png">
* will look for an image file in $basedir/images/a.png and convert it to inline.
* If you don't want to apply these transformations to your HTML, just set Body and AltBody yourself.
* @access public * @access public
* @param string $message HTML message string * @param string $message HTML message string
* @param string $basedir baseline directory for path * @param string $basedir base directory for relative paths to images
* @param boolean|callable $advanced Whether to use the internal HTML to text converter * @param boolean|callable $advanced Whether to use the internal HTML to text converter
* or your own custom converter @see PHPMailer::html2text() * or your own custom converter @see PHPMailer::html2text()
* @return string $message * @return string $message The transformed message Body
*/ */
public function msgHTML($message, $basedir = '', $advanced = false) public function msgHTML($message, $basedir = '', $advanced = false)
{ {
@@ -3375,7 +3458,7 @@ class PHPMailer
* Convert an HTML string into plain text. * Convert an HTML string into plain text.
* This is used by msgHTML(). * This is used by msgHTML().
* Note - older versions of this function used a bundled advanced converter * Note - older versions of this function used a bundled advanced converter
* which was been removed for license reasons in #232 * which was been removed for license reasons in #232.
* Example usage: * Example usage:
* <code> * <code>
* // Use default conversion * // Use default conversion
@@ -3675,7 +3758,7 @@ class PHPMailer
* @access public * @access public
* @param string $signHeader * @param string $signHeader
* @throws phpmailerException * @throws phpmailerException
* @return string * @return string The DKIM signature value
*/ */
public function DKIM_Sign($signHeader) public function DKIM_Sign($signHeader)
{ {
@@ -3685,16 +3768,34 @@ class PHPMailer
} }
return ''; return '';
} }
$privKeyStr = file_get_contents($this->DKIM_private); $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private);
if ($this->DKIM_passphrase != '') { if ('' != $this->DKIM_passphrase) {
$privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
} else { } else {
$privKey = openssl_pkey_get_private($privKeyStr); $privKey = openssl_pkey_get_private($privKeyStr);
} }
if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) { //sha1WithRSAEncryption //Workaround for missing digest algorithms in old PHP & OpenSSL versions
//@link http://stackoverflow.com/a/11117338/333340
if (version_compare(PHP_VERSION, '5.3.0') >= 0 and
in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
openssl_pkey_free($privKey); openssl_pkey_free($privKey);
return base64_encode($signature); return base64_encode($signature);
} }
} else {
$pinfo = openssl_pkey_get_details($privKey);
$hash = hash('sha256', $signHeader);
//'Magic' constant for SHA256 from RFC3447
//@link https://tools.ietf.org/html/rfc3447#page-43
$t = '3031300d060960864801650304020105000420' . $hash;
$pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3);
$eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t);
if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) {
openssl_pkey_free($privKey);
return base64_encode($signature);
}
}
openssl_pkey_free($privKey); openssl_pkey_free($privKey);
return ''; return '';
} }

View File

@@ -30,7 +30,7 @@ class SMTP
* The PHPMailer SMTP version number. * The PHPMailer SMTP version number.
* @var string * @var string
*/ */
const VERSION = '5.2.16'; const VERSION = '5.2.21';
/** /**
* SMTP line break constant. * SMTP line break constant.
@@ -81,7 +81,7 @@ class SMTP
* @deprecated Use the `VERSION` constant instead * @deprecated Use the `VERSION` constant instead
* @see SMTP::VERSION * @see SMTP::VERSION
*/ */
public $Version = '5.2.16'; public $Version = '5.2.21';
/** /**
* SMTP server port number. * SMTP server port number.
@@ -150,6 +150,17 @@ class SMTP
*/ */
public $Timelimit = 300; 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.
*/
protected $smtp_transaction_id_patterns = array(
'exim' => '/[0-9]{3} OK id=(.*)/',
'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
);
/** /**
* The socket for the server connection. * The socket for the server connection.
* @var resource * @var resource
@@ -206,7 +217,7 @@ class SMTP
} }
//Avoid clash with built-in function names //Avoid clash with built-in function names
if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
call_user_func($this->Debugoutput, $str, $this->do_debug); call_user_func($this->Debugoutput, $str, $level);
return; return;
} }
switch ($this->Debugoutput) { switch ($this->Debugoutput) {
@@ -272,8 +283,8 @@ class SMTP
$errstr = ''; $errstr = '';
if ($streamok) { if ($streamok) {
$socket_context = stream_context_create($options); $socket_context = stream_context_create($options);
//Suppress errors; connection failures are handled at a higher level set_error_handler(array($this, 'errorHandler'));
$this->smtp_conn = @stream_socket_client( $this->smtp_conn = stream_socket_client(
$host . ":" . $port, $host . ":" . $port,
$errno, $errno,
$errstr, $errstr,
@@ -281,12 +292,14 @@ class SMTP
STREAM_CLIENT_CONNECT, STREAM_CLIENT_CONNECT,
$socket_context $socket_context
); );
restore_error_handler();
} else { } else {
//Fall back to fsockopen which should work in more places, but is missing some features //Fall back to fsockopen which should work in more places, but is missing some features
$this->edebug( $this->edebug(
"Connection: stream_socket_client not available, falling back to fsockopen", "Connection: stream_socket_client not available, falling back to fsockopen",
self::DEBUG_CONNECTION self::DEBUG_CONNECTION
); );
set_error_handler(array($this, 'errorHandler'));
$this->smtp_conn = fsockopen( $this->smtp_conn = fsockopen(
$host, $host,
$port, $port,
@@ -294,6 +307,7 @@ class SMTP
$errstr, $errstr,
$timeout $timeout
); );
restore_error_handler();
} }
// Verify we connected properly // Verify we connected properly
if (!is_resource($this->smtp_conn)) { if (!is_resource($this->smtp_conn)) {
@@ -474,7 +488,7 @@ class SMTP
$temp = new stdClass; $temp = new stdClass;
$ntlm_client = new ntlm_sasl_client_class; $ntlm_client = new ntlm_sasl_client_class;
//Check that functions are available //Check that functions are available
if (!$ntlm_client->Initialize($temp)) { if (!$ntlm_client->initialize($temp)) {
$this->setError($temp->error); $this->setError($temp->error);
$this->edebug( $this->edebug(
'You need to enable some modules in your php.ini file: ' 'You need to enable some modules in your php.ini file: '
@@ -484,7 +498,7 @@ class SMTP
return false; return false;
} }
//msg1 //msg1
$msg1 = $ntlm_client->TypeMsg1($realm, $workstation); //msg1 $msg1 = $ntlm_client->typeMsg1($realm, $workstation); //msg1
if (!$this->sendCommand( if (!$this->sendCommand(
'AUTH NTLM', 'AUTH NTLM',
@@ -503,7 +517,7 @@ class SMTP
$password $password
); );
//msg3 //msg3
$msg3 = $ntlm_client->TypeMsg3( $msg3 = $ntlm_client->typeMsg3(
$ntlm_res, $ntlm_res,
$username, $username,
$realm, $realm,
@@ -1189,4 +1203,47 @@ class SMTP
{ {
return $this->Timeout; return $this->Timeout;
} }
/**
* Reports an error number and string.
* @param integer $errno The error number returned by PHP.
* @param string $errmsg The error message returned by PHP.
*/
protected function errorHandler($errno, $errmsg)
{
$notice = 'Connection: Failed to connect to server.';
$this->setError(
$notice,
$errno,
$errmsg
);
$this->edebug(
$notice . ' Error number ' . $errno . '. "Error notice: ' . $errmsg,
self::DEBUG_CONNECTION
);
}
/**
* Will return the ID of the last smtp transaction based on a list of patterns provided
* in SMTP::$smtp_transaction_id_patterns.
* If no reply has been received yet, it will return null.
* If no pattern has been matched, it will return false.
* @return bool|null|string
*/
public function getLastTransactionID()
{
$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];
}
}
return false;
}
} }