From 6302f980fe6dad83aab7d2dc1046dadea5ffd939 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 14 Dec 2015 16:52:24 -0500 Subject: New source tree structure for modules. From commands/cs_access => chanserv/access etc. --- modules/operserv/CMakeLists.txt | 1 + modules/operserv/akill.cpp | 437 +++++++++++++++ modules/operserv/chankill.cpp | 113 ++++ modules/operserv/config.cpp | 147 +++++ modules/operserv/defcon.cpp | 619 +++++++++++++++++++++ modules/operserv/dns.cpp | 1012 ++++++++++++++++++++++++++++++++++ modules/operserv/forbid.cpp | 577 +++++++++++++++++++ modules/operserv/ignore.cpp | 393 +++++++++++++ modules/operserv/info.cpp | 260 +++++++++ modules/operserv/jupe.cpp | 72 +++ modules/operserv/kick.cpp | 81 +++ modules/operserv/kill.cpp | 63 +++ modules/operserv/list.cpp | 238 ++++++++ modules/operserv/login.cpp | 148 +++++ modules/operserv/logsearch.cpp | 151 +++++ modules/operserv/main/CMakeLists.txt | 1 + modules/operserv/main/operserv.cpp | 337 +++++++++++ modules/operserv/mode.cpp | 187 +++++++ modules/operserv/modinfo.cpp | 214 +++++++ modules/operserv/module.cpp | 179 ++++++ modules/operserv/news.cpp | 459 +++++++++++++++ modules/operserv/noop.cpp | 98 ++++ modules/operserv/oline.cpp | 74 +++ modules/operserv/oper.cpp | 241 ++++++++ modules/operserv/reload.cpp | 62 +++ modules/operserv/session.cpp | 768 ++++++++++++++++++++++++++ modules/operserv/set.cpp | 243 ++++++++ modules/operserv/shutdown.cpp | 101 ++++ modules/operserv/stats.cpp | 271 +++++++++ modules/operserv/svs.cpp | 171 ++++++ modules/operserv/sxline.cpp | 673 ++++++++++++++++++++++ modules/operserv/update.cpp | 49 ++ 32 files changed, 8440 insertions(+) create mode 100644 modules/operserv/CMakeLists.txt create mode 100644 modules/operserv/akill.cpp create mode 100644 modules/operserv/chankill.cpp create mode 100644 modules/operserv/config.cpp create mode 100644 modules/operserv/defcon.cpp create mode 100644 modules/operserv/dns.cpp create mode 100644 modules/operserv/forbid.cpp create mode 100644 modules/operserv/ignore.cpp create mode 100644 modules/operserv/info.cpp create mode 100644 modules/operserv/jupe.cpp create mode 100644 modules/operserv/kick.cpp create mode 100644 modules/operserv/kill.cpp create mode 100644 modules/operserv/list.cpp create mode 100644 modules/operserv/login.cpp create mode 100644 modules/operserv/logsearch.cpp create mode 100644 modules/operserv/main/CMakeLists.txt create mode 100644 modules/operserv/main/operserv.cpp create mode 100644 modules/operserv/mode.cpp create mode 100644 modules/operserv/modinfo.cpp create mode 100644 modules/operserv/module.cpp create mode 100644 modules/operserv/news.cpp create mode 100644 modules/operserv/noop.cpp create mode 100644 modules/operserv/oline.cpp create mode 100644 modules/operserv/oper.cpp create mode 100644 modules/operserv/reload.cpp create mode 100644 modules/operserv/session.cpp create mode 100644 modules/operserv/set.cpp create mode 100644 modules/operserv/shutdown.cpp create mode 100644 modules/operserv/stats.cpp create mode 100644 modules/operserv/svs.cpp create mode 100644 modules/operserv/sxline.cpp create mode 100644 modules/operserv/update.cpp (limited to 'modules/operserv') diff --git a/modules/operserv/CMakeLists.txt b/modules/operserv/CMakeLists.txt new file mode 100644 index 000000000..cd225a94d --- /dev/null +++ b/modules/operserv/CMakeLists.txt @@ -0,0 +1 @@ +build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/operserv/akill.cpp b/modules/operserv/akill.cpp new file mode 100644 index 000000000..7f366e0c9 --- /dev/null +++ b/modules/operserv/akill.cpp @@ -0,0 +1,437 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +static ServiceReference akills("XLineManager", "xlinemanager/sgline"); + +class CommandOSAKill : public Command +{ + private: + void DoAdd(CommandSource &source, const std::vector ¶ms) + { + Anope::string expiry, mask; + + if (params.size() < 2) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + spacesepstream sep(params[1]); + sep.GetToken(mask); + + if (mask[0] == '+') + { + expiry = mask; + sep.GetToken(mask); + } + + time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->GetModule("operserv")->Get("autokillexpiry", "30d"); + /* If the expiry given does not contain a final letter, it's in days, + * said the doc. Ah well. + */ + if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) + expires *= 86400; + /* Do not allow less than a minute expiry time */ + if (expires && expires < 60) + { + source.Reply(_("Invalid expiry time \002{0}\002."), expiry); + return; + } + else if (expires > 0) + expires += Anope::CurTime; + + if (sep.StreamEnd()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + Anope::string reason; + if (mask.find('#') != Anope::string::npos) + { + Anope::string remaining = sep.GetRemaining(); + + size_t co = remaining[0] == ':' ? 0 : remaining.rfind(" :"); + if (co == Anope::string::npos) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + if (co != 0) + ++co; + + reason = remaining.substr(co + 1); + mask += " " + remaining.substr(0, co); + mask.trim(); + } + else + reason = sep.GetRemaining(); + + if (mask[0] == '/' && mask[mask.length() - 1] == '/') + { + if (!Config->regex_flags) + { + source.Reply(_("Regex is disabled.")); + return; + } + + Anope::string stripped_mask = mask.substr(1, mask.length() - 2); + try + { + std::regex(stripped_mask.str(), Config->regex_flags); + } + catch (const std::regex_error &ex) + { + source.Reply("%s", ex.what()); + return; + } + } + + User *targ = User::Find(mask, true); + if (targ) + mask = "*@" + targ->host; + + if (Config->GetModule("operserv")->Get("addakiller", "yes") && !source.GetNick().empty()) + reason = "[" + source.GetNick() + "] " + reason; + + if (!akills->CanAdd(source, mask, expires, reason)) + return; + + if (mask.find_first_not_of("/~@.*?") == Anope::string::npos) + { + source.Reply(_("\002{0}\002 coverage is too wide; Please use a more specific mask."), mask); + return; + } + + if (mask.find('@') == Anope::string::npos) + { + source.Reply(_("Mask must be in the form \037user\037@\037host\037.")); + return; + } + + XLine *x = new XLine(mask, source.GetNick(), expires, reason); + if (Config->GetModule("operserv")->Get("akillids")) + x->SetID(XLineManager::GenerateUID()); + + unsigned int affected = 0; + for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + if (akills->Check(it->second, x)) + ++affected; + float percent = static_cast(affected) / static_cast(UserListByNick.size()) * 100.0; + + if (percent > 95) + { + source.Reply(_("\002{0}\002 coverage is too wide; Please use a more specific mask."), mask); + Log(LOG_ADMIN, source, this) << "tried to akill " << percent << "% of the network (" << affected << " users)"; + delete x; + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnAddXLine(&Event::AddXLine::OnAddXLine, source, x, akills); + if (MOD_RESULT == EVENT_STOP) + { + delete x; + return; + } + + akills->AddXLine(x); + if (Config->GetModule("operserv")->Get("akillonadd")) + akills->Send(NULL, x); + + source.Reply(_("\002{0}\002 added to the akill list."), mask); + + Log(LOG_ADMIN, source, this) << "on " << mask << " (" << x->GetReason() << "), expires in " << (expires ? Anope::Duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]"; + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + } + + void DoDel(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + if (mask.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (akills->GetXLines().empty()) + { + source.Reply(_("The akill list is empty.")); + return; + } + + if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + unsigned int deleted = 0; + + NumberList(mask, true, + [&](unsigned int number) + { + XLine *x = akills->GetEntry(number - 1); + + if (!x) + return; + + Log(LOG_ADMIN, source, this) << "to remove " << x->GetMask() << " from the list"; + + ++deleted; + x->Delete(); + }, + [&]() + { + if (!deleted) + source.Reply(_("No matching entries on the akill list.")); + else if (deleted == 1) + source.Reply(_("Deleted \0021\002 entry from the akill list.")); + else + source.Reply(_("Deleted \002{0}\002 entries from the akill list."), deleted); + }); + } + else + { + XLine *x = akills->HasEntry(mask); + + if (!x) + { + source.Reply(_("\002{0}\002 was not found on the akill list."), mask); + return; + } + + do + { + Event::OnDelXLine(&Event::DelXLine::OnDelXLine, source, x, akills); + + Log(LOG_ADMIN, source, this) << "to remove " << x->GetMask() << " from the list"; + source.Reply(_("\002{0}\002 deleted from the akill list."), x->GetMask()); + x->Delete(); + } + while ((x = akills->HasEntry(mask))); + + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + } + + void ProcessList(CommandSource &source, const std::vector ¶ms, ListFormatter &list) + { + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + NumberList(mask, false, + [&](unsigned int number) + { + XLine *x = akills->GetEntry(number - 1); + + if (!x) + return; + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(number); + entry["Mask"] = x->GetMask(); + entry["Creator"] = x->GetBy(); + entry["Created"] = Anope::strftime(x->GetCreated(), NULL, true); + entry["Expires"] = Anope::Expires(x->GetExpires(), source.nc); + entry["Reason"] = x->GetReason(); + list.AddEntry(entry); + }, + [&]{}); + } + else + { + unsigned int i = 0; + for (XLine *x : akills->GetXLines()) + { + if (mask.empty() || mask.equals_ci(x->GetMask()) || mask == x->id || Anope::Match(x->GetMask(), mask, false, true)) + { + ListFormatter::ListEntry entry; + entry["Number"] = stringify(++i); + entry["Mask"] = x->GetMask(); + entry["Creator"] = x->GetBy(); + entry["Created"] = Anope::strftime(x->GetCreated(), NULL, true); + entry["Expires"] = Anope::Expires(x->GetExpires(), source.nc); + entry["Reason"] = x->GetReason(); + list.AddEntry(entry); + } + } + } + + if (list.IsEmpty()) + { + source.Reply(_("There are no matching entries on the akill list.")); + return; + } + + source.Reply(_("Current akill list:")); + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of akill list.")); + } + + void DoList(CommandSource &source, const std::vector ¶ms) + { + if (akills->GetXLines().empty()) + { + source.Reply(_("The akill list is empty.")); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Reason")); + + this->ProcessList(source, params, list); + } + + void DoView(CommandSource &source, const std::vector ¶ms) + { + if (akills->GetXLines().empty()) + { + source.Reply(_("The akill list is empty.")); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Creator")).AddColumn(_("Created")).AddColumn(_("Expires")).AddColumn(_("Reason")); + + this->ProcessList(source, params, list); + } + + void DoClear(CommandSource &source) + { + for (XLine *x : akills->GetXLines()) + { + Event::OnDelXLine(&Event::DelXLine::OnDelXLine, source, x, akills); + x->Delete(); + } + + Log(LOG_ADMIN, source, this) << "to CLEAR the list"; + source.Reply(_("The akill list has been cleared.")); + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + } + public: + CommandOSAKill(Module *creator) : Command(creator, "operserv/akill", 1, 2) + { + this->SetDesc(_("Manipulate the AKILL list")); + this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037 \037reason\037")); + this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037 | \037id\037}")); + this->SetSyntax(_("LIST [\037mask\037 | \037list\037 | \037id\037]")); + this->SetSyntax(_("VIEW [\037mask\037 | \037list\037 | \037id\037]")); + this->SetSyntax("CLEAR"); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &cmd = params[0]; + + if (!akills) + return; + + if (cmd.equals_ci("ADD")) + return this->DoAdd(source, params); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, params); + else if (cmd.equals_ci("LIST")) + return this->DoList(source, params); + else if (cmd.equals_ci("VIEW")) + return this->DoView(source, params); + else if (cmd.equals_ci("CLEAR")) + return this->DoClear(source); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.equals_ci("ADD")) + { + source.Reply(_("The \002{0} ADD\002 command adds \037mask\037 to the auto kill list with the given \037reason\037." + "\037mask\037 can be in the format of nickname!username@hostname#realname." + " If a real name is specified, the reason must be prefixed with a :." + " \037expiry\037 is optional, and if specified must be an integer followed by one of \037d\037 (days), \037h\037 (hours), or \037m\037 (minutes)." + " If a unit specifier is not included, the default is days, so \037+30\037 means 30 days." + " To add an auto kill which does not expire, use \037+0\037. " + " The default auto kill expiry time is \002{1}\002"), + source.command, Anope::Duration(Config->GetModule("operserv")->Get("autokillexpiry", "30d"), source.GetAccount())); + + const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); + if (!regexengine.empty()) + { + source.Reply(" "); + source.Reply(_("Regex matches are also supported using the %s engine. Enclose your mask in // if this is desired."), regexengine); + } + } + else if (subcommand.equals_ci("DEL")) + source.Reply(_("The \002{0} DEL\002 command removes the given \037mask\037 from the auto kill list, if present." + " If a list of entry numbers is given, those entries are deleted."), + source.command); + else if (subcommand.equals_ci("LIST") || subcommand.equals_ci("VIEW")) + source.Reply(_("The \002{0} LIST\002 and \002{0} VIEW\002 commands display the auto kill list." + " If a wildcard \037mask\037 is given, only those entries matching the mask are displayed." + " If a list of entry numbers is given, only those entries are shown." + " \002VIEW\002 is similar to \002LIST\002 but also shows who created the auto kill, when it was created, and when it expires.\n" + "\n" + "Example:\n" + "\n" + " {0} LIST 2-5,7-9\n" + " Lists auto kill entries numbered 2 through 5 and 7 through 9."), + source.command); + else if (subcommand.equals_ci("CLEAR")) + source.Reply(_("The \002{0} CLEAR\002 command removes all auto kills from the auto kill list."), + source.command); + else + { + CommandInfo *help = source.service->FindCommand("generic/help"); + source.Reply(_("Allows manipulating the AKILL list." + " If a user matching an AKILL mask connects, services will issue a kill the user and prevent them from reconnecting.\n" + "\n" + "The \002ADD\002 command adds \037mask\037 to the auto kill list.\n" + "\002{msg}{service} {help} {command} ADD\002 for more information.\n" + "\n" + "The \002DEL\002 command removes \037mask\037 from the auto kill list.\n" + "\002{msg}{service} {help} {command} DEL\002 for more information.\n" + "\n" + "The \002LIST\002 and \002VIEW\002 commands both show the auto kill list, but \002VIEW\002 also shows who created the auto kill entry, when it was created, and when it expires.\n" + "\002{msg}{service} {help} {command} [LIST | VIEW]\002 for more information.\n" + "\n" + "The \002CLEAR\002 command clears th auto kill list." + "\002{msg}{service} {help} {command} CLEAR\002 for more information.\n"), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "help"_kw = help->cname, "command"_kw = source.command); + } + return true; + } +}; + +class OSAKill : public Module +{ + CommandOSAKill commandosakill; + + public: + OSAKill(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosakill(this) + { + + } +}; + +MODULE_INIT(OSAKill) diff --git a/modules/operserv/chankill.cpp b/modules/operserv/chankill.cpp new file mode 100644 index 000000000..8d3773e9d --- /dev/null +++ b/modules/operserv/chankill.cpp @@ -0,0 +1,113 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +static ServiceReference akills("XLineManager", "xlinemanager/sgline"); + +class CommandOSChanKill : public Command +{ + public: + CommandOSChanKill(Module *creator) : Command(creator, "operserv/chankill", 2, 3) + { + this->SetDesc(_("AKILL all users on a specific channel")); + this->SetSyntax(_("[+\037expiry\037] \037channel\037 \037reason\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (!akills) + return; + + Anope::string expiry, channel; + unsigned last_param = 1; + + channel = params[0]; + if (!channel.empty() && channel[0] == '+') + { + expiry = channel; + channel = params[1]; + last_param = 2; + } + + time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->GetModule("operserv")->Get("autokillexpiry", "30d"); + if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) + expires *= 86400; + + if (expires) + { + if (expires < 60) + { + source.Reply(_("Invalid expiry time \002{0}\002."), expiry); + return; + } + + expires += Anope::CurTime; + } + + if (params.size() <= last_param) + { + this->OnSyntaxError(source, ""); + return; + } + + Anope::string reason = params[last_param]; + if (params.size() > last_param + 1) + reason += params[last_param + 1]; + + Anope::string realreason; + if (Config->GetModule("operserv")->Get("addakiller") && !source.GetNick().empty()) + realreason = "[" + source.GetNick() + "] " + reason; + else + realreason = reason; + + Channel *c = Channel::Find(channel); + if (!c) + { + source.Reply(_("Channel \002{0}\002 doesn't exist."), channel); + return; + } + + for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ++it) + { + ChanUserContainer *uc = it->second; + + if (uc->user->server == Me || uc->user->HasMode("OPER")) + continue; + + XLine *x = new XLine("*@" + uc->user->host, source.GetNick(), expires, realreason, XLineManager::GenerateUID()); + akills->AddXLine(x); + akills->OnMatch(uc->user, x); + } + + Log(LOG_ADMIN, source, this) << "on " << c->name << " (" << realreason << ")"; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Adds an auto kill for every user on \037channel\037, except for IRC operators.")); + return true; + } +}; + +class OSChanKill : public Module +{ + CommandOSChanKill commandoschankill; + + public: + OSChanKill(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandoschankill(this) + { + + } +}; + +MODULE_INIT(OSChanKill) diff --git a/modules/operserv/config.cpp b/modules/operserv/config.cpp new file mode 100644 index 000000000..257f8fee4 --- /dev/null +++ b/modules/operserv/config.cpp @@ -0,0 +1,147 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSConfig : public Command +{ + public: + CommandOSConfig(Module *creator) : Command(creator, "operserv/config", 1, 4) + { + this->SetDesc(_("View and change configuration file settings")); + this->SetSyntax(_("{\037MODIFY\037|\037VIEW\037} [\037block name\037 \037item name\037 \037item value\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &what = params[0]; + + if (what.equals_ci("MODIFY") && params.size() > 3) + { + if (!source.HasPriv("operserv/config")) + { + source.Reply(_("Access denied. You do not have the operator privilege \002{0}\002."), "operserv/config"); + return; + } + + Configuration::Block *block = Config->GetBlock(params[1]); + if (!block) + block = Config->GetModule(params[1]); + + if (!block) + { + source.Reply(_("There is no such configuration block \002{0}\002."), params[1]); + return; + } + + block->Set(params[2], params[3]); + + Log(LOG_ADMIN, source, this) << "to change the configuration value of " << params[1] << ":" << params[2] << " to " << params[3]; + source.Reply(_("Value of \002{0}:{1}\002 changed to \002{2}\002."), params[1], params[2], params[3]); + } + else if (what.equals_ci("VIEW")) + { + /* Blocks we should show */ + const Anope::string show_blocks[] = { "serverinfo", "networkinfo", "options", "" }; + + Log(LOG_ADMIN, source, this) << "VIEW"; + + for (unsigned i = 0; !show_blocks[i].empty(); ++i) + { + Configuration::Block *block = Config->GetBlock(show_blocks[i]); + const Configuration::Block::item_map *items = block->GetItems(); + + if (!items) + continue; + + ListFormatter lflist(source.GetAccount()); + lflist.AddColumn(_("Name")).AddColumn(_("Value")); + + for (Configuration::Block::item_map::const_iterator it = items->begin(), it_end = items->end(); it != it_end; ++it) + { + ListFormatter::ListEntry entry; + entry["Name"] = it->first; + entry["Value"] = it->second; + lflist.AddEntry(entry); + } + + std::vector replies; + lflist.Process(replies); + + source.Reply(_("%s settings:"), block->GetName()); + + for (unsigned j = 0; j < replies.size(); ++j) + source.Reply(replies[j]); + + source.Reply(" "); + } + + ListFormatter lflist(source.GetAccount()); + lflist.AddColumn(_("Module Name")).AddColumn(_("Name")).AddColumn(_("Value")); + + for (int i = 0; i < Config->CountBlock("module"); ++i) + { + Configuration::Block *block = Config->GetBlock("module", i); + const Configuration::Block::item_map *items = block->GetItems(); + + if (!items || items->size() <= 1) + continue; + + ListFormatter::ListEntry entry; + entry["Module Name"] = block->Get("name"); + + for (Configuration::Block::item_map::const_iterator it = items->begin(), it_end = items->end(); it != it_end; ++it) + { + entry["Name"] = it->first; + entry["Value"] = it->second; + lflist.AddEntry(entry); + } + } + + std::vector replies; + lflist.Process(replies); + + source.Reply(_("Module settings:")); + + for (unsigned j = 0; j < replies.size(); ++j) + source.Reply(replies[j]); + + source.Reply(_("End of configuration.")); + } + else + this->OnSyntaxError(source, what); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows you to change and view configuration settings. Settings changed by this command are temporary and will not be reflected back into the configuration file, and will be lost if Anope is shut down, restarted, or the configuration is reloaded.\n" + "Use of the \002MODIFY\002 command requires the \002{0}\002 operator privilege.\n" + "\n" + "Example:\n" + " {0} MODIFY nickserv forcemail no" + " Changes the \"forceemail\" setting of the module configuration for \"nickserv\" to \"no\"")); + return true; + } +}; + +class OSConfig : public Module +{ + CommandOSConfig commandosconfig; + + public: + OSConfig(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosconfig(this) + { + + } +}; + +MODULE_INIT(OSConfig) diff --git a/modules/operserv/defcon.cpp b/modules/operserv/defcon.cpp new file mode 100644 index 000000000..804b66c27 --- /dev/null +++ b/modules/operserv/defcon.cpp @@ -0,0 +1,619 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/os_session.h" +#include "modules/os_defcon.h" +#include "modules/global.h" + +enum DefconLevel +{ + DEFCON_NO_NEW_CHANNELS, + DEFCON_NO_NEW_NICKS, + DEFCON_NO_MLOCK_CHANGE, + DEFCON_FORCE_CHAN_MODES, + DEFCON_REDUCE_SESSION, + DEFCON_NO_NEW_CLIENTS, + DEFCON_OPER_ONLY, + DEFCON_SILENT_OPER_ONLY, + DEFCON_AKILL_NEW_CLIENTS, + DEFCON_NO_NEW_MEMOS +}; + +bool DefConModesSet = false; + +struct DefconConfig +{ + std::vector > DefCon; + std::set DefConModesOn, DefConModesOff; + std::map DefConModesOnParams; + + int defaultlevel, sessionlimit; + Anope::string chanmodes, message, offmessage, akillreason; + std::vector defcons; + time_t akillexpire, timeout; + bool globalondefcon; + + unsigned max_session_kill; + time_t session_autokill_expiry; + Anope::string sle_reason, sle_detailsloc; + + DefconConfig() + { + this->DefCon.resize(6); + this->defcons.resize(5); + } + + bool Check(DefconLevel level) + { + return this->Check(this->defaultlevel, level); + } + + bool Check(int dlevel, DefconLevel level) + { + return this->DefCon[dlevel].test(level); + } + + void Add(int dlevel, DefconLevel level) + { + this->DefCon[dlevel][level] = true; + } + + void Del(int dlevel, DefconLevel level) + { + this->DefCon[dlevel][level] = false; + } + + bool SetDefConParam(const Anope::string &name, const Anope::string &buf) + { + return DefConModesOnParams.insert(std::make_pair(name, buf)).second; + } + + void UnsetDefConParam(const Anope::string &name) + { + DefConModesOnParams.erase(name); + } + + bool GetDefConParam(const Anope::string &name, Anope::string &buf) + { + std::map::iterator it = DefConModesOnParams.find(name); + + buf.clear(); + + if (it != DefConModesOnParams.end()) + { + buf = it->second; + return true; + } + + return false; + } +}; + +static DefconConfig DConfig; + +static void runDefCon(); +static Anope::string defconReverseModes(const Anope::string &modes); + +static Timer *timeout; + +class DefConTimeout : public Timer +{ + EventHandlers &eventdefcon; + int level; + + public: + DefConTimeout(EventHandlers &ev, Module *mod, int newlevel) : Timer(mod, DConfig.timeout), eventdefcon(ev), level(newlevel) + { + timeout = this; + } + + ~DefConTimeout() + { + timeout = NULL; + } + + void Tick(time_t) override + { + if (DConfig.defaultlevel != level) + { + DConfig.defaultlevel = level; + this->eventdefcon(&Event::DefconLevel::OnDefconLevel, level); + Log(Config->GetClient("OperServ"), "operserv/defcon") << "Defcon level timeout, returning to level " << level; + + if (DConfig.globalondefcon && Global::service) + { + if (!DConfig.offmessage.empty()) + Global::service->SendGlobal(NULL, "", DConfig.offmessage); + else + Global::service->SendGlobal(NULL, "", Anope::printf(Language::Translate(_("The Defcon level is now at: \002%d\002")), DConfig.defaultlevel)); + + if (!DConfig.message.empty()) + Global::service->SendGlobal(NULL, "", DConfig.message); + } + + runDefCon(); + } + } +}; + +class CommandOSDefcon : public Command +{ + void SendLevels(CommandSource &source) + { + if (DConfig.Check(DEFCON_NO_NEW_CHANNELS)) + source.Reply(_("* No new channel registrations")); + if (DConfig.Check(DEFCON_NO_NEW_NICKS)) + source.Reply(_("* No new nick registrations")); + if (DConfig.Check(DEFCON_NO_MLOCK_CHANGE)) + source.Reply(_("* No mode lock changes")); + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && !DConfig.chanmodes.empty()) + source.Reply(_("* Force channel modes (\002{0}\002) to be set on all channels"), DConfig.chanmodes); + if (DConfig.Check(DEFCON_REDUCE_SESSION)) + source.Reply(_("* Use the reduced session limit of \002{0}\002"), DConfig.sessionlimit); + if (DConfig.Check(DEFCON_NO_NEW_CLIENTS)) + source.Reply(_("* Kill any new clients connecting")); + if (DConfig.Check(DEFCON_OPER_ONLY)) + source.Reply(_("* Ignore non-opers with a message")); + if (DConfig.Check(DEFCON_SILENT_OPER_ONLY)) + source.Reply(_("* Silently ignore non-opers")); + if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) + source.Reply(_("* AKILL any new clients connecting")); + if (DConfig.Check(DEFCON_NO_NEW_MEMOS)) + source.Reply(_("* No new memos sent")); + } + + EventHandlers &ondefconlevel; + + public: + CommandOSDefcon(Module *creator, EventHandlers &event) : Command(creator, "operserv/defcon", 1, 1), ondefconlevel(event) + { + this->SetDesc(_("Manipulate the DefCon system")); + this->SetSyntax(_("[\0021\002|\0022\002|\0023\002|\0024\002|\0025\002]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &lvl = params[0]; + + if (lvl.empty()) + { + source.Reply(_("Services are now at defcon \002{0}\002."), DConfig.defaultlevel); + this->SendLevels(source); + return; + } + + int newLevel = 0; + try + { + newLevel = convertTo(lvl); + } + catch (const ConvertException &) { } + + if (newLevel < 1 || newLevel > 5) + { + this->OnSyntaxError(source, ""); + return; + } + + DConfig.defaultlevel = newLevel; + + this->ondefconlevel(&Event::DefconLevel::OnDefconLevel, newLevel); + + delete timeout; + + if (DConfig.timeout) + timeout = new DefConTimeout(this->ondefconlevel, this->module, 5); + + source.Reply(_("Services are now at defcon \002{0}\002."), DConfig.defaultlevel); + this->SendLevels(source); + Log(LOG_ADMIN, source, this) << "to change defcon level to " << newLevel; + + /* Global notice the user what is happening. Also any Message that + the Admin would like to add. Set in config file. */ + if (DConfig.globalondefcon && Global::service) + { + if (DConfig.defaultlevel == 5 && !DConfig.offmessage.empty()) + Global::service->SendGlobal(NULL, "", DConfig.offmessage); + else if (DConfig.defaultlevel != 5) + { + Global::service->SendGlobal(NULL, "", Anope::printf(_("The defcon level is now at \002%d\002"), DConfig.defaultlevel)); + if (!DConfig.message.empty()) + Global::service->SendGlobal(NULL, "", DConfig.message); + } + } + + /* Run any defcon functions, e.g. FORCE CHAN MODE */ + runDefCon(); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("The defcon system can be used to implement a pre-defined set of restrictions to services useful during an attempted attack on the network.")); + // XXX actually explain what this does + return true; + } +}; + +class OSDefcon : public Module + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + ServiceReference session_service; + ServiceReference akills; + CommandOSDefcon commandosdefcon; + EventHandlers ondefconlevel; + + void ParseModeString() + { + int add = -1; /* 1 if adding, 0 if deleting, -1 if neither */ + unsigned char mode; + ChannelMode *cm; + ChannelModeParam *cmp; + Anope::string modes, param; + + spacesepstream ss(DConfig.chanmodes); + + DConfig.DefConModesOn.clear(); + DConfig.DefConModesOff.clear(); + ss.GetToken(modes); + + /* Loop while there are modes to set */ + for (unsigned i = 0, end = modes.length(); i < end; ++i) + { + mode = modes[i]; + + switch (mode) + { + case '+': + add = 1; + continue; + case '-': + add = 0; + continue; + default: + if (add < 0) + continue; + } + + if ((cm = ModeManager::FindChannelModeByChar(mode))) + { + if (cm->type == MODE_STATUS || cm->type == MODE_LIST) + { + Log(this) << "DefConChanModes mode character '" << mode << "' cannot be locked"; + continue; + } + else if (add) + { + DConfig.DefConModesOn.insert(cm->name); + DConfig.DefConModesOff.erase(cm->name); + + if (cm->type == MODE_PARAM) + { + cmp = anope_dynamic_static_cast(cm); + + if (!ss.GetToken(param)) + { + Log(this) << "DefConChanModes mode character '" << mode << "' has no parameter while one is expected"; + continue; + } + + if (!cmp->IsValid(param)) + continue; + + DConfig.SetDefConParam(cmp->name, param); + } + } + else if (DConfig.DefConModesOn.count(cm->name)) + { + DConfig.DefConModesOn.erase(cm->name); + + if (cm->type == MODE_PARAM) + DConfig.UnsetDefConParam(cm->name); + } + } + } + + /* We can't mlock +L if +l is not mlocked as well. */ + if ((cm = ModeManager::FindChannelModeByName("REDIRECT")) && DConfig.DefConModesOn.count(cm->name) && !DConfig.DefConModesOn.count("LIMIT")) + { + DConfig.DefConModesOn.erase("REDIRECT"); + + Log(this) << "DefConChanModes must lock mode +l as well to lock mode +L"; + } + } + + public: + OSDefcon(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , session_service("SessionService", "session") + , akills("XLineManager", "xlinemanager/sgline") + , commandosdefcon(this, ondefconlevel) + , ondefconlevel(this) + { + + } + + ~OSDefcon() + { + delete timeout; + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + DefconConfig dconfig; + + dconfig.defaultlevel = block->Get("defaultlevel"); + dconfig.defcons[4] = block->Get("level4"); + dconfig.defcons[3] = block->Get("level3"); + dconfig.defcons[2] = block->Get("level2"); + dconfig.defcons[1] = block->Get("level1"); + dconfig.sessionlimit = block->Get("sessionlimit"); + dconfig.akillreason = block->Get("akillreason"); + dconfig.akillexpire = block->Get("akillexpire"); + dconfig.chanmodes = block->Get("chanmodes"); + dconfig.timeout = block->Get("timeout"); + dconfig.globalondefcon = block->Get("globalondefcon"); + dconfig.message = block->Get("message"); + dconfig.offmessage = block->Get("offmessage"); + + Module *session = ModuleManager::FindModule("os_session"); + block = conf->GetModule(session); + + dconfig.max_session_kill = block->Get("maxsessionkill"); + dconfig.session_autokill_expiry = block->Get("sessionautokillexpiry"); + dconfig.sle_reason = block->Get("sessionlimitexceeded"); + dconfig.sle_detailsloc = block->Get("sessionlimitdetailsloc"); + + if (dconfig.defaultlevel < 1 || dconfig.defaultlevel > 5) + throw ConfigException("The value for must be between 1 and 5"); + else if (dconfig.akillexpire <= 0) + throw ConfigException("The value for must be greater than zero!"); + + for (unsigned level = 1; level < 5; ++level) + { + spacesepstream operations(dconfig.defcons[level]); + Anope::string operation; + while (operations.GetToken(operation)) + { + if (operation.equals_ci("nonewchannels")) + dconfig.Add(level, DEFCON_NO_NEW_CHANNELS); + else if (operation.equals_ci("nonewnicks")) + dconfig.Add(level, DEFCON_NO_NEW_NICKS); + else if (operation.equals_ci("nomlockchanges")) + dconfig.Add(level, DEFCON_NO_MLOCK_CHANGE); + else if (operation.equals_ci("forcechanmodes")) + dconfig.Add(level, DEFCON_FORCE_CHAN_MODES); + else if (operation.equals_ci("reducedsessions")) + dconfig.Add(level, DEFCON_REDUCE_SESSION); + else if (operation.equals_ci("nonewclients")) + dconfig.Add(level, DEFCON_NO_NEW_CLIENTS); + else if (operation.equals_ci("operonly")) + dconfig.Add(level, DEFCON_OPER_ONLY); + else if (operation.equals_ci("silentoperonly")) + dconfig.Add(level, DEFCON_SILENT_OPER_ONLY); + else if (operation.equals_ci("akillnewclients")) + dconfig.Add(level, DEFCON_AKILL_NEW_CLIENTS); + else if (operation.equals_ci("nonewmemos")) + dconfig.Add(level, DEFCON_NO_NEW_MEMOS); + } + + if (dconfig.Check(level, DEFCON_REDUCE_SESSION) && dconfig.sessionlimit <= 0) + throw ConfigException("The value for must be greater than zero!"); + else if (dconfig.Check(level, DEFCON_AKILL_NEW_CLIENTS) && dconfig.akillreason.empty()) + throw ConfigException("The value for must not be empty!"); + else if (dconfig.Check(level, DEFCON_FORCE_CHAN_MODES) && dconfig.chanmodes.empty()) + throw ConfigException("The value for must not be empty!"); + } + + DConfig = dconfig; + this->ParseModeString(); + } + + EventReturn OnChannelModeSet(Channel *c, const MessageSource &source, ChannelMode *mode, const Anope::string ¶m) override + { + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && DConfig.DefConModesOff.count(mode->name) && source.GetUser() && !source.GetBot()) + { + c->RemoveMode(Config->GetClient("OperServ"), mode, param); + + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + EventReturn OnChannelModeUnset(Channel *c, const MessageSource &source, ChannelMode *mode, const Anope::string &) override + { + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && DConfig.DefConModesOn.count(mode->name) && source.GetUser() && !source.GetBot()) + { + Anope::string param; + + if (DConfig.GetDefConParam(mode->name, param)) + c->SetMode(Config->GetClient("OperServ"), mode, param); + else + c->SetMode(Config->GetClient("OperServ"), mode); + + return EVENT_STOP; + + } + + return EVENT_CONTINUE; + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override + { + if (DConfig.Check(DEFCON_OPER_ONLY) && !source.IsOper()) + { + source.Reply(_("Services are in DefCon mode, please try again later.")); + return EVENT_STOP; + } + else if (DConfig.Check(DEFCON_SILENT_OPER_ONLY) && !source.IsOper()) + { + return EVENT_STOP; + } + else if (command->name == "nickserv/register" || command->name == "nickserv/group") + { + if (DConfig.Check(DEFCON_NO_NEW_NICKS)) + { + source.Reply(_("Services are in defcon mode, please try again later.")); + return EVENT_STOP; + } + } + else if (command->name == "chanserv/mode" && params.size() > 1 && params[1].equals_ci("LOCK")) + { + if (DConfig.Check(DEFCON_NO_MLOCK_CHANGE)) + { + source.Reply(_("Services are in defcon mode, please try again later.")); + return EVENT_STOP; + } + } + else if (command->name == "chanserv/register") + { + if (DConfig.Check(DEFCON_NO_NEW_CHANNELS)) + { + source.Reply(_("Services are in defcon mode, please try again later.")); + return EVENT_STOP; + } + } + else if (command->name == "memoserv/send") + { + if (DConfig.Check(DEFCON_NO_NEW_MEMOS)) + { + source.Reply(_("Services are in defcon mode, please try again later.")); + return EVENT_STOP; + } + } + + return EVENT_CONTINUE; + } + + void OnUserConnect(User *u, bool &exempt) override + { + if (exempt || u->Quitting() || !u->server->IsSynced() || u->server->IsULined()) + return; + + ServiceBot *OperServ = Config->GetClient("OperServ"); + if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS) && akills) + { + Log(OperServ, "operserv/defcon") << "DEFCON: adding akill for *@" << u->host; + XLine x("*@" + u->host, OperServ ? OperServ->nick : "defcon", Anope::CurTime + DConfig.akillexpire, DConfig.akillreason, XLineManager::GenerateUID()); + akills->Send(NULL, &x); + } + + if (DConfig.Check(DEFCON_NO_NEW_CLIENTS) || DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) + { + u->Kill(OperServ, DConfig.akillreason); + return; + } + + if (DConfig.sessionlimit <= 0 || !session_service) + return; + + Session *session = session_service->FindSession(u->ip.addr()); + Exception *e = session_service->FindException(u); + + if (DConfig.Check(DEFCON_REDUCE_SESSION) && !e) + { + if (session && session->count > static_cast(DConfig.sessionlimit)) + { + if (!DConfig.sle_reason.empty()) + { + Anope::string message = DConfig.sle_reason.replace_all_cs("%IP%", u->ip.addr()); + u->SendMessage(OperServ, message); + } + if (!DConfig.sle_detailsloc.empty()) + u->SendMessage(OperServ, DConfig.sle_detailsloc); + + ++session->hits; + if (akills && DConfig.max_session_kill && session->hits >= DConfig.max_session_kill) + { + XLine x("*@" + session->addr.mask(), OperServ ? OperServ->nick : "", Anope::CurTime + DConfig.session_autokill_expiry, "Defcon session limit exceeded", XLineManager::GenerateUID()); + akills->Send(NULL, &x); + Log(OperServ, "akill/defcon") << "[DEFCON] Added a temporary AKILL for \002*@" << session->addr.mask() << "\002 due to excessive connections"; + } + else + { + u->Kill(OperServ, "Defcon session limit exceeded"); + } + } + } + } + + void OnChannelModeAdd(ChannelMode *cm) override + { + if (DConfig.chanmodes.find(cm->mchar) != Anope::string::npos) + this->ParseModeString(); + } + + void OnChannelSync(Channel *c) override + { + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES)) + c->SetModes(Config->GetClient("OperServ"), false, "%s", DConfig.chanmodes.c_str()); + } +}; + +static void runDefCon() +{ + ServiceBot *OperServ = Config->GetClient("OperServ"); + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES)) + { + if (!DConfig.chanmodes.empty() && !DefConModesSet) + { + if (DConfig.chanmodes[0] == '+' || DConfig.chanmodes[0] == '-') + { + Log(OperServ, "operserv/defcon") << "DEFCON: setting " << DConfig.chanmodes << " on all channels"; + DefConModesSet = true; + for (channel_map::const_iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it) + it->second->SetModes(OperServ, false, "%s", DConfig.chanmodes.c_str()); + } + } + } + else + { + if (!DConfig.chanmodes.empty() && DefConModesSet) + { + if (DConfig.chanmodes[0] == '+' || DConfig.chanmodes[0] == '-') + { + DefConModesSet = false; + Anope::string newmodes = defconReverseModes(DConfig.chanmodes); + if (!newmodes.empty()) + { + Log(OperServ, "operserv/defcon") << "DEFCON: setting " << newmodes << " on all channels"; + for (channel_map::const_iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it) + it->second->SetModes(OperServ, true, "%s", newmodes.c_str()); + } + } + } + } +} + +static Anope::string defconReverseModes(const Anope::string &modes) +{ + if (modes.empty()) + return ""; + Anope::string newmodes; + for (unsigned i = 0, end = modes.length(); i < end; ++i) + { + if (modes[i] == '+') + newmodes += '-'; + else if (modes[i] == '-') + newmodes += '+'; + else + newmodes += modes[i]; + } + return newmodes; +} + +MODULE_INIT(OSDefcon) diff --git a/modules/operserv/dns.cpp b/modules/operserv/dns.cpp new file mode 100644 index 000000000..a231a1e74 --- /dev/null +++ b/modules/operserv/dns.cpp @@ -0,0 +1,1012 @@ +/* + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +#include "module.h" +#include "modules/dns.h" +#include "modules/os_dns.h" + +static std::map > server_quit_times; + +class DNSZoneImpl : public DNSZone +{ + public: + DNSZoneImpl(Serialize::TypeBase *type) : DNSZone(type) { } + DNSZoneImpl(Serialize::TypeBase *type, Serialize::ID id) : DNSZone(type, id) { } + + Anope::string GetName() override; + void SetName(const Anope::string &) override; + + static DNSZone *Find(const Anope::string &name) + { + for (DNSZone *zone : Serialize::GetObjects(dnszone)) + if (zone->GetName().equals_ci(name)) + return zone; + return nullptr; + } +}; + +class DNSZoneType : public Serialize::Type +{ + public: + Serialize::Field name; + + DNSZoneType(Module *creator) : Serialize::Type(creator, "DNSZone") + , name(this, "name") + { + } +}; + +Anope::string DNSZoneImpl::GetName() +{ + return Get(&DNSZoneType::name); +} + +void DNSZoneImpl::SetName(const Anope::string &name) +{ + Set(&DNSZoneType::name, name); +} + +class DNSServerImpl : public DNSServer +{ + /* is actually in the pool */ + bool active = false; + + public: + std::set zones; + time_t repool = 0; + + DNSServerImpl(Serialize::TypeBase *type) : DNSServer(type) { } + DNSServerImpl(Serialize::TypeBase *type, Serialize::ID id) : DNSServer(type, id) { } + + DNSZone *GetZone() override; + void SetZone(DNSZone *) override; + + Anope::string GetName() override; + void SetName(const Anope::string &) override; + + unsigned int GetLimit() override; + void SetLimit(const unsigned int &) override; + + bool GetPooled() override; + void SetPool(const bool &) override; + + bool Active() + { + return GetPooled() && active; + } + + void SetActive(bool p) + { + if (p) + this->SetPool(p); + active = p; + + if (DNS::manager) + { + DNS::manager->UpdateSerial(); + for (std::set::iterator it = zones.begin(), it_end = zones.end(); it != it_end; ++it) + DNS::manager->Notify(*it); + } + } + + static DNSServerImpl *Find(const Anope::string &s) + { + for (DNSServerImpl *server : Serialize::GetObjects(dnsserver)) + if (server->GetName().equals_ci(s)) + return server; + return nullptr; + } +}; + +class DNSServerType : public Serialize::Type +{ + public: + Serialize::ObjectField zone; + Serialize::Field name; + Serialize::Field limit; + Serialize::Field pooled; + + DNSServerType(Module *creator) : Serialize::Type(creator, "DNSServer") + , zone(this, "zone") + , name(this, "name") + , limit(this, "limit") + , pooled(this, "pooled") + { + } +}; + +DNSZone *DNSServerImpl::GetZone() +{ + return Get(&DNSServerType::zone); +} + +void DNSServerImpl::SetZone(DNSZone *z) +{ + Set(&DNSServerType::zone, z); +} + +Anope::string DNSServerImpl::GetName() +{ + return Get(&DNSServerType::name); +} + +void DNSServerImpl::SetName(const Anope::string &n) +{ + Set(&DNSServerType::name, n); +} + +unsigned int DNSServerImpl::GetLimit() +{ + return Get(&DNSServerType::limit); +} + +void DNSServerImpl::SetLimit(const unsigned int &l) +{ + Set(&DNSServerType::limit, l); +} + +bool DNSServerImpl::GetPooled() +{ + return Get(&DNSServerType::pooled); +} + +void DNSServerImpl::SetPool(const bool &p) +{ + Set(&DNSServerType::pooled, p); +} + +class DNSZoneMembershipImpl : public DNSZoneMembership +{ + public: + DNSZoneMembershipImpl(Serialize::TypeBase *type) : DNSZoneMembership(type) { } + DNSZoneMembershipImpl(Serialize::TypeBase *type, Serialize::ID id) : DNSZoneMembership(type, id) { } + + DNSServer *GetServer() override; + void SetServer(DNSServer *) override; + + DNSZone *GetZone() override; + void SetZone(DNSZone *) override; + + static DNSZoneMembership *Find(DNSServer *server, DNSZone *zone) + { + for (DNSZoneMembership *mem : Serialize::GetObjects(dnszonemembership)) + if (mem->GetServer() == server && mem->GetZone() == zone) + return mem; + return nullptr; + } +}; + +class DNSZoneMembershipType : public Serialize::Type +{ + public: + Serialize::ObjectField server; + Serialize::ObjectField zone; + + DNSZoneMembershipType(Module *creator) : Serialize::Type(creator, "DNSZoneMembership") + , server(this, "server") + , zone(this, "zone") + { + } +}; + +DNSServer *DNSZoneMembershipImpl::GetServer() +{ + return Get(&DNSZoneMembershipType::server); +} + +void DNSZoneMembershipImpl::SetServer(DNSServer *s) +{ + Set(&DNSZoneMembershipType::server, s); +} + +DNSZone *DNSZoneMembershipImpl::GetZone() +{ + return Get(&DNSZoneMembershipType::zone); +} + +void DNSZoneMembershipImpl::SetZone(DNSZone *z) +{ + Set(&DNSZoneMembershipType::zone, z); +} + +class DNSIPImpl : public DNSIP +{ + public: + DNSIPImpl(Serialize::TypeBase *type) : DNSIP(type) { } + DNSIPImpl(Serialize::TypeBase *type, Serialize::ID id) : DNSIP(type, id) { } + + DNSServer *GetServer() override; + void SetServer(DNSServer *) override; + + Anope::string GetIP() override; + void SetIP(const Anope::string &) override; +}; + +class DNSIPType : public Serialize::Type +{ + public: + Serialize::ObjectField server; + Serialize::Field ip; + + DNSIPType(Module *creator) : Serialize::Type(creator, "DNSIP") + , server(this, "server") + , ip(this, "ip") + { + } +}; + +DNSServer *DNSIPImpl::GetServer() +{ + return Get(&DNSIPType::server); +} + +void DNSIPImpl::SetServer(DNSServer *s) +{ + Set(&DNSIPType::server, s); +} + +Anope::string DNSIPImpl::GetIP() +{ + return Get(&DNSIPType::ip); +} + +void DNSIPImpl::SetIP(const Anope::string &ip) +{ + Set(&DNSIPType::ip, ip); +} + +class CommandOSDNS : public Command +{ + void DisplayPoolState(CommandSource &source) + { + std::vector servers = Serialize::GetObjects(dnsserver); + + if (servers.empty()) + { + source.Reply(_("There are no configured servers.")); + return; + } + + ListFormatter lf(source.GetAccount()); + lf.AddColumn(_("Server")).AddColumn(_("IP")).AddColumn(_("Limit")).AddColumn(_("State")); + for (DNSServerImpl *s : servers) + { + Server *srv = Server::Find(s->GetName(), true); + + ListFormatter::ListEntry entry; + entry["Server"] = s->GetName(); + entry["Limit"] = s->GetLimit() ? stringify(s->GetLimit()) : Language::Translate(source.GetAccount(), _("None")); + + Anope::string ip_str; + for (DNSIP *ip : s->GetRefs(dnsip)) + ip_str += ip->GetIP() + " "; + ip_str.trim(); + if (ip_str.empty()) + ip_str = "None"; + entry["IP"] = ip_str; + + if (s->Active()) + entry["State"] = Language::Translate(source.GetAccount(), _("Pooled/Active")); + else if (s->GetPooled()) + entry["State"] = Language::Translate(source.GetAccount(), _("Pooled/Not Active")); + else + entry["State"] = Language::Translate(source.GetAccount(), _("Unpooled")); + + if (!srv) + entry["State"] += Anope::string(" ") + Language::Translate(source.GetAccount(), _("(Split)")); + + lf.AddEntry(entry); + } + + std::vector replies; + lf.Process(replies); + + std::vector zones = Serialize::GetObjects(dnszone); + if (!zones.empty()) + { + ListFormatter lf2(source.GetAccount()); + lf2.AddColumn(_("Zone")).AddColumn(_("Servers")); + + for (DNSZone *z : zones) + { + ListFormatter::ListEntry entry; + entry["Zone"] = z->GetName(); + + Anope::string server_str; + for (DNSServer *s : z->GetRefs(dnsserver)) + server_str += s->GetName() + " "; + server_str.trim(); + + if (server_str.empty()) + server_str = "None"; + + entry["Servers"] = server_str; + + lf2.AddEntry(entry); + } + + lf2.Process(replies); + } + + for (const Anope::string &r : replies) + source.Reply(r); + } + + void AddZone(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &zone = params[1]; + + if (DNSZoneImpl::Find(zone)) + { + source.Reply(_("Zone \002{0}\002 already exists."), zone); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "to add zone " << zone; + + DNSZone *z = dnszone.Create(); + z->SetName(zone); + source.Reply(_("Added zone \002{0}\002."), zone); + } + + void DelZone(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &zone = params[1]; + + DNSZone *z = DNSZoneImpl::Find(zone); + if (!z) + { + source.Reply(_("Zone \002{0}\002 does not exist."), zone); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "to delete zone " << z->GetName(); + + for (DNSZoneMembership *mem : z->GetRefs(dnszonemembership)) + mem->Delete(); + + if (DNS::manager) + { + DNS::manager->UpdateSerial(); + DNS::manager->Notify(z->GetName()); + } + + source.Reply(_("Zone \002{0}\002 removed."), z->GetName()); + z->Delete(); + } + + void AddServer(CommandSource &source, const std::vector ¶ms) + { + DNSServer *s = DNSServerImpl::Find(params[1]); + const Anope::string &zone = params.size() > 2 ? params[2] : ""; + + if (s) + { + if (zone.empty()) + { + source.Reply(_("Server \002{0}\002 already exists."), s->GetName()); + } + else + { + DNSZone *z = DNSZoneImpl::Find(zone); + if (!z) + { + source.Reply(_("Zone \002{0}\002 does not exist."), zone); + return; + } + + DNSZoneMembership *mem = DNSZoneMembershipImpl::Find(s, z); + if (mem) + { + source.Reply(_("Server \002{0}\002 is already in zone \002{1}\002."), s->GetName(), z->GetName()); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + mem = dnszonemembership.Create(); + mem->SetZone(z); + mem->SetServer(s); + + if (DNS::manager) + { + DNS::manager->UpdateSerial(); + DNS::manager->Notify(zone); + } + + Log(LOG_ADMIN, source, this) << "to add server " << s->GetName() << " to zone " << z->GetName(); + + source.Reply(_("Server \002{0}\002 added to zone \002{1}\002."), s->GetName(), z->GetName()); + } + + return; + } + + Server *serv = Server::Find(params[1], true); + if (!serv || serv == Me || serv->IsJuped()) + { + source.Reply(_("Server \002{0}\002 is not linked to the network."), params[1]); + return; + } + + s = dnsserver.Create(); + s->SetName(params[1]); + if (zone.empty()) + { + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "to add server " << s->GetName(); + source.Reply(_("Added server \002{0}\002."), s->GetName()); + } + else + { + DNSZone *z = DNSZoneImpl::Find(zone); + if (!z) + { + source.Reply(_("Zone \002{0}\002 does not exist."), zone); + s->Delete(); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "to add server " << s->GetName() << " to zone " << zone; + + DNSZoneMembership *mem = dnszonemembership.Create(); + mem->SetServer(s); + mem->SetZone(z); + + if (DNS::manager) + { + DNS::manager->UpdateSerial(); + DNS::manager->Notify(z->GetName()); + } + } + } + + void DelServer(CommandSource &source, const std::vector ¶ms) + { + DNSServer *s = DNSServerImpl::Find(params[1]); + const Anope::string &zone = params.size() > 2 ? params[2] : ""; + + if (!s) + { + source.Reply(_("Server \002{0}\002 does not exist."), params[1]); + return; + } + + if (!zone.empty()) + { + DNSZone *z = DNSZoneImpl::Find(zone); + if (!z) + { + source.Reply(_("Zone \002{0}\002 does not exist."), zone); + return; + } + + DNSZoneMembership *mem = DNSZoneMembershipImpl::Find(s, z); + if (!mem) + { + source.Reply(_("Server \002{0}\002 is not in zone \002{1}\002."), s->GetName(), z->GetName()); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "to remove server " << s->GetName() << " from zone " << z->GetName(); + + if (DNS::manager) + { + DNS::manager->UpdateSerial(); + DNS::manager->Notify(z->GetName()); + } + + mem->Delete(); + source.Reply(_("Removed server \002{0}\002 from zone \002{1}\002."), s->GetName(), z->GetName()); + return; + } + + if (Server::Find(s->GetName(), true)) + { + source.Reply(_("Server \002{0}\002 must be quit before it can be deleted."), s->GetName()); + return; + } + + for (DNSZoneMembership *mem : s->GetRefs(dnszonemembership)) + mem->Delete(); + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + if (DNS::manager) + DNS::manager->UpdateSerial(); + + Log(LOG_ADMIN, source, this) << "to delete server " << s->GetName(); + source.Reply(_("Removed server \002{0}\002."), s->GetName()); + s->Delete(); + } + + void AddIP(CommandSource &source, const std::vector ¶ms) + { + DNSServerImpl *s = DNSServerImpl::Find(params[1]); + + if (!s) + { + source.Reply(_("Server \002{0}\002 does not exist."), params[1]); + return; + } + + for (DNSIP *ip : s->GetRefs(dnsip)) + if (params[2].equals_ci(ip->GetIP())) + { + source.Reply(_("IP \002{0}\002 already exists for \002{1}\002."), ip->GetIP(), s->GetName()); + return; + } + + sockaddrs addr(params[2]); + if (!addr.valid()) + { + source.Reply(_("\002{0}\002 is not a valid IP address."), params[2]); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + DNSIP *ip = dnsip.Create(); + ip->SetServer(s); + ip->SetIP(params[2]); + + source.Reply(_("Added IP \002{0}\002 to \002{1}\002."), params[2], s->GetName()); + Log(LOG_ADMIN, source, this) << "to add IP " << params[2] << " to " << s->GetName(); + + if (s->Active() && DNS::manager) + { + DNS::manager->UpdateSerial(); + for (DNSZone *zone : s->GetRefs(dnszone)) + DNS::manager->Notify(zone->GetName()); + } + } + + void DelIP(CommandSource &source, const std::vector ¶ms) + { + DNSServerImpl *s = DNSServerImpl::Find(params[1]); + + if (!s) + { + source.Reply(_("Server \002{0}\002 does not exist."), params[1]); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + for (DNSIP *ip : s->GetRefs(dnsip)) + if (params[2].equals_ci(ip->GetIP())) + { + ip->Delete(); + + source.Reply(_("Removed IP \002{0}\002 from \002{1}\002."), params[2], s->GetName()); + Log(LOG_ADMIN, source, this) << "to remove IP " << params[2] << " from " << s->GetName(); + + if (s->GetRefs(dnsip).empty()) + { + s->repool = 0; + s->SetPool(false); + } + + if (s->Active() && DNS::manager) + { + DNS::manager->UpdateSerial(); + for (DNSZone *zone : s->GetRefs(dnszone)) + DNS::manager->Notify(zone->GetName()); + } + + return; + } + + source.Reply(_("IP \002{0}\002 does not exist for \002{1}\002."), params[2], s->GetName()); + } + + void OnSet(CommandSource &source, const std::vector ¶ms) + { + DNSServer *s = DNSServerImpl::Find(params[1]); + + if (!s) + { + source.Reply(_("Server \002{0}\002 does not exist."), params[1]); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + if (params[2].equals_ci("LIMIT")) + { + try + { + unsigned l = convertTo(params[3]); + s->SetLimit(l); + if (l) + source.Reply(_("User limit for \002{0}\002 set to \002{1}\002."), s->GetName(), l); + else + source.Reply(_("User limit for \002{0}\002 removed."), s->GetName()); + } + catch (const ConvertException &ex) + { + source.Reply(_("Invalid value for limit. Must be numerical.")); + } + } + else + source.Reply(_("Unknown SET option.")); + } + + void OnPool(CommandSource &source, const std::vector ¶ms) + { + DNSServerImpl *s = DNSServerImpl::Find(params[1]); + + if (!s) + { + source.Reply(_("Server \002{0}\002 does not exist."), params[1]); + return; + } + + if (!Server::Find(s->GetName(), true)) + { + source.Reply(_("Server \002{0}\002 is not currently linked."), s->GetName()); + return; + } + + if (s->GetPooled()) + { + source.Reply(_("Server \002{0}\002 is already pooled."), s->GetName()); + return; + } + + if (s->GetRefs(dnsip).empty()) + { + source.Reply(_("Server \002{0}\002 has no configured IPs."), s->GetName()); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + s->SetActive(true); + + source.Reply(_("Pooled \002{0}\002."), s->GetName()); + Log(LOG_ADMIN, source, this) << "to pool " << s->GetName(); + } + + + void OnDepool(CommandSource &source, const std::vector ¶ms) + { + DNSServer *s = DNSServerImpl::Find(params[1]); + + if (!s) + { + source.Reply(_("Server \002{0}\002 does not exist."), params[1]); + return; + } + + if (!s->GetPooled()) + { + source.Reply(_("Server \002{0}\002 is not pooled."), s->GetName()); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + s->SetPool(false); + + source.Reply(_("Depooled \002{0}\002."), s->GetName()); + Log(LOG_ADMIN, source, this) << "to depool " << s->GetName(); + } + + public: + CommandOSDNS(Module *creator) : Command(creator, "operserv/dns", 0, 4) + { + this->SetDesc(_("Manage DNS zones for this network")); + this->SetSyntax(_("ADDZONE \037zone.name\037")); + this->SetSyntax(_("DELZONE \037zone.name\037")); + this->SetSyntax(_("ADDSERVER \037server.name\037 [\037zone.name\037]")); + this->SetSyntax(_("DELSERVER \037server.name\037 [\037zone.name\037]")); + this->SetSyntax(_("ADDIP \037server.name\037 \037ip\037")); + this->SetSyntax(_("DELIP \037server.name\037 \037ip\037")); + this->SetSyntax(_("SET \037server.name\037 \037option\037 \037value\037")); + this->SetSyntax(_("POOL \037server.name\037")); + this->SetSyntax(_("DEPOOL \037server.name\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (params.empty()) + this->DisplayPoolState(source); + else if (params[0].equals_ci("ADDZONE") && params.size() > 1) + this->AddZone(source, params); +