From 05d3d94613168187cbf7d54ac6de345bb75910dd Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Tue, 9 Jul 2019 22:36:35 +0100 Subject: Avoid SSL_accept() blocking if the client fails to do TLS negotiation. --- sockets.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) (limited to 'sockets.c') diff --git a/sockets.c b/sockets.c index bf83176..d10811e 100644 --- a/sockets.c +++ b/sockets.c @@ -228,6 +228,7 @@ int socksend(SSL *fd, char *buf, int bufsize, int tls) { } } +// Return character array of latest OpenSSL error char *openssl_error_string() { BIO *bio = BIO_new (BIO_s_mem ()); ERR_print_errors (bio); @@ -240,3 +241,67 @@ char *openssl_error_string() { BIO_free (bio); return ret; } + +// Set a socket "fd" to be blocking ("blocking" = 1) or non-blocking ("blocking" = 0). +// Returns 1 on success or 0 on failure. +int fd_toggle_blocking(int fd, int blocking) { + debugprint(DEBUG_FULL, "fd_toggle_blocking(): setting blocking to %d for fd %d.\n", blocking, fd); + + // Save the current flags + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { + // Error getting current flags + return 0; + } + + // Add or remove O_NONBLOCK as appropriate + if (blocking) { + flags &= ~O_NONBLOCK; + } else { + flags |= O_NONBLOCK; + } + + if (fcntl(fd, F_SETFL, flags) == -1) { + return 0; + } else { + return 1; + } +} + +// Attempt to do SSL_accept() on a client with fd "fd". Expects the socket fd to have just been set +// to non-blocking. Will make the socket blocking again and set the client's pendingsslaccept status +// to 0 if SSL_accept() succeeds. Calls disconnectclient() on hard failure. +// Returns 1 on success, 0 on hard failure, or -1 on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. +int openssl_accept(int fd, struct client *clients, struct ircdstate *ircdstate, struct settings *settings, struct clientcodes *clientcodes) { + // Get the index of the this client fd + int clientindex = arrindex(clients, fd); + + // Clear OpenSSL errors before proceeding so we can reliably get errors from SSL_accept + ERR_clear_error(); + // Try to SSL_accept(); + int ret; + if ((ret = SSL_accept(clients[clientindex].ssl)) <= 0) { + // SSL_accept() either failed (bad) or is just pending read or write (which is OK) + int sslerr = SSL_get_error(clients[clientindex].ssl, ret); + if (sslerr == SSL_ERROR_WANT_READ || sslerr == SSL_ERROR_WANT_WRITE) { + debugprint(DEBUG_CRIT, "SSL_accept() pending for new connection fd %d (ret = %d, sslerr = %d), looping...\n", clients[clientindex].fd, ret, sslerr); + return -1; + } else { + char* errstr = openssl_error_string(); + debugprint(DEBUG_CRIT, "SSL_accept failed for new connection fd %d (ret = %d) - %s", clients[clientindex].fd, ret, errstr); + if (errstr != NULL) free(errstr); + disconnectclient(clients[clientindex].fd, clients, ircdstate, settings, clientcodes); + return 0; + } + } else { + debugprint(DEBUG_FULL, "SSL_accept succeeded for new connection fd %d.\n", clients[clientindex].fd); + // Change the socket back to blocking + if (!fd_toggle_blocking(clients[clientindex].fd, 1)) { + debugprint(DEBUG_CRIT, "fd_toggle_blocking off failed for fd %d: %s.\n", clients[clientindex].fd, strerror(errno)); + disconnectclient(clients[clientindex].fd, clients, ircdstate, settings, clientcodes); + } + // And mark as no longer pending SSL_accept() + clients[clientindex].pendingsslaccept = 0; + return 1; + } +} -- cgit v1.2.3