| version 1.2, 2005/11/07 20:37:52 |
version 1.3, 2015/11/24 17:21:06 |
|
|
| <?php |
<?php |
| /* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */ |
/** vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */ |
| // +----------------------------------------------------------------------+ |
// +----------------------------------------------------------------------+ |
| // | PHP Version 4 | |
// | PHP Version 5 and 7 | |
| // +----------------------------------------------------------------------+ |
// +----------------------------------------------------------------------+ |
| // | Copyright (c) 1997-2003 The PHP Group | |
// | Copyright (c) 1997-2015 Jon Parise and Chuck Hagenbuch | |
| // +----------------------------------------------------------------------+ |
// +----------------------------------------------------------------------+ |
| // | This source file is subject to version 2.02 of the PHP license, | |
// | This source file is subject to version 3.01 of the PHP license, | |
| // | that is bundled with this package in the file LICENSE, and is | |
// | that is bundled with this package in the file LICENSE, and is | |
| // | available at through the world-wide-web at | |
// | available at through the world-wide-web at | |
| // | http://www.php.net/license/2_02.txt. | |
// | http://www.php.net/license/3_01.txt. | |
| // | If you did not receive a copy of the PHP license and are unable to | |
// | If you did not receive a copy of the PHP license and are unable to | |
| // | obtain it through the world-wide-web, please send a note to | |
// | obtain it through the world-wide-web, please send a note to | |
| // | license@php.net so we can mail you a copy immediately. | |
// | license@php.net so we can mail you a copy immediately. | |
|
|
| // | Jon Parise <jon@php.net> | |
// | Jon Parise <jon@php.net> | |
| // | Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar> | |
// | Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar> | |
| // +----------------------------------------------------------------------+ |
// +----------------------------------------------------------------------+ |
| // |
|
| // $Id$ |
|
| |
|
| require_once 'PEAR.php'; |
require_once 'PEAR.php'; |
| require_once 'Net/Socket.php'; |
require_once 'Net/Socket.php'; |
| |
|
| /** |
/** |
| * Provides an implementation of the SMTP protocol using PEAR's |
* Provides an implementation of the SMTP protocol using PEAR's |
| * Net_Socket:: class. |
* Net_Socket class. |
| * |
* |
| * @package Net_SMTP |
* @package Net_SMTP |
| * @author Chuck Hagenbuch <chuck@horde.org> |
* @author Chuck Hagenbuch <chuck@horde.org> |
| * @author Jon Parise <jon@php.net> |
* @author Jon Parise <jon@php.net> |
| * @author Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar> |
* @author Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar> |
| * |
* |
| * @example basic.php A basic implementation of the Net_SMTP package. |
* @example basic.php A basic implementation of the Net_SMTP package. |
| */ |
*/ |
| class Net_SMTP |
class Net_SMTP |
| { |
{ |
| |
|
| /** |
/** |
| * The server to connect to. |
* The server to connect to. |
| * @var string |
* @var string |
| * @access public |
|
| */ |
*/ |
| var $host = 'localhost'; |
public $host = 'localhost'; |
| |
|
| /** |
/** |
| * The port to connect to. |
* The port to connect to. |
| * @var int |
* @var int |
| * @access public |
|
| */ |
*/ |
| var $port = 25; |
public $port = 25; |
| |
|
| /** |
/** |
| * The value to give when sending EHLO or HELO. |
* The value to give when sending EHLO or HELO. |
| * @var string |
* @var string |
| * @access public |
|
| */ |
*/ |
| var $localhost = 'localhost'; |
public $localhost = 'localhost'; |
| |
|
| /** |
/** |
| * List of supported authentication methods, in preferential order. |
* List of supported authentication methods, in preferential order. |
| * @var array |
* @var array |
| * @access public |
|
| */ |
*/ |
| var $auth_methods = array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN', 'PLAIN'); |
public $auth_methods = array(); |
| |
|
| |
/** |
| |
* Use SMTP command pipelining (specified in RFC 2920) if the SMTP |
| |
* server supports it. |
| |
* |
| |
* When pipeling is enabled, rcptTo(), mailFrom(), sendFrom(), |
| |
* somlFrom() and samlFrom() do not wait for a response from the |
| |
* SMTP server but return immediately. |
| |
* |
| |
* @var bool |
| |
*/ |
| |
public $pipelining = false; |
| |
|
| |
/** |
| |
* Number of pipelined commands. |
| |
* @var int |
| |
*/ |
| |
protected $pipelined_commands = 0; |
| |
|
| /** |
/** |
| * Should debugging output be enabled? |
* Should debugging output be enabled? |
| * @var boolean |
* @var boolean |
| * @access private |
|
| */ |
*/ |
| var $_debug = false; |
protected $debug = false; |
| |
|
| |
/** |
| |
* Debug output handler. |
| |
* @var callback |
| |
*/ |
| |
protected $debug_handler = null; |
| |
|
| /** |
/** |
| * The socket resource being used to connect to the SMTP server. |
* The socket resource being used to connect to the SMTP server. |
| * @var resource |
* @var resource |
| * @access private |
|
| */ |
*/ |
| var $_socket = null; |
protected $socket = null; |
| |
|
| |
/** |
| |
* Array of socket options that will be passed to Net_Socket::connect(). |
| |
* @see stream_context_create() |
| |
* @var array |
| |
*/ |
| |
protected $socket_options = null; |
| |
|
| |
/** |
| |
* The socket I/O timeout value in seconds. |
| |
* @var int |
| |
*/ |
| |
protected $timeout = 0; |
| |
|
| /** |
/** |
| * The most recent server response code. |
* The most recent server response code. |
| * @var int |
* @var int |
| * @access private |
|
| */ |
*/ |
| var $_code = -1; |
protected $code = -1; |
| |
|
| /** |
/** |
| * The most recent server response arguments. |
* The most recent server response arguments. |
| * @var array |
* @var array |
| * @access private |
|
| */ |
*/ |
| var $_arguments = array(); |
protected $arguments = array(); |
| |
|
| |
/** |
| |
* Stores the SMTP server's greeting string. |
| |
* @var string |
| |
*/ |
| |
protected $greeting = null; |
| |
|
| /** |
/** |
| * Stores detected features of the SMTP server. |
* Stores detected features of the SMTP server. |
| * @var array |
* @var array |
| * @access private |
|
| */ |
*/ |
| var $_esmtp = array(); |
protected $esmtp = array(); |
| |
|
| /** |
/** |
| * Instantiates a new Net_SMTP object, overriding any defaults |
* Instantiates a new Net_SMTP object, overriding any defaults |
|
|
| * $smtp = new Net_SMTP('ssl://mail.host.com', 465); |
* $smtp = new Net_SMTP('ssl://mail.host.com', 465); |
| * $smtp->connect(); |
* $smtp->connect(); |
| * |
* |
| * @param string $host The server to connect to. |
* @param string $host The server to connect to. |
| * @param integer $port The port to connect to. |
* @param integer $port The port to connect to. |
| * @param string $localhost The value to give when sending EHLO or HELO. |
* @param string $localhost The value to give when sending EHLO or HELO. |
| |
* @param boolean $pipelining Use SMTP command pipelining |
| |
* @param integer $timeout Socket I/O timeout in seconds. |
| |
* @param array $socket_options Socket stream_context_create() options. |
| * |
* |
| * @access public |
* @since 1.0 |
| * @since 1.0 |
|
| */ |
*/ |
| function Net_SMTP($host = null, $port = null, $localhost = null) |
public function __construct($host = null, $port = null, $localhost = null, |
| { |
$pipelining = false, $timeout = 0, $socket_options = null |
| if (isset($host)) $this->host = $host; |
) { |
| if (isset($port)) $this->port = $port; |
if (isset($host)) { |
| if (isset($localhost)) $this->localhost = $localhost; |
$this->host = $host; |
| |
} |
| |
if (isset($port)) { |
| |
$this->port = $port; |
| |
} |
| |
if (isset($localhost)) { |
| |
$this->localhost = $localhost; |
| |
} |
| |
|
| $this->_socket = &new Net_Socket(); |
$this->pipelining = $pipelining; |
| |
$this->socket = new Net_Socket(); |
| |
$this->socket_options = $socket_options; |
| |
$this->timeout = $timeout; |
| |
|
| /* |
/* Include the Auth_SASL package. If the package is available, we |
| * Include the Auth_SASL package. If the package is not available, |
* enable the authentication methods that depend upon it. */ |
| * we disable the authentication methods that depend upon it. |
if (@include_once 'Auth/SASL.php') { |
| */ |
$this->setAuthMethod('CRAM-MD5', array($this, 'authCramMD5')); |
| if ((@include_once 'Auth/SASL.php') === false) { |
$this->setAuthMethod('DIGEST-MD5', array($this, 'authDigestMD5')); |
| $pos = array_search('DIGEST-MD5', $this->auth_methods); |
|
| unset($this->auth_methods[$pos]); |
|
| $pos = array_search('CRAM-MD5', $this->auth_methods); |
|
| unset($this->auth_methods[$pos]); |
|
| } |
} |
| |
|
| |
/* These standard authentication methods are always available. */ |
| |
$this->setAuthMethod('LOGIN', array($this, 'authLogin'), false); |
| |
$this->setAuthMethod('PLAIN', array($this, 'authPlain'), false); |
| |
} |
| |
|
| |
/** |
| |
* Set the socket I/O timeout value in seconds plus microseconds. |
| |
* |
| |
* @param integer $seconds Timeout value in seconds. |
| |
* @param integer $microseconds Additional value in microseconds. |
| |
* |
| |
* @since 1.5.0 |
| |
*/ |
| |
public function setTimeout($seconds, $microseconds = 0) |
| |
{ |
| |
return $this->socket->setTimeout($seconds, $microseconds); |
| } |
} |
| |
|
| /** |
/** |
| * Set the value of the debugging flag. |
* Set the value of the debugging flag. |
| * |
* |
| * @param boolean $debug New value for the debugging flag. |
* @param boolean $debug New value for the debugging flag. |
| |
* @param callback $handler Debug handler callback |
| * |
* |
| * @access public |
* @since 1.1.0 |
| * @since 1.1.0 |
|
| */ |
*/ |
| function setDebug($debug) |
public function setDebug($debug, $handler = null) |
| { |
{ |
| $this->_debug = $debug; |
$this->debug = $debug; |
| |
$this->debug_handler = $handler; |
| |
} |
| |
|
| |
/** |
| |
* Write the given debug text to the current debug output handler. |
| |
* |
| |
* @param string $message Debug mesage text. |
| |
* |
| |
* @since 1.3.3 |
| |
*/ |
| |
protected function debug($message) |
| |
{ |
| |
if ($this->debug) { |
| |
if ($this->debug_handler) { |
| |
call_user_func_array( |
| |
$this->debug_handler, array(&$this, $message) |
| |
); |
| |
} else { |
| |
echo "DEBUG: $message\n"; |
| |
} |
| |
} |
| } |
} |
| |
|
| /** |
/** |
| * Send the given string of data to the server. |
* Send the given string of data to the server. |
| * |
* |
| * @param string $data The string of data to send. |
* @param string $data The string of data to send. |
| * |
* |
| * @return mixed True on success or a PEAR_Error object on failure. |
* @return mixed The number of bytes that were actually written, |
| |
* or a PEAR_Error object on failure. |
| * |
* |
| * @access private |
* @since 1.1.0 |
| * @since 1.1.0 |
|
| */ |
*/ |
| function _send($data) |
protected function send($data) |
| { |
{ |
| if ($this->_debug) { |
$this->debug("Send: $data"); |
| echo "DEBUG: Send: $data\n"; |
|
| } |
|
| |
|
| if (PEAR::isError($error = $this->_socket->write($data))) { |
$result = $this->socket->write($data); |
| return PEAR::raiseError('Failed to write to socket: ' . |
if (!$result || PEAR::isError($result)) { |
| $error->getMessage()); |
$msg = $result ? $result->getMessage() : "unknown error"; |
| |
return PEAR::raiseError("Failed to write to socket: $msg"); |
| } |
} |
| |
|
| return true; |
return $result; |
| } |
} |
| |
|
| /** |
/** |
|
|
| * arguments. A carriage return / linefeed (CRLF) sequence will |
* arguments. A carriage return / linefeed (CRLF) sequence will |
| * be appended to each command string before it is sent to the |
* be appended to each command string before it is sent to the |
| * SMTP server - an error will be thrown if the command string |
* SMTP server - an error will be thrown if the command string |
| * already contains any newline characters. Use _send() for |
* already contains any newline characters. Use send() for |
| * commands that must contain newlines. |
* commands that must contain newlines. |
| * |
* |
| * @param string $command The SMTP command to send to the server. |
* @param string $command The SMTP command to send to the server. |
| * @param string $args A string of optional arguments to append |
* @param string $args A string of optional arguments to append |
| * to the command. |
* to the command. |
| * |
* |
| * @return mixed The result of the _send() call. |
* @return mixed The result of the send() call. |
| * |
* |
| * @access private |
* @since 1.1.0 |
| * @since 1.1.0 |
|
| */ |
*/ |
| function _put($command, $args = '') |
protected function put($command, $args = '') |
| { |
{ |
| if (!empty($args)) { |
if (!empty($args)) { |
| $command .= ' ' . $args; |
$command .= ' ' . $args; |
|
|
| return PEAR::raiseError('Commands cannot contain newlines'); |
return PEAR::raiseError('Commands cannot contain newlines'); |
| } |
} |
| |
|
| return $this->_send($command . "\r\n"); |
return $this->send($command . "\r\n"); |
| } |
} |
| |
|
| /** |
/** |
| * Read a reply from the SMTP server. The reply consists of a response |
* Read a reply from the SMTP server. The reply consists of a response |
| * code and a response message. |
* code and a response message. |
| * |
* |
| * @param mixed $valid The set of valid response codes. These |
* @param mixed $valid The set of valid response codes. These |
| * may be specified as an array of integer |
* may be specified as an array of integer |
| * values or as a single integer value. |
* values or as a single integer value. |
| |
* @param bool $later Do not parse the response now, but wait |
| |
* until the last command in the pipelined |
| |
* command group |
| * |
* |
| * @return mixed True if the server returned a valid response code or |
* @return mixed True if the server returned a valid response code or |
| * a PEAR_Error object is an error condition is reached. |
* a PEAR_Error object is an error condition is reached. |
| * |
* |
| * @access private |
* @since 1.1.0 |
| * @since 1.1.0 |
|
| * |
* |
| * @see getResponse |
* @see getResponse |
| */ |
*/ |
| function _parseResponse($valid) |
protected function parseResponse($valid, $later = false) |
| { |
{ |
| $this->_code = -1; |
$this->code = -1; |
| $this->_arguments = array(); |
$this->arguments = array(); |
| |
|
| while ($line = $this->_socket->readLine()) { |
|
| if ($this->_debug) { |
|
| echo "DEBUG: Recv: $line\n"; |
|
| } |
|
| |
|
| /* If we receive an empty line, the connection has been closed. */ |
if ($later) { |
| if (empty($line)) { |
$this->pipelined_commands++; |
| $this->disconnect(); |
return true; |
| return PEAR::raiseError('Connection was unexpectedly closed'); |
} |
| } |
|
| |
|
| /* Read the code and store the rest in the arguments array. */ |
for ($i = 0; $i <= $this->pipelined_commands; $i++) { |
| $code = substr($line, 0, 3); |
while ($line = $this->socket->readLine()) { |
| $this->_arguments[] = trim(substr($line, 4)); |
$this->debug("Recv: $line"); |
| |
|
| |
/* If we receive an empty line, the connection was closed. */ |
| |
if (empty($line)) { |
| |
$this->disconnect(); |
| |
return PEAR::raiseError('Connection was closed'); |
| |
} |
| |
|
| /* Check the syntax of the response code. */ |
/* Read the code and store the rest in the arguments array. */ |
| if (is_numeric($code)) { |
$code = substr($line, 0, 3); |
| $this->_code = (int)$code; |
$this->arguments[] = trim(substr($line, 4)); |
| } else { |
|
| $this->_code = -1; |
/* Check the syntax of the response code. */ |
| break; |
if (is_numeric($code)) { |
| } |
$this->code = (int)$code; |
| |
} else { |
| |
$this->code = -1; |
| |
break; |
| |
} |
| |
|
| /* If this is not a multiline response, we're done. */ |
/* If this is not a multiline response, we're done. */ |
| if (substr($line, 3, 1) != '-') { |
if (substr($line, 3, 1) != '-') { |
| break; |
break; |
| |
} |
| } |
} |
| } |
} |
| |
|
| /* Compare the server's response code with the valid code. */ |
$this->pipelined_commands = 0; |
| if (is_int($valid) && ($this->_code === $valid)) { |
|
| |
/* Compare the server's response code with the valid code/codes. */ |
| |
if (is_int($valid) && ($this->code === $valid)) { |
| |
return true; |
| |
} elseif (is_array($valid) && in_array($this->code, $valid, true)) { |
| return true; |
return true; |
| } |
} |
| |
|
| /* If we were given an array of valid response codes, check each one. */ |
return PEAR::raiseError('Invalid response code received from server', $this->code); |
| if (is_array($valid)) { |
} |
| foreach ($valid as $valid_code) { |
|
| if ($this->_code === $valid_code) { |
/** |
| return true; |
* Issue an SMTP command and verify its response. |
| } |
* |
| } |
* @param string $command The SMTP command string or data. |
| |
* @param mixed $valid The set of valid response codes. These |
| |
* may be specified as an array of integer |
| |
* values or as a single integer value. |
| |
* |
| |
* @return mixed True on success or a PEAR_Error object on failure. |
| |
* |
| |
* @since 1.6.0 |
| |
*/ |
| |
public function command($command, $valid) |
| |
{ |
| |
if (PEAR::isError($error = $this->put($command))) { |
| |
return $error; |
| |
} |
| |
if (PEAR::isError($error = $this->parseResponse($valid))) { |
| |
return $error; |
| } |
} |
| |
|
| return PEAR::raiseError('Invalid response code received from server'); |
return true; |
| } |
} |
| |
|
| /** |
/** |
| * Return a 2-tuple containing the last response from the SMTP server. |
* Return a 2-tuple containing the last response from the SMTP server. |
| * |
* |
| * @return array A two-element array: the first element contains the |
* @return array A two-element array: the first element contains the |
| * response code as an integer and the second element |
* response code as an integer and the second element |
| * contains the response's arguments as a string. |
* contains the response's arguments as a string. |
| |
* |
| |
* @since 1.1.0 |
| |
*/ |
| |
public function getResponse() |
| |
{ |
| |
return array($this->code, join("\n", $this->arguments)); |
| |
} |
| |
|
| |
/** |
| |
* Return the SMTP server's greeting string. |
| |
* |
| |
* @return string A string containing the greeting string, or null if |
| |
* a greeting has not been received. |
| * |
* |
| * @access public |
* @since 1.3.3 |
| * @since 1.1.0 |
|
| */ |
*/ |
| function getResponse() |
public function getGreeting() |
| { |
{ |
| return array($this->_code, join("\n", $this->_arguments)); |
return $this->greeting; |
| } |
} |
| |
|
| /** |
/** |
| * Attempt to connect to the SMTP server. |
* Attempt to connect to the SMTP server. |
| * |
* |
| * @param int $timeout The timeout value (in seconds) for the |
* @param int $timeout The timeout value (in seconds) for the |
| * socket connection. |
* socket connection attempt. |
| * @param bool $persistent Should a persistent socket connection |
* @param bool $persistent Should a persistent socket connection |
| * be used? |
* be used? |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access public |
* @since 1.0 |
| * @since 1.0 |
|
| */ |
*/ |
| function connect($timeout = null, $persistent = false) |
public function connect($timeout = null, $persistent = false) |
| { |
{ |
| $result = $this->_socket->connect($this->host, $this->port, |
$this->greeting = null; |
| $persistent, $timeout); |
|
| |
$result = $this->socket->connect( |
| |
$this->host, $this->port, $persistent, $timeout, $this->socket_options |
| |
); |
| |
|
| if (PEAR::isError($result)) { |
if (PEAR::isError($result)) { |
| return PEAR::raiseError('Failed to connect socket: ' . |
return PEAR::raiseError( |
| $result->getMessage()); |
'Failed to connect socket: ' . $result->getMessage() |
| |
); |
| |
} |
| |
|
| |
/* |
| |
* Now that we're connected, reset the socket's timeout value for |
| |
* future I/O operations. This allows us to have different socket |
| |
* timeout values for the initial connection (our $timeout parameter) |
| |
* and all other socket operations. |
| |
*/ |
| |
if ($this->timeout > 0) { |
| |
if (PEAR::isError($error = $this->setTimeout($this->timeout))) { |
| |
return $error; |
| |
} |
| } |
} |
| |
|
| if (PEAR::isError($error = $this->_parseResponse(220))) { |
if (PEAR::isError($error = $this->parseResponse(220))) { |
| return $error; |
return $error; |
| } |
} |
| if (PEAR::isError($error = $this->_negotiate())) { |
|
| |
/* Extract and store a copy of the server's greeting string. */ |
| |
list(, $this->greeting) = $this->getResponse(); |
| |
|
| |
if (PEAR::isError($error = $this->negotiate())) { |
| return $error; |
return $error; |
| } |
} |
| |
|
|
|
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access public |
* @since 1.0 |
| * @since 1.0 |
|
| */ |
*/ |
| function disconnect() |
public function disconnect() |
| { |
{ |
| if (PEAR::isError($error = $this->_put('QUIT'))) { |
if (PEAR::isError($error = $this->put('QUIT'))) { |
| return $error; |
return $error; |
| } |
} |
| if (PEAR::isError($error = $this->_parseResponse(221))) { |
if (PEAR::isError($error = $this->parseResponse(221))) { |
| return $error; |
return $error; |
| } |
} |
| if (PEAR::isError($error = $this->_socket->disconnect())) { |
if (PEAR::isError($error = $this->socket->disconnect())) { |
| return PEAR::raiseError('Failed to disconnect socket: ' . |
return PEAR::raiseError( |
| $error->getMessage()); |
'Failed to disconnect socket: ' . $error->getMessage() |
| |
); |
| } |
} |
| |
|
| return true; |
return true; |
|
|
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * |
* |
| * @access private |
* @since 1.1.0 |
| * @since 1.1.0 |
|
| */ |
*/ |
| function _negotiate() |
protected function negotiate() |
| { |
{ |
| if (PEAR::isError($error = $this->_put('EHLO', $this->localhost))) { |
if (PEAR::isError($error = $this->put('EHLO', $this->localhost))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
| if (PEAR::isError($this->_parseResponse(250))) { |
if (PEAR::isError($this->parseResponse(250))) { |
| /* If we receive a 503 response, we're already authenticated. */ |
|
| if ($this->_code === 503) { |
|
| return true; |
|
| } |
|
| |
|
| /* If the EHLO failed, try the simpler HELO command. */ |
/* If the EHLO failed, try the simpler HELO command. */ |
| if (PEAR::isError($error = $this->_put('HELO', $this->localhost))) { |
if (PEAR::isError($error = $this->put('HELO', $this->localhost))) { |
| return $error; |
return $error; |
| } |
} |
| if (PEAR::isError($this->_parseResponse(250))) { |
if (PEAR::isError($this->parseResponse(250))) { |
| return PEAR::raiseError('HELO was not accepted: ', $this->_code); |
return PEAR::raiseError('HELO was not accepted', $this->code); |
| } |
} |
| |
|
| return true; |
return true; |
| } |
} |
| |
|
| foreach ($this->_arguments as $argument) { |
foreach ($this->arguments as $argument) { |
| $verb = strtok($argument, ' '); |
$verb = strtok($argument, ' '); |
| $arguments = substr($argument, strlen($verb) + 1, |
$len = strlen($verb); |
| strlen($argument) - strlen($verb) - 1); |
$arguments = substr($argument, $len + 1, strlen($argument) - $len - 1); |
| $this->_esmtp[$verb] = $arguments; |
$this->esmtp[$verb] = $arguments; |
| |
} |
| |
|
| |
if (!isset($this->esmtp['PIPELINING'])) { |
| |
$this->pipelining = false; |
| } |
} |
| |
|
| return true; |
return true; |
|
|
| * Returns the name of the best authentication method that the server |
* Returns the name of the best authentication method that the server |
| * has advertised. |
* has advertised. |
| * |
* |
| * @return mixed Returns a string containing the name of the best |
* @return mixed Returns a string containing the name of the best |
| * supported authentication method or a PEAR_Error object |
* supported authentication method or a PEAR_Error object |
| * if a failure condition is encountered. |
* if a failure condition is encountered. |
| * @access private |
* @since 1.1.0 |
| * @since 1.1.0 |
|
| */ |
*/ |
| function _getBestAuthMethod() |
protected function getBestAuthMethod() |
| { |
{ |
| $available_methods = explode(' ', $this->_esmtp['AUTH']); |
$available_methods = explode(' ', $this->esmtp['AUTH']); |
| |
|
| foreach ($this->auth_methods as $method) { |
foreach ($this->auth_methods as $method => $callback) { |
| if (in_array($method, $available_methods)) { |
if (in_array($method, $available_methods)) { |
| return $method; |
return $method; |
| } |
} |
|
|
| /** |
/** |
| * Attempt to do SMTP authentication. |
* Attempt to do SMTP authentication. |
| * |
* |
| * @param string The userid to authenticate as. |
* @param string $uid The userid to authenticate as. |
| * @param string The password to authenticate with. |
* @param string $pwd The password to authenticate with. |
| * @param string The requested authentication method. If none is |
* @param string $method The requested authentication method. If none is |
| * specified, the best supported method will be used. |
* specified, the best supported method will be used. |
| |
* @param bool $tls Flag indicating whether or not TLS should be attempted. |
| |
* @param string $authz An optional authorization identifier. If specified, this |
| |
* identifier will be used as the authorization proxy. |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access public |
* @since 1.0 |
| * @since 1.0 |
*/ |
| */ |
public function auth($uid, $pwd , $method = '', $tls = true, $authz = '') |
| function auth($uid, $pwd , $method = '') |
{ |
| { |
/* We can only attempt a TLS connection if one has been requested, |
| if (empty($this->_esmtp['AUTH'])) { |
* we're running PHP 5.1.0 or later, have access to the OpenSSL |
| return PEAR::raiseError('SMTP server does no support authentication'); |
* extension, are connected to an SMTP server which supports the |
| |
* STARTTLS extension, and aren't already connected over a secure |
| |
* (SSL) socket connection. */ |
| |
if ($tls && version_compare(PHP_VERSION, '5.1.0', '>=') |
| |
&& extension_loaded('openssl') && isset($this->esmtp['STARTTLS']) |
| |
&& strncasecmp($this->host, 'ssl://', 6) !== 0 |
| |
) { |
| |
/* Start the TLS connection attempt. */ |
| |
if (PEAR::isError($result = $this->put('STARTTLS'))) { |
| |
return $result; |
| |
} |
| |
if (PEAR::isError($result = $this->parseResponse(220))) { |
| |
return $result; |
| |
} |
| |
if (isset($this->socket_options['ssl']['crypto_method'])) { |
| |
$crypto_method = $this->socket_options['ssl']['crypto_method']; |
| |
} else { |
| |
/* STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT constant does not exist |
| |
* and STREAM_CRYPTO_METHOD_SSLv23_CLIENT constant is |
| |
* inconsistent across PHP versions. */ |
| |
$crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT |
| |
| @STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT |
| |
| @STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; |
| |
} |
| |
if (PEAR::isError($result = $this->socket->enableCrypto(true, $crypto_method))) { |
| |
return $result; |
| |
} elseif ($result !== true) { |
| |
return PEAR::raiseError('STARTTLS failed'); |
| |
} |
| |
|
| |
/* Send EHLO again to recieve the AUTH string from the |
| |
* SMTP server. */ |
| |
$this->negotiate(); |
| |
} |
| |
|
| |
if (empty($this->esmtp['AUTH'])) { |
| |
return PEAR::raiseError('SMTP server does not support authentication'); |
| } |
} |
| |
|
| /* If no method has been specified, get the name of the best |
/* If no method has been specified, get the name of the best |
| * supported method advertised by the SMTP server. */ |
* supported method advertised by the SMTP server. */ |
| if (empty($method)) { |
if (empty($method)) { |
| if (PEAR::isError($method = $this->_getBestAuthMethod())) { |
if (PEAR::isError($method = $this->getBestAuthMethod())) { |
| /* Return the PEAR_Error object from _getBestAuthMethod(). */ |
/* Return the PEAR_Error object from _getBestAuthMethod(). */ |
| return $method; |
return $method; |
| } |
} |
| } else { |
} else { |
| $method = strtoupper($method); |
$method = strtoupper($method); |
| if (!in_array($method, $this->auth_methods)) { |
if (!array_key_exists($method, $this->auth_methods)) { |
| return PEAR::raiseError("$method is not a supported authentication method"); |
return PEAR::raiseError("$method is not a supported authentication method"); |
| } |
} |
| } |
} |
| |
|
| switch ($method) { |
if (!isset($this->auth_methods[$method])) { |
| case 'DIGEST-MD5': |
return PEAR::raiseError("$method is not a supported authentication method"); |
| $result = $this->_authDigest_MD5($uid, $pwd); |
} |
| break; |
|
| case 'CRAM-MD5': |
if (!is_callable($this->auth_methods[$method], false)) { |
| $result = $this->_authCRAM_MD5($uid, $pwd); |
return PEAR::raiseError("$method authentication method cannot be called"); |
| break; |
} |
| case 'LOGIN': |
|
| $result = $this->_authLogin($uid, $pwd); |
if (is_array($this->auth_methods[$method])) { |
| break; |
list($object, $method) = $this->auth_methods[$method]; |
| case 'PLAIN': |
$result = $object->{$method}($uid, $pwd, $authz, $this); |
| $result = $this->_authPlain($uid, $pwd); |
} else { |
| break; |
$func = $this->auth_methods[$method]; |
| default: |
$result = $func($uid, $pwd, $authz, $this); |
| $result = PEAR::raiseError("$method is not a supported authentication method"); |
|
| break; |
|
| } |
} |
| |
|
| /* If an error was encountered, return the PEAR_Error object. */ |
/* If an error was encountered, return the PEAR_Error object. */ |
|
|
| } |
} |
| |
|
| /** |
/** |
| |
* Add a new authentication method. |
| |
* |
| |
* @param string $name The authentication method name (e.g. 'PLAIN') |
| |
* @param mixed $callback The authentication callback (given as the name of a |
| |
* function or as an (object, method name) array). |
| |
* @param bool $prepend Should the new method be prepended to the list of |
| |
* available methods? This is the default behavior, |
| |
* giving the new method the highest priority. |
| |
* |
| |
* @return mixed True on success or a PEAR_Error object on failure. |
| |
* |
| |
* @since 1.6.0 |
| |
*/ |
| |
public function setAuthMethod($name, $callback, $prepend = true) |
| |
{ |
| |
if (!is_string($name)) { |
| |
return PEAR::raiseError('Method name is not a string'); |
| |
} |
| |
|
| |
if (!is_string($callback) && !is_array($callback)) { |
| |
return PEAR::raiseError('Method callback must be string or array'); |
| |
} |
| |
|
| |
if (is_array($callback)) { |
| |
if (!is_object($callback[0]) || !is_string($callback[1])) { |
| |
return PEAR::raiseError('Bad mMethod callback array'); |
| |
} |
| |
} |
| |
|
| |
if ($prepend) { |
| |
$this->auth_methods = array_merge( |
| |
array($name => $callback), $this->auth_methods |
| |
); |
| |
} else { |
| |
$this->auth_methods[$name] = $callback; |
| |
} |
| |
|
| |
return true; |
| |
} |
| |
|
| |
/** |
| * Authenticates the user using the DIGEST-MD5 method. |
* Authenticates the user using the DIGEST-MD5 method. |
| * |
* |
| * @param string The userid to authenticate as. |
* @param string $uid The userid to authenticate as. |
| * @param string The password to authenticate with. |
* @param string $pwd The password to authenticate with. |
| |
* @param string $authz The optional authorization proxy identifier. |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access private |
* @since 1.1.0 |
| * @since 1.1.0 |
|
| */ |
*/ |
| function _authDigest_MD5($uid, $pwd) |
protected function authDigestMD5($uid, $pwd, $authz = '') |
| { |
{ |
| if (PEAR::isError($error = $this->_put('AUTH', 'DIGEST-MD5'))) { |
if (PEAR::isError($error = $this->put('AUTH', 'DIGEST-MD5'))) { |
| return $error; |
return $error; |
| } |
} |
| /* 334: Continue authentication request */ |
/* 334: Continue authentication request */ |
| if (PEAR::isError($error = $this->_parseResponse(334))) { |
if (PEAR::isError($error = $this->parseResponse(334))) { |
| /* 503: Error: already authenticated */ |
/* 503: Error: already authenticated */ |
| if ($this->_code === 503) { |
if ($this->code === 503) { |
| return true; |
return true; |
| } |
} |
| return $error; |
return $error; |
| } |
} |
| |
|
| $challenge = base64_decode($this->_arguments[0]); |
$digest = Auth_SASL::factory('digest-md5'); |
| $digest = &Auth_SASL::factory('digestmd5'); |
$challenge = base64_decode($this->arguments[0]); |
| $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge, |
$auth_str = base64_encode( |
| $this->host, "smtp")); |
$digest->getResponse($uid, $pwd, $challenge, $this->host, "smtp", $authz) |
| |
); |
| |
|
| if (PEAR::isError($error = $this->_put($auth_str))) { |
if (PEAR::isError($error = $this->put($auth_str))) { |
| return $error; |
return $error; |
| } |
} |
| /* 334: Continue authentication request */ |
/* 334: Continue authentication request */ |
| if (PEAR::isError($error = $this->_parseResponse(334))) { |
if (PEAR::isError($error = $this->parseResponse(334))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
| /* We don't use the protocol's third step because SMTP doesn't |
/* We don't use the protocol's third step because SMTP doesn't |
| * allow subsequent authentication, so we just silently ignore |
* allow subsequent authentication, so we just silently ignore |
| * it. */ |
* it. */ |
| if (PEAR::isError($error = $this->_put(' '))) { |
if (PEAR::isError($error = $this->put(''))) { |
| return $error; |
return $error; |
| } |
} |
| /* 235: Authentication successful */ |
/* 235: Authentication successful */ |
| if (PEAR::isError($error = $this->_parseResponse(235))) { |
if (PEAR::isError($error = $this->parseResponse(235))) { |
| return $error; |
return $error; |
| } |
} |
| } |
} |
|
|
| /** |
/** |
| * Authenticates the user using the CRAM-MD5 method. |
* Authenticates the user using the CRAM-MD5 method. |
| * |
* |
| * @param string The userid to authenticate as. |
* @param string $uid The userid to authenticate as. |
| * @param string The password to authenticate with. |
* @param string $pwd The password to authenticate with. |
| |
* @param string $authz The optional authorization proxy identifier. |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access private |
* @since 1.1.0 |
| * @since 1.1.0 |
|
| */ |
*/ |
| function _authCRAM_MD5($uid, $pwd) |
protected function authCRAMMD5($uid, $pwd, $authz = '') |
| { |
{ |
| if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) { |
if (PEAR::isError($error = $this->put('AUTH', 'CRAM-MD5'))) { |
| return $error; |
return $error; |
| } |
} |
| /* 334: Continue authentication request */ |
/* 334: Continue authentication request */ |
| if (PEAR::isError($error = $this->_parseResponse(334))) { |
if (PEAR::isError($error = $this->parseResponse(334))) { |
| /* 503: Error: already authenticated */ |
/* 503: Error: already authenticated */ |
| if ($this->_code === 503) { |
if ($this->code === 503) { |
| return true; |
return true; |
| } |
} |
| return $error; |
return $error; |
| } |
} |
| |
|
| $challenge = base64_decode($this->_arguments[0]); |
$challenge = base64_decode($this->arguments[0]); |
| $cram = &Auth_SASL::factory('crammd5'); |
$cram = Auth_SASL::factory('cram-md5'); |
| $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge)); |
$auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge)); |
| |
|
| if (PEAR::isError($error = $this->_put($auth_str))) { |
if (PEAR::isError($error = $this->put($auth_str))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
| /* 235: Authentication successful */ |
/* 235: Authentication successful */ |
| if (PEAR::isError($error = $this->_parseResponse(235))) { |
if (PEAR::isError($error = $this->parseResponse(235))) { |
| return $error; |
return $error; |
| } |
} |
| } |
} |
|
|
| /** |
/** |
| * Authenticates the user using the LOGIN method. |
* Authenticates the user using the LOGIN method. |
| * |
* |
| * @param string The userid to authenticate as. |
* @param string $uid The userid to authenticate as. |
| * @param string The password to authenticate with. |
* @param string $pwd The password to authenticate with. |
| |
* @param string $authz The optional authorization proxy identifier. |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access private |
* @since 1.1.0 |
| * @since 1.1.0 |
|
| */ |
*/ |
| function _authLogin($uid, $pwd) |
protected function authLogin($uid, $pwd, $authz = '') |
| { |
{ |
| if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) { |
if (PEAR::isError($error = $this->put('AUTH', 'LOGIN'))) { |
| return $error; |
return $error; |
| } |
} |
| /* 334: Continue authentication request */ |
/* 334: Continue authentication request */ |
| if (PEAR::isError($error = $this->_parseResponse(334))) { |
if (PEAR::isError($error = $this->parseResponse(334))) { |
| /* 503: Error: already authenticated */ |
/* 503: Error: already authenticated */ |
| if ($this->_code === 503) { |
if ($this->code === 503) { |
| return true; |
return true; |
| } |
} |
| return $error; |
return $error; |
| } |
} |
| |
|
| if (PEAR::isError($error = $this->_put(base64_encode($uid)))) { |
if (PEAR::isError($error = $this->put(base64_encode($uid)))) { |
| return $error; |
return $error; |
| } |
} |
| /* 334: Continue authentication request */ |
/* 334: Continue authentication request */ |
| if (PEAR::isError($error = $this->_parseResponse(334))) { |
if (PEAR::isError($error = $this->parseResponse(334))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
| if (PEAR::isError($error = $this->_put(base64_encode($pwd)))) { |
if (PEAR::isError($error = $this->put(base64_encode($pwd)))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
| /* 235: Authentication successful */ |
/* 235: Authentication successful */ |
| if (PEAR::isError($error = $this->_parseResponse(235))) { |
if (PEAR::isError($error = $this->parseResponse(235))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
|
|
| /** |
/** |
| * Authenticates the user using the PLAIN method. |
* Authenticates the user using the PLAIN method. |
| * |
* |
| * @param string The userid to authenticate as. |
* @param string $uid The userid to authenticate as. |
| * @param string The password to authenticate with. |
* @param string $pwd The password to authenticate with. |
| |
* @param string $authz The optional authorization proxy identifier. |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access private |
* @since 1.1.0 |
| * @since 1.1.0 |
|
| */ |
*/ |
| function _authPlain($uid, $pwd) |
protected function authPlain($uid, $pwd, $authz = '') |
| { |
{ |
| if (PEAR::isError($error = $this->_put('AUTH', 'PLAIN'))) { |
if (PEAR::isError($error = $this->put('AUTH', 'PLAIN'))) { |
| return $error; |
return $error; |
| } |
} |
| /* 334: Continue authentication request */ |
/* 334: Continue authentication request */ |
| if (PEAR::isError($error = $this->_parseResponse(334))) { |
if (PEAR::isError($error = $this->parseResponse(334))) { |
| /* 503: Error: already authenticated */ |
/* 503: Error: already authenticated */ |
| if ($this->_code === 503) { |
if ($this->code === 503) { |
| return true; |
return true; |
| } |
} |
| return $error; |
return $error; |
| } |
} |
| |
|
| $auth_str = base64_encode(chr(0) . $uid . chr(0) . $pwd); |
$auth_str = base64_encode($authz . chr(0) . $uid . chr(0) . $pwd); |
| |
|
| if (PEAR::isError($error = $this->_put($auth_str))) { |
if (PEAR::isError($error = $this->put($auth_str))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
| /* 235: Authentication successful */ |
/* 235: Authentication successful */ |
| if (PEAR::isError($error = $this->_parseResponse(235))) { |
if (PEAR::isError($error = $this->parseResponse(235))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
|
|
| /** |
/** |
| * Send the HELO command. |
* Send the HELO command. |
| * |
* |
| * @param string The domain name to say we are. |
* @param string $domain The domain name to say we are. |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access public |
* @since 1.0 |
| * @since 1.0 |
|
| */ |
*/ |
| function helo($domain) |
public function helo($domain) |
| { |
{ |
| if (PEAR::isError($error = $this->_put('HELO', $domain))) { |
if (PEAR::isError($error = $this->put('HELO', $domain))) { |
| return $error; |
return $error; |
| } |
} |
| if (PEAR::isError($error = $this->_parseResponse(250))) { |
if (PEAR::isError($error = $this->parseResponse(250))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
|
|
| } |
} |
| |
|
| /** |
/** |
| |
* Return the list of SMTP service extensions advertised by the server. |
| |
* |
| |
* @return array The list of SMTP service extensions. |
| |
* @since 1.3 |
| |
*/ |
| |
public function getServiceExtensions() |
| |
{ |
| |
return $this->esmtp; |
| |
} |
| |
|
| |
/** |
| * Send the MAIL FROM: command. |
* Send the MAIL FROM: command. |
| * |
* |
| * @param string The sender (reverse path) to set. |
* @param string $sender The sender (reverse path) to set. |
| |
* @param string $params String containing additional MAIL parameters, |
| |
* such as the NOTIFY flags defined by RFC 1891 |
| |
* or the VERP protocol. |
| * |
* |
| * @param array optional arguments. Currently supported: |
* If $params is an array, only the 'verp' option |
| * verp boolean or string. If true or string |
* is supported. If 'verp' is true, the XVERP |
| * verp is enabled. If string the characters |
* parameter is appended to the MAIL command. |
| * are considered verp separators. |
* If the 'verp' value is a string, the full |
| |
* XVERP=value parameter is appended. |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access public |
* @since 1.0 |
| * @since 1.0 |
|
| */ |
*/ |
| function mailFrom($sender, $args = array()) |
public function mailFrom($sender, $params = null) |
| { |
{ |
| $argstr = ''; |
$args = "FROM:<$sender>"; |
| |
|
| if (isset($args['verp'])) { |
|
| /* XVERP */ |
|
| if ($args['verp'] === true) { |
|
| $argstr .= ' XVERP'; |
|
| |
|
| /* XVERP=something */ |
/* Support the deprecated array form of $params. */ |
| } elseif (trim($args['verp'])) { |
if (is_array($params) && isset($params['verp'])) { |
| $argstr .= ' XVERP=' . $args['verp']; |
if ($params['verp'] === true) { |
| |
$args .= ' XVERP'; |
| |
} elseif (trim($params['verp'])) { |
| |
$args .= ' XVERP=' . $params['verp']; |
| } |
} |
| |
} elseif (is_string($params) && !empty($params)) { |
| |
$args .= ' ' . $params; |
| } |
} |
| |
|
| if (PEAR::isError($error = $this->_put('MAIL', "FROM:<$sender>$argstr"))) { |
if (PEAR::isError($error = $this->put('MAIL', $args))) { |
| return $error; |
return $error; |
| } |
} |
| if (PEAR::isError($error = $this->_parseResponse(250))) { |
if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
|
|
| /** |
/** |
| * Send the RCPT TO: command. |
* Send the RCPT TO: command. |
| * |
* |
| * @param string The recipient (forward path) to add. |
* @param string $recipient The recipient (forward path) to add. |
| |
* @param string $params String containing additional RCPT parameters, |
| |
* such as the NOTIFY flags defined by RFC 1891. |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access public |
* |
| * @since 1.0 |
* @since 1.0 |
| */ |
*/ |
| function rcptTo($recipient) |
public function rcptTo($recipient, $params = null) |
| { |
{ |
| if (PEAR::isError($error = $this->_put('RCPT', "TO:<$recipient>"))) { |
$args = "TO:<$recipient>"; |
| |
if (is_string($params)) { |
| |
$args .= ' ' . $params; |
| |
} |
| |
|
| |
if (PEAR::isError($error = $this->put('RCPT', $args))) { |
| return $error; |
return $error; |
| } |
} |
| if (PEAR::isError($error = $this->_parseResponse(array(250, 251)))) { |
if (PEAR::isError($error = $this->parseResponse(array(250, 251), $this->pipelining))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
|
|
| * easier overloading for the cases where it is desirable to |
* easier overloading for the cases where it is desirable to |
| * customize the quoting behavior. |
* customize the quoting behavior. |
| * |
* |
| * @param string $data The message text to quote. The string must be passed |
* @param string &$data The message text to quote. The string must be passed |
| * by reference, and the text will be modified in place. |
* by reference, and the text will be modified in place. |
| * |
* |
| * @access public |
* @since 1.2 |
| * @since 1.2 |
|
| */ |
*/ |
| function quotedata(&$data) |
public function quotedata(&$data) |
| { |
{ |
| /* Change Unix (\n) and Mac (\r) linefeeds into |
|
| * Internet-standard CRLF (\r\n) linefeeds. */ |
|
| $data = preg_replace(array('/(?<!\r)\n/','/\r(?!\n)/'), "\r\n", $data); |
|
| |
|
| /* Because a single leading period (.) signifies an end to the |
/* Because a single leading period (.) signifies an end to the |
| * data, legitimate leading periods need to be "doubled" |
* data, legitimate leading periods need to be "doubled" ('..'). */ |
| * (e.g. '..'). */ |
$data = preg_replace('/^\./m', '..', $data); |
| $data = str_replace("\n.", "\n..", $data); |
|
| |
/* Change Unix (\n) and Mac (\r) linefeeds into CRLF's (\r\n). */ |
| |
$data = preg_replace('/(?:\r\n|\n|\r(?!\n))/', "\r\n", $data); |
| } |
} |
| |
|
| /** |
/** |
| * Send the DATA command. |
* Send the DATA command. |
| * |
* |
| * @param string $data The message body to send. |
* @param mixed $data The message data, either as a string or an open |
| |
* file resource. |
| |
* @param string $headers The message headers. If $headers is provided, |
| |
* $data is assumed to contain only body data. |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access public |
* @since 1.0 |
| * @since 1.0 |
|
| */ |
*/ |
| function data($data) |
public function data($data, $headers = null) |
| { |
{ |
| /* RFC 1870, section 3, subsection 3 states "a value of zero |
/* Verify that $data is a supported type. */ |
| * indicates that no fixed maximum message size is in force". |
if (!is_string($data) && !is_resource($data)) { |
| * Furthermore, it says that if "the parameter is omitted no |
return PEAR::raiseError('Expected a string or file resource'); |
| * information is conveyed about the server's fixed maximum |
} |
| * message size". */ |
|
| if (isset($this->_esmtp['SIZE']) && ($this->_esmtp['SIZE'] > 0)) { |
/* Start by considering the size of the optional headers string. We |
| if (strlen($data) >= $this->_esmtp['SIZE']) { |
* also account for the addition 4 character "\r\n\r\n" separator |
| $this->disconnect(); |
* sequence. */ |
| return PEAR::raiseError('Message size excedes the server limit'); |
$size = (is_null($headers)) ? 0 : strlen($headers) + 4; |
| |
|
| |
if (is_resource($data)) { |
| |
$stat = fstat($data); |
| |
if ($stat === false) { |
| |
return PEAR::raiseError('Failed to get file size'); |
| } |
} |
| |
$size += $stat['size']; |
| |
} else { |
| |
$size += strlen($data); |
| } |
} |
| |
|
| /* Quote the data based on the SMTP standards. */ |
/* RFC 1870, section 3, subsection 3 states "a value of zero indicates |
| $this->quotedata($data); |
* that no fixed maximum message size is in force". Furthermore, it |
| |
* says that if "the parameter is omitted no information is conveyed |
| |
* about the server's fixed maximum message size". */ |
| |
$limit = (isset($this->esmtp['SIZE'])) ? $this->esmtp['SIZE'] : 0; |
| |
if ($limit > 0 && $size >= $limit) { |
| |
$this->disconnect(); |
| |
return PEAR::raiseError('Message size exceeds server limit'); |
| |
} |
| |
|
| if (PEAR::isError($error = $this->_put('DATA'))) { |
/* Initiate the DATA command. */ |
| |
if (PEAR::isError($error = $this->put('DATA'))) { |
| return $error; |
return $error; |
| } |
} |
| if (PEAR::isError($error = $this->_parseResponse(354))) { |
if (PEAR::isError($error = $this->parseResponse(354))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
| if (PEAR::isError($result = $this->_send($data . "\r\n.\r\n"))) { |
/* If we have a separate headers string, send it first. */ |
| |
if (!is_null($headers)) { |
| |
$this->quotedata($headers); |
| |
if (PEAR::isError($result = $this->send($headers . "\r\n\r\n"))) { |
| |
return $result; |
| |
} |
| |
} |
| |
|
| |
/* Now we can send the message body data. */ |
| |
if (is_resource($data)) { |
| |
/* Stream the contents of the file resource out over our socket |
| |
* connection, line by line. Each line must be run through the |
| |
* quoting routine. */ |
| |
while (strlen($line = fread($data, 8192)) > 0) { |
| |
/* If the last character is an newline, we need to grab the |
| |
* next character to check to see if it is a period. */ |
| |
while (!feof($data)) { |
| |
$char = fread($data, 1); |
| |
$line .= $char; |
| |
if ($char != "\n") { |
| |
break; |
| |
} |
| |
} |
| |
$this->quotedata($line); |
| |
if (PEAR::isError($result = $this->send($line))) { |
| |
return $result; |
| |
} |
| |
} |
| |
|
| |
$last = $line; |
| |
} else { |
| |
/* |
| |
* Break up the data by sending one chunk (up to 512k) at a time. |
| |
* This approach reduces our peak memory usage. |
| |
*/ |
| |
for ($offset = 0; $offset < $size;) { |
| |
$end = $offset + 512000; |
| |
|
| |
/* |
| |
* Ensure we don't read beyond our data size or span multiple |
| |
* lines. quotedata() can't properly handle character data |
| |
* that's split across two line break boundaries. |
| |
*/ |
| |
if ($end >= $size) { |
| |
$end = $size; |
| |
} else { |
| |
for (; $end < $size; $end++) { |
| |
if ($data[$end] != "\n") { |
| |
break; |
| |
} |
| |
} |
| |
} |
| |
|
| |
/* Extract our chunk and run it through the quoting routine. */ |
| |
$chunk = substr($data, $offset, $end - $offset); |
| |
$this->quotedata($chunk); |
| |
|
| |
/* If we run into a problem along the way, abort. */ |
| |
if (PEAR::isError($result = $this->send($chunk))) { |
| |
return $result; |
| |
} |
| |
|
| |
/* Advance the offset to the end of this chunk. */ |
| |
$offset = $end; |
| |
} |
| |
|
| |
$last = $chunk; |
| |
} |
| |
|
| |
/* Don't add another CRLF sequence if it's already in the data */ |
| |
$terminator = (substr($last, -2) == "\r\n" ? '' : "\r\n") . ".\r\n"; |
| |
|
| |
/* Finally, send the DATA terminator sequence. */ |
| |
if (PEAR::isError($result = $this->send($terminator))) { |
| return $result; |
return $result; |
| } |
} |
| if (PEAR::isError($error = $this->_parseResponse(250))) { |
|
| |
/* Verify that the data was successfully received by the server. */ |
| |
if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
|
|
| /** |
/** |
| * Send the SEND FROM: command. |
* Send the SEND FROM: command. |
| * |
* |
| * @param string The reverse path to send. |
* @param string $path The reverse path to send. |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access public |
* @since 1.2.6 |
| * @since 1.2.6 |
|
| */ |
*/ |
| function sendFrom($path) |
public function sendFrom($path) |
| { |
{ |
| if (PEAR::isError($error = $this->_put('SEND', "FROM:<$path>"))) { |
if (PEAR::isError($error = $this->put('SEND', "FROM:<$path>"))) { |
| return $error; |
return $error; |
| } |
} |
| if (PEAR::isError($error = $this->_parseResponse(250))) { |
if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
|
|
| } |
} |
| |
|
| /** |
/** |
| * Backwards-compatibility wrapper for sendFrom(). |
|
| * |
|
| * @param string The reverse path to send. |
|
| * |
|
| * @return mixed Returns a PEAR_Error with an error message on any |
|
| * kind of failure, or true on success. |
|
| * |
|
| * @access public |
|
| * @since 1.0 |
|
| * @deprecated 1.2.6 |
|
| */ |
|
| function send_from($path) |
|
| { |
|
| return sendFrom($path); |
|
| } |
|
| |
|
| /** |
|
| * Send the SOML FROM: command. |
* Send the SOML FROM: command. |
| * |
* |
| * @param string The reverse path to send. |
* @param string $path The reverse path to send. |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access public |
* @since 1.2.6 |
| * @since 1.2.6 |
|
| */ |
*/ |
| function somlFrom($path) |
public function somlFrom($path) |
| { |
{ |
| if (PEAR::isError($error = $this->_put('SOML', "FROM:<$path>"))) { |
if (PEAR::isError($error = $this->put('SOML', "FROM:<$path>"))) { |
| return $error; |
return $error; |
| } |
} |
| if (PEAR::isError($error = $this->_parseResponse(250))) { |
if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
|
|
| } |
} |
| |
|
| /** |
/** |
| * Backwards-compatibility wrapper for somlFrom(). |
|
| * |
|
| * @param string The reverse path to send. |
|
| * |
|
| * @return mixed Returns a PEAR_Error with an error message on any |
|
| * kind of failure, or true on success. |
|
| * |
|
| * @access public |
|
| * @since 1.0 |
|
| * @deprecated 1.2.6 |
|
| */ |
|
| function soml_from($path) |
|
| { |
|
| return somlFrom($path); |
|
| } |
|
| |
|
| /** |
|
| * Send the SAML FROM: command. |
* Send the SAML FROM: command. |
| * |
* |
| * @param string The reverse path to send. |
* @param string $path The reverse path to send. |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access public |
* @since 1.2.6 |
| * @since 1.2.6 |
|
| */ |
*/ |
| function samlFrom($path) |
public function samlFrom($path) |
| { |
{ |
| if (PEAR::isError($error = $this->_put('SAML', "FROM:<$path>"))) { |
if (PEAR::isError($error = $this->put('SAML', "FROM:<$path>"))) { |
| return $error; |
return $error; |
| } |
} |
| if (PEAR::isError($error = $this->_parseResponse(250))) { |
if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
|
|
| } |
} |
| |
|
| /** |
/** |
| * Backwards-compatibility wrapper for samlFrom(). |
|
| * |
|
| * @param string The reverse path to send. |
|
| * |
|
| * @return mixed Returns a PEAR_Error with an error message on any |
|
| * kind of failure, or true on success. |
|
| * |
|
| * @access public |
|
| * @since 1.0 |
|
| * @deprecated 1.2.6 |
|
| */ |
|
| function saml_from($path) |
|
| { |
|
| return samlFrom($path); |
|
| } |
|
| |
|
| /** |
|
| * Send the RSET command. |
* Send the RSET command. |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access public |
|
| * @since 1.0 |
* @since 1.0 |
| */ |
*/ |
| function rset() |
public function rset() |
| { |
{ |
| if (PEAR::isError($error = $this->_put('RSET'))) { |
if (PEAR::isError($error = $this->put('RSET'))) { |
| return $error; |
return $error; |
| } |
} |
| if (PEAR::isError($error = $this->_parseResponse(250))) { |
if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
|
|
| /** |
/** |
| * Send the VRFY command. |
* Send the VRFY command. |
| * |
* |
| * @param string The string to verify |
* @param string $string The string to verify |
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access public |
* @since 1.0 |
| * @since 1.0 |
|
| */ |
*/ |
| function vrfy($string) |
public function vrfy($string) |
| { |
{ |
| /* Note: 251 is also a valid response code */ |
/* Note: 251 is also a valid response code */ |
| if (PEAR::isError($error = $this->_put('VRFY', $string))) { |
if (PEAR::isError($error = $this->put('VRFY', $string))) { |
| return $error; |
return $error; |
| } |
} |
| if (PEAR::isError($error = $this->_parseResponse(array(250, 252)))) { |
if (PEAR::isError($error = $this->parseResponse(array(250, 252)))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
|
|
| * |
* |
| * @return mixed Returns a PEAR_Error with an error message on any |
* @return mixed Returns a PEAR_Error with an error message on any |
| * kind of failure, or true on success. |
* kind of failure, or true on success. |
| * @access public |
* @since 1.0 |
| * @since 1.0 |
|
| */ |
*/ |
| function noop() |
public function noop() |
| { |
{ |
| if (PEAR::isError($error = $this->_put('NOOP'))) { |
if (PEAR::isError($error = $this->put('NOOP'))) { |
| return $error; |
return $error; |
| } |
} |
| if (PEAR::isError($error = $this->_parseResponse(250))) { |
if (PEAR::isError($error = $this->parseResponse(250))) { |
| return $error; |
return $error; |
| } |
} |
| |
|
|
|
| * Backwards-compatibility method. identifySender()'s functionality is |
* Backwards-compatibility method. identifySender()'s functionality is |
| * now handled internally. |
* now handled internally. |
| * |
* |
| * @return boolean This method always return true. |
* @return boolean This method always return true. |
| * |
* |
| * @access public |
* @since 1.0 |
| * @since 1.0 |
|
| */ |
*/ |
| function identifySender() |
public function identifySender() |
| { |
{ |
| return true; |
return true; |
| } |
} |
| |
|
| } |
} |