/* * This file is part of blabouncer (https://www.blatech.co.uk/l_bratch/blabouncer). * Copyright (C) 2019 Luke Bratch . * * 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 . */ #include "functions.h" // Global debug control extern int debug; extern char debugpath[PATH_MAX]; extern int background; // Internal function just to replace nick in server greeting strings // (as in ":servername 00x oldnick :Blablabla" -> ":servername 00x newnick :Blablabla") void updategreetingnick(char *greeting, char *greetingnum, char *newnick, char *oldnick) { debugprint(DEBUG_FULL, "updategreetingnick(): '%s' '%s' '%s' '%s'.\n", greeting, greetingnum, newnick, oldnick); // Find the position of the old nick in the current greeting char searchstr[MAXDATASIZE]; snprintf(searchstr, MAXDATASIZE, " %s %s :", greetingnum, oldnick); char *ret; ret = strstr(greeting, searchstr); // If ret not found, try again without the colon (e.g. for greeting 004) if (ret == NULL) { snprintf(searchstr, MAXDATASIZE, " %s %s ", greetingnum, oldnick); ret = strstr(greeting, searchstr); } // Perhaps the new nick is already present (seen for instance when connecting to another bouncer like Miau) if (ret == NULL) { snprintf(searchstr, MAXDATASIZE, " %s %s ", greetingnum, newnick); ret = strstr(greeting, searchstr); if (ret != NULL) { debugprint(DEBUG_FULL, "updategreetingnick(): newnick is already present, returning.\n"); return; } } // If ret *still* not found, abandon ship if (ret == NULL) { debugprint(DEBUG_CRIT, "Error updating greeting string, substring not found. Exiting!\n"); printf("Error updating greeting string, substring not found. Exiting!\n"); exit(1); } int pos = ret - greeting + 5; // +5 for " 001 " // Copy the start of the old greeting into a new string char greetingtmp[MAXDATASIZE]; strncpy(greetingtmp, greeting, pos); // Terminate it greetingtmp[pos] = '\0'; // Now smash everything (start of old greeting + new nick + remainder of old greeting) // together into the new greeting, put in a new temporary string char greetingtmp2[MAXDATASIZE]; if (!snprintf(greetingtmp2, MAXDATASIZE, "%s%s %s", greetingtmp, newnick, greeting + pos + strlen(oldnick) + 1)) { fprintf(stderr, "Error while preparing new greeting string!\n"); debugprint(DEBUG_CRIT, "Error while preparing new greeting string!\n"); exit(1); } // And finally copy back to source string strcpy(greeting, greetingtmp2); debugprint(DEBUG_FULL, "updategreetingnick(): Built new greeting '%s' '%s', length '%ld'.\n", greetingnum, greeting, strlen(greeting)); } // Write debug string to file. // Debug level is provided by level, set to one of DEBUG_CRIT, DEBUG_SOME or DEBUG_FULL. // Debug is only written if the global int "debug" is greater than or equal to the level. void debugprint(int level, char *format, ...) { // Stop here if the user's debug level is less than the level of the current message if (debug < level) return; if (strlen(debugpath) < 1) { // debugpath isn't set, we can't do anything here return; } va_list args; va_start(args, format); FILE *fp; int bytes = 0; fp = fopen(debugpath, "a"); if (fp == NULL) { printf("Couldn't open debugpath '%s'!\n", debugpath); return; } // Prepend a timestamp to each line time_t rawtime; struct tm * timeinfo; time(&rawtime); timeinfo = localtime(&rawtime); // Strip the trailing newline char timestr[MAXCHAR]; snprintf(timestr, MAXCHAR, "%s", asctime(timeinfo)); timestr[strlen(timestr) - 1] = '\0'; if ((bytes = fprintf(fp, "%s: ", timestr)) < 0) { debugprint(DEBUG_CRIT, "error: could not write timestamp to debug file.\n"); // TODO - This might not be useful if we can't write } if ((bytes = vfprintf(fp, format, args)) < 0) { debugprint(DEBUG_CRIT, "error: could not write to debug file.\n"); // TODO - This might not be useful if we can't write } fclose(fp); va_end(args); } // Get stdin line with buffer overrun protection int getstdin(char *prompt, char *buff, size_t sz) { int ch, extra; // Print optional prompt if (prompt != NULL) { printf ("%s", prompt); fflush (stdout); } // Get the intput from stdin if (fgets (buff, sz, stdin) == NULL) { return NO_INPUT; } // If it was too long, there'll be no newline. In that case, we flush // to end of line so that excess doesn't affect the next call. if (buff[strlen(buff) - 1] != '\n') { // strlen of the actually entered line, not the original array size extra = 0; while (((ch = getchar()) != '\n') && (ch != EOF)) { extra = 1; } return (extra == 1) ? TOO_LONG : OK; } // Otherwise remove newline and give string back to caller. buff[strlen(buff) - 1] = '\0'; return OK; } // Append CR-LF to the end of a string (after cleaning up any existing trailing CR or LF) void appendcrlf(char *string) { // Make sure it doesn't already end with CR or LF // (But only if string is at least two characters long already) if (strlen(string) >= 2) { while (string[strlen(string) - 1] == '\r' || string[strlen(string) - 1] == '\n') { string[strlen(string) - 1] = '\0'; } } int startlen = strlen(string); string[startlen] = '\r'; // Add CR string[startlen + 1] = '\n'; // Add LF string[startlen + 2] = '\0'; // Finish with null terminator } // Remove leading colon ':' which is the starting character of a prefix in an IRC message void stripprefix(char *string) { // Make a copy to work with char string2[strlen(string)]; debugprint(DEBUG_FULL, "stripprefix(): starting with '%s', strlen: %zd.\n", string, strlen(string)); // Don't bother if this isn't a prefix with a leading ':' if (string[0] != ':') { debugprint(DEBUG_FULL, "stripprefix(): no leading ':', returning.\n"); return; } // Copy the old string into a new one, but... for (size_t i = 1; i < strlen(string); i++) { string2[i - 1] = string[i]; } // Copy result back to original string strncpy(string, string2, strlen(string) - 1); // Finish with null terminator string[strlen(string) - 1] = '\0'; debugprint(DEBUG_FULL, "stripprefix(): finishing with '%s', strlen: %zd.\n", string, strlen(string)); } // Extract final parameter from IRC message, removing the leading colon ':' // e.g. given this string: // ":irc.tghost.co.uk 332 blabounce #test :This is a test topic!" // We want to end up with: // "This is a test topic!" void extractfinalparameter(char *string) { // Make a copy to work with char string2[strlen(string)]; // Position of colon int colonpos = -1; debugprint(DEBUG_FULL, "extractfinalparameter(): starting with '%s', strlen: %zd.\n", string, strlen(string)); // Strip the colon at position 0 if there is one stripprefix(string); // Find the colon for (size_t i = 0; i < strlen(string); i++) { if (string[i] == ':') { debugprint(DEBUG_FULL, "Found colon at position %zd!\n", i); colonpos = i; break; } } if (colonpos == -1) { debugprint(DEBUG_FULL, "no colon found, returning\n"); return; } // Build a new string starting from the next position after the colon int counter = 0; for (size_t i = colonpos + 1; i < strlen(string); i++) { string2[counter] = string[i]; counter++; } // Copy result back to original string strncpy(string, string2, counter); // Finish with null terminator string[counter] = '\0'; debugprint(DEBUG_FULL, "extractfinalparameter(): finishing with '%s', strlen: %zd.\n", string, strlen(string)); } // Extract the IRC nick from a prefix // e.g. given this string: // ":foo!bar@baz" // We want to end up with: // "foo" void extractnickfromprefix(char *string) { // Position of bang int bangpos = -1; debugprint(DEBUG_FULL, "extractnickfromprefix(): starting with '%s', strlen: %zd.\n", string, strlen(string)); // Strip the colon at position 0 if there is one stripprefix(string); // Find the bang for (size_t i = 0; i < strlen(string); i++) { if (string[i] == '!') { debugprint(DEBUG_FULL, "Found bang at position %zd!\n", i); bangpos = i; break; } } if (bangpos == -1) { debugprint(DEBUG_FULL, "no bang found, returning\n"); return; } // Terminate the string at whatever position we found the bang string[bangpos] = '\0'; debugprint(DEBUG_FULL, "extractnickfromprefix(): finishing with '%s', strlen: %zd.\n", string, strlen(string)); } // Update an existing nickuserhost string with a new nick void updatenickuserhost(char *nickuserhost, char *nick) { debugprint(DEBUG_FULL, "updatenickuserhost(): updating '%s' with '%s'.\n", nickuserhost, nick); // Position of bang int bangpos = -1; // Find the bang for (size_t i = 0; i < strlen(nickuserhost); i++) { if (nickuserhost[i] == '!') { debugprint(DEBUG_FULL, "Found bang at position %ld!\n", i); bangpos = i; break; } } if (bangpos == -1) { debugprint(DEBUG_FULL, "No bang found in existing nickuserhost, quitting!\n"); return; } // Make a new string combining the nick nick and the old nickuserhost + the offset of the bang char newstr[MAXDATASIZE]; snprintf(newstr, MAXDATASIZE, "%s%s", nick, nickuserhost + bangpos); newstr[strlen(nickuserhost) - bangpos + strlen(nick)] = '\0'; // Copy back to source string strcpy(nickuserhost, newstr); debugprint(DEBUG_FULL, "updatenickuserhost(): new nickuserhost '%s', length '%ld'.\n", nickuserhost, strlen(nickuserhost)); } // Update existing greeting strings with a new nickuserhost and new nick void updategreetings(char *greeting001, char *greeting002, char *greeting003, char *greeting004, char *greeting005a, char *greeting005b, char *greeting005c, char *newnickuserhost, char *oldnickuserhost, char *newnick, char *oldnick) { debugprint(DEBUG_FULL, "updategreetings(): updating greetings with new nickuserhost '%s' and nick '%s'.\n", newnickuserhost, newnick); // nickuserhost and greeting001's final component first // (final component as in ":servername 001 nick :Blablabla final!com@ponent" // Make copies of the nickuserhosts to work with char newnickuserhostcpy[MAXDATASIZE]; strcpy(newnickuserhostcpy, newnickuserhost); stripprefix(newnickuserhostcpy); char oldnickuserhostcpy[MAXDATASIZE]; strcpy(oldnickuserhostcpy, oldnickuserhost); stripprefix(oldnickuserhostcpy); // Find the position of the old nickuserhost in the current greeting 001 char *ret; ret = strstr(greeting001, oldnickuserhostcpy); int pos = ret - greeting001; // Terminate greeting001 where the nickuserhost begins greeting001[pos] = '\0'; // Stick the new nickuserhost in place in a temporary greeting 001 string char greetingtmp[MAXDATASIZE]; snprintf(greetingtmp, MAXDATASIZE, "%s%s", greeting001, newnickuserhostcpy); // Terminate it greetingtmp[pos + strlen(newnickuserhostcpy)] = '\0'; // Copy back to source greeting 001 strcpy(greeting001, greetingtmp); debugprint(DEBUG_FULL, "updategreetings(): new greeting 001 '%s', length '%ld'.\n", greeting001, strlen(greeting001)); // Get the new nick char newnickcpy[MAXDATASIZE]; strcpy(newnickcpy, newnick); extractnickfromprefix(newnickcpy); // greeting nicks next // (as in ":servername 00x oldnick :Blablabla" -> ":servername 00x newnick :Blablabla") updategreetingnick(greeting001, "001", newnickcpy, oldnick); updategreetingnick(greeting002, "002", newnickcpy, oldnick); updategreetingnick(greeting003, "003", newnickcpy, oldnick); updategreetingnick(greeting004, "004", newnickcpy, oldnick); if (greeting005a[0]) { updategreetingnick(greeting005a, "005", newnickcpy, oldnick); } if (greeting005b[0]) { updategreetingnick(greeting005b, "005", newnickcpy, oldnick); } if (greeting005c[0]) { 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); } // Exit the program cleanly - tell clients, tell the server, then exit(0) // Optional quit message string "quitmsg" // "sourcefd" of 0 means the request didn't come from a client void cleanexit(SSL *server_ssl, struct client *clients, int sourcefd, struct ircdstrings *ircdstrings, struct settings *settings, char *quitmsg) { char outgoingmsg[MAXDATASIZE]; // Tell clients and debug log if (sourcefd) { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Exiting on request from client with fd '%d', message '%s'.", ircdstrings->ircnick, sourcefd, quitmsg); debugprint(DEBUG_CRIT, "Exiting on request from client with fd '%d', message '%s'.\n", sourcefd, quitmsg); } else { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Exiting on request (not from a client), message '%s'.", ircdstrings->ircnick, quitmsg); debugprint(DEBUG_CRIT, "Exiting on request (not from a client), message '%s'.\n", quitmsg); } sendtoallclients(clients, outgoingmsg, EXCEPT_NONE, settings); // Tell the server // Combine "QUIT :" with any (optional) quit message snprintf(outgoingmsg, MAXDATASIZE, "QUIT :%s", quitmsg); sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), sourcefd, clients, settings); exit(0); }