-/* +------------------------------------+\r * | Inspire Internet Relay Chat Daemon |\r * +------------------------------------+\r *\r * InspIRCd: (C) 2002-2007 InspIRCd Development Team\r * See: http://www.inspircd.org/wiki/index.php/Credits\r *\r * This program is free but copyrighted software; see\r * the file COPYING for details.\r *\r * ---------------------------------------------------\r */\r\r#include "inspircd.h"\r#include "configreader.h"\r#include <sstream>\r#include <fstream>\r#include "xline.h"\r#include "exitcodes.h"\r#include "commands/cmd_whowas.h"\r\rstd::vector<std::string> old_module_names, new_module_names, added_modules, removed_modules;\r\rServerConfig::ServerConfig(InspIRCd* Instance) : ServerInstance(Instance)\r{\r this->ClearStack();\r *ServerName = *Network = *ServerDesc = *AdminName = '\0';\r *HideWhoisServer = *AdminEmail = *AdminNick = *diepass = *restartpass = *FixedQuit = *HideKillsServer = '\0';\r *DefaultModes = *CustomVersion = *motd = *rules = *PrefixQuit = *DieValue = *DNSServer = '\0';\r *UserStats = *ModPath = *MyExecutable = *DisabledCommands = *PID = *SuffixQuit = '\0';\r WhoWasGroupSize = WhoWasMaxGroups = WhoWasMaxKeep = 0;\r log_file = NULL;\r NoUserDns = forcedebug = OperSpyWhois = nofork = HideBans = HideSplits = UndernetMsgPrefix = false;\r CycleHosts = writelog = AllowHalfop = true;\r dns_timeout = DieDelay = 5;\r MaxTargets = 20;\r NetBufferSize = 10240;\r SoftLimit = MAXCLIENTS;\r MaxConn = SOMAXCONN;\r MaxWhoResults = 0;\r debugging = 0;\r MaxChans = 20;\r OperMaxChans = 30;\r LogLevel = DEFAULT;\r maxbans.clear();\r}\r\rvoid ServerConfig::ClearStack()\r{\r include_stack.clear();\r}\r\rModule* ServerConfig::GetIOHook(int port)\r{\r std::map<int,Module*>::iterator x = IOHookModule.find(port);\r return (x != IOHookModule.end() ? x->second : NULL);\r}\r\rModule* ServerConfig::GetIOHook(InspSocket* is)\r{\r std::map<InspSocket*,Module*>::iterator x = SocketIOHookModule.find(is);\r return (x != SocketIOHookModule.end() ? x->second : NULL);\r}\r\rbool ServerConfig::AddIOHook(int port, Module* iomod)\r{\r if (!GetIOHook(port))\r {\r IOHookModule[port] = iomod;\r return true;\r }\r else\r {\r throw ModuleException("Port already hooked by another module");\r return false;\r }\r}\r\rbool ServerConfig::AddIOHook(Module* iomod, InspSocket* is)\r{\r if (!GetIOHook(is))\r {\r SocketIOHookModule[is] = iomod;\r is->IsIOHooked = true;\r return true;\r }\r else\r {\r throw ModuleException("InspSocket derived class already hooked by another module");\r return false;\r }\r}\r\rbool ServerConfig::DelIOHook(int port)\r{\r std::map<int,Module*>::iterator x = IOHookModule.find(port);\r if (x != IOHookModule.end())\r {\r IOHookModule.erase(x);\r return true;\r }\r return false;\r}\r\rbool ServerConfig::DelIOHook(InspSocket* is)\r{\r std::map<InspSocket*,Module*>::iterator x = SocketIOHookModule.find(is);\r if (x != SocketIOHookModule.end())\r {\r SocketIOHookModule.erase(x);\r return true;\r }\r return false;\r}\r\rvoid ServerConfig::Update005()\r{\r std::stringstream out(data005);\r std::string token;\r std::string line5;\r int token_counter = 0;\r isupport.clear();\r while (out >> token)\r {\r line5 = line5 + token + " ";\r token_counter++;\r if (token_counter >= 13)\r {\r char buf[MAXBUF];\r snprintf(buf, MAXBUF, "%s:are supported by this server", line5.c_str());\r isupport.push_back(buf);\r line5.clear();\r token_counter = 0;\r }\r }\r if (!line5.empty())\r {\r char buf[MAXBUF];\r snprintf(buf, MAXBUF, "%s:are supported by this server", line5.c_str());\r isupport.push_back(buf);\r }\r}\r\rvoid ServerConfig::Send005(userrec* user)\r{\r for (std::vector<std::string>::iterator line = ServerInstance->Config->isupport.begin(); line != ServerInstance->Config->isupport.end(); line++)\r user->WriteServ("005 %s %s", user->nick, line->c_str());\r}\r\rbool ServerConfig::CheckOnce(char* tag, bool bail, userrec* user)\r{\r int count = ConfValueEnum(this->config_data, tag);\r\r if (count > 1)\r {\r throw CoreException("You have more than one <"+std::string(tag)+"> tag, this is not permitted.");\r return false;\r }\r if (count < 1)\r {\r throw CoreException("You have not defined a <"+std::string(tag)+"> tag, this is required.");\r return false;\r }\r return true;\r}\r\rbool NoValidation(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r return true;\r}\r\rbool ValidateMaxTargets(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r if ((data.GetInteger() < 0) || (data.GetInteger() > 31))\r {\r conf->GetInstance()->Log(DEFAULT,"WARNING: <options:maxtargets> value is greater than 31 or less than 0, set to 20.");\r data.Set(20);\r }\r return true;\r}\r\rbool ValidateSoftLimit(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r if ((data.GetInteger() < 1) || (data.GetInteger() > MAXCLIENTS))\r {\r conf->GetInstance()->Log(DEFAULT,"WARNING: <options:softlimit> value is greater than %d or less than 0, set to %d.",MAXCLIENTS,MAXCLIENTS);\r data.Set(MAXCLIENTS);\r }\r return true;\r}\r\rbool ValidateMaxConn(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r if (data.GetInteger() > SOMAXCONN)\r conf->GetInstance()->Log(DEFAULT,"WARNING: <options:somaxconn> value may be higher than the system-defined SOMAXCONN value!");\r return true;\r}\r\rbool InitializeDisabledCommands(const char* data, InspIRCd* ServerInstance)\r{\r std::stringstream dcmds(data);\r std::string thiscmd;\r\r /* Enable everything first */\r for (command_table::iterator x = ServerInstance->Parser->cmdlist.begin(); x != ServerInstance->Parser->cmdlist.end(); x++)\r x->second->Disable(false);\r\r /* Now disable all the ones which the user wants disabled */\r while (dcmds >> thiscmd)\r {\r command_table::iterator cm = ServerInstance->Parser->cmdlist.find(thiscmd);\r if (cm != ServerInstance->Parser->cmdlist.end())\r {\r cm->second->Disable(true);\r }\r }\r return true;\r}\r\rbool ValidateDnsServer(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r if (!*(data.GetString()))\r {\r std::string nameserver;\r#ifdef WINDOWS\r conf->GetInstance()->Log(DEFAULT,"WARNING: <dns:server> not defined, attempting to find working server in the registry...");\r nameserver = FindNameServerWin();\r /* Windows stacks multiple nameservers in one registry key, seperated by commas.\r * Spotted by Cataclysm.\r */\r if (nameserver.find(',') != std::string::npos)\r nameserver = nameserver.substr(0, nameserver.find(','));\r data.Set(nameserver.c_str());\r conf->GetInstance()->Log(DEFAULT,"<dns:server> set to '%s' as first active resolver in registry.", nameserver.c_str());\r#else\r // attempt to look up their nameserver from /etc/resolv.conf\r conf->GetInstance()->Log(DEFAULT,"WARNING: <dns:server> not defined, attempting to find working server in /etc/resolv.conf...");\r ifstream resolv("/etc/resolv.conf");\r bool found_server = false;\r\r if (resolv.is_open())\r {\r while (resolv >> nameserver)\r {\r if ((nameserver == "nameserver") && (!found_server))\r {\r resolv >> nameserver;\r data.Set(nameserver.c_str());\r found_server = true;\r conf->GetInstance()->Log(DEFAULT,"<dns:server> set to '%s' as first resolver in /etc/resolv.conf.",nameserver.c_str());\r }\r }\r\r if (!found_server)\r {\r conf->GetInstance()->Log(DEFAULT,"/etc/resolv.conf contains no viable nameserver entries! Defaulting to nameserver '127.0.0.1'!");\r data.Set("127.0.0.1");\r }\r }\r else\r {\r conf->GetInstance()->Log(DEFAULT,"/etc/resolv.conf can't be opened! Defaulting to nameserver '127.0.0.1'!");\r data.Set("127.0.0.1");\r }\r#endif\r }\r return true;\r}\r\rbool ValidateServerName(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r /* If we already have a servername, and they changed it, we should throw an exception. */\r if ((strcasecmp(conf->ServerName, data.GetString())) && (*conf->ServerName))\r {\r throw CoreException("Configuration error: You cannot change your servername at runtime! Please restart your server for this change to be applied.");\r /* XXX: We don't actually reach this return of course... */\r return false;\r }\r if (!strchr(data.GetString(),'.'))\r {\r conf->GetInstance()->Log(DEFAULT,"WARNING: <server:name> '%s' is not a fully-qualified domain name. Changed to '%s%c'",data.GetString(),data.GetString(),'.');\r std::string moo = std::string(data.GetString()).append(".");\r data.Set(moo.c_str());\r }\r return true;\r}\r\rbool ValidateNetBufferSize(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r if ((!data.GetInteger()) || (data.GetInteger() > 65535) || (data.GetInteger() < 1024))\r {\r conf->GetInstance()->Log(DEFAULT,"No NetBufferSize specified or size out of range, setting to default of 10240.");\r data.Set(10240);\r }\r return true;\r}\r\rbool ValidateMaxWho(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r if ((data.GetInteger() > 65535) || (data.GetInteger() < 1))\r {\r conf->GetInstance()->Log(DEFAULT,"<options:maxwhoresults> size out of range, setting to default of 128.");\r data.Set(128);\r }\r return true;\r}\r\rbool ValidateLogLevel(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r std::string dbg = data.GetString();\r conf->LogLevel = DEFAULT;\r\r if (dbg == "debug")\r conf->LogLevel = DEBUG;\r else if (dbg == "verbose")\r conf->LogLevel = VERBOSE;\r else if (dbg == "default")\r conf->LogLevel = DEFAULT;\r else if (dbg == "sparse")\r conf->LogLevel = SPARSE;\r else if (dbg == "none")\r conf->LogLevel = NONE;\r\r conf->debugging = (conf->LogLevel == DEBUG);\r\r return true;\r}\r\rbool ValidateMotd(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r conf->ReadFile(conf->MOTD, data.GetString());\r return true;\r}\r\rbool ValidateNotEmpty(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r if (!*data.GetString())\r throw CoreException(std::string("The value for ")+tag+" cannot be empty!");\r return true;\r}\r\rbool ValidateRules(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r conf->ReadFile(conf->RULES, data.GetString());\r return true;\r}\r\rbool ValidateModeLists(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r memset(conf->HideModeLists, 0, 256);\r for (const unsigned char* x = (const unsigned char*)data.GetString(); *x; ++x)\r conf->HideModeLists[*x] = true;\r return true;\r}\r\rbool ValidateExemptChanOps(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r memset(conf->ExemptChanOps, 0, 256);\r for (const unsigned char* x = (const unsigned char*)data.GetString(); *x; ++x)\r conf->ExemptChanOps[*x] = true;\r return true;\r}\r\rbool ValidateWhoWas(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)\r{\r conf->WhoWasMaxKeep = conf->GetInstance()->Duration(data.GetString());\r\r if (conf->WhoWasGroupSize < 0)\r conf->WhoWasGroupSize = 0;\r\r if (conf->WhoWasMaxGroups < 0)\r conf->WhoWasMaxGroups = 0;\r\r if (conf->WhoWasMaxKeep < 3600)\r {\r conf->WhoWasMaxKeep = 3600;\r conf->GetInstance()->Log(DEFAULT,"WARNING: <whowas:maxkeep> value less than 3600, setting to default 3600");\r }\r\r command_t* whowas_command = conf->GetInstance()->Parser->GetHandler("WHOWAS");\r if (whowas_command)\r {\r std::deque<classbase*> params;\r whowas_command->HandleInternal(WHOWAS_PRUNE, params);\r }\r\r return true;\r}\r\r/* Callback called before processing the first <connect> tag\r */\rbool InitConnect(ServerConfig* conf, const char* tag)\r{\r conf->GetInstance()->Log(DEFAULT,"Reading connect classes...");\r conf->Classes.clear();\r return true;\r}\r\r/* Callback called to process a single <connect> tag\r */\rbool DoConnect(ServerConfig* conf, const char* tag, char** entries, ValueList &values, int* types)\r{\r ConnectClass c;\r const char* allow = values[0].GetString(); /* Yeah, there are a lot of values. Live with it. */\r const char* deny = values[1].GetString();\r const char* password = values[2].GetString();\r int timeout = values[3].GetInteger();\r int pingfreq = values[4].GetInteger();\r int flood = values[5].GetInteger();\r int threshold = values[6].GetInteger();\r int sendq = values[7].GetInteger();\r int recvq = values[8].GetInteger();\r int localmax = values[9].GetInteger();\r int globalmax = values[10].GetInteger();\r\r if (*allow)\r {\r ConnectClass c(timeout, flood, allow, pingfreq, password, threshold, sendq, recvq, localmax, globalmax);\r conf->Classes.push_back(c);\r }\r else\r {\r ConnectClass c(deny);\r conf->Classes.push_back(c);\r }\r\r return true;\r}\r\r/* Callback called when there are no more <connect> tags\r */\rbool DoneConnect(ServerConfig* conf, const char* tag)\r{\r return true;\r}\r\r/* Callback called before processing the first <uline> tag\r */\rbool InitULine(ServerConfig* conf, const char* tag)\r{\r conf->ulines.clear();\r return true;\r}\r\r/* Callback called to process a single <uline> tag\r */\rbool DoULine(ServerConfig* conf, const char* tag, char** entries, ValueList &values, int* types)\r{\r const char* server = values[0].GetString();\r const bool silent = values[1].GetBool();\r conf->ulines[server] = silent;\r return true;\r}\r\r/* Callback called when there are no more <uline> tags\r */\rbool DoneULine(ServerConfig* conf, const char* tag)\r{\r return true;\r}\r\r/* Callback called before processing the first <module> tag\r */\rbool InitModule(ServerConfig* conf, const char* tag)\r{\r old_module_names.clear();\r new_module_names.clear();\r added_modules.clear();\r removed_modules.clear();\r for (std::vector<std::string>::iterator t = conf->module_names.begin(); t != conf->module_names.end(); t++)\r {\r old_module_names.push_back(*t);\r }\r return true;\r}\r\r/* Callback called to process a single <module> tag\r */\rbool DoModule(ServerConfig* conf, const char* tag, char** entries, ValueList &values, int* types)\r{\r const char* modname = values[0].GetString();\r new_module_names.push_back(modname);\r return true;\r}\r\r/* Callback called when there are no more <module> tags\r */\rbool DoneModule(ServerConfig* conf, const char* tag)\r{\r // now create a list of new modules that are due to be loaded\r // and a seperate list of modules which are due to be unloaded\r for (std::vector<std::string>::iterator _new = new_module_names.begin(); _new != new_module_names.end(); _new++)\r {\r bool added = true;\r\r for (std::vector<std::string>::iterator old = old_module_names.begin(); old != old_module_names.end(); old++)\r {\r if (*old == *_new)\r added = false;\r }\r\r if (added)\r added_modules.push_back(*_new);\r }\r\r for (std::vector<std::string>::iterator oldm = old_module_names.begin(); oldm != old_module_names.end(); oldm++)\r {\r bool removed = true;\r for (std::vector<std::string>::iterator newm = new_module_names.begin(); newm != new_module_names.end(); newm++)\r {\r if (*newm == *oldm)\r removed = false;\r }\r\r if (removed)\r removed_modules.push_back(*oldm);\r }\r return true;\r}\r\r/* Callback called before processing the first <banlist> tag\r */\rbool InitMaxBans(ServerConfig* conf, const char* tag)\r{\r conf->maxbans.clear();\r return true;\r}\r\r/* Callback called to process a single <banlist> tag\r */\rbool DoMaxBans(ServerConfig* conf, const char* tag, char** entries, ValueList &values, int* types)\r{\r const char* channel = values[0].GetString();\r int limit = values[1].GetInteger();\r conf->maxbans[channel] = limit;\r return true;\r}\r\r/* Callback called when there are no more <banlist> tags.\r */\rbool DoneMaxBans(ServerConfig* conf, const char* tag)\r{\r return true;\r}\r\rvoid ServerConfig::ReportConfigError(const std::string &errormessage, bool bail, userrec* user)\r{\r ServerInstance->Log(DEFAULT, "There were errors in your configuration file: %s", errormessage.c_str());\r if (bail)\r {\r /* Unneeded because of the ServerInstance->Log() aboive? */\r printf("There were errors in your configuration:\n%s\n\n",errormessage.c_str());\r InspIRCd::Exit(EXIT_STATUS_CONFIG);\r }\r else\r {\r std::string errors = errormessage;\r std::string::size_type start;\r unsigned int prefixlen;\r start = 0;\r /* ":ServerInstance->Config->ServerName NOTICE user->nick :" */\r if (user)\r {\r prefixlen = strlen(this->ServerName) + strlen(user->nick) + 11;\r user->WriteServ("NOTICE %s :There were errors in the configuration file:",user->nick);\r while (start < errors.length())\r {\r user->WriteServ("NOTICE %s :%s",user->nick, errors.substr(start, 510 - prefixlen).c_str());\r start += 510 - prefixlen;\r }\r }\r else\r {\r ServerInstance->WriteOpers("There were errors in the configuration file:");\r while (start < errors.length())\r {\r ServerInstance->WriteOpers(errors.substr(start, 360).c_str());\r start += 360;\r }\r }\r return;\r }\r}\r\rvoid ServerConfig::Read(bool bail, userrec* user)\r{\r static char debug[MAXBUF]; /* Temporary buffer for debugging value */\r static char maxkeep[MAXBUF]; /* Temporary buffer for WhoWasMaxKeep value */\r static char hidemodes[MAXBUF]; /* Modes to not allow listing from users below halfop */\r static char exemptchanops[MAXBUF]; /* Exempt channel ops from these modes */\r int rem = 0, add = 0; /* Number of modules added, number of modules removed */\r std::ostringstream errstr; /* String stream containing the error output */\r\r /* These tags MUST occur and must ONLY occur once in the config file */\r static char* Once[] = { "server", "admin", "files", "power", "options", NULL };\r\r /* These tags can occur ONCE or not at all */\r InitialConfig Values[] = {\r {"options", "softlimit", MAXCLIENTS_S, new ValueContainerUInt (&this->SoftLimit), DT_INTEGER, ValidateSoftLimit},\r {"options", "somaxconn", SOMAXCONN_S, new ValueContainerInt (&this->MaxConn), DT_INTEGER, ValidateMaxConn},\r {"options", "moronbanner", "Youre banned!", new ValueContainerChar (this->MoronBanner), DT_CHARPTR, NoValidation},\r {"server", "name", "", new ValueContainerChar (this->ServerName), DT_CHARPTR, ValidateServerName},\r {"server", "description", "Configure Me", new ValueContainerChar (this->ServerDesc), DT_CHARPTR, NoValidation},\r {"server", "network", "Network", new ValueContainerChar (this->Network), DT_CHARPTR, NoValidation},\r {"admin", "name", "", new ValueContainerChar (this->AdminName), DT_CHARPTR, NoValidation},\r {"admin", "email", "Mis@configu.red", new ValueContainerChar (this->AdminEmail), DT_CHARPTR, NoValidation},\r {"admin", "nick", "Misconfigured", new ValueContainerChar (this->AdminNick), DT_CHARPTR, NoValidation},\r {"files", "motd", "", new ValueContainerChar (this->motd), DT_CHARPTR, ValidateMotd},\r {"files", "rules", "", new ValueContainerChar (this->rules), DT_CHARPTR, ValidateRules},\r {"power", "diepass", "", new ValueContainerChar (this->diepass), DT_CHARPTR, ValidateNotEmpty},\r {"power", "pause", "", new ValueContainerInt (&this->DieDelay), DT_INTEGER, NoValidation},\r {"power", "restartpass", "", new ValueContainerChar (this->restartpass), DT_CHARPTR, ValidateNotEmpty},\r {"options", "prefixquit", "", new ValueContainerChar (this->PrefixQuit), DT_CHARPTR, NoValidation},\r {"options", "suffixquit", "", new ValueContainerChar (this->SuffixQuit), DT_CHARPTR, NoValidation},\r {"options", "fixedquit", "", new ValueContainerChar (this->FixedQuit), DT_CHARPTR, NoValidation},\r {"options", "loglevel", "default", new ValueContainerChar (debug), DT_CHARPTR, ValidateLogLevel},\r {"options", "netbuffersize","10240", new ValueContainerInt (&this->NetBufferSize), DT_INTEGER, ValidateNetBufferSize},\r {"options", "maxwho", "128", new ValueContainerInt (&this->MaxWhoResults), DT_INTEGER, ValidateMaxWho},\r {"options", "allowhalfop", "0", new ValueContainerBool (&this->AllowHalfop), DT_BOOLEAN, NoValidation},\r {"dns", "server", "", new ValueContainerChar (this->DNSServer), DT_CHARPTR, ValidateDnsServer},\r {"dns", "timeout", "5", new ValueContainerInt (&this->dns_timeout), DT_INTEGER, NoValidation},\r {"options", "moduledir", MOD_PATH, new ValueContainerChar (this->ModPath), DT_CHARPTR, NoValidation},\r {"disabled", "commands", "", new ValueContainerChar (this->DisabledCommands), DT_CHARPTR, NoValidation},\r {"options", "userstats", "", new ValueContainerChar (this->UserStats), DT_CHARPTR, NoValidation},\r {"options", "customversion","", new ValueContainerChar (this->CustomVersion), DT_CHARPTR, NoValidation},\r {"options", "hidesplits", "0", new ValueContainerBool (&this->HideSplits), DT_BOOLEAN, NoValidation},\r {"options", "hidebans", "0", new ValueContainerBool (&this->HideBans), DT_BOOLEAN, NoValidation},\r {"options", "hidewhois", "", new ValueContainerChar (this->HideWhoisServer), DT_CHARPTR, NoValidation},\r {"options", "hidekills", "", new ValueContainerChar (this->HideKillsServer), DT_CHARPTR, NoValidation},\r {"options", "operspywhois", "0", new ValueContainerBool (&this->OperSpyWhois), DT_BOOLEAN, NoValidation},\r {"options", "nouserdns", "0", new ValueContainerBool (&this->NoUserDns), DT_BOOLEAN, NoValidation},\r {"options", "syntaxhints", "0", new ValueContainerBool (&this->SyntaxHints), DT_BOOLEAN, NoValidation},\r {"options", "cyclehosts", "0", new ValueContainerBool (&this->CycleHosts), DT_BOOLEAN, NoValidation},\r {"options", "ircumsgprefix","0", new ValueContainerBool (&this->UndernetMsgPrefix), DT_BOOLEAN, NoValidation},\r {"options", "announceinvites", "1", new ValueContainerBool (&this->AnnounceInvites), DT_BOOLEAN, NoValidation},\r {"options", "hostintopic", "1", new ValueContainerBool (&this->FullHostInTopic), DT_BOOLEAN, NoValidation},\r {"options", "hidemodes", "", new ValueContainerChar (hidemodes), DT_CHARPTR, ValidateModeLists},\r {"options", "exemptchanops","", new ValueContainerChar (exemptchanops), DT_CHARPTR, ValidateExemptChanOps},\r {"options", "defaultmodes", "nt", new ValueContainerChar (this->DefaultModes), DT_CHARPTR, NoValidation},\r {"pid", "file", "", new ValueContainerChar (this->PID), DT_CHARPTR, NoValidation},\r {"whowas", "groupsize", "10", new ValueContainerInt (&this->WhoWasGroupSize), DT_INTEGER, NoValidation},\r {"whowas", "maxgroups", "10240", new ValueContainerInt (&this->WhoWasMaxGroups), DT_INTEGER, NoValidation},\r {"whowas", "maxkeep", "3600", new ValueContainerChar (maxkeep), DT_CHARPTR, ValidateWhoWas},\r {"die", "value", "", new ValueContainerChar (this->DieValue), DT_CHARPTR, NoValidation},\r {"channels", "users", "20", new ValueContainerUInt (&this->MaxChans), DT_INTEGER, NoValidation},\r {"channels", "opers", "60", new ValueContainerUInt (&this->OperMaxChans), DT_INTEGER, NoValidation},\r {NULL}\r };\r\r /* These tags can occur multiple times, and therefore they have special code to read them\r * which is different to the code for reading the singular tags listed above.\r */\r MultiConfig MultiValues[] = {\r\r {"connect",\r {"allow", "deny", "password", "timeout", "pingfreq", "flood",\r "threshold", "sendq", "recvq", "localmax", "globalmax", "port",\r NULL},\r {"", "", "", "", "120", "",\r "", "", "", "3", "3", "0",\r NULL},\r {DT_CHARPTR, DT_CHARPTR, DT_CHARPTR, DT_INTEGER, DT_INTEGER, DT_INTEGER,\r DT_INTEGER, DT_INTEGER, DT_INTEGER, DT_INTEGER, DT_INTEGER, DT_INTEGER},\r InitConnect, DoConnect, DoneConnect},\r\r {"uline",\r {"server", "silent", NULL},\r {"", "0", NULL},\r {DT_CHARPTR, DT_BOOLEAN},\r InitULine,DoULine,DoneULine},\r\r {"banlist",\r {"chan", "limit", NULL},\r {"", "", NULL},\r {DT_CHARPTR, DT_INTEGER},\r InitMaxBans, DoMaxBans, DoneMaxBans},\r\r {"module",\r {"name", NULL},\r {"", NULL},\r {DT_CHARPTR},\r InitModule, DoModule, DoneModule},\r\r {"badip",\r {"reason", "ipmask", NULL},\r {"No reason", "", NULL},\r {DT_CHARPTR, DT_CHARPTR},\r InitXLine, DoZLine, DoneZLine},\r\r {"badnick",\r {"reason", "nick", NULL},\r {"No reason", "", NULL},\r {DT_CHARPTR, DT_CHARPTR},\r InitXLine, DoQLine, DoneQLine},\r\r {"badhost",\r {"reason", "host", NULL},\r {"No reason", "", NULL},\r {DT_CHARPTR, DT_CHARPTR},\r InitXLine, DoKLine, DoneKLine},\r\r {"exception",\r {"reason", "host", NULL},\r {"No reason", "", NULL},\r {DT_CHARPTR, DT_CHARPTR},\r InitXLine, DoELine, DoneELine},\r\r {"type",\r {"name", "classes", NULL},\r {"", "", NULL},\r {DT_CHARPTR, DT_CHARPTR},\r InitTypes, DoType, DoneClassesAndTypes},\r\r {"class",\r {"name", "commands", NULL},\r {"", "", NULL},\r {DT_CHARPTR, DT_CHARPTR},\r InitClasses, DoClass, DoneClassesAndTypes},\r\r {NULL}\r };\r\r include_stack.clear();\r\r /* Load and parse the config file, if there are any errors then explode */\r\r /* Make a copy here so if it fails then we can carry on running with an unaffected config */\r ConfigDataHash newconfig;\r\r if (this->LoadConf(newconfig, ServerInstance->ConfigFileName, errstr))\r {\r /* If we succeeded, set the ircd config to the new one */\r this->config_data = newconfig;\r }\r else\r {\r ReportConfigError(errstr.str(), bail, user);\r return;\r }\r\r /* The stuff in here may throw CoreException, be sure we're in a position to catch it. */\r try\r {\r /* Check we dont have more than one of singular tags, or any of them missing\r */\r for (int Index = 0; Once[Index]; Index++)\r if (!CheckOnce(Once[Index], bail, user))\r return;\r\r /* Read the values of all the tags which occur once or not at all, and call their callbacks.\r */\r for (int Index = 0; Values[Index].tag; Index++)\r {\r char item[MAXBUF];\r int dt = Values[Index].datatype;\r bool allow_newlines = ((dt & DT_ALLOW_NEWLINE) > 0);\r dt &= ~DT_ALLOW_NEWLINE;\r\r ConfValue(this->config_data, Values[Index].tag, Values[Index].value, Values[Index].default_value, 0, item, MAXBUF, allow_newlines);\r ValueItem vi(item);\r\r if (!Values[Index].validation_function(this, Values[Index].tag, Values[Index].value, vi))\r throw CoreException("One or more values in your configuration file failed to validate. Please see your ircd.log for more information.");\r\r switch (Values[Index].datatype)\r {\r case DT_CHARPTR:\r {\r ValueContainerChar* vcc = (ValueContainerChar*)Values[Index].val;\r /* Make sure we also copy the null terminator */\r vcc->Set(vi.GetString(), strlen(vi.GetString()) + 1);\r }\r break;\r case DT_INTEGER:\r {\r int val = vi.GetInteger();\r ValueContainerInt* vci = (ValueContainerInt*)Values[Index].val;\r vci->Set(&val, sizeof(int));\r }\r break;\r case DT_BOOLEAN:\r {\r bool val = vi.GetBool();\r ValueContainerBool* vcb = (ValueContainerBool*)Values[Index].val;\r vcb->Set(&val, sizeof(bool));\r }\r break;\r default:\r /* You don't want to know what happens if someones bad code sends us here. */\r break;\r }\r\r /* We're done with this now */\r delete Values[Index].val;\r }\r\r /* Read the multiple-tag items (class tags, connect tags, etc)\r * and call the callbacks associated with them. We have three\r * callbacks for these, a 'start', 'item' and 'end' callback.\r */\r for (int Index = 0; MultiValues[Index].tag; Index++)\r {\r MultiValues[Index].init_function(this, MultiValues[Index].tag);\r\r int number_of_tags = ConfValueEnum(this->config_data, MultiValues[Index].tag);\r\r for (int tagnum = 0; tagnum < number_of_tags; tagnum++)\r {\r ValueList vl;\r for (int valuenum = 0; MultiValues[Index].items[valuenum]; valuenum++)\r {\r int dt = MultiValues[Index].datatype[valuenum];\r bool allow_newlines = ((dt & DT_ALLOW_NEWLINE) > 0);\r dt &= ~DT_ALLOW_NEWLINE;\r\r switch (dt)\r {\r case DT_CHARPTR:\r {\r char item[MAXBUF];\r if (ConfValue(this->config_data, MultiValues[Index].tag, MultiValues[Index].items[valuenum], MultiValues[Index].items_default[valuenum], tagnum, item, MAXBUF, allow_newlines))\r vl.push_back(ValueItem(item));\r else\r vl.push_back(ValueItem(""));\r }\r break;\r case DT_INTEGER:\r {\r int item = 0;\r if (ConfValueInteger(this->config_data, MultiValues[Index].tag, MultiValues[Index].items[valuenum], MultiValues[Index].items_default[valuenum], tagnum, item))\r vl.push_back(ValueItem(item));\r else\r vl.push_back(ValueItem(0));\r }\r break;\r case DT_BOOLEAN:\r {\r bool item = ConfValueBool(this->config_data, MultiValues[Index].tag, MultiValues[Index].items[valuenum], MultiValues[Index].items_default[valuenum], tagnum);\r vl.push_back(ValueItem(item));\r }\r break;\r default:\r /* Someone was smoking craq if we got here, and we're all gonna die. */\r break;\r }\r }\r\r MultiValues[Index].validation_function(this, MultiValues[Index].tag, (char**)MultiValues[Index].items, vl, MultiValues[Index].datatype);\r }\r\r MultiValues[Index].finish_function(this, MultiValues[Index].tag);\r }\r\r }\r\r catch (CoreException &ce)\r {\r ReportConfigError(ce.GetReason(), bail, user);\r return;\r }\r\r // write once here, to try it out and make sure its ok\r ServerInstance->WritePID(this->PID);\r\r ServerInstance->Log(DEFAULT,"Done reading configuration file.");\r\r /* If we're rehashing, let's load any new modules, and unload old ones\r */\r if (!bail)\r {\r int found_ports = 0;\r FailedPortList pl;\r ServerInstance->BindPorts(false, found_ports, pl);\r\r if (pl.size())\r {\r user->WriteServ("NOTICE %s :*** Not all your client ports could be bound.", user->nick);\r user->WriteServ("NOTICE %s :*** The following port(s) failed to bind:", user->nick);\r int j = 1;\r for (FailedPortList::iterator i = pl.begin(); i != pl.end(); i++, j++)\r {\r user->WriteServ("NOTICE %s :*** %d. IP: %s Port: %lu", user->nick, j, i->first.empty() ? "<all>" : i->first.c_str(), (unsigned long)i->second);\r }\r }\r\r if (!removed_modules.empty())\r {\r for (std::vector<std::string>::iterator removing = removed_modules.begin(); removing != removed_modules.end(); removing++)\r {\r if (ServerInstance->UnloadModule(removing->c_str()))\r {\r ServerInstance->WriteOpers("*** REHASH UNLOADED MODULE: %s",removing->c_str());\r\r if (user)\r user->WriteServ("973 %s %s :Module %s successfully unloaded.",user->nick, removing->c_str(), removing->c_str());\r\r rem++;\r }\r else\r {\r if (user)\r user->WriteServ("972 %s %s :Failed to unload module %s: %s",user->nick, removing->c_str(), removing->c_str(), ServerInstance->ModuleError());\r }\r }\r }\r\r if (!added_modules.empty())\r {\r for (std::vector<std::string>::iterator adding = added_modules.begin(); adding != added_modules.end(); adding++)\r {\r if (ServerInstance->LoadModule(adding->c_str()))\r {\r ServerInstance->WriteOpers("*** REHASH LOADED MODULE: %s",adding->c_str());\r\r if (user)\r user->WriteServ("975 %s %s :Module %s successfully loaded.",user->nick, adding->c_str(), adding->c_str());\r\r add++;\r }\r else\r {\r if (user)\r user->WriteServ("974 %s %s :Failed to load module %s: %s",user->nick, adding->c_str(), adding->c_str(), ServerInstance->ModuleError());\r }\r }\r }\r\r ServerInstance->Log(DEFAULT,"Successfully unloaded %lu of %lu modules and loaded %lu of %lu modules.",(unsigned long)rem,(unsigned long)removed_modules.size(),(unsigned long)add,(unsigned long)added_modules.size());\r }\r\r if (user)\r user->WriteServ("NOTICE %s :*** Successfully rehashed server.", user->nick);\r else\r ServerInstance->WriteOpers("*** Successfully rehashed server.");\r}\r\rbool ServerConfig::LoadConf(ConfigDataHash &target, const char* filename, std::ostringstream &errorstream)\r{\r std::ifstream conf(filename);\r std::string line;\r char ch;\r long linenumber;\r bool in_tag;\r bool in_quote;\r bool in_comment;\r int character_count = 0;\r\r linenumber = 1;\r in_tag = false;\r in_quote = false;\r in_comment = false;\r\r /* Check if the file open failed first */\r if (!conf)\r {\r errorstream << "LoadConf: Couldn't open config file: " << filename << std::endl;\r return false;\r }\r\r /* Fix the chmod of the file to restrict it to the current user and group */\r chmod(filename,0600);\r\r for (unsigned int t = 0; t < include_stack.size(); t++)\r {\r if (std::string(filename) == include_stack[t])\r {\r errorstream << "File " << filename << " is included recursively (looped inclusion)." << std::endl;\r return false;\r }\r }\r\r /* It's not already included, add it to the list of files we've loaded */\r include_stack.push_back(filename);\r\r /* Start reading characters... */\r while(conf.get(ch))\r {\r\r /*\r * Fix for moronic windows issue spotted by Adremelech.\r * Some windows editors save text files as utf-16, which is\r * a total pain in the ass to parse. Users should save in the\r * right config format! If we ever see a file where the first\r * byte is 0xFF or 0xFE, or the second is 0xFF or 0xFE, then\r * this is most likely a utf-16 file. Bail out and insult user.\r */\r if ((character_count++ < 2) && (ch == '\xFF' || ch == '\xFE'))\r {\r errorstream << "File " << filename << " cannot be read, as it is encoded in braindead UTF-16. Save your file as plain ASCII!" << std::endl;\r return false;\r }\r\r /*\r * Here we try and get individual tags on separate lines,\r * this would be so easy if we just made people format\r * their config files like that, but they don't so...\r * We check for a '<' and then know the line is over when\r * we get a '>' not inside quotes. If we find two '<' and\r * no '>' then die with an error.\r */\r\r if((ch == '#') && !in_quote)\r in_comment = true;\r\r switch(ch)\r {\r case '\n':\r if (in_quote)\r line += '\n';\r linenumber++;\r case '\r':\r if (!in_quote)\r in_comment = false;\r case '\0':\r continue;\r case '\t':\r ch = ' ';\r }\r\r if(in_comment)\r continue;\r\r /* XXX: Added by Brain, May 1st 2006 - Escaping of characters.\r * Note that this WILL NOT usually allow insertion of newlines,\r * because a newline is two characters long. Use it primarily to\r * insert the " symbol.\r *\r * Note that this also involves a further check when parsing the line,\r * which can be found below.\r */\r if ((ch == '\\') && (in_quote) && (in_tag))\r {\r line += ch;\r char real_character;\r if (conf.get(real_character))\r {\r if (real_character == 'n')\r real_character = '\n';\r line += real_character;\r continue;\r }\r else\r {\r errorstream << "End of file after a \\, what did you want to escape?: " << filename << ":" << linenumber << std::endl;\r return false;\r }\r }\r\r if (ch != '\r')\r line += ch;\r\r if(ch == '<')\r {\r if(in_tag)\r {\r if(!in_quote)\r {\r errorstream << "Got another opening < when the first one wasn't closed: " << filename << ":" << linenumber << std::endl;\r return false;\r }\r }\r else\r {\r if(in_quote)\r {\r errorstream << "We're in a quote but outside a tag, interesting. " << filename << ":" << linenumber << std::endl;\r return false;\r }\r else\r {\r // errorstream << "Opening new config tag on line " << linenumber << std::endl;\r in_tag = true;\r }\r }\r }\r else if(ch == '"')\r {\r if(in_tag)\r {\r if(in_quote)\r {\r // errorstream << "Closing quote in config tag on line " << linenumber << std::endl;\r in_quote = false;\r }\r else\r {\r // errorstream << "Opening quote in config tag on line " << linenumber << std::endl;\r in_quote = true;\r }\r }\r else\r {\r if(in_quote)\r {\r errorstream << "Found a (closing) \" outside a tag: " << filename << ":" << linenumber << std::endl;\r }\r else\r {\r errorstream << "Found a (opening) \" outside a tag: " << filename << ":" << linenumber << std::endl;\r }\r }\r }\r else if(ch == '>')\r {\r if(!in_quote)\r {\r if(in_tag)\r {\r // errorstream << "Closing config tag on line " << linenumber << std::endl;\r in_tag = false;\r\r /*\r * If this finds an <include> then ParseLine can simply call\r * LoadConf() and load the included config into the same ConfigDataHash\r */\r\r if(!this->ParseLine(target, line, linenumber, errorstream))\r return false;\r\r line.clear();\r }\r else\r {\r errorstream << "Got a closing > when we weren't inside a tag: " << filename << ":" << linenumber << std::endl;\r return false;\r }\r }\r }\r }\r\r return true;\r}\r\rbool ServerConfig::LoadConf(ConfigDataHash &target, const std::string &filename, std::ostringstream &errorstream)\r{\r return this->LoadConf(target, filename.c_str(), errorstream);\r}\r\rbool ServerConfig::ParseLine(ConfigDataHash &target, std::string &line, long linenumber, std::ostringstream &errorstream)\r{\r std::string tagname;\r std::string current_key;\r std::string current_value;\r KeyValList results;\r bool got_name;\r bool got_key;\r bool in_quote;\r\r got_name = got_key = in_quote = false;\r\r //std::cout << "ParseLine(data, '" << line << "', " << linenumber << ", stream)" << std::endl;\r\r for(std::string::iterator c = line.begin(); c != line.end(); c++)\r {\r if(!got_name)\r {\r /* We don't know the tag name yet. */\r\r if(*c != ' ')\r {\r if(*c != '<')\r {\r tagname += *c;\r }\r }\r else\r {\r /* We got to a space, we should have the tagname now. */\r if(tagname.length())\r {\r got_name = true;\r }\r }\r }\r else\r {\r /* We have the tag name */\r if (!got_key)\r {\r /* We're still reading the key name */\r if (*c != '=')\r {\r if (*c != ' ')\r {\r current_key += *c;\r }\r }\r else\r {\r /* We got an '=', end of the key name. */\r got_key = true;\r }\r }\r else\r {\r /* We have the key name, now we're looking for quotes and the value */\r\r /* Correctly handle escaped characters here.\r * See the XXX'ed section above.\r */\r if ((*c == '\\') && (in_quote))\r {\r c++;\r if (*c == 'n')\r current_value += '\n';\r else\r current_value += *c;\r continue;\r }\r else if ((*c == '\n') && (in_quote))\r {\r /* Got a 'real' \n, treat it as part of the value */\r current_value += '\n';\r continue;\r }\r else if ((*c == '\r') && (in_quote))\r /* Got a \r, drop it */\r continue;\r\r if (*c == '"')\r {\r if (!in_quote)\r {\r /* We're not already in a quote. */\r in_quote = true;\r }\r else\r {\r /* Leaving quotes, we have the value */\r results.push_back(KeyVal(current_key, current_value));\r\r // std::cout << "<" << tagname << ":" << current_key << "> " << current_value << std::endl;\r\r in_quote = false;\r got_key = false;\r\r if((tagname == "include") && (current_key == "file"))\r {\r if(!this->DoInclude(target, current_value, errorstream))\r return false;\r }\r\r current_key.clear();\r current_value.clear();\r }\r }\r else\r {\r if(in_quote)\r {\r current_value += *c;\r }\r }\r }\r }\r }\r\r /* Finished parsing the tag, add it to the config hash */\r target.insert(std::pair<std::string, KeyValList > (tagname, results));\r\r return true;\r}\r\rbool ServerConfig::DoInclude(ConfigDataHash &target, const std::string &file, std::ostringstream &errorstream)\r{\r std::string confpath;\r std::string newfile;\r std::string::size_type pos;\r\r confpath = ServerInstance->ConfigFileName;\r newfile = file;\r\r for (std::string::iterator c = newfile.begin(); c != newfile.end(); c++)\r {\r if (*c == '\\')\r {\r *c = '/';\r }\r }\r\r if (file[0] != '/')\r {\r if((pos = confpath.rfind("/")) != std::string::npos)\r {\r /* Leaves us with just the path */\r newfile = confpath.substr(0, pos) + std::string("/") + newfile;\r }\r else\r {\r errorstream << "Couldn't get config path from: " << confpath << std::endl;\r return false;\r }\r }\r\r return LoadConf(target, newfile, errorstream);\r}\r\rbool ServerConfig::ConfValue(ConfigDataHash &target, const char* tag, const char* var, int index, char* result, int length, bool allow_linefeeds)\r{\r return ConfValue(target, tag, var, "", index, result, length, allow_linefeeds);\r}\r\rbool ServerConfig::ConfValue(ConfigDataHash &target, const char* tag, const char* var, const char* default_value, int index, char* result, int length, bool allow_linefeeds)\r{\r std::string value;\r bool r = ConfValue(target, std::string(tag), std::string(var), std::string(default_value), index, value, allow_linefeeds);\r strlcpy(result, value.c_str(), length);\r return r;\r}\r\rbool ServerConfig::ConfValue(ConfigDataHash &target, const std::string &tag, const std::string &var, int index, std::string &result, bool allow_linefeeds)\r{\r return ConfValue(target, tag, var, "", index, result, allow_linefeeds);\r}\r\rbool ServerConfig::ConfValue(ConfigDataHash &target, const std::string &tag, const std::string &var, const std::string &default_value, int index, std::string &result, bool allow_linefeeds)\r{\r ConfigDataHash::size_type pos = index;\r if((pos >= 0) && (pos < target.count(tag)))\r {\r ConfigDataHash::iterator iter = target.find(tag);\r\r for(int i = 0; i < index; i++)\r iter++;\r\r for(KeyValList::iterator j = iter->second.begin(); j != iter->second.end(); j++)\r {\r if(j->first == var)\r {\r if ((!allow_linefeeds) && (j->second.find('\n') != std::string::npos))\r {\r ServerInstance->Log(DEFAULT, "Value of <" + tag + ":" + var+ "> contains a linefeed, and linefeeds in this value are not permitted -- stripped to spaces.");\r for (std::string::iterator n = j->second.begin(); n != j->second.end(); n++)\r if (*n == '\n')\r *n = ' ';\r }\r else\r {\r result = j->second;\r return true;\r }\r }\r }\r if (!default_value.empty())\r {\r result = default_value;\r return true;\r }\r }\r else if(pos == 0)\r {\r if (!default_value.empty())\r {\r result = default_value;\r return true;\r }\r }\r return false;\r}\r\rbool ServerConfig::ConfValueInteger(ConfigDataHash &target, const char* tag, const char* var, int index, int &result)\r{\r return ConfValueInteger(target, std::string(tag), std::string(var), "", index, result);\r}\r\rbool ServerConfig::ConfValueInteger(ConfigDataHash &target, const char* tag, const char* var, const char* default_value, int index, int &result)\r{\r return ConfValueInteger(target, std::string(tag), std::string(var), std::string(default_value), index, result);\r}\r\rbool ServerConfig::ConfValueInteger(ConfigDataHash &target, const std::string &tag, const std::string &var, int index, int &result)\r{\r return ConfValueInteger(target, tag, var, "", index, result);\r}\r\rbool ServerConfig::ConfValueInteger(ConfigDataHash &target, const std::string &tag, const std::string &var, const std::string &default_value, int index, int &result)\r{\r std::string value;\r std::istringstream stream;\r bool r = ConfValue(target, tag, var, default_value, index, value);\r stream.str(value);\r if(!(stream >> result))\r return false;\r else\r {\r if (!value.empty())\r {\r if (value.substr(0,2) == "0x")\r {\r char* endptr;\r\r value.erase(0,2);\r result = strtol(value.c_str(), &endptr, 16);\r\r /* No digits found */\r if (endptr == value.c_str())\r return false;\r }\r else\r {\r char denominator = *(value.end() - 1);\r switch (toupper(denominator))\r {\r case 'K':\r /* Kilobytes -> bytes */\r result = result * 1024;\r break;\r case 'M':\r /* Megabytes -> bytes */\r result = result * 1024 * 1024;\r break;\r case 'G':\r /* Gigabytes -> bytes */\r result = result * 1024 * 1024 * 1024;\r break;\r }\r }\r }\r }\r return r;\r}\r\r\rbool ServerConfig::ConfValueBool(ConfigDataHash &target, const char* tag, const char* var, int index)\r{\r return ConfValueBool(target, std::string(tag), std::string(var), "", index);\r}\r\rbool ServerConfig::ConfValueBool(ConfigDataHash &target, const char* tag, const char* var, const char* default_value, int index)\r{\r return ConfValueBool(target, std::string(tag), std::string(var), std::string(default_value), index);\r}\r\rbool ServerConfig::ConfValueBool(ConfigDataHash &target, const std::string &tag, const std::string &var, int index)\r{\r return ConfValueBool(target, tag, var, "", index);\r}\r\rbool ServerConfig::ConfValueBool(ConfigDataHash &target, const std::string &tag, const std::string &var, const std::string &default_value, int index)\r{\r std::string result;\r if(!ConfValue(target, tag, var, default_value, index, result))\r return false;\r\r return ((result == "yes") || (result == "true") || (result == "1"));\r}\r\rint ServerConfig::ConfValueEnum(ConfigDataHash &target, const char* tag)\r{\r return target.count(tag);\r}\r\rint ServerConfig::ConfValueEnum(ConfigDataHash &target, const std::string &tag)\r{\r return target.count(tag);\r}\r\rint ServerConfig::ConfVarEnum(ConfigDataHash &target, const char* tag, int index)\r{\r return ConfVarEnum(target, std::string(tag), index);\r}\r\rint ServerConfig::ConfVarEnum(ConfigDataHash &target, const std::string &tag, int index)\r{\r ConfigDataHash::size_type pos = index;\r\r if((pos >= 0) && (pos < target.count(tag)))\r {\r ConfigDataHash::const_iterator iter = target.find(tag);\r\r for(int i = 0; i < index; i++)\r iter++;\r\r return iter->second.size();\r }\r\r return 0;\r}\r\r/** Read the contents of a file located by `fname' into a file_cache pointed at by `F'.\r */\rbool ServerConfig::ReadFile(file_cache &F, const char* fname)\r{\r if (!fname || !*fname)\r return false;\r\r FILE* file = NULL;\r char linebuf[MAXBUF];\r\r F.clear();\r\r if ((*fname != '/') && (*fname != '\\'))\r {\r std::string::size_type pos;\r std::string confpath = ServerInstance->ConfigFileName;\r std::string newfile = fname;\r\r if ((pos = confpath.rfind("/")) != std::string::npos)\r newfile = confpath.substr(0, pos) + std::string("/") + fname;\r else if ((pos = confpath.rfind("\\")) != std::string::npos)\r newfile = confpath.substr(0, pos) + std::string("\\") + fname;\r\r if (!FileExists(newfile.c_str()))\r return false;\r file = fopen(newfile.c_str(), "r");\r }\r else\r {\r if (!FileExists(fname))\r return false;\r file = fopen(fname, "r");\r }\r\r if (file)\r {\r while (!feof(file))\r {\r if (fgets(linebuf, sizeof(linebuf), file))\r linebuf[strlen(linebuf)-1] = 0;\r else\r *linebuf = 0;\r\r if (!feof(file))\r {\r F.push_back(*linebuf ? linebuf : " ");\r }\r }\r\r fclose(file);\r }\r else\r return false;\r\r return true;\r}\r\rbool ServerConfig::FileExists(const char* file)\r{\r struct stat sb;\r if (stat(file, &sb) == -1)\r return false;\r\r if ((sb.st_mode & S_IFDIR) > 0)\r return false;\r \r FILE *input;\r if ((input = fopen (file, "r")) == NULL)\r return false;\r else\r {\r fclose(input);\r return true;\r }\r}\r\rchar* ServerConfig::CleanFilename(char* name)\r{\r char* p = name + strlen(name);\r while ((p != name) && (*p != '/') && (*p != '\\')) p--;\r return (p != name ? ++p : p);\r}\r\r\rbool ServerConfig::DirValid(const char* dirandfile)\r{\r#ifdef WINDOWS\r return true;\r#endif\r\r char work[1024];\r char buffer[1024];\r char otherdir[1024];\r int p;\r\r strlcpy(work, dirandfile, 1024);\r p = strlen(work);\r\r // we just want the dir\r while (*work)\r {\r if (work[p] == '/')\r {\r work[p] = '\0';\r break;\r }\r\r work[p--] = '\0';\r }\r\r // Get the current working directory\r if (getcwd(buffer, 1024 ) == NULL )\r return false;\r\r if (chdir(work) == -1)\r return false;\r\r if (getcwd(otherdir, 1024 ) == NULL )\r return false;\r\r if (chdir(buffer) == -1)\r return false;\r\r size_t t = strlen(work);\r\r if (strlen(otherdir) >= t)\r {\r otherdir[t] = '\0';\r if (!strcmp(otherdir,work))\r {\r return true;\r }\r\r return false;\r }\r else\r {\r return false;\r }\r}\r\rstd::string ServerConfig::GetFullProgDir()\r{\r char buffer[PATH_MAX+1];\r#ifdef WINDOWS\r /* Windows has specific api calls to get the exe path that never fail.\r * For once, windows has something of use, compared to the POSIX code\r * for this, this is positively neato.\r */\r if (GetModuleFileName(NULL, buffer, MAX_PATH))\r {\r std::string fullpath = buffer;\r std::string::size_type n = fullpath.rfind("\\inspircd.exe");\r return std::string(fullpath, 0, n);\r }\r#else\r // Get the current working directory\r if (getcwd(buffer, PATH_MAX))\r {\r std::string remainder = this->argv[0];\r\r /* Does argv[0] start with /? its a full path, use it */\r if (remainder[0] == '/')\r {\r std::string::size_type n = remainder.rfind("/inspircd");\r return std::string(remainder, 0, n);\r }\r\r std::string fullpath = std::string(buffer) + "/" + remainder;\r std::string::size_type n = fullpath.rfind("/inspircd");\r return std::string(fullpath, 0, n);\r }\r#endif\r return "/";\r}\r\rInspIRCd* ServerConfig::GetInstance()\r{\r return ServerInstance;\r}\r\r\rValueItem::ValueItem(int value)\r{\r std::stringstream n;\r n << value;\r v = n.str();\r}\r\rValueItem::ValueItem(bool value)\r{\r std::stringstream n;\r n << value;\r v = n.str();\r}\r\rValueItem::ValueItem(char* value)\r{\r v = value;\r}\r\rvoid ValueItem::Set(char* value)\r{\r v = value;\r}\r\rvoid ValueItem::Set(const char* value)\r{\r v = value;\r}\r\rvoid ValueItem::Set(int value)\r{\r std::stringstream n;\r n << value;\r v = n.str();\r}\r\rint ValueItem::GetInteger()\r{\r if (v.empty())\r return 0;\r return atoi(v.c_str());\r}\r\rchar* ValueItem::GetString()\r{\r return (char*)v.c_str();\r}\r\rbool ValueItem::GetBool()\r{\r return (GetInteger() || v == "yes" || v == "true");\r}\r\r
\ No newline at end of file
+/* +------------------------------------+
+ * | Inspire Internet Relay Chat Daemon |
+ * +------------------------------------+
+ *
+ * InspIRCd: (C) 2002-2009 InspIRCd Development Team
+ * See: http://wiki.inspircd.org/Credits
+ *
+ * This program is free but copyrighted software; see
+ * the file COPYING for details.
+ *
+ * ---------------------------------------------------
+ */
+
+/* $Core */
+/* $CopyInstall: conf/inspircd.quotes.example $(CONPATH) */
+/* $CopyInstall: conf/inspircd.rules.example $(CONPATH) */
+/* $CopyInstall: conf/inspircd.motd.example $(CONPATH) */
+/* $CopyInstall: conf/inspircd.helpop-full.example $(CONPATH) */
+/* $CopyInstall: conf/inspircd.helpop.example $(CONPATH) */
+/* $CopyInstall: conf/inspircd.censor.example $(CONPATH) */
+/* $CopyInstall: conf/inspircd.filter.example $(CONPATH) */
+/* $CopyInstall: conf/inspircd.conf.example $(CONPATH) */
+/* $CopyInstall: conf/modules.conf.example $(CONPATH) */
+/* $CopyInstall: conf/opers.conf.example $(CONPATH) */
+/* $CopyInstall: conf/links.conf.example $(CONPATH) */
+/* $CopyInstall: .gdbargs $(BASE) */
+
+#include "inspircd.h"
+#include <fstream>
+#include "xline.h"
+#include "exitcodes.h"
+#include "commands/cmd_whowas.h"
+#include "modes/cmode_h.h"
+
+ServerConfig::ServerConfig(InspIRCd* Instance) : ServerInstance(Instance)
+{
+ *sid = *ServerName = *Network = *ServerDesc = *AdminName = '\0';
+ *HideWhoisServer = *AdminEmail = *AdminNick = *diepass = *restartpass = *FixedQuit = *HideKillsServer = '\0';
+ *DefaultModes = *CustomVersion = *motd = *rules = *PrefixQuit = *DieValue = *DNSServer = '\0';
+ *UserStats = *DisabledCommands = *SuffixQuit = '\0';
+ WhoWasGroupSize = WhoWasMaxGroups = WhoWasMaxKeep = 0;
+ log_file = NULL;
+ NoUserDns = forcedebug = OperSpyWhois = nofork = HideBans = HideSplits = UndernetMsgPrefix = false;
+ CycleHosts = writelog = AllowHalfop = InvBypassModes = true;
+ dns_timeout = DieDelay = 5;
+ MaxTargets = 20;
+ NetBufferSize = 10240;
+ SoftLimit = Instance->SE->GetMaxFds();
+ MaxConn = SOMAXCONN;
+ MaxWhoResults = 0;
+ debugging = 0;
+ MaxChans = 20;
+ OperMaxChans = 30;
+ c_ipv4_range = 32;
+ c_ipv6_range = 128;
+}
+
+void ServerConfig::Update005()
+{
+ std::stringstream out(data005);
+ std::string token;
+ std::string line5;
+ int token_counter = 0;
+ isupport.clear();
+ while (out >> token)
+ {
+ line5 = line5 + token + " ";
+ token_counter++;
+ if (token_counter >= 13)
+ {
+ char buf[MAXBUF];
+ snprintf(buf, MAXBUF, "%s:are supported by this server", line5.c_str());
+ isupport.push_back(buf);
+ line5.clear();
+ token_counter = 0;
+ }
+ }
+ if (!line5.empty())
+ {
+ char buf[MAXBUF];
+ snprintf(buf, MAXBUF, "%s:are supported by this server", line5.c_str());
+ isupport.push_back(buf);
+ }
+}
+
+void ServerConfig::Send005(User* user)
+{
+ for (std::vector<std::string>::iterator line = ServerInstance->Config->isupport.begin(); line != ServerInstance->Config->isupport.end(); line++)
+ user->WriteNumeric(RPL_ISUPPORT, "%s %s", user->nick.c_str(), line->c_str());
+}
+
+bool ServerConfig::CheckOnce(const char* tag)
+{
+ int count = ConfValueEnum(tag);
+
+ if (count > 1)
+ throw CoreException("You have more than one <"+std::string(tag)+"> tag, this is not permitted.");
+ if (count < 1)
+ throw CoreException("You have not defined a <"+std::string(tag)+"> tag, this is required.");
+ return true;
+}
+
+static void ValidateNoSpaces(const char* p, const std::string &tag, const std::string &val)
+{
+ for (const char* ptr = p; *ptr; ++ptr)
+ {
+ if (*ptr == ' ')
+ throw CoreException("The value of <"+tag+":"+val+"> cannot contain spaces");
+ }
+}
+
+/* NOTE: Before anyone asks why we're not using inet_pton for this, it is because inet_pton and friends do not return so much detail,
+ * even in strerror(errno). They just return 'yes' or 'no' to an address without such detail as to whats WRONG with the address.
+ * Because ircd users arent as technical as they used to be (;)) we are going to give more of a useful error message.
+ */
+static void ValidateIP(const char* p, const std::string &tag, const std::string &val, bool wild)
+{
+ int num_dots = 0;
+ int num_seps = 0;
+ int not_numbers = false;
+ int not_hex = false;
+
+ if (*p)
+ {
+ if (*p == '.')
+ throw CoreException("The value of <"+tag+":"+val+"> is not an IP address");
+
+ for (const char* ptr = p; *ptr; ++ptr)
+ {
+ if (wild && (*ptr == '*' || *ptr == '?' || *ptr == '/'))
+ continue;
+
+ if (*ptr != ':' && *ptr != '.')
+ {
+ if (*ptr < '0' || *ptr > '9')
+ not_numbers = true;
+ if ((*ptr < '0' || *ptr > '9') && (toupper(*ptr) < 'A' || toupper(*ptr) > 'F'))
+ not_hex = true;
+ }
+ switch (*ptr)
+ {
+ case ' ':
+ throw CoreException("The value of <"+tag+":"+val+"> is not an IP address");
+ case '.':
+ num_dots++;
+ break;
+ case ':':
+ num_seps++;
+ break;
+ }
+ }
+
+ if (num_dots > 3)
+ throw CoreException("The value of <"+tag+":"+val+"> is an IPv4 address with too many fields!");
+
+ if (num_seps > 8)
+ throw CoreException("The value of <"+tag+":"+val+"> is an IPv6 address with too many fields!");
+
+ if (num_seps == 0 && num_dots < 3 && !wild)
+ throw CoreException("The value of <"+tag+":"+val+"> looks to be a malformed IPv4 address");
+
+ if (num_seps == 0 && num_dots == 3 && not_numbers)
+ throw CoreException("The value of <"+tag+":"+val+"> contains non-numeric characters in an IPv4 address");
+
+ if (num_seps != 0 && not_hex)
+ throw CoreException("The value of <"+tag+":"+val+"> contains non-hexdecimal characters in an IPv6 address");
+
+ if (num_seps != 0 && num_dots != 3 && num_dots != 0 && !wild)
+ throw CoreException("The value of <"+tag+":"+val+"> is a malformed IPv6 4in6 address");
+ }
+}
+
+static void ValidateHostname(const char* p, const std::string &tag, const std::string &val)
+{
+ int num_dots = 0;
+ if (*p)
+ {
+ if (*p == '.')
+ throw CoreException("The value of <"+tag+":"+val+"> is not a valid hostname");
+ for (const char* ptr = p; *ptr; ++ptr)
+ {
+ switch (*ptr)
+ {
+ case ' ':
+ throw CoreException("The value of <"+tag+":"+val+"> is not a valid hostname");
+ case '.':
+ num_dots++;
+ break;
+ }
+ }
+ if (num_dots == 0)
+ throw CoreException("The value of <"+tag+":"+val+"> is not a valid hostname");
+ }
+}
+
+// Specialized validators
+
+static bool ValidateMaxTargets(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ if ((data.GetInteger() < 1) || (data.GetInteger() > 31))
+ {
+ conf->GetInstance()->Logs->Log("CONFIG",DEFAULT,"WARNING: <security:maxtargets> value is greater than 31 or less than 1, set to 20.");
+ data.Set(20);
+ }
+ return true;
+}
+
+static bool ValidateSoftLimit(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ if ((data.GetInteger() < 1) || (data.GetInteger() > conf->GetInstance()->SE->GetMaxFds()))
+ {
+ conf->GetInstance()->Logs->Log("CONFIG",DEFAULT,"WARNING: <performance:softlimit> value is greater than %d or less than 0, set to %d.",conf->GetInstance()->SE->GetMaxFds(),conf->GetInstance()->SE->GetMaxFds());
+ data.Set(conf->GetInstance()->SE->GetMaxFds());
+ }
+ return true;
+}
+
+static bool ValidateMaxConn(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ if (data.GetInteger() > SOMAXCONN)
+ conf->GetInstance()->Logs->Log("CONFIG",DEFAULT,"WARNING: <performance:somaxconn> value may be higher than the system-defined SOMAXCONN value!");
+ return true;
+}
+
+bool ServerConfig::ApplyDisabledCommands(const char* data)
+{
+ std::stringstream dcmds(data);
+ std::string thiscmd;
+
+ /* Enable everything first */
+ for (Commandtable::iterator x = ServerInstance->Parser->cmdlist.begin(); x != ServerInstance->Parser->cmdlist.end(); x++)
+ x->second->Disable(false);
+
+ /* Now disable all the ones which the user wants disabled */
+ while (dcmds >> thiscmd)
+ {
+ Commandtable::iterator cm = ServerInstance->Parser->cmdlist.find(thiscmd);
+ if (cm != ServerInstance->Parser->cmdlist.end())
+ {
+ cm->second->Disable(true);
+ }
+ }
+ return true;
+}
+
+static bool ValidateDisabledUModes(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ memset(conf->DisabledUModes, 0, sizeof(conf->DisabledUModes));
+ for (const unsigned char* p = (const unsigned char*)data.GetString(); *p; ++p)
+ {
+ if (*p < 'A' || *p > ('A' + 64)) throw CoreException(std::string("Invalid usermode ")+(char)*p+" was found.");
+ conf->DisabledUModes[*p - 'A'] = 1;
+ }
+ return true;
+}
+
+static bool ValidateDisabledCModes(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ memset(conf->DisabledCModes, 0, sizeof(conf->DisabledCModes));
+ for (const unsigned char* p = (const unsigned char*)data.GetString(); *p; ++p)
+ {
+ if (*p < 'A' || *p > ('A' + 64)) throw CoreException(std::string("Invalid chanmode ")+(char)*p+" was found.");
+ conf->DisabledCModes[*p - 'A'] = 1;
+ }
+ return true;
+}
+
+#ifdef WINDOWS
+// Note: the windows validator is in win32wrapper.cpp
+bool ValidateDnsServer(ServerConfig* conf, const char*, const char*, ValueItem &data);
+#else
+static bool ValidateDnsServer(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ if (!*(data.GetString()))
+ {
+ std::string nameserver;
+ // attempt to look up their nameserver from /etc/resolv.conf
+ conf->GetInstance()->Logs->Log("CONFIG",DEFAULT,"WARNING: <dns:server> not defined, attempting to find working server in /etc/resolv.conf...");
+ std::ifstream resolv("/etc/resolv.conf");
+ bool found_server = false;
+
+ if (resolv.is_open())
+ {
+ while (resolv >> nameserver)
+ {
+ if ((nameserver == "nameserver") && (!found_server))
+ {
+ resolv >> nameserver;
+ data.Set(nameserver.c_str());
+ found_server = true;
+ conf->GetInstance()->Logs->Log("CONFIG",DEFAULT,"<dns:server> set to '%s' as first resolver in /etc/resolv.conf.",nameserver.c_str());
+ }
+ }
+
+ if (!found_server)
+ {
+ conf->GetInstance()->Logs->Log("CONFIG",DEFAULT,"/etc/resolv.conf contains no viable nameserver entries! Defaulting to nameserver '127.0.0.1'!");
+ data.Set("127.0.0.1");
+ }
+ }
+ else
+ {
+ conf->GetInstance()->Logs->Log("CONFIG",DEFAULT,"/etc/resolv.conf can't be opened! Defaulting to nameserver '127.0.0.1'!");
+ data.Set("127.0.0.1");
+ }
+ }
+ return true;
+}
+#endif
+
+static bool ValidateServerName(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ conf->GetInstance()->Logs->Log("CONFIG",DEFAULT,"Validating server name");
+ /* If we already have a servername, and they changed it, we should throw an exception. */
+ if (!strchr(data.GetString(), '.'))
+ {
+ conf->GetInstance()->Logs->Log("CONFIG",DEFAULT,"WARNING: <server:name> '%s' is not a fully-qualified domain name. Changed to '%s.'",
+ data.GetString(),data.GetString());
+ std::string moo = data.GetValue();
+ data.Set(moo.append("."));
+ }
+ ValidateHostname(data.GetString(), "server", "name");
+ return true;
+}
+
+static bool ValidateNetBufferSize(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ // 65534 not 65535 because of null terminator
+ if ((!data.GetInteger()) || (data.GetInteger() > 65534) || (data.GetInteger() < 1024))
+ {
+ conf->GetInstance()->Logs->Log("CONFIG",DEFAULT,"No NetBufferSize specified or size out of range, setting to default of 10240.");
+ data.Set(10240);
+ }
+ return true;
+}
+
+static bool ValidateMaxWho(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ if ((data.GetInteger() > 65535) || (data.GetInteger() < 1))
+ {
+ conf->GetInstance()->Logs->Log("CONFIG",DEFAULT,"<performance:maxwho> size out of range, setting to default of 128.");
+ data.Set(128);
+ }
+ return true;
+}
+
+static bool ValidateHalfOp(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ ModeHandler* mh = conf->GetInstance()->Modes->FindMode('h', MODETYPE_CHANNEL);
+ if (data.GetBool() && !mh) {
+ conf->GetInstance()->Logs->Log("CONFIG", DEFAULT, "Enabling halfop mode.");
+ mh = new ModeChannelHalfOp(conf->GetInstance());
+ conf->GetInstance()->Modes->AddMode(mh);
+ } else if (!data.GetBool() && mh) {
+ conf->GetInstance()->Logs->Log("CONFIG", DEFAULT, "Disabling halfop mode.");
+ conf->GetInstance()->Modes->DelMode(mh);
+ delete mh;
+ }
+ return true;
+}
+
+static bool ValidateMotd(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ conf->ReadFile(conf->MOTD, data.GetString());
+ return true;
+}
+
+static bool ValidateNotEmpty(ServerConfig*, const char* tag, const char* val, ValueItem &data)
+{
+ if (data.GetValue().empty())
+ throw CoreException(std::string("The value for <")+tag+":"+val+"> cannot be empty!");
+ return true;
+}
+
+static bool ValidateRules(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ conf->ReadFile(conf->RULES, data.GetString());
+ return true;
+}
+
+static bool ValidateModeLists(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ memset(conf->HideModeLists, 0, sizeof(conf->HideModeLists));
+ for (const unsigned char* x = (const unsigned char*)data.GetString(); *x; ++x)
+ conf->HideModeLists[*x] = true;
+ return true;
+}
+
+static bool ValidateExemptChanOps(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ memset(conf->ExemptChanOps, 0, sizeof(conf->ExemptChanOps));
+ for (const unsigned char* x = (const unsigned char*)data.GetString(); *x; ++x)
+ conf->ExemptChanOps[*x] = true;
+ return true;
+}
+
+static bool ValidateInvite(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ const std::string& v = data.GetValue();
+
+ if (v == "ops")
+ conf->AnnounceInvites = ServerConfig::INVITE_ANNOUNCE_OPS;
+ else if (v == "all")
+ conf->AnnounceInvites = ServerConfig::INVITE_ANNOUNCE_ALL;
+ else if (v == "dynamic")
+ conf->AnnounceInvites = ServerConfig::INVITE_ANNOUNCE_DYNAMIC;
+ else
+ conf->AnnounceInvites = ServerConfig::INVITE_ANNOUNCE_NONE;
+
+ return true;
+}
+
+static bool ValidateSID(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ conf->GetInstance()->Logs->Log("CONFIG",DEFAULT,"Validating server id");
+
+ const char *sid = data.GetString();
+
+ if (*sid && !conf->GetInstance()->IsSID(sid))
+ {
+ throw CoreException(std::string(sid) + " is not a valid server ID. A server ID must be 3 characters long, with the first character a digit and the next two characters a digit or letter.");
+ }
+
+ strlcpy(conf->sid, sid, 5);
+
+ return true;
+}
+
+static bool ValidateWhoWas(ServerConfig* conf, const char*, const char*, ValueItem &data)
+{
+ conf->WhoWasMaxKeep = conf->GetInstance()->Duration(data.GetString());
+
+ if (conf->WhoWasGroupSize < 0)
+ conf->WhoWasGroupSize = 0;
+
+ if (conf->WhoWasMaxGroups < 0)
+ conf->WhoWasMaxGroups = 0;
+
+ if (conf->WhoWasMaxKeep < 3600)
+ {
+ conf->WhoWasMaxKeep = 3600;
+ conf->GetInstance()->Logs->Log("CONFIG",DEFAULT,"WARNING: <whowas:maxkeep> value less than 3600, setting to default 3600");
+ }
+
+ Command* whowas_command = conf->GetInstance()->Parser->GetHandler("WHOWAS");
+ if (whowas_command)
+ {
+ std::deque<classbase*> params;
+ whowas_command->HandleInternal(WHOWAS_PRUNE, params);
+ }
+
+ return true;
+}
+
+/* Callback called to process a single <uline> tag
+ */
+static bool DoULine(ServerConfig* conf, const char*, const char**, ValueList &values, int*)
+{
+ const char* server = values[0].GetString();
+ const bool silent = values[1].GetBool();
+ conf->ulines[server] = silent;
+ return true;
+}
+
+/* Callback called to process a single <banlist> tag
+ */
+static bool DoMaxBans(ServerConfig* conf, const char*, const char**, ValueList &values, int*)
+{
+ const char* channel = values[0].GetString();
+ int limit = values[1].GetInteger();
+ conf->maxbans[channel] = limit;
+ return true;
+}
+
+static bool DoZLine(ServerConfig* conf, const char* tag, const char** entries, ValueList &values, int* types)
+{
+ const char* reason = values[0].GetString();
+ const char* ipmask = values[1].GetString();
+
+ ZLine* zl = new ZLine(conf->GetInstance(), conf->GetInstance()->Time(), 0, "<Config>", reason, ipmask);
+ if (!conf->GetInstance()->XLines->AddLine(zl, NULL))
+ delete zl;
+
+ return true;
+}
+
+static bool DoQLine(ServerConfig* conf, const char* tag, const char** entries, ValueList &values, int* types)
+{
+ const char* reason = values[0].GetString();
+ const char* nick = values[1].GetString();
+
+ QLine* ql = new QLine(conf->GetInstance(), conf->GetInstance()->Time(), 0, "<Config>", reason, nick);
+ if (!conf->GetInstance()->XLines->AddLine(ql, NULL))
+ delete ql;
+
+ return true;
+}
+
+static bool DoKLine(ServerConfig* conf, const char* tag, const char** entries, ValueList &values, int* types)
+{
+ const char* reason = values[0].GetString();
+ const char* host = values[1].GetString();
+
+ XLineManager* xlm = conf->GetInstance()->XLines;
+
+ IdentHostPair ih = xlm->IdentSplit(host);
+
+ KLine* kl = new KLine(conf->GetInstance(), conf->GetInstance()->Time(), 0, "<Config>", reason, ih.first.c_str(), ih.second.c_str());
+ if (!xlm->AddLine(kl, NULL))
+ delete kl;
+ return true;
+}
+
+static bool DoELine(ServerConfig* conf, const char* tag, const char** entries, ValueList &values, int* types)
+{
+ const char* reason = values[0].GetString();
+ const char* host = values[1].GetString();
+
+ XLineManager* xlm = conf->GetInstance()->XLines;
+
+ IdentHostPair ih = xlm->IdentSplit(host);
+
+ ELine* el = new ELine(conf->GetInstance(), conf->GetInstance()->Time(), 0, "<Config>", reason, ih.first.c_str(), ih.second.c_str());
+ if (!xlm->AddLine(el, NULL))
+ delete el;
+ return true;
+}
+
+static bool DoType(ServerConfig* conf, const char*, const char**, ValueList &values, int*)
+{
+ const char* TypeName = values[0].GetString();
+ const char* Classes = values[1].GetString();
+
+ conf->opertypes[TypeName] = std::string(Classes);
+ return true;
+}
+
+static bool DoClass(ServerConfig* conf, const char* tag, const char**, ValueList &values, int*)
+{
+ const char* ClassName = values[0].GetString();
+ const char* CommandList = values[1].GetString();
+ const char* UModeList = values[2].GetString();
+ const char* CModeList = values[3].GetString();
+ const char *PrivsList = values[4].GetString();
+
+ for (const char* c = UModeList; *c; ++c)
+ {
+ if ((*c < 'A' || *c > 'z') && *c != '*')
+ {
+ throw CoreException("Character " + std::string(1, *c) + " is not a valid mode in <class:usermodes>");
+ }
+ }
+ for (const char* c = CModeList; *c; ++c)
+ {
+ if ((*c < 'A' || *c > 'z') && *c != '*')
+ {
+ throw CoreException("Character " + std::string(1, *c) + " is not a valid mode in <class:chanmodes>");
+ }
+ }
+
+ conf->operclass[ClassName].commandlist = strnewdup(CommandList);
+ conf->operclass[ClassName].umodelist = strnewdup(UModeList);
+ conf->operclass[ClassName].cmodelist = strnewdup(CModeList);
+ conf->operclass[ClassName].privs = strnewdup(PrivsList);
+ return true;
+}
+
+void ServerConfig::CrossCheckOperClassType()
+{
+ for (int i = 0; i < ConfValueEnum("type"); ++i)
+ {
+ char item[MAXBUF], classn[MAXBUF], classes[MAXBUF];
+ std::string classname;
+ ConfValue("type", "classes", "", i, classes, MAXBUF, false);
+ irc::spacesepstream str(classes);
+ ConfValue("type", "name", "", i, item, MAXBUF, false);
+ while (str.GetToken(classname))
+ {
+ std::string lost;
+ bool foundclass = false;
+ for (int j = 0; j < ConfValueEnum("class"); ++j)
+ {
+ ConfValue("class", "name", "", j, classn, MAXBUF, false);
+ if (!strcmp(classn, classname.c_str()))
+ {
+ foundclass = true;
+ break;
+ }
+ }
+ if (!foundclass)
+ {
+ char msg[MAXBUF];
+ snprintf(msg, MAXBUF, " Warning: Oper type '%s' has a missing class named '%s', this does nothing!\n",
+ item, classname.c_str());
+ throw CoreException(msg);
+ }
+ }
+ }
+}
+
+void ServerConfig::CrossCheckConnectBlocks(ServerConfig* current)
+{
+ typedef std::map<std::string, ConnectClass*> ClassMap;
+ ClassMap oldBlocksByMask;
+ if (current)
+ {
+ for(ClassVector::iterator i = current->Classes.begin(); i != current->Classes.end(); ++i)
+ {
+ ConnectClass* c = *i;
+ std::string typeMask = (c->type == CC_ALLOW) ? "a" : "d";
+ typeMask += c->host;
+ oldBlocksByMask[typeMask] = c;
+ }
+ }
+
+ int block_count = ConfValueEnum("connect");
+ ClassMap newBlocksByMask;
+ Classes.resize(block_count, NULL);
+ std::map<std::string, int> names;
+
+ bool try_again = true;
+ for(int tries=0; try_again && tries < block_count + 1; tries++)
+ {
+ try_again = false;
+ for(int i=0; i < block_count; i++)
+ {
+ if (Classes[i])
+ continue;
+
+ ConnectClass* parent = NULL;
+ std::string parentName;
+ if (ConfValue("connect", "parent", i, parentName, false))
+ {
+ std::map<std::string,int>::iterator parentIter = names.find(parentName);
+ if (parentIter == names.end())
+ {
+ try_again = true;
+ // couldn't find parent this time. If it's the last time, we'll never find it.
+ if (tries == block_count)
+ throw CoreException("Could not find parent connect class \"" + parentName + "\" for connect block " + ConvToStr(i));
+ continue;
+ }
+ parent = Classes[parentIter->second];
+ }
+
+ std::string name;
+ if (ConfValue("connect", "name", i, name, false))
+ {
+ if (names.find(name) != names.end())
+ throw CoreException("Two connect classes with name \"" + name + "\" defined!");
+ names[name] = i;
+ }
+
+ std::string mask, typeMask;
+ char type;
+
+ if (ConfValue("connect", "allow", i, mask, false))
+ {
+ type = CC_ALLOW;
+ typeMask = 'a' + mask;
+ }
+ else if (ConfValue("connect", "deny", i, mask, false))
+ {
+ type = CC_DENY;
+ typeMask = 'd' + mask;
+ }
+ else
+ {
+ throw CoreException("Connect class must have an allow or deny mask (#" + ConvToStr(i) + ")");
+ }
+ ClassMap::iterator dupMask = newBlocksByMask.find(typeMask);
+ if (dupMask != newBlocksByMask.end())
+ throw CoreException("Two connect classes cannot have the same mask (" + mask + ")");
+
+ ConnectClass* me = parent ?
+ new ConnectClass(type, mask, *parent) :
+ new ConnectClass(type, mask);
+
+ if (!name.empty())
+ me->name = name;
+
+ std::string tmpv;
+ if (ConfValue("connect", "password", i, tmpv, false))
+ me->pass= tmpv;
+ if (ConfValue("connect", "hash", i, tmpv, false))
+ me->hash = tmpv;
+ if (ConfValue("connect", "timeout", i, tmpv, false))
+ me->registration_timeout = atol(tmpv.c_str());
+ if (ConfValue("connect", "pingfreq", i, tmpv, false))
+ me->pingtime = atol(tmpv.c_str());
+ if (ConfValue("connect", "sendq", i, tmpv, false))
+ me->sendqmax = atol(tmpv.c_str());
+ if (ConfValue("connect", "recvq", i, tmpv, false))
+ me->recvqmax = atol(tmpv.c_str());
+ if (ConfValue("connect", "localmax", i, tmpv, false))
+ me->maxlocal = atol(tmpv.c_str());
+ if (ConfValue("connect", "globalmax", i, tmpv, false))
+ me->maxglobal = atol(tmpv.c_str());
+ if (ConfValue("connect", "port", i, tmpv, false))
+ me->port = atol(tmpv.c_str());
+ if (ConfValue("connect", "maxchans", i, tmpv, false))
+ me->maxchans = atol(tmpv.c_str());
+ if (ConfValue("connect", "limit", i, tmpv, false))
+ me->limit = atol(tmpv.c_str());
+
+ ClassMap::iterator oldMask = oldBlocksByMask.find(typeMask);
+ if (oldMask != oldBlocksByMask.end())
+ {
+ oldBlocksByMask.erase(oldMask);
+ ConnectClass* old = oldMask->second;
+ old->Update(me);
+ delete me;
+ me = old;
+ }
+ newBlocksByMask[typeMask] = me;
+ Classes[i] = me;
+ }
+ }
+
+ for(ClassMap::iterator toRemove = oldBlocksByMask.begin(); toRemove != oldBlocksByMask.end(); toRemove++)
+ {
+ removed_classes.push_back(toRemove->second);
+ }
+}
+
+
+static const Deprecated ChangedConfig[] = {
+ {"options", "hidelinks", "has been moved to <security:hidelinks> as of 1.2a3"},
+ {"options", "hidewhois", "has been moved to <security:hidewhois> as of 1.2a3"},
+ {"options", "userstats", "has been moved to <security:userstats> as of 1.2a3"},
+ {"options", "customversion", "has been moved to <security:customversion> as of 1.2a3"},
+ {"options", "hidesplits", "has been moved to <security:hidesplits> as of 1.2a3"},
+ {"options", "hidebans", "has been moved to <security:hidebans> as of 1.2a3"},
+ {"options", "hidekills", "has been moved to <security:hidekills> as of 1.2a3"},
+ {"options", "operspywhois", "has been moved to <security:operspywhois> as of 1.2a3"},
+ {"options", "announceinvites", "has been moved to <security:announceinvites> as of 1.2a3"},
+ {"options", "hidemodes", "has been moved to <security:hidemodes> as of 1.2a3"},
+ {"options", "maxtargets", "has been moved to <security:maxtargets> as of 1.2a3"},
+ {"options", "nouserdns", "has been moved to <performance:nouserdns> as of 1.2a3"},
+ {"options", "maxwho", "has been moved to <performance:maxwho> as of 1.2a3"},
+ {"options", "softlimit", "has been moved to <performance:softlimit> as of 1.2a3"},
+ {"options", "somaxconn", "has been moved to <performance:somaxconn> as of 1.2a3"},
+ {"options", "netbuffersize", "has been moved to <performance:netbuffersize> as of 1.2a3"},
+ {"options", "maxwho", "has been moved to <performance:maxwho> as of 1.2a3"},
+ {"options", "loglevel", "1.2 does not use the loglevel value. Please define <log> tags instead."},
+ {"die", "value", "has always been deprecated"},
+};
+
+/* These tags can occur ONCE or not at all */
+static const InitialConfig Values[] = {
+ {"performance", "softlimit", "0", new ValueContainerUInt (&ServerConfig::SoftLimit), DT_INTEGER, ValidateSoftLimit},
+ {"performance", "somaxconn", SOMAXCONN_S, new ValueContainerInt (&ServerConfig::MaxConn), DT_INTEGER, ValidateMaxConn},
+ {"options", "moronbanner", "You're banned!", new ValueContainerChar (&ServerConfig::MoronBanner), DT_CHARPTR, NULL},
+ {"server", "name", "", new ValueContainerChar (&ServerConfig::ServerName), DT_HOSTNAME, ValidateServerName},
+ {"server", "description", "Configure Me", new ValueContainerChar (&ServerConfig::ServerDesc), DT_CHARPTR, NULL},
+ {"server", "network", "Network", new ValueContainerChar (&ServerConfig::Network), DT_NOSPACES, NULL},
+ {"server", "id", "", new ValueContainerChar (&ServerConfig::sid), DT_CHARPTR, ValidateSID},
+ {"admin", "name", "", new ValueContainerChar (&ServerConfig::AdminName), DT_CHARPTR, NULL},
+ {"admin", "email", "Mis@configu.red", new ValueContainerChar (&ServerConfig::AdminEmail), DT_CHARPTR, NULL},
+ {"admin", "nick", "Misconfigured", new ValueContainerChar (&ServerConfig::AdminNick), DT_CHARPTR, NULL},
+ {"files", "motd", "", new ValueContainerChar (&ServerConfig::motd), DT_CHARPTR, ValidateMotd},
+ {"files", "rules", "", new ValueContainerChar (&ServerConfig::rules), DT_CHARPTR, ValidateRules},
+ {"power", "diepass", "", new ValueContainerChar (&ServerConfig::diepass), DT_CHARPTR, ValidateNotEmpty},
+ {"power", "pause", "", new ValueContainerInt (&ServerConfig::DieDelay), DT_INTEGER, NULL},
+ {"power", "hash", "", new ValueContainerChar (&ServerConfig::powerhash), DT_CHARPTR, NULL},
+ {"power", "restartpass", "", new ValueContainerChar (&ServerConfig::restartpass), DT_CHARPTR, ValidateNotEmpty},
+ {"options", "prefixquit", "", new ValueContainerChar (&ServerConfig::PrefixQuit), DT_CHARPTR, NULL},
+ {"options", "suffixquit", "", new ValueContainerChar (&ServerConfig::SuffixQuit), DT_CHARPTR, NULL},
+ {"options", "fixedquit", "", new ValueContainerChar (&ServerConfig::FixedQuit), DT_CHARPTR, NULL},
+ {"options", "prefixpart", "", new ValueContainerChar (&ServerConfig::PrefixPart), DT_CHARPTR, NULL},
+ {"options", "suffixpart", "", new ValueContainerChar (&ServerConfig::SuffixPart), DT_CHARPTR, NULL},
+ {"options", "fixedpart", "", new ValueContainerChar (&ServerConfig::FixedPart), DT_CHARPTR, NULL},
+ {"performance", "netbuffersize","10240", new ValueContainerInt (&ServerConfig::NetBufferSize), DT_INTEGER, ValidateNetBufferSize},
+ {"performance", "maxwho", "128", new ValueContainerInt (&ServerConfig::MaxWhoResults), DT_INTEGER, ValidateMaxWho},
+ {"options", "allowhalfop", "0", new ValueContainerBool (&ServerConfig::AllowHalfop), DT_BOOLEAN, ValidateHalfOp},
+ {"dns", "server", "", new ValueContainerChar (&ServerConfig::DNSServer), DT_IPADDRESS,ValidateDnsServer},
+ {"dns", "timeout", "5", new ValueContainerInt (&ServerConfig::dns_timeout), DT_INTEGER, NULL},
+ {"options", "moduledir", MOD_PATH, new ValueContainerString (&ServerConfig::ModPath), DT_CHARPTR, NULL},
+ {"disabled", "commands", "", new ValueContainerChar (&ServerConfig::DisabledCommands), DT_CHARPTR, NULL},
+ {"disabled", "usermodes", "", NULL, DT_NOTHING, ValidateDisabledUModes},
+ {"disabled", "chanmodes", "", NULL, DT_NOTHING, ValidateDisabledCModes},
+ {"disabled", "fakenonexistant", "0", new ValueContainerBool (&ServerConfig::DisabledDontExist), DT_BOOLEAN, NULL},
+
+ {"security", "runasuser", "", new ValueContainerChar(&ServerConfig::SetUser), DT_CHARPTR, NULL},
+ {"security", "runasgroup", "", new ValueContainerChar(&ServerConfig::SetGroup), DT_CHARPTR, NULL},
+ {"security", "userstats", "", new ValueContainerChar (&ServerConfig::UserStats), DT_CHARPTR, NULL},
+ {"security", "customversion","", new ValueContainerChar (&ServerConfig::CustomVersion), DT_CHARPTR, NULL},
+ {"security", "hidesplits", "0", new ValueContainerBool (&ServerConfig::HideSplits), DT_BOOLEAN, NULL},
+ {"security", "hidebans", "0", new ValueContainerBool (&ServerConfig::HideBans), DT_BOOLEAN, NULL},
+ {"security", "hidewhois", "", new ValueContainerChar (&ServerConfig::HideWhoisServer), DT_NOSPACES, NULL},
+ {"security", "hidekills", "", new ValueContainerChar (&ServerConfig::HideKillsServer), DT_NOSPACES, NULL},
+ {"security", "operspywhois", "0", new ValueContainerBool (&ServerConfig::OperSpyWhois), DT_BOOLEAN, NULL},
+ {"security", "restrictbannedusers", "1", new ValueContainerBool (&ServerConfig::RestrictBannedUsers), DT_BOOLEAN, NULL},
+ {"security", "genericoper", "0", new ValueContainerBool (&ServerConfig::GenericOper), DT_BOOLEAN, NULL},
+ {"performance", "nouserdns", "0", new ValueContainerBool (&ServerConfig::NoUserDns), DT_BOOLEAN, NULL},
+ {"options", "syntaxhints", "0", new ValueContainerBool (&ServerConfig::SyntaxHints), DT_BOOLEAN, NULL},
+ {"options", "cyclehosts", "0", new ValueContainerBool (&ServerConfig::CycleHosts), DT_BOOLEAN, NULL},
+ {"options", "ircumsgprefix","0", new ValueContainerBool (&ServerConfig::UndernetMsgPrefix), DT_BOOLEAN, NULL},
+ {"security", "announceinvites", "1", NULL, DT_NOTHING, ValidateInvite},
+ {"options", "hostintopic", "1", new ValueContainerBool (&ServerConfig::FullHostInTopic), DT_BOOLEAN, NULL},
+ {"security", "hidemodes", "", NULL, DT_NOTHING, ValidateModeLists},
+ {"options", "exemptchanops","", NULL, DT_NOTHING, ValidateExemptChanOps},
+ {"security", "maxtargets", "20", new ValueContainerUInt (&ServerConfig::MaxTargets), DT_INTEGER, ValidateMaxTargets},
+ {"options", "defaultmodes", "nt", new ValueContainerChar (&ServerConfig::DefaultModes), DT_CHARPTR, NULL},
+ {"pid", "file", "", new ValueContainerString (&ServerConfig::PID), DT_CHARPTR, NULL},
+ {"whowas", "groupsize", "10", new ValueContainerInt (&ServerConfig::WhoWasGroupSize), DT_INTEGER, NULL},
+ {"whowas", "maxgroups", "10240", new ValueContainerInt (&ServerConfig::WhoWasMaxGroups), DT_INTEGER, NULL},
+ {"whowas", "maxkeep", "3600", NULL, DT_NOTHING, ValidateWhoWas},
+ {"die", "value", "", new ValueContainerChar (&ServerConfig::DieValue), DT_CHARPTR, NULL},
+ {"channels", "users", "20", new ValueContainerUInt (&ServerConfig::MaxChans), DT_INTEGER, NULL},
+ {"channels", "opers", "60", new ValueContainerUInt (&ServerConfig::OperMaxChans), DT_INTEGER, NULL},
+ {"cidr", "ipv4clone", "32", new ValueContainerInt (&ServerConfig::c_ipv4_range), DT_INTEGER, NULL},
+ {"cidr", "ipv6clone", "128", new ValueContainerInt (&ServerConfig::c_ipv6_range), DT_INTEGER, NULL},
+ {"limits", "maxnick", "32", new ValueContainerLimit (&ServerLimits::NickMax), DT_LIMIT, NULL},
+ {"limits", "maxchan", "64", new ValueContainerLimit (&ServerLimits::ChanMax), DT_LIMIT, NULL},
+ {"limits", "maxmodes", "20", new ValueContainerLimit (&ServerLimits::MaxModes), DT_LIMIT, NULL},
+ {"limits", "maxident", "11", new ValueContainerLimit (&ServerLimits::IdentMax), DT_LIMIT, NULL},
+ {"limits", "maxquit", "255", new ValueContainerLimit (&ServerLimits::MaxQuit), DT_LIMIT, NULL},
+ {"limits", "maxtopic", "307", new ValueContainerLimit (&ServerLimits::MaxTopic), DT_LIMIT, NULL},
+ {"limits", "maxkick", "255", new ValueContainerLimit (&ServerLimits::MaxKick), DT_LIMIT, NULL},
+ {"limits", "maxgecos", "128", new ValueContainerLimit (&ServerLimits::MaxGecos), DT_LIMIT, NULL},
+ {"limits", "maxaway", "200", new ValueContainerLimit (&ServerLimits::MaxAway), DT_LIMIT, NULL},
+ {"options", "invitebypassmodes", "1", new ValueContainerBool (&ServerConfig::InvBypassModes), DT_BOOLEAN, NULL},
+};
+
+/* These tags can occur multiple times, and therefore they have special code to read them
+ * which is different to the code for reading the singular tags listed above.
+ */
+MultiConfig MultiValues[] = {
+
+ {"connect",
+ {"allow", "deny", "password", "timeout", "pingfreq",
+ "sendq", "recvq", "localmax", "globalmax", "port",
+ "name", "parent", "maxchans", "limit", "hash",
+ NULL},
+ {"", "", "", "", "120",
+ "", "", "3", "3", "0",
+ "", "", "0", "0", "",
+ NULL},
+ {DT_IPADDRESS|DT_ALLOW_WILD, DT_IPADDRESS|DT_ALLOW_WILD, DT_CHARPTR, DT_INTEGER, DT_INTEGER,
+ DT_INTEGER, DT_INTEGER, DT_INTEGER, DT_INTEGER, DT_INTEGER,
+ DT_NOSPACES, DT_NOSPACES, DT_INTEGER, DT_INTEGER, DT_CHARPTR},
+ NULL,},
+
+ {"uline",
+ {"server", "silent", NULL},
+ {"", "0", NULL},
+ {DT_HOSTNAME, DT_BOOLEAN},
+ DoULine},
+
+ {"banlist",
+ {"chan", "limit", NULL},
+ {"", "", NULL},
+ {DT_CHARPTR, DT_INTEGER},
+ DoMaxBans},
+
+ {"module",
+ {"name", NULL},
+ {"", NULL},
+ {DT_CHARPTR},
+ NULL},
+
+ {"badip",
+ {"reason", "ipmask", NULL},
+ {"No reason", "", NULL},
+ {DT_CHARPTR, DT_IPADDRESS|DT_ALLOW_WILD},
+ DoZLine},
+
+ {"badnick",
+ {"reason", "nick", NULL},
+ {"No reason", "", NULL},
+ {DT_CHARPTR, DT_CHARPTR},
+ DoQLine},
+
+ {"badhost",
+ {"reason", "host", NULL},
+ {"No reason", "", NULL},
+ {DT_CHARPTR, DT_CHARPTR},
+ DoKLine},
+
+ {"exception",
+ {"reason", "host", NULL},
+ {"No reason", "", NULL},
+ {DT_CHARPTR, DT_CHARPTR},
+ DoELine},
+
+ {"type",
+ {"name", "classes", NULL},
+ {"", "", NULL},
+ {DT_NOSPACES, DT_CHARPTR},
+ DoType},
+
+ {"class",
+ {"name", "commands", "usermodes", "chanmodes", "privs", NULL},
+ {"", "", "", "", "", NULL},
+ {DT_NOSPACES, DT_CHARPTR, DT_CHARPTR, DT_CHARPTR, DT_CHARPTR},
+ DoClass},
+};
+
+/* These tags MUST occur and must ONLY occur once in the config file */
+static const char* Once[] = { "server", "admin", "files", "power", "options" };
+
+// WARNING: it is not safe to use most of the codebase in this function, as it
+// will run in the config reader thread
+void ServerConfig::Read()
+{
+ /* Load and parse the config file, if there are any errors then explode */
+
+ if (!this->DoInclude(ServerInstance->ConfigFileName))
+ {
+ valid = false;
+ return;
+ }
+}
+
+void ServerConfig::Apply(ServerConfig* old, const std::string &useruid)
+{
+ valid = true;
+ /* std::ostringstream::clear() does not clear the string itself, only the error flags. */
+ errstr.clear();
+ errstr.str().clear();
+ include_stack.clear();
+
+ /* The stuff in here may throw CoreException, be sure we're in a position to catch it. */
+ try
+ {
+ /* Check we dont have more than one of singular tags, or any of them missing
+ */
+ for (int Index = 0; Index * sizeof(*Once) < sizeof(Once); Index++)
+ CheckOnce(Once[Index]);
+
+ for (int Index = 0; Index * sizeof(Deprecated) < sizeof(ChangedConfig); Index++)
+ {
+ char item[MAXBUF];
+ *item = 0;
+ if (ConfValue(ChangedConfig[Index].tag, ChangedConfig[Index].value, "", 0, item, MAXBUF, true) || *item)
+ throw CoreException(std::string("Your configuration contains a deprecated value: <") + ChangedConfig[Index].tag + ":" + ChangedConfig[Index].value + "> - " + ChangedConfig[Index].reason);
+ }
+
+ /* Read the values of all the tags which occur once or not at all, and call their callbacks.
+ */
+ for (int Index = 0; Index * sizeof(*Values) < sizeof(Values); ++Index)
+ {
+ char item[MAXBUF];
+ int dt = Values[Index].datatype;
+ bool allow_newlines = ((dt & DT_ALLOW_NEWLINE) > 0);
+ bool allow_wild = ((dt & DT_ALLOW_WILD) > 0);
+ dt &= ~DT_ALLOW_NEWLINE;
+ dt &= ~DT_ALLOW_WILD;
+
+ ConfValue(Values[Index].tag, Values[Index].value, Values[Index].default_value, 0, item, MAXBUF, allow_newlines);
+ ValueItem vi(item);
+
+ if (Values[Index].validation_function && !Values[Index].validation_function(this, Values[Index].tag, Values[Index].value, vi))
+ throw CoreException("One or more values in your configuration file failed to validate. Please see your ircd.log for more information.");
+
+ switch (dt)
+ {
+ case DT_NOSPACES:
+ {
+ ValueContainerChar* vcc = (ValueContainerChar*)Values[Index].val;
+ ValidateNoSpaces(vi.GetString(), Values[Index].tag, Values[Index].value);
+ vcc->Set(this, vi);
+ }
+ break;
+ case DT_HOSTNAME:
+ {
+ ValueContainerChar* vcc = (ValueContainerChar*)Values[Index].val;
+ ValidateHostname(vi.GetString(), Values[Index].tag, Values[Index].value);
+ vcc->Set(this, vi);
+ }
+ break;
+ case DT_IPADDRESS:
+ {
+ ValueContainerChar* vcc = (ValueContainerChar*)Values[Index].val;
+ ValidateIP(vi.GetString(), Values[Index].tag, Values[Index].value, allow_wild);
+ vcc->Set(this, vi);
+ }
+ break;
+ case DT_CHANNEL:
+ {
+ ValueContainerChar* vcc = (ValueContainerChar*)Values[Index].val;
+ if (*(vi.GetString()) && !ServerInstance->IsChannel(vi.GetString(), MAXBUF))
+ {
+ throw CoreException("The value of <"+std::string(Values[Index].tag)+":"+Values[Index].value+"> is not a valid channel name");
+ }
+ vcc->Set(this, vi);
+ }
+ break;
+ case DT_CHARPTR:
+ {
+ ValueContainerChar* vcc = dynamic_cast<ValueContainerChar*>(Values[Index].val);
+ if (vcc)
+ vcc->Set(this, vi);
+ ValueContainerString* vcs = dynamic_cast<ValueContainerString*>(Values[Index].val);
+ if (vcs)
+ vcs->Set(this, vi.GetValue());
+ }
+ break;
+ case DT_INTEGER:
+ {
+ int val = vi.GetInteger();
+ ValueContainerInt* vci = (ValueContainerInt*)Values[Index].val;
+ vci->Set(this, val);
+ }
+ break;
+ case DT_LIMIT:
+ {
+ int val = vi.GetInteger();
+ ValueContainerLimit* vci = (ValueContainerLimit*)Values[Index].val;
+ vci->Set(this, val);
+ }
+ break;
+ case DT_BOOLEAN:
+ {
+ bool val = vi.GetBool();
+ ValueContainerBool* vcb = (ValueContainerBool*)Values[Index].val;
+ vcb->Set(this, val);
+ }
+ break;
+ }
+ }
+
+ /* Read the multiple-tag items (class tags, connect tags, etc)
+ * and call the callbacks associated with them. We have three
+ * callbacks for these, a 'start', 'item' and 'end' callback.
+ */
+ for (int Index = 0; Index * sizeof(MultiConfig) < sizeof(MultiValues); ++Index)
+ {
+ int number_of_tags = ConfValueEnum(MultiValues[Index].tag);
+
+ for (int tagnum = 0; tagnum < number_of_tags; ++tagnum)
+ {
+ ValueList vl;
+ for (int valuenum = 0; (MultiValues[Index].items[valuenum]) && (valuenum < MAX_VALUES_PER_TAG); ++valuenum)
+ {
+ int dt = MultiValues[Index].datatype[valuenum];
+ bool allow_newlines = ((dt & DT_ALLOW_NEWLINE) > 0);
+ bool allow_wild = ((dt & DT_ALLOW_WILD) > 0);
+ dt &= ~DT_ALLOW_NEWLINE;
+ dt &= ~DT_ALLOW_WILD;
+
+ switch (dt)
+ {
+ case DT_NOSPACES:
+ {
+ char item[MAXBUF];
+ if (ConfValue(MultiValues[Index].tag, MultiValues[Index].items[valuenum], MultiValues[Index].items_default[valuenum], tagnum, item, MAXBUF, allow_newlines))
+ vl.push_back(ValueItem(item));
+ else
+ vl.push_back(ValueItem(""));
+ ValidateNoSpaces(vl[vl.size()-1].GetString(), MultiValues[Index].tag, MultiValues[Index].items[valuenum]);
+ }
+ break;
+ case DT_HOSTNAME:
+ {
+ char item[MAXBUF];
+ if (ConfValue(MultiValues[Index].tag, MultiValues[Index].items[valuenum], MultiValues[Index].items_default[valuenum], tagnum, item, MAXBUF, allow_newlines))
+ vl.push_back(ValueItem(item));
+ else
+ vl.push_back(ValueItem(""));
+ ValidateHostname(vl[vl.size()-1].GetString(), MultiValues[Index].tag, MultiValues[Index].items[valuenum]);
+ }
+ break;
+ case DT_IPADDRESS:
+ {
+ char item[MAXBUF];
+ if (ConfValue(MultiValues[Index].tag, MultiValues[Index].items[valuenum], MultiValues[Index].items_default[valuenum], tagnum, item, MAXBUF, allow_newlines))
+ vl.push_back(ValueItem(item));
+ else
+ vl.push_back(ValueItem(""));
+ ValidateIP(vl[vl.size()-1].GetString(), MultiValues[Index].tag, MultiValues[Index].items[valuenum], allow_wild);
+ }
+ break;
+ case DT_CHANNEL:
+ {
+ char item[MAXBUF];
+ if (ConfValue(MultiValues[Index].tag, MultiValues[Index].items[valuenum], MultiValues[Index].items_default[valuenum], tagnum, item, MAXBUF, allow_newlines))
+ vl.push_back(ValueItem(item));
+ else
+ vl.push_back(ValueItem(""));
+ if (!ServerInstance->IsChannel(vl[vl.size()-1].GetString(), MAXBUF))
+ throw CoreException("The value of <"+std::string(MultiValues[Index].tag)+":"+MultiValues[Index].items[valuenum]+"> number "+ConvToStr(tagnum + 1)+" is not a valid channel name");
+ }
+ break;
+ case DT_CHARPTR:
+ {
+ char item[MAXBUF];
+ if (ConfValue(MultiValues[Index].tag, MultiValues[Index].items[valuenum], MultiValues[Index].items_default[valuenum], tagnum, item, MAXBUF, allow_newlines))
+ vl.push_back(ValueItem(item));
+ else
+ vl.push_back(ValueItem(""));
+ }
+ break;
+ case DT_INTEGER:
+ {
+ int item = 0;
+ if (ConfValueInteger(MultiValues[Index].tag, MultiValues[Index].items[valuenum], MultiValues[Index].items_default[valuenum], tagnum, item))
+ vl.push_back(ValueItem(item));
+ else
+ vl.push_back(ValueItem(0));
+ }
+ break;
+ case DT_BOOLEAN:
+ {
+ bool item = ConfValueBool(MultiValues[Index].tag, MultiValues[Index].items[valuenum], MultiValues[Index].items_default[valuenum], tagnum);
+ vl.push_back(ValueItem(item));
+ }
+ break;
+ }
+ }
+ if (MultiValues[Index].validation_function)
+ MultiValues[Index].validation_function(this, MultiValues[Index].tag, MultiValues[Index].items, vl, MultiValues[Index].datatype);
+ }
+ }
+
+ /* Finalise the limits, increment them all by one so that we can just put assign(str, 0, val)
+ * rather than assign(str, 0, val + 1)
+ */
+ Limits.Finalise();
+
+ // Handle special items
+ CrossCheckOperClassType();
+ CrossCheckConnectBlocks(old);
+ }
+ catch (CoreException &ce)
+ {
+ errstr << ce.GetReason();
+ valid = false;
+ }
+
+ // write once here, to try it out and make sure its ok
+ ServerInstance->WritePID(this->PID);
+
+ FailedPortList pl;
+ ServerInstance->BindPorts(pl);
+
+ /*
+ * These values can only be set on boot. Keep their old values. Do it before we send messages so we actually have a servername.
+ */
+ if (old)
+ {
+ memcpy(this->ServerName, old->ServerName, sizeof(this->ServerName));
+ memcpy(this->sid, old->sid, sizeof(this->sid));
+ }
+
+ if (pl.size())
+ {
+ errstr << "Not all your client ports could be bound.\nThe following port(s) failed to bind:\n";
+
+ int j = 1;
+ for (FailedPortList::iterator i = pl.begin(); i != pl.end(); i++, j++)
+ {
+ char buf[MAXBUF];
+ snprintf(buf, MAXBUF, "%d. Address: %s Reason: %s\n", j, i->first.empty() ? "<all>" : i->first.c_str(), i->second.c_str());
+ errstr << buf;
+ }
+ }
+
+ User* user = useruid.empty() ? NULL : ServerInstance->FindNick(useruid);
+
+ valid = errstr.str().empty();
+ if (!valid)
+ ServerInstance->Logs->Log("CONFIG",DEFAULT, "There were errors in your configuration file:");
+
+ while (errstr.good())
+ {
+ std::string line;
+ getline(errstr, line, '\n');
+ if (!line.empty())
+ {
+ if (user)
+ user->WriteServ("NOTICE %s :*** %s", user->nick.c_str(), line.c_str());
+ else
+ ServerInstance->SNO->WriteGlobalSno('a', line);
+ }
+
+ if (!old)
+ {
+ // Starting up, so print it out so it's seen. XXX this is a bit of a hack.
+ printf("%s\n", line.c_str());
+ }
+ }
+
+ errstr.clear();
+ errstr.str(std::string());
+
+ /* No old configuration -> initial boot, nothing more to do here */
+ if (!old)
+ {
+ if (!valid)
+ {
+ ServerInstance->Exit(EXIT_STATUS_CONFIG);
+ }
+
+ return;
+ }
+
+ // If there were errors processing configuration, don't touch modules.
+ if (!valid)
+ return;
+
+ ApplyModules(user);
+ for (std::vector<ConnectClass*>::iterator i = removed_classes.begin(); i != removed_classes.end(); i++)
+ {
+ ConnectClass* c = *i;
+ if (0 == --c->RefCount)
+ delete c;
+ }
+}
+
+void ServerConfig::ApplyModules(User* user)
+{
+ const std::vector<std::string> v = ServerInstance->Modules->GetAllModuleNames(0);
+ std::vector<std::string> added_modules;
+ std::set<std::string> removed_modules(v.begin(), v.end());
+
+ int new_module_count = ConfValueEnum("module");
+ for(int i=0; i < new_module_count; i++)
+ {
+ std::string name;
+ if (ConfValue("module", "name", i, name, false))
+ {
+ // if this module is already loaded, the erase will succeed, so we need do nothing
+ // otherwise, we need to add the module (which will be done later)
+ if (removed_modules.erase(name) == 0)
+ added_modules.push_back(name);
+ }
+ }
+
+ for (std::set<std::string>::iterator removing = removed_modules.begin(); removing != removed_modules.end(); removing++)
+ {
+ if (ServerInstance->Modules->Unload(removing->c_str()))
+ {
+ ServerInstance->SNO->WriteToSnoMask('a', "*** REHASH UNLOADED MODULE: %s",removing->c_str());
+
+ if (user)
+ user->WriteNumeric(RPL_UNLOADEDMODULE, "%s %s :Module %s successfully unloaded.",user->nick.c_str(), removing->c_str(), removing->c_str());
+ else
+ ServerInstance->SNO->WriteToSnoMask('a', "Module %s successfully unloaded.", removing->c_str());
+ }
+ else
+ {
+ if (user)
+ user->WriteNumeric(ERR_CANTUNLOADMODULE, "%s %s :Failed to unload module %s: %s",user->nick.c_str(), removing->c_str(), removing->c_str(), ServerInstance->Modules->LastError().c_str());
+ else
+ ServerInstance->SNO->WriteToSnoMask('a', "Failed to unload module %s: %s", removing->c_str(), ServerInstance->Modules->LastError().c_str());
+ }
+ }
+
+ for (std::vector<std::string>::iterator adding = added_modules.begin(); adding != added_modules.end(); adding++)
+ {
+ if (ServerInstance->Modules->Load(adding->c_str()))
+ {
+ ServerInstance->SNO->WriteToSnoMask('a', "*** REHASH LOADED MODULE: %s",adding->c_str());
+ if (user)
+ user->WriteNumeric(RPL_LOADEDMODULE, "%s %s :Module %s successfully loaded.",user->nick.c_str(), adding->c_str(), adding->c_str());
+ else
+ ServerInstance->SNO->WriteToSnoMask('a', "Module %s successfully loaded.", adding->c_str());
+ }
+ else
+ {
+ if (user)
+ user->WriteNumeric(ERR_CANTLOADMODULE, "%s %s :Failed to load module %s: %s",user->nick.c_str(), adding->c_str(), adding->c_str(), ServerInstance->Modules->LastError().c_str());
+ else
+ ServerInstance->SNO->WriteToSnoMask('a', "Failed to load module %s: %s", adding->c_str(), ServerInstance->Modules->LastError().c_str());
+ }
+ }
+
+ if (user)
+ user->WriteServ("NOTICE %s :*** Successfully rehashed server.", user->nick.c_str());
+ else
+ ServerInstance->SNO->WriteToSnoMask('a', "*** Successfully rehashed server.");
+}
+
+bool ServerConfig::LoadConf(FILE* &conf, const char* filename)
+{
+ std::string line;
+ char ch;
+ long linenumber = 1;
+ long last_successful_parse = 1;
+ bool in_tag;
+ bool in_quote;
+ bool in_comment;
+ int character_count = 0;
+
+ in_tag = false;
+ in_quote = false;
+ in_comment = false;
+
+ ServerInstance->Logs->Log("CONFIG", DEBUG, "Reading %s", filename);
+
+ /* Check if the file open failed first */
+ if (!conf)
+ {
+ errstr << "LoadConf: Couldn't open config file: " << filename << std::endl;
+ return false;
+ }
+
+ for (unsigned int t = 0; t < include_stack.size(); t++)
+ {
+ if (std::string(filename) == include_stack[t])
+ {
+ errstr << "File " << filename << " is included recursively (looped inclusion)." << std::endl;
+ return false;
+ }
+ }
+
+ /* It's not already included, add it to the list of files we've loaded */
+ include_stack.push_back(filename);
+
+ /* Start reading characters... */
+ while ((ch = fgetc(conf)) != EOF)
+ {
+ /*
+ * Fix for moronic windows issue spotted by Adremelech.
+ * Some windows editors save text files as utf-16, which is
+ * a total pain in the ass to parse. Users should save in the
+ * right config format! If we ever see a file where the first
+ * byte is 0xFF or 0xFE, or the second is 0xFF or 0xFE, then
+ * this is most likely a utf-16 file. Bail out and insult user.
+ */
+ if ((character_count++ < 2) && (ch == '\xFF' || ch == '\xFE'))
+ {
+ errstr << "File " << filename << " cannot be read, as it is encoded in braindead UTF-16. Save your file as plain ASCII!" << std::endl;
+ return false;
+ }
+
+ /*
+ * Here we try and get individual tags on separate lines,
+ * this would be so easy if we just made people format
+ * their config files like that, but they don't so...
+ * We check for a '<' and then know the line is over when
+ * we get a '>' not inside quotes. If we find two '<' and
+ * no '>' then die with an error.
+ */
+
+ if ((ch == '#') && !in_quote)
+ in_comment = true;
+
+ switch (ch)
+ {
+ case '\n':
+ if (in_quote)
+ line += '\n';
+ linenumber++;
+ case '\r':
+ if (!in_quote)
+ in_comment = false;
+ case '\0':
+ continue;
+ case '\t':
+ ch = ' ';
+ }
+
+ if(in_comment)
+ continue;
+
+ /* XXX: Added by Brain, May 1st 2006 - Escaping of characters.
+ * Note that this WILL NOT usually allow insertion of newlines,
+ * because a newline is two characters long. Use it primarily to
+ * insert the " symbol.
+ *
+ * Note that this also involves a further check when parsing the line,
+ * which can be found below.
+ */
+ if ((ch == '\\') && (in_quote) && (in_tag))
+ {
+ line += ch;
+ char real_character;
+ if (!feof(conf))
+ {
+ real_character = fgetc(conf);
+ if (real_character == 'n')
+ real_character = '\n';
+ line += real_character;
+ continue;
+ }
+ else
+ {
+ errstr << "End of file after a \\, what did you want to escape?: " << filename << ":" << linenumber << std::endl;
+ return false;
+ }
+ }
+
+ if (ch != '\r')
+ line += ch;
+
+ if ((ch != '<') && (!in_tag) && (!in_comment) && (ch > ' ') && (ch != 9))
+ {
+ errstr << "You have stray characters beyond the tag which starts at " << filename << ":" << last_successful_parse << std::endl;
+ return false;
+ }
+
+ if (ch == '<')
+ {
+ if (in_tag)
+ {
+ if (!in_quote)
+ {
+ errstr << "The tag at location " << filename << ":" << last_successful_parse << " was valid, but there is an error in the tag which comes after it. You are possibly missing a \" or >. Please check this." << std::endl;
+ return false;
+ }
+ }
+ else
+ {
+ if (in_quote)
+ {
+ errstr << "Parser error: Inside a quote but not within the last valid tag, which was opened at: " << filename << ":" << last_successful_parse << std::endl;
+ return false;
+ }
+ else
+ {
+ // errstr << "Opening new config tag on line " << linenumber << std::endl;
+ in_tag = true;
+ }
+ }
+ }
+ else if (ch == '"')
+ {
+ if (in_tag)
+ {
+ if (in_quote)
+ {
+ // errstr << "Closing quote in config tag on line " << linenumber << std::endl;
+ in_quote = false;
+ }
+ else
+ {
+ // errstr << "Opening quote in config tag on line " << linenumber << std::endl;
+ in_quote = true;
+ }
+ }
+ else
+ {
+ if (in_quote)
+ {
+ errstr << "The tag immediately after the one at " << filename << ":" << last_successful_parse << " has a missing closing \" symbol. Please check this." << std::endl;
+ }
+ else
+ {
+ errstr << "You have opened a quote (\") beyond the tag at " << filename << ":" << last_successful_parse << " without opening a new tag. Please check this." << std::endl;
+ }
+ }
+ }
+ else if (ch == '>')
+ {
+ if (!in_quote)
+ {
+ if (in_tag)
+ {
+ // errstr << "Closing config tag on line " << linenumber << std::endl;
+ in_tag = false;
+
+ /*
+ * If this finds an <include> then ParseLine can simply call
+ * LoadConf() and load the included config into the same ConfigDataHash
+ */
+ long bl = linenumber;
+ if (!this->ParseLine(filename, line, linenumber))
+ return false;
+ last_successful_parse = linenumber;
+
+ linenumber = bl;
+
+ line.clear();
+ }
+ else
+ {
+ errstr << "You forgot to close the tag which comes immediately after the one at " << filename << ":" << last_successful_parse << std::endl;
+ return false;
+ }
+ }
+ }
+ }
+
+ /* Fix for bug #392 - if we reach the end of a file and we are still in a quote or comment, most likely the user fucked up */
+ if (in_comment || in_quote)
+ {
+ errstr << "Reached end of file whilst still inside a quoted section or tag. This is most likely an error or there \
+ is a newline missing from the end of the file: " << filename << ":" << linenumber << std::endl;
+ }
+
+ return true;
+}
+
+
+bool ServerConfig::LoadConf(FILE* &conf, const std::string &filename)
+{
+ return this->LoadConf(conf, filename.c_str());
+}
+
+bool ServerConfig::ParseLine(const std::string &filename, std::string &line, long &linenumber)
+{
+ std::string tagname;
+ std::string current_key;
+ std::string current_value;
+ KeyValList results;
+ char last_char = 0;
+ bool got_name;
+ bool got_key;
+ bool in_quote;
+
+ got_name = got_key = in_quote = false;
+
+ for(std::string::iterator c = line.begin(); c != line.end(); c++)
+ {
+ if (!got_name)
+ {
+ /* We don't know the tag name yet. */
+
+ if (*c != ' ')
+ {
+ if (*c != '<')
+ {
+ if ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <='Z') || (*c >= '0' && *c <= '9') || *c == '_')
+ tagname += *c;
+ else
+ {
+ errstr << "Invalid character in value name of tag: '" << *c << "' in value '" << tagname << "' in filename: " << filename << ":" << linenumber << std::endl;
+ return false;
+ }
+ }
+ }
+ else
+ {
+ /* We got to a space, we should have the tagname now. */
+ if(tagname.length())
+ {
+ got_name = true;
+ }
+ }
+ }
+ else
+ {
+ /* We have the tag name */
+ if (!got_key)
+ {
+ /* We're still reading the key name */
+ if ((*c != '=') && (*c != '>'))
+ {
+ if (*c != ' ')
+ {
+ if ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <='Z') || (*c >= '0' && *c <= '9') || *c == '_')
+ current_key += *c;
+ else
+ {
+ errstr << "Invalid character in key: '" << *c << "' in key '" << current_key << "' in filename: " << filename << ":" << linenumber << std::endl;
+ return false;
+ }
+ }
+ }
+ else
+ {
+ /* We got an '=', end of the key name. */
+ got_key = true;
+ }
+ }
+ else
+ {
+ /* We have the key name, now we're looking for quotes and the value */
+
+ /* Correctly handle escaped characters here.
+ * See the XXX'ed section above.
+ */
+ if ((*c == '\\') && (in_quote))
+ {
+ c++;
+ if (*c == 'n')
+ current_value += '\n';
+ else
+ current_value += *c;
+ continue;
+ }
+ else if ((*c == '\\') && (!in_quote))
+ {
+ errstr << "You can't have an escape sequence outside of a quoted section: " << filename << ":" << linenumber << std::endl;
+ return false;
+ }
+ else if ((*c == '\n') && (in_quote))
+ {
+ /* Got a 'real' \n, treat it as part of the value */
+ current_value += '\n';
+ continue;
+ }
+ else if ((*c == '\r') && (in_quote))
+ {
+ /* Got a \r, drop it */
+ continue;
+ }
+
+ if (*c == '"')
+ {
+ if (!in_quote)
+ {
+ /* We're not already in a quote. */
+ in_quote = true;
+ }
+ else
+ {
+ /* Leaving the quotes, we have the current value */
+ results.push_back(KeyVal(current_key, current_value));
+
+ // std::cout << "<" << tagname << ":" << current_key << "> " << current_value << std::endl;
+
+ in_quote = false;
+ got_key = false;
+
+ if ((tagname == "include") && (current_key == "file"))
+ {
+ if (!this->DoInclude(current_value))
+ return false;
+ }
+ else if ((tagname == "include") && (current_key == "executable"))
+ {
+ /* Pipe an executable and use its stdout as config data */
+ if (!this->DoPipe(current_value))
+ return false;
+ }
+
+ current_key.clear();
+ current_value.clear();
+ }
+ }
+ else
+ {
+ if (in_quote)
+ {
+ last_char = *c;
+ current_value += *c;
+ }
+ }
+ }
+ }
+ }
+
+ /* Finished parsing the tag, add it to the config hash */
+ config_data.insert(std::pair<std::string, KeyValList > (tagname, results));
+
+ return true;
+}
+
+bool ServerConfig::DoPipe(const std::string &file)
+{
+ FILE* conf = popen(file.c_str(), "r");
+ bool ret = false;
+
+ if (conf)
+ {
+ ret = LoadConf(conf, file.c_str());
+ pclose(conf);
+ }
+ else
+ errstr << "Couldn't execute: " << file << std::endl;
+
+ return ret;
+}
+
+bool ServerConfig::StartsWithWindowsDriveLetter(const std::string &path)
+{
+ return (path.length() > 2 && isalpha(path[0]) && path[1] == ':');
+}
+
+bool ServerConfig::DoInclude(const std::string &file)
+{
+ std::string confpath;
+ std::string newfile;
+ std::string::size_type pos;
+
+ confpath = ServerInstance->ConfigFileName;
+ newfile = file;
+
+ std::replace(newfile.begin(),newfile.end(),'\\','/');
+ std::replace(confpath.begin(),confpath.end(),'\\','/');
+
+ if ((newfile[0] != '/') && (!StartsWithWindowsDriveLetter(newfile)))
+ {
+ if((pos = confpath.rfind("/")) != std::string::npos)
+ {
+ /* Leaves us with just the path */
+ newfile = confpath.substr(0, pos) + std::string("/") + newfile;
+ }
+ else
+ {
+ errstr << "Couldn't get config path from: " << ServerInstance->ConfigFileName << std::endl;
+ return false;
+ }
+ }
+
+ FILE* conf = fopen(newfile.c_str(), "r");
+ bool ret = false;
+
+ if (conf)
+ {
+ ret = LoadConf(conf, newfile);
+ fclose(conf);
+ }
+ else
+ errstr << "Couldn't open config file: " << file << std::endl;
+
+ return ret;
+}
+
+bool ServerConfig::ConfValue(const char* tag, const char* var, int index, char* result, int length, bool allow_linefeeds)
+{
+ return ConfValue(tag, var, "", index, result, length, allow_linefeeds);
+}
+
+bool ServerConfig::ConfValue(const char* tag, const char* var, const char* default_value, int index, char* result, int length, bool allow_linefeeds)
+{
+ std::string value;
+ bool r = ConfValue(std::string(tag), std::string(var), std::string(default_value), index, value, allow_linefeeds);
+ strlcpy(result, value.c_str(), length);
+ return r;
+}
+
+bool ServerConfig::ConfValue(const std::string &tag, const std::string &var, int index, std::string &result, bool allow_linefeeds)
+{
+ return ConfValue(tag, var, "", index, result, allow_linefeeds);
+}
+
+bool ServerConfig::ConfValue(const std::string &tag, const std::string &var, const std::string &default_value, int index, std::string &result, bool allow_linefeeds)
+{
+ ConfigDataHash::size_type pos = index;
+ if (pos < config_data.count(tag))
+ {
+ ConfigDataHash::iterator iter = config_data.find(tag);
+
+ for(int i = 0; i < index; i++)
+ iter++;
+
+ for(KeyValList::iterator j = iter->second.begin(); j != iter->second.end(); j++)
+ {
+ if(j->first == var)
+ {
+ if ((!allow_linefeeds) && (j->second.find('\n') != std::string::npos))
+ {
+ ServerInstance->Logs->Log("CONFIG",DEFAULT, "Value of <" + tag + ":" + var+ "> contains a linefeed, and linefeeds in this value are not permitted -- stripped to spaces.");
+ for (std::string::iterator n = j->second.begin(); n != j->second.end(); n++)
+ if (*n == '\n')
+ *n = ' ';
+ }
+ else
+ {
+ result = j->second;
+ return true;
+ }
+ }
+ }
+ if (!default_value.empty())
+ {
+ result = default_value;
+ return true;
+ }
+ }
+ else if (pos == 0)
+ {
+ if (!default_value.empty())
+ {
+ result = default_value;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ServerConfig::ConfValueInteger(const char* tag, const char* var, int index, int &result)
+{
+ return ConfValueInteger(std::string(tag), std::string(var), "", index, result);
+}
+
+bool ServerConfig::ConfValueInteger(const char* tag, const char* var, const char* default_value, int index, int &result)
+{
+ return ConfValueInteger(std::string(tag), std::string(var), std::string(default_value), index, result);
+}
+
+bool ServerConfig::ConfValueInteger(const std::string &tag, const std::string &var, int index, int &result)
+{
+ return ConfValueInteger(tag, var, "", index, result);
+}
+
+bool ServerConfig::ConfValueInteger(const std::string &tag, const std::string &var, const std::string &default_value, int index, int &result)
+{
+ std::string value;
+ std::istringstream stream;
+ bool r = ConfValue(tag, var, default_value, index, value);
+ stream.str(value);
+ if(!(stream >> result))
+ return false;
+ else
+ {
+ if (!value.empty())
+ {
+ if (value.substr(0,2) == "0x")
+ {
+ char* endptr;
+
+ value.erase(0,2);
+ result = strtol(value.c_str(), &endptr, 16);
+
+ /* No digits found */
+ if (endptr == value.c_str())
+ return false;
+ }
+ else
+ {
+ char denominator = *(value.end() - 1);
+ switch (toupper(denominator))
+ {
+ case 'K':
+ /* Kilobytes -> bytes */
+ result = result * 1024;
+ break;
+ case 'M':
+ /* Megabytes -> bytes */
+ result = result * 1024 * 1024;
+ break;
+ case 'G':
+ /* Gigabytes -> bytes */
+ result = result * 1024 * 1024 * 1024;
+ break;
+ }
+ }
+ }
+ }
+ return r;
+}
+
+
+bool ServerConfig::ConfValueBool(const char* tag, const char* var, int index)
+{
+ return ConfValueBool(std::string(tag), std::string(var), "", index);
+}
+
+bool ServerConfig::ConfValueBool(const char* tag, const char* var, const char* default_value, int index)
+{
+ return ConfValueBool(std::string(tag), std::string(var), std::string(default_value), index);
+}
+
+bool ServerConfig::ConfValueBool(const std::string &tag, const std::string &var, int index)
+{
+ return ConfValueBool(tag, var, "", index);
+}
+
+bool ServerConfig::ConfValueBool(const std::string &tag, const std::string &var, const std::string &default_value, int index)
+{
+ std::string result;
+ if(!ConfValue(tag, var, default_value, index, result))
+ return false;
+
+ return ((result == "yes") || (result == "true") || (result == "1"));
+}
+
+int ServerConfig::ConfValueEnum(const char* tag)
+{
+ return config_data.count(tag);
+}
+
+int ServerConfig::ConfValueEnum(const std::string &tag)
+{
+ return config_data.count(tag);
+}
+
+int ServerConfig::ConfVarEnum(const char* tag, int index)
+{
+ return ConfVarEnum(std::string(tag), index);
+}
+
+int ServerConfig::ConfVarEnum(const std::string &tag, int index)
+{
+ ConfigDataHash::size_type pos = index;
+
+ if (pos < config_data.count(tag))
+ {
+ ConfigDataHash::const_iterator iter = config_data.find(tag);
+
+ for(int i = 0; i < index; i++)
+ iter++;
+
+ return iter->second.size();
+ }
+
+ return 0;
+}
+
+/** Read the contents of a file located by `fname' into a file_cache pointed at by `F'.
+ */
+bool ServerConfig::ReadFile(file_cache &F, const char* fname)
+{
+ if (!fname || !*fname)
+ return false;
+
+ FILE* file = NULL;
+ char linebuf[MAXBUF];
+
+ F.clear();
+
+ if ((*fname != '/') && (*fname != '\\') && (!StartsWithWindowsDriveLetter(fname)))
+ {
+ std::string::size_type pos;
+ std::string confpath = ServerInstance->ConfigFileName;
+ std::string newfile = fname;
+
+ if (((pos = confpath.rfind("/"))) != std::string::npos)
+ newfile = confpath.substr(0, pos) + std::string("/") + fname;
+ else if (((pos = confpath.rfind("\\"))) != std::string::npos)
+ newfile = confpath.substr(0, pos) + std::string("\\") + fname;
+
+ ServerInstance->Logs->Log("config", DEBUG, "Filename: %s", newfile.c_str());
+
+ if (!FileExists(newfile.c_str()))
+ return false;
+ file = fopen(newfile.c_str(), "r");
+ }
+ else
+ {
+ if (!FileExists(fname))
+ return false;
+ file = fopen(fname, "r");
+ }
+
+ if (file)
+ {
+ while (!feof(file))
+ {
+ if (fgets(linebuf, sizeof(linebuf), file))
+ linebuf[strlen(linebuf)-1] = 0;
+ else
+ *linebuf = 0;
+
+ F.push_back(*linebuf ? linebuf : " ");
+ }
+
+ fclose(file);
+ }
+ else
+ return false;
+
+ return true;
+}
+
+bool ServerConfig::FileExists(const char* file)
+{
+ struct stat sb;
+ if (stat(file, &sb) == -1)
+ return false;
+
+ if ((sb.st_mode & S_IFDIR) > 0)
+ return false;
+
+ FILE *input;
+ if ((input = fopen (file, "r")) == NULL)
+ return false;
+ else
+ {
+ fclose(input);
+ return true;
+ }
+}
+
+char* ServerConfig::CleanFilename(char* name)
+{
+ char* p = name + strlen(name);
+ while ((p != name) && (*p != '/') && (*p != '\\')) p--;
+ return (p != name ? ++p : p);
+}
+
+
+std::string ServerConfig::GetFullProgDir()
+{
+ char buffer[PATH_MAX];
+#ifdef WINDOWS
+ /* Windows has specific api calls to get the exe path that never fail.
+ * For once, windows has something of use, compared to the POSIX code
+ * for this, this is positively neato.
+ */
+ if (GetModuleFileName(NULL, buffer, MAX_PATH))
+ {
+ std::string fullpath = buffer;
+ std::string::size_type n = fullpath.rfind("\\inspircd.exe");
+ return std::string(fullpath, 0, n);
+ }
+#else
+ // Get the current working directory
+ if (getcwd(buffer, PATH_MAX))
+ {
+ std::string remainder = this->argv[0];
+
+ /* Does argv[0] start with /? its a full path, use it */
+ if (remainder[0] == '/')
+ {
+ std::string::size_type n = remainder.rfind("/inspircd");
+ return std::string(remainder, 0, n);
+ }
+
+ std::string fullpath = std::string(buffer) + "/" + remainder;
+ std::string::size_type n = fullpath.rfind("/inspircd");
+ return std::string(fullpath, 0, n);
+ }
+#endif
+ return "/";
+}
+
+InspIRCd* ServerConfig::GetInstance()
+{
+ return ServerInstance;
+}
+
+std::string ServerConfig::GetSID()
+{
+ return sid;
+}
+
+ValueItem::ValueItem(int value)
+{
+ std::stringstream n;
+ n << value;
+ v = n.str();
+}
+
+ValueItem::ValueItem(bool value)
+{
+ std::stringstream n;
+ n << value;
+ v = n.str();
+}
+
+void ValueItem::Set(const std::string& value)
+{
+ v = value;
+}
+
+void ValueItem::Set(int value)
+{
+ std::stringstream n;
+ n << value;
+ v = n.str();
+}
+
+int ValueItem::GetInteger()
+{
+ if (v.empty())
+ return 0;
+ return atoi(v.c_str());
+}
+
+const char* ValueItem::GetString() const
+{
+ return v.c_str();
+}
+
+bool ValueItem::GetBool()
+{
+ return (GetInteger() || v == "yes" || v == "true");
+}
+
+
+void ConfigReaderThread::Run()
+{
+ Config = new ServerConfig(ServerInstance);
+ Config->Read();
+ done = true;
+}
+
+void ConfigReaderThread::Finish()
+{
+ ServerConfig* old = ServerInstance->Config;
+ ServerInstance->Logs->Log("CONFIG",DEBUG,"Switching to new configuration...");
+ ServerInstance->Logs->CloseLogs();
+ ServerInstance->Config = this->Config;
+ ServerInstance->Logs->OpenFileLogs();
+ Config->Apply(old, TheUserUID);
+
+ if (Config->valid)
+ {
+ /*
+ * Apply the changed configuration from the rehash.
+ *
+ * XXX: The order of these is IMPORTANT, do not reorder them without testing
+ * thoroughly!!!
+ */
+ ServerInstance->XLines->CheckELines();
+ ServerInstance->XLines->CheckELines();
+ ServerInstance->XLines->ApplyLines();
+ ServerInstance->Res->Rehash();
+ ServerInstance->ResetMaxBans();
+ Config->ApplyDisabledCommands(Config->DisabledCommands);
+ User* user = TheUserUID.empty() ? ServerInstance->FindNick(TheUserUID) : NULL;
+ FOREACH_MOD_I(ServerInstance, I_OnRehash, OnRehash(user));
+ ServerInstance->BuildISupport();
+
+ delete old;
+ }
+ else
+ {
+ // whoops, abort!
+ ServerInstance->Logs->CloseLogs();
+ ServerInstance->Config = old;
+ ServerInstance->Logs->OpenFileLogs();
+ delete this->Config;
+ }
+}
+
+template<>
+void ValueContainer<char[MAXBUF]>::Set(ServerConfig* conf, ValueItem const& item)
+{
+ strlcpy(conf->*vptr, item.GetString(), MAXBUF);
+}