mirror of
https://github.com/anope/anope.git
synced 2026-06-25 11:36:37 +02:00
1047 lines
28 KiB
C++
1047 lines
28 KiB
C++
// Anope IRC Services <https://www.anope.org/>
|
|
//
|
|
// Copyright (C) 2003-2026 Anope Contributors
|
|
//
|
|
// Anope is free software. You can use, modify, and/or distribute it under the
|
|
// terms of version 2 of the GNU General Public License. See docs/LICENSE.txt
|
|
// for the complete terms of this license and docs/AUTHORS.txt for a list of
|
|
// contributors.
|
|
//
|
|
// Based on the original code of Epona by Lara
|
|
// Based on the original code of Services by Andy Church
|
|
//
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
#include "services.h"
|
|
#include "config.h"
|
|
#include "bots.h"
|
|
#include "access.h"
|
|
#include "opertype.h"
|
|
#include "channels.h"
|
|
#include "hashcomp.h"
|
|
|
|
#include <stack>
|
|
#include <stdexcept>
|
|
|
|
using Configuration::File;
|
|
using Configuration::Conf;
|
|
using Configuration::Block;
|
|
using Configuration::Uplink;
|
|
|
|
File ServicesConf("anope.conf", false); // Configuration file name
|
|
Conf *Config = NULL;
|
|
|
|
Block Block::EmptyBlock("");
|
|
|
|
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) const
|
|
{
|
|
return blocks.count(bname);
|
|
}
|
|
|
|
const Block &Block::GetBlock(const Anope::string &bname, int num) const
|
|
{
|
|
std::pair<block_map::const_iterator, block_map::const_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 EmptyBlock;
|
|
}
|
|
|
|
Block *Block::GetMutableBlock(const Anope::string &bname, int num)
|
|
{
|
|
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)
|
|
{
|
|
items[tag] = value;
|
|
return true;
|
|
}
|
|
|
|
const Block::item_map &Block::GetItems() const
|
|
{
|
|
return items;
|
|
}
|
|
|
|
template<> const Anope::string Block::Get(const Anope::string &tag, const Anope::string &def) const
|
|
{
|
|
auto 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 + "> (" + value + ") 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 + "> (" + value + ") 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;
|
|
DefPrivmsg = false;
|
|
|
|
this->LoadConf(ServicesConf);
|
|
|
|
for (int i = 0; i < this->CountBlock("include"); ++i)
|
|
{
|
|
const 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 (const auto &tag : noreload)
|
|
{
|
|
if (this->GetBlock(tag.block).Get<const Anope::string>(tag.name) != Config->GetBlock(tag.block).Get<const Anope::string>(tag.name))
|
|
throw ConfigException("<" + tag.block + ":" + tag.name + "> can not be modified once set");
|
|
}
|
|
}
|
|
|
|
const 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("networkinfo", "nicklen", networkinfo.Get<unsigned>("nicklen", "1"));
|
|
ValidateNotZero("networkinfo", "userlen", networkinfo.Get<unsigned>("userlen", "1"));
|
|
ValidateNotZero("networkinfo", "hostlen", networkinfo.Get<unsigned>("hostlen", "1"));
|
|
ValidateNotZero("networkinfo", "chanlen", networkinfo.Get<unsigned>("chanlen", "1"));
|
|
|
|
spacesepstream(options.Get<const Anope::string>("ulineservers")).GetTokens(this->Ulines);
|
|
|
|
if (mail.Get<bool>("usemail"))
|
|
{
|
|
Anope::string check[] = { "sendfrom", "registration_subject", "registration_message", "emailchange_subject", "emailchange_message", "memo_subject", "memo_message" };
|
|
for (const auto &field : check)
|
|
ValidateNotEmpty("mail", field, mail.Get<const Anope::string>(field));
|
|
}
|
|
|
|
this->ReadTimeout = options.Get<time_t>("readtimeout");
|
|
this->ServiceAlias = options.Get<bool>("servicealias");
|
|
{
|
|
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)
|
|
{
|
|
const Block &uplink = this->GetBlock("uplink", i);
|
|
|
|
int protocol;
|
|
const Anope::string &protocolstr = uplink.Get<const Anope::string>("protocol", "ipv4");
|
|
if (protocolstr == "ipv4")
|
|
protocol = AF_INET;
|
|
else if (protocolstr == "ipv6")
|
|
protocol = AF_INET6;
|
|
else if (protocolstr == "unix")
|
|
protocol = AF_UNIX;
|
|
else
|
|
throw ConfigException("uplink:protocol must be set to ipv4, ipv6, or unix");
|
|
|
|
const Anope::string &host = uplink.Get<const Anope::string>("host");
|
|
ValidateNotEmptyOrSpaces("uplink", "host", host);
|
|
|
|
int port = 0;
|
|
if (protocol != AF_UNIX)
|
|
{
|
|
port = uplink.Get<int>("port");
|
|
ValidateNotZero("uplink", "port", port);
|
|
}
|
|
|
|
const Anope::string &password = uplink.Get<const Anope::string>("password");
|
|
ValidateNotEmptyOrSpaces("uplink", "password", password);
|
|
if (password[0] == ':')
|
|
throw ConfigException("uplink:password is not valid");
|
|
|
|
this->Uplinks.emplace_back(host, port, password, protocol);
|
|
}
|
|
|
|
for (int i = 0; i < this->CountBlock("module"); ++i)
|
|
{
|
|
const 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)
|
|
{
|
|
const 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);
|
|
|
|
auto *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 (auto *ot2 : this->MyOperTypes)
|
|
{
|
|
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)
|
|
{
|
|
const 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"),
|
|
&password_hash = oper.Get<const Anope::string>("password_hash"),
|
|
&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 (auto *opertype : this->MyOperTypes)
|
|
{
|
|
if (opertype->GetName() == type)
|
|
ot = opertype;
|
|
}
|
|
if (ot == NULL)
|
|
throw ConfigException("Oper block for " + nname + " has invalid oper type " + type);
|
|
|
|
auto *o = new Oper(nname, ot);
|
|
o->require_oper = require_oper;
|
|
o->password = password;
|
|
o->password_hash = password_hash;
|
|
spacesepstream(certfp).GetTokens(o->certfp);
|
|
spacesepstream(host).GetTokens(o->hosts);
|
|
o->vhost = vhost;
|
|
|
|
this->Opers.push_back(o);
|
|
}
|
|
|
|
for (const auto &[_, bi] : *BotListByNick)
|
|
bi->conf = false;
|
|
for (int i = 0; i < this->CountBlock("service"); ++i)
|
|
{
|
|
const Block &service = this->GetBlock("service", i);
|
|
|
|
const Anope::string &nick = service.Get<const Anope::string>("nick"),
|
|
&user = service.Get<const Anope::string>("user", nick.lower()),
|
|
&host = service.Get<const Anope::string>("host", servername),
|
|
&real = service.Get<const Anope::string>("real", nick),
|
|
&modes = service.Get<const Anope::string>("modes"),
|
|
&channels = service.Get<const Anope::string>("channels"),
|
|
&alias = service.Get<const Anope::string>("alias", nick.upper());
|
|
|
|
ValidateNotEmptyOrSpaces("service", "nick", nick);
|
|
ValidateNotEmptyOrSpaces("service", "user", user);
|
|
ValidateNotEmptyOrSpaces("service", "host", host);
|
|
ValidateNotEmpty("service", "real", real);
|
|
ValidateNoSpaces("service", "channels", channels);
|
|
|
|
BotInfo *bi = BotInfo::Find(nick, true);
|
|
if (!bi)
|
|
bi = new BotInfo(nick, user, host, real, modes);
|
|
|
|
bi->alias = alias;
|
|
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 */
|
|
auto *memb = c->FindUser(bi);
|
|
if (memb != NULL)
|
|
{
|
|
auto modes = memb->status.Modes();
|
|
for (auto mode : modes)
|
|
c->RemoveMode(bi, mode, bi->GetUID());
|
|
}
|
|
/* Set the new modes */
|
|
for (char want_mode : want_modes)
|
|
{
|
|
ChannelMode *cm = ModeManager::FindChannelModeByChar(want_mode);
|
|
if (cm == NULL)
|
|
cm = ModeManager::FindChannelModeByChar(ModeManager::GetStatusChar(want_mode));
|
|
if (cm && cm->type == MODE_STATUS)
|
|
c->SetMode(bi, cm, bi->GetUID());
|
|
}
|
|
}
|
|
for (const auto &oldchannel : oldchannels)
|
|
{
|
|
size_t ch = oldchannel.find('#');
|
|
Anope::string chname = oldchannel.substr(ch != Anope::string::npos ? ch : 0);
|
|
bool found = false;
|
|
for (const auto &botchannel : bi->botchannels)
|
|
{
|
|
ch = botchannel.find('#');
|
|
Anope::string ochname = botchannel.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)
|
|
{
|
|
const 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 (const auto &[_, bi] : *BotListByNick)
|
|
bi->commands.clear();
|
|
for (int i = 0; i < this->CountBlock("command"); ++i)
|
|
{
|
|
const 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)
|
|
{
|
|
const 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)
|
|
{
|
|
const 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");
|
|
|
|
ValidateNotEmpty("fantasy", "name", nname);
|
|
ValidateNotEmptyOrSpaces("fantasy", "command", service);
|
|
|
|
CommandInfo &c = this->Fantasy[nname];
|
|
c.name = service;
|
|
c.permission = permission;
|
|
c.group = group;
|
|
c.hide = fantasy.Get<bool>("hide");
|
|
c.prepend_channel = fantasy.Get<bool>("prepend_channel", "yes");
|
|
c.require_privilege = fantasy.Get<bool>("require_privilege", "yes");
|
|
}
|
|
|
|
for (int i = 0; i < this->CountBlock("command_group"); ++i)
|
|
{
|
|
const 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 (const auto &[_, nc] : *NickCoreList)
|
|
{
|
|
if (nc->o && std::find(Config->Opers.begin(), Config->Opers.end(), nc->o) != Config->Opers.end())
|
|
nc->o = NULL;
|
|
}
|
|
/* Apply new opers */
|
|
for (auto *o : this->Opers)
|
|
{
|
|
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();
|
|
}
|
|
|
|
Conf::~Conf()
|
|
{
|
|
for (const auto *opertype : MyOperTypes)
|
|
delete opertype;
|
|
|
|
for (const auto *oper : Opers)
|
|
delete oper;
|
|
}
|
|
|
|
void Conf::Post(Conf *old)
|
|
{
|
|
/* Apply module changes */
|
|
for (const auto &mod : old->ModulesAutoLoad)
|
|
{
|
|
if (std::find(this->ModulesAutoLoad.begin(), this->ModulesAutoLoad.end(), mod) == this->ModulesAutoLoad.end())
|
|
ModuleManager::UnloadModule(ModuleManager::FindModule(mod), NULL);
|
|
}
|
|
|
|
for (const auto &mod : this->ModulesAutoLoad)
|
|
{
|
|
if (std::find(old->ModulesAutoLoad.begin(), old->ModulesAutoLoad.end(), mod) == old->ModulesAutoLoad.end())
|
|
ModuleManager::LoadModule(mod, 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 (auto *opertype : MyOperTypes)
|
|
{
|
|
if (ot->GetName() == opertype->GetName())
|
|
o->ot = opertype;
|
|
}
|
|
|
|
if (o->ot == NULL)
|
|
{
|
|
/* Oper block has lost type */
|
|
auto 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Anope::string Uplink::str() const
|
|
{
|
|
switch (protocol)
|
|
{
|
|
case AF_INET:
|
|
return Anope::Format("%s:%u", this->host.c_str(), this->port);
|
|
case AF_INET6:
|
|
return Anope::Format("[%s]:%u", this->host.c_str(), this->port);
|
|
case AF_UNIX:
|
|
return this->host;
|
|
}
|
|
|
|
// Should never be reached.
|
|
return "";
|
|
}
|
|
|
|
|
|
Block &Conf::GetModule(const Module *m)
|
|
{
|
|
if (!m)
|
|
return Block::EmptyBlock;
|
|
|
|
return GetModule(m->name);
|
|
}
|
|
|
|
Block &Conf::GetModule(const Anope::string &mname)
|
|
{
|
|
auto 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;
|
|
}
|
|
}
|
|
|
|
if (!block)
|
|
block = &Block::EmptyBlock;
|
|
|
|
return GetModule(mname);
|
|
}
|
|
|
|
BotInfo *Conf::GetClient(const Anope::string &cname)
|
|
{
|
|
auto 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);
|
|
}
|
|
|
|
const 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 Block::EmptyBlock;
|
|
}
|
|
|
|
File::File(const Anope::string &n, bool e) : name(n), executable(e)
|
|
{
|
|
}
|
|
|
|
File::~File()
|
|
{
|
|
this->Close();
|
|
}
|
|
|
|
const Anope::string &File::GetName() const
|
|
{
|
|
return this->name;
|
|
}
|
|
|
|
Anope::string File::GetPath() const
|
|
{
|
|
return this->executable ? this->name : Anope::ExpandConfig(this->name);
|
|
}
|
|
|
|
bool File::IsOpen() const
|
|
{
|
|
return this->fp != NULL;
|
|
}
|
|
|
|
bool File::Open()
|
|
{
|
|
this->Close();
|
|
this->fp = (this->executable ? popen(GetPath().c_str(), "r") : fopen(GetPath().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[1024];
|
|
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(Anope::Format("File %s could not be opened: %s.",
|
|
file.GetPath().c_str(), strerror(errno)));
|
|
}
|
|
|
|
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() + ":" + Anope::ToString(linenumber));
|
|
}
|
|
if (in_word || !wordbuffer.empty())
|
|
{
|
|
file.Close();
|
|
throw ConfigException("Unexpected quoted string (prior unhandled words): " + file.GetName() + ":" + Anope::ToString(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() + ":" + Anope::ToString(linenumber));
|
|
}
|
|
|
|
if (!itemname.empty() || wordbuffer.empty())
|
|
{
|
|
file.Close();
|
|
throw ConfigException("Stray '=' sign or item without value: " + file.GetName() + ":" + Anope::ToString(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();
|
|
auto it = b->blocks.emplace(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() + ":" + Anope::ToString(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() + ":" + Anope::ToString(linenumber));
|
|
}
|
|
|
|
Block *b = block_stack.top();
|
|
if (b)
|
|
{
|
|
Log(LOG_DEBUG) << "ln " << linenumber << " EOL: s='" << b->name << "' '" << itemname << "' set to '" << wordbuffer << "'";
|
|
b->items[itemname] = ReplaceVars(wordbuffer, file, linenumber);
|
|
}
|
|
|
|
wordbuffer.clear();
|
|
itemname.clear();
|
|
}
|
|
|
|
if (ch == '}')
|
|
{
|
|
if (block_stack.empty())
|
|
{
|
|
file.Close();
|
|
throw ConfigException("Stray '}': " + file.GetName() + ":" + Anope::ToString(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())
|
|
{
|
|
if (block_stack.top())
|
|
throw ConfigException("Unterminated block at end of file: " + file.GetName() + ". Block was opened on line " + Anope::ToString(block_stack.top()->linenum));
|
|
else
|
|
throw ConfigException("Unterminated commented block at end of file: " + file.GetName());
|
|
}
|
|
}
|
|
|
|
Anope::string Conf::ReplaceVars(const Anope::string &str, const File &file, int linenumber)
|
|
{
|
|
Anope::string ret;
|
|
for (auto it = str.begin(); it != str.end(); )
|
|
{
|
|
if (*it != '$')
|
|
{
|
|
ret.push_back(*it++);
|
|
continue;
|
|
}
|
|
|
|
if (++it == str.end() || *it != '{')
|
|
{
|
|
ret.push_back('$');
|
|
continue;
|
|
}
|
|
|
|
it++;
|
|
Anope::string var;
|
|
while (it != str.end() && *it != '}')
|
|
var.push_back(*it++);
|
|
|
|
if (it == str.end())
|
|
throw ConfigException("Unterminated variable: " + file.GetName() + ":" + Anope::ToString(linenumber));
|
|
|
|
it++;
|
|
if (var.compare(0, 4, "env.", 4) == 0)
|
|
{
|
|
// This is an environment variable rather than a defined variable
|
|
const char* envstr = getenv(var.c_str() + 4);
|
|
if (envstr && envstr)
|
|
ret.append(envstr);
|
|
continue;
|
|
}
|
|
|
|
auto found = false;
|
|
for (int i = 0; i < this->CountBlock("define"); ++i)
|
|
{
|
|
const auto &define = this->GetBlock("define", i);
|
|
const auto defname = define.Get<const Anope::string>("name");
|
|
if (defname == var)
|
|
{
|
|
ret.append(define.Get<const Anope::string>("value"));
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
throw ConfigException("Undefined variable: " + var + " at " + file.GetName() + ":" + Anope::ToString(linenumber));
|
|
}
|
|
|
|
if (!str.equals_cs(ret))
|
|
Log(LOG_DEBUG) << "Expanded \"" << str << "\" to \"" << ret << "\"";
|
|
return ret;
|
|
}
|