Changeset 48033 for trunk/src/wp-includes/class-smtp.php
- Timestamp:
- 06/12/2020 03:45:30 PM (5 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/class-smtp.php
r46097 r48033 1 1 <?php 2 /**3 * PHPMailer RFC821 SMTP email transport class.4 * PHP Version 55 * @package PHPMailer6 * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project7 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>8 * @author Jim Jagielski (jimjag) <jimjag@gmail.com>9 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>10 * @author Brent R. Matzelle (original founder)11 * @copyright 2014 Marcus Bointon12 * @copyright 2010 - 2012 Jim Jagielski13 * @copyright 2004 - 2009 Andy Prevost14 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License15 * @note This program is distributed in the hope that it will be useful - WITHOUT16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or17 * FITNESS FOR A PARTICULAR PURPOSE.18 */19 2 20 3 /** 21 * PHPMailer RFC821 SMTP email transport class. 22 * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server. 23 * @package PHPMailer 24 * @author Chris Ryan 25 * @author Marcus Bointon <phpmailer@synchromedia.co.uk> 4 * The SMTP class has been moved to the wp-includes/PHPMailer subdirectory and now uses the PHPMailer\PHPMailer namespace. 26 5 */ 27 class SMTP 28 { 29 /** 30 * The PHPMailer SMTP version number. 31 * @var string 32 */ 33 const VERSION = '5.2.27'; 34 35 /** 36 * SMTP line break constant. 37 * @var string 38 */ 39 const CRLF = "\r\n"; 40 41 /** 42 * The SMTP port to use if one is not specified. 43 * @var integer 44 */ 45 const DEFAULT_SMTP_PORT = 25; 46 47 /** 48 * The maximum line length allowed by RFC 2822 section 2.1.1 49 * @var integer 50 */ 51 const MAX_LINE_LENGTH = 998; 52 53 /** 54 * Debug level for no output 55 */ 56 const DEBUG_OFF = 0; 57 58 /** 59 * Debug level to show client -> server messages 60 */ 61 const DEBUG_CLIENT = 1; 62 63 /** 64 * Debug level to show client -> server and server -> client messages 65 */ 66 const DEBUG_SERVER = 2; 67 68 /** 69 * Debug level to show connection status, client -> server and server -> client messages 70 */ 71 const DEBUG_CONNECTION = 3; 72 73 /** 74 * Debug level to show all messages 75 */ 76 const DEBUG_LOWLEVEL = 4; 77 78 /** 79 * The PHPMailer SMTP Version number. 80 * @var string 81 * @deprecated Use the `VERSION` constant instead 82 * @see SMTP::VERSION 83 */ 84 public $Version = '5.2.27'; 85 86 /** 87 * SMTP server port number. 88 * @var integer 89 * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead 90 * @see SMTP::DEFAULT_SMTP_PORT 91 */ 92 public $SMTP_PORT = 25; 93 94 /** 95 * SMTP reply line ending. 96 * @var string 97 * @deprecated Use the `CRLF` constant instead 98 * @see SMTP::CRLF 99 */ 100 public $CRLF = "\r\n"; 101 102 /** 103 * Debug output level. 104 * Options: 105 * * self::DEBUG_OFF (`0`) No debug output, default 106 * * self::DEBUG_CLIENT (`1`) Client commands 107 * * self::DEBUG_SERVER (`2`) Client commands and server responses 108 * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status 109 * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages 110 * @var integer 111 */ 112 public $do_debug = self::DEBUG_OFF; 113 114 /** 115 * How to handle debug output. 116 * Options: 117 * * `echo` Output plain-text as-is, appropriate for CLI 118 * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output 119 * * `error_log` Output to error log as configured in php.ini 120 * 121 * Alternatively, you can provide a callable expecting two params: a message string and the debug level: 122 * <code> 123 * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; 124 * </code> 125 * @var string|callable 126 */ 127 public $Debugoutput = 'echo'; 128 129 /** 130 * Whether to use VERP. 131 * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path 132 * @link http://www.postfix.org/VERP_README.html Info on VERP 133 * @var boolean 134 */ 135 public $do_verp = false; 136 137 /** 138 * The timeout value for connection, in seconds. 139 * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 140 * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. 141 * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2 142 * @var integer 143 */ 144 public $Timeout = 300; 145 146 /** 147 * How long to wait for commands to complete, in seconds. 148 * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 149 * @var integer 150 */ 151 public $Timelimit = 300; 152 153 /** 154 * @var array Patterns to extract an SMTP transaction id from reply to a DATA command. 155 * The first capture group in each regex will be used as the ID. 156 */ 157 protected $smtp_transaction_id_patterns = array( 158 'exim' => '/[0-9]{3} OK id=(.*)/', 159 'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/', 160 'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/' 161 ); 162 163 /** 164 * @var string The last transaction ID issued in response to a DATA command, 165 * if one was detected 166 */ 167 protected $last_smtp_transaction_id; 168 169 /** 170 * The socket for the server connection. 171 * @var resource 172 */ 173 protected $smtp_conn; 174 175 /** 176 * Error information, if any, for the last SMTP command. 177 * @var array 178 */ 179 protected $error = array( 180 'error' => '', 181 'detail' => '', 182 'smtp_code' => '', 183 'smtp_code_ex' => '' 184 ); 185 186 /** 187 * The reply the server sent to us for HELO. 188 * If null, no HELO string has yet been received. 189 * @var string|null 190 */ 191 protected $helo_rply = null; 192 193 /** 194 * The set of SMTP extensions sent in reply to EHLO command. 195 * Indexes of the array are extension names. 196 * Value at index 'HELO' or 'EHLO' (according to command that was sent) 197 * represents the server name. In case of HELO it is the only element of the array. 198 * Other values can be boolean TRUE or an array containing extension options. 199 * If null, no HELO/EHLO string has yet been received. 200 * @var array|null 201 */ 202 protected $server_caps = null; 203 204 /** 205 * The most recent reply received from the server. 206 * @var string 207 */ 208 protected $last_reply = ''; 209 210 /** 211 * Output debugging info via a user-selected method. 212 * @see SMTP::$Debugoutput 213 * @see SMTP::$do_debug 214 * @param string $str Debug string to output 215 * @param integer $level The debug level of this message; see DEBUG_* constants 216 * @return void 217 */ 218 protected function edebug($str, $level = 0) 219 { 220 if ($level > $this->do_debug) { 221 return; 222 } 223 //Avoid clash with built-in function names 224 if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { 225 call_user_func($this->Debugoutput, $str, $level); 226 return; 227 } 228 switch ($this->Debugoutput) { 229 case 'error_log': 230 //Don't output, just log 231 error_log($str); 232 break; 233 case 'html': 234 //Cleans up output a bit for a better looking, HTML-safe output 235 echo gmdate('Y-m-d H:i:s') . ' ' . htmlentities( 236 preg_replace('/[\r\n]+/', '', $str), 237 ENT_QUOTES, 238 'UTF-8' 239 ) . "<br>\n"; 240 break; 241 case 'echo': 242 default: 243 //Normalize line breaks 244 $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str); 245 echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( 246 "\n", 247 "\n \t ", 248 trim($str) 249 ) . "\n"; 250 } 251 } 252 253 /** 254 * Connect to an SMTP server. 255 * @param string $host SMTP server IP or host name 256 * @param integer $port The port number to connect to 257 * @param integer $timeout How long to wait for the connection to open 258 * @param array $options An array of options for stream_context_create() 259 * @access public 260 * @return boolean 261 */ 262 public function connect($host, $port = null, $timeout = 30, $options = array()) 263 { 264 static $streamok; 265 //This is enabled by default since 5.0.0 but some providers disable it 266 //Check this once and cache the result 267 if (is_null($streamok)) { 268 $streamok = function_exists('stream_socket_client'); 269 } 270 // Clear errors to avoid confusion 271 $this->setError(''); 272 // Make sure we are __not__ connected 273 if ($this->connected()) { 274 // Already connected, generate error 275 $this->setError('Already connected to a server'); 276 return false; 277 } 278 if (empty($port)) { 279 $port = self::DEFAULT_SMTP_PORT; 280 } 281 // Connect to the SMTP server 282 $this->edebug( 283 "Connection: opening to $host:$port, timeout=$timeout, options=" . 284 var_export($options, true), 285 self::DEBUG_CONNECTION 286 ); 287 $errno = 0; 288 $errstr = ''; 289 if ($streamok) { 290 $socket_context = stream_context_create($options); 291 set_error_handler(array($this, 'errorHandler')); 292 $this->smtp_conn = stream_socket_client( 293 $host . ":" . $port, 294 $errno, 295 $errstr, 296 $timeout, 297 STREAM_CLIENT_CONNECT, 298 $socket_context 299 ); 300 restore_error_handler(); 301 } else { 302 //Fall back to fsockopen which should work in more places, but is missing some features 303 $this->edebug( 304 "Connection: stream_socket_client not available, falling back to fsockopen", 305 self::DEBUG_CONNECTION 306 ); 307 set_error_handler(array($this, 'errorHandler')); 308 $this->smtp_conn = fsockopen( 309 $host, 310 $port, 311 $errno, 312 $errstr, 313 $timeout 314 ); 315 restore_error_handler(); 316 } 317 // Verify we connected properly 318 if (!is_resource($this->smtp_conn)) { 319 $this->setError( 320 'Failed to connect to server', 321 $errno, 322 $errstr 323 ); 324 $this->edebug( 325 'SMTP ERROR: ' . $this->error['error'] 326 . ": $errstr ($errno)", 327 self::DEBUG_CLIENT 328 ); 329 return false; 330 } 331 $this->edebug('Connection: opened', self::DEBUG_CONNECTION); 332 // SMTP server can take longer to respond, give longer timeout for first read 333 // Windows does not have support for this timeout function 334 if (substr(PHP_OS, 0, 3) != 'WIN') { 335 $max = ini_get('max_execution_time'); 336 // Don't bother if unlimited 337 if ($max != 0 && $timeout > $max) { 338 @set_time_limit($timeout); 339 } 340 stream_set_timeout($this->smtp_conn, $timeout, 0); 341 } 342 // Get any announcement 343 $announce = $this->get_lines(); 344 $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); 345 return true; 346 } 347 348 /** 349 * Initiate a TLS (encrypted) session. 350 * @access public 351 * @return boolean 352 */ 353 public function startTLS() 354 { 355 if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { 356 return false; 357 } 358 359 //Allow the best TLS version(s) we can 360 $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT; 361 362 //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT 363 //so add them back in manually if we can 364 if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { 365 $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; 366 $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; 367 } 368 369 // Begin encrypted connection 370 set_error_handler(array($this, 'errorHandler')); 371 $crypto_ok = stream_socket_enable_crypto( 372 $this->smtp_conn, 373 true, 374 $crypto_method 375 ); 376 restore_error_handler(); 377 return $crypto_ok; 378 } 379 380 /** 381 * Perform SMTP authentication. 382 * Must be run after hello(). 383 * @see hello() 384 * @param string $username The user name 385 * @param string $password The password 386 * @param string $authtype The auth type (PLAIN, LOGIN, CRAM-MD5) 387 * @param string $realm The auth realm for NTLM 388 * @param string $workstation The auth workstation for NTLM 389 * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth) 390 * @return bool True if successfully authenticated.* @access public 391 */ 392 public function authenticate( 393 $username, 394 $password, 395 $authtype = null, 396 $realm = '', 397 $workstation = '', 398 $OAuth = null 399 ) { 400 if (!$this->server_caps) { 401 $this->setError('Authentication is not allowed before HELO/EHLO'); 402 return false; 403 } 404 405 if (array_key_exists('EHLO', $this->server_caps)) { 406 // SMTP extensions are available; try to find a proper authentication method 407 if (!array_key_exists('AUTH', $this->server_caps)) { 408 $this->setError('Authentication is not allowed at this stage'); 409 // 'at this stage' means that auth may be allowed after the stage changes 410 // e.g. after STARTTLS 411 return false; 412 } 413 414 self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL); 415 self::edebug( 416 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), 417 self::DEBUG_LOWLEVEL 418 ); 419 420 if (empty($authtype)) { 421 foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN') as $method) { 422 if (in_array($method, $this->server_caps['AUTH'])) { 423 $authtype = $method; 424 break; 425 } 426 } 427 if (empty($authtype)) { 428 $this->setError('No supported authentication methods found'); 429 return false; 430 } 431 self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL); 432 } 433 434 if (!in_array($authtype, $this->server_caps['AUTH'])) { 435 $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); 436 return false; 437 } 438 } elseif (empty($authtype)) { 439 $authtype = 'LOGIN'; 440 } 441 switch ($authtype) { 442 case 'PLAIN': 443 // Start authentication 444 if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { 445 return false; 446 } 447 // Send encoded username and password 448 if (!$this->sendCommand( 449 'User & Password', 450 base64_encode("\0" . $username . "\0" . $password), 451 235 452 ) 453 ) { 454 return false; 455 } 456 break; 457 case 'LOGIN': 458 // Start authentication 459 if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { 460 return false; 461 } 462 if (!$this->sendCommand("Username", base64_encode($username), 334)) { 463 return false; 464 } 465 if (!$this->sendCommand("Password", base64_encode($password), 235)) { 466 return false; 467 } 468 break; 469 case 'CRAM-MD5': 470 // Start authentication 471 if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { 472 return false; 473 } 474 // Get the challenge 475 $challenge = base64_decode(substr($this->last_reply, 4)); 476 477 // Build the response 478 $response = $username . ' ' . $this->hmac($challenge, $password); 479 480 // send encoded credentials 481 return $this->sendCommand('Username', base64_encode($response), 235); 482 default: 483 $this->setError("Authentication method \"$authtype\" is not supported"); 484 return false; 485 } 486 return true; 487 } 488 489 /** 490 * Calculate an MD5 HMAC hash. 491 * Works like hash_hmac('md5', $data, $key) 492 * in case that function is not available 493 * @param string $data The data to hash 494 * @param string $key The key to hash with 495 * @access protected 496 * @return string 497 */ 498 protected function hmac($data, $key) 499 { 500 if (function_exists('hash_hmac')) { 501 return hash_hmac('md5', $data, $key); 502 } 503 504 // The following borrowed from 505 // http://php.net/manual/en/function.mhash.php#27225 506 507 // RFC 2104 HMAC implementation for php. 508 // Creates an md5 HMAC. 509 // Eliminates the need to install mhash to compute a HMAC 510 // by Lance Rushing 511 512 $bytelen = 64; // byte length for md5 513 if (strlen($key) > $bytelen) { 514 $key = pack('H*', md5($key)); 515 } 516 $key = str_pad($key, $bytelen, chr(0x00)); 517 $ipad = str_pad('', $bytelen, chr(0x36)); 518 $opad = str_pad('', $bytelen, chr(0x5c)); 519 $k_ipad = $key ^ $ipad; 520 $k_opad = $key ^ $opad; 521 522 return md5($k_opad . pack('H*', md5($k_ipad . $data))); 523 } 524 525 /** 526 * Check connection state. 527 * @access public 528 * @return boolean True if connected. 529 */ 530 public function connected() 531 { 532 if (is_resource($this->smtp_conn)) { 533 $sock_status = stream_get_meta_data($this->smtp_conn); 534 if ($sock_status['eof']) { 535 // The socket is valid but we are not connected 536 $this->edebug( 537 'SMTP NOTICE: EOF caught while checking if connected', 538 self::DEBUG_CLIENT 539 ); 540 $this->close(); 541 return false; 542 } 543 return true; // everything looks good 544 } 545 return false; 546 } 547 548 /** 549 * Close the socket and clean up the state of the class. 550 * Don't use this function without first trying to use QUIT. 551 * @see quit() 552 * @access public 553 * @return void 554 */ 555 public function close() 556 { 557 $this->setError(''); 558 $this->server_caps = null; 559 $this->helo_rply = null; 560 if (is_resource($this->smtp_conn)) { 561 // close the connection and cleanup 562 fclose($this->smtp_conn); 563 $this->smtp_conn = null; //Makes for cleaner serialization 564 $this->edebug('Connection: closed', self::DEBUG_CONNECTION); 565 } 566 } 567 568 /** 569 * Send an SMTP DATA command. 570 * Issues a data command and sends the msg_data to the server, 571 * finializing the mail transaction. $msg_data is the message 572 * that is to be send with the headers. Each header needs to be 573 * on a single line followed by a <CRLF> with the message headers 574 * and the message body being separated by and additional <CRLF>. 575 * Implements rfc 821: DATA <CRLF> 576 * @param string $msg_data Message data to send 577 * @access public 578 * @return boolean 579 */ 580 public function data($msg_data) 581 { 582 //This will use the standard timelimit 583 if (!$this->sendCommand('DATA', 'DATA', 354)) { 584 return false; 585 } 586 587 /* The server is ready to accept data! 588 * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF) 589 * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into 590 * smaller lines to fit within the limit. 591 * We will also look for lines that start with a '.' and prepend an additional '.'. 592 * NOTE: this does not count towards line-length limit. 593 */ 594 595 // Normalize line breaks before exploding 596 $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data)); 597 598 /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field 599 * of the first line (':' separated) does not contain a space then it _should_ be a header and we will 600 * process all lines before a blank line as headers. 601 */ 602 603 $field = substr($lines[0], 0, strpos($lines[0], ':')); 604 $in_headers = false; 605 if (!empty($field) && strpos($field, ' ') === false) { 606 $in_headers = true; 607 } 608 609 foreach ($lines as $line) { 610 $lines_out = array(); 611 if ($in_headers and $line == '') { 612 $in_headers = false; 613 } 614 //Break this line up into several smaller lines if it's too long 615 //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), 616 while (isset($line[self::MAX_LINE_LENGTH])) { 617 //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on 618 //so as to avoid breaking in the middle of a word 619 $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); 620 //Deliberately matches both false and 0 621 if (!$pos) { 622 //No nice break found, add a hard break 623 $pos = self::MAX_LINE_LENGTH - 1; 624 $lines_out[] = substr($line, 0, $pos); 625 $line = substr($line, $pos); 626 } else { 627 //Break at the found point 628 $lines_out[] = substr($line, 0, $pos); 629 //Move along by the amount we dealt with 630 $line = substr($line, $pos + 1); 631 } 632 //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 633 if ($in_headers) { 634 $line = "\t" . $line; 635 } 636 } 637 $lines_out[] = $line; 638 639 //Send the lines to the server 640 foreach ($lines_out as $line_out) { 641 //RFC2821 section 4.5.2 642 if (!empty($line_out) and $line_out[0] == '.') { 643 $line_out = '.' . $line_out; 644 } 645 $this->client_send($line_out . self::CRLF); 646 } 647 } 648 649 //Message data has been sent, complete the command 650 //Increase timelimit for end of DATA command 651 $savetimelimit = $this->Timelimit; 652 $this->Timelimit = $this->Timelimit * 2; 653 $result = $this->sendCommand('DATA END', '.', 250); 654 $this->recordLastTransactionID(); 655 //Restore timelimit 656 $this->Timelimit = $savetimelimit; 657 return $result; 658 } 659 660 /** 661 * Send an SMTP HELO or EHLO command. 662 * Used to identify the sending server to the receiving server. 663 * This makes sure that client and server are in a known state. 664 * Implements RFC 821: HELO <SP> <domain> <CRLF> 665 * and RFC 2821 EHLO. 666 * @param string $host The host name or IP to connect to 667 * @access public 668 * @return boolean 669 */ 670 public function hello($host = '') 671 { 672 //Try extended hello first (RFC 2821) 673 return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); 674 } 675 676 /** 677 * Send an SMTP HELO or EHLO command. 678 * Low-level implementation used by hello() 679 * @see hello() 680 * @param string $hello The HELO string 681 * @param string $host The hostname to say we are 682 * @access protected 683 * @return boolean 684 */ 685 protected function sendHello($hello, $host) 686 { 687 $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); 688 $this->helo_rply = $this->last_reply; 689 if ($noerror) { 690 $this->parseHelloFields($hello); 691 } else { 692 $this->server_caps = null; 693 } 694 return $noerror; 695 } 696 697 /** 698 * Parse a reply to HELO/EHLO command to discover server extensions. 699 * In case of HELO, the only parameter that can be discovered is a server name. 700 * @access protected 701 * @param string $type - 'HELO' or 'EHLO' 702 */ 703 protected function parseHelloFields($type) 704 { 705 $this->server_caps = array(); 706 $lines = explode("\n", $this->helo_rply); 707 708 foreach ($lines as $n => $s) { 709 //First 4 chars contain response code followed by - or space 710 $s = trim(substr($s, 4)); 711 if (empty($s)) { 712 continue; 713 } 714 $fields = explode(' ', $s); 715 if (!empty($fields)) { 716 if (!$n) { 717 $name = $type; 718 $fields = $fields[0]; 719 } else { 720 $name = array_shift($fields); 721 switch ($name) { 722 case 'SIZE': 723 $fields = ($fields ? $fields[0] : 0); 724 break; 725 case 'AUTH': 726 if (!is_array($fields)) { 727 $fields = array(); 728 } 729 break; 730 default: 731 $fields = true; 732 } 733 } 734 $this->server_caps[$name] = $fields; 735 } 736 } 737 } 738 739 /** 740 * Send an SMTP MAIL command. 741 * Starts a mail transaction from the email address specified in 742 * $from. Returns true if successful or false otherwise. If True 743 * the mail transaction is started and then one or more recipient 744 * commands may be called followed by a data command. 745 * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF> 746 * @param string $from Source address of this message 747 * @access public 748 * @return boolean 749 */ 750 public function mail($from) 751 { 752 $useVerp = ($this->do_verp ? ' XVERP' : ''); 753 return $this->sendCommand( 754 'MAIL FROM', 755 'MAIL FROM:<' . $from . '>' . $useVerp, 756 250 757 ); 758 } 759 760 /** 761 * Send an SMTP QUIT command. 762 * Closes the socket if there is no error or the $close_on_error argument is true. 763 * Implements from rfc 821: QUIT <CRLF> 764 * @param boolean $close_on_error Should the connection close if an error occurs? 765 * @access public 766 * @return boolean 767 */ 768 public function quit($close_on_error = true) 769 { 770 $noerror = $this->sendCommand('QUIT', 'QUIT', 221); 771 $err = $this->error; //Save any error 772 if ($noerror or $close_on_error) { 773 $this->close(); 774 $this->error = $err; //Restore any error from the quit command 775 } 776 return $noerror; 777 } 778 779 /** 780 * Send an SMTP RCPT command. 781 * Sets the TO argument to $toaddr. 782 * Returns true if the recipient was accepted false if it was rejected. 783 * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF> 784 * @param string $address The address the message is being sent to 785 * @access public 786 * @return boolean 787 */ 788 public function recipient($address) 789 { 790 return $this->sendCommand( 791 'RCPT TO', 792 'RCPT TO:<' . $address . '>', 793 array(250, 251) 794 ); 795 } 796 797 /** 798 * Send an SMTP RSET command. 799 * Abort any transaction that is currently in progress. 800 * Implements rfc 821: RSET <CRLF> 801 * @access public 802 * @return boolean True on success. 803 */ 804 public function reset() 805 { 806 return $this->sendCommand('RSET', 'RSET', 250); 807 } 808 809 /** 810 * Send a command to an SMTP server and check its return code. 811 * @param string $command The command name - not sent to the server 812 * @param string $commandstring The actual command to send 813 * @param integer|array $expect One or more expected integer success codes 814 * @access protected 815 * @return boolean True on success. 816 */ 817 protected function sendCommand($command, $commandstring, $expect) 818 { 819 if (!$this->connected()) { 820 $this->setError("Called $command without being connected"); 821 return false; 822 } 823 //Reject line breaks in all commands 824 if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) { 825 $this->setError("Command '$command' contained line breaks"); 826 return false; 827 } 828 $this->client_send($commandstring . self::CRLF); 829 830 $this->last_reply = $this->get_lines(); 831 // Fetch SMTP code and possible error code explanation 832 $matches = array(); 833 if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) { 834 $code = $matches[1]; 835 $code_ex = (count($matches) > 2 ? $matches[2] : null); 836 // Cut off error code from each response line 837 $detail = preg_replace( 838 "/{$code}[ -]" . 839 ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . "/m", 840 '', 841 $this->last_reply 842 ); 843 } else { 844 // Fall back to simple parsing if regex fails 845 $code = substr($this->last_reply, 0, 3); 846 $code_ex = null; 847 $detail = substr($this->last_reply, 4); 848 } 849 850 $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); 851 852 if (!in_array($code, (array)$expect)) { 853 $this->setError( 854 "$command command failed", 855 $detail, 856 $code, 857 $code_ex 858 ); 859 $this->edebug( 860 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, 861 self::DEBUG_CLIENT 862 ); 863 return false; 864 } 865 866 $this->setError(''); 867 return true; 868 } 869 870 /** 871 * Send an SMTP SAML command. 872 * Starts a mail transaction from the email address specified in $from. 873 * Returns true if successful or false otherwise. If True 874 * the mail transaction is started and then one or more recipient 875 * commands may be called followed by a data command. This command 876 * will send the message to the users terminal if they are logged 877 * in and send them an email. 878 * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF> 879 * @param string $from The address the message is from 880 * @access public 881 * @return boolean 882 */ 883 public function sendAndMail($from) 884 { 885 return $this->sendCommand('SAML', "SAML FROM:$from", 250); 886 } 887 888 /** 889 * Send an SMTP VRFY command. 890 * @param string $name The name to verify 891 * @access public 892 * @return boolean 893 */ 894 public function verify($name) 895 { 896 return $this->sendCommand('VRFY', "VRFY $name", array(250, 251)); 897 } 898 899 /** 900 * Send an SMTP NOOP command. 901 * Used to keep keep-alives alive, doesn't actually do anything 902 * @access public 903 * @return boolean 904 */ 905 public function noop() 906 { 907 return $this->sendCommand('NOOP', 'NOOP', 250); 908 } 909 910 /** 911 * Send an SMTP TURN command. 912 * This is an optional command for SMTP that this class does not support. 913 * This method is here to make the RFC821 Definition complete for this class 914 * and _may_ be implemented in future 915 * Implements from rfc 821: TURN <CRLF> 916 * @access public 917 * @return boolean 918 */ 919 public function turn() 920 { 921 $this->setError('The SMTP TURN command is not implemented'); 922 $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); 923 return false; 924 } 925 926 /** 927 * Send raw data to the server. 928 * @param string $data The data to send 929 * @access public 930 * @return integer|boolean The number of bytes sent to the server or false on error 931 */ 932 public function client_send($data) 933 { 934 $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); 935 set_error_handler(array($this, 'errorHandler')); 936 $result = fwrite($this->smtp_conn, $data); 937 restore_error_handler(); 938 return $result; 939 } 940 941 /** 942 * Get the latest error. 943 * @access public 944 * @return array 945 */ 946 public function getError() 947 { 948 return $this->error; 949 } 950 951 /** 952 * Get SMTP extensions available on the server 953 * @access public 954 * @return array|null 955 */ 956 public function getServerExtList() 957 { 958 return $this->server_caps; 959 } 960 961 /** 962 * A multipurpose method 963 * The method works in three ways, dependent on argument value and current state 964 * 1. HELO/EHLO was not sent - returns null and set up $this->error 965 * 2. HELO was sent 966 * $name = 'HELO': returns server name 967 * $name = 'EHLO': returns boolean false 968 * $name = any string: returns null and set up $this->error 969 * 3. EHLO was sent 970 * $name = 'HELO'|'EHLO': returns server name 971 * $name = any string: if extension $name exists, returns boolean True 972 * or its options. Otherwise returns boolean False 973 * In other words, one can use this method to detect 3 conditions: 974 * - null returned: handshake was not or we don't know about ext (refer to $this->error) 975 * - false returned: the requested feature exactly not exists 976 * - positive value returned: the requested feature exists 977 * @param string $name Name of SMTP extension or 'HELO'|'EHLO' 978 * @return mixed 979 */ 980 public function getServerExt($name) 981 { 982 if (!$this->server_caps) { 983 $this->setError('No HELO/EHLO was sent'); 984 return null; 985 } 986 987 // the tight logic knot ;) 988 if (!array_key_exists($name, $this->server_caps)) { 989 if ($name == 'HELO') { 990 return $this->server_caps['EHLO']; 991 } 992 if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) { 993 return false; 994 } 995 $this->setError('HELO handshake was used. Client knows nothing about server extensions'); 996 return null; 997 } 998 999 return $this->server_caps[$name]; 1000 } 1001 1002 /** 1003 * Get the last reply from the server. 1004 * @access public 1005 * @return string 1006 */ 1007 public function getLastReply() 1008 { 1009 return $this->last_reply; 1010 } 1011 1012 /** 1013 * Read the SMTP server's response. 1014 * Either before eof or socket timeout occurs on the operation. 1015 * With SMTP we can tell if we have more lines to read if the 1016 * 4th character is '-' symbol. If it is a space then we don't 1017 * need to read anything else. 1018 * @access protected 1019 * @return string 1020 */ 1021 protected function get_lines() 1022 { 1023 // If the connection is bad, give up straight away 1024 if (!is_resource($this->smtp_conn)) { 1025 return ''; 1026 } 1027 $data = ''; 1028 $endtime = 0; 1029 stream_set_timeout($this->smtp_conn, $this->Timeout); 1030 if ($this->Timelimit > 0) { 1031 $endtime = time() + $this->Timelimit; 1032 } 1033 while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { 1034 $str = @fgets($this->smtp_conn, 515); 1035 $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); 1036 $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); 1037 $data .= $str; 1038 // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled), 1039 // or 4th character is a space, we are done reading, break the loop, 1040 // string array access is a micro-optimisation over strlen 1041 if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) { 1042 break; 1043 } 1044 // Timed-out? Log and break 1045 $info = stream_get_meta_data($this->smtp_conn); 1046 if ($info['timed_out']) { 1047 $this->edebug( 1048 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', 1049 self::DEBUG_LOWLEVEL 1050 ); 1051 break; 1052 } 1053 // Now check if reads took too long 1054 if ($endtime and time() > $endtime) { 1055 $this->edebug( 1056 'SMTP -> get_lines(): timelimit reached (' . 1057 $this->Timelimit . ' sec)', 1058 self::DEBUG_LOWLEVEL 1059 ); 1060 break; 1061 } 1062 } 1063 return $data; 1064 } 1065 1066 /** 1067 * Enable or disable VERP address generation. 1068 * @param boolean $enabled 1069 */ 1070 public function setVerp($enabled = false) 1071 { 1072 $this->do_verp = $enabled; 1073 } 1074 1075 /** 1076 * Get VERP address generation mode. 1077 * @return boolean 1078 */ 1079 public function getVerp() 1080 { 1081 return $this->do_verp; 1082 } 1083 1084 /** 1085 * Set error messages and codes. 1086 * @param string $message The error message 1087 * @param string $detail Further detail on the error 1088 * @param string $smtp_code An associated SMTP error code 1089 * @param string $smtp_code_ex Extended SMTP code 1090 */ 1091 protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '') 1092 { 1093 $this->error = array( 1094 'error' => $message, 1095 'detail' => $detail, 1096 'smtp_code' => $smtp_code, 1097 'smtp_code_ex' => $smtp_code_ex 1098 ); 1099 } 1100 1101 /** 1102 * Set debug output method. 1103 * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it. 1104 */ 1105 public function setDebugOutput($method = 'echo') 1106 { 1107 $this->Debugoutput = $method; 1108 } 1109 1110 /** 1111 * Get debug output method. 1112 * @return string 1113 */ 1114 public function getDebugOutput() 1115 { 1116 return $this->Debugoutput; 1117 } 1118 1119 /** 1120 * Set debug output level. 1121 * @param integer $level 1122 */ 1123 public function setDebugLevel($level = 0) 1124 { 1125 $this->do_debug = $level; 1126 } 1127 1128 /** 1129 * Get debug output level. 1130 * @return integer 1131 */ 1132 public function getDebugLevel() 1133 { 1134 return $this->do_debug; 1135 } 1136 1137 /** 1138 * Set SMTP timeout. 1139 * @param integer $timeout 1140 */ 1141 public function setTimeout($timeout = 0) 1142 { 1143 $this->Timeout = $timeout; 1144 } 1145 1146 /** 1147 * Get SMTP timeout. 1148 * @return integer 1149 */ 1150 public function getTimeout() 1151 { 1152 return $this->Timeout; 1153 } 1154 1155 /** 1156 * Reports an error number and string. 1157 * @param integer $errno The error number returned by PHP. 1158 * @param string $errmsg The error message returned by PHP. 1159 * @param string $errfile The file the error occurred in 1160 * @param integer $errline The line number the error occurred on 1161 */ 1162 protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0) 1163 { 1164 $notice = 'Connection failed.'; 1165 $this->setError( 1166 $notice, 1167 $errno, 1168 $errmsg 1169 ); 1170 $this->edebug( 1171 $notice . ' Error #' . $errno . ': ' . $errmsg . " [$errfile line $errline]", 1172 self::DEBUG_CONNECTION 1173 ); 1174 } 1175 1176 /** 1177 * Extract and return the ID of the last SMTP transaction based on 1178 * a list of patterns provided in SMTP::$smtp_transaction_id_patterns. 1179 * Relies on the host providing the ID in response to a DATA command. 1180 * If no reply has been received yet, it will return null. 1181 * If no pattern was matched, it will return false. 1182 * @return bool|null|string 1183 */ 1184 protected function recordLastTransactionID() 1185 { 1186 $reply = $this->getLastReply(); 1187 1188 if (empty($reply)) { 1189 $this->last_smtp_transaction_id = null; 1190 } else { 1191 $this->last_smtp_transaction_id = false; 1192 foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) { 1193 if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) { 1194 $this->last_smtp_transaction_id = $matches[1]; 1195 } 1196 } 1197 } 1198 1199 return $this->last_smtp_transaction_id; 1200 } 1201 1202 /** 1203 * Get the queue/transaction ID of the last SMTP transaction 1204 * If no reply has been received yet, it will return null. 1205 * If no pattern was matched, it will return false. 1206 * @return bool|null|string 1207 * @see recordLastTransactionID() 1208 */ 1209 public function getLastTransactionID() 1210 { 1211 return $this->last_smtp_transaction_id; 1212 } 1213 } 6 _deprecated_file( basename( __FILE__ ), '5.5.0', WPINC . '/PHPMailer/SMTP.php', __( 'The SMTP class has been moved to the wp-includes/PHPMailer subdirectory and now uses the PHPMailer\PHPMailer namespace.' ) ); 7 require __DIR__ . '/PHPMailer/SMTP.php';
Note: See TracChangeset
for help on using the changeset viewer.