diff options
| -rw-r--r-- | README | 51 | ||||
| -rw-r--r-- | TODO | 2 | ||||
| -rw-r--r-- | blabouncer.c | 43 | ||||
| -rw-r--r-- | blabouncer.conf.example | 1 | ||||
| -rw-r--r-- | config.c | 1 | ||||
| -rw-r--r-- | functions.c | 94 | ||||
| -rw-r--r-- | functions.h | 14 | ||||
| -rw-r--r-- | message.c | 59 | ||||
| -rw-r--r-- | message.h | 2 | ||||
| -rw-r--r-- | structures.h | 9 | 
10 files changed, 243 insertions, 33 deletions
| @@ -20,9 +20,11 @@ Arguments are all optional, but they must be specified in the order shown above.  An example configuration file is provided named "blabouncer.conf". -If you don't specify one using "-c /path/to/configuration/file" then the example configuration one will be created for you in $HOME/.blabouncer/ when starting. +If you don't specify one using "-c /path/to/configuration/file" then the example configuration one will +be created for you in $HOME/.blabouncer/ when starting. -Certain configuration options can be changed at runtime, either at any time, or by issuing a BLABOUNCER REHASH command or by sending SIGHUP to blabouncer. +Certain configuration options can be changed at runtime, either at any time, or by issuing a BLABOUNCER +REHASH command or by sending SIGHUP to blabouncer.  These options can be changed by issuing a BLABOUNCER REHASH command or by sending SIGHUP to blabouncer:   - nick @@ -42,21 +44,58 @@ Once connected to blabouncer with a client, you can use the following commands:  "BLABOUNCER REPLAY [[[[days:]hours:]minutes:]seconds]" (To replay a given length of time of replay log.)  "BLABOUNCER QUIT [quit message]" (To quit blabouncer, optionally sending [quit message] to the server.)  "BLABOUNCER REHASH" (To reload settings from the configuration file, see above for details.) +"BLABOUNCER CLIENTCODE [clientcode]" (To set an identifier for the current client for auto replaying just +what this client has missed.)  Blabouncer commands are all prefixed with BLABOUNCER which you can usually send using "/QUOTE BLABOUNCER". +== Replaying history == + +You can request that history is replayed at any time by using the command BLABOUNCER REPLAY +[[[[days:]hours:]minutes:]seconds], assuming replaylogging is enabled in blabouncer.conf. + +In addition, you can have history automatically replayed upon a client connecting.  There are multiple +automatic replay methods available by setting replaymode in blabouncer.conf: + +- "none" +  Don't auto replay. + +- "time" +  Always send the last "replayseconds" worth of logs. + +- "lastspoke" +  All messages since your current nick last spoke. + +- "noclients" +  All messages since you last had no clients connected. + +- "lastchange" +  All messages since your last client connection/disconnection. + +- "perclient". +  All messages since the current client last disconnected.  Clients are uniquely identified using the +  BLABOUNCER CLIENTCODE command. +  The intention is that a given client (for instance that on a given device) will be configured to send +  BLABOUNCER CLIENTCODE as a connect command automatically, thus getting all replay history since it was +  last connected. +  == Client SSL/TLS == -To use TLS for client connections, which is enabled by default in the example configuration file, you will need to provide both a certificate and a key file. +To use TLS for client connections, which is enabled by default in the example configuration file, you will +need to provide both a certificate and a key file. +  One way of creating these is to use OpenSSL:  # openssl genrsa 2048 > ~/.blabouncer/key.pem  # openssl req -x509 -days 30 -new -key ~/.blabouncer/key.pem -out ~/.blabouncer/cert.pem -But you can create them however you like, or disable client TLS (not recommended) using the "clienttls" configuration file option. +But you can create them however you like, or disable client TLS (not recommended) using the "clienttls" +configuration file option. -By default blabouncer looks for the certificate and key files in $HOME/.blabouncer/cert.pem and $HOME/.blabouncer/key.pem, respectively. +By default blabouncer looks for the certificate and key files in $HOME/.blabouncer/cert.pem and +$HOME/.blabouncer/key.pem, respectively.  == Server SSL/TLS == -Server TLS is also enabled by default in the example configuration file, and can be disabled (not recommended) using the "servertls" configuration file option. +Server TLS is also enabled by default in the example configuration file, and can be disabled (not +recommended) using the "servertls" configuration file option. @@ -1,2 +0,0 @@ -Add various auto replay options: - - All logs since the current client last disconnected (track clients with some special token the client auto sends on connect) diff --git a/blabouncer.c b/blabouncer.c index 95521f1..73bef8c 100644 --- a/blabouncer.c +++ b/blabouncer.c @@ -134,6 +134,7 @@ int connecttoircserver(SSL_CTX **serverctx, SSL **server_ssl, int *serversockfd,    ircdstate->timeoutcheck = 0;    // ircdstate.reconnecting is not set here as we want to track reconnections separately    // ircdstate.clientchangetime and ircdstate.clientsnonetime not set here as they are set at startup and only changed when clients connect/disconnect +  // ircdstate.clientcodes not set here, set on startup and whenever a client sets one    // Populate nick and username from our configuration file for now, real IRCd may change them later (TODO - Is this true of username?)    strcpy(ircdstate->ircnick, settings->ircnick); @@ -176,7 +177,8 @@ int connecttoircserver(SSL_CTX **serverctx, SSL **server_ssl, int *serversockfd,  // 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 processircmessage(int *serversockfd, int *clientsockfd, char *str, int source) { -int processircmessage(SSL *server_ssl, char *str, int source, struct client *clients, int sourcefd, struct ircdstate *ircdstate, struct channel *channels, struct settings *settings) { +int processircmessage(SSL *server_ssl, char *str, int source, struct client *clients, int sourcefd, struct ircdstate *ircdstate, struct channel *channels, +                      struct settings *settings, struct clientcodes *clientcodes) {    // Track which space-separated token within this response we're on    int counter = 0; @@ -215,7 +217,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli        // Don't return if we got here because this means we didn't process something in processservermessage()        break;      case SOURCE_CLIENT: // If message(s) were from a real IRC client -      if (processclientmessage(server_ssl, str, clients, sourcefd, ircdstate, channels, settings, tokens, counter)) { +      if (processclientmessage(server_ssl, str, clients, sourcefd, ircdstate, channels, settings, tokens, counter, clientcodes)) {          // We processed something so return true          free(strcopyPtr);          return 1; @@ -254,7 +256,8 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli  //  // Return 0 if something went wrong  // Return 1 if everything OK -int processrawstring(SSL *server_ssl, char *str, int source, struct client *clients, int sourcefd, struct ircdstate *ircdstate, struct channel *channels, struct settings *settings) { +int processrawstring(SSL *server_ssl, char *str, int source, struct client *clients, int sourcefd, struct ircdstate *ircdstate, struct channel *channels, +                     struct settings *settings, struct clientcodes *clientcodes) {    // Copy to a temporary string so we still have the original in case it's not processed    char *strcopy = strdup(str);    // Keep track of initial pointer for free()ing later @@ -318,7 +321,7 @@ int processrawstring(SSL *server_ssl, char *str, int source, struct client *clie    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(server_ssl, messagecopy, source, clients, sourcefd, ircdstate, channels, settings)) { +    if (processircmessage(server_ssl, messagecopy, source, clients, sourcefd, ircdstate, channels, settings, clientcodes)) {        debugprint(DEBUG_FULL, "Message processed: \"%s\", NULLing...\n", messages[i]);        messages[i][0] = '\0';      } @@ -392,11 +395,25 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {      clients[i].pendingwhowas = 0;      clients[i].pendingnames = 0;      clients[i].pendingcap = 0; +    clients[i].clientcode[0] = '\0';    }    // 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 ircdstate ircdstate; +  // Set reconnection and other things to null/zero for now (not used unless reconnecting to server) +  ircdstate.oldnick[0] = '\0'; +  ircdstate.reconnecting = 0; +  ircdstate.clientchangetime = time(NULL); +  ircdstate.clientsnonetime = time(NULL); + +  // Struct of client codes +  // Used to track the last time a client identifying as a given client connected to handle auto replay for a known client. +  struct clientcodes clientcodes[MAXCLIENTCODES]; +  for (int i = 0; i < MAXCLIENTCODES; i++) { +    clientcodes[i].code[0] = '\0'; +    clientcodes[i].lastdisconnected = 0; +  }    // Struct of channels we're in    struct channel *channels; @@ -413,11 +430,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {    SSL_CTX *serverctx = NULL;    SSL *server_ssl = NULL; // Need to create this either way as referenced later -  // Set reconnection and other things to null/zero for now (not used unless reconnecting to server) -  ircdstate.oldnick[0] = '\0'; -  ircdstate.reconnecting = 0; -  ircdstate.clientchangetime = time(NULL); -  ircdstate.clientsnonetime = time(NULL); +  // Try to connect to IRC!    if (!connecttoircserver(&serverctx, &server_ssl, serversockfd, &ircdstate, settings, clients)) {      fprintf(stderr, "Failed to connect to IRC server, exiting.\n");      debugprint(DEBUG_CRIT, "Failed to connect to IRC server, exiting.\n"); @@ -648,7 +661,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {        // 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? -      if (!processrawstring(server_ssl, serverbuf, SOURCE_SERVER, clients, EXCEPT_NONE, &ircdstate, channels, settings)) { +      if (!processrawstring(server_ssl, serverbuf, SOURCE_SERVER, clients, EXCEPT_NONE, &ircdstate, channels, settings, clientcodes)) {          fprintf(stderr, "Error: bouncer-server failed to process raw string.\n");          debugprint(DEBUG_CRIT, "Error: bouncer-server failed to process raw string.\n");        } @@ -713,7 +726,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {            if (numclients(clients) >= MAXCLIENTS) {              fprintf(stderr, "too many clients, disconnecting and skipping loop iteration!\n");              debugprint(DEBUG_CRIT, "too many clients, disconnecting and skipping loop iteration!\n"); -            disconnectclient(i, clients, &ircdstate, settings); +            disconnectclient(i, clients, &ircdstate, settings, clientcodes);              continue;            }            addrlen = sizeof remoteaddr; @@ -779,7 +792,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {                debugprint(DEBUG_CRIT, "dochat(): client sockread() error fd '%d'.\n", i);              }              // Disconnect the client -            disconnectclient(i, clients, &ircdstate, settings); +            disconnectclient(i, clients, &ircdstate, settings, clientcodes);              FD_CLR(i, &rfds); // remove from master set - TODO is this needed at the moment since we just add everything from *clientsockfd to fdmax to rfds              // TODO - Handle the "remove the client" loop not finding the old fd              debugprint(DEBUG_FULL, "bouncer-client: total client connections: %d\n", numclients(clients)); @@ -805,7 +818,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) {              // 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(server_ssl, clientbuf, SOURCE_CLIENT, clients, i, &ircdstate, channels, settings)) { +            if (!processrawstring(server_ssl, clientbuf, SOURCE_CLIENT, clients, i, &ircdstate, channels, settings, clientcodes)) {                fprintf(stderr, "Error: bouncer-client failed to process raw string.\n");                debugprint(DEBUG_CRIT, "Error: bouncer-client failed to process raw string.\n");              } @@ -881,8 +894,8 @@ int main(int argc, char *argv[]) {      exit(1);    } else {      if (strcmp(settings.replaymode, "none") && strcmp(settings.replaymode, "time") && strcmp(settings.replaymode, "lastspoke") && -        strcmp(settings.replaymode, "noclients") && strcmp(settings.replaymode, "lastchange")) { -      printf("main(): replaymode in configuration file must be one of \"none\", \"time\", \"lastspoke\", \"noclients\", or \"lastchange\".\n"); +        strcmp(settings.replaymode, "noclients") && strcmp(settings.replaymode, "lastchange") && strcmp(settings.replaymode, "perclient")) { +      printf("main(): replaymode in configuration file must be one of \"none\", \"time\", \"lastspoke\", \"noclients\", \"lastchange\" or \"perclient\".\n");        exit(1);      }    } diff --git a/blabouncer.conf.example b/blabouncer.conf.example index 1537805..76df607 100644 --- a/blabouncer.conf.example +++ b/blabouncer.conf.example @@ -26,6 +26,7 @@ realname = "Mr Bla Bouncer"  # "lastspoke" = All messages since your current nick last spoke  # "noclients" = All messages since you last had no clients connected  # "lastchange" = All messages since your last client connection/disconnection +# "perclient" = All messages since the current client last disconnected (see README)  replaymode = "time"  # How many seconds of replay log should be sent to connecting clients if replaymode = "time" @@ -180,6 +180,7 @@ int createconfigfile(char *filename) {    "# \"lastspoke\" = All messages since your current nick last spoke\n"    "# \"noclients\" = All messages since you last had no clients connected\n"    "# \"lastchange\" = All messages since your last client connection/disconnection\n" +  "# \"perclient\" = All messages since the current client last disconnected (see README)\n"    "replaymode = \"time\"\n"    "\n"    "# How many seconds of replay log should be sent to connecting clients if replaymode = \"time\"\n" diff --git a/functions.c b/functions.c index 1efb2c1..f8609b1 100644 --- a/functions.c +++ b/functions.c @@ -536,7 +536,7 @@ int sendtoserver(SSL *server_ssl, char *strsrc, int str_len, int clientfd, struc  // it from the array of clients.  // Also set its authentication and registration statuses to 0.  // Also set the pending statuses to 0 -int disconnectclient(int fd, struct client *clients, struct ircdstate *ircdstate, struct settings *settings) { +int disconnectclient(int fd, struct client *clients, struct ircdstate *ircdstate, struct settings *settings, struct clientcodes *clientcodes) {    debugprint(DEBUG_SOME, "disconnectclient(): disconnecting client fd '%d'\n", fd);    // Alert other clients about the disconnection (don't send yet, we haven't removed from the clients array yet) @@ -553,6 +553,10 @@ int disconnectclient(int fd, struct client *clients, struct ircdstate *ircdstate        // If the client was registered, record the time of the last client disconnect        if (clients[i].registered) {          ircdstate->clientchangetime = time(NULL); +        // And set their client code, if any, to last disconnecting at this time. +        if (clients[i].clientcode[0]) { +          setclientcodetime(clients[i].clientcode, clientcodes); +        }        }        clients[i].fd = 0;        clients[i].authed = 0; @@ -565,6 +569,7 @@ int disconnectclient(int fd, struct client *clients, struct ircdstate *ircdstate        clients[i].pendingwhowas = 0;        clients[i].pendingnames = 0;        clients[i].pendingcap = 0; +      clients[i].clientcode[0] = '\0';        if (settings->clienttls) {          // Finish up with OpenSSL if using client TLS          SSL_free(clients[i].ssl); @@ -902,6 +907,11 @@ int doautoreplay(int sourcefd, struct client *clients, struct settings *settings      return 1;    } +  // If replaymode = "lastchange" then don't handle this here, it is done when the client registers its client code +  if (!strncmp(settings->replaymode, "perclient", strlen("perclient"))) { +    return 1; +  } +    // We shouldn't get here    return 0;  } @@ -1075,9 +1085,9 @@ int rehash(struct settings *settings, char *failuremsg) {      return 0;    } else {      if (strcmp(settings->replaymode, "none") && strcmp(settings->replaymode, "time") && strcmp(settings->replaymode, "lastspoke") && -        strcmp(settings->replaymode, "noclients") && strcmp(settings->replaymode, "lastchange")) { +        strcmp(settings->replaymode, "noclients") && strcmp(settings->replaymode, "lastchange") && strcmp(settings->replaymode, "perclient")) {        strcpy(settings->replaymode, oldreplaymode); -      strcpy(failuremsg, "replaymode in configuration file must be one of \"none\", \"time\", \"lastspoke\", \"noclients\", or \"lastchange\""); +      strcpy(failuremsg, "replaymode in configuration file must be one of \"none\", \"time\", \"lastspoke\", \"noclients\", \"lastchange\", or \"perclient\"");        return 0;      }    } @@ -1155,3 +1165,81 @@ int checkpassword(char *password, struct settings *settings) {    // No messing around with password stuff    exit(EXIT_FAILURE);  } + +// Adds a client code to the clientcode structure if it doesn't already exist. +// On success, copy it to the client's clientcode field. +// Returns 1 on adding a new code, 0 if the code already existed, or -1 on error. +int addclientcode(int sourcefd, char *code, struct clientcodes *clientcodes, struct client *clients) { +  debugprint(DEBUG_SOME, "addclientcode(): Adding client code '%s' if it doesn't already exist.\n", code); + +  // Make sure there aren't too many client codes already +  int counter; +  for (int i = 0; i < MAXCLIENTCODES; i++) { +    if (clientcodes[i].code[0]) { +      counter++; +    } +  } +  if (counter >= MAXCLIENTCODES) { +    debugprint(DEBUG_CRIT, "addclientcode(): too many client codes.\n"); +    return -1; +  } + +  // Copy it to the current client +  for (int j = 0; j < MAXCLIENTS; j++) { +    if (clients[j].fd == sourcefd) { +      strcpy(clients[j].clientcode, code); +      debugprint(DEBUG_FULL, "addclientcode(): set client code for fd '%d' to '%s'.\n", clients[j].fd, clients[j].clientcode); +    } +  } + +  // And add it to the clientcode structure if it doesn't already exist +  for (int i = 0; i < MAXCLIENTCODES; i++) { +    if (strlen(code) == strlen(clientcodes[i].code) && (strncmp(code, clientcodes[i].code, strlen(code)) == 0)) { +      debugprint(DEBUG_FULL, "addclientcode(): client code already existed.\n"); +      // It already exists +      return 0; +    } else { +      // It doesn't, add it +      strcpy(clientcodes[i].code, code); +      return 1; +    } +  } + +  // We shouldn't get here +  debugprint(DEBUG_CRIT, "addclientcode(): we shouldn't get here"); +  return -1; +} + +// Sets a given client code as last disconnecting at the current time. +// Returns 1 on success or 0 on failure. - TODO have callers do something on failure, or change this to a void. +int setclientcodetime(char *code, struct clientcodes *clientcodes) { +  debugprint(DEBUG_FULL, "setclientcodetime(): Setting disconnect time for '%s'.\n", code); + +  // Look for this code and set it as the current time if found +  for (int i = 0; i < MAXCLIENTCODES; i++) { +    if (strlen(code) == strlen(clientcodes[i].code) && (strncmp(code, clientcodes[i].code, strlen(code)) == 0)) { +      clientcodes[i].lastdisconnected = time(NULL); +      debugprint(DEBUG_FULL, "setclientcodetime(): Set time to '%d'.\n", clientcodes[i].lastdisconnected); +      return 1; +    } +  } + +  // If we got here, the code was never found +  return 0; +} + +// Return the timestamp that a given client last disconnected, or 0 on failure. +int getclientcodetime(char *code, struct clientcodes *clientcodes) { +  debugprint(DEBUG_FULL, "getclientcodetime(): looking for '%s'.\n", code); +  // Look for this code and set it as the current time if found +  for (int i = 0; i < MAXCLIENTCODES; i++) { +//printf("%s.\n", clientcodes[i].code); +    if (strlen(code) == strlen(clientcodes[i].code) && (strncmp(code, clientcodes[i].code, strlen(code)) == 0)) { +      debugprint(DEBUG_FULL, "getclientcodetime(): code '%s' found at index '%d', timestamp '%d'.\n", code, i, clientcodes[i].lastdisconnected); +      return clientcodes[i].lastdisconnected; +    } +  } + +  // If we got here, the code was never found +  return 0; +} diff --git a/functions.h b/functions.h index c91bfdc..29ebff5 100644 --- a/functions.h +++ b/functions.h @@ -103,7 +103,7 @@ int sendtoserver(SSL *server_ssl, char *strsrc, int str_len, int clientfd, struc  // it from the array of clients.  // Also set its authentication and registration statuses to 0.  // Also set the pending statuses to 0 -int disconnectclient(int fd, struct client *clients, struct ircdstate *ircdstate, struct settings *settings); +int disconnectclient(int fd, struct client *clients, struct ircdstate *ircdstate, struct settings *settings, struct clientcodes *clientcodes);  int createchannel(struct channel *channels, char *name, char *topic, char *topicwho, char *topicwhen); @@ -166,4 +166,16 @@ int rehash(struct settings *settings, char *failuremsg);  // Return 0 for password mismatch, or 1 for password match.  int checkpassword(char *password, struct settings *settings); +// Adds a client code to the clientcode structure if it doesn't already exist. +// On success, copy it to the client's clientcode field. +// Returns 1 on adding a new code, 0 if the code already existed, or -1 on error. +int addclientcode(int sourcefd, char *code, struct clientcodes *clientcodes, struct client *clients); + +// Sets a given client code as last disconnecting at the current time. +// Returns 1 on success or 0 on failure. +int setclientcodetime(char *code, struct clientcodes *clientcodes); + +// Return the timestamp that a given client last disconnected, or 0 on failure. +int getclientcodetime(char *code, struct clientcodes *clientcodes); +  #endif @@ -727,7 +727,7 @@ int processservermessage(SSL *server_ssl, char *str, struct client *clients, int  // Process an IRC message that came from a client.  // Return 1 if we processed it, or 0 if we didn't.  int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int sourcefd, struct ircdstate *ircdstate, -                         struct channel *channels, struct settings *settings, char tokens[MAXTOKENS][MAXDATASIZE], int counter) { +                         struct channel *channels, struct settings *settings, char tokens[MAXTOKENS][MAXDATASIZE], int counter, struct clientcodes *clientcodes) {    // PASS received?  User is trying to log in, check their password.    if (strncasecmp(tokens[0], "PASS", strlen(tokens[0])) == 0) {      if (checkpassword(tokens[1], settings)) { @@ -751,7 +751,7 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int        }      } else {        debugprint(DEBUG_SOME, "Password rejected, disconnecting fd %d.\n", sourcefd); -      disconnectclient(sourcefd, clients, ircdstate, settings); +      disconnectclient(sourcefd, clients, ircdstate, settings, clientcodes);        // Alert other clients about the failed authentication        char alertmsg[MAXDATASIZE];        if (!snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :blabouncer: new client with fd %d has failed to authenticate.", ircdstate->ircnick, sourcefd)) { @@ -828,7 +828,7 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int      // Tell the client to go away if we aren't registered with the real server yet as defined by the last greeting not being set yet      if (!strlen(ircdstate->greeting004)) {        sendtoclient(sourcefd, "Sorry, we aren't registered with a real IRC server yet.", clients, settings, 0); -      disconnectclient(sourcefd, clients, ircdstate, settings); +      disconnectclient(sourcefd, clients, ircdstate, settings, clientcodes);        return 1;      } @@ -865,6 +865,8 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int      sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);      snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REHASH\" (To reload settings from the configuration file, see README for which settings can be reloaded.)", ircdstate->ircnick);      sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); +    snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER CLIENTCODE [clientcode]\" (To set an identifier for the current client for auto replaying just what this client has missed.)", ircdstate->ircnick); +    sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);      snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER QUIT [quit message]\" (To quit blabouncer, optionally sending [quit message] to the server.)", ircdstate->ircnick);      sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); @@ -1039,7 +1041,7 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int    // A client has QUIT, so disconnect (close) them and don't do anything else for now - TODO: Let another clients know with a NOTICE or something    if (strncasecmp(tokens[0], "QUIT", strlen(tokens[0])) == 0) {      debugprint(DEBUG_FULL, "Client QUIT found from fd %d and it is: %s with length %zd!  Disconnecting that fd.\n", sourcefd, tokens[0], strlen(tokens[0])); -    disconnectclient(sourcefd, clients, ircdstate, settings); +    disconnectclient(sourcefd, clients, ircdstate, settings, clientcodes);      return 1;    } @@ -1160,7 +1162,7 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int      char outgoingmsg[MAXDATASIZE];      debugprint(DEBUG_FULL, "Client BLABOUNCER found and it is: %s with length %zd!\n", tokens[1], strlen(tokens[1]));      // REPLAY received, send the requested length of replay time to the client -    if (strncasecmp(tokens[1], "REPLAY", strlen("REPLAY")) == 0) { +    if (strncasecmp(tokens[1], "REPLAY", strlen("REPLAY")) == 0 && counter == 3) {        debugprint(DEBUG_FULL, "Client BLABOUNCER REPLAY (custom blabouncer command) found and it is: %s with length %zd!\n", tokens[1], strlen(tokens[1]));        // Split the request into days:hours:minutes:seconds @@ -1283,6 +1285,51 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int        }        return 1; +    // CLIENTCODE received, set the provided string as the client code for handling auto replays for when this client code is next seen +    } else if (strncasecmp(tokens[1], "CLIENTCODE", strlen("CLIENTCODE")) == 0 && counter == 3) { +      debugprint(DEBUG_FULL, "Client BLABOUNCER CLIENTCODE found and it is: %s %s!  Setting as this client's client code.\n", tokens[1], tokens[2]); + +      // Make sure replaymode = "perclient" is set +      if (strcmp(settings->replaymode, "perclient")) { +        debugprint(DEBUG_SOME, "CLIENTCODE requested but replaymode not set to \"perclient\".\n"); +        snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :CLIENTCODE requested but replaymode not set to \"perclient\".", ircdstate->ircnick); +        sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); +        return 1; +      } + +      // Make sure the client code length is good +      if (strlen(tokens[2]) < 1 || strlen(tokens[2]) > CLIENTCODELEN - 1) { +        debugprint(DEBUG_SOME, "Invalid CLIENTODE length.\n"); +        snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Invalid CLIENTODE length.  Must be 1 to %d characters.", ircdstate->ircnick, CLIENTCODELEN - 1); +        sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); +        return 1; +      } + +      // Register the client code (if it doesn't already exist) +      int ret = addclientcode(sourcefd, tokens[2], clientcodes, clients); +      if (ret == -1) { +        // Something went wrong +        debugprint(DEBUG_CRIT, "error: addclientcode() returned 0.\n"); +        snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Problem registering client code.", ircdstate->ircnick); +        sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); +        return 1; +      } else if (ret == 0) { +        // If it did already exist, do a replay of everything since this client code last disconnected +        int codetime = getclientcodetime(tokens[2], clientcodes); +        if (!codetime) { +          debugprint(DEBUG_CRIT, "Error finding last disconnect time of this client code!\n"); +          snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Error finding last disconnect time of this client code!", ircdstate->ircnick); +          sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); +          return 1; +        } +        if (!doreplay(sourcefd, time(NULL) - codetime, clients, settings, ircdstate, channels)) { +          snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstate->ircnick); +          sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); +          return 1; +        } +      } + +      return 1;      // 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])); @@ -1292,6 +1339,8 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int        sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);        snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REHASH\" (To reload settings from the configuration file, see README for which settings can be reloaded.)", ircdstate->ircnick);        sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); +      snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER CLIENTCODE [clientcode]\" (To set an identifier for the current client for auto replaying just what this client has missed.)", ircdstate->ircnick); +      sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);        snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER QUIT [quit message]\" (To quit blabouncer, optionally sending [quit message] to the server.)", ircdstate->ircnick);        sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);        return 1; @@ -32,6 +32,6 @@ int processservermessage(SSL *server_ssl, char *str, struct client *clients, int  // Process an IRC message that came from a client.  // Return 1 if we processed it, or 0 if we didn't.  int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int sourcefd, struct ircdstate *ircdstate, -                         struct channel *channels, struct settings *settings, char tokens[MAXTOKENS][MAXDATASIZE], int counter); +                         struct channel *channels, struct settings *settings, char tokens[MAXTOKENS][MAXDATASIZE], int counter, struct clientcodes *clientcodes);  #endif diff --git a/structures.h b/structures.h index d1ec4b0..b96b781 100644 --- a/structures.h +++ b/structures.h @@ -27,6 +27,8 @@  #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 CLIENTCODELEN 17 // Max length of a client code + 1 for null +#define MAXCLIENTCODES 64 // Max number of client codes to track  struct ircdstate {    char greeting001[MAXDATASIZE]; @@ -95,6 +97,13 @@ struct client {    int pendingwhowas; // Whether the client is waiting to hear back from a "WHOWAS" command    int pendingnames; // Count of RPL_NAMREPLYs the client is waiting on.    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). +  char clientcode[CLIENTCODELEN]; // This client's client code +}; + +// Structure of client codes.  Used to track the last time a client identifying as a given client connected to handle auto replay for a known client. +struct clientcodes { +  char code[CLIENTCODELEN]; // The client code string +  int lastdisconnected; // When this client code last disconnected  };  // Structure of a channel - this struct is expected to be in an array of channel structs | 
