xref: /plugin/pureldap/vendor/freedsx/ldap/src/FreeDSx/Ldap/LdapServer.php (revision dad993c57a70866aa1db59c43f043769c2eb7ed0)
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