10b3fd2d3SAndreas Gohr<?php 2*dad993c5SAndreas Gohr 30b3fd2d3SAndreas Gohr/** 40b3fd2d3SAndreas Gohr * This file is part of the FreeDSx LDAP package. 50b3fd2d3SAndreas Gohr * 60b3fd2d3SAndreas Gohr * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 70b3fd2d3SAndreas Gohr * 80b3fd2d3SAndreas Gohr * For the full copyright and license information, please view the LICENSE 90b3fd2d3SAndreas Gohr * file that was distributed with this source code. 100b3fd2d3SAndreas Gohr */ 110b3fd2d3SAndreas Gohr 120b3fd2d3SAndreas Gohrnamespace FreeDSx\Ldap\Protocol; 130b3fd2d3SAndreas Gohr 14*dad993c5SAndreas Gohruse FreeDSx\Asn1\Exception\EncoderException; 150b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control\Control; 160b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control\ControlBag; 170b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Entry\Entry; 18*dad993c5SAndreas Gohruse FreeDSx\Ldap\Exception\BindException; 190b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\ConnectionException; 200b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\OperationException; 21*dad993c5SAndreas Gohruse FreeDSx\Ldap\Exception\ProtocolException; 22*dad993c5SAndreas Gohruse FreeDSx\Ldap\Exception\ReferralException; 230b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\UnsolicitedNotificationException; 240b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Request\RequestInterface; 250b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Response\ExtendedResponse; 260b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Response\SearchResponse; 270b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operations; 280b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Protocol\ClientProtocolHandler\ClientProtocolContext; 290b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Protocol\Factory\ClientProtocolHandlerFactory; 300b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Protocol\Queue\ClientQueue; 31*dad993c5SAndreas Gohruse FreeDSx\Sasl\Exception\SaslException; 320b3fd2d3SAndreas Gohruse FreeDSx\Socket\Exception\ConnectionException as SocketException; 330b3fd2d3SAndreas Gohruse FreeDSx\Socket\SocketPool; 340b3fd2d3SAndreas Gohr 350b3fd2d3SAndreas Gohr/** 360b3fd2d3SAndreas Gohr * Handles client specific protocol communication details. 370b3fd2d3SAndreas Gohr * 380b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com> 390b3fd2d3SAndreas Gohr */ 400b3fd2d3SAndreas Gohrclass ClientProtocolHandler 410b3fd2d3SAndreas Gohr{ 42*dad993c5SAndreas Gohr public const ROOTDSE_ATTRIBUTES = [ 430b3fd2d3SAndreas Gohr 'supportedSaslMechanisms', 440b3fd2d3SAndreas Gohr 'supportedControl', 450b3fd2d3SAndreas Gohr 'supportedLDAPVersion', 460b3fd2d3SAndreas Gohr ]; 470b3fd2d3SAndreas Gohr 480b3fd2d3SAndreas Gohr /** 490b3fd2d3SAndreas Gohr * @var SocketPool 500b3fd2d3SAndreas Gohr */ 510b3fd2d3SAndreas Gohr protected $pool; 520b3fd2d3SAndreas Gohr 530b3fd2d3SAndreas Gohr /** 540b3fd2d3SAndreas Gohr * @var ClientQueue|null 550b3fd2d3SAndreas Gohr */ 560b3fd2d3SAndreas Gohr protected $queue; 570b3fd2d3SAndreas Gohr 580b3fd2d3SAndreas Gohr /** 590b3fd2d3SAndreas Gohr * @var array 600b3fd2d3SAndreas Gohr */ 610b3fd2d3SAndreas Gohr protected $options; 620b3fd2d3SAndreas Gohr 630b3fd2d3SAndreas Gohr /** 640b3fd2d3SAndreas Gohr * @var ControlBag 650b3fd2d3SAndreas Gohr */ 660b3fd2d3SAndreas Gohr protected $controls; 670b3fd2d3SAndreas Gohr 680b3fd2d3SAndreas Gohr /** 690b3fd2d3SAndreas Gohr * @var ClientProtocolHandlerFactory 700b3fd2d3SAndreas Gohr */ 710b3fd2d3SAndreas Gohr protected $protocolHandlerFactory; 720b3fd2d3SAndreas Gohr 730b3fd2d3SAndreas Gohr /** 740b3fd2d3SAndreas Gohr * @var null|Entry 750b3fd2d3SAndreas Gohr */ 760b3fd2d3SAndreas Gohr protected $rootDse; 770b3fd2d3SAndreas Gohr 780b3fd2d3SAndreas Gohr public function __construct(array $options, ClientQueue $queue = null, SocketPool $pool = null, ClientProtocolHandlerFactory $clientProtocolHandlerFactory = null) 790b3fd2d3SAndreas Gohr { 800b3fd2d3SAndreas Gohr $this->options = $options; 810b3fd2d3SAndreas Gohr $this->pool = $pool ?? new SocketPool($options); 820b3fd2d3SAndreas Gohr $this->protocolHandlerFactory = $clientProtocolHandlerFactory ?? new ClientProtocolHandlerFactory(); 830b3fd2d3SAndreas Gohr $this->controls = new ControlBag(); 840b3fd2d3SAndreas Gohr $this->queue = $queue; 850b3fd2d3SAndreas Gohr } 860b3fd2d3SAndreas Gohr 870b3fd2d3SAndreas Gohr /** 880b3fd2d3SAndreas Gohr * @return ControlBag 890b3fd2d3SAndreas Gohr */ 900b3fd2d3SAndreas Gohr public function controls(): ControlBag 910b3fd2d3SAndreas Gohr { 920b3fd2d3SAndreas Gohr return $this->controls; 930b3fd2d3SAndreas Gohr } 940b3fd2d3SAndreas Gohr 950b3fd2d3SAndreas Gohr /** 960b3fd2d3SAndreas Gohr * Make a single search request to fetch the RootDSE. Handle the various errors that could occur. 970b3fd2d3SAndreas Gohr * 98*dad993c5SAndreas Gohr * @param bool $reload 99*dad993c5SAndreas Gohr * @return Entry 1000b3fd2d3SAndreas Gohr * @throws ConnectionException 1010b3fd2d3SAndreas Gohr * @throws OperationException 102*dad993c5SAndreas Gohr * @throws SocketException 1030b3fd2d3SAndreas Gohr * @throws UnsolicitedNotificationException 104*dad993c5SAndreas Gohr * @throws EncoderException 105*dad993c5SAndreas Gohr * @throws BindException 106*dad993c5SAndreas Gohr * @throws ProtocolException 107*dad993c5SAndreas Gohr * @throws ReferralException 108*dad993c5SAndreas Gohr * @throws SaslException 1090b3fd2d3SAndreas Gohr */ 1100b3fd2d3SAndreas Gohr public function fetchRootDse(bool $reload = false): Entry 1110b3fd2d3SAndreas Gohr { 1120b3fd2d3SAndreas Gohr if ($reload === false && $this->rootDse !== null) { 1130b3fd2d3SAndreas Gohr return $this->rootDse; 1140b3fd2d3SAndreas Gohr } 1150b3fd2d3SAndreas Gohr $message = $this->send(Operations::read('', ...self::ROOTDSE_ATTRIBUTES)); 1160b3fd2d3SAndreas Gohr if ($message === null) { 1170b3fd2d3SAndreas Gohr throw new OperationException('Expected a search response for the RootDSE. None received.'); 1180b3fd2d3SAndreas Gohr } 1190b3fd2d3SAndreas Gohr 1200b3fd2d3SAndreas Gohr $searchResponse = $message->getResponse(); 1210b3fd2d3SAndreas Gohr if (!$searchResponse instanceof SearchResponse) { 1220b3fd2d3SAndreas Gohr throw new OperationException('Expected a search response for the RootDSE. None received.'); 1230b3fd2d3SAndreas Gohr } 1240b3fd2d3SAndreas Gohr 1250b3fd2d3SAndreas Gohr $entry = $searchResponse->getEntries()->first(); 1260b3fd2d3SAndreas Gohr if ($entry === null) { 1270b3fd2d3SAndreas Gohr throw new OperationException('Expected a single entry for the RootDSE. None received.'); 1280b3fd2d3SAndreas Gohr } 1290b3fd2d3SAndreas Gohr $this->rootDse = $entry; 1300b3fd2d3SAndreas Gohr 1310b3fd2d3SAndreas Gohr return $entry; 1320b3fd2d3SAndreas Gohr } 1330b3fd2d3SAndreas Gohr 1340b3fd2d3SAndreas Gohr /** 135*dad993c5SAndreas Gohr * @param RequestInterface $request 136*dad993c5SAndreas Gohr * @param Control ...$controls 137*dad993c5SAndreas Gohr * @return LdapMessageResponse|null 1380b3fd2d3SAndreas Gohr * @throws ConnectionException 1390b3fd2d3SAndreas Gohr * @throws OperationException 140*dad993c5SAndreas Gohr * @throws SocketException 1410b3fd2d3SAndreas Gohr * @throws UnsolicitedNotificationException 142*dad993c5SAndreas Gohr * @throws EncoderException 143*dad993c5SAndreas Gohr * @throws BindException 144*dad993c5SAndreas Gohr * @throws ProtocolException 145*dad993c5SAndreas Gohr * @throws ReferralException 146*dad993c5SAndreas Gohr * @throws SaslException 1470b3fd2d3SAndreas Gohr */ 1480b3fd2d3SAndreas Gohr public function send(RequestInterface $request, Control ...$controls): ?LdapMessageResponse 1490b3fd2d3SAndreas Gohr { 1500b3fd2d3SAndreas Gohr try { 1510b3fd2d3SAndreas Gohr $context = new ClientProtocolContext( 1520b3fd2d3SAndreas Gohr $request, 1530b3fd2d3SAndreas Gohr $controls, 1540b3fd2d3SAndreas Gohr $this, 1550b3fd2d3SAndreas Gohr $this->queue(), 1560b3fd2d3SAndreas Gohr $this->options 1570b3fd2d3SAndreas Gohr ); 1580b3fd2d3SAndreas Gohr 1590b3fd2d3SAndreas Gohr $messageFrom = $this->protocolHandlerFactory->forRequest($request)->handleRequest($context); 1600b3fd2d3SAndreas Gohr $messageTo = $context->messageToSend(); 1610b3fd2d3SAndreas Gohr if ($messageFrom !== null) { 1620b3fd2d3SAndreas Gohr $messageFrom = $this->protocolHandlerFactory->forResponse($messageTo->getRequest(), $messageFrom->getResponse())->handleResponse( 1630b3fd2d3SAndreas Gohr $messageTo, 1640b3fd2d3SAndreas Gohr $messageFrom, 1650b3fd2d3SAndreas Gohr $this->queue(), 1660b3fd2d3SAndreas Gohr $this->options 1670b3fd2d3SAndreas Gohr ); 1680b3fd2d3SAndreas Gohr } 1690b3fd2d3SAndreas Gohr 1700b3fd2d3SAndreas Gohr return $messageFrom; 1710b3fd2d3SAndreas Gohr } catch (UnsolicitedNotificationException $exception) { 1720b3fd2d3SAndreas Gohr if ($exception->getOid() === ExtendedResponse::OID_NOTICE_OF_DISCONNECTION) { 1730b3fd2d3SAndreas Gohr $this->queue()->close(); 1740b3fd2d3SAndreas Gohr throw new ConnectionException( 1750b3fd2d3SAndreas Gohr sprintf('The remote server has disconnected the session. %s', $exception->getMessage()), 1760b3fd2d3SAndreas Gohr $exception->getCode() 1770b3fd2d3SAndreas Gohr ); 1780b3fd2d3SAndreas Gohr } 1790b3fd2d3SAndreas Gohr 1800b3fd2d3SAndreas Gohr throw $exception; 1810b3fd2d3SAndreas Gohr } catch (SocketException $exception) { 1820b3fd2d3SAndreas Gohr throw new ConnectionException( 1830b3fd2d3SAndreas Gohr $exception->getMessage(), 1840b3fd2d3SAndreas Gohr $exception->getCode(), 1850b3fd2d3SAndreas Gohr $exception 1860b3fd2d3SAndreas Gohr ); 1870b3fd2d3SAndreas Gohr } 1880b3fd2d3SAndreas Gohr } 1890b3fd2d3SAndreas Gohr 1900b3fd2d3SAndreas Gohr /** 1910b3fd2d3SAndreas Gohr * @return bool 1920b3fd2d3SAndreas Gohr */ 1930b3fd2d3SAndreas Gohr public function isConnected(): bool 1940b3fd2d3SAndreas Gohr { 1950b3fd2d3SAndreas Gohr return ($this->queue !== null && $this->queue->isConnected()); 1960b3fd2d3SAndreas Gohr } 1970b3fd2d3SAndreas Gohr 1980b3fd2d3SAndreas Gohr /** 1990b3fd2d3SAndreas Gohr * @throws SocketException 2000b3fd2d3SAndreas Gohr */ 2010b3fd2d3SAndreas Gohr protected function queue(): ClientQueue 2020b3fd2d3SAndreas Gohr { 2030b3fd2d3SAndreas Gohr if ($this->queue === null) { 2040b3fd2d3SAndreas Gohr $this->queue = new ClientQueue($this->pool); 2050b3fd2d3SAndreas Gohr } 2060b3fd2d3SAndreas Gohr 2070b3fd2d3SAndreas Gohr return $this->queue; 2080b3fd2d3SAndreas Gohr } 2090b3fd2d3SAndreas Gohr} 210