summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Bratch <luke@bratch.co.uk>2019-05-12 15:59:21 +0100
committerLuke Bratch <luke@bratch.co.uk>2019-05-12 15:59:21 +0100
commite09bc8128960305c9ae48278cfcdbce0e55b97d6 (patch)
tree11c9f9e816ab83eb2e3a9f60fe88d09cb72a9250
parent01c0e36bb3f6c8345d4a94b157b68a0c0f4c85cf (diff)
Handle large raw strings from the server and properly track/rejoin long split/truncated messages
-rw-r--r--blabouncer.c63
1 files 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';
}