From c4de74dce0ada6bafd297222e85bfe170805d6fb Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Sun, 19 May 2019 14:25:22 +0100 Subject: Rework all channel RPL_NAMREPLY code to properly relay user prefixes in channels when clients connect. Do this by asking server for the latest NAMES whenever a client connects and relay that to new clients. --- blabouncer.c | 136 ++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 84 insertions(+), 52 deletions(-) (limited to 'blabouncer.c') diff --git a/blabouncer.c b/blabouncer.c index e817f7e..2c210b1 100644 --- a/blabouncer.c +++ b/blabouncer.c @@ -6,6 +6,9 @@ // - Comma separated channel list in JOINs/PARTs // - Perhaps rename clients.ssl and server_ssl since they may not even be OpenSSL sockets // - Is it possible to replay JOINs/PARTs accurately? +// - Add help output for missing certs +// - Only relay WHOIS replies to requesting client +// - Remove any old channel name/mode/prefix code that isn't needed any more // "server" means the real IRC server // "client" means bouncer clients @@ -62,6 +65,7 @@ struct channel { char topicwhen[11]; // 32-bit unixtime is up to 10 characters (+1 for null char) We use "0" to mean "not set". 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? + int gotnames; // Have we finished getting the RPL_NAMREPLYs for this channel yet? }; struct ircdstrings { @@ -106,6 +110,7 @@ struct client { int pendingban; // Whether the client is waiting to hear back from a "MODE #channel b" command int pendingwho; // Whether the client is waiting to hear back from a "MODE #channel" command int pendinglist; // Whether the client is waiting to hear back from a "LIST" command + int pendingnames; // Count of RPL_NAMREPLYs the client is waiting on. }; // Return index of requested client FD within the clients array. @@ -280,6 +285,7 @@ int disconnectclient(int fd, struct client *clients, struct ircdstrings *ircdstr clients[i].pendingban = 0; clients[i].pendingwho = 0; clients[i].pendinglist = 0; + clients[i].pendingnames = 0; // Finish up with OpenSSL SSL_free(clients[i].ssl); // Close the socket @@ -323,6 +329,7 @@ int createchannel(struct channel *channels, char *name, char *topic, char *topic channels[i].topicwhen[strlen(topicwhen)] = '\0'; strncpy(channels[i].namestype, topic, strlen(namestype)); channels[i].namestype[strlen(namestype)] = '\0'; + channels[i].gotnames = 0; return 1; break; // TODO - This should be safe to remove since return is hit first } @@ -436,21 +443,6 @@ int setchanneltopic(struct channel *channels, char *channelname, char *topic) { return 0; } -int setchannelnamestype(struct channel *channels, char *channelname, char *type) { - printf("setchannelnamestype(): given \"%s\" and \"%s\".\n", channelname, 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; - } - } - - // TODO - Make a failed return do something to callers - return 0; -} - int getchannelcount(struct channel *channels) { int count = 0; @@ -513,6 +505,27 @@ int removechannel(struct channel *channels, char *name) { return 0; } +// Check if we have the NAMES for the channel 'name' already. +// Return the index in the channel array if so, or 0 if not. +int channelgotnames(struct channel *channels, char *name) { + printf("channelgotnames(): given '%s'.\n", name); + for (int i = 0; i < MAXCHANNELS; i++) { + if (strncmp(channels[i].name, name, strlen(name)) == 0) { + if (channels[i].gotnames) { + printf("channelgotnames(): channel '%s' gotnames was set, returning '%d'.\n", channels[i].name, channels[i].gotnames); + return channels[i].gotnames; + } else { + printf("channelgotnames(): channel '%s' gotnames was not set, returning '%d'.\n", channels[i].name, channels[i].gotnames); + return 0; + } + } + } + + // We didn't find the channel, this isn't good! TODO - Do something if this happens. + printf("channelgotnames(): channel '%s' not found, this is bad, returning 0.\n", name); + return 0; +} + // Send the requested number of lines of replay log to the requested client // 'sourcefd' is the client to send to, and replayseconds is the number of // seconds of replay to replay. @@ -665,6 +678,9 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli strncpy(ircdstrings->ircdname, tokens[3], strlen(tokens[3])); // Null the end of the string ircdstrings->ircdname[strlen(tokens[3])] = '\0'; + // Receiving greeting 004 means we're now registered + // Request IRCv3 multi-prefix extension so we can more accurately inform new clients about current user prefixes + sendtoserver(server_ssl, "CAP REQ multi-prefix", strlen("CAP REQ multi-prefix"), 0, clients, settings); free(strcopyPtr); return 1; } @@ -744,19 +760,53 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli } else if (strncmp(tokens[1], "333", strlen(tokens[1])) == 0) { printf("Server 333 (RPL_TOPICWHOTIME) found, extracting who and when, and storing in channel struct.\n"); setchanneltopicwhotime(channels, tokens[3], tokens[4], tokens[5]); - // Server 353 (RPL_NAMREPLY) add channel names type (secret/private/public) and the names of those already in the channel + // Server 353 (RPL_NAMREPLY), relay to all clients if we've just JOINed the channel, or relay to any clients + // who were pending RPL_NAMREPLYs if it's an existing channel. } else if (strncmp(tokens[1], "353", strlen(tokens[1])) == 0) { - // TODO - Do we also need to watch out for 366 (RPL_ENDOFNAMES)? - printf("Server 353 (RPL_NAMREPLY) found, extracting nicks and storing in channel struct.\n"); - // Set channel type - setchannelnamestype(channels, tokens[4], tokens[3]); - // Add nicks - // Strip prefix from first nick first - stripprefix(tokens[5]); - // Now go through all nicks and add them to the channel - for (int i = 5; i < counter; i++) { - addusertochannel(channels, tokens[4], tokens[i]); + // We were already in the channel and have the NAMES + if (!channelgotnames(channels, tokens[4])) { + printf("Server 353 received for a new channel, sending to all clients.\n"); + // Relay to all clients + sendtoallclients(clients, str, sourcefd, settings); + } else { + // It must be a new channel and we don't have the NAMES + printf("Server 353 received for an existing channel, sending to all clients who were pending RPL_NAMREPLYs.\n"); + // If any clients were pending RPL_NAMREPLYs, send this on to them + // TODO - Make sure clients only get the ones they were waiting on in case there are multiple conflicting requests going on at once + for (int i = 0; i < MAXCLIENTS; i++) { + if (clients[i].pendingnames > 0) { + printf("Sending 353 RPL_NAMREPLY for channel '%s' to client with fd '%d' who was pending %d RPL_NAMREPLYs.\n", tokens[4], clients[i].fd, clients[i].pendingnames); + sendtoclient(clients[i].fd, str, clients, settings); + } + } } + free(strcopyPtr); + return 1; + // Server 366 (RPL_ENDOFNAMES), relay to all clients if we've just JOINed the channel, or relay to + // and decrement from any clients who were waiting on RPL_NAMREPLY if it's an existing channel. + } else if (strncmp(tokens[1], "366", strlen(tokens[1])) == 0) { + int channelelement; + // We were already in the channel and have the NAMES + if (!(channelelement = channelgotnames(channels, tokens[3]))) { + printf("Server 366 received for a new channel, sending to all clients and set as got names.\n"); + channels[channelelement].gotnames = 1; + // Relay to all clients + sendtoallclients(clients, str, sourcefd, settings); + } else { + // TODO - Make sure clients only get the ones they were waiting on in case there are multiple conflicting requests going on at once + printf("Server 366 received for an existing channel, sending to all clients who were pending RPL_NAMREPLYs and decrementing their pendingnames count.\n"); + for (int i = 0; i < MAXCLIENTS; i++) { + if (clients[i].pendingnames > 0) { + sendtoclient(clients[i].fd, str, clients, settings); + // And decrement their pendingnames count + printf("Decrementing pendingnames due 366 RPL_ENDOFNAMES to for channel '%s' to client with fd '%d' who was pending %d RPL_NAMREPLYs.\n", tokens[3], clients[i].fd, clients[i].pendingnames); + clients[i].pendingnames--; + printf("Client with fd '%d' has '%d' pendingnames left.\n", clients[i].fd, clients[i].pendingnames); + } + } + } + free(strcopyPtr); + return 1; } // Server TOPIC received? Update our local channel topic info then relay to clients. @@ -1065,6 +1115,9 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli // Get the channel count so we can enumerate over all channels. // Storing separately so we can skip over blank channels. int channelcount = getchannelcount(channels); + // Set the client as pending RPL_NAMREPLYs for 'channelcount' channels + printf("Setting pendingnames to '%d' for client with fd '%d'.\n", channelcount, sourcefd); + clients[arrindex(clients, sourcefd)].pendingnames = channelcount; // Get client to join channels, and tell client about those channels for (int i = 0; i < channelcount; i++) { @@ -1112,32 +1165,10 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli sendtoclient(sourcefd, outgoingmsg, clients, settings); } - // Send list of names - // Store count of names so we can increment it in the loop if we encounter gaps in the names array - int namescount = getchannelnamescount(channels, channels[i].name); - // Go through each name... - for (int j = 0; j < namescount; j++) { - // TODO - Batch these up and send multiple names per line - // ...making sure it they are not null (probably a gap in the names array) - if (!channels[i].nicks[j][0]) { - // Skip this one and increment namescount so we find it later on instead - namescount++; - continue; - } - // If there was a nick found, send it to the client (one per line at the moment - TODO - batch them up into fewer lines) - if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 353 %s %s %s :%s", ircdstrings->ircdname, ircdstrings->ircnick, channels[i].namestype, channels[i].name, channels[i].nicks[j])) { - fprintf(stderr, "Error while preparing USER just connected, channel NAMES responses!\n"); - exit(1); - } - sendtoclient(sourcefd, outgoingmsg, clients, settings); - } - - // Once all names are sent, send the "end of /NAMES" 366 (RPL_ENDOFNAMES) message - if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 366 %s %s :End of /NAMES list.", ircdstrings->ircdname, ircdstrings->ircnick, channels[i].name)) { - fprintf(stderr, "Error while preparing USER just connected, end of NAMES response!\n"); - exit(1); - } - sendtoclient(sourcefd, outgoingmsg, clients, settings); + // Get the latest RPL_NAMREPLY for this channel to relay to the client when it arrives + char namesreq[MAXDATASIZE]; + snprintf(namesreq, MAXDATASIZE, "NAMES %s", channels[i].name); + sendtoserver(server_ssl, namesreq, strlen(namesreq), 0, clients, settings); } // Send our mode to the client @@ -1492,6 +1523,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { clients[i].pendingban = 0; clients[i].pendingwho = 0; clients[i].pendinglist = 0; + clients[i].pendingnames = 0; } // Initialise OpenSSL (used for both client and server) -- cgit v1.2.3