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