/* * 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 "config.h" // Sets 'dest' to the value of the configuration option with name // 'confname' from configuration file 'filename'. // Returns 1 for success or 0 for error/failure. int getconfstr(char *confname, char *filename, char* dest) { FILE *fp; char str[MAXCHAR]; int found = 0; // Have we found the configuration option? // Set string to zero-length to begin dest[0] = '\0'; // Length of requested configuration option name long int namelen = strlen(confname); fp = fopen(filename, "r"); if (fp == NULL) { printf("error: could not open configuration file '%s'.\n", filename); debugprint(DEBUG_CRIT, "error: could not open configuration file '%s'.\n", filename); exit(1); } // Loop through the whole file, looking for the requested configuration option while (fgets(str, MAXCHAR, fp) != NULL) { char substr[MAXCHAR]; // Check if the next character after the length of the requested option // name is an equals sign, a space, or a tab if (str[namelen] != '=' && str[namelen] != ' ' && str[namelen] != '\t') { // If it isn't this can't have been our option continue; } // Copy the number of characters that the requested option name is long // to a temporary string strncpy(substr, str, namelen); substr[namelen] = '\0'; // If the resulting temporary string contains the requested option name, // we have found our configuration option and it is in the current 'str' if (strstr(substr, confname)) { found = 1; break; } } // If we got here, then either we found the configuration option or we ran out of file if (!found) { debugprint(DEBUG_SOME, "Error reading configuration option '%s', did not find it in configuration file '%s'.\n", confname, filename); fclose(fp); return 0; } long int pos = 0; char conf[MAXCHAR]; // Temporary string to build configuration value in // Starting from the end of the option name, find the position of the start of the configuration value // (including its double quotes) by skipping over everything that isn't an equals sign, a space, or a tab for (size_t i = namelen; i < strlen(str); i++) { if (str[i] == '=' || str[i] == ' ' || str[i] == '\t') { continue; } else { // Record current/final position in string pos = i; break; } } strncpy(conf, str + pos, strlen(str) - pos - 1); // Copy remainder to new string and lop off the newline conf[strlen(str) - pos - 1] = '\0'; // Null terminate // Check for start and end quotes if (conf[0] != '"' || conf[strlen(conf) - 1] != '"') { printf("Error reading configuration option '%s', not enclosed in double quotes in configuration file '%s'!\n", confname, filename); debugprint(DEBUG_CRIT, "Error reading configuration option '%s', not enclosed in double quotes in configuration file '%s'!\n", confname, filename); exit(1); } strncpy(dest, conf + 1, strlen(conf) - 2); // Copy result to destination string without quotes dest[strlen(conf) - 2] = '\0'; // Null terminate debugprint(DEBUG_FULL, "getconfstr(): returning '%s'.\n", dest); // Close fine and return success fclose(fp); return 1; } // Populates 'dest' with the values of the configuration array option // with name 'confname' from configuration file 'filename'. // Returns 1 on success, 0 on reading no values, or -1 on an error. // On error, dest[0] is set to the error string for later retrieval. int getconfarr(char *confname, char *filename, char dest[MAXCONFARR][MAXDATASIZE]) { debugprint(DEBUG_FULL, "getconfarr(): '%s', '%s' and a dest array.\n", confname, filename); FILE *fp; char line[MAXCHAR]; int found = 0; // Have we found the configuration option? int valuecount = 0; // Which element in the configuration array we are on // Set strings to zero-length to begin for (int i = 0; i < MAXCONFARR; i++) { dest[i][0] = '\0'; } // Length of requested configuration array name long int namelen = strlen(confname); fp = fopen(filename, "r"); if (fp == NULL) { debugprint(DEBUG_CRIT, "error: could not open configuration file '%s'.\n", filename); exit(1); } // Loop through the whole file, looking for the requested configuration array while (fgets(line, MAXCHAR, fp) != NULL) { // Don't bother with any of this if this line is a comment int comment = 0; for (size_t i = 0; i < strlen(line); i++) { // Ignore spaces/tabs if (line[i] == ' ' || line[i] == '\t') { continue; } else if (line[i] == '#') { // If it's a comment, ignore the line comment = 1; break; } else { // Found something else before a comment, so carry on break; } } if (comment) { continue; } // If we haven't found our array yet, try to find the opening line if (!found) { char substr[MAXCHAR]; // Check if the next character after the length of the requested array // name is an equals sign, a space, or a tab if (line[namelen] != '=' && line[namelen] != ' ' && line[namelen] != '\t') { // If it isn't this can't have been our array continue; } // Copy the number of characters that the requested array name is long // to a temporary string strncpy(substr, line, namelen); substr[namelen] = '\0'; // If the resulting temporary string contains the requested array name, // we have found our configuration array if (strstr(substr, confname)) { // Make sure it is a valid start of array line for (size_t i = namelen; i < strlen(line); i++) { if (line[i] == ' ' || line[i] == '\t' || line[i] == '=') { // Ignore spaces, tabs and equals signs continue; } else if (line[i] == '{') { // Success, found an opening brace // Ignore anything else on this line found = 1; break; } else { // Unexpected character found, return failure snprintf(dest[0], MAXDATASIZE, "Unexpected character '%c' found on configuration array opening line for '%s'.\n", line[i], confname); fclose(fp); return -1; } } } // If we have found our array, extract the value from each line in it } else { int valuelen = 0; int inquotes = 0; for (size_t i = 0; i < strlen(line); i++) { // If we've on the closing brace line, then we're done if (line[i] == '}') { for (int i = 0; i < valuecount; i++) { debugprint(DEBUG_FULL, "getconfarr(): returning '%s'.\n", dest[i]); } // Close fine and return success (or 0 if no values found in an otherwise valid array) fclose(fp); if (valuecount) { return 1; } else { return 0; } } // If not in the quotes yet if (!inquotes) { // Skip over initial spaces and tabs if (line[i] == ' ' || line[i] == '\t') { continue; } else if (line[i] == '"') { // Quotes found, we're now reading the value inquotes = 1; continue; } else { // Unexpected character found, return failure snprintf(dest[0], MAXDATASIZE, "Unexpected character '%c' found before opening quotes on array line for '%s'.\n", line[i], confname); fclose(fp); return -1; } // If inside the quotes (so, we've got to the actual value) } else if (inquotes) { // If we're on the last character and it isn't a closing quote, something is wrong if (i == strlen(line) - 1 && line[i] != '"') { snprintf(dest[0], MAXDATASIZE, "Reached end of line without finding closing quotes on array line for '%s'.\n", confname); fclose(fp); return -1; } // If we've found too many values, return an error if (valuecount > MAXCONFARR - 1) { snprintf(dest[0], MAXDATASIZE, "Too many elements defined for configuration array '%s', maximum number is '%d'.\n", confname, MAXCONFARR); fclose(fp); return -1; } // Otherwise, copy everything that isn't the closing quotes to the current element in the dest array if (line[i] != '"') { dest[valuecount][valuelen] = line[i]; valuelen++; continue; } else { // When we find the closing quotes, the value is read, so terminate the dest array element string dest[valuecount][valuelen] = '\0'; // We're done with this value, ignore anything that may be after the closing quotes on this line valuecount++; break; } } } } } // If we get this far, then something went wrong snprintf(dest[0], MAXDATASIZE, "getconfarr(): didn't find any configuration array for '%s'.\n", confname); fclose(fp); return 0; } // Returns the value of the configuration option with name // 'confname' from configuration file 'filename'. // Sets errno to 0 on success, or ECONFINT if it fails, in which case the return value is undefined. int getconfint(char *confname, char *filename) { errno = 0; char result[MAXCHAR]; if (!getconfstr(confname, filename, result)) { debugprint(DEBUG_CRIT, "getconfint(): error getting configuration option '%s' from configuration file '%s'.\n", confname, filename); errno = ECONFINT; } return strtol(result, NULL, 10); // Convert resulting string to an integer, base 10 } // Create the default configuration file. // Return 1 on success, 0 on failure. int createconfigfile(char *filename) { char *dirtmp; char *dir; dirtmp = strdup(filename); dir = strdup(dirname(dirtmp)); // Make sure the parent directory exists struct stat st = {0}; if (stat(dir, &st) == -1) { if (mkdir(dir, 0700)) { printf("Error creating config directory '%s'.\n", dir); debugprint(DEBUG_CRIT, "Error creating config directory '%s'.\n", dir); exit(1); } else { printf("Created config directory '%s'.\n", dir); } } FILE *fp; fp = fopen(filename, "a"); if (fp == NULL) { printf("error: could not open default configuration file '%s' for writing.\n", filename); debugprint(DEBUG_CRIT, "error: could not open default configuration file '%s' for writing.\n", filename); exit(1); } // Prepare the string char *string = "# blabouncer configuration file\n" "#\n" "# Normal entries must be in the form:\n" "# option name, space, equals sign, space, double quote, option value, double quote\n" "# e.g.\n" "# realname = \"Mr Bla Bouncer\"\n" "#\n" "# Array entries must be in the form:\n" "# option name, space, equals sign, space, open brace\n" "# (optional indentation,) double quote, element value, double quoute\n" "# (optional multiple values to be repeated after the first one(s))\n" "# close brace\n" "# e.g.\n" "# connectcommands = {\n" "# \"PRIVMSG NickServ IDENTIFY bananas\"\n" "# \"PRIVMSG myfriend I'm online!\"\n" "# }\n" "#\n" "# Shell expansion is not supported, so do not try and specify e.g.\n" "# \"~/.blabouncer/\" or \"$HOME/.blabouncer/\", instead use \"/home/foo/.blabouncer\"\n" "#\n" "# Some settings can be reloaded at runtime, please refer to README for details.\n" "\n" "# Nick(s) to use when connecting - will be cycled through in order in the event of\n" "# a nick being in use or invalid\n" "nicks = {\n" " \"blabounce\"\n" " \"bbounce2\"\n" " \"bbounce3\"\n" "}\n" "\n" "username = \"bounceusr\"\n" "realname = \"Mr Bla Bouncer\"\n" "\n" "# Channels to automatically join (defaults to none)\n" "# Put channel keys/passwords after channel names (separated with a space)\n" "channels = {\n" " \"#blabouncer keyword\"\n" " \"#test\"\n" "}\n" "\n" "# Auto replay mode upon a bouncer client connecting\n" "# \"none\" = Don't auto replay\n" "# \"time\" = Always send the last \"replayseconds\" worth of logs\n" "# \"lastspoke\" = All messages since your current nick last spoke\n" "# \"noclients\" = All messages since you last had no clients connected\n" "# \"lastchange\" = All messages since your last client connection/disconnection\n" "# \"perclient\" = All messages since the current client last disconnected (see README)\n" "replaymode = \"time\"\n" "\n" "# How many seconds of replay log should be sent to connecting clients if replaymode = \"time\"\n" "replayseconds = \"600\"\n" "\n" "# Should replay log timestamps include the date when replaying? (\"1\" for yes or \"0\" for no)\n" "replaydates = \"0\"\n" "\n" "# Connect password clients must provided to connect\n" "password = \"bananas\"\n" "\n" "# Port the bouncer should listen on\n" "clientport = \"1234\"\n" "\n" "# Enable TLS for clients connecting to the bouncer (\"1\" for yes or \"0\" for no)\n" "# If \"0\" then certfile and keyfile need not be set\n" "clienttls = \"1\"\n" "\n" "# Enable TLS for the bouncer connecting to the IRC server (\"1\" for yes or \"0\" for no)\n" "servertls = \"1\"\n" "\n" "# Real IRC server the bouncer connects to\n" "ircserver = \"irc.blatech.net\"\n" "\n" "# Real IRC server port\n" "ircserverport = \"6697\"\n" "\n" "# Real IRC server password\n" "#ircserverpassword = \"apples\"\n" "\n" "# Command(s) to send to the server upon completing registration (e.g. a NickServ password)\n" "#connectcommands = {\n" "# \"PRIVMSG NickServ IDENTIFY bananas\"\n" "# \"PRIVMSG myfriend I'm online!\"\n" "#}\n" "\n" "# Base directory (defaults to $HOME/.blabouncer/)\n" "# Things such as the logs directory will be placed below this\n" "#basedir = \"/home/foo/.blabouncer/\"\n" "\n" "# Certificate file (defaults to /cert.pem)\n" "# If clienttls = \"0\" then this need not be set\n" "#certfile = \"/home/foo/.blabouncer/cert.pem\"\n" "\n" "# Certificate key file (defaults to /key.pem)\n" "# If clienttls = \"0\" then this need not be set\n" "#keyfile = \"/home/foo/.blabouncer/key.pem\"\n" "\n" "# Enable logging (\"1\" for yes or \"0\" for no)\n" "# Logs go to basedir/logs/ with one file per channel/nick\n" "logging = \"1\"\n" "\n" "# Enable replay logging (\"1\" for yes or \"0\" for no)\n" "# Replay log goes to basedir/replay.log\n" "replaylogging = \"1\"\n" "\n" "# Debug verbosity (\"0\" for critical only, \"1\" for some extra info, \"2\" for full debug mode)\n" "# (All output goes to /debug.txt)\n" "debug = \"2\"\n" "\n" "# Number of debug logs to keep\n" "debugkeep = \"5\"\n"; // Write complete string to file if ((fprintf(fp, "%s", string)) < 0) { debugprint(DEBUG_CRIT, "error: could not write to replay log file.\n"); } fclose(fp); free(dirtmp); free(dir); return 0; }