source: github/program/lib/Net/SMTP.php @ 8ffd08a

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