From 3612ac309895be690c8dc3080898dfb713b8a23e Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Sat, 30 Mar 2024 16:09:37 +0000 Subject: Implement changing client-side TLS certificate and key paths, and reloading certificate/key at runtime when doing a REHASH (BLABOUNCER command or SIGHUP). --- README | 7 +++++-- TODO | 4 +--- blabouncer.c | 29 ++++++++++++++++++++++------- functions.c | 35 ++++++++++++++++++++++++++++++++++- functions.h | 3 ++- message.c | 13 +++++++------ message.h | 3 ++- 7 files changed, 73 insertions(+), 21 deletions(-) diff --git a/README b/README index 7381b49..5b217cf 100644 --- a/README +++ b/README @@ -23,7 +23,8 @@ If you don't specify one using "-c /path/to/configuration/file" then the example for you at $HOME/.blabouncer/blabouncer.conf when starting for the first time. Certain configuration options can be changed at runtime by changing them in the configuration file, then -issuing a BLABOUNCER REHASH command, or by sending SIGHUP to the blabouncer process. +issuing a BLABOUNCER REHASH command, or by sending SIGHUP to the blabouncer process. This will also +reload the client-side TLS certificate and key if clienttls = "1". These options can be changed by issuing a BLABOUNCER REHASH command or by sending SIGHUP to blabouncer: - nicks @@ -34,6 +35,8 @@ These options can be changed by issuing a BLABOUNCER REHASH command or by sendin - logging - replaylogging - debug + - certfile + - keyfile == Commands == @@ -41,7 +44,7 @@ Once connected to blabouncer with an IRC client, you can use the following speci "BLABOUNCER REPLAY [[[days:]hours:]minutes]" (To replay a given length of time of replay log.) "BLABOUNCER QUIT [quit message]" (To quit blabouncer, optionally sending [quit message] to the server.) -"BLABOUNCER REHASH" (To reload settings from the configuration file, see above for details.) +"BLABOUNCER REHASH" (To reload configuration file settings and the client-side TLS certificate/key - see above for details.) "BLABOUNCER CLIENTCODE [clientcode]" (To set an identifier for the current client for auto replaying just what this client has missed.) "BLABOUNCER LISTCLIENTS" (To list all connected clients and their authentication status.) diff --git a/TODO b/TODO index 3c64735..c0000c8 100644 --- a/TODO +++ b/TODO @@ -12,8 +12,6 @@ macOS compiler may need limits.h included in structures.h. Can memory usage be reduced further? (e.g. better channel struct management) -Ability to load new certificate whilst running. - Remaining strncmps are safe but could do with length comparisons too. "Our initial MODE" comparison may need to be more complex. "/topic" response goes to all clients. @@ -48,7 +46,7 @@ Sun Apr 9 04:16:09 2023: Server PART: nick is ours ('bbounce2' vs 'bbounce2'). Crash when requesting 30 hour replay. -Users in channel not being populated observed once - 27/07/2023 #pipewire on irc.oftc.net. +Users in channel not being populated observed once - 27/07/2023 #pipewire on irc.oftc.net. (and again 14/09/2023) (related to?): 11/06/2023 01:00:41 "processrawstring(): combined truncated message '2131365056' is too long (1006 out of a maximum of 512 characters), clearing it." seen during long/truncated/recombined 353 RPL_NAMREPLY #winehq on irc.libera.chat. diff --git a/blabouncer.c b/blabouncer.c index c56fc9d..72118ec 100644 --- a/blabouncer.c +++ b/blabouncer.c @@ -180,7 +180,7 @@ int connecttoircserver(SSL_CTX **serverctx, SSL **server_ssl, int *serversockfd, // Return 0 if we didn't process it and the caller might want to do something //int processircmessage(int *serversockfd, int *clientsockfd, char *str, int source) { int processircmessage(SSL *server_ssl, char *str, int source, struct client *clients, int sourcefd, struct ircdstate *ircdstate, struct channel *channels, - struct settings *settings, struct clientcodes *clientcodes) { + struct settings *settings, struct clientcodes *clientcodes, SSL_CTX *ctx) { // Track which space-separated token within this response we're on int counter = 0; @@ -219,7 +219,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli // Don't return if we got here because this means we didn't process something in processservermessage() break; case SOURCE_CLIENT: // If message(s) were from a real IRC client - if (processclientmessage(server_ssl, str, clients, sourcefd, ircdstate, channels, settings, tokens, counter, clientcodes)) { + if (processclientmessage(server_ssl, str, clients, sourcefd, ircdstate, channels, settings, tokens, counter, clientcodes, ctx)) { // We processed something so return true free(strcopyPtr); return 1; @@ -265,7 +265,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli // Return 0 if something went wrong // Return 1 if everything OK int processrawstring(SSL *server_ssl, char *str, int source, struct client *clients, int sourcefd, struct ircdstate *ircdstate, struct channel *channels, - struct settings *settings, struct clientcodes *clientcodes) { + struct settings *settings, struct clientcodes *clientcodes, SSL_CTX *ctx) { // Copy to a temporary string so we still have the original in case it's not processed char *strcopy = strdup(str); // Keep track of initial pointer for free()ing later @@ -334,7 +334,7 @@ int processrawstring(SSL *server_ssl, char *str, int source, struct client *clie for (int i = 0; i < messagecount; i++) { // Copy to a temporary string so we still have the original in case it's not processed char *messagecopy = strdup(messages[i]); - if (processircmessage(server_ssl, messagecopy, source, clients, sourcefd, ircdstate, channels, settings, clientcodes)) { + if (processircmessage(server_ssl, messagecopy, source, clients, sourcefd, ircdstate, channels, settings, clientcodes, ctx)) { debugprint(DEBUG_FULL, "Message processed: \"%s\", NULLing...\n", messages[i]); messages[i][0] = '\0'; } @@ -518,7 +518,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { failuremsg[0] = '\0'; // Try to rehash... - if (!rehash(settings, failuremsg)) { + if (!rehash(settings, failuremsg, ctx)) { // ...or log and tell all clients if it failed debugprint(DEBUG_CRIT, "SIGHUP REHASH failed: %s.\n", failuremsg); if (!snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :SIGHUP REHASH failed: %s.", ircdstate.ircnick, failuremsg)) { @@ -707,7 +707,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { // Try to process received string (which should contain one or more server responses/commands) // TODO - What if there were two server responses/commands and only one didn't need relaying? - if (!processrawstring(server_ssl, serverbuf, SOURCE_SERVER, clients, EXCEPT_NONE, &ircdstate, channels, settings, clientcodes)) { + if (!processrawstring(server_ssl, serverbuf, SOURCE_SERVER, clients, EXCEPT_NONE, &ircdstate, channels, settings, clientcodes, ctx)) { fprintf(stderr, "Error: bouncer-server failed to process raw string.\n"); debugprint(DEBUG_CRIT, "Error: bouncer-server failed to process raw string.\n"); } @@ -736,6 +736,8 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { } // STDIN based commands for debugging + + // STDIN: List all channels if (strncmp(outgoingmsg, "listchannels", strlen("listchannels")) == 0) { printf("STDIN command starting: listchannels\n"); debugprint(DEBUG_SOME, "dochat(): stdin: STDIN command starting: listchannels\n"); @@ -761,6 +763,19 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { continue; } + // STDIN: Reconfigure OpenSSL context + if (strncmp(outgoingmsg, "reopenssl", strlen("reopenssl")) == 0) { + printf("STDIN command starting: reopenssl\n"); + debugprint(DEBUG_SOME, "dochat(): stdin: STDIN command starting: reopenssl\n"); + + configure_openssl_context(ctx, settings->certfile, settings->keyfile); + + debugprint(DEBUG_SOME, "dochat(): stdin: STDIN command complete: reopenssl\n"); + printf("STDIN command complete: reopenssl\n"); + + continue; + } + debugprint(DEBUG_FULL, "dochat(): stdin: '%s' not processed as a command, sending to server.\n", outgoingmsg); // sourcefd = 0 as this is a trusted message sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings); @@ -919,7 +934,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { // Try to process received string (which should contain one or more client responses/commands) // TODO - What if there were two client responses/commands and only one didn't need relaying? - if (!processrawstring(server_ssl, clientbuf, SOURCE_CLIENT, clients, i, &ircdstate, channels, settings, clientcodes)) { + if (!processrawstring(server_ssl, clientbuf, SOURCE_CLIENT, clients, i, &ircdstate, channels, settings, clientcodes, ctx)) { fprintf(stderr, "Error: bouncer-client failed to process raw string.\n"); debugprint(DEBUG_CRIT, "Error: bouncer-client failed to process raw string.\n"); } diff --git a/functions.c b/functions.c index 377f1af..d0c0341 100644 --- a/functions.c +++ b/functions.c @@ -1076,8 +1076,9 @@ void cleanexit(SSL *server_ssl, struct client *clients, int sourcefd, struct irc } // Re-read the configuration file, setting 'failuremsg' to a failure message on failure. +// 'ctx' is the client OpenSSL context for changing the certificate/key. // Returns 1 on success or 0 on failure. -int rehash(struct settings *settings, char *failuremsg) { +int rehash(struct settings *settings, char *failuremsg, SSL_CTX *ctx) { // TODO - Try to share some/all of this code with the initial main() settings loading // What are the configured nick(s)? @@ -1198,6 +1199,38 @@ int rehash(struct settings *settings, char *failuremsg) { return 0; } + // If clienttls = 1, re-read the certificate and key file paths (we don't support switching between TLS and non-TLS) + if (settings->clienttls) { + + // What is the certificate file path? + char oldcertfile[PATH_MAX]; + strcpy(oldcertfile, settings->certfile); + if (!getconfstr("certfile", settings->conffile, settings->certfile)) { + // If none provided, set to default + if (!snprintf(settings->certfile, PATH_MAX, "%s/cert.pem", settings->basedir)) { + strcpy(settings->certfile, oldcertfile); + strcpy(failuremsg, "didn't get 'certfile' from configuration file and failed to prepare default certfile location"); + return 0; + } + } + + // What is the key file path? + char oldkeyfile[PATH_MAX]; + strcpy(oldkeyfile, settings->keyfile); + if (!getconfstr("keyfile", settings->conffile, settings->keyfile)) { + + // If none provided, set to default + if (!snprintf(settings->keyfile, PATH_MAX, "%s/key.pem", settings->basedir)) { + strcpy(settings->keyfile, oldkeyfile); + strcpy(failuremsg, "didn't get 'keyfile' from configuration file and failed to prepare default keyfile location"); + return 0; + } + } + + // Reconfigure OpenSSL context in case the certificate or the key changed + configure_openssl_context(ctx, settings->certfile, settings->keyfile); + } + // All is good, no failure message, return 1. failuremsg[0] = '\0'; return 1; diff --git a/functions.h b/functions.h index 6ec2c28..ff9d656 100644 --- a/functions.h +++ b/functions.h @@ -167,8 +167,9 @@ void tryautonick(struct ircdstate *ircdstate); void cleanexit(SSL *server_ssl, struct client *clients, int sourcefd, struct ircdstate *ircdstate, struct settings *settings, char *quitmsg); // Re-read the configuration file, setting 'failuremsg' to a failure message on failure. +// 'ctx' is the client OpenSSL context for changing the certificate/key. // Returns 1 on success or 0 on failure. -int rehash(struct settings *settings, char *failuremsg); +int rehash(struct settings *settings, char *failuremsg, SSL_CTX *ctx); // Check the password provided in the string 'str' against what is in // the settings structure 'settings'. diff --git a/message.c b/message.c index c45f56b..f9e65a2 100644 --- a/message.c +++ b/message.c @@ -189,9 +189,9 @@ int processservermessage(SSL *server_ssl, char *str, struct client *clients, int if (strncmp(tokens[1], "JOIN", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server JOIN found and it is: %s with length %zd! Next token is '%s'. Adding to local channel list if it's us.\n", tokens[0], strlen(tokens[0]), tokens[2]); // Next token should be the channel name but it probably needs the leading ':' stripping - debugprint(DEBUG_FULL, "processircmessage(): Channel name was '%s'\n", tokens[2]); + debugprint(DEBUG_FULL, "processservermessage(): Channel name was '%s'\n", tokens[2]); stripprefix(tokens[2], 1); - debugprint(DEBUG_FULL, "processircmessage(): Channel name now '%s'\n", tokens[2]); + debugprint(DEBUG_FULL, "processservermessage(): Channel name now '%s'\n", tokens[2]); // If the user JOINing is us, then we must have joined a channel, so add to our local channel array. // Copy to a temporary string so we still have the original in case we need it @@ -850,7 +850,8 @@ int processservermessage(SSL *server_ssl, char *str, struct client *clients, int // Process an IRC message that came from a client. // Return 1 if we processed it, or 0 if we didn't. int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int sourcefd, struct ircdstate *ircdstate, - struct channel *channels, struct settings *settings, char tokens[MAXTOKENS][MAXDATASIZE], int counter, struct clientcodes *clientcodes) { + struct channel *channels, struct settings *settings, char tokens[MAXTOKENS][MAXDATASIZE], int counter, + struct clientcodes *clientcodes, SSL_CTX *ctx) { // Index of client fd in clients array for use later int clientindex = arrindex(clients, sourcefd); if (clientindex < 0) { @@ -999,7 +1000,7 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REPLAY [[[days:]hours:]minutes]\" (To replay a given length of time of replay log.)", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); - snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REHASH\" (To reload settings from the configuration file, see README for which settings can be reloaded.)", ircdstate->ircnick); + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REHASH\" (To reload configuration file settings and the client-side TLS certificate/key - see README for details.)", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER CLIENTCODE [clientcode]\" (To set an identifier for the current client for auto replaying just what this client has missed.)", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); @@ -1422,7 +1423,7 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int failuremsg[0] = '\0'; // Try to rehash... - if (!rehash(settings, failuremsg)) { + if (!rehash(settings, failuremsg, ctx)) { // ...or log and tell client if it failed debugprint(DEBUG_CRIT, "REHASH failed: %s.\n", failuremsg); if (!snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :REHASH failed: %s.", ircdstate->ircnick, failuremsg)) { @@ -1545,7 +1546,7 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int } snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REPLAY [[[days:]hours:]minutes]\" (To replay a given length of time of replay log.)", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); - snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REHASH\" (To reload settings from the configuration file, see README for which settings can be reloaded.)", ircdstate->ircnick); + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REHASH\" (To reload configuration file settings and the client-side TLS certificate/key - see README for details.)", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER CLIENTCODE [clientcode]\" (To set an identifier for the current client for auto replaying just what this client has missed.)", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); diff --git a/message.h b/message.h index 5d6cfff..2bf4bab 100644 --- a/message.h +++ b/message.h @@ -32,6 +32,7 @@ int processservermessage(SSL *server_ssl, char *str, struct client *clients, int // Process an IRC message that came from a client. // Return 1 if we processed it, or 0 if we didn't. int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int sourcefd, struct ircdstate *ircdstate, - struct channel *channels, struct settings *settings, char tokens[MAXTOKENS][MAXDATASIZE], int counter, struct clientcodes *clientcodes); + struct channel *channels, struct settings *settings, char tokens[MAXTOKENS][MAXDATASIZE], int counter, + struct clientcodes *clientcodes, SSL_CTX *ctx); #endif -- cgit v1.2.3