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;
12
13use FreeDSx\Ldap\Exception\RuntimeException;
14use FreeDSx\Ldap\Server\RequestHandler\GenericRequestHandler;
15use FreeDSx\Ldap\Server\RequestHandler\RequestHandlerInterface;
16use FreeDSx\Ldap\Server\ServerRunner\PcntlServerRunner;
17use FreeDSx\Ldap\Server\ServerRunner\ServerRunnerInterface;
18use FreeDSx\Socket\SocketServer;
19
20/**
21 * The LDAP server.
22 *
23 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
24 */
25class LdapServer
26{
27    /**
28     * @var array
29     */
30    protected $options = [
31        'ip' => '0.0.0.0',
32        'port' => 389,
33        'idle_timeout' => 600,
34        'require_authentication' => true,
35        'allow_anonymous' => false,
36        'request_handler' => null,
37        'ssl_cert' => null,
38        'ssl_cert_passphrase' => 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 ServerRunnerInterface
47     */
48    protected $runner;
49
50    /**
51     * @param array $options
52     * @param ServerRunnerInterface|null $serverRunner
53     */
54    public function __construct(array $options = [], ServerRunnerInterface $serverRunner = null)
55    {
56        $this->options = array_merge($this->options, $options);
57        $this->validateRequestHandler();
58        $this->runner = $serverRunner ?? new PcntlServerRunner($this->options);
59    }
60
61    /**
62     * Runs the LDAP server. Binds the socket on the request IP/port and sends it to the server runner.
63     */
64    public function run(): void
65    {
66        $this->runner->run(SocketServer::bind($this->options['ip'], $this->options['port'], $this->options));
67    }
68
69    /**
70     * The request handler should be constructed from a string class name. This is to make sure that each client instance
71     * has its own version of the handler to avoid conflicts and potential security issues sharing a request handler.
72     */
73    protected function validateRequestHandler(): void
74    {
75        if (!isset($this->options['request_handler'])) {
76            $this->options['request_handler'] = GenericRequestHandler::class;
77
78            return;
79        }
80        if (!\is_string($this->options['request_handler'])) {
81            throw new RuntimeException(sprintf(
82                'The request handler must be a string class name, got %s.',
83                gettype($this->options['request_handler'])
84            ));
85        }
86        if (!\class_exists($this->options['request_handler'])) {
87            throw new RuntimeException(sprintf(
88                'The request handler class does not exist: %s',
89                $this->options['request_handler']
90            ));
91        }
92        if (!\is_subclass_of($this->options['request_handler'], RequestHandlerInterface::class)) {
93            throw new RuntimeException(sprintf(
94                'The request handler class must implement "%s"',
95                RequestHandlerInterface::class
96            ));
97        }
98    }
99}
100