// TODO: // - FIGURE OUT WHEN WE'RE REGISTERED (NICK and USER from user are replied to with commands 001, 002, 003, and 004 from the ircd when registration successful) // - int registered = 0; already created to track when this has happened (assuming still want to do it this way) // - handle nick in use // - Might need to change channel struct nicks to be channel struct user struct with its own nick/modes/etc. // - Do we actually need to store the modes in the channel struct? // - Get CAP from server and relay to client // - Add blabouncer MOTD (375, 372, 376) // - "01:53:47 -!- ServerMode/#test [b] by irc.tghost.co.uk" on existing clients when new client connects // - Keep track of changing user nicks/modes // - Should replay log do more than PRIVMSGs? // - Consider moving the three fd-related arrays into one struct // - Check authentication before even getting to the send functions to save unnecessary processing // - Configurable auto channels // - Comma separated channel list in JOINs/PARTs // - Only send some things to the requesting client (e.g. LIST replies) // - Normal (non-replay) log // - Alert when clients connect/authenticate/disconnect // - Perhaps rename arr_ssl and server_ssl since they may not even be OpenSSL sockets // // Example WHOIS reply: // BOUNCER-SERVER RECEIVED: :irc.tghost.co.uk 307 blabounce l_bratch :is identified for this nick // "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 "functions.h" #include "sockets.h" #include "config.h" #include "replay.h" #define SOURCE_SERVER 0 #define SOURCE_CLIENT 1 #define EXCEPT_NONE 0 // It seems to be that *message length* is max 512 bytes, but a socket read from at least UnrealIRCd seems to be up to at least 2416 (+1 for null) bytes. // 1208 bytes with OpenSSL, 2416 bytes with plain text. #define MAXRCVSIZE 2417 #define MAXDATASIZE 513 // max number of bytes we can get at once (RFC2812 says 512, plus one for null terminator) #define STDIN 0 // stdin is fd 0 #define MAXCLIENTS 32 // maximum number of clients that can connect to the bouncer at a time #define MAXTOKENS 100 // maximum number of (CRLF or space) separated tokens per server response we expect (TODO - check this is reasonable) (CRLF and spaces just grouped here due to laziness) #define MAXPONGSIZE 32 // let's assume PING/PONG responses can't be larger than this (TODO - check this [it's totally made up]!) #define MAXCHANNELS 1024 // let's assume 1024 is reasonable for now (it's configured per IRCd) #define MAXCHANLENGTH 50 // 50 according to RFC 2811 and RFC 2822 #define MAXCHANUSERS 8192 // Randomly picked (TODO - is there an actual maximum number of users per channel?) #define MAXNICKLENGTH 64 // Randomly picked (TODO - is there an actual maximum number (ignoring the RFC preference of 9)?) #define 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 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". char modes[MAXDATASIZE]; // TODO - Is there a particular maximum modes length? char nicks[MAXCHANUSERS][MAXNICKLENGTH]; // TODO - Need to modify this as people leave/join, not just when we first join char namestype[2]; // Single character (@/*/=) (+1 for null char) // TODO - Is this a sensible name? }; struct ircdstrings { char greeting001[MAXDATASIZE]; char greeting002[MAXDATASIZE]; char greeting003[MAXDATASIZE]; char greeting004[MAXDATASIZE]; char ircdname[MAXDATASIZE]; char nickuserhost[MAXDATASIZE]; // TODO - Make sure this changes when nick changes, if that's meant to happen. char ircnick[MAXNICKLENGTH]; // TODO - Make sure this changes when nick changes 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) }; // 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 ircserver[HOST_NAME_MAX]; char ircserverport[MAXPORTLEN]; char conffile[PATH_MAX]; char certfile[PATH_MAX]; char keyfile[PATH_MAX]; int clienttls; int servertls; }; // Return index of requested client FD within arr_clients // This is used because at least arr_clients, arr_authed, and ssl share the same element order. // TODO - Use this wherever we are calculating the position (various places) instead of // duplicating code. int arrindex(int arr_clients[], int clientfd) { // Find the client in the clients array and make sure they are authenticated for (int i = 0; i < MAXCLIENTS; i++) { if (arr_clients[i] == clientfd) { return i; } } // Something went wrong, we didn't find it return 0; } // Send whatever string to a specific client by providing the FD int sendtoclient(int fd, char *str, int arr_clients[], int arr_authed[], SSL **arr_ssl, struct settings *settings) { appendcrlf(str); // Do this just before sending so callers don't need to worry about it // Find the client in the clients array and make sure they are authenticated for (int i = 0; i < MAXCLIENTS; i++) { if (arr_clients[i] == fd) { // Found client in array, check authentication status if (!arr_authed[i]) { printf("sendtoclient(): skipping unauthenticated client with fd %d.\n", arr_clients[i]); return 0; } } } printf("sendtoclient(): sending \"%s\" (length %zd) to client with fd %d.\n", str, strlen(str), fd); if (socksend(arr_ssl[arrindex(arr_clients, fd)], str, strlen(str), settings->clienttls) == -1) { perror("error: sendtoclient() send()\n"); return 0; } return 1; } // Disconnect the client fd "fd" by close()ing it and remove // it from the arr_clients array of clients. // Also set its authentication status to 0. int disconnectclient(int fd, int arr_clients[], int arr_authed[]) { printf("disconnectclient(): disconnecting client fd '%d'\n", fd); close(fd); // bye! // Remove the client from the clients array for (int j = 0; j < MAXCLIENTS; j++) { if (arr_clients[j] == fd) { printf("found and clearing fd %d from arr_clients[%d]\n", fd, j); arr_clients[j] = 0; arr_authed[j] = 0; return 1; } } // If we got here, we didn't find and clear the client // TODO - Do something with a failed return code return 0; } // 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(int *clientsockfd, int fdmax, int arr_clients[], char *str, int except, int arr_authed[], SSL **arr_ssl, struct settings *settings) { char *sendertype; 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) { printf("sendtoallclients(): trusting clientfd of 0.\n"); break; } if (arr_clients[i] == except) { // Found client in array, check authentication status if (!arr_authed[i]) { printf("sendtoallclients(): skipping unauthenticated client with fd %d.\n", arr_clients[i]); return 0; } } } // relay/send to all clients... for (int i = *clientsockfd + 1; i <= fdmax; i++) { // Skip the current client if "except" non-zero (no need to send back to itself) if (i == except) { continue; } // TODO maybe see if things are in rfds (not sure what this means any more - perhaps it was to do with only sending to connected things which is now solved) // ...but only if they are connected for (int j = 0; j < MAXCLIENTS; j++) { if (arr_clients[j] == i) { if (!arr_authed[j]) { printf("sendtoallclients(): skipping unauthenticated client with fd %d.\n", arr_clients[j]); continue; } printf("sendtoallclients(): %s: sending '%s' to client with fd %d.\n", sendertype, str, i); if (socksend(arr_ssl[arrindex(arr_clients, i)], str, strlen(str), settings->clienttls) == -1) { perror("error: sendtoallclients() send()\n"); } } } } return 0; } // 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 *str, int str_len, int clientfd, int arr_clients[], int arr_authed[], struct settings *settings) { 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) { printf("sendtoserver(): trusting clientfd of 0.\n"); break; } if (arr_clients[i] == clientfd) { // Found client in array, check authentication status if (!arr_authed[i]) { printf("sendtoserver(): skipping unauthenticated client with fd %d.\n", arr_clients[i]); return 0; } } } printf("sendtoserver(): sending %s to IRC server (length %d).\n", str, str_len); if (socksend(server_ssl, str, str_len, settings->servertls) == -1) { printf("error: sendtoserver() send()\n"); return 0; } return 1; } int createchannel(struct channel *channels, char *name, char *topic, char *topicwho, char *topicwhen, char *modes, char *namestype) { printf("createchannel(): given \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", and \"%s\".\n", name, topic, topicwho, topicwhen, modes, namestype); // 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'; printf("createchannel(): name given was '%s', length '%ld'.\n", name, strlen(name)); printf("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'; printf("createchannel(): topic given was '%s', length '%ld'.\n", topic, strlen(topic)); printf("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'; strncpy(channels[i].modes, modes, strlen(modes)); channels[i].modes[strlen(modes)] = '\0'; strncpy(channels[i].namestype, topic, strlen(namestype)); channels[i].namestype[strlen(namestype)] = '\0'; return 1; break; // TODO - This should be safe to remove since return is hit first } } perror("error: createchannel() didn't create a channel\n"); // TODO - Make a failed return do something to callers return 0; } int addusertochannel(struct channel *channels, char *channelname, char *nick) { printf("addusertochannel(): given \"%s\" and \"%s\".\n", channelname, nick); for (int i = 0; i < MAXCHANNELS; i++) { if (strncmp(channels[i].name, channelname, strlen(channelname)) == 0) { // Looking for free user slot... for (int j = 0; j < MAXCHANUSERS; j++) { if (!channels[i].nicks[j][0]) { // ...and adding user strncpy(channels[i].nicks[j], nick, strlen(nick)); channels[i].nicks[j][strlen(nick)] = '\0'; return 1; } } break; } } // TODO - Make a failed return do something to callers return 0; } int setchanneltopicwhotime(struct channel *channels, char *channelname, char *who, char *when) { printf("setchanneltopicwhotime(): given \"%s\", \"%s\", and \"%s\".\n", channelname, who, when); printf("setchanneltopicwhotime(): who: '%s' with length '%ld'.\n", who, strlen(who)); printf("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) { printf("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 setchannelnamestype(struct channel *channels, char *channelname, char *type) { printf("setchannelnamestype(): given \"%s\" and \"%s\".\n", channelname, type); for (int i = 0; i < MAXCHANNELS; i++) { if (strncmp(channels[i].name, channelname, strlen(channelname)) == 0) { strncpy(channels[i].namestype, type, strlen(type)); channels[i].namestype[strlen(type)] = '\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++; } } printf("getchannelcount(): counted %d channels.\n", count); return count; } int getchannelnamescount(struct channel *channels, char *channelname) { int count = 0; // Find channel for (int i = 0; i < MAXCHANNELS; i++) { if (strncmp(channels[i].name, channelname, strlen(channelname)) == 0) { // Count nicks for (int j = 0; j < MAXCHANUSERS; j++) { // If not null character then it's a nick if (channels[i].nicks[j][0]) { count++; } } break; } } printf("getchannelnamescount(): counted %d names for channel '%s'.\n", count, channelname); return count; } int removechannel(struct channel *channels, char *name) { printf("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'; printf("removechannel(): channel removed and topicwhen set to '%s'.\n", channels[i].topicwhen); return 1; } } perror("error: removechannel() didn't remove a channel\n"); // TODO - Make a failed return do something to callers return 0; } // 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, int *clientsockfd, char *str, int source, int fdmax, int arr_clients[], int sourcefd, struct ircdstrings *ircdstrings, struct channel *channels, int arr_authed[], SSL **arr_ssl, 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]; printf(" >> 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); char *token; while ((token = strsep(&strcopy, " ")) != NULL) { if (*token == '\0') continue; // Skip consecutive matches printf(" >> 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) switch(source) { case SOURCE_SERVER: // If message(s) were from the real IRC server // 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) { printf("Server PING found and it is: %s with length %zd! Sending response...\n", tokens[0], strlen(tokens[0])); char outgoingmsg[MAXDATASIZE]; // String to send to server if (!snprintf(outgoingmsg, MAXDATASIZE, "PONG %s", tokens[1])) { // TODO - Make sure tokens[1] actually has a token fprintf(stderr, "Error while preparing PONG response!\n"); exit(1); } // sourcefd = 0 as this is a trusted response sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), 0, arr_clients, arr_authed, settings); // We processed something so return true return 1; } // Prefix received? TODO - Care about what the prefix is - what if it's a different server/network/whatever? if (tokens[0][0] == ':') { printf("Prefix found: '%s'! Next token is '%s', length %zd.\n", tokens[0], tokens[1], strlen(tokens[1])); // Greetings 001 through to 004, 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) { printf("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'; printf("Storing our nick!user@host (%s) from greeting 001 in ircdstrings struct.\n", tokens[counter - 1]); strncpy(ircdstrings->nickuserhost, tokens[counter - 1], strlen(tokens[counter - 1])); // Null the end of the string ircdstrings->nickuserhost[strlen(tokens[counter - 1])] = '\0'; return 1; } else if (strncmp(tokens[1], "002", strlen(tokens[1])) == 0) { printf("Found greeting 002 (%s), storing in ircdstrings struct.\n", str); strncpy(ircdstrings->greeting002, str, strlen(str)); // Null the end of the string ircdstrings->greeting002[strlen(str)] = '\0'; return 1; } else if (strncmp(tokens[1], "003", strlen(tokens[1])) == 0) { printf("Found greeting 003 (%s), storing in ircdstrings struct.\n", str); strncpy(ircdstrings->greeting003, str, strlen(str)); // Null the end of the string ircdstrings->greeting003[strlen(str)] = '\0'; return 1; } else if (strncmp(tokens[1], "004", strlen(tokens[1])) == 0) { printf("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'; printf("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'; 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) { printf("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 printf("processircmessage(): Channel name was '%s'\n", tokens[2]); stripprefix(tokens[2]); printf("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]); stripprefix(prefixcopy); if (strncmp(prefixcopy, ircdstrings->nickuserhost, strlen(tokens[0])) == 0) { printf("Server JOIN: nickuserhost is ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->nickuserhost); // 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", "CHANNELMODES", "="); // If the user JOINing is not us, record the user in our channel array. } else { printf("Server JOIN: nickuserhost is NOT ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->nickuserhost); extractnickfromprefix(prefixcopy); printf("Server JOIN: adding user '%s' to channel '%s'.\n", prefixcopy, tokens[2]); addusertochannel(channels, tokens[2], prefixcopy); } // And then send to all clients sendtoallclients(clientsockfd, fdmax, arr_clients, str, sourcefd, arr_authed, arr_ssl, settings); return 1; } // Server PART received? Remove from our local channel list if it's us. if (strncmp(tokens[1], "PART", strlen(tokens[1])) == 0) { printf("Server PART 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]); // 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]); stripprefix(prefixcopy); if (strncmp(prefixcopy, ircdstrings->nickuserhost, strlen(tokens[0])) == 0) { printf("Server PART: nickuserhost is ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->nickuserhost); removechannel(channels, tokens[2]); } else { printf("Server PART: nickuserhost is NOT ours ('%s' vs '%s').\n", prefixcopy, ircdstrings->nickuserhost); } // And then send to all clients sendtoallclients(clientsockfd, fdmax, arr_clients, str, sourcefd, arr_authed, arr_ssl, settings); 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) { printf("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); // 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) { printf("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) add channel names type (secret/private/public) and the names of those already in the channel } else if (strncmp(tokens[1], "353", strlen(tokens[1])) == 0) { // TODO - Do we also need to watch out for 366 (RPL_ENDOFNAMES)? printf("Server 353 (RPL_NAMREPLY) found, extracting nicks and storing in channel struct.\n"); // Set channel type setchannelnamestype(channels, tokens[4], tokens[3]); // Add nicks // Strip prefix from first nick first stripprefix(tokens[5]); // Now go through all nicks and add them to the channel for (int i = 5; i < counter; i++) { addusertochannel(channels, tokens[4], tokens[i]); } } // Server TOPIC received? Update our local channel topic info then relay to clients. if (strncmp(tokens[1], "TOPIC", strlen(tokens[1])) == 0) { printf("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(clientsockfd, fdmax, arr_clients, str, sourcefd, arr_authed, arr_ssl, settings); return 1; } // Server PRIVMSG received? Relay to all clients and write to replay log. if (strncmp(tokens[1], "PRIVMSG", strlen(tokens[1])) == 0) { printf("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(clientsockfd, fdmax, arr_clients, str, sourcefd, arr_authed, arr_ssl, settings); // Write to replay log writereplayline(str); return 1; } } // 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 (strncmp(tokens[0], "PASS", strlen(tokens[0])) == 0) { if (checkpassword(tokens[1], settings->conffile)) { printf("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 (arr_clients[i] == sourcefd) { // Found client in array, set to authenticated arr_authed[i] = 1; printf("Found and authenticated fd in arr_authed.\n"); } } } else { printf("Password rejected, disconnecting fd %d.\n", sourcefd); disconnectclient(sourcefd, arr_clients, arr_authed); } return 1; } // USER received? If so, assume this is a new client connecting and catch them on up on the state if (strncmp(tokens[0], "USER", strlen(tokens[0])) == 0) { // Somewhere to store the several strings we will need to build and send char outgoingmsg[MAXDATASIZE]; // String to send to client // Send IRC greeting strings (001/RPL_WELCOME, 002/RPL_YOURHOST, 003/RPL_CREATED, 004/RPL_MYINFO) to client snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting001); sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting002); sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting003); sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting004); sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); // Get the channel count so we can enumerate over all channels. // Storing separately so we can skip over blank channels. int channelcount = getchannelcount(channels); // Get client to join channels, and tell client about those channels for (int i = 0; i < channelcount; i++) { printf("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]) { printf("Skipping channel[%d], incrementing channelcount.\n", i); channelcount++; continue; } // Get client to join channels if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s JOIN :%s", ircdstrings->nickuserhost, channels[i].name)) { fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses!\n"); exit(1); } sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); // Send topic (or lack thereof) to client // If there isn't one set (we guess this if topic timestamp is 0), send 331 RPL_NOTOPIC if (strncmp(channels[i].topicwhen, "0", 1) == 0) { // Prepare the no topic message... if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 331 %s %s :No topic is set.", ircdstrings->ircdname, ircdstrings->ircnick, channels[i].name)) { fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses, 331 RPL_NOTOPIC!\n"); exit(1); } // ..and send it to the client sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); // If there is one set, send 332 RPL_TOPIC and 333 RPL_TOPICWHOTIME } else { // Prepare the topic message... if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 332 %s %s :%s", ircdstrings->ircdname, ircdstrings->ircnick, channels[i].name, channels[i].topic)) { fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses, 332 RPL_TOPIC!\n"); exit(1); } // ..and send it to the client sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); // Next prepare the topic who/when message... if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 333 %s %s %s %s", ircdstrings->ircdname, ircdstrings->ircnick, channels[i].name, channels[i].topicwho, channels[i].topicwhen)) { fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses, 333 RPL_TOPICWHOTIME!\n"); exit(1); } // ..and send it to the client sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); } // Send list of names // Store count of names so we can increment it in the loop if we encounter gaps in the names array int namescount = getchannelnamescount(channels, channels[i].name); // Go through each name... for (int j = 0; j < namescount; j++) { // TODO - Batch these up and send multiple names per line // ...making sure it they are not null (probably a gap in the names array) if (!channels[i].nicks[j][0]) { // Skip this one and increment namescount so we find it later on instead namescount++; continue; } // If there was a nick found, send it to the client (one per line at the moment - TODO - batch them up into fewer lines) if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 353 %s %s %s :%s", ircdstrings->ircdname, ircdstrings->ircnick, channels[i].namestype, channels[i].name, channels[i].nicks[j])) { fprintf(stderr, "Error while preparing USER just connected, channel NAMES responses!\n"); exit(1); } sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); } // Once all names are sent, send the "end of /NAMES" 366 (RPL_ENDOFNAMES) message if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 366 %s %s :End of /NAMES list.", ircdstrings->ircdname, ircdstrings->ircnick, channels[i].name)) { fprintf(stderr, "Error while preparing USER just connected, end of NAMES response!\n"); exit(1); } sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); } // Figure out how many lines to replay int numlines = replaylines(settings->replayseconds); printf("Replay log lines: '%d'.\n", numlines); if (numlines < 0) { printf("Error getting number of replay lines.\n"); exit(1); } // Relay those lines! for (int i = 0; i < numlines; i++) { if (!readreplayline(settings->replayseconds, i, outgoingmsg)) { printf("Error requesting replay line.\n"); exit(1); } printf("Sending replay line: '%s'.\n", outgoingmsg); sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); } return 1; } // Client PING received? If so, send a PONG back with the next element as the argument. if (strncmp(tokens[0], "PING", strlen(tokens[0])) == 0) { printf("Client PING found and it is: %s with length %zd! Sending response...\n", tokens[0], strlen(tokens[0])); char outgoingmsg[MAXDATASIZE]; // String to send to client if (!snprintf(outgoingmsg, MAXDATASIZE, "PONG %s", tokens[1])) { // TODO - Make sure tokens[1] actually has a token fprintf(stderr, "Error while preparing PONG response!\n"); exit(1); } sendtoclient(sourcefd, outgoingmsg, arr_clients, arr_authed, arr_ssl, settings); // We processed something so return true return 1; } // TODO - Ignoring CAP for now so as not to confuse other clients, but we should probably query the server then relay the response to the client if (strncmp(tokens[0], "CAP", strlen(tokens[0])) == 0) { printf("Client CAP found and it is: %s with length %zd! Ignoring completely for now - TODO - do something useful!\n", tokens[0], strlen(tokens[0])); return 1; } // Just send NICK to server and let it change all clients' nicks if needed if (strncmp(tokens[0], "NICK", strlen(tokens[0])) == 0) { printf("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, arr_clients, arr_authed, settings); return 1; } // If PRIVMSG received, send to server, but also reformat and send to all other clients and log to replay file. if (strncmp(tokens[0], "PRIVMSG", strlen(tokens[0])) == 0) { printf("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, arr_clients, arr_authed, settings); // Rebuild to full PRIVMSG string and relay to all other clients char outgoingmsg[MAXDATASIZE]; // String to send to client if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s %s", ircdstrings->nickuserhost, str)) { fprintf(stderr, "Error while preparing PRIVMSG relay from another bouncer client.\n"); exit(1); } // Send to all except source client sendtoallclients(clientsockfd, fdmax, arr_clients, outgoingmsg, sourcefd, arr_authed, arr_ssl, settings); // Write to replay log writereplayline(outgoingmsg); return 1; } // Just send JOIN to server and let it talk back to clients as required if (strncmp(tokens[0], "JOIN", strlen(tokens[0])) == 0) { printf("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, arr_clients, arr_authed, settings); return 1; } // Don't do anything with QUIT, just let the client go - TODO: Let another clients know with a NOTICE or something // A client has QUIT, so disconnect (close) them and don't do anything else for now - TODO: Let another clients know with a NOTICE or something if (strncmp(tokens[0], "QUIT", strlen(tokens[0])) == 0) { printf("Client QUIT found from fd %d and it is: %s with length %zd! Disconnecting that fd.\n", sourcefd, tokens[0], strlen(tokens[0])); close(sourcefd); disconnectclient(sourcefd, arr_clients, arr_authed); return 1; } // Just send PART to server and let it talk back to clients as required if (strncmp(tokens[0], "PART", strlen(tokens[0])) == 0) { printf("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, arr_clients, arr_authed, settings); return 1; } // Just send TOPIC to server and let it talk back to clients as required if (strncmp(tokens[0], "TOPIC", strlen(tokens[0])) == 0) { printf("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, arr_clients, arr_authed, settings); return 1; } break; default: fprintf(stderr, "Unexpected raw IRC string source!\n"); exit(1); } // =============================================> printf("Done with processircmessage()\n"); // If we got here then we didn't process anything 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, int *clientsockfd, char *str, int source, int fdmax, int arr_clients[], int sourcefd, struct ircdstrings *ircdstrings, struct channel *channels, int arr_authed[], SSL **arr_ssl, struct settings *settings) { // Copy to a temporary string so we still have the original in case it's not processed char *strcopy = strdup(str); printf("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 printf("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(strcopy); // 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]); printf("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 printf("...into new string '%s' and clearing currentmsg holding area.\n", messages[0]); ircdstrings->currentmsg[0] = '\0'; } // 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) { printf("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, clientsockfd, messagecopy, source, fdmax, arr_clients, sourcefd, ircdstrings, channels, arr_authed, arr_ssl, settings)) { printf("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') { printf("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!? printf("bouncer-server: sending unprocessed server message \"%s\" to all clients, length %zd.\n", messages[i], strlen(messages[i])); sendtoallclients(clientsockfd, fdmax, arr_clients, messages[i], EXCEPT_NONE, arr_authed, arr_ssl, settings); break; case SOURCE_CLIENT: // If message(s) were from a real IRC client // Send to server printf("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, arr_clients, arr_authed, settings); printf("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(clientsockfd, fdmax, arr_clients, messages[i], sourcefd, arr_authed, arr_ssl, settings); break; default: fprintf(stderr, "Unexpected raw IRC string source for unprocessed message \"%s\", length %zd.!\n", messages[i], strlen(messages[i])); exit(1); } } } printf("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 char outgoingmsg[MAXDATASIZE]; // String to send to server int outgoingmsgrc; // Return code from getstdin() for outgoing message int arr_clients[MAXCLIENTS]; // Array of all client FDs - 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) int arr_authed[MAXCLIENTS]; // Array of client authentication statuses - 0 means not authenticated, 1 means authenticated. Element numbers match those of arr_clients. SSL *arr_ssl[MAXCLIENTS]; // Array of OpenSSL structures when using TLS, or faked by casting fd ints to SSL* if not. - TODO - Can we drop one of either arr_clients or arr_ssl now? int num_clients = 0; // Current number of clients int fdmax; // highest numbered socket fd socklen_t addrlen; // client remote address size char remoteIP[INET6_ADDRSTRLEN]; // remote IP (assume up to IPv6 size) int newfd; // newly accept()ed socket descriptor struct sockaddr_storage remoteaddr; // client address int clientnumbytes; fdmax = *clientsockfd; // keep track of highest fd number, currently client socket as created last (after server socket) fd_set rfds; // set of read fds to monitor with select() - 0: stdin, 1: stdout, 2: stderr, 3 and higher: sockets (at time of writing, 3: real IRC server, 4: client listener, 5 and higher: clients) // Set all the clients to be "not connected" and "not authenticated" for (int i = 0; i < MAXCLIENTS; i++) { arr_clients[i] = 0; arr_authed[i] = 0; } // Initialise OpenSSL (used for both client and server) init_openssl(); // OpenSSL for server side if configured SSL *server_ssl; // Need to create this either way as referenced later if (settings->servertls) { printf("server openssl start.\n"); SSL_CTX *serverctx; 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 { printf("SSL_connect() success.\n"); } printf("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 // Registered with the IRCd yet? ////////////// int registered = 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; // Set them to zero-length strings for now ircdstrings.greeting001[0] = '\0'; ircdstrings.greeting002[0] = '\0'; ircdstrings.greeting003[0] = '\0'; ircdstrings.greeting004[0] = '\0'; ircdstrings.ircdname[0] = '\0'; ircdstrings.nickuserhost[0] = '\0'; ircdstrings.ircnick[0] = '\0'; ircdstrings.ircusername[0] = '\0'; ircdstrings.currentmsg[0] = '\0'; // 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 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, arr_clients, arr_authed, 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, arr_clients, arr_authed, settings); // Struct of channels we're in struct channel *channels; channels = malloc(sizeof(struct channel) * MAXCHANNELS); // =============================================> // OpenSSL context for client side (that clients connect to) (need to create this whether or not using TLS as it is referenced later) SSL_CTX *ctx; // If using client TLS if (settings->clienttls) { // Set up and configure client OpenSSL context ctx = create_openssl_context(SOURCE_CLIENT); configure_openssl_context(ctx, settings->certfile, settings->keyfile); } while (1) { printf("top of loop, fdmax %d.\n", fdmax); FD_ZERO(&rfds); // clear entries from fd set FD_SET(STDIN, &rfds); // add STDIN (fd 0) to read fds to monitor FD_SET(*serversockfd, &rfds); // add our server network socket to monitor FD_SET(*clientsockfd, &rfds); // add our client network socket to monitor // Add all connected clients to monitor (only add ones that are connected (clients[i] > 0)) // TODO - make sure *serversockfd stays at the same value (probably 3?) in all cases - what if the server disconnects/reconnects/etc. // TODO - now that only connected clients are monitored, perhaps tracking using both fdmax and num_client loops is unnecessary? for (int i = 0; i < MAXCLIENTS; i++) { if (arr_clients[i] > 0) { printf("monitoring fd %d.\n", arr_clients[i]); FD_SET(arr_clients[i], &rfds); } } printf("select()ing...\n"); // check to see if anything in the fd_set is waiting - waits here until one of the fds in the set does something if (select(fdmax + 1, &rfds, NULL, NULL, NULL) < 0) { // network socket + 1, rfds, no writes, no exceptions/errors, no timeout printf("receive error, exiting!?\n"); perror("select"); } // TODO - switch around the serversockfd and STDIN FD_ISSET if-statements? They feel the wrong way round. Are they like this on purpose? I can't remember. // (although actually stdin may go once its not wanted for possible direct interaction for debugging) // See if there's anything to read from the server side (the real IRCd) if (FD_ISSET(*serversockfd, &rfds)) { printf("reading server socket!\n"); if ((servernumbytes = sockread(server_ssl, serverbuf, MAXRCVSIZE - 1, settings->servertls)) == -1) { printf("receive error (-1), exiting...\n"); perror("recv"); exit(1); } else if (servernumbytes == 0) { printf("socket closed (or no data received) (0), exiting...\n"); perror("recv"); exit(1); } serverbuf[servernumbytes] = '\0'; printf("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, clientsockfd, serverbuf, SOURCE_SERVER, fdmax, arr_clients, EXCEPT_NONE, &ircdstrings, channels, arr_authed, arr_ssl, settings)) { fprintf(stderr, "Error: bouncer-server failed to process raw string.\n"); exit(1); } } // see if there's anything from stdin if (FD_ISSET(STDIN, &rfds)) { printf("reading stdin!\n"); 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]) { printf("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, arr_clients, arr_authed, 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; } printf("checking client socket %d out of %d.\n", i, fdmax); if (FD_ISSET(i, &rfds)) { printf("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) { printf("...new connection!\n"); // handle new connections if (num_clients >= MAXCLIENTS) { fprintf(stderr, "too many clients!\n"); exit(1); // TODO - handle cleanly instead of exiting! } addrlen = sizeof remoteaddr; newfd = accept(*clientsockfd, (struct sockaddr *)&remoteaddr, &addrlen); if (newfd == -1) { // something went wrong when accept()ing perror("accept"); } else { FD_SET(newfd, &rfds); // add to master set // TODO - needed? going to be re-done at the top anyway... if (newfd > fdmax) { // keep track of the max fdmax = newfd; } // Find a free element in the clients array and set to new fd value for (int j = 0; j < MAXCLIENTS; j++) { if (arr_clients[j] == 0) { arr_clients[j] = newfd; // Ensure its authentication status is set to 0 arr_authed[j] = 0; // If using TLS then... if (settings->clienttls) { // ...set as OpenSSL FD and SSL_accept it arr_ssl[j] = SSL_new(ctx); SSL_set_fd(arr_ssl[j], newfd); if (SSL_accept(arr_ssl[j]) <= 0) { printf("SSL_accept failed for fd %d.\n", j); ERR_print_errors_fp(stderr); } else { printf("SSL_accept succeeded for fd %d.\n", j); } } else { // If not using TLS then cast newfd to SSL* even though it will just be the original newfd int really arr_ssl[j] = (SSL*)(long int)newfd; } break; } } // TODO - Handle the "find a free element" loop not finding a free element num_clients++; // Track total number of clients printf("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); printf("bouncer-client: total client connections: %d\n", num_clients); } } else { printf("...previous connection!\n"); // handle data from a client if ((clientnumbytes = sockread(arr_ssl[arrindex(arr_clients, i)], clientbuf, sizeof clientbuf, settings->clienttls)) <= 0) { // got error or connection closed by client if (clientnumbytes == 0) { // connection closed printf("bouncer-client: socket %d hung up\n", i); } else { perror("recv"); } // Disconnect the client disconnectclient(i, arr_clients, arr_authed); 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 num_clients--; // Track total number of clients printf("bouncer-client: total client connections: %d\n", num_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'; } printf("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, clientsockfd, clientbuf, SOURCE_CLIENT, fdmax, arr_clients, i, &ircdstrings, channels, arr_authed, arr_ssl, settings)) { fprintf(stderr, "Error: bouncer-client failed to process raw string.\n"); exit(1); } } } } } } } int main(int argc, char *argv[]) { // Structure of our various settings which are to either be read from the configuration file or set at runtime struct settings settings; // Check to see if a configuration file was specified on the command line if (argc == 3) { if (strcmp(argv[1], "-c")) { fprintf(stderr,"usage: %s [-c /path/to/blabouncer.conf]\n", argv[0]); exit(1); } else { strcpy(settings.conffile, argv[2]); } } else if (argc == 2 || argc > 3) { fprintf(stderr,"usage: %s [-c /path/to/blabouncer.conf]\n", argv[0]); exit(1); } else { // If none provided, set to default strcpy(settings.conffile, "blabouncer.conf"); } printf("Using configuration file '%s'.\n", settings.conffile); // Populate said settings from configuration file // How many seconds of replay log should automatically be replayed - TODO - Can we do error checking on this? settings.replayseconds = getconfint("replayseconds", settings.conffile); // What port should the bouncer listen on if (!getconfstr("clientport", settings.conffile, settings.clientport)) { printf("main(): error getting 'clientport' from configuration file.\n"); exit(1); } // What is the configured nick? if (!getconfstr("nick", settings.conffile, settings.ircnick)) { printf("main(): error getting 'nick' from configuration file.\n"); exit(1); } // What is the configured username? if (!getconfstr("username", settings.conffile, settings.ircusername)) { printf("main(): error getting 'username' from configuration file.\n"); exit(1); } // What is the configured real name? if (!getconfstr("realname", settings.conffile, settings.ircrealname)) { printf("main(): error getting 'realname' from configuration file.\n"); exit(1); } // What 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); } // 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)) { printf("main(): error getting 'certfile' from configuration file.\n"); exit(1); } // What is the certificate key file path? if (!getconfstr("keyfile", settings.conffile, settings.keyfile)) { printf("main(): error getting 'keyfile' from configuration file.\n"); exit(1); } } // 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) // TODO: track fdmax - kind of doing this now with arr_clients and num_clients but might be pointlessly tracking both in some places (?) // 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 // BOUNCER-TO-SERVER socket things // 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; }