From 4c9ef597e5503fa571276fd16739410cd9847f91 Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Wed, 8 May 2019 21:02:26 +0100 Subject: Make TOPIC tracking/following/setting/etc. work for most/all scenarios and ensure it's always given out to new clients correctly. Also misc other bug fixes. --- blabouncer.c | 196 +++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 157 insertions(+), 39 deletions(-) (limited to 'blabouncer.c') diff --git a/blabouncer.c b/blabouncer.c index d3ba587..4127b6a 100644 --- a/blabouncer.c +++ b/blabouncer.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "functions.h" #include "sockets.h" @@ -52,7 +53,9 @@ struct channel { char name[MAXCHANLENGTH]; char topic[MAXDATASIZE]; // TODO - Is there a particular maximum topic length? char topicwho[MAXNICKLENGTH]; - char topicwhen[11]; // 32-bit unixtime is up to 10 characters (+1 for null char) // TODO - Make this Year 2038 proof + // TODO - Make this Year 2038 proof + // TODO - Make this an int? It's just going to arrive and leave as a string every time anyway... + char topicwhen[11]; // 32-bit unixtime is up to 10 characters (+1 for null char) We use "0" to mean "not set". char modes[MAXDATASIZE]; // TODO - Is there a particular maximum modes length? char nicks[MAXCHANUSERS][MAXNICKLENGTH]; // TODO - Need to modify this as people leave/join, not just when we first join char namestype[2]; // Single character (@/*/=) (+1 for null char) // TODO - Is this a sensible name? @@ -152,38 +155,38 @@ int sendtoserver(int *serversockfd, char *str, int str_len) { return 0; } -int removechannel(struct channel *channels, char *name) { - printf("removechannel(): given \"%s\".\n", name); +int createchannel(struct channel *channels, char *name, char *topic, char *topicwho, char *topicwhen, char *modes, char *namestype) { + printf("createchannel(): given \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", and \"%s\".\n", name, topic, topicwho, topicwhen, modes, namestype); - // Find the channel in the channel array... + // Make sure the channel doesn't already exist for (int i = 0; i < MAXCHANNELS; i++) { if (strncmp(channels[i].name, name, strlen(name)) == 0) { - // ..and NULL its name (0th character = '\0') - channels[i].name[0] = '\0'; - printf("removechannel(): channel removed.\n"); - return 1; + perror("error: createchannel(): channel name already exists.\n"); + return 0; + break; } } - perror("error: removechannel() didn't remove a channel\n"); - - // TODO - Make a failed return do something to callers - return 0; -} - -int createchannel(struct channel *channels, char *name, char *topic, char *topicwho, char *topicwhen, char *modes, char *namestype) { - printf("createchannel(): given \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", and \"%s\".\n", name, topic, topicwho, topicwhen, modes, namestype); - // Find a free slot in the array (when the channel name is not set (0th character is '\0'))... for (int i = 0; i < MAXCHANNELS; i++) { if (!channels[i].name[0]) { // ...and set the name and topic strncpy(channels[i].name, name, strlen(name)); + channels[i].name[strlen(name)] = '\0'; + printf("createchannel(): name given was '%s', length '%ld'.\n", name, strlen(name)); + printf("createchannel(): name set to '%s', length '%ld'.\n", channels[i].name, strlen(channels[i].name)); strncpy(channels[i].topic, topic, strlen(topic)); + channels[i].topic[strlen(topic)] = '\0'; + printf("createchannel(): topic given was '%s', length '%ld'.\n", topic, strlen(topic)); + printf("createchannel(): topic set to '%s', length '%ld'.\n", channels[i].topic, strlen(channels[i].topic)); strncpy(channels[i].topicwho, topicwho, strlen(topicwho)); + channels[i].topicwho[strlen(topicwho)] = '\0'; strncpy(channels[i].topicwhen, topicwhen, strlen(topicwhen)); + channels[i].topicwhen[strlen(topicwhen)] = '\0'; strncpy(channels[i].modes, modes, strlen(modes)); + channels[i].modes[strlen(modes)] = '\0'; strncpy(channels[i].namestype, topic, strlen(namestype)); + channels[i].namestype[strlen(namestype)] = '\0'; return 1; break; // TODO - This should be safe to remove since return is hit first } @@ -205,6 +208,7 @@ int addusertochannel(struct channel *channels, char *channelname, char *nick) { if (!channels[i].nicks[j][0]) { // ...and adding user strncpy(channels[i].nicks[j], nick, strlen(nick)); + channels[i].nicks[j][strlen(nick)] = '\0'; return 1; } } @@ -219,10 +223,15 @@ int addusertochannel(struct channel *channels, char *channelname, char *nick) { int setchanneltopicwhotime(struct channel *channels, char *channelname, char *who, char *when) { printf("setchanneltopicwhotime(): given \"%s\", \"%s\", and \"%s\".\n", channelname, who, when); + printf("setchanneltopicwhotime(): who: '%s' with length '%ld'.\n", who, strlen(who)); + printf("setchanneltopicwhotime(): when: '%s' with length '%ld'.\n", when, strlen(when)); + for (int i = 0; i < MAXCHANNELS; i++) { if (strncmp(channels[i].name, channelname, strlen(channelname)) == 0) { strncpy(channels[i].topicwho, who, strlen(who)); + channels[i].topicwho[strlen(who)] = '\0'; strncpy(channels[i].topicwhen, when, strlen(when)); + channels[i].topicwhen[strlen(when)] = '\0'; return 1; } } @@ -237,6 +246,7 @@ int setchanneltopic(struct channel *channels, char *channelname, char *topic) { for (int i = 0; i < MAXCHANNELS; i++) { if (strncmp(channels[i].name, channelname, strlen(channelname)) == 0) { strncpy(channels[i].topic, topic, strlen(topic)); + channels[i].topic[strlen(topic)] = '\0'; return 1; } } @@ -251,6 +261,7 @@ int setchannelnamestype(struct channel *channels, char *channelname, char *type) for (int i = 0; i < MAXCHANNELS; i++) { if (strncmp(channels[i].name, channelname, strlen(channelname)) == 0) { strncpy(channels[i].namestype, type, strlen(type)); + channels[i].namestype[strlen(type)] = '\0'; return 1; } } @@ -288,10 +299,32 @@ int getchannelnamescount(struct channel *channels, char *channelname) { } break; } - } + } + printf("getchannelnamescount(): counted %d names for channel '%s'.\n", count, channelname); return count; - printf("getchannelnamescount(): counted %d channels for channel '%s'.\n", count, channelname); +} + +int removechannel(struct channel *channels, char *name) { + printf("removechannel(): given \"%s\".\n", name); + + // Clear its topic setter and timestamp... + setchanneltopicwhotime(channels, name, "", "0"); + + // Find the channel in the channel array... + for (int i = 0; i < MAXCHANNELS; i++) { + if (strncmp(channels[i].name, name, strlen(name)) == 0) { + // ..and NULL its name (0th character = '\0') + channels[i].name[0] = '\0'; + printf("removechannel(): channel removed and topicwhen set to '%s'.\n", channels[i].topicwhen); + return 1; + } + } + + perror("error: removechannel() didn't remove a channel\n"); + + // TODO - Make a failed return do something to callers + return 0; } // Figure out what to do with each CRLF-split IRC message (if anything) @@ -390,16 +423,27 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc return 1; } - // Server JOIN received? Add to our local channel list. + // Server JOIN received? Add to our local channel array if it's us. if (strncmp(tokens[1], "JOIN", strlen(tokens[1])) == 0) { - printf("Server JOIN found and it is: %s with length %zd! Next token is '%s'. Adding to local channel list.\n", tokens[0], strlen(tokens[0]), tokens[2]); + printf("Server JOIN found and it is: %s with length %zd! Next token is '%s'. Adding to local channel list if it's us.\n", tokens[0], strlen(tokens[0]), tokens[2]); // Next token should be the channel name but it probably needs the leading ':' stripping printf("processircmessage(): Channel name was '%s'\n", tokens[2]); stripprefix(tokens[2]); printf("processircmessage(): Channel name now '%s'\n", tokens[2]); - createchannel(channels, tokens[2], "TOPIC", "TOPICWHO", "0", "CHANNELMODES", "="); // TODO - Saner way to initialise this since we don't have the variables yet? - // - Defaulting to type '=' which is "public" since I don't know what else to guess. + // If the user JOINing is us, then we must have joined a channel, so add to our local channel array. + // (If it's not us, then it's another user JOINing a channel, so just pass straight through to letting all our clients know.) + // Copy to a temporary string so we still have the original in case we need it + char *prefixcopy = strdup(tokens[0]); + stripprefix(prefixcopy); + if (strncmp(prefixcopy, ircdstrings->nickuserhost, strlen(tokens[0])) == 0) { + printf("Server JOIN: nickuserhost is ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->nickuserhost); + // TODO - Saner way to initialise this since we don't have the variables yet? + // TODO - Defaulting to type '=' which is "public" since I don't know what else to guess. + createchannel(channels, tokens[2], "TOPIC", "TOPICWHO", "0", "CHANNELMODES", "="); + } else { + printf("Server JOIN: nickuserhost is NOT ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->nickuserhost); + } // And then send to all clients sendtoallclients(clientsockfd, fdmax, arr_clients, str, sourcefd); @@ -417,8 +461,14 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc } // Channel topics/names/nicks/etc. + // Server 331 (RPL_NOTOPIC) the topic is blank which we track by having a set timestamp of 0 + if (strncmp(tokens[1], "331", strlen(tokens[1])) == 0) { + // Might as well blank our current topic value + setchanneltopic(channels, tokens[3], ""); + // Set the topic timestamp to 0 which we use to determine an "unset" topic when new clients connect + setchanneltopicwhotime(channels, tokens[3], "", "0"); // Server 332 (RPL_TOPIC) set the channel topic - if (strncmp(tokens[1], "332", strlen(tokens[1])) == 0) { + } else if (strncmp(tokens[1], "332", strlen(tokens[1])) == 0) { printf("Server 332 (RPL_TOPIC) found, extracting topic and storing in channel struct.\n"); // Need to extract the final parameter as topics can have spaces // Copy to a temporary string so we still have the original in case we need it @@ -444,6 +494,39 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc } } + // Server TOPIC received? Update our local channel topic info then relay to clients + if (strncmp(tokens[1], "TOPIC", strlen(tokens[1])) == 0) { + printf("Server TOPIC found and it is: %s with length %zd! Next token is '%s'. Updating our local channel topic info.\n", tokens[0], strlen(tokens[0]), tokens[2]); + + // Set the topic itself + + // Need to extract the final parameter as topics can have spaces + // Copy to a temporary string so we still have the original in case we need it + char *topiccopy = strdup(str); + extractfinalparameter(topiccopy); + setchanneltopic(channels, tokens[2], topiccopy); + + // Extract the author and get the current timestamp + + // Extract the topic setter from the prefix (Prefix of ":foo!bar@baz" means "foo" set the topic.) + // Copy to a temporary string so we still have the original in case we need it + char *prefixcopy = strdup(tokens[0]); + extractnickfromprefix(prefixcopy); + + // Get the current time and manipulate it into a C string + time_t timenow = time(NULL); + int timenowlen = snprintf(NULL, 0, "%ld", timenow); + char timenowstr[timenowlen + 1]; // TODO - Make this Year 2038 proof. + snprintf(timenowstr, timenowlen + 1, "%ld", timenow); + + // Actually set the author and timestamp + setchanneltopicwhotime(channels, tokens[2], prefixcopy, timenowstr); + + // And then finally relay to all clients + sendtoallclients(clientsockfd, fdmax, arr_clients, str, sourcefd); + return 1; + } + // Don't return if we got here because this means we didn't process something above } @@ -467,8 +550,20 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting004); sendtoclient(sourcefd, outgoingmsg); + // Get the channel count so we can enumerate over all channels. + // Storing separately so we can skip over blank channels. + int channelcount = getchannelcount(channels); + // Get client to join channels, and tell client about those channels - for (int i = 0; i < getchannelcount(channels); i++) { + for (int i = 0; i < channelcount; i++) { + printf("JOINing channel[%d] out of %d.\n", i, channelcount); + // Skip this one and increment channelcount if it's a blank channel + if (!channels[i].name[0]) { + printf("Skipping channel[%d], incrementing channelcount.\n", i); + channelcount++; + continue; + } + // Get client to join channels if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s JOIN :%s", ircdstrings->nickuserhost, channels[i].name)) { fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses!\n"); @@ -476,12 +571,34 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc } sendtoclient(sourcefd, outgoingmsg); - // Send topic to client - TODO: Handle not having a topic set (331) - if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 332 %s %s :%s", ircdstrings->ircdname, IRCNICK, channels[i].name, channels[i].topic)) { - fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses!\n"); - exit(1); + // Send topic (or lack thereof) to client + // If there isn't one set (we guess this if topic timestamp is 0), send 331 RPL_NOTOPIC + if (strncmp(channels[i].topicwhen, "0", 1) == 0) { + // Prepare the no topic message... + if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 331 %s %s :No topic is set.", ircdstrings->ircdname, IRCNICK, channels[i].name)) { + fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses, 331 RPL_NOTOPIC!\n"); + exit(1); + } + // ..and send it to the client + sendtoclient(sourcefd, outgoingmsg); + // If there is one set, send 332 RPL_TOPIC and 333 RPL_TOPICWHOTIME + } else { + // Prepare the topic message... + if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 332 %s %s :%s", ircdstrings->ircdname, IRCNICK, channels[i].name, channels[i].topic)) { + fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses, 332 RPL_TOPIC!\n"); + exit(1); + } + // ..and send it to the client + sendtoclient(sourcefd, outgoingmsg); + + // Next prepare the topic who/when message... + if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 333 %s %s %s %s", ircdstrings->ircdname, IRCNICK, channels[i].name, channels[i].topicwho, channels[i].topicwhen)) { + fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses, 333 RPL_TOPICWHOTIME!\n"); + exit(1); + } + // ..and send it to the client + sendtoclient(sourcefd, outgoingmsg); } - sendtoclient(sourcefd, outgoingmsg); // Send list of names // Store count of names so we can increment it in the loop if we encounter gaps in the names array @@ -581,6 +698,13 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc return 1; } + // Just send TOPIC to server and let it talk back to clients as required + if (strncmp(tokens[0], "TOPIC", strlen(tokens[0])) == 0) { + printf("Client TOPIC found and it is: %s with length %zd! Sending to server...\n", tokens[0], strlen(tokens[0])); + sendtoserver(serversockfd, str, strlen(str)); + return 1; + } + break; default: fprintf(stderr, "Unexpected raw IRC string source!\n"); @@ -654,14 +778,15 @@ int processrawstring(int *serversockfd, int *clientsockfd, char *str, int source case SOURCE_SERVER: // If message(s) were from the real IRC server // Relay/send to all clients ("except" = 0 because this should send to all clients) // TODO - Is this really going to send the original string if we have messed it with it in processrawstring() and friends!? - printf("bouncer-server: sending unprocessed messasge \"%s\" to all clients, length %zd.\n", messages[i], strlen(messages[i])); + printf("bouncer-server: sending unprocessed server messasge \"%s\" to all clients, length %zd.\n", messages[i], strlen(messages[i])); sendtoallclients(clientsockfd, fdmax, arr_clients, messages[i], EXCEPT_NONE); break; case SOURCE_CLIENT: // If message(s) were from a real IRC client // Send to server - printf("bouncer-client: sending unprocessed messasge \"%s\" to the server, length %zd.\n", messages[i], strlen(messages[i])); + printf("bouncer-client: sending unprocessed client messasge \"%s\" to the server, length %zd.\n", messages[i], strlen(messages[i])); sendtoserver(serversockfd, messages[i], strlen(messages[i])); + printf("bouncer-client: sending unprocessed client messasge \"%s\" to all other clients, length %zd.\n", messages[i], strlen(messages[i])); // send the same thing to all *other* clients (all except for source fd) sendtoallclients(clientsockfd, fdmax, arr_clients, messages[i], sourcefd); break; @@ -733,13 +858,6 @@ void dochat(int *serversockfd, int *clientsockfd) { ircdstrings.ircdname[0] = '\0'; ircdstrings.nickuserhost[0] = '\0'; -/* - createchannel(channels, "testchannel", "Test topic!", "+"); - addusertochannel(channels, "testchannel", "l_bratch"); - createchannel(channels, "secondchan", "Yo yo yo", "+nrst"); - addusertochannel(channels, "secondchan", "l_bratch"); - addusertochannel(channels, "secondchan", "coold00d"); -*/ // =============================================> while (1) { -- cgit v1.2.3