diff options
author | Luke Bratch <luke@bratch.co.uk> | 2019-06-10 21:17:10 +0100 |
---|---|---|
committer | Luke Bratch <luke@bratch.co.uk> | 2019-06-10 21:17:10 +0100 |
commit | c84d8c493ccdc840a866a9f51f10fe6b1f2bc377 (patch) | |
tree | b064e1f554b19378beb0909c78061001b331eb3a | |
parent | 853efa64ff8ad6ebde47d1265f8b13e954a0d2f0 (diff) |
Refactoring - split giant processircmessage() switch statement into separate server and client functions in message.h/message.c.
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | blabouncer.c | 1313 | ||||
-rw-r--r-- | functions.c | 24 | ||||
-rw-r--r-- | functions.h | 17 | ||||
-rw-r--r-- | message.c | 1264 | ||||
-rw-r--r-- | message.h | 37 |
6 files changed, 1346 insertions, 1315 deletions
@@ -1,8 +1,8 @@ CC=gcc -DEPS = functions.h sockets.h config.h replay.h logging.h +DEPS = functions.h sockets.h config.h replay.h logging.h structures.h message.h %.o: %.c $(DEPS) $(CC) -Wall -Wextra -c -o $@ $< -blabouncer: blabouncer.o functions.o sockets.o config.o replay.o logging.o - $(CC) -Wall -Wextra -lssl -lcrypto -o blabouncer blabouncer.o functions.o sockets.o config.o replay.o logging.o +blabouncer: blabouncer.o functions.o sockets.o config.o replay.o logging.o message.o + $(CC) -Wall -Wextra -lssl -lcrypto -o blabouncer blabouncer.o functions.o sockets.o config.o replay.o logging.o message.o diff --git a/blabouncer.c b/blabouncer.c index 438107b..bf27835 100644 --- a/blabouncer.c +++ b/blabouncer.c @@ -46,6 +46,7 @@ #include "replay.h" #include "logging.h" #include "structures.h" +#include "message.h" #define SOURCE_SERVER 0 #define SOURCE_CLIENT 1 @@ -154,30 +155,6 @@ int connecttoircserver(SSL_CTX **serverctx, SSL **server_ssl, int *serversockfd, return 1; // TODO - Return 0 if this fails and make callers do something with that } -// Exit the program cleanly - tell clients, tell the server, then exit(0) -// Optional quit message string "quitmsg" -// "sourcefd" of 0 means the request didn't come from a client -void cleanexit(SSL *server_ssl, struct client *clients, int sourcefd, struct ircdstrings *ircdstrings, struct settings *settings, char *quitmsg) { - char outgoingmsg[MAXDATASIZE]; - - // Tell clients and debug log - if (sourcefd) { - snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Exiting on request from client with fd '%d', message '%s'.", ircdstrings->ircnick, sourcefd, quitmsg); - debugprint(DEBUG_CRIT, "Exiting on request from client with fd '%d', message '%s'.\n", sourcefd, quitmsg); - } else { - snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Exiting on request (not from a client), message '%s'.", ircdstrings->ircnick, quitmsg); - debugprint(DEBUG_CRIT, "Exiting on request (not from a client), message '%s'.\n", quitmsg); - } - sendtoallclients(clients, outgoingmsg, EXCEPT_NONE, settings); - - // Tell the server - // Combine "QUIT :" with any (optional) quit message - snprintf(outgoingmsg, MAXDATASIZE, "QUIT :%s", quitmsg); - sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), sourcefd, clients, settings); - - exit(0); -} - // Figure out what to do with each CRLF-split IRC message (if anything) // by splitting out the different components by space character (ASCII 0x20). // @@ -222,1300 +199,22 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli switch(source) { case SOURCE_SERVER: // If message(s) were from the real IRC server - // 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); - + if (processservermessage(server_ssl, str, clients, sourcefd, ircdstrings, channels, settings, tokens, counter)) { // We processed something so return true free(strcopyPtr); 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); - free(strcopyPtr); - 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'; - free(strcopyPtr); - 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'; - free(strcopyPtr); - 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); - } - free(strcopyPtr); - 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"); - } - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - } - } - } - free(strcopyPtr); - return 1; - // Server 366 (RPL_ENDOFNAMES), relay to all clients if we've just JOINed the channel, or relay to - // and decrement from any clients who were waiting on RPL_NAMREPLY if it's an existing channel. - } else if (strncmp(tokens[1], "366", strlen(tokens[1])) == 0) { - int channelelement; - // 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); - } - } - } - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - } - - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - - free(strcopyPtr); - 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); - - free(strcopyPtr); - return 1; - } - } - - // Relay to all current clients if not processed by the above - sendtoallclients(clients, str, sourcefd, settings); - - free(strcopyPtr); - 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; - } - } - - free(strcopyPtr); - 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; - } - } - - free(strcopyPtr); - 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; - } - } - } - - free(strcopyPtr); - 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; - } - } - } - - free(strcopyPtr); - 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; - } - } - } - - free(strcopyPtr); - 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; - } - } - } - - free(strcopyPtr); - 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); - } - - free(strcopyPtr); - 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); - } - - free(strcopyPtr); - 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); - - free(strcopyPtr); - 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); - } - - free(strcopyPtr); - return 1; - } else { - debugprint(DEBUG_FULL, "Server NOTICE does not appear to be from a user, passing through.\n"); - } - } - } - - // Don't return if we got here because this means we didn't process something above - + // Don't return if we got here because this means we didn't process something in processservermessage() break; case SOURCE_CLIENT: // If message(s) were from a real IRC client - // 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); - } - - free(strcopyPtr); - 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"); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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"); - free(strcopyPtr); - 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"); - free(strcopyPtr); - 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"); - free(strcopyPtr); - 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"); - free(strcopyPtr); - 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"); - free(strcopyPtr); - 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); - } - - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - + if (processclientmessage(server_ssl, str, clients, sourcefd, ircdstrings, channels, settings, tokens, counter)) { // We processed something so return true free(strcopyPtr); 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])); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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"); - free(strcopyPtr); - 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"); - free(strcopyPtr); - 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); - } - - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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"); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - 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); - free(strcopyPtr); - return 1; - } - } - + // Don't return if we got here because this means we didn't process something in processclientmessage() break; default: fprintf(stderr, "Unexpected raw IRC string source!\n"); @@ -1526,7 +225,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli // =============================================> - debugprint(DEBUG_FULL, "Done with processircmessage()\n"); + debugprint(DEBUG_FULL, "Done with processircmessage(), didn't process anything.\n"); // If we got here then we didn't process anything free(strcopyPtr); diff --git a/functions.c b/functions.c index 0aefa05..4ea5a51 100644 --- a/functions.c +++ b/functions.c @@ -912,3 +912,27 @@ void tryautonick(struct ircdstrings *ircdstrings) { debugprint(DEBUG_FULL, "tryautonick(): set irdstrings->ircnick to '%s'.\n", ircdstrings->ircnick); } + +// Exit the program cleanly - tell clients, tell the server, then exit(0) +// Optional quit message string "quitmsg" +// "sourcefd" of 0 means the request didn't come from a client +void cleanexit(SSL *server_ssl, struct client *clients, int sourcefd, struct ircdstrings *ircdstrings, struct settings *settings, char *quitmsg) { + char outgoingmsg[MAXDATASIZE]; + + // Tell clients and debug log + if (sourcefd) { + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Exiting on request from client with fd '%d', message '%s'.", ircdstrings->ircnick, sourcefd, quitmsg); + debugprint(DEBUG_CRIT, "Exiting on request from client with fd '%d', message '%s'.\n", sourcefd, quitmsg); + } else { + snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Exiting on request (not from a client), message '%s'.", ircdstrings->ircnick, quitmsg); + debugprint(DEBUG_CRIT, "Exiting on request (not from a client), message '%s'.\n", quitmsg); + } + sendtoallclients(clients, outgoingmsg, EXCEPT_NONE, settings); + + // Tell the server + // Combine "QUIT :" with any (optional) quit message + snprintf(outgoingmsg, MAXDATASIZE, "QUIT :%s", quitmsg); + sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), sourcefd, clients, settings); + + exit(0); +} diff --git a/functions.h b/functions.h index cba1d72..91ce35f 100644 --- a/functions.h +++ b/functions.h @@ -36,11 +36,6 @@ #include "structures.h" #include "replay.h" -#define MAXDATASIZE 513 // Maximum number of bytes we can get at once (RFC2812 says 512, plus one for null terminator) -#define MAXCLIENTS 32 // Maximum number of clients that can connect to the bouncer at a time -#define MAXCHANNELS 1024 // Let's assume 1024 is reasonable for now (it's configured per IRCd) -#define MAXRFCNICKLEN 9 // From RFC 1459 - // getstdin() return codes #define OK 0 #define NO_INPUT 1 @@ -50,6 +45,13 @@ #define DEBUG_SOME 1 #define DEBUG_FULL 2 +#define EXCEPT_NONE 0 + +#define MAXDATASIZE 513 // Maximum number of bytes we can get at once (RFC2812 says 512, plus one for null terminator) +#define MAXCLIENTS 32 // Maximum number of clients that can connect to the bouncer at a time +#define MAXCHANNELS 1024 // Let's assume 1024 is reasonable for now (it's configured per IRCd) +#define MAXRFCNICKLEN 9 // From RFC 1459 + // Write debug string to file. // Debug level is provided by level, set to one of DEBUG_CRIT, DEBUG_SOME or DEBUG_FULL. // Debug is only written if the global int "debug" is greater than or equal to the level. @@ -144,4 +146,9 @@ int joinautochannels(SSL *server_ssl, struct client *clients, struct settings *s // 1 through to 9. void tryautonick(struct ircdstrings *ircdstrings); +// Exit the program cleanly - tell clients, tell the server, then exit(0) +// Optional quit message string "quitmsg" +// "sourcefd" of 0 means the request didn't come from a client +void cleanexit(SSL *server_ssl, struct client *clients, int sourcefd, struct ircdstrings *ircdstrings, struct settings *settings, char *quitmsg); + #endif 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; +} diff --git a/message.h b/message.h new file mode 100644 index 0000000..349a764 --- /dev/null +++ b/message.h @@ -0,0 +1,37 @@ +/* + * 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/>. +*/ + +#ifndef MESSAGE_H_INCLUDED +#define MESSAGE_H_INCLUDED + +#include "functions.h" +#include "config.h" +#include "logging.h" + +#define MAXTOKENS 100 // maximum number of (CRLF or space) separated tokens per server response we expect (TODO - check this is reasonable) (CRLF and spaces just grouped here due to laziness) + +// 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); + +// 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); + +#endif |