summaryrefslogtreecommitdiff
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
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.
-rw-r--r--TODO6
-rw-r--r--functions.c32
-rw-r--r--functions.h4
-rw-r--r--replay.c94
-rw-r--r--replay.h2
5 files changed, 119 insertions, 19 deletions
diff --git a/TODO b/TODO
index e10a4e9..8d254bd 100644
--- a/TODO
+++ b/TODO
@@ -2,16 +2,14 @@ All the TODOs sprinkled throughout the code!
Configurable rotation of replay and debug logs.
+Automatic rotation of replay log along with debug log?
+
Configurable timestamp format in logs.
macOS compiler may need limits.h included in structures.h.
"Starting log replay...." followed by "Unable to read replay log file!" even though replay seemed to work?
-Sometimes replaymode = "lastspoke" will replay the last message you sent if you spoke last and sometimes it doesn't - change to always include your last message?
-Time based issue due to "seconds ago" rather than specific timestamp? Perhaps extract timestamp and replay from there.
-(Pending real world testing to be marked as fixed.)
-
Can memory usage be reduced further? (e.g. better channel struct management)
Ability to load new certificate whilst running.
diff --git a/functions.c b/functions.c
index 6d85d8e..6f8d131 100644
--- a/functions.c
+++ b/functions.c
@@ -1446,6 +1446,38 @@ int updatenickinallchannels(char *nickuserhost, char *newnick, struct channel *c
return 1;
}
+// Check if "nick" is in any channel or not.
+// Return 1 if it is, or 0 if not.
+int isnickinanychannel(struct channel *channels, int maxchannelcount, char *nick) {
+ debugprint(DEBUG_FULL, "isnickinanychannel(): given '%s'.\n", nick);
+
+ // Make sure the nick has a length of at least one
+ if (strlen(nick) < 1) {
+ debugprint(DEBUG_CRIT, "isnickinanychannel(): nick has no length, returning 0!\n");
+ return 0;
+ }
+
+ // Go through all channels and see if nick is present
+ for (int i = 0; i < maxchannelcount; i++) {
+ // Don't bother checking this channel index if it isn't used
+ if (!channels[i].name[0]) {
+ continue;
+ }
+ // Go through all nicks in channel
+ for (int j = 0; j < MAXCHANNICKS; j++) {
+ // See if the nick is here
+ if (strlen(channels[i].nicks[j]) == strlen(nick) && !strcmp(channels[i].nicks[j], nick)) {
+ // Found it!
+ debugprint(DEBUG_FULL, "isnickinanychannel(): nick '%s' found in channel '%s', returning 1.\n", nick, channels[i].name);
+ return 1;
+ }
+ }
+ }
+
+ debugprint(DEBUG_FULL, "isnickinanychannel(): nick '%s' not found in any channel '%s', returning 0.\n", nick);
+ return 0;
+}
+
// Populate our channels struct with all nicks in a RPL_NAMREPLY
// Returns 1 on success or 0 on failure
int addnamereplytochannel(char *namereply, struct channel *channels, int maxchannelcount) {
diff --git a/functions.h b/functions.h
index 28b6413..5799aa6 100644
--- a/functions.h
+++ b/functions.h
@@ -205,6 +205,10 @@ int removenickfromallchannels(char *nickuserhost, struct channel *channels, int
// Returns 1 on success or 0 on failure
int updatenickinallchannels(char *nickuserhost, char *newnick, struct channel *channels, int maxchannelcount);
+// Check if "nick" is in a channel or not.
+// Return 1 if it is, or 0 if not.
+int isnickinanychannel(struct channel *channels, int maxchannelcount, char *nick);
+
// Populate our channels struct with all nicks in a RPL_NAMREPLY
// Returns 1 on success or 0 on failure
int addnamereplytochannel(char *namereply, struct channel *channels, int maxchannelcount);
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;
}
diff --git a/replay.h b/replay.h
index dcf6771..d2d2c6d 100644
--- a/replay.h
+++ b/replay.h
@@ -42,7 +42,7 @@
// 'basedir' is the directory in which to find 'replay.log'.
int replaylinestime(int seconds, char *basedir);
-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);
// 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'.