summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Bratch <luke@bratch.co.uk>2025-08-11 23:02:08 +0100
committerLuke Bratch <luke@bratch.co.uk>2025-08-11 23:02:08 +0100
commit2a1d4b2e958de1581e9bda7b07b705b963e394a6 (patch)
treedb66b6cd7f3441a244469c57b35dcab65b3f5353
parent0e7f232b3d5ecb484d9d91bdd7e4b6d4e7791585 (diff)
Implement update checking using the command "BLABOUNCER UPDATECHECK", or optionally (enabled by default, toggled with configuration option "checkupdates") at startup and successful client authentication.HEADmaster
This is implemented using a DNS TXT record check to the domain "version.blabouncer.blatech.net".
-rw-r--r--Makefile2
-rw-r--r--README1
-rw-r--r--TODO2
-rw-r--r--blabouncer.c22
-rw-r--r--blabouncer.conf.example4
-rw-r--r--functions.c116
-rw-r--r--functions.h13
-rw-r--r--message.c54
-rw-r--r--structures.h1
9 files changed, 212 insertions, 3 deletions
diff --git a/Makefile b/Makefile
index baa334c..7b02278 100644
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,7 @@ ifeq ($(PREFIX),)
endif
blabouncer: blabouncer.c functions.c sockets.c config.c replay.c logging.c message.c config.h functions.h logging.h message.h replay.h sockets.h structures.h
- $(CC) -D_DEFAULT_SOURCE -D_BSD_SOURCE -DVERSION=\"$(GIT_VERSION)\" -std=gnu99 -Wall -Wextra -lssl -lcrypto -o $(BINARY) blabouncer.c functions.c sockets.c config.c replay.c logging.c message.c
+ $(CC) -D_DEFAULT_SOURCE -D_BSD_SOURCE -DVERSION=\"$(GIT_VERSION)\" -std=gnu99 -Wall -Wextra -lssl -lcrypto -lresolv -o $(BINARY) blabouncer.c functions.c sockets.c config.c replay.c logging.c message.c
.PHONY: clean
clean:
diff --git a/README b/README
index e3e5c7b..d03330d 100644
--- a/README
+++ b/README
@@ -56,6 +56,7 @@ what this client has missed.)
"BLABOUNCER DISCONNECT [FD]" (To disconnect a client with file descriptor number [FD] (see LISTCLIENTS
output).)
"BLABOUNCER VERSION" (To show the current blabouncer version.)
+"BLABOUNCER UPDATECHECK" (To check for blabouncer updates. Set checkupdates = "0" to stop this happening automatically.)
Blabouncer commands are all prefixed with BLABOUNCER which you can usually send using "/QUOTE BLABOUNCER".
diff --git a/TODO b/TODO
index 895e3c2..eb7bf2b 100644
--- a/TODO
+++ b/TODO
@@ -52,8 +52,6 @@ QUIT not logged in all channels a person was in? (e.g. Joey Mon 1 Apr 20:49:14
"/whois" with no nick - "No nickname given" goes to all clients - fixable?
-Ability to check for updates (and optional at startup?).
-
Absurd CPU usage and duration doing "/BLABOUNCER REPLAY 24:0" approx. 14/09/2024 17:35.
Custom OpenSSL protocols/ciphers?
diff --git a/blabouncer.c b/blabouncer.c
index b3e239d..443c75c 100644
--- a/blabouncer.c
+++ b/blabouncer.c
@@ -1331,6 +1331,13 @@ int main(int argc, char *argv[]) {
strncat(conffailmsg, "Error getting 'alertautheddisconnect' from configuration file.\n", sizeof conffailmsg - strlen(conffailmsg) - 1);
}
+ // Is check for updates upon startup and successful client authentication enabled?
+ settings.checkupdates = getconfint("checkupdates", settings.conffile);
+ if (errno == ECONFINT) {
+ conffail = 1;
+ strncat(conffailmsg, "Error getting 'checkupdates' from configuration file.\n", sizeof conffailmsg - strlen(conffailmsg) - 1);
+ }
+
// How many debug logs should we keep?
settings.debugkeep = getconfint("debugkeep", settings.conffile);
if (errno == ECONFINT) {
@@ -1439,6 +1446,21 @@ int main(int argc, char *argv[]) {
debugprint(DEBUG_SOME, "Using configuration file '%s'.\n", settings.conffile);
+ // Check for updates (if enabled in configuration file with checkupdates = "1")
+ if (settings.checkupdates) {
+ debugprint(DEBUG_SOME, "Checking for blabouncer updates (checkupdates = \"0\" to disable)...\n");
+ char version[MAXDNSTXTLEN];
+ version[0] = '\0';
+ int ret = checkversion(version);
+ if (ret == 1) {
+ debugprint(DEBUG_SOME, "main(): blabouncer appears to be up to date.\n");
+ } else if (ret == -1) {
+ debugprint(DEBUG_SOME, "main(): blabouncer appears to be out of date, latest version is %s.\n", version);
+ } else {
+ debugprint(DEBUG_CRIT, "main(): Version check failed!\n");
+ }
+ }
+
// Unless specified otherwise on the command line, fork to background
if (settings.background) {
pid_t pid, sid;
diff --git a/blabouncer.conf.example b/blabouncer.conf.example
index 2281806..4e8e7f1 100644
--- a/blabouncer.conf.example
+++ b/blabouncer.conf.example
@@ -123,3 +123,7 @@ alertunautheddisconnect = "1"
# Send NOTICE to all other clients upon authenticated client disconnections ("1" for yes or "0" for no)
alertautheddisconnect = "1"
+
+# Check for updates upon startup and successful client authentication ("1" for yes or "0" for no)
+# This sends a DNS TXT request to blatech.net, disable this if you do not wish for that to happen
+checkupdates = "1"
diff --git a/functions.c b/functions.c
index 54e2663..35be8e4 100644
--- a/functions.c
+++ b/functions.c
@@ -1710,3 +1710,119 @@ void strlower(char *string) {
string[i] = tolower(string[i]);
}
}
+
+// Gets a single TXT record from DNS for "dnsname" and stores the result in "record"
+// Returns 1 on success or 0 on failure
+// TODO: If this is ever used for more than just version checks, then it needs to do more than a single record.
+int gettxtrecordsingle(char *dnsname, char *record) {
+ debugprint(DEBUG_FULL, "gettxtrecordsingle(): given '%s'.\n", dnsname);
+
+ unsigned char buffer[8000] = {0};
+ char result[MAXDNSTXTLEN] = {0};
+ const unsigned char *presult = NULL;
+ struct __res_state resState = {0};
+ ns_msg nsMsg = {0};
+ ns_rr rr;
+
+ int type = 0;
+ int ret = 0;
+ int size = 0;
+ int count = 0;
+ long unsigned int len = 0;
+ int res_init = 0;
+
+ ret = res_ninit(&resState);
+
+ if (ret) {
+ debugprint(DEBUG_CRIT, "gettxtrecordsingle(): res_ninit() returned failure (it returned %d)! Returning 0.\n", ret);
+ return 0;
+ } else {
+ res_init = 1;
+ }
+
+ memset(buffer, 0, sizeof (buffer));
+ size = res_nquery(&resState, dnsname, C_IN, T_TXT, buffer, sizeof(buffer) - 1);
+
+ if (size < 1) {
+ res_nclose(&resState);
+ debugprint(DEBUG_CRIT, "gettxtrecordsingle(): res_nquery() returned zero-length or failure (it returend %d)! Returning 0.\n", size);
+ return 0;
+ }
+
+ ret = ns_initparse(buffer, size, &nsMsg);
+
+ if (ret) {
+ res_nclose(&resState);
+ debugprint(DEBUG_CRIT, "gettxtrecordsingle(): ns_initparse() returned failure (failed to parse the buffer buffer?) (it returned %d)! Returning 0.\n", ret);
+ return 0;
+ }
+
+ count = ns_msg_count(nsMsg, ns_s_an);
+
+ for (int i = 0; i < count; i++) {
+ ret = ns_parserr(&nsMsg, ns_s_an, i , &rr);
+
+ if (ret) {
+ res_nclose(&resState);
+ return 0;
+ }
+
+ type = ns_rr_type(rr);
+ if (ns_t_txt == type) {
+ len = ns_rr_rdlen(rr);
+ presult = ns_rr_rdata(rr);
+
+ if ((len > 1) && (len < sizeof(result))) {
+ len--;
+ memcpy (result, presult+1, len);
+ result[len] = '\0';
+ debugprint(DEBUG_FULL, "gettxtrecordsingle(): record '%d' from DNS is '%s'.\n", i, result);
+
+ // Only using the first result
+ if (i == 0) {
+ strncpy(record, result, sizeof(result) - 1);
+ }
+ }
+ }
+ }
+
+ debugprint(DEBUG_FULL, "gettxtrecordsingle(): using record '%s'.\n", result);
+
+ if (res_init) {
+ res_nclose (&resState);
+ }
+
+ return 1;
+}
+
+// Gets the latest blabouncer version string from DNS and stores it in "version"
+// Ignores the Git version portion (everything after the first '-') of VERSION, if present
+// Returns 1 if local VERSION is current, -1 if local VERSION is not current, or 0 on failure
+int checkversion(char *version) {
+ if (!gettxtrecordsingle(VERSIONTXTNAME, version)) {
+ debugprint(DEBUG_CRIT, "checkversion(): gettxtrecordsingle() failure and the buffer contains '%s', returning 0.\n", version);
+ return 0;
+ } else {
+ debugprint(DEBUG_FULL, "checkversion(): gettxtrecordsingle() success and the returned record is '%s'.\n", version);
+ }
+
+ // Ignore the Git version portion (everything after the first '-') of VERSION, if present
+ char localversion[MAXDNSTXTLEN];
+ strncpy(localversion, VERSION, sizeof(localversion) - 1);
+
+ for (size_t i = 0; i < strlen(localversion); i++) {
+ if (localversion[i] == '-') {
+ localversion[i] = '\0';
+ debugprint(DEBUG_FULL, "checkversion(): Trimmed local VERSION string to '%s'.\n", localversion);
+ break;
+ }
+ }
+
+ if (strncmp(localversion, version, sizeof(localversion))) {
+ debugprint(DEBUG_SOME, "checkversion(): Returned version '%s' doesn't match expected '%s', returning -1.\n", version, localversion);
+ return -1;
+ } else {
+ debugprint(DEBUG_FULL, "checkversion(): Returned version '%s' matches expected '%s', returning 1.\n", version, localversion);
+ return 1;
+ }
+}
diff --git a/functions.h b/functions.h
index ff9d656..72a2129 100644
--- a/functions.h
+++ b/functions.h
@@ -28,6 +28,7 @@
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
+#include <resolv.h>
#include <sys/select.h>
#include <stdarg.h>
#include <limits.h>
@@ -52,6 +53,8 @@
#define MAXCLIENTS 32 // Maximum number of clients that can connect to the bouncer at a time
#define MAXCHANNELS 1024 // Let's assume 1024 is reasonable for now (it's configured per IRCd)
#define MAXRFCNICKLEN 9 // From RFC 1459
+#define MAXDNSTXTLEN 2048 // Maximum size to store in a DNS TXT record response
+#define VERSIONTXTNAME "version.blabouncer.blatech.net" // DNS name of blabouncer latest version check TXT record
#define MAXTOKENS 100 // For strsep string splitting
@@ -220,4 +223,14 @@ void stripprefixesfromnick(char *nick);
// Convert the given 'string' into lowercase
void strlower(char *string);
+
+// Gets a single TXT record from DNS for "dnsname" and stores the result in "record"
+// Returns 1 on success or 0 on failure
+// TODO: If this is ever used for more than just version checks, then it needs to do more than a single record.
+int gettxtrecordsingle(char *dnsname, char *record);
+
+// Gets the latest blabouncer version string from DNS and stores it in "version"
+// Ignores the Git version portion (everything after the first '-') of VERSION, if present
+// Returns 1 if local VERSION is current, -1 if local VERSION is not current, or 0 on failure
+int checkversion(char *version);
#endif
diff --git a/message.c b/message.c
index f8e14cb..f580419 100644
--- a/message.c
+++ b/message.c
@@ -1033,6 +1033,31 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int
// Send our own greeting message
snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Welcome to blabouncer version %s!", ircdstate->ircnick, VERSION);
sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+
+ // Check for updates (if enabled in configuration file with checkupdates = "1")
+ if (settings->checkupdates) {
+ debugprint(DEBUG_FULL, "Checking for blabouncer updates on client connection.\n");
+ char version[MAXDNSTXTLEN];
+ version[0] = '\0';
+ int ret = checkversion(version);
+ if (ret == 1) {
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Blabouncer appears to be up to date.", ircdstate->ircnick);
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+ } else if (ret == -1) {
+
+ // Next prepare the topic who/when message...
+ if (!snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Blabouncer appears to be out of date, latest version is %s.", ircdstate->ircnick, version)) {
+ fprintf(stderr, "processclientmessage() Error while preparing out of date version warning after client connected (version string too long?)!\n");
+ debugprint(DEBUG_CRIT, "processclientmessage() Error while preparing out of date version warning after client connected (version string too long?)!\n");
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Blabouncer appears to be out of date, but couldn't print latest version in this NOTICE.", ircdstate->ircnick);
+ }
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+ } else {
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Version check failed!", ircdstate->ircnick);
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+ }
+ }
+
snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Blabouncer commands are all prefixed with BLABOUNCER which you can usually send using \"/QUOTE BLABOUNCER\"", ircdstate->ircnick);
sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Valid blabouncer commands are:", ircdstate->ircnick);
@@ -1051,6 +1076,8 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int
sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER VERSION\" (To show the current blabouncer version.)", ircdstate->ircnick);
sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER UPDATECHECK\" (To check for blabouncer version updates.)", ircdstate->ircnick);
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
// Get the channel count so we can iterate over all channels.
int channelcount = getchannelcount(channels, ircdstate->maxchannelcount);
@@ -1528,6 +1555,31 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int
snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :This is blabouncer version %s!", ircdstate->ircnick, VERSION);
sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
return 1;
+ // UPDATECHECK received, send current blabouncer version
+ } else if (strncasecmp(tokens[1], "UPDATECHECK", strlen("UPDATECHECK")) == 0) {
+ debugprint(DEBUG_SOME, "Client BLABOUNCER UPDATECHECK found and it is: %s with length %zd!\n", tokens[1], strlen(tokens[1]));
+
+ char version[MAXDNSTXTLEN];
+ version[0] = '\0';
+ int ret = checkversion(version);
+ if (ret == 1) {
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Blabouncer appears to be up to date.", ircdstate->ircnick);
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+ } else if (ret == -1) {
+
+ // Next prepare the topic who/when message...
+ if (!snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Blabouncer appears to be out of date, latest version is %s.", ircdstate->ircnick, version)) {
+ fprintf(stderr, "processclientmessage() Error while preparing out of date version warning after client connected (version string too long?)!\n");
+ debugprint(DEBUG_CRIT, "processclientmessage() Error while preparing out of date version warning after client connected (version string too long?)!\n");
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Blabouncer appears to be out of date, but couldn't print latest version in this NOTICE.", ircdstate->ircnick);
+ }
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+ } else {
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Version check failed!", ircdstate->ircnick);
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+ }
+
+ return 1;
// LISTCLIENTS received, send list of connected clients and their authentication status
} else if (strncasecmp(tokens[1], "LISTCLIENTS", strlen("LISTCLIENTS")) == 0) {
debugprint(DEBUG_SOME, "Client BLABOUNCER LISTCLIENTS found and it is: %s with length %zd!\n", tokens[1], strlen(tokens[1]));
@@ -1610,6 +1662,8 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int
sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER VERSION\" (To show the current blabouncer version.)", ircdstate->ircnick);
sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER UPDATECHECK\" (To check for blabouncer version updates.)", ircdstate->ircnick);
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
return 1;
}
}
diff --git a/structures.h b/structures.h
index c4d20e0..86f83b7 100644
--- a/structures.h
+++ b/structures.h
@@ -90,6 +90,7 @@ struct settings {
int alertauthsuccess;
int alertunautheddisconnect;
int alertautheddisconnect;
+ int checkupdates;
};
// Structure of a connected client, their socket/file descriptors, their authentication status, and their OpenSSL structures