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