Line data Source code
1 : // Copyright (c) 2012-2019 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/eventdispatcher
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program; if not, write to the Free Software
18 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 :
20 : /** \file
21 : * \brief Event dispatch class.
22 : *
23 : * Class used to handle events.
24 : */
25 :
26 : // make sure we use OpenSSL with multi-thread support
27 : // (TODO: move to .cpp once we have the impl!)
28 : #define OPENSSL_THREAD_DEFINES
29 :
30 : // self
31 : //
32 : #include "eventdispatcher/tcp_bio_server.h"
33 :
34 : #include "eventdispatcher/exception.h"
35 : #include "eventdispatcher/tcp_private.h"
36 :
37 :
38 : // snaplogger lib
39 : //
40 : #include <snaplogger/message.h>
41 :
42 :
43 : // snapdev lib
44 : //
45 : #include <snapdev/not_used.h>
46 :
47 :
48 : // OpenSSL lib
49 : //
50 : #include <openssl/ssl.h>
51 :
52 :
53 : // last include
54 : //
55 : #include <snapdev/poison.h>
56 :
57 :
58 :
59 :
60 : #ifndef OPENSSL_THREADS
61 : #error "OPENSSL_THREADS is not defined. Snap! requires support for multiple threads in OpenSSL."
62 : #endif
63 :
64 : namespace ed
65 : {
66 :
67 :
68 : namespace detail
69 : {
70 :
71 :
72 0 : class tcp_bio_server_impl
73 : {
74 : public:
75 : int f_max_connections = MAX_CONNECTIONS;
76 : std::shared_ptr<SSL_CTX> f_ssl_ctx = std::shared_ptr<SSL_CTX>();
77 : std::shared_ptr<BIO> f_listen = std::shared_ptr<BIO>();
78 : bool f_keepalive = true;
79 : };
80 :
81 :
82 : }
83 :
84 :
85 : /** \class tcp_bio_server
86 : * \brief Create a BIO server, bind it, and listen for connections.
87 : *
88 : * This class is a server socket implementation used to listen for
89 : * connections that are to use TLS encryptions.
90 : *
91 : * The bind address must be available for the server initialization
92 : * to succeed.
93 : *
94 : * The BIO extension is from the OpenSSL library and it allows the server
95 : * to allow connections using SSL (TLS really now a day). The server
96 : * expects to be given information about a certificate and a private
97 : * key to function. You may also use the server in a non-secure manner
98 : * (without the TLS layer) so you do not need to implement two instances
99 : * of your server, one with tcp_bio_server and one with tcp_server.
100 : */
101 :
102 :
103 :
104 :
105 : /** \brief Contruct a tcp_bio_server object.
106 : *
107 : * The tcp_bio_server constructor initializes a BIO server and listens
108 : * for connections from the specified address and port.
109 : *
110 : * The \p certificate and \p private_key filenames are expected to point
111 : * to a PEM file (.pem extension) that include the encryption information.
112 : *
113 : * The certificate file may include a chain in which case the whole chain
114 : * will be taken in account.
115 : *
116 : * \warning
117 : * Currently the max_connections parameter is pretty much ignored since
118 : * there is no way to pass that paramter down to the BIO interface. In
119 : * that code they use the SOMAXCONN definition which under Linux is
120 : * defined at 128 (Ubuntu 16.04.1). See:
121 : * /usr/include/x86_64-linux-gnu/bits/socket.h
122 : *
123 : * \param[in] addr_port The address and port defined in an addr object.
124 : * \param[in] max_connections The number of connections to keep in the listen queue.
125 : * \param[in] reuse_addr Whether to mark the socket with the SO_REUSEADDR flag.
126 : * \param[in] certificate The server certificate filename (PEM).
127 : * \param[in] private_key The server private key filename (PEM).
128 : * \param[in] mode The mode used to create the listening socket.
129 : */
130 0 : tcp_bio_server::tcp_bio_server(addr::addr const & addr_port, int max_connections, bool reuse_addr, std::string const & certificate, std::string const & private_key, mode_t mode)
131 0 : : f_impl(new detail::tcp_bio_server_impl)
132 : {
133 0 : f_impl->f_max_connections = max_connections <= 0 ? MAX_CONNECTIONS : max_connections;
134 0 : if(f_impl->f_max_connections < 5)
135 : {
136 0 : f_impl->f_max_connections = 5;
137 : }
138 0 : else if(f_impl->f_max_connections > 1000)
139 : {
140 0 : f_impl->f_max_connections = 1000;
141 : }
142 :
143 0 : detail::bio_initialize();
144 :
145 0 : switch(mode)
146 : {
147 : case mode_t::MODE_SECURE:
148 : {
149 : // the following code is based on the example shown in the man page
150 : //
151 : // man BIO_f_ssl
152 : //
153 0 : if(certificate.empty()
154 0 : || private_key.empty())
155 : {
156 0 : throw event_dispatcher_initialization_error("with MODE_SECURE you must specify a certificate and a private_key filename");
157 : }
158 :
159 0 : std::shared_ptr<SSL_CTX> ssl_ctx; // use reset(), see SNAP-507
160 0 : ssl_ctx.reset(SSL_CTX_new(SSLv23_server_method()), detail::ssl_ctx_deleter);
161 0 : if(!ssl_ctx)
162 : {
163 0 : detail::bio_log_errors();
164 0 : throw event_dispatcher_initialization_error("failed creating an SSL_CTX server object");
165 : }
166 :
167 0 : SSL_CTX_set_cipher_list(ssl_ctx.get(), "ALL");//"HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");
168 :
169 : // Assign the certificate to the SSL context
170 : //
171 : // TBD: we may want to use SSL_CTX_use_certificate_file() instead
172 : // (i.e. not the "chained" version)
173 : //
174 0 : if(!SSL_CTX_use_certificate_chain_file(ssl_ctx.get(), certificate.c_str()))
175 : {
176 0 : detail::bio_log_errors();
177 0 : throw event_dispatcher_initialization_error("failed initializing an SSL_CTX server object certificate");
178 : }
179 :
180 : // Assign the private key to the SSL context
181 : //
182 0 : if(!SSL_CTX_use_PrivateKey_file(ssl_ctx.get(), private_key.c_str(), SSL_FILETYPE_PEM))
183 : {
184 : // on failure, try again again with the RSA version, just in case
185 : // (probably useless?)
186 : //
187 0 : if(!SSL_CTX_use_RSAPrivateKey_file(ssl_ctx.get(), private_key.c_str(), SSL_FILETYPE_PEM))
188 : {
189 0 : detail::bio_log_errors();
190 0 : throw event_dispatcher_initialization_error("failed initializing an SSL_CTX server object private key");
191 : }
192 : }
193 :
194 : // Verify that the private key and certifcate are a match
195 : //
196 0 : if(!SSL_CTX_check_private_key(ssl_ctx.get()))
197 : {
198 0 : detail::bio_log_errors();
199 0 : throw event_dispatcher_initialization_error("failed initializing an SSL_CTX server object private key");
200 : }
201 :
202 : // create a BIO connection with SSL
203 : //
204 0 : std::unique_ptr<BIO, void (*)(BIO *)> bio(BIO_new_ssl(ssl_ctx.get(), 0), detail::bio_deleter);
205 0 : if(!bio)
206 : {
207 0 : detail::bio_log_errors();
208 0 : throw event_dispatcher_initialization_error("failed initializing a BIO server object");
209 : }
210 :
211 : // get the SSL pointer, which generally means that the BIO
212 : // allocate succeeded fully, so we can set auto-retry
213 : //
214 0 : SSL * ssl(nullptr);
215 : #pragma GCC diagnostic push
216 : #pragma GCC diagnostic ignored "-Wold-style-cast"
217 0 : BIO_get_ssl(bio.get(), &ssl);
218 : #pragma GCC diagnostic pop
219 0 : if(ssl == nullptr)
220 : {
221 : // TBD: does this mean we would have a plain connection?
222 0 : detail::bio_log_errors();
223 0 : throw event_dispatcher_initialization_error("failed connecting BIO object with SSL_CTX object");
224 : }
225 :
226 : // allow automatic retries in case the connection somehow needs
227 : // an SSL renegotiation (maybe we should turn that off for cases
228 : // where we connect to a secure payment gateway?)
229 : //
230 0 : SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
231 :
232 : // create a listening connection
233 : //
234 0 : std::shared_ptr<BIO> listen; // use reset(), see SNAP-507
235 0 : listen.reset(BIO_new_accept(addr_port.to_ipv4or6_string(addr::addr::string_ip_t::STRING_IP_PORT).c_str()), detail::bio_deleter);
236 0 : if(!listen)
237 : {
238 0 : detail::bio_log_errors();
239 0 : throw event_dispatcher_initialization_error("failed initializing a BIO server object");
240 : }
241 :
242 0 : BIO_set_bind_mode(listen.get(), reuse_addr ? BIO_BIND_REUSEADDR : BIO_BIND_NORMAL);
243 :
244 : // Attach the SSL bio to the listening BIO, this means whenever
245 : // a new connection is accepted, it automatically attaches it to
246 : // an SSL connection
247 : //
248 : #pragma GCC diagnostic push
249 : #pragma GCC diagnostic ignored "-Wold-style-cast"
250 0 : BIO_set_accept_bios(listen.get(), bio.get());
251 : #pragma GCC diagnostic pop
252 :
253 : // WARNING: the listen object takes ownership of the `bio`
254 : // pointer and thus we have to make sure that we
255 : // do not keep it in our unique_ptr<>().
256 : //
257 0 : snap::NOTUSED(bio.release());
258 :
259 : // Actually call bind() and listen() on the socket
260 : //
261 : // IMPORTANT NOTE: The BIO_do_accept() is overloaded, it does
262 : // two things: (a) it bind() + listen() when called the very
263 : // first time (i.e. the call right here); (b) it actually
264 : // accepts a client connection
265 : //
266 0 : int const r(BIO_do_accept(listen.get()));
267 0 : if(r <= 0)
268 : {
269 0 : detail::bio_log_errors();
270 0 : throw event_dispatcher_initialization_error("failed initializing the BIO server socket to listen for client connections");
271 : }
272 :
273 : // it worked, save the results
274 0 : f_impl->f_ssl_ctx.swap(ssl_ctx);
275 0 : f_impl->f_listen.swap(listen);
276 :
277 : // secure connection ready
278 : }
279 0 : break;
280 :
281 : case mode_t::MODE_PLAIN:
282 : {
283 0 : std::shared_ptr<BIO> listen; // use reset(), see SNAP-507
284 0 : listen.reset(BIO_new_accept(addr_port.to_ipv4or6_string(addr::addr::string_ip_t::STRING_IP_PORT).c_str()), detail::bio_deleter);
285 0 : if(!listen)
286 : {
287 0 : detail::bio_log_errors();
288 0 : throw event_dispatcher_initialization_error("failed initializing a BIO server object");
289 : }
290 :
291 0 : BIO_set_bind_mode(listen.get(), BIO_BIND_REUSEADDR);
292 :
293 : // Actually call bind() and listen() on the socket
294 : //
295 : // IMPORTANT NOTE: The BIO_do_accept() is overloaded, it does
296 : // two things: (a) it bind() + listen() when called the very
297 : // first time (i.e. the call right here); (b) it actually
298 : // accepts a client connection
299 : //
300 0 : int const r(BIO_do_accept(listen.get()));
301 0 : if(r <= 0)
302 : {
303 0 : detail::bio_log_errors();
304 0 : throw event_dispatcher_initialization_error("failed initializing the BIO server socket to listen for client connections");
305 : }
306 :
307 : // it worked, save the results
308 : //
309 0 : f_impl->f_listen.swap(listen);
310 : }
311 0 : break;
312 :
313 : }
314 0 : }
315 :
316 :
317 : /** \brief Clean up the TCP Bio Server object.
318 : *
319 : * This function cleans up the object on destruction.
320 : */
321 0 : tcp_bio_server::~tcp_bio_server()
322 : {
323 0 : }
324 :
325 :
326 : /** \brief Tell you whether the server uses a secure BIO or not.
327 : *
328 : * This function checks whether the BIO is using encryption (true)
329 : * or is a plain connection (false).
330 : *
331 : * \return true if the BIO was created in secure mode.
332 : */
333 0 : bool tcp_bio_server::is_secure() const
334 : {
335 0 : return f_impl->f_ssl_ctx != nullptr;
336 : }
337 :
338 :
339 : /** \brief Get the listening socket.
340 : *
341 : * This function returns the file descriptor of the listening socket.
342 : * By default the socket is in blocking mode.
343 : *
344 : * \return The listening socket file descriptor.
345 : */
346 0 : int tcp_bio_server::get_socket() const
347 : {
348 0 : if(f_impl->f_listen)
349 : {
350 : int c;
351 : #pragma GCC diagnostic push
352 : #pragma GCC diagnostic ignored "-Wold-style-cast"
353 0 : BIO_get_fd(f_impl->f_listen.get(), &c);
354 : #pragma GCC diagnostic pop
355 0 : return c;
356 : }
357 :
358 0 : return -1;
359 : }
360 :
361 :
362 : /** \brief Retrieve one new connection.
363 : *
364 : * This function will wait until a new connection arrives and returns a
365 : * new bio_client object for each new connection.
366 : *
367 : * If the socket is made non-blocking then the function may return without
368 : * a bio_client object (i.e. a null pointer instead.)
369 : *
370 : * \return A shared pointer to a newly allocated bio_client object.
371 : */
372 0 : tcp_bio_client::pointer_t tcp_bio_server::accept()
373 : {
374 : // TBD: does one call to BIO_do_accept() accept at most one connection
375 : // at a time or could it be that 'r' will be set to 2, 3, 4...
376 : // as more connections get accepted?
377 : //
378 0 : int const r(BIO_do_accept(f_impl->f_listen.get()));
379 0 : if(r <= 0)
380 : {
381 : // TBD: should we instead return an empty shared pointer in this case?
382 : //
383 0 : detail::bio_log_errors();
384 0 : throw event_dispatcher_runtime_error("failed accepting a new BIO");
385 : }
386 :
387 : // retrieve the new connection by "popping it"
388 : //
389 0 : std::shared_ptr<BIO> bio; // use reset(), see SNAP-507
390 0 : bio.reset(BIO_pop(f_impl->f_listen.get()), detail::bio_deleter);
391 0 : if(bio == nullptr)
392 : {
393 0 : detail::bio_log_errors();
394 0 : throw event_dispatcher_runtime_error("failed retrieving the accepted BIO");
395 : }
396 :
397 : // mark the new connection with the SO_KEEPALIVE flag
398 0 : if(f_impl->f_keepalive)
399 : {
400 : // retrieve the socket (we do not yet have a bio_client object
401 : // so we cannot call a get_socket() function...)
402 : //
403 0 : int socket(-1);
404 : #pragma GCC diagnostic push
405 : #pragma GCC diagnostic ignored "-Wold-style-cast"
406 0 : BIO_get_fd(bio.get(), &socket);
407 : #pragma GCC diagnostic pop
408 0 : if(socket >= 0)
409 : {
410 : // if this call fails, we ignore the error, but still log the event
411 : //
412 0 : int optval(1);
413 0 : socklen_t const optlen(sizeof(optval));
414 0 : if(setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) != 0)
415 : {
416 : SNAP_LOG_WARNING
417 : << "tcp_bio_server::accept(): an error occurred trying"
418 0 : " to mark accepted socket with SO_KEEPALIVE.";
419 : }
420 : }
421 : }
422 :
423 0 : tcp_bio_client::pointer_t client(new tcp_bio_client());
424 :
425 0 : client->f_impl->f_bio = bio;
426 :
427 : // TODO: somehow this does not seem to give us any information
428 : // about the cipher and other details...
429 : //
430 : // this is because it is (way) too early, we did not even
431 : // receive the HELLO yet!
432 : //
433 0 : SSL * ssl(nullptr);
434 : #pragma GCC diagnostic push
435 : #pragma GCC diagnostic ignored "-Wold-style-cast"
436 0 : BIO_get_ssl(bio.get(), &ssl);
437 : #pragma GCC diagnostic pop
438 0 : if(ssl != nullptr)
439 : {
440 0 : char const * cipher_name(SSL_get_cipher(ssl));
441 0 : int cipher_bits(0);
442 0 : SSL_get_cipher_bits(ssl, &cipher_bits);
443 : SNAP_LOG_DEBUG
444 0 : << "accepted BIO client with SSL cipher \""
445 0 : << cipher_name
446 0 : << "\" representing "
447 0 : << cipher_bits
448 0 : << " bits of encryption.";
449 : }
450 :
451 0 : return client;
452 : }
453 :
454 :
455 :
456 6 : } // namespace ed
457 : // vim: ts=4 sw=4 et
|