2 /** vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
3 // +----------------------------------------------------------------------+
4 // | PHP Version 5 and 7 |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2017 Jon Parise and Chuck Hagenbuch |
7 // | All rights reserved. |
9 // | Redistribution and use in source and binary forms, with or without |
10 // | modification, are permitted provided that the following conditions |
13 // | 1. Redistributions of source code must retain the above copyright |
14 // | notice, this list of conditions and the following disclaimer. |
16 // | 2. Redistributions in binary form must reproduce the above copyright |
17 // | notice, this list of conditions and the following disclaimer in |
18 // | the documentation and/or other materials provided with the |
21 // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
22 // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
23 // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
24 // | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
25 // | COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
26 // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
27 // | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
28 // | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
29 // | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
30 // | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
31 // | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
32 // | POSSIBILITY OF SUCH DAMAGE. |
33 // +----------------------------------------------------------------------+
34 // | Authors: Chuck Hagenbuch <chuck@horde.org> |
35 // | Jon Parise <jon@php.net> |
36 // | Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar> |
37 // +----------------------------------------------------------------------+
39 require_once 'PEAR.php';
40 require_once 'Net/Socket.php';
43 * Provides an implementation of the SMTP protocol using PEAR's
47 * @author Chuck Hagenbuch <chuck@horde.org>
48 * @author Jon Parise <jon@php.net>
49 * @author Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
50 * @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause
52 * @example basic.php A basic implementation of the Net_SMTP package.
57 * The server to connect to.
60 public $host = 'localhost';
63 * The port to connect to.
69 * The value to give when sending EHLO or HELO.
72 public $localhost = 'localhost';
75 * List of supported authentication methods, in preferential order.
78 public $auth_methods = array();
81 * Use SMTP command pipelining (specified in RFC 2920) if the SMTP
84 * When pipeling is enabled, rcptTo(), mailFrom(), sendFrom(),
85 * somlFrom() and samlFrom() do not wait for a response from the
86 * SMTP server but return immediately.
90 public $pipelining = false;
93 * Number of pipelined commands.
96 protected $pipelined_commands = 0;
99 * Should debugging output be enabled?
102 protected $debug = false;
105 * Debug output handler.
108 protected $debug_handler = null;
111 * The socket resource being used to connect to the SMTP server.
114 protected $socket = null;
117 * Array of socket options that will be passed to Net_Socket::connect().
118 * @see stream_context_create()
121 protected $socket_options = null;
124 * The socket I/O timeout value in seconds.
127 protected $timeout = 0;
130 * The most recent server response code.
133 protected $code = -1;
136 * The most recent server response arguments.
139 protected $arguments = array();
142 * Stores the SMTP server's greeting string.
145 protected $greeting = null;
148 * Stores detected features of the SMTP server.
151 protected $esmtp = array();
154 * Instantiates a new Net_SMTP object, overriding any defaults
155 * with parameters that are passed in.
157 * If you have SSL support in PHP, you can connect to a server
158 * over SSL using an 'ssl://' prefix:
160 * // 465 is a common smtps port.
161 * $smtp = new Net_SMTP('ssl://mail.host.com', 465);
164 * @param string $host The server to connect to.
165 * @param integer $port The port to connect to.
166 * @param string $localhost The value to give when sending EHLO or HELO.
167 * @param boolean $pipelining Use SMTP command pipelining
168 * @param integer $timeout Socket I/O timeout in seconds.
169 * @param array $socket_options Socket stream_context_create() options.
173 public function __construct($host = null, $port = null, $localhost = null,
174 $pipelining = false, $timeout = 0, $socket_options = null
182 if (isset($localhost)) {
183 $this->localhost = $localhost;
186 $this->pipelining = $pipelining;
187 $this->socket = new Net_Socket();
188 $this->socket_options = $socket_options;
189 $this->timeout = $timeout;
191 /* Include the Auth_SASL package. If the package is available, we
192 * enable the authentication methods that depend upon it. */
193 if (@include_once 'Auth/SASL.php') {
194 $this->setAuthMethod('CRAM-MD5', array($this, 'authCramMD5'));
195 $this->setAuthMethod('DIGEST-MD5', array($this, 'authDigestMD5'));
198 /* These standard authentication methods are always available. */
199 $this->setAuthMethod('LOGIN', array($this, 'authLogin'), false);
200 $this->setAuthMethod('PLAIN', array($this, 'authPlain'), false);
204 * Set the socket I/O timeout value in seconds plus microseconds.
206 * @param integer $seconds Timeout value in seconds.
207 * @param integer $microseconds Additional value in microseconds.
211 public function setTimeout($seconds, $microseconds = 0)
213 return $this->socket->setTimeout($seconds, $microseconds);
217 * Set the value of the debugging flag.
219 * @param boolean $debug New value for the debugging flag.
220 * @param callback $handler Debug handler callback
224 public function setDebug($debug, $handler = null)
226 $this->debug = $debug;
227 $this->debug_handler = $handler;
231 * Write the given debug text to the current debug output handler.
233 * @param string $message Debug mesage text.
237 protected function debug($message)
240 if ($this->debug_handler) {
241 call_user_func_array(
242 $this->debug_handler, array(&$this, $message)
245 echo "DEBUG: $message\n";
251 * Send the given string of data to the server.
253 * @param string $data The string of data to send.
255 * @return mixed The number of bytes that were actually written,
256 * or a PEAR_Error object on failure.
260 protected function send($data)
262 $this->debug("Send: $data");
264 $result = $this->socket->write($data);
265 if (!$result || PEAR::isError($result)) {
266 $msg = $result ? $result->getMessage() : "unknown error";
267 return PEAR::raiseError("Failed to write to socket: $msg");
274 * Send a command to the server with an optional string of
275 * arguments. A carriage return / linefeed (CRLF) sequence will
276 * be appended to each command string before it is sent to the
277 * SMTP server - an error will be thrown if the command string
278 * already contains any newline characters. Use send() for
279 * commands that must contain newlines.
281 * @param string $command The SMTP command to send to the server.
282 * @param string $args A string of optional arguments to append
285 * @return mixed The result of the send() call.
289 protected function put($command, $args = '')
292 $command .= ' ' . $args;
295 if (strcspn($command, "\r\n") !== strlen($command)) {
296 return PEAR::raiseError('Commands cannot contain newlines');
299 return $this->send($command . "\r\n");
303 * Read a reply from the SMTP server. The reply consists of a response
304 * code and a response message.
306 * @param mixed $valid The set of valid response codes. These
307 * may be specified as an array of integer
308 * values or as a single integer value.
309 * @param bool $later Do not parse the response now, but wait
310 * until the last command in the pipelined
313 * @return mixed True if the server returned a valid response code or
314 * a PEAR_Error object is an error condition is reached.
320 protected function parseResponse($valid, $later = false)
323 $this->arguments = array();
326 $this->pipelined_commands++;
330 for ($i = 0; $i <= $this->pipelined_commands; $i++) {
331 while ($line = $this->socket->readLine()) {
332 $this->debug("Recv: $line");
334 /* If we receive an empty line, the connection was closed. */
337 return PEAR::raiseError('Connection was closed');
340 /* Read the code and store the rest in the arguments array. */
341 $code = substr($line, 0, 3);
342 $this->arguments[] = trim(substr($line, 4));
344 /* Check the syntax of the response code. */
345 if (is_numeric($code)) {
346 $this->code = (int)$code;
352 /* If this is not a multiline response, we're done. */
353 if (substr($line, 3, 1) != '-') {
359 $this->pipelined_commands = 0;
361 /* Compare the server's response code with the valid code/codes. */
362 if (is_int($valid) && ($this->code === $valid)) {
364 } elseif (is_array($valid) && in_array($this->code, $valid, true)) {
368 return PEAR::raiseError('Invalid response code received from server', $this->code);
372 * Issue an SMTP command and verify its response.
374 * @param string $command The SMTP command string or data.
375 * @param mixed $valid The set of valid response codes. These
376 * may be specified as an array of integer
377 * values or as a single integer value.
379 * @return mixed True on success or a PEAR_Error object on failure.
383 public function command($command, $valid)
385 if (PEAR::isError($error = $this->put($command))) {
388 if (PEAR::isError($error = $this->parseResponse($valid))) {
396 * Return a 2-tuple containing the last response from the SMTP server.
398 * @return array A two-element array: the first element contains the
399 * response code as an integer and the second element
400 * contains the response's arguments as a string.
404 public function getResponse()
406 return array($this->code, join("\n", $this->arguments));
410 * Return the SMTP server's greeting string.
412 * @return string A string containing the greeting string, or null if
413 * a greeting has not been received.
417 public function getGreeting()
419 return $this->greeting;
423 * Attempt to connect to the SMTP server.
425 * @param int $timeout The timeout value (in seconds) for the
426 * socket connection attempt.
427 * @param bool $persistent Should a persistent socket connection
430 * @return mixed Returns a PEAR_Error with an error message on any
431 * kind of failure, or true on success.
434 public function connect($timeout = null, $persistent = false)
436 $this->greeting = null;
438 $result = $this->socket->connect(
439 $this->host, $this->port, $persistent, $timeout, $this->socket_options
442 if (PEAR::isError($result)) {
443 return PEAR::raiseError(
444 'Failed to connect socket: ' . $result->getMessage()
449 * Now that we're connected, reset the socket's timeout value for
450 * future I/O operations. This allows us to have different socket
451 * timeout values for the initial connection (our $timeout parameter)
452 * and all other socket operations.
454 if ($this->timeout > 0) {
455 if (PEAR::isError($error = $this->setTimeout($this->timeout))) {
460 if (PEAR::isError($error = $this->parseResponse(220))) {
464 /* Extract and store a copy of the server's greeting string. */
465 list(, $this->greeting) = $this->getResponse();
467 if (PEAR::isError($error = $this->negotiate())) {
475 * Attempt to disconnect from the SMTP server.
477 * @return mixed Returns a PEAR_Error with an error message on any
478 * kind of failure, or true on success.
481 public function disconnect()
483 if (PEAR::isError($error = $this->put('QUIT'))) {
486 if (PEAR::isError($error = $this->parseResponse(221))) {
489 if (PEAR::isError($error = $this->socket->disconnect())) {
490 return PEAR::raiseError(
491 'Failed to disconnect socket: ' . $error->getMessage()
499 * Attempt to send the EHLO command and obtain a list of ESMTP
500 * extensions available, and failing that just send HELO.
502 * @return mixed Returns a PEAR_Error with an error message on any
503 * kind of failure, or true on success.
507 protected function negotiate()
509 if (PEAR::isError($error = $this->put('EHLO', $this->localhost))) {
513 if (PEAR::isError($this->parseResponse(250))) {
514 /* If the EHLO failed, try the simpler HELO command. */
515 if (PEAR::isError($error = $this->put('HELO', $this->localhost))) {
518 if (PEAR::isError($this->parseResponse(250))) {
519 return PEAR::raiseError('HELO was not accepted', $this->code);
525 foreach ($this->arguments as $argument) {
526 $verb = strtok($argument, ' ');
527 $len = strlen($verb);
528 $arguments = substr($argument, $len + 1, strlen($argument) - $len - 1);
529 $this->esmtp[$verb] = $arguments;
532 if (!isset($this->esmtp['PIPELINING'])) {
533 $this->pipelining = false;
540 * Returns the name of the best authentication method that the server
543 * @return mixed Returns a string containing the name of the best
544 * supported authentication method or a PEAR_Error object
545 * if a failure condition is encountered.
548 protected function getBestAuthMethod()
550 $available_methods = explode(' ', $this->esmtp['AUTH']);
552 foreach ($this->auth_methods as $method => $callback) {
553 if (in_array($method, $available_methods)) {
558 return PEAR::raiseError('No supported authentication methods');
562 * Attempt to do SMTP authentication.
564 * @param string $uid The userid to authenticate as.
565 * @param string $pwd The password to authenticate with.
566 * @param string $method The requested authentication method. If none is
567 * specified, the best supported method will be used.
568 * @param bool $tls Flag indicating whether or not TLS should be attempted.
569 * @param string $authz An optional authorization identifier. If specified, this
570 * identifier will be used as the authorization proxy.
572 * @return mixed Returns a PEAR_Error with an error message on any
573 * kind of failure, or true on success.
576 public function auth($uid, $pwd , $method = '', $tls = true, $authz = '')
578 /* We can only attempt a TLS connection if one has been requested,
579 * we're running PHP 5.1.0 or later, have access to the OpenSSL
580 * extension, are connected to an SMTP server which supports the
581 * STARTTLS extension, and aren't already connected over a secure
582 * (SSL) socket connection. */
583 if ($tls && version_compare(PHP_VERSION, '5.1.0', '>=')
584 && extension_loaded('openssl') && isset($this->esmtp['STARTTLS'])
585 && strncasecmp($this->host, 'ssl://', 6) !== 0
587 /* Start the TLS connection attempt. */
588 if (PEAR::isError($result = $this->put('STARTTLS'))) {
591 if (PEAR::isError($result = $this->parseResponse(220))) {
594 if (isset($this->socket_options['ssl']['crypto_method'])) {
595 $crypto_method = $this->socket_options['ssl']['crypto_method'];
597 /* STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT constant does not exist
598 * and STREAM_CRYPTO_METHOD_SSLv23_CLIENT constant is
599 * inconsistent across PHP versions. */
600 $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT
601 | @STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
602 | @STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
604 if (PEAR::isError($result = $this->socket->enableCrypto(true, $crypto_method))) {
606 } elseif ($result !== true) {
607 return PEAR::raiseError('STARTTLS failed');
610 /* Send EHLO again to recieve the AUTH string from the
615 if (empty($this->esmtp['AUTH'])) {
616 return PEAR::raiseError('SMTP server does not support authentication');
619 /* If no method has been specified, get the name of the best
620 * supported method advertised by the SMTP server. */
621 if (empty($method)) {
622 if (PEAR::isError($method = $this->getBestAuthMethod())) {
623 /* Return the PEAR_Error object from _getBestAuthMethod(). */
627 $method = strtoupper($method);
628 if (!array_key_exists($method, $this->auth_methods)) {
629 return PEAR::raiseError("$method is not a supported authentication method");
633 if (!isset($this->auth_methods[$method])) {
634 return PEAR::raiseError("$method is not a supported authentication method");
637 if (!is_callable($this->auth_methods[$method], false)) {
638 return PEAR::raiseError("$method authentication method cannot be called");
641 if (is_array($this->auth_methods[$method])) {
642 list($object, $method) = $this->auth_methods[$method];
643 $result = $object->{$method}($uid, $pwd, $authz, $this);
645 $func = $this->auth_methods[$method];
646 $result = $func($uid, $pwd, $authz, $this);
649 /* If an error was encountered, return the PEAR_Error object. */
650 if (PEAR::isError($result)) {
658 * Add a new authentication method.
660 * @param string $name The authentication method name (e.g. 'PLAIN')
661 * @param mixed $callback The authentication callback (given as the name of a
662 * function or as an (object, method name) array).
663 * @param bool $prepend Should the new method be prepended to the list of
664 * available methods? This is the default behavior,
665 * giving the new method the highest priority.
667 * @return mixed True on success or a PEAR_Error object on failure.
671 public function setAuthMethod($name, $callback, $prepend = true)
673 if (!is_string($name)) {
674 return PEAR::raiseError('Method name is not a string');
677 if (!is_string($callback) && !is_array($callback)) {
678 return PEAR::raiseError('Method callback must be string or array');
681 if (is_array($callback)) {
682 if (!is_object($callback[0]) || !is_string($callback[1])) {
683 return PEAR::raiseError('Bad mMethod callback array');
688 $this->auth_methods = array_merge(
689 array($name => $callback), $this->auth_methods
692 $this->auth_methods[$name] = $callback;
699 * Authenticates the user using the DIGEST-MD5 method.
701 * @param string $uid The userid to authenticate as.
702 * @param string $pwd The password to authenticate with.
703 * @param string $authz The optional authorization proxy identifier.
705 * @return mixed Returns a PEAR_Error with an error message on any
706 * kind of failure, or true on success.
709 protected function authDigestMD5($uid, $pwd, $authz = '')
711 if (PEAR::isError($error = $this->put('AUTH', 'DIGEST-MD5'))) {
714 /* 334: Continue authentication request */
715 if (PEAR::isError($error = $this->parseResponse(334))) {
716 /* 503: Error: already authenticated */
717 if ($this->code === 503) {
723 $auth_sasl = new Auth_SASL;
724 $digest = $auth_sasl->factory('digest-md5');
725 $challenge = base64_decode($this->arguments[0]);
726 $auth_str = base64_encode(
727 $digest->getResponse($uid, $pwd, $challenge, $this->host, "smtp", $authz)
730 if (PEAR::isError($error = $this->put($auth_str))) {
733 /* 334: Continue authentication request */
734 if (PEAR::isError($error = $this->parseResponse(334))) {
738 /* We don't use the protocol's third step because SMTP doesn't
739 * allow subsequent authentication, so we just silently ignore
741 if (PEAR::isError($error = $this->put(''))) {
744 /* 235: Authentication successful */
745 if (PEAR::isError($error = $this->parseResponse(235))) {
751 * Authenticates the user using the CRAM-MD5 method.
753 * @param string $uid The userid to authenticate as.
754 * @param string $pwd The password to authenticate with.
755 * @param string $authz The optional authorization proxy identifier.
757 * @return mixed Returns a PEAR_Error with an error message on any
758 * kind of failure, or true on success.
761 protected function authCRAMMD5($uid, $pwd, $authz = '')
763 if (PEAR::isError($error = $this->put('AUTH', 'CRAM-MD5'))) {
766 /* 334: Continue authentication request */
767 if (PEAR::isError($error = $this->parseResponse(334))) {
768 /* 503: Error: already authenticated */
769 if ($this->code === 503) {
775 $auth_sasl = new Auth_SASL;
776 $challenge = base64_decode($this->arguments[0]);
777 $cram = $auth_sasl->factory('cram-md5');
778 $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
780 if (PEAR::isError($error = $this->put($auth_str))) {
784 /* 235: Authentication successful */
785 if (PEAR::isError($error = $this->parseResponse(235))) {
791 * Authenticates the user using the LOGIN method.
793 * @param string $uid The userid to authenticate as.
794 * @param string $pwd The password to authenticate with.
795 * @param string $authz The optional authorization proxy identifier.
797 * @return mixed Returns a PEAR_Error with an error message on any
798 * kind of failure, or true on success.
801 protected function authLogin($uid, $pwd, $authz = '')
803 if (PEAR::isError($error = $this->put('AUTH', 'LOGIN'))) {
806 /* 334: Continue authentication request */
807 if (PEAR::isError($error = $this->parseResponse(334))) {
808 /* 503: Error: already authenticated */
809 if ($this->code === 503) {
815 if (PEAR::isError($error = $this->put(base64_encode($uid)))) {
818 /* 334: Continue authentication request */
819 if (PEAR::isError($error = $this->parseResponse(334))) {
823 if (PEAR::isError($error = $this->put(base64_encode($pwd)))) {
827 /* 235: Authentication successful */
828 if (PEAR::isError($error = $this->parseResponse(235))) {
836 * Authenticates the user using the PLAIN method.
838 * @param string $uid The userid to authenticate as.
839 * @param string $pwd The password to authenticate with.
840 * @param string $authz The optional authorization proxy identifier.
842 * @return mixed Returns a PEAR_Error with an error message on any
843 * kind of failure, or true on success.
846 protected function authPlain($uid, $pwd, $authz = '')
848 if (PEAR::isError($error = $this->put('AUTH', 'PLAIN'))) {
851 /* 334: Continue authentication request */
852 if (PEAR::isError($error = $this->parseResponse(334))) {
853 /* 503: Error: already authenticated */
854 if ($this->code === 503) {
860 $auth_str = base64_encode($authz . chr(0) . $uid . chr(0) . $pwd);
862 if (PEAR::isError($error = $this->put($auth_str))) {
866 /* 235: Authentication successful */
867 if (PEAR::isError($error = $this->parseResponse(235))) {
875 * Send the HELO command.
877 * @param string $domain The domain name to say we are.
879 * @return mixed Returns a PEAR_Error with an error message on any
880 * kind of failure, or true on success.
883 public function helo($domain)
885 if (PEAR::isError($error = $this->put('HELO', $domain))) {
888 if (PEAR::isError($error = $this->parseResponse(250))) {
896 * Return the list of SMTP service extensions advertised by the server.
898 * @return array The list of SMTP service extensions.
901 public function getServiceExtensions()
907 * Send the MAIL FROM: command.
909 * @param string $sender The sender (reverse path) to set.
910 * @param string $params String containing additional MAIL parameters,
911 * such as the NOTIFY flags defined by RFC 1891
912 * or the VERP protocol.
914 * If $params is an array, only the 'verp' option
915 * is supported. If 'verp' is true, the XVERP
916 * parameter is appended to the MAIL command.
917 * If the 'verp' value is a string, the full
918 * XVERP=value parameter is appended.
920 * @return mixed Returns a PEAR_Error with an error message on any
921 * kind of failure, or true on success.
924 public function mailFrom($sender, $params = null)
926 $args = "FROM:<$sender>";
928 /* Support the deprecated array form of $params. */
929 if (is_array($params) && isset($params['verp'])) {
930 if ($params['verp'] === true) {
932 } elseif (trim($params['verp'])) {
933 $args .= ' XVERP=' . $params['verp'];
935 } elseif (is_string($params) && !empty($params)) {
936 $args .= ' ' . $params;
939 if (PEAR::isError($error = $this->put('MAIL', $args))) {
942 if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) {
950 * Send the RCPT TO: command.
952 * @param string $recipient The recipient (forward path) to add.
953 * @param string $params String containing additional RCPT parameters,
954 * such as the NOTIFY flags defined by RFC 1891.
956 * @return mixed Returns a PEAR_Error with an error message on any
957 * kind of failure, or true on success.
961 public function rcptTo($recipient, $params = null)
963 $args = "TO:<$recipient>";
964 if (is_string($params)) {
965 $args .= ' ' . $params;
968 if (PEAR::isError($error = $this->put('RCPT', $args))) {
971 if (PEAR::isError($error = $this->parseResponse(array(250, 251), $this->pipelining))) {
979 * Quote the data so that it meets SMTP standards.
981 * This is provided as a separate public function to facilitate
982 * easier overloading for the cases where it is desirable to
983 * customize the quoting behavior.
985 * @param string &$data The message text to quote. The string must be passed
986 * by reference, and the text will be modified in place.
990 public function quotedata(&$data)
992 /* Because a single leading period (.) signifies an end to the
993 * data, legitimate leading periods need to be "doubled" ('..'). */
994 $data = preg_replace('/^\./m', '..', $data);
996 /* Change Unix (\n) and Mac (\r) linefeeds into CRLF's (\r\n). */
997 $data = preg_replace('/(?:\r\n|\n|\r(?!\n))/', "\r\n", $data);
1001 * Send the DATA command.
1003 * @param mixed $data The message data, either as a string or an open
1005 * @param string $headers The message headers. If $headers is provided,
1006 * $data is assumed to contain only body data.
1008 * @return mixed Returns a PEAR_Error with an error message on any
1009 * kind of failure, or true on success.
1012 public function data($data, $headers = null)
1014 /* Verify that $data is a supported type. */
1015 if (!is_string($data) && !is_resource($data)) {
1016 return PEAR::raiseError('Expected a string or file resource');
1019 /* Start by considering the size of the optional headers string. We
1020 * also account for the addition 4 character "\r\n\r\n" separator
1022 $size = $headers_size = (is_null($headers)) ? 0 : strlen($headers) + 4;
1024 if (is_resource($data)) {
1025 $stat = fstat($data);
1026 if ($stat === false) {
1027 return PEAR::raiseError('Failed to get file size');
1029 $size += $stat['size'];
1031 $size += strlen($data);
1034 /* RFC 1870, section 3, subsection 3 states "a value of zero indicates
1035 * that no fixed maximum message size is in force". Furthermore, it
1036 * says that if "the parameter is omitted no information is conveyed
1037 * about the server's fixed maximum message size". */
1038 $limit = (isset($this->esmtp['SIZE'])) ? $this->esmtp['SIZE'] : 0;
1039 if ($limit > 0 && $size >= $limit) {
1040 $this->disconnect();
1041 return PEAR::raiseError('Message size exceeds server limit');
1044 /* Initiate the DATA command. */
1045 if (PEAR::isError($error = $this->put('DATA'))) {
1048 if (PEAR::isError($error = $this->parseResponse(354))) {
1052 /* If we have a separate headers string, send it first. */
1053 if (!is_null($headers)) {
1054 $this->quotedata($headers);
1055 if (PEAR::isError($result = $this->send($headers . "\r\n\r\n"))) {
1059 /* Subtract the headers size now that they've been sent. */
1060 $size -= $headers_size;
1063 /* Now we can send the message body data. */
1064 if (is_resource($data)) {
1065 /* Stream the contents of the file resource out over our socket
1066 * connection, line by line. Each line must be run through the
1067 * quoting routine. */
1068 while (strlen($line = fread($data, 8192)) > 0) {
1069 /* If the last character is an newline, we need to grab the
1070 * next character to check to see if it is a period. */
1071 while (!feof($data)) {
1072 $char = fread($data, 1);
1074 if ($char != "\n") {
1078 $this->quotedata($line);
1079 if (PEAR::isError($result = $this->send($line))) {
1087 * Break up the data by sending one chunk (up to 512k) at a time.
1088 * This approach reduces our peak memory usage.
1090 for ($offset = 0; $offset < $size;) {
1091 $end = $offset + 512000;
1094 * Ensure we don't read beyond our data size or span multiple
1095 * lines. quotedata() can't properly handle character data
1096 * that's split across two line break boundaries.
1098 if ($end >= $size) {
1101 for (; $end < $size; $end++) {
1102 if ($data[$end] != "\n") {
1108 /* Extract our chunk and run it through the quoting routine. */
1109 $chunk = substr($data, $offset, $end - $offset);
1110 $this->quotedata($chunk);
1112 /* If we run into a problem along the way, abort. */
1113 if (PEAR::isError($result = $this->send($chunk))) {
1117 /* Advance the offset to the end of this chunk. */
1124 /* Don't add another CRLF sequence if it's already in the data */
1125 $terminator = (substr($last, -2) == "\r\n" ? '' : "\r\n") . ".\r\n";
1127 /* Finally, send the DATA terminator sequence. */
1128 if (PEAR::isError($result = $this->send($terminator))) {
1132 /* Verify that the data was successfully received by the server. */
1133 if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) {
1141 * Send the SEND FROM: command.
1143 * @param string $path The reverse path to send.
1145 * @return mixed Returns a PEAR_Error with an error message on any
1146 * kind of failure, or true on success.
1149 public function sendFrom($path)
1151 if (PEAR::isError($error = $this->put('SEND', "FROM:<$path>"))) {
1154 if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) {
1162 * Send the SOML FROM: command.
1164 * @param string $path The reverse path to send.
1166 * @return mixed Returns a PEAR_Error with an error message on any
1167 * kind of failure, or true on success.
1170 public function somlFrom($path)
1172 if (PEAR::isError($error = $this->put('SOML', "FROM:<$path>"))) {
1175 if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) {
1183 * Send the SAML FROM: command.
1185 * @param string $path The reverse path to send.
1187 * @return mixed Returns a PEAR_Error with an error message on any
1188 * kind of failure, or true on success.
1191 public function samlFrom($path)
1193 if (PEAR::isError($error = $this->put('SAML', "FROM:<$path>"))) {
1196 if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) {
1204 * Send the RSET command.
1206 * @return mixed Returns a PEAR_Error with an error message on any
1207 * kind of failure, or true on success.
1210 public function rset()
1212 if (PEAR::isError($error = $this->put('RSET'))) {
1215 if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) {
1223 * Send the VRFY command.
1225 * @param string $string The string to verify
1227 * @return mixed Returns a PEAR_Error with an error message on any
1228 * kind of failure, or true on success.
1231 public function vrfy($string)
1233 /* Note: 251 is also a valid response code */
1234 if (PEAR::isError($error = $this->put('VRFY', $string))) {
1237 if (PEAR::isError($error = $this->parseResponse(array(250, 252)))) {
1245 * Send the NOOP command.
1247 * @return mixed Returns a PEAR_Error with an error message on any
1248 * kind of failure, or true on success.
1251 public function noop()
1253 if (PEAR::isError($error = $this->put('NOOP'))) {
1256 if (PEAR::isError($error = $this->parseResponse(250))) {
1264 * Backwards-compatibility method. identifySender()'s functionality is
1265 * now handled internally.
1267 * @return boolean This method always return true.
1271 public function identifySender()