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