// 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 #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 #define DEBUG_CRIT 0 #define DEBUG_SOME 1 #define DEBUG_FULL 2 #define STDIN 0 // stdin is fd 0 #define SIGINT 2 // SIGINT is signal 2 #define SIGTERM 15 // SIGTERM is signal 15 // 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 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; // Debug verbosity ("0" for critical only, "1" for some extra info, "2" for full debug mode) char debugpath[PATH_MAX]; // Path to debug file // Set if certain signal (just SIGINT and SIGKILL at time of writing) is received int signum = 0; 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 connectcommand[MAXDATASIZE]; char conffile[PATH_MAX]; char certfile[PATH_MAX]; char keyfile[PATH_MAX]; int clienttls; int servertls; char basedir[PATH_MAX]; int logging; int replaylogging; int background; // Whether or not we're running in the background (detached from the terminal as a daemon) }; // 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). }; // Signal handler // We don't actually do anything in here, the main pselect() notice signals void sighandler(int sig) { signum = sig; } // 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(DEBUG_SOME, "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(DEBUG_SOME, "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() socksend()\n"); debugprint(DEBUG_CRIT, "error: sendtoclient() socksend() error sending to client with fd '%d', errno '%d'.\n", fd, errno); 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(DEBUG_FULL, "sendtoallclients(): trusting clientfd of 0.\n"); break; } if (clients[i].fd == except) { // Found client in array, check authentication status if (!clients[i].authed) { debugprint(DEBUG_SOME, "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(DEBUG_SOME, "sendtoallclients(): skipping unauthenticated client with fd %d.\n", clients[i].fd); continue; } debugprint(DEBUG_SOME, "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) { perror("error: sendtoallclients() socksend()\n"); debugprint(DEBUG_CRIT, "error: sendtoallclients() socksend() error sending to client with fd '%d', errno '%d'.\n", clients[i].fd, errno); } } } 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(DEBUG_FULL, "sendtoserver(): trusting clientfd of 0.\n"); break; } if (clients[i].fd == clientfd) { // Found client in array, check authentication status if (!clients[i].authed) { debugprint(DEBUG_SOME, "sendtoserver(): skipping unauthenticated client with fd %d.\n", clients[i].fd); return 0; } } } debugprint(DEBUG_SOME, "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() socksend()\n"); debugprint(DEBUG_CRIT, "error: sendtoserver() socksend() error sending to server, errno '%d'.\n", clientfd, errno); 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(DEBUG_SOME, "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"); debugprint(DEBUG_CRIT, "Error while preparing authentication failure NOTICE!\n"); alertmsg[0] = '\0'; } // Remove the client from the clients array for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].fd == fd) { debugprint(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "createchannel(): name given was '%s', length '%ld'.\n", name, strlen(name)); debugprint(DEBUG_FULL, "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(DEBUG_FULL, "createchannel(): topic given was '%s', length '%ld'.\n", topic, strlen(topic)); debugprint(DEBUG_FULL, "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 } } debugprint(DEBUG_CRIT, "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(DEBUG_FULL, "setchanneltopicwhotime(): given '%s', '%s', and '%s'.\n", channelname, who, when); debugprint(DEBUG_FULL, "setchanneltopicwhotime(): who: '%s' with length '%ld'.\n", who, strlen(who)); debugprint(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "getchannelcount(): counted %d channels.\n", count); return count; } int removechannel(struct channel *channels, char *name) { debugprint(DEBUG_FULL, "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(DEBUG_FULL, "removechannel(): channel '%s' removed and topicwhen set to '%s'.\n", name, channels[i].topicwhen); return 1; } } debugprint(DEBUG_CRIT, "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(DEBUG_FULL, "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(DEBUG_FULL, "channelgotnames(): channel '%s' gotnames was set, returning '%d'.\n", channels[i].name, channels[i].gotnames); return 1; } else { debugprint(DEBUG_FULL, "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. debugprint(DEBUG_CRIT, "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(DEBUG_FULL, "inchannel(): in channel '%s'.\n", name); return 1; } } // We're not in the channel debugprint(DEBUG_FULL, "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(DEBUG_FULL, "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. debugprint(DEBUG_CRIT, "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(DEBUG_FULL, "Replay log lines: '%d'.\n", numlines); if (numlines < 0) { debugprint(DEBUG_CRIT, "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(DEBUG_CRIT, "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(DEBUG_CRIT, "doreplay(): error splitting string on iteration '%d', returning!\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(DEBUG_FULL, "Not sending '%s' replay line '%s'.\n", tokens[1], outgoingmsg); free(strcopyPtr); continue; } } free(strcopyPtr); debugprint(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, " >> 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); debugprint(DEBUG_CRIT, "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(DEBUG_FULL, "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"); debugprint(DEBUG_CRIT, "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(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "SSL_connect() success.\n"); } debugprint(DEBUG_FULL, "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 } // Exit the program cleanly - tell clients, tell the server, then exit(0) // Optional quit message string "quitmsg" // "sourcefd" of 0 means the request didn't come from a client void cleanexit(SSL *server_ssl, struct client *clients, int sourcefd, struct ircdstrings *ircdstrings, struct settings *settings, char *quitmsg) { char outgoingmsg[MAXDATASIZE]; // Tell clients and debug log if (sourcefd) { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Exiting on request from client with fd '%d', message '%s'.", ircdstrings->ircnick, sourcefd, quitmsg); debugprint(DEBUG_CRIT, "Exiting on request from client with fd '%d', message '%s'.\n", sourcefd, quitmsg); } else { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Exiting on request (not from a client), message '%s'.", ircdstrings->ircnick, quitmsg); debugprint(DEBUG_CRIT, "Exiting on request (not from a client), message '%s'.\n", quitmsg); } sendtoallclients(clients, outgoingmsg, EXCEPT_NONE, settings); // Tell the server // Combine "QUIT :" with any (optional) quit message snprintf(outgoingmsg, MAXDATASIZE, "QUIT :%s", quitmsg); sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), sourcefd, clients, settings); exit(0); } // Figure out what to do with each CRLF-split IRC message (if anything) // by splitting out the different components by space character (ASCII 0x20). // // 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(DEBUG_FULL, " >> 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(DEBUG_FULL, " >> 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(DEBUG_FULL, "Server PING found and it is: %s with length %zd! Sending response...\n", tokens[0], strlen(tokens[0])); char outgoingmsg[MAXDATASIZE]; // String to send to server if (!snprintf(outgoingmsg, MAXDATASIZE, "PONG %s", tokens[1])) { // TODO - Make sure tokens[1] actually has a token fprintf(stderr, "Error while preparing PONG response!\n"); debugprint(DEBUG_CRIT, "Error while preparing PONG response!\n"); outgoingmsg[0] = '\0'; } // sourcefd = 0 as this is a trusted response sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings); // We processed something so return true free(strcopyPtr); return 1; } // Prefix received? TODO - Care about what the prefix is - what if it's a different server/network/whatever? if (tokens[0][0] == ':') { debugprint(DEBUG_FULL, "Prefix found: '%s'! Next token is '%s', length %zd.\n", tokens[0], tokens[1], strlen(tokens[1])); // Greetings 001 through to 005, store in ircdstrings array for resending when clients connect // Also store our nick!user@host from greeting 001 // Also store the real IRCd's name from greeting 004 if (strncmp(tokens[1], "001", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Found greeting 001 (%s) (length %zd), storing in ircdstrings struct.\n", str, strlen(str)); strncpy(ircdstrings->greeting001, str, strlen(str)); // Null the end of the string ircdstrings->greeting001[strlen(str)] = '\0'; debugprint(DEBUG_FULL, "Storing our nick!user@host (:%s) from greeting 001 in ircdstrings struct.\n", tokens[counter - 1]); // Prepend a colon (:) first since everything (so far) needs one if (!snprintf(ircdstrings->nickuserhost, MAXDATASIZE, ":%s", tokens[counter - 1])) { fprintf(stderr, "Error while preparing nickuserhost for storage!\n"); debugprint(DEBUG_CRIT, "Error while preparing nickuserhost for storage!\n"); exit(1); } // Null the end of the new string ircdstrings->nickuserhost[strlen(tokens[counter - 1]) + 1] = '\0'; // +1 for the inserted colon debugprint(DEBUG_FULL, "nickuserhost '%s' stored.\n", ircdstrings->nickuserhost); free(strcopyPtr); return 1; } else if (strncmp(tokens[1], "002", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Found greeting 002 (%s), storing in ircdstrings struct.\n", str); strncpy(ircdstrings->greeting002, str, strlen(str)); // Null the end of the string ircdstrings->greeting002[strlen(str)] = '\0'; free(strcopyPtr); return 1; } else if (strncmp(tokens[1], "003", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Found greeting 003 (%s), storing in ircdstrings struct.\n", str); strncpy(ircdstrings->greeting003, str, strlen(str)); // Null the end of the string ircdstrings->greeting003[strlen(str)] = '\0'; free(strcopyPtr); return 1; } else if (strncmp(tokens[1], "004", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Found greeting 004 (%s), storing in ircdstrings struct.\n", str); strncpy(ircdstrings->greeting004, str, strlen(str)); // Null the end of the string ircdstrings->greeting004[strlen(str)] = '\0'; debugprint(DEBUG_FULL, "Storing the real IRCd's name (%s) from greeting 004 in ircdstrings struct.\n", tokens[3]); strncpy(ircdstrings->ircdname, tokens[3], strlen(tokens[3])); // Null the end of the string ircdstrings->ircdname[strlen(tokens[3])] = '\0'; // Receiving greeting 004 means we're now registered // Request IRCv3 multi-prefix extension so we can more accurately inform new clients about current user prefixes sendtoserver(server_ssl, "CAP REQ multi-prefix", strlen("CAP REQ multi-prefix"), 0, clients, settings); // Send the connect command, if set if (settings->connectcommand[0]) { sendtoserver(server_ssl, settings->connectcommand, strlen(settings->connectcommand), 0, clients, settings); } // If this is a reconnection, JOIN existing channels and catch clients up again if (ircdstrings->reconnecting) { // First tell clients if our nick changed if (!strcmp(ircdstrings->ircnick, ircdstrings->oldnick) == 0) { debugprint(DEBUG_SOME, "Telling clients about nick change.\n"); char nickmsg[MAXDATASIZE]; snprintf(nickmsg, MAXDATASIZE, ":%s NICK :%s", ircdstrings->oldnick, ircdstrings->ircnick); sendtoallclients(clients, nickmsg, sourcefd, settings); } // Next re-join channels // Storing separately so we can skip over blank channels. int channelcount = getchannelcount(channels); // Join all the channels and make a note of the channel count for (int i = 0; i < channelcount; i++) { // Skip this one and increment channelcount if it's a blank channel if (!channels[i].name[0]) { debugprint(DEBUG_FULL, "Reconnection: Skipping channel[%d], incrementing channelcount.\n", i); channelcount++; continue; } debugprint(DEBUG_SOME, "Reconnection: Re-joining '%s'.\n", channels[i].name); char joinmsg[MAXDATASIZE]; snprintf(joinmsg, MAXDATASIZE, "JOIN %s", channels[i].name); sendtoserver(server_ssl, joinmsg, strlen(joinmsg), 0, clients, settings); } // Finally do a replay for all clients and tell them we're reconnected for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].fd) { char alertmsg[MAXDATASIZE]; if (!doreplay(clients[i].fd, settings->replayseconds, clients, settings, ircdstrings, channels)) { snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstrings->ircnick); sendtoclient(sourcefd, alertmsg, clients, settings, 0); } snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Reconnection complete.", ircdstrings->ircnick); sendtoclient(clients[i].fd, alertmsg, clients, settings, 0); } } // Reconnection complete ircdstrings->oldnick[0] = '\0'; ircdstrings->reconnecting = 0; // If it's not, deal with auto channels } else { // Join any auto channels set in the configuration file joinautochannels(server_ssl, clients, settings); } free(strcopyPtr); return 1; } else if (strncmp(tokens[1], "005", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Found greeting 005 (%s), storing in ircdstrings struct.\n", str); // Find an empty greeting005 string in ircdstrings and store in there... if (!ircdstrings->greeting005a[0]) { strncpy(ircdstrings->greeting005a, str, strlen(str)); ircdstrings->greeting005a[strlen(str)] = '\0'; } else if (!ircdstrings->greeting005b[0]) { strncpy(ircdstrings->greeting005b, str, strlen(str)); ircdstrings->greeting005b[strlen(str)] = '\0'; } else if (!ircdstrings->greeting005c[0]) { strncpy(ircdstrings->greeting005c, str, strlen(str)); ircdstrings->greeting005c[strlen(str)] = '\0'; } else { // ...or if they are all fill, discard - TODO - Support more than three greeting005 strings! debugprint(DEBUG_CRIT, "Already stored three greeting 005 strings, discarding this one.\n"); } free(strcopyPtr); return 1; } // Server JOIN received? Add to our local channel array if it's us, or record the user in the channel if it's not us. if (strncmp(tokens[1], "JOIN", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server JOIN found and it is: %s with length %zd! Next token is '%s'. Adding to local channel list if it's us.\n", tokens[0], strlen(tokens[0]), tokens[2]); // Next token should be the channel name but it probably needs the leading ':' stripping debugprint(DEBUG_FULL, "processircmessage(): Channel name was '%s'\n", tokens[2]); stripprefix(tokens[2]); debugprint(DEBUG_FULL, "processircmessage(): Channel name now '%s'\n", tokens[2]); // If the user JOINing is us, then we must have joined a channel, so add to our local channel array. // Copy to a temporary string so we still have the original in case we need it char *prefixcopy = strdup(tokens[0]); // Just get the nick for comparison extractnickfromprefix(prefixcopy); if (strncmp(prefixcopy, ircdstrings->ircnick, strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Server JOIN: nick is ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->ircnick); // TODO - Saner way to initialise this since we don't have the variables yet? // TODO - Defaulting to type '=' which is "public" since I don't know what else to guess. createchannel(channels, tokens[2], "TOPIC", "TOPICWHO", "0"); } else { debugprint(DEBUG_FULL, "Server JOIN: nick is NOT ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->ircnick); } // And then send to all clients sendtoallclients(clients, str, sourcefd, settings); // Write to replay log if replay logging enabled if (settings->replaylogging) { writereplayline(str, settings->basedir); } // Write to normal log if logging enabled if (settings->logging) { logline(str, settings->ircnick, settings->basedir, LOG_JOINPART); } free(prefixcopy); free(strcopyPtr); return 1; } // Server PART received? Remove from our local channel list if it's us. if (strncmp(tokens[1], "PART", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server PART found and it is: %s with length %zd! Next token is '%s'. Removing from local channel list if it's us.\n", tokens[0], strlen(tokens[0]), tokens[2]); // If the user PARTing is us, then we must have left a channel, so remove it from our local channel array. // (If it's not us, then it's another user PARTing a channel, so just pass straight through to letting all our clients know.) // Copy to a temporary string so we still have the original in case we need it char *prefixcopy = strdup(tokens[0]); // Just get the nick for comparison extractnickfromprefix(prefixcopy); if (strncmp(prefixcopy, ircdstrings->ircnick, strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Server PART: nick is ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->ircnick); removechannel(channels, tokens[2]); } else { debugprint(DEBUG_FULL, "Server PART: nick is NOT ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->ircnick); } // And then send to all clients sendtoallclients(clients, str, sourcefd, settings); // Write to replay log if replay logging enabled if (settings->replaylogging) { writereplayline(str, settings->basedir); } // Write to normal log if logging enabled if (settings->logging) { logline(str, settings->ircnick, settings->basedir, LOG_JOINPART); } free(prefixcopy); free(strcopyPtr); return 1; } // Channel topics/names/nicks/etc. // Server 331 (RPL_NOTOPIC) the topic is blank which we track by having a set timestamp of 0 if (strncmp(tokens[1], "331", strlen(tokens[1])) == 0) { // Might as well blank our current topic value setchanneltopic(channels, tokens[3], ""); // Set the topic timestamp to 0 which we use to determine an "unset" topic when new clients connect setchanneltopicwhotime(channels, tokens[3], "", "0"); // Server 332 (RPL_TOPIC) set the channel topic } else if (strncmp(tokens[1], "332", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server 332 (RPL_TOPIC) found, extracting topic and storing in channel struct.\n"); // Need to extract the final parameter as topics can have spaces // Copy to a temporary string so we still have the original in case we need it char *topiccopy = strdup(str); extractfinalparameter(topiccopy); setchanneltopic(channels, tokens[3], topiccopy); free(topiccopy); // Server 333 (RPL_TOPICWHOTIME) set the channel topic setter and the time it was set } else if (strncmp(tokens[1], "333", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server 333 (RPL_TOPICWHOTIME) found, extracting who and when, and storing in channel struct.\n"); setchanneltopicwhotime(channels, tokens[3], tokens[4], tokens[5]); // Server 353 (RPL_NAMREPLY), relay to all clients if we've just JOINed the channel, or relay to any clients // who were pending RPL_NAMREPLYs if it's an existing channel. } else if (strncmp(tokens[1], "353", strlen(tokens[1])) == 0) { // It must be a new channel and we don't have the NAMES if (!channelgotnames(channels, tokens[4])) { debugprint(DEBUG_FULL, "Server 353 received for a new channel, sending to all clients.\n"); // Relay to all clients sendtoallclients(clients, str, sourcefd, settings); } else { // We were already in the channel and have the NAMES debugprint(DEBUG_FULL, "Server 353 received for an existing channel, sending to all clients who were pending RPL_NAMREPLYs.\n"); // If any clients were pending RPL_NAMREPLYs, send this on to them // TODO - Make sure clients only get the ones they were waiting on in case there are multiple conflicting requests going on at once for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingnames > 0) { debugprint(DEBUG_FULL, "Sending 353 RPL_NAMREPLY for channel '%s' to client with fd '%d' who was pending %d RPL_NAMREPLYs.\n", tokens[4], clients[i].fd, clients[i].pendingnames); sendtoclient(clients[i].fd, str, clients, settings, 0); } } } free(strcopyPtr); return 1; // Server 366 (RPL_ENDOFNAMES), relay to all clients if we've just JOINed the channel, or relay to // and decrement from any clients who were waiting on RPL_NAMREPLY if it's an existing channel. } else if (strncmp(tokens[1], "366", strlen(tokens[1])) == 0) { int channelelement; // It must be a new channel and we don't have the NAMES if (!(channelelement = channelgotnames(channels, tokens[3]))) { debugprint(DEBUG_FULL, "Server 366 received for a new channel, sending to all clients and set as got names.\n"); // We have the names now! channels[channelindex(channels, tokens[3])].gotnames = 1; // Relay to all clients sendtoallclients(clients, str, sourcefd, settings); } else { // We were already in the channel and have the NAMES // TODO - Make sure clients only get the ones they were waiting on in case there are multiple conflicting requests going on at once debugprint(DEBUG_FULL, "Server 366 received for an existing channel, sending to all clients who were pending RPL_NAMREPLYs and decrementing their pendingnames count.\n"); for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingnames > 0) { sendtoclient(clients[i].fd, str, clients, settings, 0); // And decrement their pendingnames count debugprint(DEBUG_FULL, "Decrementing pendingnames due 366 RPL_ENDOFNAMES to for channel '%s' to client with fd '%d' who was pending %d RPL_NAMREPLYs.\n", tokens[3], clients[i].fd, clients[i].pendingnames); clients[i].pendingnames--; debugprint(DEBUG_FULL, "Client with fd '%d' has '%d' pendingnames left.\n", clients[i].fd, clients[i].pendingnames); } } } free(strcopyPtr); return 1; } // Server TOPIC received? Update our local channel topic info then relay to clients. if (strncmp(tokens[1], "TOPIC", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server TOPIC found and it is: %s with length %zd! Next token is '%s'. Updating our local channel topic info.\n", tokens[0], strlen(tokens[0]), tokens[2]); // Set the topic itself // Need to extract the final parameter as topics can have spaces // Copy to a temporary string so we still have the original in case we need it char *topiccopy = strdup(str); extractfinalparameter(topiccopy); setchanneltopic(channels, tokens[2], topiccopy); // Extract the author and get the current timestamp // Extract the topic setter from the prefix (Prefix of ":foo!bar@baz" means "foo" set the topic.) // Copy to a temporary string so we still have the original in case we need it char *prefixcopy = strdup(tokens[0]); extractnickfromprefix(prefixcopy); // Get the current time and manipulate it into a C string time_t timenow = time(NULL); int timenowlen = snprintf(NULL, 0, "%ld", timenow); char timenowstr[timenowlen + 1]; // TODO - Make this Year 2038 proof. snprintf(timenowstr, timenowlen + 1, "%ld", timenow); // Actually set the author and timestamp setchanneltopicwhotime(channels, tokens[2], prefixcopy, timenowstr); // And then finally relay to all clients sendtoallclients(clients, str, sourcefd, settings); // Write to replay log if replay logging enabled if (settings->replaylogging) { writereplayline(str, settings->basedir); } // Write to normal log if logging enabled if (settings->logging) { logline(str, settings->ircnick, settings->basedir, LOG_TOPIC); } free(topiccopy); free(prefixcopy); free(strcopyPtr); return 1; } // Server PRIVMSG received? Relay to all clients and write to replay log. if (strncmp(tokens[1], "PRIVMSG", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server PRIVMSG found and it is: %s with length %zd! Next token is '%s'. Relaying to all clients.\n", tokens[0], strlen(tokens[0]), tokens[2]); sendtoallclients(clients, str, sourcefd, settings); // Write to replay log if replay logging enabled if (settings->replaylogging) { writereplayline(str, settings->basedir); } // Write to normal log if logging enabled if (settings->logging) { logline(str, settings->ircnick, settings->basedir, LOG_PRIVMSG); } free(strcopyPtr); return 1; } // Server NICK received? // 1. Find out if it was us and change ircnick and nickuserhost if so // 2. Either way, relay to all clients if (strncmp(tokens[1], "NICK", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server NICK found and it is: %s with length %zd! Next token is '%s'. Updating records and relaying to all clients.\n", tokens[0], strlen(tokens[0]), tokens[2]); // Was it us? // Copy to a temporary string so we still have the original in case we need it char *svrprefixcopy = strdup(tokens[0]); // Just get the nick for comparison extractnickfromprefix(svrprefixcopy); if (strncmp(ircdstrings->ircnick, svrprefixcopy, strlen(ircdstrings->ircnick)) == 0) { debugprint(DEBUG_FULL, "Server NICK: nick is ours ('%s' vs '%s').\n", svrprefixcopy, ircdstrings->ircnick); // Make a copy of the old nickuserhost for updategreetings() below char *nickuserhostcpy = strdup(ircdstrings->nickuserhost); // Update nickuserhost with the new :nick!user@host updatenickuserhost(ircdstrings->nickuserhost, tokens[2]); debugprint(DEBUG_FULL, "Updated nickuserhost to '%s'.\n", ircdstrings->nickuserhost); // Prepare to update ircnick and greetings strings // Temporary copy of new nickuserhost char *prefixcopy = strdup(ircdstrings->nickuserhost); // Get nick from it extractnickfromprefix(prefixcopy); // Update greeting strings for relaying to new clients updategreetings(ircdstrings->greeting001, ircdstrings->greeting002, ircdstrings->greeting003, ircdstrings->greeting004, ircdstrings->greeting005a, ircdstrings->greeting005b, ircdstrings->greeting005c, ircdstrings->nickuserhost, nickuserhostcpy, tokens[2], ircdstrings->ircnick); // Update our nick strcpy(ircdstrings->ircnick, prefixcopy); debugprint(DEBUG_FULL, "Updated ircnick to '%s'.\n", ircdstrings->ircnick); free(nickuserhostcpy); free(prefixcopy); } // Relay to all clients sendtoallclients(clients, str, sourcefd, settings); free(svrprefixcopy); free(strcopyPtr); return 1; } // Server MODE received? See what sort it is and act accordingly. if (strncmp(tokens[1], "MODE", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server MODE found and it is: %s with length %zd! Next token is '%s'. Analysing...\n", tokens[1], strlen(tokens[1]), tokens[2]); // 4 tokens could be either our initial mode or a channel mode (or something else - TODO - what else?) if (counter == 4) { // Might be our initial mode (e.g. ":nick MODE nick :+iwz") char comparison[MAXDATASIZE]; snprintf(comparison, MAXDATASIZE, ":%s MODE %s :", ircdstrings->ircnick, ircdstrings->ircnick); if (strncmp(str, comparison, strlen(comparison)) == 0) { // Looks like it! debugprint(DEBUG_FULL, "Our initial MODE found (%s), storing for later.\n", tokens[3]); // Store in ircdstrings for when clients connect and relay to current clients. strcpy(ircdstrings->mode, tokens[3]); // Relay to all current clients anyway - TODO - Necessary? sendtoallclients(clients, str, sourcefd, settings); free(strcopyPtr); return 1; } // Might be a channel mode (e.g. ":nick!user@host MODE #channel +s") if (tokens[2][0] == '#') { // Looks like it! Tell all clients. debugprint(DEBUG_FULL, "Channel MODE found (%s %s), telling all clients.\n", tokens[2], tokens[3]); sendtoallclients(clients, str, sourcefd, settings); free(strcopyPtr); return 1; } } // Relay to all current clients if not processed by the above sendtoallclients(clients, str, sourcefd, settings); free(strcopyPtr); return 1; } // Server 324 (RPL_CHANNELMODEIS) received? Send to any clients who requested a channel MODE. if (strncmp(tokens[1], "324", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server 324 (RPL_CHANNELMODEIS) found and it is: %s with length %zd! Sending to clients who are pending this.\n", tokens[1], strlen(tokens[1])); // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingchannelmode == 1) { sendtoclient(clients[i].fd, str, clients, settings, 0); // And clear the pending flag clients[i].pendingchannelmode = 0; } } free(strcopyPtr); return 1; } // Server 368 (RPL_ENDOFBANLIST) received? Send to any clients who requested a ban MODE query. - TODO - Identify and handle start/middle of ban responses. if (strncmp(tokens[1], "368", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server 368 (RPL_ENDOFBANLIST) found and it is: %s with length %zd! Sending to clients who are pending this.\n", tokens[1], strlen(tokens[1])); // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingban == 1) { sendtoclient(clients[i].fd, str, clients, settings, 0); // And clear the pending flag clients[i].pendingban = 0; } } free(strcopyPtr); return 1; } // Server 329 (RPL_CREATIONTIME), 352 (RPL_WHOREPLY), or 315 (RPL_ENDOFWHO) received? Send to any clients who requested a WHO. if (strncmp(tokens[1], "329", strlen(tokens[1])) == 0 || strncmp(tokens[1], "352", strlen(tokens[1])) == 0 || strncmp(tokens[1], "315", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server 329 (RPL_CREATIONTIME), 352 (RPL_WHOREPLY), or 315 (RPL_ENDOFWHO) found and it is: %s with length %zd! Sending to clients who are pending one of these.\n", tokens[1], strlen(tokens[1])); // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingwho == 1 && clients[i].fd) { sendtoclient(clients[i].fd, str, clients, settings, 0); // And clear the pending flag if it's 315 (RPL_ENDOFWHO) if (strncmp(tokens[1], "315", strlen(tokens[1])) == 0) { clients[i].pendingwho = 0; } } } free(strcopyPtr); return 1; } // Server 321 (RPL_LISTSTART), 322 (RPL_LIST), or 323 (RPL_LISTEND) received? Send to any clients who requested a WHO. if (strncmp(tokens[1], "321", strlen(tokens[1])) == 0 || strncmp(tokens[1], "322", strlen(tokens[1])) == 0 || strncmp(tokens[1], "323", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server 321 (RPL_LISTSTART), 322 (RPL_LIST), or 323 (RPL_LISTEND) found and it is: %s with length %zd! Sending to clients who are pending one of these.\n", tokens[1], strlen(tokens[1])); // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendinglist == 1 && clients[i].fd) { sendtoclient(clients[i].fd, str, clients, settings, 0); // And clear the pending flag if it's 323 (RPL_LISTEND) if (strncmp(tokens[1], "323", strlen(tokens[1])) == 0) { clients[i].pendinglist = 0; } } } free(strcopyPtr); return 1; } // Server 307 (RPL_SUSERHOST), 311 (RPL_WHOISUSER), 312 (RPL_WHOISSERVER), 313 (RPL_WHOISOPERATOR), 317 (RPL_WHOISIDLE), // 319 (RPL_WHOISCHANNELS), 320 (RPL_WHOISSPECIAL), 671 (RPL_WHOISSECURE), or 318 (RPL_ENDOFWHOIS) received? // Send to any clients who requested a WHOIS. if (strncmp(tokens[1], "307", strlen(tokens[1])) == 0 || strncmp(tokens[1], "311", strlen(tokens[1])) == 0 || strncmp(tokens[1], "312", strlen(tokens[1])) == 0 || strncmp(tokens[1], "313", strlen(tokens[1])) == 0 || strncmp(tokens[1], "317", strlen(tokens[1])) == 0 || strncmp(tokens[1], "319", strlen(tokens[1])) == 0 || strncmp(tokens[1], "320", strlen(tokens[1])) == 0 || strncmp(tokens[1], "671", strlen(tokens[1])) == 0 || strncmp(tokens[1], "318", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server 307 RPL_SUSERHOST, 311 RPL_WHOISUSER, 312 RPL_WHOISSERVER, 313 (RPL_WHOISOPERATOR), 317 RPL_WHOISIDLE, " "319 RPL_WHOISCHANNELS, 320 (RPL_WHOISSPECIAL), 671 (RPL_WHOISSECURE), or 318 RPL_ENDOFWHOIS found and it is: " "%s with length %zd! Sending to clients who are pending one of these.\n", tokens[1], strlen(tokens[1])); // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingwhois == 1 && clients[i].fd) { sendtoclient(clients[i].fd, str, clients, settings, 0); // And clear the pending flag if it's 318 RPL_ENDOFWHOIS if (strncmp(tokens[1], "318", strlen(tokens[1])) == 0) { clients[i].pendingwhois = 0; } } } free(strcopyPtr); return 1; } // Server 314 (RPL_WHOWASUSER), 406 (ERR_WASNOSUCHNICK), or 369 (RPL_ENDOFWHOWAS) received? // Send to any clients who requested a WHOWAS. if (strncmp(tokens[1], "314", strlen(tokens[1])) == 0 || strncmp(tokens[1], "406", strlen(tokens[1])) == 0 || strncmp(tokens[1], "369", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "314 (RPL_WHOWASUSER), 406 (ERR_WASNOSUCHNICK), or 369 (RPL_ENDOFWHOWAS) " "found and it is: %s with length %zd! Sending to clients who are pending one of these.\n", tokens[1], strlen(tokens[1])); // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingwhowas == 1 && clients[i].fd) { sendtoclient(clients[i].fd, str, clients, settings, 0); // And clear the pending flag if it's 369 RPL_ENDOFWHOWAS if (strncmp(tokens[1], "369", strlen(tokens[1])) == 0) { clients[i].pendingwhowas = 0; } } } free(strcopyPtr); return 1; } // Server 312 (RPL_WHOISSERVER) received? Check to see if anyone was pending a WHOIS or a WHOWAS and send to them, if not send to everyone. if (strncmp(tokens[1], "312", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server 312 (RPL_WHOISSERVER) found and it is: %s with length %zd! Sending to clients who are pending this or to everyone if nobody is.\n", tokens[1], strlen(tokens[1])); int waspending = 0; // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingwhois == 1 || clients[i].pendingwhowas == 1) { sendtoclient(clients[i].fd, str, clients, settings, 0); // Note that we were pending this waspending = 1; } } // If no client was pending this, send to everyone if (!waspending) { sendtoallclients(clients, str, 0, settings); } free(strcopyPtr); return 1; } // Server 401 (ERR_NOSUCHNICK) received? Check to see if anyone was pending a WHOIS and send to them, // if not send to everyone (401 was probably in reply to something else like a PRIVMSG). if (strncmp(tokens[1], "401", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server 401 (ERR_NOSUCHNICK) found and it is: %s with length %zd! Sending to clients who are pending this or to everyone if nobody is.\n", tokens[1], strlen(tokens[1])); int waspending = 0; // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingwhois == 1) { sendtoclient(clients[i].fd, str, clients, settings, 0); // Note that we were pending this waspending = 1; } } // If no client was pending this, send to everyone if (!waspending) { sendtoallclients(clients, str, 0, settings); } free(strcopyPtr); return 1; } // Server 432 (ERR_ERRONEUSNICKNAME) or 433 (ERR_NICKNAMEINUSE) received? See which nick we're on and try another. // (But only if we're not already registered with the real IRC server.) if ((strncmp(tokens[1], "432", strlen(tokens[1])) == 0 || strncmp(tokens[1], "433", strlen(tokens[1])) == 0) && !strlen(ircdstrings->greeting004)) { debugprint(DEBUG_SOME, "Server 432 (ERR_ERRONEUSNICKNAME) or 433 (ERR_NICKNAMEINUSE) found and it is: %s with length %zd! Trying another nick...\n", tokens[1], strlen(tokens[1])); // Try to get nick2 and nick3 from the configuration file char nick2[MAXNICKLENGTH]; char nick3[MAXNICKLENGTH]; if (!getconfstr("nick2", settings->conffile, nick2)) { nick2[0] = '\0'; } if (!getconfstr("nick3", settings->conffile, nick3)) { nick3[0] = '\0'; } // Do we have both a nick2 and a nick3? (And not tried autonick yet.) if (nick2[0] && nick3[0] && !ircdstrings->autonicknum) { // Has nick3 already been tried? if (strncmp(ircdstrings->ircnick, nick3, strlen(settings->ircnick)) == 0) { // Then try autonick tryautonick(ircdstrings); // Has nick2 already been tried? } else if (strncmp(ircdstrings->ircnick, nick2, strlen(settings->ircnick)) == 0) { // Then try nick3 debugprint(DEBUG_SOME, "Trying nick3, nick2 was already tried.\n"); strcpy(ircdstrings->ircnick, nick3); // Have neither been tried? } else { // Then try nick2 debugprint(DEBUG_SOME, "Trying nick2, nick3 is also configured.\n"); strcpy(ircdstrings->ircnick, nick2); } // Do we only have a nick2? (And not tried autonick yet.) } else if (nick2[0] && !ircdstrings->autonicknum) { // Has it already been tried? if (strncmp(ircdstrings->ircnick, nick2, strlen(settings->ircnick)) == 0) { // Then try autonick tryautonick(ircdstrings); } else { // Then try it debugprint(DEBUG_SOME, "Trying nick2, nick3 is not configured.\n"); strcpy(ircdstrings->ircnick, nick2); } // Do we have neither? (Or have already started autonick.) } else { // Then try autonick tryautonick(ircdstrings); } // Set whichever one we settled on in the settings in case we reference settings later strcpy(settings->ircnick, ircdstrings->ircnick); // Try it with the server char outgoingmsg[MAXDATASIZE]; snprintf(outgoingmsg, MAXDATASIZE, "NICK %s", ircdstrings->ircnick); // sourcefd = 0 as this is a trusted message sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings); free(strcopyPtr); return 1; } // Server CAP received? if (strncmp(tokens[1], "CAP", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server CAP found and it is: %s with length %zd! Analysing...\n", tokens[1], strlen(tokens[1])); // If the server said "CAP ACK :multi-prefix" then it must have approved our CAP multi-prefix request if (counter == 5) { if (strncmp(tokens[2], ircdstrings->ircnick, strlen(tokens[2])) == 0 && strncmp(tokens[3], "ACK", strlen(tokens[3])) == 0 && strncmp(tokens[4], ":multi-prefix", strlen(tokens[4])) == 0) { ircdstrings->capmultiprefix = 1; } } // We didn't handle it debugprint(DEBUG_FULL, "Unhandled server CAP response.\n"); } // Server NOTICE received? Handle and log if it's from a user, otherwise let pass through to default handler. if (strncmp(tokens[1], "NOTICE", strlen(tokens[1])) == 0) { debugprint(DEBUG_FULL, "Server NOTICE found and it is: %s with length %zd! Analysing...\n", tokens[1], strlen(tokens[1])); // If the first token is a nick!user@host then it's probably from a user if (strstr(tokens[0], "!") != NULL && strstr(tokens[0], "@") != NULL) { debugprint(DEBUG_FULL, "Server NOTICE appears to be from a user, sending to all clients and logging.\n"); sendtoallclients(clients, str, 0, settings); // Write to replay log if replay logging enabled if (settings->replaylogging) { writereplayline(str, settings->basedir); } // Write to normal log if logging enabled if (settings->logging) { logline(str, settings->ircnick, settings->basedir, LOG_PRIVMSG); } free(strcopyPtr); return 1; } else { debugprint(DEBUG_FULL, "Server NOTICE does not appear to be from a user, passing through.\n"); } } } // Don't return if we got here because this means we didn't process something above break; case SOURCE_CLIENT: // If message(s) were from a real IRC client // PASS received? User is trying to log in, check their password. if (strncasecmp(tokens[0], "PASS", strlen(tokens[0])) == 0) { if (checkpassword(tokens[1], settings->conffile)) { debugprint(DEBUG_FULL, "Password accepted! Setting fd %d to authenticated.\n", sourcefd); // Find the client in the clients array and set them as authenticated for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].fd == sourcefd) { // Found client in array, set to authenticated clients[i].authed = 1; debugprint(DEBUG_FULL, "Found and authenticated fd in arr_authed.\n"); // Alert other clients about the successful authentication char alertmsg[MAXDATASIZE]; if (!snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :blabouncer: new client with fd %d has successfully authenticated.", ircdstrings->ircnick, sourcefd)) { fprintf(stderr, "Error while preparing authentication success NOTICE!\n"); debugprint(DEBUG_CRIT, "Error while preparing authentication success NOTICE!\n"); alertmsg[0] = '\0'; } // "except" the current fd - we can use this as "except/sourcefd" since we set them as authed just above sendtoallclients(clients, alertmsg, sourcefd, settings); } } } else { debugprint(DEBUG_SOME, "Password rejected, disconnecting fd %d.\n", sourcefd); disconnectclient(sourcefd, clients, ircdstrings, settings); // Alert other clients about the failed authentication char alertmsg[MAXDATASIZE]; if (!snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :blabouncer: new client with fd %d has failed to authenticate.", ircdstrings->ircnick, sourcefd)) { fprintf(stderr, "Error while preparing authentication failure NOTICE!\n"); debugprint(DEBUG_CRIT, "Error while preparing authentication failure NOTICE!\n"); alertmsg[0] = '\0'; } // "except" 0 since we trust this message sendtoallclients(clients, alertmsg, 0, settings); } free(strcopyPtr); return 1; } // CAP received? Clients can send CAP before PASS so we have to deal with this even if they are not authenticated yet. if (strncasecmp(tokens[0], "CAP", strlen(tokens[0])) == 0) { // But only do something if the real server told us it had a CAP (only multi-prefix for now) if (ircdstrings->capmultiprefix == 1) { debugprint(DEBUG_FULL, "Client CAP received and the server supports CAPs, continuing.\n"); } else { debugprint(DEBUG_FULL, "Client CAP received but the server doesn't support CAPs, returning.\n"); free(strcopyPtr); return 1; } // Get the real IRC server name from greeting001 // They are now pending CAP negotiation clients[arrindex(clients, sourcefd)].pendingcap = 1; char outgoingmsg[MAXDATASIZE]; // If client is requesting CAP list, send it... if (strncasecmp(tokens[1], "LS", strlen(tokens[1])) == 0) { if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s CAP * LS :multi-prefix", ircdstrings->ircdname)) { fprintf(stderr, "Error while preparing CAP LS response!\n"); debugprint(DEBUG_CRIT, "Error while preparing CAP LS response!\n"); outgoingmsg[0] = '\0'; } // ...even if unauthenticated sendtoclient(sourcefd, outgoingmsg, clients, settings, 1); free(strcopyPtr); return 1; // If client is requesting a CAP... } else if (strncasecmp(tokens[1], "REQ", strlen(tokens[1])) == 0) { // ...and it is "multi-prefix", send it if (strncasecmp(tokens[2], ":multi-prefix", strlen(tokens[2])) == 0) { if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s CAP %s ACK :multi-prefix ", ircdstrings->ircdname, ircdstrings->ircnick)) { fprintf(stderr, "Error while preparing CAP ACK response!\n"); debugprint(DEBUG_CRIT, "Error while preparing CAP ACK response!\n"); outgoingmsg[0] = '\0'; } // ...even if unauthenticated sendtoclient(sourcefd, outgoingmsg, clients, settings, 1); free(strcopyPtr); return 1; } // If client is finishing CAP negotiation then mark them as so } else if (strncasecmp(tokens[1], "END", strlen(tokens[1])) == 0) { clients[arrindex(clients, sourcefd)].pendingcap = -1; } } // We're past PASS in the list of possible commands, so ignore // anything else the client says if they are not authenticated yet. if (!clients[arrindex(clients, sourcefd)].authed) { debugprint(DEBUG_CRIT, "Ignoring client command '%s' from sourcefd '%d' as not authenticated yet.\n", tokens[0], sourcefd); free(strcopyPtr); return 1; } // USER received and not pending CAP negotiation during registration? // Or client has just finished negotiating CAP (pendingcap = -1)? // If so, assume this is a new client connecting and catch them on up on the state if ((strncasecmp(tokens[0], "USER", strlen(tokens[0])) == 0 && clients[arrindex(clients, sourcefd)].pendingcap == 0) || clients[arrindex(clients, sourcefd)].pendingcap == -1) { // Somewhere to store the several strings we will need to build and send char outgoingmsg[MAXDATASIZE]; // String to send to client // If registering then they must no longer be pending CAP negotiation clients[arrindex(clients, sourcefd)].pendingcap = 0; // Tell the client to go away if we aren't registered with the real server yet as defined by the last greeting not being set yet if (!strlen(ircdstrings->greeting004)) { sendtoclient(sourcefd, "Sorry, we aren't registered with a real IRC server yet.", clients, settings, 0); disconnectclient(sourcefd, clients, ircdstrings, settings); free(strcopyPtr); return 1; } // Send IRC greeting strings (001/RPL_WELCOME, 002/RPL_YOURHOST, 003/RPL_CREATED, 004/RPL_MYINFO, 005/RPL_ISUPPORT) to client snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting001); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting002); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting003); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting004); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); if (ircdstrings->greeting005a[0]) { snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting005a); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } if (ircdstrings->greeting005b[0]) { snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting005b); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } if (ircdstrings->greeting005c[0]) { snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting005c); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } // Send our own greeting message snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Welcome to blabouncer!", ircdstrings->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Blabouncer commands are all prefixed with BLABOUNCER which you can usually send using \"/QUOTE BLABOUNCER\"", ircdstrings->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Valid blabouncer commands are:", ircdstrings->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REPLAY [[[[days:]hours:]minutes:]seconds]\" (To replay a given length of time of replay log.)", ircdstrings->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER QUIT [quit message]\" (To quit blabouncer, optionally sending [quit message] to the server.)", ircdstrings->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); // Get the channel count so we can enumerate over all channels. // Storing separately so we can skip over blank channels. int channelcount = getchannelcount(channels); // Set the client as pending RPL_NAMREPLYs for 'channelcount' channels debugprint(DEBUG_FULL, "Setting pendingnames to '%d' for client with fd '%d'.\n", channelcount, sourcefd); clients[arrindex(clients, sourcefd)].pendingnames = channelcount; // Get client to join channels, and tell client about those channels for (int i = 0; i < channelcount; i++) { debugprint(DEBUG_FULL, "JOINing channel[%d] out of %d.\n", i, channelcount); // Skip this one and increment channelcount if it's a blank channel if (!channels[i].name[0]) { debugprint(DEBUG_FULL, "Skipping channel[%d], incrementing channelcount.\n", i); channelcount++; continue; } // Get client to join channels if (!snprintf(outgoingmsg, MAXDATASIZE, "%s JOIN :%s", ircdstrings->nickuserhost, channels[i].name)) { fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses!\n"); debugprint(DEBUG_CRIT, "Error while preparing USER just connected, channel JOIN responses!\n"); free(strcopyPtr); return 0; } sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); // Send topic (or lack thereof) to client // If there isn't one set (we guess this if topic timestamp is 0), send 331 RPL_NOTOPIC if (strncmp(channels[i].topicwhen, "0", 1) == 0) { // Prepare the no topic message... if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 331 %s %s :No topic is set.", ircdstrings->ircdname, ircdstrings->ircnick, channels[i].name)) { fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses, 331 RPL_NOTOPIC!\n"); debugprint(DEBUG_CRIT, "Error while preparing USER just connected, channel JOIN responses, 331 RPL_NOTOPIC!\n"); free(strcopyPtr); return 0; } // ..and send it to the client sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); // If there is one set, send 332 RPL_TOPIC and 333 RPL_TOPICWHOTIME } else { // Prepare the topic message... if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 332 %s %s :%s", ircdstrings->ircdname, ircdstrings->ircnick, channels[i].name, channels[i].topic)) { fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses, 332 RPL_TOPIC!\n"); debugprint(DEBUG_CRIT, "Error while preparing USER just connected, channel JOIN responses, 332 RPL_TOPIC!\n"); free(strcopyPtr); return 0; } // ..and send it to the client sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); // Next prepare the topic who/when message... if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 333 %s %s %s %s", ircdstrings->ircdname, ircdstrings->ircnick, channels[i].name, channels[i].topicwho, channels[i].topicwhen)) { fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses, 333 RPL_TOPICWHOTIME!\n"); debugprint(DEBUG_CRIT, "Error while preparing USER just connected, channel JOIN responses, 333 RPL_TOPICWHOTIME!\n"); free(strcopyPtr); return 0; } // ..and send it to the client sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } // Get the latest RPL_NAMREPLY for this channel to relay to the client when it arrives char namesreq[MAXDATASIZE]; snprintf(namesreq, MAXDATASIZE, "NAMES %s", channels[i].name); sendtoserver(server_ssl, namesreq, strlen(namesreq), 0, clients, settings); } // Send our mode to the client (if we have one) if (strlen(ircdstrings->mode) > 0) { if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s MODE %s %s", ircdstrings->ircnick, ircdstrings->ircnick, ircdstrings->mode)) { fprintf(stderr, "Error while preparing USER just connected, MODE response!\n"); debugprint(DEBUG_CRIT, "Error while preparing USER just connected, MODE response!\n"); free(strcopyPtr); return 0; } sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } // Set the client as registered clients[arrindex(clients, sourcefd)].registered = 1; // Catch the client up with the default number of seconds of replay if (!doreplay(sourcefd, settings->replayseconds, clients, settings, ircdstrings, channels)) { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstrings->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } free(strcopyPtr); return 1; } // Pretty much ignore anything else the client says if it's not registered yet, // as the first thing we want to hear is either PASS or USER if (!clients[arrindex(clients, sourcefd)].registered) { debugprint(DEBUG_SOME, "Ignoring client command '%s' from sourcefd '%d' as not registered yet.\n", tokens[0], sourcefd); free(strcopyPtr); return 1; } // Client PING received? If so, send a PONG back with the next element as the argument. if (strncasecmp(tokens[0], "PING", strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Client PING found and it is: %s with length %zd! Sending response...\n", tokens[0], strlen(tokens[0])); char outgoingmsg[MAXDATASIZE]; // String to send to client if (!snprintf(outgoingmsg, MAXDATASIZE, "PONG %s", tokens[1])) { // TODO - Make sure tokens[1] actually has a token fprintf(stderr, "Error while preparing PONG response!\n"); debugprint(DEBUG_CRIT, "Error while preparing PONG response!\n"); outgoingmsg[0] = '\0'; } sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); // We processed something so return true free(strcopyPtr); return 1; } // TODO - Ignoring CAP for now so as not to confuse other clients, but we should probably query the server then relay the response to the client if (strncasecmp(tokens[0], "CAP", strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Client CAP found and it is: %s with length %zd! Ignoring completely for now - TODO - do something useful!\n", tokens[0], strlen(tokens[0])); free(strcopyPtr); return 1; } // Just send NICK to server and let it change all clients' nicks if needed if (strncasecmp(tokens[0], "NICK", strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Client NICK found and it is: %s with length %zd! Sending to server...\n", tokens[0], strlen(tokens[0])); sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); free(strcopyPtr); return 1; } // If PRIVMSG received, send to server, but also reformat and send to all other clients and log to replay file. if (strncasecmp(tokens[0], "PRIVMSG", strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Client PRIVMSG found and it is: %s with length %zd! Sending to server then back to other clients...\n", tokens[0], strlen(tokens[0])); // Send original request straight to server sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); // If it seems to be a CTCP VERSION request, just send to the server (CTCP requests are delimited with \1) if (counter == 3 && strncmp(tokens[2], ":\1VERSION\1", strlen(tokens[2])) == 0) { debugprint(DEBUG_FULL, "Client PRIVMSG looked like a CTCP VERSION request, so not sending to other clients.\n"); free(strcopyPtr); return 1; } // Rebuild to full PRIVMSG string and relay to all other clients char outgoingmsg[MAXDATASIZE]; // String to send to client if (!snprintf(outgoingmsg, MAXDATASIZE, "%s %s", ircdstrings->nickuserhost, str)) { fprintf(stderr, "Error while preparing PRIVMSG relay from another bouncer client.\n"); debugprint(DEBUG_CRIT, "Error while preparing PRIVMSG relay from another bouncer client.\n"); free(strcopyPtr); return 0; } // Send to all except source client sendtoallclients(clients, outgoingmsg, sourcefd, settings); // Write to replay log if replay logging enabled if (settings->replaylogging) { writereplayline(outgoingmsg, settings->basedir); } // Write to normal log if logging enabled if (settings->logging) { logline(outgoingmsg, settings->ircnick, settings->basedir, LOG_PRIVMSG); } free(strcopyPtr); return 1; } // Just send JOIN to server and let it talk back to clients as required if (strncasecmp(tokens[0], "JOIN", strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Client JOIN found and it is: %s with length %zd! Sending to server...\n", tokens[0], strlen(tokens[0])); sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); free(strcopyPtr); return 1; } // Don't do anything with QUIT, just let the client go - TODO: Let another clients know with a NOTICE or something // A client has QUIT, so disconnect (close) them and don't do anything else for now - TODO: Let another clients know with a NOTICE or something if (strncasecmp(tokens[0], "QUIT", strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Client QUIT found from fd %d and it is: %s with length %zd! Disconnecting that fd.\n", sourcefd, tokens[0], strlen(tokens[0])); disconnectclient(sourcefd, clients, ircdstrings, settings); free(strcopyPtr); return 1; } // Just send PART to server and let it talk back to clients as required if (strncasecmp(tokens[0], "PART", strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Client PART found and it is: %s with length %zd! Sending to server...\n", tokens[0], strlen(tokens[0])); sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); free(strcopyPtr); return 1; } // Just send TOPIC to server and let it talk back to clients as required if (strncasecmp(tokens[0], "TOPIC", strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Client TOPIC found and it is: %s with length %zd! Sending to server...\n", tokens[0], strlen(tokens[0])); sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); free(strcopyPtr); return 1; } // Take note of what sort of MODE was requested, mark the client as waiting for the reply (so not all clients get it) // Send it to the server either way and let it talk back to the requesting client as required if (strncasecmp(tokens[0], "MODE", strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Client MODE found and it is: %s with length %zd! Analysing...\n", tokens[0], strlen(tokens[0])); // Is it a ban MODE request (MODE #channel b)? if (counter >= 3 && strncmp(tokens[2], "b", strlen("b")) == 0) { debugprint(DEBUG_FULL, "Ban MODE request received, marking as pending.\n"); clients[arrindex(clients, sourcefd)].pendingban = 1; } else if (counter == 2) { // Assume a normal channel mode request (MODE #channel) - TODO - What if it isn't!? debugprint(DEBUG_FULL, "Assuming channel MODE request received, marking as pending.\n"); clients[arrindex(clients, sourcefd)].pendingchannelmode = 1; } // Either way, send it on the server sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); free(strcopyPtr); return 1; } // WHO requested, mark the client as waiting for the reply (so not all clients get it) and send it on the server if (strncasecmp(tokens[0], "WHO", strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Client WHO found and it is: %s with length %zd! Marking as pending.\n", tokens[0], strlen(tokens[0])); clients[arrindex(clients, sourcefd)].pendingwho = 1; // Either way, send it on the server sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); free(strcopyPtr); return 1; } // LIST requested, mark the client as waiting for the reply (so not all clients get it) and send it on the server if (strncasecmp(tokens[0], "LIST", strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Client LIST found and it is: %s with length %zd! Marking as pending.\n", tokens[0], strlen(tokens[0])); clients[arrindex(clients, sourcefd)].pendinglist = 1; // Either way, send it on the server sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); free(strcopyPtr); return 1; } // Client WHOIS received, send straight on to server and mark the client as pending the response if (strncasecmp(tokens[0], "WHOIS", strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Client WHOIS found and it is: %s with length %zd! Sending to server and setting client as pending.\n", tokens[0], strlen(tokens[0])); clients[arrindex(clients, sourcefd)].pendingwhois = 1; sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); free(strcopyPtr); return 1; } // Client WHOWAS received, send straight on to server and mark the client as pending the response if (strncasecmp(tokens[0], "WHOWAS", strlen(tokens[0])) == 0) { debugprint(DEBUG_FULL, "Client WHOWAS found and it is: %s with length %zd! Sending to server and setting client as pending.\n", tokens[0], strlen(tokens[0])); clients[arrindex(clients, sourcefd)].pendingwhowas = 1; sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); free(strcopyPtr); return 1; } // Client NOTICE received if (strncasecmp(tokens[0], "NOTICE", strlen(tokens[0])) == 0) { // Rebuild to full NOTICE string including our nick!user@host for logging char fullmsg[MAXDATASIZE]; if (!snprintf(fullmsg, MAXDATASIZE, "%s %s", ircdstrings->nickuserhost, str)) { fprintf(stderr, "Error while preparing NOTICE string for logging.\n"); debugprint(DEBUG_CRIT, "Error while preparing NOTICE string for logging.\n"); free(strcopyPtr); return 0; } // Write to replay log if replay logging enabled if (settings->replaylogging) { writereplayline(fullmsg, settings->basedir); } // Write to normal log if logging enabled if (settings->logging) { logline(fullmsg, settings->ircnick, settings->basedir, LOG_PRIVMSG); } // If it's a CTCP VERSION response then only send to the server (CTCP requests are delimited with \1) if (counter >= 3 && strncmp(tokens[2], ":\1VERSION", strlen(tokens[2])) == 0) { debugprint(DEBUG_FULL, "Client NOTICE looked like a CTCP VERSION response, so just sending to the server.\n"); sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings); free(strcopyPtr); return 1; } // If it wasn't a CTCP VERSION response, then let this fall through to the default unhandled action by not returning here } // Custom BLABOUNCER command received // Case insensitive comparisons here since clients won't be recognising and uppercasing these commands if (strncasecmp(tokens[0], "BLABOUNCER", strlen(tokens[0])) == 0) { char outgoingmsg[MAXDATASIZE]; debugprint(DEBUG_FULL, "Client BLABOUNCER found and it is: %s with length %zd!\n", tokens[1], strlen(tokens[1])); // REPLAY received, send the requested length of replay time to the client if (strncasecmp(tokens[1], "REPLAY", strlen("REPLAY")) == 0) { debugprint(DEBUG_FULL, "Client BLABOUNCER REPLAY (custom blabouncer command) found and it is: %s with length %zd!\n", tokens[1], strlen(tokens[1])); // Split the request into days:hours:minutes:seconds // Track which colon-separated token within this request we're on int timecounter = 0; // Build array of colon-separated tokens char timetokens[MAXTOKENS][MAXDATASIZE]; // Copy to a temporary string so we still have the original in case it's not processed char *timestrcopy = strdup(tokens[2]); // Keep track of initial pointer for free()ing later char *timestrcopyPtr = timestrcopy; char *timetoken; while ((timetoken = strsep(×trcopy, ":")) != NULL) { if (*timetoken == '\0') continue; // Skip consecutive matches if (timecounter >= MAXTOKENS) break; // Too many tokens debugprint(DEBUG_FULL, "Time token: \"%s\", length %zd.\n", timetoken, strlen(timetoken)); // Copy into the token array (strlen + 1 to get the NULL terminator) strncpy(timetokens[timecounter], timetoken, strlen(timetoken) + 1); timecounter++; } // Make sure we don't have more than four (d:h:m:s) components if (timecounter > 4) { debugprint(DEBUG_SOME, "Too many time components requested by REPLAY command. Telling client.\n"); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Too many time components requestd by REPLAY command. Expected up to four (days:hours:minutes:seconds).", ircdstrings->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); free(timestrcopyPtr); free(strcopyPtr); return 1; } // Make sure all the components are numbers for (int i = 0; i < timecounter; i++) { // Temporary number and pointer for checking errors long check; char *str_end; errno = 0; check = strtol(timetokens[i], &str_end, 10); if (str_end == timetokens[i] || ((check == LONG_MAX || check == LONG_MIN) && errno == ERANGE)) { debugprint(DEBUG_SOME, "Invalid number '%s' requested by REPLAY command. Telling client.\n", timetokens[i]); if (!snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Invalid number '%s' requested by REPLAY command.", ircdstrings->ircnick, timetokens[i])) { fprintf(stderr, "Error while preparing REPLAY invalid number response!\n"); debugprint(DEBUG_CRIT, "Error while preparing REPLAY invalid number response!\n"); outgoingmsg[0] = '\0'; } sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); free(timestrcopyPtr); free(strcopyPtr); return 1; } } // How many seconds we're going to replay int replayseconds = 0; // If d:h:m:s provided if (timecounter == 4) { replayseconds += 86400 * strtol(timetokens[0], NULL, 10); replayseconds += 3600 * strtol(timetokens[1], NULL, 10); replayseconds += 60 * strtol(timetokens[2], NULL, 10); replayseconds += strtol(timetokens[3], NULL, 10); } // If h:m:s provided if (timecounter == 3) { replayseconds += 3600 * strtol(timetokens[0], NULL, 10); replayseconds += 60 * strtol(timetokens[1], NULL, 10); replayseconds += strtol(timetokens[2], NULL, 10); } // If m:s provided if (timecounter == 2) { replayseconds += 60 * strtol(timetokens[0], NULL, 10); replayseconds += strtol(timetokens[1], NULL, 10); } // If s provided if (timecounter == 1) { replayseconds += strtol(timetokens[0], NULL, 10); } debugprint(DEBUG_FULL, "Replaying '%s' which is '%d' seconds.\n", tokens[2], replayseconds); if (!doreplay(sourcefd, replayseconds, clients, settings, ircdstrings, channels)) { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstrings->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } free(timestrcopyPtr); free(strcopyPtr); return 1; // QUIT received, send QUIT message to server and exit cleanly } else if (strncasecmp(tokens[1], "QUIT", strlen("QUIT")) == 0) { debugprint(DEBUG_SOME, "Client BLABOUNCER QUIT found and it is: %s with length %zd!\n", tokens[1], strlen(tokens[1])); // Combine "QUIT :" with any optional quit message the user provided if (counter > 2) { // Any optional quit message comes 16 characters after the start of the string ("BLABOUNCER QUIT " is 16 characters) cleanexit(server_ssl, clients, sourcefd, ircdstrings, settings, str + 16); } else { cleanexit(server_ssl, clients, sourcefd, ircdstrings, settings, ""); } // Unrecognised BLABOUNCER command received, send some help instructions } else { debugprint(DEBUG_SOME, "Client BLABOUNCER unrecognised command found and it is: %s with length %zd! Sending a help message.\n", tokens[1], strlen(tokens[1])); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unrecognised BLABOUNCER command received. Valid commands are:", ircdstrings->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REPLAY [[[[days:]hours:]minutes:]seconds]\" (To replay a given length of time of replay log.)", ircdstrings->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER QUIT [quit message]\" (To quit blabouncer, optionally sending [quit message] to the server.)", ircdstrings->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); free(strcopyPtr); return 1; } } break; default: fprintf(stderr, "Unexpected raw IRC string source!\n"); debugprint(DEBUG_CRIT, "Unexpected raw IRC string source!\n"); free(strcopyPtr); return 0; } // =============================================> debugprint(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "...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(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "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])); debugprint(DEBUG_CRIT, "Unexpected raw IRC string source for unprocessed message \"%s\", length %zd.!\n", messages[i], strlen(messages[i])); return 0; } } } debugprint(DEBUG_FULL, "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 pselect() - 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); } // Let's set up signal handling stuff here since we're about to enter The Big Loop (TM) // We'll handle SIGINT (Ctrl-C) and SIGTERM (default signal of `kill`) signal(SIGINT, sighandler); // SIGINT (2) signal(SIGTERM, sighandler); // SIGTERM (15) // Block those two signals sigset_t sigset, oldset; sigemptyset(&sigset); sigaddset(&sigset, SIGINT); sigaddset(&sigset, SIGTERM); sigprocmask(SIG_BLOCK, &sigset, &oldset); while (1) { debugprint(DEBUG_FULL, "top of loop, fdmax %d.\n", fdmax); FD_ZERO(&rfds); // clear entries from fd set // Add STDIN (fd 0) to read fds to monitor if we're not in background/daemon mode if (!settings->background) { FD_SET(STDIN, &rfds); } 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(DEBUG_FULL, "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(DEBUG_FULL, "pselect()ing...\n"); // Check to see if any fd in the fd_set is waiting or a signal happened - blocks here until one one of those things happens // (pselect() to do signal handling in additiona to fd monitoring) if (pselect(fdmax + 1, &rfds, NULL, NULL, NULL, &oldset) < 0) { // network socket + 1, rfds, no writes, no exceptions/errors, no timeout, original sigmask if (errno == EINTR) { // Signal caught, do signal handling debugprint(DEBUG_CRIT, "signal '%d' happened, exiting!\n", signum); if (signum == SIGINT) { cleanexit(server_ssl, clients, 0, &ircdstrings, settings, "SIGINT received"); } else if (signum == SIGTERM) { cleanexit(server_ssl, clients, 0, &ircdstrings, settings, "SIGTERM received"); } else { cleanexit(server_ssl, clients, 0, &ircdstrings, settings, "Unexpected signal received"); } } else { // Some other error perror("pselect"); debugprint(DEBUG_CRIT, "pselect() error, errno '%d'.\n", errno); continue; } } // 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(DEBUG_FULL, "reading server socket!\n"); if ((servernumbytes = sockread(server_ssl, serverbuf, MAXRCVSIZE - 1, settings->servertls)) == -1) { perror("recv"); printf("receive error (-1), skipping loop iteration...\n"); debugprint(DEBUG_CRIT, "serversockfd receive error (-1), skipping loop iteration, errno '%d'.\n"); } else if (servernumbytes == 0) { perror("recv"); printf("socket closed (or no data received) (0), skipping loop iteration...\n"); debugprint(DEBUG_CRIT, "serversockfd socket closed (or no data received) (0), skipping loop iteration, errno '%d'.\n"); } // 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(DEBUG_SOME, "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"); debugprint(DEBUG_CRIT, "Error: bouncer-server failed to process raw string.\n"); } } // see if there's anything from stdin (unless we're in background/daemon mode) if (!settings->background && FD_ISSET(STDIN, &rfds)) { debugprint(DEBUG_FULL, "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(DEBUG_FULL, "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(DEBUG_FULL, "checking client socket %d out of %d.\n", i, fdmax); if (FD_ISSET(i, &rfds)) { debugprint(DEBUG_FULL, "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(DEBUG_SOME, "...new connection!\n"); // handle new connections if (numclients(clients) >= MAXCLIENTS) { fprintf(stderr, "too many clients, disconnecting and skipping loop iteration!\n"); debugprint(DEBUG_CRIT, "too many clients, disconnecting and skipping loop iteration!\n"); disconnectclient(i, clients, &ircdstrings, settings); continue; } 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) { debugprint(DEBUG_CRIT, "SSL_accept failed for fd %d.\n", clients[j].fd); ERR_print_errors_fp(stderr); } else { debugprint(DEBUG_FULL, "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(DEBUG_FULL, "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"); debugprint(DEBUG_CRIT, "Error while preparing new client connection NOTICE!\n"); alertmsg[0] = '\0'; } // "except" 0 since we trust this message sendtoallclients(clients, alertmsg, 0, settings); debugprint(DEBUG_FULL, "bouncer-client: total client connections: %d\n", numclients(clients)); } } else { debugprint(DEBUG_FULL, "...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(DEBUG_SOME, "bouncer-client: socket %d hung up\n", i); } else { perror("recv"); debugprint(DEBUG_CRIT, "bouncer-client: socket error, clientnum bytes '%d', errno '%d'.\n", clientnumbytes, errno); } // 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(DEBUG_FULL, "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(DEBUG_SOME, "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"); debugprint(DEBUG_CRIT, "Error: bouncer-client failed to process raw string.\n"); } } } } } } 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; // Terminate our global debug file string in case it's referenced before being read from file debugpath[0] = '\0'; // Assume background/daemon mode unless specified otherwise on the command line settings.background = 1; // Set configuration file to string to blank for now settings.conffile[0] = '\0'; // Check to see what was specified on the command line // TODO - Do better command line argument handling (no required order) char helptext[] = "usage: %s [-f] [-c /path/to/blabouncer.conf] (-f for foreground mode)\n"; if (argc == 2) { if (strcmp(argv[1], "-f")) { fprintf(stderr, helptext, argv[0]); exit(1); } else { settings.background = 0; } } else if (argc == 3) { if (strcmp(argv[1], "-c")) { fprintf(stderr, helptext, argv[0]); exit(1); } else { strcpy(settings.conffile, argv[2]); } } else if (argc == 4) { if (strcmp(argv[1], "-f") || strcmp(argv[2], "-c")) { fprintf(stderr, helptext, argv[0]); exit(1); } else { settings.background = 0; strcpy(settings.conffile, argv[3]); } } else if (argc > 4) { fprintf(stderr, helptext, argv[0]); exit(1); } // If a configuration file wasn't set on the command line, set it now if (!settings.conffile[0]) { 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); } } // Populate 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'; } // What is the connect command, if any? if (!getconfstr("connectcommand", settings.conffile, settings.connectcommand)) { settings.connectcommand[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(DEBUG_CRIT, "blabouncer started at %s, debugging is set to %d.\n", timestr, debugold); // Set debugging back to whatever it was debug = debugold; debugprint(DEBUG_SOME, "Using configuration file '%s'.\n", settings.conffile); // Unless specified otherwise on the command line, fork to background if (settings.background) { pid_t pid, sid; // Fork from parent if ((pid = fork()) < 0) { printf("Couldn't fork, exiting.\n"); debugprint(DEBUG_CRIT, "Couldn't fork, exiting.\n"); exit(1); } // Exit parent (pid will be > 0 for the child) if (pid > 0) { exit(0); } umask(0); // Let child become process group leader if ((sid = setsid()) < 0) { printf("Couldn't setsid() child, exiting.\n"); debugprint(DEBUG_CRIT, "Couldn't setsid() child, exiting.\n"); exit(1); } // In case our cwd changes chdir("/"); // TODO - close() STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO here, might need to change fd number // logic/assumptions elsewhere (such as fd 3 being server, fd 4 being clients, etc.) } // 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; }