From f4070050375a621d4297e0629c0072519cdf5d2d Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Fri, 19 Apr 2019 02:15:41 +0200 Subject: Lots and lots of refactoring and comments work in preparation for more actual IRC/bouncer functionality --- blabouncer.c | 190 ++++++++++++++++++++++++++++++++++++++--------------------- functions.c | 6 +- sockets.c | 34 +++++------ 3 files changed, 143 insertions(+), 87 deletions(-) diff --git a/blabouncer.c b/blabouncer.c index bd280d2..39e8166 100644 --- a/blabouncer.c +++ b/blabouncer.c @@ -1,18 +1,12 @@ // TODO FOR TOMORROW: -// - SPLIT THE RECEIVED MESSAGES INTO TOKENS AND FIGURE OUT WHEN WE'RE REGISTERED (NICK and USER from user are replied to with commands 001, 002, 003, and 004 from ircd when registration successful -// - perhaps split with strtok() +// - 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) -// - Actually maybe let's just try to register with the server ourselves without a client (telnet might be more useful than irssi for debugging) -// - implement automatic PING/PONG responses // 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 -// uses socket demo code from https://beej.us/guide/bgnet/html/single/bgnet.html -// and getstdin() uses getLine() from https://stackoverflow.com/questions/4023895/ - #include #include #include @@ -28,11 +22,15 @@ #include "functions.h" #include "sockets.h" +#define SOURCE_SERVER 0 +#define SOURCE_CLIENT 1 +#define EXCEPT_NONE 0 + #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 space-separated tokens per server response -#define MAXPONGSIZE 32 // let's assume PING/PONG responses can't be larger than this (TODO - check this!) +#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 IRCNICK "blabounce" // TODO: change this to a config option! #define IRCUSER "blabounce" // TODO: change this to a config option! @@ -41,7 +39,7 @@ int debugmode = 0; // Relay/send message to all clients (optionally except one) -// "except" is used to send to all clients _except_ the fd provided (except = 0 avoids this, i.e. sends to all) +// "except" is used to send to all clients _except_ the fd provided (except = 0 (EXCEPT_NONE) avoids this, i.e. sends to all) int sendtoallclients(int *clientsockfd, int fdmax, int arr_clients[], char *str, int str_len, int except) { char *sendertype; @@ -78,6 +76,8 @@ int sendtoallclients(int *clientsockfd, int fdmax, int arr_clients[], char *str, // Send whatever string to the real IRC server int sendtoserver(int *serversockfd, char *str, int str_len) { + 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?) printf("sendtoserver(): sending %s to IRC server (length %d).\n", str, str_len); if (send(*serversockfd, str, str_len, 0) == -1) { // 0 is bitwise OR for no send() flags printf("send error, exiting...\n"); @@ -87,89 +87,150 @@ int sendtoserver(int *serversockfd, char *str, int str_len) { return 0; } -// Figure out what to do with each CRLF-split server response (if anything) -// Now by splitting out the different components by space (ASCII 0x20) -// Get serversockfd in case we need to send a response +// Figure out what to do with each CRLF-split IRC message (if anything) +// by splitting out the different components by space character (ASCII 0x20). +// Get serversockfd and clientsockfd in case we need to send a response. +// +// 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 processserverresponse(int *serversockfd, char *str) { +int processircmessage(int *serversockfd, int *clientsockfd, char *str, int source) { // 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(" >> processserverresponse(): The server response is: \"%s\". Processing it...\n", str); + printf(" >> processircmessage(): Processing source %d message \"%s\"...\n", source, str); char *token; while ((token = strsep(&str, " ")) != NULL) { if (*token == '\0') continue; // Skip consecutive matches - printf("Response Token: \"%s\", length %zd.\n", token, strlen(token)); + 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++; } - printf("We found these tokens:\n"); - for (int i = 0; i < counter; i++) { - printf("Token %d: \"%s\"\n", i, tokens[i]); - } - // <============================================= // IRC command processing (commands from 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("PING found and it is: %s with length %zd! Sending response...\n", tokens[0], strlen(tokens[0])); + switch(source) { + case SOURCE_SERVER: // If message(s) were from the real IRC 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("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); - } - appendcrlf(outgoingmsg); - sendtoserver(serversockfd, outgoingmsg, strlen(outgoingmsg)); + 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); + } + sendtoserver(serversockfd, outgoingmsg, strlen(outgoingmsg)); - // We processed something so return true - return 1; + // We processed something so return true + return 1; + } + break; + case SOURCE_CLIENT: // If message(s) were from a real IRC client + break; + default: + fprintf(stderr, "Unexpected raw IRC string source!\n"); + exit(1); } // =============================================> - printf("Done with processserverresponse()\n"); + + printf("Done with processircmessage()\n"); // If we got here then we didn't process anything return 0; } -// Figure out what the server sent and decide what to do with it (if anything) -// First by splitting out the different responses by CRLF -// Get serversockfd in case we need to send a response +// 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. // -// 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 processserverstring(int *serversockfd, char *str) { - // Track if something in this string got processed - int processed = 0; +// serversockfd, clientsockfd, fdmax, arr_clients, except 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(int *serversockfd, int *clientsockfd, char *str, int source, int fdmax, int arr_clients[], int except) { + // 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); - printf("processserverstring(): The server sent: \"%s\". Processing it...\n", str); + // Track which CLRF-separated message within this string we're on + int messagecount = 0; - // Split the string by CRLF and send each CRLF-separated string (each "response") on to processserverresponse(s) + // 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(&str, "\r\n")) != NULL) { - if (*token == '\0') continue; // Skip consecutive matches + while ((token = strsep(&strcopy, "\r\n")) != NULL) { + if (*token == '\0') continue; // Skip consecutive matches printf("String Token: \"%s\", length %zd.\n", token, strlen(token)); - // Pass on for the individual response to be processed - if (processserverresponse(serversockfd, token)) { - processed = 1; + // Copy into the token array (strlen + 1 to get the NULL terminator) + strncpy(messages[messagecount], token, strlen(token) + 1); + messagecount++; + } + + free(strcopy); + + // 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(serversockfd, clientsockfd, messagecopy, source)) { + printf("Message processed: \"%s\", NULLing...\n", messages[i]); + messages[i][0] = '\0'; } + free(messagecopy); } - printf("Done with processserverstring()\n"); + // 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 \"%s\" to all clients, length %zd.\n", messages[i], strlen(messages[i])); + sendtoallclients(clientsockfd, fdmax, arr_clients, messages[i], strlen(messages[i]), EXCEPT_NONE); + break; + case SOURCE_CLIENT: // If message(s) were from a real IRC client + // Send to server + printf("bouncer-client: sending \"%s\" to the server, length %zd.\n", messages[i], strlen(messages[i])); + sendtoserver(serversockfd, messages[i], strlen(messages[i])); + + // send the same thing to all *other* clients (all except for fd "i") + sendtoallclients(clientsockfd, fdmax, arr_clients, messages[i], strlen(messages[i]), except); + break; + default: + fprintf(stderr, "Unexpected raw IRC string source!\n"); + exit(1); + } + } + } + + printf("Done with processrawstring()\n"); - return processed; + return 1; } // Where the big bouncing loop is @@ -207,13 +268,12 @@ void dochat(int *serversockfd, int *clientsockfd) { // Send our NICK snprintf(outgoingmsg, MAXDATASIZE, "NICK %s", IRCNICK); // TODO - Check for success (with return code) - appendcrlf(outgoingmsg); sendtoserver(serversockfd, outgoingmsg, strlen(outgoingmsg)); // Send our USER snprintf(outgoingmsg, MAXDATASIZE, "USER %s 8 * : %s", IRCUSER, IRCREALNAME); // TODO - Check for success (with return code) - appendcrlf(outgoingmsg); + // TODO - Send a more intelligent/correct USER string sendtoserver(serversockfd, outgoingmsg, strlen(outgoingmsg)); // =============================================> @@ -261,15 +321,12 @@ void dochat(int *serversockfd, int *clientsockfd) { serverbuf[servernumbytes] = '\0'; printf("BOUNCER-SERVER RECEIVED: %s\n", serverbuf); - printf("bouncer-server: sending it to all clients...\n"); // Try to process received string (which should contain one or more server responses/commands) - // If 0 returned, do nothing because we processed it elsewhere - // If non-0 returned, send to all clients to see if they want to handle it // TODO - What if there were two server respones/commands and only one didn't need relaying? - if (!processserverstring(serversockfd, serverbuf)) { - // Relay/send to all clients ("except" = 0 because this should send to all clients) - sendtoallclients(clientsockfd, fdmax, arr_clients, serverbuf, servernumbytes, 0); + if (!processrawstring(serversockfd, clientsockfd, serverbuf, SOURCE_SERVER, fdmax, arr_clients, EXCEPT_NONE)) { + fprintf(stderr, "Error: bouncer-server failed to process raw string.\n"); + exit(1); } } @@ -285,8 +342,6 @@ void dochat(int *serversockfd, int *clientsockfd) { printf ("Error! Too long. Would have allowed up to: [%s]\n", outgoingmsg); } - appendcrlf(outgoingmsg); - sendtoserver(serversockfd, outgoingmsg, strlen(outgoingmsg)); } @@ -294,7 +349,7 @@ void dochat(int *serversockfd, int *clientsockfd) { // (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 as we know we have just accept()ed it + // skip if newfd is the current iteration of this loop, since we know we have just accept()ed it if (i == newfd) { continue; } @@ -364,12 +419,13 @@ void dochat(int *serversockfd, int *clientsockfd) { clientbuf[strlen(clientbuf) - 1] = '\0'; } printf("BOUNCER-CLIENT RECEIVED: '%s'\n", clientbuf); - printf("bouncer-client: sending it to the server...\n"); - appendcrlf(clientbuf); - sendtoserver(serversockfd, clientbuf, strlen(clientbuf)); - // send the same thing to all *other* clients (all except for fd "i") - sendtoallclients(clientsockfd, fdmax, arr_clients, clientbuf, strlen(clientbuf), i); + // 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(serversockfd, clientsockfd, clientbuf, SOURCE_CLIENT, fdmax, arr_clients, i)) { + fprintf(stderr, "Error: bouncer-client failed to process raw string.\n"); + exit(1); + } } } } diff --git a/functions.c b/functions.c index 73302b8..97f8629 100644 --- a/functions.c +++ b/functions.c @@ -46,7 +46,7 @@ void appendcrlf(char *string) { } int startlen = strlen(string); - string[startlen] = '\r'; - string[startlen + 1] = '\n'; - string[startlen + 2] = '\0'; + string[startlen] = '\r'; // Add CR + string[startlen + 1] = '\n'; // Add LF + string[startlen + 2] = '\0'; // Finish with null terminator } diff --git a/sockets.c b/sockets.c index 6c1370b..40079c2 100644 --- a/sockets.c +++ b/sockets.c @@ -60,22 +60,22 @@ int createclientsocket(char *listenport) { int listener; // listening socket descriptor int rv; // return value for getaddrinfo (for error message) - struct addrinfo hints, *ai, *p; + struct addrinfo hints, *ai, *p; int yes = 1; // for enabling socket options with setsockopt - // get us a socket and bind it - memset(&hints, 0, sizeof hints); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE; + // get us a socket and bind it + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; - if ((rv = getaddrinfo(NULL, listenport, &hints, &ai)) != 0) { - fprintf(stderr, "bouncer-client: %s\n", gai_strerror(rv)); - exit(1); - } + if ((rv = getaddrinfo(NULL, listenport, &hints, &ai)) != 0) { + fprintf(stderr, "bouncer-client: %s\n", gai_strerror(rv)); + exit(1); + } // Try for IPv6 - for (p = ai; p != NULL; p = p->ai_next) { + for (p = ai; p != NULL; p = p->ai_next) { if (p->ai_family == AF_INET6) { listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol); if (listener != -1) { @@ -110,13 +110,13 @@ int createclientsocket(char *listenport) { exit(1); } - // if we got here, it means we didn't get bound - if (p == NULL) { - fprintf(stderr, "bouncer-client: failed to bind\n"); - exit(2); - } + // if we got here, it means we didn't get bound + if (p == NULL) { + fprintf(stderr, "bouncer-client: failed to bind\n"); + exit(2); + } - freeaddrinfo(ai); // all done with this + freeaddrinfo(ai); // all done with this // listen if (listen(listener, BACKLOG) == -1) { -- cgit v1.2.3