From e1f41810ac85a0d210062ed33f43938dc4b03be4 Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Sat, 14 Sep 2019 17:57:07 +0100 Subject: Implement arrays in the configuration file and start using them to allow for multiple connect commands. --- README | 3 + TODO | 4 +- blabouncer.c | 18 +++++- config.c | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- config.h | 6 ++ message.c | 8 ++- structures.h | 3 +- 7 files changed, 209 insertions(+), 14 deletions(-) diff --git a/README b/README index ce415fa..e2a026f 100644 --- a/README +++ b/README @@ -20,6 +20,9 @@ All arguments are optional, but they must be specified in the order shown above An example configuration file is provided named "blabouncer.conf.example". +Configuration options are either simple single string options, or multiple line arrays. The usage is +explained in the example configuration file. + If you don't specify one using "-c /path/to/configuration/file" then the example one will be created for you at $HOME/.blabouncer/blabouncer.conf when starting for the first time. diff --git a/TODO b/TODO index f0071c3..7b94d3d 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,4 @@ -Support multiple connect commands. - -Support arrays or similar in the configuration file (for nick(s), connectcommand(s), etc.) +Specify multiple nicks using configuration arrays. All the TODOs sprinkled throughout the code! diff --git a/blabouncer.c b/blabouncer.c index d94a165..e6a56c1 100644 --- a/blabouncer.c +++ b/blabouncer.c @@ -1025,9 +1025,21 @@ int main(int argc, char *argv[]) { settings.ircserverpassword[0] = '\0'; } - // What is the connect command, if any? - if (!getconfstr("connectcommand", settings.conffile, settings.connectcommand)) { - settings.connectcommand[0] = '\0'; + // What are the connect commands, if any? + int ret = getconfarr("connectcommands", settings.conffile, settings.connectcommands); + if (!ret) { + for (int i = 0; i < MAXCONFARR; i++) { + settings.connectcommands[i][0] = '\0'; + } + } else if (ret == -1) { + // Remove any newlines from the middle of the string so error printing works nicely + for (size_t i = 0; i < strlen(settings.connectcommands[0]) - 1; i++) { + if (settings.connectcommands[0][i] == '\n') { + settings.connectcommands[0][i] = ' '; + } + } + printf("main(): error getting 'commandcommands' from configuration file: %s", settings.connectcommands[0]); + exit(1); } // Is the base directory set? If not, use the default. diff --git a/config.c b/config.c index cea404c..842f389 100644 --- a/config.c +++ b/config.c @@ -25,7 +25,7 @@ int getconfstr(char *confname, char *filename, char* dest) { char str[MAXCHAR]; int found = 0; // Have we found the configuration option? - // Set strings to zero-length to begin + // Set string to zero-length to begin dest[0] = '\0'; // Length of requested configuration option name @@ -105,6 +105,165 @@ int getconfstr(char *confname, char *filename, char* dest) { 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, "getconfstr(): 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) { + 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. @@ -153,11 +312,22 @@ int createconfigfile(char *filename) { char *string = "# blabouncer configuration file\n" "#\n" - "# Entries must be in the form:\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" @@ -210,8 +380,11 @@ int createconfigfile(char *filename) { "# Real IRC server password\n" "#ircserverpassword = \"apples\"\n" "\n" - "# Command to send to the server upon completing registration (e.g. a NickServ password)\n" - "#connectcommand \"PRIVMSG NickServ IDENTIFY bananas\"\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" diff --git a/config.h b/config.h index 2f2db9f..d478480 100644 --- a/config.h +++ b/config.h @@ -40,6 +40,12 @@ // getconfstr() returns. int getconfstr(char *confname, char *filename, char* dest); +// 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]); + // 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. diff --git a/message.c b/message.c index 5cfff91..3887ec9 100644 --- a/message.c +++ b/message.c @@ -89,9 +89,11 @@ int processservermessage(SSL *server_ssl, char *str, struct client *clients, int // Receiving greeting 004 means we're now registered // Request IRCv3 multi-prefix extension so we can more accurately inform new clients about current user prefixes sendtoserver(server_ssl, "CAP REQ multi-prefix", strlen("CAP REQ multi-prefix"), 0, clients, settings); - // Send the connect command, if set - if (settings->connectcommand[0]) { - sendtoserver(server_ssl, settings->connectcommand, strlen(settings->connectcommand), 0, clients, settings); + // Send any configured connect commands + for (int i = 0; i < MAXCONFARR; i++) { + if (settings->connectcommands[i][0]) { + sendtoserver(server_ssl, settings->connectcommands[i], strlen(settings->connectcommands[i]), 0, clients, settings); + } } // If this is a reconnection, JOIN existing channels and catch clients up again if (ircdstate->reconnecting) { diff --git a/structures.h b/structures.h index e58c1b1..8a99163 100644 --- a/structures.h +++ b/structures.h @@ -29,6 +29,7 @@ #define MAXAUTOCHANLEN 1024 // Randomly picked maximum length of the auto channel list #define CLIENTCODELEN 17 // Max length of a client code + 1 for null #define MAXCLIENTCODES 64 // Max number of client codes to track +#define MAXCONFARR 10 // Max number of entries that a configuration array can have struct ircdstate { char greeting001[MAXDATASIZE]; @@ -70,7 +71,7 @@ struct settings { char ircserver[HOST_NAME_MAX]; char ircserverport[MAXPORTLEN]; char ircserverpassword[MAXDATASIZE - 5]; // -5 for "PASS " - char connectcommand[MAXDATASIZE]; + char connectcommands[MAXCONFARR][MAXDATASIZE]; char conffile[PATH_MAX]; char certfile[PATH_MAX]; char keyfile[PATH_MAX]; -- cgit v1.2.3