Source for file SMTP.php
Documentation is available at SMTP.php
/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | 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 |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Chuck Hagenbuch <chuck@horde.org> |
// | Jon Parise <jon@php.net> |
// | Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar> |
// +----------------------------------------------------------------------+
// $Id: SMTP.php,v 1.64 2008/12/20 23:03:49 jon Exp $
require_once 'Net/Socket.php';
* Provides an implementation of the SMTP protocol using PEAR's
* @author Chuck Hagenbuch <chuck@horde.org>
* @author Jon Parise <jon@php.net>
* @author Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
* @example basic.php A basic implementation of the Net_SMTP package.
* The server to connect to.
* The port to connect to.
* The value to give when sending EHLO or HELO.
* List of supported authentication methods, in preferential order.
var $auth_methods = array ('DIGEST-MD5', 'CRAM-MD5', 'LOGIN', 'PLAIN');
* Use SMTP command pipelining (specified in RFC 2920) if the SMTP
* When pipeling is enabled, rcptTo(), mailFrom(), sendFrom(),
* somlFrom() and samlFrom() do not wait for a response from the
* SMTP server but return immediately.
* Number of pipelined commands.
var $_pipelined_commands = 0;
* Should debugging output be enabled?
* The socket resource being used to connect to the SMTP server.
* The most recent server response code.
* The most recent server response arguments.
var $_arguments = array ();
* Stores detected features of the SMTP server.
* Instantiates a new Net_SMTP object, overriding any defaults
* with parameters that are passed in.
* If you have SSL support in PHP, you can connect to a server
* over SSL using an 'ssl://' prefix:
* // 465 is a common smtps port.
* $smtp = new Net_SMTP('ssl://mail.host.com', 465);
* @param string $host The server to connect to.
* @param integer $port The port to connect to.
* @param string $localhost The value to give when sending EHLO or HELO.
* @param boolean $pipeling Use SMTP command pipelining
function Net_SMTP($host = null , $port = null , $localhost = null , $pipelining = false )
$this->_socket = new Net_Socket ();
/* Include the Auth_SASL package. If the package is not
* available, we disable the authentication methods that
if ((@include_once 'Auth/SASL.php') === false ) {
* Set the value of the debugging flag.
* @param boolean $debug New value for the debugging flag.
* Send the given string of data to the server.
* @param string $data The string of data to send.
* @return mixed True on success or a PEAR_Error object on failure.
echo " DEBUG: Send: $data\n";
if (PEAR ::isError ($error = $this->_socket->write ($data))) {
return PEAR ::raiseError ('Failed to write to socket: ' .
* Send a command to the server with an optional string of
* arguments. A carriage return / linefeed (CRLF) sequence will
* be appended to each command string before it is sent to the
* SMTP server - an error will be thrown if the command string
* already contains any newline characters. Use _send() for
* commands that must contain newlines.
* @param string $command The SMTP command to send to the server.
* @param string $args A string of optional arguments to append
* @return mixed The result of the _send() call.
function _put ($command, $args = '')
return PEAR ::raiseError ('Commands cannot contain newlines');
return $this->_send ($command . "\r\n");
* Read a reply from the SMTP server. The reply consists of a response
* code and a response message.
* @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.
* @param bool $later Do not parse the response now, but wait
* until the last command in the pipelined
* @return mixed True if the server returned a valid response code or
* a PEAR_Error object is an error condition is reached.
function _parseResponse ($valid, $later = false )
$this->_arguments = array ();
$this->_pipelined_commands++;
for ($i = 0; $i <= $this->_pipelined_commands; $i++ ) {
while ($line = $this->_socket->readLine ()) {
echo " DEBUG: Recv: $line\n";
/* If we receive an empty line, the connection has been closed. */
return PEAR ::raiseError ('Connection was unexpectedly closed');
/* Read the code and store the rest in the arguments array. */
/* Check the syntax of the response code. */
$this->_code = (int) $code;
/* If this is not a multiline response, we're done. */
if (substr($line, 3 , 1 ) != '-') {
$this->_pipelined_commands = 0;
/* Compare the server's response code with the valid code/codes. */
if (is_int($valid) && ($this->_code === $valid)) {
return PEAR ::raiseError ('Invalid response code received from server',
* Return a 2-tuple containing the last response from the SMTP server.
* @return array A two-element array: the first element contains the
* response code as an integer and the second element
* contains the response's arguments as a string.
return array ($this->_code, join("\n", $this->_arguments));
* Attempt to connect to the SMTP server.
* @param int $timeout The timeout value (in seconds) for the
* @param bool $persistent Should a persistent socket connection
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
function connect($timeout = null , $persistent = false )
$result = $this->_socket->connect ($this->host, $this->port,
if (PEAR ::isError ($result)) {
return PEAR ::raiseError ('Failed to connect socket: ' .
if (PEAR ::isError ($error = $this->_parseResponse (220 ))) {
if (PEAR ::isError ($error = $this->_negotiate ())) {
* Attempt to disconnect from the SMTP server.
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
if (PEAR ::isError ($error = $this->_put ('QUIT'))) {
if (PEAR ::isError ($error = $this->_parseResponse (221 ))) {
if (PEAR ::isError ($error = $this->_socket->disconnect ())) {
return PEAR ::raiseError ('Failed to disconnect socket: ' .
* Attempt to send the EHLO command and obtain a list of ESMTP
* extensions available, and failing that just send HELO.
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
if (PEAR ::isError ($error = $this->_put ('EHLO', $this->localhost))) {
if (PEAR ::isError ($this->_parseResponse (250 ))) {
/* If we receive a 503 response, we're already authenticated. */
if ($this->_code === 503 ) {
/* If the EHLO failed, try the simpler HELO command. */
if (PEAR ::isError ($error = $this->_put ('HELO', $this->localhost))) {
if (PEAR ::isError ($this->_parseResponse (250 ))) {
return PEAR ::raiseError ('HELO was not accepted: ', $this->_code);
foreach ($this->_arguments as $argument) {
$verb = strtok($argument, ' ');
$this->_esmtp[$verb] = $arguments;
if (!isset ($this->_esmtp['PIPELINING'])) {
* Returns the name of the best authentication method that the server
* @return mixed Returns a string containing the name of the best
* supported authentication method or a PEAR_Error object
* if a failure condition is encountered.
function _getBestAuthMethod ()
$available_methods = explode(' ', $this->_esmtp['AUTH']);
if (in_array($method, $available_methods)) {
return PEAR ::raiseError ('No supported authentication methods');
* Attempt to do SMTP authentication.
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
* @param string The requested authentication method. If none is
* specified, the best supported method will be used.
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
function auth($uid, $pwd , $method = '')
if (version_compare(PHP_VERSION , '5.1.0', '>=') && isset ($this->_esmtp['STARTTLS'])) {
if (PEAR ::isError ($result = $this->_put ('STARTTLS'))) {
if (PEAR ::isError ($result = $this->_parseResponse (220 ))) {
if (PEAR ::isError ($result = $this->_socket->enableCrypto (true , STREAM_CRYPTO_METHOD_TLS_CLIENT ))) {
} elseif ($result !== true ) {
return PEAR ::raiseError ('STARTTLS failed');
/* Send EHLO again to recieve the AUTH string from the
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
* supported method advertised by the SMTP server. */
if (PEAR ::isError ($method = $this->_getBestAuthMethod ())) {
/* Return the PEAR_Error object from _getBestAuthMethod(). */
return PEAR ::raiseError (" $method is not a supported authentication method" );
$result = $this->_authDigest_MD5 ($uid, $pwd);
$result = $this->_authCRAM_MD5 ($uid, $pwd);
$result = $this->_authLogin ($uid, $pwd);
$result = $this->_authPlain ($uid, $pwd);
$result = PEAR ::raiseError (" $method is not a supported authentication method" );
/* If an error was encountered, return the PEAR_Error object. */
if (PEAR ::isError ($result)) {
* Authenticates the user using the DIGEST-MD5 method.
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
function _authDigest_MD5 ($uid, $pwd)
if (PEAR ::isError ($error = $this->_put ('AUTH', 'DIGEST-MD5'))) {
/* 334: Continue authentication request */
if (PEAR ::isError ($error = $this->_parseResponse (334 ))) {
/* 503: Error: already authenticated */
if ($this->_code === 503 ) {
$digest = &Auth_SASL ::factory ('digestmd5');
$auth_str = base64_encode($digest->getResponse ($uid, $pwd, $challenge,
if (PEAR ::isError ($error = $this->_put ($auth_str))) {
/* 334: Continue authentication request */
if (PEAR ::isError ($error = $this->_parseResponse (334 ))) {
/* We don't use the protocol's third step because SMTP doesn't
* allow subsequent authentication, so we just silently ignore
if (PEAR ::isError ($error = $this->_put (''))) {
/* 235: Authentication successful */
if (PEAR ::isError ($error = $this->_parseResponse (235 ))) {
* Authenticates the user using the CRAM-MD5 method.
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
function _authCRAM_MD5 ($uid, $pwd)
if (PEAR ::isError ($error = $this->_put ('AUTH', 'CRAM-MD5'))) {
/* 334: Continue authentication request */
if (PEAR ::isError ($error = $this->_parseResponse (334 ))) {
/* 503: Error: already authenticated */
if ($this->_code === 503 ) {
$cram = &Auth_SASL ::factory ('crammd5');
$auth_str = base64_encode($cram->getResponse ($uid, $pwd, $challenge));
if (PEAR ::isError ($error = $this->_put ($auth_str))) {
/* 235: Authentication successful */
if (PEAR ::isError ($error = $this->_parseResponse (235 ))) {
* Authenticates the user using the LOGIN method.
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
function _authLogin ($uid, $pwd)
if (PEAR ::isError ($error = $this->_put ('AUTH', 'LOGIN'))) {
/* 334: Continue authentication request */
if (PEAR ::isError ($error = $this->_parseResponse (334 ))) {
/* 503: Error: already authenticated */
if ($this->_code === 503 ) {
/* 334: Continue authentication request */
if (PEAR ::isError ($error = $this->_parseResponse (334 ))) {
/* 235: Authentication successful */
if (PEAR ::isError ($error = $this->_parseResponse (235 ))) {
* Authenticates the user using the PLAIN method.
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
function _authPlain ($uid, $pwd)
if (PEAR ::isError ($error = $this->_put ('AUTH', 'PLAIN'))) {
/* 334: Continue authentication request */
if (PEAR ::isError ($error = $this->_parseResponse (334 ))) {
/* 503: Error: already authenticated */
if ($this->_code === 503 ) {
if (PEAR ::isError ($error = $this->_put ($auth_str))) {
/* 235: Authentication successful */
if (PEAR ::isError ($error = $this->_parseResponse (235 ))) {
* @param string The domain name to say we are.
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
if (PEAR ::isError ($error = $this->_put ('HELO', $domain))) {
if (PEAR ::isError ($error = $this->_parseResponse (250 ))) {
* Return the list of SMTP service extensions advertised by the server.
* @return array The list of SMTP service extensions.
* Send the MAIL FROM: command.
* @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
* If $params is an array, only the 'verp' option
* is supported. If 'verp' is true, the XVERP
* parameter is appended to the MAIL command. 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
* kind of failure, or true on success.
function mailFrom($sender, $params = null )
$args = " FROM:<$sender>";
/* Support the deprecated array form of $params. */
if (is_array($params) && isset ($params['verp'])) {
if ($params['verp'] === true ) {
} elseif (trim($params['verp'])) {
$args .= ' XVERP=' . $params['verp'];
if (PEAR ::isError ($error = $this->_put ('MAIL', $args))) {
if (PEAR ::isError ($error = $this->_parseResponse (250 , $this->pipelining))) {
* Send the RCPT TO: command.
* @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
* kind of failure, or true on success.
function rcptTo($recipient, $params = null )
$args = " TO:<$recipient>";
if (PEAR ::isError ($error = $this->_put ('RCPT', $args))) {
if (PEAR ::isError ($error = $this->_parseResponse (array (250 , 251 ), $this->pipelining))) {
* Quote the data so that it meets SMTP standards.
* This is provided as a separate public function to facilitate
* easier overloading for the cases where it is desirable to
* customize the quoting behavior.
* @param string $data The message text to quote. The string must be passed
* by reference, and the text will be modified in place.
/* 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
* data, legitimate leading periods need to be "doubled"
* @param string $data The message body to send.
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
/* RFC 1870, section 3, subsection 3 states "a value of zero
* indicates 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
if (isset ($this->_esmtp['SIZE']) && ($this->_esmtp['SIZE'] > 0 )) {
if (strlen($data) >= $this->_esmtp['SIZE']) {
return PEAR ::raiseError ('Message size excedes the server limit');
/* Quote the data based on the SMTP standards. */
if (PEAR ::isError ($error = $this->_put ('DATA'))) {
if (PEAR ::isError ($error = $this->_parseResponse (354 ))) {
if (PEAR ::isError ($result = $this->_send ($data . "\r\n.\r\n"))) {
if (PEAR ::isError ($error = $this->_parseResponse (250 , $this->pipelining))) {
* Send the SEND FROM: command.
* @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.
if (PEAR ::isError ($error = $this->_put ('SEND', " FROM:<$path>" ))) {
if (PEAR ::isError ($error = $this->_parseResponse (250 , $this->pipelining))) {
* 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.
* Send the SOML FROM: command.
* @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.
if (PEAR ::isError ($error = $this->_put ('SOML', " FROM:<$path>" ))) {
if (PEAR ::isError ($error = $this->_parseResponse (250 , $this->pipelining))) {
* 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.
* Send the SAML FROM: command.
* @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.
if (PEAR ::isError ($error = $this->_put ('SAML', " FROM:<$path>" ))) {
if (PEAR ::isError ($error = $this->_parseResponse (250 , $this->pipelining))) {
* 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.
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
if (PEAR ::isError ($error = $this->_put ('RSET'))) {
if (PEAR ::isError ($error = $this->_parseResponse (250 , $this->pipelining))) {
* @param string The string to verify
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
/* Note: 251 is also a valid response code */
if (PEAR ::isError ($error = $this->_put ('VRFY', $string))) {
if (PEAR ::isError ($error = $this->_parseResponse (array (250 , 252 )))) {
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
if (PEAR ::isError ($error = $this->_put ('NOOP'))) {
if (PEAR ::isError ($error = $this->_parseResponse (250 ))) {
* Backwards-compatibility method. identifySender()'s functionality is
* now handled internally.
* @return boolean This method always return true.
Documentation generated on Mon, 11 Mar 2019 15:27:59 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|