run:R W Run
6.72 KB
2026-03-11 16:18:51
R W Run
1.23 KB
2026-03-11 16:18:51
R W Run
3.7 KB
2026-03-11 16:18:51
R W Run
1.5 KB
2026-03-11 16:18:51
R W Run
186.56 KB
2026-03-11 16:18:51
R W Run
12.06 KB
2026-03-11 16:18:51
R W Run
51.25 KB
2026-03-11 16:18:51
R W Run
error_log
📄SMTP.php
1<?php
2
3/**
4 * PHPMailer RFC821 SMTP email transport class.
5 * PHP Version 5.5.
6 *
7 * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
8 *
9 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
10 * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
11 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
12 * @author Brent R. Matzelle (original founder)
13 * @copyright 2012 - 2020 Marcus Bointon
14 * @copyright 2010 - 2012 Jim Jagielski
15 * @copyright 2004 - 2009 Andy Prevost
16 * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
17 * @note This program is distributed in the hope that it will be useful - WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE.
20 */
21
22namespace PHPMailer\PHPMailer;
23
24/**
25 * PHPMailer RFC821 SMTP email transport class.
26 * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
27 *
28 * @author Chris Ryan
29 * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
30 */
31class SMTP
32{
33 /**
34 * The PHPMailer SMTP version number.
35 *
36 * @var string
37 */
38 const VERSION = '7.0.0';
39
40 /**
41 * SMTP line break constant.
42 *
43 * @var string
44 */
45 const LE = "\r\n";
46
47 /**
48 * The SMTP port to use if one is not specified.
49 *
50 * @var int
51 */
52 const DEFAULT_PORT = 25;
53
54 /**
55 * The SMTPs port to use if one is not specified.
56 *
57 * @var int
58 */
59 const DEFAULT_SECURE_PORT = 465;
60
61 /**
62 * The maximum line length allowed by RFC 5321 section 4.5.3.1.6,
63 * *excluding* a trailing CRLF break.
64 *
65 * @see https://www.rfc-editor.org/rfc/rfc5321#section-4.5.3.1.6
66 *
67 * @var int
68 */
69 const MAX_LINE_LENGTH = 998;
70
71 /**
72 * The maximum line length allowed for replies in RFC 5321 section 4.5.3.1.5,
73 * *including* a trailing CRLF line break.
74 *
75 * @see https://www.rfc-editor.org/rfc/rfc5321#section-4.5.3.1.5
76 *
77 * @var int
78 */
79 const MAX_REPLY_LENGTH = 512;
80
81 /**
82 * Debug level for no output.
83 *
84 * @var int
85 */
86 const DEBUG_OFF = 0;
87
88 /**
89 * Debug level to show client -> server messages.
90 *
91 * @var int
92 */
93 const DEBUG_CLIENT = 1;
94
95 /**
96 * Debug level to show client -> server and server -> client messages.
97 *
98 * @var int
99 */
100 const DEBUG_SERVER = 2;
101
102 /**
103 * Debug level to show connection status, client -> server and server -> client messages.
104 *
105 * @var int
106 */
107 const DEBUG_CONNECTION = 3;
108
109 /**
110 * Debug level to show all messages.
111 *
112 * @var int
113 */
114 const DEBUG_LOWLEVEL = 4;
115
116 /**
117 * Debug output level.
118 * Options:
119 * * self::DEBUG_OFF (`0`) No debug output, default
120 * * self::DEBUG_CLIENT (`1`) Client commands
121 * * self::DEBUG_SERVER (`2`) Client commands and server responses
122 * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
123 * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages.
124 *
125 * @var int
126 */
127 public $do_debug = self::DEBUG_OFF;
128
129 /**
130 * How to handle debug output.
131 * Options:
132 * * `echo` Output plain-text as-is, appropriate for CLI
133 * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
134 * * `error_log` Output to error log as configured in php.ini
135 * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
136 *
137 * ```php
138 * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
139 * ```
140 *
141 * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
142 * level output is used:
143 *
144 * ```php
145 * $mail->Debugoutput = new myPsr3Logger;
146 * ```
147 *
148 * @var string|callable|\Psr\Log\LoggerInterface
149 */
150 public $Debugoutput = 'echo';
151
152 /**
153 * Whether to use VERP.
154 *
155 * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path
156 * @see https://www.postfix.org/VERP_README.html Info on VERP
157 *
158 * @var bool
159 */
160 public $do_verp = false;
161
162 /**
163 * Whether to use SMTPUTF8.
164 *
165 * @see https://www.rfc-editor.org/rfc/rfc6531
166 *
167 * @var bool
168 */
169 public $do_smtputf8 = false;
170
171 /**
172 * The timeout value for connection, in seconds.
173 * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
174 * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
175 *
176 * @see https://www.rfc-editor.org/rfc/rfc2821#section-4.5.3.2
177 *
178 * @var int
179 */
180 public $Timeout = 300;
181
182 /**
183 * How long to wait for commands to complete, in seconds.
184 * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
185 *
186 * @var int
187 */
188 public $Timelimit = 300;
189
190 /**
191 * Patterns to extract an SMTP transaction id from reply to a DATA command.
192 * The first capture group in each regex will be used as the ID.
193 * MS ESMTP returns the message ID, which may not be correct for internal tracking.
194 *
195 * @var string[]
196 */
197 protected $smtp_transaction_id_patterns = [
198 'exim' => '/[\d]{3} OK id=(.*)/',
199 'sendmail' => '/[\d]{3} 2\.0\.0 (.*) Message/',
200 'postfix' => '/[\d]{3} 2\.0\.0 Ok: queued as (.*)/',
201 'Microsoft_ESMTP' => '/[0-9]{3} 2\.[\d]\.0 (.*)@(?:.*) Queued mail for delivery/',
202 'Amazon_SES' => '/[\d]{3} Ok (.*)/',
203 'SendGrid' => '/[\d]{3} Ok: queued as (.*)/',
204 'CampaignMonitor' => '/[\d]{3} 2\.0\.0 OK:([a-zA-Z\d]{48})/',
205 'Haraka' => '/[\d]{3} Message Queued \((.*)\)/',
206 'ZoneMTA' => '/[\d]{3} Message queued as (.*)/',
207 'Mailjet' => '/[\d]{3} OK queued as (.*)/',
208 'Gsmtp' => '/[\d]{3} 2\.0\.0 OK (.*) - gsmtp/',
209 ];
210
211 /**
212 * Allowed SMTP XCLIENT attributes.
213 * Must be allowed by the SMTP server. EHLO response is not checked.
214 *
215 * @see https://www.postfix.org/XCLIENT_README.html
216 *
217 * @var array
218 */
219 public static $xclient_allowed_attributes = [
220 'NAME', 'ADDR', 'PORT', 'PROTO', 'HELO', 'LOGIN', 'DESTADDR', 'DESTPORT'
221 ];
222
223 /**
224 * The last transaction ID issued in response to a DATA command,
225 * if one was detected.
226 *
227 * @var string|bool|null
228 */
229 protected $last_smtp_transaction_id;
230
231 /**
232 * The socket for the server connection.
233 *
234 * @var ?resource
235 */
236 protected $smtp_conn;
237
238 /**
239 * Error information, if any, for the last SMTP command.
240 *
241 * @var array
242 */
243 protected $error = [
244 'error' => '',
245 'detail' => '',
246 'smtp_code' => '',
247 'smtp_code_ex' => '',
248 ];
249
250 /**
251 * The reply the server sent to us for HELO.
252 * If null, no HELO string has yet been received.
253 *
254 * @var string|null
255 */
256 protected $helo_rply;
257
258 /**
259 * The set of SMTP extensions sent in reply to EHLO command.
260 * Indexes of the array are extension names.
261 * Value at index 'HELO' or 'EHLO' (according to command that was sent)
262 * represents the server name. In case of HELO it is the only element of the array.
263 * Other values can be boolean TRUE or an array containing extension options.
264 * If null, no HELO/EHLO string has yet been received.
265 *
266 * @var array|null
267 */
268 protected $server_caps;
269
270 /**
271 * The most recent reply received from the server.
272 *
273 * @var string
274 */
275 protected $last_reply = '';
276
277 /**
278 * Output debugging info via a user-selected method.
279 *
280 * @param string $str Debug string to output
281 * @param int $level The debug level of this message; see DEBUG_* constants
282 *
283 * @see SMTP::$Debugoutput
284 * @see SMTP::$do_debug
285 */
286 protected function edebug($str, $level = 0)
287 {
288 if ($level > $this->do_debug) {
289 return;
290 }
291 //Is this a PSR-3 logger?
292 if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
293 //Remove trailing line breaks potentially added by calls to SMTP::client_send()
294 $this->Debugoutput->debug(rtrim($str, "\r\n"));
295
296 return;
297 }
298 //Avoid clash with built-in function names
299 if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {
300 call_user_func($this->Debugoutput, $str, $level);
301
302 return;
303 }
304 switch ($this->Debugoutput) {
305 case 'error_log':
306 //Don't output, just log
307 /** @noinspection ForgottenDebugOutputInspection */
308 error_log($str);
309 break;
310 case 'html':
311 //Cleans up output a bit for a better looking, HTML-safe output
312 echo gmdate('Y-m-d H:i:s'), ' ', htmlentities(
313 preg_replace('/[\r\n]+/', '', $str),
314 ENT_QUOTES,
315 'UTF-8'
316 ), "<br>\n";
317 break;
318 case 'echo':
319 default:
320 //Normalize line breaks
321 $str = preg_replace('/\r\n|\r/m', "\n", $str);
322 echo gmdate('Y-m-d H:i:s'),
323 "\t",
324 //Trim trailing space
325 trim(
326 //Indent for readability, except for trailing break
327 str_replace(
328 "\n",
329 "\n \t ",
330 trim($str)
331 )
332 ),
333 "\n";
334 }
335 }
336
337 /**
338 * Connect to an SMTP server.
339 *
340 * @param string $host SMTP server IP or host name
341 * @param int $port The port number to connect to
342 * @param int $timeout How long to wait for the connection to open
343 * @param array $options An array of options for stream_context_create()
344 *
345 * @return bool
346 */
347 public function connect($host, $port = null, $timeout = 30, $options = [])
348 {
349 //Clear errors to avoid confusion
350 $this->setError('');
351 //Make sure we are __not__ connected
352 if ($this->connected()) {
353 //Already connected, generate error
354 $this->setError('Already connected to a server');
355
356 return false;
357 }
358 if (empty($port)) {
359 $port = self::DEFAULT_PORT;
360 }
361 //Connect to the SMTP server
362 $this->edebug(
363 "Connection: opening to $host:$port, timeout=$timeout, options=" .
364 (count($options) > 0 ? var_export($options, true) : 'array()'),
365 self::DEBUG_CONNECTION
366 );
367
368 $this->smtp_conn = $this->getSMTPConnection($host, $port, $timeout, $options);
369
370 if ($this->smtp_conn === false) {
371 //Error info already set inside `getSMTPConnection()`
372 return false;
373 }
374
375 $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
376
377 //Get any announcement
378 $this->last_reply = $this->get_lines();
379 $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
380 $responseCode = (int)substr($this->last_reply, 0, 3);
381 if ($responseCode === 220) {
382 return true;
383 }
384 //Anything other than a 220 response means something went wrong
385 //RFC 5321 says the server will wait for us to send a QUIT in response to a 554 error
386 //https://www.rfc-editor.org/rfc/rfc5321#section-3.1
387 if ($responseCode === 554) {
388 $this->quit();
389 }
390 //This will handle 421 responses which may not wait for a QUIT (e.g. if the server is being shut down)
391 $this->edebug('Connection: closing due to error', self::DEBUG_CONNECTION);
392 $this->close();
393 return false;
394 }
395
396 /**
397 * Create connection to the SMTP server.
398 *
399 * @param string $host SMTP server IP or host name
400 * @param int $port The port number to connect to
401 * @param int $timeout How long to wait for the connection to open
402 * @param array $options An array of options for stream_context_create()
403 *
404 * @return false|resource
405 */
406 protected function getSMTPConnection($host, $port = null, $timeout = 30, $options = [])
407 {
408 static $streamok;
409 //This is enabled by default since 5.0.0 but some providers disable it
410 //Check this once and cache the result
411 if (null === $streamok) {
412 $streamok = function_exists('stream_socket_client');
413 }
414
415 $errno = 0;
416 $errstr = '';
417 if ($streamok) {
418 $socket_context = stream_context_create($options);
419 set_error_handler(function () {
420 call_user_func_array([$this, 'errorHandler'], func_get_args());
421 });
422 $connection = stream_socket_client(
423 $host . ':' . $port,
424 $errno,
425 $errstr,
426 $timeout,
427 STREAM_CLIENT_CONNECT,
428 $socket_context
429 );
430 } else {
431 //Fall back to fsockopen which should work in more places, but is missing some features
432 $this->edebug(
433 'Connection: stream_socket_client not available, falling back to fsockopen',
434 self::DEBUG_CONNECTION
435 );
436 set_error_handler(function () {
437 call_user_func_array([$this, 'errorHandler'], func_get_args());
438 });
439 $connection = fsockopen(
440 $host,
441 $port,
442 $errno,
443 $errstr,
444 $timeout
445 );
446 }
447 restore_error_handler();
448
449 //Verify we connected properly
450 if (!is_resource($connection)) {
451 $this->setError(
452 'Failed to connect to server',
453 '',
454 (string) $errno,
455 $errstr
456 );
457 $this->edebug(
458 'SMTP ERROR: ' . $this->error['error']
459 . ": $errstr ($errno)",
460 self::DEBUG_CLIENT
461 );
462
463 return false;
464 }
465
466 //SMTP server can take longer to respond, give longer timeout for first read
467 //Windows does not have support for this timeout function
468 if (strpos(PHP_OS, 'WIN') !== 0) {
469 $max = (int)ini_get('max_execution_time');
470 //Don't bother if unlimited, or if set_time_limit is disabled
471 if (0 !== $max && $timeout > $max && strpos(ini_get('disable_functions'), 'set_time_limit') === false) {
472 @set_time_limit($timeout);
473 }
474 stream_set_timeout($connection, $timeout, 0);
475 }
476
477 return $connection;
478 }
479
480 /**
481 * Initiate a TLS (encrypted) session.
482 *
483 * @return bool
484 */
485 public function startTLS()
486 {
487 if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
488 return false;
489 }
490
491 //Allow the best TLS version(s) we can
492 $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
493
494 //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
495 //so add them back in manually if we can
496 if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
497 $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
498 $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
499 }
500
501 //Begin encrypted connection
502 set_error_handler(function () {
503 call_user_func_array([$this, 'errorHandler'], func_get_args());
504 });
505 $crypto_ok = stream_socket_enable_crypto(
506 $this->smtp_conn,
507 true,
508 $crypto_method
509 );
510 restore_error_handler();
511
512 return (bool) $crypto_ok;
513 }
514
515 /**
516 * Perform SMTP authentication.
517 * Must be run after hello().
518 *
519 * @see hello()
520 *
521 * @param string $username The user name
522 * @param string $password The password
523 * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2)
524 * @param OAuthTokenProvider $OAuth An optional OAuthTokenProvider instance for XOAUTH2 authentication
525 *
526 * @return bool True if successfully authenticated
527 */
528 public function authenticate(
529 $username,
530 $password,
531 $authtype = null,
532 $OAuth = null
533 ) {
534 if (!$this->server_caps) {
535 $this->setError('Authentication is not allowed before HELO/EHLO');
536
537 return false;
538 }
539
540 if (array_key_exists('EHLO', $this->server_caps)) {
541 //SMTP extensions are available; try to find a proper authentication method
542 if (!array_key_exists('AUTH', $this->server_caps)) {
543 $this->setError('Authentication is not allowed at this stage');
544 //'at this stage' means that auth may be allowed after the stage changes
545 //e.g. after STARTTLS
546
547 return false;
548 }
549
550 $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL);
551 $this->edebug(
552 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
553 self::DEBUG_LOWLEVEL
554 );
555
556 //If we have requested a specific auth type, check the server supports it before trying others
557 if (null !== $authtype && !in_array($authtype, $this->server_caps['AUTH'], true)) {
558 $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL);
559 $authtype = null;
560 }
561
562 if (empty($authtype)) {
563 //If no auth mechanism is specified, attempt to use these, in this order
564 //Try CRAM-MD5 first as it's more secure than the others
565 foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) {
566 if (in_array($method, $this->server_caps['AUTH'], true)) {
567 $authtype = $method;
568 break;
569 }
570 }
571 if (empty($authtype)) {
572 $this->setError('No supported authentication methods found');
573
574 return false;
575 }
576 $this->edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
577 }
578
579 if (!in_array($authtype, $this->server_caps['AUTH'], true)) {
580 $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
581
582 return false;
583 }
584 } elseif (empty($authtype)) {
585 $authtype = 'LOGIN';
586 }
587 switch ($authtype) {
588 case 'PLAIN':
589 //Start authentication
590 if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
591 return false;
592 }
593 //Send encoded username and password
594 if (
595 //Format from https://www.rfc-editor.org/rfc/rfc4616#section-2
596 //We skip the first field (it's forgery), so the string starts with a null byte
597 !$this->sendCommand(
598 'User & Password',
599 base64_encode("\0" . $username . "\0" . $password),
600 235
601 )
602 ) {
603 return false;
604 }
605 break;
606 case 'LOGIN':
607 //Start authentication
608 if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
609 return false;
610 }
611 if (!$this->sendCommand('Username', base64_encode($username), 334)) {
612 return false;
613 }
614 if (!$this->sendCommand('Password', base64_encode($password), 235)) {
615 return false;
616 }
617 break;
618 case 'CRAM-MD5':
619 //Start authentication
620 if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
621 return false;
622 }
623 //Get the challenge
624 $challenge = base64_decode(substr($this->last_reply, 4));
625
626 //Build the response
627 $response = $username . ' ' . $this->hmac($challenge, $password);
628
629 //send encoded credentials
630 return $this->sendCommand('Username', base64_encode($response), 235);
631 case 'XOAUTH2':
632 //The OAuth instance must be set up prior to requesting auth.
633 if (null === $OAuth) {
634 return false;
635 }
636 $oauth = $OAuth->getOauth64();
637 /*
638 * An SMTP command line can have a maximum length of 512 bytes, including the command name,
639 * so the base64-encoded OAUTH token has a maximum length of:
640 * 512 - 13 (AUTH XOAUTH2) - 2 (CRLF) = 497 bytes
641 * If the token is longer than that, the command and the token must be sent separately as described in
642 * https://www.rfc-editor.org/rfc/rfc4954#section-4
643 */
644 if ($oauth === '') {
645 //Sending an empty auth token is legitimate, but it must be encoded as '='
646 //to indicate it's not a 2-part command
647 if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 =', 235)) {
648 return false;
649 }
650 } elseif (strlen($oauth) <= 497) {
651 //Authenticate using a token in the initial-response part
652 if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
653 return false;
654 }
655 } else {
656 //The token is too long, so we need to send it in two parts.
657 //Send the auth command without a token and expect a 334
658 if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2', 334)) {
659 return false;
660 }
661 //Send the token
662 if (!$this->sendCommand('OAuth TOKEN', $oauth, [235, 334])) {
663 return false;
664 }
665 //If the server answers with 334, send an empty line and wait for a 235
666 if (
667 substr($this->last_reply, 0, 3) === '334'
668 && $this->sendCommand('AUTH End', '', 235)
669 ) {
670 return false;
671 }
672 }
673 break;
674 default:
675 $this->setError("Authentication method \"$authtype\" is not supported");
676
677 return false;
678 }
679
680 return true;
681 }
682
683 /**
684 * Calculate an MD5 HMAC hash.
685 * Works like hash_hmac('md5', $data, $key)
686 * in case that function is not available.
687 *
688 * @param string $data The data to hash
689 * @param string $key The key to hash with
690 *
691 * @return string
692 */
693 protected function hmac($data, $key)
694 {
695 if (function_exists('hash_hmac')) {
696 return hash_hmac('md5', $data, $key);
697 }
698
699 //The following borrowed from
700 //https://www.php.net/manual/en/function.mhash.php#27225
701
702 //RFC 2104 HMAC implementation for php.
703 //Creates an md5 HMAC.
704 //Eliminates the need to install mhash to compute a HMAC
705 //by Lance Rushing
706
707 $bytelen = 64; //byte length for md5
708 if (strlen($key) > $bytelen) {
709 $key = pack('H*', md5($key));
710 }
711 $key = str_pad($key, $bytelen, chr(0x00));
712 $ipad = str_pad('', $bytelen, chr(0x36));
713 $opad = str_pad('', $bytelen, chr(0x5c));
714 $k_ipad = $key ^ $ipad;
715 $k_opad = $key ^ $opad;
716
717 return md5($k_opad . pack('H*', md5($k_ipad . $data)));
718 }
719
720 /**
721 * Check connection state.
722 *
723 * @return bool True if connected
724 */
725 public function connected()
726 {
727 if (is_resource($this->smtp_conn)) {
728 $sock_status = stream_get_meta_data($this->smtp_conn);
729 if ($sock_status['eof']) {
730 //The socket is valid but we are not connected
731 $this->edebug(
732 'SMTP NOTICE: EOF caught while checking if connected',
733 self::DEBUG_CLIENT
734 );
735 $this->close();
736
737 return false;
738 }
739
740 return true; //everything looks good
741 }
742
743 return false;
744 }
745
746 /**
747 * Close the socket and clean up the state of the class.
748 * Don't use this function without first trying to use QUIT.
749 *
750 * @see quit()
751 */
752 public function close()
753 {
754 $this->server_caps = null;
755 $this->helo_rply = null;
756 if (is_resource($this->smtp_conn)) {
757 //Close the connection and cleanup
758 fclose($this->smtp_conn);
759 $this->smtp_conn = null; //Makes for cleaner serialization
760 $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
761 }
762 }
763
764 /**
765 * Send an SMTP DATA command.
766 * Issues a data command and sends the msg_data to the server,
767 * finalizing the mail transaction. $msg_data is the message
768 * that is to be sent with the headers. Each header needs to be
769 * on a single line followed by a <CRLF> with the message headers
770 * and the message body being separated by an additional <CRLF>.
771 * Implements RFC 821: DATA <CRLF>.
772 *
773 * @param string $msg_data Message data to send
774 *
775 * @return bool
776 */
777 public function data($msg_data)
778 {
779 //This will use the standard timelimit
780 if (!$this->sendCommand('DATA', 'DATA', 354)) {
781 return false;
782 }
783
784 /* The server is ready to accept data!
785 * According to rfc821 we should not send more than 1000 characters on a single line (including the LE)
786 * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
787 * smaller lines to fit within the limit.
788 * We will also look for lines that start with a '.' and prepend an additional '.'.
789 * NOTE: this does not count towards line-length limit.
790 */
791
792 //Normalize line breaks before exploding
793 $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
794
795 /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
796 * of the first line (':' separated) does not contain a space then it _should_ be a header, and we will
797 * process all lines before a blank line as headers.
798 */
799
800 $field = substr($lines[0], 0, strpos($lines[0], ':'));
801 $in_headers = false;
802 if (!empty($field) && strpos($field, ' ') === false) {
803 $in_headers = true;
804 }
805
806 foreach ($lines as $line) {
807 $lines_out = [];
808 if ($in_headers && $line === '') {
809 $in_headers = false;
810 }
811 //Break this line up into several smaller lines if it's too long
812 //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
813 while (isset($line[self::MAX_LINE_LENGTH])) {
814 //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
815 //so as to avoid breaking in the middle of a word
816 $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
817 //Deliberately matches both false and 0
818 if (!$pos) {
819 //No nice break found, add a hard break
820 $pos = self::MAX_LINE_LENGTH - 1;
821 $lines_out[] = substr($line, 0, $pos);
822 $line = substr($line, $pos);
823 } else {
824 //Break at the found point
825 $lines_out[] = substr($line, 0, $pos);
826 //Move along by the amount we dealt with
827 $line = substr($line, $pos + 1);
828 }
829 //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
830 if ($in_headers) {
831 $line = "\t" . $line;
832 }
833 }
834 $lines_out[] = $line;
835
836 //Send the lines to the server
837 foreach ($lines_out as $line_out) {
838 //Dot-stuffing as per RFC5321 section 4.5.2
839 //https://www.rfc-editor.org/rfc/rfc5321#section-4.5.2
840 if (!empty($line_out) && $line_out[0] === '.') {
841 $line_out = '.' . $line_out;
842 }
843 $this->client_send($line_out . static::LE, 'DATA');
844 }
845 }
846
847 //Message data has been sent, complete the command
848 //Increase timelimit for end of DATA command
849 $savetimelimit = $this->Timelimit;
850 $this->Timelimit *= 2;
851 $result = $this->sendCommand('DATA END', '.', 250);
852 $this->recordLastTransactionID();
853 //Restore timelimit
854 $this->Timelimit = $savetimelimit;
855
856 return $result;
857 }
858
859 /**
860 * Send an SMTP HELO or EHLO command.
861 * Used to identify the sending server to the receiving server.
862 * This makes sure that client and server are in a known state.
863 * Implements RFC 821: HELO <SP> <domain> <CRLF>
864 * and RFC 2821 EHLO.
865 *
866 * @param string $host The host name or IP to connect to
867 *
868 * @return bool
869 */
870 public function hello($host = '')
871 {
872 //Try extended hello first (RFC 2821)
873 if ($this->sendHello('EHLO', $host)) {
874 return true;
875 }
876
877 //Some servers shut down the SMTP service here (RFC 5321)
878 if (substr($this->helo_rply, 0, 3) == '421') {
879 return false;
880 }
881
882 return $this->sendHello('HELO', $host);
883 }
884
885 /**
886 * Send an SMTP HELO or EHLO command.
887 * Low-level implementation used by hello().
888 *
889 * @param string $hello The HELO string
890 * @param string $host The hostname to say we are
891 *
892 * @return bool
893 *
894 * @see hello()
895 */
896 protected function sendHello($hello, $host)
897 {
898 $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
899 $this->helo_rply = $this->last_reply;
900 if ($noerror) {
901 $this->parseHelloFields($hello);
902 } else {
903 $this->server_caps = null;
904 }
905
906 return $noerror;
907 }
908
909 /**
910 * Parse a reply to HELO/EHLO command to discover server extensions.
911 * In case of HELO, the only parameter that can be discovered is a server name.
912 *
913 * @param string $type `HELO` or `EHLO`
914 */
915 protected function parseHelloFields($type)
916 {
917 $this->server_caps = [];
918 $lines = explode("\n", $this->helo_rply);
919
920 foreach ($lines as $n => $s) {
921 //First 4 chars contain response code followed by - or space
922 $s = trim(substr($s, 4));
923 if (empty($s)) {
924 continue;
925 }
926 $fields = explode(' ', $s);
927 if (!empty($fields)) {
928 if (!$n) {
929 $name = $type;
930 $fields = $fields[0];
931 } else {
932 $name = array_shift($fields);
933 switch ($name) {
934 case 'SIZE':
935 $fields = ($fields ? $fields[0] : 0);
936 break;
937 case 'AUTH':
938 if (!is_array($fields)) {
939 $fields = [];
940 }
941 break;
942 default:
943 $fields = true;
944 }
945 }
946 $this->server_caps[$name] = $fields;
947 }
948 }
949 }
950
951 /**
952 * Send an SMTP MAIL command.
953 * Starts a mail transaction from the email address specified in
954 * $from. Returns true if successful or false otherwise. If True
955 * the mail transaction is started and then one or more recipient
956 * commands may be called followed by a data command.
957 * Implements RFC 821: MAIL <SP> FROM:<reverse-path> <CRLF> and
958 * two extensions, namely XVERP and SMTPUTF8.
959 *
960 * The server's EHLO response is not checked. If use of either
961 * extensions is enabled even though the server does not support
962 * that, mail submission will fail.
963 *
964 * XVERP is documented at https://www.postfix.org/VERP_README.html
965 * and SMTPUTF8 is specified in RFC 6531.
966 *
967 * @param string $from Source address of this message
968 *
969 * @return bool
970 */
971 public function mail($from)
972 {
973 $useVerp = ($this->do_verp ? ' XVERP' : '');
974 $useSmtputf8 = ($this->do_smtputf8 ? ' SMTPUTF8' : '');
975
976 return $this->sendCommand(
977 'MAIL FROM',
978 'MAIL FROM:<' . $from . '>' . $useSmtputf8 . $useVerp,
979 250
980 );
981 }
982
983 /**
984 * Send an SMTP QUIT command.
985 * Closes the socket if there is no error or the $close_on_error argument is true.
986 * Implements from RFC 821: QUIT <CRLF>.
987 *
988 * @param bool $close_on_error Should the connection close if an error occurs?
989 *
990 * @return bool
991 */
992 public function quit($close_on_error = true)
993 {
994 $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
995 $err = $this->error; //Save any error
996 if ($noerror || $close_on_error) {
997 $this->close();
998 $this->error = $err; //Restore any error from the quit command
999 }
1000
1001 return $noerror;
1002 }
1003
1004 /**
1005 * Send an SMTP RCPT command.
1006 * Sets the TO argument to $toaddr.
1007 * Returns true if the recipient was accepted false if it was rejected.
1008 * Implements from RFC 821: RCPT <SP> TO:<forward-path> <CRLF>.
1009 *
1010 * @param string $address The address the message is being sent to
1011 * @param string $dsn Comma separated list of DSN notifications. NEVER, SUCCESS, FAILURE
1012 * or DELAY. If you specify NEVER all other notifications are ignored.
1013 *
1014 * @return bool
1015 */
1016 public function recipient($address, $dsn = '')
1017 {
1018 if (empty($dsn)) {
1019 $rcpt = 'RCPT TO:<' . $address . '>';
1020 } else {
1021 $dsn = strtoupper($dsn);
1022 $notify = [];
1023
1024 if (strpos($dsn, 'NEVER') !== false) {
1025 $notify[] = 'NEVER';
1026 } else {
1027 foreach (['SUCCESS', 'FAILURE', 'DELAY'] as $value) {
1028 if (strpos($dsn, $value) !== false) {
1029 $notify[] = $value;
1030 }
1031 }
1032 }
1033
1034 $rcpt = 'RCPT TO:<' . $address . '> NOTIFY=' . implode(',', $notify);
1035 }
1036
1037 return $this->sendCommand(
1038 'RCPT TO',
1039 $rcpt,
1040 [250, 251]
1041 );
1042 }
1043
1044 /**
1045 * Send SMTP XCLIENT command to server and check its return code.
1046 *
1047 * @return bool True on success
1048 */
1049 public function xclient(array $vars)
1050 {
1051 $xclient_options = "";
1052 foreach ($vars as $key => $value) {
1053 if (in_array($key, SMTP::$xclient_allowed_attributes)) {
1054 $xclient_options .= " {$key}={$value}";
1055 }
1056 }
1057 if (!$xclient_options) {
1058 return true;
1059 }
1060 return $this->sendCommand('XCLIENT', 'XCLIENT' . $xclient_options, 250);
1061 }
1062
1063 /**
1064 * Send an SMTP RSET command.
1065 * Abort any transaction that is currently in progress.
1066 * Implements RFC 821: RSET <CRLF>.
1067 *
1068 * @return bool True on success
1069 */
1070 public function reset()
1071 {
1072 return $this->sendCommand('RSET', 'RSET', 250);
1073 }
1074
1075 /**
1076 * Send a command to an SMTP server and check its return code.
1077 *
1078 * @param string $command The command name - not sent to the server
1079 * @param string $commandstring The actual command to send
1080 * @param int|array $expect One or more expected integer success codes
1081 *
1082 * @return bool True on success
1083 */
1084 protected function sendCommand($command, $commandstring, $expect)
1085 {
1086 if (!$this->connected()) {
1087 $this->setError("Called $command without being connected");
1088
1089 return false;
1090 }
1091 //Reject line breaks in all commands
1092 if ((strpos($commandstring, "\n") !== false) || (strpos($commandstring, "\r") !== false)) {
1093 $this->setError("Command '$command' contained line breaks");
1094
1095 return false;
1096 }
1097 $this->client_send($commandstring . static::LE, $command);
1098
1099 $this->last_reply = $this->get_lines();
1100 //Fetch SMTP code and possible error code explanation
1101 $matches = [];
1102 if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) {
1103 $code = (int) $matches[1];
1104 $code_ex = (count($matches) > 2 ? $matches[2] : null);
1105 //Cut off error code from each response line
1106 $detail = preg_replace(
1107 "/{$code}[ -]" .
1108 ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
1109 '',
1110 $this->last_reply
1111 );
1112 } else {
1113 //Fall back to simple parsing if regex fails
1114 $code = (int) substr($this->last_reply, 0, 3);
1115 $code_ex = null;
1116 $detail = substr($this->last_reply, 4);
1117 }
1118
1119 $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
1120
1121 if (!in_array($code, (array) $expect, true)) {
1122 $this->setError(
1123 "$command command failed",
1124 $detail,
1125 $code,
1126 $code_ex
1127 );
1128 $this->edebug(
1129 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
1130 self::DEBUG_CLIENT
1131 );
1132
1133 return false;
1134 }
1135
1136 //Don't clear the error store when using keepalive
1137 if ($command !== 'RSET') {
1138 $this->setError('');
1139 }
1140
1141 return true;
1142 }
1143
1144 /**
1145 * Send an SMTP SAML command.
1146 * Starts a mail transaction from the email address specified in $from.
1147 * Returns true if successful or false otherwise. If True
1148 * the mail transaction is started and then one or more recipient
1149 * commands may be called followed by a data command. This command
1150 * will send the message to the users terminal if they are logged
1151 * in and send them an email.
1152 * Implements RFC 821: SAML <SP> FROM:<reverse-path> <CRLF>.
1153 *
1154 * @param string $from The address the message is from
1155 *
1156 * @return bool
1157 */
1158 public function sendAndMail($from)
1159 {
1160 return $this->sendCommand('SAML', "SAML FROM:$from", 250);
1161 }
1162
1163 /**
1164 * Send an SMTP VRFY command.
1165 *
1166 * @param string $name The name to verify
1167 *
1168 * @return bool
1169 */
1170 public function verify($name)
1171 {
1172 return $this->sendCommand('VRFY', "VRFY $name", [250, 251]);
1173 }
1174
1175 /**
1176 * Send an SMTP NOOP command.
1177 * Used to keep keep-alives alive, doesn't actually do anything.
1178 *
1179 * @return bool
1180 */
1181 public function noop()
1182 {
1183 return $this->sendCommand('NOOP', 'NOOP', 250);
1184 }
1185
1186 /**
1187 * Send an SMTP TURN command.
1188 * This is an optional command for SMTP that this class does not support.
1189 * This method is here to make the RFC821 Definition complete for this class
1190 * and _may_ be implemented in future.
1191 * Implements from RFC 821: TURN <CRLF>.
1192 *
1193 * @return bool
1194 */
1195 public function turn()
1196 {
1197 $this->setError('The SMTP TURN command is not implemented');
1198 $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
1199
1200 return false;
1201 }
1202
1203 /**
1204 * Send raw data to the server.
1205 *
1206 * @param string $data The data to send
1207 * @param string $command Optionally, the command this is part of, used only for controlling debug output
1208 *
1209 * @return int|bool The number of bytes sent to the server or false on error
1210 */
1211 public function client_send($data, $command = '')
1212 {
1213 //If SMTP transcripts are left enabled, or debug output is posted online
1214 //it can leak credentials, so hide credentials in all but lowest level
1215 if (
1216 self::DEBUG_LOWLEVEL > $this->do_debug &&
1217 in_array($command, ['User & Password', 'Username', 'Password'], true)
1218 ) {
1219 $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT);
1220 } else {
1221 $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT);
1222 }
1223 set_error_handler(function () {
1224 call_user_func_array([$this, 'errorHandler'], func_get_args());
1225 });
1226 $result = fwrite($this->smtp_conn, $data);
1227 restore_error_handler();
1228
1229 return $result;
1230 }
1231
1232 /**
1233 * Get the latest error.
1234 *
1235 * @return array
1236 */
1237 public function getError()
1238 {
1239 return $this->error;
1240 }
1241
1242 /**
1243 * Get SMTP extensions available on the server.
1244 *
1245 * @return array|null
1246 */
1247 public function getServerExtList()
1248 {
1249 return $this->server_caps;
1250 }
1251
1252 /**
1253 * Get metadata about the SMTP server from its HELO/EHLO response.
1254 * The method works in three ways, dependent on argument value and current state:
1255 * 1. HELO/EHLO has not been sent - returns null and populates $this->error.
1256 * 2. HELO has been sent -
1257 * $name == 'HELO': returns server name
1258 * $name == 'EHLO': returns boolean false
1259 * $name == any other string: returns null and populates $this->error
1260 * 3. EHLO has been sent -
1261 * $name == 'HELO'|'EHLO': returns the server name
1262 * $name == any other string: if extension $name exists, returns True
1263 * or its options (e.g. AUTH mechanisms supported). Otherwise returns False.
1264 *
1265 * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
1266 *
1267 * @return string|bool|null
1268 */
1269 public function getServerExt($name)
1270 {
1271 if (!$this->server_caps) {
1272 $this->setError('No HELO/EHLO was sent');
1273
1274 return null;
1275 }
1276
1277 if (!array_key_exists($name, $this->server_caps)) {
1278 if ('HELO' === $name) {
1279 return $this->server_caps['EHLO'];
1280 }
1281 if ('EHLO' === $name || array_key_exists('EHLO', $this->server_caps)) {
1282 return false;
1283 }
1284 $this->setError('HELO handshake was used; No information about server extensions available');
1285
1286 return null;
1287 }
1288
1289 return $this->server_caps[$name];
1290 }
1291
1292 /**
1293 * Get the last reply from the server.
1294 *
1295 * @return string
1296 */
1297 public function getLastReply()
1298 {
1299 return $this->last_reply;
1300 }
1301
1302 /**
1303 * Read the SMTP server's response.
1304 * Either before eof or socket timeout occurs on the operation.
1305 * With SMTP we can tell if we have more lines to read if the
1306 * 4th character is '-' symbol. If it is a space then we don't
1307 * need to read anything else.
1308 *
1309 * @return string
1310 */
1311 protected function get_lines()
1312 {
1313 //If the connection is bad, give up straight away
1314 if (!is_resource($this->smtp_conn)) {
1315 return '';
1316 }
1317 $data = '';
1318 $endtime = 0;
1319 stream_set_timeout($this->smtp_conn, $this->Timeout);
1320 if ($this->Timelimit > 0) {
1321 $endtime = time() + $this->Timelimit;
1322 }
1323 $selR = [$this->smtp_conn];
1324 $selW = null;
1325 while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
1326 //Must pass vars in here as params are by reference
1327 //solution for signals inspired by https://github.com/symfony/symfony/pull/6540
1328 set_error_handler(function () {
1329 call_user_func_array([$this, 'errorHandler'], func_get_args());
1330 });
1331 $n = stream_select($selR, $selW, $selW, $this->Timelimit);
1332 restore_error_handler();
1333
1334 if ($n === false) {
1335 $message = $this->getError()['detail'];
1336
1337 $this->edebug(
1338 'SMTP -> get_lines(): select failed (' . $message . ')',
1339 self::DEBUG_LOWLEVEL
1340 );
1341
1342 //stream_select returns false when the `select` system call is interrupted
1343 //by an incoming signal, try the select again
1344 if (
1345 stripos($message, 'interrupted system call') !== false ||
1346 (
1347 // on applications with a different locale than english, the message above is not found because
1348 // it's translated. So we also check for the SOCKET_EINTR constant which is defined under
1349 // Windows and UNIX-like platforms (if available on the platform).
1350 defined('SOCKET_EINTR') &&
1351 stripos($message, 'stream_select(): Unable to select [' . SOCKET_EINTR . ']') !== false
1352 )
1353 ) {
1354 $this->edebug(
1355 'SMTP -> get_lines(): retrying stream_select',
1356 self::DEBUG_LOWLEVEL
1357 );
1358 $this->setError('');
1359 continue;
1360 }
1361
1362 break;
1363 }
1364
1365 if (!$n) {
1366 $this->edebug(
1367 'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)',
1368 self::DEBUG_LOWLEVEL
1369 );
1370 break;
1371 }
1372
1373 //Deliberate noise suppression - errors are handled afterwards
1374 $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH);
1375 $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
1376 $data .= $str;
1377 //If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
1378 //or 4th character is a space or a line break char, we are done reading, break the loop.
1379 //String array access is a significant micro-optimisation over strlen
1380 if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") {
1381 break;
1382 }
1383 //Timed-out? Log and break
1384 $info = stream_get_meta_data($this->smtp_conn);
1385 if ($info['timed_out']) {
1386 $this->edebug(
1387 'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)',
1388 self::DEBUG_LOWLEVEL
1389 );
1390 break;
1391 }
1392 //Now check if reads took too long
1393 if ($endtime && time() > $endtime) {
1394 $this->edebug(
1395 'SMTP -> get_lines(): timelimit reached (' .
1396 $this->Timelimit . ' sec)',
1397 self::DEBUG_LOWLEVEL
1398 );
1399 break;
1400 }
1401 }
1402
1403 return $data;
1404 }
1405
1406 /**
1407 * Enable or disable VERP address generation.
1408 *
1409 * @param bool $enabled
1410 */
1411 public function setVerp($enabled = false)
1412 {
1413 $this->do_verp = $enabled;
1414 }
1415
1416 /**
1417 * Get VERP address generation mode.
1418 *
1419 * @return bool
1420 */
1421 public function getVerp()
1422 {
1423 return $this->do_verp;
1424 }
1425
1426 /**
1427 * Enable or disable use of SMTPUTF8.
1428 *
1429 * @param bool $enabled
1430 */
1431 public function setSMTPUTF8($enabled = false)
1432 {
1433 $this->do_smtputf8 = $enabled;
1434 }
1435
1436 /**
1437 * Get SMTPUTF8 use.
1438 *
1439 * @return bool
1440 */
1441 public function getSMTPUTF8()
1442 {
1443 return $this->do_smtputf8;
1444 }
1445
1446 /**
1447 * Set error messages and codes.
1448 *
1449 * @param string $message The error message
1450 * @param string $detail Further detail on the error
1451 * @param string $smtp_code An associated SMTP error code
1452 * @param string $smtp_code_ex Extended SMTP code
1453 */
1454 protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
1455 {
1456 $this->error = [
1457 'error' => $message,
1458 'detail' => $detail,
1459 'smtp_code' => $smtp_code,
1460 'smtp_code_ex' => $smtp_code_ex,
1461 ];
1462 }
1463
1464 /**
1465 * Set debug output method.
1466 *
1467 * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it
1468 */
1469 public function setDebugOutput($method = 'echo')
1470 {
1471 $this->Debugoutput = $method;
1472 }
1473
1474 /**
1475 * Get debug output method.
1476 *
1477 * @return string
1478 */
1479 public function getDebugOutput()
1480 {
1481 return $this->Debugoutput;
1482 }
1483
1484 /**
1485 * Set debug output level.
1486 *
1487 * @param int $level
1488 */
1489 public function setDebugLevel($level = 0)
1490 {
1491 $this->do_debug = $level;
1492 }
1493
1494 /**
1495 * Get debug output level.
1496 *
1497 * @return int
1498 */
1499 public function getDebugLevel()
1500 {
1501 return $this->do_debug;
1502 }
1503
1504 /**
1505 * Set SMTP timeout.
1506 *
1507 * @param int $timeout The timeout duration in seconds
1508 */
1509 public function setTimeout($timeout = 0)
1510 {
1511 $this->Timeout = $timeout;
1512 }
1513
1514 /**
1515 * Get SMTP timeout.
1516 *
1517 * @return int
1518 */
1519 public function getTimeout()
1520 {
1521 return $this->Timeout;
1522 }
1523
1524 /**
1525 * Reports an error number and string.
1526 *
1527 * @param int $errno The error number returned by PHP
1528 * @param string $errmsg The error message returned by PHP
1529 * @param string $errfile The file the error occurred in
1530 * @param int $errline The line number the error occurred on
1531 */
1532 protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
1533 {
1534 $notice = 'Connection failed.';
1535 $this->setError(
1536 $notice,
1537 $errmsg,
1538 (string) $errno
1539 );
1540 $this->edebug(
1541 "$notice Error #$errno: $errmsg [$errfile line $errline]",
1542 self::DEBUG_CONNECTION
1543 );
1544 }
1545
1546 /**
1547 * Extract and return the ID of the last SMTP transaction based on
1548 * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
1549 * Relies on the host providing the ID in response to a DATA command.
1550 * If no reply has been received yet, it will return null.
1551 * If no pattern was matched, it will return false.
1552 *
1553 * @return bool|string|null
1554 */
1555 protected function recordLastTransactionID()
1556 {
1557 $reply = $this->getLastReply();
1558
1559 if (empty($reply)) {
1560 $this->last_smtp_transaction_id = null;
1561 } else {
1562 $this->last_smtp_transaction_id = false;
1563 foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
1564 $matches = [];
1565 if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
1566 $this->last_smtp_transaction_id = trim($matches[1]);
1567 break;
1568 }
1569 }
1570 }
1571
1572 return $this->last_smtp_transaction_id;
1573 }
1574
1575 /**
1576 * Get the queue/transaction ID of the last SMTP transaction
1577 * If no reply has been received yet, it will return null.
1578 * If no pattern was matched, it will return false.
1579 *
1580 * @return bool|string|null
1581 *
1582 * @see recordLastTransactionID()
1583 */
1584 public function getLastTransactionID()
1585 {
1586 return $this->last_smtp_transaction_id;
1587 }
1588}
1589
Ui Ux Design – Teachers Night Out https://cardgames4educators.com Wed, 16 Oct 2024 22:24:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://cardgames4educators.com/wp-content/uploads/2024/06/cropped-Card-4-Educators-logo-32x32.png Ui Ux Design – Teachers Night Out https://cardgames4educators.com 32 32 Masters In English How English Speaker https://cardgames4educators.com/masters-in-english-how-english-speaker/ https://cardgames4educators.com/masters-in-english-how-english-speaker/#comments Mon, 27 May 2024 08:54:45 +0000 https://themexriver.com/wp/kadu/?p=1

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1