anope/src/config.cpp

948 lines
27 KiB
C++

/* Configuration file handling.
*
* (C) 2003-2022 Anope Team
* Contact us at team@anope.org
*
* 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.
*/
#include "services.h"
#include "config.h"
#include "bots.h"
#include "access.h"
#include "opertype.h"
#include "channels.h"
#include "hashcomp.h"
using namespace Configuration;
File ServicesConf("services.conf", false); // Services configuration file name
Conf *Config = NULL;
Block::Block(const Anope::string &n) : name(n), linenum(-1)
{
}
const Anope::string &Block::GetName() const
{
return name;
}
int Block::CountBlock(const Anope::string &bname)
{
if (!this)
return 0;
return blocks.count(bname);
}
Block* Block::GetBlock(const Anope::string &bname, int num)
{
if (!this)
return NULL;
std::pair<block_map::iterator, block_map::iterator> it = blocks.equal_range(bname);
for (int i = 0; it.first != it.second; ++it.first, ++i)
if (i == num)
return &it.first->second;
return NULL;
}
bool Block::Set(const Anope::string &tag, const Anope::string &value)
{
if (!this)
return false;
items[tag] = value;
return true;
}
const Block::item_map* Block::GetItems() const
{
if (this)
return &items;
else
return NULL;
}
template<> const Anope::string Block::Get(const Anope::string &tag, const Anope::string& def) const
{
if (!this)
return def;
Anope::map<Anope::string>::const_iterator it = items.find(tag);
if (it != items.end())
return it->second;
return def;
}
template<> time_t Block::Get(const Anope::string &tag, const Anope::string &def) const
{
return Anope::DoTime(Get<const Anope::string>(tag, def));
}
template<> bool Block::Get(const Anope::string &tag, const Anope::string &def) const
{
const Anope::string &str = Get<const Anope::string>(tag, def);
return !str.empty() && !str.equals_ci("no") && !str.equals_ci("off") && !str.equals_ci("false") && !str.equals_ci("0");
}
static void ValidateNotEmpty(const Anope::string &block, const Anope::string &name, const Anope::string &value)
{
if (value.empty())
throw ConfigException("The value for <" + block + ":" + name + "> cannot be empty!");
}
static void ValidateNoSpaces(const Anope::string &block, const Anope::string &name, const Anope::string &value)
{
if (value.find(' ') != Anope::string::npos)
throw ConfigException("The value for <" + block + ":" + name + "> may not contain spaces!");
}
static void ValidateNotEmptyOrSpaces(const Anope::string &block, const Anope::string &name, const Anope::string &value)
{
ValidateNotEmpty(block, name, value);
ValidateNoSpaces(block, name, value);
}
template<typename T> static void ValidateNotZero(const Anope::string &block, const Anope::string &name, T value)
{
if (!value)
throw ConfigException("The value for <" + block + ":" + name + "> cannot be zero!");
}
Conf::Conf() : Block("")
{
ReadTimeout = 0;
UsePrivmsg = DefPrivmsg = false;
this->LoadConf(ServicesConf);
for (int i = 0; i < this->CountBlock("include"); ++i)
{
Block *include = this->GetBlock("include", i);
const Anope::string &type = include->Get<const Anope::string>("type"),
&file = include->Get<const Anope::string>("name");
File f(file, type == "executable");
this->LoadConf(f);
}
FOREACH_MOD(OnReload, (this));
/* Check for modified values that aren't allowed to be modified */
if (Config)
{
struct
{
Anope::string block;
Anope::string name;
} noreload[] = {
{"serverinfo", "name"},
{"serverinfo", "description"},
{"serverinfo", "localhost"},
{"serverinfo", "id"},
{"serverinfo", "pid"},
{"networkinfo", "nicklen"},
{"networkinfo", "userlen"},
{"networkinfo", "hostlen"},
{"networkinfo", "chanlen"},
};
for (unsigned i = 0; i < sizeof(noreload) / sizeof(noreload[0]); ++i)
if (this->GetBlock(noreload[i].block)->Get<const Anope::string>(noreload[i].name) != Config->GetBlock(noreload[i].block)->Get<const Anope::string>(noreload[i].name))
throw ConfigException("<" + noreload[i].block + ":" + noreload[i].name + "> can not be modified once set");
}
Block *serverinfo = this->GetBlock("serverinfo"), *options = this->GetBlock("options"),
*mail = this->GetBlock("mail"), *networkinfo = this->GetBlock("networkinfo");
const Anope::string &servername = serverinfo->Get<Anope::string>("name");
ValidateNotEmptyOrSpaces("serverinfo", "name", servername);
if (servername.find(' ') != Anope::string::npos || servername.find('.') == Anope::string::npos)
throw ConfigException("serverinfo:name is not a valid server name");
ValidateNotEmpty("serverinfo", "description", serverinfo->Get<const Anope::string>("description"));
ValidateNotEmpty("serverinfo", "pid", serverinfo->Get<const Anope::string>("pid"));
ValidateNotEmpty("serverinfo", "motd", serverinfo->Get<const Anope::string>("motd"));
ValidateNotZero("options", "readtimeout", options->Get<time_t>("readtimeout"));
ValidateNotZero("options", "warningtimeout", options->Get<time_t>("warningtimeout"));
ValidateNotZero("networkinfo", "nicklen", networkinfo->Get<unsigned>("nicklen"));
ValidateNotZero("networkinfo", "userlen", networkinfo->Get<unsigned>("userlen"));
ValidateNotZero("networkinfo", "hostlen", networkinfo->Get<unsigned>("hostlen"));
ValidateNotZero("networkinfo", "chanlen", networkinfo->Get<unsigned>("chanlen"));
spacesepstream(options->Get<const Anope::string>("ulineservers")).GetTokens(this->Ulines);
if (mail->Get<bool>("usemail"))
{
Anope::string check[] = { "sendmailpath", "sendfrom", "registration_subject", "registration_message", "emailchange_subject", "emailchange_message", "memo_subject", "memo_message" };
for (unsigned i = 0; i < sizeof(check) / sizeof(Anope::string); ++i)
ValidateNotEmpty("mail", check[i], mail->Get<const Anope::string>(check[i]));
}
this->ReadTimeout = options->Get<time_t>("readtimeout");
this->UsePrivmsg = options->Get<bool>("useprivmsg");
this->UseStrictPrivmsg = options->Get<bool>("usestrictprivmsg");
this->StrictPrivmsg = !UseStrictPrivmsg ? "/msg " : "/";
{
std::vector<Anope::string> defaults;
spacesepstream(this->GetModule("nickserv")->Get<const Anope::string>("defaults")).GetTokens(defaults);
this->DefPrivmsg = std::find(defaults.begin(), defaults.end(), "msg") != defaults.end();
}
this->DefLanguage = options->Get<const Anope::string>("defaultlanguage");
this->TimeoutCheck = options->Get<time_t>("timeoutcheck");
this->NickChars = networkinfo->Get<Anope::string>("nick_chars");
for (int i = 0; i < this->CountBlock("uplink"); ++i)
{
Block *uplink = this->GetBlock("uplink", i);
const Anope::string &host = uplink->Get<const Anope::string>("host");
bool ipv6 = uplink->Get<bool>("ipv6");
int port = uplink->Get<int>("port");
const Anope::string &password = uplink->Get<const Anope::string>("password");
ValidateNotEmptyOrSpaces("uplink", "host", host);
ValidateNotZero("uplink", "port", port);
ValidateNotEmptyOrSpaces("uplink", "password", password);
if (password.find(' ') != Anope::string::npos || password[0] == ':')
throw ConfigException("uplink:password is not valid");
this->Uplinks.push_back(Uplink(host, port, password, ipv6));
}
for (int i = 0; i < this->CountBlock("module"); ++i)
{
Block *module = this->GetBlock("module", i);
const Anope::string &modname = module->Get<const Anope::string>("name");
ValidateNotEmptyOrSpaces("module", "name", modname);
this->ModulesAutoLoad.push_back(modname);
}
for (int i = 0; i < this->CountBlock("opertype"); ++i)
{
Block *opertype = this->GetBlock("opertype", i);
const Anope::string &oname = opertype->Get<const Anope::string>("name"),
&modes = opertype->Get<const Anope::string>("modes"),
&inherits = opertype->Get<const Anope::string>("inherits"),
&commands = opertype->Get<const Anope::string>("commands"),
&privs = opertype->Get<const Anope::string>("privs");
ValidateNotEmpty("opertype", "name", oname);
OperType *ot = new OperType(oname);
ot->modes = modes;
spacesepstream cmdstr(commands);
for (Anope::string str; cmdstr.GetToken(str);)
ot->AddCommand(str);
spacesepstream privstr(privs);
for (Anope::string str; privstr.GetToken(str);)
ot->AddPriv(str);
commasepstream inheritstr(inherits);
for (Anope::string str; inheritstr.GetToken(str);)
{
/* Strip leading ' ' after , */
if (str.length() > 1 && str[0] == ' ')
str.erase(str.begin());
for (unsigned j = 0; j < this->MyOperTypes.size(); ++j)
{
OperType *ot2 = this->MyOperTypes[j];
if (ot2->GetName().equals_ci(str))
{
Log() << "Inheriting commands and privs from " << ot2->GetName() << " to " << ot->GetName();
ot->Inherits(ot2);
break;
}
}
}
this->MyOperTypes.push_back(ot);
}
for (int i = 0; i < this->CountBlock("oper"); ++i)
{
Block *oper = this->GetBlock("oper", i);
const Anope::string &nname = oper->Get<const Anope::string>("name"),
&type = oper->Get<const Anope::string>("type"),
&password = oper->Get<const Anope::string>("password"),
&certfp = oper->Get<const Anope::string>("certfp"),
&host = oper->Get<const Anope::string>("host"),
&vhost = oper->Get<const Anope::string>("vhost");
bool require_oper = oper->Get<bool>("require_oper");
ValidateNotEmptyOrSpaces("oper", "name", nname);
ValidateNotEmpty("oper", "type", type);
OperType *ot = NULL;
for (unsigned j = 0; j < this->MyOperTypes.size(); ++j)
if (this->MyOperTypes[j]->GetName() == type)
ot = this->MyOperTypes[j];
if (ot == NULL)
throw ConfigException("Oper block for " + nname + " has invalid oper type " + type);
Oper *o = new Oper(nname, ot);
o->require_oper = require_oper;
o->password = password;
o->certfp = certfp;
spacesepstream(host).GetTokens(o->hosts);
o->vhost = vhost;
this->Opers.push_back(o);
}
for (botinfo_map::const_iterator it = BotListByNick->begin(), it_end = BotListByNick->end(); it != it_end; ++it)
it->second->conf = false;
for (int i = 0; i < this->CountBlock("service"); ++i)
{
Block *service = this->GetBlock("service", i);
const Anope::string &nick = service->Get<const Anope::string>("nick"),
&user = service->Get<const Anope::string>("user"),
&host = service->Get<const Anope::string>("host"),
&gecos = service->Get<const Anope::string>("gecos"),
&modes = service->Get<const Anope::string>("modes"),
&channels = service->Get<const Anope::string>("channels");
ValidateNotEmptyOrSpaces("service", "nick", nick);
ValidateNotEmptyOrSpaces("service", "user", user);
ValidateNotEmptyOrSpaces("service", "host", host);
ValidateNotEmpty("service", "gecos", gecos);
ValidateNoSpaces("service", "channels", channels);
BotInfo *bi = BotInfo::Find(nick, true);
if (!bi)
bi = new BotInfo(nick, user, host, gecos, modes);
bi->conf = true;
std::vector<Anope::string> oldchannels = bi->botchannels;
bi->botchannels.clear();
commasepstream sep(channels);
for (Anope::string token; sep.GetToken(token);)
{
bi->botchannels.push_back(token);
size_t ch = token.find('#');
Anope::string chname, want_modes;
if (ch == Anope::string::npos)
chname = token;
else
{
want_modes = token.substr(0, ch);
chname = token.substr(ch);
}
bi->Join(chname);
Channel *c = Channel::Find(chname);
if (!c)
continue; // Can't happen
c->botchannel = true;
/* Remove all existing modes */
ChanUserContainer *cu = c->FindUser(bi);
if (cu != NULL)
for (size_t j = 0; j < cu->status.Modes().length(); ++j)
c->RemoveMode(bi, ModeManager::FindChannelModeByChar(cu->status.Modes()[j]), bi->GetUID());
/* Set the new modes */
for (unsigned j = 0; j < want_modes.length(); ++j)
{
ChannelMode *cm = ModeManager::FindChannelModeByChar(want_modes[j]);
if (cm == NULL)
cm = ModeManager::FindChannelModeByChar(ModeManager::GetStatusChar(want_modes[j]));
if (cm && cm->type == MODE_STATUS)
c->SetMode(bi, cm, bi->GetUID());
}
}
for (unsigned k = 0; k < oldchannels.size(); ++k)
{
size_t ch = oldchannels[k].find('#');
Anope::string chname = oldchannels[k].substr(ch != Anope::string::npos ? ch : 0);
bool found = false;
for (unsigned j = 0; j < bi->botchannels.size(); ++j)
{
ch = bi->botchannels[j].find('#');
Anope::string ochname = bi->botchannels[j].substr(ch != Anope::string::npos ? ch : 0);
if (chname.equals_ci(ochname))
found = true;
}
if (found)
continue;
Channel *c = Channel::Find(chname);
if (c)
{
c->botchannel = false;
bi->Part(c);
}
}
}
for (int i = 0; i < this->CountBlock("log"); ++i)
{
Block *log = this->GetBlock("log", i);
int logage = log->Get<int>("logage");
bool rawio = log->Get<bool>("rawio");
bool debug = log->Get<bool>("debug");
LogInfo l(logage, rawio, debug);
l.bot = BotInfo::Find(log->Get<const Anope::string>("bot", "Global"), true);
spacesepstream(log->Get<const Anope::string>("target")).GetTokens(l.targets);
spacesepstream(log->Get<const Anope::string>("source")).GetTokens(l.sources);
spacesepstream(log->Get<const Anope::string>("admin")).GetTokens(l.admin);
spacesepstream(log->Get<const Anope::string>("override")).GetTokens(l.override);
spacesepstream(log->Get<const Anope::string>("commands")).GetTokens(l.commands);
spacesepstream(log->Get<const Anope::string>("servers")).GetTokens(l.servers);
spacesepstream(log->Get<const Anope::string>("channels")).GetTokens(l.channels);
spacesepstream(log->Get<const Anope::string>("users")).GetTokens(l.users);
spacesepstream(log->Get<const Anope::string>("other")).GetTokens(l.normal);
this->LogInfos.push_back(l);
}
for (botinfo_map::const_iterator it = BotListByNick->begin(), it_end = BotListByNick->end(); it != it_end; ++it)
it->second->commands.clear();
for (int i = 0; i < this->CountBlock("command"); ++i)
{
Block *command = this->GetBlock("command", i);
const Anope::string &service = command->Get<const Anope::string>("service"),
&nname = command->Get<const Anope::string>("name"),
&cmd = command->Get<const Anope::string>("command"),
&permission = command->Get<const Anope::string>("permission"),
&group = command->Get<const Anope::string>("group");
bool hide = command->Get<bool>("hide");
ValidateNotEmptyOrSpaces("command", "service", service);
ValidateNotEmpty("command", "name", nname);
ValidateNotEmptyOrSpaces("command", "command", cmd);
BotInfo *bi = this->GetClient(service);
if (!bi)
continue;
CommandInfo &ci = bi->SetCommand(nname, cmd, permission);
ci.group = group;
ci.hide = hide;
}
PrivilegeManager::ClearPrivileges();
for (int i = 0; i < this->CountBlock("privilege"); ++i)
{
Block *privilege = this->GetBlock("privilege", i);
const Anope::string &nname = privilege->Get<const Anope::string>("name"),
&desc = privilege->Get<const Anope::string>("desc");
int rank = privilege->Get<int>("rank");
PrivilegeManager::AddPrivilege(Privilege(nname, desc, rank));
}
for (int i = 0; i < this->CountBlock("fantasy"); ++i)
{
Block *fantasy = this->GetBlock("fantasy", i);
const Anope::string &nname = fantasy->Get<const Anope::string>("name"),
&service = fantasy->Get<const Anope::string>("command"),
&permission = fantasy->Get<const Anope::string>("permission"),
&group = fantasy->Get<const Anope::string>("group");
bool hide = fantasy->Get<bool>("hide"),
prepend_channel = fantasy->Get<bool>("prepend_channel", "yes");
ValidateNotEmptyOrSpaces("fantasy", "name", nname);
ValidateNotEmptyOrSpaces("fantasy", "command", service);
CommandInfo &c = this->Fantasy[nname];
c.name = service;
c.permission = permission;
c.group = group;
c.hide = hide;
c.prepend_channel = prepend_channel;
}
for (int i = 0; i < this->CountBlock("command_group"); ++i)
{
Block *command_group = this->GetBlock("command_group", i);
const Anope::string &nname = command_group->Get<const Anope::string>("name"),
&description = command_group->Get<const Anope::string>("description");
CommandGroup gr;
gr.name = nname;
gr.description = description;
this->CommandGroups.push_back(gr);
}
/* Below here can't throw */
if (Config)
/* Clear existing conf opers */
for (nickcore_map::const_iterator it = NickCoreList->begin(), it_end = NickCoreList->end(); it != it_end; ++it)
{
NickCore *nc = it->second;
if (nc->o && std::find(Config->Opers.begin(), Config->Opers.end(), nc->o) != Config->Opers.end())
nc->o = NULL;
}
/* Apply new opers */
for (unsigned i = 0; i < this->Opers.size(); ++i)
{
Oper *o = this->Opers[i];
NickAlias *na = NickAlias::Find(o->name);
if (!na)
continue;
if (!na->nc || na->nc->o)
{
// If the account is already an oper it might mean two oper blocks for the same nick, or
// something else has configured them as an oper (like a module)
continue;
}
na->nc->o = o;
Log() << "Tied oper " << na->nc->display << " to type " << o->ot->GetName();
}
if (options->Get<const Anope::string>("casemap", "ascii") == "ascii")
Anope::casemap = std::locale(std::locale(), new Anope::ascii_ctype<char>());
else if (options->Get<const Anope::string>("casemap") == "rfc1459")
Anope::casemap = std::locale(std::locale(), new Anope::rfc1459_ctype<char>());
else
{
try
{
Anope::casemap = std::locale(options->Get<const Anope::string>("casemap").c_str());
}
catch (const std::runtime_error &)
{
Log() << "Unknown casemap " << options->Get<const Anope::string>("casemap") << " - casemap not changed";
}
}
Anope::CaseMapRebuild();
/* Check the user keys */
if (!options->Get<unsigned>("seed"))
Log() << "Configuration option options:seed should be set. It's for YOUR safety! Remember that!";
}
Conf::~Conf()
{
for (unsigned i = 0; i < MyOperTypes.size(); ++i)
delete MyOperTypes[i];
for (unsigned i = 0; i < Opers.size(); ++i)
delete Opers[i];
}
void Conf::Post(Conf *old)
{
/* Apply module changes */
for (unsigned i = 0; i < old->ModulesAutoLoad.size(); ++i)
if (std::find(this->ModulesAutoLoad.begin(), this->ModulesAutoLoad.end(), old->ModulesAutoLoad[i]) == this->ModulesAutoLoad.end())
ModuleManager::UnloadModule(ModuleManager::FindModule(old->ModulesAutoLoad[i]), NULL);
for (unsigned i = 0; i < this->ModulesAutoLoad.size(); ++i)
if (std::find(old->ModulesAutoLoad.begin(), old->ModulesAutoLoad.end(), this->ModulesAutoLoad[i]) == old->ModulesAutoLoad.end())
ModuleManager::LoadModule(this->ModulesAutoLoad[i], NULL);
/* Apply opertype changes, as non-conf opers still point to the old oper types */
for (unsigned i = Oper::opers.size(); i > 0; --i)
{
Oper *o = Oper::opers[i - 1];
/* Oper's type is in the old config, so update it */
if (std::find(old->MyOperTypes.begin(), old->MyOperTypes.end(), o->ot) != old->MyOperTypes.end())
{
OperType *ot = o->ot;
o->ot = NULL;
for (unsigned j = 0; j < MyOperTypes.size(); ++j)
if (ot->GetName() == MyOperTypes[j]->GetName())
o->ot = MyOperTypes[j];
if (o->ot == NULL)
{
/* Oper block has lost type */
std::vector<Oper *>::iterator it = std::find(old->Opers.begin(), old->Opers.end(), o);
if (it != old->Opers.end())
old->Opers.erase(it);
it = std::find(this->Opers.begin(), this->Opers.end(), o);
if (it != this->Opers.end())
this->Opers.erase(it);
delete o;
}
}
}
}
Block *Conf::GetModule(Module *m)
{
if (!m)
return NULL;
return GetModule(m->name);
}
Block *Conf::GetModule(const Anope::string &mname)
{
std::map<Anope::string, Block *>::iterator it = modules.find(mname);
if (it != modules.end())
return it->second;
Block* &block = modules[mname];
/* Search for the block */
for (std::pair<block_map::iterator, block_map::iterator> iters = blocks.equal_range("module"); iters.first != iters.second; ++iters.first)
{
Block *b = &iters.first->second;
if (b->Get<const Anope::string>("name") == mname)
{
block = b;
break;
}
}
return GetModule(mname);
}
BotInfo *Conf::GetClient(const Anope::string &cname)
{
Anope::map<Anope::string>::iterator it = bots.find(cname);
if (it != bots.end())
return BotInfo::Find(!it->second.empty() ? it->second : cname, true);
Block *block = GetModule(cname.lower());
const Anope::string &client = block->Get<const Anope::string>("client");
bots[cname] = client;
return GetClient(cname);
}
Block *Conf::GetCommand(CommandSource &source)
{
const Anope::string &block_name = source.c ? "fantasy" : "command";
for (std::pair<block_map::iterator, block_map::iterator> iters = blocks.equal_range(block_name); iters.first != iters.second; ++iters.first)
{
Block *b = &iters.first->second;
if (b->Get<Anope::string>("name") == source.command)
return b;
}
return NULL;
}
File::File(const Anope::string &n, bool e) : name(n), executable(e), fp(NULL)
{
}
File::~File()
{
this->Close();
}
const Anope::string &File::GetName() const
{
return this->name;
}
Anope::string File::GetPath() const
{
return (this->executable ? "" : Anope::ConfigDir + "/") + this->name;
}
bool File::IsOpen() const
{
return this->fp != NULL;
}
bool File::Open()
{
this->Close();
this->fp = (this->executable ? popen(this->name.c_str(), "r") : fopen((Anope::ConfigDir + "/" + this->name).c_str(), "r"));
return this->fp != NULL;
}
void File::Close()
{
if (this->fp != NULL)
{
if (this->executable)
pclose(this->fp);
else
fclose(this->fp);
this->fp = NULL;
}
}
bool File::End() const
{
return !this->IsOpen() || feof(this->fp);
}
Anope::string File::Read()
{
Anope::string ret;
char buf[BUFSIZE];
while (fgets(buf, sizeof(buf), this->fp) != NULL)
{
char *nl = strchr(buf, '\n');
if (nl != NULL)
*nl = 0;
else if (!this->End())
{
ret += buf;
continue;
}
ret = buf;
break;
}
return ret;
}
void Conf::LoadConf(File &file)
{
if (file.GetName().empty())
return;
if (!file.Open())
throw ConfigException("File " + file.GetPath() + " could not be opened.");
Anope::string itemname, wordbuffer;
std::stack<Block *> block_stack;
int linenumber = 0;
bool in_word = false, in_quote = false, in_comment = false;
Log(LOG_DEBUG) << "Start to read conf " << file.GetPath();
// Start reading characters...
while (!file.End())
{
Anope::string line = file.Read();
++linenumber;
/* If this line is completely empty and we are in a quote, just append a newline */
if (line.empty() && in_quote)
wordbuffer += "\n";
for (unsigned c = 0, len = line.length(); c < len; ++c)
{
char ch = line[c];
if (in_quote)
{
/* Strip leading white spaces from multi line quotes */
if (c == 0)
{
while (c < len && isspace(line[c]))
++c;
ch = line[c];
}
/* Allow \" in quotes */
if (ch == '\\' && c + 1 < len && line[c + 1] == '"')
wordbuffer += line[++c];
else if (ch == '"')
in_quote = in_word = false;
else if (ch)
wordbuffer += ch;
}
else if (in_comment)
{
if (ch == '*' && c + 1 < len && line[c + 1] == '/')
{
in_comment = false;
++c;
// We might be at an eol, so continue on and process it
}
else
continue;
}
else if (ch == '#' || (ch == '/' && c + 1 < len && line[c + 1] == '/'))
c = len - 1; // 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_comment = true;
++c;
continue;
}
else if (!in_word && (ch == '(' || ch == '_' || ch == ')'))
;
else if (ch == '"')
{
// Quotes are valid only in the value position
if (block_stack.empty() || itemname.empty())
{
file.Close();
throw ConfigException("Unexpected quoted string: " + file.GetName() + ":" + stringify(linenumber));
}
if (in_word || !wordbuffer.empty())
{
file.Close();
throw ConfigException("Unexpected quoted string (prior unhandled words): " + file.GetName() + ":" + stringify(linenumber));
}
in_quote = in_word = true;
}
else if (ch == '=')
{
if (block_stack.empty())
{
file.Close();
throw ConfigException("Config item outside of section (or stray '='): " + file.GetName() + ":" + stringify(linenumber));
}
if (!itemname.empty() || wordbuffer.empty())
{
file.Close();
throw ConfigException("Stray '=' sign or item without value: " + file.GetName() + ":" + stringify(linenumber));
}
in_word = false;
itemname = wordbuffer;
wordbuffer.clear();
}
else if (ch == '{')
{
if (wordbuffer.empty())
{
block_stack.push(NULL);
// Commented or unnamed section
continue;
}
if (!block_stack.empty() && !block_stack.top())
{
// Named block inside of a commented block
in_word = false;
wordbuffer.clear();
block_stack.push(NULL);
continue;
}
Block *b = block_stack.empty() ? this : block_stack.top();
block_map::iterator it = b->blocks.insert(std::make_pair(wordbuffer, Configuration::Block(wordbuffer)));
b = &it->second;
b->linenum = linenumber;
block_stack.push(b);
in_word = false;
wordbuffer.clear();
continue;
}
else if (ch == ' ' || ch == '\r' || ch == '\t')
{
// Terminate word
in_word = false;
}
else if (ch == ';' || ch == '}')
;
else
{
if (!in_word && !wordbuffer.empty())
{
file.Close();
throw ConfigException("Unexpected word: " + file.GetName() + ":" + stringify(linenumber));
}
wordbuffer += ch;
in_word = true;
}
if (ch == ';' || ch == '}' || c + 1 >= len)
{
bool eol = c + 1 >= len;
if (!eol && in_quote)
// Allow ; and } in quoted strings
continue;
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 (block_stack.empty())
{
file.Close();
throw ConfigException("Stray ';' outside of block: " + file.GetName() + ":" + stringify(linenumber));
}
Block *b = block_stack.top();
if (b)
Log(LOG_DEBUG) << "ln " << linenumber << " EOL: s='" << b->name << "' '" << itemname << "' set to '" << wordbuffer << "'";
/* Check defines */
for (int i = 0; i < this->CountBlock("define"); ++i)
{
Block *define = this->GetBlock("define", i);
const Anope::string &dname = define->Get<const Anope::string>("name");
if (dname == wordbuffer && define != b)
wordbuffer = define->Get<const Anope::string>("value");
}
if (b)
b->items[itemname] = wordbuffer;
wordbuffer.clear();
itemname.clear();
}
if (ch == '}')
{
if (block_stack.empty())
{
file.Close();
throw ConfigException("Stray '}': " + file.GetName() + ":" + stringify(linenumber));
}
block_stack.pop();
}
}
}
}
file.Close();
if (in_comment)
throw ConfigException("Unterminated multiline comment at end of file: " + file.GetName());
if (in_quote)
throw ConfigException("Unterminated quote at end of file: " + file.GetName());
if (!itemname.empty() || !wordbuffer.empty())
throw ConfigException("Unexpected garbage at end of file: " + file.GetName());
if (!block_stack.empty())
throw ConfigException("Unterminated block at end of file: " + file.GetName() + ". Block was opened on line " + stringify(block_stack.top()->linenum));
}