diff options
Diffstat (limited to 'replay.c')
-rw-r--r-- | replay.c | 233 |
1 files changed, 212 insertions, 21 deletions
@@ -175,9 +175,78 @@ void formattime(char *str, int replaydates) { str[len + strlen(timestampf)] = '\0'; } +// Sanitise a potential replay string 'str' by detecting if replay line is any of: +// 1. TOPIC/JOIN/PART but we're not in the channel any more +// 2. TOPIC/JOIN/PART and it was from us +// 3. NICK from us but not our current nick +// Returns 1 if the replay line should go through as is +// Returns 0 if the replay line should be skipped +// Returns -1 on error +int sanitisereplay (char *str, struct ircdstate *ircdstate, struct channel *channels) { + // Check if the replay line is a TOPIC, a JOIN, or a PART so we don't + // replay those if we are not currently in the channel they are from + // otherwise clients and state go a bit mad. + // Never replay them if they are from us. + + // 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 + char tokens[3][MAXDATASIZE]; + char *token; + + for (int i = 0; i < 3; i++) { + // Try to split + if ((token = strsep(&strcopy, " ")) == NULL) { + debugprint(DEBUG_CRIT, "sanitisereplay(): error splitting string on iteration '%d', returning!\n", i); + free(strcopyPtr); + return -1; + } + // Copy into the token array (strlen + 1 to get the NULL terminator) + strncpy(tokens[i], token, strlen(token) + 1); + } + + if (strncmp(tokens[1], "TOPIC", strlen("TOPIC")) == 0 || + strncmp(tokens[1], "JOIN", strlen("JOIN")) == 0 || + strncmp(tokens[1], "PART", strlen("PART")) == 0) { + // Skip over colon if present in channel name + int offset = 0; + if (tokens[2][0] == ':') { + offset = 1; + } + + // To make sure it's not us + extractnickfromprefix(tokens[0], 1); + + // Check if we're currently in this channel or if the log line is from us + if (!inchannel(channels, ircdstate->maxchannelcount, tokens[2] + offset) || + ((strlen(tokens[0]) == strlen(ircdstate->ircnick)) && (strncmp(tokens[0], ircdstate->ircnick, strlen(tokens[0])) == 0))) { + debugprint(DEBUG_FULL, "sanitisereplay(): Not sending '%s' replay line '%s'.\n", tokens[1], str); + free(strcopyPtr); + return 0; + } + } + + // Separate special check for if a NICK change is from us but it isn't our current nick + if (strncmp(tokens[1], "NICK", strlen("NICK")) == 0) { + extractnickfromprefix(tokens[0], 1); + + if ((strlen(tokens[0]) == strlen(ircdstate->ircnick)) && (strncmp(tokens[0], ircdstate->ircnick, strlen(tokens[0])) == 0)) { + debugprint(DEBUG_FULL, "sanitisereplay(): Not sending '%s' replay line '%s'.\n", tokens[1], str); + free(strcopyPtr); + return 0; + } + } + + free(strcopyPtr); + return 1; +} + // Return the number of lines in the replay log since 'seconds' seconds ago, or -1 if there a problem. // 'basedir' is the directory in which to find 'replay.log'. -int replaylines(int seconds, char *basedir) { +int replaylinestime(int seconds, char *basedir) { FILE *fp; char str[MAXCHAR]; char filename[PATH_MAX]; @@ -287,35 +356,37 @@ int readreplayline(int seconds, int linenum, char *str, struct settings *setting return 0; } -// Returns the number of seconds ago that 'nick' last spoke, or -1 if there is a problem. +// Returns the line number in the replay log file on which '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) { +long lastspokelinenumber(char *nick, char *basedir) { FILE *fp; - char str[MAXCHAR]; + char line[MAXCHAR]; char filename[PATH_MAX]; // Build path - snprintf(filename, PATH_MAX, "%s/replay.log", basedir); - - int lastspoketime = 0; // When 'nick' last spoke + if (!snprintf(filename, PATH_MAX, "%s/replay.log", basedir)) { + debugprint(DEBUG_CRIT, "lastspokelinenumber(): error: couldn't prepare replay path, exiting!\n"); + exit(1); + } - // Get the current time for comparison later - int timenow = (int)time(NULL); + long linenumber = -1; // Line number where 'nick' last spoke fp = fopen(filename, "r"); if (fp == NULL) { - debugprint(DEBUG_FULL, "error: replaylineslastspoke(): could not open replay log '%s'.\n", filename); + debugprint(DEBUG_FULL, "error: lastspokelinenumber(): could not open replay log '%s'.\n", filename); // Assume the file just doesn't exist yet - TODO - Interpret error codes to see what happened. - return 0; + return -1; } - while (fgets(str, MAXCHAR, fp) != NULL) { + long curlinenumber = -1; // Current line number of this file reading loop + while (fgets(line, MAXCHAR, fp) != NULL) { + curlinenumber++; // 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); + char *strcopy = strdup(line); // Keep track of initial pointer for free()ing later char *strcopyPtr = strcopy; @@ -326,7 +397,7 @@ int lastspokesecondsago(char *nick, char *basedir) { 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); + debugprint(DEBUG_CRIT, "lastspokelinenumber(): error splitting string on iteration '%d', returning -1!\n", i); return -1; } // Copy into the token array (strlen + 1 to get the NULL terminator) @@ -337,14 +408,14 @@ int lastspokesecondsago(char *nick, char *basedir) { // 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); + debugprint(DEBUG_CRIT, "lastspokelinenumber(): 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); + debugprint(DEBUG_CRIT, "lastspokelinenumber(): line didn't start with a timestamp, returning -1!\n", counter); } // Is it a PRIVMSG? @@ -356,16 +427,136 @@ int lastspokesecondsago(char *nick, char *basedir) { // Was it said by our 'nick'? Disable extractnickfromprefix() debugging // as it gets very noisy when we call it from here. extractnickfromprefix(tokens[1], 0); - if ((strlen(tokens[1]) == strlen(nick)) && (strncmp(tokens[1], nick, strlen(nick)))) { - // Not our 'nick', continue + if ((strlen(tokens[1]) == strlen(nick)) && (strncmp(tokens[1], nick, strlen(nick)) == 0)) { + // Our 'nick' found, set the line number + linenumber = curlinenumber; + } + } + + debugprint(DEBUG_FULL, "lastspokelinenumber(): last spoke on line '%ld'.\n", linenumber); + + fclose(fp); + return linenumber; +} + +// Send the requested number of seconds worth of replay log lines to the requested client. +// 'sourcefd' is the client to send to, and 'replayseconds' is the number of +// seconds of replay log to replay. +// Returns 1 for success or 0 for failure. +int doreplaytime(int sourcefd, int replayseconds, struct client *clients, struct settings *settings, struct ircdstate *ircdstate, struct channel *channels) { + char outgoingmsg[MAXDATASIZE]; + + // Figure out how many lines to replay + int numlines = replaylinestime(replayseconds, settings->basedir); + debugprint(DEBUG_FULL, "doreplaytime(): Replay log lines: '%d'.\n", numlines); + + if (numlines < 0) { + debugprint(DEBUG_CRIT, "doreplaytime(): Error getting number of replay lines.\n"); + return 0; + } else if (numlines == 0) { + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :0 replay log lines found in the time requested, nothing to send.", ircdstate->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + return 1; + } + + // Announce the start + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Starting log replay....", ircdstate->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + + // Replay those lines! + for (int i = 0; i < numlines; i++) { + int ret = readreplayline(replayseconds, i, outgoingmsg, settings, ircdstate); + if (ret == 0) { + debugprint(DEBUG_CRIT, "doreplaytime(): Error requesting replay line.\n"); + return 0; + } else if (ret == -1) { + debugprint(DEBUG_FULL, "doreplaytime(): readreplayline() said to ignore replay line.\n"); continue; } - lastspoketime = timestamp; + if (sanitisereplay(outgoingmsg, ircdstate, channels) < 1) { + continue; + } + + debugprint(DEBUG_FULL, "doreplaytime(): Sending replay line: '%s'.\n", outgoingmsg); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } - fclose(fp); - return timenow - lastspoketime; + // Announce the end + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Log replay complete.", ircdstate->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + + return 1; +} + +// Send replay log lines from line number 'linenumber' onwards, to client 'sourcefd'. +// Returns 1 for success or 0 for failure. +int doreplaylastspoke(int sourcefd, long linenumber, struct client *clients, struct settings *settings, struct ircdstate *ircdstate, struct channel *channels) { + debugprint(DEBUG_FULL, "doreplaylastspoke(): replaying from linenumber '%ld' to sourcefd '%d'.\n", linenumber, sourcefd); + + FILE *fp; + char line[MAXCHAR]; + char filename[PATH_MAX]; + char outgoingmsg[MAXDATASIZE]; + + // Build path + if (!snprintf(filename, PATH_MAX, "%s/replay.log", settings->basedir)) { + debugprint(DEBUG_CRIT, "doreplaylastspoke(): error: couldn't prepare replay path, exiting!\n"); + exit(1); + } + + fp = fopen(filename, "r"); + + if (fp == NULL) { + debugprint(DEBUG_FULL, "error: doreplaylastspoke(): could not open replay log '%s'.\n", filename); + // Assume the file just doesn't exist yet - TODO - Interpret error codes to see what happened. + return 0; + } + + // Announce the start + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Starting log replay....", ircdstate->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + + long curlinenumber = -1; // Current line number of this file reading loop + while (fgets(line, MAXCHAR, fp) != NULL) { + curlinenumber++; + // Skip through the file until line number 'linenumber'... + if (curlinenumber < linenumber) { + continue; + } + // ...carry on once we've reached 'linenumber' + + // Read the timestamp from the line + int timestamp = gettimestamp(line); + + // Make sure and it wasn't before blabouncer launched... + if (timestamp < ircdstate->launchtime) { + // Don't replay if this replay line happened before blabouncer launched, + // to avoid weird synchronisation issues with uncertain events from before + // we launched. + debugprint(DEBUG_FULL, "doreplaylastspoke(): Ignoring line '%s' from before we launched.\n", line); + continue; + } + + // Insert our formatted [HH:MM:SS] timestamp into the message + formattime(line, settings->replaydates); + + strncpy(outgoingmsg, line, strlen(line)); + outgoingmsg[strlen(line)] = '\0'; + + if (sanitisereplay(outgoingmsg, ircdstate, channels) < 1) { + continue; + } + + debugprint(DEBUG_FULL, "doreplaylastspoke(): Sending replay line: '%s'.\n", outgoingmsg); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + } + + // Announce the end + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Log replay complete.", ircdstate->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + + return 1; } // Write the line 'str' to the replay log file after prepending it with |