diff options
Diffstat (limited to 'message.c')
-rw-r--r-- | message.c | 1264 |
1 files changed, 1264 insertions, 0 deletions
diff --git a/message.c b/message.c new file mode 100644 index 0000000..f837b83 --- /dev/null +++ b/message.c @@ -0,0 +1,1264 @@ +/* + * This file is part of blabouncer (https://www.blatech.co.uk/l_bratch/blabouncer). + * Copyright (C) 2019 Luke Bratch <luke@bratch.co.uk>. + * + * Blabouncer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * Blabouncer is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with blabouncer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "message.h" + +// Process an IRC message that came from the real server. +// Return 1 if we processed it, or 0 if we didn't. +int processservermessage(SSL *server_ssl, char *str, struct client *clients, int sourcefd, struct ircdstrings *ircdstrings, + struct channel *channels, struct settings *settings, char tokens[MAXTOKENS][MAXDATASIZE], int counter) { + // Record that we received something from the server for timeout checking purposes + ircdstrings->lastmessagetime = time(NULL); // snprintf(NULL, 0, "%ld", timenow); + + // Server PING received? If so, send a PONG back with the next element as the argument. + if (strncmp(tokens[0], "PING", strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Server PING found and it is: %s with length %zd! Sending response...\n", tokens[0], strlen(tokens[0])); + + char outgoingmsg[MAXDATASIZE]; // String to send to server + if (!snprintf(outgoingmsg, MAXDATASIZE, "PONG %s", tokens[1])) { // TODO - Make sure tokens[1] actually has a token + fprintf(stderr, "Error while preparing PONG response!\n"); + debugprint(DEBUG_CRIT, "Error while preparing PONG response!\n"); + outgoingmsg[0] = '\0'; + } + // sourcefd = 0 as this is a trusted response + sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings); + + // We processed something so return true + return 1; + } + + // Prefix received? TODO - Care about what the prefix is - what if it's a different server/network/whatever? + if (tokens[0][0] == ':') { + debugprint(DEBUG_FULL, "Prefix found: '%s'! Next token is '%s', length %zd.\n", tokens[0], tokens[1], strlen(tokens[1])); + // Greetings 001 through to 005, store in ircdstrings array for resending when clients connect + // Also store our nick!user@host from greeting 001 + // Also store the real IRCd's name from greeting 004 + if (strncmp(tokens[1], "001", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Found greeting 001 (%s) (length %zd), storing in ircdstrings struct.\n", str, strlen(str)); + strncpy(ircdstrings->greeting001, str, strlen(str)); + // Null the end of the string + ircdstrings->greeting001[strlen(str)] = '\0'; + debugprint(DEBUG_FULL, "Storing our nick!user@host (:%s) from greeting 001 in ircdstrings struct.\n", tokens[counter - 1]); + // Prepend a colon (:) first since everything (so far) needs one + if (!snprintf(ircdstrings->nickuserhost, MAXDATASIZE, ":%s", tokens[counter - 1])) { + fprintf(stderr, "Error while preparing nickuserhost for storage!\n"); + debugprint(DEBUG_CRIT, "Error while preparing nickuserhost for storage!\n"); + exit(1); + } + // Null the end of the new string + ircdstrings->nickuserhost[strlen(tokens[counter - 1]) + 1] = '\0'; // +1 for the inserted colon + debugprint(DEBUG_FULL, "nickuserhost '%s' stored.\n", ircdstrings->nickuserhost); + return 1; + } else if (strncmp(tokens[1], "002", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Found greeting 002 (%s), storing in ircdstrings struct.\n", str); + strncpy(ircdstrings->greeting002, str, strlen(str)); + // Null the end of the string + ircdstrings->greeting002[strlen(str)] = '\0'; + return 1; + } else if (strncmp(tokens[1], "003", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Found greeting 003 (%s), storing in ircdstrings struct.\n", str); + strncpy(ircdstrings->greeting003, str, strlen(str)); + // Null the end of the string + ircdstrings->greeting003[strlen(str)] = '\0'; + return 1; + } else if (strncmp(tokens[1], "004", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Found greeting 004 (%s), storing in ircdstrings struct.\n", str); + strncpy(ircdstrings->greeting004, str, strlen(str)); + // Null the end of the string + ircdstrings->greeting004[strlen(str)] = '\0'; + debugprint(DEBUG_FULL, "Storing the real IRCd's name (%s) from greeting 004 in ircdstrings struct.\n", tokens[3]); + 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); + // Send the connect command, if set + if (settings->connectcommand[0]) { + sendtoserver(server_ssl, settings->connectcommand, strlen(settings->connectcommand), 0, clients, settings); + } + // If this is a reconnection, JOIN existing channels and catch clients up again + if (ircdstrings->reconnecting) { + // First tell clients if our nick changed + if (!strcmp(ircdstrings->ircnick, ircdstrings->oldnick) == 0) { + debugprint(DEBUG_SOME, "Telling clients about nick change.\n"); + char nickmsg[MAXDATASIZE]; + snprintf(nickmsg, MAXDATASIZE, ":%s NICK :%s", ircdstrings->oldnick, ircdstrings->ircnick); + sendtoallclients(clients, nickmsg, sourcefd, settings); + } + + // Next re-join channels + // Storing separately so we can skip over blank channels. + int channelcount = getchannelcount(channels); + // Join all the channels and make a note of the channel count + for (int i = 0; i < channelcount; i++) { + // Skip this one and increment channelcount if it's a blank channel + if (!channels[i].name[0]) { + debugprint(DEBUG_FULL, "Reconnection: Skipping channel[%d], incrementing channelcount.\n", i); + channelcount++; + continue; + } + + debugprint(DEBUG_SOME, "Reconnection: Re-joining '%s'.\n", channels[i].name); + + char joinmsg[MAXDATASIZE]; + snprintf(joinmsg, MAXDATASIZE, "JOIN %s", channels[i].name); + sendtoserver(server_ssl, joinmsg, strlen(joinmsg), 0, clients, settings); + } + + // Finally do a replay for all clients and tell them we're reconnected + for (int i = 0; i < MAXCLIENTS; i++) { + if (clients[i].fd) { + char alertmsg[MAXDATASIZE]; + if (!doreplay(clients[i].fd, settings->replayseconds, clients, settings, ircdstrings, channels)) { + snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstrings->ircnick); + sendtoclient(sourcefd, alertmsg, clients, settings, 0); + } + snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Reconnection complete.", ircdstrings->ircnick); + sendtoclient(clients[i].fd, alertmsg, clients, settings, 0); + } + } + + // Reconnection complete + ircdstrings->oldnick[0] = '\0'; + ircdstrings->reconnecting = 0; + // If it's not, deal with auto channels + } else { + // Join any auto channels set in the configuration file + joinautochannels(server_ssl, clients, settings); + } + return 1; + } else if (strncmp(tokens[1], "005", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Found greeting 005 (%s), storing in ircdstrings struct.\n", str); + // Find an empty greeting005 string in ircdstrings and store in there... + if (!ircdstrings->greeting005a[0]) { + strncpy(ircdstrings->greeting005a, str, strlen(str)); + ircdstrings->greeting005a[strlen(str)] = '\0'; + } else if (!ircdstrings->greeting005b[0]) { + strncpy(ircdstrings->greeting005b, str, strlen(str)); + ircdstrings->greeting005b[strlen(str)] = '\0'; + } else if (!ircdstrings->greeting005c[0]) { + strncpy(ircdstrings->greeting005c, str, strlen(str)); + ircdstrings->greeting005c[strlen(str)] = '\0'; + } else { + // ...or if they are all fill, discard - TODO - Support more than three greeting005 strings! + debugprint(DEBUG_CRIT, "Already stored three greeting 005 strings, discarding this one.\n"); + } + return 1; + } + + // Server JOIN received? Add to our local channel array if it's us, or record the user in the channel if it's not us. + if (strncmp(tokens[1], "JOIN", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "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 + debugprint(DEBUG_FULL, "processircmessage(): Channel name was '%s'\n", tokens[2]); + stripprefix(tokens[2]); + debugprint(DEBUG_FULL, "processircmessage(): Channel name now '%s'\n", tokens[2]); + + // If the user JOINing is us, then we must have joined a channel, so add to our local channel array. + // Copy to a temporary string so we still have the original in case we need it + char *prefixcopy = strdup(tokens[0]); + // Just get the nick for comparison + extractnickfromprefix(prefixcopy); + if (strncmp(prefixcopy, ircdstrings->ircnick, strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Server JOIN: nick is ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->ircnick); + // 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"); + } else { + debugprint(DEBUG_FULL, "Server JOIN: nick is NOT ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->ircnick); + } + + // And then send to all clients + sendtoallclients(clients, str, sourcefd, settings); + + // Write to replay log if replay logging enabled + if (settings->replaylogging) { + writereplayline(str, settings->basedir); + } + + // Write to normal log if logging enabled + if (settings->logging) { + logline(str, settings->ircnick, settings->basedir, LOG_JOINPART); + } + + free(prefixcopy); + return 1; + } + + // Server PART received? Remove from our local channel list if it's us. + if (strncmp(tokens[1], "PART", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Server PART found and it is: %s with length %zd! Next token is '%s'. Removing from local channel list if it's us.\n", tokens[0], strlen(tokens[0]), tokens[2]); + + // If the user PARTing is us, then we must have left a channel, so remove it from our local channel array. + // (If it's not us, then it's another user PARTing 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]); + // Just get the nick for comparison + extractnickfromprefix(prefixcopy); + if (strncmp(prefixcopy, ircdstrings->ircnick, strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Server PART: nick is ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->ircnick); + removechannel(channels, tokens[2]); + } else { + debugprint(DEBUG_FULL, "Server PART: nick is NOT ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->ircnick); + } + + // And then send to all clients + sendtoallclients(clients, str, sourcefd, settings); + + // Write to replay log if replay logging enabled + if (settings->replaylogging) { + writereplayline(str, settings->basedir); + } + + // Write to normal log if logging enabled + if (settings->logging) { + logline(str, settings->ircnick, settings->basedir, LOG_JOINPART); + } + + free(prefixcopy); + return 1; + } + + // 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 + } else if (strncmp(tokens[1], "332", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "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 + char *topiccopy = strdup(str); + extractfinalparameter(topiccopy); + setchanneltopic(channels, tokens[3], topiccopy); + free(topiccopy); + // Server 333 (RPL_TOPICWHOTIME) set the channel topic setter and the time it was set + } else if (strncmp(tokens[1], "333", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "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), 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) { + // It must be a new channel and we don't have the NAMES + if (!channelgotnames(channels, tokens[4])) { + debugprint(DEBUG_FULL, "Server 353 received for a new channel, sending to all clients.\n"); + // Relay to all clients + sendtoallclients(clients, str, sourcefd, settings); + } else { + // We were already in the channel and have the NAMES + debugprint(DEBUG_FULL, "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) { + debugprint(DEBUG_FULL, "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, 0); + } + } + } + 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; + // It must be a new channel and we don't have the NAMES + if (!(channelelement = channelgotnames(channels, tokens[3]))) { + debugprint(DEBUG_FULL, "Server 366 received for a new channel, sending to all clients and set as got names.\n"); + // We have the names now! + channels[channelindex(channels, tokens[3])].gotnames = 1; + // Relay to all clients + sendtoallclients(clients, str, sourcefd, settings); + } else { + // We were already in the channel and have the NAMES + // TODO - Make sure clients only get the ones they were waiting on in case there are multiple conflicting requests going on at once + debugprint(DEBUG_FULL, "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, 0); + // And decrement their pendingnames count + debugprint(DEBUG_FULL, "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--; + debugprint(DEBUG_FULL, "Client with fd '%d' has '%d' pendingnames left.\n", clients[i].fd, clients[i].pendingnames); + } + } + } + return 1; + } + + // Server TOPIC received? Update our local channel topic info then relay to clients. + if (strncmp(tokens[1], "TOPIC", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "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(clients, str, sourcefd, settings); + + // Write to replay log if replay logging enabled + if (settings->replaylogging) { + writereplayline(str, settings->basedir); + } + + // Write to normal log if logging enabled + if (settings->logging) { + logline(str, settings->ircnick, settings->basedir, LOG_TOPIC); + } + + free(topiccopy); + free(prefixcopy); + return 1; + } + + // Server PRIVMSG received? Relay to all clients and write to replay log. + if (strncmp(tokens[1], "PRIVMSG", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Server PRIVMSG found and it is: %s with length %zd! Next token is '%s'. Relaying to all clients.\n", + tokens[0], strlen(tokens[0]), tokens[2]); + + sendtoallclients(clients, str, sourcefd, settings); + + // Write to replay log if replay logging enabled + if (settings->replaylogging) { + writereplayline(str, settings->basedir); + } + + // Write to normal log if logging enabled + if (settings->logging) { + logline(str, settings->ircnick, settings->basedir, LOG_PRIVMSG); + } + + return 1; + } + + // Server NICK received? + // 1. Find out if it was us and change ircnick and nickuserhost if so + // 2. Either way, relay to all clients + if (strncmp(tokens[1], "NICK", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Server NICK found and it is: %s with length %zd! Next token is '%s'. Updating records and relaying to all clients.\n", + tokens[0], strlen(tokens[0]), tokens[2]); + + // Was it us? + // Copy to a temporary string so we still have the original in case we need it + char *svrprefixcopy = strdup(tokens[0]); + // Just get the nick for comparison + extractnickfromprefix(svrprefixcopy); + if (strncmp(ircdstrings->ircnick, svrprefixcopy, strlen(ircdstrings->ircnick)) == 0) { + debugprint(DEBUG_FULL, "Server NICK: nick is ours ('%s' vs '%s').\n", svrprefixcopy, ircdstrings->ircnick); + // Make a copy of the old nickuserhost for updategreetings() below + char *nickuserhostcpy = strdup(ircdstrings->nickuserhost); + // Update nickuserhost with the new :nick!user@host + updatenickuserhost(ircdstrings->nickuserhost, tokens[2]); + debugprint(DEBUG_FULL, "Updated nickuserhost to '%s'.\n", ircdstrings->nickuserhost); + // Prepare to update ircnick and greetings strings + // Temporary copy of new nickuserhost + char *prefixcopy = strdup(ircdstrings->nickuserhost); + // Get nick from it + extractnickfromprefix(prefixcopy); + // Update greeting strings for relaying to new clients + updategreetings(ircdstrings->greeting001, ircdstrings->greeting002, ircdstrings->greeting003, ircdstrings->greeting004, + ircdstrings->greeting005a, ircdstrings->greeting005b, ircdstrings->greeting005c, ircdstrings->nickuserhost, + nickuserhostcpy, tokens[2], ircdstrings->ircnick); + // Update our nick + strcpy(ircdstrings->ircnick, prefixcopy); + debugprint(DEBUG_FULL, "Updated ircnick to '%s'.\n", ircdstrings->ircnick); + free(nickuserhostcpy); + free(prefixcopy); + } + + // Relay to all clients + sendtoallclients(clients, str, sourcefd, settings); + + free(svrprefixcopy); + return 1; + } + + // Server MODE received? See what sort it is and act accordingly. + if (strncmp(tokens[1], "MODE", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Server MODE found and it is: %s with length %zd! Next token is '%s'. Analysing...\n", + tokens[1], strlen(tokens[1]), tokens[2]); + + // 4 tokens could be either our initial mode or a channel mode (or something else - TODO - what else?) + if (counter == 4) { + // Might be our initial mode (e.g. ":nick MODE nick :+iwz") + char comparison[MAXDATASIZE]; + snprintf(comparison, MAXDATASIZE, ":%s MODE %s :", ircdstrings->ircnick, ircdstrings->ircnick); + if (strncmp(str, comparison, strlen(comparison)) == 0) { + // Looks like it! + debugprint(DEBUG_FULL, "Our initial MODE found (%s), storing for later.\n", tokens[3]); + // Store in ircdstrings for when clients connect and relay to current clients. + strcpy(ircdstrings->mode, tokens[3]); + + // Relay to all current clients anyway - TODO - Necessary? + sendtoallclients(clients, str, sourcefd, settings); + + return 1; + } + + // Might be a channel mode (e.g. ":nick!user@host MODE #channel +s") + if (tokens[2][0] == '#') { + // Looks like it! Tell all clients. + debugprint(DEBUG_FULL, "Channel MODE found (%s %s), telling all clients.\n", tokens[2], tokens[3]); + sendtoallclients(clients, str, sourcefd, settings); + + return 1; + } + } + + // Relay to all current clients if not processed by the above + sendtoallclients(clients, str, sourcefd, settings); + + return 1; + } + + // Server 324 (RPL_CHANNELMODEIS) received? Send to any clients who requested a channel MODE. + if (strncmp(tokens[1], "324", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Server 324 (RPL_CHANNELMODEIS) found and it is: %s with length %zd! Sending to clients who are pending this.\n", + tokens[1], strlen(tokens[1])); + + // Relay to all pending clients + for (int i = 0; i < MAXCLIENTS; i++) { + if (clients[i].pendingchannelmode == 1) { + sendtoclient(clients[i].fd, str, clients, settings, 0); + // And clear the pending flag + clients[i].pendingchannelmode = 0; + } + } + + return 1; + } + + // Server 368 (RPL_ENDOFBANLIST) received? Send to any clients who requested a ban MODE query. - TODO - Identify and handle start/middle of ban responses. + if (strncmp(tokens[1], "368", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Server 368 (RPL_ENDOFBANLIST) found and it is: %s with length %zd! Sending to clients who are pending this.\n", + tokens[1], strlen(tokens[1])); + + // Relay to all pending clients + for (int i = 0; i < MAXCLIENTS; i++) { + if (clients[i].pendingban == 1) { + sendtoclient(clients[i].fd, str, clients, settings, 0); + // And clear the pending flag + clients[i].pendingban = 0; + } + } + + return 1; + } + + // Server 329 (RPL_CREATIONTIME), 352 (RPL_WHOREPLY), or 315 (RPL_ENDOFWHO) received? Send to any clients who requested a WHO. + if (strncmp(tokens[1], "329", strlen(tokens[1])) == 0 || strncmp(tokens[1], "352", strlen(tokens[1])) == 0 || strncmp(tokens[1], "315", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Server 329 (RPL_CREATIONTIME), 352 (RPL_WHOREPLY), or 315 (RPL_ENDOFWHO) found and it is: %s with length %zd! Sending to clients who are pending one of these.\n", + tokens[1], strlen(tokens[1])); + + // Relay to all pending clients + for (int i = 0; i < MAXCLIENTS; i++) { + if (clients[i].pendingwho == 1 && clients[i].fd) { + sendtoclient(clients[i].fd, str, clients, settings, 0); + // And clear the pending flag if it's 315 (RPL_ENDOFWHO) + if (strncmp(tokens[1], "315", strlen(tokens[1])) == 0) { + clients[i].pendingwho = 0; + } + } + } + + return 1; + } + + // Server 321 (RPL_LISTSTART), 322 (RPL_LIST), or 323 (RPL_LISTEND) received? Send to any clients who requested a WHO. + if (strncmp(tokens[1], "321", strlen(tokens[1])) == 0 || strncmp(tokens[1], "322", strlen(tokens[1])) == 0 || strncmp(tokens[1], "323", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Server 321 (RPL_LISTSTART), 322 (RPL_LIST), or 323 (RPL_LISTEND) found and it is: %s with length %zd! Sending to clients who are pending one of these.\n", + tokens[1], strlen(tokens[1])); + + // Relay to all pending clients + for (int i = 0; i < MAXCLIENTS; i++) { + if (clients[i].pendinglist == 1 && clients[i].fd) { + sendtoclient(clients[i].fd, str, clients, settings, 0); + // And clear the pending flag if it's 323 (RPL_LISTEND) + if (strncmp(tokens[1], "323", strlen(tokens[1])) == 0) { + clients[i].pendinglist = 0; + } + } + } + + return 1; + } + + // Server 307 (RPL_SUSERHOST), 311 (RPL_WHOISUSER), 312 (RPL_WHOISSERVER), 313 (RPL_WHOISOPERATOR), 317 (RPL_WHOISIDLE), + // 319 (RPL_WHOISCHANNELS), 320 (RPL_WHOISSPECIAL), 671 (RPL_WHOISSECURE), or 318 (RPL_ENDOFWHOIS) received? + // Send to any clients who requested a WHOIS. + if (strncmp(tokens[1], "307", strlen(tokens[1])) == 0 || strncmp(tokens[1], "311", strlen(tokens[1])) == 0 || + strncmp(tokens[1], "312", strlen(tokens[1])) == 0 || strncmp(tokens[1], "313", strlen(tokens[1])) == 0 || + strncmp(tokens[1], "317", strlen(tokens[1])) == 0 || strncmp(tokens[1], "319", strlen(tokens[1])) == 0 || + strncmp(tokens[1], "320", strlen(tokens[1])) == 0 || strncmp(tokens[1], "671", strlen(tokens[1])) == 0 || + strncmp(tokens[1], "318", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Server 307 RPL_SUSERHOST, 311 RPL_WHOISUSER, 312 RPL_WHOISSERVER, 313 (RPL_WHOISOPERATOR), 317 RPL_WHOISIDLE, " + "319 RPL_WHOISCHANNELS, 320 (RPL_WHOISSPECIAL), 671 (RPL_WHOISSECURE), or 318 RPL_ENDOFWHOIS found and it is: " + "%s with length %zd! Sending to clients who are pending one of these.\n", tokens[1], strlen(tokens[1])); + + // Relay to all pending clients + for (int i = 0; i < MAXCLIENTS; i++) { + if (clients[i].pendingwhois == 1 && clients[i].fd) { + sendtoclient(clients[i].fd, str, clients, settings, 0); + // And clear the pending flag if it's 318 RPL_ENDOFWHOIS + if (strncmp(tokens[1], "318", strlen(tokens[1])) == 0) { + clients[i].pendingwhois = 0; + } + } + } + + return 1; + } + + // Server 314 (RPL_WHOWASUSER), 406 (ERR_WASNOSUCHNICK), or 369 (RPL_ENDOFWHOWAS) received? + // Send to any clients who requested a WHOWAS. + if (strncmp(tokens[1], "314", strlen(tokens[1])) == 0 || strncmp(tokens[1], "406", strlen(tokens[1])) == 0 || strncmp(tokens[1], "369", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "314 (RPL_WHOWASUSER), 406 (ERR_WASNOSUCHNICK), or 369 (RPL_ENDOFWHOWAS) " + "found and it is: %s with length %zd! Sending to clients who are pending one of these.\n", tokens[1], strlen(tokens[1])); + + // Relay to all pending clients + for (int i = 0; i < MAXCLIENTS; i++) { + if (clients[i].pendingwhowas == 1 && clients[i].fd) { + sendtoclient(clients[i].fd, str, clients, settings, 0); + // And clear the pending flag if it's 369 RPL_ENDOFWHOWAS + if (strncmp(tokens[1], "369", strlen(tokens[1])) == 0) { + clients[i].pendingwhowas = 0; + } + } + } + + return 1; + } + + // Server 312 (RPL_WHOISSERVER) received? Check to see if anyone was pending a WHOIS or a WHOWAS and send to them, if not send to everyone. + if (strncmp(tokens[1], "312", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Server 312 (RPL_WHOISSERVER) found and it is: %s with length %zd! Sending to clients who are pending this or to everyone if nobody is.\n", + tokens[1], strlen(tokens[1])); + + int waspending = 0; + + // Relay to all pending clients + for (int i = 0; i < MAXCLIENTS; i++) { + if (clients[i].pendingwhois == 1 || clients[i].pendingwhowas == 1) { + sendtoclient(clients[i].fd, str, clients, settings, 0); + // Note that we were pending this + waspending = 1; + } + } + + // If no client was pending this, send to everyone + if (!waspending) { + sendtoallclients(clients, str, 0, settings); + } + + return 1; + } + + + // Server 401 (ERR_NOSUCHNICK) received? Check to see if anyone was pending a WHOIS and send to them, + // if not send to everyone (401 was probably in reply to something else like a PRIVMSG). + if (strncmp(tokens[1], "401", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Server 401 (ERR_NOSUCHNICK) found and it is: %s with length %zd! Sending to clients who are pending this or to everyone if nobody is.\n", + tokens[1], strlen(tokens[1])); + + int waspending = 0; + + // Relay to all pending clients + for (int i = 0; i < MAXCLIENTS; i++) { + if (clients[i].pendingwhois == 1) { + sendtoclient(clients[i].fd, str, clients, settings, 0); + // Note that we were pending this + waspending = 1; + } + } + + // If no client was pending this, send to everyone + if (!waspending) { + sendtoallclients(clients, str, 0, settings); + } + + return 1; + } + + // Server 432 (ERR_ERRONEUSNICKNAME) or 433 (ERR_NICKNAMEINUSE) received? See which nick we're on and try another. + // (But only if we're not already registered with the real IRC server.) + if ((strncmp(tokens[1], "432", strlen(tokens[1])) == 0 || strncmp(tokens[1], "433", strlen(tokens[1])) == 0) && !strlen(ircdstrings->greeting004)) { + debugprint(DEBUG_SOME, "Server 432 (ERR_ERRONEUSNICKNAME) or 433 (ERR_NICKNAMEINUSE) found and it is: %s with length %zd! Trying another nick...\n", + tokens[1], strlen(tokens[1])); + + // Try to get nick2 and nick3 from the configuration file + char nick2[MAXNICKLENGTH]; + char nick3[MAXNICKLENGTH]; + if (!getconfstr("nick2", settings->conffile, nick2)) { + nick2[0] = '\0'; + } + if (!getconfstr("nick3", settings->conffile, nick3)) { + nick3[0] = '\0'; + } + + // Do we have both a nick2 and a nick3? (And not tried autonick yet.) + if (nick2[0] && nick3[0] && !ircdstrings->autonicknum) { + // Has nick3 already been tried? + if (strncmp(ircdstrings->ircnick, nick3, strlen(settings->ircnick)) == 0) { + // Then try autonick + tryautonick(ircdstrings); + // Has nick2 already been tried? + } else if (strncmp(ircdstrings->ircnick, nick2, strlen(settings->ircnick)) == 0) { + // Then try nick3 + debugprint(DEBUG_SOME, "Trying nick3, nick2 was already tried.\n"); + strcpy(ircdstrings->ircnick, nick3); + // Have neither been tried? + } else { + // Then try nick2 + debugprint(DEBUG_SOME, "Trying nick2, nick3 is also configured.\n"); + strcpy(ircdstrings->ircnick, nick2); + } + // Do we only have a nick2? (And not tried autonick yet.) + } else if (nick2[0] && !ircdstrings->autonicknum) { + // Has it already been tried? + if (strncmp(ircdstrings->ircnick, nick2, strlen(settings->ircnick)) == 0) { + // Then try autonick + tryautonick(ircdstrings); + } else { + // Then try it + debugprint(DEBUG_SOME, "Trying nick2, nick3 is not configured.\n"); + strcpy(ircdstrings->ircnick, nick2); + } + // Do we have neither? (Or have already started autonick.) + } else { + // Then try autonick + tryautonick(ircdstrings); + } + + // Set whichever one we settled on in the settings in case we reference settings later + strcpy(settings->ircnick, ircdstrings->ircnick); + + // Try it with the server + char outgoingmsg[MAXDATASIZE]; + snprintf(outgoingmsg, MAXDATASIZE, "NICK %s", ircdstrings->ircnick); + // sourcefd = 0 as this is a trusted message + sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings); + + return 1; + } + + // Server CAP received? + if (strncmp(tokens[1], "CAP", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Server CAP found and it is: %s with length %zd! Analysing...\n", tokens[1], strlen(tokens[1])); + // If the server said "CAP <ournick> ACK :multi-prefix" then it must have approved our CAP multi-prefix request + if (counter == 5) { + if (strncmp(tokens[2], ircdstrings->ircnick, strlen(tokens[2])) == 0 && + strncmp(tokens[3], "ACK", strlen(tokens[3])) == 0 && + strncmp(tokens[4], ":multi-prefix", strlen(tokens[4])) == 0) { + ircdstrings->capmultiprefix = 1; + } + } + // We didn't handle it + debugprint(DEBUG_FULL, "Unhandled server CAP response.\n"); + } + + // Server NOTICE received? Handle and log if it's from a user, otherwise let pass through to default handler. + if (strncmp(tokens[1], "NOTICE", strlen(tokens[1])) == 0) { + debugprint(DEBUG_FULL, "Server NOTICE found and it is: %s with length %zd! Analysing...\n", tokens[1], strlen(tokens[1])); + // If the first token is a nick!user@host then it's probably from a user + if (strstr(tokens[0], "!") != NULL && strstr(tokens[0], "@") != NULL) { + debugprint(DEBUG_FULL, "Server NOTICE appears to be from a user, sending to all clients and logging.\n"); + + sendtoallclients(clients, str, 0, settings); + + // Write to replay log if replay logging enabled + if (settings->replaylogging) { + writereplayline(str, settings->basedir); + } + + // Write to normal log if logging enabled + if (settings->logging) { + logline(str, settings->ircnick, settings->basedir, LOG_PRIVMSG); + } + + return 1; + } else { + debugprint(DEBUG_FULL, "Server NOTICE does not appear to be from a user, passing through.\n"); + } + } + } + + // We didn't process anything so return 0 + return 0; +} + +// Process an IRC message that came from a client. +// Return 1 if we processed it, or 0 if we didn't. +int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int sourcefd, struct ircdstrings *ircdstrings, + struct channel *channels, struct settings *settings, char tokens[MAXTOKENS][MAXDATASIZE], int counter) { + // PASS received? User is trying to log in, check their password. + if (strncasecmp(tokens[0], "PASS", strlen(tokens[0])) == 0) { + if (checkpassword(tokens[1], settings->conffile)) { + debugprint(DEBUG_FULL, "Password accepted! Setting fd %d to authenticated.\n", sourcefd); + // Find the client in the clients array and set them as authenticated + for (int i = 0; i < MAXCLIENTS; i++) { + if (clients[i].fd == sourcefd) { + // Found client in array, set to authenticated + clients[i].authed = 1; + debugprint(DEBUG_FULL, "Found and authenticated fd in arr_authed.\n"); + // Alert other clients about the successful authentication + char alertmsg[MAXDATASIZE]; + if (!snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :blabouncer: new client with fd %d has successfully authenticated.", ircdstrings->ircnick, sourcefd)) { + fprintf(stderr, "Error while preparing authentication success NOTICE!\n"); + debugprint(DEBUG_CRIT, "Error while preparing authentication success NOTICE!\n"); + alertmsg[0] = '\0'; + } + // "except" the current fd - we can use this as "except/sourcefd" since we set them as authed just above + sendtoallclients(clients, alertmsg, sourcefd, settings); + } + } + } else { + debugprint(DEBUG_SOME, "Password rejected, disconnecting fd %d.\n", sourcefd); + disconnectclient(sourcefd, clients, ircdstrings, settings); + // Alert other clients about the failed authentication + char alertmsg[MAXDATASIZE]; + if (!snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :blabouncer: new client with fd %d has failed to authenticate.", ircdstrings->ircnick, sourcefd)) { + fprintf(stderr, "Error while preparing authentication failure NOTICE!\n"); + debugprint(DEBUG_CRIT, "Error while preparing authentication failure NOTICE!\n"); + alertmsg[0] = '\0'; + } + // "except" 0 since we trust this message + sendtoallclients(clients, alertmsg, 0, settings); + } + + return 1; + } + + // CAP received? Clients can send CAP before PASS so we have to deal with this even if they are not authenticated yet. + if (strncasecmp(tokens[0], "CAP", strlen(tokens[0])) == 0) { + // But only do something if the real server told us it had a CAP (only multi-prefix for now) + if (ircdstrings->capmultiprefix == 1) { + debugprint(DEBUG_FULL, "Client CAP received and the server supports CAPs, continuing.\n"); + } else { + debugprint(DEBUG_FULL, "Client CAP received but the server doesn't support CAPs, returning.\n"); + return 1; + } + // Get the real IRC server name from greeting001 + // They are now pending CAP negotiation + clients[arrindex(clients, sourcefd)].pendingcap = 1; + char outgoingmsg[MAXDATASIZE]; + // If client is requesting CAP list, send it... + if (strncasecmp(tokens[1], "LS", strlen(tokens[1])) == 0) { + if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s CAP * LS :multi-prefix", ircdstrings->ircdname)) { + fprintf(stderr, "Error while preparing CAP LS response!\n"); + debugprint(DEBUG_CRIT, "Error while preparing CAP LS response!\n"); + outgoingmsg[0] = '\0'; + } + // ...even if unauthenticated + sendtoclient(sourcefd, outgoingmsg, clients, settings, 1); + return 1; + // If client is requesting a CAP... + } else if (strncasecmp(tokens[1], "REQ", strlen(tokens[1])) == 0) { + // ...and it is "multi-prefix", send it + if (strncasecmp(tokens[2], ":multi-prefix", strlen(tokens[2])) == 0) { + if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s CAP %s ACK :multi-prefix ", ircdstrings->ircdname, ircdstrings->ircnick)) { + fprintf(stderr, "Error while preparing CAP ACK response!\n"); + debugprint(DEBUG_CRIT, "Error while preparing CAP ACK response!\n"); + outgoingmsg[0] = '\0'; + } + // ...even if unauthenticated + sendtoclient(sourcefd, outgoingmsg, clients, settings, 1); + return 1; + } + // If client is finishing CAP negotiation then mark them as so + } else if (strncasecmp(tokens[1], "END", strlen(tokens[1])) == 0) { + clients[arrindex(clients, sourcefd)].pendingcap = -1; + } + } + + // We're past PASS in the list of possible commands, so ignore + // anything else the client says if they are not authenticated yet. + if (!clients[arrindex(clients, sourcefd)].authed) { + debugprint(DEBUG_CRIT, "Ignoring client command '%s' from sourcefd '%d' as not authenticated yet.\n", tokens[0], sourcefd); + return 1; + } + + // USER received and not pending CAP negotiation during registration? + // Or client has just finished negotiating CAP (pendingcap = -1)? + // If so, assume this is a new client connecting and catch them on up on the state + if ((strncasecmp(tokens[0], "USER", strlen(tokens[0])) == 0 && clients[arrindex(clients, sourcefd)].pendingcap == 0) || clients[arrindex(clients, sourcefd)].pendingcap == -1) { + // Somewhere to store the several strings we will need to build and send + char outgoingmsg[MAXDATASIZE]; // String to send to client + + // If registering then they must no longer be pending CAP negotiation + clients[arrindex(clients, sourcefd)].pendingcap = 0; + + // Tell the client to go away if we aren't registered with the real server yet as defined by the last greeting not being set yet + if (!strlen(ircdstrings->greeting004)) { + sendtoclient(sourcefd, "Sorry, we aren't registered with a real IRC server yet.", clients, settings, 0); + disconnectclient(sourcefd, clients, ircdstrings, settings); + return 1; + } + + // Send IRC greeting strings (001/RPL_WELCOME, 002/RPL_YOURHOST, 003/RPL_CREATED, 004/RPL_MYINFO, 005/RPL_ISUPPORT) to client + snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting001); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting002); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting003); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting004); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + if (ircdstrings->greeting005a[0]) { + snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting005a); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + } + if (ircdstrings->greeting005b[0]) { + snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting005b); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + } + if (ircdstrings->greeting005c[0]) { + snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting005c); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + } + + // Send our own greeting message + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Welcome to blabouncer!", ircdstrings->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Blabouncer commands are all prefixed with BLABOUNCER which you can usually send using \"/QUOTE BLABOUNCER\"", ircdstrings->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Valid blabouncer commands are:", ircdstrings->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REPLAY [[[[days:]hours:]minutes:]seconds]\" (To replay a given length of time of replay log.)", ircdstrings->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER QUIT [quit message]\" (To quit blabouncer, optionally sending [quit message] to the server.)", ircdstrings->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + + // 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 + debugprint(DEBUG_FULL, "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++) { + debugprint(DEBUG_FULL, "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]) { + debugprint(DEBUG_FULL, "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"); + debugprint(DEBUG_CRIT, "Error while preparing USER just connected, channel JOIN responses!\n"); + return 0; + } + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + + // 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, ircdstrings->ircnick, channels[i].name)) { + fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses, 331 RPL_NOTOPIC!\n"); + debugprint(DEBUG_CRIT, "Error while preparing USER just connected, channel JOIN responses, 331 RPL_NOTOPIC!\n"); + return 0; + } + // ..and send it to the client + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + // 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, ircdstrings->ircnick, channels[i].name, channels[i].topic)) { + fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses, 332 RPL_TOPIC!\n"); + debugprint(DEBUG_CRIT, "Error while preparing USER just connected, channel JOIN responses, 332 RPL_TOPIC!\n"); + return 0; + } + // ..and send it to the client + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + + // Next prepare the topic who/when message... + if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 333 %s %s %s %s", ircdstrings->ircdname, ircdstrings->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"); + debugprint(DEBUG_CRIT, "Error while preparing USER just connected, channel JOIN responses, 333 RPL_TOPICWHOTIME!\n"); + return 0; + } + // ..and send it to the client + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + } + + // 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 (if we have one) + if (strlen(ircdstrings->mode) > 0) { + if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s MODE %s %s", ircdstrings->ircnick, ircdstrings->ircnick, ircdstrings->mode)) { + fprintf(stderr, "Error while preparing USER just connected, MODE response!\n"); + debugprint(DEBUG_CRIT, "Error while preparing USER just connected, MODE response!\n"); + return 0; + } + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + } + + // Set the client as registered + clients[arrindex(clients, sourcefd)].registered = 1; + + // Catch the client up with the default number of seconds of replay + if (!doreplay(sourcefd, settings->replayseconds, clients, settings, ircdstrings, channels)) { + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstrings->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + } + + return 1; + } + + // Pretty much ignore anything else the client says if it's not registered yet, + // as the first thing we want to hear is either PASS or USER + if (!clients[arrindex(clients, sourcefd)].registered) { + debugprint(DEBUG_SOME, "Ignoring client command '%s' from sourcefd '%d' as not registered yet.\n", tokens[0], sourcefd); + return 1; + } + + // Client PING received? If so, send a PONG back with the next element as the argument. + if (strncasecmp(tokens[0], "PING", strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Client PING found and it is: %s with length %zd! Sending response...\n", tokens[0], strlen(tokens[0])); + + char outgoingmsg[MAXDATASIZE]; // String to send to client + if (!snprintf(outgoingmsg, MAXDATASIZE, "PONG %s", tokens[1])) { // TODO - Make sure tokens[1] actually has a token + fprintf(stderr, "Error while preparing PONG response!\n"); + debugprint(DEBUG_CRIT, "Error while preparing PONG response!\n"); + outgoingmsg[0] = '\0'; + } + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + + // We processed something so return true + return 1; + } + + // TODO - Ignoring CAP for now so as not to confuse other clients, but we should probably query the server then relay the response to the client + if (strncasecmp(tokens[0], "CAP", strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Client CAP found and it is: %s with length %zd! Ignoring completely for now - TODO - do something useful!\n", tokens[0], strlen(tokens[0])); + return 1; + } + + // Just send NICK to server and let it change all clients' nicks if needed + if (strncasecmp(tokens[0], "NICK", strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Client NICK found and it is: %s with length %zd! Sending to server...\n", tokens[0], strlen(tokens[0])); + sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); + return 1; + } + + // If PRIVMSG received, send to server, but also reformat and send to all other clients and log to replay file. + if (strncasecmp(tokens[0], "PRIVMSG", strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Client PRIVMSG found and it is: %s with length %zd! Sending to server then back to other clients...\n", tokens[0], strlen(tokens[0])); + // Send original request straight to server + sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); + + // If it seems to be a CTCP VERSION request, just send to the server (CTCP requests are delimited with \1) + if (counter == 3 && strncmp(tokens[2], ":\1VERSION\1", strlen(tokens[2])) == 0) { + debugprint(DEBUG_FULL, "Client PRIVMSG looked like a CTCP VERSION request, so not sending to other clients.\n"); + return 1; + } + + // Rebuild to full PRIVMSG string and relay to all other clients + char outgoingmsg[MAXDATASIZE]; // String to send to client + + if (!snprintf(outgoingmsg, MAXDATASIZE, "%s %s", ircdstrings->nickuserhost, str)) { + fprintf(stderr, "Error while preparing PRIVMSG relay from another bouncer client.\n"); + debugprint(DEBUG_CRIT, "Error while preparing PRIVMSG relay from another bouncer client.\n"); + return 0; + } + // Send to all except source client + sendtoallclients(clients, outgoingmsg, sourcefd, settings); + + // Write to replay log if replay logging enabled + if (settings->replaylogging) { + writereplayline(outgoingmsg, settings->basedir); + } + + // Write to normal log if logging enabled + if (settings->logging) { + logline(outgoingmsg, settings->ircnick, settings->basedir, LOG_PRIVMSG); + } + + return 1; + } + + // Just send JOIN to server and let it talk back to clients as required + if (strncasecmp(tokens[0], "JOIN", strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Client JOIN found and it is: %s with length %zd! Sending to server...\n", tokens[0], strlen(tokens[0])); + sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); + return 1; + } + + // Don't do anything with QUIT, just let the client go - TODO: Let another clients know with a NOTICE or something + // A client has QUIT, so disconnect (close) them and don't do anything else for now - TODO: Let another clients know with a NOTICE or something + if (strncasecmp(tokens[0], "QUIT", strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Client QUIT found from fd %d and it is: %s with length %zd! Disconnecting that fd.\n", sourcefd, tokens[0], strlen(tokens[0])); + disconnectclient(sourcefd, clients, ircdstrings, settings); + return 1; + } + + // Just send PART to server and let it talk back to clients as required + if (strncasecmp(tokens[0], "PART", strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Client PART found and it is: %s with length %zd! Sending to server...\n", tokens[0], strlen(tokens[0])); + sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); + return 1; + } + + // Just send TOPIC to server and let it talk back to clients as required + if (strncasecmp(tokens[0], "TOPIC", strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Client TOPIC found and it is: %s with length %zd! Sending to server...\n", tokens[0], strlen(tokens[0])); + sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); + return 1; + } + + // Take note of what sort of MODE was requested, mark the client as waiting for the reply (so not all clients get it) + // Send it to the server either way and let it talk back to the requesting client as required + if (strncasecmp(tokens[0], "MODE", strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Client MODE found and it is: %s with length %zd! Analysing...\n", tokens[0], strlen(tokens[0])); + // Is it a ban MODE request (MODE #channel b)? + if (counter >= 3 && strncmp(tokens[2], "b", strlen("b")) == 0) { + debugprint(DEBUG_FULL, "Ban MODE request received, marking as pending.\n"); + clients[arrindex(clients, sourcefd)].pendingban = 1; + } else if (counter == 2) { + // Assume a normal channel mode request (MODE #channel) - TODO - What if it isn't!? + debugprint(DEBUG_FULL, "Assuming channel MODE request received, marking as pending.\n"); + clients[arrindex(clients, sourcefd)].pendingchannelmode = 1; + } + + // Either way, send it on the server + sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); + return 1; + } + + // WHO requested, mark the client as waiting for the reply (so not all clients get it) and send it on the server + if (strncasecmp(tokens[0], "WHO", strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Client WHO found and it is: %s with length %zd! Marking as pending.\n", tokens[0], strlen(tokens[0])); + clients[arrindex(clients, sourcefd)].pendingwho = 1; + + // Either way, send it on the server + sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); + return 1; + } + + // LIST requested, mark the client as waiting for the reply (so not all clients get it) and send it on the server + if (strncasecmp(tokens[0], "LIST", strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Client LIST found and it is: %s with length %zd! Marking as pending.\n", tokens[0], strlen(tokens[0])); + clients[arrindex(clients, sourcefd)].pendinglist = 1; + + // Either way, send it on the server + sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); + return 1; + } + + // Client WHOIS received, send straight on to server and mark the client as pending the response + if (strncasecmp(tokens[0], "WHOIS", strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Client WHOIS found and it is: %s with length %zd! Sending to server and setting client as pending.\n", tokens[0], strlen(tokens[0])); + clients[arrindex(clients, sourcefd)].pendingwhois = 1; + sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); + return 1; + } + + // Client WHOWAS received, send straight on to server and mark the client as pending the response + if (strncasecmp(tokens[0], "WHOWAS", strlen(tokens[0])) == 0) { + debugprint(DEBUG_FULL, "Client WHOWAS found and it is: %s with length %zd! Sending to server and setting client as pending.\n", tokens[0], strlen(tokens[0])); + clients[arrindex(clients, sourcefd)].pendingwhowas = 1; + sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); + return 1; + } + + // Client NOTICE received + if (strncasecmp(tokens[0], "NOTICE", strlen(tokens[0])) == 0) { + + // Rebuild to full NOTICE string including our nick!user@host for logging + char fullmsg[MAXDATASIZE]; + + if (!snprintf(fullmsg, MAXDATASIZE, "%s %s", ircdstrings->nickuserhost, str)) { + fprintf(stderr, "Error while preparing NOTICE string for logging.\n"); + debugprint(DEBUG_CRIT, "Error while preparing NOTICE string for logging.\n"); + return 0; + } + + // Write to replay log if replay logging enabled + if (settings->replaylogging) { + writereplayline(fullmsg, settings->basedir); + } + + // Write to normal log if logging enabled + if (settings->logging) { + logline(fullmsg, settings->ircnick, settings->basedir, LOG_PRIVMSG); + } + + // If it's a CTCP VERSION response then only send to the server (CTCP requests are delimited with \1) + if (counter >= 3 && strncmp(tokens[2], ":\1VERSION", strlen(tokens[2])) == 0) { + debugprint(DEBUG_FULL, "Client NOTICE looked like a CTCP VERSION response, so just sending to the server.\n"); + sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); + return 1; + } + + // If it wasn't a CTCP VERSION response, then let this fall through to the default unhandled action by not returning here + } + + // Custom BLABOUNCER command received + // Case insensitive comparisons here since clients won't be recognising and uppercasing these commands + if (strncasecmp(tokens[0], "BLABOUNCER", strlen(tokens[0])) == 0) { + char outgoingmsg[MAXDATASIZE]; + debugprint(DEBUG_FULL, "Client BLABOUNCER found and it is: %s with length %zd!\n", tokens[1], strlen(tokens[1])); + // REPLAY received, send the requested length of replay time to the client + if (strncasecmp(tokens[1], "REPLAY", strlen("REPLAY")) == 0) { + debugprint(DEBUG_FULL, "Client BLABOUNCER REPLAY (custom blabouncer command) found and it is: %s with length %zd!\n", tokens[1], strlen(tokens[1])); + + // Split the request into days:hours:minutes:seconds + + // Track which colon-separated token within this request we're on + int timecounter = 0; + + // Build array of colon-separated tokens + char timetokens[MAXTOKENS][MAXDATASIZE]; + // Copy to a temporary string so we still have the original in case it's not processed + char *timestrcopy = strdup(tokens[2]); + // Keep track of initial pointer for free()ing later + char *timestrcopyPtr = timestrcopy; + + char *timetoken; + while ((timetoken = strsep(×trcopy, ":")) != NULL) { + if (*timetoken == '\0') continue; // Skip consecutive matches + if (timecounter >= MAXTOKENS) break; // Too many tokens + debugprint(DEBUG_FULL, "Time token: \"%s\", length %zd.\n", timetoken, strlen(timetoken)); + // Copy into the token array (strlen + 1 to get the NULL terminator) + strncpy(timetokens[timecounter], timetoken, strlen(timetoken) + 1); + timecounter++; + } + + // Make sure we don't have more than four (d:h:m:s) components + if (timecounter > 4) { + debugprint(DEBUG_SOME, "Too many time components requested by REPLAY command. Telling client.\n"); + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Too many time components requestd by REPLAY command. Expected up to four (days:hours:minutes:seconds).", ircdstrings->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + free(timestrcopyPtr); + return 1; + } + + // Make sure all the components are numbers + for (int i = 0; i < timecounter; i++) { + // Temporary number and pointer for checking errors + long check; + char *str_end; + errno = 0; + check = strtol(timetokens[i], &str_end, 10); + if (str_end == timetokens[i] || ((check == LONG_MAX || check == LONG_MIN) && errno == ERANGE)) { + debugprint(DEBUG_SOME, "Invalid number '%s' requested by REPLAY command. Telling client.\n", timetokens[i]); + if (!snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Invalid number '%s' requested by REPLAY command.", ircdstrings->ircnick, timetokens[i])) { + fprintf(stderr, "Error while preparing REPLAY invalid number response!\n"); + debugprint(DEBUG_CRIT, "Error while preparing REPLAY invalid number response!\n"); + outgoingmsg[0] = '\0'; + } + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + free(timestrcopyPtr); + return 1; + } + } + + // How many seconds we're going to replay + int replayseconds = 0; + + // If d:h:m:s provided + if (timecounter == 4) { + replayseconds += 86400 * strtol(timetokens[0], NULL, 10); + replayseconds += 3600 * strtol(timetokens[1], NULL, 10); + replayseconds += 60 * strtol(timetokens[2], NULL, 10); + replayseconds += strtol(timetokens[3], NULL, 10); + } + // If h:m:s provided + if (timecounter == 3) { + replayseconds += 3600 * strtol(timetokens[0], NULL, 10); + replayseconds += 60 * strtol(timetokens[1], NULL, 10); + replayseconds += strtol(timetokens[2], NULL, 10); + } + // If m:s provided + if (timecounter == 2) { + replayseconds += 60 * strtol(timetokens[0], NULL, 10); + replayseconds += strtol(timetokens[1], NULL, 10); + } + // If s provided + if (timecounter == 1) { + replayseconds += strtol(timetokens[0], NULL, 10); + } + + debugprint(DEBUG_FULL, "Replaying '%s' which is '%d' seconds.\n", tokens[2], replayseconds); + + if (!doreplay(sourcefd, replayseconds, clients, settings, ircdstrings, channels)) { + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstrings->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + } + free(timestrcopyPtr); + return 1; + // QUIT received, send QUIT message to server and exit cleanly + } else if (strncasecmp(tokens[1], "QUIT", strlen("QUIT")) == 0) { + debugprint(DEBUG_SOME, "Client BLABOUNCER QUIT found and it is: %s with length %zd!\n", tokens[1], strlen(tokens[1])); + // Combine "QUIT :" with any optional quit message the user provided + if (counter > 2) { + // Any optional quit message comes 16 characters after the start of the string ("BLABOUNCER QUIT " is 16 characters) + cleanexit(server_ssl, clients, sourcefd, ircdstrings, settings, str + 16); + } else { + cleanexit(server_ssl, clients, sourcefd, ircdstrings, settings, ""); + } + + // Unrecognised BLABOUNCER command received, send some help instructions + } else { + debugprint(DEBUG_SOME, "Client BLABOUNCER unrecognised command found and it is: %s with length %zd! Sending a help message.\n", tokens[1], strlen(tokens[1])); + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unrecognised BLABOUNCER command received. Valid commands are:", ircdstrings->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REPLAY [[[[days:]hours:]minutes:]seconds]\" (To replay a given length of time of replay log.)", ircdstrings->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER QUIT [quit message]\" (To quit blabouncer, optionally sending [quit message] to the server.)", ircdstrings->ircnick); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + return 1; + } + } + + // We didn't process anything so return 0 + return 0; +} |