From 0559bff00a6be2054194632c3543bf62af1fb56f Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Sat, 11 May 2019 19:03:57 +0100 Subject: Add the ability to replay messages from a replay log file. (No replay log file writing yet.) --- Makefile | 6 +- blabouncer.c | 27 ++++++- blabouncer.conf | 3 + config.c | 41 +++++++++- config.h | 2 + replay.c | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ replay.h | 21 ++++++ 7 files changed, 325 insertions(+), 6 deletions(-) create mode 100644 replay.c create mode 100644 replay.h diff --git a/Makefile b/Makefile index 3f8d034..5bf17a3 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ CC=gcc -DEPS = functions.h sockets.h config.h +DEPS = functions.h sockets.h config.h replay.h %.o: %.c $(DEPS) $(CC) -Wall -Wextra -c -o $@ $< -blabouncer: blabouncer.o functions.o sockets.o config.o - $(CC) -Wall -Wextra -o blabouncer blabouncer.o functions.o sockets.o config.o +blabouncer: blabouncer.o functions.o sockets.o config.o replay.o + $(CC) -Wall -Wextra -o blabouncer blabouncer.o functions.o sockets.o config.o replay.o diff --git a/blabouncer.c b/blabouncer.c index ba9dca0..2e1f414 100644 --- a/blabouncer.c +++ b/blabouncer.c @@ -8,7 +8,9 @@ // - Add blabouncer MOTD (375, 372, 376) // - "01:53:47 -!- ServerMode/#test [b] by irc.tghost.co.uk" on existing clients when new client connects // - Keep track of changing user nicks/modes -// - Relay log can just be "log/resend everything that ever hit sendto[all]client[s]()" +// - Relay log can just be "log/resend everything that ever hit sendto[all]client[s]()" (or maybe just PRIVMSGs? + NOTICEs and friends?) +// - Implement TLS +// - Implement password/login // // Example WHOIS reply: // BOUNCER-SERVER RECEIVED: :irc.tghost.co.uk 307 blabounce l_bratch :is identified for this nick @@ -32,6 +34,7 @@ #include "functions.h" #include "sockets.h" #include "config.h" +#include "replay.h" #define SOURCE_SERVER 0 #define SOURCE_CLIENT 1 @@ -645,6 +648,28 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc sendtoclient(sourcefd, outgoingmsg); } + // Send the client however many relay lines have been requested + int relaysecs = confrelayseconds(); + + // Figure out how many lines to relay + int numlines = relaylines(relaysecs); + printf("Replay log lines: '%d'.\n", numlines); + + if (numlines < 0) { + printf("Error getting number of replay lines.\n"); + exit(1); + } + + // Relay those lines! + for (int i = 0; i < numlines; i++) { + if (!readrelayline(relaysecs, i, outgoingmsg)) { + printf("Error requesting relay line.\n"); + exit(1); + } + printf("Sending relay line: '%s'.\n", outgoingmsg); + sendtoclient(sourcefd, outgoingmsg); + } + return 1; } diff --git a/blabouncer.conf b/blabouncer.conf index 4f569dd..5b68918 100644 --- a/blabouncer.conf +++ b/blabouncer.conf @@ -7,3 +7,6 @@ nick = "blabounce" username = "blabounce" realname = "Mr Bla Bouncer" + +# How many seconds of relay log should be sent to connecting clients +relayseconds = "7200" diff --git a/config.c b/config.c index ca6b158..7fec4de 100644 --- a/config.c +++ b/config.c @@ -1,5 +1,9 @@ #include "config.h" +// TODO - Multiple functions here (at least readnames() and relayseconds() have the file opening code, rewrite. + +// TODO - Can isconf() and getconf() just be merged into one function? + // Check to see if a line is the configuration option specified by // checking to see if str starts with conf followed by a space, a tab, // or an equals sign. @@ -69,9 +73,10 @@ int readnames(char *nick, char *username, char *realname) { fp = fopen(filename, "r"); if (fp == NULL) { - printf("error: could not open configuration file '%s.'", filename); - return 1; + printf("error: could not open configuration file '%s'.\n", filename); + exit(1); } + while (fgets(str, MAXCHAR, fp) != NULL) { long int len; if ((len = isconf(str, "nick"))) { @@ -101,3 +106,35 @@ int readnames(char *nick, char *username, char *realname) { fclose(fp); return 1; } + +// Return the relayseconds configuration option +// (How many seconds of relay should be sent to connecting clients) +int confrelayseconds() { + FILE *fp; + char str[MAXCHAR]; + char* filename = "blabouncer.conf"; + + char secondsstr[MAXCHAR]; + int seconds; + + fp = fopen(filename, "r"); + + if (fp == NULL) { + printf("error: could not open configuration file '%s'.\n", filename); + exit(1); + } + + while (fgets(str, MAXCHAR, fp) != NULL) { + long int len; + if ((len = isconf(str, "relayseconds"))) { + getconf(str, len); + strncpy(secondsstr, str, strlen(str)); + secondsstr[strlen(str)] = '\0'; + printf("secondsstr is: '%s', length '%ld'.\n", secondsstr, strlen(secondsstr)); + seconds = strtol(secondsstr, NULL, 10); // Convert resulting string to an integer, base 10 + printf("seconds is: '%d'.\n", seconds); + } + } + + return seconds; +} diff --git a/config.h b/config.h index 5307178..57defb6 100644 --- a/config.h +++ b/config.h @@ -9,4 +9,6 @@ int readnames(char *nick, char *username, char *realname); +int confrelayseconds(); + #endif diff --git a/replay.c b/replay.c new file mode 100644 index 0000000..8c37d4c --- /dev/null +++ b/replay.c @@ -0,0 +1,231 @@ +#include "replay.h" + +// Return the unixtime timestamp at the start of a line, +// or -1 if a timestamp couldn't be extracted +int gettimestamp(char *str) { + int timestamp; + char timestr[TIMELEN]; + int count = 0; + + // Make sure we're starting with a digit + if (!isdigit(str[0])) { + return -1; + } + + // Extract each digit until we encounter a non-digit + for (int i = 0; i < TIMELEN; i++) { + if (isdigit(str[i])) { + timestr[count] = str[i]; + count++; + } + } + + timestr[count] = '\0'; // Null terminate + + timestamp = strtol(timestr, NULL, 10); // Convert resulting string to an integer, base 10 + + return timestamp; +} + +// Set 'str' to a string with the leading unixtime timestamp removed. +// Returns 1 on success, 0 on failure +int striptimestamp(char *str) { + char line[MAXCHAR]; + int count = 0; + + // Make sure we're starting with a digit + if (!isdigit(str[0])) { + return 0; + } + + // Skip over each digit until we encounter a non-digit, record the position + for (int i = 0; i < TIMELEN; i++) { + if (isdigit(str[i])) { + count++; + } + } + + // Skip over the space + count++; + + int count2 = 0; + + // Copy from the end of the digits (and the space) until the end of the string + for (size_t i = count; i < strlen(str); i++) { + line[count2] = str[i]; + count2++; + } + + strncpy(str, line, count2); + str[count2] = '\0'; + + return 1; +} + +// Take a string like: +// 1557592901 :foo!bar@baz PRIVMSG foo :hello world +// And convert it to: +// :foo!bar@baz PRIVMSG foo :[17:41:41] hello world +// Only inserts the formatted time for PRIVMSGs at the moment (and maybe only needs to!). +void formattime(char *str) { + // Extract the timestamp for conversion into [HH:MM:SS] + char timestr[TIMELEN]; + sprintf(timestr, "%d", gettimestamp(str)); // Convert int time to string time + struct tm tm; + strptime(timestr, "%s", &tm); + char timestampf[TIMELEN]; // Formatted timestamp + // Convert into [HH:MM:SS] + strftime(timestampf, TIMELEN, "[%H:%M:%S]", &tm); + printf("Formatted time string: '%s'.\n", timestampf); + + // Strip the original unixtimestamp + striptimestamp(str); + printf("Now got '%s' and '%s'.\n", timestampf, str); + + // Take note of the length + int len = strlen(str); + + // Find the start of the message if it's a PRIVMSG + char *ret; + int pos1; + if ((ret = strstr(str, "PRIVMSG")) != NULL) { + // Position within str of "PRIVMSG" + pos1 = ret - str; + printf("Found 'PRIVMSG' at position '%d' of '%s'.\n", pos1, str); + } else { + // If it's not a PRIVMSG, stop here + return; + } + + char *ret2; + int pos2; + // Find the start of the actual message within the PRIVMSG + if ((ret2 = strstr(ret, ":")) != NULL) { + // Position within ret of ":" + pos2 = ret2 - ret; + printf("Found ':' at position '%d' of '%s'.\n", pos2, ret); + } else { + // Didn't find the real message, weird. + return; + } + + // Position of start of PRIVMSG colon in original string + int realpos = pos1 + pos2 + 1; + + // Build the new formatted string + char newline[MAXCHAR]; + + // First bit (:foo!bar@baz PRIVMSG foo :) + for (int i = 0; i < realpos; i++) { + newline[i] = str[i]; + } + + // Second bit (:foo!bar@baz PRIVMSG foo :[HH:MM:SS]) + int j = 0; + for (int i = realpos; i < TIMELEN + realpos - 1; i++) { // -1 to avoid the null char from timestampf + newline[i] = timestampf[j]; + j++; + } + + // Insert a space after the formatted timestamp + newline[TIMELEN + realpos - 1] = ' '; + + // Final bit (the original PRIVMSG contents) + for (int i = TIMELEN + realpos; i < len + TIMELEN; i++) { + newline[i] = str[i - TIMELEN]; + } + + // Copy the whole thing back to str and null terminate + strncpy(str, newline, len + TIMELEN); + str[len + TIMELEN] = '\0'; + + printf("Ended up with relay string of: '%s'.\n", str); +} + +// Return the number of lines in the replay log since 'seconds' seconds ago, or -1 if there a problem. +int relaylines(int seconds) { + FILE *fp; + char str[MAXCHAR]; + char* filename = "replay.log"; + + int numlines = 0; + + fp = fopen(filename, "r"); + + if (fp == NULL) { + printf("error: could not open replay log '%s'.\n", filename); + exit(1); + } + + // Get the current time for comparison later + int timenow = (int)time(NULL); + + while (fgets(str, MAXCHAR, fp) != NULL) { + // Read the timestamp from each line + int timestamp = gettimestamp(str); + if (timestamp < 1) { + printf("Error reading timestamp from replay log file.\n"); + return -1; + } + + printf("Timestamp is: '%d'.\n", timestamp); + + // If the line is within range of the requested time, count it + if (timestamp > timenow - seconds) { + numlines++; + } + } + + fclose(fp); + return numlines; +} + +// Set 'str' to the line in the log with a timestamp of greater than 'seconds' +// seconds ago, plus however many lines 'linenum' is set to. +// Also modify the line to include a timestamp in the form "[HH:MM:SS]". +// Returns 1 on success, or 0 on failure. +// TODO - This is horribly inefficient since it re-reads the entire file each call, rewrite this! +int readrelayline(int seconds, int linenum, char *str) { + FILE *fp; + char line[MAXCHAR]; + char* filename = "replay.log"; + + int count = 0; + + fp = fopen(filename, "r"); + + if (fp == NULL) { + printf("error: could not open replay log '%s'.\n", filename); + return 1; + } + + // Get the current time for comparison later + int timenow = (int)time(NULL); + + while (fgets(line, MAXCHAR, fp) != NULL) { + printf("Read: '%s'.\n", line); + // Read the timestamp from each line + int timestamp = gettimestamp(line); + if (timestamp < 1) { + printf("Error reading timestamp from replay log file.\n"); + exit(1); + } + + // If the line is within range of the requested time... + 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 + formattime(line); + + strncpy(str, line, strlen(line)); + str[strlen(line)] = '\0'; + return 1; + } + count++; + } + } + + // If we got here something went wrong + return 0; +} diff --git a/replay.h b/replay.h new file mode 100644 index 0000000..e0b5822 --- /dev/null +++ b/replay.h @@ -0,0 +1,21 @@ +#ifndef REPLAY_H_INCLUDED +#define REPLAY_H_INCLUDED + +#define _XOPEN_SOURCE +#include +#include +#include +#include +#include + +#define MAXCHAR 1000 +#define TIMELEN 11 // 32-bit unixtime is up to 10 characters (+1 for null char) // TODO - Make this Year 2038 proof +#define TIMELENF 11 // [HH:MM:SS] = 10 characters + 1 for null char + +int relaylines(int seconds); + +int readrelayline(int seconds, int linenum, char *str); + +int writerelayline(char *str); + +#endif -- cgit v1.2.3