source: github/program/lib/Net/SMTP.php @ 5228a55

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