From 511e258e901e5248e1706609ba1099507fd750ae Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Tue, 11 Jun 2019 22:54:50 +0100 Subject: Implement configurable auto replay modes. So far there is "none", "time" (auto replay the last X seconds), and "lastspoke" (auto replay everything since you last spoke). --- Makefile | 4 +-- TODO | 2 +- blabouncer.c | 11 ++++++ blabouncer.conf.example | 8 ++++- config.c | 8 ++++- functions.c | 44 ++++++++++++++++++++++-- functions.h | 9 +++-- message.c | 4 +-- replay.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++--- replay.h | 5 +++ structures.h | 1 + 11 files changed, 170 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index caa293d..28c469f 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ CC=gcc DEPS = functions.h sockets.h config.h replay.h logging.h structures.h message.h %.o: %.c $(DEPS) - $(CC) -Wall -Wextra -c -o $@ $< + $(CC) -D_DEFAULT_SOURCE -Wall -Wextra -c -o $@ $< blabouncer: blabouncer.o functions.o sockets.o config.o replay.o logging.o message.o - $(CC) -Wall -Wextra -lssl -lcrypto -o blabouncer blabouncer.o functions.o sockets.o config.o replay.o logging.o message.o + $(CC) -D_DEFAULT_SOURCE -Wall -Wextra -lssl -lcrypto -o blabouncer blabouncer.o functions.o sockets.o config.o replay.o logging.o message.o diff --git a/TODO b/TODO index 618b2b9..b49d7bd 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,7 @@ Add various auto replay options: - All logs since the final client disconnected - All logs since the most recent client connect/disconnect - - All logs since *you* last sent a message + - All logs since *you* last sent a message (already implemented) - All logs since X seconds ago (already implemented) - 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 469eb68..0db2dca 100644 --- a/blabouncer.c +++ b/blabouncer.c @@ -789,6 +789,17 @@ int main(int argc, char *argv[]) { // Populate settings from configuration file + // What is the auto replay mode? + if (!getconfstr("replaymode", settings.conffile, settings.replaymode)) { + printf("main(): error getting 'replaymode' from configuration file.\n"); + exit(1); + } else { + if (strcmp(settings.replaymode, "none") && strcmp(settings.replaymode, "time") && strcmp(settings.replaymode, "lastspoke")) { + printf("main(): replaymode in configuration file must be one of \"none\", \"time\", or \"lastspoke\".\n"); + exit(1); + } + } + // How many seconds of replay log should automatically be replayed - TODO - Can we do error checking on this? settings.replayseconds = getconfint("replayseconds", settings.conffile); if (errno == ECONFINT) { diff --git a/blabouncer.conf.example b/blabouncer.conf.example index f4de92e..f9c09bc 100644 --- a/blabouncer.conf.example +++ b/blabouncer.conf.example @@ -18,7 +18,13 @@ realname = "Mr Bla Bouncer" # Put channel keywords/passwords after channel names following a space. #channels = "#blabouncer keyword,#test" -# How many seconds of replay log should be sent to connecting clients +# Auto replay mode upon a bouncer client connecting +# "none" = Don't auto replay +# "time" = Always send the last "replayseconds" worth of logs +# "lastspoke" = All messages since your current nick last spoke +replaymode = "time" + +# How many seconds of replay log should be sent to connecting clients if replaymode = "time" replayseconds = "600" # Connect password clients must provided to connect diff --git a/config.c b/config.c index dd99f2a..94831d1 100644 --- a/config.c +++ b/config.c @@ -202,7 +202,13 @@ int createconfigfile(char *filename) { "# Put channel keywords/passwords after channel names following a space.\n" "#channels = \"#blabouncer keyword,#test\"\n" "\n" - "# How many seconds of replay log should be sent to connecting clients\n" + "# Auto replay mode upon a bouncer client connecting\n" + "# \"none\" = Don't auto replay\n" + "# \"time\" = Always send the last \"replayseconds\" worth of logs\n" + "# \"lastspoke\" = All messages since your current nick last spoke\n" + "replaymode = \"time\"\n" + "\n" + "# How many seconds of replay log should be sent to connecting clients if replaymode = \"time\"\n" "replayseconds = \"600\"\n" "\n" "# Connect password clients must provided to connect\n" diff --git a/functions.c b/functions.c index 93fd52b..31dce90 100644 --- a/functions.c +++ b/functions.c @@ -734,9 +734,9 @@ int channelindex(struct channel *channels, char *name) { return -1; } -// Send the requested number of lines of replay log to the requested client +// Send the requested number of lines of replay log to the requested client. // 'sourcefd' is the client to send to, and replayseconds is the number of -// seconds of replay to replay. +// seconds of replay log to replay. // Returns 1 for success or 0 for failure. int doreplay(int sourcefd, int replayseconds, struct client *clients, struct settings *settings, struct ircdstate *ircdstate, struct channel *channels) { char outgoingmsg[MAXDATASIZE]; @@ -822,6 +822,46 @@ int doreplay(int sourcefd, int replayseconds, struct client *clients, struct set return 1; } +// Send the auto replay to the requested client, where 'sourcefd' is the client +// to send to. The type of replay will depend on the user's settings. +// Returns 1 for success or 0 for failure. +int doautoreplay(int sourcefd, struct client *clients, struct settings *settings, struct ircdstate *ircdstate, struct channel *channels) { + // If replaymode = "none" then don't send anything + if (!strncmp(settings->replaymode, "none", strlen("none"))) { + debugprint(DEBUG_FULL, "doautoreplay() requested but replaymode = \"none\".\n"); + return 1; + } + + // If replaymode = "time" then send whatever replayseconds is set to + if (!strncmp(settings->replaymode, "time", strlen("time"))) { + debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"time\", calling doreplay() with '%d' seconds.\n", settings->replayseconds); + if (!doreplay(sourcefd, settings->replayseconds, clients, settings, ircdstate, channels)) { + debugprint(DEBUG_SOME, "doautoreplay(): doreplay() returned 0, returning 0 to caller...\n"); + return 0; + } + return 1; + } + + // If replaymode = "lastspoke" then send whatever replayseconds is set to + if (!strncmp(settings->replaymode, "lastspoke", strlen("lastspoke"))) { + debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"lastspoke\".\n", settings->replayseconds); + int secondsago = lastspokesecondsago(ircdstate->ircnick, settings->basedir); + if (secondsago < 1) { + debugprint(DEBUG_SOME, "doautoreplay(): lastspokesecondsago() returned < 1, returning 0 to caller...\n"); + return 0; + } + debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"lastspoke\", sending lines from '%d' seconds ago to sourcefd '%d'.\n", secondsago, sourcefd); + if (!doreplay(sourcefd, secondsago, clients, settings, ircdstate, channels)) { + debugprint(DEBUG_SOME, "doautoreplay(): doreplay() returned 0, returning 0 to caller...\n"); + return 0; + } + return 1; + } + + // We shouldn't get here + return 0; +} + // Return a count of the number of connected clients int numclients(struct client *clients) { int count = 0; diff --git a/functions.h b/functions.h index 7638b61..c0e90f8 100644 --- a/functions.h +++ b/functions.h @@ -127,12 +127,17 @@ int inchannel(struct channel *channels, char *name); // Returns -1 if there was an error. int channelindex(struct channel *channels, char *name); -// Send the requested number of lines of replay log to the requested client +// Send the requested number of lines of replay log to the requested client. // 'sourcefd' is the client to send to, and replayseconds is the number of -// seconds of replay to replay. +// seconds of replay log to replay. // Returns 1 for success or 0 for failure. int doreplay(int sourcefd, int replayseconds, struct client *clients, struct settings *settings, struct ircdstate *ircdstate, struct channel *channels); +// Send the auto replay to the requested client, where 'sourcefd' is the client +// to send to. The type of replay will depend on the user's settings. +// Returns 1 for success or 0 for failure. +int doautoreplay(int sourcefd, struct client *clients, struct settings *settings, struct ircdstate *ircdstate, struct channel *channels); + // Return a count of the number of connected clients int numclients(struct client *clients); diff --git a/message.c b/message.c index e8879b2..97d15c8 100644 --- a/message.c +++ b/message.c @@ -126,7 +126,7 @@ int processservermessage(SSL *server_ssl, char *str, struct client *clients, int for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].fd) { char alertmsg[MAXDATASIZE]; - if (!doreplay(clients[i].fd, settings->replayseconds, clients, settings, ircdstate, channels)) { + if (!doautoreplay(clients[i].fd, clients, settings, ircdstate, channels)) { snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstate->ircnick); sendtoclient(sourcefd, alertmsg, clients, settings, 0); } @@ -947,7 +947,7 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int clients[arrindex(clients, sourcefd)].registered = 1; // Catch the client up with the default number of seconds of replay - if (!doreplay(sourcefd, settings->replayseconds, clients, settings, ircdstate, channels)) { + if (!doautoreplay(sourcefd, clients, settings, ircdstate, channels)) { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } diff --git a/replay.c b/replay.c index 4850a22..3a40e16 100644 --- a/replay.c +++ b/replay.c @@ -188,7 +188,7 @@ int replaylines(int seconds, char *basedir) { } // If the line is within range of the requested time, count it - if (timestamp > timenow - seconds) { + if (timestamp >= timenow - seconds) { numlines++; } } @@ -216,7 +216,7 @@ int readreplayline(int seconds, int linenum, char *str, char *basedir) { fp = fopen(filename, "r"); if (fp == NULL) { - debugprint(DEBUG_CRIT, "error: could not open replay log '%s'.\n", filename); + debugprint(DEBUG_CRIT, "error: readreplayline(): could not open replay log '%s'.\n", filename); fclose(fp); return 0; } @@ -228,13 +228,13 @@ int readreplayline(int seconds, int linenum, char *str, char *basedir) { // Read the timestamp from each line int timestamp = gettimestamp(line); if (timestamp < 1) { - debugprint(DEBUG_CRIT, "Error reading timestamp from replay log file.\n"); + debugprint(DEBUG_CRIT, "readreplayline(): Error reading timestamp from replay log file.\n"); fclose(fp); return 0; } // If the line is within range of the requested time... - if (timestamp > timenow - seconds) { + if (timestamp >= timenow - seconds) { // ...and it is the current requested line then return it if (count == linenum) { // Insert our formatted [HH:MM:SS] timestamp into the message @@ -254,6 +254,87 @@ int readreplayline(int seconds, int linenum, char *str, char *basedir) { return 0; } +// Returns the number of seconds ago that 'nick' last spoke, or -1 if there is a problem. +// 'basedir' is the directory in which to find 'replay.log'. +int lastspokesecondsago(char *nick, char *basedir) { + FILE *fp; + char str[MAXCHAR]; + char filename[PATH_MAX]; + + // Build path + snprintf(filename, PATH_MAX, "%s/replay.log", basedir); + + int lastspoketime = 0; // When 'nick' last spoke + + // Get the current time for comparison later + int timenow = (int)time(NULL); + + fp = fopen(filename, "r"); + + if (fp == NULL) { + printf("error: replaylineslastspoke(): could not open replay log '%s'.\n", filename); + // Assume the file just doesn't exist yet - TODO - Interpret error codes to see what happened. + fclose(fp); + return 0; + } + + while (fgets(str, MAXCHAR, fp) != NULL) { + // Split the line up to determine if it was a PRIVMSG sent by the requested 'nick' + // TODO - This may also be terribly inefficient + + // Copy to a temporary string + char *strcopy = strdup(str); + // Keep track of initial pointer for free()ing later + char *strcopyPtr = strcopy; + + // Build array of each space-separated token, only need three ( :!@ PRIVMSG) + char tokens[3][MAXDATASIZE + TIMELEN]; // Make IRC message length + our unix timestamp + char *token; + int counter = 0; + for (int i = 0; i < 3; i++) { + // Try to split + if ((token = strsep(&strcopy, " ")) == NULL) { + debugprint(DEBUG_CRIT, "replaylineslastspoke(): error splitting string on iteration '%d', returning -1!\n", i); + return -1; + } + // Copy into the token array (strlen + 1 to get the NULL terminator) + strncpy(tokens[i], token, strlen(token) + 1); + counter++; + } + free(strcopyPtr); + + // Make sure there were at least three tokens + if (counter < 3) { + debugprint(DEBUG_CRIT, "replaylineslastspoke(): not enough tokens on line, only '%d', returning -1!\n", counter); + return -1; + } + + // Make sure it started with a valid timestamp + int timestamp = gettimestamp(tokens[0]); + if (timestamp < 0) { + debugprint(DEBUG_CRIT, "replaylineslastspoke(): line didn't start with a timestamp, returning -1!\n", counter); + } + + // Is it a PRIVMSG? + if (strncmp(tokens[2], "PRIVMSG", strlen("PRIVMSG"))) { + // Not a PRIVMSG, continue + continue; + } + + // Was it said by our 'nick'? + extractnickfromprefix(tokens[1]); + if (strncmp(tokens[1], nick, strlen(nick))) { + // Not our 'nick', continue + continue; + } + + lastspoketime = timestamp; + } + + fclose(fp); + return timenow - lastspoketime; +} + // Write the line 'str' to the replay log file after prepending it with // the current unixtime timestamp. 'basedir' is the directory in which // to write to 'replay.log'. diff --git a/replay.h b/replay.h index ee3f483..5ae9994 100644 --- a/replay.h +++ b/replay.h @@ -19,6 +19,7 @@ #define REPLAY_H_INCLUDED #define _XOPEN_SOURCE +#define _XOPEN_SOURCE_EXTENDED #include #include #include @@ -40,6 +41,10 @@ int replaylines(int seconds, char *basedir); int readreplayline(int seconds, int linenum, char *str, char *basedir); +// Returns the number of seconds ago that 'nick' last spoke, or -1 if there is a problem. +// 'basedir' is the directory in which to find 'replay.log'. +int lastspokesecondsago(char *nick, char *basedir); + int writereplayline(char *str, char *basedir); #endif diff --git a/structures.h b/structures.h index ac68ba9..9246fa4 100644 --- a/structures.h +++ b/structures.h @@ -52,6 +52,7 @@ struct ircdstate { // Structure of settings either to be read from the configuration file or set/changed at runtime struct settings { + char replaymode[MAXDATASIZE]; int replayseconds; char clientport[MAXPORTLEN]; char ircnick[MAXNICKLENGTH]; // In both settings and ircdstate as settings is from our file whereas server may change ircdstate copy -- cgit v1.2.3