/* * Anope IRC Services * * Copyright (C) 2003-2017 Anope Team * * This file is part of Anope. Anope is free software; you can * redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software * Foundation, version 2. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see see . */ #include "services.h" #include "config.h" #include "opertype.h" #include "channels.h" #include "hashcomp.h" #include "event.h" #include "bots.h" #include "modules.h" #include "servers.h" #include "protocol.h" #include "modules/nickserv.h" using namespace Configuration; File ServicesConf("anope.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) { return blocks.count(bname); } Block* Block::GetBlock(const Anope::string &bname) { auto it = blocks.find(bname); if (it != blocks.end()) return &it->second; blocks.emplace(bname, bname); return GetBlock(bname); } Block* Block::GetBlock(const Anope::string &bname, int num) { std::pair it = blocks.equal_range(bname); for (int i = 0; it.first != it.second; ++it.first, ++i) if (i == num) return &it.first->second; return nullptr; } const Block::item_map* Block::GetItems() const { return &items; } template<> Anope::string Block::Get(const Anope::string &tag, const Anope::string& def) const { Anope::map::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(tag, def)); } template<> bool Block::Get(const Anope::string &tag, const Anope::string &def) const { const Anope::string &str = Get(tag, def); return !str.empty() && !str.equals_ci("no") && !str.equals_ci("off") && !str.equals_ci("false") && !str.equals_ci("0"); } template<> unsigned int Block::Get(const Anope::string &tag, const Anope::string &def) const { const Anope::string &str = Get(tag, def); std::size_t pos = str.length(); unsigned long l; try { l = std::stoul(str.str(), &pos, 0); } catch (...) { return 0; } if (pos != str.length()) return 0; return l; } template<> void Block::Set(const Anope::string &tag, const Anope::string &value) { items[tag] = value; } 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!"); } template 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("type"), &file = include->Get("name"); ValidateNotEmpty("include", "name", file); File f(file, type == "executable"); this->LoadConf(f); } for (Module *m : ModuleManager::Modules) m->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"}, {"options", "casemap"}, }; for (unsigned i = 0; i < sizeof(noreload) / sizeof(noreload[0]); ++i) if (this->GetBlock(noreload[i].block)->Get(noreload[i].name) != Config->GetBlock(noreload[i].block)->Get(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"); ValidateNotEmpty("serverinfo", "name", serverinfo->Get("name")); ValidateNotEmpty("serverinfo", "description", serverinfo->Get("description")); ValidateNotEmpty("serverinfo", "pid", serverinfo->Get("pid")); ValidateNotEmpty("serverinfo", "motd", serverinfo->Get("motd")); ValidateNotZero("options", "readtimeout", options->Get("readtimeout")); ValidateNotZero("options", "warningtimeout", options->Get("warningtimeout")); ValidateNotZero("networkinfo", "nicklen", networkinfo->Get("nicklen")); ValidateNotZero("networkinfo", "userlen", networkinfo->Get("userlen")); ValidateNotZero("networkinfo", "hostlen", networkinfo->Get("hostlen")); ValidateNotZero("networkinfo", "chanlen", networkinfo->Get("chanlen")); spacesepstream(options->Get("ulineservers")).GetTokens(this->Ulines); if (mail->Get("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(check[i])); } this->ReadTimeout = options->Get("readtimeout"); this->UsePrivmsg = options->Get("useprivmsg"); this->UseStrictPrivmsg = options->Get("usestrictprivmsg"); this->StrictPrivmsg = !UseStrictPrivmsg ? "/msg " : "/"; { std::vector defaults; spacesepstream(this->GetModule("nickserv/main")->Get("defaults")).GetTokens(defaults); this->DefPrivmsg = std::find(defaults.begin(), defaults.end(), "msg") != defaults.end(); } this->DefLanguage = options->Get("defaultlanguage"); this->TimeoutCheck = options->Get("timeoutcheck"); this->NickChars = networkinfo->Get("nick_chars"); Anope::string localename = options->Get("locale"); Anope::string casemap = options->Get("casemap"); if (localename.empty() == casemap.empty()) throw ConfigException("One of options:locale and options:casemap must be set"); if (localename.empty()) { // load locale conf File f(casemap + ".conf", false); this->LoadConf(f); } else { #if Boost_FOUND this->locale = new std::locale(Anope::locale::generate(localename.str())); #else throw ConfigException("Boost.Locale is not enabled, cannot use locale " + localename); #endif } for (int i = 0; i < this->CountBlock("uplink"); ++i) { Block *uplink = this->GetBlock("uplink", i); const Anope::string &host = uplink->Get("host"); bool ipv6 = uplink->Get("ipv6"); int port = uplink->Get("port"); const Anope::string &password = uplink->Get("password"); ValidateNotEmpty("uplink", "host", host); ValidateNotZero("uplink", "port", port); ValidateNotEmpty("uplink", "password", password); 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("name"); ValidateNotEmpty("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("name"), &modes = opertype->Get("modes"), &inherits = opertype->Get("inherits"), &commands = opertype->Get("commands"), &privs = opertype->Get("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)) { Anope::Logger.Log(_("Inheriting commands and privs from {0} to {1}"), ot2->GetName(), ot->GetName()); ot->Inherits(ot2); break; } } } this->MyOperTypes.push_back(ot); } this->LoadBots(); for (int i = 0; i < this->CountBlock("log"); ++i) { Block *log = this->GetBlock("log", i); int logage = log->Get("logage"); bool rawio = log->Get("rawio"); bool debug = log->Get("debug"); LogInfo l(logage, rawio, debug); l.bot = ServiceBot::Find(log->Get("bot", "Global"), true); spacesepstream(log->Get("target")).GetTokens(l.targets); spacesepstream(log->Get("source")).GetTokens(l.sources); spacesepstream(log->Get("admin")).GetTokens(l.admin); spacesepstream(log->Get("override")).GetTokens(l.override); spacesepstream(log->Get("commands")).GetTokens(l.commands); spacesepstream(log->Get("servers")).GetTokens(l.servers); spacesepstream(log->Get("channels")).GetTokens(l.channels); spacesepstream(log->Get("users")).GetTokens(l.users); spacesepstream(log->Get("other")).GetTokens(l.normal); this->LogInfos.push_back(l); } for (std::pair p : UserListByNick) { User *u = p.second; if (u->type != UserType::BOT) continue; ServiceBot *bi = anope_dynamic_static_cast(u); bi->commands.clear(); } for (int i = 0; i < this->CountBlock("command"); ++i) { Block *command = this->GetBlock("command", i); const Anope::string &service = command->Get("service"), &nname = command->Get("name"), &cmd = command->Get("command"), &permission = command->Get("permission"), &group = command->Get("group"); bool hide = command->Get("hide"); ValidateNotEmpty("command", "service", service); ValidateNotEmpty("command", "name", nname); ValidateNotEmpty("command", "command", cmd); ServiceBot *bi = this->GetClient(service); if (!bi) continue; CommandInfo &ci = bi->SetCommand(nname, cmd, permission); ci.group = group; ci.hide = hide; } for (int i = 0; i < this->CountBlock("fantasy"); ++i) { Block *fantasy = this->GetBlock("fantasy", i); const Anope::string &nname = fantasy->Get("name"), &service = fantasy->Get("command"), &permission = fantasy->Get("permission"), &group = fantasy->Get("group"); bool hide = fantasy->Get("hide"), prepend_channel = fantasy->Get("prepend_channel", "yes"); ValidateNotEmpty("fantasy", "name", nname); ValidateNotEmpty("fantasy", "command", service); CommandInfo &c = this->Fantasy[nname]; c.name = service; c.cname = nname; 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("name"), &description = command_group->Get("description"); CommandGroup gr; gr.name = nname; gr.description = description; this->CommandGroups.push_back(gr); } for (int i = 0; i < this->CountBlock("usermode"); ++i) { Block *usermode = this->GetBlock("usermode", i); Anope::string nname = usermode->Get("name"), character = usermode->Get("character"); bool oper_only = usermode->Get("oper_only"), setable = usermode->Get("setable"), param = usermode->Get("param"); ValidateNotEmpty("usermode", "name", nname); if (character.length() != 1) throw ConfigException("Usermode character length must be 1"); Usermode um; um.name = nname; um.character = character[0]; um.param = param; um.oper_only = oper_only; um.setable = setable; this->Usermodes.push_back(um); } for (int i = 0; i < this->CountBlock("channelmode"); ++i) { Block *channelmode = this->GetBlock("channelmode", i); Anope::string nname = channelmode->Get("name"), character = channelmode->Get("character"), status = channelmode->Get("status"), param_regex = channelmode->Get("param_regex"); bool oper_only = channelmode->Get("oper_only"), setable = channelmode->Get("setable"), list = channelmode->Get("list"), param = channelmode->Get("param"), param_unset = channelmode->Get("param_unset", "yes"); int level = channelmode->Get("level"); ValidateNotEmpty("usermode", "name", nname); if (character.length() != 1) throw ConfigException("Channelmode character length must be 1"); if (status.length() > 1) throw ConfigException("Channelmode status must be at most one character"); if (list || !param_regex.empty() || !status.empty()) param = true; Channelmode cm; cm.name = nname; cm.character = character[0]; cm.status = !status.empty() ? status[0] : 0; cm.param_regex = param_regex; cm.oper_only = oper_only; cm.setable = setable; cm.list = list; cm.param = param; cm.param_unset = param_unset; cm.level = level; this->Channelmodes.push_back(cm); } for (int i = 0; i < this->CountBlock("casemap"); ++i) { Block *casemapb = this->GetBlock("casemap", i); unsigned char upper = casemapb->Get("upper"), lower = casemapb->Get("lower"); if (!upper) { Anope::string s = casemapb->Get("upper"); if (s.length() == 1) upper = s[0]; } if (!lower) { Anope::string s = casemapb->Get("lower"); if (s.length() == 1) lower = s[0]; } if (upper && lower) { CaseMapUpper[lower] = CaseMapUpper[upper] = upper; CaseMapLower[lower] = CaseMapLower[upper] = lower; } } /* Below here can't throw */ /* Clear existing conf opers */ if (Config) { for (int i = 0; i < Config->CountBlock("oper"); ++i) { Block *oper = Config->GetBlock("oper", i); const Anope::string &nname = oper->Get("name"); Oper *o = Oper::Find(nname); if (o != nullptr) { o->Delete(); } } } /* Check the user keys */ if (!options->Get("seed")) Anope::Logger.Log(_("Configuration option options:seed should be set. It's for YOUR safety! Remember that!")); /* check regexengine */ const Anope::string ®ex_engine = options->Get("regexengine"); if (regex_engine == "ecmascript") regex_flags = std::regex::ECMAScript; else if (regex_engine == "basic") regex_flags = std::regex::basic; else if (regex_engine == "extended") regex_flags = std::regex::extended; else if (regex_engine == "awk") regex_flags = std::regex::awk; else if (regex_engine == "grep") regex_flags = std::regex::grep; else if (regex_engine == "egrep") regex_flags = std::regex::egrep; else regex_flags = static_cast::flag_type>(0); /* always enable icase and optimize */ if (regex_flags) regex_flags |= std::regex::icase | std::regex::optimize; this->LineWrap = options->Get("linewrap", "200"); } Conf::~Conf() { for (unsigned i = 0; i < MyOperTypes.size(); ++i) delete MyOperTypes[i]; delete locale; } 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); LoadOpers(); ApplyBots(); ModeManager::Apply(old); for (BotInfo *bi : Serialize::GetObjects()) { if (!bi->conf) { bi->Delete(); continue; } for (int i = 0; i < bi->conf->CountBlock("channel"); ++i) { Block *channel = bi->conf->GetBlock("channel", i); const Anope::string &chname = channel->Get("name"), &modes = channel->Get("modes"); bi->bot->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->bot); if (cu != NULL) for (size_t j = 0; j < cu->status.Modes().length(); ++j) c->RemoveMode(bi->bot, ModeManager::FindChannelModeByChar(cu->status.Modes()[j]), bi->bot->GetUID()); /* Set the new modes */ for (unsigned j = 0; j < modes.length(); ++j) { ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[j]); if (cm == NULL) cm = ModeManager::FindChannelModeByChar(ModeManager::GetStatusChar(modes[j])); if (cm && cm->type == MODE_STATUS) c->SetMode(bi->bot, cm, bi->bot->GetUID()); } } } } void Conf::LoadBots() { for (BotInfo *bi : Serialize::GetObjects()) bi->conf = nullptr; for (int i = 0; i < this->CountBlock("service"); ++i) { Block *service = this->GetBlock("service", i); const Anope::string &nick = service->Get("nick"), &user = service->Get("user"), &host = service->Get("host"), &gecos = service->Get("gecos"), &modes = service->Get("modes"), &channels = service->Get("channels"); ValidateNotEmpty("service", "nick", nick); ValidateNotEmpty("service", "user", user); ValidateNotEmpty("service", "host", host); ValidateNotEmpty("service", "gecos", gecos); ValidateNoSpaces("service", "channels", channels); if (User *u = User::Find(nick, true)) { if (u->type != UserType::BOT) { u->Kill(Me, "Nickname required by services"); } } ServiceBot *sb = ServiceBot::Find(nick, true); if (!sb) sb = new ServiceBot(nick, user, host, gecos, modes); } } void Conf::ApplyBots() { /* Associate conf bots with db bots */ for (BotInfo *bi : Serialize::GetObjects()) { ServiceBot *sb = ServiceBot::Find(bi->GetNick()); if (sb == nullptr) continue; bi->bot = sb; sb->bi = bi; } /* Apply conf to conf bots */ for (int i = 0; i < this->CountBlock("service"); ++i) { Block *service = this->GetBlock("service", i); const Anope::string &nick = service->Get("nick"); for (BotInfo *bi : Serialize::GetObjects()) if (bi->GetNick().equals_ci(nick)) bi->conf = this; } /* Create new bots for new conf bots */ for (std::pair p : UserListByNick) { User *u = p.second; if (u->type != UserType::BOT) continue; ServiceBot *sb = anope_dynamic_static_cast(u); if (sb->bi != nullptr) continue; sb->bi = Serialize::New(); sb->bi->bot = sb; sb->bi->conf = this; sb->bi->SetNick(sb->nick); sb->bi->SetUser(sb->GetIdent()); sb->bi->SetHost(sb->host); sb->bi->SetRealName(sb->realname); sb->bi->SetCreated(Anope::CurTime); } } void Conf::LoadOpers() { for (int i = 0; i < this->CountBlock("oper"); ++i) { Block *oper = this->GetBlock("oper", i); const Anope::string &nname = oper->Get("name"), &type = oper->Get("type"), &password = oper->Get("password"), &certfp = oper->Get("certfp"), &host = oper->Get("host"), &vhost = oper->Get("vhost"); bool require_oper = oper->Get("require_oper"); ValidateNotEmpty("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) { Anope::Logger.Log(_("Oper block for {0} has invalid oper type {1}"), nname, type); continue; } Oper *o = Oper::Find(nname); if (o == nullptr) o = Serialize::New(); o->SetName(nname); o->SetType(ot); o->SetRequireOper(require_oper); o->SetPassword(password); o->SetCertFP(certfp); o->SetHost(host); o->SetVhost(vhost); Anope::Logger.Debug(_("Creating oper {0} of type {1}"), nname, ot->GetName()); } /* Apply new opers */ for (Oper *o : Serialize::GetObjects()) { NickServ::Nick *na = NickServ::FindNick(o->GetName()); if (!na) continue; na->GetAccount()->SetOper(o); Anope::Logger.Log(_("Tied oper {0} to type {1}"), na->GetAccount()->GetDisplay(), o->GetType()->GetName()); } } Block *Conf::GetModule(Module *m) { if (!m) return NULL; return GetModule(m->name); } Block *Conf::GetModule(const Anope::string &mname) { std::map::iterator it = modules.find(mname); if (it != modules.end()) return it->second; Block* &block = modules[mname]; /* Search for the block */ for (std::pair iters = blocks.equal_range("module"); iters.first != iters.second; ++iters.first) { Block *b = &iters.first->second; if (b->Get("name") == mname) { block = b; break; } } if (block == nullptr) { /* not found, create new block */ auto it2 = blocks.emplace(mname, mname); block = &it2->second; } return block; } ServiceBot *Conf::GetClient(const Anope::string &cname) { Anope::map::iterator it = bots.find(cname); if (it != bots.end()) return ServiceBot::Find(!it->second.empty() ? it->second : cname, true); Block *block = GetModule(cname.lower()); const Anope::string &client = block->Get("client"); bots[cname] = client; return GetClient(cname); } Block *Conf::GetCommand(CommandSource &source) { const Anope::string &block_name = source.c ? "fantasy" : "command"; for (std::pair iters = blocks.equal_range(block_name); iters.first != iters.second; ++iters.first) { Block *b = &iters.first->second; if (b->Get("name") == source.GetCommand()) 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; } 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.GetName() + " could not be opened."); Anope::string itemname, wordbuffer; std::stack block_stack; int linenumber = 0; bool in_word = false, in_quote = false, in_comment = false; Anope::Logger.Debug("Start to read conf {0}", file.GetName()); // 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 int 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) Anope::Logger.Debug("ln {0} EOL: s='{1}' '{2}' set to '{3}'", linenumber, b->name, itemname, wordbuffer); /* Check defines */ for (int i = 0; i < this->CountBlock("define"); ++i) { Block *define = this->GetBlock("define", i); const Anope::string &dname = define->Get("name"); if (dname == wordbuffer && define != b) wordbuffer = define->Get("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)); }