diff options
Diffstat (limited to 'modules/operserv/dns.cpp')
-rw-r--r-- | modules/operserv/dns.cpp | 1055 |
1 files changed, 1055 insertions, 0 deletions
diff --git a/modules/operserv/dns.cpp b/modules/operserv/dns.cpp new file mode 100644 index 000000000..de22e0266 --- /dev/null +++ b/modules/operserv/dns.cpp @@ -0,0 +1,1055 @@ +/* + * Anope IRC Services + * + * Copyright (C) 2012-2017 Anope Team <team@anope.org> + * + * This file is part of Anope. Anope is free software; you can + * redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software + * Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see see <http://www.gnu.org/licenses/>. + */ + +#include "module.h" +#include "modules/dns.h" +#include "modules/operserv/dns.h" + +static std::map<Anope::string, std::list<time_t> > server_quit_times; + +class DNSZoneImpl : public DNSZone +{ + friend class DNSZoneType; + + Serialize::Storage<Anope::string> name; + + public: + using DNSZone::DNSZone; + + 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<DNSZoneImpl> +{ + public: + Serialize::Field<DNSZoneImpl, Anope::string> name; + + DNSZoneType(Module *creator) : Serialize::Type<DNSZoneImpl>(creator) + , name(this, "name", &DNSZoneImpl::name) + { + } +}; + +Anope::string DNSZoneImpl::GetName() +{ + return Get(&DNSZoneType::name); +} + +void DNSZoneImpl::SetName(const Anope::string &name) +{ + Set(&DNSZoneType::name, name); +} + +class DNSServerImpl : public DNSServer +{ + friend class DNSServerType; + + ServiceReference<DNS::Manager> manager; + + Serialize::Storage<DNSZone *> zone; + Serialize::Storage<Anope::string> name; + Serialize::Storage<unsigned int> limit; + Serialize::Storage<bool> pooled; + + /* is actually in the pool */ + bool active = false; + + public: + std::set<Anope::string, ci::less> zones; + time_t repool = 0; + + using DNSServer::DNSServer; + + DNSZone *GetZone() override; + void SetZone(DNSZone *) override; + + Anope::string GetName() override; + void SetName(const Anope::string &) override; + + unsigned int GetLimit() override; + void SetLimit(unsigned int) override; + + bool GetPooled() override; + void SetPool(bool) override; + + bool Active() + { + return GetPooled() && active; + } + + void SetActive(bool p) + { + if (p) + this->SetPool(p); + active = p; + + if (manager) + { + manager->UpdateSerial(); + for (std::set<Anope::string, ci::less>::iterator it = zones.begin(), it_end = zones.end(); it != it_end; ++it) + manager->Notify(*it); + } + } + + static DNSServerImpl *Find(const Anope::string &s) + { + for (DNSServerImpl *server : Serialize::GetObjects<DNSServerImpl *>()) + if (server->GetName().equals_ci(s)) + return server; + return nullptr; + } +}; + +class DNSServerType : public Serialize::Type<DNSServerImpl> +{ + public: + Serialize::ObjectField<DNSServerImpl, DNSZone *> zone; + Serialize::Field<DNSServerImpl, Anope::string> name; + Serialize::Field<DNSServerImpl, unsigned int> limit; + Serialize::Field<DNSServerImpl, bool> pooled; + + DNSServerType(Module *creator) : Serialize::Type<DNSServerImpl>(creator) + , zone(this, "zone", &DNSServerImpl::zone) + , name(this, "name", &DNSServerImpl::name) + , limit(this, "limit", &DNSServerImpl::limit) + , pooled(this, "pooled", &DNSServerImpl::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(unsigned int l) +{ + Set(&DNSServerType::limit, l); +} + +bool DNSServerImpl::GetPooled() +{ + return Get(&DNSServerType::pooled); +} + +void DNSServerImpl::SetPool(bool p) +{ + Set(&DNSServerType::pooled, p); +} + +class DNSZoneMembershipImpl : public DNSZoneMembership +{ + friend class DNSZoneMembershipType; + + Serialize::Storage<DNSServer *> server; + Serialize::Storage<DNSZone *> zone; + + public: + using DNSZoneMembership::DNSZoneMembership; + + 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<DNSZoneMembershipImpl> +{ + public: + Serialize::ObjectField<DNSZoneMembershipImpl, DNSServer *> server; + Serialize::ObjectField<DNSZoneMembershipImpl, DNSZone *> zone; + + DNSZoneMembershipType(Module *creator) : Serialize::Type<DNSZoneMembershipImpl>(creator) + , server(this, "server", &DNSZoneMembershipImpl::server) + , zone(this, "zone", &DNSZoneMembershipImpl::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 +{ + friend class DNSIPType; + + Serialize::Storage<DNSServer *> server; + Serialize::Storage<Anope::string> ip; + + 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<DNSIPImpl> +{ + public: + Serialize::ObjectField<DNSIPImpl, DNSServer *> server; + Serialize::Field<DNSIPImpl, Anope::string> ip; + + DNSIPType(Module *creator) : Serialize::Type<DNSIPImpl>(creator) + , server(this, "server", &DNSIPImpl::server) + , ip(this, "ip", &DNSIPImpl::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 +{ + ServiceReference<DNS::Manager> manager; + + void DisplayPoolState(CommandSource &source) + { + std::vector<DNSServerImpl *> servers = Serialize::GetObjects<DNSServerImpl *>(); + + 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<Anope::string> replies; + lf.Process(replies); + + std::vector<DNSZone *> 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<Anope::string> ¶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.")); + + logger.Admin(source, _("{source} used {command} to add zone {0}"), zone); + + DNSZone *z = Serialize::New<DNSZone *>(); + z->SetName(zone); + source.Reply(_("Added zone \002{0}\002."), zone); + } + + void DelZone(CommandSource &source, const std::vector<Anope::string> ¶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.")); + + logger.Admin(source, _("{source} used {command} to delete zone {0}"), z->GetName()); + + for (DNSZoneMembership *mem : z->GetRefs<DNSZoneMembership *>()) + mem->Delete(); + + if (manager) + { + manager->UpdateSerial(); + manager->Notify(z->GetName()); + } + + source.Reply(_("Zone \002{0}\002 removed."), z->GetName()); + z->Delete(); + } + + void AddServer(CommandSource &source, const std::vector<Anope::string> ¶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 = Serialize::New<DNSZoneMembership *>(); + mem->SetZone(z); + mem->SetServer(s); + + if (manager) + { + manager->UpdateSerial(); + manager->Notify(zone); + } + + logger.Admin(source, _("{source} used {command} to add server {0} to zone {1}"), s->GetName(), 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 = Serialize::New<DNSServer *>(); + s->SetName(params[1]); + if (zone.empty()) + { + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + logger.Admin(source, _("{source} used {command} to add server {2}"), 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.")); + + logger.Admin(source, _("{source} used {command} to add server {0} to zone {1}"), s->GetName(), z->GetName()); + + DNSZoneMembership *mem = Serialize::New<DNSZoneMembership *>(); + mem->SetServer(s); + mem->SetZone(z); + + if (manager) + { + manager->UpdateSerial(); + manager->Notify(z->GetName()); + } + } + } + + void DelServer(CommandSource &source, const std::vector<Anope::string> ¶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.")); + + logger.Admin(source, _("{source} used {command} to remove server {0} to zone {1}"), s->GetName(), z->GetName()); + + if (manager) + { + manager->UpdateSerial(); + 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 (manager) + manager->UpdateSerial(); + + logger.Admin(source, _("{source} used {command} to delete server {0}"), s->GetName()); + + source.Reply(_("Removed server \002{0}\002."), s->GetName()); + s->Delete(); + } + + void AddIP(CommandSource &source, const std::vector<Anope::string> ¶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 = Serialize::New<DNSIP *>(); + ip->SetServer(s); + ip->SetIP(params[2]); + + source.Reply(_("Added IP \002{0}\002 to \002{1}\002."), params[2], s->GetName()); + + logger.Admin(source, _("{source} used {command} to add IP {0} to {1}"), params[2], s->GetName()); + + if (s->Active() && manager) + { + manager->UpdateSerial(); + for (DNSZone *zone : s->GetRefs<DNSZone *>()) + manager->Notify(zone->GetName()); + } + } + + void DelIP(CommandSource &source, const std::vector<Anope::string> ¶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()); + + logger.Admin(source, _("{source} used {command} to add IP {0} to {1}"), params[2], s->GetName()); + + if (s->GetRefs<DNSIP *>().empty()) + { + s->repool = 0; + s->SetPool(false); + } + + if (s->Active() && manager) + { + manager->UpdateSerial(); + for (DNSZone *zone : s->GetRefs<DNSZone *>()) + 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<Anope::string> ¶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<unsigned>(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<Anope::string> ¶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()); + + logger.Admin(source, _("{source} used {command} to pool {0}"), s->GetName()); + } + + + void OnDepool(CommandSource &source, const std::vector<Anope::string> ¶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()); + + logger.Admin(source, _("{source} used {command} to depool {0}"), 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<Anope::string> ¶ms) override + { + if (params.empty()) + this->DisplayPoolState(source); + else if (params[0].equals_ci("ADDZONE") && params.size() > 1) + this->AddZone(source, params); + else if (params[0].equals_ci("DELZONE") && params.size() > 1) + this->DelZone(source, params); + else if (params[0].equals_ci("ADDSERVER") && params.size() > 1) + this->AddServer(source, params); + else if (params[0].equals_ci("DELSERVER") && params.size() > 1) + this->DelServer(source, params); + else if (params[0].equals_ci("ADDIP") && params.size() > 2) + this->AddIP(source, params); + else if (params[0].equals_ci("DELIP") && params.size() > 2) + this->DelIP(source, params); + else if (params[0].equals_ci("SET") && params.size() > 3) + this->OnSet(source, params); + else if (params[0].equals_ci("POOL") && params.size() > 1) + this->OnPool(source, params); + else if (params[0].equals_ci("DEPOOL") && params.size() > 1) + this->OnDepool(source, params); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command allows managing DNS zones used for controlling what servers users are directed to when connecting." + " Omitting all parameters prints out the status of the DNS zone.\n" + "\n" + "\002{0} ADDZONE\002 adds a zone, eg. us.example.com. Servers can then be added to this zone with the \002ADDSERVER\002 command.\n" + "\n" + "The \002{0} ADDSERVER\002 command adds a server to the given zone." + " When a DNS query is done, the zone in question is served if it exists, otherwise all servers in all zones are served." + " A server may be in more than one zone.\n" + "\n" + "The \002{0} ADDIP\002 command associates an IP with a server.\n" + " \n" + "The \002{0} POOL\002 and \002{0} DEPOOL\002 commands actually add and remove servers to their given zones."), + source.GetCommand()); + return true; + } +}; + +class ModuleDNS : public Module + , public EventHook<Event::NewServer> + , public EventHook<Event::ServerQuit> + , public EventHook<Event::UserConnect> + , public EventHook<Event::PreUserLogoff> + , public EventHook<Event::DnsRequest> +{ + DNSZoneType zonetype; + DNSServerType servertype; + DNSZoneMembershipType zonemembtype; + DNSIPType iptype; + CommandOSDNS commandosdns; + + time_t ttl = 0; + int user_drop_mark = 0; + time_t user_drop_time = 0; + time_t user_drop_readd_time = 0; + bool remove_split_servers = 0; + bool readd_connected_servers = 0; + + time_t last_warn = 0; + + public: + ModuleDNS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + , EventHook<Event::NewServer>(this) + , EventHook<Event::ServerQuit>(this) + , EventHook<Event::UserConnect>(this) + , EventHook<Event::PreUserLogoff>(this) + , EventHook<Event::DnsRequest>(this) + , zonetype(this) + , servertype(this) + , zonemembtype(this) + , iptype(this) + , commandosdns(this) + { + /* enable active flag for linked servers */ + std::vector<DNSServerImpl *> servers = Serialize::GetObjects<DNSServerImpl *>(); + + for (DNSServerImpl *s : servers) + if (s->GetPooled() && Server::Find(s->GetName(), true)) + s->SetActive(true); + } + + ~ModuleDNS() + { + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + this->ttl = block->Get<time_t>("ttl"); + this->user_drop_mark = block->Get<int>("user_drop_mark"); + this->user_drop_time = block->Get<time_t>("user_drop_time"); + this->user_drop_readd_time = block->Get<time_t>("user_drop_readd_time"); + this->remove_split_servers = block->Get<bool>("remove_split_servers"); + this->readd_connected_servers = block->Get<bool>("readd_connected_servers"); + } + + void OnNewServer(Server *s) override + { + if (s == Me || s->IsJuped()) + return; + if (!Me->IsSynced() || this->readd_connected_servers) + { + DNSServerImpl *dns = DNSServerImpl::Find(s->GetName()); + if (dns && dns->GetPooled() && !dns->Active() && !dns->GetRefs<DNSIP *>().empty()) + { + dns->SetActive(true); + logger.Log(_("Pooling server {0}"), s->GetName()); + } + } + } + + void OnServerQuit(Server *s) override + { + DNSServerImpl *dns = DNSServerImpl::Find(s->GetName()); + if (remove_split_servers && dns && dns->GetPooled() && dns->Active()) + { + if (readd_connected_servers) + dns->SetActive(false); // Will be reactivated when it comes back + else + dns->SetPool(false); // Otherwise permanently pull this + logger.Log(_("Depooling delinked server {0}"), s->GetName()); + } + } + + void OnUserConnect(User *u, bool &exempt) override + { + if (!u->Quitting() && u->server) + { + DNSServerImpl *s = DNSServerImpl::Find(u->server->GetName()); + /* Check for user limit reached */ + if (s && s->GetPooled() && s->Active() && s->GetLimit() && u->server->users >= s->GetLimit()) + { + logger.Log(_("Depooling full server {0}: {1} users"), s->GetName(), u->server->users); + s->SetActive(false); + } + } + } + + void OnPreUserLogoff(User *u) override + { + if (u && u->server) + { + DNSServerImpl *s = DNSServerImpl::Find(u->server->GetName()); + if (!s || !s->GetPooled()) + return; + + /* Check for dropping under userlimit */ + if (s->GetLimit() && !s->Active() && s->GetLimit() > u->server->users) + { + logger.Log(_("Pooling server {0}"), s->GetName()); + s->SetActive(true); + } + + if (this->user_drop_mark > 0) + { + std::list<time_t>& times = server_quit_times[u->server->GetName()]; + times.push_back(Anope::CurTime); + if (times.size() > static_cast<unsigned>(this->user_drop_mark)) + times.pop_front(); + + if (times.size() == static_cast<unsigned>(this->user_drop_mark)) + { + time_t diff = Anope::CurTime - *times.begin(); + + /* Check for very fast user drops */ + if (s->Active() && diff <= this->user_drop_time) + { + logger.Log(_("Depooling server {0}: dropped {1} users in {2} seconds"), s->GetName(), this->user_drop_mark, diff); + s->repool = Anope::CurTime + this->user_drop_readd_time; + s->SetActive(false); + } + /* Check for needing to re-pool a server that dropped users */ + else if (!s->Active() && s->repool && s->repool <= Anope::CurTime) + { + s->SetActive(true); + s->repool = 0; + logger.Log(_("Pooling server {0}"), s->GetName()); + } + } + } + } + } + + void OnDnsRequest(DNS::Query &req, DNS::Query *packet) override + { + if (req.questions.empty()) + return; + /* Currently we reply to any QR for A/AAAA */ + const DNS::Question& q = req.questions[0]; + if (q.type != DNS::QUERY_A && q.type != DNS::QUERY_AAAA && q.type != DNS::QUERY_AXFR && q.type != DNS::QUERY_ANY) + return; + + DNSZone *zone = DNSZoneImpl::Find(q.name); + size_t answer_size = packet->answers.size(); + if (zone) + { + for (DNSServerImpl *s : zone->GetRefs<DNSServerImpl *>()) + { + if (!s->Active()) + continue; + + for (DNSIP *ip : s->GetRefs<DNSIP *>()) + { + DNS::QueryType q_type = ip->GetIP().find(':') != Anope::string::npos ? DNS::QUERY_AAAA : DNS::QUERY_A; + + if (q.type == DNS::QUERY_AXFR || q.type == DNS::QUERY_ANY || q_type == q.type) + { + DNS::ResourceRecord rr(q.name, q_type); + rr.ttl = this->ttl; + rr.rdata = ip->GetIP(); + packet->answers.push_back(rr); + } + } + } + } + + if (packet->answers.size() == answer_size) + { + /* Default zone */ + for (DNSServerImpl *s : Serialize::GetObjects<DNSServerImpl *>()) + { + if (!s->Active()) + continue; + + for (DNSIP *ip : s->GetRefs<DNSIP *>()) + { + DNS::QueryType q_type = ip->GetIP().find(':') != Anope::string::npos ? DNS::QUERY_AAAA : DNS::QUERY_A; + + if (q.type == DNS::QUERY_AXFR || q.type == DNS::QUERY_ANY || q_type == q.type) + { + DNS::ResourceRecord rr(q.name, q_type); + rr.ttl = this->ttl; + rr.rdata = ip->GetIP(); + packet->answers.push_back(rr); + } + } + } + } + + if (packet->answers.size() == answer_size) + { + if (last_warn + 60 < Anope::CurTime) + { + last_warn = Anope::CurTime; + logger.Log("Warning! There are no pooled servers!"); + } + + /* Something messed up, just return them all and hope one is available */ + for (DNSServer *s : Serialize::GetObjects<DNSServer *>()) + for (DNSIP *ip : s->GetRefs<DNSIP *>()) + { + DNS::QueryType q_type = ip->GetIP().find(':') != Anope::string::npos ? DNS::QUERY_AAAA : DNS::QUERY_A; + + if (q.type == DNS::QUERY_AXFR || q.type == DNS::QUERY_ANY || q_type == q.type) + { + DNS::ResourceRecord rr(q.name, q_type); + rr.ttl = this->ttl; + rr.rdata = ip->GetIP(); + packet->answers.push_back(rr); + } + } + + if (packet->answers.size() == answer_size) + { + logger.Log("Error! There are no servers with any IPs of type {0}", q.type); + } + } + } +}; + +MODULE_INIT(ModuleDNS) |