From 8869477885718844d368b48774f926489385e3b3 Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Sun, 12 May 2019 22:47:29 +0100 Subject: Make using TLS for clients connecting to the bouncer optional. --- blabouncer.c | 116 ++++++++++++++++++++++++++++++-------------------------- blabouncer.conf | 6 +++ sockets.c | 20 ++++++++++ sockets.h | 6 +++ 4 files changed, 94 insertions(+), 54 deletions(-) diff --git a/blabouncer.c b/blabouncer.c index dcfdd1b..c883ed6 100644 --- a/blabouncer.c +++ b/blabouncer.c @@ -9,16 +9,11 @@ // - "01:53:47 -!- ServerMode/#test [b] by irc.tghost.co.uk" on existing clients when new client connects // - Keep track of changing user nicks/modes // - Should replay log do more than PRIVMSGs? -// - Make certificate paths configurable // - Implement TLS on real IRCd server side -// - Make TLS optional -// - Make cert/key filenames configurable -// - Make listen port configurable // - Consider moving the three fd-related arrays into one struct // - Check authentication before even getting to the send functions to save unnecessary processing // - Configurable auto channels // - Comma separated channel list in JOINs/PARTs -// - Long raw strings like a 321/322/323 channel LIST, and the initial server welcome, need to be read and split properly // - Only send some things to the requesting client (e.g. LIST replies) // - Normal (non-replay) log // - Alert when clients connect/authenticate/disconnect @@ -106,6 +101,7 @@ struct settings { char conffile[PATH_MAX]; char certfile[PATH_MAX]; char keyfile[PATH_MAX]; + int clienttls; }; // Return index of requested client FD within arr_clients @@ -125,7 +121,7 @@ int arrindex(int arr_clients[], int clientfd) { } // Send whatever string to a specific client by providing the FD -int sendtoclient(int fd, char *str, int arr_clients[], int arr_authed[], SSL **arr_ssl) { +int sendtoclient(int fd, char *str, int arr_clients[], int arr_authed[], SSL **arr_ssl, struct settings *settings) { appendcrlf(str); // Do this just before sending so callers don't need to worry about it // Find the client in the clients array and make sure they are authenticated @@ -140,8 +136,7 @@ int sendtoclient(int fd, char *str, int arr_clients[], int arr_authed[], SSL **a } printf("sendtoclient(): sending \"%s\" (length %zd) to client with fd %d.\n", str, strlen(str), fd); -// if (send(fd, str, strlen(str), 0) == -1) { - if (SSL_write(arr_ssl[arrindex(arr_clients, fd)], str, strlen(str)) == -1) { + if (socksend(arr_ssl[arrindex(arr_clients, fd)], str, strlen(str), settings->clienttls) == -1) { perror("error: sendtoclient() send()\n"); return 0; } @@ -174,7 +169,7 @@ int disconnectclient(int fd, int arr_clients[], int arr_authed[]) { // "except" is used to send to all clients _except_ the fd provided (except = 0 (EXCEPT_NONE) avoids this, i.e. sends to all) // "except" is really the "sourcefd" and is also used as part of the authentication check - this is messy and they should perhaps be two separate arguments. // TODO - is passing str_len useful if we're appendcrlfing and then using strlen(str) in the send? I guess not... (As long as we're always null terminated in the correct place.) -int sendtoallclients(int *clientsockfd, int fdmax, int arr_clients[], char *str, int except, int arr_authed[], SSL **arr_ssl) { +int sendtoallclients(int *clientsockfd, int fdmax, int arr_clients[], char *str, int except, int arr_authed[], SSL **arr_ssl, struct settings *settings) { char *sendertype; @@ -220,8 +215,7 @@ int sendtoallclients(int *clientsockfd, int fdmax, int arr_clients[], char *str, continue; } printf("sendtoallclients(): %s: sending '%s' to client with fd %d.\n", sendertype, str, i); -// if (send(i, str, strlen(str), 0) == -1) { - if (SSL_write(arr_ssl[arrindex(arr_clients, i)], str, strlen(str)) == -1) { + if (socksend(arr_ssl[arrindex(arr_clients, i)], str, strlen(str), settings->clienttls) == -1) { perror("error: sendtoallclients() send()\n"); } } @@ -559,7 +553,7 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc } // And then send to all clients - sendtoallclients(clientsockfd, fdmax, arr_clients, str, sourcefd, arr_authed, arr_ssl); + sendtoallclients(clientsockfd, fdmax, arr_clients, str, sourcefd, arr_authed, arr_ssl, settings); return 1; } @@ -580,7 +574,7 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc } // And then send to all clients - sendtoallclients(clientsockfd, fdmax, arr_clients, str, sourcefd, arr_authed, arr_ssl); + sendtoallclients(clientsockfd, fdmax, arr_clients, str, sourcefd, arr_authed, arr_ssl, settings); return 1; } @@ -647,7 +641,7 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc setchanneltopicwhotime(channels, tokens[2], prefixcopy, timenowstr); // And then finally relay to all clients - sendtoallclients(clientsockfd, fdmax, arr_clients, str, sourcefd, arr_authed, arr_ssl); + sendtoallclients(clientsockfd, fdmax, arr_clients, str, sourcefd, arr_authed, arr_ssl, settings); return 1; } @@ -655,7 +649,7 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc if (strncmp(tokens[1], "PRIVMSG", strlen(tokens[1])) == 0) { printf("Server PRIVMSG found and it is: %s with length %zd! Next token is '%s'. Relaying to all clients.\n", tokens[0], strlen(tokens[0]), tokens[2]); - sendtoallclients(clientsockfd, fdmax, arr_clients, str, sourcefd, arr_authed, arr_ssl); + sendtoallclients(clientsockfd, fdmax, arr_clients, str, sourcefd, arr_authed, arr_ssl, settings); // Write to replay log writereplayline(str); @@ -695,13 +689,13 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc // Send IRC greeting strings (001/RPL_WELCOME, 002/RPL_YOURHOST, 003/RPL_CREATED, 004/RPL_MYINFO) to client snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting001); - sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl); + sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting002); - sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl); + sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting003); - sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl); + sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting004); - sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl); + sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); // Get the channel count so we can enumerate over all channels. // Storing separately so we can skip over blank channels. @@ -722,7 +716,7 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses!\n"); exit(1); } - sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl); + sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); // Send topic (or lack thereof) to client // If there isn't one set (we guess this if topic timestamp is 0), send 331 RPL_NOTOPIC @@ -733,7 +727,7 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc exit(1); } // ..and send it to the client - sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl); + sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); // If there is one set, send 332 RPL_TOPIC and 333 RPL_TOPICWHOTIME } else { // Prepare the topic message... @@ -742,7 +736,7 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc exit(1); } // ..and send it to the client - sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl); + sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); // Next prepare the topic who/when message... if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 333 %s %s %s %s", ircdstrings->ircdname, ircdstrings->ircnick, channels[i].name, channels[i].topicwho, channels[i].topicwhen)) { @@ -750,7 +744,7 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc exit(1); } // ..and send it to the client - sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl); + sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); } // Send list of names @@ -770,7 +764,7 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc fprintf(stderr, "Error while preparing USER just connected, channel NAMES responses!\n"); exit(1); } - sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl); + sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); } // Once all names are sent, send the "end of /NAMES" 366 (RPL_ENDOFNAMES) message @@ -778,7 +772,7 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc fprintf(stderr, "Error while preparing USER just connected, end of NAMES response!\n"); exit(1); } - sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl); + sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); } // Figure out how many lines to replay @@ -797,7 +791,7 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc exit(1); } printf("Sending replay line: '%s'.\n", outgoingmsg); - sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl); + sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); } return 1; @@ -812,7 +806,7 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc fprintf(stderr, "Error while preparing PONG response!\n"); exit(1); } - sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl); + sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); // We processed something so return true return 1; @@ -845,7 +839,7 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc exit(1); } // Send to all except source client - sendtoallclients(clientsockfd, fdmax, arr_clients, outgoingmsg, sourcefd, arr_authed, arr_ssl); + sendtoallclients(clientsockfd, fdmax, arr_clients, outgoingmsg, sourcefd, arr_authed, arr_ssl, settings); // Write to replay log writereplayline(outgoingmsg); @@ -983,7 +977,7 @@ int processrawstring(int *serversockfd, int *clientsockfd, char *str, int source // Relay/send to all clients ("except" = 0 because this should send to all clients) // TODO - Is this really going to send the original string if we have messed it with it in processrawstring() and friends!? printf("bouncer-server: sending unprocessed server message \"%s\" to all clients, length %zd.\n", messages[i], strlen(messages[i])); - sendtoallclients(clientsockfd, fdmax, arr_clients, messages[i], EXCEPT_NONE, arr_authed, arr_ssl); + sendtoallclients(clientsockfd, fdmax, arr_clients, messages[i], EXCEPT_NONE, arr_authed, arr_ssl, settings); break; case SOURCE_CLIENT: // If message(s) were from a real IRC client // Send to server @@ -992,7 +986,7 @@ int processrawstring(int *serversockfd, int *clientsockfd, char *str, int source printf("bouncer-client: sending unprocessed client message \"%s\" to all other clients, length %zd.\n", messages[i], strlen(messages[i])); // send the same thing to all *other* clients (all except for source fd) - sendtoallclients(clientsockfd, fdmax, arr_clients, messages[i], sourcefd, arr_authed, arr_ssl); + sendtoallclients(clientsockfd, fdmax, arr_clients, messages[i], sourcefd, arr_authed, arr_ssl, settings); break; default: fprintf(stderr, "Unexpected raw IRC string source for unprocessed message \"%s\", length %zd.!\n", messages[i], strlen(messages[i])); @@ -1015,7 +1009,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { int outgoingmsgrc; // Return code from getstdin() for outgoing message int arr_clients[MAXCLIENTS]; // Array of all client FDs - 0 means not connected, greater than 0 means connected and the value is the fd number (so we know which ones to try to read) int arr_authed[MAXCLIENTS]; // Array of client authentication statuses - 0 means not authenticated, 1 means authenticated. Element numbers match those of arr_clients. - SSL *arr_ssl[MAXCLIENTS]; // Array of OpenSSL structures + SSL *arr_ssl[MAXCLIENTS]; // Array of OpenSSL structures when using TLS, or faked by casting fd ints to SSL* if not. - TODO - Can we drop one of either arr_clients or arr_ssl now? int num_clients = 0; // Current number of clients int fdmax; // highest numbered socket fd @@ -1076,13 +1070,16 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { channels = malloc(sizeof(struct channel) * MAXCHANNELS); // =============================================> - // OpenSSL context for client side (that clients connect to) + // OpenSSL context for client side (that clients connect to) (need to create this whether or not using TLS as it is referenced later) SSL_CTX *ctx; - // Initialise OpenSSL - init_openssl(); - ctx = create_context(); - configure_context(ctx, settings->certfile, settings->keyfile); + // If using client TLS + if (settings->clienttls) { + // Initialise OpenSSL + init_openssl(); + ctx = create_context(); + configure_context(ctx, settings->certfile, settings->keyfile); + } while (1) { printf("top of loop, fdmax %d.\n", fdmax); @@ -1210,14 +1207,20 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { arr_clients[j] = newfd; // Ensure its authentication status is set to 0 arr_authed[j] = 0; - // Set as OpenSSL FD and SSL_accept it - arr_ssl[j] = SSL_new(ctx); - SSL_set_fd(arr_ssl[j], newfd); - if (SSL_accept(arr_ssl[j]) <= 0) { - printf("SSL_accept failed for fd %d.\n", j); - ERR_print_errors_fp(stderr); + // If using TLS then... + if (settings->clienttls) { + // ...set as OpenSSL FD and SSL_accept it + arr_ssl[j] = SSL_new(ctx); + SSL_set_fd(arr_ssl[j], newfd); + if (SSL_accept(arr_ssl[j]) <= 0) { + printf("SSL_accept failed for fd %d.\n", j); + ERR_print_errors_fp(stderr); + } else { + printf("SSL_accept succeeded for fd %d.\n", j); + } } else { - printf("SSL_accept succeeded for fd %d.\n", j); + // If not using TLS then cast newfd to SSL* even though it will just be the original newfd int really + arr_ssl[j] = (SSL*)(long int)newfd; } break; } @@ -1230,8 +1233,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { } else { printf("...previous connection!\n"); // handle data from a client -// if ((clientnumbytes = recv(i, clientbuf, sizeof clientbuf, 0)) <= 0) { - if ((clientnumbytes = SSL_read(arr_ssl[arrindex(arr_clients, i)], clientbuf, sizeof clientbuf)) <= 0) { + if ((clientnumbytes = sockread(arr_ssl[arrindex(arr_clients, i)], clientbuf, sizeof clientbuf, settings->clienttls)) <= 0) { // got error or connection closed by client if (clientnumbytes == 0) { // connection closed @@ -1330,16 +1332,22 @@ int main(int argc, char *argv[]) { exit(1); } - // What is the certificate file path? - if (!getconfstr("certfile", settings.conffile, settings.certfile)) { - printf("main(): error getting 'certfile' from configuration file.\n"); - exit(1); - } + // Should the bouncer use TLS for clients? + settings.clienttls = getconfint("clienttls", settings.conffile); - // What is the certificate key file path? - if (!getconfstr("keyfile", settings.conffile, settings.keyfile)) { - printf("main(): error getting 'keyfile' from configuration file.\n"); - exit(1); + // If so, load the certificates + if (settings.clienttls) { + // What is the certificate file path? + if (!getconfstr("certfile", settings.conffile, settings.certfile)) { + printf("main(): error getting 'certfile' from configuration file.\n"); + exit(1); + } + + // What is the certificate key file path? + if (!getconfstr("keyfile", settings.conffile, settings.keyfile)) { + printf("main(): error getting 'keyfile' from configuration file.\n"); + exit(1); + } } // TODO: see if any of this can be shared (i.e. 1. avoid code duplication, and 2. see if variables can be shared between client/server sockets) diff --git a/blabouncer.conf b/blabouncer.conf index 3428960..72646de 100644 --- a/blabouncer.conf +++ b/blabouncer.conf @@ -17,6 +17,10 @@ password = "bananas" # Port the bouncer should listen on clientport = "1234" +# Enable TLS for clients connecting to the bouncer ("1" for yes or "0" for no) +# If "0" then certfile and keyfile need not be set +clienttls = "1" + # Real IRC server the bouncer connects to ircserver = "irc.blatech.net" @@ -24,7 +28,9 @@ ircserver = "irc.blatech.net" ircserverport = "6667" # Certificate file +# If clienttls = "0" then this need not be set certfile = "cert.pem" # Certificate key file +# If clienttls = "0" then this need not be set keyfile = "key.pem" diff --git a/sockets.c b/sockets.c index 0f78b21..c5ea41f 100644 --- a/sockets.c +++ b/sockets.c @@ -165,3 +165,23 @@ void configure_context(SSL_CTX *ctx, char *certfile, char *keyfile) { exit(EXIT_FAILURE); } } + +// Read from a socket, whether or not using TLS +int sockread(SSL *fd, char *buf, int bufsize, int tls) { + if (tls) { + return SSL_read(fd, buf, bufsize); + } else { + // Cast the supposed SSL *fd to a long int if we're not using TLS + return recv((long int)fd, buf, bufsize, 0); + } +} + +// Write to a socket, whether or not using TLS +int socksend(SSL *fd, char *buf, int bufsize, int tls) { + if (tls) { + return SSL_write(fd, buf, bufsize); + } else { + // Cast the supposed SSL *fd to a long int if we're not using TLS + return send((long int)fd, buf, bufsize, 0); + } +} diff --git a/sockets.h b/sockets.h index c342de2..099fc53 100644 --- a/sockets.h +++ b/sockets.h @@ -34,4 +34,10 @@ SSL_CTX *create_context(); void configure_context(SSL_CTX *ctx, char *certfile, char *keyfile); +// Read from a socket, whether or not using TLS +int sockread(SSL *fd, char *buf, int bufsize, int tls); + +// Write to a socket, whether or not using TLS +int socksend(SSL *fd, char *buf, int bufsize, int tls); + #endif -- cgit v1.2.3