summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/configreader.h378
-rw-r--r--src/config.c705
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 */