anope

- supernets anope source code & configuration
git clone git://git.acid.vegas/anope.git
Log | Files | Refs | Archive | README

config.cpp (27620B)

      1 /* Configuration file handling.
      2  *
      3  * (C) 2003-2022 Anope Team
      4  * Contact us at team@anope.org
      5  *
      6  * Please read COPYING and README for further details.
      7  *
      8  * Based on the original code of Epona by Lara.
      9  * Based on the original code of Services by Andy Church.
     10  */
     11 
     12 #include "services.h"
     13 #include "config.h"
     14 #include "bots.h"
     15 #include "access.h"
     16 #include "opertype.h"
     17 #include "channels.h"
     18 #include "hashcomp.h"
     19 
     20 using namespace Configuration;
     21 
     22 File ServicesConf("services.conf", false); // Services configuration file name
     23 Conf *Config = NULL;
     24 
     25 Block::Block(const Anope::string &n) : name(n), linenum(-1)
     26 {
     27 }
     28 
     29 const Anope::string &Block::GetName() const
     30 {
     31 	return name;
     32 }
     33 
     34 int Block::CountBlock(const Anope::string &bname)
     35 {
     36 	if (!this)
     37 		return 0;
     38 
     39 	return blocks.count(bname);
     40 }
     41 
     42 Block* Block::GetBlock(const Anope::string &bname, int num)
     43 {
     44 	if (!this)
     45 		return NULL;
     46 
     47 	std::pair<block_map::iterator, block_map::iterator> it = blocks.equal_range(bname);
     48 
     49 	for (int i = 0; it.first != it.second; ++it.first, ++i)
     50 		if (i == num)
     51 			return &it.first->second;
     52 	return NULL;
     53 }
     54 
     55 bool Block::Set(const Anope::string &tag, const Anope::string &value)
     56 {
     57 	if (!this)
     58 		return false;
     59 
     60 	items[tag] = value;
     61 	return true;
     62 }
     63 
     64 const Block::item_map* Block::GetItems() const
     65 {
     66 	if (this)
     67 		return &items;
     68 	else
     69 		return NULL;
     70 }
     71 
     72 template<> const Anope::string Block::Get(const Anope::string &tag, const Anope::string& def) const
     73 {
     74 	if (!this)
     75 		return def;
     76 
     77 	Anope::map<Anope::string>::const_iterator it = items.find(tag);
     78 	if (it != items.end())
     79 		return it->second;
     80 
     81 	return def;
     82 }
     83 
     84 template<> time_t Block::Get(const Anope::string &tag, const Anope::string &def) const
     85 {
     86 	return Anope::DoTime(Get<const Anope::string>(tag, def));
     87 }
     88 
     89 template<> bool Block::Get(const Anope::string &tag, const Anope::string &def) const
     90 {
     91 	const Anope::string &str = Get<const Anope::string>(tag, def);
     92 	return !str.empty() && !str.equals_ci("no") && !str.equals_ci("off") && !str.equals_ci("false") && !str.equals_ci("0");
     93 }
     94 
     95 static void ValidateNotEmpty(const Anope::string &block, const Anope::string &name, const Anope::string &value)
     96 {
     97 	if (value.empty())
     98 		throw ConfigException("The value for <" + block + ":" + name + "> cannot be empty!");
     99 }
    100 
    101 static void ValidateNoSpaces(const Anope::string &block, const Anope::string &name, const Anope::string &value)
    102 {
    103 	if (value.find(' ') != Anope::string::npos)
    104 		throw ConfigException("The value for <" + block + ":" + name + "> may not contain spaces!");
    105 }
    106 
    107 static void ValidateNotEmptyOrSpaces(const Anope::string &block, const Anope::string &name, const Anope::string &value)
    108 {
    109 	ValidateNotEmpty(block, name, value);
    110 	ValidateNoSpaces(block, name, value);
    111 }
    112 
    113 template<typename T> static void ValidateNotZero(const Anope::string &block, const Anope::string &name, T value)
    114 {
    115 	if (!value)
    116 		throw ConfigException("The value for <" + block + ":" + name + "> cannot be zero!");
    117 }
    118 
    119 Conf::Conf() : Block("")
    120 {
    121 	ReadTimeout = 0;
    122 	UsePrivmsg = DefPrivmsg = false;
    123 
    124 	this->LoadConf(ServicesConf);
    125 
    126 	for (int i = 0; i < this->CountBlock("include"); ++i)
    127 	{
    128 		Block *include = this->GetBlock("include", i);
    129 
    130 		const Anope::string &type = include->Get<const Anope::string>("type"),
    131 					&file = include->Get<const Anope::string>("name");
    132 
    133 		File f(file, type == "executable");
    134 		this->LoadConf(f);
    135 	}
    136 
    137 	FOREACH_MOD(OnReload, (this));
    138 
    139 	/* Check for modified values that aren't allowed to be modified */
    140 	if (Config)
    141 	{
    142 		struct
    143 		{
    144 			Anope::string block;
    145 			Anope::string name;
    146 		} noreload[] = {
    147 			{"serverinfo", "name"},
    148 			{"serverinfo", "description"},
    149 			{"serverinfo", "localhost"},
    150 			{"serverinfo", "id"},
    151 			{"serverinfo", "pid"},
    152 			{"networkinfo", "nicklen"},
    153 			{"networkinfo", "userlen"},
    154 			{"networkinfo", "hostlen"},
    155 			{"networkinfo", "chanlen"},
    156 		};
    157 
    158 		for (unsigned i = 0; i < sizeof(noreload) / sizeof(noreload[0]); ++i)
    159 			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))
    160 				throw ConfigException("<" + noreload[i].block + ":" + noreload[i].name + "> can not be modified once set");
    161 	}
    162 
    163 	Block *serverinfo = this->GetBlock("serverinfo"), *options = this->GetBlock("options"),
    164 		*mail = this->GetBlock("mail"), *networkinfo = this->GetBlock("networkinfo");
    165 
    166 	const Anope::string &servername = serverinfo->Get<Anope::string>("name");
    167 
    168 	ValidateNotEmptyOrSpaces("serverinfo", "name", servername);
    169 
    170 	if (servername.find(' ') != Anope::string::npos || servername.find('.') == Anope::string::npos)
    171 		throw ConfigException("serverinfo:name is not a valid server name");
    172 
    173 	ValidateNotEmpty("serverinfo", "description", serverinfo->Get<const Anope::string>("description"));
    174 	ValidateNotEmpty("serverinfo", "pid", serverinfo->Get<const Anope::string>("pid"));
    175 	ValidateNotEmpty("serverinfo", "motd", serverinfo->Get<const Anope::string>("motd"));
    176 
    177 	ValidateNotZero("options", "readtimeout", options->Get<time_t>("readtimeout"));
    178 	ValidateNotZero("options", "warningtimeout", options->Get<time_t>("warningtimeout"));
    179 
    180 	ValidateNotZero("networkinfo", "nicklen", networkinfo->Get<unsigned>("nicklen"));
    181 	ValidateNotZero("networkinfo", "userlen", networkinfo->Get<unsigned>("userlen"));
    182 	ValidateNotZero("networkinfo", "hostlen", networkinfo->Get<unsigned>("hostlen"));
    183 	ValidateNotZero("networkinfo", "chanlen", networkinfo->Get<unsigned>("chanlen"));
    184 
    185 	spacesepstream(options->Get<const Anope::string>("ulineservers")).GetTokens(this->Ulines);
    186 
    187 	if (mail->Get<bool>("usemail"))
    188 	{
    189 		Anope::string check[] = { "sendmailpath", "sendfrom", "registration_subject", "registration_message", "emailchange_subject", "emailchange_message", "memo_subject", "memo_message" };
    190 		for (unsigned i = 0; i < sizeof(check) / sizeof(Anope::string); ++i)
    191 			ValidateNotEmpty("mail", check[i], mail->Get<const Anope::string>(check[i]));
    192 	}
    193 
    194 	this->ReadTimeout = options->Get<time_t>("readtimeout");
    195 	this->UsePrivmsg = options->Get<bool>("useprivmsg");
    196 	this->UseStrictPrivmsg = options->Get<bool>("usestrictprivmsg");
    197 	this->StrictPrivmsg = !UseStrictPrivmsg ? "/msg " : "/";
    198 	{
    199 		std::vector<Anope::string> defaults;
    200 		spacesepstream(this->GetModule("nickserv")->Get<const Anope::string>("defaults")).GetTokens(defaults);
    201 		this->DefPrivmsg = std::find(defaults.begin(), defaults.end(), "msg") != defaults.end();
    202 	}
    203 	this->DefLanguage = options->Get<const Anope::string>("defaultlanguage");
    204 	this->TimeoutCheck = options->Get<time_t>("timeoutcheck");
    205 	this->NickChars = networkinfo->Get<Anope::string>("nick_chars");
    206 
    207 	for (int i = 0; i < this->CountBlock("uplink"); ++i)
    208 	{
    209 		Block *uplink = this->GetBlock("uplink", i);
    210 
    211 		const Anope::string &host = uplink->Get<const Anope::string>("host");
    212 		bool ipv6 = uplink->Get<bool>("ipv6");
    213 		int port = uplink->Get<int>("port");
    214 		const Anope::string &password = uplink->Get<const Anope::string>("password");
    215 
    216 		ValidateNotEmptyOrSpaces("uplink", "host", host);
    217 		ValidateNotZero("uplink", "port", port);
    218 		ValidateNotEmptyOrSpaces("uplink", "password", password);
    219 
    220 		if (password.find(' ') != Anope::string::npos || password[0] == ':')
    221 			throw ConfigException("uplink:password is not valid");
    222 
    223 		this->Uplinks.push_back(Uplink(host, port, password, ipv6));
    224 	}
    225 
    226 	for (int i = 0; i < this->CountBlock("module"); ++i)
    227 	{
    228 		Block *module = this->GetBlock("module", i);
    229 
    230 		const Anope::string &modname = module->Get<const Anope::string>("name");
    231 
    232 		ValidateNotEmptyOrSpaces("module", "name", modname);
    233 
    234 		this->ModulesAutoLoad.push_back(modname);
    235 	}
    236 
    237 	for (int i = 0; i < this->CountBlock("opertype"); ++i)
    238 	{
    239 		Block *opertype = this->GetBlock("opertype", i);
    240 
    241 		const Anope::string &oname = opertype->Get<const Anope::string>("name"),
    242 				&modes = opertype->Get<const Anope::string>("modes"),
    243 				&inherits = opertype->Get<const Anope::string>("inherits"),
    244 				&commands = opertype->Get<const Anope::string>("commands"),
    245 				&privs = opertype->Get<const Anope::string>("privs");
    246 
    247 		ValidateNotEmpty("opertype", "name", oname);
    248 
    249 		OperType *ot = new OperType(oname);
    250 		ot->modes = modes;
    251 
    252 		spacesepstream cmdstr(commands);
    253 		for (Anope::string str; cmdstr.GetToken(str);)
    254 			ot->AddCommand(str);
    255 
    256 		spacesepstream privstr(privs);
    257 		for (Anope::string str; privstr.GetToken(str);)
    258 			ot->AddPriv(str);
    259 
    260 		commasepstream inheritstr(inherits);
    261 		for (Anope::string str; inheritstr.GetToken(str);)
    262 		{
    263 			/* Strip leading ' ' after , */
    264 			if (str.length() > 1 && str[0] == ' ')
    265 				str.erase(str.begin());
    266 			for (unsigned j = 0; j < this->MyOperTypes.size(); ++j)
    267 			{
    268 				OperType *ot2 = this->MyOperTypes[j];
    269 
    270 				if (ot2->GetName().equals_ci(str))
    271 				{
    272 					Log() << "Inheriting commands and privs from " << ot2->GetName() << " to " << ot->GetName();
    273 					ot->Inherits(ot2);
    274 					break;
    275 				}
    276 			}
    277 		}
    278 
    279 		this->MyOperTypes.push_back(ot);
    280 	}
    281 
    282 	for (int i = 0; i < this->CountBlock("oper"); ++i)
    283 	{
    284 		Block *oper = this->GetBlock("oper", i);
    285 
    286 		const Anope::string &nname = oper->Get<const Anope::string>("name"),
    287 					&type = oper->Get<const Anope::string>("type"),
    288 					&password = oper->Get<const Anope::string>("password"),
    289 					&certfp = oper->Get<const Anope::string>("certfp"),
    290 					&host = oper->Get<const Anope::string>("host"),
    291 					&vhost = oper->Get<const Anope::string>("vhost");
    292 		bool require_oper = oper->Get<bool>("require_oper");
    293 
    294 		ValidateNotEmptyOrSpaces("oper", "name", nname);
    295 		ValidateNotEmpty("oper", "type", type);
    296 
    297 		OperType *ot = NULL;
    298 		for (unsigned j = 0; j < this->MyOperTypes.size(); ++j)
    299 			if (this->MyOperTypes[j]->GetName() == type)
    300 				ot = this->MyOperTypes[j];
    301 		if (ot == NULL)
    302 			throw ConfigException("Oper block for " + nname + " has invalid oper type " + type);
    303 
    304 		Oper *o = new Oper(nname, ot);
    305 		o->require_oper = require_oper;
    306 		o->password = password;
    307 		o->certfp = certfp;
    308 		spacesepstream(host).GetTokens(o->hosts);
    309 		o->vhost = vhost;
    310 
    311 		this->Opers.push_back(o);
    312 	}
    313 
    314 	for (botinfo_map::const_iterator it = BotListByNick->begin(), it_end = BotListByNick->end(); it != it_end; ++it)
    315 		it->second->conf = false;
    316 	for (int i = 0; i < this->CountBlock("service"); ++i)
    317 	{
    318 		Block *service = this->GetBlock("service", i);
    319 
    320 		const Anope::string &nick = service->Get<const Anope::string>("nick"),
    321 					&user = service->Get<const Anope::string>("user"),
    322 					&host = service->Get<const Anope::string>("host"),
    323 					&gecos = service->Get<const Anope::string>("gecos"),
    324 					&modes = service->Get<const Anope::string>("modes"),
    325 					&channels = service->Get<const Anope::string>("channels");
    326 
    327 		ValidateNotEmptyOrSpaces("service", "nick", nick);
    328 		ValidateNotEmptyOrSpaces("service", "user", user);
    329 		ValidateNotEmptyOrSpaces("service", "host", host);
    330 		ValidateNotEmpty("service", "gecos", gecos);
    331 		ValidateNoSpaces("service", "channels", channels);
    332 
    333 		BotInfo *bi = BotInfo::Find(nick, true);
    334 		if (!bi)
    335 			bi = new BotInfo(nick, user, host, gecos, modes);
    336 		bi->conf = true;
    337 
    338 		std::vector<Anope::string> oldchannels = bi->botchannels;
    339 		bi->botchannels.clear();
    340 		commasepstream sep(channels);
    341 		for (Anope::string token; sep.GetToken(token);)
    342 		{
    343 			bi->botchannels.push_back(token);
    344 			size_t ch = token.find('#');
    345 			Anope::string chname, want_modes;
    346 			if (ch == Anope::string::npos)
    347 				chname = token;
    348 			else
    349 			{
    350 				want_modes = token.substr(0, ch);
    351 				chname = token.substr(ch);
    352 			}
    353 			bi->Join(chname);
    354 			Channel *c = Channel::Find(chname);
    355 			if (!c)
    356 				continue; // Can't happen
    357 
    358 			c->botchannel = true;
    359 
    360 			/* Remove all existing modes */
    361 			ChanUserContainer *cu = c->FindUser(bi);
    362 			if (cu != NULL)
    363 				for (size_t j = 0; j < cu->status.Modes().length(); ++j)
    364 					c->RemoveMode(bi, ModeManager::FindChannelModeByChar(cu->status.Modes()[j]), bi->GetUID());
    365 			/* Set the new modes */
    366 			for (unsigned j = 0; j < want_modes.length(); ++j)
    367 			{
    368 				ChannelMode *cm = ModeManager::FindChannelModeByChar(want_modes[j]);
    369 				if (cm == NULL)
    370 					cm = ModeManager::FindChannelModeByChar(ModeManager::GetStatusChar(want_modes[j]));
    371 				if (cm && cm->type == MODE_STATUS)
    372 					c->SetMode(bi, cm, bi->GetUID());
    373 			}
    374 		}
    375 		for (unsigned k = 0; k < oldchannels.size(); ++k)
    376 		{
    377 			size_t ch = oldchannels[k].find('#');
    378 			Anope::string chname = oldchannels[k].substr(ch != Anope::string::npos ? ch : 0);
    379 
    380 			bool found = false;
    381 			for (unsigned j = 0; j < bi->botchannels.size(); ++j)
    382 			{
    383 				ch = bi->botchannels[j].find('#');
    384 				Anope::string ochname = bi->botchannels[j].substr(ch != Anope::string::npos ? ch : 0);
    385 
    386 				if (chname.equals_ci(ochname))
    387 					found = true;
    388 			}
    389 
    390 			if (found)
    391 				continue;
    392 
    393 			Channel *c = Channel::Find(chname);
    394 			if (c)
    395 			{
    396 				c->botchannel = false;
    397 				bi->Part(c);
    398 			}
    399 		}
    400 	}
    401 
    402 	for (int i = 0; i < this->CountBlock("log"); ++i)
    403 	{
    404 		Block *log = this->GetBlock("log", i);
    405 
    406 		int logage = log->Get<int>("logage");
    407 		bool rawio = log->Get<bool>("rawio");
    408 		bool debug = log->Get<bool>("debug");
    409 
    410 		LogInfo l(logage, rawio, debug);
    411 
    412 		l.bot = BotInfo::Find(log->Get<const Anope::string>("bot", "Global"), true);
    413 		spacesepstream(log->Get<const Anope::string>("target")).GetTokens(l.targets);
    414 		spacesepstream(log->Get<const Anope::string>("source")).GetTokens(l.sources);
    415 		spacesepstream(log->Get<const Anope::string>("admin")).GetTokens(l.admin);
    416 		spacesepstream(log->Get<const Anope::string>("override")).GetTokens(l.override);
    417 		spacesepstream(log->Get<const Anope::string>("commands")).GetTokens(l.commands);
    418 		spacesepstream(log->Get<const Anope::string>("servers")).GetTokens(l.servers);
    419 		spacesepstream(log->Get<const Anope::string>("channels")).GetTokens(l.channels);
    420 		spacesepstream(log->Get<const Anope::string>("users")).GetTokens(l.users);
    421 		spacesepstream(log->Get<const Anope::string>("other")).GetTokens(l.normal);
    422 
    423 		this->LogInfos.push_back(l);
    424 	}
    425 
    426 	for (botinfo_map::const_iterator it = BotListByNick->begin(), it_end = BotListByNick->end(); it != it_end; ++it)
    427 		it->second->commands.clear();
    428 	for (int i = 0; i < this->CountBlock("command"); ++i)
    429 	{
    430 		Block *command = this->GetBlock("command", i);
    431 
    432 		const Anope::string &service = command->Get<const Anope::string>("service"),
    433 					&nname = command->Get<const Anope::string>("name"),
    434 					&cmd = command->Get<const Anope::string>("command"),
    435 					&permission = command->Get<const Anope::string>("permission"),
    436 					&group = command->Get<const Anope::string>("group");
    437 		bool hide = command->Get<bool>("hide");
    438 
    439 		ValidateNotEmptyOrSpaces("command", "service", service);
    440 		ValidateNotEmpty("command", "name", nname);
    441 		ValidateNotEmptyOrSpaces("command", "command", cmd);
    442 
    443 		BotInfo *bi = this->GetClient(service);
    444 		if (!bi)
    445 			continue;
    446 
    447 		CommandInfo &ci = bi->SetCommand(nname, cmd, permission);
    448 		ci.group = group;
    449 		ci.hide = hide;
    450 	}
    451 
    452 	PrivilegeManager::ClearPrivileges();
    453 	for (int i = 0; i < this->CountBlock("privilege"); ++i)
    454 	{
    455 		Block *privilege = this->GetBlock("privilege", i);
    456 
    457 		const Anope::string &nname = privilege->Get<const Anope::string>("name"),
    458 					&desc = privilege->Get<const Anope::string>("desc");
    459 		int rank = privilege->Get<int>("rank");
    460 
    461 		PrivilegeManager::AddPrivilege(Privilege(nname, desc, rank));
    462 	}
    463 
    464 	for (int i = 0; i < this->CountBlock("fantasy"); ++i)
    465 	{
    466 		Block *fantasy = this->GetBlock("fantasy", i);
    467 
    468 		const Anope::string &nname = fantasy->Get<const Anope::string>("name"),
    469 					&service = fantasy->Get<const Anope::string>("command"),
    470 					&permission = fantasy->Get<const Anope::string>("permission"),
    471 					&group = fantasy->Get<const Anope::string>("group");
    472 		bool hide = fantasy->Get<bool>("hide"),
    473 			prepend_channel = fantasy->Get<bool>("prepend_channel", "yes");
    474 
    475 		ValidateNotEmptyOrSpaces("fantasy", "name", nname);
    476 		ValidateNotEmptyOrSpaces("fantasy", "command", service);
    477 
    478 		CommandInfo &c = this->Fantasy[nname];
    479 		c.name = service;
    480 		c.permission = permission;
    481 		c.group = group;
    482 		c.hide = hide;
    483 		c.prepend_channel = prepend_channel;
    484 	}
    485 
    486 	for (int i = 0; i < this->CountBlock("command_group"); ++i)
    487 	{
    488 		Block *command_group = this->GetBlock("command_group", i);
    489 
    490 		const Anope::string &nname = command_group->Get<const Anope::string>("name"),
    491 					&description = command_group->Get<const Anope::string>("description");
    492 
    493 		CommandGroup gr;
    494 		gr.name = nname;
    495 		gr.description = description;
    496 
    497 		this->CommandGroups.push_back(gr);
    498 	}
    499 
    500 	/* Below here can't throw */
    501 
    502 	if (Config)
    503 		/* Clear existing conf opers */
    504 		for (nickcore_map::const_iterator it = NickCoreList->begin(), it_end = NickCoreList->end(); it != it_end; ++it)
    505 		{
    506 			NickCore *nc = it->second;
    507 			if (nc->o && std::find(Config->Opers.begin(), Config->Opers.end(), nc->o) != Config->Opers.end())
    508 				nc->o = NULL;
    509 		}
    510 	/* Apply new opers */
    511 	for (unsigned i = 0; i < this->Opers.size(); ++i)
    512 	{
    513 		Oper *o = this->Opers[i];
    514 
    515 		NickAlias *na = NickAlias::Find(o->name);
    516 		if (!na)
    517 			continue;
    518 
    519 		if (!na->nc || na->nc->o)
    520 		{
    521 			// If the account is already an oper it might mean two oper blocks for the same nick, or
    522 			// something else has configured them as an oper (like a module)
    523 			continue;
    524 		}
    525 
    526 		na->nc->o = o;
    527 		Log() << "Tied oper " << na->nc->display << " to type " << o->ot->GetName();
    528 	}
    529 
    530 	if (options->Get<const Anope::string>("casemap", "ascii") == "ascii")
    531 		Anope::casemap = std::locale(std::locale(), new Anope::ascii_ctype<char>());
    532 	else if (options->Get<const Anope::string>("casemap") == "rfc1459")
    533 		Anope::casemap = std::locale(std::locale(), new Anope::rfc1459_ctype<char>());
    534 	else
    535 	{
    536 		try
    537 		{
    538 			Anope::casemap = std::locale(options->Get<const Anope::string>("casemap").c_str());
    539 		}
    540 		catch (const std::runtime_error &)
    541 		{
    542 			Log() << "Unknown casemap " << options->Get<const Anope::string>("casemap") << " - casemap not changed";
    543 		}
    544 	}
    545 	Anope::CaseMapRebuild();
    546 
    547 	/* Check the user keys */
    548 	if (!options->Get<unsigned>("seed"))
    549 		Log() << "Configuration option options:seed should be set. It's for YOUR safety! Remember that!";
    550 }
    551 
    552 Conf::~Conf()
    553 {
    554 	for (unsigned i = 0; i < MyOperTypes.size(); ++i)
    555 		delete MyOperTypes[i];
    556 	for (unsigned i = 0; i < Opers.size(); ++i)
    557 		delete Opers[i];
    558 }
    559 
    560 void Conf::Post(Conf *old)
    561 {
    562 	/* Apply module changes */
    563 	for (unsigned i = 0; i < old->ModulesAutoLoad.size(); ++i)
    564 		if (std::find(this->ModulesAutoLoad.begin(), this->ModulesAutoLoad.end(), old->ModulesAutoLoad[i]) == this->ModulesAutoLoad.end())
    565 			ModuleManager::UnloadModule(ModuleManager::FindModule(old->ModulesAutoLoad[i]), NULL);
    566 	for (unsigned i = 0; i < this->ModulesAutoLoad.size(); ++i)
    567 		if (std::find(old->ModulesAutoLoad.begin(), old->ModulesAutoLoad.end(), this->ModulesAutoLoad[i]) == old->ModulesAutoLoad.end())
    568 			ModuleManager::LoadModule(this->ModulesAutoLoad[i], NULL);
    569 
    570 	/* Apply opertype changes, as non-conf opers still point to the old oper types */
    571 	for (unsigned i = Oper::opers.size(); i > 0; --i)
    572 	{
    573 		Oper *o = Oper::opers[i - 1];
    574 
    575 		/* Oper's type is in the old config, so update it */
    576 		if (std::find(old->MyOperTypes.begin(), old->MyOperTypes.end(), o->ot) != old->MyOperTypes.end())
    577 		{
    578 			OperType *ot = o->ot;
    579 			o->ot = NULL;
    580 
    581 			for (unsigned j = 0; j < MyOperTypes.size(); ++j)
    582 				if (ot->GetName() == MyOperTypes[j]->GetName())
    583 					o->ot = MyOperTypes[j];
    584 
    585 			if (o->ot == NULL)
    586 			{
    587 				/* Oper block has lost type */
    588 				std::vector<Oper *>::iterator it = std::find(old->Opers.begin(), old->Opers.end(), o);
    589 				if (it != old->Opers.end())
    590 					old->Opers.erase(it);
    591 
    592 				it = std::find(this->Opers.begin(), this->Opers.end(), o);
    593 				if (it != this->Opers.end())
    594 					this->Opers.erase(it);
    595 
    596 				delete o;
    597 			}
    598 		}
    599 	}
    600 }
    601 
    602 Block *Conf::GetModule(Module *m)
    603 {
    604 	if (!m)
    605 		return NULL;
    606 
    607 	return GetModule(m->name);
    608 }
    609 
    610 Block *Conf::GetModule(const Anope::string &mname)
    611 {
    612 	std::map<Anope::string, Block *>::iterator it = modules.find(mname);
    613 	if (it != modules.end())
    614 		return it->second;
    615 
    616 	Block* &block = modules[mname];
    617 
    618 	/* Search for the block */
    619 	for (std::pair<block_map::iterator, block_map::iterator> iters = blocks.equal_range("module"); iters.first != iters.second; ++iters.first)
    620 	{
    621 		Block *b = &iters.first->second;
    622 
    623 		if (b->Get<const Anope::string>("name") == mname)
    624 		{
    625 			block = b;
    626 			break;
    627 		}
    628 	}
    629 
    630 	return GetModule(mname);
    631 }
    632 
    633 BotInfo *Conf::GetClient(const Anope::string &cname)
    634 {
    635 	Anope::map<Anope::string>::iterator it = bots.find(cname);
    636 	if (it != bots.end())
    637 		return BotInfo::Find(!it->second.empty() ? it->second : cname, true);
    638 
    639 	Block *block = GetModule(cname.lower());
    640 	const Anope::string &client = block->Get<const Anope::string>("client");
    641 	bots[cname] = client;
    642 	return GetClient(cname);
    643 }
    644 
    645 Block *Conf::GetCommand(CommandSource &source)
    646 {
    647 	const Anope::string &block_name = source.c ? "fantasy" : "command";
    648 
    649 	for (std::pair<block_map::iterator, block_map::iterator> iters = blocks.equal_range(block_name); iters.first != iters.second; ++iters.first)
    650 	{
    651 		Block *b = &iters.first->second;
    652 
    653 		if (b->Get<Anope::string>("name") == source.command)
    654 			return b;
    655 	}
    656 
    657 	return NULL;
    658 }
    659 
    660 File::File(const Anope::string &n, bool e) : name(n), executable(e), fp(NULL)
    661 {
    662 }
    663 
    664 File::~File()
    665 {
    666 	this->Close();
    667 }
    668 
    669 const Anope::string &File::GetName() const
    670 {
    671 	return this->name;
    672 }
    673 
    674 Anope::string File::GetPath() const
    675 {
    676 	return (this->executable ? "" : Anope::ConfigDir + "/") + this->name;
    677 }
    678 
    679 bool File::IsOpen() const
    680 {
    681 	return this->fp != NULL;
    682 }
    683 
    684 bool File::Open()
    685 {
    686 	this->Close();
    687 	this->fp = (this->executable ? popen(this->name.c_str(), "r") : fopen((Anope::ConfigDir + "/" + this->name).c_str(), "r"));
    688 	return this->fp != NULL;
    689 }
    690 
    691 void File::Close()
    692 {
    693 	if (this->fp != NULL)
    694 	{
    695 		if (this->executable)
    696 			pclose(this->fp);
    697 		else
    698 			fclose(this->fp);
    699 		this->fp = NULL;
    700 	}
    701 }
    702 
    703 bool File::End() const
    704 {
    705 	return !this->IsOpen() || feof(this->fp);
    706 }
    707 
    708 Anope::string File::Read()
    709 {
    710 	Anope::string ret;
    711 	char buf[BUFSIZE];
    712 	while (fgets(buf, sizeof(buf), this->fp) != NULL)
    713 	{
    714 		char *nl = strchr(buf, '\n');
    715 		if (nl != NULL)
    716 			*nl = 0;
    717 		else if (!this->End())
    718 		{
    719 			ret += buf;
    720 			continue;
    721 		}
    722 
    723 		ret = buf;
    724 		break;
    725 	}
    726 
    727 	return ret;
    728 }
    729 
    730 void Conf::LoadConf(File &file)
    731 {
    732 	if (file.GetName().empty())
    733 		return;
    734 
    735 	if (!file.Open())
    736 		throw ConfigException("File " + file.GetPath() + " could not be opened.");
    737 
    738 	Anope::string itemname, wordbuffer;
    739 	std::stack<Block *> block_stack;
    740 	int linenumber = 0;
    741 	bool in_word = false, in_quote = false, in_comment = false;
    742 
    743 	Log(LOG_DEBUG) << "Start to read conf " << file.GetPath();
    744 	// Start reading characters...
    745 	while (!file.End())
    746 	{
    747 		Anope::string line = file.Read();
    748 		++linenumber;
    749 
    750 		/* If this line is completely empty and we are in a quote, just append a newline */
    751 		if (line.empty() && in_quote)
    752 			wordbuffer += "\n";
    753 
    754 		for (unsigned c = 0, len = line.length(); c < len; ++c)
    755 		{
    756 			char ch = line[c];
    757 			if (in_quote)
    758 			{
    759 				/* Strip leading white spaces from multi line quotes */
    760 				if (c == 0)
    761 				{
    762 					while (c < len && isspace(line[c]))
    763 						++c;
    764 					ch = line[c];
    765 				}
    766 
    767 				/* Allow \" in quotes */
    768 				if (ch == '\\' && c + 1 < len && line[c + 1] == '"')
    769 					wordbuffer += line[++c];
    770 				else if (ch == '"')
    771 					in_quote = in_word = false;
    772 				else if (ch)
    773 					wordbuffer += ch;
    774 			}
    775 			else if (in_comment)
    776 			{
    777 				if (ch == '*' && c + 1 < len && line[c + 1] == '/')
    778 				{
    779 					in_comment = false;
    780 					++c;
    781 					// We might be at an eol, so continue on and process it
    782 				}
    783 				else
    784 					continue;
    785 			}
    786 			else if (ch == '#' || (ch == '/' && c + 1 < len && line[c + 1] == '/'))
    787 				c = len - 1; // Line comment, ignore the rest of the line (much like this one!)
    788 			else if (ch == '/' && c + 1 < len && line[c + 1] == '*')
    789 			{
    790 				// Multiline (or less than one line) comment
    791 				in_comment = true;
    792 				++c;
    793 				continue;
    794 			}
    795 			else if (!in_word && (ch == '(' || ch == '_' || ch == ')'))
    796 				;
    797 			else if (ch == '"')
    798 			{
    799 				// Quotes are valid only in the value position
    800 				if (block_stack.empty() || itemname.empty())
    801 				{
    802 					file.Close();
    803 					throw ConfigException("Unexpected quoted string: " + file.GetName() + ":" + stringify(linenumber));
    804 				}
    805 				if (in_word || !wordbuffer.empty())
    806 				{
    807 					file.Close();
    808 					throw ConfigException("Unexpected quoted string (prior unhandled words): " + file.GetName() + ":" + stringify(linenumber));
    809 				}
    810 				in_quote = in_word = true;
    811 			}
    812 			else if (ch == '=')
    813 			{
    814 				if (block_stack.empty())
    815 				{
    816 					file.Close();
    817 					throw ConfigException("Config item outside of section (or stray '='): " + file.GetName() + ":" + stringify(linenumber));
    818 				}
    819 
    820 				if (!itemname.empty() || wordbuffer.empty())
    821 				{
    822 					file.Close();
    823 					throw ConfigException("Stray '=' sign or item without value: " + file.GetName() + ":" + stringify(linenumber));
    824 				}
    825 
    826 				in_word = false;
    827 				itemname = wordbuffer;
    828 				wordbuffer.clear();
    829 			}
    830 			else if (ch == '{')
    831 			{
    832 				if (wordbuffer.empty())
    833 				{
    834 					block_stack.push(NULL);
    835 					// Commented or unnamed section
    836 					continue;
    837 				}
    838 
    839 				if (!block_stack.empty() && !block_stack.top())
    840 				{
    841 					// Named block inside of a commented block
    842 					in_word = false;
    843 					wordbuffer.clear();
    844 					block_stack.push(NULL);
    845 					continue;
    846 				}
    847 
    848 				Block *b = block_stack.empty() ? this : block_stack.top();
    849 				block_map::iterator it = b->blocks.insert(std::make_pair(wordbuffer, Configuration::Block(wordbuffer)));
    850 				b = &it->second;
    851 				b->linenum = linenumber;
    852 				block_stack.push(b);
    853 
    854 				in_word = false;
    855 				wordbuffer.clear();
    856 				continue;
    857 			}
    858 			else if (ch == ' ' || ch == '\r' || ch == '\t')
    859 			{
    860 				// Terminate word
    861 				in_word = false;
    862 			}
    863 			else if (ch == ';' || ch == '}')
    864 				;
    865 			else
    866 			{
    867 				if (!in_word && !wordbuffer.empty())
    868 				{
    869 					file.Close();
    870 					throw ConfigException("Unexpected word: " + file.GetName() + ":" + stringify(linenumber));
    871 				}
    872 				wordbuffer += ch;
    873 				in_word = true;
    874 			}
    875 
    876 			if (ch == ';' || ch == '}' || c + 1 >= len)
    877 			{
    878 				bool eol = c + 1 >= len;
    879 
    880 				if (!eol && in_quote)
    881 					// Allow ; and } in quoted strings
    882 					continue;
    883 
    884 				if (in_quote)
    885 				{
    886 					// Quotes can span multiple lines; all we need to do is go to the next line without clearing things
    887 					wordbuffer += "\n";
    888 					continue;
    889 				}
    890 
    891 				in_word = false;
    892 				if (!itemname.empty())
    893 				{
    894 					if (block_stack.empty())
    895 					{
    896 						file.Close();
    897 						throw ConfigException("Stray ';' outside of block: " + file.GetName() + ":" + stringify(linenumber));
    898 					}
    899 
    900 					Block *b = block_stack.top();
    901 
    902 					if (b)
    903 						Log(LOG_DEBUG) << "ln " << linenumber << " EOL: s='" << b->name << "' '" << itemname << "' set to '" << wordbuffer << "'";
    904 
    905 					/* Check defines */
    906 					for (int i = 0; i < this->CountBlock("define"); ++i)
    907 					{
    908 						Block *define = this->GetBlock("define", i);
    909 
    910 						const Anope::string &dname = define->Get<const Anope::string>("name");
    911 
    912 						if (dname == wordbuffer && define != b)
    913 							wordbuffer = define->Get<const Anope::string>("value");
    914 					}
    915 
    916 					if (b)
    917 						b->items[itemname] = wordbuffer;
    918 
    919 					wordbuffer.clear();
    920 					itemname.clear();
    921 				}
    922 
    923 				if (ch == '}')
    924 				{
    925 					if (block_stack.empty())
    926 					{
    927 						file.Close();
    928 						throw ConfigException("Stray '}': " + file.GetName() + ":" + stringify(linenumber));
    929 					}
    930 
    931 					block_stack.pop();
    932 				}
    933 			}
    934 		}
    935 	}
    936 
    937 	file.Close();
    938 
    939 	if (in_comment)
    940 		throw ConfigException("Unterminated multiline comment at end of file: " + file.GetName());
    941 	if (in_quote)
    942 		throw ConfigException("Unterminated quote at end of file: " + file.GetName());
    943 	if (!itemname.empty() || !wordbuffer.empty())
    944 		throw ConfigException("Unexpected garbage at end of file: " + file.GetName());
    945 	if (!block_stack.empty())
    946 		throw ConfigException("Unterminated block at end of file: " + file.GetName() + ". Block was opened on line " + stringify(block_stack.top()->linenum));
    947 }