From e09bc8128960305c9ae48278cfcdbce0e55b97d6 Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Sun, 12 May 2019 15:59:21 +0100 Subject: Handle large raw strings from the server and properly track/rejoin long split/truncated messages --- blabouncer.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/blabouncer.c b/blabouncer.c index 2ac787e..751143d 100644 --- a/blabouncer.c +++ b/blabouncer.c @@ -10,9 +10,17 @@ // - Keep track of changing user nicks/modes // - Should relay log do more than PRIVMSGs? // - Make certificate paths configurable -// - Rename "ssl" to "arr_ssl" and remove the old send() based commented lines when ready +// - Rename "ssl" to "arr_ssl" +// - Implement TLS on real IRCd server side +// - Make TLS optional +// - Make cert/key filenames configurable +// - Make listen port configurable // - 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 +// - Long raw strings like a 321/322/323 channel LIST, and the initial server welcome, need to be read and split properly +// - Only send some things to the requesting client (e.g. LIST replies) // // Example WHOIS reply: // BOUNCER-SERVER RECEIVED: :irc.tghost.co.uk 307 blabounce l_bratch :is identified for this nick @@ -45,6 +53,9 @@ #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 @@ -79,6 +90,7 @@ struct ircdstrings { char ircnick[MAXNICKLENGTH]; // TODO - Make sure this changes when nick changes char ircusername[MAXUSERNAMELEN]; char ircrealname[MAXREALNAMELEN]; + 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) }; int debugmode = 0; @@ -637,10 +649,10 @@ int processircmessage(int *serversockfd, int *clientsockfd, char *str, int sourc return 1; } - - // Don't return if we got here because this means we didn't process something above } + // Don't return if we got here because this means we didn't process something 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. @@ -915,6 +927,32 @@ int processrawstring(int *serversockfd, int *clientsockfd, char *str, int source 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 @@ -934,20 +972,20 @@ int processrawstring(int *serversockfd, int *clientsockfd, char *str, int 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 messasge \"%s\" to all clients, length %zd.\n", messages[i], strlen(messages[i])); + 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, ssl); break; case SOURCE_CLIENT: // If message(s) were from a real IRC client // Send to server - printf("bouncer-client: sending unprocessed client messasge \"%s\" to the server, length %zd.\n", messages[i], strlen(messages[i])); + printf("bouncer-client: sending unprocessed client message \"%s\" to the server, length %zd.\n", messages[i], strlen(messages[i])); sendtoserver(serversockfd, messages[i], strlen(messages[i]), sourcefd, arr_clients, arr_authed); - printf("bouncer-client: sending unprocessed client messasge \"%s\" to all other clients, length %zd.\n", messages[i], strlen(messages[i])); + 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, ssl); break; default: - fprintf(stderr, "Unexpected raw IRC string source for unprocessed messasge \"%s\", length %zd.!\n", messages[i], strlen(messages[i])); + fprintf(stderr, "Unexpected raw IRC string source for unprocessed message \"%s\", length %zd.!\n", messages[i], strlen(messages[i])); exit(1); } } @@ -960,8 +998,8 @@ int processrawstring(int *serversockfd, int *clientsockfd, char *str, int source // Where the big bouncing loop is void dochat(int *serversockfd, int *clientsockfd) { - char serverbuf[MAXDATASIZE]; // buffer for receiving data on server socket - char clientbuf[MAXDATASIZE]; // buffer for receiving data on client socket(s) + 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 @@ -1007,6 +1045,7 @@ void dochat(int *serversockfd, int *clientsockfd) { ircdstrings.ircnick[0] = '\0'; ircdstrings.ircusername[0] = '\0'; ircdstrings.ircrealname[0] = '\0'; + ircdstrings.currentmsg[0] = '\0'; // Read required things from configuration file readnames(ircdstrings.ircnick, ircdstrings.ircusername, ircdstrings.ircrealname); @@ -1067,7 +1106,7 @@ void dochat(int *serversockfd, int *clientsockfd) { if (FD_ISSET(*serversockfd, &rfds)) { printf("reading server socket!\n"); - if ((servernumbytes = recv(*serversockfd, serverbuf, MAXDATASIZE - 1, 0)) == -1) { + if ((servernumbytes = recv(*serversockfd, serverbuf, MAXRCVSIZE - 1, 0)) == -1) { printf("receive error (-1), exiting...\n"); perror("recv"); exit(1); @@ -1078,7 +1117,7 @@ void dochat(int *serversockfd, int *clientsockfd) { } serverbuf[servernumbytes] = '\0'; - printf("BOUNCER-SERVER RECEIVED: '%s'\n", serverbuf); + 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? @@ -1200,7 +1239,7 @@ void dochat(int *serversockfd, int *clientsockfd) { // 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 + // 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'; } -- cgit v1.2.3