summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Bratch <luke@bratch.co.uk>2019-06-01 17:54:11 +0100
committerLuke Bratch <luke@bratch.co.uk>2019-06-01 17:54:11 +0100
commit0c153207be8a31fc66b7f6e9d0b849357017e34a (patch)
treec8211ce50268d2181582f594b440b82d023dfb58
parentd5ab728e85409b5074d4d61aaf5ba60862528e60 (diff)
Handle SIGINT (Ctrl-C) and SIGTERM (kill command) cleanly and fix unrelated server reconnect upon send() returning -1 issue.
-rw-r--r--TODO2
-rw-r--r--blabouncer.c92
2 files changed, 75 insertions, 19 deletions
diff --git a/TODO b/TODO
index 81e62bc..2b3b030 100644
--- a/TODO
+++ b/TODO
@@ -9,4 +9,4 @@ Might need to #include <limits.h> in blabouncer.c to make some operating systems
Send a PING to the server before assuming a timeout is definite.
-Capture Ctrl-C/kill and exit cleanly.
+Put debug logs in subdirectory and have max number to keep be configurable.
diff --git a/blabouncer.c b/blabouncer.c
index cd50a0a..02c62ad 100644
--- a/blabouncer.c
+++ b/blabouncer.c
@@ -18,6 +18,7 @@
#include <time.h>
#include <sys/stat.h>
#include <limits.h>
+#include <signal.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.h>
@@ -38,11 +39,14 @@
#define DEBUG_SOME 1
#define DEBUG_FULL 2
+#define STDIN 0 // stdin is fd 0
+#define SIGINT 2 // SIGINT is signal 2
+#define SIGTERM 15 // SIGTERM is signal 15
+
// 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]!)
@@ -62,6 +66,9 @@
int debug = 0; // Debug verbosity ("0" for critical only, "1" for some extra info, "2" for full debug mode)
char debugpath[PATH_MAX]; // Path to debug file
+// Set if certain signal (just SIGINT and SIGKILL at time of writing) is received
+int signum = 0;
+
struct channel {
char name[MAXCHANLENGTH];
char topic[MAXDATASIZE]; // TODO - Is there a particular maximum topic length?
@@ -133,6 +140,12 @@ struct client {
int pendingcap; // Whether the client is still negotiating IRCv3 CAPabilities. 0 = no, 1 = yes, -1 = just finished (so register them as if they had just sent USER).
};
+// Signal handler
+// We don't actually do anything in here, the main pselect() notice signals
+void sighandler(int sig) {
+ signum = sig;
+}
+
// Return index of requested client FD within the clients array.
// TODO - Use this wherever we are calculating the position (various places) instead of
// duplicating code.
@@ -762,6 +775,30 @@ int connecttoircserver(SSL_CTX **serverctx, SSL **server_ssl, int *serversockfd,
return 1; // TODO - Return 0 if this fails and make callers do something with that
}
+// Exit the program cleanly - tell clients, tell the server, then exit(0)
+// Optional quit message string "quitmsg"
+// "sourcefd" of 0 means the request didn't come from a client
+void cleanexit(SSL *server_ssl, struct client *clients, int sourcefd, struct ircdstrings *ircdstrings, struct settings *settings, char *quitmsg) {
+ char outgoingmsg[MAXDATASIZE];
+
+ // Tell clients and debug log
+ if (sourcefd) {
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Exiting on request from client with fd '%d', message '%s'.", ircdstrings->ircnick, sourcefd, quitmsg);
+ debugprint(DEBUG_CRIT, "Exiting on request from client with fd '%d', message '%s'.\n", sourcefd, quitmsg);
+ } else {
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Exiting on request (not from a client), message '%s'.", ircdstrings->ircnick, quitmsg);
+ debugprint(DEBUG_CRIT, "Exiting on request (not from a client), message '%s'.\n", quitmsg);
+ }
+ sendtoallclients(clients, outgoingmsg, EXCEPT_NONE, settings);
+
+ // Tell the server
+ // Combine "QUIT :" with any (optional) quit message
+ snprintf(outgoingmsg, MAXDATASIZE, "QUIT :%s", quitmsg);
+ sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), sourcefd, clients, settings);
+
+ exit(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).
//
@@ -2078,20 +2115,14 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli
// QUIT received, send QUIT message to server and exit cleanly
} else if (strncasecmp(tokens[1], "QUIT", strlen("QUIT")) == 0) {
debugprint(DEBUG_SOME, "Client BLABOUNCER QUIT found and it is: %s with length %zd!\n", tokens[1], strlen(tokens[1]));
- snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Exiting on BLABOUNCER QUIT command from client with fd '%d'.", ircdstrings->ircnick, sourcefd);
- sendtoallclients(clients, outgoingmsg, EXCEPT_NONE, settings);
// Combine "QUIT :" with any optional quit message the user provided
- char quitmsg[MAXDATASIZE];
if (counter > 2) {
// Any optional quit message comes 16 characters after the start of the string ("BLABOUNCER QUIT " is 16 characters)
- snprintf(quitmsg, MAXDATASIZE, "QUIT :%s", str + 16);
+ cleanexit(server_ssl, clients, sourcefd, ircdstrings, settings, str + 16);
} else {
- snprintf(quitmsg, MAXDATASIZE, "QUIT");
+ cleanexit(server_ssl, clients, sourcefd, ircdstrings, settings, "");
}
- sendtoserver(server_ssl, quitmsg, strlen(quitmsg), sourcefd, clients, settings);
- debugprint(DEBUG_CRIT, "Exiting on client request.\n");
- free(strcopyPtr);
- exit(0);
+
// Unrecognised BLABOUNCER command received, send some help instructions
} else {
debugprint(DEBUG_SOME, "Client BLABOUNCER unrecognised command found and it is: %s with length %zd! Sending a help message.\n", tokens[1], strlen(tokens[1]));
@@ -2253,7 +2284,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {
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)
+ fd_set rfds; // set of read fds to monitor with pselect() - 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 up clients structure
struct client clients[MAXCLIENTS];
@@ -2307,6 +2338,18 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {
configure_openssl_context(ctx, settings->certfile, settings->keyfile);
}
+ // Let's set up signal handling stuff here since we're about to enter The Big Loop (TM)
+ // We'll handle SIGINT (Ctrl-C) and SIGTERM (default signal of `kill`)
+ signal(SIGINT, sighandler); // SIGINT (2)
+ signal(SIGTERM, sighandler); // SIGTERM (15)
+
+ // Block those two signals
+ sigset_t sigset, oldset;
+ sigemptyset(&sigset);
+ sigaddset(&sigset, SIGINT);
+ sigaddset(&sigset, SIGTERM);
+ sigprocmask(SIG_BLOCK, &sigset, &oldset);
+
while (1) {
debugprint(DEBUG_FULL, "top of loop, fdmax %d.\n", fdmax);
FD_ZERO(&rfds); // clear entries from fd set
@@ -2355,12 +2398,26 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {
continue;
}
- debugprint(DEBUG_FULL, "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
- perror("select");
- debugprint(DEBUG_CRIT, "select() error, errno '%d'.\n", errno);
- continue;
+ debugprint(DEBUG_FULL, "pselect()ing...\n");
+ // Check to see if any fd in the fd_set is waiting or a signal happened - blocks here until one one of those things happens
+ // (pselect() to do signal handling in additiona to fd monitoring)
+ if (pselect(fdmax + 1, &rfds, NULL, NULL, NULL, &oldset) < 0) { // network socket + 1, rfds, no writes, no exceptions/errors, no timeout, original sigmask
+ if (errno == EINTR) {
+ // Signal caught, do signal handling
+ debugprint(DEBUG_CRIT, "signal '%d' happened, exiting!\n", signum);
+ if (signum == SIGINT) {
+ cleanexit(server_ssl, clients, 0, &ircdstrings, settings, "SIGINT received");
+ } else if (signum == SIGTERM) {
+ cleanexit(server_ssl, clients, 0, &ircdstrings, settings, "SIGTERM received");
+ } else {
+ cleanexit(server_ssl, clients, 0, &ircdstrings, settings, "Unexpected signal received");
+ }
+ } else {
+ // Some other error
+ perror("pselect");
+ debugprint(DEBUG_CRIT, "pselect() error, errno '%d'.\n", errno);
+ continue;
+ }
}
// 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.
@@ -2374,7 +2431,6 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {
perror("recv");
printf("receive error (-1), skipping loop iteration...\n");
debugprint(DEBUG_CRIT, "serversockfd receive error (-1), skipping loop iteration, errno '%d'.\n");
- continue;
} else if (servernumbytes == 0) {
perror("recv");
printf("socket closed (or no data received) (0), skipping loop iteration...\n");