// TODO: // - Perhaps rename clients.ssl and server_ssl since they may not even be OpenSSL sockets // "server" means the real IRC server // "client" means bouncer clients #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "functions.h" #include "sockets.h" #include "config.h" #include "replay.h" #include "logging.h" #define SOURCE_SERVER 0 #define SOURCE_CLIENT 1 #define EXCEPT_NONE 0 #define LOG_PRIVMSG 0 #define LOG_JOINPART 1 #define LOG_TOPIC 2 // It seems to be that *message length* is max 512 bytes, but a socket read from at least UnrealIRCd seems to be up to at least 2416 (+1 for null) bytes. // 1208 bytes with OpenSSL, 2416 bytes with plain text. #define MAXRCVSIZE 2417 #define MAXDATASIZE 513 // max number of bytes we can get at once (RFC2812 says 512, plus one for null terminator) #define STDIN 0 // stdin is fd 0 #define MAXCLIENTS 32 // maximum number of clients that can connect to the bouncer at a time #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) #define MAXPONGSIZE 32 // let's assume PING/PONG responses can't be larger than this (TODO - check this [it's totally made up]!) #define MAXCHANNELS 1024 // let's assume 1024 is reasonable for now (it's configured per IRCd) #define MAXCHANLENGTH 50 // 50 according to RFC 2811 and RFC 2822 #define MAXCHANUSERS 8192 // Randomly picked (TODO - is there an actual maximum number of users per channel?) #define MAXNICKLENGTH 64 // Randomly picked (TODO - is there an actual maximum number (ignoring the RFC preference of 9)?) #define MAXRFCNICKLEN 9 // From RFC 1459 #define MAXUSERNAMELEN 64 // Randomly picked (TODO - is there an actual maximum username length?) #define MAXREALNAMELEN 128 // Randomly picked (TODO - is there an actual maximum real name length?) #define MAXPORTLEN 6 // Up to 65535, so 5 characters + 1 for null #define MAXAUTOCHANLEN 1024 // Randomly picked maximum length of the auto channel list #define SERVERTIMEOUT 300 // How many seconds to wait without hearing from the server before assuming a timeout #define DEBUGFILESKEEP 5 // How many debug files to keep around // Global debug control int debug = 0; char debugpath[PATH_MAX]; struct channel { char name[MAXCHANLENGTH]; char topic[MAXDATASIZE]; // TODO - Is there a particular maximum topic length? char topicwho[MAXNICKLENGTH]; // TODO - Make this Year 2038 proof // TODO - Make this an int? It's just going to arrive and leave as a string every time anyway... char topicwhen[11]; // 32-bit unixtime is up to 10 characters (+1 for null char) We use "0" to mean "not set". int gotnames; // Have we finished getting the RPL_NAMREPLYs for this channel yet? }; // TODO - Rename this or split into multiple structs since it's no longer strictly just IRCd strings struct ircdstrings { char greeting001[MAXDATASIZE]; char greeting002[MAXDATASIZE]; char greeting003[MAXDATASIZE]; char greeting004[MAXDATASIZE]; char greeting005a[MAXDATASIZE]; char greeting005b[MAXDATASIZE]; char greeting005c[MAXDATASIZE]; char ircdname[MAXDATASIZE]; char nickuserhost[MAXDATASIZE]; char ircnick[MAXNICKLENGTH]; char ircusername[MAXUSERNAMELEN]; char currentmsg[MAXDATASIZE]; // Holding area for the current server-received IRC message being processed in case it needs building across multiple reads (i.e. a truncated/split message) char mode[MAXDATASIZE]; int capmultiprefix; // Whether the server approved our CAP multi-prefix request int autonicknum; // Number of attempts made at automatically setting a nick if all configured nicks were in use int lastmessagetime; // The last time we heard from the server int reconnecting; // Whether or not we're reconnecting due to an earlier disconnection char oldnick[MAXNICKLENGTH]; // Set temporarily if we end up reconnecting in case we need to tell existing clients about a nick change }; // Structure of settings either to be read from the configuration file or set/changed at runtime struct settings { int replayseconds; char clientport[MAXPORTLEN]; char ircnick[MAXNICKLENGTH]; // In both settings and ircdstrings as settings is from our file whereas server may change ircdstrings copy char ircusername[MAXUSERNAMELEN]; // (Is this also true for the username? Can the server change that?) char ircrealname[MAXREALNAMELEN]; char autochannels[MAXAUTOCHANLEN]; char ircserver[HOST_NAME_MAX]; char ircserverport[MAXPORTLEN]; char ircserverpassword[MAXDATASIZE - 5]; // -5 for "PASS " char conffile[PATH_MAX]; char certfile[PATH_MAX]; char keyfile[PATH_MAX]; int clienttls; int servertls; char basedir[PATH_MAX]; int logging; int replaylogging; }; // Structure of connected clients, their socket/file descriptors, their authentication status, and their OpenSSL structures struct client { int fd; // Client socket fd - 0 means not connected, greater than 0 means connected and the value is the fd number (so we know which ones to try to read and send to) int authed; // Client authentication status - 0 means not authenticated, 1 means authenticated. SSL *ssl; // OpenSSL structures when using TLS, or faked by casting fd ints to SSL* if not. - TODO - Can we drop one of either "int fd" or "SSL *ssl" now? int registered; // Whether the client has finished registering with the bouncer int pendingchannelmode; // Whether the client is waiting to hear back from a "MODE #channel" command int pendingban; // Whether the client is waiting to hear back from a "MODE #channel b" command int pendingwho; // Whether the client is waiting to hear back from a "MODE #channel" command int pendinglist; // Whether the client is waiting to hear back from a "LIST" command int pendingwhois; // Whether the client is waiting to hear back from a "WHOIS" command int pendingwhowas; // Whether the client is waiting to hear back from a "WHOWAS" command int pendingnames; // Count of RPL_NAMREPLYs the client is waiting on. int pendingcap; // Whether the client is still negotiating IRCv3 CAPabilities. 0 = no, 1 = yes, -1 = just finished (so register them as if they had just sent USER). }; // Return index of requested client FD within the clients array. // TODO - Use this wherever we are calculating the position (various places) instead of // duplicating code. int arrindex(struct client *clients, int clientfd) { // Find the client in the clients array and make sure they are authenticated for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].fd == clientfd) { return i; } } // Something went wrong, we didn't find it return 0; } // Send whatever string to a specific client by providing the FD // If "bypass" == 1 then permit sending to client even if unauthenticated (for instance for a CAP LS response) int sendtoclient(int fd, char *strsrc, struct client *clients, struct settings *settings, int bypass) { // Copy to new string for passing to appendcrlf() to avoid overrun in appendcrlf() char str[MAXDATASIZE]; strcpy(str, strsrc); appendcrlf(str); // Do this just before sending so callers don't need to worry about it int i = 0; // Find the client in the clients array and make sure they are authenticated for (i = 0; i < MAXCLIENTS; i++) { if (clients[i].fd == fd) { // Found client in array, check authentication status if (!clients[i].authed && !bypass) { debugprint("sendtoclient(): skipping unauthenticated client with fd %d.\n", clients[i].fd); return 0; } // Break when we get to the correct fd, "i" is now the position in the clients array of our client break; } } debugprint("sendtoclient(): sending \"%s\" (length %zd) to client with fd %d.\n", str, strlen(str), fd); if (socksend(clients[i].ssl, str, strlen(str), settings->clienttls) == -1) { perror("error: sendtoclient() send()\n"); return 0; } return 1; } // Relay/send message to all clients (optionally except one) // "except" is used to send to all clients _except_ the fd provided (except = 0 (EXCEPT_NONE) avoids this, i.e. sends to all) // "except" is really the "sourcefd" and is also used as part of the authentication check - this is messy and they should perhaps be two separate arguments. // TODO - is passing str_len useful if we're appendcrlfing and then using strlen(str) in the send? I guess not... (As long as we're always null terminated in the correct place.) int sendtoallclients(struct client *clients, char *strsrc, int except, struct settings *settings) { char *sendertype; // Copy to new string for passing to appendcrlf() to avoid overrun in appendcrlf() char str[MAXDATASIZE]; strcpy(str, strsrc); appendcrlf(str); // Do this just before sending so callers don't need to worry about it // Decide what sort of text to prefix the debug output with // At the moment if non-zero "except" is specified then it must be a message from a bouncer client // and if "except" is zero then it must be a message from the real IRC server if (except) { sendertype = "bouncer-client"; } else { sendertype = "bouncer-server"; } // Find the sending client in the clients array and make sure they are authenticated for (int i = 0; i < MAXCLIENTS; i++) { // Trust clientfd of 0, only we can set that ourselves if (!except) { debugprint("sendtoallclients(): trusting clientfd of 0.\n"); break; } if (clients[i].fd == except) { // Found client in array, check authentication status if (!clients[i].authed) { printf("sendtoallclients(): skipping unauthenticated client with fd %d.\n", clients[i].fd); return 0; } } } // Relay/send to all clients... for (int i = 0; i < MAXCLIENTS; i++) { // Skip the current client if "except" non-zero (no need to send back to itself) if (clients[i].fd == except) { continue; } // ...but only if they are connected... if (clients[i].fd > 0) { // ...and authenticated if (!clients[i].authed) { debugprint("sendtoallclients(): skipping unauthenticated client with fd %d.\n", clients[i].fd); continue; } debugprint("sendtoallclients(): %s: sending '%s' to client with fd %d.\n", sendertype, str, clients[i].fd); if (socksend(clients[i].ssl, str, strlen(str), settings->clienttls) == -1) { printf("error: sendtoallclients() send() with fd '%d'.\n", clients[i].fd); } } } return 1; } // Send whatever string to the real IRC server // Client FD and arrays needed to make sure anything relayed from a client is from an authenticated client. // clientfd of "0" means trusted, used when we are sending things ourselves that weren't relayed // from a real client. int sendtoserver(SSL *server_ssl, char *strsrc, int str_len, int clientfd, struct client *clients, struct settings *settings) { // Copy to new string for passing to appendcrlf() to avoid overrun in appendcrlf() char str[MAXDATASIZE]; strcpy(str, strsrc); appendcrlf(str); // Do this just before sending so callers don't need to worry about it str_len = strlen(str); // Recalculate str_len in case it changed (TODO: so do we even need to pass it to this function?) // Find the sending client in the clients array and make sure they are authenticated for (int i = 0; i < MAXCLIENTS; i++) { // Trust clientfd of 0, only we can set that ourselves if (!clientfd) { debugprint("sendtoserver(): trusting clientfd of 0.\n"); break; } if (clients[i].fd == clientfd) { // Found client in array, check authentication status if (!clients[i].authed) { printf("sendtoserver(): skipping unauthenticated client with fd %d.\n", clients[i].fd); return 0; } } } debugprint("sendtoserver(): sending %s to IRC server (length %d).\n", str, str_len); if (socksend(server_ssl, str, str_len, settings->servertls) == -1) { printf("error: sendtoserver() send()\n"); return 0; } return 1; } // Disconnect the client fd "fd" by close()ing it and remove // it from the array of clients. // Also set its authentication and registration statuses to 0. // Also set the pending statuses to 0 int disconnectclient(int fd, struct client *clients, struct ircdstrings *ircdstrings, struct settings *settings) { debugprint("disconnectclient(): disconnecting client fd '%d'\n", fd); // Alert other clients about the disconnection (don't send yet, we haven't removed from the clients array yet) char alertmsg[MAXDATASIZE]; if (!snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :blabouncer: client with fd %d has disconnected.", ircdstrings->ircnick, fd)) { fprintf(stderr, "Error while preparing authentication failure NOTICE!\n"); exit(1); } // Remove the client from the clients array for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].fd == fd) { debugprint("found and clearing fd %d from clients[%d]\n", fd, i); clients[i].fd = 0; clients[i].authed = 0; clients[i].registered = 0; clients[i].pendingchannelmode = 0; clients[i].pendingban = 0; clients[i].pendingwho = 0; clients[i].pendinglist = 0; clients[i].pendingwhois = 0; clients[i].pendingwhowas = 0; clients[i].pendingnames = 0; clients[i].pendingcap = 0; if (settings->clienttls) { // Finish up with OpenSSL if using client TLS SSL_free(clients[i].ssl); } // Close the socket close(fd); // Now clients array is cleared, inform all other clients (source "0" since we trust this message) sendtoallclients(clients, alertmsg, 0, settings); return 1; } } // If we got here, we didn't find and clear the client // TODO - Do something with a failed return code return 0; } int createchannel(struct channel *channels, char *name, char *topic, char *topicwho, char *topicwhen) { debugprint("createchannel(): given \"%s\", \"%s\", \"%s\", and \"%s\".\n", name, topic, topicwho, topicwhen); // Make sure the channel doesn't already exist for (int i = 0; i < MAXCHANNELS; i++) { if (strncmp(channels[i].name, name, strlen(name)) == 0) { perror("error: createchannel(): channel name already exists.\n"); return 0; break; } } // Find a free slot in the array (when the channel name is not set (0th character is '\0'))... for (int i = 0; i < MAXCHANNELS; i++) { if (!channels[i].name[0]) { // ...and set the name and topic strncpy(channels[i].name, name, strlen(name)); channels[i].name[strlen(name)] = '\0'; debugprint("createchannel(): name given was '%s', length '%ld'.\n", name, strlen(name)); debugprint("createchannel(): name set to '%s', length '%ld'.\n", channels[i].name, strlen(channels[i].name)); strncpy(channels[i].topic, topic, strlen(topic)); channels[i].topic[strlen(topic)] = '\0'; debugprint("createchannel(): topic given was '%s', length '%ld'.\n", topic, strlen(topic)); debugprint("createchannel(): topic set to '%s', length '%ld'.\n", channels[i].topic, strlen(channels[i].topic)); strncpy(channels[i].topicwho, topicwho, strlen(topicwho)); channels[i].topicwho[strlen(topicwho)] = '\0'; strncpy(channels[i].topicwhen, topicwhen, strlen(topicwhen)); channels[i].topicwhen[strlen(topicwhen)] = '\0'; channels[i].gotnames = 0; return 1; break; // TODO - This should be safe to remove since return is hit first } } perror("error: createchannel() didn't create a channel\n"); // TODO - Make a failed return do something to callers return 0; } int setchanneltopicwhotime(struct channel *channels, char *channelname, char *who, char *when) { debugprint("setchanneltopicwhotime(): given \"%s\", \"%s\", and \"%s\".\n", channelname, who, when); debugprint("setchanneltopicwhotime(): who: '%s' with length '%ld'.\n", who, strlen(who)); debugprint("setchanneltopicwhotime(): when: '%s' with length '%ld'.\n", when, strlen(when)); for (int i = 0; i < MAXCHANNELS; i++) { if (strncmp(channels[i].name, channelname, strlen(channelname)) == 0) { strncpy(channels[i].topicwho, who, strlen(who)); channels[i].topicwho[strlen(who)] = '\0'; strncpy(channels[i].topicwhen, when, strlen(when)); channels[i].topicwhen[strlen(when)] = '\0'; return 1; } } // TODO - Make a failed return do something to callers return 0; } int setchanneltopic(struct channel *channels, char *channelname, char *topic) { debugprint("setchanneltopic(): given \"%s\" and \"%s\".\n", channelname, topic); for (int i = 0; i < MAXCHANNELS; i++) { if (strncmp(channels[i].name, channelname, strlen(channelname)) == 0) { strncpy(channels[i].topic, topic, strlen(topic)); channels[i].topic[strlen(topic)] = '\0'; return 1; } } // TODO - Make a failed return do something to callers return 0; } int getchannelcount(struct channel *channels) { int count = 0; for (int i = 0; i < MAXCHANNELS; i++) { if (channels[i].name[0]) { count++; } } debugprint("getchannelcount(): counted %d channels.\n", count); return count; } int removechannel(struct channel *channels, char *name) { debugprint("removechannel(): given \"%s\".\n", name); // Clear its topic setter and timestamp... setchanneltopicwhotime(channels, name, "", "0"); // Find the channel in the channel array... for (int i = 0; i < MAXCHANNELS; i++) { if (strncmp(channels[i].name, name, strlen(name)) == 0) { // ..and NULL its name (0th character = '\0') channels[i].name[0] = '\0'; debugprint("removechannel(): channel '%s' removed and topicwhen set to '%s'.\n", name, channels[i].topicwhen); return 1; } } perror("error: removechannel() didn't remove a channel\n"); // TODO - Make a failed return do something to callers return 0; } // Check if we have the NAMES for the channel 'name' already. // Return the 1 if we do, 0 if we don't, or -1 if there's an error. int channelgotnames(struct channel *channels, char *name) { debugprint("channelgotnames(): given '%s'.\n", name); for (int i = 0; i < MAXCHANNELS; i++) { if (strncmp(channels[i].name, name, strlen(name)) == 0) { if (channels[i].gotnames) { debugprint("channelgotnames(): channel '%s' gotnames was set, returning '%d'.\n", channels[i].name, channels[i].gotnames); return 1; } else { debugprint("channelgotnames(): channel '%s' gotnames was not set, returning '%d'.\n", channels[i].name, channels[i].gotnames); return 0; } } } // We didn't find the channel, this isn't good! TODO - Do something if this happens. printf("channelgotnames(): channel '%s' not found, this is bad, returning -1.\n", name); return -1; } // Check if we are in a channel named "name" or not. // Return 1 if we are, or 0 if not. int inchannel(struct channel *channels, char *name) { // Make sure the name doesn't have any trailing CR or LF // (But only if name is at least two characters long already) if (strlen(name) >= 2) { while (name[strlen(name) - 1] == '\r' || name[strlen(name) - 1] == '\n') { name[strlen(name) - 1] = '\0'; } } for (int i = 0; i < MAXCHANNELS; i++) { if (strncmp(channels[i].name, name, strlen(name)) == 0) { debugprint("inchannel(): in channel '%s'.\n", name); return 1; } } // We're not in the channel debugprint("inchannel(): NOT in channel '%s'.\n", name); return 0; } // Returns the array index in the 'channels' array of the channel // named 'channel'. // Returns -1 if there was an error. int channelindex(struct channel *channels, char *name) { debugprint("channelindex(): given '%s'.\n", name); for (int i = 0; i < MAXCHANNELS; i++) { if (strncmp(channels[i].name, name, strlen(name)) == 0) { return i; } } // We didn't find the channel, this isn't good! TODO - Do something if this happens. printf("channelindex(): channel '%s' not found, this is bad, returning -1.\n", name); return -1; } // Send the requested number of lines of replay log to the requested client // 'sourcefd' is the client to send to, and replayseconds is the number of // seconds of replay to replay. // Returns 1 for success or 0 for failure. int doreplay(int sourcefd, int replayseconds, struct client *clients, struct settings *settings, struct ircdstrings *ircdstrings, struct channel *channels) { char outgoingmsg[MAXDATASIZE]; // Figure out how many lines to replay int numlines = replaylines(replayseconds, settings->basedir); debugprint("Replay log lines: '%d'.\n", numlines); if (numlines < 0) { debugprint("Error getting number of replay lines.\n"); return 0; } else if (numlines == 0) { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :0 replay log lines found in the time requested, nothing to send.", ircdstrings->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; } // Announce the start snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Starting log replay....", ircdstrings->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); // Replay those lines! for (int i = 0; i < numlines; i++) { if (!readreplayline(replayseconds, i, outgoingmsg, settings->basedir)) { debugprint("Error requesting replay line.\n"); return 0; } // Check if the replay line is a TOPIC, a JOIN, or a PART so we don't // replay those if we are not currently in the channel they are from // otherwise clients and state go a bit mad. // Never replay them if they are from us. // Copy to a temporary string char *strcopy = strdup(outgoingmsg); // Keep track of initial pointer for free()ing later char *strcopyPtr = strcopy; // Build array of each space-separated token char tokens[3][MAXDATASIZE]; char *token; for (int j = 0; j < 3; j++) { // Try to split if ((token = strsep(&strcopy, " ")) == NULL) { debugprint("doreplay(): error splitting string on iteration %d, exiting!\n", j); return 0; } // Copy into the token array (strlen + 1 to get the NULL terminator) strncpy(tokens[j], token, strlen(token) + 1); } if (strncmp(tokens[1], "TOPIC", strlen("TOPIC")) == 0 || strncmp(tokens[1], "JOIN", strlen("JOIN")) == 0 || strncmp(tokens[1], "PART", strlen("PART")) == 0) { // Skip over colon if present in channel name int offset = 0; if (tokens[2][0] == ':') { offset = 1; } // To make sure it's not us extractnickfromprefix(tokens[0]); // Check if we're currently in this channel or if the log line is from us if (!inchannel(channels, tokens[2] + offset) || strncmp(tokens[0], ircdstrings->ircnick, strlen(tokens[0])) == 0) { debugprint("Not sending '%s' replay line '%s'.\n", tokens[1], outgoingmsg); free(strcopyPtr); continue; } } free(strcopyPtr); debugprint("Sending replay line: '%s'.\n", outgoingmsg); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } // Announce the end snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Log replay complete.", ircdstrings->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; } // Return a count of the number of connected clients int numclients(struct client *clients) { int count = 0; for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].fd) { count++; } } debugprint("numclients(): '%d' clients connected.\n", count); return count; } // Join any channels that were configured to be automatically // joined in the configuration file. // Returns 1 on success or 0 on failure. int joinautochannels(SSL *server_ssl, struct client *clients, struct settings *settings) { if (strlen(settings->autochannels) == 0) { // None configured debugprint("joinautochannels(): none configured.\n"); return 1; } // Split string up into each channel char tokens[MAXAUTOCHANLEN][MAXCHANLENGTH]; int counter = 0; // Copy to a temporary string char *strcopy = strdup(settings->autochannels); // Keep track of initial pointer for free()ing later char *strcopyPtr = strcopy; char *token; // Split on commas while ((token = strsep(&strcopy, ",")) != NULL) { if (*token == '\0') continue; // Skip consecutive matches if (counter >= MAXAUTOCHANLEN) break; // Too many tokens debugprint(" >> Auto channel: '%s', length '%ld'.\n", token, strlen(token)); // Copy into the token array (strlen + 1 to get the NULL terminator) strncpy(tokens[counter], token, strlen(token) + 1); if (strlen(tokens[counter]) > MAXCHANLENGTH) { printf("error: channel name '%s' from configuration file too long, max length is '%d'.\n", tokens[counter], MAXCHANLENGTH); exit(1); } counter++; } // Join all the channels for (int i = 0; i < counter; i++) { debugprint("joinautochannels(): Joining '%s'.\n", tokens[i]); char joinmsg[MAXDATASIZE]; snprintf(joinmsg, MAXDATASIZE, "JOIN %s", tokens[i]); sendtoserver(server_ssl, joinmsg, strlen(joinmsg), 0, clients, settings); } free(strcopyPtr); // TODO - Can we fail here? Return 0 if so and make callers handle this if so. return 1; } // Try to make a new nick if no configured are available or liked by the server // Do this by sticking a number on the end of the current nick and trying numbers // 1 through to 9. void tryautonick(struct ircdstrings *ircdstrings) { // Increment the attempts counter ircdstrings->autonicknum++; if (ircdstrings->autonicknum == 10) { // We've already tried 9 nicks and failed, give up printf("tryautonick(): Tried 9 automatic nicks and the server didn't like any, giving up.\n"); exit(1); } int oldlen = strlen(ircdstrings->ircnick); // If we've already started trying autonick, just replace the last character a the new number if (ircdstrings->autonicknum > 1) { debugprint("tryautonick(): already started autonick, starting with '%s' length '%ld'.\n", ircdstrings->ircnick, strlen(ircdstrings->ircnick)); ircdstrings->ircnick[oldlen - 1] = ircdstrings->autonicknum + '0'; // And null terminate ircdstrings->ircnick[oldlen] = '\0'; // If the nick is longer than or equal to the RFC 1459 max nick // length then try sticking the number at the end } else if (oldlen >= MAXRFCNICKLEN) { debugprint("tryautonick(): long old nick, starting with '%s' length '%ld'.\n", ircdstrings->ircnick, strlen(ircdstrings->ircnick)); // (+ '0' to make char from int) ircdstrings->ircnick[MAXRFCNICKLEN] = ircdstrings->autonicknum + '0'; // And null terminate ircdstrings->ircnick[MAXRFCNICKLEN + 1] = '\0'; // Otherwise, just stick it on the end (+ '0' to make char from int) } else { debugprint("tryautonick(): short old nick, starting with '%s' length '%ld'.\n", ircdstrings->ircnick, strlen(ircdstrings->ircnick)); ircdstrings->ircnick[oldlen] = ircdstrings->autonicknum + '0'; // And null terminate ircdstrings->ircnick[oldlen + 1] = '\0'; } debugprint("tryautonick(): set irdstrings->ircnick to '%s'.\n", ircdstrings->ircnick); } int connecttoircserver(SSL_CTX **serverctx, SSL **server_ssl, int *serversockfd, struct ircdstrings *ircdstrings, struct settings *settings, struct client *clients) { char outgoingmsg[MAXDATASIZE]; // String to send to server if (settings->servertls) { debugprint("server openssl start.\n"); *serverctx = create_openssl_context(SOURCE_SERVER); configure_openssl_context(*serverctx, NULL, NULL); *server_ssl = SSL_new(*serverctx); SSL_set_fd(*server_ssl, *serversockfd); if (SSL_connect(*server_ssl) == -1) { ERR_print_errors_fp(stderr); } else { debugprint("SSL_connect() success.\n"); } debugprint("server openssl complete.\n"); } else { // If not using TLS then just slap the serversockfd into server_ssl by casting it *server_ssl = (SSL*)(long int)*serversockfd; } // <============================================= // Initialise IRC connecting/registration state // Set ircdstrings to zero-length strings for now ircdstrings->greeting001[0] = '\0'; ircdstrings->greeting002[0] = '\0'; ircdstrings->greeting003[0] = '\0'; ircdstrings->greeting004[0] = '\0'; ircdstrings->greeting005a[0] = '\0'; ircdstrings->greeting005b[0] = '\0'; ircdstrings->greeting005c[0] = '\0'; ircdstrings->ircdname[0] = '\0'; ircdstrings->nickuserhost[0] = '\0'; ircdstrings->ircnick[0] = '\0'; ircdstrings->ircusername[0] = '\0'; ircdstrings->currentmsg[0] = '\0'; ircdstrings->mode[0] = '\0'; // ircdstrings->oldnick is not set here as we want to track reconnections separately // And set non-string things to zero (TODO - Rename this from ircdstrings since it's not all strings any more) ircdstrings->capmultiprefix = 0; ircdstrings->autonicknum = 0; ircdstrings->lastmessagetime = time(NULL); // ircdstrings.reconnecting is not set here as we want to track reconnections separately // Populate nick and username from our configuration file for now, real IRCd may change them later (TODO - Is this true of username?) strcpy(ircdstrings->ircnick, settings->ircnick); strcpy(ircdstrings->ircusername, settings->ircusername); // Send the server password if one was configured if (settings->ircserverpassword[0]) { snprintf(outgoingmsg, MAXDATASIZE, "PASS %s", settings->ircserverpassword); // sourcefd = 0 as this is a trusted message sendtoserver(*server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings); } // Send our NICK snprintf(outgoingmsg, MAXDATASIZE, "NICK %s", ircdstrings->ircnick); // TODO - Check for success (with return code) // sourcefd = 0 as this is a trusted message sendtoserver(*server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings); // Send our USER snprintf(outgoingmsg, MAXDATASIZE, "USER %s 8 * : %s", ircdstrings->ircusername, settings->ircrealname); // TODO - Check for success (with return code) // TODO - Send a more intelligent/correct USER string // sourcefd = 0 as this is a trusted message sendtoserver(*server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings); // =============================================> return 1; // TODO - Return 0 if this fails and make callers do something with that } // Figure out what to do with each CRLF-split IRC message (if anything) // by splitting out the different components by space character (ASCII 0x20). // // serversockfd, clientsockfd, fdmax, arr_clients, arr_authed, all passed to here so we can // send/relay a response to server or (other) client(s) directly from this function if // we want to. // // str is the raw string // // source is either 0 (SOURCE_SERVER) (server - from the real IRC server) or 1 (SOURCE_CLIENT) (client - from a real IRC client) // // Return 1 if we processed something and expect the caller to not need to do anything more // Return 0 if we didn't process it and the caller might want to do something //int processircmessage(int *serversockfd, int *clientsockfd, char *str, int source) { int processircmessage(SSL *server_ssl, char *str, int source, struct client *clients, int sourcefd, struct ircdstrings *ircdstrings, struct channel *channels, struct settings *settings) { // Track which space-separated token within this response we're on int counter = 0; // Build array of each space-separated token (TODO - Use counter to stop splitting once we reach some reasonable value - i.e. once we're definitely past commands and into just free text) char tokens[MAXTOKENS][MAXDATASIZE]; debugprint(" >> processircmessage(): Processing source %d message \"%s\"...\n", source, str); // Copy to a temporary string so we still have the original in case it's not processed char *strcopy = strdup(str); // Keep track of initial pointer for free()ing later char *strcopyPtr = strcopy; char *token; while ((token = strsep(&strcopy, " ")) != NULL) { if (*token == '\0') continue; // Skip consecutive matches if (counter >= MAXTOKENS) break; // Too many tokens debugprint(" >> Message Token: \"%s\", length %zd.\n", token, strlen(token)); // Copy into the token array (strlen + 1 to get the NULL terminator) strncpy(tokens[counter], token, strlen(token) + 1); counter++; } // <============================================= // IRC command processing (commands from server or client) 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("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"); exit(1); } // sourcefd = 0 as this is a trusted response sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings); // 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("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("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("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"); exit(1); } // Null the end of the new string ircdstrings->nickuserhost[strlen(tokens[counter - 1]) + 1] = '\0'; // +1 for the inserted colon debugprint("nickuserhost '%s' stored.\n", ircdstrings->nickuserhost); free(strcopyPtr); return 1; } else if (strncmp(tokens[1], "002", strlen(tokens[1])) == 0) { debugprint("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("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("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("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); // 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("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("Reconnection: Skipping channel[%d], incrementing channelcount.\n", i); channelcount++; continue; } debugprint("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("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("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("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("processircmessage(): Channel name was '%s'\n", tokens[2]); stripprefix(tokens[2]); debugprint("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("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("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("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("Server PART: nick is ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->ircnick); removechannel(channels, tokens[2]); } else { debugprint("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("Trying nick3, nick2 was already tried.\n"); strcpy(ircdstrings->ircnick, nick3); // Have neither been tried? } else { // Then try nick2 debugprint("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("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("Server CAP found and it is: %s with length %zd! Analysing...\n", tokens[1], strlen(tokens[1])); // If the server said "CAP ACK :multi-prefix" then it must have approved our CAP multi-prefix request if (counter == 5) { if (strncmp(tokens[2], 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("Unhandled server CAP response.\n"); } } // Don't return if we got here because this means we didn't process something above 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("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("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"); exit(1); } // "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("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"); exit(1); } // "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("Client CAP received and the server supports CAPs, continuing.\n"); } else { debugprint("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"); exit(1); } // ...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"); exit(1); } // ...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("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); // 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("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("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("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"); exit(1); } 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"); exit(1); } // ..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"); exit(1); } // ..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"); exit(1); } // ..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"); exit(1); } 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("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("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"); exit(1); } sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); // 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("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("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("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("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"); exit(1); } // 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("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("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("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("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("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("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("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("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("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("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("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) { // 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("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("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("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("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("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("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"); exit(1); } 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("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; // Unrecognised BLABOUNCER command received, send some help instructions } else { debugprint("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); free(strcopyPtr); return 1; } } break; default: fprintf(stderr, "Unexpected raw IRC string source!\n"); exit(1); } // =============================================> debugprint("Done with processircmessage()\n"); // If we got here then we didn't process anything free(strcopyPtr); return 0; } // Figure out what the client or server sent and decide what to do with it (if anything) // First by splitting out the different messages by CRLF, process if we know how to, // or send on to all other clients (if from the server) or to the server (if from a client) // if we don't know how to. // // serversockfd, clientsockfd, fdmax, arr_clients, arr_authed all passed to here so we can // send/relay a response to server or (other) client(s) for things we don't know how to process here, // or for any other reasons we need to send stuff for // // str is the raw string // // source is either 0 (SOURCE_SERVER) (server - from the real IRC server) or 1 (SOURCE_CLIENT) (client - from a real IRC client) // // Return 0 if something went wrong // Return 1 if everything OK int processrawstring(SSL *server_ssl, char *str, int source, struct client *clients, int sourcefd, struct ircdstrings *ircdstrings, struct channel *channels, struct settings *settings) { // Copy to a temporary string so we still have the original in case it's not processed char *strcopy = strdup(str); // Keep track of initial pointer for free()ing later char *strcopyPtr = strcopy; debugprint("processrawstring(): Source type %d sent: \"%s\". Processing it...\n", source, strcopy); // Track which CLRF-separated message within this string we're on int messagecount = 0; // Build array of each space-separated token char messages[MAXTOKENS][MAXDATASIZE]; // Split the string by CRLF and add each CRLF-separated IRC message to an array char *token; while ((token = strsep(&strcopy, "\r\n")) != NULL) { if (*token == '\0') continue; // Skip consecutive matches if (messagecount >= MAXTOKENS) break; // Too many tokens debugprint("String Token: \"%s\", length %zd.\n", token, strlen(token)); // Copy into the token array (strlen + 1 to get the NULL terminator) strncpy(messages[messagecount], token, strlen(token) + 1); messagecount++; } free(strcopyPtr); // If there is a previous possibly truncated message still in the holding area, the prepend that to the first message of this new lot // (Only if source was the server since we always strip \r\n from client messages when recving - TODO - Should we be doing that? if (ircdstrings->currentmsg[0] && source == SOURCE_SERVER) { // Make a copy since we can't have source and destination the same with snprintf char *strtmp = strdup(messages[0]); debugprint("processrawstring(): Previous truncated message detected, combining old '%s' with new '%s'...\n", ircdstrings->currentmsg, strtmp); snprintf(messages[0], strlen(ircdstrings->currentmsg) + strlen(strtmp) + 1, "%s%s", ircdstrings->currentmsg, strtmp); messages[0][strlen(ircdstrings->currentmsg) + strlen(strtmp)] = '\0'; // Make sure it's null terminated debugprint("...into new string '%s' and clearing currentmsg holding area.\n", messages[0]); ircdstrings->currentmsg[0] = '\0'; free(strtmp); } // If the final characters of the raw string weren't \r\n then assume the final token is a truncated message // Copy to a holding area for continuation next time // (Only if source was the server since we always strip \r\n from client messages when recving - TODO - Should we be doing that? if ((str[strlen(str)-2] != 13 || str[strlen(str)-1] != 10) && source == SOURCE_SERVER) { debugprint("processrawstring(): Truncated message detected, storing final token '%s' for later.\n", messages[messagecount - 1]); strncpy(ircdstrings->currentmsg, messages[messagecount - 1], strlen(messages[messagecount - 1])); ircdstrings->currentmsg[strlen(messages[messagecount - 1])] = '\0'; // Remove it from the message count so it's not processed time time messagecount--; } else { // Otherwise, clear the holding area ircdstrings->currentmsg[0] = '\0'; } // Go through each message, figure out what it is and if we're doing anything with it for (int i = 0; i < messagecount; i++) { // Copy to a temporary string so we still have the original in case it's not processed char *messagecopy = strdup(messages[i]); if (processircmessage(server_ssl, messagecopy, source, clients, sourcefd, ircdstrings, channels, settings)) { debugprint("Message processed: \"%s\", NULLing...\n", messages[i]); messages[i][0] = '\0'; } free(messagecopy); } // Deal with any unprocessed messages (send on to server/client(s) since we haven't dealt with them) for (int i = 0; i < messagecount; i++) { if (messages[i][0] != '\0') { debugprint("Message %d (\"%s\") remains unprocessed...\n", i, messages[i]); switch(source) { case SOURCE_SERVER: // If message(s) were from the real IRC server // Relay/send to all clients ("except" = 0 because this should send to all clients) // TODO - Is this really going to send the original string if we have messed it with it in processrawstring() and friends!? debugprint("bouncer-server: sending unprocessed server message \"%s\" to all clients, length %zd.\n", messages[i], strlen(messages[i])); sendtoallclients(clients, messages[i], EXCEPT_NONE, settings); break; case SOURCE_CLIENT: // If message(s) were from a real IRC client // Send to server debugprint("bouncer-client: sending unprocessed client message \"%s\" to the server, length %zd.\n", messages[i], strlen(messages[i])); sendtoserver(server_ssl, messages[i], strlen(messages[i]), sourcefd, clients, settings); debugprint("bouncer-client: sending unprocessed client message \"%s\" to all other clients, length %zd.\n", messages[i], strlen(messages[i])); // send the same thing to all *other* clients (all except for source fd) sendtoallclients(clients, messages[i], sourcefd, settings); break; default: fprintf(stderr, "Unexpected raw IRC string source for unprocessed message \"%s\", length %zd.!\n", messages[i], strlen(messages[i])); exit(1); } } } debugprint("Done with processrawstring()\n"); return 1; } // Where the big bouncing loop is void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { char serverbuf[MAXRCVSIZE]; // buffer for receiving data on server socket char clientbuf[MAXRCVSIZE]; // buffer for receiving data on client socket(s) int servernumbytes; // Number of bytes received from remote server int fdmax; // highest numbered socket fd socklen_t addrlen; // client remote address size char remoteIP[INET6_ADDRSTRLEN]; // remote IP (assume up to IPv6 size) int newfd; // newly accept()ed socket descriptor struct sockaddr_storage remoteaddr; // client address int clientnumbytes; fdmax = *clientsockfd; // keep track of highest fd number, currently client socket as created last (after server socket) fd_set rfds; // set of read fds to monitor with select() - 0: stdin, 1: stdout, 2: stderr, 3 and higher: sockets (at time of writing, 3: real IRC server, 4: client listener, 5 and higher: clients) // Set up clients structure struct client clients[MAXCLIENTS]; // Set all the clients to be "not connected", "not authenticated", "not registered", "not pending replies" for (int i = 0; i < MAXCLIENTS; i++) { clients[i].fd = 0; clients[i].authed = 0; clients[i].registered = 0; clients[i].pendingchannelmode = 0; clients[i].pendingban = 0; clients[i].pendingwho = 0; clients[i].pendinglist = 0; clients[i].pendingwhois = 0; clients[i].pendingwhowas = 0; clients[i].pendingnames = 0; clients[i].pendingcap = 0; } // Struct of various strings from and for the real IRCd (such as the greeting strings, the real IRCd's name, // our nick!user@host string, our nick, username, real name, etc.) struct ircdstrings ircdstrings; // Struct of channels we're in struct channel *channels; channels = malloc(sizeof(struct channel) * MAXCHANNELS); // Set initial channel names to empty strings for (int i = 0; i < MAXCHANNELS; i++) { channels[i].name[0] = '\0'; } // Initialise OpenSSL (used for both client and server) init_openssl(); // OpenSSL for server side if configured SSL_CTX *serverctx = NULL; SSL *server_ssl = NULL; // Need to create this either way as referenced later // Set reconnection things to null/zero for now (not used unless reconnecting to server) ircdstrings.oldnick[0] = '\0'; ircdstrings.reconnecting = 0; connecttoircserver(&serverctx, &server_ssl, serversockfd, &ircdstrings, settings, clients); // OpenSSL context for client side (that clients connect to) (need to create this whether or not using TLS as it is referenced later) SSL_CTX *ctx; // If using client TLS if (settings->clienttls) { // Set up and configure client OpenSSL context ctx = create_openssl_context(SOURCE_CLIENT); configure_openssl_context(ctx, settings->certfile, settings->keyfile); } while (1) { debugprint("top of loop, fdmax %d.\n", fdmax); FD_ZERO(&rfds); // clear entries from fd set FD_SET(STDIN, &rfds); // add STDIN (fd 0) to read fds to monitor FD_SET(*serversockfd, &rfds); // add our server network socket to monitor FD_SET(*clientsockfd, &rfds); // add our client network socket to monitor // Add all connected clients to monitor (only add ones that are connected (clients[i] > 0)) // TODO - make sure *serversockfd stays at the same value (probably 3?) in all cases - what if the server disconnects/reconnects/etc. // TODO - now that only connected clients are monitored, perhaps tracking using both fdmax and num_client loops is unnecessary? for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].fd > 0) { debugprint("monitoring fd %d.\n", clients[i].fd); FD_SET(clients[i].fd, &rfds); } } // Before we wait for a message, let's make sure the server is still responding // TODO - Code duplication, make a function and share with socket error code below if (ircdstrings.lastmessagetime < time(NULL) - SERVERTIMEOUT) { printf("Server has timed out (%ld seconds), reconnecting!\n", time(NULL) - ircdstrings.lastmessagetime); // Tell all clients if we timed out char alertmsg[MAXDATASIZE]; snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Server has timed out (%ld seconds), reconnecting!", ircdstrings.ircnick, time(NULL) - ircdstrings.lastmessagetime); sendtoallclients(clients, alertmsg, 0, settings); if (settings->servertls) { // Finish up with OpenSSL if using server TLS SSL_free(server_ssl); } // Close the socket close(*serversockfd); // Make a new one *serversockfd = createserversocket(settings->ircserver, settings->ircserverport); // Set reconnection marker for other functions to know we're reconnecting ircdstrings.reconnecting = 1; // Set oldnick in case we change nick when reconnecting so we can inform existing clients strcpy(ircdstrings.oldnick, ircdstrings.ircnick); connecttoircserver(&serverctx, &server_ssl, serversockfd, &ircdstrings, settings, clients); // Back to top of loop continue; } debugprint("select()ing...\n"); // check to see if anything in the fd_set is waiting - waits here until one of the fds in the set does something if (select(fdmax + 1, &rfds, NULL, NULL, NULL) < 0) { // network socket + 1, rfds, no writes, no exceptions/errors, no timeout printf("receive error, exiting!?\n"); perror("select"); } // TODO - switch around the serversockfd and STDIN FD_ISSET if-statements? They feel the wrong way round. Are they like this on purpose? I can't remember. // (although actually stdin may go once its not wanted for possible direct interaction for debugging) // See if there's anything to read from the server side (the real IRCd) if (FD_ISSET(*serversockfd, &rfds)) { debugprint("reading server socket!\n"); if ((servernumbytes = sockread(server_ssl, serverbuf, MAXRCVSIZE - 1, settings->servertls)) == -1) { printf("receive error (-1), exiting...\n"); perror("recv"); } else if (servernumbytes == 0) { printf("socket closed (or no data received) (0), exiting...\n"); perror("recv"); } // If there was a socket error (receive error or socket closed) // TODO - Code duplication, make a function and share with timeout code above if (servernumbytes < 1) { printf("Server socket had an error (sockread return code %d), reconnecting!\n", servernumbytes); // Tell all clients if we timed out char alertmsg[MAXDATASIZE]; snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Server socket had an error (sockread return code %d), reconnecting!", ircdstrings.ircnick, servernumbytes); sendtoallclients(clients, alertmsg, 0, settings); if (settings->servertls) { // Finish up with OpenSSL if using server TLS SSL_free(server_ssl); } // Close the socket close(*serversockfd); // Make a new one *serversockfd = createserversocket(settings->ircserver, settings->ircserverport); // Set reconnection marker for other functions to know we're reconnecting ircdstrings.reconnecting = 1; // Set oldnick in case we change nick when reconnecting so we can inform existing clients strcpy(ircdstrings.oldnick, ircdstrings.ircnick); connecttoircserver(&serverctx, &server_ssl, serversockfd, &ircdstrings, settings, clients); // Back to top of loop continue; } serverbuf[servernumbytes] = '\0'; debugprint("BOUNCER-SERVER RECEIVED: '%s', length '%d'.\n", serverbuf, servernumbytes); // Try to process received string (which should contain one or more server responses/commands) // TODO - What if there were two server respones/commands and only one didn't need relaying? if (!processrawstring(server_ssl, serverbuf, SOURCE_SERVER, clients, EXCEPT_NONE, &ircdstrings, channels, settings)) { fprintf(stderr, "Error: bouncer-server failed to process raw string.\n"); exit(1); } } // see if there's anything from stdin if (FD_ISSET(STDIN, &rfds)) { debugprint("reading stdin!\n"); char outgoingmsg[MAXDATASIZE]; // String to send to server int outgoingmsgrc; // Return code from getstdin() for outgoing message outgoingmsgrc = getstdin(NULL, outgoingmsg, sizeof(outgoingmsg)); if (outgoingmsgrc == NO_INPUT) { printf("\nError! No input.\n"); } else if (outgoingmsgrc == TOO_LONG) { printf ("Error! Too long. Would have allowed up to: [%s]\n", outgoingmsg); } // STDIN based commands for debugging if (strncmp(outgoingmsg, "listchannels", strlen("listchannels")) == 0) { printf("STDIN command starting: listchannels\n"); int channelcount = getchannelcount(channels); for (int i = 0; i < channelcount; i++) { printf("Checking 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("Skipping channel[%d], incrementing channelcount.\n", i); channelcount++; continue; } printf("Found channel '%s'.\n", channels[i].name); } printf("STDIN command complete: listchannels\n"); continue; } // sourcefd = 0 as this is a trusted message sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings); } // go through all the remaining sockets to see if there's anything from the client sockets (either new connections or existing clients sending messages) // (clear newfd before doing this so we can tell if we're querying a new client or not) newfd = 0; for (int i = *clientsockfd; i <= fdmax; i++) { // skip if newfd is the current iteration of this loop, since we know we have just accept()ed it if (i == newfd) { continue; } debugprint("checking client socket %d out of %d.\n", i, fdmax); if (FD_ISSET(i, &rfds)) { debugprint("fd %d is FD_ISSET and it is a...\n", i); // if value of clientsockfd then must be a new connection, if greater must be an existing connection if (i == *clientsockfd) { debugprint("...new connection!\n"); // handle new connections if (numclients(clients) >= MAXCLIENTS) { fprintf(stderr, "too many clients!\n"); exit(1); // TODO - handle cleanly instead of exiting! } addrlen = sizeof remoteaddr; newfd = accept(*clientsockfd, (struct sockaddr *)&remoteaddr, &addrlen); if (newfd == -1) { // something went wrong when accept()ing perror("accept"); } else { FD_SET(newfd, &rfds); // add to master set // TODO - needed? going to be re-done at the top anyway... if (newfd > fdmax) { // keep track of the max fdmax = newfd; } // Find a free element in the clients array and set to new fd value for (int j = 0; j < MAXCLIENTS; j++) { if (clients[j].fd == 0) { clients[j].fd = newfd; // Ensure its authentication status is set to 0 clients[j].authed = 0; // If using TLS then... if (settings->clienttls) { // ...set as OpenSSL FD and SSL_accept it clients[j].ssl = SSL_new(ctx); SSL_set_fd(clients[j].ssl, newfd); if (SSL_accept(clients[j].ssl) <= 0) { printf("SSL_accept failed for fd %d.\n", clients[j].fd); ERR_print_errors_fp(stderr); } else { debugprint("SSL_accept succeeded for fd %d.\n", clients[j].fd); } } else { // If not using TLS then cast newfd to SSL* even though it will just be the original newfd int really clients[j].ssl = (SSL*)(long int)newfd; } break; } } // TODO - Handle the "find a free element" loop not finding a free element debugprint("bouncer-client: new connection from %s on socket %d\n", inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), remoteIP, INET6_ADDRSTRLEN), newfd); // Alert other clients about the new connection char alertmsg[MAXDATASIZE]; if (!snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :blabouncer: new client connected from %s with fd %d.", ircdstrings.ircnick, inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), remoteIP, INET6_ADDRSTRLEN), newfd)) { fprintf(stderr, "Error while preparing new client connection NOTICE!\n"); exit(1); } // "except" 0 since we trust this message sendtoallclients(clients, alertmsg, 0, settings); debugprint("bouncer-client: total client connections: %d\n", numclients(clients)); } } else { debugprint("...previous connection!\n"); // handle data from a client if ((clientnumbytes = sockread(clients[arrindex(clients, i)].ssl, clientbuf, sizeof clientbuf, settings->clienttls)) <= 0) { // got error or connection closed by client if (clientnumbytes == 0) { // connection closed debugprint("bouncer-client: socket %d hung up\n", i); } else { perror("recv"); } // Disconnect the client disconnectclient(i, clients, &ircdstrings, settings); FD_CLR(i, &rfds); // remove from master set - TODO is this needed at the moment since we just add everything from *clientsockfd to fdmax to rfds // TODO - Handle the "remove the client" loop not finding the old fd debugprint("bouncer-client: total client connections: %d\n", numclients(clients)); } else { // we got some data from a client // null terminate that baby clientbuf[clientnumbytes] = '\0'; // TODO make sure this can't overrun if some super long line (max bytes?) was received // clear up any newlines - TODO - Should we be doing this? If not, we can stop only doing truncation checks for the server in processrawstring(). while (clientbuf[strlen(clientbuf) - 1] == '\n' || clientbuf[strlen(clientbuf) - 1] == '\r') { clientbuf[strlen(clientbuf) - 1] = '\0'; } debugprint("BOUNCER-CLIENT RECEIVED: '%s'\n", clientbuf); // Try to process received string (which should contain one or more client responses/commands) // TODO - What if there were two server respones/commands and only one didn't need relaying? if (!processrawstring(server_ssl, clientbuf, SOURCE_CLIENT, clients, i, &ircdstrings, channels, settings)) { fprintf(stderr, "Error: bouncer-client failed to process raw string.\n"); exit(1); } } } } } } free(channels); } int main(int argc, char *argv[]) { // Structure of our various settings which are to either be read from the configuration file or set at runtime struct settings settings; // Check to see if a configuration file was specified on the command line if (argc == 3) { if (strcmp(argv[1], "-c")) { fprintf(stderr,"usage: %s [-c /path/to/blabouncer.conf]\n", argv[0]); exit(1); } else { strcpy(settings.conffile, argv[2]); } } else if (argc == 2 || argc > 3) { fprintf(stderr,"usage: %s [-c /path/to/blabouncer.conf]\n", argv[0]); exit(1); } else { // If none provided, set to default snprintf(settings.conffile, PATH_MAX, "%s/.blabouncer/blabouncer.conf", getenv("HOME")); // Since this is the default, it might not exist yet, so let's check... struct stat st = {0}; if (stat(settings.conffile, &st) == -1) { // ...it doesn't exist, so let's create it printf("Creating default configuration file '%s'.\n", settings.conffile); createconfigfile(settings.conffile); } } debugprint("Using configuration file '%s'.\n", settings.conffile); // Populate said settings from configuration file // How many seconds of replay log should automatically be replayed - TODO - Can we do error checking on this? settings.replayseconds = getconfint("replayseconds", settings.conffile); // What port should the bouncer listen on if (!getconfstr("clientport", settings.conffile, settings.clientport)) { printf("main(): error getting 'clientport' from configuration file.\n"); exit(1); } // What is the configured nick? if (!getconfstr("nick", settings.conffile, settings.ircnick)) { printf("main(): error getting 'nick' from configuration file.\n"); exit(1); } // What is the configured username? if (!getconfstr("username", settings.conffile, settings.ircusername)) { printf("main(): error getting 'username' from configuration file.\n"); exit(1); } // What is the configured real name? if (!getconfstr("realname", settings.conffile, settings.ircrealname)) { printf("main(): error getting 'realname' from configuration file.\n"); exit(1); } // What, if anything, are the configured auto channels? if (!getconfstr("channels", settings.conffile, settings.autochannels)) { settings.autochannels[0] = '\0'; } else { // If something was set, make sure it's not too long if (strlen(settings.autochannels) >= MAXAUTOCHANLEN) { printf("main(): 'channels' option in configuration file is too long.\n"); exit(1); } } // What is the real IRC server address? if (!getconfstr("ircserver", settings.conffile, settings.ircserver)) { printf("main(): error getting 'ircserver' from configuration file.\n"); exit(1); } // What is the real IRC server port? if (!getconfstr("ircserverport", settings.conffile, settings.ircserverport)) { printf("main(): error getting 'ircserverport' from configuration file.\n"); exit(1); } // What is the real IRC server password, if any? if (!getconfstr("ircserverpassword", settings.conffile, settings.ircserverpassword)) { settings.ircserverpassword[0] = '\0'; } // Is the base directory set? If not, use the default. if (!getconfstr("basedir", settings.conffile, settings.basedir)) { snprintf(settings.basedir, PATH_MAX, "%s/.blabouncer/", getenv("HOME")); } // Should the bouncer use TLS for the IRC server? settings.servertls = getconfint("servertls", settings.conffile); // Should the bouncer use TLS for clients? settings.clienttls = getconfint("clienttls", settings.conffile); // If so, load the certificates if (settings.clienttls) { // What is the certificate file path? if (!getconfstr("certfile", settings.conffile, settings.certfile)) { // If none provided, set to default if (!snprintf(settings.certfile, PATH_MAX, "%s/cert.pem", settings.basedir)) { fprintf(stderr, "Error while preparing default certfile location!\n"); exit(1); } } // What is the certificate key file path? if (!getconfstr("keyfile", settings.conffile, settings.keyfile)) { // If none provided, set to default if (!snprintf(settings.keyfile, PATH_MAX, "%s/key.pem", settings.basedir)) { fprintf(stderr, "Error while preparing default certfile location!\n"); exit(1); } } } // Make sure the base directory exists struct stat st = {0}; if (stat(settings.basedir, &st) == -1) { if (mkdir(settings.basedir, 0700)) { printf("Error creating base directory '%s'.\n", settings.basedir); exit(1); } else { printf("Created base directory '%s'.\n", settings.basedir); } } // Is logging enabled? settings.logging = getconfint("logging", settings.conffile); // Is replay logging enabled? settings.replaylogging = getconfint("replaylogging", settings.conffile); // Is debugging enabled? debug = getconfint("debug", settings.conffile); if (!snprintf(debugpath, PATH_MAX, "%s/debug.txt", settings.basedir)) { fprintf(stderr, "Error while preparing debug path location!\n"); exit(1); } // Prepare the debug file // (Keep DEBUGFILESKEEP number of debug files around for past debugging) if (DEBUGFILESKEEP > 0) { // Check each possible debug file and rename it to one higher char tmppath[PATH_MAX]; char tmppathnew[PATH_MAX]; // Delete or rename numbered debug files first for (int i = DEBUGFILESKEEP; i > 0; i--) { if (!snprintf(tmppath, PATH_MAX - 1, "%s.%d", debugpath, i)) { fprintf(stderr, "Error while preparing to check old debug files!\n"); exit(1); } if (!access(tmppath, F_OK)) { if (i == DEBUGFILESKEEP) { if (remove(tmppath)) { printf("error deleting old debug file '%s'.\n", tmppath); exit(1); } } else { if (!snprintf(tmppathnew, PATH_MAX - 1, "%s.%d", debugpath, i + 1)) { fprintf(stderr, "Error while preparing to rename old debug file!\n"); exit(1); } if (rename(tmppath, tmppathnew)) { printf("error renaming old debug file '%s' to '%s'.\n", tmppath, tmppathnew); exit(1); } } } } // Rename the previous debug file next if (!access(debugpath, F_OK)) { if (rename(debugpath, tmppath)) { printf("error renaming old debug file '%s' to '%s'.\n", debugpath, tmppath); exit(1); } } } // Write a friendly debug message to file (even if debug to file is disabled) // Prepare a friendly time string time_t rawtime; struct tm * timeinfo; time(&rawtime); timeinfo = localtime(&rawtime); // Strip the trailing newline char timestr[MAXCHAR]; snprintf(timestr, MAXCHAR, "%s", asctime(timeinfo)); timestr[strlen(timestr) - 1] = '\0'; // Note the old debug setting int debugold = debug; // Temporarily enable debugging to file debug = 1; // Print it debugprint("blabouncer started at %s, debugging is set to %d.\n", timestr, debugold); // Set debugging back to whatever it was debug = debugold; // TODO: see if any of this can be shared (i.e. 1. avoid code duplication, and 2. see if variables can be shared between client/server sockets) // I will try to keep to the notation of "server" meaning the real IRCd, "bouncer" meaning the bouncer, and "client" meaning the real IRC client // Create server socket int serversockfd = createserversocket(settings.ircserver, settings.ircserverport); // Create client socket (after server so we can use its fd number later as fdmax) int clientsockfd = createclientsocket(settings.clientport); dochat(&serversockfd, &clientsockfd, &settings); printf("dochat() complete, closing socket...\n"); close(serversockfd); return 0; }