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