2020-02-13 20:31:32 +00:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Copyright 1997-2017 Horde LLC (http://www.horde.org/)
|
|
|
|
* Copyright (c) 2002-2007, Richard Heyes
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
*
|
|
|
|
* o Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* o Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
* o The names of the authors may not be used to endorse or promote
|
|
|
|
* products derived from this software without specific prior written
|
|
|
|
* permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*
|
|
|
|
* @category Horde
|
|
|
|
* @copyright 1997-2017 Horde LLC (http://www.horde.org/)
|
|
|
|
* @copyright 2002-2007 Richard Heyes
|
|
|
|
* @license http://www.horde.org/licenses/bsd New BSD License
|
|
|
|
* @package Mail
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mail transport base class.
|
|
|
|
*
|
|
|
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
|
|
|
* @author Richard Heyes <richard@phpguru.org>
|
|
|
|
* @author Michael Slusarz <slusarz@horde.org>
|
|
|
|
* @category Horde
|
|
|
|
* @copyright 1997-2017 Horde LLC (http://www.horde.org/)
|
|
|
|
* @copyright 2002-2007 Richard Heyes
|
|
|
|
* @license http://www.horde.org/licenses/bsd New BSD License
|
|
|
|
* @package Mail
|
|
|
|
*
|
|
|
|
* @property-read boolean $eai Does the transport driver support EAI (RFC
|
|
|
|
* 6532) headers? (@since 2.5.0)
|
|
|
|
*/
|
|
|
|
abstract class Horde_Mail_Transport
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Line terminator used for separating header lines.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
public $sep = PHP_EOL;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Configuration parameters.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $_params = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
*/
|
|
|
|
public function __get($name)
|
|
|
|
{
|
|
|
|
switch ($name) {
|
|
|
|
case 'eai':
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Send a message.
|
|
|
|
*
|
2020-02-24 19:08:28 +00:00
|
|
|
* @param mixed $recipients Either a comma-separated list of recipients
|
2020-02-13 20:31:32 +00:00
|
|
|
* (RFC822 compliant), or an array of
|
|
|
|
* recipients, each RFC822 valid. This may
|
|
|
|
* contain recipients not specified in the
|
|
|
|
* headers, for Bcc:, resending messages, etc.
|
|
|
|
* @param array $headers The headers to send with the mail, in an
|
|
|
|
* associative array, where the array key is the
|
|
|
|
* header name (ie, 'Subject'), and the array
|
|
|
|
* value is the header value (ie, 'test'). The
|
|
|
|
* header produced from those values would be
|
|
|
|
* 'Subject: test'.
|
|
|
|
* If the '_raw' key exists, the value of this
|
|
|
|
* key will be used as the exact text for
|
|
|
|
* sending the message.
|
|
|
|
* @param mixed $body The full text of the message body, including
|
|
|
|
* any Mime parts, etc. Either a string or a
|
|
|
|
* stream resource.
|
|
|
|
*
|
|
|
|
* @throws Horde_Mail_Exception
|
|
|
|
*/
|
|
|
|
abstract public function send($recipients, array $headers, $body);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Take an array of mail headers and return a string containing text
|
|
|
|
* usable in sending a message.
|
|
|
|
*
|
|
|
|
* @param array $headers The array of headers to prepare, in an
|
|
|
|
* associative array, where the array key is the
|
|
|
|
* header name (ie, 'Subject'), and the array value
|
|
|
|
* is the header value (ie, 'test'). The header
|
|
|
|
* produced from those values would be 'Subject:
|
|
|
|
* test'.
|
|
|
|
* If the '_raw' key exists, the value of this key
|
|
|
|
* will be used as the exact text for sending the
|
|
|
|
* message.
|
|
|
|
*
|
|
|
|
* @return mixed Returns false if it encounters a bad address; otherwise
|
|
|
|
* returns an array containing two elements: Any From:
|
|
|
|
* address found in the headers, and the plain text version
|
|
|
|
* of the headers.
|
|
|
|
* @throws Horde_Mail_Exception
|
|
|
|
*/
|
|
|
|
public function prepareHeaders(array $headers)
|
|
|
|
{
|
|
|
|
$from = null;
|
|
|
|
$lines = array();
|
|
|
|
$raw = isset($headers['_raw'])
|
|
|
|
? $headers['_raw']
|
|
|
|
: null;
|
|
|
|
|
|
|
|
foreach ($headers as $key => $value) {
|
|
|
|
if (strcasecmp($key, 'From') === 0) {
|
|
|
|
$parser = new Horde_Mail_Rfc822();
|
|
|
|
$addresses = $parser->parseAddressList($value, array(
|
|
|
|
'validate' => $this->eai ? 'eai' : true
|
|
|
|
));
|
|
|
|
$from = $addresses[0]->bare_address;
|
|
|
|
|
|
|
|
// Reject envelope From: addresses with spaces.
|
|
|
|
if (strstr($from, ' ')) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$lines[] = $key . ': ' . $this->_normalizeEOL($value);
|
|
|
|
} elseif (!$raw && (strcasecmp($key, 'Received') === 0)) {
|
|
|
|
$received = array();
|
|
|
|
if (!is_array($value)) {
|
|
|
|
$value = array($value);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($value as $line) {
|
|
|
|
$received[] = $key . ': ' . $this->_normalizeEOL($line);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put Received: headers at the top. Spam detectors often
|
|
|
|
// flag messages with Received: headers after the Subject:
|
|
|
|
// as spam.
|
|
|
|
$lines = array_merge($received, $lines);
|
|
|
|
} elseif (!$raw) {
|
|
|
|
// If $value is an array (i.e., a list of addresses), convert
|
|
|
|
// it to a comma-delimited string of its elements (addresses).
|
|
|
|
if (is_array($value)) {
|
|
|
|
$value = implode(', ', $value);
|
|
|
|
}
|
|
|
|
$lines[] = $key . ': ' . $this->_normalizeEOL($value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return array($from, $raw ? $raw : implode($this->sep, $lines));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Take a set of recipients and parse them, returning an array of bare
|
|
|
|
* addresses (forward paths) that can be passed to sendmail or an SMTP
|
|
|
|
* server with the 'RCPT TO:' command.
|
|
|
|
*
|
|
|
|
* @param mixed $recipients Either a comma-separated list of recipients
|
|
|
|
* (RFC822 compliant), or an array of
|
|
|
|
* recipients, each RFC822 valid.
|
|
|
|
*
|
|
|
|
* @return array Forward paths (bare addresses, IDN encoded).
|
|
|
|
* @throws Horde_Mail_Exception
|
|
|
|
*/
|
|
|
|
public function parseRecipients($recipients)
|
|
|
|
{
|
|
|
|
// Parse recipients, leaving out all personal info. This is
|
|
|
|
// for smtp recipients, etc. All relevant personal information
|
|
|
|
// should already be in the headers.
|
|
|
|
$rfc822 = new Horde_Mail_Rfc822();
|
|
|
|
return $rfc822->parseAddressList($recipients, array(
|
|
|
|
'validate' => $this->eai ? 'eai' : true
|
|
|
|
))->bare_addresses_idn;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sanitize an array of mail headers by removing any additional header
|
|
|
|
* strings present in a legitimate header's value. The goal of this
|
|
|
|
* filter is to prevent mail injection attacks.
|
|
|
|
*
|
|
|
|
* Raw headers are sent as-is.
|
|
|
|
*
|
|
|
|
* @param array $headers The associative array of headers to sanitize.
|
|
|
|
*
|
|
|
|
* @return array The sanitized headers.
|
|
|
|
*/
|
|
|
|
protected function _sanitizeHeaders($headers)
|
|
|
|
{
|
|
|
|
foreach (array_diff(array_keys($headers), array('_raw')) as $key) {
|
|
|
|
$headers[$key] = preg_replace('=((<CR>|<LF>|0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', null, $headers[$key]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $headers;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Normalizes EOLs in string data.
|
|
|
|
*
|
|
|
|
* @param string $data Data.
|
|
|
|
*
|
|
|
|
* @return string Normalized data.
|
|
|
|
*/
|
|
|
|
protected function _normalizeEOL($data)
|
|
|
|
{
|
|
|
|
return strtr($data, array(
|
|
|
|
"\r\n" => $this->sep,
|
|
|
|
"\r" => $this->sep,
|
|
|
|
"\n" => $this->sep
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the from address.
|
|
|
|
*
|
|
|
|
* @param string $from From address.
|
|
|
|
* @param array $headers Headers array.
|
|
|
|
*
|
|
|
|
* @return string Address object.
|
|
|
|
* @throws Horde_Mail_Exception
|
|
|
|
*/
|
|
|
|
protected function _getFrom($from, $headers)
|
|
|
|
{
|
|
|
|
/* Since few MTAs are going to allow this header to be forged unless
|
|
|
|
* it's in the MAIL FROM: exchange, we'll use Return-Path instead of
|
|
|
|
* From: if it's set. */
|
|
|
|
foreach (array_keys($headers) as $hdr) {
|
|
|
|
if (strcasecmp($hdr, 'Return-Path') === 0) {
|
|
|
|
$from = $headers[$hdr];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strlen($from)) {
|
|
|
|
throw new Horde_Mail_Exception('No from address provided.');
|
|
|
|
}
|
|
|
|
|
|
|
|
$from = new Horde_Mail_Rfc822_Address($from);
|
|
|
|
|
|
|
|
return $from->bare_address_idn;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|