diff --git a/lib/classes/phpmailer/class.PHPMailer.php b/lib/classes/phpmailer/class.PHPMailer.php index cdb9413c..ff04b18b 100644 --- a/lib/classes/phpmailer/class.PHPMailer.php +++ b/lib/classes/phpmailer/class.PHPMailer.php @@ -1,38 +1,38 @@ -* @author Jim Jagielski (jimjag) -* @author Andy Prevost (codeworxtech) -* @author Brent R. Matzelle (original founder) -* @copyright 2012 - 2014 Marcus Bointon -* @copyright 2010 - 2012 Jim Jagielski -* @copyright 2004 - 2009 Andy Prevost -* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License -* @note This program is distributed in the hope that it will be useful - WITHOUT -* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -* FITNESS FOR A PARTICULAR PURPOSE. -*/ + * PHP Version 5 + * @package PHPMailer + * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2014 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ /** * PHPMailer - PHP email creation and transport class. -* @package PHPMailer -* @author Marcus Bointon (Synchro/coolbru) -* @author Jim Jagielski (jimjag) -* @author Andy Prevost (codeworxtech) -* @author Brent R. Matzelle (original founder) -*/ + * @package PHPMailer + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + */ class PHPMailer { /** * The PHPMailer Version number. * @var string */ - public $Version = '5.2.21'; - + public $Version = '5.2.26'; + /** * Email priority. * Options: null (default), 1 = High, 3 = Normal, 5 = low. @@ -40,51 +40,51 @@ class PHPMailer * @var integer */ public $Priority = null; - + /** * The character set of the message. * @var string */ public $CharSet = 'iso-8859-1'; - + /** * The MIME Content-type of the message. * @var string */ public $ContentType = 'text/plain'; - + /** * The message encoding. * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". * @var string */ public $Encoding = '8bit'; - + /** * Holds the most recent mailer error message. * @var string */ public $ErrorInfo = ''; - + /** * The From email address for the message. * @var string */ public $From = 'root@localhost'; - + /** * The From name of the message. * @var string */ public $FromName = 'Root User'; - + /** * The Sender email (Return-Path) of the message. * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode. * @var string */ public $Sender = ''; - + /** * The Return-Path of the message. * If empty, it will be set to either From or Sender. @@ -94,20 +94,20 @@ class PHPMailer * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference */ public $ReturnPath = ''; - + /** * The Subject of the message. * @var string */ public $Subject = ''; - + /** * An HTML or plain text message body. * If HTML then call isHTML(true). * @var string */ public $Body = ''; - + /** * The plain-text message body. * This body can be read by mail clients that do not have HTML email @@ -116,7 +116,7 @@ class PHPMailer * @var string */ public $AltBody = ''; - + /** * An iCal message part body. * Only supported in simple alt or alt_inline message types @@ -126,55 +126,55 @@ class PHPMailer * @var string */ public $Ical = ''; - + /** * The complete compiled MIME message body. * @access protected * @var string */ protected $MIMEBody = ''; - + /** * The complete compiled MIME message headers. * @var string * @access protected */ protected $MIMEHeader = ''; - + /** * Extra headers that createHeader() doesn't fold in. * @var string * @access protected */ protected $mailHeader = ''; - + /** * Word-wrap the message body to this number of chars. * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. * @var integer */ public $WordWrap = 0; - + /** * Which method to use to send mail. * Options: "mail", "sendmail", or "smtp". * @var string */ public $Mailer = 'mail'; - + /** * The path to the sendmail program. * @var string */ public $Sendmail = '/usr/sbin/sendmail'; - + /** * Whether mail() uses a fully sendmail-compatible MTA. * One which supports sendmail's "-oi -f" options. * @var boolean */ public $UseSendmailOptions = true; - + /** * Path to PHPMailer plugins. * Useful if the SMTP class is not in the PHP include path. @@ -182,13 +182,13 @@ class PHPMailer * @deprecated Should not be needed now there is an autoloader. */ public $PluginDir = ''; - + /** * The email address that a reading confirmation should be sent to, also known as read receipt. * @var string */ public $ConfirmReadingTo = ''; - + /** * The hostname to use in the Message-ID header and as default HELO string. * If empty, PHPMailer attempts to find one with, in order, @@ -197,7 +197,7 @@ class PHPMailer * @var string */ public $Hostname = ''; - + /** * An ID to be used in the Message-ID header. * If empty, a unique id will be generated. @@ -207,14 +207,14 @@ class PHPMailer * @var string */ public $MessageID = ''; - + /** * The message Date to be used in the Date header. * If empty, the current date will be added. * @var string */ public $MessageDate = ''; - + /** * SMTP hosts. * Either a single hostname or multiple semicolon-delimited hostnames. @@ -227,14 +227,14 @@ class PHPMailer * @var string */ public $Host = 'localhost'; - + /** * The default SMTP server port. * @var integer * @TODO Why is this needed when the SMTP class takes care of it? */ public $Port = 25; - + /** * The SMTP HELO of the message. * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find @@ -243,14 +243,14 @@ class PHPMailer * @see PHPMailer::$Hostname */ public $Helo = ''; - + /** * What kind of encryption to use on the SMTP connection. * Options: '', 'ssl' or 'tls' * @var string */ public $SMTPSecure = ''; - + /** * Whether to enable TLS encryption automatically if a server supports it, * even if `SMTPSecure` is not set to 'tls'. @@ -258,7 +258,7 @@ class PHPMailer * @var boolean */ public $SMTPAutoTLS = true; - + /** * Whether to use SMTP authentication. * Uses the Username and Password properties. @@ -267,53 +267,53 @@ class PHPMailer * @see PHPMailer::$Password */ public $SMTPAuth = false; - + /** * Options array passed to stream_context_create when connecting via SMTP. * @var array */ public $SMTPOptions = array(); - + /** * SMTP username. * @var string */ public $Username = ''; - + /** * SMTP password. * @var string */ public $Password = ''; - + /** * SMTP auth type. * Options are CRAM-MD5, LOGIN, PLAIN, NTLM, XOAUTH2, attempted in that order if not specified * @var string */ public $AuthType = ''; - + /** * SMTP realm. * Used for NTLM auth * @var string */ public $Realm = ''; - + /** * SMTP workstation. * Used for NTLM auth * @var string */ public $Workstation = ''; - + /** * The SMTP server timeout in seconds. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 * @var integer */ public $Timeout = 300; - + /** * SMTP class debug output mode. * Debug output level. @@ -327,7 +327,7 @@ class PHPMailer * @see SMTP::$do_debug */ public $SMTPDebug = 0; - + /** * How to handle debug output. * Options: @@ -343,7 +343,7 @@ class PHPMailer * @see SMTP::$Debugoutput */ public $Debugoutput = 'echo'; - + /** * Whether to keep SMTP connection open after each message. * If this is set to true then to close the connection @@ -351,7 +351,7 @@ class PHPMailer * @var boolean */ public $SMTPKeepAlive = false; - + /** * Whether to split multiple to addresses into multiple messages * or send them all in one message. @@ -359,14 +359,14 @@ class PHPMailer * @var boolean */ public $SingleTo = false; - + /** * Storage for addresses when SingleTo is enabled. * @var array * @TODO This should really not be public */ public $SingleToArray = array(); - + /** * Whether to generate VERP addresses on send. * Only applicable when sending via SMTP. @@ -375,13 +375,13 @@ class PHPMailer * @var boolean */ public $do_verp = false; - + /** * Whether to allow sending messages with an empty body. * @var boolean */ public $AllowEmpty = false; - + /** * The default line ending. * @note The default remains "\n". We force CRLF where we know @@ -389,47 +389,47 @@ class PHPMailer * @var string */ public $LE = "\n"; - + /** * DKIM selector. * @var string */ public $DKIM_selector = ''; - + /** * DKIM Identity. * Usually the email address used as the source of the email. * @var string */ public $DKIM_identity = ''; - + /** * DKIM passphrase. * Used if your key is encrypted. * @var string */ public $DKIM_passphrase = ''; - + /** * DKIM signing domain name. * @example 'example.com' * @var string */ public $DKIM_domain = ''; - + /** * DKIM private key file path. * @var string */ public $DKIM_private = ''; - + /** * DKIM private key string. * If set, takes precedence over `$DKIM_private`. * @var string */ public $DKIM_private_string = ''; - + /** * Callback Action function name. * @@ -440,23 +440,23 @@ 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 * @var string */ public $action_function = ''; - + /** * What to put in the X-Mailer header. * Options: An empty string for PHPMailer default, whitespace for none, or a string to use * @var string */ public $XMailer = ''; - + /** * Which validator to use by default when validating email addresses. * May be a callable to inject your own validator, but there are several built-in validators. @@ -465,42 +465,42 @@ class PHPMailer * @static */ public static $validator = 'auto'; - + /** * An instance of the SMTP sender class. * @var SMTP * @access protected */ protected $smtp = null; - + /** * The array of 'to' names and addresses. * @var array * @access protected */ protected $to = array(); - + /** * The array of 'cc' names and addresses. * @var array * @access protected */ protected $cc = array(); - + /** * The array of 'bcc' names and addresses. * @var array * @access protected */ protected $bcc = array(); - + /** * The array of reply-to names and addresses. * @var array * @access protected */ protected $ReplyTo = array(); - + /** * An array of all kinds of addresses. * Includes all of $to, $cc, $bcc @@ -509,7 +509,7 @@ class PHPMailer * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc */ protected $all_recipients = array(); - + /** * An array of names and addresses queued for validation. * In send(), valid and non duplicate entries are moved to $all_recipients @@ -521,7 +521,7 @@ class PHPMailer * @see PHPMailer::$all_recipients */ protected $RecipientsQueue = array(); - + /** * An array of reply-to names and addresses queued for validation. * In send(), valid and non duplicate entries are moved to $ReplyTo. @@ -531,77 +531,77 @@ class PHPMailer * @see PHPMailer::$ReplyTo */ protected $ReplyToQueue = array(); - + /** * The array of attachments. * @var array * @access protected */ protected $attachment = array(); - + /** * The array of custom headers. * @var array * @access protected */ protected $CustomHeader = array(); - + /** * The most recent Message-ID (including angular brackets). * @var string * @access protected */ protected $lastMessageID = ''; - + /** * The message's MIME type. * @var string * @access protected */ protected $message_type = ''; - + /** * The array of MIME boundary strings. * @var array * @access protected */ protected $boundary = array(); - + /** * The array of available languages. * @var array * @access protected */ protected $language = array(); - + /** * The number of errors encountered. * @var integer * @access protected */ protected $error_count = 0; - + /** * The S/MIME certificate file path. * @var string * @access protected */ protected $sign_cert_file = ''; - + /** * The S/MIME key file path. * @var string * @access protected */ protected $sign_key_file = ''; - + /** * The optional S/MIME extra certificates ("CA Chain") file path. * @var string * @access protected */ protected $sign_extracerts_file = ''; - + /** * The S/MIME password for the key. * Used only if the key is encrypted. @@ -609,47 +609,47 @@ class PHPMailer * @access protected */ protected $sign_key_pass = ''; - + /** * Whether to throw exceptions for errors. * @var boolean * @access protected */ protected $exceptions = false; - + /** * Unique ID used for message ID and boundaries. * @var string * @access protected */ protected $uniqueid = ''; - + /** * Error severity: message only, continue processing. */ const STOP_MESSAGE = 0; - + /** * Error severity: message, likely ok to continue processing. */ const STOP_CONTINUE = 1; - + /** * Error severity: message, plus full stop, critical error reached. */ const STOP_CRITICAL = 2; - + /** * SMTP RFC standard line ending. */ const CRLF = "\r\n"; - + /** * The maximum line length allowed by RFC 2822 section 2.1.1 * @var integer */ const MAX_LINE_LENGTH = 998; - + /** * Constructor. * @param boolean $exceptions Should we throw external exceptions? @@ -659,8 +659,10 @@ 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'); } - + /** * Destructor. */ @@ -669,7 +671,7 @@ class PHPMailer //Close any open SMTP connection nicely $this->smtpClose(); } - + /** * Call mail() in a safe_mode-aware fashion. * Also, unless sendmail_path points to sendmail (or something that @@ -691,7 +693,7 @@ class PHPMailer } else { $subject = $this->encodeHeader($this->secureHeader($subject)); } - + //Can't use additional_parameters in safe_mode, calling mail() with null params breaks //@link http://php.net/manual/en/function.mail.php if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) { @@ -743,7 +745,7 @@ class PHPMailer ) . "\n"; } } - + /** * Sets message type to HTML or plain. * @param boolean $isHtml True for HTML mode. @@ -757,7 +759,7 @@ class PHPMailer $this->ContentType = 'text/plain'; } } - + /** * Send messages using SMTP. * @return void @@ -766,7 +768,7 @@ class PHPMailer { $this->Mailer = 'smtp'; } - + /** * Send messages using PHP's mail() function. * @return void @@ -775,7 +777,7 @@ class PHPMailer { $this->Mailer = 'mail'; } - + /** * Send messages using $Sendmail. * @return void @@ -783,7 +785,7 @@ class PHPMailer public function isSendmail() { $ini_sendmail_path = ini_get('sendmail_path'); - + if (!stristr($ini_sendmail_path, 'sendmail')) { $this->Sendmail = '/usr/sbin/sendmail'; } else { @@ -791,7 +793,7 @@ class PHPMailer } $this->Mailer = 'sendmail'; } - + /** * Send messages using qmail. * @return void @@ -799,7 +801,7 @@ class PHPMailer public function isQmail() { $ini_sendmail_path = ini_get('sendmail_path'); - + if (!stristr($ini_sendmail_path, 'qmail')) { $this->Sendmail = '/var/qmail/bin/qmail-inject'; } else { @@ -807,7 +809,7 @@ class PHPMailer } $this->Mailer = 'qmail'; } - + /** * Add a "To" address. * @param string $address The email address to send to @@ -818,7 +820,7 @@ class PHPMailer { return $this->addOrEnqueueAnAddress('to', $address, $name); } - + /** * Add a "CC" address. * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. @@ -830,7 +832,7 @@ class PHPMailer { return $this->addOrEnqueueAnAddress('cc', $address, $name); } - + /** * Add a "BCC" address. * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. @@ -842,7 +844,7 @@ class PHPMailer { return $this->addOrEnqueueAnAddress('bcc', $address, $name); } - + /** * Add a "Reply-To" address. * @param string $address The email address to reply to @@ -853,7 +855,7 @@ class PHPMailer { return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); } - + /** * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still @@ -899,7 +901,7 @@ class PHPMailer // Immediately add standard addresses without IDN. return call_user_func_array(array($this, 'addAnAddress'), $params); } - + /** * Add an address to one of the recipient arrays or to the ReplyTo array. * Addresses that have been added already return false, but do not throw exceptions. @@ -944,7 +946,7 @@ class PHPMailer } return false; } - + /** * Parse and validate a string containing one or more RFC822-style comma-separated email addresses * of the form "display name
" into an array of name/address pairs. @@ -999,7 +1001,7 @@ class PHPMailer } return $addresses; } - + /** * Set the From and FromName properties. * @param string $address @@ -1033,7 +1035,7 @@ class PHPMailer } return true; } - + /** * Return the Message-ID header of the last email. * Technically this is the value from the last time the headers were created, @@ -1045,7 +1047,7 @@ class PHPMailer { return $this->lastMessageID; } - + /** * Check that a string looks like an email address. * @param string $address The email address to check @@ -1155,7 +1157,7 @@ class PHPMailer return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL); } } - + /** * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the * "intl" and "mbstring" PHP extensions. @@ -1166,7 +1168,7 @@ class PHPMailer // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2. return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding'); } - + /** * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. @@ -1197,7 +1199,7 @@ class PHPMailer } return $address; } - + /** * Create a message and send it. * Uses the sending method specified by $Mailer. @@ -1220,7 +1222,7 @@ class PHPMailer return false; } } - + /** * Prepare a message for sending. * @throws phpmailerException @@ -1231,7 +1233,7 @@ class PHPMailer try { $this->error_count = 0; // Reset errors $this->mailHeader = ''; - + // Dequeue recipient and Reply-To addresses with IDN foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { $params[1] = $this->punyencodeAddress($params[1]); @@ -1240,7 +1242,7 @@ class PHPMailer if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL); } - + // Validate From, Sender, and ConfirmReadingTo addresses foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) { $this->$address_kind = trim($this->$address_kind); @@ -1258,18 +1260,18 @@ class PHPMailer return false; } } - + // Set whether the message is multipart/alternative if ($this->alternativeExists()) { $this->ContentType = 'multipart/alternative'; } - + $this->setMessageType(); // Refuse to send an empty message unless we are specifically allowing it if (!$this->AllowEmpty and empty($this->Body)) { throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL); } - + // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) $this->MIMEHeader = ''; $this->MIMEBody = $this->createBody(); @@ -1277,7 +1279,7 @@ class PHPMailer $tempheaders = $this->MIMEHeader; $this->MIMEHeader = $this->createHeader(); $this->MIMEHeader .= $tempheaders; - + // To capture the complete message when using mail(), create // an extra header list which createHeader() doesn't fold in if ($this->Mailer == 'mail') { @@ -1291,7 +1293,7 @@ class PHPMailer $this->encodeHeader($this->secureHeader(trim($this->Subject))) ); } - + // Sign with DKIM if enabled if (!empty($this->DKIM_domain) && !empty($this->DKIM_selector) @@ -1316,7 +1318,7 @@ class PHPMailer return false; } } - + /** * Actually send a message. * Send the email via the selected mechanism @@ -1340,7 +1342,7 @@ class PHPMailer if (method_exists($this, $sendMethod)) { return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); } - + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); } } catch (phpmailerException $exc) { @@ -1352,7 +1354,7 @@ class PHPMailer } return false; } - + /** * Send mail using the $Sendmail program. * @param string $header The message headers @@ -1378,10 +1380,10 @@ class PHPMailer $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')) { @@ -1426,7 +1428,7 @@ class PHPMailer } return true; } - + /** * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. * @@ -1444,12 +1446,12 @@ class PHPMailer ) { 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. @@ -1457,10 +1459,10 @@ class PHPMailer return false; } } - + return true; } - + /** * Send mail using the PHP mail() function. * @param string $header The message headers @@ -1477,7 +1479,7 @@ class PHPMailer $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)) { @@ -1508,7 +1510,7 @@ class PHPMailer } return true; } - + /** * Get an instance to use for SMTP operations. * Override this function to load your own SMTP implementation @@ -1521,7 +1523,7 @@ class PHPMailer } return $this->smtp; } - + /** * Send mail via SMTP. * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. @@ -1549,7 +1551,7 @@ class PHPMailer $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) { @@ -1563,7 +1565,7 @@ class PHPMailer $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); @@ -1587,7 +1589,7 @@ class PHPMailer } return true; } - + /** * Initiate a connection to an SMTP server. * Returns false if the operation failed. @@ -1602,104 +1604,109 @@ class PHPMailer 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); + 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; } - } - $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(); + // $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); } - $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')); + } + $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(); } - // 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')); + //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(); } - 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(); @@ -1709,7 +1716,7 @@ class PHPMailer } return false; } - + /** * Close the active SMTP session if one exists. * @return void @@ -1723,7 +1730,7 @@ class PHPMailer } } } - + /** * Set the language for error messages. * Returns false if it cannot load the language file. @@ -1742,12 +1749,13 @@ class PHPMailer 'dk' => 'da', 'no' => 'nb', 'se' => 'sv', + 'sr' => 'rs' ); - + 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.', @@ -1794,7 +1802,7 @@ class PHPMailer $this->language = $PHPMAILER_LANG; return (boolean)$foundlang; // Returns false if language not found } - + /** * Get the array of strings for the current language. * @return array @@ -1803,7 +1811,7 @@ class PHPMailer { return $this->language; } - + /** * Create recipient headers. * @access public @@ -1822,7 +1830,7 @@ class PHPMailer } return $type . ': ' . implode(', ', $addresses) . $this->LE; } - + /** * Format an address for use in a message header. * @access public @@ -1840,7 +1848,7 @@ class PHPMailer ) . '>'; } } - + /** * Word-wrap message. * For use with mailers that do not automatically perform wrapping @@ -1864,13 +1872,13 @@ class PHPMailer $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 @@ -1915,7 +1923,7 @@ class PHPMailer } $part = substr($word, 0, $len); $word = substr($word, $len); - + if (strlen($word) > 0) { $message .= $part . sprintf('=%s', self::CRLF); } else { @@ -1928,7 +1936,7 @@ class PHPMailer $buf .= ' '; } $buf .= $word; - + if (strlen($buf) > $length and $buf_o != '') { $message .= $buf_o . $soft_break; $buf = $word; @@ -1938,10 +1946,10 @@ class PHPMailer } $message .= $buf . self::CRLF; } - + return $message; } - + /** * Find the last character boundary prior to $maxLength in a utf-8 * quoted-printable encoded string. @@ -1987,7 +1995,7 @@ class PHPMailer } return $maxLength; } - + /** * Apply word wrapping to the message body. * Wraps the message body to the number of chars set in the WordWrap property. @@ -2001,7 +2009,7 @@ class PHPMailer if ($this->WordWrap < 1) { return; } - + switch ($this->message_type) { case 'alt': case 'alt_inline': @@ -2014,7 +2022,7 @@ class PHPMailer break; } } - + /** * Assemble message headers. * @access public @@ -2023,12 +2031,9 @@ class PHPMailer public function createHeader() { $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) { if ($this->Mailer != 'mail') { @@ -2045,14 +2050,14 @@ class PHPMailer $result .= $this->headerLine('To', 'undisclosed-recipients:;'); } } - + $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName))); - + // sendmail and mail() extract Cc from the header before sending if (count($this->cc) > 0) { $result .= $this->addrAppend('Cc', $this->cc); } - + // sendmail and mail() extract Bcc from the header before sending if (( $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail' @@ -2061,16 +2066,16 @@ class PHPMailer ) { $result .= $this->addrAppend('Bcc', $this->bcc); } - + if (count($this->ReplyTo) > 0) { $result .= $this->addrAppend('Reply-To', $this->ReplyTo); } - + // mail() sets the subject itself if ($this->Mailer != 'mail') { $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)) { @@ -2093,11 +2098,11 @@ class PHPMailer $result .= $this->headerLine('X-Mailer', $myXmailer); } } - + if ($this->ConfirmReadingTo != '') { $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>'); } - + // Add custom headers foreach ($this->CustomHeader as $header) { $result .= $this->headerLine( @@ -2109,10 +2114,10 @@ class PHPMailer $result .= $this->headerLine('MIME-Version', '1.0'); $result .= $this->getMailMIME(); } - + return $result; } - + /** * Get the message MIME type headers. * @access public @@ -2157,14 +2162,14 @@ class PHPMailer $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); } } - + if ($this->Mailer != 'mail') { $result .= $this->LE; } - + return $result; } - + /** * Returns the whole MIME message. * Includes complete headers and body. @@ -2177,7 +2182,7 @@ class PHPMailer { return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody; } - + /** * Create unique ID * @return string @@ -2185,7 +2190,7 @@ class PHPMailer protected function generateId() { return md5(uniqid(time())); } - + /** * Assemble the message body. * Returns an empty string on failure. @@ -2201,13 +2206,13 @@ class PHPMailer $this->boundary[1] = 'b1_' . $this->uniqueid; $this->boundary[2] = 'b2_' . $this->uniqueid; $this->boundary[3] = 'b3_' . $this->uniqueid; - + if ($this->sign_key_file) { $body .= $this->getMailMIME() . $this->LE; } - + $this->setWordWrap(); - + $bodyEncoding = $this->Encoding; $bodyCharSet = $this->CharSet; //Can we do a 7-bit downgrade? @@ -2221,7 +2226,7 @@ class PHPMailer if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) { $bodyEncoding = 'quoted-printable'; } - + $altBodyEncoding = $this->Encoding; $altBodyCharSet = $this->CharSet; //Can we do a 7-bit downgrade? @@ -2341,7 +2346,7 @@ class PHPMailer $body .= $this->encodeString($this->Body, $this->Encoding); break; } - + if ($this->isError()) { $body = ''; } elseif ($this->sign_key_file) { @@ -2397,7 +2402,7 @@ class PHPMailer } return $body; } - + /** * Return the start of a message boundary. * @access protected @@ -2427,10 +2432,10 @@ class PHPMailer $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); } $result .= $this->LE; - + return $result; } - + /** * Return the end of a message boundary. * @access protected @@ -2441,7 +2446,7 @@ class PHPMailer { return $this->LE . '--' . $boundary . '--' . $this->LE; } - + /** * Set the message type. * PHPMailer only supports some preset message types, not arbitrary MIME structures. @@ -2466,7 +2471,7 @@ class PHPMailer $this->message_type = 'plain'; } } - + /** * Format a header line. * @access public @@ -2478,7 +2483,7 @@ class PHPMailer { return $name . ': ' . $value . $this->LE; } - + /** * Return a formatted mail line. * @access public @@ -2489,9 +2494,10 @@ class PHPMailer { return $value . $this->LE; } - + /** * 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. * @param string $path Path to the attachment. * @param string $name Overrides the attachment name. @@ -2507,17 +2513,17 @@ class PHPMailer if (!@is_file($path)) { throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE); } - + // If a MIME type is not specified, try to work it out from the file name if ($type == '') { $type = self::filenameToType($path); } - + $filename = basename($path); if ($name == '') { $name = $filename; } - + $this->attachment[] = array( 0 => $path, 1 => $filename, @@ -2528,7 +2534,7 @@ class PHPMailer 6 => $disposition, 7 => 0 ); - + } catch (phpmailerException $exc) { $this->setError($exc->getMessage()); $this->edebug($exc->getMessage()); @@ -2539,7 +2545,7 @@ class PHPMailer } return true; } - + /** * Return the array of attachments. * @return array @@ -2548,7 +2554,7 @@ class PHPMailer { return $this->attachment; } - + /** * Attach all file, string, and binary attachments to the message. * Returns an empty string on failure. @@ -2563,7 +2569,7 @@ class PHPMailer $mime = array(); $cidUniq = array(); $incl = array(); - + // Add all attachments foreach ($this->attachment as $attachment) { // Check if it is a valid disposition_filter @@ -2577,7 +2583,7 @@ class PHPMailer } else { $path = $attachment[0]; } - + $inclhash = md5(serialize($attachment)); if (in_array($inclhash, $incl)) { continue; @@ -2592,7 +2598,7 @@ class PHPMailer continue; } $cidUniq[$cid] = true; - + $mime[] = sprintf('--%s%s', $boundary, $this->LE); //Only include a filename property if we have one if (!empty($name)) { @@ -2613,11 +2619,11 @@ class PHPMailer if ($encoding != '7bit') { $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE); } - + if ($disposition == 'inline') { $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE); } - + // If a filename contains any of these chars, it should be quoted, // but not otherwise: RFC2183 & RFC2045 5.1 // Fixes a warning in IETF's msglint MIME checker @@ -2650,7 +2656,7 @@ class PHPMailer } else { $mime[] = $this->LE; } - + // Encode as string attachment if ($bString) { $mime[] = $this->encodeString($string, $encoding); @@ -2667,12 +2673,12 @@ class PHPMailer } } } - + $mime[] = sprintf('--%s--%s', $boundary, $this->LE); - + return implode('', $mime); } - + /** * Encode a file attachment in requested format. * Returns an empty string on failure. @@ -2714,7 +2720,7 @@ class PHPMailer return ''; } } - + /** * Encode a string in requested format. * Returns an empty string on failure. @@ -2750,7 +2756,7 @@ class PHPMailer } return $encoded; } - + /** * Encode a header string optimally. * Picks shortest of Q, B, quoted-printable or none. @@ -2784,12 +2790,12 @@ class PHPMailer $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); break; } - + //There are no chars that need encoding if ($matchcount == 0) { return ($str); } - + $maxlen = 75 - 7 - strlen($this->CharSet); // Try to select the encoding which should produce the shortest output if ($matchcount > strlen($str) / 3) { @@ -2810,13 +2816,13 @@ class PHPMailer $encoded = $this->wrapText($encoded, $maxlen, true); $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded)); } - + $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); $encoded = trim(str_replace("\n", $this->LE, $encoded)); - + return $encoded; } - + /** * Check if a string contains multi-byte characters. * @access public @@ -2831,7 +2837,7 @@ class PHPMailer return false; } } - + /** * Does a string contain any 8-bit chars (in any charset)? * @param string $text @@ -2841,7 +2847,7 @@ class PHPMailer { return (boolean)preg_match('/[\x80-\xFF]/', $text); } - + /** * Encode and wrap long multibyte strings for mail headers * without breaking lines within a character. @@ -2860,7 +2866,7 @@ class PHPMailer if ($linebreak === null) { $linebreak = $this->LE; } - + $mb_length = mb_strlen($str, $this->CharSet); // Each line must have length <= 75, including $start and $end $length = 75 - strlen($start) - strlen($end); @@ -2868,7 +2874,7 @@ class PHPMailer $ratio = $mb_length / strlen($str); // Base64 has a 4:3 ratio $avgLength = floor($length * $ratio * .75); - + for ($i = 0; $i < $mb_length; $i += $offset) { $lookBack = 0; do { @@ -2879,12 +2885,12 @@ class PHPMailer } while (strlen($chunk) > $length); $encoded .= $chunk . $linebreak; } - + // Chomp the last linefeed $encoded = substr($encoded, 0, -strlen($linebreak)); return $encoded; } - + /** * Encode a string in quoted-printable format. * According to RFC2045 section 6.7. @@ -2908,7 +2914,7 @@ class PHPMailer ); return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string); } - + /** * Backward compatibility wrapper for an old QP encoding function that was removed. * @see PHPMailer::encodeQP() @@ -2926,7 +2932,7 @@ class PHPMailer ) { return $this->encodeQP($string, $line_max); } - + /** * Encode a string using Q encoding. * @link http://tools.ietf.org/html/rfc2047 @@ -2974,7 +2980,7 @@ class PHPMailer // Replace every spaces to _ (more readable than =20) return str_replace(' ', '_', $encoded); } - + /** * Add a string or binary attachment (non-filesystem). * This method can be used to attach ascii or binary data, @@ -3009,7 +3015,7 @@ class PHPMailer 7 => 0 ); } - + /** * Add an embedded (inline) attachment from a file. * This can include images, sounds, and just about any other document type. @@ -3017,6 +3023,7 @@ class PHPMailer * displayed inline with the message, not just attached for download. * This is used in HTML messages that embed the images * the HTML refers to using the $cid value. + * Never use a user-supplied path to a file! * @param string $path Path to the attachment. * @param string $cid Content ID of the attachment; Use this to reference * the content when using an embedded image in HTML. @@ -3032,17 +3039,17 @@ class PHPMailer $this->setError($this->lang('file_access') . $path); return false; } - + // If a MIME type is not specified, try to work it out from the file name if ($type == '') { $type = self::filenameToType($path); } - + $filename = basename($path); if ($name == '') { $name = $filename; } - + // Append to $attachment array $this->attachment[] = array( 0 => $path, @@ -3056,7 +3063,7 @@ class PHPMailer ); return true; } - + /** * Add an embedded stringified attachment. * This can include images, sounds, and just about any other document type. @@ -3083,7 +3090,7 @@ class PHPMailer if ($type == '' and !empty($name)) { $type = self::filenameToType($name); } - + // Append to $attachment array $this->attachment[] = array( 0 => $string, @@ -3097,7 +3104,7 @@ class PHPMailer ); return true; } - + /** * Check if an inline attachment is present. * @access public @@ -3112,7 +3119,7 @@ class PHPMailer } return false; } - + /** * Check if an attachment (non-inline) is present. * @return boolean @@ -3126,7 +3133,7 @@ class PHPMailer } return false; } - + /** * Check if this message has an alternative body set. * @return boolean @@ -3135,7 +3142,7 @@ class PHPMailer { return !empty($this->AltBody); } - + /** * Clear queued addresses of given kind. * @access protected @@ -3151,7 +3158,7 @@ class PHPMailer } } } - + /** * Clear all To recipients. * @return void @@ -3164,7 +3171,7 @@ class PHPMailer $this->to = array(); $this->clearQueuedAddresses('to'); } - + /** * Clear all CC recipients. * @return void @@ -3177,7 +3184,7 @@ class PHPMailer $this->cc = array(); $this->clearQueuedAddresses('cc'); } - + /** * Clear all BCC recipients. * @return void @@ -3190,7 +3197,7 @@ class PHPMailer $this->bcc = array(); $this->clearQueuedAddresses('bcc'); } - + /** * Clear all ReplyTo recipients. * @return void @@ -3200,7 +3207,7 @@ class PHPMailer $this->ReplyTo = array(); $this->ReplyToQueue = array(); } - + /** * Clear all recipient types. * @return void @@ -3213,7 +3220,7 @@ class PHPMailer $this->all_recipients = array(); $this->RecipientsQueue = array(); } - + /** * Clear all filesystem, string, and binary attachments. * @return void @@ -3222,7 +3229,7 @@ class PHPMailer { $this->attachment = array(); } - + /** * Clear all custom headers. * @return void @@ -3231,7 +3238,7 @@ class PHPMailer { $this->CustomHeader = array(); } - + /** * Add an error message to the error container. * @access protected @@ -3258,7 +3265,7 @@ class PHPMailer } $this->ErrorInfo = $msg; } - + /** * Return an RFC 822 formatted date. * @access public @@ -3272,7 +3279,7 @@ class PHPMailer date_default_timezone_set(@date_default_timezone_get()); return date('D, j M Y H:i:s O'); } - + /** * Get the server hostname. * Returns 'localhost.localdomain' if unknown. @@ -3293,7 +3300,7 @@ class PHPMailer } return $result; } - + /** * Get an error message in the current language. * @access protected @@ -3305,7 +3312,7 @@ class PHPMailer if (count($this->language) < 1) { $this->setLanguage('en'); // set the default language } - + if (array_key_exists($key, $this->language)) { if ($key == 'smtp_connect_failed') { //Include a link to troubleshooting docs on SMTP connection failure @@ -3319,7 +3326,7 @@ class PHPMailer return $key; } } - + /** * Check if an error occurred. * @access public @@ -3329,7 +3336,7 @@ class PHPMailer { return ($this->error_count > 0); } - + /** * Ensure consistent line endings in a string. * Changes every end of line from CRLF, CR or LF to $this->LE. @@ -3347,7 +3354,7 @@ class PHPMailer } return $nstr; } - + /** * Add a custom header. * $name value can be overloaded to contain @@ -3366,7 +3373,7 @@ class PHPMailer $this->CustomHeader[] = array($name, $value); } } - + /** * Returns all custom headers. * @return array @@ -3375,17 +3382,19 @@ class PHPMailer { return $this->CustomHeader; } - + /** * Create a message body from an HTML string. * Automatically inlines images and creates a plain-text version by converting the HTML, * overwriting any existing values in Body and AltBody. - * $basedir is used when handling relative image paths, e.g. + * Do not source $message content from user input! + * $basedir is prepended when handling relative URLs, e.g. and must not be empty * 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. + * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email) + * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly. * @access public * @param string $message HTML message string - * @param string $basedir base directory for relative paths to images + * @param string $basedir Absolute path to a base directory to prepend to relative paths to images * @param boolean|callable $advanced Whether to use the internal HTML to text converter * or your own custom converter @see PHPMailer::html2text() * @return string $message The transformed message Body @@ -3394,6 +3403,10 @@ class PHPMailer { preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images); if (array_key_exists(2, $images)) { + if (strlen($basedir) > 1 && substr($basedir, -1) != '/') { + // Ensure $basedir has a trailing / + $basedir .= '/'; + } foreach ($images[2] as $imgindex => $url) { // Convert data URIs into embedded images if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) { @@ -3411,36 +3424,42 @@ class PHPMailer $message ); } - } elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[a-z][a-z0-9+.-]*://#i', $url)) { - // Do not change urls for absolute images (thanks to corvuscorax) - // Do not change urls that are already inline images - $filename = basename($url); - $directory = dirname($url); - if ($directory == '.') { - $directory = ''; - } - $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 - if (strlen($basedir) > 1 && substr($basedir, -1) != '/') { - $basedir .= '/'; - } - if (strlen($directory) > 1 && substr($directory, -1) != '/') { - $directory .= '/'; - } - if ($this->addEmbeddedImage( - $basedir . $directory . $filename, - $cid, - $filename, - 'base64', - self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION)) - ) - ) { - $message = preg_replace( - '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', - $images[1][$imgindex] . '="cid:' . $cid . '"', - $message - ); - } + continue; } + if ( + // Only process relative URLs if a basedir is provided (i.e. no absolute local paths) + !empty($basedir) + // Ignore URLs containing parent dir traversal (..) + && (strpos($url, '..') === false) + // Do not change urls that are already inline images + && substr($url, 0, 4) !== 'cid:' + // Do not change absolute URLs, including anonymous protocol + && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url) + ) { + $filename = basename($url); + $directory = dirname($url); + if ($directory == '.') { + $directory = ''; + } + $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 + if (strlen($directory) > 1 && substr($directory, -1) != '/') { + $directory .= '/'; + } + if ($this->addEmbeddedImage( + $basedir . $directory . $filename, + $cid, + $filename, + 'base64', + self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION)) + ) + ) { + $message = preg_replace( + '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', + $images[1][$imgindex] . '="cid:' . $cid . '"', + $message + ); + } + } } } $this->isHTML(true); @@ -3453,7 +3472,7 @@ class PHPMailer } return $this->Body; } - + /** * Convert an HTML string into plain text. * This is used by msgHTML(). @@ -3485,7 +3504,7 @@ class PHPMailer $this->CharSet ); } - + /** * Get the MIME type for a file extension. * @param string $ext File extension @@ -3600,7 +3619,7 @@ class PHPMailer } return 'application/octet-stream'; } - + /** * Map a file name to a MIME type. * Defaults to 'application/octet-stream', i.e.. arbitrary binary data. @@ -3618,7 +3637,7 @@ class PHPMailer $pathinfo = self::mb_pathinfo($filename); return self::_mime_types($pathinfo['extension']); } - + /** * Multi-byte-safe pathinfo replacement. * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe. @@ -3665,7 +3684,7 @@ class PHPMailer return $ret; } } - + /** * Set or reset instance properties. * You should avoid this function - it's more verbose, less efficient, more error-prone and @@ -3690,7 +3709,7 @@ class PHPMailer return false; } } - + /** * Strip newlines to prevent header injection. * @access public @@ -3701,7 +3720,7 @@ class PHPMailer { return trim(str_replace(array("\r", "\n"), '', $str)); } - + /** * Normalize line breaks in a string. * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format. @@ -3716,7 +3735,7 @@ class PHPMailer { return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text); } - + /** * Set the public and private key files and password for S/MIME signing. * @access public @@ -3732,7 +3751,7 @@ class PHPMailer $this->sign_key_pass = $key_pass; $this->sign_extracerts_file = $extracerts_filename; } - + /** * Quoted-Printable-encode a DKIM header. * @access public @@ -3752,7 +3771,7 @@ class PHPMailer } return $line; } - + /** * Generate a DKIM signature. * @access public @@ -3790,7 +3809,7 @@ class PHPMailer $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); @@ -3799,7 +3818,7 @@ class PHPMailer openssl_pkey_free($privKey); return ''; } - + /** * Generate a DKIM canonicalization header. * @access public @@ -3819,7 +3838,7 @@ class PHPMailer $signHeader = implode("\r\n", $lines); return $signHeader; } - + /** * Generate a DKIM canonicalization body. * @access public @@ -3840,7 +3859,7 @@ class PHPMailer } return $body; } - + /** * Create the DKIM header and body in a new message header. * @access public @@ -3920,7 +3939,7 @@ class PHPMailer $signed = $this->DKIM_Sign($toSign); return $dkimhdrs . $signed . "\r\n"; } - + /** * Detect if a string contains a line longer than the maximum line length allowed. * @param string $str @@ -3932,7 +3951,7 @@ class PHPMailer //+2 to include CRLF line break for a 1000 total return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str); } - + /** * Allows for public read access to 'to' property. * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. @@ -3943,7 +3962,7 @@ class PHPMailer { return $this->to; } - + /** * Allows for public read access to 'cc' property. * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. @@ -3954,7 +3973,7 @@ class PHPMailer { return $this->cc; } - + /** * Allows for public read access to 'bcc' property. * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. @@ -3965,7 +3984,7 @@ class PHPMailer { return $this->bcc; } - + /** * Allows for public read access to 'ReplyTo' property. * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. @@ -3976,7 +3995,7 @@ class PHPMailer { return $this->ReplyTo; } - + /** * Allows for public read access to 'all_recipients' property. * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. @@ -3987,7 +4006,7 @@ class PHPMailer { return $this->all_recipients; } - + /** * Perform a callback. * @param boolean $isSent @@ -4019,7 +4038,7 @@ class phpmailerException extends Exception */ public function errorMessage() { - $errorMsg = '' . $this->getMessage() . "
\n"; + $errorMsg = '' . htmlspecialchars($this->getMessage()) . "
\n"; return $errorMsg; } -} +} \ No newline at end of file diff --git a/lib/classes/phpmailer/class.SMTP.php b/lib/classes/phpmailer/class.SMTP.php index 886782dc..a3a67baa 100644 --- a/lib/classes/phpmailer/class.SMTP.php +++ b/lib/classes/phpmailer/class.SMTP.php @@ -1,88 +1,88 @@ -* @author Jim Jagielski (jimjag) -* @author Andy Prevost (codeworxtech) -* @author Brent R. Matzelle (original founder) -* @copyright 2014 Marcus Bointon -* @copyright 2010 - 2012 Jim Jagielski -* @copyright 2004 - 2009 Andy Prevost -* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License -* @note This program is distributed in the hope that it will be useful - WITHOUT -* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -* FITNESS FOR A PARTICULAR PURPOSE. -*/ + * PHP Version 5 + * @package PHPMailer + * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2014 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ /** * PHPMailer RFC821 SMTP email transport class. -* Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server. -* @package PHPMailer -* @author Chris Ryan -* @author Marcus Bointon -*/ + * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server. + * @package PHPMailer + * @author Chris Ryan + * @author Marcus Bointon + */ class SMTP { /** * The PHPMailer SMTP version number. * @var string */ - const VERSION = '5.2.21'; - + const VERSION = '5.2.26'; + /** * SMTP line break constant. * @var string */ const CRLF = "\r\n"; - + /** * The SMTP port to use if one is not specified. * @var integer */ const DEFAULT_SMTP_PORT = 25; - + /** * The maximum line length allowed by RFC 2822 section 2.1.1 * @var integer */ const MAX_LINE_LENGTH = 998; - + /** * Debug level for no output */ const DEBUG_OFF = 0; - + /** * Debug level to show client -> server messages */ const DEBUG_CLIENT = 1; - + /** * Debug level to show client -> server and server -> client messages */ const DEBUG_SERVER = 2; - + /** * Debug level to show connection status, client -> server and server -> client messages */ const DEBUG_CONNECTION = 3; - + /** * Debug level to show all messages */ const DEBUG_LOWLEVEL = 4; - + /** * The PHPMailer SMTP Version number. * @var string * @deprecated Use the `VERSION` constant instead * @see SMTP::VERSION */ - public $Version = '5.2.21'; - + public $Version = '5.2.26'; + /** * SMTP server port number. * @var integer @@ -90,7 +90,7 @@ class SMTP * @see SMTP::DEFAULT_SMTP_PORT */ public $SMTP_PORT = 25; - + /** * SMTP reply line ending. * @var string @@ -98,7 +98,7 @@ class SMTP * @see SMTP::CRLF */ public $CRLF = "\r\n"; - + /** * Debug output level. * Options: @@ -110,7 +110,7 @@ class SMTP * @var integer */ public $do_debug = self::DEBUG_OFF; - + /** * How to handle debug output. * Options: @@ -125,7 +125,7 @@ class SMTP * @var string|callable */ public $Debugoutput = 'echo'; - + /** * Whether to use VERP. * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path @@ -133,7 +133,7 @@ class SMTP * @var boolean */ public $do_verp = false; - + /** * The timeout value for connection, in seconds. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 @@ -142,31 +142,36 @@ class SMTP * @var integer */ public $Timeout = 300; - + /** * How long to wait for commands to complete, in seconds. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 * @var integer */ 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=(.*)/', 'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/', '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 */ protected $smtp_conn; - + /** * Error information, if any, for the last SMTP command. * @var array @@ -177,14 +182,14 @@ class SMTP 'smtp_code' => '', 'smtp_code_ex' => '' ); - + /** * The reply the server sent to us for HELO. * If null, no HELO string has yet been received. * @var string|null */ protected $helo_rply = null; - + /** * The set of SMTP extensions sent in reply to EHLO command. * Indexes of the array are extension names. @@ -195,13 +200,13 @@ class SMTP * @var array|null */ protected $server_caps = null; - + /** * The most recent reply received from the server. * @var string */ protected $last_reply = ''; - + /** * Output debugging info via a user-selected method. * @see SMTP::$Debugoutput @@ -227,12 +232,11 @@ 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' - ) - . "
\n"; + ) . "
\n"; break; case 'echo': default: @@ -242,10 +246,10 @@ class SMTP "\n", "\n \t ", trim($str) - )."\n"; + ) . "\n"; } } - + /** * Connect to an SMTP server. * @param string $host SMTP server IP or host name @@ -276,7 +280,8 @@ class SMTP } // Connect to the SMTP server $this->edebug( - "Connection: opening to $host:$port, timeout=$timeout, options=".var_export($options, true), + "Connection: opening to $host:$port, timeout=$timeout, options=" . + var_export($options, true), self::DEBUG_CONNECTION ); $errno = 0; @@ -339,7 +344,7 @@ class SMTP $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); return true; } - + /** * Initiate a TLS (encrypted) session. * @access public @@ -350,28 +355,28 @@ class SMTP if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { return false; } - + //Allow the best TLS version(s) we can $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT; - + //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT //so add them back in manually if we can if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; } - + // Begin encrypted connection - if (!stream_socket_enable_crypto( + set_error_handler(array($this, 'errorHandler')); + $crypto_ok = stream_socket_enable_crypto( $this->smtp_conn, true, $crypto_method - )) { - return false; - } - return true; + ); + restore_error_handler(); + return $crypto_ok; } - + /** * Perform SMTP authentication. * Must be run after hello(). @@ -396,23 +401,22 @@ class SMTP $this->setError('Authentication is not allowed before HELO/EHLO'); return false; } - + if (array_key_exists('EHLO', $this->server_caps)) { - // SMTP extensions are available. Let's try to find a proper authentication method - + // SMTP extensions are available; try to find a proper authentication method if (!array_key_exists('AUTH', $this->server_caps)) { $this->setError('Authentication is not allowed at this stage'); // 'at this stage' means that auth may be allowed after the stage changes // e.g. after STARTTLS return false; } - + self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL); self::edebug( 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), self::DEBUG_LOWLEVEL ); - + if (empty($authtype)) { foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN', 'NTLM', 'XOAUTH2') as $method) { if (in_array($method, $this->server_caps['AUTH'])) { @@ -424,9 +428,9 @@ class SMTP $this->setError('No supported authentication methods found'); return false; } - self::edebug('Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL); + self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL); } - + if (!in_array($authtype, $this->server_caps['AUTH'])) { $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); return false; @@ -469,7 +473,7 @@ class SMTP return false; } $oauth = $OAuth->getOauth64(); - + // Start authentication if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) { return false; @@ -499,7 +503,7 @@ class SMTP } //msg1 $msg1 = $ntlm_client->typeMsg1($realm, $workstation); //msg1 - + if (!$this->sendCommand( 'AUTH NTLM', 'AUTH NTLM ' . base64_encode($msg1), @@ -532,10 +536,10 @@ class SMTP } // Get the challenge $challenge = base64_decode(substr($this->last_reply, 4)); - + // Build the response $response = $username . ' ' . $this->hmac($challenge, $password); - + // send encoded credentials return $this->sendCommand('Username', base64_encode($response), 235); default: @@ -544,13 +548,13 @@ class SMTP } return true; } - + /** * Calculate an MD5 HMAC hash. * Works like hash_hmac('md5', $data, $key) * in case that function is not available * @param string $data The data to hash - * @param string $key The key to hash with + * @param string $key The key to hash with * @access protected * @return string */ @@ -559,15 +563,15 @@ class SMTP if (function_exists('hash_hmac')) { return hash_hmac('md5', $data, $key); } - + // The following borrowed from // http://php.net/manual/en/function.mhash.php#27225 - + // RFC 2104 HMAC implementation for php. // Creates an md5 HMAC. // Eliminates the need to install mhash to compute a HMAC // by Lance Rushing - + $bytelen = 64; // byte length for md5 if (strlen($key) > $bytelen) { $key = pack('H*', md5($key)); @@ -577,10 +581,10 @@ class SMTP $opad = str_pad('', $bytelen, chr(0x5c)); $k_ipad = $key ^ $ipad; $k_opad = $key ^ $opad; - + return md5($k_opad . pack('H*', md5($k_ipad . $data))); } - + /** * Check connection state. * @access public @@ -603,7 +607,7 @@ class SMTP } return false; } - + /** * Close the socket and clean up the state of the class. * Don't use this function without first trying to use QUIT. @@ -623,7 +627,7 @@ class SMTP $this->edebug('Connection: closed', self::DEBUG_CONNECTION); } } - + /** * Send an SMTP DATA command. * Issues a data command and sends the msg_data to the server, @@ -642,7 +646,7 @@ class SMTP if (!$this->sendCommand('DATA', 'DATA', 354)) { return false; } - + /* The server is ready to accept data! * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF) * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into @@ -650,21 +654,21 @@ class SMTP * We will also look for lines that start with a '.' and prepend an additional '.'. * NOTE: this does not count towards line-length limit. */ - + // Normalize line breaks before exploding $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data)); - + /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field * of the first line (':' separated) does not contain a space then it _should_ be a header and we will * process all lines before a blank line as headers. */ - + $field = substr($lines[0], 0, strpos($lines[0], ':')); $in_headers = false; if (!empty($field) && strpos($field, ' ') === false) { $in_headers = true; } - + foreach ($lines as $line) { $lines_out = array(); if ($in_headers and $line == '') { @@ -694,7 +698,7 @@ class SMTP } } $lines_out[] = $line; - + //Send the lines to the server foreach ($lines_out as $line_out) { //RFC2821 section 4.5.2 @@ -704,17 +708,18 @@ class SMTP $this->client_send($line_out . self::CRLF); } } - + //Message data has been sent, complete the command //Increase timelimit for end of DATA command $savetimelimit = $this->Timelimit; $this->Timelimit = $this->Timelimit * 2; $result = $this->sendCommand('DATA END', '.', 250); + $this->recordLastTransactionID(); //Restore timelimit $this->Timelimit = $savetimelimit; return $result; } - + /** * Send an SMTP HELO or EHLO command. * Used to identify the sending server to the receiving server. @@ -730,7 +735,7 @@ class SMTP //Try extended hello first (RFC 2821) return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); } - + /** * Send an SMTP HELO or EHLO command. * Low-level implementation used by hello() @@ -751,7 +756,7 @@ class SMTP } return $noerror; } - + /** * Parse a reply to HELO/EHLO command to discover server extensions. * In case of HELO, the only parameter that can be discovered is a server name. @@ -762,7 +767,7 @@ class SMTP { $this->server_caps = array(); $lines = explode("\n", $this->helo_rply); - + foreach ($lines as $n => $s) { //First 4 chars contain response code followed by - or space $s = trim(substr($s, 4)); @@ -793,7 +798,7 @@ class SMTP } } } - + /** * Send an SMTP MAIL command. * Starts a mail transaction from the email address specified in @@ -814,7 +819,7 @@ class SMTP 250 ); } - + /** * Send an SMTP QUIT command. * Closes the socket if there is no error or the $close_on_error argument is true. @@ -833,7 +838,7 @@ class SMTP } return $noerror; } - + /** * Send an SMTP RCPT command. * Sets the TO argument to $toaddr. @@ -851,7 +856,7 @@ class SMTP array(250, 251) ); } - + /** * Send an SMTP RSET command. * Abort any transaction that is currently in progress. @@ -863,7 +868,7 @@ class SMTP { return $this->sendCommand('RSET', 'RSET', 250); } - + /** * Send a command to an SMTP server and check its return code. * @param string $command The command name - not sent to the server @@ -884,7 +889,7 @@ class SMTP return false; } $this->client_send($commandstring . self::CRLF); - + $this->last_reply = $this->get_lines(); // Fetch SMTP code and possible error code explanation $matches = array(); @@ -893,7 +898,8 @@ class SMTP $code_ex = (count($matches) > 2 ? $matches[2] : null); // Cut off error code from each response line $detail = preg_replace( - "/{$code}[ -]".($code_ex ? str_replace('.', '\\.', $code_ex).' ' : '')."/m", + "/{$code}[ -]" . + ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . "/m", '', $this->last_reply ); @@ -903,9 +909,9 @@ class SMTP $code_ex = null; $detail = substr($this->last_reply, 4); } - + $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); - + if (!in_array($code, (array)$expect)) { $this->setError( "$command command failed", @@ -919,11 +925,11 @@ class SMTP ); return false; } - + $this->setError(''); return true; } - + /** * Send an SMTP SAML command. * Starts a mail transaction from the email address specified in $from. @@ -941,7 +947,7 @@ class SMTP { return $this->sendCommand('SAML', "SAML FROM:$from", 250); } - + /** * Send an SMTP VRFY command. * @param string $name The name to verify @@ -952,7 +958,7 @@ class SMTP { return $this->sendCommand('VRFY', "VRFY $name", array(250, 251)); } - + /** * Send an SMTP NOOP command. * Used to keep keep-alives alive, doesn't actually do anything @@ -963,7 +969,7 @@ class SMTP { return $this->sendCommand('NOOP', 'NOOP', 250); } - + /** * Send an SMTP TURN command. * This is an optional command for SMTP that this class does not support. @@ -979,7 +985,7 @@ class SMTP $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); return false; } - + /** * Send raw data to the server. * @param string $data The data to send @@ -989,9 +995,12 @@ 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; } - + /** * Get the latest error. * @access public @@ -1001,7 +1010,7 @@ class SMTP { return $this->error; } - + /** * Get SMTP extensions available on the server * @access public @@ -1011,7 +1020,7 @@ class SMTP { return $this->server_caps; } - + /** * A multipurpose method * The method works in three ways, dependent on argument value and current state @@ -1037,7 +1046,7 @@ class SMTP $this->setError('No HELO/EHLO was sent'); return null; } - + // the tight logic knot ;) if (!array_key_exists($name, $this->server_caps)) { if ($name == 'HELO') { @@ -1049,10 +1058,10 @@ class SMTP $this->setError('HELO handshake was used. Client knows nothing about server extensions'); return null; } - + return $this->server_caps[$name]; } - + /** * Get the last reply from the server. * @access public @@ -1062,7 +1071,7 @@ class SMTP { return $this->last_reply; } - + /** * Read the SMTP server's response. * Either before eof or socket timeout occurs on the operation. @@ -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 @@ -1105,7 +1116,7 @@ class SMTP // Now check if reads took too long if ($endtime and time() > $endtime) { $this->edebug( - 'SMTP -> get_lines(): timelimit reached ('. + 'SMTP -> get_lines(): timelimit reached (' . $this->Timelimit . ' sec)', self::DEBUG_LOWLEVEL ); @@ -1114,7 +1125,7 @@ class SMTP } return $data; } - + /** * Enable or disable VERP address generation. * @param boolean $enabled @@ -1123,7 +1134,7 @@ class SMTP { $this->do_verp = $enabled; } - + /** * Get VERP address generation mode. * @return boolean @@ -1132,7 +1143,7 @@ class SMTP { return $this->do_verp; } - + /** * Set error messages and codes. * @param string $message The error message @@ -1149,7 +1160,7 @@ class SMTP 'smtp_code_ex' => $smtp_code_ex ); } - + /** * Set debug output method. * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it. @@ -1158,7 +1169,7 @@ class SMTP { $this->Debugoutput = $method; } - + /** * Get debug output method. * @return string @@ -1167,7 +1178,7 @@ class SMTP { return $this->Debugoutput; } - + /** * Set debug output level. * @param integer $level @@ -1176,7 +1187,7 @@ class SMTP { $this->do_debug = $level; } - + /** * Get debug output level. * @return integer @@ -1185,7 +1196,7 @@ class SMTP { return $this->do_debug; } - + /** * Set SMTP timeout. * @param integer $timeout @@ -1194,7 +1205,7 @@ class SMTP { $this->Timeout = $timeout; } - + /** * Get SMTP timeout. * @return integer @@ -1203,47 +1214,63 @@ class SMTP { 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. + * @param string $errfile The file the error occurred in + * @param integer $errline The line number the error occurred on */ - protected function errorHandler($errno, $errmsg) + protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0) { - $notice = 'Connection: Failed to connect to server.'; + $notice = 'Connection failed.'; $this->setError( $notice, $errno, $errmsg ); $this->edebug( - $notice . ' Error number ' . $errno . '. "Error notice: ' . $errmsg, + $notice . ' Error #' . $errno . ': ' . $errmsg . " [$errfile line $errline]", 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. + * 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 */ + protected function recordLastTransactionID() + { + $reply = $this->getLastReply(); + + if (empty($reply)) { + $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 $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() { - $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; + return $this->last_smtp_transaction_id; } }