Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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