]> git.donarmstrong.com Git - roundcube.git/blob - program/lib/Net/SMTP.php
Imported Debian patch 0.1~rc1~dfsg-3
[roundcube.git] / program / lib / 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 399 2006-12-06 21:37:37Z thomasb $
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     /**
41      * The server to connect to.
42      * @var string
43      * @access public
44      */
45     var $host = 'localhost';
46
47     /**
48      * The port to connect to.
49      * @var int
50      * @access public
51      */
52     var $port = 25;
53
54     /**
55      * The value to give when sending EHLO or HELO.
56      * @var string
57      * @access public
58      */
59     var $localhost = 'localhost';
60
61     /**
62      * List of supported authentication methods, in preferential order.
63      * @var array
64      * @access public
65      */
66     var $auth_methods = array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN', 'PLAIN');
67
68     /**
69      * Should debugging output be enabled?
70      * @var boolean
71      * @access private
72      */
73     var $_debug = false;
74
75     /**
76      * The socket resource being used to connect to the SMTP server.
77      * @var resource
78      * @access private
79      */
80     var $_socket = null;
81
82     /**
83      * The most recent server response code.
84      * @var int
85      * @access private
86      */
87     var $_code = -1;
88
89     /**
90      * The most recent server response arguments.
91      * @var array
92      * @access private
93      */
94     var $_arguments = array();
95
96     /**
97      * Stores detected features of the SMTP server.
98      * @var array
99      * @access private
100      */
101     var $_esmtp = array();
102
103     /**
104      * Instantiates a new Net_SMTP object, overriding any defaults
105      * with parameters that are passed in.
106      *
107      * If you have SSL support in PHP, you can connect to a server
108      * over SSL using an 'ssl://' prefix:
109      *
110      *   // 465 is a common smtps port.
111      *   $smtp = new Net_SMTP('ssl://mail.host.com', 465);
112      *   $smtp->connect();
113      *
114      * @param string  $host       The server to connect to.
115      * @param integer $port       The port to connect to.
116      * @param string  $localhost  The value to give when sending EHLO or HELO.
117      *
118      * @access  public
119      * @since   1.0
120      */
121     function Net_SMTP($host = null, $port = null, $localhost = null)
122     {
123         if (isset($host)) $this->host = $host;
124         if (isset($port)) $this->port = $port;
125         if (isset($localhost)) $this->localhost = $localhost;
126
127         $this->_socket = &new Net_Socket();
128
129         /*
130          * Include the Auth_SASL package.  If the package is not available,
131          * we disable the authentication methods that depend upon it.
132          */
133         if ((@include_once 'Auth/SASL.php') === false) {
134             $pos = array_search('DIGEST-MD5', $this->auth_methods);
135             unset($this->auth_methods[$pos]);
136             $pos = array_search('CRAM-MD5', $this->auth_methods);
137             unset($this->auth_methods[$pos]);
138         }
139     }
140
141     /**
142      * Set the value of the debugging flag.
143      *
144      * @param   boolean $debug      New value for the debugging flag.
145      *
146      * @access  public
147      * @since   1.1.0
148      */
149     function setDebug($debug)
150     {
151         $this->_debug = $debug;
152     }
153
154     /**
155      * Send the given string of data to the server.
156      *
157      * @param   string  $data       The string of data to send.
158      *
159      * @return  mixed   True on success or a PEAR_Error object on failure.
160      *
161      * @access  private
162      * @since   1.1.0
163      */
164     function _send($data)
165     {
166         if ($this->_debug) {
167             echo "DEBUG: Send: $data\n";
168         }
169
170         if (PEAR::isError($error = $this->_socket->write($data))) {
171             return PEAR::raiseError('Failed to write to socket: ' .
172                                     $error->getMessage());
173         }
174
175         return true;
176     }
177
178     /**
179      * Send a command to the server with an optional string of
180      * arguments.  A carriage return / linefeed (CRLF) sequence will
181      * be appended to each command string before it is sent to the
182      * SMTP server - an error will be thrown if the command string
183      * already contains any newline characters. Use _send() for
184      * commands that must contain newlines.
185      *
186      * @param   string  $command    The SMTP command to send to the server.
187      * @param   string  $args       A string of optional arguments to append
188      *                              to the command.
189      *
190      * @return  mixed   The result of the _send() call.
191      *
192      * @access  private
193      * @since   1.1.0
194      */
195     function _put($command, $args = '')
196     {
197         if (!empty($args)) {
198             $command .= ' ' . $args;
199         }
200
201         if (strcspn($command, "\r\n") !== strlen($command)) {
202             return PEAR::raiseError('Commands cannot contain newlines');
203         }
204
205         return $this->_send($command . "\r\n");
206     }
207
208     /**
209      * Read a reply from the SMTP server.  The reply consists of a response
210      * code and a response message.
211      *
212      * @param   mixed   $valid      The set of valid response codes.  These
213      *                              may be specified as an array of integer
214      *                              values or as a single integer value.
215      *
216      * @return  mixed   True if the server returned a valid response code or
217      *                  a PEAR_Error object is an error condition is reached.
218      *
219      * @access  private
220      * @since   1.1.0
221      *
222      * @see     getResponse
223      */
224     function _parseResponse($valid)
225     {
226         $this->_code = -1;
227         $this->_arguments = array();
228
229         while ($line = $this->_socket->readLine()) {
230             if ($this->_debug) {
231                 echo "DEBUG: Recv: $line\n";
232             }
233
234             /* If we receive an empty line, the connection has been closed. */
235             if (empty($line)) {
236                 $this->disconnect();
237                 return PEAR::raiseError('Connection was unexpectedly closed');
238             }
239
240             /* Read the code and store the rest in the arguments array. */
241             $code = substr($line, 0, 3);
242             $this->_arguments[] = trim(substr($line, 4));
243
244             /* Check the syntax of the response code. */
245             if (is_numeric($code)) {
246                 $this->_code = (int)$code;
247             } else {
248                 $this->_code = -1;
249                 break;
250             }
251
252             /* If this is not a multiline response, we're done. */
253             if (substr($line, 3, 1) != '-') {
254                 break;
255             }
256         }
257
258         /* Compare the server's response code with the valid code. */
259         if (is_int($valid) && ($this->_code === $valid)) {
260             return true;
261         }
262
263         /* If we were given an array of valid response codes, check each one. */
264         if (is_array($valid)) {
265             foreach ($valid as $valid_code) {
266                 if ($this->_code === $valid_code) {
267                     return true;
268                 }
269             }
270         }
271
272         return PEAR::raiseError('Invalid response code received from server');
273     }
274
275     /**
276      * Return a 2-tuple containing the last response from the SMTP server.
277      *
278      * @return  array   A two-element array: the first element contains the
279      *                  response code as an integer and the second element
280      *                  contains the response's arguments as a string.
281      *
282      * @access  public
283      * @since   1.1.0
284      */
285     function getResponse()
286     {
287         return array($this->_code, join("\n", $this->_arguments));
288     }
289
290     /**
291      * Attempt to connect to the SMTP server.
292      *
293      * @param   int     $timeout    The timeout value (in seconds) for the
294      *                              socket connection.
295      * @param   bool    $persistent Should a persistent socket connection
296      *                              be used?
297      *
298      * @return mixed Returns a PEAR_Error with an error message on any
299      *               kind of failure, or true on success.
300      * @access public
301      * @since  1.0
302      */
303     function connect($timeout = null, $persistent = false)
304     {
305         $result = $this->_socket->connect($this->host, $this->port,
306                                           $persistent, $timeout);
307         if (PEAR::isError($result)) {
308             return PEAR::raiseError('Failed to connect socket: ' .
309                                     $result->getMessage());
310         }
311
312         if (PEAR::isError($error = $this->_parseResponse(220))) {
313             return $error;
314         }
315         if (PEAR::isError($error = $this->_negotiate())) {
316             return $error;
317         }
318
319         return true;
320     }
321
322     /**
323      * Attempt to disconnect from the SMTP server.
324      *
325      * @return mixed Returns a PEAR_Error with an error message on any
326      *               kind of failure, or true on success.
327      * @access public
328      * @since  1.0
329      */
330     function disconnect()
331     {
332         if (PEAR::isError($error = $this->_put('QUIT'))) {
333             return $error;
334         }
335         if (PEAR::isError($error = $this->_parseResponse(221))) {
336             return $error;
337         }
338         if (PEAR::isError($error = $this->_socket->disconnect())) {
339             return PEAR::raiseError('Failed to disconnect socket: ' .
340                                     $error->getMessage());
341         }
342
343         return true;
344     }
345
346     /**
347      * Attempt to send the EHLO command and obtain a list of ESMTP
348      * extensions available, and failing that just send HELO.
349      *
350      * @return mixed Returns a PEAR_Error with an error message on any
351      *               kind of failure, or true on success.
352      *
353      * @access private
354      * @since  1.1.0
355      */
356     function _negotiate()
357     {
358         if (PEAR::isError($error = $this->_put('EHLO', $this->localhost))) {
359             return $error;
360         }
361
362         if (PEAR::isError($this->_parseResponse(250))) {
363             /* If we receive a 503 response, we're already authenticated. */
364             if ($this->_code === 503) {
365                 return true;
366             }
367
368             /* If the EHLO failed, try the simpler HELO command. */
369             if (PEAR::isError($error = $this->_put('HELO', $this->localhost))) {
370                 return $error;
371             }
372             if (PEAR::isError($this->_parseResponse(250))) {
373                 return PEAR::raiseError('HELO was not accepted: ', $this->_code);
374             }
375
376             return true;
377         }
378
379         foreach ($this->_arguments as $argument) {
380             $verb = strtok($argument, ' ');
381             $arguments = substr($argument, strlen($verb) + 1,
382                                 strlen($argument) - strlen($verb) - 1);
383             $this->_esmtp[$verb] = $arguments;
384         }
385
386         return true;
387     }
388
389     /**
390      * Returns the name of the best authentication method that the server
391      * has advertised.
392      *
393      * @return mixed    Returns a string containing the name of the best
394      *                  supported authentication method or a PEAR_Error object
395      *                  if a failure condition is encountered.
396      * @access private
397      * @since  1.1.0
398      */
399     function _getBestAuthMethod()
400     {
401         $available_methods = explode(' ', $this->_esmtp['AUTH']);
402
403         foreach ($this->auth_methods as $method) {
404             if (in_array($method, $available_methods)) {
405                 return $method;
406             }
407         }
408
409         return PEAR::raiseError('No supported authentication methods');
410     }
411
412     /**
413      * Attempt to do SMTP authentication.
414      *
415      * @param string The userid to authenticate as.
416      * @param string The password to authenticate with.
417      * @param string The requested authentication method.  If none is
418      *               specified, the best supported method will be used.
419      *
420      * @return mixed Returns a PEAR_Error with an error message on any
421      *               kind of failure, or true on success.
422      * @access public
423      * @since  1.0
424      */
425     function auth($uid, $pwd , $method = '')
426     {
427         if (empty($this->_esmtp['AUTH'])) {
428             return PEAR::raiseError('SMTP server does no support authentication');
429         }
430
431         /* If no method has been specified, get the name of the best
432          * supported method advertised by the SMTP server. */
433         if (empty($method)) {
434             if (PEAR::isError($method = $this->_getBestAuthMethod())) {
435                 /* Return the PEAR_Error object from _getBestAuthMethod(). */
436                 return $method;
437             }
438         } else {
439             $method = strtoupper($method);
440             if (!in_array($method, $this->auth_methods)) {
441                 return PEAR::raiseError("$method is not a supported authentication method");
442             }
443         }
444
445         switch ($method) {
446             case 'DIGEST-MD5':
447                 $result = $this->_authDigest_MD5($uid, $pwd);
448                 break;
449             case 'CRAM-MD5':
450                 $result = $this->_authCRAM_MD5($uid, $pwd);
451                 break;
452             case 'LOGIN':
453                 $result = $this->_authLogin($uid, $pwd);
454                 break;
455             case 'PLAIN':
456                 $result = $this->_authPlain($uid, $pwd);
457                 break;
458             default:
459                 $result = PEAR::raiseError("$method is not a supported authentication method");
460                 break;
461         }
462
463         /* If an error was encountered, return the PEAR_Error object. */
464         if (PEAR::isError($result)) {
465             return $result;
466         }
467
468         return true;
469     }
470
471     /**
472      * Authenticates the user using the DIGEST-MD5 method.
473      *
474      * @param string The userid to authenticate as.
475      * @param string The password to authenticate with.
476      *
477      * @return mixed Returns a PEAR_Error with an error message on any
478      *               kind of failure, or true on success.
479      * @access private
480      * @since  1.1.0
481      */
482     function _authDigest_MD5($uid, $pwd)
483     {
484         if (PEAR::isError($error = $this->_put('AUTH', 'DIGEST-MD5'))) {
485             return $error;
486         }
487         /* 334: Continue authentication request */
488         if (PEAR::isError($error = $this->_parseResponse(334))) {
489             /* 503: Error: already authenticated */
490             if ($this->_code === 503) {
491                 return true;
492             }
493             return $error;
494         }
495
496         $challenge = base64_decode($this->_arguments[0]);
497         $digest = &Auth_SASL::factory('digestmd5');
498         $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge,
499                                                        $this->host, "smtp"));
500
501         if (PEAR::isError($error = $this->_put($auth_str))) {
502             return $error;
503         }
504         /* 334: Continue authentication request */
505         if (PEAR::isError($error = $this->_parseResponse(334))) {
506             return $error;
507         }
508
509         /* We don't use the protocol's third step because SMTP doesn't
510          * allow subsequent authentication, so we just silently ignore
511          * it. */
512         if (PEAR::isError($error = $this->_put(' '))) {
513             return $error;
514         }
515         /* 235: Authentication successful */
516         if (PEAR::isError($error = $this->_parseResponse(235))) {
517             return $error;
518         }
519     }
520
521     /**
522      * Authenticates the user using the CRAM-MD5 method.
523      *
524      * @param string The userid to authenticate as.
525      * @param string The password to authenticate with.
526      *
527      * @return mixed Returns a PEAR_Error with an error message on any
528      *               kind of failure, or true on success.
529      * @access private
530      * @since  1.1.0
531      */
532     function _authCRAM_MD5($uid, $pwd)
533     {
534         if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) {
535             return $error;
536         }
537         /* 334: Continue authentication request */
538         if (PEAR::isError($error = $this->_parseResponse(334))) {
539             /* 503: Error: already authenticated */
540             if ($this->_code === 503) {
541                 return true;
542             }
543             return $error;
544         }
545
546         $challenge = base64_decode($this->_arguments[0]);
547         $cram = &Auth_SASL::factory('crammd5');
548         $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
549
550         if (PEAR::isError($error = $this->_put($auth_str))) {
551             return $error;
552         }
553
554         /* 235: Authentication successful */
555         if (PEAR::isError($error = $this->_parseResponse(235))) {
556             return $error;
557         }
558     }
559
560     /**
561      * Authenticates the user using the LOGIN method.
562      *
563      * @param string The userid to authenticate as.
564      * @param string The password to authenticate with.
565      *
566      * @return mixed Returns a PEAR_Error with an error message on any
567      *               kind of failure, or true on success.
568      * @access private
569      * @since  1.1.0
570      */
571     function _authLogin($uid, $pwd)
572     {
573         if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) {
574             return $error;
575         }
576         /* 334: Continue authentication request */
577         if (PEAR::isError($error = $this->_parseResponse(334))) {
578             /* 503: Error: already authenticated */
579             if ($this->_code === 503) {
580                 return true;
581             }
582             return $error;
583         }
584
585         if (PEAR::isError($error = $this->_put(base64_encode($uid)))) {
586             return $error;
587         }
588         /* 334: Continue authentication request */
589         if (PEAR::isError($error = $this->_parseResponse(334))) {
590             return $error;
591         }
592
593         if (PEAR::isError($error = $this->_put(base64_encode($pwd)))) {
594             return $error;
595         }
596
597         /* 235: Authentication successful */
598         if (PEAR::isError($error = $this->_parseResponse(235))) {
599             return $error;
600         }
601
602         return true;
603     }
604
605     /**
606      * Authenticates the user using the PLAIN method.
607      *
608      * @param string The userid to authenticate as.
609      * @param string The password to authenticate with.
610      *
611      * @return mixed Returns a PEAR_Error with an error message on any
612      *               kind of failure, or true on success.
613      * @access private
614      * @since  1.1.0
615      */
616     function _authPlain($uid, $pwd)
617     {
618         if (PEAR::isError($error = $this->_put('AUTH', 'PLAIN'))) {
619             return $error;
620         }
621         /* 334: Continue authentication request */
622         if (PEAR::isError($error = $this->_parseResponse(334))) {
623             /* 503: Error: already authenticated */
624             if ($this->_code === 503) {
625                 return true;
626             }
627             return $error;
628         }
629
630         $auth_str = base64_encode(chr(0) . $uid . chr(0) . $pwd);
631
632         if (PEAR::isError($error = $this->_put($auth_str))) {
633             return $error;
634         }
635
636         /* 235: Authentication successful */
637         if (PEAR::isError($error = $this->_parseResponse(235))) {
638             return $error;
639         }
640
641         return true;
642     }
643
644     /**
645      * Send the HELO command.
646      *
647      * @param string The domain name to say we are.
648      *
649      * @return mixed Returns a PEAR_Error with an error message on any
650      *               kind of failure, or true on success.
651      * @access public
652      * @since  1.0
653      */
654     function helo($domain)
655     {
656         if (PEAR::isError($error = $this->_put('HELO', $domain))) {
657             return $error;
658         }
659         if (PEAR::isError($error = $this->_parseResponse(250))) {
660             return $error;
661         }
662
663         return true;
664     }
665
666     /**
667      * Send the MAIL FROM: command.
668      *
669      * @param string The sender (reverse path) to set.
670      *
671      * @param array optional arguments. Currently supported:
672      *        verp   boolean or string. If true or string
673      *               verp is enabled. If string the characters
674      *               are considered verp separators.
675      *
676      * @return mixed Returns a PEAR_Error with an error message on any
677      *               kind of failure, or true on success.
678      * @access public
679      * @since  1.0
680      */
681     function mailFrom($sender, $args = array())
682     {
683         $argstr = '';
684
685         if (isset($args['verp'])) {
686             /* XVERP */
687             if ($args['verp'] === true) {
688                 $argstr .= ' XVERP';
689
690             /* XVERP=something */
691             } elseif (trim($args['verp'])) {
692                 $argstr .= ' XVERP=' . $args['verp'];
693             }
694         }
695
696         if (PEAR::isError($error = $this->_put('MAIL', "FROM:<$sender>$argstr"))) {
697             return $error;
698         }
699         if (PEAR::isError($error = $this->_parseResponse(250))) {
700             return $error;
701         }
702
703         return true;
704     }
705
706     /**
707      * Send the RCPT TO: command.
708      *
709      * @param string The recipient (forward path) to add.
710      *
711      * @return mixed Returns a PEAR_Error with an error message on any
712      *               kind of failure, or true on success.
713      * @access public
714      * @since  1.0
715      */
716     function rcptTo($recipient)
717     {
718         if (PEAR::isError($error = $this->_put('RCPT', "TO:<$recipient>"))) {
719             return $error;
720         }
721         if (PEAR::isError($error = $this->_parseResponse(array(250, 251)))) {
722             return $error;
723         }
724
725         return true;
726     }
727
728     /**
729      * Quote the data so that it meets SMTP standards.
730      *
731      * This is provided as a separate public function to facilitate
732      * easier overloading for the cases where it is desirable to
733      * customize the quoting behavior.
734      *
735      * @param string $data  The message text to quote. The string must be passed
736      *                      by reference, and the text will be modified in place.
737      *
738      * @access public
739      * @since  1.2
740      */
741     function quotedata(&$data)
742     {
743         /* Change Unix (\n) and Mac (\r) linefeeds into
744          * Internet-standard CRLF (\r\n) linefeeds. */
745         $data = preg_replace(array('/(?<!\r)\n/','/\r(?!\n)/'), "\r\n", $data);
746
747         /* Because a single leading period (.) signifies an end to the
748          * data, legitimate leading periods need to be "doubled"
749          * (e.g. '..'). */
750         $data = str_replace("\n.", "\n..", $data);
751     }
752
753     /**
754      * Send the DATA command.
755      *
756      * @param string $data  The message body to send.
757      *
758      * @return mixed Returns a PEAR_Error with an error message on any
759      *               kind of failure, or true on success.
760      * @access public
761      * @since  1.0
762      */
763     function data(&$data)
764     {
765         /* RFC 1870, section 3, subsection 3 states "a value of zero
766          * indicates that no fixed maximum message size is in force".
767          * Furthermore, it says that if "the parameter is omitted no
768          * information is conveyed about the server's fixed maximum
769          * message size". */
770         if (isset($this->_esmtp['SIZE']) && ($this->_esmtp['SIZE'] > 0)) {
771             if (strlen($data) >= $this->_esmtp['SIZE']) {
772                 $this->disconnect();
773                 return PEAR::raiseError('Message size excedes the server limit');
774             }
775         }
776
777         /* Quote the data based on the SMTP standards. */
778         $this->quotedata($data);
779
780         if (PEAR::isError($error = $this->_put('DATA'))) {
781             return $error;
782         }
783         if (PEAR::isError($error = $this->_parseResponse(354))) {
784             return $error;
785         }
786
787         $data .= "\r\n.\r\n";
788         if (PEAR::isError($result = $this->_send($data))) {
789             return $result;
790         }
791         if (PEAR::isError($error = $this->_parseResponse(250))) {
792             return $error;
793         }
794
795         return true;
796     }
797
798     /**
799      * Send the SEND FROM: command.
800      *
801      * @param string The reverse path to send.
802      *
803      * @return mixed Returns a PEAR_Error with an error message on any
804      *               kind of failure, or true on success.
805      * @access public
806      * @since  1.2.6
807      */
808     function sendFrom($path)
809     {
810         if (PEAR::isError($error = $this->_put('SEND', "FROM:<$path>"))) {
811             return $error;
812         }
813         if (PEAR::isError($error = $this->_parseResponse(250))) {
814             return $error;
815         }
816
817         return true;
818     }
819
820     /**
821      * Backwards-compatibility wrapper for sendFrom().
822      *
823      * @param string The reverse path to send.
824      *
825      * @return mixed Returns a PEAR_Error with an error message on any
826      *               kind of failure, or true on success.
827      *
828      * @access      public
829      * @since       1.0
830      * @deprecated  1.2.6
831      */
832     function send_from($path)
833     {
834         return sendFrom($path);
835     }
836
837     /**
838      * Send the SOML FROM: command.
839      *
840      * @param string The reverse path to send.
841      *
842      * @return mixed Returns a PEAR_Error with an error message on any
843      *               kind of failure, or true on success.
844      * @access public
845      * @since  1.2.6
846      */
847     function somlFrom($path)
848     {
849         if (PEAR::isError($error = $this->_put('SOML', "FROM:<$path>"))) {
850             return $error;
851         }
852         if (PEAR::isError($error = $this->_parseResponse(250))) {
853             return $error;
854         }
855
856         return true;
857     }
858
859     /**
860      * Backwards-compatibility wrapper for somlFrom().
861      *
862      * @param string The reverse path to send.
863      *
864      * @return mixed Returns a PEAR_Error with an error message on any
865      *               kind of failure, or true on success.
866      *
867      * @access      public
868      * @since       1.0
869      * @deprecated  1.2.6
870      */
871     function soml_from($path)
872     {
873         return somlFrom($path);
874     }
875
876     /**
877      * Send the SAML FROM: command.
878      *
879      * @param string The reverse path to send.
880      *
881      * @return mixed Returns a PEAR_Error with an error message on any
882      *               kind of failure, or true on success.
883      * @access public
884      * @since  1.2.6
885      */
886     function samlFrom($path)
887     {
888         if (PEAR::isError($error = $this->_put('SAML', "FROM:<$path>"))) {
889             return $error;
890         }
891         if (PEAR::isError($error = $this->_parseResponse(250))) {
892             return $error;
893         }
894
895         return true;
896     }
897
898     /**
899      * Backwards-compatibility wrapper for samlFrom().
900      *
901      * @param string The reverse path to send.
902      *
903      * @return mixed Returns a PEAR_Error with an error message on any
904      *               kind of failure, or true on success.
905      *
906      * @access      public
907      * @since       1.0
908      * @deprecated  1.2.6
909      */
910     function saml_from($path)
911     {
912         return samlFrom($path);
913     }
914
915     /**
916      * Send the RSET command.
917      *
918      * @return mixed Returns a PEAR_Error with an error message on any
919      *               kind of failure, or true on success.
920      * @access public
921      * @since  1.0
922      */
923     function rset()
924     {
925         if (PEAR::isError($error = $this->_put('RSET'))) {
926             return $error;
927         }
928         if (PEAR::isError($error = $this->_parseResponse(250))) {
929             return $error;
930         }
931
932         return true;
933     }
934
935     /**
936      * Send the VRFY command.
937      *
938      * @param string The string to verify
939      *
940      * @return mixed Returns a PEAR_Error with an error message on any
941      *               kind of failure, or true on success.
942      * @access public
943      * @since  1.0
944      */
945     function vrfy($string)
946     {
947         /* Note: 251 is also a valid response code */
948         if (PEAR::isError($error = $this->_put('VRFY', $string))) {
949             return $error;
950         }
951         if (PEAR::isError($error = $this->_parseResponse(array(250, 252)))) {
952             return $error;
953         }
954
955         return true;
956     }
957
958     /**
959      * Send the NOOP command.
960      *
961      * @return mixed Returns a PEAR_Error with an error message on any
962      *               kind of failure, or true on success.
963      * @access public
964      * @since  1.0
965      */
966     function noop()
967     {
968         if (PEAR::isError($error = $this->_put('NOOP'))) {
969             return $error;
970         }
971         if (PEAR::isError($error = $this->_parseResponse(250))) {
972             return $error;
973         }
974
975         return true;
976     }
977
978     /**
979      * Backwards-compatibility method.  identifySender()'s functionality is
980      * now handled internally.
981      *
982      * @return  boolean     This method always return true.
983      *
984      * @access  public
985      * @since   1.0
986      */
987     function identifySender()
988     {
989         return true;
990     }
991
992 }