/* * This file is part of blabouncer (https://www.blatech.co.uk/l_bratch/blabouncer). * Copyright (C) 2019 Luke Bratch . * * 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 . */ #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 ircdstate *ircdstate, struct channel *channels, struct settings *settings, char tokens[MAXTOKENS][MAXDATASIZE], int counter) { // Record that we received something from the server for timeout checking purposes ircdstate->lastmessagetime = time(NULL); // snprintf(NULL, 0, "%ld", timenow); // And we can't be timing out ircdstate->timeoutcheck = 0; // 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 ircdstate 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 ircdstate struct.\n", str, strlen(str)); strncpy(ircdstate->greeting001, str, strlen(str)); // Null the end of the string ircdstate->greeting001[strlen(str)] = '\0'; debugprint(DEBUG_FULL, "Storing our nick!user@host (:%s) from greeting 001 in ircdstate struct.\n", tokens[counter - 1]); // Prepend a colon (:) first since everything (so far) needs one if (!snprintf(ircdstate->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 ircdstate->nickuserhost[strlen(tokens[counter - 1]) + 1] = '\0'; // +1 for the inserted colon debugprint(DEBUG_FULL, "nickuserhost '%s' stored.\n", ircdstate->nickuserhost); return 1; } else if (strncmp(tokens[1], "002", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Found greeting 002 (%s), storing in ircdstate struct.\n", str); strncpy(ircdstate->greeting002, str, strlen(str)); // Null the end of the string ircdstate->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 ircdstate struct.\n", str); strncpy(ircdstate->greeting003, str, strlen(str)); // Null the end of the string ircdstate->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 ircdstate struct.\n", str); strncpy(ircdstate->greeting004, str, strlen(str)); // Null the end of the string ircdstate->greeting004[strlen(str)] = '\0'; debugprint(DEBUG_FULL, "Storing the real IRCd's name (%s) from greeting 004 in ircdstate struct.\n", tokens[3]); strncpy(ircdstate->ircdname, tokens[3], strlen(tokens[3])); // Null the end of the string ircdstate->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 (ircdstate->reconnecting) { // First tell clients if our nick changed if (!strcmp(ircdstate->ircnick, ircdstate->oldnick)) { debugprint(DEBUG_SOME, "Telling clients about nick change.\n"); char nickmsg[MAXDATASIZE]; snprintf(nickmsg, MAXDATASIZE, ":%s NICK :%s", ircdstate->oldnick, ircdstate->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 (!doautoreplay(clients[i].fd, clients, settings, ircdstate, channels)) { snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstate->ircnick); sendtoclient(sourcefd, alertmsg, clients, settings, 0); } snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Reconnection complete.", ircdstate->ircnick); sendtoclient(clients[i].fd, alertmsg, clients, settings, 0); } } // Reconnection complete ircdstate->oldnick[0] = '\0'; ircdstate->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 ircdstate struct.\n", str); // Find an empty greeting005 string in ircdstate and store in there... if (!ircdstate->greeting005a[0]) { strncpy(ircdstate->greeting005a, str, strlen(str)); ircdstate->greeting005a[strlen(str)] = '\0'; } else if (!ircdstate->greeting005b[0]) { strncpy(ircdstate->greeting005b, str, strlen(str)); ircdstate->greeting005b[strlen(str)] = '\0'; } else if (!ircdstate->greeting005c[0]) { strncpy(ircdstate->greeting005c, str, strlen(str)); ircdstate->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, ircdstate->ircnick, strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Server JOIN: nick is ours ('%s' vs '%s').\n", prefixcopy, ircdstate->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, ircdstate->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, ircdstate->ircnick, strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Server PART: nick is ours ('%s' vs '%s').\n", prefixcopy, ircdstate->ircnick); removechannel(channels, tokens[2]); } else { debugprint(DEBUG_FULL, "Server PART: nick is NOT ours ('%s' vs '%s').\n", prefixcopy, ircdstate->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(ircdstate->ircnick, svrprefixcopy, strlen(ircdstate->ircnick)) == 0) { debugprint(DEBUG_FULL, "Server NICK: nick is ours ('%s' vs '%s').\n", svrprefixcopy, ircdstate->ircnick); // Make a copy of the old nickuserhost for updategreetings() below char *nickuserhostcpy = strdup(ircdstate->nickuserhost); // Update nickuserhost with the new :nick!user@host updatenickuserhost(ircdstate->nickuserhost, tokens[2]); debugprint(DEBUG_FULL, "Updated nickuserhost to '%s'.\n", ircdstate->nickuserhost); // Prepare to update ircnick and greetings strings // Temporary copy of new nickuserhost char *prefixcopy = strdup(ircdstate->nickuserhost); // Get nick from it extractnickfromprefix(prefixcopy); // Update greeting strings for relaying to new clients updategreetings(ircdstate->greeting001, ircdstate->greeting002, ircdstate->greeting003, ircdstate->greeting004, ircdstate->greeting005a, ircdstate->greeting005b, ircdstate->greeting005c, ircdstate->nickuserhost, nickuserhostcpy, tokens[2], ircdstate->ircnick); // Update our nick strcpy(ircdstate->ircnick, prefixcopy); debugprint(DEBUG_FULL, "Updated ircnick to '%s'.\n", ircdstate->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 :", ircdstate->ircnick, ircdstate->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 ircdstate for when clients connect and relay to current clients. strcpy(ircdstate->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(ircdstate->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])); // Do we have both a nick2 and a nick3? (And not tried autonick yet.) if (settings->ircnick2[0] && settings->ircnick3[0] && !ircdstate->autonicknum) { // Has nick3 already been tried? if (strncmp(ircdstate->ircnick, settings->ircnick3, strlen(settings->ircnick)) == 0) { // Then try autonick tryautonick(ircdstate); // Has nick2 already been tried? } else if (strncmp(ircdstate->ircnick, settings->ircnick2, strlen(settings->ircnick)) == 0) { // Then try nick3 debugprint(DEBUG_SOME, "Trying nick3, nick2 was already tried.\n"); strcpy(ircdstate->ircnick, settings->ircnick3); // Have neither been tried? } else { // Then try nick2 debugprint(DEBUG_SOME, "Trying nick2, nick3 is also configured.\n"); strcpy(ircdstate->ircnick, settings->ircnick2); } // Do we only have a nick2? (And not tried autonick yet.) } else if (settings->ircnick2[0] && !ircdstate->autonicknum) { // Has it already been tried? if (strncmp(ircdstate->ircnick, settings->ircnick2, strlen(settings->ircnick)) == 0) { // Then try autonick tryautonick(ircdstate); } else { // Then try it debugprint(DEBUG_SOME, "Trying nick2, nick3 is not configured.\n"); strcpy(ircdstate->ircnick, settings->ircnick2); } // Do we have neither? (Or have already started autonick.) } else { // Then try autonick tryautonick(ircdstate); } // Set whichever one we settled on in the settings in case we reference settings later strcpy(settings->ircnick, ircdstate->ircnick); // Try it with the server char outgoingmsg[MAXDATASIZE]; snprintf(outgoingmsg, MAXDATASIZE, "NICK %s", ircdstate->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 ACK :multi-prefix" then it must have approved our CAP multi-prefix request if (counter == 5) { if (strncmp(tokens[2], ircdstate->ircnick, strlen(tokens[2])) == 0 && strncmp(tokens[3], "ACK", strlen(tokens[3])) == 0 && strncmp(tokens[4], ":multi-prefix", strlen(tokens[4])) == 0) { ircdstate->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"); } } // Server PONG received? This is probably in response to our PING to see if we're still connected, discard it. if (strncmp(tokens[1], "PONG", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server PONG found and it is: %s with length %zd! Discarding.\n", tokens[1], strlen(tokens[1])); return 1; } } // 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 ircdstate *ircdstate, struct channel *channels, struct settings *settings, char tokens[MAXTOKENS][MAXDATASIZE], int counter, struct clientcodes *clientcodes) { // 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)) { 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.", ircdstate->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, ircdstate, settings, clientcodes); // 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.", ircdstate->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 (ircdstate->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", ircdstate->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 ", ircdstate->ircdname, ircdstate->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(ircdstate->greeting004)) { sendtoclient(sourcefd, "Sorry, we aren't registered with a real IRC server yet.", clients, settings, 0); disconnectclient(sourcefd, clients, ircdstate, settings, clientcodes); 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", ircdstate->greeting001); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstate->greeting002); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstate->greeting003); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstate->greeting004); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); if (ircdstate->greeting005a[0]) { snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstate->greeting005a); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } if (ircdstate->greeting005b[0]) { snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstate->greeting005b); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } if (ircdstate->greeting005c[0]) { snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstate->greeting005c); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } // Send our own greeting message snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Welcome to blabouncer version %s!", ircdstate->ircnick, VERSION); 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\"", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Valid blabouncer commands are:", ircdstate->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.)", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REHASH\" (To reload settings from the configuration file, see README for which settings can be reloaded.)", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER CLIENTCODE [clientcode]\" (To set an identifier for the current client for auto replaying just what this client has missed.)", ircdstate->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.)", ircdstate->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", ircdstate->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.", ircdstate->ircdname, ircdstate->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", ircdstate->ircdname, ircdstate->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", ircdstate->ircdname, ircdstate->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(ircdstate->mode) > 0) { if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s MODE %s %s", ircdstate->ircnick, ircdstate->ircnick, ircdstate->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 (!doautoreplay(sourcefd, clients, settings, ircdstate, channels)) { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } // And record the time of the last client registration ircdstate->clientchangetime = time(NULL); 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", ircdstate->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, ircdstate, settings, clientcodes); 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", ircdstate->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 } // Client PROTOCTL received if (strncasecmp(tokens[0], "PROTOCTL", strlen(tokens[0])) == 0) { // If it's a PROTOCTL NAMESX, just pass to the server - TODO - Maybe deal with either PROTCTL NAMEX or multi-prefix CAP for mode prefixes? if (strncasecmp(tokens[1], "NAMESX", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Client PROTOCTL NAMESX found! Sending to server.\n", tokens[0], strlen(tokens[0])); sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); return 1; } } // 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 && counter == 3) { 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).", ircdstate->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.", ircdstate->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, ircdstate, channels)) { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstate->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, ircdstate, settings, str + 16); } else { cleanexit(server_ssl, clients, sourcefd, ircdstate, settings, ""); } // REHASH received, re-read the configuration file and let rehash() to the appropriate things } else if (strncasecmp(tokens[1], "REHASH", strlen("REHASH")) == 0) { debugprint(DEBUG_SOME, "Client BLABOUNCER REHASH found and it is: %s with length %zd! Attempting rehash...\n", tokens[1], strlen(tokens[1])); // TODO - This code is duplicated between here and SIGHUP handling char failuremsg[MAXDATASIZE]; failuremsg[0] = '\0'; // Try to rehash... if (!rehash(settings, failuremsg)) { // ...or log and tell client if it failed debugprint(DEBUG_CRIT, "REHASH failed: %s.\n", failuremsg); if (!snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :REHASH failed: %s.", ircdstate->ircnick, failuremsg)) { debugprint(DEBUG_CRIT, "Error while preparing REHASH failure message response!\n"); outgoingmsg[0] = '\0'; } sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } else { // ...or tell all clients it worked snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :REHASH complete!", ircdstate->ircnick); sendtoallclients(clients, outgoingmsg, 0, settings); } return 1; // CLIENTCODE received, set the provided string as the client code for handling auto replays for when this client code is next seen } else if (strncasecmp(tokens[1], "CLIENTCODE", strlen("CLIENTCODE")) == 0 && counter == 3) { debugprint(DEBUG_FULL, "Client BLABOUNCER CLIENTCODE found and it is: %s %s! Setting as this client's client code.\n", tokens[1], tokens[2]); // Make sure replaymode = "perclient" is set if (strcmp(settings->replaymode, "perclient")) { debugprint(DEBUG_SOME, "CLIENTCODE requested but replaymode not set to \"perclient\".\n"); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :CLIENTCODE requested but replaymode not set to \"perclient\".", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; } // Make sure the client code length is good if (strlen(tokens[2]) < 1 || strlen(tokens[2]) > CLIENTCODELEN - 1) { debugprint(DEBUG_SOME, "Invalid CLIENTODE length.\n"); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Invalid CLIENTODE length. Must be 1 to %d characters.", ircdstate->ircnick, CLIENTCODELEN - 1); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; } // Register the client code (if it doesn't already exist) int ret = addclientcode(sourcefd, tokens[2], clientcodes, clients); if (ret == -1) { // Something went wrong debugprint(DEBUG_CRIT, "error: addclientcode() returned 0.\n"); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Problem registering client code.", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; } else if (ret == 0) { // If it did already exist, do a replay of everything since this client code last disconnected int codetime = getclientcodetime(tokens[2], clientcodes); if (!codetime) { debugprint(DEBUG_CRIT, "Error finding last disconnect time of this client code!\n"); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Error finding last disconnect time of this client code!", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; } if (!doreplay(sourcefd, time(NULL) - codetime, clients, settings, ircdstate, channels)) { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; } } return 1; // 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:", ircdstate->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.)", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REHASH\" (To reload settings from the configuration file, see README for which settings can be reloaded.)", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER CLIENTCODE [clientcode]\" (To set an identifier for the current client for auto replaying just what this client has missed.)", ircdstate->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.)", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; } } // We didn't process anything so return 0 return 0; }