diff options
-rw-r--r-- | include/configreader.h | 378 | ||||
-rw-r--r-- | src/config.c | 705 |
2 files changed, 1079 insertions, 4 deletions
diff --git a/include/configreader.h b/include/configreader.h new file mode 100644 index 000000000..1cb0cb7b6 --- /dev/null +++ b/include/configreader.h @@ -0,0 +1,378 @@ +#ifndef _CONFIGREADER_H_ +#define _CONFIGREADER_H_ + +#include <string> +#include <fstream> +#include <sstream> +#include <vector> +#include <map> +#include <deque> + +/** A configuration key and value pair + */ +typedef std::pair<std::string, std::string> KeyVal; + +/** A list of related configuration keys and values + */ +typedef std::vector<KeyVal> KeyValList; + +/** An entire config file, built up of KeyValLists + */ +typedef std::multimap<std::string, KeyValList> ConfigDataHash; + +// Required forward definitions +class ServerConfig; + +/** Types of data in the core config + */ +enum ConfigDataType { + DT_NOTHING, // No data + DT_INTEGER, // Integer + DT_UINTEGER, // Unsigned Integer + DT_CHARPTR, // Char pointer + DT_STRING, // std::string + DT_BOOLEAN, // Boolean + DT_HOSTNAME, // Hostname syntax + DT_NOSPACES, // No spaces + DT_IPADDRESS, // IP address (v4, v6) + DT_TIME, // Time value + DT_NORELOAD = 32, // Item can't be reloaded after startup + DT_ALLOW_WILD = 64, // Allow wildcards/CIDR in DT_IPADDRESS + DT_ALLOW_NEWLINE = 128 // New line characters allowed in DT_CHARPTR +}; + +/** Holds a config value, either string, integer or boolean. + * Callback functions receive one or more of these, either on + * their own as a reference, or in a reference to a deque of them. + * The callback function can then alter the values of the ValueItem + * classes to validate the settings. + */ +class ValueItem +{ + /** Actual data */ + std::string v; + public: + /** Initialize with an int */ + ValueItem(int); + /** Initialize with a bool */ + ValueItem(bool); + /** Initialize with a char pointer */ + ValueItem(const char *); + /** Initialize with an std::string */ + ValueItem(const std::string &); + /** Change value to a char pointer */ + //void Set(char *); + /** Change value to a const char pointer */ + void Set(const char *); + /** Change value to an std::string */ + void Set(const std::string &); + /** Change value to an int */ + void Set(int); + /** Get value as an int */ + int GetInteger(); + /** Get value as a string */ + char *GetString(); + /** Get value as a bool */ + bool GetBool(); +}; + +/** The base class of the container 'ValueContainer' + * used internally by the core to hold core values. + */ +class ValueContainerBase +{ + public: + /** Constructor */ + ValueContainerBase() { } + /** Destructor */ + virtual ~ValueContainerBase() { } +}; + +/** ValueContainer is used to contain pointers to different + * core values such as the server name, maximum number of + * clients etc. + * It is specialized to hold a data type, then pointed at + * a value in the ServerConfig class. When the value has been + * read and validated, the Set method is called to write the + * value safely in a type-safe manner. + */ +template<typename T> class ValueContainer : public ValueContainerBase +{ + /** Contained item */ + T val; + public: + /** Initialize with nothing */ + ValueContainer() : ValueContainerBase(), val(NULL) { } + /** Initialize with a value of type T */ + ValueContainer(T Val) : ValueContainerBase(), val(Val) { } + /** Initialize with a copy */ + ValueContainer(const ValueContainer &Val) : ValueContainerBase(), val(Val.val) { } + ValueContainer &operator=(const ValueContainer &Val) + { + val = Val.val; + return *this; + } + /** Change value to type T of size s */ + void Set(const T newval, size_t s) + { + memcpy(val, newval, s); + } +}; + +/** This a specific version of ValueContainer to handle std::string specially + */ +template<> class ValueContainer<std::string *> : public ValueContainerBase +{ + /** Contained item */ + std::string *val; + public: + /** Initialize with nothing */ + ValueContainer() : ValueContainerBase(), val(NULL) { } + /** Initialize with an std::string */ + ValueContainer(std::string *Val) : ValueContainerBase(), val(Val) { } + /** Initialize with a copy */ + ValueContainer(const ValueContainer &Val) : ValueContainerBase(), val(Val.val) { } + ValueContainer &operator=(const ValueContainer &Val) + { + val = Val.val; + return *this; + } + /** Change value to given std::string */ + void Set(const std::string &newval) + { + *val = newval; + } + /** Change value to given char pointer */ + void Set(const char *newval) + { + *val = newval; + } +}; + +/** A specialization of ValueContainer to hold a pointer to a bool + */ +typedef ValueContainer<bool *> ValueContainerBool; + +/** A specialization of ValueContainer to hold a pointer to + * an unsigned int + */ +typedef ValueContainer<unsigned *> ValueContainerUInt; + +/** A specialization of ValueContainer to hold a pointer to + * a char array. + */ +typedef ValueContainer<char *> ValueContainerChar; + +/** A specialization of ValueContainer to hold a pointer to + * an int + */ +typedef ValueContainer<int *> ValueContainerInt; + +/** A specialization of ValueContainer to hold a pointer to + * a time_t + */ +typedef ValueContainer<time_t *> ValueContainerTime; + +/** A specialization of ValueContainer to hold a pointer to + * an std::string + */ +typedef ValueContainer<std::string *> ValueContainerString; + +/** A set of ValueItems used by multi-value validator functions + */ +typedef std::deque<ValueItem> ValueList; + +/** A callback for validating a single value + */ +typedef bool (*Validator)(ServerConfig *, const char *, const char *, ValueItem &); +/** A callback for validating multiple value entries + */ +typedef bool (*MultiValidator)(ServerConfig *, const char *, const char **, ValueList &, int *); +/** A callback indicating the end of a group of entries + */ +typedef bool (*MultiNotify)(ServerConfig *, const char *); + +/** Holds a core configuration item and its callbacks + */ +struct InitialConfig +{ + /** Tag name */ + const char *tag; + /** Value name */ + const char *value; + /** Default, if not defined */ + const char *default_value; + /** Value containers */ + ValueContainerBase *val; + /** Data types */ + int datatype; + /** Validation function */ + Validator validation_function; +}; + +/** Holds a core configuration item and its callbacks + * where there may be more than one item + */ +struct MultiConfig +{ + /** Tag name */ + const char *tag; + /** One or more items within tag */ + const char *items[17]; + /** One or more defaults for items within tags */ + const char *items_default[17]; + /** One or more data types */ + int datatype[17]; + /** Initialization function */ + MultiNotify init_function; + /** Validation function */ + MultiValidator validation_function; + /** Completion function */ + MultiNotify finish_function; +}; + +/** This class holds the bulk of the runtime configuration for the ircd. + * It allows for reading new config values, accessing configuration files, + * and storage of the configuration data needed to run the ircd, such as + * the servername, connect classes, /ADMIN data, MOTDs and filenames etc. + */ +class ServerConfig +{ + private: + /** This variable holds the names of all + * files included from the main one. This + * is used to make sure that no files are + * recursively included. + */ + std::vector<std::string> include_stack; + /** Process an include directive + */ + bool DoInclude(ConfigDataHash &, const std::string &, std::ostringstream &); + /** Check that there is only one of each configuration item + */ + bool CheckOnce(const char *); + public: + std::ostringstream errstr; + ConfigDataHash newconfig; + /** This holds all the information in the config file, + * it's indexed by tag name to a vector of key/values. + */ + ConfigDataHash config_data; + /** Construct a new ServerConfig + */ + ServerConfig(); + /** Clears the include stack in preperation for a Read() call. + */ + void ClearStack(); + /** Read the entire configuration into memory + * and initialize this class. All other methods + * should be used only by the core. + */ + int Read(bool); + /** Report a configuration error given in errormessage. + * @param bail If this is set to true, the error is sent to the console, and the program exits + * @param connection If this is set to a non-null value, and bail is false, the errors are spooled to + * this connection as SNOTICEs. + * If the parameter is NULL, the messages are spooled to all connections via WriteOpers as SNOTICEs. + */ + void ReportConfigError(const std::string &, bool); + /** Load 'filename' into 'target', with the new config parser everything is parsed into + * tag/key/value at load-time rather than at read-value time. + */ + bool LoadConf(ConfigDataHash &, const char *, std::ostringstream &); + /** Load 'filename' into 'target', with the new config parser everything is parsed into + * tag/key/value at load-time rather than at read-value time. + */ + bool LoadConf(ConfigDataHash &, const std::string &, std::ostringstream &); + // Both these return true if the value existed or false otherwise + /** Writes 'length' chars into 'result' as a string + */ + bool ConfValue(ConfigDataHash &, const char *, const char *, int, char *, int, bool = false); + /** Writes 'length' chars into 'result' as a string + */ + bool ConfValue(ConfigDataHash &, const char *, const char *, const char *, int, char *, int, bool = false); + /** Writes 'length' chars into 'result' as a string + */ + bool ConfValue(ConfigDataHash &, const std::string &, const std::string &, int, std::string &, bool = false); + /** Writes 'length' chars into 'result' as a string + */ + bool ConfValue(ConfigDataHash &, const std::string &, const std::string &, const std::string &, int, std::string &, bool = false); + /** Tries to convert the value to an integer and write it to 'result' + */ + bool ConfValueInteger(ConfigDataHash &, const char *, const char *, int, int &); + /** Tries to convert the value to an integer and write it to 'result' + */ + bool ConfValueInteger(ConfigDataHash &, const char *, const char *, const char *, int, int &); + /** Tries to convert the value to an integer and write it to 'result' + */ + bool ConfValueInteger(ConfigDataHash &, const std::string &, const std::string &, int, int &); + /** Tries to convert the value to an integer and write it to 'result' + */ + bool ConfValueInteger(ConfigDataHash &, const std::string &, const std::string &, const std::string &, int, int &); + /** Returns true if the value exists and has a true value, false otherwise + */ + bool ConfValueBool(ConfigDataHash &, const char *, const char *, int); + /** Returns true if the value exists and has a true value, false otherwise + */ + bool ConfValueBool(ConfigDataHash &, const char *, const char *, const char *, int); + /** Returns true if the value exists and has a true value, false otherwise + */ + bool ConfValueBool(ConfigDataHash &, const std::string &, const std::string &, int); + /** Returns true if the value exists and has a true value, false otherwise + */ + bool ConfValueBool(ConfigDataHash &, const std::string &, const std::string &, const std::string &, int); + /** Returns the number of occurences of tag in the config file + */ + int ConfValueEnum(ConfigDataHash &, const char *); + /** Returns the number of occurences of tag in the config file + */ + int ConfValueEnum(ConfigDataHash &, const std::string &); + /** Returns the numbers of vars inside the index'th 'tag in the config file + */ + int ConfVarEnum(ConfigDataHash &, const char *, int); + /** Returns the numbers of vars inside the index'th 'tag in the config file + */ + int ConfVarEnum(ConfigDataHash &, const std::string &, int); + void ValidateHostname(const char *, const std::string &, const std::string &); + void ValidateIP(const char *p, const std::string &, const std::string &, bool); + void ValidateNoSpaces(const char *, const std::string &, const std::string &); +}; + +/** Initialize the disabled commands list + */ +E bool InitializeDisabledCommands(const char *); + +/** This class can be used on its own to represent an exception, or derived to represent a module-specific exception. + * When a module whishes to abort, e.g. within a constructor, it should throw an exception using ModuleException or + * a class derived from ModuleException. If a module throws an exception during its constructor, the module will not + * be loaded. If this happens, the error message returned by ModuleException::GetReason will be displayed to the user + * attempting to load the module, or dumped to the console if the ircd is currently loading for the first time. + */ +class ConfigException : public std::exception +{ + protected: + /** Holds the error message to be displayed + */ + const std::string err; + public: + /** Default constructor, just uses the error mesage 'Config threw an exception'. + */ + ConfigException() : err("Config threw an exception") { } + /** This constructor can be used to specify an error message before throwing. + */ + ConfigException(const std::string &message) : err(message) {} + /** This destructor solves world hunger, cancels the world debt, and causes the world to end. + * Actually no, it does nothing. Never mind. + * @throws Nothing! + */ + virtual ~ConfigException() throw() { }; + /** Returns the reason for the exception. + * The module should probably put something informative here as the user will see this upon failure. + */ + virtual const char *GetReason() + { + return err.c_str(); + } +}; + +#endif diff --git a/src/config.c b/src/config.c index 49d3897ec..b4b9c5f0e 100644 --- a/src/config.c +++ b/src/config.c @@ -6,13 +6,14 @@ * Please read COPYING and README for further details. * * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - * - * $Id$ + * Based on the original code of Services by Andy Church. + * + * $Id$ * */ #include "services.h" +#include "configreader.h" /*************************************************************************/ @@ -277,7 +278,7 @@ int ModulesDelayedNumber; char **ModulesDelayedAutoload; /** - * Core Module Stuff + * Core Module Stuff **/ char *HostCoreModules; char **HostServCoreModules; @@ -353,6 +354,702 @@ int UseTS6; /*************************************************************************/ +ServerConfig::ServerConfig() : include_stack(), errstr(""), newconfig(), config_data() +{ + this->ClearStack(); +} + +void ServerConfig::ClearStack() +{ + include_stack.clear(); +} + +bool ServerConfig::CheckOnce(const char *tag) +{ + int count = ConfValueEnum(config_data, tag); + if (count > 1) { + throw ConfigException(static_cast<std::string>("You have more than one <") + tag + "> tag, this is not permitted."); + } + if (count < 1) { + throw ConfigException(static_cast<std::string>("You have not defined a <") + tag + "> tag, this is required."); + } + return true; +} + +bool NoValidation(ServerConfig *, const char *, const char *, ValueItem &) +{ + return true; +} + +bool DoneConfItem(ServerConfig *, const char *) +{ + return true; +} + +void ServerConfig::ValidateNoSpaces(const char *p, const std::string &tag, const std::string &val) +{ + for (const char *ptr = p; *ptr; ++ptr) if (*ptr == ' ') throw ConfigException(static_cast<std::string>("The value of <") + tag + ":" + val + + "> cannot contain spaces"); +} + +/* NOTE: Before anyone asks why we're not using inet_pton for this, it is because inet_pton and friends do not return so much detail, + * even in strerror(errno). They just return 'yes' or 'no' to an address without such detail as to whats WRONG with the address. + * Because ircd users arent as technical as they used to be (;)) we are going to give more of a useful error message. + */ +void ServerConfig::ValidateIP(const char *p, const std::string &tag, const std::string &val, bool wild) +{ + int num_dots = 0, num_seps = 0; + bool not_numbers = false, not_hex = false; + if (*p) { + if (*p == '.') throw ConfigException(static_cast<std::string>("The value of <") + tag + ":" + val + "> is not an IP address"); + for (const char *ptr = p; *ptr; ++ptr) { + if (wild && (*ptr == '*' || *ptr == '?' || *ptr == '/')) continue; + if (*ptr != ':' && *ptr != '.') { + if (*ptr < '0' || *ptr > '9') { + not_numbers = true; + if (toupper(*ptr) < 'A' || toupper(*ptr) > 'F') not_hex = true; + } + } + switch (*ptr) { + case ' ': + throw ConfigException(static_cast<std::string>("The value of <") + tag + ":" + val + "> is not an IP address"); + case '.': + ++num_dots; + break; + case ':': + ++num_seps; + } + } + if (num_dots > 3) throw ConfigException(static_cast<std::string>("The value of <") + tag + ":" + val + + "> is an IPv4 address with too many fields!"); + if (num_seps > 8) throw ConfigException(static_cast<std::string>("The value of <") + tag + ":" + val + + "> is an IPv6 address with too many fields!"); + if (!num_seps && num_dots < 3 && !wild) throw ConfigException(static_cast<std::string>("The value of <") + tag + ":" + val + + "> looks to be a malformed IPv4 address"); + if (!num_seps && num_dots == 3 && not_numbers) throw ConfigException(static_cast<std::string>("The value of <") + tag + ":" + val + + "> contains non-numeric characters in an IPv4 address"); + if (num_seps && not_hex) throw ConfigException(static_cast<std::string>("The value of <") + tag + ":" + val + + "> contains non-hexdecimal characters in an IPv6 address"); + if (num_seps && num_dots != 3 && num_dots && !wild) throw ConfigException(static_cast<std::string>("The value of <") + tag + ":" + val + + "> is a malformed IPv6 4in6 address"); + } +} + +void ServerConfig::ValidateHostname(const char *p, const std::string &tag, const std::string &val) +{ + int num_dots = 0; + if (*p) { + if (*p == '.') throw ConfigException(static_cast<std::string>("The value of <") + tag + ":" + val + "> is not a valid hostname"); + for (const char *ptr = p; *ptr; ++ptr) { + switch (*ptr) { + case ' ': + throw ConfigException(static_cast<std::string>("The value of <") + tag + ":" + val + "> is not a valid hostname"); + case '.': + ++num_dots; + } + } + if (!num_dots) throw ConfigException(static_cast<std::string>("The value of <") + tag + ":" + val + "> is not a valid hostname"); + } +} + +bool ValidateMaxTargets(ServerConfig *, const char *, const char *, ValueItem &data) +{ + if (data.GetInteger() < 0 || data.GetInteger() > 31) { + alog("WARNING: <options:maxtargets> value is greater than 31 or less than 0, set to 20."); + data.Set(20); + } + return true; +} + +bool ValidateNotEmpty(ServerConfig *, const char *tag, const char *value, ValueItem &data) +{ + if (!*data.GetString()) throw ConfigException(static_cast<std::string>("The value for <") + tag + ":" + value + "> cannot be empty!"); + return true; +} + +bool ValidatePort(ServerConfig *, const char *tag, const char *value, ValueItem &data) +{ + int port = data.GetInteger(); + if (!port) return true; + if (port < 1 || port > 65535) throw ConfigException(static_cast<std::string>("The value for <") + tag + ":" + value + + "> is not a value port, it must be between 1 and 65535!"); + return true; +} + +void ServerConfig::ReportConfigError(const std::string &errormessage, bool bail) +{ + alog("There were errors in your configuration file: %s", errormessage.c_str()); + if (bail) { + // TODO -- Need a way to stop loading in a safe way -- CyberBotX + //ServerInstance->Exit(EXIT_STATUS_CONFIG); + } +} + +int ServerConfig::Read(bool bail) +{ + errstr.clear(); + // These tags MUST occur and must ONLY occur once in the config file + static const char *Once[] = {NULL}; + // These tags can occur ONCE or not at all + InitialConfig Values[] = { + {NULL, NULL, NULL, NULL, DT_NOTHING, NoValidation} + }; + /* These tags can occur multiple times, and therefore they have special code to read them + * which is different to the code for reading the singular tags listed above. */ + MultiConfig MultiValues[] = { + {NULL, + {NULL}, + {NULL}, + {0}, + NULL, NULL, NULL} + }; + // Load and parse the config file, if there are any errors then explode + // Make a copy here so if it fails then we can carry on running with an unaffected config + newconfig.clear(); + if (LoadConf(newconfig, SERVICES_CONF, errstr)) { + // If we succeeded, set the ircd config to the new one + config_data = newconfig; + } + else { + ReportConfigError(errstr.str(), bail); + return 0; + } + // The stuff in here may throw CoreException, be sure we're in a position to catch it. + try { + // Read the values of all the tags which occur once or not at all, and call their callbacks. + for (int Index = 0; Values[Index].tag; ++Index) { + char item[BUFSIZE]; + int dt = Values[Index].datatype; + bool allow_newlines = dt & DT_ALLOW_NEWLINE, allow_wild = dt & DT_ALLOW_WILD; + dt &= ~DT_ALLOW_NEWLINE; + dt &= ~DT_ALLOW_WILD; + ConfValue(config_data, Values[Index].tag, Values[Index].value, Values[Index].default_value, 0, item, BUFSIZE, allow_newlines); + ValueItem vi(item); + if (!Values[Index].validation_function(this, Values[Index].tag, Values[Index].value, vi)) + throw ConfigException("One or more values in your configuration file failed to validate. Please see your ircd.log for more information."); + switch (dt) { + case DT_NOSPACES: { + ValueContainerChar *vcc = dynamic_cast<ValueContainerChar *>(Values[Index].val); + ValidateNoSpaces(vi.GetString(), Values[Index].tag, Values[Index].value); + vcc->Set(vi.GetString(), strlen(vi.GetString()) + 1); + } + break; + case DT_HOSTNAME: { + ValueContainerChar *vcc = dynamic_cast<ValueContainerChar *>(Values[Index].val); + ValidateHostname(vi.GetString(), Values[Index].tag, Values[Index].value); + vcc->Set(vi.GetString(), strlen(vi.GetString()) + 1); + } + break; + case DT_IPADDRESS: { + ValueContainerChar *vcc = dynamic_cast<ValueContainerChar *>(Values[Index].val); + ValidateIP(vi.GetString(), Values[Index].tag, Values[Index].value, allow_wild); + vcc->Set(vi.GetString(), strlen(vi.GetString()) + 1); + } + break; + case DT_CHARPTR: { + ValueContainerChar *vcc = dynamic_cast<ValueContainerChar *>(Values[Index].val); + // Make sure we also copy the null terminator + vcc->Set(vi.GetString(), strlen(vi.GetString()) + 1); + } + break; + case DT_STRING: { + ValueContainerString *vcs = dynamic_cast<ValueContainerString *>(Values[Index].val); + vcs->Set(vi.GetString()); + } + break; + case DT_INTEGER: { + int val = vi.GetInteger(); + ValueContainerInt *vci = dynamic_cast<ValueContainerInt *>(Values[Index].val); + vci->Set(&val, sizeof(int)); + } + break; + case DT_UINTEGER: { + unsigned val = vi.GetInteger(); + ValueContainerUInt *vci = dynamic_cast<ValueContainerUInt *>(Values[Index].val); + vci->Set(&val, sizeof(int)); + } + break; + case DT_TIME: { + time_t time = dotime(vi.GetString()); + ValueContainerTime *vci = dynamic_cast<ValueContainerTime *>(Values[Index].val); + vci->Set(&time, sizeof(time_t)); + } + break; + case DT_BOOLEAN: { + bool val = vi.GetBool(); + ValueContainerBool *vcb = dynamic_cast<ValueContainerBool *>(Values[Index].val); + vcb->Set(&val, sizeof(bool)); + } + break; + default: + break; + } + // We're done with this now + delete Values[Index].val; + } + /* Read the multiple-tag items (class tags, connect tags, etc) + * and call the callbacks associated with them. We have three + * callbacks for these, a 'start', 'item' and 'end' callback. */ + for (int Index = 0; MultiValues[Index].tag; ++Index) { + MultiValues[Index].init_function(this, MultiValues[Index].tag); + int number_of_tags = ConfValueEnum(config_data, MultiValues[Index].tag); + for (int tagnum = 0; tagnum < number_of_tags; ++tagnum) { + ValueList vl; + for (int valuenum = 0; MultiValues[Index].items[valuenum]; ++valuenum) { + int dt = MultiValues[Index].datatype[valuenum]; + bool allow_newlines = dt & DT_ALLOW_NEWLINE, allow_wild = dt & DT_ALLOW_WILD; + dt &= ~DT_ALLOW_NEWLINE; + dt &= ~DT_ALLOW_WILD; + switch (dt) { + case DT_NOSPACES: { + char item[BUFSIZE]; + if (ConfValue(config_data, MultiValues[Index].tag, MultiValues[Index].items[valuenum], + MultiValues[Index].items_default[valuenum], tagnum, item, BUFSIZE, allow_newlines)) { + vl.push_back(ValueItem(item)); + } + else vl.push_back(ValueItem("")); + ValidateNoSpaces(vl[vl.size() - 1].GetString(), MultiValues[Index].tag, MultiValues[Index].items[valuenum]); + } + break; + case DT_HOSTNAME: { + char item[BUFSIZE]; + if (ConfValue(config_data, MultiValues[Index].tag, MultiValues[Index].items[valuenum], + MultiValues[Index].items_default[valuenum], tagnum, item, BUFSIZE, allow_newlines)) { + vl.push_back(ValueItem(item)); + } + else vl.push_back(ValueItem("")); + ValidateHostname(vl[vl.size() - 1].GetString(), MultiValues[Index].tag, MultiValues[Index].items[valuenum]); + } + break; + case DT_IPADDRESS: { + char item[BUFSIZE]; + if (ConfValue(config_data, MultiValues[Index].tag, MultiValues[Index].items[valuenum], + MultiValues[Index].items_default[valuenum], tagnum, item, BUFSIZE, allow_newlines)) { + vl.push_back(ValueItem(item)); + } + else vl.push_back(ValueItem("")); + ValidateIP(vl[vl.size() - 1].GetString(), MultiValues[Index].tag, MultiValues[Index].items[valuenum], allow_wild); + } + break; + case DT_CHARPTR: { + char item[BUFSIZE]; + if (ConfValue(config_data, MultiValues[Index].tag, MultiValues[Index].items[valuenum], + MultiValues[Index].items_default[valuenum], tagnum, item, BUFSIZE, allow_newlines)) { + vl.push_back(ValueItem(item)); + } + else vl.push_back(ValueItem("")); + } + break; + case DT_STRING: { + std::string item; + if (ConfValue(config_data, static_cast<std::string>(MultiValues[Index].tag), + static_cast<std::string>(MultiValues[Index].items[valuenum]), + static_cast<std::string>(MultiValues[Index].items_default[valuenum]), tagnum, item, allow_newlines)) { + vl.push_back(ValueItem(item)); + } + else vl.push_back(ValueItem("")); + } + break; + case DT_INTEGER: + case DT_UINTEGER: { + int item = 0; + if (ConfValueInteger(config_data, MultiValues[Index].tag, MultiValues[Index].items[valuenum], + MultiValues[Index].items_default[valuenum], tagnum, item)) vl.push_back(ValueItem(item)); + else vl.push_back(ValueItem(0)); + } + break; + case DT_TIME: { + std::string item; + if (ConfValue(config_data, static_cast<std::string>(MultiValues[Index].tag), + static_cast<std::string>(MultiValues[Index].items[valuenum]), + static_cast<std::string>(MultiValues[Index].items_default[valuenum]), tagnum, item, allow_newlines)) { + int time = dotime(item.c_str()); + vl.push_back(ValueItem(time)); + } + else vl.push_back(ValueItem(0)); + } + break; + case DT_BOOLEAN: { + bool item = ConfValueBool(config_data, MultiValues[Index].tag, MultiValues[Index].items[valuenum], + MultiValues[Index].items_default[valuenum], tagnum); + vl.push_back(ValueItem(item)); + } + } + } + MultiValues[Index].validation_function(this, MultiValues[Index].tag, static_cast<const char **>(MultiValues[Index].items), vl, + MultiValues[Index].datatype); + } + MultiValues[Index].finish_function(this, MultiValues[Index].tag); + } + } + catch (ConfigException &ce) { + ReportConfigError(ce.GetReason(), bail); + return 0; + } + if (debug) alog("End config"); + for (int Index = 0; Once[Index]; ++Index) if (!CheckOnce(Once[Index])) return 0; + alog("Done reading configuration file."); + return 1; +} + +bool ServerConfig::LoadConf(ConfigDataHash &target, const char *filename, std::ostringstream &errorstream) +{ + std::string line, wordbuffer, section, itemname; + std::ifstream conf(filename); + int linenumber = 0; + bool in_word = false, in_quote = false, in_ml_comment = false; + KeyValList sectiondata; + if (conf.fail()) { + errorstream << "File " << filename << " could not be opened." << std::endl; + return false; + } + if (debug) alog("Start to read conf %s", filename); + // Start reading characters... + while (getline(conf, line)) { + ++linenumber; + unsigned c = 0, len = line.size(); + for (; c < len; ++c) { + char ch = line[c]; + if (in_quote) { + if (ch == '"') { + in_quote = in_word = false; + continue; + } + wordbuffer += ch; + continue; + } + if (in_ml_comment) { + if (ch == '*' && c + 1 < len && line[c + 1] == '/') { + in_ml_comment = false; + ++c; + } + continue; + } + if (ch == '#' || (ch == '/' && c + 1 < len && line[c + 1] == '/')) break; // Line comment, ignore the rest of the line (much like this one!) + else if (ch == '/' && c + 1 < len && line[c + 1] == '*') { + // Multiline (or less than one line) comment + in_ml_comment = true; + ++c; + continue; + } + else if (ch == '"') { + // Quotes are valid only in the value position + if (section.empty() || itemname.empty()) { + errorstream << "Unexpected quoted string: " << filename << ":" << linenumber << std::endl; + return false; + } + if (in_word || !wordbuffer.empty()) { + errorstream << "Unexpected quoted string (prior unhandled words): " << filename << ":" << linenumber << std::endl; + return false; + } + in_quote = in_word = true; + continue; + } + else if (ch == '=') { + if (section.empty()) { + errorstream << "Config item outside of section (or stray '='): " << filename << ":" << linenumber << std::endl; + return false; + } + if (!itemname.empty()) { + errorstream << "Stray '=' sign or item without value: " << filename << ":" << linenumber << std::endl; + return false; + } + if (in_word) in_word = false; + itemname = wordbuffer; + wordbuffer.clear(); + } + else if (ch == '{') { + if (!section.empty()) { + errorstream << "Section inside another section: " << filename << ":" << linenumber << std::endl; + return false; + } + if (wordbuffer.empty()) { + errorstream << "Section without a name or unexpected '{': " << filename << ":" << linenumber << std::endl; + return false; + } + if (in_word) in_word = false; + section = wordbuffer; + wordbuffer.clear(); + } + else if (ch == '}') { + if (section.empty()) { + errorstream << "Stray '}': " << filename << ":" << linenumber << std::endl; + return false; + } + if (!wordbuffer.empty() || !itemname.empty()) { + errorstream << "Unexpected end of section: " << filename << ":" << linenumber << std::endl; + return false; + } + target.insert(std::pair<std::string, KeyValList>(section, sectiondata)); + section.clear(); + sectiondata.clear(); + } + else if (ch == ';' || ch == '\r') continue; // Ignore + else if (ch == ' ' || ch == '\t') { + // Terminate word + if (in_word) in_word = false; + } + else { + if (!in_word && !wordbuffer.empty()) { + errorstream << "Unexpected word: " << filename << ":" << linenumber << std::endl; + return false; + } + wordbuffer += ch; + in_word = true; + } + } + if (in_quote) { + // Quotes can span multiple lines; all we need to do is go to the next line without clearing things + wordbuffer += "\n"; + continue; + } + in_word = false; + if (!itemname.empty()) { + if (wordbuffer.empty()) { + errorstream << "Item without value: " << filename << ":" << linenumber << std::endl; + return false; + } + if (debug) alog("ln %d EOL: s='%s' '%s' set to '%s'", linenumber, section.c_str(), itemname.c_str(), wordbuffer.c_str()); + sectiondata.push_back(KeyVal(itemname, wordbuffer)); + wordbuffer.clear(); + itemname.clear(); + } + } + if (in_ml_comment) { + errorstream << "Unterminated multiline comment at end of file: " << filename << std::endl; + return false; + } + if (in_quote) { + errorstream << "Unterminated quote at end of file: " << filename << std::endl; + return false; + } + if (!itemname.empty() || !wordbuffer.empty()) { + errorstream << "Unexpected garbage at end of file: " << filename << std::endl; + return false; + } + if (!section.empty()) { + errorstream << "Unterminated section at end of file: " << filename << std::endl; + return false; + } + return true; +} + +bool ServerConfig::LoadConf(ConfigDataHash &target, const std::string &filename, std::ostringstream &errorstream) +{ + return LoadConf(target, filename.c_str(), errorstream); +} + +bool ServerConfig::ConfValue(ConfigDataHash &target, const char *tag, const char *var, int index, char *result, int length, bool allow_linefeeds) +{ + return ConfValue(target, tag, var, "", index, result, length, allow_linefeeds); +} + +bool ServerConfig::ConfValue(ConfigDataHash &target, const char *tag, const char *var, const char *default_value, int index, char *result, + int length, bool allow_linefeeds) +{ + std::string value; + bool r = ConfValue(target, static_cast<std::string>(tag), static_cast<std::string>(var), static_cast<std::string>(default_value), index, value, + allow_linefeeds); + strlcpy(result, value.c_str(), length); + return r; +} + +bool ServerConfig::ConfValue(ConfigDataHash &target, const std::string &tag, const std::string &var, int index, std::string &result, + bool allow_linefeeds) +{ + return ConfValue(target, tag, var, "", index, result, allow_linefeeds); +} + +bool ServerConfig::ConfValue(ConfigDataHash &target, const std::string &tag, const std::string &var, const std::string &default_value, int index, + std::string &result, bool allow_linefeeds) +{ + ConfigDataHash::size_type pos = index; + if (pos < target.count(tag)) { + ConfigDataHash::iterator iter = target.find(tag); + for (int i = 0; i < index; ++i) ++iter; + KeyValList::iterator j = iter->second.begin(), jend = iter->second.end(); + for (; j != jend; ++j) { + if (j->first == var) { + if (!allow_linefeeds && j->second.find('\n') != std::string::npos) { + alog("Value of <%s:%s> contains a linefeed, and linefeeds in this value are not permitted -- stripped to spaces.", tag.c_str(), var.c_str()); + std::string::iterator n = j->second.begin(), nend = j->second.end(); + for (; n != nend; ++n) if (*n == '\n') *n = ' '; + } + else { + result = j->second; + return true; + } + } + } + if (!default_value.empty()) { + result = default_value; + return true; + } + } + else if (!pos) { + if (!default_value.empty()) { + result = default_value; + return true; + } + } + return false; +} + +bool ServerConfig::ConfValueInteger(ConfigDataHash &target, const char *tag, const char *var, int index, int &result) +{ + return ConfValueInteger(target, static_cast<std::string>(tag), static_cast<std::string>(var), "", index, result); +} + +bool ServerConfig::ConfValueInteger(ConfigDataHash &target, const char *tag, const char *var, const char *default_value, int index, int &result) +{ + return ConfValueInteger(target, static_cast<std::string>(tag), static_cast<std::string>(var), static_cast<std::string>(default_value), index, + result); +} + +bool ServerConfig::ConfValueInteger(ConfigDataHash &target, const std::string &tag, const std::string &var, int index, int &result) +{ + return ConfValueInteger(target, tag, var, "", index, result); +} + +bool ServerConfig::ConfValueInteger(ConfigDataHash &target, const std::string &tag, const std::string &var, const std::string &default_value, int index, int &result) +{ + std::string value; + std::istringstream stream; + bool r = ConfValue(target, tag, var, default_value, index, value); + stream.str(value); + if (!(stream >> result)) return false; + else { + if (!value.empty()) { + if (value.substr(0, 2) == "0x") { + char *endptr; + value.erase(0, 2); + result = strtol(value.c_str(), &endptr, 16); + /* No digits found */ + if (endptr == value.c_str()) return false; + } + else { + char denominator = *(value.end() - 1); + switch (toupper(denominator)) { + case 'K': + // Kilobytes -> bytes + result = result * 1024; + break; + case 'M': + // Megabytes -> bytes + result = result * 1048576; + break; + case 'G': + // Gigabytes -> bytes + result = result * 1073741824; + break; + } + } + } + } + return r; +} + +bool ServerConfig::ConfValueBool(ConfigDataHash &target, const char *tag, const char *var, int index) +{ + return ConfValueBool(target, static_cast<std::string>(tag), static_cast<std::string>(var), "", index); +} + +bool ServerConfig::ConfValueBool(ConfigDataHash &target, const char *tag, const char *var, const char *default_value, int index) +{ + return ConfValueBool(target, static_cast<std::string>(tag), static_cast<std::string>(var), static_cast<std::string>(default_value), index); +} + +bool ServerConfig::ConfValueBool(ConfigDataHash &target, const std::string &tag, const std::string &var, int index) +{ + return ConfValueBool(target, tag, var, "", index); +} + +bool ServerConfig::ConfValueBool(ConfigDataHash &target, const std::string &tag, const std::string &var, const std::string &default_value, int index) +{ + std::string result; + if (!ConfValue(target, tag, var, default_value, index, result)) return false; + return result == "yes" || result == "true" || result == "1"; +} + +int ServerConfig::ConfValueEnum(ConfigDataHash &target, const char *tag) +{ + return target.count(tag); +} + +int ServerConfig::ConfValueEnum(ConfigDataHash &target, const std::string &tag) +{ + return target.count(tag); +} + +int ServerConfig::ConfVarEnum(ConfigDataHash &target, const char *tag, int index) +{ + return ConfVarEnum(target, static_cast<std::string>(tag), index); +} + +int ServerConfig::ConfVarEnum(ConfigDataHash &target, const std::string &tag, int index) +{ + ConfigDataHash::size_type pos = index; + if (pos < target.count(tag)) { + ConfigDataHash::const_iterator iter = target.find(tag); + for (int i = 0; i < index; ++i) ++iter; + return iter->second.size(); + } + return 0; +} + +ValueItem::ValueItem(int value) : v("") +{ + std::stringstream n; + n << value; + v = n.str(); +} + +ValueItem::ValueItem(bool value) : v("") +{ + std::stringstream n; + n << value; + v = n.str(); +} + +ValueItem::ValueItem(const char *value) : v(value) { } + +ValueItem::ValueItem(const std::string &value) : v(value) { } + +void ValueItem::Set(const char *value) +{ + v = value; +} + +void ValueItem::Set(const std::string &value) +{ + v = value; +} + +void ValueItem::Set(int value) +{ + std::stringstream n; + n << value; + v = n.str(); +} + +int ValueItem::GetInteger() +{ + if (v.empty()) return 0; + return atoi(v.c_str()); +} + +char *ValueItem::GetString() +{ + return const_cast<char *>(v.c_str()); +} + +bool ValueItem::GetBool() +{ + return GetInteger() || v == "yes" || v == "true"; +} + +/*************************************************************************/ + /* Deprecated directive (dep_) and value checking (chk_) functions: */ /* Hey, there are no left! -GD */ |