From c70cd5cccc966a35f175913f2281ce251fd62425 Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Mon, 17 Jun 2019 01:46:28 +0100 Subject: Implement a per-client identifier so auto replay can replay everything a given client has missed. --- README | 51 +++++++++++++++++++++++---- TODO | 2 -- blabouncer.c | 43 ++++++++++++++-------- blabouncer.conf.example | 1 + config.c | 1 + functions.c | 94 +++++++++++++++++++++++++++++++++++++++++++++++-- functions.h | 14 +++++++- message.c | 59 ++++++++++++++++++++++++++++--- message.h | 2 +- structures.h | 9 +++++ 10 files changed, 243 insertions(+), 33 deletions(-) diff --git a/README b/README index e531121..b0c30d0 100644 --- a/README +++ b/README @@ -20,9 +20,11 @@ Arguments are all optional, but they must be specified in the order shown above. An example configuration file is provided named "blabouncer.conf". -If you don't specify one using "-c /path/to/configuration/file" then the example configuration one will be created for you in $HOME/.blabouncer/ when starting. +If you don't specify one using "-c /path/to/configuration/file" then the example configuration one will +be created for you in $HOME/.blabouncer/ when starting. -Certain configuration options can be changed at runtime, either at any time, or by issuing a BLABOUNCER REHASH command or by sending SIGHUP to blabouncer. +Certain configuration options can be changed at runtime, either at any time, or by issuing a BLABOUNCER +REHASH command or by sending SIGHUP to blabouncer. These options can be changed by issuing a BLABOUNCER REHASH command or by sending SIGHUP to blabouncer: - nick @@ -42,21 +44,58 @@ Once connected to blabouncer with a client, you can use the following commands: "BLABOUNCER REPLAY [[[[days:]hours:]minutes:]seconds]" (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 CLIENTCODE [clientcode]" (To set an identifier for the current client for auto replaying just +what this client has missed.) Blabouncer commands are all prefixed with BLABOUNCER which you can usually send using "/QUOTE BLABOUNCER". +== Replaying history == + +You can request that history is replayed at any time by using the command BLABOUNCER REPLAY +[[[[days:]hours:]minutes:]seconds], assuming replaylogging is enabled in blabouncer.conf. + +In addition, you can have history automatically replayed upon a client connecting. There are multiple +automatic replay methods available by setting replaymode in blabouncer.conf: + +- "none" + Don't auto replay. + +- "time" + Always send the last "replayseconds" worth of logs. + +- "lastspoke" + All messages since your current nick last spoke. + +- "noclients" + All messages since you last had no clients connected. + +- "lastchange" + All messages since your last client connection/disconnection. + +- "perclient". + All messages since the current client last disconnected. Clients are uniquely identified using the + BLABOUNCER CLIENTCODE command. + The intention is that a given client (for instance that on a given device) will be configured to send + BLABOUNCER CLIENTCODE as a connect command automatically, thus getting all replay history since it was + last connected. + == Client SSL/TLS == -To use TLS for client connections, which is enabled by default in the example configuration file, you will need to provide both a certificate and a key file. +To use TLS for client connections, which is enabled by default in the example configuration file, you will +need to provide both a certificate and a key file. + One way of creating these is to use OpenSSL: # openssl genrsa 2048 > ~/.blabouncer/key.pem # openssl req -x509 -days 30 -new -key ~/.blabouncer/key.pem -out ~/.blabouncer/cert.pem -But you can create them however you like, or disable client TLS (not recommended) using the "clienttls" configuration file option. +But you can create them however you like, or disable client TLS (not recommended) using the "clienttls" +configuration file option. -By default blabouncer looks for the certificate and key files in $HOME/.blabouncer/cert.pem and $HOME/.blabouncer/key.pem, respectively. +By default blabouncer looks for the certificate and key files in $HOME/.blabouncer/cert.pem and +$HOME/.blabouncer/key.pem, respectively. == Server SSL/TLS == -Server TLS is also enabled by default in the example configuration file, and can be disabled (not recommended) using the "servertls" configuration file option. +Server TLS is also enabled by default in the example configuration file, and can be disabled (not +recommended) using the "servertls" configuration file option. diff --git a/TODO b/TODO index b1ff47d..e69de29 100644 --- a/TODO +++ b/TODO @@ -1,2 +0,0 @@ -Add various auto replay options: - - All logs since the current client last disconnected (track clients with some special token the client auto sends on connect) diff --git a/blabouncer.c b/blabouncer.c index 95521f1..73bef8c 100644 --- a/blabouncer.c +++ b/blabouncer.c @@ -134,6 +134,7 @@ int connecttoircserver(SSL_CTX **serverctx, SSL **server_ssl, int *serversockfd, ircdstate->timeoutcheck = 0; // ircdstate.reconnecting is not set here as we want to track reconnections separately // ircdstate.clientchangetime and ircdstate.clientsnonetime not set here as they are set at startup and only changed when clients connect/disconnect + // ircdstate.clientcodes not set here, set on startup and whenever a client sets one // Populate nick and username from our configuration file for now, real IRCd may change them later (TODO - Is this true of username?) strcpy(ircdstate->ircnick, settings->ircnick); @@ -176,7 +177,8 @@ int connecttoircserver(SSL_CTX **serverctx, SSL **server_ssl, int *serversockfd, // Return 1 if we processed something and expect the caller to not need to do anything more // 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) { +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) { // Track which space-separated token within this response we're on int counter = 0; @@ -215,7 +217,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)) { + if (processclientmessage(server_ssl, str, clients, sourcefd, ircdstate, channels, settings, tokens, counter, clientcodes)) { // We processed something so return true free(strcopyPtr); return 1; @@ -254,7 +256,8 @@ 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) { +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) { // 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 @@ -318,7 +321,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)) { + if (processircmessage(server_ssl, messagecopy, source, clients, sourcefd, ircdstate, channels, settings, clientcodes)) { debugprint(DEBUG_FULL, "Message processed: \"%s\", NULLing...\n", messages[i]); messages[i][0] = '\0'; } @@ -392,11 +395,25 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { clients[i].pendingwhowas = 0; clients[i].pendingnames = 0; clients[i].pendingcap = 0; + clients[i].clientcode[0] = '\0'; } // Struct of various strings from and for the real IRCd (such as the greeting strings, the real IRCd's name, // our nick!user@host string, our nick, username, real name, etc.) struct ircdstate ircdstate; + // Set reconnection and other things to null/zero for now (not used unless reconnecting to server) + ircdstate.oldnick[0] = '\0'; + ircdstate.reconnecting = 0; + ircdstate.clientchangetime = time(NULL); + ircdstate.clientsnonetime = time(NULL); + + // Struct of client codes + // Used to track the last time a client identifying as a given client connected to handle auto replay for a known client. + struct clientcodes clientcodes[MAXCLIENTCODES]; + for (int i = 0; i < MAXCLIENTCODES; i++) { + clientcodes[i].code[0] = '\0'; + clientcodes[i].lastdisconnected = 0; + } // Struct of channels we're in struct channel *channels; @@ -413,11 +430,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { SSL_CTX *serverctx = NULL; SSL *server_ssl = NULL; // Need to create this either way as referenced later - // Set reconnection and other things to null/zero for now (not used unless reconnecting to server) - ircdstate.oldnick[0] = '\0'; - ircdstate.reconnecting = 0; - ircdstate.clientchangetime = time(NULL); - ircdstate.clientsnonetime = time(NULL); + // Try to connect to IRC! if (!connecttoircserver(&serverctx, &server_ssl, serversockfd, &ircdstate, settings, clients)) { fprintf(stderr, "Failed to connect to IRC server, exiting.\n"); debugprint(DEBUG_CRIT, "Failed to connect to IRC server, exiting.\n"); @@ -648,7 +661,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 respones/commands and only one didn't need relaying? - if (!processrawstring(server_ssl, serverbuf, SOURCE_SERVER, clients, EXCEPT_NONE, &ircdstate, channels, settings)) { + if (!processrawstring(server_ssl, serverbuf, SOURCE_SERVER, clients, EXCEPT_NONE, &ircdstate, channels, settings, clientcodes)) { fprintf(stderr, "Error: bouncer-server failed to process raw string.\n"); debugprint(DEBUG_CRIT, "Error: bouncer-server failed to process raw string.\n"); } @@ -713,7 +726,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { if (numclients(clients) >= MAXCLIENTS) { fprintf(stderr, "too many clients, disconnecting and skipping loop iteration!\n"); debugprint(DEBUG_CRIT, "too many clients, disconnecting and skipping loop iteration!\n"); - disconnectclient(i, clients, &ircdstate, settings); + disconnectclient(i, clients, &ircdstate, settings, clientcodes); continue; } addrlen = sizeof remoteaddr; @@ -779,7 +792,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { debugprint(DEBUG_CRIT, "dochat(): client sockread() error fd '%d'.\n", i); } // Disconnect the client - disconnectclient(i, clients, &ircdstate, settings); + disconnectclient(i, clients, &ircdstate, settings, clientcodes); FD_CLR(i, &rfds); // remove from master set - TODO is this needed at the moment since we just add everything from *clientsockfd to fdmax to rfds // TODO - Handle the "remove the client" loop not finding the old fd debugprint(DEBUG_FULL, "bouncer-client: total client connections: %d\n", numclients(clients)); @@ -805,7 +818,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 server respones/commands and only one didn't need relaying? - if (!processrawstring(server_ssl, clientbuf, SOURCE_CLIENT, clients, i, &ircdstate, channels, settings)) { + if (!processrawstring(server_ssl, clientbuf, SOURCE_CLIENT, clients, i, &ircdstate, channels, settings, clientcodes)) { fprintf(stderr, "Error: bouncer-client failed to process raw string.\n"); debugprint(DEBUG_CRIT, "Error: bouncer-client failed to process raw string.\n"); } @@ -881,8 +894,8 @@ int main(int argc, char *argv[]) { exit(1); } else { if (strcmp(settings.replaymode, "none") && strcmp(settings.replaymode, "time") && strcmp(settings.replaymode, "lastspoke") && - strcmp(settings.replaymode, "noclients") && strcmp(settings.replaymode, "lastchange")) { - printf("main(): replaymode in configuration file must be one of \"none\", \"time\", \"lastspoke\", \"noclients\", or \"lastchange\".\n"); + strcmp(settings.replaymode, "noclients") && strcmp(settings.replaymode, "lastchange") && strcmp(settings.replaymode, "perclient")) { + printf("main(): replaymode in configuration file must be one of \"none\", \"time\", \"lastspoke\", \"noclients\", \"lastchange\" or \"perclient\".\n"); exit(1); } } diff --git a/blabouncer.conf.example b/blabouncer.conf.example index 1537805..76df607 100644 --- a/blabouncer.conf.example +++ b/blabouncer.conf.example @@ -26,6 +26,7 @@ realname = "Mr Bla Bouncer" # "lastspoke" = All messages since your current nick last spoke # "noclients" = All messages since you last had no clients connected # "lastchange" = All messages since your last client connection/disconnection +# "perclient" = All messages since the current client last disconnected (see README) replaymode = "time" # How many seconds of replay log should be sent to connecting clients if replaymode = "time" diff --git a/config.c b/config.c index 6246789..7f08e52 100644 --- a/config.c +++ b/config.c @@ -180,6 +180,7 @@ int createconfigfile(char *filename) { "# \"lastspoke\" = All messages since your current nick last spoke\n" "# \"noclients\" = All messages since you last had no clients connected\n" "# \"lastchange\" = All messages since your last client connection/disconnection\n" + "# \"perclient\" = All messages since the current client last disconnected (see README)\n" "replaymode = \"time\"\n" "\n" "# How many seconds of replay log should be sent to connecting clients if replaymode = \"time\"\n" diff --git a/functions.c b/functions.c index 1efb2c1..f8609b1 100644 --- a/functions.c +++ b/functions.c @@ -536,7 +536,7 @@ int sendtoserver(SSL *server_ssl, char *strsrc, int str_len, int clientfd, struc // it from the array of clients. // Also set its authentication and registration statuses to 0. // Also set the pending statuses to 0 -int disconnectclient(int fd, struct client *clients, struct ircdstate *ircdstate, struct settings *settings) { +int disconnectclient(int fd, struct client *clients, struct ircdstate *ircdstate, struct settings *settings, struct clientcodes *clientcodes) { debugprint(DEBUG_SOME, "disconnectclient(): disconnecting client fd '%d'\n", fd); // Alert other clients about the disconnection (don't send yet, we haven't removed from the clients array yet) @@ -553,6 +553,10 @@ int disconnectclient(int fd, struct client *clients, struct ircdstate *ircdstate // If the client was registered, record the time of the last client disconnect if (clients[i].registered) { ircdstate->clientchangetime = time(NULL); + // And set their client code, if any, to last disconnecting at this time. + if (clients[i].clientcode[0]) { + setclientcodetime(clients[i].clientcode, clientcodes); + } } clients[i].fd = 0; clients[i].authed = 0; @@ -565,6 +569,7 @@ int disconnectclient(int fd, struct client *clients, struct ircdstate *ircdstate clients[i].pendingwhowas = 0; clients[i].pendingnames = 0; clients[i].pendingcap = 0; + clients[i].clientcode[0] = '\0'; if (settings->clienttls) { // Finish up with OpenSSL if using client TLS SSL_free(clients[i].ssl); @@ -902,6 +907,11 @@ int doautoreplay(int sourcefd, struct client *clients, struct settings *settings return 1; } + // If replaymode = "lastchange" then don't handle this here, it is done when the client registers its client code + if (!strncmp(settings->replaymode, "perclient", strlen("perclient"))) { + return 1; + } + // We shouldn't get here return 0; } @@ -1075,9 +1085,9 @@ int rehash(struct settings *settings, char *failuremsg) { return 0; } else { if (strcmp(settings->replaymode, "none") && strcmp(settings->replaymode, "time") && strcmp(settings->replaymode, "lastspoke") && - strcmp(settings->replaymode, "noclients") && strcmp(settings->replaymode, "lastchange")) { + strcmp(settings->replaymode, "noclients") && strcmp(settings->replaymode, "lastchange") && strcmp(settings->replaymode, "perclient")) { strcpy(settings->replaymode, oldreplaymode); - strcpy(failuremsg, "replaymode in configuration file must be one of \"none\", \"time\", \"lastspoke\", \"noclients\", or \"lastchange\""); + strcpy(failuremsg, "replaymode in configuration file must be one of \"none\", \"time\", \"lastspoke\", \"noclients\", \"lastchange\", or \"perclient\""); return 0; } } @@ -1155,3 +1165,81 @@ int checkpassword(char *password, struct settings *settings) { // No messing around with password stuff exit(EXIT_FAILURE); } + +// Adds a client code to the clientcode structure if it doesn't already exist. +// On success, copy it to the client's clientcode field. +// Returns 1 on adding a new code, 0 if the code already existed, or -1 on error. +int addclientcode(int sourcefd, char *code, struct clientcodes *clientcodes, struct client *clients) { + debugprint(DEBUG_SOME, "addclientcode(): Adding client code '%s' if it doesn't already exist.\n", code); + + // Make sure there aren't too many client codes already + int counter; + for (int i = 0; i < MAXCLIENTCODES; i++) { + if (clientcodes[i].code[0]) { + counter++; + } + } + if (counter >= MAXCLIENTCODES) { + debugprint(DEBUG_CRIT, "addclientcode(): too many client codes.\n"); + return -1; + } + + // Copy it to the current client + for (int j = 0; j < MAXCLIENTS; j++) { + if (clients[j].fd == sourcefd) { + strcpy(clients[j].clientcode, code); + debugprint(DEBUG_FULL, "addclientcode(): set client code for fd '%d' to '%s'.\n", clients[j].fd, clients[j].clientcode); + } + } + + // And add it to the clientcode structure if it doesn't already exist + for (int i = 0; i < MAXCLIENTCODES; i++) { + if (strlen(code) == strlen(clientcodes[i].code) && (strncmp(code, clientcodes[i].code, strlen(code)) == 0)) { + debugprint(DEBUG_FULL, "addclientcode(): client code already existed.\n"); + // It already exists + return 0; + } else { + // It doesn't, add it + strcpy(clientcodes[i].code, code); + return 1; + } + } + + // We shouldn't get here + debugprint(DEBUG_CRIT, "addclientcode(): we shouldn't get here"); + return -1; +} + +// Sets a given client code as last disconnecting at the current time. +// Returns 1 on success or 0 on failure. - TODO have callers do something on failure, or change this to a void. +int setclientcodetime(char *code, struct clientcodes *clientcodes) { + debugprint(DEBUG_FULL, "setclientcodetime(): Setting disconnect time for '%s'.\n", code); + + // Look for this code and set it as the current time if found + for (int i = 0; i < MAXCLIENTCODES; i++) { + if (strlen(code) == strlen(clientcodes[i].code) && (strncmp(code, clientcodes[i].code, strlen(code)) == 0)) { + clientcodes[i].lastdisconnected = time(NULL); + debugprint(DEBUG_FULL, "setclientcodetime(): Set time to '%d'.\n", clientcodes[i].lastdisconnected); + return 1; + } + } + + // If we got here, the code was never found + return 0; +} + +// Return the timestamp that a given client last disconnected, or 0 on failure. +int getclientcodetime(char *code, struct clientcodes *clientcodes) { + debugprint(DEBUG_FULL, "getclientcodetime(): looking for '%s'.\n", code); + // Look for this code and set it as the current time if found + for (int i = 0; i < MAXCLIENTCODES; i++) { +//printf("%s.\n", clientcodes[i].code); + if (strlen(code) == strlen(clientcodes[i].code) && (strncmp(code, clientcodes[i].code, strlen(code)) == 0)) { + debugprint(DEBUG_FULL, "getclientcodetime(): code '%s' found at index '%d', timestamp '%d'.\n", code, i, clientcodes[i].lastdisconnected); + return clientcodes[i].lastdisconnected; + } + } + + // If we got here, the code was never found + return 0; +} diff --git a/functions.h b/functions.h index c91bfdc..29ebff5 100644 --- a/functions.h +++ b/functions.h @@ -103,7 +103,7 @@ int sendtoserver(SSL *server_ssl, char *strsrc, int str_len, int clientfd, struc // it from the array of clients. // Also set its authentication and registration statuses to 0. // Also set the pending statuses to 0 -int disconnectclient(int fd, struct client *clients, struct ircdstate *ircdstate, struct settings *settings); +int disconnectclient(int fd, struct client *clients, struct ircdstate *ircdstate, struct settings *settings, struct clientcodes *clientcodes); int createchannel(struct channel *channels, char *name, char *topic, char *topicwho, char *topicwhen); @@ -166,4 +166,16 @@ int rehash(struct settings *settings, char *failuremsg); // Return 0 for password mismatch, or 1 for password match. int checkpassword(char *password, struct settings *settings); +// Adds a client code to the clientcode structure if it doesn't already exist. +// On success, copy it to the client's clientcode field. +// Returns 1 on adding a new code, 0 if the code already existed, or -1 on error. +int addclientcode(int sourcefd, char *code, struct clientcodes *clientcodes, struct client *clients); + +// Sets a given client code as last disconnecting at the current time. +// Returns 1 on success or 0 on failure. +int setclientcodetime(char *code, struct clientcodes *clientcodes); + +// Return the timestamp that a given client last disconnected, or 0 on failure. +int getclientcodetime(char *code, struct clientcodes *clientcodes); + #endif diff --git a/message.c b/message.c index 3e79605..8eceb4b 100644 --- a/message.c +++ b/message.c @@ -727,7 +727,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 channel *channels, struct settings *settings, char tokens[MAXTOKENS][MAXDATASIZE], int counter, struct clientcodes *clientcodes) { // PASS received? User is trying to log in, check their password. if (strncasecmp(tokens[0], "PASS", strlen(tokens[0])) == 0) { if (checkpassword(tokens[1], settings)) { @@ -751,7 +751,7 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int } } else { debugprint(DEBUG_SOME, "Password rejected, disconnecting fd %d.\n", sourcefd); - disconnectclient(sourcefd, clients, ircdstate, settings); + disconnectclient(sourcefd, clients, ircdstate, settings, clientcodes); // Alert other clients about the failed authentication char alertmsg[MAXDATASIZE]; if (!snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :blabouncer: new client with fd %d has failed to authenticate.", ircdstate->ircnick, sourcefd)) { @@ -828,7 +828,7 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int // Tell the client to go away if we aren't registered with the real server yet as defined by the last greeting not being set yet if (!strlen(ircdstate->greeting004)) { sendtoclient(sourcefd, "Sorry, we aren't registered with a real IRC server yet.", clients, settings, 0); - disconnectclient(sourcefd, clients, ircdstate, settings); + disconnectclient(sourcefd, clients, ircdstate, settings, clientcodes); return 1; } @@ -865,6 +865,8 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int 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); 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); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER QUIT [quit message]\" (To quit blabouncer, optionally sending [quit message] to the server.)", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); @@ -1039,7 +1041,7 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int // A client has QUIT, so disconnect (close) them and don't do anything else for now - TODO: Let another clients know with a NOTICE or something if (strncasecmp(tokens[0], "QUIT", strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Client QUIT found from fd %d and it is: %s with length %zd! Disconnecting that fd.\n", sourcefd, tokens[0], strlen(tokens[0])); - disconnectclient(sourcefd, clients, ircdstate, settings); + disconnectclient(sourcefd, clients, ircdstate, settings, clientcodes); return 1; } @@ -1160,7 +1162,7 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int char outgoingmsg[MAXDATASIZE]; debugprint(DEBUG_FULL, "Client BLABOUNCER found and it is: %s with length %zd!\n", tokens[1], strlen(tokens[1])); // REPLAY received, send the requested length of replay time to the client - if (strncasecmp(tokens[1], "REPLAY", strlen("REPLAY")) == 0) { + if (strncasecmp(tokens[1], "REPLAY", strlen("REPLAY")) == 0 && counter == 3) { debugprint(DEBUG_FULL, "Client BLABOUNCER REPLAY (custom blabouncer command) found and it is: %s with length %zd!\n", tokens[1], strlen(tokens[1])); // Split the request into days:hours:minutes:seconds @@ -1282,6 +1284,51 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int sendtoallclients(clients, outgoingmsg, 0, settings); } + return 1; + // CLIENTCODE received, set the provided string as the client code for handling auto replays for when this client code is next seen + } else if (strncasecmp(tokens[1], "CLIENTCODE", strlen("CLIENTCODE")) == 0 && counter == 3) { + debugprint(DEBUG_FULL, "Client BLABOUNCER CLIENTCODE found and it is: %s %s! Setting as this client's client code.\n", tokens[1], tokens[2]); + + // Make sure replaymode = "perclient" is set + if (strcmp(settings->replaymode, "perclient")) { + debugprint(DEBUG_SOME, "CLIENTCODE requested but replaymode not set to \"perclient\".\n"); + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :CLIENTCODE requested but replaymode not set to \"perclient\".", ircdstate->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + return 1; + } + + // Make sure the client code length is good + if (strlen(tokens[2]) < 1 || strlen(tokens[2]) > CLIENTCODELEN - 1) { + debugprint(DEBUG_SOME, "Invalid CLIENTODE length.\n"); + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Invalid CLIENTODE length. Must be 1 to %d characters.", ircdstate->ircnick, CLIENTCODELEN - 1); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + return 1; + } + + // Register the client code (if it doesn't already exist) + int ret = addclientcode(sourcefd, tokens[2], clientcodes, clients); + if (ret == -1) { + // Something went wrong + debugprint(DEBUG_CRIT, "error: addclientcode() returned 0.\n"); + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Problem registering client code.", ircdstate->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + return 1; + } else if (ret == 0) { + // If it did already exist, do a replay of everything since this client code last disconnected + int codetime = getclientcodetime(tokens[2], clientcodes); + if (!codetime) { + debugprint(DEBUG_CRIT, "Error finding last disconnect time of this client code!\n"); + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Error finding last disconnect time of this client code!", ircdstate->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + return 1; + } + if (!doreplay(sourcefd, time(NULL) - codetime, clients, settings, ircdstate, channels)) { + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstate->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + return 1; + } + } + return 1; // Unrecognised BLABOUNCER command received, send some help instructions } else { @@ -1292,6 +1339,8 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int 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); 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); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER QUIT [quit message]\" (To quit blabouncer, optionally sending [quit message] to the server.)", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; diff --git a/message.h b/message.h index b614541..5d6cfff 100644 --- a/message.h +++ b/message.h @@ -32,6 +32,6 @@ 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 channel *channels, struct settings *settings, char tokens[MAXTOKENS][MAXDATASIZE], int counter, struct clientcodes *clientcodes); #endif diff --git a/structures.h b/structures.h index d1ec4b0..b96b781 100644 --- a/structures.h +++ b/structures.h @@ -27,6 +27,8 @@ #define MAXREALNAMELEN 128 // Randomly picked (TODO - is there an actual maximum real name length?) #define MAXPORTLEN 6 // Up to 65535, so 5 characters + 1 for null #define MAXAUTOCHANLEN 1024 // Randomly picked maximum length of the auto channel list +#define CLIENTCODELEN 17 // Max length of a client code + 1 for null +#define MAXCLIENTCODES 64 // Max number of client codes to track struct ircdstate { char greeting001[MAXDATASIZE]; @@ -95,6 +97,13 @@ struct client { int pendingwhowas; // Whether the client is waiting to hear back from a "WHOWAS" command int pendingnames; // Count of RPL_NAMREPLYs the client is waiting on. int pendingcap; // Whether the client is still negotiating IRCv3 CAPabilities. 0 = no, 1 = yes, -1 = just finished (so register them as if they had just sent USER). + char clientcode[CLIENTCODELEN]; // This client's client code +}; + +// Structure of client codes. Used to track the last time a client identifying as a given client connected to handle auto replay for a known client. +struct clientcodes { + char code[CLIENTCODELEN]; // The client code string + int lastdisconnected; // When this client code last disconnected }; // Structure of a channel - this struct is expected to be in an array of channel structs -- cgit v1.2.3