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