source: branches/version-2_5-dev/data/module/Net/SMTP.php @ 20119

Revision 20119, 37.6 KB checked in by nanasess, 12 years ago (diff)

module 以下は svn:keywords を除外

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