ircd/config.cpp
#include "config.hpp"
#include <assert.h>
#define CONFIG_FLAG(name, dflt, mult, spn_s, spn_e, help) .name = { STRINGIFY(name), dflt, mult, NULL, std::vector<const char*>(), spn_s, spn_e, help }
#define for_each(new_node) for (config_node_t* new_node = (config_node_t*)&config; new_node < (config_node_t*)&config + sizeof(config_t)/sizeof(config_node_t); new_node++)
config_t config = {
CONFIG_FLAG(port, "", true, "123456789", "0123456789", "port to listen on"),
CONFIG_FLAG(server_name, "", false, NULL, "0123456789abcdefghijklmnopqrstuvwxyz._-", "irc server/domain name"),
CONFIG_FLAG(server_pw, NULL, false, NULL, NULL, "global server password"),
CONFIG_FLAG(oper_pw, NULL, false, NULL, NULL, "global oper password"),
CONFIG_FLAG(channel, NULL, true, "@+#", NULL, "pre-defined persistent channel. #-prefixed for normal channel, @-prefixed for oper-only, +-prefixed for voice/nickserv-only. supports an appended :topic."),
CONFIG_FLAG(oper_channels, "0", false, "01", "", "enable/disable (0|1) the creation of new channels for operators."),
CONFIG_FLAG(nickserv_dir, NULL, false, NULL, NULL, "nickserv db directory. disabled if not set."),
CONFIG_FLAG(chroot_user, "nobody", false, NULL, "abcdefghijklmnopqrstuvwxyz-", "username to drop to when starting as root"),
CONFIG_FLAG(chroot_dir, NULL, false, "/", NULL, "directory to chroot to when starting as root"),
#ifdef USE_OPENSSL
CONFIG_FLAG(ssl, "0", false, "01", "", "enable/disable (0|1) ssl support for all connections."), // would make not much sense e.g. for a particular port only security-wise
CONFIG_FLAG(ssl_key, NULL, false, NULL, NULL, "private key when using ssl"),
CONFIG_FLAG(ssl_cert, NULL, false, NULL, NULL, "certificate for ssl"),
#else
CONFIG_FLAG(ssl, "0", false, "0", "", "ssl support not available."),
#endif
};
static config_node_t* config_node_find(const char* name) {
for_each (tmp) {
if (strcasecmp(name, tmp->name) == 0) {
return tmp;
}
}
return NULL;
}
static bool config_node_set(const char* name, char* val) {
config_node_t* node = config_node_find(name);
if (!node) {
LOG("unknown option '%s'", name);
return false;
}
if (node->str && !node->multi) {
LOG("option '%s' already set", name);
return false;
}
if (!*val) {
LOG("empty value for option '%s'", name);
return false;
}
if (node->spn_start && strspn(val, node->spn_start) < 1) {
LOG("invalid value for option '%s'", name);
return false;
}
if (node->spn && strspn(val+1, node->spn) != strlen(val)-1) {
LOG("invalid value for option '%s'", name);
return false;
}
node->strs.push_back(strdup(val));
node->str = node->strs.front();
return true;
}
static bool parse_line(char* l) {
while (*l == ' ' || *l == '\t') ++l;
if (*l == '#' || *l == '\r' || *l == '\n' || *l == '\0') return true;
char* p = strchr(l, '\n');
if (p) {
*p = '\0';
}
p = strchr(l, ' ');
if (!p) {
LOG("no config value at: '%s'", l);
return false;
}
*(p++) = '\0';
while (*p == ' ' || *p == '\t') ++p;
return config_node_set(l, p);
}
bool parse_config(const char* fn) {
FILE* fp = fopen(fn, "r");
if (!fp) {
LOG_ERRNO("cannot open %s", fn);
return false;
}
char buf[1024];
while (fgets(buf, sizeof(buf), fp)) {
if (!parse_line(buf)) {
fclose(fp);
free_config();
return false;
}
}
fclose(fp);
for_each (node) {
if (!node->str) {
if (node->dflt) {
if (*(node->dflt)) {
node->strs.push_back(strdup(node->dflt));
node->str = node->strs.front();
} else {
LOG("option '%s' missing", node->name);
free_config();
return false;
}
}
}
}
return true;
}
void log_usage() {
LOG("Supported config flags:");
for_each (node) {
LOG(" * %s (%s)%s: %s",
node->name,
(node->dflt)? (*(node->dflt)? node->dflt: "required"): "optional",
node->multi? " (multi)": "",
node->help
);
}
}
void free_config() {
for_each (node) {
while (!node->strs.empty()) {
free((void*)*node->strs.rbegin());
node->strs.pop_back();
}
node->str = NULL;
}
}