| 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 | } |