summaryrefslogtreecommitdiff
path: root/replay.c
diff options
context:
space:
mode:
authorLuke Bratch <luke@bratch.co.uk>2022-11-25 00:03:17 +0000
committerLuke Bratch <luke@bratch.co.uk>2022-11-25 00:03:17 +0000
commitae1390b2bef4dc6a1a113cddaee37d745b559b1f (patch)
treea5f3894e616ae387c079ae03048461afe0854994 /replay.c
parent2e665e03b6175b3f31f0ef1e058183417df1456e (diff)
Implement fake QUIT handling via a NOTICE when doing a replay.
Since user QUIT messages don't contain channel names, clients that didn't know which channels a nick was in when receiving a quit message for that nick can't show a per-channel QUIT notification. Some clients just display the QUIT message in the server status window, others silently ignore it. Cater for those clients by sending a NOTICE about the QUIT rather than replaying it - assuming the nick isn't still in any of our channels.
Diffstat (limited to 'replay.c')
-rw-r--r--replay.c94
1 files changed, 80 insertions, 14 deletions
diff --git a/replay.c b/replay.c
index c63e859..b90431c 100644
--- a/replay.c
+++ b/replay.c
@@ -45,9 +45,11 @@ int gettimestamp(char *str) {
}
// Set 'str' to a string with the leading unixtime timestamp removed.
+// Record the stripped timestamp in 'origtimestamp'.
// Returns 1 on success, 0 on failure
-int striptimestamp(char *str) {
+int striptimestamp(char *str, int *origtimestamp) {
char line[MAXCHAR];
+ char origtimestr[MAXCHAR];
int count = 0;
// Make sure we're starting with a digit
@@ -58,10 +60,15 @@ int striptimestamp(char *str) {
// Skip over each digit until we encounter a non-digit, record the position
for (int i = 0; i < TIMELEN; i++) {
if (isdigit(str[i])) {
+ // Copy timestamp into 'origtimestr'
+ origtimestr[i] = str[i];
count++;
}
}
+ origtimestr[count] = '\0';
+ *origtimestamp = strtol(origtimestr, NULL, 10);
+
// Skip over the space
count++;
@@ -85,7 +92,8 @@ int striptimestamp(char *str) {
// :foo!bar@baz PRIVMSG foo :[17:41:41] hello world
// Or the same but e.g. [DD/MM/YY HH:MM:SS] if replaydates == 1.
// Only inserts the formatted time for PRIVMSGs at the moment (and maybe only needs to!).
-void formattime(char *str, int replaydates) {
+// Saves the original timestamp in 'origtimestamp' either way.
+void formattime(char *str, int replaydates, int *origtimestamp) {
// Extract the timestamp for conversion into [HH:MM:SS]
char timestr[TIMELEN];
sprintf(timestr, "%d", gettimestamp(str)); // Convert int time to string time
@@ -99,8 +107,8 @@ void formattime(char *str, int replaydates) {
strftime(timestampf, DATETIMELEN, "[%H:%M:%S]", &tm);
}
- // Strip the original unixtimestamp
- striptimestamp(str);
+ // Strip the original unixtimestamp, recording the original timestamp in case it's needed
+ striptimestamp(str, origtimestamp);
// Take note of the length
int len = strlen(str);
@@ -179,10 +187,12 @@ void formattime(char *str, int replaydates) {
// 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
+// 4. QUIT from a nick that is not currently in any channel (in which case
+// 'sourcefd' is used to fake the QUIT using a NOTICE)
// 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) {
+int sanitisereplay (char *str, int sourcefd, int *origtimestamp, struct client *clients, struct settings *settings,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.
@@ -208,6 +218,9 @@ int sanitisereplay (char *str, struct ircdstate *ircdstate, struct channel *chan
strncpy(tokens[i], token, strlen(token) + 1);
}
+ // Done with strcopy
+ free(strcopyPtr);
+
if (strncmp(tokens[1], "TOPIC", strlen("TOPIC")) == 0 ||
strncmp(tokens[1], "JOIN", strlen("JOIN")) == 0 ||
strncmp(tokens[1], "PART", strlen("PART")) == 0) {
@@ -224,7 +237,6 @@ int sanitisereplay (char *str, struct ircdstate *ircdstate, struct channel *chan
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;
}
}
@@ -235,12 +247,63 @@ int sanitisereplay (char *str, struct ircdstate *ircdstate, struct channel *chan
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);
+ // Fake QUIT handling. Since user QUIT messages don't contain channel names, clients that
+ // didn't know which channels a nick was in when receiving a quit message for that nick
+ // can't show a per-channel QUIT notification. Some clients just display the QUIT message
+ // in the server status window, others silently ignore it. Cater for those clients by
+ // sending a NOTICE about the QUIT rather than replaying it - assuming the nick isn't
+ // still in any of our channels.
+ if (strncmp(tokens[1], "QUIT", strlen("QUIT")) == 0) {
+ extractnickfromprefix(tokens[0], 1);
+
+ // Only do any of this if the nick isn't in any channels
+ if (isnickinanychannel(channels, ircdstate->maxchannelcount, tokens[0])) {
+ return 1;
+ }
+
+ debugprint(DEBUG_FULL, "sanitisereplay(): Faking QUIT message as NOTICE for '%s'.\n", tokens[0]);
+
+ time_t rawtime = *origtimestamp;
+ struct tm tm;
+ char timestampf[DATETIMELEN]; // Formatted timestamp
+
+ // Format time, "ddd yyyy-mm-dd hh:mm:ss zzz"
+ tm = *localtime(&rawtime);
+ if (settings->replaydates) {
+ strftime(timestampf, DATETIMELEN, "[%x %H:%M:%S]", &tm);
+ } else {
+ strftime(timestampf, DATETIMELEN, "[%H:%M:%S]", &tm);
+ }
+
+ // Prepare a new temporary string for the original QUIT message
+ char *strcopyquit = strdup(str);
+ // Keep track of initial pointer for free()ing later
+ char *strcopyquitPtr = strcopyquit;
+ extractfinalparameter(strcopyquit);
+ // Remove trailing CRLF (if any)
+ if (strlen(strcopyquit) > 2) {
+ for (int i = 0; i < 2; i++) {
+ if (strcopyquit[strlen(strcopyquit) - 1] == '\n' || strcopyquit[strlen(strcopyquit) - 1] == '\r') {
+ strcopyquit[strlen(strcopyquit) - 1] = '\0';
+ }
+ }
+ }
+
+ char outgoingmsg[MAXDATASIZE];
+ if (!snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :%s %s has quit (%s).", ircdstate->ircnick, timestampf, tokens[0], strcopyquit)) {
+ debugprint(DEBUG_CRIT, "sanitisereplay(): error: couldn't prepare fake QUIT message as NOTICE!\n");
+ } else {
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+ }
+
+ free(strcopyquitPtr);
+ return 1;
+ }
+
return 1;
}
@@ -292,7 +355,7 @@ int replaylinestime(int seconds, char *basedir) {
// if settings.replaydates == 1.
// Returns 1 on success, 0 on failure, or -1 if the line should be ignored.
// TODO - This is horribly inefficient since it re-reads the entire file each call, rewrite this!
-int readreplayline(int seconds, int linenum, char *str, struct settings *settings, struct ircdstate *ircdstate) {
+int readreplayline(int seconds, int linenum, char *str, int *origtimestamp, struct settings *settings, struct ircdstate *ircdstate) {
FILE *fp;
char line[MAXCHAR];
char filename[PATH_MAX];
@@ -339,7 +402,7 @@ int readreplayline(int seconds, int linenum, char *str, struct settings *setting
}
// ...then return it
// Insert our formatted [HH:MM:SS] timestamp into the message
- formattime(line, settings->replaydates);
+ formattime(line, settings->replaydates, origtimestamp);
strncpy(str, line, strlen(line));
str[strlen(line)] = '\0';
@@ -465,7 +528,9 @@ int doreplaytime(int sourcefd, int replayseconds, struct client *clients, struct
// Replay those lines!
for (int i = 0; i < numlines; i++) {
- int ret = readreplayline(replayseconds, i, outgoingmsg, settings, ircdstate);
+ int origtimestamp;
+// origtimestamp[0] = '\0';
+ int ret = readreplayline(replayseconds, i, outgoingmsg, &origtimestamp, settings, ircdstate);
if (ret == 0) {
debugprint(DEBUG_CRIT, "doreplaytime(): Error requesting replay line.\n");
return 0;
@@ -474,7 +539,7 @@ int doreplaytime(int sourcefd, int replayseconds, struct client *clients, struct
continue;
}
- if (sanitisereplay(outgoingmsg, ircdstate, channels) < 1) {
+ if (sanitisereplay(outgoingmsg, sourcefd, &origtimestamp, clients, settings, ircdstate, channels) < 1) {
continue;
}
@@ -539,12 +604,13 @@ int doreplaylastspoke(int sourcefd, long linenumber, struct client *clients, str
}
// Insert our formatted [HH:MM:SS] timestamp into the message
- formattime(line, settings->replaydates);
+ int origtimestamp;
+ formattime(line, settings->replaydates, &origtimestamp);
strncpy(outgoingmsg, line, strlen(line));
outgoingmsg[strlen(line)] = '\0';
- if (sanitisereplay(outgoingmsg, ircdstate, channels) < 1) {
+ if (sanitisereplay(outgoingmsg, sourcefd, &origtimestamp, clients, settings, ircdstate, channels) < 1) {
continue;
}