/*
 * This file is part of blabouncer (https://www.blatech.co.uk/l_bratch/blabouncer).
 * Copyright (C) 2019 Luke Bratch <luke@bratch.co.uk>.
 *
 * Blabouncer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * Blabouncer is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with blabouncer. If not, see <http://www.gnu.org/licenses/>.
*/

#include "message.h"

// Process an IRC message that came from the real server.
// Return 1 if we processed it, or 0 if we didn't.
int processservermessage(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) {
  // Record that we received something from the server for timeout checking purposes
  ircdstate->lastmessagetime = time(NULL); // snprintf(NULL, 0, "%ld", timenow);
  // And we can't be timing out
  ircdstate->timeoutcheck = 0;

  // 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) {
    debugprint(DEBUG_FULL, "Server PING found and it is: %s with length %zd!  Sending response...\n", tokens[0], strlen(tokens[0]));

    char outgoingmsg[MAXDATASIZE]; // String to send to server
    if (!snprintf(outgoingmsg, MAXDATASIZE, "PONG %s", tokens[1])) { // TODO - Make sure tokens[1] actually has a token
      fprintf(stderr, "Error while preparing PONG response!\n");
      debugprint(DEBUG_CRIT, "Error while preparing PONG response!\n");
      outgoingmsg[0] = '\0';
    }
    // sourcefd = 0 as this is a trusted response
    sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings);

    // Also relay the request to all clients in case they were expecting PINGs to keep themselves alive
    if (!snprintf(outgoingmsg, MAXDATASIZE, "PING %s", tokens[1])) { // TODO - Make sure tokens[1] actually has a token
      fprintf(stderr, "Error while preparing PING relay!\n");
      debugprint(DEBUG_CRIT, "Error while preparing PING relay!\n");
      outgoingmsg[0] = '\0';
    }
    sendtoallclients(clients, outgoingmsg, EXCEPT_NONE, settings);

    // We processed something so return true
    return 1;
  }

  // Prefix received?  TODO - Care about what the prefix is - what if it's a different server/network/whatever?
  if (tokens[0][0] == ':') {
    debugprint(DEBUG_FULL, "Prefix found: '%s'!  Next token is '%s', length %zd.\n", tokens[0], tokens[1], strlen(tokens[1]));
    // Greetings 001 through to 005, store in ircdstate array for resending when clients connect
    // Also store our nick!user@host from greeting 001
    // Also store the real IRCd's name from greeting 004
    if (strncmp(tokens[1], "001", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Found greeting 001 (%s) (length %zd), storing in ircdstate struct.\n", str, strlen(str));
      strncpy(ircdstate->greeting001, str, strlen(str));
      // Null the end of the string
      ircdstate->greeting001[strlen(str)] = '\0';
      debugprint(DEBUG_FULL, "Storing our nick!user@host (:%s) from greeting 001 in ircdstate struct.\n", tokens[counter - 1]);
      // Prepend a colon (:) first since everything (so far) needs one
      if (!snprintf(ircdstate->nickuserhost, MAXDATASIZE, ":%s", tokens[counter - 1])) {
        fprintf(stderr, "Error while preparing nickuserhost for storage!\n");
        debugprint(DEBUG_CRIT, "Error while preparing nickuserhost for storage!\n");
        exit(1);
      }
      // Null the end of the new string
      ircdstate->nickuserhost[strlen(tokens[counter - 1]) + 1] = '\0'; // +1 for the inserted colon
      debugprint(DEBUG_FULL, "nickuserhost '%s' stored.\n", ircdstate->nickuserhost);
      // Set our current ircnick based on whatever was in greeting 001
      if (counter >= 3) {
        // Assuming there at least three tokens (:ircdname 001 nick etc.) then store the nick
        strcpy(ircdstate->ircnick, tokens[2]);
        debugprint(DEBUG_FULL, "Updated ircnick to '%s' from greeting 001.\n", ircdstate->ircnick);
      } else {
        // Something has gone fairly wrong with greeting 001
        debugprint(DEBUG_CRIT, "Greeting 001 ('%s') is not long enough, don't know how to proceed, exiting...\n", str);
        exit(1);
      }
      return 1;
    } else if (strncmp(tokens[1], "002", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Found greeting 002 (%s), storing in ircdstate struct.\n", str);
      strncpy(ircdstate->greeting002, str, strlen(str));
      // Null the end of the string
      ircdstate->greeting002[strlen(str)] = '\0';
      return 1;
    } else if (strncmp(tokens[1], "003", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Found greeting 003 (%s), storing in ircdstate struct.\n", str);
      strncpy(ircdstate->greeting003, str, strlen(str));
      // Null the end of the string
      ircdstate->greeting003[strlen(str)] = '\0';
      return 1;
    } else if (strncmp(tokens[1], "004", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Found greeting 004 (%s), storing in ircdstate struct.\n", str);
      strncpy(ircdstate->greeting004, str, strlen(str));
      // Null the end of the string
      ircdstate->greeting004[strlen(str)] = '\0';
      debugprint(DEBUG_FULL, "Storing the real IRCd's name (%s) from greeting 004 in ircdstate struct.\n", tokens[3]);
      strncpy(ircdstate->ircdname, tokens[3], strlen(tokens[3]));
      // Null the end of the string
      ircdstate->ircdname[strlen(tokens[3])] = '\0';
      // 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);
      // Send any configured connect commands
      for (int i = 0; i < MAXCONFARR; i++) {
        if (settings->connectcommands[i][0]) {
          sendtoserver(server_ssl, settings->connectcommands[i], strlen(settings->connectcommands[i]), 0, clients, settings);
        }
      }
      // If this is a reconnection, JOIN existing channels and catch clients up again
      if (ircdstate->reconnecting) {
        // First tell clients if our nick changed
        if (strcmp(ircdstate->ircnick, ircdstate->oldnick)) {
          debugprint(DEBUG_SOME, "Telling clients about nick change.\n");
          char nickmsg[MAXDATASIZE];
          snprintf(nickmsg, MAXDATASIZE, ":%s NICK :%s", ircdstate->oldnick, ircdstate->ircnick);
          sendtoallclients(clients, nickmsg, sourcefd, settings);
        }

        // Next re-join channels
        // Join all the channels
        for (int i = 0; i < ircdstate->maxchannelcount; i++) {
          // Skip this one if it's a blank channel
          if (!channels[i].name[0]) {
              debugprint(DEBUG_FULL, "Reconnection: Skipping blank channel channel[%d].\n", i);
              continue;
          }

          // Remove nicks in the channel, we will re-get them when re-JOINing
          for (int j = 0; j < MAXCHANNICKS; j++) {
            channels[i].nicks[j][0] = '\0';
          }

          debugprint(DEBUG_SOME, "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) {
            char alertmsg[MAXDATASIZE];
            if (!doautoreplay(clients[i].fd, clients, settings, ircdstate, channels)) {
              snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstate->ircnick);
              sendtoclient(sourcefd, alertmsg, clients, settings, 0);
            }
            snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Reconnection complete.", ircdstate->ircnick);
            sendtoclient(clients[i].fd, alertmsg, clients, settings, 0);
          }
        }

        // Reconnection complete
        ircdstate->oldnick[0] = '\0';
        ircdstate->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);
      }
      return 1;
    } else if (strncmp(tokens[1], "005", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Found greeting 005 (%s), storing in ircdstate struct.\n", str);
      // Find an empty greeting005 string in ircdstate and store in there...
      if (!ircdstate->greeting005a[0]) {
        strncpy(ircdstate->greeting005a, str, strlen(str));
        ircdstate->greeting005a[strlen(str)] = '\0';
      } else if (!ircdstate->greeting005b[0]) {
        strncpy(ircdstate->greeting005b, str, strlen(str));
        ircdstate->greeting005b[strlen(str)] = '\0';
      } else if (!ircdstate->greeting005c[0]) {
        strncpy(ircdstate->greeting005c, str, strlen(str));
        ircdstate->greeting005c[strlen(str)] = '\0';
      } else {
        // ...or if they are all fill, discard - TODO - Support more than three greeting005 strings!
        debugprint(DEBUG_CRIT, "Already stored three greeting 005 strings, discarding this one.\n");
      }
      return 1;
    }

    // Server JOIN received?  Add to our local channel array if it's us, or record the user in the channel if it's not us.
    if (strncmp(tokens[1], "JOIN", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server JOIN found and it is: %s with length %zd!  Next token is '%s'.  Adding to local channel list if it's us.\n", tokens[0], strlen(tokens[0]), tokens[2]);
      // Next token should be the channel name but it probably needs the leading ':' stripping
      debugprint(DEBUG_FULL, "processircmessage(): Channel name was '%s'\n", tokens[2]);
      stripprefix(tokens[2], 1);
      debugprint(DEBUG_FULL, "processircmessage(): Channel name now '%s'\n", tokens[2]);

      // If the user JOINing is us, then we must have joined a channel, so add to our local channel array.
      // Copy to a temporary string so we still have the original in case we need it
      char *prefixcopy = strdup(tokens[0]);
      // Just get the nick for comparison
      extractnickfromprefix(prefixcopy, 1);
      if ((strlen(prefixcopy) == strlen(ircdstate->ircnick)) && (strncmp(prefixcopy, ircdstate->ircnick, strlen(tokens[0])) == 0)) {
        debugprint(DEBUG_FULL, "Server JOIN: nick is ours ('%s' vs '%s').\n", prefixcopy, ircdstate->ircnick);
        // TODO - Saner way to initialise this since we don't have the variables yet?
        // TODO - Defaulting to type '=' which is "public" since I don't know what else to guess.
        createchannel(channels, ircdstate, tokens[2], "TOPIC", "TOPICWHO", "0");
      } else {
        debugprint(DEBUG_FULL, "Server JOIN: nick is NOT ours ('%s' vs '%s').\n", prefixcopy, ircdstate->ircnick);
      }

      // Add the JOINing nick to our local channel struct
      if (!addnicktochannel(tokens[0], tokens[2], channels, ircdstate->maxchannelcount)) {
        debugprint(DEBUG_CRIT, "Failed to add nick to channel struct.\n");
      }

      // And then send to all clients
      sendtoallclients(clients, str, sourcefd, settings);

      // Write to replay log if replay logging enabled
      if (settings->replaylogging) {
        writereplayline(str, settings->basedir);
      }

      // Write to normal log if logging enabled
      if (settings->logging) {
        logline(str, ircdstate, settings->basedir, LOG_JOINPART);
      }

      free(prefixcopy);
      return 1;
    }

    // Server PART received?  Remove from our local channel list if it's us.
    if (strncmp(tokens[1], "PART", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server PART found and it is: %s with length %zd!  Next token is '%s'.  Removing from local channel list if it's us.\n", tokens[0], strlen(tokens[0]), tokens[2]);

      // If the user PARTing is us, then we must have left a channel, so remove it from our local channel array.
      // (If it's not us, then it's another user PARTing a channel, so just pass straight through to letting all our clients know.)
      // Copy to a temporary string so we still have the original in case we need it
      char *prefixcopy = strdup(tokens[0]);
      // Just get the nick for comparison
      extractnickfromprefix(prefixcopy, 1);
      if ((strlen(prefixcopy) == strlen(ircdstate->ircnick)) && (strncmp(prefixcopy, ircdstate->ircnick, strlen(tokens[0])) == 0)) {
        debugprint(DEBUG_FULL, "Server PART: nick is ours ('%s' vs '%s').\n", prefixcopy, ircdstate->ircnick);
        removechannel(channels, ircdstate->maxchannelcount, tokens[2]);
      } else {
        debugprint(DEBUG_FULL, "Server PART: nick is NOT ours ('%s' vs '%s').\n", prefixcopy, ircdstate->ircnick);
      }

      // Remove the PARTing nick from our local channel struct
      if (!removenickfromchannel(tokens[0], tokens[2], channels, ircdstate->maxchannelcount)) {
        debugprint(DEBUG_CRIT, "Failed to remove nick from channel struct.\n");
      }

      // And then send to all clients
      sendtoallclients(clients, str, sourcefd, settings);

      // Write to replay log if replay logging enabled
      if (settings->replaylogging) {
        writereplayline(str, settings->basedir);
      }

      // Write to normal log if logging enabled
      if (settings->logging) {
        logline(str, ircdstate, settings->basedir, LOG_JOINPART);
      }

      free(prefixcopy);
      return 1;
    }

    // Server QUIT received?  Tell all clients and also remove the user from our local channels struct.
    if (strncmp(tokens[1], "QUIT", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server QUIT found and it is: %s with length %zd!  Next token is '%s'.\n", tokens[0], strlen(tokens[0]), tokens[2]);

      // Tell all clients
      sendtoallclients(clients, str, sourcefd, settings);

      // Write to replay log if replay logging enabled
      if (settings->replaylogging) {
        writereplayline(str, settings->basedir);
      }

      // Get each channel the QUITting user was in, and log the quit from that channel if enabled
      if (settings->logging) {
        char quitnick[MAXDATASIZE];
        strcpy(quitnick, tokens[0]);
        extractnickfromprefix(quitnick, 1);
        for (int i = 0; i < ircdstate->maxchannelcount; i++) {
          if (channels[i].name[0]) {
            for (int j = 0; j < MAXCHANNICKS; j++) {
              if (strlen(channels[i].nicks[j]) == strlen(quitnick) && !strcmp(channels[i].nicks[j], quitnick)) {
                char logstring[MAXDATASIZE];
                snprintf(logstring, MAXDATASIZE, "%s %s", channels[i].name, str);
                logline(logstring, ircdstate, settings->basedir, LOG_QUIT);
                break;
              }
            }
          }
        }
      }

      // Remove the QUITting nick from our local channel struct
      if (!removenickfromallchannels(tokens[0], channels, ircdstate->maxchannelcount)) {
        debugprint(DEBUG_CRIT, "Failed to remove nick from channel structs.\n");
      }

      return 1;
    }

    // Channel topics/names/nicks/etc.
    // Server 331 (RPL_NOTOPIC) the topic is blank which we track by having a set timestamp of 0
    if (strncmp(tokens[1], "331", strlen(tokens[1])) == 0) {
      // Might as well blank our current topic value
      setchanneltopic(channels, ircdstate->maxchannelcount, tokens[3], "");
      // Set the topic timestamp to 0 which we use to determine an "unset" topic when new clients connect
      setchanneltopicwhotime(channels, ircdstate->maxchannelcount, tokens[3], "", "0");
    // Server 332 (RPL_TOPIC) set the channel topic
    } else if (strncmp(tokens[1], "332", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server 332 (RPL_TOPIC) found, extracting topic and storing in channel struct.\n");
      // Need to extract the final parameter as topics can have spaces
      // Copy to a temporary string so we still have the original in case we need it
      char *topiccopy = strdup(str);
      extractfinalparameter(topiccopy);
      setchanneltopic(channels, ircdstate->maxchannelcount, tokens[3], topiccopy);
      free(topiccopy);
    // Server 333 (RPL_TOPICWHOTIME) set the channel topic setter and the time it was set
    } else if (strncmp(tokens[1], "333", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server 333 (RPL_TOPICWHOTIME) found, extracting who and when, and storing in channel struct.\n");
      setchanneltopicwhotime(channels, ircdstate->maxchannelcount, tokens[3], tokens[4], tokens[5]);
    // Server 353 (RPL_NAMREPLY), relay to all clients if we've just JOINed the channel, or relay to any clients
    // who were pending RPL_NAMREPLYs if it's an existing channel.
    } else if (strncmp(tokens[1], "353", strlen(tokens[1])) == 0) {
      // It must be a new channel and we don't have the NAMES
      if (!channelgotnames(channels, ircdstate->maxchannelcount, tokens[4])) {
        debugprint(DEBUG_FULL, "Server 353 received for a new channel, sending to all clients.\n");
        // Relay to all clients
        sendtoallclients(clients, str, sourcefd, settings);
      } else {
        // We were already in the channel and have the NAMES
        debugprint(DEBUG_FULL, "Server 353 received for an existing channel, sending to all clients who were pending RPL_NAMREPLYs.\n");
        // If any clients were pending RPL_NAMREPLYs, send this on to them
        // TODO - Make sure clients only get the ones they were waiting on in case there are multiple conflicting requests going on at once
        for (int i = 0; i < MAXCLIENTS; i++) {
          if (clients[i].pendingnames > 0) {
            debugprint(DEBUG_FULL, "Sending 353 RPL_NAMREPLY for channel '%s' to client with fd '%d' who was pending %d RPL_NAMREPLYs.\n", tokens[4], clients[i].fd, clients[i].pendingnames);
            sendtoclient(clients[i].fd, str, clients, settings, 0);
          }
        }
      }

      // Update our local channels struct with all nicks from this RPL_NAMREPLY
      if (!addnamereplytochannel(str, channels, ircdstate->maxchannelcount)) {
        debugprint(DEBUG_CRIT, "Failed to add RPL_NAMREPLY to channels.\n");
      }

      return 1;
    // Server 366 (RPL_ENDOFNAMES), relay to all clients if we've just JOINed the channel, or relay to
    // and decrement from any clients who were waiting on RPL_NAMREPLY if it's an existing channel.
    } else if (strncmp(tokens[1], "366", strlen(tokens[1])) == 0) {
      int channelelement;
      // It must be a new channel and we don't have the NAMES
      if (!(channelelement = channelgotnames(channels, ircdstate->maxchannelcount, tokens[3]))) {
        debugprint(DEBUG_FULL, "Server 366 received for a new channel, sending to all clients and set as got names.\n");
        // We have the names now!
        channels[channelindex(channels, ircdstate->maxchannelcount, tokens[3])].gotnames = 1;
        // Relay to all clients
        sendtoallclients(clients, str, sourcefd, settings);
      } else {
        // We were already in the channel and have the NAMES
        // TODO - Make sure clients only get the ones they were waiting on in case there are multiple conflicting requests going on at once
        debugprint(DEBUG_FULL, "Server 366 received for an existing channel, sending to all clients who were pending RPL_NAMREPLYs and decrementing their pendingnames count.\n");
        for (int i = 0; i < MAXCLIENTS; i++) {
          if (clients[i].pendingnames > 0) {
            sendtoclient(clients[i].fd, str, clients, settings, 0);
            // And decrement their pendingnames count
            debugprint(DEBUG_FULL, "Decrementing pendingnames due 366 RPL_ENDOFNAMES to for channel '%s' to client with fd '%d' who was pending %d RPL_NAMREPLYs.\n",
                       tokens[3], clients[i].fd, clients[i].pendingnames);
            clients[i].pendingnames--;
            debugprint(DEBUG_FULL, "Client with fd '%d' has '%d' pendingnames left.\n", clients[i].fd, clients[i].pendingnames);
          }
        }
      }
      return 1;
    }

    // Server TOPIC received?  Update our local channel topic info then relay to clients.
    if (strncmp(tokens[1], "TOPIC", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server TOPIC found and it is: %s with length %zd!  Next token is '%s'.  Updating our local channel topic info.\n",
                 tokens[0], strlen(tokens[0]), tokens[2]);

      // Set the topic itself

      // Need to extract the final parameter as topics can have spaces
      // Copy to a temporary string so we still have the original in case we need it
      char *topiccopy = strdup(str);
      extractfinalparameter(topiccopy);
      setchanneltopic(channels, ircdstate->maxchannelcount, tokens[2], topiccopy);

      // Extract the author and get the current timestamp

      // Extract the topic setter from the prefix (Prefix of ":foo!bar@baz" means "foo" set the topic.)
      // Copy to a temporary string so we still have the original in case we need it
      char *prefixcopy = strdup(tokens[0]);
      extractnickfromprefix(prefixcopy, 1);

      // Get the current time and manipulate it into a C string
      time_t timenow = time(NULL);
      int timenowlen = snprintf(NULL, 0, "%ld", timenow);
      char timenowstr[timenowlen + 1]; // TODO - Make this Year 2038 proof.
      snprintf(timenowstr, timenowlen + 1, "%ld", timenow);

      // Actually set the author and timestamp
      setchanneltopicwhotime(channels, ircdstate->maxchannelcount, tokens[2], prefixcopy, timenowstr);

      // And then finally relay to all clients
      sendtoallclients(clients, str, sourcefd, settings);

      // Write to replay log if replay logging enabled
      if (settings->replaylogging) {
        writereplayline(str, settings->basedir);
      }

      // Write to normal log if logging enabled
      if (settings->logging) {
        logline(str, ircdstate, settings->basedir, LOG_TOPIC);
      }

      free(topiccopy);
      free(prefixcopy);
      return 1;
    }

    // Server PRIVMSG received?  Relay to all clients and write to replay log.
    if (strncmp(tokens[1], "PRIVMSG", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server PRIVMSG found and it is: %s with length %zd!  Next token is '%s'.  Relaying to all clients.\n",
                 tokens[0], strlen(tokens[0]), tokens[2]);

      sendtoallclients(clients, str, sourcefd, settings);

      // Write to replay log if replay logging enabled
      if (settings->replaylogging) {
        writereplayline(str, settings->basedir);
      }

      // Write to normal log if logging enabled
      if (settings->logging) {
        logline(str, ircdstate, settings->basedir, LOG_PRIVMSG);
      }

      return 1;
    }

    // Server NICK received?
    // 1. Find out if it was us and change ircnick and nickuserhost if so
    // 2. Either way, relay to all clients
    if (strncmp(tokens[1], "NICK", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server NICK found and it is: %s with length %zd!  Next token is '%s'.  Updating records and relaying to all clients.\n",
                 tokens[0], strlen(tokens[0]), tokens[2]);

      // Was it us?
      // Copy to a temporary string so we still have the original in case we need it
      char *svrprefixcopy = strdup(tokens[0]);
      // Just get the nick for comparison
      extractnickfromprefix(svrprefixcopy, 1);
      if ((strlen(ircdstate->ircnick) && strlen(svrprefixcopy)) && (strncmp(ircdstate->ircnick, svrprefixcopy, strlen(ircdstate->ircnick)) == 0)) {
        debugprint(DEBUG_FULL, "Server NICK: nick is ours ('%s' vs '%s').\n", svrprefixcopy, ircdstate->ircnick);
        // Make a copy of the old nickuserhost for updategreetings() below
        char *nickuserhostcpy = strdup(ircdstate->nickuserhost);
        // Update nickuserhost with the new :nick!user@host
        updatenickuserhost(ircdstate->nickuserhost, tokens[2]);
        debugprint(DEBUG_FULL, "Updated nickuserhost to '%s'.\n", ircdstate->nickuserhost);
        // Prepare to update ircnick and greetings strings
        // Temporary copy of new nickuserhost
        char *prefixcopy = strdup(ircdstate->nickuserhost);
        // Get nick from it
        extractnickfromprefix(prefixcopy, 1);
        // Update greeting strings for relaying to new clients
        updategreetings(ircdstate->greeting001, ircdstate->greeting002, ircdstate->greeting003, ircdstate->greeting004,
                        ircdstate->greeting005a, ircdstate->greeting005b, ircdstate->greeting005c, ircdstate->nickuserhost,
                        nickuserhostcpy, tokens[2], ircdstate->ircnick);
        // Update our nick
        strcpy(ircdstate->ircnick, prefixcopy);
        debugprint(DEBUG_FULL, "Updated ircnick to '%s'.\n", ircdstate->ircnick);
        free(nickuserhostcpy);
        free(prefixcopy);
      }

      // Relay to all clients
      sendtoallclients(clients, str, sourcefd, settings);

      // Write to replay log if replay logging enabled
      if (settings->replaylogging) {
        writereplayline(str, settings->basedir);
      }

      // Get each channel the old nick was in, and log the NICK change in that channel if enabled
      if (settings->logging) {
        char oldnick[MAXDATASIZE];
        strcpy(oldnick, tokens[0]);
        extractnickfromprefix(oldnick, 1);
        for (int i = 0; i < ircdstate->maxchannelcount; i++) {
          if (channels[i].name[0]) {
            for (int j = 0; j < MAXCHANNICKS; j++) {
              if (strlen(channels[i].nicks[j]) == strlen(oldnick) && !strcmp(channels[i].nicks[j], oldnick)) {
                char logstring[MAXDATASIZE];
                snprintf(logstring, MAXDATASIZE, "%s %s", channels[i].name, str);
                logline(logstring, ircdstate, settings->basedir, LOG_NICK);
                break;
              }
            }
          }
        }
      }

      // Update old nick to the new nick in our local channel struct
      if (!updatenickinallchannels(tokens[0], tokens[2], channels, ircdstate->maxchannelcount)) {
        debugprint(DEBUG_CRIT, "Failed to update old nick to new nick in channels.\n");
      }

      free(svrprefixcopy);
      return 1;
    }

    // Server MODE received?  See what sort it is and act accordingly.
    if (strncmp(tokens[1], "MODE", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server MODE found and it is: %s with length %zd!  Next token is '%s'.  Analysing...\n",
                 tokens[1], strlen(tokens[1]), tokens[2]);

      // Might be our initial mode (e.g. ":nick MODE nick :+iwz")
      char comparison[MAXDATASIZE];
      snprintf(comparison, MAXDATASIZE, ":%s MODE %s :", ircdstate->ircnick, ircdstate->ircnick);
      if (strncmp(str, comparison, strlen(comparison)) == 0) {
        // Looks like it!
        debugprint(DEBUG_FULL, "Our initial MODE found (%s), storing for later.\n", tokens[3]);
        // Store in ircdstate for when clients connect and relay to current clients.
        strcpy(ircdstate->mode, tokens[3]);

        // Relay to all current clients anyway - TODO - Necessary?
        sendtoallclients(clients, str, sourcefd, settings);

        return 1;
      }

      // Might be a channel mode (e.g. ":nick!user@host MODE #channel +s")
      if (tokens[2][0] == '#') {
        // Looks like it!  Tell all clients.
        debugprint(DEBUG_FULL, "Channel MODE found (%s %s), telling all clients.\n", tokens[2], tokens[3]);
        sendtoallclients(clients, str, sourcefd, settings);

        // Write to replay log if replay logging enabled
        if (settings->replaylogging) {
          writereplayline(str, settings->basedir);
        }

        // Write to normal log if logging enabled
        if (settings->logging) {
          logline(str, ircdstate, settings->basedir, LOG_MODE);
        }

        return 1;
      }

      // Relay to all current clients if not processed by the above
      sendtoallclients(clients, str, sourcefd, settings);

      return 1;
    }

    // Server 324 (RPL_CHANNELMODEIS) received?  Send to any clients who requested a channel MODE.
    if (strncmp(tokens[1], "324", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server 324 (RPL_CHANNELMODEIS) found and it is: %s with length %zd!  Sending to clients who are pending this.\n",
                 tokens[1], strlen(tokens[1]));

      // Relay to all pending clients
      for (int i = 0; i < MAXCLIENTS; i++) {
        if (clients[i].pendingchannelmode == 1) {
          sendtoclient(clients[i].fd, str, clients, settings, 0);
          // And clear the pending flag
          clients[i].pendingchannelmode = 0;
        }
      }

      return 1;
    }

    // Server 367 (RPL_BANLIST) or 368 (RPL_ENDOFBANLIST) received?  Send to any clients who requested a ban MODE query.
    if (strncmp(tokens[1], "367", strlen(tokens[1])) == 0 || strncmp(tokens[1], "368", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server 367 (RPL_BANLIST) or 368 (RPL_ENDOFBANLIST) found and it is: %s with length %zd!  Sending to clients who are pending this.\n",
                 tokens[1], strlen(tokens[1]));

      // Relay to all pending clients
      for (int i = 0; i < MAXCLIENTS; i++) {
        if (clients[i].pendingban == 1) {
          sendtoclient(clients[i].fd, str, clients, settings, 0);
          // And clear the pending flag if it's 368 (RPL_ENDOFBANLIST)
          if (strncmp(tokens[1], "368", strlen(tokens[1])) == 0) {
            clients[i].pendingban = 0;
          }
        }
      }

      return 1;
    }

    // Server 329 (RPL_CREATIONTIME), 352 (RPL_WHOREPLY), or 315 (RPL_ENDOFWHO) received?  Send to any clients who requested a WHO.
    if (strncmp(tokens[1], "329", strlen(tokens[1])) == 0 || strncmp(tokens[1], "352", strlen(tokens[1])) == 0 || strncmp(tokens[1], "315", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server 329 (RPL_CREATIONTIME), 352 (RPL_WHOREPLY), or 315 (RPL_ENDOFWHO) found and it is: %s with length %zd!  Sending to clients who are pending one of these.\n",
                 tokens[1], strlen(tokens[1]));

      // Relay to all pending clients
      for (int i = 0; i < MAXCLIENTS; i++) {
        if (clients[i].pendingwho == 1 && clients[i].fd) {
          sendtoclient(clients[i].fd, str, clients, settings, 0);
          // And clear the pending flag if it's 315 (RPL_ENDOFWHO)
          if (strncmp(tokens[1], "315", strlen(tokens[1])) == 0) {
            clients[i].pendingwho = 0;
          }
        }
      }

      return 1;
    }

    // Server 321 (RPL_LISTSTART), 322 (RPL_LIST), or 323 (RPL_LISTEND) received?  Send to any clients who requested a WHO.
    if (strncmp(tokens[1], "321", strlen(tokens[1])) == 0 || strncmp(tokens[1], "322", strlen(tokens[1])) == 0 || strncmp(tokens[1], "323", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server 321 (RPL_LISTSTART), 322 (RPL_LIST), or 323 (RPL_LISTEND) found and it is: %s with length %zd!  Sending to clients who are pending one of these.\n",
                 tokens[1], strlen(tokens[1]));

      // Relay to all pending clients
      for (int i = 0; i < MAXCLIENTS; i++) {
        if (clients[i].pendinglist == 1 && clients[i].fd) {
          sendtoclient(clients[i].fd, str, clients, settings, 0);
          // And clear the pending flag if it's 323 (RPL_LISTEND)
          if (strncmp(tokens[1], "323", strlen(tokens[1])) == 0) {
            clients[i].pendinglist = 0;
          }
        }
      }

      return 1;
    }

    // Server 307 (RPL_SUSERHOST), 311 (RPL_WHOISUSER), 312 (RPL_WHOISSERVER), 313 (RPL_WHOISOPERATOR), 317 (RPL_WHOISIDLE),
    // 319 (RPL_WHOISCHANNELS), 320 (RPL_WHOISSPECIAL), 378 (RPL_WHOISHOST), 379 (RPL_WHOISMODES), 671 (RPL_WHOISSECURE),
    // or 318 (RPL_ENDOFWHOIS) received?
    // Send to any clients who requested a WHOIS.
    if (strncmp(tokens[1], "307", strlen(tokens[1])) == 0 || strncmp(tokens[1], "311", strlen(tokens[1])) == 0 ||
        strncmp(tokens[1], "312", strlen(tokens[1])) == 0 || strncmp(tokens[1], "313", strlen(tokens[1])) == 0 ||
        strncmp(tokens[1], "317", strlen(tokens[1])) == 0 || strncmp(tokens[1], "319", strlen(tokens[1])) == 0 ||
        strncmp(tokens[1], "320", strlen(tokens[1])) == 0 || strncmp(tokens[1], "378", strlen(tokens[1])) == 0 ||
        strncmp(tokens[1], "379", strlen(tokens[1])) == 0 || strncmp(tokens[1], "671", strlen(tokens[1])) == 0 ||
        strncmp(tokens[1], "318", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server 307 RPL_SUSERHOST, 311 RPL_WHOISUSER, 312 RPL_WHOISSERVER, 313 (RPL_WHOISOPERATOR), 317 RPL_WHOISIDLE, "
                 "319 RPL_WHOISCHANNELS, 320 (RPL_WHOISSPECIAL), 378 RPL_WHOISHOST, 379 (RPL_WHOISMODES), 671 (RPL_WHOISSECURE), "
                 "or 318 RPL_ENDOFWHOIS found and it is: %s with length %zd!  Sending to clients who are pending one of these.\n", tokens[1],
                 strlen(tokens[1]));

      // Relay to all pending clients
      for (int i = 0; i < MAXCLIENTS; i++) {
        if (clients[i].pendingwhois == 1 && clients[i].fd) {
          sendtoclient(clients[i].fd, str, clients, settings, 0);
          // And clear the pending flag if it's 318 RPL_ENDOFWHOIS
          if (strncmp(tokens[1], "318", strlen(tokens[1])) == 0) {
            clients[i].pendingwhois = 0;
          }
        }
      }

      return 1;
    }

    // Server 314 (RPL_WHOWASUSER), 406 (ERR_WASNOSUCHNICK), or 369 (RPL_ENDOFWHOWAS) received?
    // Send to any clients who requested a WHOWAS.
    if (strncmp(tokens[1], "314", strlen(tokens[1])) == 0 || strncmp(tokens[1], "406", strlen(tokens[1])) == 0 || strncmp(tokens[1], "369", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "314 (RPL_WHOWASUSER), 406 (ERR_WASNOSUCHNICK), or 369 (RPL_ENDOFWHOWAS) "
                 "found and it is: %s with length %zd!  Sending to clients who are pending one of these.\n", tokens[1], strlen(tokens[1]));

      // Relay to all pending clients
      for (int i = 0; i < MAXCLIENTS; i++) {
        if (clients[i].pendingwhowas == 1 && clients[i].fd) {
          sendtoclient(clients[i].fd, str, clients, settings, 0);
          // And clear the pending flag if it's 369 RPL_ENDOFWHOWAS
          if (strncmp(tokens[1], "369", strlen(tokens[1])) == 0) {
            clients[i].pendingwhowas = 0;
          }
        }
      }

      return 1;
    }

    // Server 312 (RPL_WHOISSERVER) received?  Check to see if anyone was pending a WHOIS or a WHOWAS and send to them, if not send to everyone.
    if (strncmp(tokens[1], "312", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server 312 (RPL_WHOISSERVER) found and it is: %s with length %zd!  Sending to clients who are pending this or to everyone if nobody is.\n",
                 tokens[1], strlen(tokens[1]));

      int waspending = 0;

      // Relay to all pending clients
      for (int i = 0; i < MAXCLIENTS; i++) {
        if (clients[i].pendingwhois == 1 || clients[i].pendingwhowas == 1) {
          sendtoclient(clients[i].fd, str, clients, settings, 0);
          // Note that we were pending this
          waspending = 1;
        }
      }

      // If no client was pending this, send to everyone
      if (!waspending) {
        sendtoallclients(clients, str, 0, settings);
      }

      return 1;
    }


    // Server 401 (ERR_NOSUCHNICK) received?  Check to see if anyone was pending a WHOIS and send to them,
    // if not send to everyone (401 was probably in reply to something else like a PRIVMSG).
    if (strncmp(tokens[1], "401", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server 401 (ERR_NOSUCHNICK) found and it is: %s with length %zd!  Sending to clients who are pending this or to everyone if nobody is.\n",
                 tokens[1], strlen(tokens[1]));

      int waspending = 0;

      // Relay to all pending clients
      for (int i = 0; i < MAXCLIENTS; i++) {
        if (clients[i].pendingwhois == 1) {
          sendtoclient(clients[i].fd, str, clients, settings, 0);
          // Note that we were pending this
          waspending = 1;
        }
      }

      // If no client was pending this, send to everyone
      if (!waspending) {
        sendtoallclients(clients, str, 0, settings);
      }

      return 1;
    }

    // Server 432 (ERR_ERRONEUSNICKNAME) or 433 (ERR_NICKNAMEINUSE) received?  See which nick we're on and try another.
    // (But only if we're not already registered with the real IRC server.)
    if ((strncmp(tokens[1], "432", strlen(tokens[1])) == 0 || strncmp(tokens[1], "433", strlen(tokens[1])) == 0) && !strlen(ircdstate->greeting004)) {
      debugprint(DEBUG_SOME, "Server 432 (ERR_ERRONEUSNICKNAME) or 433 (ERR_NICKNAMEINUSE) found and it is: %s with length %zd!  Trying another nick...\n",
                 tokens[1], strlen(tokens[1]));

      // Find the nick (its index in the nicks array) currently selected
      int nickindex = -1; // -1 used later if current nick isn't in the configuration array
      int nickcount = 0; // How many nicks are configured in the configuration array
      for (int i = 0; i < MAXCONFARR; i++) {
        if (settings->ircnicks[i][0]) {
          nickcount++;
          if (strncmp(ircdstate->ircnick, settings->ircnicks[i], strlen(settings->ircnicks[i])) == 0 && strlen(ircdstate->ircnick) == strlen(settings->ircnicks[i])) {
            nickindex = i;
          }
        }
      }

      // If there are more nicks left to try, then try the next one
      if (nickindex < nickcount - 1) {
        strcpy(ircdstate->ircnick, settings->ircnicks[nickindex + 1]);
        debugprint(DEBUG_SOME, "Switched nick to '%s' and retrying...\n", ircdstate->ircnick);
      // Otherwise, give up on configured nicks and switch to autonick
      } else {
        debugprint(DEBUG_SOME, "Giving up on preconfigured nicks trying autonick...\n", ircdstate->ircnick);
        tryautonick(ircdstate);
      }

      // Try it with the server
      char outgoingmsg[MAXDATASIZE];
      snprintf(outgoingmsg, MAXDATASIZE, "NICK %s", ircdstate->ircnick);
      // sourcefd = 0 as this is a trusted message
      sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings);

      return 1;
    }

    // Server CAP received?
    if (strncmp(tokens[1], "CAP", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server CAP found and it is: %s with length %zd!  Analysing...\n", tokens[1], strlen(tokens[1]));
      // If the server said "CAP <ournick> ACK :multi-prefix" then it must have approved our CAP multi-prefix request
      if (counter == 5) {
        if (strlen(tokens[2]) == strlen(ircdstate->ircnick) &&
            strncmp(tokens[2], ircdstate->ircnick, strlen(tokens[2])) == 0 &&
            strncmp(tokens[3], "ACK", strlen(tokens[3])) == 0 &&
            strncmp(tokens[4], ":multi-prefix", strlen(tokens[4])) == 0) {
          ircdstate->capmultiprefix = 1;
        }
      }
      // We didn't handle it
      debugprint(DEBUG_FULL, "Unhandled server CAP response.\n");
    }

    // Server NOTICE received?  Handle and log if it's from a user, otherwise let pass through to default handler.
    if (strncmp(tokens[1], "NOTICE", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server NOTICE found and it is: %s with length %zd!  Analysing...\n", tokens[1], strlen(tokens[1]));
      // If the first token is a nick!user@host then it's probably from a user
      if (strstr(tokens[0], "!") != NULL && strstr(tokens[0], "@") != NULL) {
        debugprint(DEBUG_FULL, "Server NOTICE appears to be from a user, sending to all clients and logging.\n");

        sendtoallclients(clients, str, 0, settings);

        // Write to replay log if replay logging enabled
        if (settings->replaylogging) {
          writereplayline(str, settings->basedir);
        }

        // Write to normal log if logging enabled
        if (settings->logging) {
          logline(str, ircdstate, settings->basedir, LOG_PRIVMSG);
        }

        return 1;
      } else {
        debugprint(DEBUG_FULL, "Server NOTICE does not appear to be from a user, passing through.\n");
      }
    }

    // Server PONG received?  This is probably in response to our PING to see if we're still connected, discard it.
    if (strncmp(tokens[1], "PONG", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Server PONG found and it is: %s with length %zd!  Discarding.\n", tokens[1], strlen(tokens[1]));
      return 1;
    }
  }

  // We didn't process anything so return 0
  return 0;
}

// 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 clientcodes *clientcodes) {
  // Index of client fd in clients array for use later
  int clientindex = arrindex(clients, sourcefd);
  if (clientindex < 0) {
    // Client not found (perhaps disconnected after failing to authenticate following a previous ircmessage in the same rawstring)
    debugprint(DEBUG_CRIT, "processclientmessage(): error: arrindex() returned '%d', returning 1!\n", clientindex);
    return 1;
  }

  // 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)) {
      debugprint(DEBUG_FULL, "Password accepted!  Setting client %s with fd %d to authenticated.\n", clients[clientindex].remoteip, sourcefd);
      // Find the client in the clients array and set them as authenticated
      for (int i = 0; i < MAXCLIENTS; i++) {
        if (clients[i].fd == sourcefd) {
          // Found client in array, set to authenticated
          clients[i].authed = 1;
          debugprint(DEBUG_FULL, "Found and authenticated fd in arr_authed.\n");
          // Alert other clients about the successful authentication
          char alertmsg[MAXDATASIZE];
          if (!snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :blabouncer: new client %s has successfully authenticated.", ircdstate->ircnick,
              clients[clientindex].remoteip)) {
            fprintf(stderr, "Error while preparing authentication success NOTICE!\n");
            debugprint(DEBUG_CRIT, "Error while preparing authentication success NOTICE!\n");
            alertmsg[0] = '\0';
          }
          // "except" the current fd - we can use this as "except/sourcefd" since we set them as authed just above
          sendtoallclients(clients, alertmsg, sourcefd, settings);
        }
      }
    } else {
      // Store the client's IP address for now, since we need to refer to it after disconnecting
      // them (thus clearing the array entry that the IP is read from)
      char remoteip[INET6_ADDRSTRLEN];
      strncpy(remoteip, clients[clientindex].remoteip, INET6_ADDRSTRLEN);
      debugprint(DEBUG_SOME, "Password rejected, disconnecting client %s with fd %d.\n", remoteip, sourcefd);
      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 %s failed to authenticate.", ircdstate->ircnick, remoteip)) {
        fprintf(stderr, "Error while preparing authentication failure NOTICE!\n");
        debugprint(DEBUG_CRIT, "Error while preparing authentication failure NOTICE!\n");
        alertmsg[0] = '\0';
      }
      // "except" 0 since we trust this message
      sendtoallclients(clients, alertmsg, 0, settings);
    }

    return 1;
  }

  // CAP received?  Clients can send CAP before PASS so we have to deal with this even if they are not authenticated yet.
  if (strncasecmp(tokens[0], "CAP", strlen(tokens[0])) == 0) {
    // But only do something if the real server told us it had a CAP (only multi-prefix for now)
    if (ircdstate->capmultiprefix == 1) {
      debugprint(DEBUG_FULL, "Client CAP received and the server supports CAPs, continuing.\n");
    } else {
      debugprint(DEBUG_FULL, "Client CAP received but the server doesn't support CAPs, returning.\n");
      return 1;
    }
    // Get the real IRC server name from greeting001
    // They are now pending CAP negotiation
    clients[clientindex].pendingcap = 1;
    char outgoingmsg[MAXDATASIZE];
    // If client is requesting CAP list, send it...
    if (strncasecmp(tokens[1], "LS", strlen(tokens[1])) == 0) {
      if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s CAP * LS :multi-prefix", ircdstate->ircdname)) {
        fprintf(stderr, "Error while preparing CAP LS response!\n");
        debugprint(DEBUG_CRIT, "Error while preparing CAP LS response!\n");
        outgoingmsg[0] = '\0';
      }
      // ...even if unauthenticated
      sendtoclient(sourcefd, outgoingmsg, clients, settings, 1);
      return 1;
    // If client is requesting a CAP...
    } else if (strncasecmp(tokens[1], "REQ", strlen(tokens[1])) == 0) {
      // ...and it is "multi-prefix", send it
      if (strncasecmp(tokens[2], ":multi-prefix", strlen(tokens[2])) == 0) {
        if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s CAP %s ACK :multi-prefix ", ircdstate->ircdname, ircdstate->ircnick)) {
          fprintf(stderr, "Error while preparing CAP ACK response!\n");
          debugprint(DEBUG_CRIT, "Error while preparing CAP ACK response!\n");
          outgoingmsg[0] = '\0';
        }
        // ...even if unauthenticated
        sendtoclient(sourcefd, outgoingmsg, clients, settings, 1);
        return 1;
      }
    // If client is finishing CAP negotiation then mark them as so
    } else if (strncasecmp(tokens[1], "END", strlen(tokens[1])) == 0) {
      clients[clientindex].pendingcap = -1;
    }
  }

  // We're past PASS in the list of possible commands, so ignore
  // anything else the client says if they are not authenticated yet.
  if (!clients[clientindex].authed) {
    debugprint(DEBUG_CRIT, "Ignoring client command '%s' from sourcefd '%d' as not authenticated yet.\n", tokens[0], sourcefd);
    return 1;
  }

  // USER received and not pending CAP negotiation during registration?
  // Or client has just finished negotiating CAP (pendingcap = -1)?
  // If so, assume this is a new client connecting and catch them on up on the state
  if ((strncasecmp(tokens[0], "USER", strlen(tokens[0])) == 0 && clients[clientindex].pendingcap == 0) || clients[clientindex].pendingcap == -1) {
    // Somewhere to store the several strings we will need to build and send
    char outgoingmsg[MAXDATASIZE]; // String to send to client

    // If registering then they must no longer be pending CAP negotiation
    clients[clientindex].pendingcap = 0;

    // 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, clientcodes);
      return 1;
    }

    // Send IRC greeting strings (001/RPL_WELCOME, 002/RPL_YOURHOST, 003/RPL_CREATED, 004/RPL_MYINFO, 005/RPL_ISUPPORT) to client
    snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstate->greeting001);
    sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
    snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstate->greeting002);
    sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
    snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstate->greeting003);
    sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
    snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstate->greeting004);
    sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
    if (ircdstate->greeting005a[0]) {
      snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstate->greeting005a);
      sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
    }
    if (ircdstate->greeting005b[0]) {
      snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstate->greeting005b);
      sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
    }
    if (ircdstate->greeting005c[0]) {
      snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstate->greeting005c);
      sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
    }

    // Send our own greeting message
    snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Welcome to blabouncer version %s!", ircdstate->ircnick, VERSION);
    sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
    snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Blabouncer commands are all prefixed with BLABOUNCER which you can usually send using \"/QUOTE BLABOUNCER\"", ircdstate->ircnick);
    sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
    snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Valid blabouncer commands are:", ircdstate->ircnick);
    sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
    snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REPLAY [[[days:]hours:]minutes]\" (To replay a given length of time of replay log.)", ircdstate->ircnick);
    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);
    snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER VERSION\" (To show the current blabouncer version.)", ircdstate->ircnick);
    sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);

    // Get the channel count so we can iterate over all channels.
    int channelcount = getchannelcount(channels, ircdstate->maxchannelcount);
    // Set the client as pending RPL_NAMREPLYs for 'channelcount' channels
    debugprint(DEBUG_FULL, "Setting pendingnames to '%d' for client with fd '%d'.\n", channelcount, sourcefd);
    clients[clientindex].pendingnames = channelcount;

    // Get client to join channels, and tell client about those channels
    for (int i = 0; i < ircdstate->maxchannelcount; i++) {
      debugprint(DEBUG_FULL, "JOINing channel[%d] out of %d.\n", i, channelcount);
      // Skip this one and increment channelcount if it's a blank channel
      if (!channels[i].name[0]) {
          debugprint(DEBUG_FULL, "Actually, skipping blank channel channel[%d].\n", i);
          channelcount++;
          continue;
      }

      // Get client to join channels
      if (!snprintf(outgoingmsg, MAXDATASIZE, "%s JOIN :%s", ircdstate->nickuserhost, channels[i].name)) {
        fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses!\n");
        debugprint(DEBUG_CRIT, "Error while preparing USER just connected, channel JOIN responses!\n");
        return 0;
      }
      sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);

      // Send topic (or lack thereof) to client
      // If there isn't one set (we guess this if topic timestamp is 0), send 331 RPL_NOTOPIC
      // TODO - What if the topic is "0"?
      if (strncmp(channels[i].topicwhen, "0", 1) == 0) {
        // Prepare the no topic message...
        if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 331 %s %s :No topic is set.", ircdstate->ircdname, ircdstate->ircnick, channels[i].name)) {
          fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses, 331 RPL_NOTOPIC!\n");
          debugprint(DEBUG_CRIT, "Error while preparing USER just connected, channel JOIN responses, 331 RPL_NOTOPIC!\n");
          return 0;
        }
        // ..and send it to the client
        sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
      // If there is one set, send 332 RPL_TOPIC and 333 RPL_TOPICWHOTIME
      } else {
        // Prepare the topic message...
        if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 332 %s %s :%s", ircdstate->ircdname, ircdstate->ircnick, channels[i].name, channels[i].topic)) {
          fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses, 332 RPL_TOPIC!\n");
          debugprint(DEBUG_CRIT, "Error while preparing USER just connected, channel JOIN responses, 332 RPL_TOPIC!\n");
          return 0;
        }
        // ..and send it to the client
        sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);

        // Next prepare the topic who/when message...
        if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 333 %s %s %s %s", ircdstate->ircdname, ircdstate->ircnick, channels[i].name, channels[i].topicwho, channels[i].topicwhen)) {
          fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses, 333 RPL_TOPICWHOTIME!\n");
          debugprint(DEBUG_CRIT, "Error while preparing USER just connected, channel JOIN responses, 333 RPL_TOPICWHOTIME!\n");
          return 0;
        }
        // ..and send it to the client
        sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
      }

      // Get the latest RPL_NAMREPLY for this channel to relay to the client when it arrives
      char namesreq[MAXDATASIZE];
      snprintf(namesreq, MAXDATASIZE, "NAMES %s", channels[i].name);
      sendtoserver(server_ssl, namesreq, strlen(namesreq), 0, clients, settings);
    }

    // Send our mode to the client (if we have one)
    if (strlen(ircdstate->mode) > 0) {
      if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s MODE %s %s", ircdstate->ircnick, ircdstate->ircnick, ircdstate->mode)) {
        fprintf(stderr, "Error while preparing USER just connected, MODE response!\n");
        debugprint(DEBUG_CRIT, "Error while preparing USER just connected, MODE response!\n");
        return 0;
      }
      sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
    }

    // Set the client as registered
    clients[clientindex].registered = 1;

    // Catch the client up with the default number of seconds of replay
    if (!doautoreplay(sourcefd, clients, settings, ircdstate, channels)) {
      snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstate->ircnick);
      sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
    }

    // And record the time of the last client registration
    ircdstate->clientchangetime = time(NULL);

    return 1;
  }

  // Pretty much ignore anything else the client says if it's not registered yet,
  // as the first thing we want to hear is either PASS or USER
  if (!clients[clientindex].registered) {
    debugprint(DEBUG_SOME, "Ignoring client command '%s' from sourcefd '%d' as not registered yet.\n", tokens[0], sourcefd);
    return 1;
  }

  // Client PING received?  If so, send a PONG back with the next element as the argument.
  if (strncasecmp(tokens[0], "PING", strlen(tokens[0])) == 0) {
    debugprint(DEBUG_FULL, "Client PING found and it is: %s with length %zd!  Sending response...\n", tokens[0], strlen(tokens[0]));

    char outgoingmsg[MAXDATASIZE]; // String to send to client
    if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s PONG %s :%s", ircdstate->ircdname, ircdstate->ircdname, tokens[1])) { // TODO - Make sure tokens[1] actually has a token
      fprintf(stderr, "Error while preparing PONG response!\n");
      debugprint(DEBUG_CRIT, "Error while preparing PONG response!\n");
      outgoingmsg[0] = '\0';
    }
    sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);

    // We processed something so return true
    return 1;
  }

  // Client PONG received?  This is almost certainly in response to a relayed server PING from us.
  // We can ignore it since we don't actively PING clients, instead relying on the server's PING frequency.
  if (strncasecmp(tokens[0], "PONG", strlen(tokens[0])) == 0) {
    debugprint(DEBUG_FULL, "Client PONG found and it is: %s with length %zd!  Ignoring since we don't track client PONGs.\n", tokens[0], strlen(tokens[0]));  
    return 1;
  }

  // TODO - Ignoring CAP for now so as not to confuse other clients, but we should probably query the server then relay the response to the client
  if (strncasecmp(tokens[0], "CAP", strlen(tokens[0])) == 0) {
    debugprint(DEBUG_FULL, "Client CAP found and it is: %s with length %zd!  Ignoring completely for now - TODO - do something useful!\n", tokens[0], strlen(tokens[0]));
    return 1;
  }

  // Just send NICK to server and let it change all clients' nicks if needed
  if (strncasecmp(tokens[0], "NICK", strlen(tokens[0])) == 0) {
    debugprint(DEBUG_FULL, "Client NICK found and it is: %s with length %zd!  Sending to server...\n", tokens[0], strlen(tokens[0]));
    sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings);
    return 1;
  }

  // If PRIVMSG received, send to server, but also reformat and send to all other clients and log to replay file.
  if (strncasecmp(tokens[0], "PRIVMSG", strlen(tokens[0])) == 0) {
    debugprint(DEBUG_FULL, "Client PRIVMSG found and it is: %s with length %zd!  Sending to server then back to other clients...\n", tokens[0], strlen(tokens[0]));
    // Send original request straight to server
    sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings);

    // If it seems to be a CTCP VERSION request, just send to the server (CTCP requests are delimited with \1)
    if (counter == 3 && strncmp(tokens[2], ":\1VERSION\1", strlen(tokens[2])) == 0) {
      debugprint(DEBUG_FULL, "Client PRIVMSG looked like a CTCP VERSION request, so not sending to other clients.\n");
      return 1;
    }

    // Rebuild to full PRIVMSG string and relay to all other clients
    char outgoingmsg[MAXDATASIZE]; // String to send to client

    if (!snprintf(outgoingmsg, MAXDATASIZE, "%s %s", ircdstate->nickuserhost, str)) {
      fprintf(stderr, "Error while preparing PRIVMSG relay from another bouncer client.\n");
      debugprint(DEBUG_CRIT, "Error while preparing PRIVMSG relay from another bouncer client.\n");
      return 0;
    }
    // Send to all except source client
    sendtoallclients(clients, outgoingmsg, sourcefd, settings);

    // Write to replay log if replay logging enabled
    if (settings->replaylogging) {
      writereplayline(outgoingmsg, settings->basedir);
    }

    // Write to normal log if logging enabled
    if (settings->logging) {
      logline(outgoingmsg, ircdstate, settings->basedir, LOG_PRIVMSG);
    }

    return 1;
  }

  // Just send JOIN to server and let it talk back to clients as required
  if (strncasecmp(tokens[0], "JOIN", strlen(tokens[0])) == 0) {
    debugprint(DEBUG_FULL, "Client JOIN found and it is: %s with length %zd!  Sending to server...\n", tokens[0], strlen(tokens[0]));
    sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings);
    return 1;
  }

  // Don't do anything with QUIT, just let the client go - TODO: Let another clients know with a NOTICE or something
  // 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, clientcodes);
    return 1;
  }

  // Just send PART to server and let it talk back to clients as required
  if (strncasecmp(tokens[0], "PART", strlen(tokens[0])) == 0) {
    debugprint(DEBUG_FULL, "Client PART found and it is: %s with length %zd!  Sending to server...\n", tokens[0], strlen(tokens[0]));
    sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings);
    return 1;
  }

  // Just send TOPIC to server and let it talk back to clients as required
  if (strncasecmp(tokens[0], "TOPIC", strlen(tokens[0])) == 0) {
    debugprint(DEBUG_FULL, "Client TOPIC found and it is: %s with length %zd!  Sending to server...\n", tokens[0], strlen(tokens[0]));
    sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings);
    return 1;
  }

  // Take note of what sort of MODE was requested, mark the client as waiting for the reply (so not all clients get it)
  // Send it to the server either way and let it talk back to the requesting client as required
  if (strncasecmp(tokens[0], "MODE", strlen(tokens[0])) == 0) {
    debugprint(DEBUG_FULL, "Client MODE found and it is: %s with length %zd!  Analysing...\n", tokens[0], strlen(tokens[0]));
    // Is it a ban MODE request (MODE #channel b)?
    // TODO - Can something else beginning with "b" be in this position?  Need a length comparison?
    if (counter >= 3 && strncmp(tokens[2], "b", strlen("b")) == 0) {
      debugprint(DEBUG_FULL, "Ban MODE request received, marking as pending.\n");
      clients[clientindex].pendingban = 1;
    } else if (counter == 2) {
      // Assume a normal channel mode request (MODE #channel) - TODO - What if it isn't!?
      debugprint(DEBUG_FULL, "Assuming channel MODE request received, marking as pending.\n");
      clients[clientindex].pendingchannelmode = 1;
    }

    // Either way, send it on the server
    sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings);
    return 1;
  }

  // WHO requested, mark the client as waiting for the reply (so not all clients get it) and send it on the server
  if (strncasecmp(tokens[0], "WHO", strlen(tokens[0])) == 0) {
    debugprint(DEBUG_FULL, "Client WHO found and it is: %s with length %zd!  Marking as pending.\n", tokens[0], strlen(tokens[0]));
    clients[clientindex].pendingwho = 1;

    // Either way, send it on the server
    sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings);
    return 1;
  }

  // LIST requested, mark the client as waiting for the reply (so not all clients get it) and send it on the server
  if (strncasecmp(tokens[0], "LIST", strlen(tokens[0])) == 0) {
    debugprint(DEBUG_FULL, "Client LIST found and it is: %s with length %zd!  Marking as pending.\n", tokens[0], strlen(tokens[0]));
    clients[clientindex].pendinglist = 1;

    // Either way, send it on the server
    sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings);
    return 1;
  }

  // Client WHOIS received, send straight on to server and mark the client as pending the response
  if (strncasecmp(tokens[0], "WHOIS", strlen(tokens[0])) == 0) {
    debugprint(DEBUG_FULL, "Client WHOIS found and it is: %s with length %zd!  Sending to server and setting client as pending.\n", tokens[0], strlen(tokens[0]));
    clients[clientindex].pendingwhois = 1;
    sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings);
    return 1;
  }

  // Client WHOWAS received, send straight on to server and mark the client as pending the response
  if (strncasecmp(tokens[0], "WHOWAS", strlen(tokens[0])) == 0) {
    debugprint(DEBUG_FULL, "Client WHOWAS found and it is: %s with length %zd!  Sending to server and setting client as pending.\n", tokens[0], strlen(tokens[0]));
    clients[clientindex].pendingwhowas = 1;
    sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings);
    return 1;
  }

  // Client NOTICE received
  if (strncasecmp(tokens[0], "NOTICE", strlen(tokens[0])) == 0) {

    // Rebuild to full NOTICE string including our nick!user@host for logging
    char fullmsg[MAXDATASIZE];

    if (!snprintf(fullmsg, MAXDATASIZE, "%s %s", ircdstate->nickuserhost, str)) {
      fprintf(stderr, "Error while preparing NOTICE string for logging.\n");
      debugprint(DEBUG_CRIT, "Error while preparing NOTICE string for logging.\n");
      return 0;
    }

    // Write to replay log if replay logging enabled
    if (settings->replaylogging) {
      writereplayline(fullmsg, settings->basedir);
    }

    // Write to normal log if logging enabled
    if (settings->logging) {
      logline(fullmsg, ircdstate, settings->basedir, LOG_PRIVMSG);
    }

    // If it's a CTCP VERSION response then only send to the server (CTCP requests are delimited with \1)
    if (counter >= 3 && strncmp(tokens[2], ":\1VERSION", strlen(tokens[2])) == 0) {
      debugprint(DEBUG_FULL, "Client NOTICE looked like a CTCP VERSION response, so just sending to the server.\n");
      sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings);
      return 1;
    }

    // If it wasn't a CTCP VERSION response, then let this fall through to the default unhandled action by not returning here
  }

  // Client PROTOCTL received
  if (strncasecmp(tokens[0], "PROTOCTL", strlen(tokens[0])) == 0) {
    // If it's a PROTOCTL NAMESX, just pass to the server - TODO - Maybe deal with either PROTCTL NAMEX or multi-prefix CAP for mode prefixes?
    if (strncasecmp(tokens[1], "NAMESX", strlen(tokens[1])) == 0) {
      debugprint(DEBUG_FULL, "Client PROTOCTL NAMESX found!  Sending to server.\n", tokens[0], strlen(tokens[0]));
      sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings);
      return 1;
    }
  }

  // Client OPER received, send straight on to server
  if (strncasecmp(tokens[0], "OPER", strlen(tokens[0])) == 0) {
    debugprint(DEBUG_FULL, "Client OPER found and it is: %s with length %zd!  Sending to server..\n", tokens[0], strlen(tokens[0]));
    sendtoserver(server_ssl, str, strlen(str), sourcefd, clients, settings);
    return 1;
  }

  // Custom BLABOUNCER command received
  // Case insensitive comparisons here since clients won't be recognising and uppercasing these commands
  if (strncasecmp(tokens[0], "BLABOUNCER", strlen(tokens[0])) == 0) {
    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 && 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

      // Track which colon-separated token within this request we're on
      int timecounter = 0;

      // Build array of colon-separated tokens
      char timetokens[MAXTOKENS][MAXDATASIZE];
      // Copy to a temporary string so we still have the original in case it's not processed
      char *timestrcopy = strdup(tokens[2]);
      // Keep track of initial pointer for free()ing later
      char *timestrcopyPtr = timestrcopy;

      char *timetoken;
      while ((timetoken = strsep(&timestrcopy, ":")) != NULL) {
        if (*timetoken == '\0') continue; // Skip consecutive matches
        if (timecounter >= MAXTOKENS) break; // Too many tokens
        debugprint(DEBUG_FULL, "Time token: \"%s\", length %zd.\n", timetoken, strlen(timetoken));
        // Copy into the token array (strlen + 1 to get the NULL terminator)
        strncpy(timetokens[timecounter], timetoken, strlen(timetoken) + 1);
        timecounter++;
      }

      // Make sure we don't have more than three (d:h:m) components
      if (timecounter > 3) {
        debugprint(DEBUG_SOME, "Too many time components requested by REPLAY command.  Telling client.\n");
        snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Too many time components requested by REPLAY command.  Expected up to three (days:hours:minutes).", ircdstate->ircnick);
        sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
        free(timestrcopyPtr);
        return 1;
      }

      // Make sure all the components are numbers
      for (int i = 0; i < timecounter; i++) {
        // Temporary number and pointer for checking errors
        long check;
        char *str_end;
        errno = 0;
        check = strtol(timetokens[i], &str_end, 10);
        if (str_end == timetokens[i] || ((check == LONG_MAX || check == LONG_MIN) && errno == ERANGE)) {
          debugprint(DEBUG_SOME, "Invalid number '%s' requested by REPLAY command.  Telling client.\n", timetokens[i]);
          if (!snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Invalid number '%s' requested by REPLAY command.", ircdstate->ircnick, timetokens[i])) {
            fprintf(stderr, "Error while preparing REPLAY invalid number response!\n");
            debugprint(DEBUG_CRIT, "Error while preparing REPLAY invalid number response!\n");
            outgoingmsg[0] = '\0';
          }
          sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
          free(timestrcopyPtr);
          return 1;
        }
      }

      // How many seconds we're going to replay
      int replayseconds = 0;

      // If d:h:m provided
      if (timecounter == 3) {
        replayseconds += 86400 * strtol(timetokens[0], NULL, 10);
        replayseconds += 3600 * strtol(timetokens[1], NULL, 10);
        replayseconds += 60 * strtol(timetokens[2], NULL, 10);
      }
      // If h:m provided
      if (timecounter == 2) {
        replayseconds += 3600 * strtol(timetokens[0], NULL, 10);
        replayseconds += 60 * strtol(timetokens[1], NULL, 10);
      }
      // If m provided
      if (timecounter == 1) {
        replayseconds += 60 * strtol(timetokens[0], NULL, 10);
      }

      debugprint(DEBUG_FULL, "Replaying '%s' which is '%d' seconds.\n", tokens[2], replayseconds);

      if (!doreplaytime(sourcefd, replayseconds, clients, settings, ircdstate, channels)) {
        snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstate->ircnick);
        sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
      }
      free(timestrcopyPtr);
      return 1;
    // 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]));
      // Combine "QUIT :" with any optional quit message the user provided
      if (counter > 2) {
        // Any optional quit message comes 16 characters after the start of the string ("BLABOUNCER QUIT " is 16 characters)
        cleanexit(server_ssl, clients, sourcefd, ircdstate, settings, str + 16);
      } else {
        cleanexit(server_ssl, clients, sourcefd, ircdstate, settings, "");
      }
    // REHASH received, re-read the configuration file and let rehash() to the appropriate things
    } else if (strncasecmp(tokens[1], "REHASH", strlen("REHASH")) == 0) {
      debugprint(DEBUG_SOME, "Client BLABOUNCER REHASH found and it is: %s with length %zd!  Attempting rehash...\n", tokens[1], strlen(tokens[1]));

      // TODO - This code is duplicated between here and SIGHUP handling

      char failuremsg[MAXDATASIZE];
      failuremsg[0] = '\0';

      // Try to rehash...
      if (!rehash(settings, failuremsg)) {
        // ...or log and tell client if it failed
        debugprint(DEBUG_CRIT, "REHASH failed: %s.\n", failuremsg);
        if (!snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :REHASH failed: %s.", ircdstate->ircnick, failuremsg)) {
          debugprint(DEBUG_CRIT, "Error while preparing REHASH failure message response!\n");
          outgoingmsg[0] = '\0';
        }
        sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
      } else {
        // ...or tell all clients it worked
        snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :REHASH complete!", ircdstate->ircnick);
        sendtoallclients(clients, outgoingmsg, 0, settings);
      }

      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 CLIENTCODE length.\n");
        snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Invalid CLIENTCODE 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 (!doreplaytime(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;
    // VERSION received, send current blabouncer version
    } else if (strncasecmp(tokens[1], "VERSION", strlen("VERSION")) == 0) {
      debugprint(DEBUG_SOME, "Client BLABOUNCER VERSION found and it is: %s with length %zd!\n", tokens[1], strlen(tokens[1]));
      snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :This is blabouncer version %s!", ircdstate->ircnick, VERSION);
      sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
      return 1;
    // Unrecognised BLABOUNCER command received (or a BLABOUNCER HELP command), send some help instructions
    } else {
      // Debug and NOTICE an unrecognised command error unless the command was "HELP"
      if (strncasecmp(tokens[1], "HELP", strlen("HELP")) != 0) {
        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]));
        snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unrecognised BLABOUNCER command received.  Valid commands are:", ircdstate->ircnick);
        sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
      }
      snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REPLAY [[[days:]hours:]minutes]\" (To replay a given length of time of replay log.)", ircdstate->ircnick);
      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);
      snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER VERSION\" (To show the current blabouncer version.)", ircdstate->ircnick);
      sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
      return 1;
    }
  }

  // We didn't process anything so return 0
  return 0;
}