Changeset 39729 for branches/3.9/src/wp-includes/class-smtp.php
- Timestamp:
- 01/06/2017 06:03:31 AM (9 years ago)
- Location:
- branches/3.9
- Files:
-
- 2 edited
-
. (modified) (1 prop)
-
src/wp-includes/class-smtp.php (modified) (42 diffs)
Legend:
- Unmodified
- Added
- Removed
-
branches/3.9
- Property svn:mergeinfo changed
/trunk merged: 29783,33124,33142,36083,39645
- Property svn:mergeinfo changed
-
branches/3.9/src/wp-includes/class-smtp.php
r27385 r39729 2 2 /** 3 3 * PHPMailer RFC821 SMTP email transport class. 4 * Version 5.2.7 5 * PHP version 5.0.0 6 * @category PHP 7 * @package PHPMailer 8 * @link https://github.com/PHPMailer/PHPMailer/ 9 * @author Marcus Bointon (coolbru) <phpmailer@synchromedia.co.uk> 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> 10 8 * @author Jim Jagielski (jimjag) <jimjag@gmail.com> 11 9 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net> 12 * @ copyright 2013 Marcus Bointon13 * @copyright 20 04 - 2008 Andy Prevost10 * @author Brent R. Matzelle (original founder) 11 * @copyright 2014 Marcus Bointon 14 12 * @copyright 2010 - 2012 Jim Jagielski 15 * @license http://www.gnu.org/copyleft/lesser.html Distributed under the Lesser General Public License (LGPL) 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. 16 18 */ 17 19 18 20 /** 19 21 * PHPMailer RFC821 SMTP email transport class. 20 * 21 * Implements RFC 821 SMTP commands 22 * and provides some utility methods for sending mail to an SMTP server. 23 * 24 * PHP Version 5.0.0 25 * 26 * @category PHP 27 * @package PHPMailer 28 * @link https://github.com/PHPMailer/PHPMailer/blob/master/class.smtp.php 29 * @author Chris Ryan <unknown@example.com> 30 * @author Marcus Bointon <phpmailer@synchromedia.co.uk> 31 * @license http://www.gnu.org/copyleft/lesser.html Distributed under the Lesser General Public License (LGPL) 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> 32 26 */ 33 34 27 class SMTP 35 28 { 36 29 /** 30 * The PHPMailer SMTP version number. 31 * @var string 32 */ 33 const VERSION = '5.2.21'; 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 /** 37 79 * The PHPMailer SMTP Version number. 38 */ 39 const VERSION = '5.2.7'; 40 41 /** 42 * SMTP line break constant. 43 */ 44 const CRLF = "\r\n"; 45 46 /** 47 * The SMTP port to use if one is not specified. 48 */ 49 const DEFAULT_SMTP_PORT = 25; 50 51 /** 52 * The PHPMailer SMTP Version number. 53 * @type string 54 * @deprecated This should be a constant 80 * @var string 81 * @deprecated Use the `VERSION` constant instead 55 82 * @see SMTP::VERSION 56 83 */ 57 public $Version = '5.2. 7';84 public $Version = '5.2.21'; 58 85 59 86 /** 60 87 * SMTP server port number. 61 * @ type int62 * @deprecated This is only ever u ed as default value, so should be a constant88 * @var integer 89 * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead 63 90 * @see SMTP::DEFAULT_SMTP_PORT 64 91 */ … … 66 93 67 94 /** 68 * SMTP reply line ending 69 * @ typestring70 * @deprecated Use the classconstant instead95 * SMTP reply line ending. 96 * @var string 97 * @deprecated Use the `CRLF` constant instead 71 98 * @see SMTP::CRLF 72 99 */ … … 75 102 /** 76 103 * Debug output level. 77 * Options: 0 for no output, 1 for commands, 2 for data and commands 78 * @type int 79 */ 80 public $do_debug = 0; 81 82 /** 83 * The function/method to use for debugging output. 84 * Options: 'echo', 'html' or 'error_log' 85 * @type string 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 86 126 */ 87 127 public $Debugoutput = 'echo'; … … 89 129 /** 90 130 * Whether to use VERP. 91 * @type bool 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 92 134 */ 93 135 public $do_verp = false; 94 136 95 137 /** 96 * The SMTP timeout value for reads, in seconds. 97 * @type int 98 */ 99 public $Timeout = 15; 100 101 /** 102 * The SMTP timelimit value for reads, in seconds. 103 * @type int 104 */ 105 public $Timelimit = 30; 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 smtp transaction id from smtp reply 155 * Only first capture group will be use, use non-capturing group to deal with it 156 * Extend this class to override this property to fulfil your needs. 157 */ 158 protected $smtp_transaction_id_patterns = array( 159 'exim' => '/[0-9]{3} OK id=(.*)/', 160 'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/', 161 'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/' 162 ); 106 163 107 164 /** 108 165 * The socket for the server connection. 109 * @ typeresource166 * @var resource 110 167 */ 111 168 protected $smtp_conn; 112 169 113 170 /** 114 * Error message, if any, for the last call. 115 * @type string 116 */ 117 protected $error = ''; 171 * Error information, if any, for the last SMTP command. 172 * @var array 173 */ 174 protected $error = array( 175 'error' => '', 176 'detail' => '', 177 'smtp_code' => '', 178 'smtp_code_ex' => '' 179 ); 118 180 119 181 /** 120 182 * The reply the server sent to us for HELO. 121 * @type string 122 */ 123 protected $helo_rply = ''; 183 * If null, no HELO string has yet been received. 184 * @var string|null 185 */ 186 protected $helo_rply = null; 187 188 /** 189 * The set of SMTP extensions sent in reply to EHLO command. 190 * Indexes of the array are extension names. 191 * Value at index 'HELO' or 'EHLO' (according to command that was sent) 192 * represents the server name. In case of HELO it is the only element of the array. 193 * Other values can be boolean TRUE or an array containing extension options. 194 * If null, no HELO/EHLO string has yet been received. 195 * @var array|null 196 */ 197 protected $server_caps = null; 124 198 125 199 /** 126 200 * The most recent reply received from the server. 127 * @ typestring201 * @var string 128 202 */ 129 203 protected $last_reply = ''; 130 204 131 205 /** 132 * Constructor.133 * @access public134 */135 public function __construct()136 {137 $this->smtp_conn = 0;138 $this->error = null;139 $this->helo_rply = null;140 141 $this->do_debug = 0;142 }143 144 /**145 206 * Output debugging info via a user-selected method. 207 * @see SMTP::$Debugoutput 208 * @see SMTP::$do_debug 146 209 * @param string $str Debug string to output 210 * @param integer $level The debug level of this message; see DEBUG_* constants 147 211 * @return void 148 212 */ 149 protected function edebug($str) 150 { 213 protected function edebug($str, $level = 0) 214 { 215 if ($level > $this->do_debug) { 216 return; 217 } 218 //Avoid clash with built-in function names 219 if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { 220 call_user_func($this->Debugoutput, $str, $level); 221 return; 222 } 151 223 switch ($this->Debugoutput) { 152 224 case 'error_log': … … 165 237 case 'echo': 166 238 default: 167 //Just echoes whatever was received 168 echo $str; 239 //Normalize line breaks 240 $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str); 241 echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( 242 "\n", 243 "\n \t ", 244 trim($str) 245 )."\n"; 169 246 } 170 247 } … … 172 249 /** 173 250 * Connect to an SMTP server. 174 * @param string $host SMTP server IP or host name175 * @param int $portThe port number to connect to176 * @param int $timeout How long to wait for the connection to open251 * @param string $host SMTP server IP or host name 252 * @param integer $port The port number to connect to 253 * @param integer $timeout How long to wait for the connection to open 177 254 * @param array $options An array of options for stream_context_create() 178 255 * @access public 179 * @return bool 256 * @return boolean 180 257 */ 181 258 public function connect($host, $port = null, $timeout = 30, $options = array()) 182 259 { 260 static $streamok; 261 //This is enabled by default since 5.0.0 but some providers disable it 262 //Check this once and cache the result 263 if (is_null($streamok)) { 264 $streamok = function_exists('stream_socket_client'); 265 } 183 266 // Clear errors to avoid confusion 184 $this->error = null; 185 267 $this->setError(''); 186 268 // Make sure we are __not__ connected 187 269 if ($this->connected()) { 188 270 // Already connected, generate error 189 $this-> error = array('error' =>'Already connected to a server');271 $this->setError('Already connected to a server'); 190 272 return false; 191 273 } 192 193 274 if (empty($port)) { 194 275 $port = self::DEFAULT_SMTP_PORT; 195 276 } 196 197 277 // Connect to the SMTP server 278 $this->edebug( 279 "Connection: opening to $host:$port, timeout=$timeout, options=".var_export($options, true), 280 self::DEBUG_CONNECTION 281 ); 198 282 $errno = 0; 199 283 $errstr = ''; 200 $socket_context = stream_context_create($options); 201 //Suppress errors; connection failures are handled at a higher level 202 $this->smtp_conn = @stream_socket_client( 203 $host . ":" . $port, 204 $errno, 205 $errstr, 206 $timeout, 207 STREAM_CLIENT_CONNECT, 208 $socket_context 209 ); 210 284 if ($streamok) { 285 $socket_context = stream_context_create($options); 286 set_error_handler(array($this, 'errorHandler')); 287 $this->smtp_conn = stream_socket_client( 288 $host . ":" . $port, 289 $errno, 290 $errstr, 291 $timeout, 292 STREAM_CLIENT_CONNECT, 293 $socket_context 294 ); 295 restore_error_handler(); 296 } else { 297 //Fall back to fsockopen which should work in more places, but is missing some features 298 $this->edebug( 299 "Connection: stream_socket_client not available, falling back to fsockopen", 300 self::DEBUG_CONNECTION 301 ); 302 set_error_handler(array($this, 'errorHandler')); 303 $this->smtp_conn = fsockopen( 304 $host, 305 $port, 306 $errno, 307 $errstr, 308 $timeout 309 ); 310 restore_error_handler(); 311 } 211 312 // Verify we connected properly 212 if ( empty($this->smtp_conn)) {213 $this-> error = array(214 ' error' => 'Failed to connect to server',215 'errno' =>$errno,216 'errstr' =>$errstr313 if (!is_resource($this->smtp_conn)) { 314 $this->setError( 315 'Failed to connect to server', 316 $errno, 317 $errstr 217 318 ); 218 if ($this->do_debug >= 1) { 219 $this->edebug( 220 'SMTP -> ERROR: ' . $this->error['error'] 221 . ": $errstr ($errno)" 222 ); 223 } 319 $this->edebug( 320 'SMTP ERROR: ' . $this->error['error'] 321 . ": $errstr ($errno)", 322 self::DEBUG_CLIENT 323 ); 224 324 return false; 225 325 } 226 326 $this->edebug('Connection: opened', self::DEBUG_CONNECTION); 227 327 // SMTP server can take longer to respond, give longer timeout for first read 228 328 // Windows does not have support for this timeout function 229 329 if (substr(PHP_OS, 0, 3) != 'WIN') { 230 330 $max = ini_get('max_execution_time'); 231 if ($max != 0 && $timeout > $max) { // Don't bother if unlimited 331 // Don't bother if unlimited 332 if ($max != 0 && $timeout > $max) { 232 333 @set_time_limit($timeout); 233 334 } 234 335 stream_set_timeout($this->smtp_conn, $timeout, 0); 235 336 } 236 237 337 // Get any announcement 238 338 $announce = $this->get_lines(); 239 240 if ($this->do_debug >= 2) { 241 $this->edebug('SMTP -> FROM SERVER:' . $announce); 242 } 243 339 $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); 244 340 return true; 245 341 } … … 248 344 * Initiate a TLS (encrypted) session. 249 345 * @access public 250 * @return bool 346 * @return boolean 251 347 */ 252 348 public function startTLS() 253 349 { 254 if (!$this->sendCommand( "STARTTLS", "STARTTLS", 220)) {350 if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { 255 351 return false; 256 352 } 353 354 //Allow the best TLS version(s) we can 355 $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT; 356 357 //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT 358 //so add them back in manually if we can 359 if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { 360 $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; 361 $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; 362 } 363 257 364 // Begin encrypted connection 258 365 if (!stream_socket_enable_crypto( 259 366 $this->smtp_conn, 260 367 true, 261 STREAM_CRYPTO_METHOD_TLS_CLIENT 262 ) 263 ) { 368 $crypto_method 369 )) { 264 370 return false; 265 371 } … … 271 377 * Must be run after hello(). 272 378 * @see hello() 273 * @param string $username The user name274 * @param string $password The password275 * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5)276 * @param string $realm The auth realm for NTLM379 * @param string $username The user name 380 * @param string $password The password 381 * @param string $authtype The auth type (PLAIN, LOGIN, CRAM-MD5) 382 * @param string $realm The auth realm for NTLM 277 383 * @param string $workstation The auth workstation for NTLM 278 * @ access public279 * @return bool True if successfully authenticated. 384 * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth) 385 * @return bool True if successfully authenticated.* @access public 280 386 */ 281 387 public function authenticate( 282 388 $username, 283 389 $password, 284 $authtype = 'LOGIN',390 $authtype = null, 285 391 $realm = '', 286 $workstation = '' 392 $workstation = '', 393 $OAuth = null 287 394 ) { 288 if (empty($authtype)) { 395 if (!$this->server_caps) { 396 $this->setError('Authentication is not allowed before HELO/EHLO'); 397 return false; 398 } 399 400 if (array_key_exists('EHLO', $this->server_caps)) { 401 // SMTP extensions are available. Let's try to find a proper authentication method 402 403 if (!array_key_exists('AUTH', $this->server_caps)) { 404 $this->setError('Authentication is not allowed at this stage'); 405 // 'at this stage' means that auth may be allowed after the stage changes 406 // e.g. after STARTTLS 407 return false; 408 } 409 410 self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL); 411 self::edebug( 412 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), 413 self::DEBUG_LOWLEVEL 414 ); 415 416 if (empty($authtype)) { 417 foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN') as $method) { 418 if (in_array($method, $this->server_caps['AUTH'])) { 419 $authtype = $method; 420 break; 421 } 422 } 423 if (empty($authtype)) { 424 $this->setError('No supported authentication methods found'); 425 return false; 426 } 427 self::edebug('Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL); 428 } 429 430 if (!in_array($authtype, $this->server_caps['AUTH'])) { 431 $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); 432 return false; 433 } 434 } elseif (empty($authtype)) { 289 435 $authtype = 'LOGIN'; 290 436 } 291 292 437 switch ($authtype) { 293 438 case 'PLAIN': … … 318 463 } 319 464 break; 320 case 'NTLM':321 /*322 * ntlm_sasl_client.php323 * Bundled with Permission324 *325 * How to telnet in windows:326 * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx327 * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication328 */329 require_once 'extras/ntlm_sasl_client.php';330 $temp = new stdClass();331 $ntlm_client = new ntlm_sasl_client_class;332 //Check that functions are available333 if (!$ntlm_client->Initialize($temp)) {334 $this->error = array('error' => $temp->error);335 if ($this->do_debug >= 1) {336 $this->edebug(337 'You need to enable some modules in your php.ini file: '338 . $this->error['error']339 );340 }341 return false;342 }343 //msg1344 $msg1 = $ntlm_client->TypeMsg1($realm, $workstation); //msg1345 346 if (!$this->sendCommand(347 'AUTH NTLM',348 'AUTH NTLM ' . base64_encode($msg1),349 334350 )351 ) {352 return false;353 }354 355 //Though 0 based, there is a white space after the 3 digit number356 //msg2357 $challenge = substr($this->last_reply, 3);358 $challenge = base64_decode($challenge);359 $ntlm_res = $ntlm_client->NTLMResponse(360 substr($challenge, 24, 8),361 $password362 );363 //msg3364 $msg3 = $ntlm_client->TypeMsg3(365 $ntlm_res,366 $username,367 $realm,368 $workstation369 );370 // send encoded username371 return $this->sendCommand('Username', base64_encode($msg3), 235);372 break;373 465 case 'CRAM-MD5': 374 466 // Start authentication … … 384 476 // send encoded credentials 385 477 return $this->sendCommand('Username', base64_encode($response), 235); 386 break; 478 default: 479 $this->setError("Authentication method \"$authtype\" is not supported"); 480 return false; 387 481 } 388 482 return true; … … 410 504 // Creates an md5 HMAC. 411 505 // Eliminates the need to install mhash to compute a HMAC 412 // Hackedby Lance Rushing413 414 $b = 64; // byte length for md5415 if (strlen($key) > $b ) {506 // by Lance Rushing 507 508 $bytelen = 64; // byte length for md5 509 if (strlen($key) > $bytelen) { 416 510 $key = pack('H*', md5($key)); 417 511 } 418 $key = str_pad($key, $b , chr(0x00));419 $ipad = str_pad('', $b , chr(0x36));420 $opad = str_pad('', $b , chr(0x5c));512 $key = str_pad($key, $bytelen, chr(0x00)); 513 $ipad = str_pad('', $bytelen, chr(0x36)); 514 $opad = str_pad('', $bytelen, chr(0x5c)); 421 515 $k_ipad = $key ^ $ipad; 422 516 $k_opad = $key ^ $opad; … … 428 522 * Check connection state. 429 523 * @access public 430 * @return bool True if connected.524 * @return boolean True if connected. 431 525 */ 432 526 public function connected() 433 527 { 434 if ( !empty($this->smtp_conn)) {528 if (is_resource($this->smtp_conn)) { 435 529 $sock_status = stream_get_meta_data($this->smtp_conn); 436 530 if ($sock_status['eof']) { 437 // the socket is valid but we are not connected 438 if ($this->do_debug >= 1) { 439 $this->edebug( 440 'SMTP -> NOTICE: EOF caught while checking if connected' 441 ); 442 } 531 // The socket is valid but we are not connected 532 $this->edebug( 533 'SMTP NOTICE: EOF caught while checking if connected', 534 self::DEBUG_CLIENT 535 ); 443 536 $this->close(); 444 537 return false; … … 458 551 public function close() 459 552 { 460 $this->error = null; // so there is no confusion 553 $this->setError(''); 554 $this->server_caps = null; 461 555 $this->helo_rply = null; 462 if ( !empty($this->smtp_conn)) {556 if (is_resource($this->smtp_conn)) { 463 557 // close the connection and cleanup 464 558 fclose($this->smtp_conn); 465 $this->smtp_conn = 0; 559 $this->smtp_conn = null; //Makes for cleaner serialization 560 $this->edebug('Connection: closed', self::DEBUG_CONNECTION); 466 561 } 467 562 } … … 477 572 * @param string $msg_data Message data to send 478 573 * @access public 479 * @return bool 574 * @return boolean 480 575 */ 481 576 public function data($msg_data) 482 577 { 578 //This will use the standard timelimit 483 579 if (!$this->sendCommand('DATA', 'DATA', 354)) { 484 580 return false; … … 486 582 487 583 /* The server is ready to accept data! 488 * according to rfc821 we should not send more than 1000 489 * including the CRLF 490 * characters on a single line so we will break the data up 491 * into lines by \r and/or \n then if needed we will break 492 * each of those into smaller lines to fit within the limit. 493 * in addition we will be looking for lines that start with 494 * a period '.' and append and additional period '.' to that 495 * line. NOTE: this does not count towards limit. 584 * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF) 585 * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into 586 * smaller lines to fit within the limit. 587 * We will also look for lines that start with a '.' and prepend an additional '.'. 588 * NOTE: this does not count towards line-length limit. 496 589 */ 497 590 498 // Normalize the line breaks before exploding 499 $msg_data = str_replace("\r\n", "\n", $msg_data); 500 $msg_data = str_replace("\r", "\n", $msg_data); 501 $lines = explode("\n", $msg_data); 502 503 /* We need to find a good way to determine if headers are 504 * in the msg_data or if it is a straight msg body 505 * currently I am assuming rfc822 definitions of msg headers 506 * and if the first field of the first line (':' separated) 507 * does not contain a space then it _should_ be a header 508 * and we can process all lines before a blank "" line as 509 * headers. 591 // Normalize line breaks before exploding 592 $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data)); 593 594 /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field 595 * of the first line (':' separated) does not contain a space then it _should_ be a header and we will 596 * process all lines before a blank line as headers. 510 597 */ 511 598 512 599 $field = substr($lines[0], 0, strpos($lines[0], ':')); 513 600 $in_headers = false; 514 if (!empty($field) && !strstr($field, ' ')) {601 if (!empty($field) && strpos($field, ' ') === false) { 515 602 $in_headers = true; 516 603 } 517 604 518 //RFC 2822 section 2.1.1 limit519 $max_line_length = 998;520 521 605 foreach ($lines as $line) { 522 $lines_out = null;523 if ($ line == '' && $in_headers) {606 $lines_out = array(); 607 if ($in_headers and $line == '') { 524 608 $in_headers = false; 525 609 } 526 // ok we need to break this line up into several smaller lines 527 while (strlen($line) > $max_line_length) { 528 $pos = strrpos(substr($line, 0, $max_line_length), ' '); 529 530 // Patch to fix DOS attack 610 //Break this line up into several smaller lines if it's too long 611 //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), 612 while (isset($line[self::MAX_LINE_LENGTH])) { 613 //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on 614 //so as to avoid breaking in the middle of a word 615 $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); 616 //Deliberately matches both false and 0 531 617 if (!$pos) { 532 $pos = $max_line_length - 1; 618 //No nice break found, add a hard break 619 $pos = self::MAX_LINE_LENGTH - 1; 533 620 $lines_out[] = substr($line, 0, $pos); 534 621 $line = substr($line, $pos); 535 622 } else { 623 //Break at the found point 536 624 $lines_out[] = substr($line, 0, $pos); 625 //Move along by the amount we dealt with 537 626 $line = substr($line, $pos + 1); 538 627 } 539 540 /* If processing headers add a LWSP-char to the front of new line 541 * rfc822 on long msg headers 542 */ 628 //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 543 629 if ($in_headers) { 544 630 $line = "\t" . $line; … … 547 633 $lines_out[] = $line; 548 634 549 // send the lines to the server 550 while (list(, $line_out) = @each($lines_out)) { 551 if (strlen($line_out) > 0) { 552 if (substr($line_out, 0, 1) == '.') { 553 $line_out = '.' . $line_out; 554 } 635 //Send the lines to the server 636 foreach ($lines_out as $line_out) { 637 //RFC2821 section 4.5.2 638 if (!empty($line_out) and $line_out[0] == '.') { 639 $line_out = '.' . $line_out; 555 640 } 556 641 $this->client_send($line_out . self::CRLF); … … 558 643 } 559 644 560 // Message data has been sent, complete the command 561 return $this->sendCommand('DATA END', '.', 250); 645 //Message data has been sent, complete the command 646 //Increase timelimit for end of DATA command 647 $savetimelimit = $this->Timelimit; 648 $this->Timelimit = $this->Timelimit * 2; 649 $result = $this->sendCommand('DATA END', '.', 250); 650 //Restore timelimit 651 $this->Timelimit = $savetimelimit; 652 return $result; 562 653 } 563 654 … … 566 657 * Used to identify the sending server to the receiving server. 567 658 * This makes sure that client and server are in a known state. 568 * Implements fromRFC 821: HELO <SP> <domain> <CRLF>659 * Implements RFC 821: HELO <SP> <domain> <CRLF> 569 660 * and RFC 2821 EHLO. 570 661 * @param string $host The host name or IP to connect to 571 662 * @access public 572 * @return bool 663 * @return boolean 573 664 */ 574 665 public function hello($host = '') 575 666 { 576 // Try extended hello first (RFC 2821) 577 if (!$this->sendHello('EHLO', $host)) { 578 if (!$this->sendHello('HELO', $host)) { 579 return false; 580 } 581 } 582 583 return true; 667 //Try extended hello first (RFC 2821) 668 return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); 584 669 } 585 670 … … 589 674 * @see hello() 590 675 * @param string $hello The HELO string 591 * @param string $host The hostname to say we are676 * @param string $host The hostname to say we are 592 677 * @access protected 593 * @return bool 678 * @return boolean 594 679 */ 595 680 protected function sendHello($hello, $host) … … 597 682 $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); 598 683 $this->helo_rply = $this->last_reply; 684 if ($noerror) { 685 $this->parseHelloFields($hello); 686 } else { 687 $this->server_caps = null; 688 } 599 689 return $noerror; 690 } 691 692 /** 693 * Parse a reply to HELO/EHLO command to discover server extensions. 694 * In case of HELO, the only parameter that can be discovered is a server name. 695 * @access protected 696 * @param string $type - 'HELO' or 'EHLO' 697 */ 698 protected function parseHelloFields($type) 699 { 700 $this->server_caps = array(); 701 $lines = explode("\n", $this->helo_rply); 702 703 foreach ($lines as $n => $s) { 704 //First 4 chars contain response code followed by - or space 705 $s = trim(substr($s, 4)); 706 if (empty($s)) { 707 continue; 708 } 709 $fields = explode(' ', $s); 710 if (!empty($fields)) { 711 if (!$n) { 712 $name = $type; 713 $fields = $fields[0]; 714 } else { 715 $name = array_shift($fields); 716 switch ($name) { 717 case 'SIZE': 718 $fields = ($fields ? $fields[0] : 0); 719 break; 720 case 'AUTH': 721 if (!is_array($fields)) { 722 $fields = array(); 723 } 724 break; 725 default: 726 $fields = true; 727 } 728 } 729 $this->server_caps[$name] = $fields; 730 } 731 } 600 732 } 601 733 … … 609 741 * @param string $from Source address of this message 610 742 * @access public 611 * @return bool 743 * @return boolean 612 744 */ 613 745 public function mail($from) … … 625 757 * Closes the socket if there is no error or the $close_on_error argument is true. 626 758 * Implements from rfc 821: QUIT <CRLF> 627 * @param bool $close_on_error Should the connection close if an error occurs?628 * @access public 629 * @return bool 759 * @param boolean $close_on_error Should the connection close if an error occurs? 760 * @access public 761 * @return boolean 630 762 */ 631 763 public function quit($close_on_error = true) 632 764 { 633 765 $noerror = $this->sendCommand('QUIT', 'QUIT', 221); 634 $e = $this->error; //Save any error766 $err = $this->error; //Save any error 635 767 if ($noerror or $close_on_error) { 636 768 $this->close(); 637 $this->error = $e ; //Restore any error from the quit command769 $this->error = $err; //Restore any error from the quit command 638 770 } 639 771 return $noerror; … … 642 774 /** 643 775 * Send an SMTP RCPT command. 644 * Sets the TO argument to $to .776 * Sets the TO argument to $toaddr. 645 777 * Returns true if the recipient was accepted false if it was rejected. 646 778 * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF> 647 * @param string $ toThe address the message is being sent to648 * @access public 649 * @return bool 650 */ 651 public function recipient($ to)779 * @param string $address The address the message is being sent to 780 * @access public 781 * @return boolean 782 */ 783 public function recipient($address) 652 784 { 653 785 return $this->sendCommand( 654 'RCPT TO ',655 'RCPT TO:<' . $ to. '>',786 'RCPT TO', 787 'RCPT TO:<' . $address . '>', 656 788 array(250, 251) 657 789 ); … … 663 795 * Implements rfc 821: RSET <CRLF> 664 796 * @access public 665 * @return bool True on success.797 * @return boolean True on success. 666 798 */ 667 799 public function reset() … … 672 804 /** 673 805 * Send a command to an SMTP server and check its return code. 674 * @param string $command The command name - not sent to the server806 * @param string $command The command name - not sent to the server 675 807 * @param string $commandstring The actual command to send 676 * @param int |array $expectOne or more expected integer success codes808 * @param integer|array $expect One or more expected integer success codes 677 809 * @access protected 678 * @return bool True on success.810 * @return boolean True on success. 679 811 */ 680 812 protected function sendCommand($command, $commandstring, $expect) 681 813 { 682 814 if (!$this->connected()) { 683 $this->error = array( 684 "error" => "Called $command without being connected" 815 $this->setError("Called $command without being connected"); 816 return false; 817 } 818 //Reject line breaks in all commands 819 if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) { 820 $this->setError("Command '$command' contained line breaks"); 821 return false; 822 } 823 $this->client_send($commandstring . self::CRLF); 824 825 $this->last_reply = $this->get_lines(); 826 // Fetch SMTP code and possible error code explanation 827 $matches = array(); 828 if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) { 829 $code = $matches[1]; 830 $code_ex = (count($matches) > 2 ? $matches[2] : null); 831 // Cut off error code from each response line 832 $detail = preg_replace( 833 "/{$code}[ -]".($code_ex ? str_replace('.', '\\.', $code_ex).' ' : '')."/m", 834 '', 835 $this->last_reply 836 ); 837 } else { 838 // Fall back to simple parsing if regex fails 839 $code = substr($this->last_reply, 0, 3); 840 $code_ex = null; 841 $detail = substr($this->last_reply, 4); 842 } 843 844 $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); 845 846 if (!in_array($code, (array)$expect)) { 847 $this->setError( 848 "$command command failed", 849 $detail, 850 $code, 851 $code_ex 852 ); 853 $this->edebug( 854 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, 855 self::DEBUG_CLIENT 685 856 ); 686 857 return false; 687 858 } 688 $this->client_send($commandstring . self::CRLF); 689 690 $reply = $this->get_lines(); 691 $code = substr($reply, 0, 3); 692 693 if ($this->do_debug >= 2) { 694 $this->edebug('SMTP -> FROM SERVER:' . $reply); 695 } 696 697 if (!in_array($code, (array)$expect)) { 698 $this->last_reply = null; 699 $this->error = array( 700 "error" => "$command command failed", 701 "smtp_code" => $code, 702 "detail" => substr($reply, 4) 703 ); 704 if ($this->do_debug >= 1) { 705 $this->edebug( 706 'SMTP -> ERROR: ' . $this->error['error'] . ': ' . $reply 707 ); 708 } 709 return false; 710 } 711 712 $this->last_reply = $reply; 713 $this->error = null; 859 860 $this->setError(''); 714 861 return true; 715 862 } … … 726 873 * @param string $from The address the message is from 727 874 * @access public 728 * @return bool 875 * @return boolean 729 876 */ 730 877 public function sendAndMail($from) 731 878 { 732 return $this->sendCommand( "SAML", "SAML FROM:$from", 250);879 return $this->sendCommand('SAML', "SAML FROM:$from", 250); 733 880 } 734 881 … … 737 884 * @param string $name The name to verify 738 885 * @access public 739 * @return bool 886 * @return boolean 740 887 */ 741 888 public function verify($name) 742 889 { 743 return $this->sendCommand( "VRFY", "VRFY $name", array(250, 251));890 return $this->sendCommand('VRFY', "VRFY $name", array(250, 251)); 744 891 } 745 892 … … 748 895 * Used to keep keep-alives alive, doesn't actually do anything 749 896 * @access public 750 * @return bool 897 * @return boolean 751 898 */ 752 899 public function noop() 753 900 { 754 return $this->sendCommand( "NOOP", "NOOP", 250);901 return $this->sendCommand('NOOP', 'NOOP', 250); 755 902 } 756 903 … … 758 905 * Send an SMTP TURN command. 759 906 * This is an optional command for SMTP that this class does not support. 760 * This method is here to make the RFC821 Definition 761 * complete for this class and __may__ be implemented in future907 * This method is here to make the RFC821 Definition complete for this class 908 * and _may_ be implemented in future 762 909 * Implements from rfc 821: TURN <CRLF> 763 910 * @access public 764 * @return bool 911 * @return boolean 765 912 */ 766 913 public function turn() 767 914 { 768 $this->error = array( 769 'error' => 'The SMTP TURN command is not implemented' 770 ); 771 if ($this->do_debug >= 1) { 772 $this->edebug('SMTP -> NOTICE: ' . $this->error['error']); 773 } 915 $this->setError('The SMTP TURN command is not implemented'); 916 $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); 774 917 return false; 775 918 } … … 779 922 * @param string $data The data to send 780 923 * @access public 781 * @return int |bool The number of bytes sent to the server or FALSEon error924 * @return integer|boolean The number of bytes sent to the server or false on error 782 925 */ 783 926 public function client_send($data) 784 927 { 785 if ($this->do_debug >= 1) { 786 $this->edebug("CLIENT -> SMTP: $data"); 787 } 928 $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); 788 929 return fwrite($this->smtp_conn, $data); 789 930 } … … 797 938 { 798 939 return $this->error; 940 } 941 942 /** 943 * Get SMTP extensions available on the server 944 * @access public 945 * @return array|null 946 */ 947 public function getServerExtList() 948 { 949 return $this->server_caps; 950 } 951 952 /** 953 * A multipurpose method 954 * The method works in three ways, dependent on argument value and current state 955 * 1. HELO/EHLO was not sent - returns null and set up $this->error 956 * 2. HELO was sent 957 * $name = 'HELO': returns server name 958 * $name = 'EHLO': returns boolean false 959 * $name = any string: returns null and set up $this->error 960 * 3. EHLO was sent 961 * $name = 'HELO'|'EHLO': returns server name 962 * $name = any string: if extension $name exists, returns boolean True 963 * or its options. Otherwise returns boolean False 964 * In other words, one can use this method to detect 3 conditions: 965 * - null returned: handshake was not or we don't know about ext (refer to $this->error) 966 * - false returned: the requested feature exactly not exists 967 * - positive value returned: the requested feature exists 968 * @param string $name Name of SMTP extension or 'HELO'|'EHLO' 969 * @return mixed 970 */ 971 public function getServerExt($name) 972 { 973 if (!$this->server_caps) { 974 $this->setError('No HELO/EHLO was sent'); 975 return null; 976 } 977 978 // the tight logic knot ;) 979 if (!array_key_exists($name, $this->server_caps)) { 980 if ($name == 'HELO') { 981 return $this->server_caps['EHLO']; 982 } 983 if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) { 984 return false; 985 } 986 $this->setError('HELO handshake was used. Client knows nothing about server extensions'); 987 return null; 988 } 989 990 return $this->server_caps[$name]; 799 991 } 800 992 … … 820 1012 protected function get_lines() 821 1013 { 1014 // If the connection is bad, give up straight away 1015 if (!is_resource($this->smtp_conn)) { 1016 return ''; 1017 } 822 1018 $data = ''; 823 1019 $endtime = 0; 824 // If the connection is bad, give up now825 if (!is_resource($this->smtp_conn)) {826 return $data;827 }828 1020 stream_set_timeout($this->smtp_conn, $this->Timeout); 829 1021 if ($this->Timelimit > 0) { … … 832 1024 while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { 833 1025 $str = @fgets($this->smtp_conn, 515); 834 if ($this->do_debug >= 4) { 835 $this->edebug("SMTP -> get_lines(): \$data was \"$data\""); 836 $this->edebug("SMTP -> get_lines(): \$str is \"$str\""); 837 } 1026 $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); 1027 $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); 838 1028 $data .= $str; 839 if ($this->do_debug >= 4) { 840 $this->edebug("SMTP -> get_lines(): \$data is \"$data\""); 841 } 842 // if 4th character is a space, we are done reading, break the loop 843 if (substr($str, 3, 1) == ' ') { 1029 // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen 1030 if ((isset($str[3]) and $str[3] == ' ')) { 844 1031 break; 845 1032 } … … 847 1034 $info = stream_get_meta_data($this->smtp_conn); 848 1035 if ($info['timed_out']) { 849 if ($this->do_debug >= 4) { 850 $this->edebug( 851 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)' 852 ); 853 } 1036 $this->edebug( 1037 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', 1038 self::DEBUG_LOWLEVEL 1039 ); 854 1040 break; 855 1041 } 856 1042 // Now check if reads took too long 857 if ($endtime) { 858 if (time() > $endtime) { 859 if ($this->do_debug >= 4) { 860 $this->edebug( 861 'SMTP -> get_lines(): timelimit reached (' 862 . $this->Timelimit . ' sec)' 863 ); 864 } 865 break; 866 } 1043 if ($endtime and time() > $endtime) { 1044 $this->edebug( 1045 'SMTP -> get_lines(): timelimit reached ('. 1046 $this->Timelimit . ' sec)', 1047 self::DEBUG_LOWLEVEL 1048 ); 1049 break; 867 1050 } 868 1051 } … … 872 1055 /** 873 1056 * Enable or disable VERP address generation. 874 * @param bool $enabled1057 * @param boolean $enabled 875 1058 */ 876 1059 public function setVerp($enabled = false) … … 881 1064 /** 882 1065 * Get VERP address generation mode. 883 * @return bool 1066 * @return boolean 884 1067 */ 885 1068 public function getVerp() … … 889 1072 890 1073 /** 1074 * Set error messages and codes. 1075 * @param string $message The error message 1076 * @param string $detail Further detail on the error 1077 * @param string $smtp_code An associated SMTP error code 1078 * @param string $smtp_code_ex Extended SMTP code 1079 */ 1080 protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '') 1081 { 1082 $this->error = array( 1083 'error' => $message, 1084 'detail' => $detail, 1085 'smtp_code' => $smtp_code, 1086 'smtp_code_ex' => $smtp_code_ex 1087 ); 1088 } 1089 1090 /** 891 1091 * Set debug output method. 892 * @param string $method The function/method to use for debugging output.1092 * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it. 893 1093 */ 894 1094 public function setDebugOutput($method = 'echo') … … 908 1108 /** 909 1109 * Set debug output level. 910 * @param int $level1110 * @param integer $level 911 1111 */ 912 1112 public function setDebugLevel($level = 0) … … 917 1117 /** 918 1118 * Get debug output level. 919 * @return int 1119 * @return integer 920 1120 */ 921 1121 public function getDebugLevel() … … 926 1126 /** 927 1127 * Set SMTP timeout. 928 * @param int $timeout1128 * @param integer $timeout 929 1129 */ 930 1130 public function setTimeout($timeout = 0) … … 935 1135 /** 936 1136 * Get SMTP timeout. 937 * @return int 1137 * @return integer 938 1138 */ 939 1139 public function getTimeout() … … 941 1141 return $this->Timeout; 942 1142 } 1143 1144 /** 1145 * Reports an error number and string. 1146 * @param integer $errno The error number returned by PHP. 1147 * @param string $errmsg The error message returned by PHP. 1148 */ 1149 protected function errorHandler($errno, $errmsg) 1150 { 1151 $notice = 'Connection: Failed to connect to server.'; 1152 $this->setError( 1153 $notice, 1154 $errno, 1155 $errmsg 1156 ); 1157 $this->edebug( 1158 $notice . ' Error number ' . $errno . '. "Error notice: ' . $errmsg, 1159 self::DEBUG_CONNECTION 1160 ); 1161 } 1162 1163 /** 1164 * Will return the ID of the last smtp transaction based on a list of patterns provided 1165 * in SMTP::$smtp_transaction_id_patterns. 1166 * If no reply has been received yet, it will return null. 1167 * If no pattern has been matched, it will return false. 1168 * @return bool|null|string 1169 */ 1170 public function getLastTransactionID() 1171 { 1172 $reply = $this->getLastReply(); 1173 1174 if (empty($reply)) { 1175 return null; 1176 } 1177 1178 foreach($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) { 1179 if(preg_match($smtp_transaction_id_pattern, $reply, $matches)) { 1180 return $matches[1]; 1181 } 1182 } 1183 1184 return false; 1185 } 943 1186 }
Note: See TracChangeset
for help on using the changeset viewer.