+ return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
+ }
+ } catch (phpmailerException $exc) {
+ $this->setError($exc->getMessage());
+ $this->edebug($exc->getMessage());
+ if ($this->exceptions) {
+ throw $exc;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Send mail using the $Sendmail program.
+ * @param string $header The message headers
+ * @param string $body The message body
+ * @see PHPMailer::$Sendmail
+ * @throws phpmailerException
+ * @access protected
+ * @return boolean
+ */
+ protected function sendmailSend($header, $body)
+ {
+ // 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') {
+ $sendmailFmt = '%s -f%s';
+ } else {
+ $sendmailFmt = '%s -oi -f%s -t';
+ }
+ } else {
+ if ($this->Mailer == 'qmail') {
+ $sendmailFmt = '%s';
+ } else {
+ $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) {
+ foreach ($this->SingleToArray as $toAddr) {
+ if (!@$mail = popen($sendmail, 'w')) {
+ throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
+ }
+ fputs($mail, 'To: ' . $toAddr . "\n");
+ fputs($mail, $header);
+ fputs($mail, $body);
+ $result = pclose($mail);
+ $this->doCallback(
+ ($result == 0),
+ array($toAddr),
+ $this->cc,
+ $this->bcc,
+ $this->Subject,
+ $body,
+ $this->From
+ );
+ if ($result != 0) {
+ throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
+ }
+ }
+ } else {
+ if (!@$mail = popen($sendmail, 'w')) {
+ throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
+ }
+ fputs($mail, $header);
+ fputs($mail, $body);
+ $result = pclose($mail);
+ $this->doCallback(
+ ($result == 0),
+ $this->to,
+ $this->cc,
+ $this->bcc,
+ $this->Subject,
+ $body,
+ $this->From
+ );
+ if ($result != 0) {
+ throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
+ }
+ }
+ 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.
+ * @param string $header The message headers
+ * @param string $body The message body
+ * @link http://www.php.net/manual/en/book.mail.php
+ * @throws phpmailerException
+ * @access protected
+ * @return boolean
+ */
+ protected function mailSend($header, $body)
+ {
+ $toArr = array();
+ foreach ($this->to as $toaddr) {
+ $toArr[] = $this->addrFormat($toaddr);
+ }
+ $to = implode(', ', $toArr);
+
+ $params = null;
+ //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
+ 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);
+ }
+ }
+ if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
+ $old_from = ini_get('sendmail_from');
+ ini_set('sendmail_from', $this->Sender);
+ }
+ $result = false;
+ if ($this->SingleTo and count($toArr) > 1) {
+ foreach ($toArr as $toAddr) {
+ $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
+ $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
+ }
+ } else {
+ $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
+ $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
+ }
+ if (isset($old_from)) {
+ ini_set('sendmail_from', $old_from);
+ }
+ if (!$result) {
+ throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
+ }
+ return true;
+ }
+
+ /**
+ * Get an instance to use for SMTP operations.
+ * Override this function to load your own SMTP implementation
+ * @return SMTP
+ */
+ public function getSMTPInstance()
+ {
+ if (!is_object($this->smtp)) {
+ require_once( 'class-smtp.php' );
+ $this->smtp = new SMTP;
+ }
+ return $this->smtp;
+ }
+
+ /**
+ * Send mail via SMTP.
+ * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
+ * Uses the PHPMailerSMTP class by default.
+ * @see PHPMailer::getSMTPInstance() to use a different class.
+ * @param string $header The message headers
+ * @param string $body The message body
+ * @throws phpmailerException
+ * @uses SMTP
+ * @access protected
+ * @return boolean
+ */
+ protected function smtpSend($header, $body)
+ {
+ $bad_rcpt = array();
+ if (!$this->smtpConnect($this->SMTPOptions)) {
+ throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
+ }
+ if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
+ $smtp_from = $this->Sender;
+ } else {
+ $smtp_from = $this->From;
+ }
+ if (!$this->smtp->mail($smtp_from)) {
+ $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
+ throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL);
+ }
+
+ // Attempt to send to all recipients
+ foreach (array($this->to, $this->cc, $this->bcc) as $togroup) {
+ foreach ($togroup as $to) {
+ if (!$this->smtp->recipient($to[0])) {
+ $error = $this->smtp->getError();
+ $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']);
+ $isSent = false;
+ } else {
+ $isSent = true;
+ }
+ $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From);
+ }
+ }
+
+ // Only send the DATA command if we have viable recipients
+ if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
+ throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL);
+ }
+ if ($this->SMTPKeepAlive) {
+ $this->smtp->reset();
+ } else {
+ $this->smtp->quit();
+ $this->smtp->close();
+ }
+ //Create error message for any bad addresses
+ if (count($bad_rcpt) > 0) {
+ $errstr = '';
+ foreach ($bad_rcpt as $bad) {
+ $errstr .= $bad['to'] . ': ' . $bad['error'];
+ }
+ throw new phpmailerException(
+ $this->lang('recipients_failed') . $errstr,
+ self::STOP_CONTINUE
+ );
+ }
+ return true;
+ }
+
+ /**
+ * Initiate a connection to an SMTP server.
+ * Returns false if the operation failed.
+ * @param array $options An array of options compatible with stream_context_create()
+ * @uses SMTP
+ * @access public
+ * @throws phpmailerException
+ * @return boolean
+ */
+ public function smtpConnect($options = null)
+ {
+ if (is_null($this->smtp)) {
+ $this->smtp = $this->getSMTPInstance();
+ }
+
+ //If no options are provided, use whatever is set in the instance
+ if (is_null($options)) {
+ $options = $this->SMTPOptions;
+ }
+
+ // Already connected?
+ if ($this->smtp->connected()) {
+ return true;
+ }
+
+ $this->smtp->setTimeout($this->Timeout);
+ $this->smtp->setDebugLevel($this->SMTPDebug);
+ $this->smtp->setDebugOutput($this->Debugoutput);
+ $this->smtp->setVerp($this->do_verp);
+ $hosts = explode(';', $this->Host);
+ $lastexception = null;
+
+ foreach ($hosts as $hostentry) {
+ $hostinfo = array();
+ if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) {
+ // Not a valid host entry
+ continue;
+ }
+ // $hostinfo[2]: optional ssl or tls prefix
+ // $hostinfo[3]: the hostname
+ // $hostinfo[4]: optional port number
+ // The host string prefix can temporarily override the current setting for SMTPSecure
+ // If it's not specified, the default value is used
+ $prefix = '';
+ $secure = $this->SMTPSecure;
+ $tls = ($this->SMTPSecure == 'tls');
+ if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
+ $prefix = 'ssl://';
+ $tls = false; // Can't have SSL and TLS at the same time
+ $secure = 'ssl';
+ } elseif ($hostinfo[2] == 'tls') {
+ $tls = true;
+ // tls doesn't use a prefix
+ $secure = 'tls';
+ }
+ //Do we need the OpenSSL extension?
+ $sslext = defined('OPENSSL_ALGO_SHA1');
+ if ('tls' === $secure or 'ssl' === $secure) {
+ //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
+ if (!$sslext) {
+ throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
+ }
+ }
+ $host = $hostinfo[3];
+ $port = $this->Port;
+ $tport = (integer)$hostinfo[4];
+ if ($tport > 0 and $tport < 65536) {
+ $port = $tport;
+ }
+ if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
+ try {
+ if ($this->Helo) {
+ $hello = $this->Helo;
+ } else {
+ $hello = $this->serverHostname();
+ }
+ $this->smtp->hello($hello);
+ //Automatically enable TLS encryption if:
+ // * it's not disabled
+ // * we have openssl extension
+ // * we are not already using SSL
+ // * the server offers STARTTLS
+ if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) {
+ $tls = true;
+ }
+ if ($tls) {
+ if (!$this->smtp->startTLS()) {
+ throw new phpmailerException($this->lang('connect_host'));
+ }
+ // We must resend EHLO after TLS negotiation
+ $this->smtp->hello($hello);
+ }
+ if ($this->SMTPAuth) {
+ if (!$this->smtp->authenticate(
+ $this->Username,
+ $this->Password,
+ $this->AuthType,
+ $this->Realm,
+ $this->Workstation
+ )
+ ) {
+ throw new phpmailerException($this->lang('authenticate'));
+ }
+ }
+ return true;
+ } catch (phpmailerException $exc) {
+ $lastexception = $exc;
+ $this->edebug($exc->getMessage());
+ // We must have connected, but then failed TLS or Auth, so close connection nicely
+ $this->smtp->quit();
+ }
+ }
+ }
+ // If we get here, all connection attempts have failed, so close connection hard
+ $this->smtp->close();
+ // As we've caught all exceptions, just report whatever the last one was
+ if ($this->exceptions and !is_null($lastexception)) {
+ throw $lastexception;
+ }
+ return false;
+ }
+
+ /**
+ * Close the active SMTP session if one exists.
+ * @return void
+ */
+ public function smtpClose()
+ {
+ if (is_a($this->smtp, 'SMTP')) {
+ if ($this->smtp->connected()) {
+ $this->smtp->quit();
+ $this->smtp->close();
+ }
+ }
+ }
+
+ /**
+ * Set the language for error messages.
+ * Returns false if it cannot load the language file.
+ * The default language is English.
+ * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")
+ * @param string $lang_path Path to the language file directory, with trailing separator (slash)
+ * @return boolean
+ * @access public
+ */
+ 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
+ $PHPMAILER_LANG = array(
+ 'authenticate' => 'SMTP Error: Could not authenticate.',
+ 'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
+ 'data_not_accepted' => 'SMTP Error: data not accepted.',
+ 'empty_message' => 'Message body empty',
+ 'encoding' => 'Unknown encoding: ',
+ 'execute' => 'Could not execute: ',
+ 'file_access' => 'Could not access file: ',
+ 'file_open' => 'File Error: Could not open file: ',
+ 'from_failed' => 'The following From address failed: ',
+ 'instantiate' => 'Could not instantiate mail function.',
+ 'invalid_address' => 'Invalid address: ',
+ 'mailer_not_supported' => ' mailer is not supported.',
+ 'provide_address' => 'You must provide at least one recipient email address.',
+ 'recipients_failed' => 'SMTP Error: The following recipients failed: ',
+ 'signing' => 'Signing Error: ',
+ 'smtp_connect_failed' => 'SMTP connect() failed.',
+ 'smtp_error' => 'SMTP server error: ',
+ 'variable_set' => 'Cannot set or reset variable: ',
+ 'extension_missing' => 'Extension missing: '
+ );
+ if (empty($lang_path)) {
+ // Calculate an absolute path so it can work if CWD is not here
+ $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;
+ $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
+ // There is no English translation file
+ if ($langcode != 'en') {
+ // Make sure language file path is readable
+ if (!is_readable($lang_file)) {
+ $foundlang = false;
+ } else {
+ // Overwrite language-specific strings.
+ // This way we'll never have missing translation keys.
+ $foundlang = include $lang_file;
+ }
+ }
+ $this->language = $PHPMAILER_LANG;
+ return (boolean)$foundlang; // Returns false if language not found
+ }
+
+ /**
+ * Get the array of strings for the current language.
+ * @return array
+ */
+ public function getTranslations()
+ {
+ return $this->language;
+ }
+
+ /**
+ * Create recipient headers.
+ * @access public
+ * @param string $type
+ * @param array $addr An array of recipient,
+ * where each recipient is a 2-element indexed array with element 0 containing an address
+ * and element 1 containing a name, like:
+ * array(array('joe@example.com', 'Joe User'), array('zoe@example.com', 'Zoe User'))
+ * @return string
+ */
+ public function addrAppend($type, $addr)
+ {
+ $addresses = array();
+ foreach ($addr as $address) {
+ $addresses[] = $this->addrFormat($address);
+ }
+ return $type . ': ' . implode(', ', $addresses) . $this->LE;
+ }
+
+ /**
+ * Format an address for use in a message header.
+ * @access public
+ * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name
+ * like array('joe@example.com', 'Joe User')
+ * @return string
+ */
+ public function addrFormat($addr)
+ {
+ if (empty($addr[1])) { // No name provided
+ return $this->secureHeader($addr[0]);
+ } else {
+ return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
+ $addr[0]
+ ) . '>';
+ }
+ }
+
+ /**
+ * Word-wrap message.
+ * For use with mailers that do not automatically perform wrapping
+ * and for quoted-printable encoded messages.
+ * Original written by philippe.
+ * @param string $message The message to wrap
+ * @param integer $length The line length to wrap to
+ * @param boolean $qp_mode Whether to run in Quoted-Printable mode
+ * @access public
+ * @return string
+ */
+ public function wrapText($message, $length, $qp_mode = false)
+ {
+ if ($qp_mode) {
+ $soft_break = sprintf(' =%s', $this->LE);
+ } else {
+ $soft_break = $this->LE;
+ }
+ // If utf-8 encoding is used, we will need to make sure we don't
+ // split multibyte characters when we wrap
+ $is_utf8 = (strtolower($this->CharSet) == 'utf-8');
+ $lelen = strlen($this->LE);
+ $crlflen = strlen(self::CRLF);
+
+ $message = $this->fixEOL($message);
+ //Remove a trailing line break
+ if (substr($message, -$lelen) == $this->LE) {
+ $message = substr($message, 0, -$lelen);
+ }
+
+ //Split message into lines
+ $lines = explode($this->LE, $message);
+ //Message will be rebuilt in here
+ $message = '';
+ foreach ($lines as $line) {
+ $words = explode(' ', $line);
+ $buf = '';
+ $firstword = true;
+ foreach ($words as $word) {
+ if ($qp_mode and (strlen($word) > $length)) {
+ $space_left = $length - strlen($buf) - $crlflen;
+ if (!$firstword) {
+ if ($space_left > 20) {
+ $len = $space_left;
+ if ($is_utf8) {
+ $len = $this->utf8CharBoundary($word, $len);
+ } elseif (substr($word, $len - 1, 1) == '=') {
+ $len--;
+ } elseif (substr($word, $len - 2, 1) == '=') {
+ $len -= 2;
+ }
+ $part = substr($word, 0, $len);
+ $word = substr($word, $len);
+ $buf .= ' ' . $part;
+ $message .= $buf . sprintf('=%s', self::CRLF);
+ } else {
+ $message .= $buf . $soft_break;
+ }
+ $buf = '';
+ }
+ while (strlen($word) > 0) {
+ if ($length <= 0) {
+ break;
+ }
+ $len = $length;
+ if ($is_utf8) {
+ $len = $this->utf8CharBoundary($word, $len);
+ } elseif (substr($word, $len - 1, 1) == '=') {
+ $len--;
+ } elseif (substr($word, $len - 2, 1) == '=') {
+ $len -= 2;
+ }
+ $part = substr($word, 0, $len);
+ $word = substr($word, $len);
+
+ if (strlen($word) > 0) {
+ $message .= $part . sprintf('=%s', self::CRLF);
+ } else {
+ $buf = $part;
+ }
+ }
+ } else {
+ $buf_o = $buf;
+ if (!$firstword) {
+ $buf .= ' ';
+ }
+ $buf .= $word;
+
+ if (strlen($buf) > $length and $buf_o != '') {
+ $message .= $buf_o . $soft_break;
+ $buf = $word;
+ }
+ }
+ $firstword = false;
+ }
+ $message .= $buf . self::CRLF;
+ }
+
+ return $message;
+ }
+
+ /**
+ * Find the last character boundary prior to $maxLength in a utf-8
+ * quoted-printable encoded string.
+ * Original written by Colin Brown.
+ * @access public
+ * @param string $encodedText utf-8 QP text
+ * @param integer $maxLength Find the last character boundary prior to this length
+ * @return integer
+ */
+ public function utf8CharBoundary($encodedText, $maxLength)
+ {
+ $foundSplitPos = false;
+ $lookBack = 3;
+ while (!$foundSplitPos) {
+ $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
+ $encodedCharPos = strpos($lastChunk, '=');
+ if (false !== $encodedCharPos) {
+ // Found start of encoded character byte within $lookBack block.
+ // Check the encoded byte value (the 2 chars after the '=')
+ $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
+ $dec = hexdec($hex);
+ if ($dec < 128) {
+ // Single byte character.
+ // If the encoded char was found at pos 0, it will fit
+ // otherwise reduce maxLength to start of the encoded char
+ if ($encodedCharPos > 0) {
+ $maxLength = $maxLength - ($lookBack - $encodedCharPos);
+ }
+ $foundSplitPos = true;
+ } elseif ($dec >= 192) {
+ // First byte of a multi byte character
+ // Reduce maxLength to split at start of character
+ $maxLength = $maxLength - ($lookBack - $encodedCharPos);
+ $foundSplitPos = true;
+ } elseif ($dec < 192) {
+ // Middle byte of a multi byte character, look further back
+ $lookBack += 3;
+ }
+ } else {
+ // No encoded character found
+ $foundSplitPos = true;
+ }
+ }
+ return $maxLength;
+ }
+
+ /**
+ * Apply word wrapping to the message body.
+ * Wraps the message body to the number of chars set in the WordWrap property.
+ * You should only do this to plain-text bodies as wrapping HTML tags may break them.
+ * This is called automatically by createBody(), so you don't need to call it yourself.
+ * @access public
+ * @return void
+ */
+ public function setWordWrap()
+ {
+ if ($this->WordWrap < 1) {
+ return;
+ }
+
+ switch ($this->message_type) {
+ case 'alt':
+ case 'alt_inline':
+ case 'alt_attach':
+ case 'alt_inline_attach':
+ $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);