summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Bratch <luke@bratch.co.uk>2019-06-17 01:46:28 +0100
committerLuke Bratch <luke@bratch.co.uk>2019-06-17 01:46:28 +0100
commitc70cd5cccc966a35f175913f2281ce251fd62425 (patch)
treecd95e1bbb28ad4df94db87b9e3080cb05d00645b
parentfa37193c83f82784e826b8477ee7d4a4cd96d7cf (diff)
Implement a per-client identifier so auto replay can replay everything a given client has missed.
-rw-r--r--README51
-rw-r--r--TODO2
-rw-r--r--blabouncer.c43
-rw-r--r--blabouncer.conf.example1
-rw-r--r--config.c1
-rw-r--r--functions.c94
-rw-r--r--functions.h14
-rw-r--r--message.c59
-rw-r--r--message.h2
-rw-r--r--structures.h9
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
@@ -1283,6 +1285,51 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int
}
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 {
debugprint(DEBUG_SOME, "Client BLABOUNCER unrecognised command found and it is: %s with length %zd! Sending a help message.\n", tokens[1], strlen(tokens[1]));
@@ -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