1<?php 2 3/* 4 * This file is part of MailSo. 5 * 6 * (c) 2014 Usenko Timur 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace MailSo\Net; 13 14/** 15 * @category MailSo 16 * @package Net 17 */ 18abstract class NetClient 19{ 20 /** 21 * @var resource 22 */ 23 protected $rConnect; 24 25 /** 26 * @var bool 27 */ 28 protected $bUnreadBuffer; 29 30 /** 31 * @var bool 32 */ 33 protected $bRunningCallback; 34 35 /** 36 * @var string 37 */ 38 protected $sResponseBuffer; 39 40 /** 41 * @var int 42 */ 43 protected $iSecurityType; 44 45 /** 46 * @var string 47 */ 48 protected $sConnectedHost; 49 50 /** 51 * @var int 52 */ 53 protected $iConnectedPort; 54 55 /** 56 * @var bool 57 */ 58 protected $bSecure; 59 60 /** 61 * @var int 62 */ 63 protected $iConnectTimeOut; 64 65 /** 66 * @var int 67 */ 68 protected $iSocketTimeOut; 69 70 /** 71 * @var int 72 */ 73 protected $iStartConnectTime; 74 75 /** 76 * @var \MailSo\Log\Logger 77 */ 78 protected $oLogger; 79 80 /** 81 * @var bool 82 */ 83 public $__AUTOLOGOUT__; 84 85 /** 86 * @access protected 87 */ 88 protected function __construct() 89 { 90 $this->rConnect = null; 91 $this->bUnreadBuffer = false; 92 $this->bRunningCallback = false; 93 $this->oLogger = null; 94 95 $this->__AUTOLOGOUT__ = true; 96 97 $this->sResponseBuffer = ''; 98 99 $this->iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::NONE; 100 $this->sConnectedHost = ''; 101 $this->iConnectedPort = 0; 102 103 $this->bSecure = false; 104 105 $this->iConnectTimeOut = 10; 106 $this->iSocketTimeOut = 10; 107 108 $this->Clear(); 109 } 110 111 /** 112 * @return void 113 */ 114 public function __destruct() 115 { 116 try 117 { 118 if ($this->__AUTOLOGOUT__) 119 { 120 $this->LogoutAndDisconnect(); 121 } 122 else 123 { 124 $this->Disconnect(); 125 } 126 } 127 catch (\Exception $oException) {} 128 } 129 130 /** 131 * @return void 132 */ 133 public function Clear() 134 { 135 $this->sResponseBuffer = ''; 136 137 $this->sConnectedHost = ''; 138 $this->iConnectedPort = 0; 139 140 $this->iStartConnectTime = 0; 141 $this->bSecure = false; 142 } 143 144 /** 145 * @return string 146 */ 147 public function GetConnectedHost() 148 { 149 return $this->sConnectedHost; 150 } 151 152 /** 153 * @return int 154 */ 155 public function GetConnectedPort() 156 { 157 return $this->iConnectedPort; 158 } 159 160 /** 161 * @param int $iConnectTimeOut = 10 162 * @param int $iSocketTimeOut = 10 163 * 164 * @return void 165 */ 166 public function SetTimeOuts($iConnectTimeOut = 10, $iSocketTimeOut = 10) 167 { 168 $this->iConnectTimeOut = 5 < $iConnectTimeOut ? $iConnectTimeOut : 5; 169 $this->iSocketTimeOut = 5 < $iSocketTimeOut ? $iSocketTimeOut : 5; 170 } 171 172 /** 173 * @return resource|null 174 */ 175 public function ConnectionResource() 176 { 177 return $this->rConnect; 178 } 179 180 /** 181 * @param int $iErrNo 182 * @param string $sErrStr 183 * @param string $sErrFile 184 * @param int $iErrLine 185 * 186 * @return bool 187 */ 188 public function capturePhpErrorWithException($iErrNo, $sErrStr, $sErrFile, $iErrLine) 189 { 190 throw new \MailSo\Base\Exceptions\Exception($sErrStr, $iErrNo); 191 } 192 193 /** 194 * @param string $sServerName 195 * @param int $iPort 196 * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT 197 * @param bool $bVerifySsl = false 198 * @param bool $bAllowSelfSigned = true 199 * @param string $sClientCert = '' 200 * 201 * @return void 202 * 203 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 204 * @throws \MailSo\Net\Exceptions\SocketAlreadyConnectedException 205 * @throws \MailSo\Net\Exceptions\SocketCanNotConnectToHostException 206 */ 207 public function Connect($sServerName, $iPort, 208 $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, 209 $bVerifySsl = false, $bAllowSelfSigned = true, 210 $sClientCert = '') 211 { 212 if (!\MailSo\Base\Validator::NotEmptyString($sServerName, true) || !\MailSo\Base\Validator::PortInt($iPort)) 213 { 214 $this->writeLogException( 215 new \MailSo\Base\Exceptions\InvalidArgumentException(), 216 \MailSo\Log\Enumerations\Type::ERROR, true); 217 } 218 219 if ($this->IsConnected()) 220 { 221 $this->writeLogException( 222 new Exceptions\SocketAlreadyConnectedException(), 223 \MailSo\Log\Enumerations\Type::ERROR, true); 224 } 225 226 $sServerName = \trim($sServerName); 227 228 $sErrorStr = ''; 229 $iErrorNo = 0; 230 231 $this->sConnectedHost = $sServerName; 232 $this->iConnectedPort = $iPort; 233 $this->iSecurityType = $iSecurityType; 234 $this->bSecure = \MailSo\Net\Enumerations\ConnectionSecurityType::UseSSL( 235 $this->iConnectedPort, $this->iSecurityType); 236 237 if (!\preg_match('/^[a-z0-9._]{2,8}:\/\//i', $this->sConnectedHost)) 238 { 239 $this->sConnectedHost = ($this->bSecure ? 'ssl://' : 'tcp://').$this->sConnectedHost; 240// $this->sConnectedHost = ($this->bSecure ? 'ssl://' : '').$this->sConnectedHost; 241 } 242 243 if (!$this->bSecure && \MailSo\Net\Enumerations\ConnectionSecurityType::SSL === $this->iSecurityType) 244 { 245 $this->writeLogException( 246 new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('SSL isn\'t supported: ('.\implode(', ', \stream_get_transports()).')'), 247 \MailSo\Log\Enumerations\Type::ERROR, true); 248 } 249 250 $this->iStartConnectTime = \microtime(true); 251 $this->writeLog('Start connection to "'.$this->sConnectedHost.':'.$this->iConnectedPort.'"', 252 \MailSo\Log\Enumerations\Type::NOTE); 253 254// $this->rConnect = @\fsockopen($this->sConnectedHost, $this->iConnectedPort, 255// $iErrorNo, $sErrorStr, $this->iConnectTimeOut); 256 257 $bVerifySsl = !!$bVerifySsl; 258 $bAllowSelfSigned = $bVerifySsl ? !!$bAllowSelfSigned : true; 259 $aStreamContextSettings = array( 260 'ssl' => array( 261 'verify_host' => $bVerifySsl, 262 'verify_peer' => $bVerifySsl, 263 'verify_peer_name' => $bVerifySsl, 264 'allow_self_signed' => $bAllowSelfSigned 265 ) 266 ); 267 268 if (!empty($sClientCert)) 269 { 270 $aStreamContextSettings['ssl']['local_cert'] = $sClientCert; 271 } 272 273 \MailSo\Hooks::Run('Net.NetClient.StreamContextSettings/Filter', array(&$aStreamContextSettings)); 274 275 $rStreamContext = \stream_context_create($aStreamContextSettings); 276 277 \set_error_handler(array(&$this, 'capturePhpErrorWithException')); 278 279 try 280 { 281 $this->rConnect = \stream_socket_client($this->sConnectedHost.':'.$this->iConnectedPort, 282 $iErrorNo, $sErrorStr, $this->iConnectTimeOut, STREAM_CLIENT_CONNECT, $rStreamContext); 283 } 284 catch (\Exception $oExc) 285 { 286 $sErrorStr = $oExc->getMessage(); 287 $iErrorNo = $oExc->getCode(); 288 } 289 290 \restore_error_handler(); 291 292 $this->writeLog('Connected ('.(\is_resource($this->rConnect) ? 'success' : 'unsuccess').')', 293 \MailSo\Log\Enumerations\Type::NOTE); 294 295 if (!\is_resource($this->rConnect)) 296 { 297 $this->writeLogException( 298 new Exceptions\SocketCanNotConnectToHostException( 299 \MailSo\Base\Utils::ConvertSystemString($sErrorStr), (int) $iErrorNo, 300 'Can\'t connect to host "'.$this->sConnectedHost.':'.$this->iConnectedPort.'"' 301 ), \MailSo\Log\Enumerations\Type::NOTICE, true); 302 } 303 304 $this->writeLog((\microtime(true) - $this->iStartConnectTime).' (raw connection)', 305 \MailSo\Log\Enumerations\Type::TIME); 306 307 if ($this->rConnect) 308 { 309 if (\MailSo\Base\Utils::FunctionExistsAndEnabled('stream_set_timeout')) 310 { 311 @\stream_set_timeout($this->rConnect, $this->iSocketTimeOut); 312 } 313 } 314 } 315 316 public function EnableCrypto() 317 { 318 $bError = true; 319 if (\is_resource($this->rConnect) && 320 \MailSo\Base\Utils::FunctionExistsAndEnabled('stream_socket_enable_crypto')) 321 { 322 switch (true) 323 { 324 case defined('STREAM_CRYPTO_METHOD_ANY_CLIENT') && 325 @\stream_socket_enable_crypto($this->rConnect, true, STREAM_CRYPTO_METHOD_ANY_CLIENT): 326 case defined('STREAM_CRYPTO_METHOD_TLS_CLIENT') && 327 @\stream_socket_enable_crypto($this->rConnect, true, STREAM_CRYPTO_METHOD_TLS_CLIENT): 328 case defined('STREAM_CRYPTO_METHOD_SSLv23_CLIENT') && 329 @\stream_socket_enable_crypto($this->rConnect, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT): 330 $bError = false; 331 break; 332 } 333 } 334 335 if ($bError) 336 { 337 $this->writeLogException( 338 new \MailSo\Net\Exceptions\Exception('Cannot enable STARTTLS.'), 339 \MailSo\Log\Enumerations\Type::ERROR, true); 340 } 341 } 342 343 /** 344 * @return void 345 */ 346 public function Disconnect() 347 { 348 if (\is_resource($this->rConnect)) 349 { 350 $bResult = \fclose($this->rConnect); 351 352 $this->writeLog('Disconnected from "'.$this->sConnectedHost.':'.$this->iConnectedPort.'" ('. 353 (($bResult) ? 'success' : 'unsuccess').')', \MailSo\Log\Enumerations\Type::NOTE); 354 355 if (0 !== $this->iStartConnectTime) 356 { 357 $this->writeLog((\microtime(true) - $this->iStartConnectTime).' (net session)', 358 \MailSo\Log\Enumerations\Type::TIME); 359 360 $this->iStartConnectTime = 0; 361 } 362 363 $this->rConnect = null; 364 } 365 } 366 367 /** 368 * @retun void 369 * 370 * @throws \MailSo\Net\Exceptions\Exception 371 */ 372 public function LogoutAndDisconnect() 373 { 374 if (\method_exists($this, 'Logout') && !$this->bUnreadBuffer && !$this->bRunningCallback) 375 { 376 $this->Logout(); 377 } 378 379 $this->Disconnect(); 380 } 381 382 /** 383 * @param bool $bThrowExceptionOnFalse = false 384 * 385 * @return bool 386 */ 387 public function IsConnected($bThrowExceptionOnFalse = false) 388 { 389 $bResult = \is_resource($this->rConnect); 390 if (!$bResult && $bThrowExceptionOnFalse) 391 { 392 $this->writeLogException( 393 new Exceptions\SocketConnectionDoesNotAvailableException(), 394 \MailSo\Log\Enumerations\Type::ERROR, true); 395 } 396 397 return $bResult; 398 } 399 400 /** 401 * @return void 402 * 403 * @throws \MailSo\Net\Exceptions\SocketConnectionDoesNotAvailableException 404 */ 405 public function IsConnectedWithException() 406 { 407 $this->IsConnected(true); 408 } 409 410 /** 411 * @return array|bool 412 */ 413 public function StreamContextParams() 414 { 415 return \is_resource($this->rConnect) && \MailSo\Base\Utils::FunctionExistsAndEnabled('stream_context_get_options') 416 ? \stream_context_get_params($this->rConnect) : false; 417 } 418 419 /** 420 * @param string $sRaw 421 * @param bool $bWriteToLog = true 422 * @param string $sFakeRaw = '' 423 * 424 * @return void 425 * 426 * @throws \MailSo\Net\Exceptions\SocketConnectionDoesNotAvailableException 427 * @throws \MailSo\Net\Exceptions\SocketWriteException 428 */ 429 protected function sendRaw($sRaw, $bWriteToLog = true, $sFakeRaw = '') 430 { 431 if ($this->bUnreadBuffer) 432 { 433 $this->writeLogException( 434 new Exceptions\SocketUnreadBufferException(), 435 \MailSo\Log\Enumerations\Type::ERROR, true); 436 } 437 438 $bFake = 0 < \strlen($sFakeRaw); 439 $sRaw .= "\r\n"; 440 441 if ($this->oLogger && $this->oLogger->IsShowSecter()) 442 { 443 $bFake = false; 444 } 445 446 if ($bFake) 447 { 448 $sFakeRaw .= "\r\n"; 449 } 450 451 $mResult = @\fwrite($this->rConnect, $sRaw); 452 if (false === $mResult) 453 { 454 $this->IsConnected(true); 455 456 $this->writeLogException( 457 new Exceptions\SocketWriteException(), 458 \MailSo\Log\Enumerations\Type::ERROR, true); 459 } 460 else 461 { 462 \MailSo\Base\Loader::IncStatistic('NetWrite', $mResult); 463 464 if ($bWriteToLog) 465 { 466 $this->writeLogWithCrlf('> '.($bFake ? $sFakeRaw : $sRaw), //.' ['.$iWriteSize.']', 467 $bFake ? \MailSo\Log\Enumerations\Type::SECURE : \MailSo\Log\Enumerations\Type::INFO); 468 } 469 } 470 } 471 472 /** 473 * @param mixed $mReadLen = null 474 * @param bool $bForceLogin = false 475 * 476 * @return void 477 * 478 * @throws \MailSo\Net\Exceptions\SocketConnectionDoesNotAvailableException 479 * @throws \MailSo\Net\Exceptions\SocketReadException 480 */ 481 protected function getNextBuffer($mReadLen = null, $bForceLogin = false) 482 { 483 if (null === $mReadLen) 484 { 485 $this->sResponseBuffer = @\fgets($this->rConnect); 486 } 487 else 488 { 489 $this->sResponseBuffer = ''; 490 $iRead = $mReadLen; 491 while (0 < $iRead) 492 { 493 $sAddRead = @\fread($this->rConnect, $iRead); 494 if (false === $sAddRead) 495 { 496 $this->sResponseBuffer = false; 497 break; 498 } 499 500 $this->sResponseBuffer .= $sAddRead; 501 $iRead -= \strlen($sAddRead); 502 } 503 } 504 505 if (false === $this->sResponseBuffer) 506 { 507 $this->IsConnected(true); 508 $this->bUnreadBuffer = true; 509 510 $aSocketStatus = @\stream_get_meta_data($this->rConnect); 511 if (isset($aSocketStatus['timed_out']) && $aSocketStatus['timed_out']) 512 { 513 $this->writeLogException( 514 new Exceptions\SocketReadTimeoutException(), 515 \MailSo\Log\Enumerations\Type::ERROR, true); 516 } 517 else 518 { 519 $this->writeLog('Stream Meta: '. 520 \print_r($aSocketStatus, true), \MailSo\Log\Enumerations\Type::ERROR); 521 522 $this->writeLogException( 523 new Exceptions\SocketReadException(), 524 \MailSo\Log\Enumerations\Type::ERROR, true); 525 } 526 } 527 else 528 { 529 $iReadedLen = \strlen($this->sResponseBuffer); 530 if (null === $mReadLen || $bForceLogin) 531 { 532 $iLimit = 5000; // 5KB 533 if ($iLimit < $iReadedLen) 534 { 535 $this->writeLogWithCrlf('[cutted:'.$iReadedLen.'] < '.\substr($this->sResponseBuffer, 0, $iLimit).'...', 536 \MailSo\Log\Enumerations\Type::INFO); 537 } 538 else 539 { 540 $this->writeLogWithCrlf('< '.$this->sResponseBuffer, //.' ['.$iReadedLen.']', 541 \MailSo\Log\Enumerations\Type::INFO); 542 } 543 } 544 else 545 { 546 $this->writeLog('Received '.$iReadedLen.'/'.$mReadLen.' bytes.', 547 \MailSo\Log\Enumerations\Type::INFO); 548 } 549 550 \MailSo\Base\Loader::IncStatistic('NetRead', $iReadedLen); 551 } 552 } 553 554 /** 555 * @return string 556 */ 557 protected function getLogName() 558 { 559 return 'NET'; 560 } 561 562 /** 563 * @param string $sDesc 564 * @param int $iDescType = \MailSo\Log\Enumerations\Type::INFO 565 * 566 * @return void 567 */ 568 protected function writeLog($sDesc, $iDescType = \MailSo\Log\Enumerations\Type::INFO, $bDiplayCrLf = false) 569 { 570 if ($this->oLogger) 571 { 572 $this->oLogger->Write($sDesc, $iDescType, $this->getLogName(), true, $bDiplayCrLf); 573 } 574 } 575 576 /** 577 * @param string $sDesc 578 * @param int $iDescType = \MailSo\Log\Enumerations\Type::INFO 579 * 580 * @return void 581 */ 582 protected function writeLogWithCrlf($sDesc, $iDescType = \MailSo\Log\Enumerations\Type::INFO) 583 { 584 $this->writeLog($sDesc, $iDescType, true); 585 } 586 587 /** 588 * @param \Exception $oException 589 * @param int $iDescType = \MailSo\Log\Enumerations\Type::NOTICE 590 * @param bool $bThrowException = false 591 * 592 * @return void 593 */ 594 protected function writeLogException($oException, 595 $iDescType = \MailSo\Log\Enumerations\Type::NOTICE, $bThrowException = false) 596 { 597 if ($this->oLogger) 598 { 599 if ($oException instanceof Exceptions\SocketCanNotConnectToHostException) 600 { 601 $this->oLogger->Write('Socket: ['.$oException->getSocketCode().'] '.$oException->getSocketMessage(), $iDescType, $this->getLogName()); 602 } 603 604 $this->oLogger->WriteException($oException, $iDescType, $this->getLogName()); 605 } 606 607 if ($bThrowException) 608 { 609 throw $oException; 610 } 611 } 612 613 /** 614 * @param \MailSo\Log\Logger $oLogger 615 * 616 * @return void 617 * 618 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 619 */ 620 public function SetLogger($oLogger) 621 { 622 if (!($oLogger instanceof \MailSo\Log\Logger)) 623 { 624 throw new \MailSo\Base\Exceptions\InvalidArgumentException(); 625 } 626 627 $this->oLogger = $oLogger; 628 } 629 630 /** 631 * @return \MailSo\Log\Logger|null 632 */ 633 public function Logger() 634 { 635 return $this->oLogger; 636 } 637} 638