1<?php 2/** 3 * This file is part of the FreeDSx LDAP package. 4 * 5 * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11namespace FreeDSx\Ldap\Protocol; 12 13use FreeDSx\Asn1\Exception\EncoderException; 14use FreeDSx\Ldap\Exception\OperationException; 15use FreeDSx\Ldap\Exception\ProtocolException; 16use FreeDSx\Ldap\Operation\Response\ExtendedResponse; 17use FreeDSx\Ldap\Operation\ResultCode; 18use FreeDSx\Ldap\Protocol\Factory\ResponseFactory; 19use FreeDSx\Ldap\Protocol\Factory\ServerBindHandlerFactory; 20use FreeDSx\Ldap\Protocol\Factory\ServerProtocolHandlerFactory; 21use FreeDSx\Ldap\Protocol\Queue\ServerQueue; 22use FreeDSx\Ldap\Server\RequestHandler\RequestHandlerInterface; 23use FreeDSx\Ldap\Server\Token\TokenInterface; 24 25/** 26 * Handles server-client specific protocol interactions. 27 * 28 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 29 */ 30class ServerProtocolHandler 31{ 32 /** 33 * @var array 34 */ 35 protected $options = [ 36 'allow_anonymous' => false, 37 'require_authentication' => true, 38 'request_handler' => null, 39 'dse_alt_server' => null, 40 'dse_naming_contexts' => 'dc=FreeDSx,dc=local', 41 'dse_vendor_name' => 'FreeDSx', 42 'dse_vendor_version' => null, 43 ]; 44 45 /** 46 * @var ServerQueue 47 */ 48 protected $queue; 49 50 /** 51 * @var int[] 52 */ 53 protected $messageIds = []; 54 55 /** 56 * @var RequestHandlerInterface 57 */ 58 protected $dispatcher; 59 60 /** 61 * @var ServerAuthorization 62 */ 63 protected $authorizer; 64 65 /** 66 * @var ServerProtocolHandlerFactory 67 */ 68 protected $protocolHandlerFactory; 69 70 /** 71 * @var ResponseFactory 72 */ 73 protected $responseFactory; 74 75 /** 76 * @var ServerBindHandlerFactory 77 */ 78 protected $bindHandlerFactory; 79 80 public function __construct( 81 ServerQueue $queue, 82 RequestHandlerInterface $dispatcher, 83 array $options = [], 84 ServerProtocolHandlerFactory $protocolHandlerFactory = null, 85 ServerBindHandlerFactory $bindHandlerFactory = null, 86 ServerAuthorization $authorizer = null, 87 ResponseFactory $responseFactory = null 88 ) { 89 $this->queue = $queue; 90 $this->dispatcher = $dispatcher; 91 $this->options = \array_merge($this->options, $options); 92 $this->authorizer = $authorizer ?? new ServerAuthorization(null, $this->options); 93 $this->protocolHandlerFactory = $protocolHandlerFactory ?? new ServerProtocolHandlerFactory(); 94 $this->bindHandlerFactory = $bindHandlerFactory ?? new ServerBindHandlerFactory(); 95 $this->responseFactory = $responseFactory ?? new ResponseFactory(); 96 } 97 98 /** 99 * Listens for messages from the socket and handles the responses/actions needed. 100 */ 101 public function handle(): void 102 { 103 try { 104 while ($message = $this->queue->getMessage()) { 105 $this->dispatchRequest($message); 106 # If a protocol handler closed the TCP connection, then just break here... 107 if (!$this->queue->isConnected()) { 108 break; 109 } 110 } 111 } catch (OperationException $e) { 112 # OperationExceptions may be thrown by any handler and will be sent back to the client as the response 113 # specific error code and message associated with the exception. 114 if (isset($message)) { 115 $this->queue->sendMessage($this->responseFactory->getStandardResponse( 116 $message, 117 $e->getCode(), 118 $e->getMessage() 119 )); 120 } 121 } catch (EncoderException | ProtocolException $e) { 122 # Per RFC 4511, 4.1.1 if the PDU cannot be parsed or is otherwise malformed a disconnect should be sent with a 123 # result code of protocol error. 124 $this->sendNoticeOfDisconnect('The message encoding is malformed.'); 125 } catch (\Exception | \Throwable $e) { 126 if ($this->queue->isConnected()) { 127 $this->sendNoticeOfDisconnect(); 128 } 129 } finally { 130 if ($this->queue->isConnected()) { 131 $this->queue->close(); 132 } 133 } 134 } 135 136 /** 137 * Routes requests from the message queue based off the current authorization state and what protocol handler the 138 * request is mapped to. 139 * 140 * @throws OperationException 141 */ 142 protected function dispatchRequest(LdapMessageRequest $message): void 143 { 144 if (!$this->isValidRequest($message)) { 145 return; 146 } 147 148 $this->messageIds[] = $message->getMessageId(); 149 150 # Send auth requests to the specific handler for it... 151 if ($this->authorizer->isAuthenticationRequest($message->getRequest())) { 152 $this->authorizer->setToken($this->handleAuthRequest($message)); 153 154 return; 155 } 156 $request = $message->getRequest(); 157 $handler = $this->protocolHandlerFactory->get($request); 158 159 # They are authenticated or authentication is not required, so pass the request along... 160 if ($this->authorizer->isAuthenticated() || !$this->authorizer->isAuthenticationRequired($request)) { 161 $handler->handleRequest( 162 $message, 163 $this->authorizer->getToken(), 164 $this->dispatcher, 165 $this->queue, 166 $this->options 167 ); 168 # Authentication is required, but they have not authenticated... 169 } else { 170 $this->queue->sendMessage($this->responseFactory->getStandardResponse( 171 $message, 172 ResultCode::INSUFFICIENT_ACCESS_RIGHTS, 173 'Authentication required.' 174 )); 175 } 176 } 177 178 /** 179 * Checks that the message ID is valid. It cannot be zero or a message ID that was already used. 180 */ 181 protected function isValidRequest(LdapMessageRequest $message): bool 182 { 183 if ($message->getMessageId() === 0) { 184 $this->queue->sendMessage($this->responseFactory->getExtendedError( 185 'The message ID 0 cannot be used in a client request.', 186 ResultCode::PROTOCOL_ERROR 187 )); 188 189 return false; 190 } 191 if (\in_array($message->getMessageId(), $this->messageIds, true)) { 192 $this->queue->sendMessage($this->responseFactory->getExtendedError( 193 sprintf('The message ID %s is not valid.', $message->getMessageId()), 194 ResultCode::PROTOCOL_ERROR 195 )); 196 197 return false; 198 } 199 200 return true; 201 } 202 203 /** 204 * Sends a bind request to the bind handler and returns the token. 205 * 206 * @throws OperationException 207 */ 208 protected function handleAuthRequest(LdapMessageRequest $message): TokenInterface 209 { 210 if (!$this->authorizer->isAuthenticationTypeSupported($message->getRequest())) { 211 throw new OperationException( 212 'The requested authentication type is not supported.', 213 ResultCode::AUTH_METHOD_UNSUPPORTED 214 ); 215 } 216 217 return $this->bindHandlerFactory->get($message->getRequest())->handleBind( 218 $message, 219 $this->dispatcher, 220 $this->queue, 221 $this->options 222 ); 223 } 224 225 protected function sendNoticeOfDisconnect(string $message = ''): void 226 { 227 $this->queue->sendMessage($this->responseFactory->getExtendedError( 228 $message, 229 ResultCode::PROTOCOL_ERROR, 230 ExtendedResponse::OID_NOTICE_OF_DISCONNECTION 231 )); 232 } 233} 234