alecpl
2011-09-23 f8ca748ab84ed267aef9a95da5bc709da96af3cd
commit | author | age
627330 1 <?php
T 2 /* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
3 // +----------------------------------------------------------------------+
4 // | PHP Version 4                                                        |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2003 The PHP Group                                |
7 // +----------------------------------------------------------------------+
8 // | This source file is subject to version 2.02 of the PHP license,      |
9 // | that is bundled with this package in the file LICENSE, and is        |
10 // | available at through the world-wide-web at                           |
11 // | http://www.php.net/license/2_02.txt.                                 |
12 // | If you did not receive a copy of the PHP license and are unable to   |
13 // | obtain it through the world-wide-web, please send a note to          |
14 // | license@php.net so we can mail you a copy immediately.               |
15 // +----------------------------------------------------------------------+
16 // | Authors: Chuck Hagenbuch <chuck@horde.org>                           |
17 // |          Jon Parise <jon@php.net>                                    |
18 // |          Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>      |
19 // +----------------------------------------------------------------------+
20 //
21 // $Id$
22
23 require_once 'PEAR.php';
24 require_once 'Net/Socket.php';
25
26 /**
27  * Provides an implementation of the SMTP protocol using PEAR's
28  * Net_Socket:: class.
29  *
30  * @package Net_SMTP
31  * @author  Chuck Hagenbuch <chuck@horde.org>
32  * @author  Jon Parise <jon@php.net>
33  * @author  Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
34  *
35  * @example basic.php   A basic implementation of the Net_SMTP package.
36  */
37 class Net_SMTP
38 {
39     /**
40      * The server to connect to.
41      * @var string
42      * @access public
43      */
44     var $host = 'localhost';
45
46     /**
47      * The port to connect to.
48      * @var int
49      * @access public
50      */
51     var $port = 25;
52
53     /**
54      * The value to give when sending EHLO or HELO.
55      * @var string
56      * @access public
57      */
58     var $localhost = 'localhost';
59
60     /**
61      * List of supported authentication methods, in preferential order.
62      * @var array
63      * @access public
64      */
65     var $auth_methods = array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN', 'PLAIN');
66
67     /**
d78c41 68      * Use SMTP command pipelining (specified in RFC 2920) if the SMTP
A 69      * server supports it.
70      *
71      * When pipeling is enabled, rcptTo(), mailFrom(), sendFrom(),
72      * somlFrom() and samlFrom() do not wait for a response from the
73      * SMTP server but return immediately.
74      *
75      * @var bool
76      * @access public
77      */
78     var $pipelining = false;
79
80     /**
81      * Number of pipelined commands.
82      * @var int
83      * @access private
84      */
85     var $_pipelined_commands = 0;
86
87     /**
627330 88      * Should debugging output be enabled?
T 89      * @var boolean
90      * @access private
91      */
92     var $_debug = false;
93
94     /**
cb19db 95      * Debug output handler.
A 96      * @var callback
97      * @access private
98      */
99     var $_debug_handler = null;
100
101     /**
627330 102      * The socket resource being used to connect to the SMTP server.
T 103      * @var resource
104      * @access private
105      */
106     var $_socket = null;
107
108     /**
462de2 109      * Array of socket options that will be passed to Net_Socket::connect().
A 110      * @see stream_context_create()
111      * @var array
112      * @access private
113      */
114     var $_socket_options = null;
115
116     /**
3e63a0 117      * The socket I/O timeout value in seconds.
A 118      * @var int
119      * @access private
120      */
121     var $_timeout = 0;
122
123     /**
627330 124      * The most recent server response code.
T 125      * @var int
126      * @access private
127      */
128     var $_code = -1;
129
130     /**
131      * The most recent server response arguments.
132      * @var array
133      * @access private
134      */
135     var $_arguments = array();
cb19db 136
A 137     /**
138      * Stores the SMTP server's greeting string.
139      * @var string
140      * @access private
141      */
142     var $_greeting = null;
627330 143
T 144     /**
145      * Stores detected features of the SMTP server.
146      * @var array
147      * @access private
148      */
149     var $_esmtp = array();
150
151     /**
152      * Instantiates a new Net_SMTP object, overriding any defaults
153      * with parameters that are passed in.
154      *
155      * If you have SSL support in PHP, you can connect to a server
156      * over SSL using an 'ssl://' prefix:
157      *
158      *   // 465 is a common smtps port.
159      *   $smtp = new Net_SMTP('ssl://mail.host.com', 465);
160      *   $smtp->connect();
161      *
162      * @param string  $host       The server to connect to.
163      * @param integer $port       The port to connect to.
164      * @param string  $localhost  The value to give when sending EHLO or HELO.
d78c41 165      * @param boolean $pipeling   Use SMTP command pipelining
3e63a0 166      * @param integer $timeout    Socket I/O timeout in seconds.
462de2 167      * @param array   $socket_options Socket stream_context_create() options.
627330 168      *
T 169      * @access  public
170      * @since   1.0
171      */
3e63a0 172     function Net_SMTP($host = null, $port = null, $localhost = null,
462de2 173         $pipelining = false, $timeout = 0, $socket_options = null)
627330 174     {
d78c41 175         if (isset($host)) {
A 176             $this->host = $host;
177         }
178         if (isset($port)) {
179             $this->port = $port;
180         }
181         if (isset($localhost)) {
182             $this->localhost = $localhost;
183         }
184         $this->pipelining = $pipelining;
627330 185
a5b598 186         $this->_socket = new Net_Socket();
462de2 187         $this->_socket_options = $socket_options;
3e63a0 188         $this->_timeout = $timeout;
627330 189
d78c41 190         /* Include the Auth_SASL package.  If the package is not
A 191          * available, we disable the authentication methods that
192          * depend upon it. */
627330 193         if ((@include_once 'Auth/SASL.php') === false) {
T 194             $pos = array_search('DIGEST-MD5', $this->auth_methods);
195             unset($this->auth_methods[$pos]);
196             $pos = array_search('CRAM-MD5', $this->auth_methods);
197             unset($this->auth_methods[$pos]);
198         }
3e63a0 199     }
A 200
201     /**
202      * Set the socket I/O timeout value in seconds plus microseconds.
203      *
204      * @param   integer $seconds        Timeout value in seconds.
205      * @param   integer $microseconds   Additional value in microseconds.
206      *
207      * @access  public
208      * @since   1.5.0
209      */
210     function setTimeout($seconds, $microseconds = 0) {
211         return $this->_socket->setTimeout($seconds, $microseconds);
627330 212     }
T 213
214     /**
215      * Set the value of the debugging flag.
216      *
217      * @param   boolean $debug      New value for the debugging flag.
218      *
219      * @access  public
220      * @since   1.1.0
221      */
cb19db 222     function setDebug($debug, $handler = null)
627330 223     {
T 224         $this->_debug = $debug;
cb19db 225         $this->_debug_handler = $handler;
A 226     }
227
228     /**
229      * Write the given debug text to the current debug output handler.
230      *
231      * @param   string  $message    Debug mesage text.
232      *
233      * @access  private
234      * @since   1.3.3
235      */
236     function _debug($message)
237     {
238         if ($this->_debug) {
239             if ($this->_debug_handler) {
240                 call_user_func_array($this->_debug_handler,
241                                      array(&$this, $message));
242             } else {
243                 echo "DEBUG: $message\n";
244             }
245         }
627330 246     }
T 247
248     /**
249      * Send the given string of data to the server.
250      *
251      * @param   string  $data       The string of data to send.
252      *
253      * @return  mixed   True on success or a PEAR_Error object on failure.
254      *
255      * @access  private
256      * @since   1.1.0
257      */
258     function _send($data)
259     {
cb19db 260         $this->_debug("Send: $data");
627330 261
67a081 262         $error = $this->_socket->write($data);
A 263         if ($error === false || PEAR::isError($error)) {
264             $msg = ($error) ? $error->getMessage() : "unknown error";
265             return PEAR::raiseError("Failed to write to socket: $msg");
627330 266         }
T 267
268         return true;
269     }
270
271     /**
272      * Send a command to the server with an optional string of
273      * arguments.  A carriage return / linefeed (CRLF) sequence will
274      * be appended to each command string before it is sent to the
275      * SMTP server - an error will be thrown if the command string
276      * already contains any newline characters. Use _send() for
277      * commands that must contain newlines.
278      *
279      * @param   string  $command    The SMTP command to send to the server.
280      * @param   string  $args       A string of optional arguments to append
281      *                              to the command.
282      *
283      * @return  mixed   The result of the _send() call.
284      *
285      * @access  private
286      * @since   1.1.0
287      */
288     function _put($command, $args = '')
289     {
290         if (!empty($args)) {
291             $command .= ' ' . $args;
292         }
293
294         if (strcspn($command, "\r\n") !== strlen($command)) {
295             return PEAR::raiseError('Commands cannot contain newlines');
296         }
297
298         return $this->_send($command . "\r\n");
299     }
300
301     /**
302      * Read a reply from the SMTP server.  The reply consists of a response
303      * code and a response message.
304      *
305      * @param   mixed   $valid      The set of valid response codes.  These
306      *                              may be specified as an array of integer
307      *                              values or as a single integer value.
d78c41 308      * @param   bool    $later      Do not parse the response now, but wait
A 309      *                              until the last command in the pipelined
310      *                              command group
627330 311      *
T 312      * @return  mixed   True if the server returned a valid response code or
313      *                  a PEAR_Error object is an error condition is reached.
314      *
315      * @access  private
316      * @since   1.1.0
317      *
318      * @see     getResponse
319      */
d78c41 320     function _parseResponse($valid, $later = false)
627330 321     {
T 322         $this->_code = -1;
323         $this->_arguments = array();
324
d78c41 325         if ($later) {
A 326             $this->_pipelined_commands++;
627330 327             return true;
T 328         }
329
d78c41 330         for ($i = 0; $i <= $this->_pipelined_commands; $i++) {
A 331             while ($line = $this->_socket->readLine()) {
cb19db 332                 $this->_debug("Recv: $line");
d78c41 333
A 334                 /* If we receive an empty line, the connection has been closed. */
335                 if (empty($line)) {
336                     $this->disconnect();
337                     return PEAR::raiseError('Connection was unexpectedly closed');
338                 }
339
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));
343
344                 /* Check the syntax of the response code. */
345                 if (is_numeric($code)) {
346                     $this->_code = (int)$code;
347                 } else {
348                     $this->_code = -1;
349                     break;
350                 }
351
352                 /* If this is not a multiline response, we're done. */
353                 if (substr($line, 3, 1) != '-') {
354                     break;
627330 355                 }
T 356             }
d78c41 357         }
A 358
359         $this->_pipelined_commands = 0;
360
361         /* Compare the server's response code with the valid code/codes. */
362         if (is_int($valid) && ($this->_code === $valid)) {
363             return true;
364         } elseif (is_array($valid) && in_array($this->_code, $valid, true)) {
365             return true;
627330 366         }
T 367
a5b598 368         return PEAR::raiseError('Invalid response code received from server',
T 369                                 $this->_code);
627330 370     }
T 371
372     /**
373      * Return a 2-tuple containing the last response from the SMTP server.
374      *
375      * @return  array   A two-element array: the first element contains the
376      *                  response code as an integer and the second element
377      *                  contains the response's arguments as a string.
378      *
379      * @access  public
380      * @since   1.1.0
381      */
382     function getResponse()
383     {
384         return array($this->_code, join("\n", $this->_arguments));
385     }
386
387     /**
cb19db 388      * Return the SMTP server's greeting string.
A 389      *
390      * @return  string  A string containing the greeting string, or null if a 
391      *                  greeting has not been received.
392      *
393      * @access  public
394      * @since   1.3.3
395      */
396     function getGreeting()
397     {
398         return $this->_greeting;
399     }
400
401     /**
627330 402      * Attempt to connect to the SMTP server.
T 403      *
404      * @param   int     $timeout    The timeout value (in seconds) for the
3e63a0 405      *                              socket connection attempt.
627330 406      * @param   bool    $persistent Should a persistent socket connection
T 407      *                              be used?
408      *
409      * @return mixed Returns a PEAR_Error with an error message on any
410      *               kind of failure, or true on success.
411      * @access public
412      * @since  1.0
413      */
414     function connect($timeout = null, $persistent = false)
415     {
cb19db 416         $this->_greeting = null;
627330 417         $result = $this->_socket->connect($this->host, $this->port,
462de2 418                                           $persistent, $timeout,
A 419                                           $this->_socket_options);
627330 420         if (PEAR::isError($result)) {
T 421             return PEAR::raiseError('Failed to connect socket: ' .
422                                     $result->getMessage());
3e63a0 423         }
A 424
425         /*
426          * Now that we're connected, reset the socket's timeout value for 
427          * future I/O operations.  This allows us to have different socket 
428          * timeout values for the initial connection (our $timeout parameter) 
429          * and all other socket operations.
430          */
462de2 431         if ($this->_timeout > 0) {
A 432             if (PEAR::isError($error = $this->setTimeout($this->_timeout))) {
433                 return $error;
434             }
627330 435         }
T 436
437         if (PEAR::isError($error = $this->_parseResponse(220))) {
438             return $error;
439         }
cb19db 440
A 441         /* Extract and store a copy of the server's greeting string. */
442         list(, $this->_greeting) = $this->getResponse();
443
627330 444         if (PEAR::isError($error = $this->_negotiate())) {
T 445             return $error;
446         }
447
448         return true;
449     }
450
451     /**
452      * Attempt to disconnect from the SMTP server.
453      *
454      * @return mixed Returns a PEAR_Error with an error message on any
455      *               kind of failure, or true on success.
456      * @access public
457      * @since  1.0
458      */
459     function disconnect()
460     {
461         if (PEAR::isError($error = $this->_put('QUIT'))) {
462             return $error;
463         }
464         if (PEAR::isError($error = $this->_parseResponse(221))) {
465             return $error;
466         }
467         if (PEAR::isError($error = $this->_socket->disconnect())) {
468             return PEAR::raiseError('Failed to disconnect socket: ' .
469                                     $error->getMessage());
470         }
471
472         return true;
473     }
474
475     /**
476      * Attempt to send the EHLO command and obtain a list of ESMTP
477      * extensions available, and failing that just send HELO.
478      *
479      * @return mixed Returns a PEAR_Error with an error message on any
480      *               kind of failure, or true on success.
481      *
482      * @access private
483      * @since  1.1.0
484      */
485     function _negotiate()
486     {
487         if (PEAR::isError($error = $this->_put('EHLO', $this->localhost))) {
488             return $error;
489         }
490
491         if (PEAR::isError($this->_parseResponse(250))) {
492             /* If we receive a 503 response, we're already authenticated. */
493             if ($this->_code === 503) {
494                 return true;
495             }
496
497             /* If the EHLO failed, try the simpler HELO command. */
498             if (PEAR::isError($error = $this->_put('HELO', $this->localhost))) {
499                 return $error;
500             }
501             if (PEAR::isError($this->_parseResponse(250))) {
502                 return PEAR::raiseError('HELO was not accepted: ', $this->_code);
503             }
504
505             return true;
506         }
7d2afc 507
627330 508         foreach ($this->_arguments as $argument) {
T 509             $verb = strtok($argument, ' ');
510             $arguments = substr($argument, strlen($verb) + 1,
511                                 strlen($argument) - strlen($verb) - 1);
512             $this->_esmtp[$verb] = $arguments;
d78c41 513         }
A 514
515         if (!isset($this->_esmtp['PIPELINING'])) {
516             $this->pipelining = false;
627330 517         }
T 518
519         return true;
520     }
521
522     /**
523      * Returns the name of the best authentication method that the server
524      * has advertised.
525      *
526      * @return mixed    Returns a string containing the name of the best
527      *                  supported authentication method or a PEAR_Error object
528      *                  if a failure condition is encountered.
529      * @access private
530      * @since  1.1.0
531      */
532     function _getBestAuthMethod()
533     {
534         $available_methods = explode(' ', $this->_esmtp['AUTH']);
535
536         foreach ($this->auth_methods as $method) {
537             if (in_array($method, $available_methods)) {
538                 return $method;
539             }
540         }
541
542         return PEAR::raiseError('No supported authentication methods');
543     }
544
545     /**
546      * Attempt to do SMTP authentication.
547      *
548      * @param string The userid to authenticate as.
549      * @param string The password to authenticate with.
550      * @param string The requested authentication method.  If none is
551      *               specified, the best supported method will be used.
49d401 552      * @param bool   Flag indicating whether or not TLS should be attempted.
e2cbca 553      * @param string An optional authorization identifier.  If specified, this
A 554      *               identifier will be used as the authorization proxy.
627330 555      *
T 556      * @return mixed Returns a PEAR_Error with an error message on any
557      *               kind of failure, or true on success.
558      * @access public
559      * @since  1.0
560      */
e2cbca 561     function auth($uid, $pwd , $method = '', $tls = true, $authz = '')
627330 562     {
49d401 563         /* We can only attempt a TLS connection if one has been requested,
A 564          * we're running PHP 5.1.0 or later, have access to the OpenSSL 
565          * extension, are connected to an SMTP server which supports the 
566          * STARTTLS extension, and aren't already connected over a secure 
567          * (SSL) socket connection. */
568         if ($tls && version_compare(PHP_VERSION, '5.1.0', '>=') &&
569             extension_loaded('openssl') && isset($this->_esmtp['STARTTLS']) &&
570             strncasecmp($this->host, 'ssl://', 6) !== 0) {
571             /* Start the TLS connection attempt. */
cb19db 572             if (PEAR::isError($result = $this->_put('STARTTLS'))) {
A 573                 return $result;
574             }
575             if (PEAR::isError($result = $this->_parseResponse(220))) {
576                 return $result;
577             }
578             if (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) {
579                 return $result;
580             } elseif ($result !== true) {
581                 return PEAR::raiseError('STARTTLS failed');
582             }
a5b598 583
cb19db 584             /* Send EHLO again to recieve the AUTH string from the
A 585              * SMTP server. */
586             $this->_negotiate();
587         }
588
589         if (empty($this->_esmtp['AUTH'])) {
44ea3f 590             return PEAR::raiseError('SMTP server does not support authentication');
627330 591         }
T 592
593         /* If no method has been specified, get the name of the best
594          * supported method advertised by the SMTP server. */
595         if (empty($method)) {
596             if (PEAR::isError($method = $this->_getBestAuthMethod())) {
597                 /* Return the PEAR_Error object from _getBestAuthMethod(). */
598                 return $method;
599             }
600         } else {
601             $method = strtoupper($method);
602             if (!in_array($method, $this->auth_methods)) {
603                 return PEAR::raiseError("$method is not a supported authentication method");
604             }
605         }
606
607         switch ($method) {
a5b598 608         case 'DIGEST-MD5':
e2cbca 609             $result = $this->_authDigest_MD5($uid, $pwd, $authz);
a5b598 610             break;
T 611
612         case 'CRAM-MD5':
613             $result = $this->_authCRAM_MD5($uid, $pwd);
614             break;
615
616         case 'LOGIN':
617             $result = $this->_authLogin($uid, $pwd);
618             break;
619
620         case 'PLAIN':
e2cbca 621             $result = $this->_authPlain($uid, $pwd, $authz);
a5b598 622             break;
T 623
624         default:
625             $result = PEAR::raiseError("$method is not a supported authentication method");
626             break;
627330 627         }
T 628
629         /* If an error was encountered, return the PEAR_Error object. */
630         if (PEAR::isError($result)) {
631             return $result;
632         }
633
634         return true;
635     }
636
637     /**
638      * Authenticates the user using the DIGEST-MD5 method.
639      *
640      * @param string The userid to authenticate as.
641      * @param string The password to authenticate with.
e2cbca 642      * @param string The optional authorization proxy identifier.
627330 643      *
T 644      * @return mixed Returns a PEAR_Error with an error message on any
645      *               kind of failure, or true on success.
646      * @access private
647      * @since  1.1.0
648      */
e2cbca 649     function _authDigest_MD5($uid, $pwd, $authz = '')
627330 650     {
T 651         if (PEAR::isError($error = $this->_put('AUTH', 'DIGEST-MD5'))) {
652             return $error;
653         }
654         /* 334: Continue authentication request */
655         if (PEAR::isError($error = $this->_parseResponse(334))) {
656             /* 503: Error: already authenticated */
657             if ($this->_code === 503) {
658                 return true;
659             }
660             return $error;
661         }
662
663         $challenge = base64_decode($this->_arguments[0]);
664         $digest = &Auth_SASL::factory('digestmd5');
665         $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge,
3e63a0 666                                                        $this->host, "smtp",
A 667                                                        $authz));
627330 668
T 669         if (PEAR::isError($error = $this->_put($auth_str))) {
670             return $error;
671         }
672         /* 334: Continue authentication request */
673         if (PEAR::isError($error = $this->_parseResponse(334))) {
674             return $error;
675         }
676
677         /* We don't use the protocol's third step because SMTP doesn't
678          * allow subsequent authentication, so we just silently ignore
679          * it. */
b751d5 680         if (PEAR::isError($error = $this->_put(''))) {
627330 681             return $error;
T 682         }
683         /* 235: Authentication successful */
684         if (PEAR::isError($error = $this->_parseResponse(235))) {
685             return $error;
686         }
687     }
688
689     /**
690      * Authenticates the user using the CRAM-MD5 method.
691      *
692      * @param string The userid to authenticate as.
693      * @param string The password to authenticate with.
694      *
695      * @return mixed Returns a PEAR_Error with an error message on any
696      *               kind of failure, or true on success.
697      * @access private
698      * @since  1.1.0
699      */
700     function _authCRAM_MD5($uid, $pwd)
701     {
702         if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) {
703             return $error;
704         }
705         /* 334: Continue authentication request */
706         if (PEAR::isError($error = $this->_parseResponse(334))) {
707             /* 503: Error: already authenticated */
708             if ($this->_code === 503) {
709                 return true;
710             }
711             return $error;
712         }
713
714         $challenge = base64_decode($this->_arguments[0]);
715         $cram = &Auth_SASL::factory('crammd5');
716         $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
717
718         if (PEAR::isError($error = $this->_put($auth_str))) {
719             return $error;
720         }
721
722         /* 235: Authentication successful */
723         if (PEAR::isError($error = $this->_parseResponse(235))) {
724             return $error;
725         }
726     }
727
728     /**
729      * Authenticates the user using the LOGIN method.
730      *
731      * @param string The userid to authenticate as.
732      * @param string The password to authenticate with.
733      *
734      * @return mixed Returns a PEAR_Error with an error message on any
735      *               kind of failure, or true on success.
736      * @access private
737      * @since  1.1.0
738      */
739     function _authLogin($uid, $pwd)
740     {
741         if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) {
742             return $error;
743         }
744         /* 334: Continue authentication request */
745         if (PEAR::isError($error = $this->_parseResponse(334))) {
746             /* 503: Error: already authenticated */
747             if ($this->_code === 503) {
748                 return true;
749             }
750             return $error;
751         }
752
753         if (PEAR::isError($error = $this->_put(base64_encode($uid)))) {
754             return $error;
755         }
756         /* 334: Continue authentication request */
757         if (PEAR::isError($error = $this->_parseResponse(334))) {
758             return $error;
759         }
760
761         if (PEAR::isError($error = $this->_put(base64_encode($pwd)))) {
762             return $error;
763         }
764
765         /* 235: Authentication successful */
766         if (PEAR::isError($error = $this->_parseResponse(235))) {
767             return $error;
768         }
769
770         return true;
771     }
772
773     /**
774      * Authenticates the user using the PLAIN method.
775      *
776      * @param string The userid to authenticate as.
777      * @param string The password to authenticate with.
e2cbca 778      * @param string The optional authorization proxy identifier.
627330 779      *
T 780      * @return mixed Returns a PEAR_Error with an error message on any
781      *               kind of failure, or true on success.
782      * @access private
783      * @since  1.1.0
784      */
e2cbca 785     function _authPlain($uid, $pwd, $authz = '')
627330 786     {
T 787         if (PEAR::isError($error = $this->_put('AUTH', 'PLAIN'))) {
788             return $error;
789         }
790         /* 334: Continue authentication request */
791         if (PEAR::isError($error = $this->_parseResponse(334))) {
792             /* 503: Error: already authenticated */
793             if ($this->_code === 503) {
794                 return true;
795             }
796             return $error;
797         }
798
e2cbca 799         $auth_str = base64_encode($authz . chr(0) . $uid . chr(0) . $pwd);
627330 800
T 801         if (PEAR::isError($error = $this->_put($auth_str))) {
802             return $error;
803         }
804
805         /* 235: Authentication successful */
806         if (PEAR::isError($error = $this->_parseResponse(235))) {
807             return $error;
808         }
809
810         return true;
811     }
812
813     /**
814      * Send the HELO command.
815      *
816      * @param string The domain name to say we are.
817      *
818      * @return mixed Returns a PEAR_Error with an error message on any
819      *               kind of failure, or true on success.
820      * @access public
821      * @since  1.0
822      */
823     function helo($domain)
824     {
825         if (PEAR::isError($error = $this->_put('HELO', $domain))) {
826             return $error;
827         }
828         if (PEAR::isError($error = $this->_parseResponse(250))) {
829             return $error;
830         }
831
832         return true;
833     }
834
835     /**
d78c41 836      * Return the list of SMTP service extensions advertised by the server.
A 837      *
838      * @return array The list of SMTP service extensions.
839      * @access public
840      * @since 1.3
841      */
842     function getServiceExtensions()
843     {
844         return $this->_esmtp;
845     }
846
847     /**
627330 848      * Send the MAIL FROM: command.
T 849      *
a5b598 850      * @param string $sender    The sender (reverse path) to set.
T 851      * @param string $params    String containing additional MAIL parameters,
852      *                          such as the NOTIFY flags defined by RFC 1891
853      *                          or the VERP protocol.
627330 854      *
a5b598 855      *                          If $params is an array, only the 'verp' option
T 856      *                          is supported.  If 'verp' is true, the XVERP
857      *                          parameter is appended to the MAIL command.  If
858      *                          the 'verp' value is a string, the full
859      *                          XVERP=value parameter is appended.
627330 860      *
T 861      * @return mixed Returns a PEAR_Error with an error message on any
862      *               kind of failure, or true on success.
863      * @access public
864      * @since  1.0
865      */
a5b598 866     function mailFrom($sender, $params = null)
627330 867     {
a5b598 868         $args = "FROM:<$sender>";
627330 869
a5b598 870         /* Support the deprecated array form of $params. */
T 871         if (is_array($params) && isset($params['verp'])) {
627330 872             /* XVERP */
a5b598 873             if ($params['verp'] === true) {
T 874                 $args .= ' XVERP';
627330 875
T 876             /* XVERP=something */
a5b598 877             } elseif (trim($params['verp'])) {
T 878                 $args .= ' XVERP=' . $params['verp'];
627330 879             }
3e63a0 880         } elseif (is_string($params) && !empty($params)) {
a5b598 881             $args .= ' ' . $params;
627330 882         }
T 883
a5b598 884         if (PEAR::isError($error = $this->_put('MAIL', $args))) {
627330 885             return $error;
T 886         }
d78c41 887         if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
627330 888             return $error;
T 889         }
890
891         return true;
892     }
893
894     /**
895      * Send the RCPT TO: command.
896      *
a5b598 897      * @param string $recipient The recipient (forward path) to add.
T 898      * @param string $params    String containing additional RCPT parameters,
899      *                          such as the NOTIFY flags defined by RFC 1891.
627330 900      *
T 901      * @return mixed Returns a PEAR_Error with an error message on any
902      *               kind of failure, or true on success.
a5b598 903      *
627330 904      * @access public
T 905      * @since  1.0
906      */
a5b598 907     function rcptTo($recipient, $params = null)
627330 908     {
a5b598 909         $args = "TO:<$recipient>";
T 910         if (is_string($params)) {
911             $args .= ' ' . $params;
912         }
913
914         if (PEAR::isError($error = $this->_put('RCPT', $args))) {
627330 915             return $error;
T 916         }
d78c41 917         if (PEAR::isError($error = $this->_parseResponse(array(250, 251), $this->pipelining))) {
627330 918             return $error;
T 919         }
920
921         return true;
922     }
923
924     /**
925      * Quote the data so that it meets SMTP standards.
926      *
927      * This is provided as a separate public function to facilitate
928      * easier overloading for the cases where it is desirable to
929      * customize the quoting behavior.
930      *
931      * @param string $data  The message text to quote. The string must be passed
932      *                      by reference, and the text will be modified in place.
933      *
934      * @access public
935      * @since  1.2
936      */
937     function quotedata(&$data)
938     {
939         /* Change Unix (\n) and Mac (\r) linefeeds into
940          * Internet-standard CRLF (\r\n) linefeeds. */
941         $data = preg_replace(array('/(?<!\r)\n/','/\r(?!\n)/'), "\r\n", $data);
942
943         /* Because a single leading period (.) signifies an end to the
944          * data, legitimate leading periods need to be "doubled"
945          * (e.g. '..'). */
946         $data = str_replace("\n.", "\n..", $data);
947     }
948
949     /**
950      * Send the DATA command.
951      *
49d401 952      * @param mixed $data     The message data, either as a string or an open
A 953      *                        file resource.
954      * @param string $headers The message headers.  If $headers is provided,
955      *                        $data is assumed to contain only body data.
627330 956      *
T 957      * @return mixed Returns a PEAR_Error with an error message on any
958      *               kind of failure, or true on success.
959      * @access public
960      * @since  1.0
961      */
49d401 962     function data($data, $headers = null)
627330 963     {
49d401 964         /* Verify that $data is a supported type. */
A 965         if (!is_string($data) && !is_resource($data)) {
966             return PEAR::raiseError('Expected a string or file resource');
967         }
968
3e63a0 969         /* Start by considering the size of the optional headers string.  We
A 970          * also account for the addition 4 character "\r\n\r\n" separator
971          * sequence. */
972         $size = (is_null($headers)) ? 0 : strlen($headers) + 4;
49d401 973
3e63a0 974         if (is_resource($data)) {
A 975             $stat = fstat($data);
976             if ($stat === false) {
977                 return PEAR::raiseError('Failed to get file size');
49d401 978             }
3e63a0 979             $size += $stat['size'];
A 980         } else {
981             $size += strlen($data);
982         }
49d401 983
3e63a0 984         /* RFC 1870, section 3, subsection 3 states "a value of zero indicates
A 985          * that no fixed maximum message size is in force".  Furthermore, it
986          * says that if "the parameter is omitted no information is conveyed
987          * about the server's fixed maximum message size". */
988         $limit = (isset($this->_esmtp['SIZE'])) ? $this->_esmtp['SIZE'] : 0;
989         if ($limit > 0 && $size >= $limit) {
990             $this->disconnect();
991             return PEAR::raiseError('Message size exceeds server limit');
627330 992         }
T 993
49d401 994         /* Initiate the DATA command. */
627330 995         if (PEAR::isError($error = $this->_put('DATA'))) {
T 996             return $error;
997         }
998         if (PEAR::isError($error = $this->_parseResponse(354))) {
999             return $error;
1000         }
1001
49d401 1002         /* If we have a separate headers string, send it first. */
A 1003         if (!is_null($headers)) {
cc2c83 1004             $this->quotedata($headers);
49d401 1005             if (PEAR::isError($result = $this->_send($headers . "\r\n\r\n"))) {
A 1006                 return $result;
1007             }
627330 1008         }
49d401 1009
A 1010         /* Now we can send the message body data. */
1011         if (is_resource($data)) {
1012             /* Stream the contents of the file resource out over our socket 
1013              * connection, line by line.  Each line must be run through the 
1014              * quoting routine. */
1015             while ($line = fgets($data, 1024)) {
1016                 $this->quotedata($line);
1017                 if (PEAR::isError($result = $this->_send($line))) {
1018                     return $result;
1019                 }
1020             }
1021         } else {
e2cbca 1022             /*
A 1023              * Break up the data by sending one chunk (up to 512k) at a time.  
1024              * This approach reduces our peak memory usage.
1025              */
1026             for ($offset = 0; $offset < $size;) {
1027                 $end = $offset + 512000;
1028
1029                 /*
1030                  * Ensure we don't read beyond our data size or span multiple 
1031                  * lines.  quotedata() can't properly handle character data 
1032                  * that's split across two line break boundaries.
1033                  */
1034                 if ($end >= $size) {
1035                     $end = $size;
1036                 } else {
1037                     for (; $end < $size; $end++) {
1038                         if ($data[$end] != "\n") {
1039                             break;
1040                         }
1041                     }
1042                 }
1043
1044                 /* Extract our chunk and run it through the quoting routine. */
1045                 $chunk = substr($data, $offset, $end - $offset);
1046                 $this->quotedata($chunk);
1047
1048                 /* If we run into a problem along the way, abort. */
1049                 if (PEAR::isError($result = $this->_send($chunk))) {
1050                     return $result;
1051                 }
1052
1053                 /* Advance the offset to the end of this chunk. */
1054                 $offset = $end;
49d401 1055             }
A 1056         }
1057
e2cbca 1058         /* Finally, send the DATA terminator sequence. */
A 1059         if (PEAR::isError($result = $this->_send("\r\n.\r\n"))) {
1060             return $result;
1061         }
1062
49d401 1063         /* Verify that the data was successfully received by the server. */
d78c41 1064         if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
627330 1065             return $error;
T 1066         }
1067
1068         return true;
1069     }
1070
1071     /**
1072      * Send the SEND FROM: command.
1073      *
1074      * @param string The reverse path to send.
1075      *
1076      * @return mixed Returns a PEAR_Error with an error message on any
1077      *               kind of failure, or true on success.
1078      * @access public
1079      * @since  1.2.6
1080      */
1081     function sendFrom($path)
1082     {
1083         if (PEAR::isError($error = $this->_put('SEND', "FROM:<$path>"))) {
1084             return $error;
1085         }
d78c41 1086         if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
627330 1087             return $error;
T 1088         }
1089
1090         return true;
1091     }
1092
1093     /**
1094      * Backwards-compatibility wrapper for sendFrom().
1095      *
1096      * @param string The reverse path to send.
1097      *
1098      * @return mixed Returns a PEAR_Error with an error message on any
1099      *               kind of failure, or true on success.
1100      *
1101      * @access      public
1102      * @since       1.0
1103      * @deprecated  1.2.6
1104      */
1105     function send_from($path)
1106     {
1107         return sendFrom($path);
1108     }
1109
1110     /**
1111      * Send the SOML FROM: command.
1112      *
1113      * @param string The reverse path to send.
1114      *
1115      * @return mixed Returns a PEAR_Error with an error message on any
1116      *               kind of failure, or true on success.
1117      * @access public
1118      * @since  1.2.6
1119      */
1120     function somlFrom($path)
1121     {
1122         if (PEAR::isError($error = $this->_put('SOML', "FROM:<$path>"))) {
1123             return $error;
1124         }
d78c41 1125         if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
627330 1126             return $error;
T 1127         }
1128
1129         return true;
1130     }
1131
1132     /**
1133      * Backwards-compatibility wrapper for somlFrom().
1134      *
1135      * @param string The reverse path to send.
1136      *
1137      * @return mixed Returns a PEAR_Error with an error message on any
1138      *               kind of failure, or true on success.
1139      *
1140      * @access      public
1141      * @since       1.0
1142      * @deprecated  1.2.6
1143      */
1144     function soml_from($path)
1145     {
1146         return somlFrom($path);
1147     }
1148
1149     /**
1150      * Send the SAML FROM: command.
1151      *
1152      * @param string The reverse path to send.
1153      *
1154      * @return mixed Returns a PEAR_Error with an error message on any
1155      *               kind of failure, or true on success.
1156      * @access public
1157      * @since  1.2.6
1158      */
1159     function samlFrom($path)
1160     {
1161         if (PEAR::isError($error = $this->_put('SAML', "FROM:<$path>"))) {
1162             return $error;
1163         }
d78c41 1164         if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
627330 1165             return $error;
T 1166         }
1167
1168         return true;
1169     }
1170
1171     /**
1172      * Backwards-compatibility wrapper for samlFrom().
1173      *
1174      * @param string The reverse path to send.
1175      *
1176      * @return mixed Returns a PEAR_Error with an error message on any
1177      *               kind of failure, or true on success.
1178      *
1179      * @access      public
1180      * @since       1.0
1181      * @deprecated  1.2.6
1182      */
1183     function saml_from($path)
1184     {
1185         return samlFrom($path);
1186     }
1187
1188     /**
1189      * Send the RSET command.
1190      *
1191      * @return mixed Returns a PEAR_Error with an error message on any
1192      *               kind of failure, or true on success.
1193      * @access public
1194      * @since  1.0
1195      */
1196     function rset()
1197     {
1198         if (PEAR::isError($error = $this->_put('RSET'))) {
1199             return $error;
1200         }
d78c41 1201         if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
627330 1202             return $error;
T 1203         }
1204
1205         return true;
1206     }
1207
1208     /**
1209      * Send the VRFY command.
1210      *
1211      * @param string The string to verify
1212      *
1213      * @return mixed Returns a PEAR_Error with an error message on any
1214      *               kind of failure, or true on success.
1215      * @access public
1216      * @since  1.0
1217      */
1218     function vrfy($string)
1219     {
1220         /* Note: 251 is also a valid response code */
1221         if (PEAR::isError($error = $this->_put('VRFY', $string))) {
1222             return $error;
1223         }
1224         if (PEAR::isError($error = $this->_parseResponse(array(250, 252)))) {
1225             return $error;
1226         }
1227
1228         return true;
1229     }
1230
1231     /**
1232      * Send the NOOP command.
1233      *
1234      * @return mixed Returns a PEAR_Error with an error message on any
1235      *               kind of failure, or true on success.
1236      * @access public
1237      * @since  1.0
1238      */
1239     function noop()
1240     {
1241         if (PEAR::isError($error = $this->_put('NOOP'))) {
1242             return $error;
1243         }
1244         if (PEAR::isError($error = $this->_parseResponse(250))) {
1245             return $error;
1246         }
1247
1248         return true;
1249     }
1250
1251     /**
1252      * Backwards-compatibility method.  identifySender()'s functionality is
1253      * now handled internally.
1254      *
1255      * @return  boolean     This method always return true.
1256      *
1257      * @access  public
1258      * @since   1.0
1259      */
1260     function identifySender()
1261     {
1262         return true;
1263     }
1264
1265 }