1
0
mirror of https://github.com/anope/anope.git synced 2026-07-05 00:33:12 +02:00

Started framework for new config reader, based on hottpd by w00t, copied over from my own fork that was in progress.

git-svn-id: http://anope.svn.sourceforge.net/svnroot/anope/trunk@1409 5417fbe8-f217-4b02-8779-1006273d7864
This commit is contained in:
Naram Qashat cyberbotx@cyberbotx.com
2008-10-03 22:06:58 +00:00
parent f09953561b
commit 6b7161fbdf
2 changed files with 1079 additions and 4 deletions
+378
View File
@@ -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
+701 -4
View File
@@ -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;
@@ -351,6 +352,702 @@ int NumUlines;
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: */