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