Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 1
<?php
2
 
3
namespace React\Socket;
4
 
5
use Evenement\EventEmitter;
6
use React\EventLoop\LoopInterface;
7
use BadMethodCallException;
8
use UnexpectedValueException;
9
 
10
/**
11
 * The `SecureServer` class implements the `ServerInterface` and is responsible
12
 * for providing a secure TLS (formerly known as SSL) server.
13
 *
14
 * It does so by wrapping a `TcpServer` instance which waits for plaintext
15
 * TCP/IP connections and then performs a TLS handshake for each connection.
16
 *
17
 * ```php
18
 * $server = new React\Socket\TcpServer(8000, $loop);
19
 * $server = new React\Socket\SecureServer($server, $loop, array(
20
 *     // tls context options here…
21
 * ));
22
 * ```
23
 *
24
 * Whenever a client completes the TLS handshake, it will emit a `connection` event
25
 * with a connection instance implementing [`ConnectionInterface`](#connectioninterface):
26
 *
27
 * ```php
28
 * $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
29
 *     echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
30
 *
31
 *     $connection->write('hello there!' . PHP_EOL);
32
 *     …
33
 * });
34
 * ```
35
 *
36
 * Whenever a client fails to perform a successful TLS handshake, it will emit an
37
 * `error` event and then close the underlying TCP/IP connection:
38
 *
39
 * ```php
40
 * $server->on('error', function (Exception $e) {
41
 *     echo 'Error' . $e->getMessage() . PHP_EOL;
42
 * });
43
 * ```
44
 *
45
 * See also the `ServerInterface` for more details.
46
 *
47
 * Note that the `SecureServer` class is a concrete implementation for TLS sockets.
48
 * If you want to typehint in your higher-level protocol implementation, you SHOULD
49
 * use the generic `ServerInterface` instead.
50
 *
51
 * @see ServerInterface
52
 * @see ConnectionInterface
53
 */
54
final class SecureServer extends EventEmitter implements ServerInterface
55
{
56
    private $tcp;
57
    private $encryption;
58
    private $context;
59
 
60
    /**
61
     * Creates a secure TLS server and starts waiting for incoming connections
62
     *
63
     * It does so by wrapping a `TcpServer` instance which waits for plaintext
64
     * TCP/IP connections and then performs a TLS handshake for each connection.
65
     * It thus requires valid [TLS context options],
66
     * which in its most basic form may look something like this if you're using a
67
     * PEM encoded certificate file:
68
     *
69
     * ```php
70
     * $server = new React\Socket\TcpServer(8000, $loop);
71
     * $server = new React\Socket\SecureServer($server, $loop, array(
72
     *     'local_cert' => 'server.pem'
73
     * ));
74
     * ```
75
     *
76
     * Note that the certificate file will not be loaded on instantiation but when an
77
     * incoming connection initializes its TLS context.
78
     * This implies that any invalid certificate file paths or contents will only cause
79
     * an `error` event at a later time.
80
     *
81
     * If your private key is encrypted with a passphrase, you have to specify it
82
     * like this:
83
     *
84
     * ```php
85
     * $server = new React\Socket\TcpServer(8000, $loop);
86
     * $server = new React\Socket\SecureServer($server, $loop, array(
87
     *     'local_cert' => 'server.pem',
88
     *     'passphrase' => 'secret'
89
     * ));
90
     * ```
91
     *
92
     * Note that available [TLS context options],
93
     * their defaults and effects of changing these may vary depending on your system
94
     * and/or PHP version.
95
     * Passing unknown context options has no effect.
96
     *
97
     * Advanced usage: Despite allowing any `ServerInterface` as first parameter,
98
     * you SHOULD pass a `TcpServer` instance as first parameter, unless you
99
     * know what you're doing.
100
     * Internally, the `SecureServer` has to set the required TLS context options on
101
     * the underlying stream resources.
102
     * These resources are not exposed through any of the interfaces defined in this
103
     * package, but only through the internal `Connection` class.
104
     * The `TcpServer` class is guaranteed to emit connections that implement
105
     * the `ConnectionInterface` and uses the internal `Connection` class in order to
106
     * expose these underlying resources.
107
     * If you use a custom `ServerInterface` and its `connection` event does not
108
     * meet this requirement, the `SecureServer` will emit an `error` event and
109
     * then close the underlying connection.
110
     *
111
     * @param ServerInterface|TcpServer $tcp
112
     * @param LoopInterface $loop
113
     * @param array $context
114
     * @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support
115
     * @see TcpServer
116
     * @link https://www.php.net/manual/en/context.ssl.php for TLS context options
117
     */
118
    public function __construct(ServerInterface $tcp, LoopInterface $loop, array $context)
119
    {
120
        if (!\function_exists('stream_socket_enable_crypto')) {
121
            throw new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore
122
        }
123
 
124
        // default to empty passphrase to suppress blocking passphrase prompt
125
        $context += array(
126
            'passphrase' => ''
127
        );
128
 
129
        $this->tcp = $tcp;
130
        $this->encryption = new StreamEncryption($loop);
131
        $this->context = $context;
132
 
133
        $that = $this;
134
        $this->tcp->on('connection', function ($connection) use ($that) {
135
            $that->handleConnection($connection);
136
        });
137
        $this->tcp->on('error', function ($error) use ($that) {
138
            $that->emit('error', array($error));
139
        });
140
    }
141
 
142
    public function getAddress()
143
    {
144
        $address = $this->tcp->getAddress();
145
        if ($address === null) {
146
            return null;
147
        }
148
 
149
        return \str_replace('tcp://' , 'tls://', $address);
150
    }
151
 
152
    public function pause()
153
    {
154
        $this->tcp->pause();
155
    }
156
 
157
    public function resume()
158
    {
159
        $this->tcp->resume();
160
    }
161
 
162
    public function close()
163
    {
164
        return $this->tcp->close();
165
    }
166
 
167
    /** @internal */
168
    public function handleConnection(ConnectionInterface $connection)
169
    {
170
        if (!$connection instanceof Connection) {
171
            $this->emit('error', array(new \UnexpectedValueException('Base server does not use internal Connection class exposing stream resource')));
172
            $connection->close();
173
            return;
174
        }
175
 
176
        foreach ($this->context as $name => $value) {
177
            \stream_context_set_option($connection->stream, 'ssl', $name, $value);
178
        }
179
 
180
        // get remote address before starting TLS handshake in case connection closes during handshake
181
        $remote = $connection->getRemoteAddress();
182
        $that = $this;
183
 
184
        $this->encryption->enable($connection)->then(
185
            function ($conn) use ($that) {
186
                $that->emit('connection', array($conn));
187
            },
188
            function ($error) use ($that, $connection, $remote) {
189
                $error = new \RuntimeException(
190
                    'Connection from ' . $remote . ' failed during TLS handshake: ' . $error->getMessage(),
191
                    $error->getCode()
192
                );
193
 
194
                $that->emit('error', array($error));
195
                $connection->close();
196
            }
197
        );
198
    }
199
}