1 <?php |
1 <?php |
|
2 |
2 /** |
3 /** |
3 * PHPMailer RFC821 SMTP email transport class. |
4 * PHPMailer RFC821 SMTP email transport class. |
4 * PHP Version 5.5. |
5 * PHP Version 5.5. |
5 * |
6 * |
6 * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project |
7 * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project |
7 * |
8 * |
8 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk> |
9 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk> |
9 * @author Jim Jagielski (jimjag) <jimjag@gmail.com> |
10 * @author Jim Jagielski (jimjag) <jimjag@gmail.com> |
10 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net> |
11 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net> |
11 * @author Brent R. Matzelle (original founder) |
12 * @author Brent R. Matzelle (original founder) |
12 * @copyright 2012 - 2019 Marcus Bointon |
13 * @copyright 2012 - 2020 Marcus Bointon |
13 * @copyright 2010 - 2012 Jim Jagielski |
14 * @copyright 2010 - 2012 Jim Jagielski |
14 * @copyright 2004 - 2009 Andy Prevost |
15 * @copyright 2004 - 2009 Andy Prevost |
15 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License |
16 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License |
16 * @note This program is distributed in the hope that it will be useful - WITHOUT |
17 * @note This program is distributed in the hope that it will be useful - WITHOUT |
17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
183 'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/', |
184 'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/', |
184 'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/', |
185 'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/', |
185 'Amazon_SES' => '/[\d]{3} Ok (.*)/', |
186 'Amazon_SES' => '/[\d]{3} Ok (.*)/', |
186 'SendGrid' => '/[\d]{3} Ok: queued as (.*)/', |
187 'SendGrid' => '/[\d]{3} Ok: queued as (.*)/', |
187 'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/', |
188 'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/', |
|
189 'Haraka' => '/[\d]{3} Message Queued \((.*)\)/', |
188 ]; |
190 ]; |
189 |
191 |
190 /** |
192 /** |
191 * The last transaction ID issued in response to a DATA command, |
193 * The last transaction ID issued in response to a DATA command, |
192 * if one was detected. |
194 * if one was detected. |
309 * |
311 * |
310 * @return bool |
312 * @return bool |
311 */ |
313 */ |
312 public function connect($host, $port = null, $timeout = 30, $options = []) |
314 public function connect($host, $port = null, $timeout = 30, $options = []) |
313 { |
315 { |
|
316 //Clear errors to avoid confusion |
|
317 $this->setError(''); |
|
318 //Make sure we are __not__ connected |
|
319 if ($this->connected()) { |
|
320 //Already connected, generate error |
|
321 $this->setError('Already connected to a server'); |
|
322 |
|
323 return false; |
|
324 } |
|
325 if (empty($port)) { |
|
326 $port = self::DEFAULT_PORT; |
|
327 } |
|
328 //Connect to the SMTP server |
|
329 $this->edebug( |
|
330 "Connection: opening to $host:$port, timeout=$timeout, options=" . |
|
331 (count($options) > 0 ? var_export($options, true) : 'array()'), |
|
332 self::DEBUG_CONNECTION |
|
333 ); |
|
334 |
|
335 $this->smtp_conn = $this->getSMTPConnection($host, $port, $timeout, $options); |
|
336 |
|
337 if ($this->smtp_conn === false) { |
|
338 //Error info already set inside `getSMTPConnection()` |
|
339 return false; |
|
340 } |
|
341 |
|
342 $this->edebug('Connection: opened', self::DEBUG_CONNECTION); |
|
343 |
|
344 //Get any announcement |
|
345 $this->last_reply = $this->get_lines(); |
|
346 $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); |
|
347 $responseCode = (int)substr($this->last_reply, 0, 3); |
|
348 if ($responseCode === 220) { |
|
349 return true; |
|
350 } |
|
351 //Anything other than a 220 response means something went wrong |
|
352 //RFC 5321 says the server will wait for us to send a QUIT in response to a 554 error |
|
353 //https://tools.ietf.org/html/rfc5321#section-3.1 |
|
354 if ($responseCode === 554) { |
|
355 $this->quit(); |
|
356 } |
|
357 //This will handle 421 responses which may not wait for a QUIT (e.g. if the server is being shut down) |
|
358 $this->edebug('Connection: closing due to error', self::DEBUG_CONNECTION); |
|
359 $this->close(); |
|
360 return false; |
|
361 } |
|
362 |
|
363 /** |
|
364 * Create connection to the SMTP server. |
|
365 * |
|
366 * @param string $host SMTP server IP or host name |
|
367 * @param int $port The port number to connect to |
|
368 * @param int $timeout How long to wait for the connection to open |
|
369 * @param array $options An array of options for stream_context_create() |
|
370 * |
|
371 * @return false|resource |
|
372 */ |
|
373 protected function getSMTPConnection($host, $port = null, $timeout = 30, $options = []) |
|
374 { |
314 static $streamok; |
375 static $streamok; |
315 //This is enabled by default since 5.0.0 but some providers disable it |
376 //This is enabled by default since 5.0.0 but some providers disable it |
316 //Check this once and cache the result |
377 //Check this once and cache the result |
317 if (null === $streamok) { |
378 if (null === $streamok) { |
318 $streamok = function_exists('stream_socket_client'); |
379 $streamok = function_exists('stream_socket_client'); |
319 } |
380 } |
320 // Clear errors to avoid confusion |
381 |
321 $this->setError(''); |
|
322 // Make sure we are __not__ connected |
|
323 if ($this->connected()) { |
|
324 // Already connected, generate error |
|
325 $this->setError('Already connected to a server'); |
|
326 |
|
327 return false; |
|
328 } |
|
329 if (empty($port)) { |
|
330 $port = self::DEFAULT_PORT; |
|
331 } |
|
332 // Connect to the SMTP server |
|
333 $this->edebug( |
|
334 "Connection: opening to $host:$port, timeout=$timeout, options=" . |
|
335 (count($options) > 0 ? var_export($options, true) : 'array()'), |
|
336 self::DEBUG_CONNECTION |
|
337 ); |
|
338 $errno = 0; |
382 $errno = 0; |
339 $errstr = ''; |
383 $errstr = ''; |
340 if ($streamok) { |
384 if ($streamok) { |
341 $socket_context = stream_context_create($options); |
385 $socket_context = stream_context_create($options); |
342 set_error_handler([$this, 'errorHandler']); |
386 set_error_handler([$this, 'errorHandler']); |
343 $this->smtp_conn = stream_socket_client( |
387 $connection = stream_socket_client( |
344 $host . ':' . $port, |
388 $host . ':' . $port, |
345 $errno, |
389 $errno, |
346 $errstr, |
390 $errstr, |
347 $timeout, |
391 $timeout, |
348 STREAM_CLIENT_CONNECT, |
392 STREAM_CLIENT_CONNECT, |
354 $this->edebug( |
398 $this->edebug( |
355 'Connection: stream_socket_client not available, falling back to fsockopen', |
399 'Connection: stream_socket_client not available, falling back to fsockopen', |
356 self::DEBUG_CONNECTION |
400 self::DEBUG_CONNECTION |
357 ); |
401 ); |
358 set_error_handler([$this, 'errorHandler']); |
402 set_error_handler([$this, 'errorHandler']); |
359 $this->smtp_conn = fsockopen( |
403 $connection = fsockopen( |
360 $host, |
404 $host, |
361 $port, |
405 $port, |
362 $errno, |
406 $errno, |
363 $errstr, |
407 $errstr, |
364 $timeout |
408 $timeout |
365 ); |
409 ); |
366 restore_error_handler(); |
410 restore_error_handler(); |
367 } |
411 } |
368 // Verify we connected properly |
412 |
369 if (!is_resource($this->smtp_conn)) { |
413 //Verify we connected properly |
|
414 if (!is_resource($connection)) { |
370 $this->setError( |
415 $this->setError( |
371 'Failed to connect to server', |
416 'Failed to connect to server', |
372 '', |
417 '', |
373 (string) $errno, |
418 (string) $errno, |
374 $errstr |
419 $errstr |
379 self::DEBUG_CLIENT |
424 self::DEBUG_CLIENT |
380 ); |
425 ); |
381 |
426 |
382 return false; |
427 return false; |
383 } |
428 } |
384 $this->edebug('Connection: opened', self::DEBUG_CONNECTION); |
429 |
385 // SMTP server can take longer to respond, give longer timeout for first read |
430 //SMTP server can take longer to respond, give longer timeout for first read |
386 // Windows does not have support for this timeout function |
431 //Windows does not have support for this timeout function |
387 if (strpos(PHP_OS, 'WIN') !== 0) { |
432 if (strpos(PHP_OS, 'WIN') !== 0) { |
388 $max = (int) ini_get('max_execution_time'); |
433 $max = (int)ini_get('max_execution_time'); |
389 // Don't bother if unlimited |
434 //Don't bother if unlimited, or if set_time_limit is disabled |
390 if (0 !== $max && $timeout > $max) { |
435 if (0 !== $max && $timeout > $max && strpos(ini_get('disable_functions'), 'set_time_limit') === false) { |
391 @set_time_limit($timeout); |
436 @set_time_limit($timeout); |
392 } |
437 } |
393 stream_set_timeout($this->smtp_conn, $timeout, 0); |
438 stream_set_timeout($connection, $timeout, 0); |
394 } |
439 } |
395 // Get any announcement |
440 |
396 $announce = $this->get_lines(); |
441 return $connection; |
397 $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); |
|
398 |
|
399 return true; |
|
400 } |
442 } |
401 |
443 |
402 /** |
444 /** |
403 * Initiate a TLS (encrypted) session. |
445 * Initiate a TLS (encrypted) session. |
404 * |
446 * |
456 |
498 |
457 return false; |
499 return false; |
458 } |
500 } |
459 |
501 |
460 if (array_key_exists('EHLO', $this->server_caps)) { |
502 if (array_key_exists('EHLO', $this->server_caps)) { |
461 // SMTP extensions are available; try to find a proper authentication method |
503 //SMTP extensions are available; try to find a proper authentication method |
462 if (!array_key_exists('AUTH', $this->server_caps)) { |
504 if (!array_key_exists('AUTH', $this->server_caps)) { |
463 $this->setError('Authentication is not allowed at this stage'); |
505 $this->setError('Authentication is not allowed at this stage'); |
464 // 'at this stage' means that auth may be allowed after the stage changes |
506 //'at this stage' means that auth may be allowed after the stage changes |
465 // e.g. after STARTTLS |
507 //e.g. after STARTTLS |
466 |
508 |
467 return false; |
509 return false; |
468 } |
510 } |
469 |
511 |
470 $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL); |
512 $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL); |
504 } elseif (empty($authtype)) { |
546 } elseif (empty($authtype)) { |
505 $authtype = 'LOGIN'; |
547 $authtype = 'LOGIN'; |
506 } |
548 } |
507 switch ($authtype) { |
549 switch ($authtype) { |
508 case 'PLAIN': |
550 case 'PLAIN': |
509 // Start authentication |
551 //Start authentication |
510 if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { |
552 if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { |
511 return false; |
553 return false; |
512 } |
554 } |
513 // Send encoded username and password |
555 //Send encoded username and password |
514 if (!$this->sendCommand( |
556 if ( |
515 'User & Password', |
557 //Format from https://tools.ietf.org/html/rfc4616#section-2 |
516 base64_encode("\0" . $username . "\0" . $password), |
558 //We skip the first field (it's forgery), so the string starts with a null byte |
517 235 |
559 !$this->sendCommand( |
518 ) |
560 'User & Password', |
|
561 base64_encode("\0" . $username . "\0" . $password), |
|
562 235 |
|
563 ) |
519 ) { |
564 ) { |
520 return false; |
565 return false; |
521 } |
566 } |
522 break; |
567 break; |
523 case 'LOGIN': |
568 case 'LOGIN': |
524 // Start authentication |
569 //Start authentication |
525 if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { |
570 if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { |
526 return false; |
571 return false; |
527 } |
572 } |
528 if (!$this->sendCommand('Username', base64_encode($username), 334)) { |
573 if (!$this->sendCommand('Username', base64_encode($username), 334)) { |
529 return false; |
574 return false; |
531 if (!$this->sendCommand('Password', base64_encode($password), 235)) { |
576 if (!$this->sendCommand('Password', base64_encode($password), 235)) { |
532 return false; |
577 return false; |
533 } |
578 } |
534 break; |
579 break; |
535 case 'CRAM-MD5': |
580 case 'CRAM-MD5': |
536 // Start authentication |
581 //Start authentication |
537 if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { |
582 if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { |
538 return false; |
583 return false; |
539 } |
584 } |
540 // Get the challenge |
585 //Get the challenge |
541 $challenge = base64_decode(substr($this->last_reply, 4)); |
586 $challenge = base64_decode(substr($this->last_reply, 4)); |
542 |
587 |
543 // Build the response |
588 //Build the response |
544 $response = $username . ' ' . $this->hmac($challenge, $password); |
589 $response = $username . ' ' . $this->hmac($challenge, $password); |
545 |
590 |
546 // send encoded credentials |
591 //send encoded credentials |
547 return $this->sendCommand('Username', base64_encode($response), 235); |
592 return $this->sendCommand('Username', base64_encode($response), 235); |
548 case 'XOAUTH2': |
593 case 'XOAUTH2': |
549 //The OAuth instance must be set up prior to requesting auth. |
594 //The OAuth instance must be set up prior to requesting auth. |
550 if (null === $OAuth) { |
595 if (null === $OAuth) { |
551 return false; |
596 return false; |
552 } |
597 } |
553 $oauth = $OAuth->getOauth64(); |
598 $oauth = $OAuth->getOauth64(); |
554 |
599 |
555 // Start authentication |
600 //Start authentication |
556 if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) { |
601 if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) { |
557 return false; |
602 return false; |
558 } |
603 } |
559 break; |
604 break; |
560 default: |
605 default: |
580 { |
625 { |
581 if (function_exists('hash_hmac')) { |
626 if (function_exists('hash_hmac')) { |
582 return hash_hmac('md5', $data, $key); |
627 return hash_hmac('md5', $data, $key); |
583 } |
628 } |
584 |
629 |
585 // The following borrowed from |
630 //The following borrowed from |
586 // http://php.net/manual/en/function.mhash.php#27225 |
631 //http://php.net/manual/en/function.mhash.php#27225 |
587 |
632 |
588 // RFC 2104 HMAC implementation for php. |
633 //RFC 2104 HMAC implementation for php. |
589 // Creates an md5 HMAC. |
634 //Creates an md5 HMAC. |
590 // Eliminates the need to install mhash to compute a HMAC |
635 //Eliminates the need to install mhash to compute a HMAC |
591 // by Lance Rushing |
636 //by Lance Rushing |
592 |
637 |
593 $bytelen = 64; // byte length for md5 |
638 $bytelen = 64; //byte length for md5 |
594 if (strlen($key) > $bytelen) { |
639 if (strlen($key) > $bytelen) { |
595 $key = pack('H*', md5($key)); |
640 $key = pack('H*', md5($key)); |
596 } |
641 } |
597 $key = str_pad($key, $bytelen, chr(0x00)); |
642 $key = str_pad($key, $bytelen, chr(0x00)); |
598 $ipad = str_pad('', $bytelen, chr(0x36)); |
643 $ipad = str_pad('', $bytelen, chr(0x36)); |
674 * smaller lines to fit within the limit. |
719 * smaller lines to fit within the limit. |
675 * We will also look for lines that start with a '.' and prepend an additional '.'. |
720 * We will also look for lines that start with a '.' and prepend an additional '.'. |
676 * NOTE: this does not count towards line-length limit. |
721 * NOTE: this does not count towards line-length limit. |
677 */ |
722 */ |
678 |
723 |
679 // Normalize line breaks before exploding |
724 //Normalize line breaks before exploding |
680 $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data)); |
725 $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data)); |
681 |
726 |
682 /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field |
727 /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field |
683 * of the first line (':' separated) does not contain a space then it _should_ be a header and we will |
728 * of the first line (':' separated) does not contain a space then it _should_ be a header and we will |
684 * process all lines before a blank line as headers. |
729 * process all lines before a blank line as headers. |
720 } |
765 } |
721 $lines_out[] = $line; |
766 $lines_out[] = $line; |
722 |
767 |
723 //Send the lines to the server |
768 //Send the lines to the server |
724 foreach ($lines_out as $line_out) { |
769 foreach ($lines_out as $line_out) { |
725 //RFC2821 section 4.5.2 |
770 //Dot-stuffing as per RFC5321 section 4.5.2 |
|
771 //https://tools.ietf.org/html/rfc5321#section-4.5.2 |
726 if (!empty($line_out) && $line_out[0] === '.') { |
772 if (!empty($line_out) && $line_out[0] === '.') { |
727 $line_out = '.' . $line_out; |
773 $line_out = '.' . $line_out; |
728 } |
774 } |
729 $this->client_send($line_out . static::LE, 'DATA'); |
775 $this->client_send($line_out . static::LE, 'DATA'); |
730 } |
776 } |
754 * @return bool |
800 * @return bool |
755 */ |
801 */ |
756 public function hello($host = '') |
802 public function hello($host = '') |
757 { |
803 { |
758 //Try extended hello first (RFC 2821) |
804 //Try extended hello first (RFC 2821) |
759 return $this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host); |
805 if ($this->sendHello('EHLO', $host)) { |
|
806 return true; |
|
807 } |
|
808 |
|
809 //Some servers shut down the SMTP service here (RFC 5321) |
|
810 if (substr($this->helo_rply, 0, 3) == '421') { |
|
811 return false; |
|
812 } |
|
813 |
|
814 return $this->sendHello('HELO', $host); |
760 } |
815 } |
761 |
816 |
762 /** |
817 /** |
763 * Send an SMTP HELO or EHLO command. |
818 * Send an SMTP HELO or EHLO command. |
764 * Low-level implementation used by hello(). |
819 * Low-level implementation used by hello(). |
944 return false; |
999 return false; |
945 } |
1000 } |
946 $this->client_send($commandstring . static::LE, $command); |
1001 $this->client_send($commandstring . static::LE, $command); |
947 |
1002 |
948 $this->last_reply = $this->get_lines(); |
1003 $this->last_reply = $this->get_lines(); |
949 // Fetch SMTP code and possible error code explanation |
1004 //Fetch SMTP code and possible error code explanation |
950 $matches = []; |
1005 $matches = []; |
951 if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) { |
1006 if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) { |
952 $code = (int) $matches[1]; |
1007 $code = (int) $matches[1]; |
953 $code_ex = (count($matches) > 2 ? $matches[2] : null); |
1008 $code_ex = (count($matches) > 2 ? $matches[2] : null); |
954 // Cut off error code from each response line |
1009 //Cut off error code from each response line |
955 $detail = preg_replace( |
1010 $detail = preg_replace( |
956 "/{$code}[ -]" . |
1011 "/{$code}[ -]" . |
957 ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m', |
1012 ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m', |
958 '', |
1013 '', |
959 $this->last_reply |
1014 $this->last_reply |
960 ); |
1015 ); |
961 } else { |
1016 } else { |
962 // Fall back to simple parsing if regex fails |
1017 //Fall back to simple parsing if regex fails |
963 $code = (int) substr($this->last_reply, 0, 3); |
1018 $code = (int) substr($this->last_reply, 0, 3); |
964 $code_ex = null; |
1019 $code_ex = null; |
965 $detail = substr($this->last_reply, 4); |
1020 $detail = substr($this->last_reply, 4); |
966 } |
1021 } |
967 |
1022 |
1056 */ |
1111 */ |
1057 public function client_send($data, $command = '') |
1112 public function client_send($data, $command = '') |
1058 { |
1113 { |
1059 //If SMTP transcripts are left enabled, or debug output is posted online |
1114 //If SMTP transcripts are left enabled, or debug output is posted online |
1060 //it can leak credentials, so hide credentials in all but lowest level |
1115 //it can leak credentials, so hide credentials in all but lowest level |
1061 if (self::DEBUG_LOWLEVEL > $this->do_debug && |
1116 if ( |
1062 in_array($command, ['User & Password', 'Username', 'Password'], true)) { |
1117 self::DEBUG_LOWLEVEL > $this->do_debug && |
|
1118 in_array($command, ['User & Password', 'Username', 'Password'], true) |
|
1119 ) { |
1063 $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT); |
1120 $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT); |
1064 } else { |
1121 } else { |
1065 $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT); |
1122 $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT); |
1066 } |
1123 } |
1067 set_error_handler([$this, 'errorHandler']); |
1124 set_error_handler([$this, 'errorHandler']); |
1164 } |
1221 } |
1165 $selR = [$this->smtp_conn]; |
1222 $selR = [$this->smtp_conn]; |
1166 $selW = null; |
1223 $selW = null; |
1167 while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { |
1224 while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { |
1168 //Must pass vars in here as params are by reference |
1225 //Must pass vars in here as params are by reference |
1169 if (!stream_select($selR, $selW, $selW, $this->Timelimit)) { |
1226 //solution for signals inspired by https://github.com/symfony/symfony/pull/6540 |
|
1227 set_error_handler([$this, 'errorHandler']); |
|
1228 $n = stream_select($selR, $selW, $selW, $this->Timelimit); |
|
1229 restore_error_handler(); |
|
1230 |
|
1231 if ($n === false) { |
|
1232 $message = $this->getError()['detail']; |
|
1233 |
|
1234 $this->edebug( |
|
1235 'SMTP -> get_lines(): select failed (' . $message . ')', |
|
1236 self::DEBUG_LOWLEVEL |
|
1237 ); |
|
1238 |
|
1239 //stream_select returns false when the `select` system call is interrupted |
|
1240 //by an incoming signal, try the select again |
|
1241 if (stripos($message, 'interrupted system call') !== false) { |
|
1242 $this->edebug( |
|
1243 'SMTP -> get_lines(): retrying stream_select', |
|
1244 self::DEBUG_LOWLEVEL |
|
1245 ); |
|
1246 $this->setError(''); |
|
1247 continue; |
|
1248 } |
|
1249 |
|
1250 break; |
|
1251 } |
|
1252 |
|
1253 if (!$n) { |
1170 $this->edebug( |
1254 $this->edebug( |
1171 'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)', |
1255 'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)', |
1172 self::DEBUG_LOWLEVEL |
1256 self::DEBUG_LOWLEVEL |
1173 ); |
1257 ); |
1174 break; |
1258 break; |
1175 } |
1259 } |
|
1260 |
1176 //Deliberate noise suppression - errors are handled afterwards |
1261 //Deliberate noise suppression - errors are handled afterwards |
1177 $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH); |
1262 $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH); |
1178 $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL); |
1263 $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL); |
1179 $data .= $str; |
1264 $data .= $str; |
1180 // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled), |
1265 //If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled), |
1181 // or 4th character is a space or a line break char, we are done reading, break the loop. |
1266 //or 4th character is a space or a line break char, we are done reading, break the loop. |
1182 // String array access is a significant micro-optimisation over strlen |
1267 //String array access is a significant micro-optimisation over strlen |
1183 if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") { |
1268 if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") { |
1184 break; |
1269 break; |
1185 } |
1270 } |
1186 // Timed-out? Log and break |
1271 //Timed-out? Log and break |
1187 $info = stream_get_meta_data($this->smtp_conn); |
1272 $info = stream_get_meta_data($this->smtp_conn); |
1188 if ($info['timed_out']) { |
1273 if ($info['timed_out']) { |
1189 $this->edebug( |
1274 $this->edebug( |
1190 'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)', |
1275 'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)', |
1191 self::DEBUG_LOWLEVEL |
1276 self::DEBUG_LOWLEVEL |
1192 ); |
1277 ); |
1193 break; |
1278 break; |
1194 } |
1279 } |
1195 // Now check if reads took too long |
1280 //Now check if reads took too long |
1196 if ($endtime && time() > $endtime) { |
1281 if ($endtime && time() > $endtime) { |
1197 $this->edebug( |
1282 $this->edebug( |
1198 'SMTP -> get_lines(): timelimit reached (' . |
1283 'SMTP -> get_lines(): timelimit reached (' . |
1199 $this->Timelimit . ' sec)', |
1284 $this->Timelimit . ' sec)', |
1200 self::DEBUG_LOWLEVEL |
1285 self::DEBUG_LOWLEVEL |