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