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; 130b3fd2d3SAndreas Gohr 140b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\RuntimeException; 15*dad993c5SAndreas Gohruse FreeDSx\Ldap\Server\RequestHandler\PagingHandlerInterface; 16*dad993c5SAndreas Gohruse FreeDSx\Ldap\Server\RequestHandler\ProxyHandler; 17*dad993c5SAndreas Gohruse FreeDSx\Ldap\Server\RequestHandler\ProxyPagingHandler; 180b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Server\RequestHandler\RequestHandlerInterface; 19*dad993c5SAndreas Gohruse FreeDSx\Ldap\Server\RequestHandler\RootDseHandlerInterface; 200b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Server\ServerRunner\PcntlServerRunner; 210b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Server\ServerRunner\ServerRunnerInterface; 22*dad993c5SAndreas Gohruse FreeDSx\Socket\Exception\ConnectionException; 230b3fd2d3SAndreas Gohruse FreeDSx\Socket\SocketServer; 24*dad993c5SAndreas Gohruse Psr\Log\LoggerInterface; 250b3fd2d3SAndreas Gohr 260b3fd2d3SAndreas Gohr/** 270b3fd2d3SAndreas Gohr * The LDAP server. 280b3fd2d3SAndreas Gohr * 290b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com> 300b3fd2d3SAndreas Gohr */ 310b3fd2d3SAndreas Gohrclass LdapServer 320b3fd2d3SAndreas Gohr{ 33*dad993c5SAndreas Gohr use LoggerTrait; 34*dad993c5SAndreas Gohr 350b3fd2d3SAndreas Gohr /** 360b3fd2d3SAndreas Gohr * @var array 370b3fd2d3SAndreas Gohr */ 380b3fd2d3SAndreas Gohr protected $options = [ 390b3fd2d3SAndreas Gohr 'ip' => '0.0.0.0', 400b3fd2d3SAndreas Gohr 'port' => 389, 41*dad993c5SAndreas Gohr 'unix_socket' => '/var/run/ldap.socket', 42*dad993c5SAndreas Gohr 'transport' => 'tcp', 430b3fd2d3SAndreas Gohr 'idle_timeout' => 600, 440b3fd2d3SAndreas Gohr 'require_authentication' => true, 450b3fd2d3SAndreas Gohr 'allow_anonymous' => false, 460b3fd2d3SAndreas Gohr 'request_handler' => null, 47*dad993c5SAndreas Gohr 'rootdse_handler' => null, 48*dad993c5SAndreas Gohr 'paging_handler' => null, 49*dad993c5SAndreas Gohr 'logger' => null, 50*dad993c5SAndreas Gohr 'use_ssl' => false, 510b3fd2d3SAndreas Gohr 'ssl_cert' => null, 520b3fd2d3SAndreas Gohr 'ssl_cert_passphrase' => null, 530b3fd2d3SAndreas Gohr 'dse_alt_server' => null, 540b3fd2d3SAndreas Gohr 'dse_naming_contexts' => 'dc=FreeDSx,dc=local', 550b3fd2d3SAndreas Gohr 'dse_vendor_name' => 'FreeDSx', 560b3fd2d3SAndreas Gohr 'dse_vendor_version' => null, 570b3fd2d3SAndreas Gohr ]; 580b3fd2d3SAndreas Gohr 590b3fd2d3SAndreas Gohr /** 60*dad993c5SAndreas Gohr * @var ServerRunnerInterface|null 610b3fd2d3SAndreas Gohr */ 620b3fd2d3SAndreas Gohr protected $runner; 630b3fd2d3SAndreas Gohr 640b3fd2d3SAndreas Gohr /** 650b3fd2d3SAndreas Gohr * @param array $options 660b3fd2d3SAndreas Gohr * @param ServerRunnerInterface|null $serverRunner 67*dad993c5SAndreas Gohr * @throws RuntimeException 680b3fd2d3SAndreas Gohr */ 69*dad993c5SAndreas Gohr public function __construct( 70*dad993c5SAndreas Gohr array $options = [], 71*dad993c5SAndreas Gohr ?ServerRunnerInterface $serverRunner = null 72*dad993c5SAndreas Gohr ) { 73*dad993c5SAndreas Gohr $this->options = array_merge( 74*dad993c5SAndreas Gohr $this->options, 75*dad993c5SAndreas Gohr $options 76*dad993c5SAndreas Gohr ); 77*dad993c5SAndreas Gohr $this->runner = $serverRunner; 780b3fd2d3SAndreas Gohr } 790b3fd2d3SAndreas Gohr 800b3fd2d3SAndreas Gohr /** 810b3fd2d3SAndreas Gohr * Runs the LDAP server. Binds the socket on the request IP/port and sends it to the server runner. 82*dad993c5SAndreas Gohr * 83*dad993c5SAndreas Gohr * @throws ConnectionException 840b3fd2d3SAndreas Gohr */ 850b3fd2d3SAndreas Gohr public function run(): void 860b3fd2d3SAndreas Gohr { 87*dad993c5SAndreas Gohr $isUnixSocket = $this->options['transport'] === 'unix'; 88*dad993c5SAndreas Gohr $resource = $isUnixSocket 89*dad993c5SAndreas Gohr ? $this->options['unix_socket'] 90*dad993c5SAndreas Gohr : $this->options['ip']; 91*dad993c5SAndreas Gohr 92*dad993c5SAndreas Gohr if ($isUnixSocket) { 93*dad993c5SAndreas Gohr $this->removeExistingSocketIfNeeded($resource); 94*dad993c5SAndreas Gohr } 95*dad993c5SAndreas Gohr 96*dad993c5SAndreas Gohr $socketServer = SocketServer::bind( 97*dad993c5SAndreas Gohr $resource, 98*dad993c5SAndreas Gohr $this->options['port'], 99*dad993c5SAndreas Gohr $this->options 100*dad993c5SAndreas Gohr ); 101*dad993c5SAndreas Gohr 102*dad993c5SAndreas Gohr $this->runner()->run($socketServer); 1030b3fd2d3SAndreas Gohr } 1040b3fd2d3SAndreas Gohr 1050b3fd2d3SAndreas Gohr /** 106*dad993c5SAndreas Gohr * Get the options currently set for the LDAP server. 107*dad993c5SAndreas Gohr * 108*dad993c5SAndreas Gohr * @return array<string, mixed> 1090b3fd2d3SAndreas Gohr */ 110*dad993c5SAndreas Gohr public function getOptions(): array 1110b3fd2d3SAndreas Gohr { 112*dad993c5SAndreas Gohr return $this->options; 113*dad993c5SAndreas Gohr } 1140b3fd2d3SAndreas Gohr 115*dad993c5SAndreas Gohr /** 116*dad993c5SAndreas Gohr * Specify an instance of a request handler to use for incoming LDAP requests. 117*dad993c5SAndreas Gohr * 118*dad993c5SAndreas Gohr * @param RequestHandlerInterface $requestHandler 119*dad993c5SAndreas Gohr * @return $this 120*dad993c5SAndreas Gohr */ 121*dad993c5SAndreas Gohr public function useRequestHandler(RequestHandlerInterface $requestHandler): self 122*dad993c5SAndreas Gohr { 123*dad993c5SAndreas Gohr $this->options['request_handler'] = $requestHandler; 124*dad993c5SAndreas Gohr 125*dad993c5SAndreas Gohr return $this; 126*dad993c5SAndreas Gohr } 127*dad993c5SAndreas Gohr 128*dad993c5SAndreas Gohr /** 129*dad993c5SAndreas Gohr * Specify an instance of a RootDSE handler to use for RootDSE requests. 130*dad993c5SAndreas Gohr * 131*dad993c5SAndreas Gohr * @param RootDseHandlerInterface $rootDseHandler 132*dad993c5SAndreas Gohr * @return $this 133*dad993c5SAndreas Gohr */ 134*dad993c5SAndreas Gohr public function useRootDseHandler(RootDseHandlerInterface $rootDseHandler): self 135*dad993c5SAndreas Gohr { 136*dad993c5SAndreas Gohr $this->options['rootdse_handler'] = $rootDseHandler; 137*dad993c5SAndreas Gohr 138*dad993c5SAndreas Gohr return $this; 139*dad993c5SAndreas Gohr } 140*dad993c5SAndreas Gohr 141*dad993c5SAndreas Gohr /** 142*dad993c5SAndreas Gohr * Specify an instance of a paging handler to use for paged search requests. 143*dad993c5SAndreas Gohr * 144*dad993c5SAndreas Gohr * @param PagingHandlerInterface $pagingHandler 145*dad993c5SAndreas Gohr * @return $this 146*dad993c5SAndreas Gohr */ 147*dad993c5SAndreas Gohr public function usePagingHandler(PagingHandlerInterface $pagingHandler): self 148*dad993c5SAndreas Gohr { 149*dad993c5SAndreas Gohr $this->options['paging_handler'] = $pagingHandler; 150*dad993c5SAndreas Gohr 151*dad993c5SAndreas Gohr return $this; 152*dad993c5SAndreas Gohr } 153*dad993c5SAndreas Gohr 154*dad993c5SAndreas Gohr /** 155*dad993c5SAndreas Gohr * Specify a logger to be used by the server process. 156*dad993c5SAndreas Gohr */ 157*dad993c5SAndreas Gohr public function useLogger(LoggerInterface $logger): self 158*dad993c5SAndreas Gohr { 159*dad993c5SAndreas Gohr $this->options['logger'] = $logger; 160*dad993c5SAndreas Gohr 161*dad993c5SAndreas Gohr return $this; 162*dad993c5SAndreas Gohr } 163*dad993c5SAndreas Gohr 164*dad993c5SAndreas Gohr /** 165*dad993c5SAndreas Gohr * Convenience method for generating an LDAP server instance that will proxy client request's to an LDAP server. 166*dad993c5SAndreas Gohr * 167*dad993c5SAndreas Gohr * Note: This is only intended to work with the PCNTL server runner. 168*dad993c5SAndreas Gohr * 169*dad993c5SAndreas Gohr * @param string|string[] $servers The LDAP server(s) to proxy the request to. 170*dad993c5SAndreas Gohr * @param array<string, mixed> $clientOptions Any additional client options for the proxy connection. 171*dad993c5SAndreas Gohr * @param array<string, mixed> $serverOptions Any additional server options for the LDAP server. 172*dad993c5SAndreas Gohr * @return LdapServer 173*dad993c5SAndreas Gohr */ 174*dad993c5SAndreas Gohr public static function makeProxy( 175*dad993c5SAndreas Gohr $servers, 176*dad993c5SAndreas Gohr array $clientOptions = [], 177*dad993c5SAndreas Gohr array $serverOptions = [] 178*dad993c5SAndreas Gohr ): LdapServer { 179*dad993c5SAndreas Gohr $client = new LdapClient(array_merge([ 180*dad993c5SAndreas Gohr 'servers' => $servers, 181*dad993c5SAndreas Gohr ], $clientOptions)); 182*dad993c5SAndreas Gohr 183*dad993c5SAndreas Gohr $proxyRequestHandler = new ProxyHandler($client); 184*dad993c5SAndreas Gohr $server = new LdapServer($serverOptions); 185*dad993c5SAndreas Gohr $server->useRequestHandler($proxyRequestHandler); 186*dad993c5SAndreas Gohr $server->useRootDseHandler($proxyRequestHandler); 187*dad993c5SAndreas Gohr $server->usePagingHandler(new ProxyPagingHandler($client)); 188*dad993c5SAndreas Gohr 189*dad993c5SAndreas Gohr return $server; 190*dad993c5SAndreas Gohr } 191*dad993c5SAndreas Gohr 192*dad993c5SAndreas Gohr private function runner(): ServerRunnerInterface 193*dad993c5SAndreas Gohr { 194*dad993c5SAndreas Gohr if (!$this->runner) { 195*dad993c5SAndreas Gohr $this->runner = new PcntlServerRunner($this->options); 196*dad993c5SAndreas Gohr } 197*dad993c5SAndreas Gohr 198*dad993c5SAndreas Gohr return $this->runner; 199*dad993c5SAndreas Gohr } 200*dad993c5SAndreas Gohr 201*dad993c5SAndreas Gohr private function removeExistingSocketIfNeeded(string $socket): void 202*dad993c5SAndreas Gohr { 203*dad993c5SAndreas Gohr if (!file_exists($socket)) { 2040b3fd2d3SAndreas Gohr return; 2050b3fd2d3SAndreas Gohr } 206*dad993c5SAndreas Gohr 207*dad993c5SAndreas Gohr if (!is_writeable($socket)) { 208*dad993c5SAndreas Gohr $this->logAndThrow(sprintf( 209*dad993c5SAndreas Gohr 'The socket "%s" already exists and is not writeable. To run the LDAP server, you must remove the existing socket.', 210*dad993c5SAndreas Gohr $socket 2110b3fd2d3SAndreas Gohr )); 2120b3fd2d3SAndreas Gohr } 213*dad993c5SAndreas Gohr 214*dad993c5SAndreas Gohr if (!unlink($socket)) { 215*dad993c5SAndreas Gohr $this->logAndThrow(sprintf( 216*dad993c5SAndreas Gohr 'The existing socket "%s" could not be removed. To run the LDAP server, you must remove the existing socket.', 217*dad993c5SAndreas Gohr $socket 2180b3fd2d3SAndreas Gohr )); 2190b3fd2d3SAndreas Gohr } 2200b3fd2d3SAndreas Gohr } 2210b3fd2d3SAndreas Gohr} 222