/* * 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 } // Make sure the finished log line ended with a trailing newline if (format[strlen(format) - 1] != '\n') { if ((bytes = fprintf(fp, "%c", '\n')) < 0) { debugprint(DEBUG_CRIT, "error: could not write trailing newline 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) { if (strlen(strsrc) >= MAXDATASIZE) { // String is too long! debugprint(DEBUG_CRIT, "sendtoclient(): strsrc '%s' was too long (%d out of a max of %d characters).\n", strsrc, strlen(strsrc), MAXDATASIZE - 1); return 0; } else if (strlen(strsrc) >= MAXDATASIZE - 2 && strsrc[strlen(strsrc) - 1] != '\r' && strsrc[strlen(strsrc)] != '\n') { // Ensure string can fit CRLF at the end if it doesn't already have it debugprint(DEBUG_CRIT, "sendtoclient(): non-CRLF message too long to append CRLF to.\n"); return 0; } // 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) { 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; if (strlen(strsrc) >= MAXDATASIZE) { // String is too long! debugprint(DEBUG_CRIT, "sendtoallclients(): strsrc '%s' was too long (%d out of a max of %d characters).\n", strsrc, strlen(strsrc), MAXDATASIZE - 1); return 0; } else if (strlen(strsrc) >= MAXDATASIZE - 2 && strsrc[strlen(strsrc) - 1] != '\r' && strsrc[strlen(strsrc)] != '\n') { // Ensure string can fit CRLF at the end if it doesn't already have it debugprint(DEBUG_CRIT, "sendtoallclients(): non-CRLF message too long to append CRLF to.\n"); return 0; } // 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) { 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. // Returns 1 on success or 0 on failure. - TODO - Make callers care about this. int sendtoserver(SSL *server_ssl, char *strsrc, int str_len, int clientfd, struct client *clients, struct settings *settings) { if (strlen(strsrc) >= MAXDATASIZE) { // String is too long! debugprint(DEBUG_CRIT, "sendtoserver(): strsrc '%s' was too long (%d out of a max of %d characters).\n", strsrc, strlen(strsrc), MAXDATASIZE - 1); return 0; } else if (strlen(strsrc) >= MAXDATASIZE - 2 && strsrc[strlen(strsrc) - 1] != '\r' && strsrc[strlen(strsrc)] != '\n') { // Ensure string can fit CRLF at the end if it doesn't already have it debugprint(DEBUG_CRIT, "sendtoserver(): non-CRLF message too long to append CRLF to.\n"); return 0; } // 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 1; } } } 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) { debugprint(DEBUG_CRIT, "error: sendtoserver() socksend() error sending to server, errno '%d'.\n", clientfd, errno); return 1; } return 0; } // 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 ircdstate *ircdstate, struct settings *settings, struct clientcodes *clientcodes) { debugprint(DEBUG_SOME, "disconnectclient(): disconnecting client fd '%d'\n", fd); // Alert other clients about the disconnection (don't send yet, we haven't removed from the clients array yet) char alertmsg[MAXDATASIZE]; if (!snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :blabouncer: client with fd %d has disconnected.", ircdstate->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); // If the client was registered, record the time of the last client disconnect if (clients[i].registered) { ircdstate->clientchangetime = time(NULL); // And set their client code, if any, to last disconnecting at this time. if (clients[i].clientcode[0]) { setclientcodetime(clients[i].clientcode, clientcodes); } } clients[i].fd = 0; clients[i].authed = 0; clients[i].registered = 0; clients[i].pendingsslaccept = 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; clients[i].clientcode[0] = '\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); // If there are now no clients connected, record the time if (numclients(clients) == 0) { ircdstate->clientsnonetime = time(NULL); } 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) { // Note that this may be happening because we just reconnected debugprint(DEBUG_CRIT, "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 log to replay. // Returns 1 for success or 0 for failure. int doreplay(int sourcefd, int replayseconds, struct client *clients, struct settings *settings, struct ircdstate *ircdstate, 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.", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; } // Announce the start snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Starting log replay....", ircdstate->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], ircdstate->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.", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; } // Send the auto replay to the requested client, where 'sourcefd' is the client // to send to. The type of replay will depend on the user's settings. // Returns 1 for success or 0 for failure. int doautoreplay(int sourcefd, struct client *clients, struct settings *settings, struct ircdstate *ircdstate, struct channel *channels) { // If replaymode = "none" then don't send anything if (!strncmp(settings->replaymode, "none", strlen("none"))) { debugprint(DEBUG_FULL, "doautoreplay() requested but replaymode = \"none\".\n"); return 1; } // If replaymode = "time" then send whatever replayseconds is set to if (!strncmp(settings->replaymode, "time", strlen("time"))) { debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"time\", calling doreplay() with '%d' seconds.\n", settings->replayseconds); if (!doreplay(sourcefd, settings->replayseconds, clients, settings, ircdstate, channels)) { debugprint(DEBUG_SOME, "doautoreplay(): doreplay() returned 0, returning 0 to caller...\n"); return 0; } return 1; } // If replaymode = "lastspoke" then send whatever happened since the user's current nick last spoke if (!strncmp(settings->replaymode, "lastspoke", strlen("lastspoke"))) { debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"lastspoke\".\n", settings->replayseconds); int secondsago = lastspokesecondsago(ircdstate->ircnick, settings->basedir); if (secondsago < 1) { debugprint(DEBUG_SOME, "doautoreplay(): lastspokesecondsago() returned < 1, returning 0 to caller...\n"); return 0; } debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"lastspoke\", sending lines from '%d' seconds ago to sourcefd '%d'.\n", secondsago, sourcefd); if (!doreplay(sourcefd, secondsago, clients, settings, ircdstate, channels)) { debugprint(DEBUG_SOME, "doautoreplay(): doreplay() returned 0, returning 0 to caller...\n"); return 0; } return 1; } // If replaymode = "noclients" and there is currently only one client connected, then send whatever happened since there were last no clients connected if (!strncmp(settings->replaymode, "noclients", strlen("noclients")) && numclients(clients) == 1) { debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"noclients\", sending '%d' seconds of replay.\n", time(NULL) - ircdstate->clientsnonetime); if (!doreplay(sourcefd, time(NULL) - ircdstate->clientsnonetime, clients, settings, ircdstate, channels)) { debugprint(DEBUG_SOME, "doautoreplay(): doreplay() returned 0, returning 0 to caller...\n"); return 0; } return 1; } // If replaymode = "lastchange" then send whatever happened since the last client registration or disconnection if (!strncmp(settings->replaymode, "lastchange", strlen("lastchange"))) { debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"lastchange\", sending '%d' seconds of replay.\n", time(NULL) - ircdstate->clientchangetime); if (!doreplay(sourcefd, time(NULL) - ircdstate->clientchangetime, clients, settings, ircdstate, channels)) { debugprint(DEBUG_SOME, "doautoreplay(): doreplay() returned 0, returning 0 to caller...\n"); return 0; } return 1; } // If replaymode = "lastchange" then don't handle this here, it is done when the client registers its client code if (!strncmp(settings->replaymode, "perclient", strlen("perclient"))) { return 1; } // We shouldn't get here return 0; } // 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 ircdstate *ircdstate) { // Increment the attempts counter ircdstate->autonicknum++; if (ircdstate->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(ircdstate->ircnick); // If we've already started trying autonick, just replace the last character a the new number if (ircdstate->autonicknum > 1) { debugprint(DEBUG_FULL, "tryautonick(): already started autonick, starting with '%s' length '%ld'.\n", ircdstate->ircnick, strlen(ircdstate->ircnick)); ircdstate->ircnick[oldlen - 1] = ircdstate->autonicknum + '0'; // And null terminate ircdstate->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", ircdstate->ircnick, strlen(ircdstate->ircnick)); // (+ '0' to make char from int) ircdstate->ircnick[MAXRFCNICKLEN] = ircdstate->autonicknum + '0'; // And null terminate ircdstate->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", ircdstate->ircnick, strlen(ircdstate->ircnick)); ircdstate->ircnick[oldlen] = ircdstate->autonicknum + '0'; // And null terminate ircdstate->ircnick[oldlen + 1] = '\0'; } debugprint(DEBUG_FULL, "tryautonick(): set irdstrings->ircnick to '%s'.\n", ircdstate->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 ircdstate *ircdstate, 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'.", ircdstate->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'.", ircdstate->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); } // Re-read the configuration file, setting 'failuremsg' to a failure message on failure. // Returns 1 on success or 0 on failure. int rehash(struct settings *settings, char *failuremsg) { // TODO - Try to share some/all of this code with the initial main() settings loading // What is the nick? char oldircnick[MAXNICKLENGTH]; strcpy(oldircnick, settings->ircnick); if (!getconfstr("nick", settings->conffile, settings->ircnick)) { strcpy(settings->ircnick, oldircnick); strcpy(failuremsg, "error getting 'nick' from configuration file"); return 0; } // What is nick2? char oldircnick2[MAXNICKLENGTH]; strcpy(oldircnick2, settings->ircnick2); if (!getconfstr("nick2", settings->conffile, settings->ircnick2)) { strcpy(settings->ircnick2, oldircnick2); // Not configured, set to blank string settings->ircnick2[0] = '\0'; } // What is nick3? char oldircnick3[MAXNICKLENGTH]; strcpy(oldircnick3, settings->ircnick3); if (!getconfstr("nick3", settings->conffile, settings->ircnick3)) { strcpy(settings->ircnick3, oldircnick3); // Not configured, set to blank string settings->ircnick3[0] = '\0'; } // What is the auto replay mode? char oldreplaymode[MAXDATASIZE]; strcpy(oldreplaymode, settings->replaymode); if (!getconfstr("replaymode", settings->conffile, settings->replaymode)) { strcpy(settings->replaymode, oldreplaymode); strcpy(failuremsg, "error getting 'replaymode' from configuration file"); return 0; } else { if (strcmp(settings->replaymode, "none") && strcmp(settings->replaymode, "time") && strcmp(settings->replaymode, "lastspoke") && strcmp(settings->replaymode, "noclients") && strcmp(settings->replaymode, "lastchange") && strcmp(settings->replaymode, "perclient")) { strcpy(settings->replaymode, oldreplaymode); strcpy(failuremsg, "replaymode in configuration file must be one of \"none\", \"time\", \"lastspoke\", \"noclients\", \"lastchange\", or \"perclient\""); return 0; } } // How many seconds of replay log should automatically be replayed - TODO - Can we do error checking on this? int oldreplayseconds = settings->replayseconds; settings->replayseconds = getconfint("replayseconds", settings->conffile); // Don't worry if replaymode != "time" if (errno == ECONFINT && strcmp(settings->replaymode, "time") == 0) { settings->replayseconds = oldreplayseconds; strcpy(failuremsg, "error getting 'replayseconds' from configuration file"); return 0; } // What is the password? char oldpassword[MAXCHAR]; strcpy(oldpassword, settings->password); if (!getconfstr("password", settings->conffile, settings->password)) { strcpy(settings->password, oldpassword); strcpy(failuremsg, "error getting 'password' from configuration file"); return 0; } // Is logging enabled? int oldlogging = settings->logging; settings->logging = getconfint("logging", settings->conffile); if (errno == ECONFINT) { settings->logging = oldlogging; strcpy(failuremsg, "error getting 'logging' from configuration file"); return 0; } // Is replay logging enabled? int oldreplaylogging = settings->replaylogging; settings->replaylogging = getconfint("replaylogging", settings->conffile); if (errno == ECONFINT) { settings->replaylogging = oldreplaylogging; strcpy(failuremsg, "error getting 'replaylogging' from configuration file"); return 0; } // Is debugging enabled? int olddebug = debug; debug = getconfint("debug", settings->conffile); if (errno == ECONFINT) { debug = olddebug; strcpy(failuremsg, "error getting 'debug' from configuration file"); return 0; } // All is good, no failure message, return 1. failuremsg[0] = '\0'; return 1; } // Check the password provided in the string 'str' against what is in // the settings structure 'settings'. // Return 0 for password mismatch, or 1 for password match. int checkpassword(char *password, struct settings *settings) { // Ensure passwords are the same length if (strlen(password) != strlen(settings->password)) { debugprint(DEBUG_SOME, "Password length mismatch!\n"); return 0; } // Ensure passwords match if (strncmp(password, settings->password, strlen(password)) == 0) { debugprint(DEBUG_FULL, "settings->password matches password.\n"); return 1; } else { debugprint(DEBUG_SOME, "settings->password does NOT match password!\n"); return 0; } printf("checkpassword(): unexpectedly got to end of function, quitting.\n"); debugprint(DEBUG_CRIT, "checkpassword(): unexpectedly got to end of function, quitting.\n"); // No messing around with password stuff exit(EXIT_FAILURE); } // Adds a client code to the clientcode structure if it doesn't already exist. // On success, copy it to the client's clientcode field. // Returns 1 on adding a new code, 0 if the code already existed, or -1 on error. int addclientcode(int sourcefd, char *code, struct clientcodes *clientcodes, struct client *clients) { debugprint(DEBUG_SOME, "addclientcode(): Adding client code '%s' if it doesn't already exist.\n", code); // Make sure there aren't too many client codes already int counter = 0; for (int i = 0; i < MAXCLIENTCODES; i++) { if (clientcodes[i].code[0]) { counter++; } } if (counter >= MAXCLIENTCODES) { debugprint(DEBUG_CRIT, "addclientcode(): too many client codes.\n"); return -1; } // Copy it to the current client for (int j = 0; j < MAXCLIENTS; j++) { if (clients[j].fd == sourcefd) { strcpy(clients[j].clientcode, code); debugprint(DEBUG_FULL, "addclientcode(): set client code for fd '%d' to '%s'.\n", clients[j].fd, clients[j].clientcode); } } // And add it to the clientcode structure if it doesn't already exist for (int i = 0; i < MAXCLIENTCODES; i++) { if (strlen(code) == strlen(clientcodes[i].code) && (strncmp(code, clientcodes[i].code, strlen(code)) == 0)) { debugprint(DEBUG_FULL, "addclientcode(): client code already existed.\n"); // It already exists return 0; } else { // It doesn't, add it strcpy(clientcodes[i].code, code); return 1; } } // We shouldn't get here debugprint(DEBUG_CRIT, "addclientcode(): we shouldn't get here"); return -1; } // Sets a given client code as last disconnecting at the current time. // Returns 1 on success or 0 on failure. - TODO have callers do something on failure, or change this to a void. int setclientcodetime(char *code, struct clientcodes *clientcodes) { debugprint(DEBUG_FULL, "setclientcodetime(): Setting disconnect time for '%s'.\n", code); // Look for this code and set it as the current time if found for (int i = 0; i < MAXCLIENTCODES; i++) { if (strlen(code) == strlen(clientcodes[i].code) && (strncmp(code, clientcodes[i].code, strlen(code)) == 0)) { clientcodes[i].lastdisconnected = time(NULL); debugprint(DEBUG_FULL, "setclientcodetime(): Set time to '%d'.\n", clientcodes[i].lastdisconnected); return 1; } } // If we got here, the code was never found return 0; } // Return the timestamp that a given client last disconnected, or 0 on failure. int getclientcodetime(char *code, struct clientcodes *clientcodes) { debugprint(DEBUG_FULL, "getclientcodetime(): looking for '%s'.\n", code); // Look for this code and set it as the current time if found for (int i = 0; i < MAXCLIENTCODES; i++) { //printf("%s.\n", clientcodes[i].code); if (strlen(code) == strlen(clientcodes[i].code) && (strncmp(code, clientcodes[i].code, strlen(code)) == 0)) { debugprint(DEBUG_FULL, "getclientcodetime(): code '%s' found at index '%d', timestamp '%d'.\n", code, i, clientcodes[i].lastdisconnected); return clientcodes[i].lastdisconnected; } } // If we got here, the code was never found return 0; } // Replace any instances of "find" with "replace" in the string "str" void replacechar(char *str, char find, char replace) { for (size_t i = 0; i < strlen(str); i++) { if (str[i] == find) { str[i] = replace; } } }