/* * 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. // Returns OK, NO_INPUT, or TOO_LONG as appropriate. int getstdin(char *prompt, char *buff, size_t sz) { if (prompt != NULL) { debugprint(DEBUG_FULL, "getstdin(): '%s' (len %d), '%s' (len %d), size %zu.\n", prompt, strlen(prompt), buff, strlen(buff), sz); } else { debugprint(DEBUG_FULL, "getstdin(): '' (len ), '%s' (len %d), size %zu.\n", buff, strlen(buff), 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) { if (feof(stdin)) { debugprint(DEBUG_FULL, "getstdin(): Clearing EOF indicator on stdin.\n"); clearerr(stdin); } 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 // If no leading colon present, string is left unchanged. // "debug" is used to determine whether stripprefix() should produce all debug output, // used as debug output with stripprefix() can be particularly noisy. void stripprefix(char *string, int debug) { // Make a copy to work with char string2[strlen(string)]; if (debug) { 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'; if (debug) { 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 either of these strings: // ":irc.tghost.co.uk 332 blabounce #test :foo:bar topic!" // ":nick!user@fe80:1:2:3:5:6:7:8 TOPIC #test :foo:bar topic!" // We want to end up with: // "foo:bar topic!" void extractfinalparameter(char *string) { // The method used is to look for the first space (" ") followed by a colon (":") // and take everything after that colon as the final parameter. // Make a copy to work with char string2[strlen(string)]; // Position of final parameter's leading 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, 1); // Look for spaces... for (size_t i = 0; i < strlen(string); i++) { if (string[i] == ' ') { debugprint(DEBUG_FULL, "extractfinalparameter(): found space at position %zd! Checking for colon...\n", i); // ...and check if the next character is a colon if (string[i + 1] == ':') { colonpos = i + 1; break; } } } if (colonpos == -1) { debugprint(DEBUG_SOME, "extractfinalparameter(): no space followed by a 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" // "debug" is used to determine whether extractnickfromprefix() should produce all debug output, // used as debug output with extractnickfromprefix() can be particularly noisy. void extractnickfromprefix(char *string, int debug) { // Position of bang int bangpos = -1; if (debug) { debugprint(DEBUG_FULL, "extractnickfromprefix(): starting with '%s', strlen: %zd.\n", string, strlen(string)); } // Strip the colon at position 0 if there is one, debugging enabled/disabled based on "debug" pass to us stripprefix(string, debug); // Find the bang for (size_t i = 0; i < strlen(string); i++) { if (string[i] == '!') { if (debug) { debugprint(DEBUG_FULL, "extractnickfromprefix(): found bang at position %zd!\n", i); } bangpos = i; break; } } if (bangpos == -1) { debugprint(DEBUG_FULL, "extractnickfromprefix(): no bang found, returning...\n"); return; } // Terminate the string at whatever position we found the bang string[bangpos] = '\0'; if (debug) { 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, "updatenickuserhost(): found bang at position %ld!\n", i); bangpos = i; break; } } // No bang found... if (bangpos == -1) { // ...assume the old nickuserhost was just the nick (e.g. if the server's greeting // 001 ended with nick rather than nick!user@host) then just update it directly debugprint(DEBUG_FULL, "updatenickuserhost(): no bang found in existing nickuserhost, assuming a nick only nickuserhost\n"); strcpy(nickuserhost, nick); debugprint(DEBUG_FULL, "updatenickuserhost(): new (nick only) nickuserhost '%s', length '%ld'.\n", nickuserhost, strlen(nickuserhost)); return; } // Make a new string combining the new 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); debugprint(DEBUG_FULL, "updategreetings(): existing greeting001: '%s'.\n", greeting001); debugprint(DEBUG_FULL, "updategreetings(): existing greeting002: '%s'.\n", greeting002); debugprint(DEBUG_FULL, "updategreetings(): existing greeting003: '%s'.\n", greeting003); debugprint(DEBUG_FULL, "updategreetings(): existing greeting004: '%s'.\n", greeting004); debugprint(DEBUG_FULL, "updategreetings(): existing greeting005a: '%s'.\n", greeting005a); debugprint(DEBUG_FULL, "updategreetings(): existing greeting005b: '%s'.\n", greeting005b); debugprint(DEBUG_FULL, "updategreetings(): existing greeting005c: '%s'.\n", greeting005c); // 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, 1); char oldnickuserhostcpy[MAXDATASIZE]; strcpy(oldnickuserhostcpy, oldnickuserhost); stripprefix(oldnickuserhostcpy, 1); // Find the position of the old nickuserhost in the current greeting 001 char *ret; ret = strstr(greeting001, oldnickuserhostcpy); int pos = ret - greeting001; // Ensure that was the last occurrence in the greeting debugprint(DEBUG_FULL, "updategreetings(): entering last occurrence check loop...\n"); while (ret) { ret = strstr(greeting001 + pos + strlen(oldnickuserhostcpy), oldnickuserhostcpy); if (!ret) { break; } 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, 1); // 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. // Returns 0 or more on success, or -1 on failure. // TODO - Use this wherever we are calculating the position (various places) instead of // duplicating code. int arrindex(struct client *clients, int clientfd) { // Make sure the client fd requested is >0 (less than 0 is nonsense, 0 is almost certainly standard input) if (clientfd < 1) { debugprint(DEBUG_CRIT, "arrindex(): error: requested client fd '%d' is less than 1, returning -1.\n", clientfd); return -1; } // Find the client in the clients array for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].fd == clientfd) { return i; } } // Something went wrong, we didn't find it debugprint(DEBUG_CRIT, "arrindex(): error: got to MAXCLIENTS (%d) but didn't find clientfd '%d' in clients struct.\n", MAXCLIENTS, clientfd); return -1; } // 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; } } if (i == MAXCLIENTS ) { debugprint(DEBUG_CRIT, "error: sendtoclient() client fd %d not found in clients array, returning 0.\n", fd); return 0; } 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) < 0) { 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) < 0) { 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 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) < 0) { 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 ircdstate *ircdstate, struct settings *settings, struct clientcodes *clientcodes) { // Index of client fd in clients array for use later int clientindex = arrindex(clients, fd); if (clientindex < 0) { debugprint(DEBUG_CRIT, "disconnectclient(): error: arrindex() returned '%d', exiting!\n", clientindex); exit(1); } debugprint(DEBUG_SOME, "disconnectclient(): disconnecting client %s with fd '%d'\n", clients[clientindex].remoteip, fd); // Record some properties of the disconnecting client before we remove it from the clients array int discauthed = clients[clientindex].authed; char *discremoteip = strdup(clients[clientindex].remoteip); // 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'; clients[i].remoteip[0] = '\0'; if (settings->clienttls) { // Finish up with OpenSSL if using client TLS SSL_free(clients[i].ssl); } // Close the socket close(fd); // If there are now no clients connected, record the time if (numclients(clients) == 0) { ircdstate->clientsnonetime = time(NULL); } // Now clients array is cleared, inform all other clients about the disconnection (if enabled) if (!discauthed && settings->alertunautheddisconnect) { // Unauthenticated client disconnection alerts enabled and disconnecting client is not authenticated char alertmsg[MAXDATASIZE]; if (!snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :blabouncer: unauthenticated client %s has disconnected.", ircdstate->ircnick, discremoteip)) { fprintf(stderr, "Error while preparing unauthenticated client disconnection NOTICE!\n"); debugprint(DEBUG_CRIT, "Error while preparing unauthenticated client disconnection NOTICE!\n"); alertmsg[0] = '\0'; } // (source "0" since we trust this message) sendtoallclients(clients, alertmsg, 0, settings); } else if (discauthed && settings->alertautheddisconnect) { // Authenticated client disconnection alerts enabled and disconnecting client is authenticated char alertmsg[MAXDATASIZE]; if (!snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :blabouncer: authenticated client %s has disconnected.", ircdstate->ircnick, discremoteip)) { fprintf(stderr, "Error while preparing authenticated client disconnection NOTICE!\n"); debugprint(DEBUG_CRIT, "Error while preparing authenticated client disconnection NOTICE!\n"); alertmsg[0] = '\0'; } // (source "0" since we trust this message) sendtoallclients(clients, alertmsg, 0, settings); } free(discremoteip); return 1; } } // If we got here, we didn't find and clear the client // TODO - Do something with a failed return code free(discremoteip); return 0; } int createchannel(struct channel *channels, struct ircdstate *ircdstate, 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 < ircdstate->maxchannelcount; i++) { if (strlen(channels[i].name) == strlen(name) && 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; } } // Make sure we aren't in too many channels already if (getchannelcount(channels, ircdstate->maxchannelcount) >= MAXCHANNELS - 1) { debugprint(DEBUG_CRIT, "error: createchannel(): already in too many channels (MAXCHANNELS = %d!\n", MAXCHANNELS); return 0; } int arrslot = -1; // See if there's a free slot in the already used section of the channels array for (int i = 0; i < ircdstate->maxchannelcount; i++) { if (!channels[i].name[0]) { // We found one, use this slot arrslot = i; debugprint(DEBUG_FULL, "createchannel(): re-using existing slot %d.\n", arrslot); break; } } // If we didn't find one, increment the total count and use the next unused slot if (arrslot < 0) { arrslot = ircdstate->maxchannelcount; ircdstate->maxchannelcount++; debugprint(DEBUG_FULL, "createchannel(): using new slot %d.\n", arrslot); } // If we got a valid slot... if (arrslot >= 0) { // ...set the name and topic strncpy(channels[arrslot].name, name, strlen(name)); channels[arrslot].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[arrslot].name, strlen(channels[arrslot].name)); strncpy(channels[arrslot].topic, topic, strlen(topic)); channels[arrslot].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[arrslot].topic, strlen(channels[arrslot].topic)); strncpy(channels[arrslot].topicwho, topicwho, strlen(topicwho)); channels[arrslot].topicwho[strlen(topicwho)] = '\0'; strncpy(channels[arrslot].topicwhen, topicwhen, strlen(topicwhen)); channels[arrslot].topicwhen[strlen(topicwhen)] = '\0'; channels[arrslot].gotnames = 0; // Set nicks to blank for (int i = 0; i < MAXCHANNICKS; i++) { channels[arrslot].nicks[i][0] = '\0'; } return 1; } 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, int maxchannelcount, 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 < maxchannelcount; i++) { if ((strlen(channels[i].name) == strlen(channelname)) && (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'; debugprint(DEBUG_FULL, "setchanneltopicwhotime(): set channel '%s' to '%s', '%s'.\n", channels[i].name, channels[i].topicwho, channels[i].topicwhen); return 1; } } // TODO - Make a failed return do something to callers debugprint(DEBUG_FULL, "setchanneltopicwhotime(): failed to set channel topic who/when.\n"); return 0; } int setchanneltopic(struct channel *channels, int maxchannelcount, char *channelname, char *topic) { debugprint(DEBUG_FULL, "setchanneltopic(): given '%s' and '%s'.\n", channelname, topic); for (int i = 0; i < maxchannelcount; i++) { if ((strlen(channels[i].name) == strlen(channelname)) && (strncmp(channels[i].name, channelname, strlen(channelname)) == 0)) { strncpy(channels[i].topic, topic, strlen(topic)); channels[i].topic[strlen(topic)] = '\0'; debugprint(DEBUG_FULL, "setchanneltopic(): set channel '%s' to '%s'.\n", channels[i].name, channels[i].topic); return 1; } } // TODO - Make a failed return do something to callers return 0; } int getchannelcount(struct channel *channels, int maxchannelcount) { int count = 0; for (int i = 0; i < maxchannelcount; i++) { if (channels[i].name[0]) { count++; } } debugprint(DEBUG_FULL, "getchannelcount(): counted %d channels.\n", count); return count; } int removechannel(struct channel *channels, int maxchannelcount, char *name) { debugprint(DEBUG_FULL, "removechannel(): given '%s'.\n", name); // Clear its topic setter and timestamp... setchanneltopicwhotime(channels, maxchannelcount, name, "", "0"); // Find the channel in the channel array... for (int i = 0; i < maxchannelcount; i++) { if ((strlen(channels[i].name) == strlen(name)) && (strncmp(channels[i].name, name, strlen(name)) == 0)) { // ..and NULL its name (0th character = '\0') channels[i].name[0] = '\0'; // Set nicks to blank for (int j = 0; j < MAXCHANNICKS; j++) { channels[i].nicks[j][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 1 if we do, 0 if we don't, or -1 if there's an error. int channelgotnames(struct channel *channels, int maxchannelcount, char *name) { debugprint(DEBUG_FULL, "channelgotnames(): given '%s'.\n", name); for (int i = 0; i < maxchannelcount; i++) { if ((strlen(channels[i].name) == strlen(name)) && 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, int maxchannelcount, 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 < maxchannelcount; i++) { if ((strlen(channels[i].name) == strlen(name)) && (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, int maxchannelcount, char *name) { debugprint(DEBUG_FULL, "channelindex(): given '%s'.\n", name); for (int i = 0; i < maxchannelcount; i++) { if ((strlen(channels[i].name) == strlen(name)) && (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 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 doreplaytime() with '%d' seconds.\n", settings->replayseconds); if (!doreplaytime(sourcefd, settings->replayseconds, clients, settings, ircdstate, channels)) { debugprint(DEBUG_SOME, "doautoreplay(): doreplaytime() 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\", calling doreplaylastspoke().\n"); long linenumber = lastspokelinenumber(ircdstate->ircnick, settings->basedir); if (linenumber < 1) { debugprint(DEBUG_SOME, "doautoreplay(): lastspokelinenumber() returned < 1, returning 0 to caller...\n"); return 0; } debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"lastspoke\", replaying from line '%ld' to sourcefd '%d'.\n", linenumber, sourcefd); if (!doreplaylastspoke(sourcefd, linenumber, clients, settings, ircdstate, channels)) { debugprint(DEBUG_SOME, "doautoreplay(): doreplaylastspoke() returned 0, returning 0 to caller...\n"); // CORRECT? 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 (!doreplaytime(sourcefd, time(NULL) - ircdstate->clientsnonetime, clients, settings, ircdstate, channels)) { debugprint(DEBUG_SOME, "doautoreplay(): doreplaytime() 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 (!doreplaytime(sourcefd, time(NULL) - ircdstate->clientchangetime, clients, settings, ircdstate, channels)) { debugprint(DEBUG_SOME, "doautoreplay(): doreplaytime() returned 0, returning 0 to caller...\n"); return 0; } return 1; } // If replaymode = "perclient" 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 (settings->autochannels[0][0] == '\0') { // None configured debugprint(DEBUG_FULL, "joinautochannels(): none configured.\n"); return 1; } // Join all the channels for (int i = 0; i < MAXCONFARR; i++) { // Unless there are none left in the array if (settings->autochannels[i][0] == '\0') { debugprint(DEBUG_FULL, "joinautochannels(): Finishing joining %d channels.\n", i); return 1; } debugprint(DEBUG_FULL, "joinautochannels(): Joining '%s'.\n", settings->autochannels[i]); char joinmsg[MAXDATASIZE]; if (!snprintf(joinmsg, MAXDATASIZE, "JOIN %s", settings->autochannels[i])) { fprintf(stderr, "joinautochannels(): Error while preparing JOIN message!\n"); debugprint(DEBUG_CRIT, "joinautochannels(): Error while preparing JOIN message\n"); joinmsg[0] = '\0'; } else { sendtoserver(server_ssl, joinmsg, strlen(joinmsg), 0, clients, settings); } } // 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]; // Index of client fd in clients array for use later int clientindex = arrindex(clients, sourcefd); if (clientindex < 0) { debugprint(DEBUG_CRIT, "cleanexit(): error: arrindex() returned '%d', exiting!\n", clientindex); exit(1); } // Tell clients and debug log if (sourcefd) { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Exiting on request from client %s, message '%s'.", ircdstate->ircnick, clients[clientindex].remoteip, quitmsg); debugprint(DEBUG_CRIT, "Exiting on request from client %s with fd '%d', message '%s'.\n", clients[clientindex].remoteip, 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. // 'ctx' is the client OpenSSL context for changing the certificate/key. // Returns 1 on success or 0 on failure. int rehash(struct settings *settings, char *failuremsg, SSL_CTX *ctx) { // TODO - Try to share some/all of this code with the initial main() settings loading // What are the configured nick(s)? char oldircnicks[MAXCONFARR][MAXDATASIZE]; // Backup the existing configured nicks in case this rehash fails for (int i = 0; i < MAXCONFARR; i++) { strcpy(oldircnicks[i], settings->ircnicks[i]); } int ret = getconfarr("nicks", settings->conffile, settings->ircnicks); if (!ret) { // No nicks read, copy the old ones back for (int i = 0; i < MAXCONFARR; i++) { strcpy(settings->ircnicks[i], oldircnicks[i]); } strcpy(failuremsg, "error getting any 'nicks' from configuration file"); return 0; } else if (ret == -1) { // Error reading an array line from the configuration file // Remove any newlines from the string so error printing works nicely for (size_t i = 0; i < strlen(settings->ircnicks[0]); i++) { if (settings->ircnicks[0][i] == '\n') { settings->ircnicks[0][i] = ' '; } } if (!snprintf(failuremsg, MAXDATASIZE, "error getting 'nicks' from configuration file: %s", settings->ircnicks[0])) { debugprint(DEBUG_CRIT, "Error while preparing nick error response!\n"); strcpy(failuremsg, "Error while preparing nick error response!"); } // Copy the old ones back (after setting failuremsg so we can read the error string from element 0) for (int i = 0; i < MAXCONFARR; i++) { strcpy(settings->ircnicks[i], oldircnicks[i]); } return 0; } // Make sure nicks aren't too long (since getconfarr() has to use MAXDATASIZE for all string lengths) for (int i = 0; i < MAXCONFARR; i++) { if (settings->ircnicks[i][0] && strlen(settings->ircnicks[i]) > MAXNICKLENGTH) { // A nick is too long, copy the old ones back for (int i = 0; i < MAXCONFARR; i++) { strcpy(settings->ircnicks[i], oldircnicks[i]); } if (!snprintf(failuremsg, MAXDATASIZE, "error: specified nick '%s' is too long, maximum length is %d.\n", settings->ircnicks[i], MAXNICKLENGTH)) { debugprint(DEBUG_CRIT, "Error while preparing nick too long response!\n"); strcpy(failuremsg, "Error while preparing nick too long response!"); } return 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; } // Should sending replay logs include a datestamp? int oldreplaydates = settings->replaydates; settings->replaydates = getconfint("replaydates", settings->conffile); if (errno == ECONFINT) { settings->replaydates = oldreplaydates; strcpy(failuremsg, "error getting 'replaydates' 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; } // If clienttls = 1, re-read the certificate and key file paths (we don't support switching between TLS and non-TLS) if (settings->clienttls) { // What is the certificate file path? char oldcertfile[PATH_MAX]; strcpy(oldcertfile, settings->certfile); if (!getconfstr("certfile", settings->conffile, settings->certfile)) { // If none provided, set to default if (!snprintf(settings->certfile, PATH_MAX, "%s/cert.pem", settings->basedir)) { strcpy(settings->certfile, oldcertfile); strcpy(failuremsg, "didn't get 'certfile' from configuration file and failed to prepare default certfile location"); return 0; } } // What is the key file path? char oldkeyfile[PATH_MAX]; strcpy(oldkeyfile, settings->keyfile); if (!getconfstr("keyfile", settings->conffile, settings->keyfile)) { // If none provided, set to default if (!snprintf(settings->keyfile, PATH_MAX, "%s/key.pem", settings->basedir)) { strcpy(settings->keyfile, oldkeyfile); strcpy(failuremsg, "didn't get 'keyfile' from configuration file and failed to prepare default keyfile location"); return 0; } } // Reconfigure OpenSSL context in case the certificate or the key changed configure_openssl_context(ctx, settings->certfile, settings->keyfile); } // Is alerting (NOTICE) upon a new connection enabled? int oldalertconnect = settings->alertconnect; settings->alertconnect = getconfint("alertconnect", settings->conffile); if (errno == ECONFINT) { settings->alertconnect = oldalertconnect; strcpy(failuremsg, "error getting 'alertconnect' from configuration file"); return 0; } // Is alerting (NOTICE) upon a failed authentication enabled? int oldalertauthfail = settings->alertauthfail; settings->alertauthfail = getconfint("alertauthfail", settings->conffile); if (errno == ECONFINT) { settings->alertauthfail = oldalertauthfail; strcpy(failuremsg, "error getting 'alertauthfail' from configuration file"); return 0; } // Is alerting (NOTICE) upon a successful authentication enabled? int oldalertauthsuccess = settings->alertauthsuccess; settings->alertauthsuccess = getconfint("alertauthsuccess", settings->conffile); if (errno == ECONFINT) { settings->alertauthsuccess = oldalertauthsuccess; strcpy(failuremsg, "error getting 'alertauthsuccess' from configuration file"); return 0; } // Is alerting (NOTICE) upon unauthenticated client disconnections enabled? int oldalertunautheddisconnect = settings->alertunautheddisconnect; settings->alertunautheddisconnect = getconfint("alertunautheddisconnect", settings->conffile); if (errno == ECONFINT) { settings->alertunautheddisconnect = oldalertunautheddisconnect; strcpy(failuremsg, "error getting 'alertunautheddisconnect' from configuration file"); return 0; } // Is alerting (NOTICE) upon authenticated client disconnections enabled? int oldalertautheddisconnect = settings->alertautheddisconnect; settings->alertautheddisconnect = getconfint("alertautheddisconnect", settings->conffile); if (errno == ECONFINT) { settings->alertautheddisconnect = oldalertautheddisconnect; strcpy(failuremsg, "error getting 'alertautheddisconnect' 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; } } } // Add nick (passed as a :nick!user@host) to channel 'channel' // Returns 1 on success or 0 on failure int addnicktochannel(char *nickuserhost, char *channel, struct channel *channels, int maxchannelcount) { debugprint(DEBUG_FULL, "addnicktochannel(): given '%s' and '%s'.\n", nickuserhost, channel); // Get the nick from the prefix extractnickfromprefix(nickuserhost, 1); // Make sure the channel exists int chanfound = 0; int chanindex; for (chanindex = 0; chanindex < maxchannelcount; chanindex++) { if (strlen(channels[chanindex].name) == strlen(channel) && !strcmp(channels[chanindex].name, channel)) { chanfound = 1; break; } } if (!chanfound) { debugprint(DEBUG_CRIT, "addnicktochannel(): channel '%s' not found in channel struct.\n", channel); return 0; } // Add the nick to the channel for (int i = 0; i < MAXCHANNICKS; i++) { // Make sure the nick isn't already in the channel struct if (strlen(channels[chanindex].nicks[i]) == strlen(nickuserhost) && !strcmp(channels[chanindex].nicks[i], nickuserhost)) { // Unexectedly the nick is already here, hopefully it's OK so let's return 1 debugprint(DEBUG_FULL, "addnicktochannel(): nick '%s' already in channel '%s', returning.\n", nickuserhost, channel); return 1; } // Find the first unoccupied slot and put the nick in if (!channels[chanindex].nicks[i][0]) { strcpy(channels[chanindex].nicks[i], nickuserhost); debugprint(DEBUG_FULL, "addnicktochannel(): added nick '%s' to channel '%s'.\n", nickuserhost, channel); return 1; } } // We shouldn't get here, return error debugprint(DEBUG_CRIT, "addnicktochannel(): got to the end of the function without adding nick '%s' to channel '%s', returning error.\n", nickuserhost, channel); return 0; } // Remove nick (passed as a :nick!user@host) from channel 'channel' // Returns 1 on success or 0 on failure int removenickfromchannel(char *nickuserhost, char *channel, struct channel *channels, int maxchannelcount) { debugprint(DEBUG_FULL, "removenickfromchannel(): given '%s' and '%s'.\n", nickuserhost, channel); // Get the username from the prefix extractnickfromprefix(nickuserhost, 1); // Make sure the channel exists int chanfound = 0; int chanindex; for (chanindex = 0; chanindex < maxchannelcount; chanindex++) { if (strlen(channels[chanindex].name) == strlen(channel) && !strcmp(channels[chanindex].name, channel)) { chanfound = 1; break; } } if (!chanfound) { debugprint(DEBUG_CRIT, "removenickfromchannel(): channel '%s' not found in channel struct.\n", channel); return 0; } // Remove the nick from the channel for (int i = 0; i < MAXCHANNICKS; i++) { // Remove the the nick if (strlen(channels[chanindex].nicks[i]) == strlen(nickuserhost) && !strcmp(channels[chanindex].nicks[i], nickuserhost)) { // By null terminating its string debugprint(DEBUG_FULL, "removenickfromchannel(): nick '%s' removed from channel '%s'.\n", nickuserhost, channel); channels[chanindex].nicks[i][0] = '\0'; return 1; } } // We shouldn't get here, return error debugprint(DEBUG_CRIT, "removenickfromchannel(): got to the end of the function without removing nick '%s' from channel '%s', returning error.\n", nickuserhost, channel); return 0; } // Remove nick (passed as a :nick!user@host) from all channels // Returns 1 on success or 0 on failure int removenickfromallchannels(char *nickuserhost, struct channel *channels, int maxchannelcount) { debugprint(DEBUG_FULL, "removenickfromallchannels(): given '%s'.\n", nickuserhost); // Get the nick from the prefix extractnickfromprefix(nickuserhost, 1); // Make sure the nick has a length of at least one if (strlen(nickuserhost) < 1) { debugprint(DEBUG_CRIT, "removenickfromallchannels(): nick has no length, returning 0!\n"); return 0; } // Go through all channels and remove nick if present for (int i = 0; i < maxchannelcount; i++) { // Don't bother checking this channel index if it isn't used if (!channels[i].name[0]) { continue; } // Go through all nicks in channel for (int j = 0; j < MAXCHANNICKS; j++) { // Remove the nick from the channel if present if (strlen(channels[i].nicks[j]) == strlen(nickuserhost) && !strcmp(channels[i].nicks[j], nickuserhost)) { // By null terminating its string channels[i].nicks[j][0] = '\0'; debugprint(DEBUG_FULL, "removenickfromallchannels(): nick '%s' removed from channel '%s'.\n", nickuserhost, channels[i].name); } } } return 1; } // Update old nick (passed as a :nick!user@host) to 'newnick' in all channels // Returns 1 on success or 0 on failure int updatenickinallchannels(char *nickuserhost, char *newnick, struct channel *channels, int maxchannelcount) { debugprint(DEBUG_FULL, "updatenickinallchannels(): given '%s' and '%s'.\n", nickuserhost, newnick); // Get the nick from the prefix extractnickfromprefix(nickuserhost, 1); // Strip prefix from newnick stripprefix(newnick, 1); // Make sure the old and new nicks have a length of at least one if (strlen(nickuserhost) < 1 || strlen(newnick) < 1) { debugprint(DEBUG_CRIT, "updatenickinallchannels(): nick has no length, returning 0!\n"); return 0; } // Go through all channels and update nick if present for (int i = 0; i < maxchannelcount; i++) { // Go through all nicks in channel for (int j = 0; j < MAXCHANNICKS; j++) { // Update the nick in the channel if present if (strlen(channels[i].nicks[j]) == strlen(nickuserhost) && !strcmp(channels[i].nicks[j], nickuserhost)) { strcpy(channels[i].nicks[j], newnick); debugprint(DEBUG_FULL, "updatenickinallchannels(): nick '%s' updated to '%s' in channel '%s'.\n", nickuserhost, newnick, channels[i].name); } } } return 1; } // Check if "nick" is in any channel or not. // Return 1 if it is, or 0 if not. int isnickinanychannel(struct channel *channels, int maxchannelcount, char *nick) { debugprint(DEBUG_FULL, "isnickinanychannel(): given '%s'.\n", nick); // Make sure the nick has a length of at least one if (strlen(nick) < 1) { debugprint(DEBUG_CRIT, "isnickinanychannel(): nick has no length, returning 0!\n"); return 0; } // Go through all channels and see if nick is present for (int i = 0; i < maxchannelcount; i++) { // Don't bother checking this channel index if it isn't used if (!channels[i].name[0]) { continue; } // Go through all nicks in channel for (int j = 0; j < MAXCHANNICKS; j++) { // See if the nick is here if (strlen(channels[i].nicks[j]) == strlen(nick) && !strcmp(channels[i].nicks[j], nick)) { // Found it! debugprint(DEBUG_FULL, "isnickinanychannel(): nick '%s' found in channel '%s', returning 1.\n", nick, channels[i].name); return 1; } } } debugprint(DEBUG_FULL, "isnickinanychannel(): nick '%s' not found in any channel, returning 0.\n", nick); return 0; } // Populate our channels struct with all nicks in a RPL_NAMREPLY // Returns 1 on success or 0 on failure int addnamereplytochannel(char *namereply, struct channel *channels, int maxchannelcount) { //:irc.tghost.co.uk 353 blabounce = #blabouncer :blabounce bbnick ~@l_bratch @l_blabnc Hughbla Bratchbot ars debugprint(DEBUG_FULL, "addnamereplytochannel(): given '%s'.\n", namereply); // Make a copy since we don't need to modify the original char strcopy[MAXDATASIZE]; strcpy(strcopy, namereply); // Strip the leading ':' stripprefix(strcopy, 1); // Find the start of the channel name, which comes after the first '=' followed by a space int channelpos = -1; for (size_t i = 0; i < strlen(strcopy) - 2; i++) { if (strcopy[i] == '=' && strcopy[i + 1] == ' ' && strcopy[i + 2] != '\0') { // Name found channelpos = i + 2; break; } } if (channelpos == -1) { // Didn't find the name, abort debugprint(DEBUG_FULL, "addnamereplytochannel(): couldn't find start of channel name in '%s'.\n", namereply); return 0; } // Find the end of the channel name char channelname[MAXCHANLENGTH]; for (size_t i = channelpos; i < strlen(strcopy); i++) { // Stop when a space is found or if we're going to exceed MAXCHANLENGTH if (strcopy[i] == ' ' || i - channelpos == MAXCHANLENGTH - 2) { break; } channelname[i - channelpos] = strcopy[i]; channelname[i - channelpos + 1] = '\0'; } // Start with a nice clean string that just consists of nicks at the end of the string char nickstr[MAXDATASIZE]; strcpy(nickstr, strcopy + channelpos + strlen(channelname) + 1); // Split nickstr up into its space-separated nick components // Copy to a temporary string for feeding to strsep char *nickcopy = strdup(nickstr); // Keep track of initial pointer for free()ing later char *nickcopyPtr = nickcopy; // Track which CLRF-separated nick we're on int nickcount = 0; // Build array of each space-separated token char nicks[MAXTOKENS][MAXDATASIZE]; // Split the string by ' ' and add each space-separated nick to an array char *token; while ((token = strsep(&nickcopy, " ")) != NULL) { if (*token == '\0') continue; // Skip consecutive matches if (nickcount >= MAXTOKENS) break; // Too many tokens debugprint(DEBUG_FULL, "addnamereplytochannel(): Token: '%s', length '%ld'.\n", token, strlen(token)); // Make sure it's not too long if (strlen(token) > MAXNICKLENGTH - 1) { debugprint(DEBUG_CRIT, "addnamereplytochannel(): nick too long, discarding.\n"); continue; } // Copy into the token array (strlen + 1 to get the NULL terminator) strncpy(nicks[nickcount], token, strlen(token) + 1); nickcount++; } free(nickcopyPtr); // Clean up each nick (remove prefixes and such) for (int i = 0; i < nickcount; i++) { stripprefixesfromnick(nicks[i]); // And add to the channel addnicktochannel(nicks[i], channelname, channels, maxchannelcount); } return 1; } // Strips all leading prefixes (colons, user modes) from a nick void stripprefixesfromnick(char *nick) { debugprint(DEBUG_FULL, "stripprefixesfromnick(): given '%s'.\n", nick); char nicktmp[MAXNICKLENGTH]; int pos = 0; for (size_t i = 0; i < strlen(nick); i++) { // Only copy non-prefix chars if (nick[i] != ':' && nick[i] != '~' && nick[i] != '&' && nick[i] != '@' && nick[i] != '%' && nick[i] != '+') { nicktmp[pos] = nick[i]; pos++; } } // Null terminate nicktmp[pos] = '\0'; debugprint(DEBUG_FULL, "stripprefixesfromnick(): produced '%s'.\n", nicktmp); // Copy back to source string strcpy(nick, nicktmp); } // Convert the given 'string' into lowercase void strlower(char *string) { for (int i = 0; string[i]; i++) { string[i] = tolower(string[i]); } }