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; |
| 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; |
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'; |
| 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'; |
205 | | /** |
206 | | * Output debugging info via a user-selected method. |
207 | | * @see SMTP::$Debugoutput |
208 | | * @see SMTP::$do_debug |
209 | | * @param string $str Debug string to output |
210 | | * @param integer $level The debug level of this message; see DEBUG_* constants |
211 | | * @return void |
212 | | */ |
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 | | } |
223 | | switch ($this->Debugoutput) { |
224 | | case 'error_log': |
225 | | //Don't output, just log |
226 | | error_log($str); |
227 | | break; |
228 | | case 'html': |
229 | | //Cleans up output a bit for a better looking, HTML-safe output |
230 | | echo htmlentities( |
231 | | preg_replace('/[\r\n]+/', '', $str), |
232 | | ENT_QUOTES, |
233 | | 'UTF-8' |
234 | | ) |
235 | | . "<br>\n"; |
236 | | break; |
237 | | case 'echo': |
238 | | default: |
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"; |
246 | | } |
247 | | } |
| 204 | /** |
| 205 | * The most recent reply received from the server. |
| 206 | * @var string |
| 207 | */ |
| 208 | protected $last_reply = ''; |
249 | | /** |
250 | | * Connect to an SMTP server. |
251 | | * @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 |
254 | | * @param array $options An array of options for stream_context_create() |
255 | | * @access public |
256 | | * @return boolean |
257 | | */ |
258 | | public function connect($host, $port = null, $timeout = 30, $options = array()) |
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 | | } |
266 | | // Clear errors to avoid confusion |
267 | | $this->setError(''); |
268 | | // Make sure we are __not__ connected |
269 | | if ($this->connected()) { |
270 | | // Already connected, generate error |
271 | | $this->setError('Already connected to a server'); |
272 | | return false; |
273 | | } |
274 | | if (empty($port)) { |
275 | | $port = self::DEFAULT_SMTP_PORT; |
276 | | } |
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 | | ); |
282 | | $errno = 0; |
283 | | $errstr = ''; |
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 | | } |
312 | | // Verify we connected properly |
313 | | if (!is_resource($this->smtp_conn)) { |
314 | | $this->setError( |
315 | | 'Failed to connect to server', |
316 | | $errno, |
317 | | $errstr |
318 | | ); |
319 | | $this->edebug( |
320 | | 'SMTP ERROR: ' . $this->error['error'] |
321 | | . ": $errstr ($errno)", |
322 | | self::DEBUG_CLIENT |
323 | | ); |
324 | | return false; |
325 | | } |
326 | | $this->edebug('Connection: opened', self::DEBUG_CONNECTION); |
327 | | // SMTP server can take longer to respond, give longer timeout for first read |
328 | | // Windows does not have support for this timeout function |
329 | | if (substr(PHP_OS, 0, 3) != 'WIN') { |
330 | | $max = ini_get('max_execution_time'); |
331 | | // Don't bother if unlimited |
332 | | if ($max != 0 && $timeout > $max) { |
333 | | @set_time_limit($timeout); |
334 | | } |
335 | | stream_set_timeout($this->smtp_conn, $timeout, 0); |
336 | | } |
337 | | // Get any announcement |
338 | | $announce = $this->get_lines(); |
339 | | $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); |
340 | | return true; |
341 | | } |
| 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 | } |
343 | | /** |
344 | | * Initiate a TLS (encrypted) session. |
345 | | * @access public |
346 | | * @return boolean |
347 | | */ |
348 | | public function startTLS() |
349 | | { |
350 | | if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { |
351 | | return false; |
352 | | } |
| 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 | } |
375 | | /** |
376 | | * Perform SMTP authentication. |
377 | | * Must be run after hello(). |
378 | | * @see hello() |
379 | | * @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 |
383 | | * @param string $workstation The auth workstation for NTLM |
384 | | * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth) |
385 | | * @return bool True if successfully authenticated.* @access public |
386 | | */ |
387 | | public function authenticate( |
388 | | $username, |
389 | | $password, |
390 | | $authtype = null, |
391 | | $realm = '', |
392 | | $workstation = '', |
393 | | $OAuth = null |
394 | | ) { |
395 | | if (!$this->server_caps) { |
396 | | $this->setError('Authentication is not allowed before HELO/EHLO'); |
397 | | return false; |
398 | | } |
| 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 | } |
400 | | if (array_key_exists('EHLO', $this->server_caps)) { |
401 | | // SMTP extensions are available. Let's try to find a proper authentication method |
| 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 | } |
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)) { |
435 | | $authtype = 'LOGIN'; |
436 | | } |
437 | | switch ($authtype) { |
438 | | case 'PLAIN': |
439 | | // Start authentication |
440 | | if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { |
441 | | return false; |
442 | | } |
443 | | // Send encoded username and password |
444 | | if (!$this->sendCommand( |
445 | | 'User & Password', |
446 | | base64_encode("\0" . $username . "\0" . $password), |
447 | | 235 |
448 | | ) |
449 | | ) { |
450 | | return false; |
451 | | } |
452 | | break; |
453 | | case 'LOGIN': |
454 | | // Start authentication |
455 | | if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { |
456 | | return false; |
457 | | } |
458 | | if (!$this->sendCommand("Username", base64_encode($username), 334)) { |
459 | | return false; |
460 | | } |
461 | | if (!$this->sendCommand("Password", base64_encode($password), 235)) { |
462 | | return false; |
463 | | } |
464 | | break; |
465 | | case 'CRAM-MD5': |
466 | | // Start authentication |
467 | | if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { |
468 | | return false; |
469 | | } |
470 | | // Get the challenge |
471 | | $challenge = base64_decode(substr($this->last_reply, 4)); |
| 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)); |
485 | | /** |
486 | | * Calculate an MD5 HMAC hash. |
487 | | * Works like hash_hmac('md5', $data, $key) |
488 | | * in case that function is not available |
489 | | * @param string $data The data to hash |
490 | | * @param string $key The key to hash with |
491 | | * @access protected |
492 | | * @return string |
493 | | */ |
494 | | protected function hmac($data, $key) |
495 | | { |
496 | | if (function_exists('hash_hmac')) { |
497 | | return hash_hmac('md5', $data, $key); |
498 | | } |
| 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 | } |
508 | | $bytelen = 64; // byte length for md5 |
509 | | if (strlen($key) > $bytelen) { |
510 | | $key = pack('H*', md5($key)); |
511 | | } |
512 | | $key = str_pad($key, $bytelen, chr(0x00)); |
513 | | $ipad = str_pad('', $bytelen, chr(0x36)); |
514 | | $opad = str_pad('', $bytelen, chr(0x5c)); |
515 | | $k_ipad = $key ^ $ipad; |
516 | | $k_opad = $key ^ $opad; |
| 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 | | * Check connection state. |
523 | | * @access public |
524 | | * @return boolean True if connected. |
525 | | */ |
526 | | public function connected() |
527 | | { |
528 | | if (is_resource($this->smtp_conn)) { |
529 | | $sock_status = stream_get_meta_data($this->smtp_conn); |
530 | | if ($sock_status['eof']) { |
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 | | ); |
536 | | $this->close(); |
537 | | return false; |
538 | | } |
539 | | return true; // everything looks good |
540 | | } |
541 | | return false; |
542 | | } |
| 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 | } |
544 | | /** |
545 | | * Close the socket and clean up the state of the class. |
546 | | * Don't use this function without first trying to use QUIT. |
547 | | * @see quit() |
548 | | * @access public |
549 | | * @return void |
550 | | */ |
551 | | public function close() |
552 | | { |
553 | | $this->setError(''); |
554 | | $this->server_caps = null; |
555 | | $this->helo_rply = null; |
556 | | if (is_resource($this->smtp_conn)) { |
557 | | // close the connection and cleanup |
558 | | fclose($this->smtp_conn); |
559 | | $this->smtp_conn = null; //Makes for cleaner serialization |
560 | | $this->edebug('Connection: closed', self::DEBUG_CONNECTION); |
561 | | } |
562 | | } |
| 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 | } |
564 | | /** |
565 | | * Send an SMTP DATA command. |
566 | | * Issues a data command and sends the msg_data to the server, |
567 | | * finializing the mail transaction. $msg_data is the message |
568 | | * that is to be send with the headers. Each header needs to be |
569 | | * on a single line followed by a <CRLF> with the message headers |
570 | | * and the message body being separated by and additional <CRLF>. |
571 | | * Implements rfc 821: DATA <CRLF> |
572 | | * @param string $msg_data Message data to send |
573 | | * @access public |
574 | | * @return boolean |
575 | | */ |
576 | | public function data($msg_data) |
577 | | { |
578 | | //This will use the standard timelimit |
579 | | if (!$this->sendCommand('DATA', 'DATA', 354)) { |
580 | | return false; |
581 | | } |
| 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 | } |
605 | | foreach ($lines as $line) { |
606 | | $lines_out = array(); |
607 | | if ($in_headers and $line == '') { |
608 | | $in_headers = false; |
609 | | } |
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 |
617 | | if (!$pos) { |
618 | | //No nice break found, add a hard break |
619 | | $pos = self::MAX_LINE_LENGTH - 1; |
620 | | $lines_out[] = substr($line, 0, $pos); |
621 | | $line = substr($line, $pos); |
622 | | } else { |
623 | | //Break at the found point |
624 | | $lines_out[] = substr($line, 0, $pos); |
625 | | //Move along by the amount we dealt with |
626 | | $line = substr($line, $pos + 1); |
627 | | } |
628 | | //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 |
629 | | if ($in_headers) { |
630 | | $line = "\t" . $line; |
631 | | } |
632 | | } |
633 | | $lines_out[] = $line; |
| 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; |
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; |
653 | | } |
| 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 | } |
655 | | /** |
656 | | * Send an SMTP HELO or EHLO command. |
657 | | * Used to identify the sending server to the receiving server. |
658 | | * This makes sure that client and server are in a known state. |
659 | | * Implements RFC 821: HELO <SP> <domain> <CRLF> |
660 | | * and RFC 2821 EHLO. |
661 | | * @param string $host The host name or IP to connect to |
662 | | * @access public |
663 | | * @return boolean |
664 | | */ |
665 | | public function hello($host = '') |
666 | | { |
667 | | //Try extended hello first (RFC 2821) |
668 | | return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); |
669 | | } |
| 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 | } |
671 | | /** |
672 | | * Send an SMTP HELO or EHLO command. |
673 | | * Low-level implementation used by hello() |
674 | | * @see hello() |
675 | | * @param string $hello The HELO string |
676 | | * @param string $host The hostname to say we are |
677 | | * @access protected |
678 | | * @return boolean |
679 | | */ |
680 | | protected function sendHello($hello, $host) |
681 | | { |
682 | | $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); |
683 | | $this->helo_rply = $this->last_reply; |
684 | | if ($noerror) { |
685 | | $this->parseHelloFields($hello); |
686 | | } else { |
687 | | $this->server_caps = null; |
688 | | } |
689 | | return $noerror; |
690 | | } |
| 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 | } |
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); |
| 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); |
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 | | } |
732 | | } |
| 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 | } |
734 | | /** |
735 | | * Send an SMTP MAIL command. |
736 | | * Starts a mail transaction from the email address specified in |
737 | | * $from. Returns true if successful or false otherwise. If True |
738 | | * the mail transaction is started and then one or more recipient |
739 | | * commands may be called followed by a data command. |
740 | | * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF> |
741 | | * @param string $from Source address of this message |
742 | | * @access public |
743 | | * @return boolean |
744 | | */ |
745 | | public function mail($from) |
746 | | { |
747 | | $useVerp = ($this->do_verp ? ' XVERP' : ''); |
748 | | return $this->sendCommand( |
749 | | 'MAIL FROM', |
750 | | 'MAIL FROM:<' . $from . '>' . $useVerp, |
751 | | 250 |
752 | | ); |
753 | | } |
| 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 | } |
755 | | /** |
756 | | * Send an SMTP QUIT command. |
757 | | * Closes the socket if there is no error or the $close_on_error argument is true. |
758 | | * Implements from rfc 821: QUIT <CRLF> |
759 | | * @param boolean $close_on_error Should the connection close if an error occurs? |
760 | | * @access public |
761 | | * @return boolean |
762 | | */ |
763 | | public function quit($close_on_error = true) |
764 | | { |
765 | | $noerror = $this->sendCommand('QUIT', 'QUIT', 221); |
766 | | $err = $this->error; //Save any error |
767 | | if ($noerror or $close_on_error) { |
768 | | $this->close(); |
769 | | $this->error = $err; //Restore any error from the quit command |
770 | | } |
771 | | return $noerror; |
772 | | } |
| 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 | } |
774 | | /** |
775 | | * Send an SMTP RCPT command. |
776 | | * Sets the TO argument to $toaddr. |
777 | | * Returns true if the recipient was accepted false if it was rejected. |
778 | | * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF> |
779 | | * @param string $address The address the message is being sent to |
780 | | * @access public |
781 | | * @return boolean |
782 | | */ |
783 | | public function recipient($address) |
784 | | { |
785 | | return $this->sendCommand( |
786 | | 'RCPT TO', |
787 | | 'RCPT TO:<' . $address . '>', |
788 | | array(250, 251) |
789 | | ); |
790 | | } |
| 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 | } |
804 | | /** |
805 | | * Send a command to an SMTP server and check its return code. |
806 | | * @param string $command The command name - not sent to the server |
807 | | * @param string $commandstring The actual command to send |
808 | | * @param integer|array $expect One or more expected integer success codes |
809 | | * @access protected |
810 | | * @return boolean True on success. |
811 | | */ |
812 | | protected function sendCommand($command, $commandstring, $expect) |
813 | | { |
814 | | if (!$this->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); |
| 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); |
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 | | } |
| 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 | } |
864 | | /** |
865 | | * Send an SMTP SAML command. |
866 | | * Starts a mail transaction from the email address specified in $from. |
867 | | * Returns true if successful or false otherwise. If True |
868 | | * the mail transaction is started and then one or more recipient |
869 | | * commands may be called followed by a data command. This command |
870 | | * will send the message to the users terminal if they are logged |
871 | | * in and send them an email. |
872 | | * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF> |
873 | | * @param string $from The address the message is from |
874 | | * @access public |
875 | | * @return boolean |
876 | | */ |
877 | | public function sendAndMail($from) |
878 | | { |
879 | | return $this->sendCommand('SAML', "SAML FROM:$from", 250); |
880 | | } |
| 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 | } |
904 | | /** |
905 | | * Send an SMTP TURN command. |
906 | | * This is an optional command for SMTP that this class does not support. |
907 | | * This method is here to make the RFC821 Definition complete for this class |
908 | | * and _may_ be implemented in future |
909 | | * Implements from rfc 821: TURN <CRLF> |
910 | | * @access public |
911 | | * @return boolean |
912 | | */ |
913 | | public function turn() |
914 | | { |
915 | | $this->setError('The SMTP TURN command is not implemented'); |
916 | | $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); |
917 | | return false; |
918 | | } |
| 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 | } |
920 | | /** |
921 | | * Send raw data to the server. |
922 | | * @param string $data The data to send |
923 | | * @access public |
924 | | * @return integer|boolean The number of bytes sent to the server or false on error |
925 | | */ |
926 | | public function client_send($data) |
927 | | { |
928 | | $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); |
929 | | return fwrite($this->smtp_conn, $data); |
930 | | } |
| 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 | } |
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 | | } |
| 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 | } |
1003 | | /** |
1004 | | * Read the SMTP server's response. |
1005 | | * Either before eof or socket timeout occurs on the operation. |
1006 | | * With SMTP we can tell if we have more lines to read if the |
1007 | | * 4th character is '-' symbol. If it is a space then we don't |
1008 | | * need to read anything else. |
1009 | | * @access protected |
1010 | | * @return string |
1011 | | */ |
1012 | | protected function get_lines() |
1013 | | { |
1014 | | // If the connection is bad, give up straight away |
1015 | | if (!is_resource($this->smtp_conn)) { |
1016 | | return ''; |
1017 | | } |
1018 | | $data = ''; |
1019 | | $endtime = 0; |
1020 | | stream_set_timeout($this->smtp_conn, $this->Timeout); |
1021 | | if ($this->Timelimit > 0) { |
1022 | | $endtime = time() + $this->Timelimit; |
1023 | | } |
1024 | | while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { |
1025 | | $str = @fgets($this->smtp_conn, 515); |
1026 | | $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); |
1027 | | $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); |
1028 | | $data .= $str; |
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] == ' ')) { |
1031 | | break; |
1032 | | } |
1033 | | // Timed-out? Log and break |
1034 | | $info = stream_get_meta_data($this->smtp_conn); |
1035 | | if ($info['timed_out']) { |
1036 | | $this->edebug( |
1037 | | 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', |
1038 | | self::DEBUG_LOWLEVEL |
1039 | | ); |
1040 | | break; |
1041 | | } |
1042 | | // Now check if reads took too long |
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; |
1050 | | } |
1051 | | } |
1052 | | return $data; |
1053 | | } |
| 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 | } |
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 | | } |
| 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 | } |
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 | | } |
| 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 | } |