diff options
author | Adam <Adam@anope.org> | 2012-10-22 00:54:30 -0400 |
---|---|---|
committer | Adam <Adam@anope.org> | 2012-10-22 00:54:30 -0400 |
commit | 0b9db15efc322336ddb08671ce68a3d45fb22520 (patch) | |
tree | 2e8149f370c63f08f2f197eaa92b1b36e857b0e9 | |
parent | d5b2f9cfa78ed176ffe1d9f2923799fdd37217a5 (diff) |
Add os_dns, a way to control your DNS zone via services
-rw-r--r-- | data/example.conf | 16 | ||||
-rw-r--r-- | data/operserv.example.conf | 69 | ||||
-rw-r--r-- | include/config.h | 8 | ||||
-rw-r--r-- | include/dns.h | 31 | ||||
-rw-r--r-- | include/modules.h | 7 | ||||
-rw-r--r-- | include/servers.h | 3 | ||||
-rw-r--r-- | modules/commands/os_dns.cpp | 552 | ||||
-rw-r--r-- | src/config.cpp | 39 | ||||
-rw-r--r-- | src/dns.cpp | 94 | ||||
-rw-r--r-- | src/init.cpp | 18 | ||||
-rw-r--r-- | src/main.cpp | 11 | ||||
-rw-r--r-- | src/users.cpp | 5 |
12 files changed, 777 insertions, 76 deletions
diff --git a/data/example.conf b/data/example.conf index 9257b7b34..6680c6bad 100644 --- a/data/example.conf +++ b/data/example.conf @@ -282,6 +282,13 @@ networkinfo options { /* + * On Linux/UNIX systems Anope can setuid and setgid to this user and group + * after starting up. This is useful if Anope has to bind to privileged ports + */ + #user = "anope" + #group = "anope" + + /* * The case mapping used by services. This must be set to a valid locale name * installed on your machine. Services use this case map to compare, with * case insensitivity, things such as nick names, channel names, etc. @@ -1047,6 +1054,15 @@ dns * How long to wait in seconds before a DNS query has timed out */ timeout = 5 + + /* + * The port services use to listen for DNS queries. + * Note that ports less than 1024 are privileged on UNIX/Linux systems, and + * require Anope to be started as root. If you do this, it is recommended you + * set options:user and options:group so Anope can change users after binding + * to this port. + */ + port = 53 } /* diff --git a/data/operserv.example.conf b/data/operserv.example.conf index 4988ad33c..167d13c04 100644 --- a/data/operserv.example.conf +++ b/data/operserv.example.conf @@ -355,15 +355,61 @@ defcon } /* - * os_list + * os_dns * - * Provides the commands operserv/chanlist and operserv/userlist. + * Provides the command operserv/dns. * - * Used to list and search the channels and users currently on the network. + * This module allows controlling a DNS zone. This is useful for + * controlling what servers users are placed on for load balancing, + * and to automatically remove split servers. + * + * To use this module you must set a nameserver record for services + * so that DNS queries go to services. + * + * We recommend you use something similar to BIND's query forwarding + * ability to hide service's IP, provide query caching, and provide + * better fault tolerance. To do this, configure BIND similar to: + * + * options { + * dnssec-enable no; + * dnssec-validation no; + * }; + * zone "irc.example.com" IN { + * type forward; + * forward first; + * forwarders { 10.0.0.1 port 5353; }; # Where this is the IP and dns:port of services + * }; + * + * And then set a NS record for irc.example.com. to BIND. */ -module { name = "os_list" } -command { service = "OperServ"; name = "CHANLIST"; command = "operserv/chanlist"; } -command { service = "OperServ"; name = "USERLIST"; command = "operserv/userlist"; } +#module { name = "os_dns" } +#command { service = "OperServ"; name = "DNS"; command = "operserv/dns"; permission = "operserv/dns"; } +os_dns +{ + /* TTL for records. This should be very low if your records change often. */ + ttl = 1m + + /* If a server drops this many users the server is automatically removed from the DNS zone. + * This directive is optional. + */ + user_drop_mark = 50 + + /* The time used for user_drop_mark */ + user_drop_time = 1m + + /* When a server is removed from the zone for dropping users, it is readded after this time. + * This directive is optional. + */ + user_drop_readd_time = 5m + + /* If set, if a server splits it is automatically removed from the zone */ + remove_split_servers = yes + + /* If set, when a server connects to the network it will be automatically added to + * the zone if it is a known server. + */ + readd_connected_servers = no +} /* * os_config @@ -426,6 +472,17 @@ module { name = "os_kill" } command { service = "OperServ"; name = "KILL"; command = "operserv/kill"; permission = "operserv/kill"; } /* + * os_list + * + * Provides the commands operserv/chanlist and operserv/userlist. + * + * Used to list and search the channels and users currently on the network. + */ +module { name = "os_list" } +command { service = "OperServ"; name = "CHANLIST"; command = "operserv/chanlist"; } +command { service = "OperServ"; name = "USERLIST"; command = "operserv/userlist"; } + +/* * os_login * * Provides the commands operserv/login and operserv/logout. diff --git a/include/config.h b/include/config.h index 554959e38..947daa3e5 100644 --- a/include/config.h +++ b/include/config.h @@ -394,6 +394,10 @@ class CoreExport ServerConfig /* Max length of channel names */ unsigned ChanLen; + /* User and group to run as */ + Anope::string User; + Anope::string Group; + /* Casemapping to use */ Anope::string CaseMap; @@ -494,8 +498,10 @@ class CoreExport ServerConfig /* Nameserver to use for resolving hostnames */ Anope::string NameServer; - /* TIme before a DNS query is considered dead */ + /* Time before a DNS query is considered dead */ time_t DNSTimeout; + /* The port DNS queries come in on */ + int DNSPort; /* Prefix of guest nicks when a user gets forced off of a nick */ Anope::string NSGuestNickPrefix; diff --git a/include/dns.h b/include/dns.h index 3102ec72c..062fed26f 100644 --- a/include/dns.h +++ b/include/dns.h @@ -95,7 +95,6 @@ struct CoreExport DNSQuery DNSQuery(); DNSQuery(const Question &q); - DNSQuery(const DNSPacket &p); }; /** The request @@ -124,7 +123,7 @@ class CoreExport DNSRequest : public Timer, public Question void Tick(time_t) anope_override; }; -/** A full packet sent or recieved to/from the nameserver, may contain multiple queries +/** A full packet sent or recieved to/from the nameserver */ class DNSPacket : public DNSQuery { @@ -139,12 +138,14 @@ class DNSPacket : public DNSQuery public: static const int HEADER_LENGTH = 12; - /* Our 16-bit id for this header */ + /* Source or destination of the packet */ + sockaddrs addr; + /* ID for this packet */ unsigned short id; - /* Flags on the query */ + /* Flags on the packet */ unsigned short flags; - DNSPacket(); + DNSPacket(const sockaddrs &a); void Fill(const unsigned char *input, const unsigned short len); unsigned short Pack(unsigned char *output, unsigned short output_size); }; @@ -155,20 +156,19 @@ class CoreExport DNSManager : public Timer, public Socket { typedef std::multimap<Anope::string, ResourceRecord, ci::less> cache_map; cache_map cache; - sockaddrs addrs; - public: + std::deque<DNSPacket *> packets; + public: + sockaddrs addrs; std::map<unsigned short, DNSRequest *> requests; - static const int DNSPort = 53; - DNSManager(const Anope::string &nameserver, int port); ~DNSManager(); - bool ProcessRead(); + bool ProcessRead() anope_override; - bool ProcessWrite(); + bool ProcessWrite() anope_override; /** Add a record to the dns cache * @param r The record @@ -189,6 +189,15 @@ class CoreExport DNSManager : public Timer, public Socket */ void Cleanup(Module *mod); + /** Get the list of packets pending to be sent + */ + std::deque<DNSPacket *>& GetPackets(); + + /** Queues a packet for sending + * @param p The packet + */ + void SendPacket(DNSPacket *p); + /** Does a BLOCKING DNS query and returns the first IP. * Only use this if you know what you are doing. Unless you specifically * need a blocking query use the DNSRequest system diff --git a/include/modules.h b/include/modules.h index 29da80cf3..3827e9234 100644 --- a/include/modules.h +++ b/include/modules.h @@ -917,6 +917,11 @@ class CoreExport Module : public Extensible */ virtual void OnLog(Log *l) { } + /** Called when a DNS request (question) is recieved. + * @param req The dns request + */ + virtual void OnDnsRequest(DNSPacket &req) { } + /** Called when a channels modes are being checked to see if they are allowed, * mostly to ensure mlock/+r are set. * @param c The channel @@ -995,7 +1000,7 @@ enum Implementation I_OnServerQuit, I_OnTopicUpdated, I_OnEncrypt, I_OnDecrypt, I_OnChannelModeSet, I_OnChannelModeUnset, I_OnUserModeSet, I_OnUserModeUnset, I_OnChannelModeAdd, I_OnUserModeAdd, - I_OnMLock, I_OnUnMLock, I_OnServerSync, I_OnUplinkSync, I_OnBotPrivmsg, I_OnPrivmsg, I_OnLog, + I_OnMLock, I_OnUnMLock, I_OnServerSync, I_OnUplinkSync, I_OnBotPrivmsg, I_OnPrivmsg, I_OnLog, I_OnDnsRequest, I_OnSerializeCheck, I_OnSerializableConstruct, I_OnSerializableDestruct, I_OnSerializableUpdate, I_END diff --git a/include/servers.h b/include/servers.h index 94dc5491e..36da6bbd8 100644 --- a/include/servers.h +++ b/include/servers.h @@ -63,6 +63,9 @@ class CoreExport Server : public Flags<ServerFlag> ~Server(); public: + /* Number of users on the server */ + unsigned Users; + /** Delete this server with a reason * @param reason The reason */ diff --git a/modules/commands/os_dns.cpp b/modules/commands/os_dns.cpp new file mode 100644 index 000000000..5a4e3e1c3 --- /dev/null +++ b/modules/commands/os_dns.cpp @@ -0,0 +1,552 @@ +/* + * (C) 2003-2012 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +#include "module.h" + +class DNSServer; +static std::vector<DNSServer *> dns_servers; + +static std::map<Anope::string, std::list<time_t> > server_quit_times; + +class DNSServer : public Serializable +{ + Anope::string server_name; + std::vector<Anope::string> ips; + unsigned limit; + + DNSServer() : Serializable("DNSServer"), limit(0), pooled(false), repool(0) { dns_servers.push_back(this); } + public: + bool pooled; + time_t repool; + + DNSServer(const Anope::string &sn) : Serializable("DNSServer"), server_name(sn), limit(0), pooled(false), repool(0) + { + dns_servers.push_back(this); + } + + ~DNSServer() + { + std::vector<DNSServer *>::iterator it = std::find(dns_servers.begin(), dns_servers.end(), this); + if (it != dns_servers.end()) + dns_servers.erase(it); + } + + const Anope::string &GetName() const { return server_name; } + std::vector<Anope::string> &GetIPs() { return ips; } + unsigned GetLimit() const { return limit; } + void SetLimit(unsigned l) { limit = l; } + + Serialize::Data serialize() const anope_override + { + Serialize::Data data; + + data["server_name"] << server_name; + for (unsigned i = 0; i < ips.size(); ++i) + data["ip" + stringify(i)] << ips[i]; + data["limit"] << limit; + data["pooled"] << pooled; + + return data; + } + + static Serializable* unserialize(Serializable *obj, Serialize::Data &data) + { + DNSServer *req; + + if (obj) + req = anope_dynamic_static_cast<DNSServer *>(obj); + else + req = new DNSServer(); + + data["server_name"] >> req->server_name; + for (unsigned i = 0; data.count("ip" + stringify(i)); ++i) + { + Anope::string ip_str; + data["ip" + stringify(i)] >> ip_str; + req->ips.push_back(ip_str); + } + data["limit"] >> req->limit; + data["pooled"] >> req->pooled; + + return req; + } + + static DNSServer *Find(const Anope::string &s) + { + for (unsigned i = 0; i < dns_servers.size(); ++i) + if (dns_servers[i]->GetName() == s) + return dns_servers[i]; + return NULL; + } +}; + +class CommandOSDNS : public Command +{ + void DisplayPoolState(CommandSource &source) + { + if (dns_servers.empty()) + { + source.Reply(_("There are no configured servers.")); + return; + } + + ListFormatter lf; + lf.addColumn("Server").addColumn("IP").addColumn("Limit").addColumn("State"); + for (unsigned i = 0; i < dns_servers.size(); ++i) + { + DNSServer *s = dns_servers[i]; + Server *srv = Server::Find(s->GetName()); + + ListFormatter::ListEntry entry; + entry["Server"] = s->GetName(); + entry["Limit"] = s->GetLimit() ? stringify(s->GetLimit()) : "None"; + + Anope::string ip_str; + for (unsigned j = 0; j < s->GetIPs().size(); ++j) + ip_str += s->GetIPs()[j] + " "; + ip_str.trim(); + if (ip_str.empty()) + ip_str = "None"; + entry["IP"] = ip_str; + + if (!srv) + entry["State"] = "Split"; + else if (s->pooled) + entry["State"] = "Pooled"; + else + entry["State"] = "Unpooled"; + + lf.addEntry(entry); + } + + std::vector<Anope::string> replies; + lf.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + + void OnAdd(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + DNSServer *s = DNSServer::Find(params[1]); + + if (s) + { + source.Reply(_("Server %s already exists."), params[1].c_str()); + return; + } + + if (!Server::Find(params[1])) + { + source.Reply(_("Server %s is not linked to the network."), params[1].c_str()); + return; + } + + s = new DNSServer(params[1]); + source.Reply(_("Added server %s."), s->GetName().c_str()); + + Log(LOG_ADMIN, source, this) << "to add server " << s->GetName(); + } + + void OnDel(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + DNSServer *s = DNSServer::Find(params[1]); + + if (!s) + { + source.Reply(_("Server %s does not exist."), params[1].c_str()); + return; + } + else if (Server::Find(s->GetName())) + { + source.Reply(_("Server %s must be quit before it can be deleted."), s->GetName().c_str()); + return; + } + + Log(LOG_ADMIN, source, this) << "to delete server " << s->GetName(); + source.Reply(_("Removed server %s."), s->GetName().c_str()); + delete s; + } + + void AddIP(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + DNSServer *s = DNSServer::Find(params[1]); + + if (!s) + { + source.Reply(_("Server %s does not exist."), params[1].c_str()); + return; + } + + for (unsigned i = 0; i < s->GetIPs().size(); ++i) + if (params[2].equals_ci(s->GetIPs()[i])) + { + source.Reply(_("IP %s already exists for %s."), s->GetIPs()[i].c_str(), s->GetName().c_str()); + return; + } + + sockaddrs addr; + try + { + addr.pton(AF_INET, params[2]); + } + catch (const SocketException &) + { + try + { + addr.pton(AF_INET6, params[2]); + } + catch (const SocketException &) + { + source.Reply(_("%s is not a valid IP address."), params[2].c_str()); + return; + } + } + + s->GetIPs().push_back(params[2]); + source.Reply(_("Added IP %s to %s."), params[2].c_str(), s->GetName().c_str()); + Log(LOG_ADMIN, source, this) << "to add IP " << params[2] << " to " << s->GetName(); + } + + void DelIP(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + DNSServer *s = DNSServer::Find(params[1]); + + if (!s) + { + source.Reply(_("Server %s does not exist."), params[1].c_str()); + return; + } + + for (unsigned i = 0; i < s->GetIPs().size(); ++i) + if (params[2].equals_ci(s->GetIPs()[i])) + { + s->GetIPs().erase(s->GetIPs().begin() + i); + source.Reply(_("Removed IP %s from %s."), params[2].c_str(), s->GetName().c_str()); + Log(LOG_ADMIN, source, this) << "to remove IP " << params[2] << " from " << s->GetName(); + + if (s->GetIPs().empty()) + { + s->repool = 0; + s->pooled = false; + } + + return; + } + + source.Reply(_("IP %s does not exist for %s."), params[2].c_str(), s->GetName().c_str()); + } + + void OnSet(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + DNSServer *s = DNSServer::Find(params[1]); + + if (!s) + { + source.Reply(_("Server %s does not exist."), params[1].c_str()); + return; + } + + if (params[2].equals_ci("LIMIT")) + { + try + { + unsigned l = convertTo<unsigned>(params[3]); + s->SetLimit(l); + if (l) + source.Reply(_("User limit for %s set to %d."), s->GetName().c_str(), l); + else + source.Reply(_("User limit for %s removed."), s->GetName().c_str()); + } + 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) + { + DNSServer *s = DNSServer::Find(params[1]); + + if (!s) + { + source.Reply(_("Server %s does not exist."), params[1].c_str()); + return; + } + else if (!Server::Find(s->GetName())) + { + source.Reply(_("Server %s is not currently linked."), s->GetName().c_str()); + return; + } + else if (s->pooled) + { + source.Reply(_("Server %s is already pooled."), s->GetName().c_str()); + return; + } + else if (s->GetIPs().empty()) + { + source.Reply(_("Server %s has no configured IPs."), s->GetName().c_str()); + } + + s->pooled = true; + + source.Reply(_("Pooled %s."), s->GetName().c_str()); + Log(LOG_ADMIN, source, this) << "to pool " << s->GetName(); + } + + + void OnDepool(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + DNSServer *s = DNSServer::Find(params[1]); + + if (!s) + { + source.Reply(_("Server %s does not exist."), params[1].c_str()); + return; + } + else if (!s->pooled) + { + source.Reply(_("Server %s is not pooled."), s->GetName().c_str()); + return; + } + + s->pooled = false; + + source.Reply(_("Depooled %s."), s->GetName().c_str()); + Log(LOG_ADMIN, source, this) << "to depool " << s->GetName(); + } + + public: + CommandOSDNS(Module *creator) : Command(creator, "operserv/dns", 0, 3) + { + this->SetDesc(_("Manage the DNS zone for this network")); + this->SetSyntax(_("ADD \037server.name\037")); + this->SetSyntax(_("DEL \037server.name\037")); + this->SetSyntax(_("ADDIP \037server.name\037 \037ip\037")); + this->SetSyntax(_("DELIP \037server.name\037 \037ip\037")); + this->SetSyntax(_("SET \037server.name\037 \37option\37 \037value\037")); + this->SetSyntax(_("POOL \037server.name\037")); + this->SetSyntax(_("DEPOOL \037server.name\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override + { + if (params.empty()) + this->DisplayPoolState(source); + else if (params[0].equals_ci("ADD") && params.size() > 1) + this->OnAdd(source, params); + else if (params[0].equals_ci("DEL") && params.size() > 1) + this->OnDel(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) anope_override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command allows managing a DNS zone\n" + "used for controlling what servers users\n" + "are directed to when connecting. Omitting\n" + "all parameters prints out the status of\n" + "the DNS zone.\n")); + return true; + } +}; + +class ModuleDNS : public Module +{ + SerializeType dns_type; + CommandOSDNS commandosdns; + + time_t ttl; + int user_drop_mark; + time_t user_drop_time; + time_t user_drop_readd_time; + bool remove_split_servers; + bool readd_connected_servers; + + public: + ModuleDNS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED), + dns_type("DNSServer", DNSServer::unserialize), commandosdns(this) + { + this->SetAuthor("Anope"); + + Implementation i[] = { I_OnReload, I_OnNewServer, I_OnServerQuit, I_OnUserConnect, I_OnUserLogoff, I_OnDnsRequest }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + this->OnReload(); + } + + void OnReload() anope_override + { + ConfigReader config; + + this->ttl = dotime(config.ReadValue("os_dns", "ttl", 0)); + this->user_drop_mark = config.ReadInteger("os_dns", "user_drop_mark", 0, false); + this->user_drop_time = dotime(config.ReadValue("os_dns", "user_drop_time", 0, false)); + this->user_drop_readd_time = dotime(config.ReadValue("os_dns", "user_drop_readd_time", 0, false)); + this->remove_split_servers = config.ReadFlag("os_dns", "remove_split_servers", 0); + this->readd_connected_servers = config.ReadFlag("os_dns", "readd_connected_servers", 0); + } + + void OnNewServer(Server *s) anope_override + { + if (this->readd_connected_servers) + { + DNSServer *dns = DNSServer::Find(s->GetName()); + if (dns && !dns->pooled && !dns->GetIPs().empty() && dns->GetLimit() < s->Users) + { + dns->pooled = true; + Log() << "Pooling server " << s->GetName(); + } + } + } + + + void OnServerQuit(Server *s) anope_override + { + DNSServer *dns = DNSServer::Find(s->GetName()); + if (dns && dns->pooled) + { + dns->pooled = false; + Log() << "Depooling delinked server " << s->GetName(); + } + } + + void OnUserConnect(dynamic_reference<User> &u, bool &exempt) anope_override + { + if (u && u->server) + { + DNSServer *s = DNSServer::Find(u->server->GetName()); + /* Check for user limit reached */ + if (s && s->GetLimit() && s->pooled && u->server->Users >= s->GetLimit()) + { + Log() << "Depooling full server " << s->GetName() << ": " << u->server->Users << " users"; + s->pooled = false; + } + } + } + + void OnUserLogoff(User *u) anope_override + { + if (u && u->server) + { + DNSServer *s = DNSServer::Find(u->server->GetName()); + if (!s) + return; + + /* Check for dropping under userlimit */ + if (s->GetLimit() && !s->pooled && s->GetLimit() > u->server->Users) + { + Log() << "Pooling server " << s->GetName(); + s->pooled = 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 (s->pooled && times.size() == static_cast<unsigned>(this->user_drop_mark)) + { + time_t diff = Anope::CurTime - *times.begin(); + + /* Check for very fast user drops */ + if (diff <= this->user_drop_time) + { + Log() << "Depooling server " << s->GetName() << ": dropped " << this->user_drop_mark << " users in " << diff << " seconds"; + s->repool = Anope::CurTime + this->user_drop_readd_time; + s->pooled = false; + } + /* Check for needing to re-pool a server that dropped users */ + else if (s->repool && s->repool <= Anope::CurTime && !s->pooled) + { + s->pooled = true; + s->repool = 0; + Log() << "Pooling server " << s->GetName(); + } + } + } + } + } + + void OnDnsRequest(DNSPacket &req) anope_override + { + if (req.questions.empty()) + return; + /* Currently we reply to any QR */ + const Question& q = req.questions[0]; + + DNSPacket *packet = new DNSPacket(req); + packet->flags |= DNS_QUERYFLAGS_QR; /* This is a reponse */ + + for (unsigned i = 0; i < dns_servers.size(); ++i) + { + DNSServer *s = dns_servers[i]; + if (!s->pooled) + continue; + + for (unsigned j = 0; j < s->GetIPs().size(); ++j) + { + ResourceRecord rr(q.name, s->GetIPs()[j].find(':') != Anope::string::npos ? DNS_QUERY_AAAA : DNS_QUERY_A); + rr.ttl = this->ttl; + rr.rdata = s->GetIPs()[j]; + packet->answers.push_back(rr); + } + } + + if (packet->answers.empty()) + { + static time_t last_warn = 0; + if (last_warn + 60 < Anope::CurTime) + { + last_warn = Anope::CurTime; + Log() << "os_dns: Warning! There are no pooled servers!"; + } + + /* Something messed up, just return them all and hope one is available */ + for (unsigned i = 0; i < dns_servers.size(); ++i) + { + DNSServer *s = dns_servers[i]; + + for (unsigned j = 0; j < s->GetIPs().size(); ++j) + { + ResourceRecord rr(q.name, s->GetIPs()[j].find(':') != Anope::string::npos ? DNS_QUERY_AAAA : DNS_QUERY_A); + rr.ttl = this->ttl; + rr.rdata = s->GetIPs()[j]; + packet->answers.push_back(rr); + } + } + + if (packet->answers.empty()) + { + Log() << "os_dns: Error! There are no servers with any IPs. At all."; + /* Send back an empty answer anyway */ + } + } + + DNSEngine->SendPacket(packet); + } +}; + +MODULE_INIT(ModuleDNS) diff --git a/src/config.cpp b/src/config.cpp index b90c8598a..6347754fb 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -17,6 +17,14 @@ #include "opertype.h" #include "channels.h" #include "hashcomp.h" +#include "dns.h" + +#ifndef _WIN32 +#include <errno.h> +#include <sys/types.h> +#include <pwd.h> +#include <grp.h> +#endif /*************************************************************************/ @@ -200,6 +208,9 @@ ServerConfig::ServerConfig() : config_data(), NSDefFlags(NickCoreFlagStrings), C this->NameServer = "127.0.0.1"; } } + if (DNSEngine) + DNSEngine->SetFlag(SF_DEAD); + DNSEngine = new DNSManager(this->NameServer, this->DNSPort); if (this->CaseMap == "ascii") Anope::casemap = std::locale(std::locale(), new Anope::ascii_ctype<char>()); @@ -219,6 +230,31 @@ ServerConfig::ServerConfig() : config_data(), NSDefFlags(NickCoreFlagStrings), C if (this->SessionIPv4CIDR > 32 || this->SessionIPv6CIDR > 128) throw ConfigException("Session CIDR value out of range"); + +#ifndef _WIN32 + if (!this->User.empty()) + { + errno = 0; + struct passwd *u = getpwnam(this->User.c_str()); + if (u == NULL) + Log() << "Unable to setuid to " << this->User << ": " << Anope::LastError(); + else if (setuid(u->pw_uid) == -1) + Log() << "Unable to setuid to " << this->User << ": " << Anope::LastError(); + else + Log() << "Successfully set user to " << this->User; + } + if (!this->Group.empty()) + { + errno = 0; + struct group *g = getgrnam(this->Group.c_str()); + if (g == NULL) + Log() << "Unable to setgid to " << this->Group << ": " << Anope::LastError(); + else if (setuid(g->gr_gid) == -1) + Log() << "Unable to setgid to " << this->Group << ": " << Anope::LastError(); + else + Log() << "Successfully set group to " << this->Group; + } +#endif } bool ServerConfig::CheckOnce(const Anope::string &tag) @@ -1191,6 +1227,8 @@ ConfigItems::ConfigItems(ServerConfig *conf) {"networkinfo", "userlen", "10", new ValueContainerUInt(&conf->UserLen), DT_UINTEGER | DT_NORELOAD, NoValidation}, {"networkinfo", "hostlen", "64", new ValueContainerUInt(&conf->HostLen), DT_UINTEGER | DT_NORELOAD, NoValidation}, {"networkinfo", "chanlen", "32", new ValueContainerUInt(&conf->ChanLen), DT_UINTEGER | DT_NORELOAD, NoValidation}, + {"options", "user", "", new ValueContainerString(&conf->User), DT_STRING, NoValidation}, + {"options", "group", "", new ValueContainerString(&conf->Group), DT_STRING, NoValidation}, {"options", "casemap", "ascii", new ValueContainerString(&conf->CaseMap), DT_STRING, NoValidation}, {"options", "passlen", "32", new ValueContainerUInt(&conf->PassLen), DT_UINTEGER | DT_NORELOAD, NoValidation}, {"options", "seed", "0", new ValueContainerLUInt(&conf->Seed), DT_LUINTEGER, NoValidation}, @@ -1264,6 +1302,7 @@ ConfigItems::ConfigItems(ServerConfig *conf) {"mail", "memo_message", "", new ValueContainerString(&conf->MailMemoMessage), DT_STRING | DT_ALLOW_NEWLINE, ValidateMail}, {"dns", "nameserver", "127.0.0.1", new ValueContainerString(&conf->NameServer), DT_STRING, NoValidation}, {"dns", "timeout", "5", new ValueContainerTime(&conf->DNSTimeout), DT_TIME, NoValidation}, + {"dns", "port", "53", new ValueContainerInt(&conf->DNSPort), DT_INTEGER, NoValidation}, {"chanserv", "name", "", new ValueContainerString(&conf->ChanServ), DT_STRING, NoValidation}, {"chanserv", "defaults", "keeptopic secure securefounder signkick", new ValueContainerString(&CSDefaults), DT_STRING, ValidateChanServ}, {"chanserv", "maxregistered", "0", new ValueContainerUInt(&conf->CSMaxReg), DT_UINTEGER, ValidateChanServ}, diff --git a/src/dns.cpp b/src/dns.cpp index f21453ddd..519e02b8f 100644 --- a/src/dns.cpp +++ b/src/dns.cpp @@ -56,20 +56,11 @@ DNSQuery::DNSQuery(const Question &q) this->error = DNS_ERROR_NONE; } -DNSQuery::DNSQuery(const DNSPacket &p) -{ - this->questions = p.questions; - this->answers = p.answers; - this->authorities = p.authorities; - this->additional = p.additional; - this->error = DNS_ERROR_NONE; -} - DNSRequest::DNSRequest(const Anope::string &addr, QueryType qt, bool cache, Module *c) : Timer(Config->DNSTimeout), Question(addr, qt), use_cache(cache), id(0), creator(c) { if (!DNSEngine) - DNSEngine = new DNSManager(Config->NameServer, DNSManager::DNSPort); - if (DNSEngine->packets.size() == 65535) + throw SocketException("No DNSEngine"); + if (DNSEngine->GetPackets().size() == 65535) throw SocketException("DNS queue full"); do @@ -101,14 +92,12 @@ void DNSRequest::Process() return; } - DNSPacket *p = new DNSPacket(); + DNSPacket *p = new DNSPacket(DNSEngine->addrs); p->flags = DNS_QUERYFLAGS_RD; p->id = this->id; p->questions.push_back(*this); - DNSEngine->packets.push_back(p); - - SocketEngine::MarkWritable(DNSEngine); + DNSEngine->SendPacket(p); } void DNSRequest::OnError(const DNSQuery *r) @@ -239,12 +228,12 @@ ResourceRecord DNSPacket::UnpackResourceRecord(const unsigned char *input, unsig if (pos + 4 > input_size) throw SocketException("Unable to unpack resource record"); - in_addr addr; - addr.s_addr = input[pos] | (input[pos + 1] << 8) | (input[pos + 2] << 16) | (input[pos + 3] << 24); + in_addr a; + a.s_addr = input[pos] | (input[pos + 1] << 8) | (input[pos + 2] << 16) | (input[pos + 3] << 24); pos += 4; sockaddrs addrs; - addrs.ntop(AF_INET, &addr); + addrs.ntop(AF_INET, &a); record.rdata = addrs.addr(); break; @@ -254,13 +243,13 @@ ResourceRecord DNSPacket::UnpackResourceRecord(const unsigned char *input, unsig if (pos + 16 > input_size) throw SocketException("Unable to unpack resource record"); - in6_addr addr; + in6_addr a; for (int j = 0; j < 16; ++j) - addr.s6_addr[j] = input[pos + j]; + a.s6_addr[j] = input[pos + j]; pos += 16; sockaddrs addrs; - addrs.ntop(AF_INET6, &addr); + addrs.ntop(AF_INET6, &a); record.rdata = addrs.addr(); break; @@ -280,9 +269,8 @@ ResourceRecord DNSPacket::UnpackResourceRecord(const unsigned char *input, unsig return record; } -DNSPacket::DNSPacket() : DNSQuery() +DNSPacket::DNSPacket(const sockaddrs &a) : DNSQuery(), addr(a), id(0), flags(0) { - this->id = this->flags = 0; } void DNSPacket::Fill(const unsigned char *input, const unsigned short len) @@ -426,13 +414,13 @@ unsigned short DNSPacket::Pack(unsigned char *output, unsigned short output_size if (pos + 6 > output_size) throw SocketException("Unable to pack packet"); - sockaddrs addr(rr.rdata); + sockaddrs a(rr.rdata); s = htons(4); memcpy(&output[pos], &s, 2); pos += 2; - memcpy(&output[pos], &addr.sa4.sin_addr, 4); + memcpy(&output[pos], &a.sa4.sin_addr, 4); pos += 4; break; } @@ -441,13 +429,13 @@ unsigned short DNSPacket::Pack(unsigned char *output, unsigned short output_size if (pos + 18 > output_size) throw SocketException("Unable to pack packet"); - sockaddrs addr(rr.rdata); + sockaddrs a(rr.rdata); s = htons(16); memcpy(&output[pos], &s, 2); pos += 2; - memcpy(&output[pos], &addr.sa6.sin6_addr, 16); + memcpy(&output[pos], &a.sa6.sin6_addr, 16); pos += 16; break; } @@ -477,6 +465,15 @@ unsigned short DNSPacket::Pack(unsigned char *output, unsigned short output_size DNSManager::DNSManager(const Anope::string &nameserver, int port) : Timer(300, Anope::CurTime, true), Socket(-1, nameserver.find(':') != Anope::string::npos, SOCK_DGRAM) { this->addrs.pton(this->IPv6 ? AF_INET6 : AF_INET, nameserver, port); + try + { + this->Bind("0.0.0.0", port); + } + catch (const SocketException &ex) + { + /* This error can be from normal operation as most people don't use services to handle DNS queries, so put it in debug log */ + Log(LOG_DEBUG) << "Unable to bind DNSManager to port " << port << ": " << ex.GetReason(); + } } DNSManager::~DNSManager() @@ -514,13 +511,7 @@ bool DNSManager::ProcessRead() if (length < DNSPacket::HEADER_LENGTH) return true; - if (this->addrs != from_server) - { - Log(LOG_DEBUG_2) << "Resolver: Received an answer from the wrong nameserver, Bad NAT or DNS forging attempt? '" << this->addrs.addr() << "' != '" << from_server.addr() << "'"; - return true; - } - - DNSPacket recv_packet; + DNSPacket recv_packet(from_server); try { @@ -532,6 +523,18 @@ bool DNSManager::ProcessRead() return true; } + if (!(recv_packet.flags & DNS_QUERYFLAGS_QR)) + { + FOREACH_MOD(I_OnDnsRequest, OnDnsRequest(recv_packet)); + return true; + } + + if (this->addrs != from_server) + { + Log(LOG_DEBUG_2) << "Resolver: Received an answer from the wrong nameserver, Bad NAT or DNS forging attempt? '" << this->addrs.addr() << "' != '" << from_server.addr() << "'"; + return true; + } + std::map<unsigned short, DNSRequest *>::iterator it = DNSEngine->requests.find(recv_packet.id); if (it == DNSEngine->requests.end()) { @@ -540,13 +543,7 @@ bool DNSManager::ProcessRead() } DNSRequest *request = it->second; - if (!(recv_packet.flags & DNS_QUERYFLAGS_QR)) - { - Log(LOG_DEBUG_2) << "Resolver: Received a non-answer"; - recv_packet.error = DNS_ERROR_NOT_AN_ANSWER; - request->OnError(&recv_packet); - } - else if (recv_packet.flags & DNS_QUERYFLAGS_OPCODE) + if (recv_packet.flags & DNS_QUERYFLAGS_OPCODE) { Log(LOG_DEBUG_2) << "Resolver: Received a nonstandard query"; recv_packet.error = DNS_ERROR_NONSTANDARD_QUERY; @@ -614,7 +611,7 @@ bool DNSManager::ProcessWrite() unsigned char buffer[524]; unsigned short len = r->Pack(buffer, sizeof(buffer)); - sendto(this->GetFD(), reinterpret_cast<char *>(buffer), len, 0, &this->addrs.sa, this->addrs.size()); + sendto(this->GetFD(), reinterpret_cast<char *>(buffer), len, 0, &r->addr.sa, r->addr.size()); } catch (const SocketException &) { } @@ -698,6 +695,19 @@ void DNSManager::Cleanup(Module *mod) } } +std::deque<DNSPacket *>& DNSManager::GetPackets() +{ + return this->packets; +} + +void DNSManager::SendPacket(DNSPacket *p) +{ + Log(LOG_DEBUG_2) << "Resolver: Queueing packet " << p->id; + this->packets.push_back(p); + + SocketEngine::MarkWritable(this); +} + DNSQuery DNSManager::BlockingQuery(const Anope::string &mask, QueryType qt) { Question question(mask, qt); diff --git a/src/init.cpp b/src/init.cpp index 4f0f8bac3..c2c44ec16 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -422,6 +422,9 @@ void Init(int ac, char **av) Log(LOG_TERMINAL) << "Using configuration file " << conf_dir << "/" << services_conf.GetName(); #endif + /* Initialize the socket engine */ + SocketEngine::Init(); + /* Read configuration file; exit if there are problems. */ try { @@ -441,6 +444,15 @@ void Init(int ac, char **av) if (!SupportedWindowsVersion()) throw FatalException(GetWindowsVersion() + " is not a supported version of Windows"); #else + /* If we're root, issue a warning now */ + if (!getuid() && !getgid()) + { + std::cerr << "WARNING: You are currently running Anope as the root superuser. Anope does not" << std::endl; + std::cerr << " require root privileges to run, and it is discouraged that you run Anope" << std::endl; + std::cerr << " as the root superuser." << std::endl; + sleep(3); + } + if (!nofork && AtTerm()) { /* Install these before fork() - it is possible for the child to @@ -478,16 +490,16 @@ void Init(int ac, char **av) } #endif - /* Initialize the socket engine */ - SocketEngine::Init(); - /* Write our PID to the PID file. */ write_pidfile(); /* Create me */ Me = new Server(NULL, Config->ServerName, 0, Config->ServerDesc, Config->Numeric); for (botinfo_map::const_iterator it = BotListByNick->begin(), it_end = BotListByNick->end(); it != it_end; ++it) + { it->second->server = Me; + ++Me->Users; + } /* Announce ourselves to the logfile. */ Log() << "Anope " << Anope::Version() << " starting up" << (debug || readonly ? " (options:" : "") << (debug ? " debug" : "") << (readonly ? " readonly" : "") << (debug || readonly ? ")" : ""); diff --git a/src/main.cpp b/src/main.cpp index d43bda2bc..26e7e67ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -362,17 +362,6 @@ Anope::string GetFullProgDir(const Anope::string &argv0) int main(int ac, char **av, char **envp) { -#ifndef _WIN32 - /* If we're root, issue a warning now */ - if (!getuid() && !getgid()) - { - std::cerr << "WARNING: You are currently running Anope as the root superuser. Anope does not" << std::endl; - std::cerr << " require root privileges to run, and it is discouraged that you run Anope" << std::endl; - std::cerr << " as the root superuser." << std::endl; - sleep(3); - } -#endif - binary_dir = GetFullProgDir(av[0]); if (binary_dir[binary_dir.length() - 1] == '.') binary_dir = binary_dir.substr(0, binary_dir.length() - 2); diff --git a/src/users.cpp b/src/users.cpp index fac00d723..187e55808 100644 --- a/src/users.cpp +++ b/src/users.cpp @@ -66,7 +66,10 @@ User::User(const Anope::string &snick, const Anope::string &sident, const Anope: this->nc = NULL; if (sserver) // Our bots are introduced on startup with no server + { + ++sserver->Users; Log(this, "connect") << (!svhost.empty() ? Anope::string("(") + svhost + ") " : "") << "(" << srealname << ") " << sip << " connected to the network (" << sserver->GetName() << ")"; + } ++usercnt; if (usercnt > maxusercnt) @@ -241,7 +244,7 @@ User::~User() Log(LOG_DEBUG_2) << "User::~User() called"; Log(this, "disconnect") << "(" << this->realname << ") " << "disconnected from the network (" << this->server->GetName() << ")"; - + --this->server->Users; FOREACH_MOD(I_OnUserLogoff, OnUserLogoff(this)); |