summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Bratch <luke@bratch.co.uk>2019-05-27 21:53:47 +0100
committerLuke Bratch <luke@bratch.co.uk>2019-05-27 21:53:47 +0100
commit871184560b17553e84cc711a487ddf928671c56c (patch)
tree72f6620f75ea822d288b3c629714a29a05824422
parentc3f58fdccc4a6019ca7b53c67f272f9c3c576051 (diff)
Rewrite all server connection code to enable reconnecting to the server if there was a timeout, an error, or if the server disconnected us.
-rw-r--r--TODO4
-rw-r--r--blabouncer.c274
2 files changed, 203 insertions, 75 deletions
diff --git a/TODO b/TODO
index 3b714ee..6ca0a72 100644
--- a/TODO
+++ b/TODO
@@ -9,4 +9,6 @@ Add various auto replay options:
Might need to #include <limits.h> in blabouncer.c to make some operating systems and/or compilers happy.
-Reconnect server if we get disconnected for some reason.
+Send a PING to the server before assuming a timeout is definite.
+
+Include server 671 responses in WHO pending code.
diff --git a/blabouncer.c b/blabouncer.c
index a180b5a..6149ef1 100644
--- a/blabouncer.c
+++ b/blabouncer.c
@@ -1,6 +1,5 @@
// TODO:
// - Perhaps rename clients.ssl and server_ssl since they may not even be OpenSSL sockets
-// - Move debug output into some debug function
// "server" means the real IRC server
// "client" means bouncer clients
@@ -52,6 +51,7 @@
#define MAXREALNAMELEN 128 // Randomly picked (TODO - is there an actual maximum real name length?)
#define MAXPORTLEN 6 // Up to 65535, so 5 characters + 1 for null
#define MAXAUTOCHANLEN 1024 // Randomly picked maximum length of the auto channel list
+#define SERVERTIMEOUT 300 // How many seconds to wait without hearing from the server before assuming a timeout
struct channel {
char name[MAXCHANLENGTH];
@@ -63,6 +63,7 @@ struct channel {
int gotnames; // Have we finished getting the RPL_NAMREPLYs for this channel yet?
};
+// TODO - Rename this or split into multiple structs since it's no longer strictly just IRCd strings
struct ircdstrings {
char greeting001[MAXDATASIZE];
char greeting002[MAXDATASIZE];
@@ -79,6 +80,9 @@ struct ircdstrings {
char mode[MAXDATASIZE];
int capmultiprefix; // Whether the server approved our CAP multi-prefix request
int autonicknum; // Number of attempts made at automatically setting a nick if all configured nicks were in use
+ int lastmessagetime; // The last time we heard from the server
+ int reconnecting; // Whether or not we're reconnecting due to an earlier disconnection
+ char oldnick[MAXNICKLENGTH]; // Set temporarily if we end up reconnecting in case we need to tell existing clients about a nick change
};
// Structure of settings either to be read from the configuration file or set/changed at runtime
@@ -670,6 +674,77 @@ void tryautonick(struct ircdstrings *ircdstrings) {
printf("tryautonick(): set irdstrings->ircnick to '%s'.\n", ircdstrings->ircnick);
}
+int connecttoircserver(SSL_CTX **serverctx, SSL **server_ssl, int *serversockfd, struct ircdstrings *ircdstrings, struct settings *settings, struct client *clients) {
+ char outgoingmsg[MAXDATASIZE]; // String to send to server
+
+ if (settings->servertls) {
+ printf("server openssl start.\n");
+ *serverctx = create_openssl_context(SOURCE_SERVER);
+ configure_openssl_context(*serverctx, NULL, NULL);
+ *server_ssl = SSL_new(*serverctx);
+ SSL_set_fd(*server_ssl, *serversockfd);
+ if (SSL_connect(*server_ssl) == -1) {
+ ERR_print_errors_fp(stderr);
+ } else {
+ 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
+
+ // Set ircdstrings to zero-length strings for now
+ ircdstrings->greeting001[0] = '\0';
+ ircdstrings->greeting002[0] = '\0';
+ ircdstrings->greeting003[0] = '\0';
+ ircdstrings->greeting004[0] = '\0';
+ ircdstrings->greeting005a[0] = '\0';
+ ircdstrings->greeting005b[0] = '\0';
+ ircdstrings->greeting005c[0] = '\0';
+ ircdstrings->ircdname[0] = '\0';
+ ircdstrings->nickuserhost[0] = '\0';
+ ircdstrings->ircnick[0] = '\0';
+ ircdstrings->ircusername[0] = '\0';
+ ircdstrings->currentmsg[0] = '\0';
+ ircdstrings->mode[0] = '\0';
+ // ircdstrings->oldnick is not set here as we want to track reconnections separately
+ // And set non-string things to zero (TODO - Rename this from ircdstrings since it's not all strings any more)
+ ircdstrings->capmultiprefix = 0;
+ ircdstrings->autonicknum = 0;
+ ircdstrings->lastmessagetime = time(NULL);
+ // ircdstrings.reconnecting is not set here as we want to track reconnections separately
+
+ // Populate nick and username from our configuration file for now, real IRCd may change them later (TODO - Is this true of username?)
+ strcpy(ircdstrings->ircnick, settings->ircnick);
+ strcpy(ircdstrings->ircusername, settings->ircusername);
+
+ // Send the server password if one was configured
+ if (settings->ircserverpassword[0]) {
+ snprintf(outgoingmsg, MAXDATASIZE, "PASS %s", settings->ircserverpassword);
+ // sourcefd = 0 as this is a trusted message
+ sendtoserver(*server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings);
+ }
+
+ // Send our NICK
+ snprintf(outgoingmsg, MAXDATASIZE, "NICK %s", ircdstrings->ircnick); // TODO - Check for success (with return code)
+ // sourcefd = 0 as this is a trusted message
+ sendtoserver(*server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings);
+
+ // Send our USER
+ snprintf(outgoingmsg, MAXDATASIZE, "USER %s 8 * : %s", ircdstrings->ircusername, settings->ircrealname); // TODO - Check for success (with return code)
+ // TODO - Send a more intelligent/correct USER string
+ // sourcefd = 0 as this is a trusted message
+ sendtoserver(*server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings);
+
+ // =============================================>
+
+ return 1; // TODO - Return 0 if this fails and make callers do something with that
+}
+
// Figure out what to do with each CRLF-split IRC message (if anything)
// by splitting out the different components by space character (ASCII 0x20).
//
@@ -710,10 +785,13 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli
}
// <=============================================
- // IRC command processing (commands from server)
+ // IRC command processing (commands from server or client)
switch(source) {
case SOURCE_SERVER: // If message(s) were from the real IRC server
+ // Record that we received something from the server for timeout checking purposes
+ ircdstrings->lastmessagetime = time(NULL); // snprintf(NULL, 0, "%ld", timenow);
+
// Server PING received? If so, send a PONG back with the next element as the argument.
if (strncmp(tokens[0], "PING", strlen(tokens[0])) == 0) {
printf("Server PING found and it is: %s with length %zd! Sending response...\n", tokens[0], strlen(tokens[0]));
@@ -779,8 +857,53 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli
// Receiving greeting 004 means we're now registered
// Request IRCv3 multi-prefix extension so we can more accurately inform new clients about current user prefixes
sendtoserver(server_ssl, "CAP REQ multi-prefix", strlen("CAP REQ multi-prefix"), 0, clients, settings);
- // Join any auto channels set in the configuration file
- joinautochannels(server_ssl, clients, settings);
+ // If this is a reconnection, JOIN existing channels and catch clients up again
+ if (ircdstrings->reconnecting) {
+ // First tell clients if our nick changed
+ if (!strcmp(ircdstrings->ircnick, ircdstrings->oldnick) == 0) {
+ printf("Telling clients about nick change.\n");
+ char nickmsg[MAXDATASIZE];
+ snprintf(nickmsg, MAXDATASIZE, ":%s NICK :%s", ircdstrings->oldnick, ircdstrings->ircnick);
+ sendtoallclients(clients, nickmsg, sourcefd, settings);
+ }
+
+ // Next re-join channels
+ // Storing separately so we can skip over blank channels.
+ int channelcount = getchannelcount(channels);
+ // Join all the channels and make a note of the channel count
+ for (int i = 0; i < channelcount; i++) {
+ // Skip this one and increment channelcount if it's a blank channel
+ if (!channels[i].name[0]) {
+ printf("Reconnection: Skipping channel[%d], incrementing channelcount.\n", i);
+ channelcount++;
+ continue;
+ }
+
+ printf("Reconnection: Re-joining '%s'.\n", channels[i].name);
+
+ char joinmsg[MAXDATASIZE];
+ snprintf(joinmsg, MAXDATASIZE, "JOIN %s", channels[i].name);
+ sendtoserver(server_ssl, joinmsg, strlen(joinmsg), 0, clients, settings);
+ }
+
+ // Finally do a replay for all clients and tell them we're reconnected
+ for (int i = 0; i < MAXCLIENTS; i++) {
+ if (clients[i].fd) {
+ doreplay(clients[i].fd, settings->replayseconds, clients, settings, ircdstrings, channels);
+ char alertmsg[MAXDATASIZE];
+ snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Reconnection complete.", ircdstrings->ircnick);
+ sendtoclient(clients[i].fd, alertmsg, clients, settings, 0);
+ }
+ }
+
+ // Reconnection complete
+ ircdstrings->oldnick[0] = '\0';
+ ircdstrings->reconnecting = 0;
+ // If it's not, deal with auto channels
+ } else {
+ // Join any auto channels set in the configuration file
+ joinautochannels(server_ssl, clients, settings);
+ }
free(strcopyPtr);
return 1;
} else if (strncmp(tokens[1], "005", strlen(tokens[1])) == 0) {
@@ -1449,6 +1572,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli
if (!strlen(ircdstrings->greeting004)) {
sendtoclient(sourcefd, "Sorry, we aren't registered with a real IRC server yet.", clients, settings, 0);
disconnectclient(sourcefd, clients, ircdstrings, settings);
+ free(strcopyPtr);
return 1;
}
@@ -1978,8 +2102,6 @@ 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 fdmax; // highest numbered socket fd
@@ -2011,74 +2133,9 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {
clients[i].pendingcap = 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
-
// 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.greeting005a[0] = '\0';
- ircdstrings.greeting005b[0] = '\0';
- ircdstrings.greeting005c[0] = '\0';
- ircdstrings.ircdname[0] = '\0';
- ircdstrings.nickuserhost[0] = '\0';
- ircdstrings.ircnick[0] = '\0';
- ircdstrings.ircusername[0] = '\0';
- ircdstrings.currentmsg[0] = '\0';
- ircdstrings.mode[0] = '\0';
- // And set non-string things to zero (TODO - Rename this from ircdstrings since it's not all strings any more)
- ircdstrings.capmultiprefix = 0;
- ircdstrings.autonicknum = 0;
-
- // Populate nick and username from our configuration file for now, real IRCd may change them later (TODO - Is this true of username?)
- strcpy(ircdstrings.ircnick, settings->ircnick);
- strcpy(ircdstrings.ircusername, settings->ircusername);
-
- // Send the server password if one was configured
- if (settings->ircserverpassword[0]) {
- snprintf(outgoingmsg, MAXDATASIZE, "PASS %s", settings->ircserverpassword);
- // sourcefd = 0 as this is a trusted message
- sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings);
- }
-
- // Send our NICK
- snprintf(outgoingmsg, MAXDATASIZE, "NICK %s", ircdstrings.ircnick); // TODO - Check for success (with return code)
- // sourcefd = 0 as this is a trusted message
- sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings);
-
- // Send our USER
- snprintf(outgoingmsg, MAXDATASIZE, "USER %s 8 * : %s", ircdstrings.ircusername, settings->ircrealname); // TODO - Check for success (with return code)
- // TODO - Send a more intelligent/correct USER string
- // sourcefd = 0 as this is a trusted message
- sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings);
// Struct of channels we're in
struct channel *channels;
@@ -2087,7 +2144,18 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {
for (int i = 0; i < MAXCHANNELS; i++) {
channels[i].name[0] = '\0';
}
- // =============================================>
+
+ // Initialise OpenSSL (used for both client and server)
+ init_openssl();
+
+ // OpenSSL for server side if configured
+ SSL_CTX *serverctx = NULL;
+ SSL *server_ssl = NULL; // Need to create this either way as referenced later
+
+ // Set reconnection things to null/zero for now (not used unless reconnecting to server)
+ ircdstrings.oldnick[0] = '\0';
+ ircdstrings.reconnecting = 0;
+ connecttoircserver(&serverctx, &server_ssl, serversockfd, &ircdstrings, settings, clients);
// OpenSSL context for client side (that clients connect to) (need to create this whether or not using TLS as it is referenced later)
SSL_CTX *ctx;
@@ -2117,6 +2185,33 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {
}
}
+ // Before we wait for a message, let's make sure the server is still responding
+ // TODO - Code duplication, make a function and share with socket error code below
+ if (ircdstrings.lastmessagetime < time(NULL) - SERVERTIMEOUT) {
+ printf("Server has timed out (%ld seconds), reconnecting!\n", time(NULL) - ircdstrings.lastmessagetime);
+ // Tell all clients if we timed out
+ char alertmsg[MAXDATASIZE];
+ snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Server has timed out (%ld seconds), reconnecting!", ircdstrings.ircnick, time(NULL) - ircdstrings.lastmessagetime);
+ sendtoallclients(clients, alertmsg, 0, settings);
+ if (settings->servertls) {
+ // Finish up with OpenSSL if using server TLS
+ SSL_free(server_ssl);
+ }
+ // Close the socket
+ close(*serversockfd);
+ // Make a new one
+ *serversockfd = createserversocket(settings->ircserver, settings->ircserverport);
+
+ // Set reconnection marker for other functions to know we're reconnecting
+ ircdstrings.reconnecting = 1;
+ // Set oldnick in case we change nick when reconnecting so we can inform existing clients
+ strcpy(ircdstrings.oldnick, ircdstrings.ircnick);
+ connecttoircserver(&serverctx, &server_ssl, serversockfd, &ircdstrings, settings, clients);
+
+ // Back to top of loop
+ continue;
+ }
+
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
@@ -2134,12 +2229,38 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {
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);
}
+
+ // If there was a socket error (receive error or socket closed)
+ // TODO - Code duplication, make a function and share with timeout code above
+ if (servernumbytes < 1) {
+ printf("Server socket had an error (sockread return code %d), reconnecting!\n", servernumbytes);
+ // Tell all clients if we timed out
+ char alertmsg[MAXDATASIZE];
+ snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Server socket had an error (sockread return code %d), reconnecting!", ircdstrings.ircnick, servernumbytes);
+ sendtoallclients(clients, alertmsg, 0, settings);
+ if (settings->servertls) {
+ // Finish up with OpenSSL if using server TLS
+ SSL_free(server_ssl);
+ }
+ // Close the socket
+ close(*serversockfd);
+ // Make a new one
+ *serversockfd = createserversocket(settings->ircserver, settings->ircserverport);
+
+ // Set reconnection marker for other functions to know we're reconnecting
+ ircdstrings.reconnecting = 1;
+ // Set oldnick in case we change nick when reconnecting so we can inform existing clients
+ strcpy(ircdstrings.oldnick, ircdstrings.ircnick);
+ connecttoircserver(&serverctx, &server_ssl, serversockfd, &ircdstrings, settings, clients);
+
+ // Back to top of loop
+ continue;
+ }
+
serverbuf[servernumbytes] = '\0';
printf("BOUNCER-SERVER RECEIVED: '%s', length '%d'.\n", serverbuf, servernumbytes);
@@ -2156,6 +2277,9 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {
if (FD_ISSET(STDIN, &rfds)) {
printf("reading stdin!\n");
+ char outgoingmsg[MAXDATASIZE]; // String to send to server
+ int outgoingmsgrc; // Return code from getstdin() for outgoing message
+
outgoingmsgrc = getstdin(NULL, outgoingmsg, sizeof(outgoingmsg));
if (outgoingmsgrc == NO_INPUT) {
@@ -2293,6 +2417,8 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {
}
}
}
+
+ free(channels);
}
int main(int argc, char *argv[]) {