source: trunk/library/Net/SMTP.php @ 7655

Revision 7655, 42.4 KB checked in by douglasz, 11 years ago (diff)

Ticket #3236 - Melhorias de performance no codigo do Expresso.

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