Overview

Namespaces

  • Kotchasan
    • Cache
    • Database
    • Http
    • Log
    • Orm
  • None
  • PHP
  • Psr
    • Cache
    • Http
      • Message
    • Log

Classes

  • ArrayIterator
  • DateInterval
  • DOMNode
  • Kotchasan
  • Kotchasan\Accordion
  • Kotchasan\ApiController
  • Kotchasan\ArrayTool
  • Kotchasan\Cache\ApcCache
  • Kotchasan\Cache\Cache
  • Kotchasan\Cache\CacheItem
  • Kotchasan\Cache\FileCache
  • Kotchasan\CKEditor
  • Kotchasan\Collection
  • Kotchasan\Config
  • Kotchasan\Controller
  • Kotchasan\Country
  • Kotchasan\Csv
  • Kotchasan\Curl
  • Kotchasan\Currency
  • Kotchasan\Database
  • Kotchasan\Database\Db
  • Kotchasan\Database\DbCache
  • Kotchasan\Database\Driver
  • Kotchasan\Database\PdoMysqlDriver
  • Kotchasan\Database\Query
  • Kotchasan\Database\QueryBuilder
  • Kotchasan\Database\Schema
  • Kotchasan\Database\Sql
  • Kotchasan\DataTable
  • Kotchasan\Date
  • Kotchasan\DOMNode
  • Kotchasan\DOMParser
  • Kotchasan\Email
  • Kotchasan\File
  • Kotchasan\Files
  • Kotchasan\Form
  • Kotchasan\Grid
  • Kotchasan\Html
  • Kotchasan\Htmldoc
  • Kotchasan\HtmlTable
  • Kotchasan\Http\AbstractMessage
  • Kotchasan\Http\AbstractRequest
  • Kotchasan\Http\Message
  • Kotchasan\Http\NotFound
  • Kotchasan\Http\Request
  • Kotchasan\Http\Response
  • Kotchasan\Http\Stream
  • Kotchasan\Http\UploadedFile
  • Kotchasan\Http\Uri
  • Kotchasan\Image
  • Kotchasan\InputItem
  • Kotchasan\Inputs
  • Kotchasan\KBase
  • Kotchasan\Language
  • Kotchasan\ListItem
  • Kotchasan\Log\AbstractLogger
  • Kotchasan\Log\Logger
  • Kotchasan\Login
  • Kotchasan\Menu
  • Kotchasan\Mime
  • Kotchasan\Model
  • Kotchasan\Number
  • Kotchasan\ObjectTool
  • Kotchasan\Orm\Field
  • Kotchasan\Orm\Recordset
  • Kotchasan\Password
  • Kotchasan\Pdf
  • Kotchasan\Province
  • Kotchasan\Router
  • Kotchasan\Session
  • Kotchasan\Singleton
  • Kotchasan\Tab
  • Kotchasan\TableRow
  • Kotchasan\Template
  • Kotchasan\Text
  • Kotchasan\Validator
  • Kotchasan\View
  • PHPMailer
  • Psr\Log\AbstractLogger
  • Psr\Log\LogLevel
  • Psr\Log\NullLogger
  • SMTP

Interfaces

  • ArrayAccess
  • Countable
  • DateTimeInterface
  • Iterator
  • IteratorAggregate
  • Psr\Cache\CacheItemInterface
  • Psr\Cache\CacheItemPoolInterface
  • Psr\Http\Message\MessageInterface
  • Psr\Http\Message\RequestInterface
  • Psr\Http\Message\ResponseInterface
  • Psr\Http\Message\ServerRequestInterface
  • Psr\Http\Message\StreamInterface
  • Psr\Http\Message\UploadedFileInterface
  • Psr\Http\Message\UriInterface
  • Psr\Log\LoggerAwareInterface
  • Psr\Log\LoggerInterface
  • SeekableIterator
  • Serializable
  • Traversable

Traits

  • Psr\Log\LoggerTrait

Exceptions

  • Exception
  • InvalidArgumentException
  • Kotchasan\ApiException
  • Kotchasan\Cache\Exception
  • Kotchasan\Database\Exception
  • Kotchasan\InputItemException
  • LogicException
  • phpmailerException
  • RuntimeException

Functions

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