source-class-SMTP

It appears that you are using AdBlocking software. The cost of running this website is covered by advertisements. If you like it please feel free to a small amount of money to secure the future of this website.
Overview

Classes

Interfaces

Exceptions

Functions

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

© 2004-2018 – Nicola Asuni - Tecnick.com - All rights reserved.
about - disclaimer - privacy