diff options
Diffstat (limited to 'functions.c')
| -rw-r--r-- | functions.c | 557 | 
1 files changed, 557 insertions, 0 deletions
diff --git a/functions.c b/functions.c index b9983d7..0aefa05 100644 --- a/functions.c +++ b/functions.c @@ -355,3 +355,560 @@ void updategreetings(char *greeting001, char *greeting002, char *greeting003, ch      updategreetingnick(greeting005c, "005", newnickcpy, oldnick);    }  } + +// Return index of requested client FD within the clients array. +// TODO - Use this wherever we are calculating the position (various places) instead of +// duplicating code. +int arrindex(struct client *clients, int clientfd) { +  // Find the client in the clients array and make sure they are authenticated +  for (int i = 0; i < MAXCLIENTS; i++) { +    if (clients[i].fd == clientfd) { +      return i; +    } +  } + +  // Something went wrong, we didn't find it +  return 0; +} + +// Send whatever string to a specific client by providing the FD +// If "bypass" == 1 then permit sending to client even if unauthenticated (for instance for a CAP LS response) +int sendtoclient(int fd, char *strsrc, struct client *clients, struct settings *settings, int bypass) { +  // Copy to new string for passing to appendcrlf() to avoid overrun in appendcrlf() +  char str[MAXDATASIZE]; +  strcpy(str, strsrc); + +  appendcrlf(str); // Do this just before sending so callers don't need to worry about it + +  int i = 0; +  // Find the client in the clients array and make sure they are authenticated +  for (i = 0; i < MAXCLIENTS; i++) { +    if (clients[i].fd == fd) { +      // Found client in array, check authentication status +      if (!clients[i].authed && !bypass) { +        debugprint(DEBUG_SOME, "sendtoclient(): skipping unauthenticated client with fd %d.\n", clients[i].fd); +        return 0; +      } +      // Break when we get to the correct fd, "i" is now the position in the clients array of our client +      break; +    } +  } + +  debugprint(DEBUG_SOME, "sendtoclient(): sending \"%s\" (length %zd) to client with fd %d.\n", str, strlen(str), fd); +  if (socksend(clients[i].ssl, str, strlen(str), settings->clienttls) == -1) { +    perror("error: sendtoclient() socksend()\n"); +    debugprint(DEBUG_CRIT, "error: sendtoclient() socksend() error sending to client with fd '%d', errno '%d'.\n", fd, errno); +    return 0; +  } + +  return 1; +} + +// Relay/send message to all clients (optionally except one) +// "except" is used to send to all clients _except_ the fd provided (except = 0 (EXCEPT_NONE) avoids this, i.e. sends to all) +// "except" is really the "sourcefd" and is also used as part of the authentication check - this is messy and they should perhaps be two separate arguments. +int sendtoallclients(struct client *clients, char *strsrc, int except, struct settings *settings) { + +  char *sendertype; + +  // Copy to new string for passing to appendcrlf() to avoid overrun in appendcrlf() +  char str[MAXDATASIZE]; +  strcpy(str, strsrc); + +  appendcrlf(str); // Do this just before sending so callers don't need to worry about it + +  // Decide what sort of text to prefix the debug output with +  // At the moment if non-zero "except" is specified then it must be a message from a bouncer client +  // and if "except" is zero then it must be a message from the real IRC server +  if (except) { +    sendertype = "bouncer-client"; +  } else { +    sendertype = "bouncer-server"; +  } + +  // Find the sending client in the clients array and make sure they are authenticated +  for (int i = 0; i < MAXCLIENTS; i++) { +    // Trust clientfd of 0, only we can set that ourselves +    if (!except) { +      debugprint(DEBUG_FULL, "sendtoallclients(): trusting clientfd of 0.\n"); +      break; +    } +    if (clients[i].fd == except) { +      // Found client in array, check authentication status +      if (!clients[i].authed) { +        debugprint(DEBUG_SOME, "sendtoallclients(): skipping unauthenticated client with fd %d.\n", clients[i].fd); +        return 0; +      } +    } +  } + +  // Relay/send to all clients... +  for (int i = 0; i < MAXCLIENTS; i++) { +    // Skip the current client if "except" non-zero (no need to send back to itself) +    if (clients[i].fd == except) { +      continue; +    } +    // ...but only if they are connected... +    if (clients[i].fd > 0) { +      // ...and authenticated +      if (!clients[i].authed) { +        debugprint(DEBUG_SOME, "sendtoallclients(): skipping unauthenticated client with fd %d.\n", clients[i].fd); +        continue; +      } +      debugprint(DEBUG_SOME, "sendtoallclients(): %s: sending '%s' to client with fd %d.\n", sendertype, str, clients[i].fd); +      if (socksend(clients[i].ssl, str, strlen(str), settings->clienttls) == -1) { +        perror("error: sendtoallclients() socksend()\n"); +        debugprint(DEBUG_CRIT, "error: sendtoallclients() socksend() error sending to client with fd '%d', errno '%d'.\n", clients[i].fd, errno); +      } +    } +  } + +  return 1; +} + +// Send whatever string to the real IRC server +// Client FD and arrays needed to make sure anything relayed from a client is from an authenticated client. +// clientfd of "0" means trusted, used when we are sending things ourselves that weren't relayed +// from a real client. +int sendtoserver(SSL *server_ssl, char *strsrc, int str_len, int clientfd, struct client *clients, struct settings *settings) { +  // Copy to new string for passing to appendcrlf() to avoid overrun in appendcrlf() +  char str[MAXDATASIZE]; +  strcpy(str, strsrc); + +  appendcrlf(str); // Do this just before sending so callers don't need to worry about it +  str_len = strlen(str); // Recalculate str_len in case it changed (TODO: so do we even need to pass it to this function?) + +  // Find the sending client in the clients array and make sure they are authenticated +  for (int i = 0; i < MAXCLIENTS; i++) { +    // Trust clientfd of 0, only we can set that ourselves +    if (!clientfd) { +      debugprint(DEBUG_FULL, "sendtoserver(): trusting clientfd of 0.\n"); +      break; +    } +    if (clients[i].fd == clientfd) { +      // Found client in array, check authentication status +      if (!clients[i].authed) { +        debugprint(DEBUG_SOME, "sendtoserver(): skipping unauthenticated client with fd %d.\n", clients[i].fd); +        return 0; +      } +    } +  } + +  debugprint(DEBUG_SOME, "sendtoserver(): sending '%s' to IRC server (length %d).\n", str, str_len); +  if (socksend(server_ssl, str, str_len, settings->servertls) == -1) { +    printf("error: sendtoserver() socksend()\n"); +    debugprint(DEBUG_CRIT, "error: sendtoserver() socksend() error sending to server, errno '%d'.\n", clientfd, errno); +    return 0; +  } + +  return 1; +} + +// Disconnect the client fd "fd" by close()ing it and remove +// 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 ircdstrings *ircdstrings, struct settings *settings) { +  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) +  char alertmsg[MAXDATASIZE]; +  if (!snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :blabouncer: client with fd %d has disconnected.", ircdstrings->ircnick, fd)) { +    fprintf(stderr, "Error while preparing authentication failure NOTICE!\n"); +    debugprint(DEBUG_CRIT, "Error while preparing authentication failure NOTICE!\n"); +    alertmsg[0] = '\0'; +  } +  // Remove the client from the clients array +  for (int i = 0; i < MAXCLIENTS; i++) { +    if (clients[i].fd == fd) { +      debugprint(DEBUG_FULL, "found and clearing fd %d from clients[%d]\n", fd, i); +      clients[i].fd = 0; +      clients[i].authed = 0; +      clients[i].registered = 0; +      clients[i].pendingchannelmode = 0; +      clients[i].pendingban = 0; +      clients[i].pendingwho = 0; +      clients[i].pendinglist = 0; +      clients[i].pendingwhois = 0; +      clients[i].pendingwhowas = 0; +      clients[i].pendingnames = 0; +      clients[i].pendingcap = 0; +      if (settings->clienttls) { +        // Finish up with OpenSSL if using client TLS +        SSL_free(clients[i].ssl); +      } +      // Close the socket +      close(fd); +      // Now clients array is cleared, inform all other clients (source "0" since we trust this message) +      sendtoallclients(clients, alertmsg, 0, settings); +      return 1; +    } +  } + +  // If we got here, we didn't find and clear the client +  // TODO - Do something with a failed return code +  return 0; +} + +int createchannel(struct channel *channels, char *name, char *topic, char *topicwho, char *topicwhen) { +  debugprint(DEBUG_FULL, "createchannel(): given '%s', '%s', '%s', and '%s'.\n", name, topic, topicwho, topicwhen); + +  // Make sure the channel doesn't already exist +  for (int i = 0; i < MAXCHANNELS; i++) { +    if (strncmp(channels[i].name, name, strlen(name)) == 0) { +      perror("error: createchannel(): channel name already exists.\n"); +      return 0; +      break; +    } +  } + +  // Find a free slot in the array (when the channel name is not set (0th character is '\0'))... +  for (int i = 0; i < MAXCHANNELS; i++) { +    if (!channels[i].name[0]) { +      // ...and set the name and topic +      strncpy(channels[i].name, name, strlen(name)); +      channels[i].name[strlen(name)] = '\0'; +      debugprint(DEBUG_FULL, "createchannel(): name given was '%s', length '%ld'.\n", name, strlen(name)); +      debugprint(DEBUG_FULL, "createchannel(): name set to '%s', length '%ld'.\n", channels[i].name, strlen(channels[i].name)); +      strncpy(channels[i].topic, topic, strlen(topic)); +      channels[i].topic[strlen(topic)] = '\0'; +      debugprint(DEBUG_FULL, "createchannel(): topic given was '%s', length '%ld'.\n", topic, strlen(topic)); +      debugprint(DEBUG_FULL, "createchannel(): topic set to '%s', length '%ld'.\n", channels[i].topic, strlen(channels[i].topic)); +      strncpy(channels[i].topicwho, topicwho, strlen(topicwho)); +      channels[i].topicwho[strlen(topicwho)] = '\0'; +      strncpy(channels[i].topicwhen, topicwhen, strlen(topicwhen)); +      channels[i].topicwhen[strlen(topicwhen)] = '\0'; +      channels[i].gotnames = 0; +      return 1; +      break; // TODO - This should be safe to remove since return is hit first +    } +  } + +  debugprint(DEBUG_CRIT, "error: createchannel() didn't create a channel\n"); + +  // TODO - Make a failed return do something to callers +  return 0; +} + +int setchanneltopicwhotime(struct channel *channels, char *channelname, char *who, char *when) { +  debugprint(DEBUG_FULL, "setchanneltopicwhotime(): given '%s', '%s', and '%s'.\n", channelname, who, when); + +  debugprint(DEBUG_FULL, "setchanneltopicwhotime(): who: '%s' with length '%ld'.\n", who, strlen(who)); +  debugprint(DEBUG_FULL, "setchanneltopicwhotime(): when: '%s' with length '%ld'.\n", when, strlen(when)); + +  for (int i = 0; i < MAXCHANNELS; i++) { +    if (strncmp(channels[i].name, channelname, strlen(channelname)) == 0) { +      strncpy(channels[i].topicwho, who, strlen(who)); +      channels[i].topicwho[strlen(who)] = '\0'; +      strncpy(channels[i].topicwhen, when, strlen(when)); +      channels[i].topicwhen[strlen(when)] = '\0'; +      return 1; +    } +  } + +  // TODO - Make a failed return do something to callers +  return 0; +} + +int setchanneltopic(struct channel *channels, char *channelname, char *topic) { +  debugprint(DEBUG_FULL, "setchanneltopic(): given '%s' and '%s'.\n", channelname, topic); + +  for (int i = 0; i < MAXCHANNELS; i++) { +    if (strncmp(channels[i].name, channelname, strlen(channelname)) == 0) { +      strncpy(channels[i].topic, topic, strlen(topic)); +      channels[i].topic[strlen(topic)] = '\0'; +      return 1; +    } +  } + +  // TODO - Make a failed return do something to callers +  return 0; +} + +int getchannelcount(struct channel *channels) { +  int count = 0; + +  for (int i = 0; i < MAXCHANNELS; i++) { +    if (channels[i].name[0]) { +      count++; +    } +  } + +  debugprint(DEBUG_FULL, "getchannelcount(): counted %d channels.\n", count); + +  return count; +} + +int removechannel(struct channel *channels, char *name) { +  debugprint(DEBUG_FULL, "removechannel(): given '%s'.\n", name); + +  // Clear its topic setter and timestamp... +  setchanneltopicwhotime(channels, name, "", "0"); + +  // Find the channel in the channel array... +  for (int i = 0; i < MAXCHANNELS; i++) { +    if (strncmp(channels[i].name, name, strlen(name)) == 0) { +      // ..and NULL its name (0th character = '\0') +      channels[i].name[0] = '\0'; +      debugprint(DEBUG_FULL, "removechannel(): channel '%s' removed and topicwhen set to '%s'.\n", name, channels[i].topicwhen); +      return 1; +    } +  } + +  debugprint(DEBUG_CRIT, "error: removechannel() didn't remove a channel\n"); + +  // TODO - Make a failed return do something to callers +  return 0; +} + +// Check if we have the NAMES for the channel 'name' already. +// Return the 1 if we do, 0 if we don't, or -1 if there's an error. +int channelgotnames(struct channel *channels, char *name) { +  debugprint(DEBUG_FULL, "channelgotnames(): given '%s'.\n", name); +  for (int i = 0; i < MAXCHANNELS; i++) { +    if (strncmp(channels[i].name, name, strlen(name)) == 0) { +      if (channels[i].gotnames) { +        debugprint(DEBUG_FULL, "channelgotnames(): channel '%s' gotnames was set, returning '%d'.\n", channels[i].name, channels[i].gotnames); +        return 1; +      } else { +        debugprint(DEBUG_FULL, "channelgotnames(): channel '%s' gotnames was not set, returning '%d'.\n", channels[i].name, channels[i].gotnames); +        return 0; +      } +    } +  } + +  // We didn't find the channel, this isn't good!  TODO - Do something if this happens. +  debugprint(DEBUG_CRIT, "channelgotnames(): channel '%s' not found, this is bad, returning -1.\n", name); +  return -1; +} + +// Check if we are in a channel named "name" or not. +// Return 1 if we are, or 0 if not. +int inchannel(struct channel *channels, char *name) { +  // Make sure the name doesn't have any trailing CR or LF +  // (But only if name is at least two characters long already) +  if (strlen(name) >= 2) { +    while (name[strlen(name) - 1] == '\r' || name[strlen(name) - 1] == '\n') { +      name[strlen(name) - 1] = '\0'; +    } +  } + +  for (int i = 0; i < MAXCHANNELS; i++) { +    if (strncmp(channels[i].name, name, strlen(name)) == 0) { +      debugprint(DEBUG_FULL, "inchannel(): in channel '%s'.\n", name); +      return 1; +    } +  } + +  // We're not in the channel +  debugprint(DEBUG_FULL, "inchannel(): NOT in channel '%s'.\n", name); +  return 0; +} + +// Returns the array index in the 'channels' array of the channel +// named 'channel'. +// Returns -1 if there was an error. +int channelindex(struct channel *channels, char *name) { +  debugprint(DEBUG_FULL, "channelindex(): given '%s'.\n", name); +  for (int i = 0; i < MAXCHANNELS; i++) { +    if (strncmp(channels[i].name, name, strlen(name)) == 0) { +      return i; +    } +  } + +  // We didn't find the channel, this isn't good!  TODO - Do something if this happens. +  debugprint(DEBUG_CRIT, "channelindex(): channel '%s' not found, this is bad, returning -1.\n", name); +  return -1; +} + +// Send the requested number of lines of replay log to the requested client +// 'sourcefd' is the client to send to, and replayseconds is the number of +// seconds of replay to replay. +// Returns 1 for success or 0 for failure. +int doreplay(int sourcefd, int replayseconds, struct client *clients, struct settings *settings, struct ircdstrings *ircdstrings, struct channel *channels) { +  char outgoingmsg[MAXDATASIZE]; + +  // Figure out how many lines to replay +  int numlines = replaylines(replayseconds, settings->basedir); +  debugprint(DEBUG_FULL, "Replay log lines: '%d'.\n", numlines); + +  if (numlines < 0) { +    debugprint(DEBUG_CRIT, "Error getting number of replay lines.\n"); +    return 0; +  } else if (numlines == 0) { +    snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :0 replay log lines found in the time requested, nothing to send.", ircdstrings->ircnick); +    sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); +    return 1; +  } + +  // Announce the start +  snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Starting log replay....", ircdstrings->ircnick); +  sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + +  // Replay those lines! +  for (int i = 0; i < numlines; i++) { +    if (!readreplayline(replayseconds, i, outgoingmsg, settings->basedir)) { +      debugprint(DEBUG_CRIT, "Error requesting replay line.\n"); +      return 0; +    } + +    // Check if the replay line is a TOPIC, a JOIN, or a PART so we don't +    // replay those if we are not currently in the channel they are from +    // otherwise clients and state go a bit mad. +    // Never replay them if they are from us. + +    // Copy to a temporary string +    char *strcopy = strdup(outgoingmsg); +    // Keep track of initial pointer for free()ing later +    char *strcopyPtr = strcopy; + +    // Build array of each space-separated token +    char tokens[3][MAXDATASIZE]; +    char *token; + +    for (int j = 0; j < 3; j++) { +      // Try to split +      if ((token = strsep(&strcopy, " ")) == NULL) { +        debugprint(DEBUG_CRIT, "doreplay(): error splitting string on iteration '%d', returning!\n", j); +        return 0; +      } +      // Copy into the token array (strlen + 1 to get the NULL terminator) +      strncpy(tokens[j], token, strlen(token) + 1); +    } + +    if (strncmp(tokens[1], "TOPIC", strlen("TOPIC")) == 0 || +        strncmp(tokens[1], "JOIN", strlen("JOIN")) == 0 || +        strncmp(tokens[1], "PART", strlen("PART")) == 0) { +      // Skip over colon if present in channel name +      int offset = 0; +      if (tokens[2][0] == ':') { +        offset = 1; +      } + +      // To make sure it's not us +      extractnickfromprefix(tokens[0]); + +      // Check if we're currently in this channel or if the log line is from us +      if (!inchannel(channels, tokens[2] + offset) || strncmp(tokens[0], ircdstrings->ircnick, strlen(tokens[0])) == 0) { +        debugprint(DEBUG_FULL, "Not sending '%s' replay line '%s'.\n", tokens[1], outgoingmsg); +        free(strcopyPtr); +        continue; +      } +    } + +    free(strcopyPtr); + +    debugprint(DEBUG_FULL, "Sending replay line: '%s'.\n", outgoingmsg); +    sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); +  } + +  // Announce the end +  snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Log replay complete.", ircdstrings->ircnick); +  sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + +  return 1; +} + +// Return a count of the number of connected clients +int numclients(struct client *clients) { +  int count = 0; + +  for (int i = 0; i < MAXCLIENTS; i++) { +    if (clients[i].fd) { +      count++; +    } +  } + +  debugprint(DEBUG_FULL, "numclients(): '%d' clients connected.\n", count); +  return count; +} + +// Join any channels that were configured to be automatically +// joined in the configuration file. +// Returns 1 on success or 0 on failure. +int joinautochannels(SSL *server_ssl, struct client *clients, struct settings *settings) { +  if (strlen(settings->autochannels) == 0) { +    // None configured +    debugprint(DEBUG_FULL, "joinautochannels(): none configured.\n"); +    return 1; +  } + +  // Split string up into each channel +  char tokens[MAXAUTOCHANLEN][MAXCHANLENGTH]; +  int counter = 0; + +  // Copy to a temporary string +  char *strcopy = strdup(settings->autochannels); +  // Keep track of initial pointer for free()ing later +  char *strcopyPtr = strcopy; + +  char *token; + +  // Split on commas +  while ((token = strsep(&strcopy, ",")) != NULL) { +    if (*token  == '\0') continue; // Skip consecutive matches +    if (counter >= MAXAUTOCHANLEN) break; // Too many tokens +    debugprint(DEBUG_FULL, "  >> Auto channel: '%s', length '%ld'.\n", token, strlen(token)); +    // Copy into the token array (strlen + 1 to get the NULL terminator) +    strncpy(tokens[counter], token, strlen(token) + 1); +    if (strlen(tokens[counter]) > MAXCHANLENGTH) { +      printf("error: channel name '%s' from configuration file too long, max length is '%d'.\n", tokens[counter], MAXCHANLENGTH); +      debugprint(DEBUG_CRIT, "error: channel name '%s' from configuration file too long, max length is '%d'.\n", tokens[counter], MAXCHANLENGTH); +      exit(1); +    } +    counter++; +  } + +  // Join all the channels +  for (int i = 0; i < counter; i++) { +    debugprint(DEBUG_FULL, "joinautochannels(): Joining '%s'.\n", tokens[i]); +    char joinmsg[MAXDATASIZE]; +    snprintf(joinmsg, MAXDATASIZE, "JOIN %s", tokens[i]); +    sendtoserver(server_ssl, joinmsg, strlen(joinmsg), 0, clients, settings); +  } + +  free(strcopyPtr); +  // TODO - Can we fail here?  Return 0 if so and make callers handle this if so. +  return 1; +} + +// Try to make a new nick if no configured are available or liked by the server +// Do this by sticking a number on the end of the current nick and trying numbers +// 1 through to 9. +void tryautonick(struct ircdstrings *ircdstrings) { +  // Increment the attempts counter +  ircdstrings->autonicknum++; + +  if (ircdstrings->autonicknum == 10) { +    // We've already tried 9 nicks and failed, give up +    printf("tryautonick(): Tried 9 automatic nicks and the server didn't like any, giving up.\n"); +    debugprint(DEBUG_CRIT, "tryautonick(): Tried 9 automatic nicks and the server didn't like any, giving up.\n"); +    exit(1); +  } + +  int oldlen = strlen(ircdstrings->ircnick); + +  // If we've already started trying autonick, just replace the last character a the new number +  if (ircdstrings->autonicknum > 1) { +    debugprint(DEBUG_FULL, "tryautonick(): already started autonick, starting with '%s' length '%ld'.\n", ircdstrings->ircnick, strlen(ircdstrings->ircnick)); +    ircdstrings->ircnick[oldlen - 1] = ircdstrings->autonicknum + '0'; +    // And null terminate +    ircdstrings->ircnick[oldlen] = '\0'; +  // If the nick is longer than or equal to the RFC 1459 max nick +  // length then try sticking the number at the end +  } else if (oldlen >= MAXRFCNICKLEN) { +    debugprint(DEBUG_FULL, "tryautonick(): long old nick, starting with '%s' length '%ld'.\n", ircdstrings->ircnick, strlen(ircdstrings->ircnick)); +    // (+ '0' to make char from int) +    ircdstrings->ircnick[MAXRFCNICKLEN] = ircdstrings->autonicknum + '0'; +    // And null terminate +    ircdstrings->ircnick[MAXRFCNICKLEN + 1] = '\0'; +  // Otherwise, just stick it on the end (+ '0' to make char from int) +  } else { +    debugprint(DEBUG_FULL, "tryautonick(): short old nick, starting with '%s' length '%ld'.\n", ircdstrings->ircnick, strlen(ircdstrings->ircnick)); +    ircdstrings->ircnick[oldlen] = ircdstrings->autonicknum + '0'; +    // And null terminate +    ircdstrings->ircnick[oldlen + 1] = '\0'; +  } + +  debugprint(DEBUG_FULL, "tryautonick(): set irdstrings->ircnick to '%s'.\n", ircdstrings->ircnick); +}  | 
