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 }