diff options
-rw-r--r-- | data/example.conf | 15 | ||||
-rw-r--r-- | data/operserv.example.conf | 22 | ||||
-rw-r--r-- | include/config.h | 6 | ||||
-rw-r--r-- | include/dns.h | 89 | ||||
-rw-r--r-- | include/sockets.h | 2 | ||||
-rw-r--r-- | modules/commands/os_dns.cpp | 61 | ||||
-rw-r--r-- | modules/extra/httpd.h | 4 | ||||
-rw-r--r-- | modules/extra/m_httpd.cpp | 2 | ||||
-rw-r--r-- | modules/extra/m_proxyscan.cpp | 2 | ||||
-rw-r--r-- | modules/extra/m_xmlrpc.cpp | 2 | ||||
-rw-r--r-- | src/config.cpp | 6 | ||||
-rw-r--r-- | src/dns.cpp | 338 | ||||
-rw-r--r-- | src/sockets.cpp | 2 |
13 files changed, 431 insertions, 120 deletions
diff --git a/data/example.conf b/data/example.conf index 8330eeb78..54bf09f2a 100644 --- a/data/example.conf +++ b/data/example.conf @@ -1037,7 +1037,7 @@ mail * [OPTIONAL] DNS Config * * This section is used to configure DNS. - * At this time DNS is only used by a few modules (m_dnsbl) + * At this time DNS is only used by a few modules (m_dnsbl and os_dns) * and is not required by the core to function. */ dns @@ -1055,6 +1055,8 @@ dns */ timeout = 5 + /* Only edit below if you are expecting to use os_dns */ + /* * The IP and port services use to listen for DNS queries. * Note that ports less than 1024 are privileged on UNIX/Linux systems, and @@ -1064,6 +1066,17 @@ dns */ ip = "0.0.0.0" port = 53 + + /* + * SOA record information. + */ + admin = "admin@example.com" + /* This should be the name of the public facing nameserver serving the records */ + primary_nameserver = "ns1.example.com" + /* The time slave servers are allowed to cache. This should be reasonably low + * if you want your records to be updated without much delay. + */ + refresh = 3600 } /* diff --git a/data/operserv.example.conf b/data/operserv.example.conf index 167d13c04..8a887c5ea 100644 --- a/data/operserv.example.conf +++ b/data/operserv.example.conf @@ -366,21 +366,21 @@ defcon * 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: + * Alternatively, you may use a slave DNS server to hide service's IP, + * provide query caching, and provide better fault tolerance. + * + * To do this using BIND, configure 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 + * type slave; + * masters { 127.0.0.1 port 5353; }; * }; * - * And then set a NS record for irc.example.com. to BIND. + * Where 127.0.0.1:5353 is the IP and port services are listening on. + * We recommend you externally firewall both UDP and TCP to the port + * Anope is listening on. + * + * Finally set a NS record for irc.example.com. to BIND or services. */ #module { name = "os_dns" } #command { service = "OperServ"; name = "DNS"; command = "operserv/dns"; permission = "operserv/dns"; } diff --git a/include/config.h b/include/config.h index 5851247e8..d1e7210e0 100644 --- a/include/config.h +++ b/include/config.h @@ -503,6 +503,12 @@ class CoreExport ServerConfig /* The IP/port DNS queries come in on */ Anope::string DNSIP; int DNSPort; + /* DNS SOA admin */ + Anope::string DNSSOAAdmin; + /* DNS SOA primary NS */ + Anope::string DNSSOANS; + /* SOA Refresh time */ + unsigned DNSSOARefresh; /* 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 bb57945f3..63882a075 100644 --- a/include/dns.h +++ b/include/dns.h @@ -27,12 +27,18 @@ enum QueryType DNS_QUERY_NONE, /* A simple A lookup */ DNS_QUERY_A = 1, + /* An authoritative name server */ + DNS_QUERY_NS = 2, /* A CNAME lookup */ DNS_QUERY_CNAME = 5, + /* Start of a zone of authority */ + DNS_QUERY_SOA = 6, /* Reverse DNS lookup */ DNS_QUERY_PTR = 12, /* IPv6 AAAA lookup */ - DNS_QUERY_AAAA = 28 + DNS_QUERY_AAAA = 28, + /* Zone transfer */ + DNS_QUERY_AXFR = 252 }; /** Flags that can be AND'd into DNSPacket::flags to receive certain values @@ -145,30 +151,85 @@ class DNSPacket : public DNSQuery /* Flags on the packet */ unsigned short flags; - DNSPacket(const sockaddrs &a); + DNSPacket(sockaddrs *a); void Fill(const unsigned char *input, const unsigned short len); unsigned short Pack(unsigned char *output, unsigned short output_size); }; -/** DNS manager, manages all requests +/** DNS manager */ -class CoreExport DNSManager : public Timer, public Socket +class CoreExport DNSManager : public Timer { + class ReplySocket : public virtual Socket + { + public: + virtual ~ReplySocket() { } + virtual void Reply(DNSPacket *p) = 0; + }; + + /* Listens for TCP requests */ + class TCPSocket : public ListenSocket + { + /* A TCP client */ + class Client : public ClientSocket, public Timer, public ReplySocket + { + TCPSocket *tcpsock; + DNSPacket *packet; + unsigned char packet_buffer[524]; + int length; + + public: + Client(TCPSocket *ls, int fd, const sockaddrs &addr); + ~Client(); + + /* Times out after a few seconds */ + void Tick(time_t) anope_override { } + void Reply(DNSPacket *p) anope_override; + bool ProcessRead() anope_override; + bool ProcessWrite() anope_override; + }; + + public: + TCPSocket(const Anope::string &ip, int port); + + ClientSocket *OnAccept(int fd, const sockaddrs &addr) anope_override; + }; + + /* Listens for UDP requests */ + class UDPSocket : public ReplySocket + { + std::deque<DNSPacket *> packets; + public: + + UDPSocket(const Anope::string &ip, int port); + ~UDPSocket(); + + void Reply(DNSPacket *p) anope_override; + + std::deque<DNSPacket *>& GetPackets() { return packets; } + + bool ProcessRead() anope_override; + + bool ProcessWrite() anope_override; + }; + typedef std::multimap<Anope::string, ResourceRecord, ci::less> cache_map; cache_map cache; - std::deque<DNSPacket *> packets; + uint32_t serial; + int last_year, last_day, last_num; + public: + TCPSocket *tcpsock; + UDPSocket *udpsock; + sockaddrs addrs; std::map<unsigned short, DNSRequest *> requests; DNSManager(const Anope::string &nameserver, const Anope::string &ip, int port); - ~DNSManager(); - bool ProcessRead() anope_override; - - bool ProcessWrite() anope_override; + bool HandlePacket(ReplySocket *s, const unsigned char *const data, int len, sockaddrs *from); /** Add a record to the dns cache * @param r The record @@ -189,14 +250,8 @@ 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); + void UpdateSerial(); + uint32_t GetSerial() const; /** Does a BLOCKING DNS query and returns the first IP. * Only use this if you know what you are doing. Unless you specifically diff --git a/include/sockets.h b/include/sockets.h index 6b1fca441..ea59346db 100644 --- a/include/sockets.h +++ b/include/sockets.h @@ -358,7 +358,7 @@ class CoreExport BinarySocket : public virtual Socket virtual bool Read(const char *buffer, size_t l); }; -class CoreExport ListenSocket : public Socket +class CoreExport ListenSocket : public virtual Socket { public: /** Constructor diff --git a/modules/commands/os_dns.cpp b/modules/commands/os_dns.cpp index d1c20f0a4..d0a56e1e4 100644 --- a/modules/commands/os_dns.cpp +++ b/modules/commands/os_dns.cpp @@ -17,10 +17,10 @@ class DNSServer : public Serializable Anope::string server_name; std::vector<Anope::string> ips; unsigned limit; + bool pooled; 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) @@ -39,6 +39,15 @@ class DNSServer : public Serializable std::vector<Anope::string> &GetIPs() { return ips; } unsigned GetLimit() const { return limit; } void SetLimit(unsigned l) { limit = l; } + bool Pooled() const { return pooled; } + + void Pool(bool p) + { + pooled = p; + if (DNSEngine) + DNSEngine->UpdateSerial(); + } + Serialize::Data serialize() const anope_override { @@ -115,7 +124,7 @@ class CommandOSDNS : public Command if (!srv) entry["State"] = "Split"; - else if (s->pooled) + else if (s->Pooled()) entry["State"] = "Pooled"; else entry["State"] = "Unpooled"; @@ -210,6 +219,9 @@ class CommandOSDNS : public Command 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(); + + if (s->Pooled()) + DNSEngine->UpdateSerial(); } void DelIP(CommandSource &source, const std::vector<Anope::string> ¶ms) @@ -232,9 +244,12 @@ class CommandOSDNS : public Command if (s->GetIPs().empty()) { s->repool = 0; - s->pooled = false; + s->Pool(false); } + if (s->Pooled()) + DNSEngine->UpdateSerial(); + return; } @@ -285,7 +300,7 @@ class CommandOSDNS : public Command source.Reply(_("Server %s is not currently linked."), s->GetName().c_str()); return; } - else if (s->pooled) + else if (s->Pooled()) { source.Reply(_("Server %s is already pooled."), s->GetName().c_str()); return; @@ -295,7 +310,7 @@ class CommandOSDNS : public Command source.Reply(_("Server %s has no configured IPs."), s->GetName().c_str()); } - s->pooled = true; + s->Pool(true); source.Reply(_("Pooled %s."), s->GetName().c_str()); Log(LOG_ADMIN, source, this) << "to pool " << s->GetName(); @@ -311,13 +326,13 @@ class CommandOSDNS : public Command source.Reply(_("Server %s does not exist."), params[1].c_str()); return; } - else if (!s->pooled) + else if (!s->Pooled()) { source.Reply(_("Server %s is not pooled."), s->GetName().c_str()); return; } - s->pooled = false; + s->Pool(false); source.Reply(_("Depooled %s."), s->GetName().c_str()); Log(LOG_ADMIN, source, this) << "to depool " << s->GetName(); @@ -412,9 +427,9 @@ class ModuleDNS : public Module if (this->readd_connected_servers) { DNSServer *dns = DNSServer::Find(s->GetName()); - if (dns && !dns->pooled && !dns->GetIPs().empty() && dns->GetLimit() < s->Users) + if (dns && !dns->Pooled() && !dns->GetIPs().empty() && dns->GetLimit() < s->Users) { - dns->pooled = true; + dns->Pool(true); Log() << "Pooling server " << s->GetName(); } } @@ -424,9 +439,9 @@ class ModuleDNS : public Module void OnServerQuit(Server *s) anope_override { DNSServer *dns = DNSServer::Find(s->GetName()); - if (dns && dns->pooled) + if (dns && dns->Pooled()) { - dns->pooled = false; + dns->Pool(false); Log() << "Depooling delinked server " << s->GetName(); } } @@ -437,10 +452,10 @@ class ModuleDNS : public Module { DNSServer *s = DNSServer::Find(u->server->GetName()); /* Check for user limit reached */ - if (s && s->GetLimit() && s->pooled && u->server->Users >= s->GetLimit()) + if (s && s->GetLimit() && s->Pooled() && u->server->Users >= s->GetLimit()) { Log() << "Depooling full server " << s->GetName() << ": " << u->server->Users << " users"; - s->pooled = false; + s->Pool(false); } } } @@ -454,10 +469,10 @@ class ModuleDNS : public Module return; /* Check for dropping under userlimit */ - if (s->GetLimit() && !s->pooled && s->GetLimit() > u->server->Users) + if (s->GetLimit() && !s->Pooled() && s->GetLimit() > u->server->Users) { Log() << "Pooling server " << s->GetName(); - s->pooled = true; + s->Pool(true); } if (this->user_drop_mark > 0) @@ -467,7 +482,7 @@ class ModuleDNS : public Module 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)) + if (s->Pooled() && times.size() == static_cast<unsigned>(this->user_drop_mark)) { time_t diff = Anope::CurTime - *times.begin(); @@ -476,12 +491,12 @@ class ModuleDNS : public Module { 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; + s->Pool(false); } /* Check for needing to re-pool a server that dropped users */ - else if (s->repool && s->repool <= Anope::CurTime && !s->pooled) + else if (s->repool && s->repool <= Anope::CurTime && !s->Pooled()) { - s->pooled = true; + s->Pool(true); s->repool = 0; Log() << "Pooling server " << s->GetName(); } @@ -496,20 +511,20 @@ class ModuleDNS : public Module return; /* Currently we reply to any QR for A/AAAA */ const Question& q = req.questions[0]; - if (q.type != DNS_QUERY_A && q.type != DNS_QUERY_AAAA) + if (q.type != DNS_QUERY_A && q.type != DNS_QUERY_AAAA && q.type != DNS_QUERY_AXFR) return; for (unsigned i = 0; i < dns_servers.size(); ++i) { DNSServer *s = dns_servers[i]; - if (!s->pooled) + if (!s->Pooled()) continue; for (unsigned j = 0; j < s->GetIPs().size(); ++j) { QueryType q_type = s->GetIPs()[j].find(':') != Anope::string::npos ? DNS_QUERY_AAAA : DNS_QUERY_A; - if (q_type == q.type) + if (q.type == DNS_QUERY_AXFR || q_type == q.type) { ResourceRecord rr(q.name, q_type); rr.ttl = this->ttl; @@ -537,7 +552,7 @@ class ModuleDNS : public Module { QueryType q_type = s->GetIPs()[j].find(':') != Anope::string::npos ? DNS_QUERY_AAAA : DNS_QUERY_A; - if (q_type == q.type) + if (q.type == DNS_QUERY_AXFR || q_type == q.type) { ResourceRecord rr(q.name, q_type); rr.ttl = this->ttl; diff --git a/modules/extra/httpd.h b/modules/extra/httpd.h index d9e800d13..490bf72bb 100644 --- a/modules/extra/httpd.h +++ b/modules/extra/httpd.h @@ -142,7 +142,7 @@ class HTTPClient : public ClientSocket, public BufferedSocket, public BinarySock virtual void SendReply(HTTPReply *) = 0; }; -class HTTPProvider : public Service, public ListenSocket +class HTTPProvider : public ListenSocket, public Service { Anope::string ip; unsigned short port; @@ -150,7 +150,7 @@ class HTTPProvider : public Service, public ListenSocket Anope::string ext_ip; std::vector<Anope::string> ext_headers; - HTTPProvider(Module *c, const Anope::string &n, const Anope::string &i, const unsigned short p) : Service(c, "HTTPProvider", n), ListenSocket(i, p, i.find(':') != Anope::string::npos), ip(i), port(p) { } + HTTPProvider(Module *c, const Anope::string &n, const Anope::string &i, const unsigned short p) : ListenSocket(i, p, i.find(':') != Anope::string::npos), Service(c, "HTTPProvider", n) { } const Anope::string &GetIP() const { diff --git a/modules/extra/m_httpd.cpp b/modules/extra/m_httpd.cpp index e802ed055..357e9fba9 100644 --- a/modules/extra/m_httpd.cpp +++ b/modules/extra/m_httpd.cpp @@ -257,7 +257,7 @@ class MyHTTPProvider : public HTTPProvider, public CallBack std::list<dynamic_reference<MyHTTPClient> > clients; public: - MyHTTPProvider(Module *c, const Anope::string &n, const Anope::string &i, const unsigned short p, const int t) : HTTPProvider(c, n, i, p), CallBack(c, 10, Anope::CurTime, true), timeout(t) { } + MyHTTPProvider(Module *c, const Anope::string &n, const Anope::string &i, const unsigned short p, const int t) : Socket(-1, i.find(':') != Anope::string::npos), HTTPProvider(c, n, i, p), CallBack(c, 10, Anope::CurTime, true), timeout(t) { } void Tick(time_t) anope_override { diff --git a/modules/extra/m_proxyscan.cpp b/modules/extra/m_proxyscan.cpp index e6ee4008a..00519c57f 100644 --- a/modules/extra/m_proxyscan.cpp +++ b/modules/extra/m_proxyscan.cpp @@ -41,7 +41,7 @@ class ProxyCallbackListener : public ListenSocket }; public: - ProxyCallbackListener(const Anope::string &b, int p) : ListenSocket(b, p, false) + ProxyCallbackListener(const Anope::string &b, int p) : Socket(-1, b.find(':') != Anope::string::npos), ListenSocket(b, p, false) { } diff --git a/modules/extra/m_xmlrpc.cpp b/modules/extra/m_xmlrpc.cpp index 138f73a2a..a804c35d2 100644 --- a/modules/extra/m_xmlrpc.cpp +++ b/modules/extra/m_xmlrpc.cpp @@ -88,7 +88,7 @@ class MyXMLRPCClientSocket : public XMLRPCClientSocket class MyXMLRPCListenSocket : public XMLRPCListenSocket { public: - MyXMLRPCListenSocket(const Anope::string &bindip, int port, bool ipv6, const Anope::string &u, const Anope::string &p, const std::vector<Anope::string> &a) : XMLRPCListenSocket(bindip, port, ipv6, u, p, a) + MyXMLRPCListenSocket(const Anope::string &bindip, int port, bool ipv6, const Anope::string &u, const Anope::string &p, const std::vector<Anope::string> &a) : Socket(-1, ipv6), XMLRPCListenSocket(bindip, port, ipv6, u, p, a) { listen_sockets.push_back(this); } diff --git a/src/config.cpp b/src/config.cpp index 6dc19e83a..b8ee7d5a2 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -208,8 +208,7 @@ ServerConfig::ServerConfig() : config_data(), NSDefFlags(NickCoreFlagStrings), C this->NameServer = "127.0.0.1"; } } - if (DNSEngine) - DNSEngine->SetFlag(SF_DEAD); + delete DNSEngine; DNSEngine = new DNSManager(this->NameServer, this->DNSIP, this->DNSPort); if (this->CaseMap == "ascii") @@ -1304,6 +1303,9 @@ ConfigItems::ConfigItems(ServerConfig *conf) {"dns", "timeout", "5", new ValueContainerTime(&conf->DNSTimeout), DT_TIME, NoValidation}, {"dns", "ip", "0.0.0.0", new ValueContainerString(&conf->DNSIP), DT_STRING, NoValidation}, {"dns", "port", "53", new ValueContainerInt(&conf->DNSPort), DT_INTEGER, NoValidation}, + {"dns", "admin", "admin@example.com", new ValueContainerString(&conf->DNSSOAAdmin), DT_STRING, NoValidation}, + {"dns", "primary_nameserver", "ns1.example.com", new ValueContainerString(&conf->DNSSOANS), DT_STRING, NoValidation}, + {"dns", "refresh", "3600", new ValueContainerUInt(&conf->DNSSOARefresh), DT_UINTEGER, 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 7c62b594a..a27526a2e 100644 --- a/src/dns.cpp +++ b/src/dns.cpp @@ -58,9 +58,9 @@ DNSQuery::DNSQuery(const Question &q) 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) + if (!DNSEngine || !DNSEngine->udpsock) throw SocketException("No DNSEngine"); - if (DNSEngine->GetPackets().size() == 65535) + if (DNSEngine->udpsock->GetPackets().size() == 65535) throw SocketException("DNS queue full"); do @@ -82,7 +82,7 @@ void DNSRequest::Process() { Log(LOG_DEBUG_2) << "Resolver: Processing request to lookup " << this->name << ", of type " << this->type; - if (!DNSEngine) + if (!DNSEngine || !DNSEngine->udpsock) throw SocketException("DNSEngine has not been initialized"); if (this->use_cache && DNSEngine->CheckCache(this)) @@ -92,12 +92,12 @@ void DNSRequest::Process() return; } - DNSPacket *p = new DNSPacket(DNSEngine->addrs); + DNSPacket *p = new DNSPacket(&DNSEngine->addrs); p->flags = DNS_QUERYFLAGS_RD; p->id = this->id; p->questions.push_back(*this); - DNSEngine->SendPacket(p); + DNSEngine->udpsock->Reply(p); } void DNSRequest::OnError(const DNSQuery *r) @@ -269,8 +269,10 @@ ResourceRecord DNSPacket::UnpackResourceRecord(const unsigned char *input, unsig return record; } -DNSPacket::DNSPacket(const sockaddrs &a) : DNSQuery(), addr(a), id(0), flags(0) +DNSPacket::DNSPacket(sockaddrs *a) : DNSQuery(), id(0), flags(0) { + if (a) + addr = *a; } void DNSPacket::Fill(const unsigned char *input, const unsigned short len) @@ -439,6 +441,7 @@ unsigned short DNSPacket::Pack(unsigned char *output, unsigned short output_size pos += 16; break; } + case DNS_QUERY_NS: case DNS_QUERY_CNAME: case DNS_QUERY_PTR: { @@ -450,8 +453,47 @@ unsigned short DNSPacket::Pack(unsigned char *output, unsigned short output_size this->PackName(output, output_size, pos, rr.rdata); - i = htons(pos - packet_pos_save - 2); - memcpy(&output[packet_pos_save], &i, 2); + s = htons(pos - packet_pos_save - 2); + memcpy(&output[packet_pos_save], &s, 2); + break; + } + case DNS_QUERY_SOA: + { + if (pos + 2 >= output_size) + throw SocketException("Unable to pack packet"); + + unsigned short packet_pos_save = pos; + pos += 2; + + this->PackName(output, output_size, pos, Config->DNSSOANS); + this->PackName(output, output_size, pos, Config->DNSSOAAdmin.replace_all_cs('@', '.')); + + if (pos + 20 >= output_size) + throw SocketException("Unable to pack SOA"); + + l = htonl(DNSEngine->GetSerial()); + memcpy(&output[pos], &l, 4); + pos += 4; + + l = htonl(Config->DNSSOARefresh); // Refresh + memcpy(&output[pos], &l, 4); + pos += 4; + + l = htonl(Config->DNSSOARefresh); // Retry + memcpy(&output[pos], &l, 4); + pos += 4; + + l = htonl(604800); // Expire + memcpy(&output[pos], &l, 4); + pos += 4; + + l = htonl(0); // Minimum + memcpy(&output[pos], &l, 4); + pos += 4; + + s = htons(pos - packet_pos_save - 2); + memcpy(&output[packet_pos_save], &s, 2); + break; } default: @@ -462,25 +504,164 @@ unsigned short DNSPacket::Pack(unsigned char *output, unsigned short output_size return pos; } -DNSManager::DNSManager(const Anope::string &nameserver, const Anope::string &ip, int port) : Timer(300, Anope::CurTime, true), Socket(-1, nameserver.find(':') != Anope::string::npos, SOCK_DGRAM) +DNSManager::TCPSocket::Client::Client(TCPSocket *ls, int fd, const sockaddrs &addr) : Socket(fd, ls->IsIPv6()), ClientSocket(ls, addr), Timer(5), tcpsock(ls), packet(NULL), length(0) +{ + Log(LOG_DEBUG_2) << "Resolver: New client from " << addr.addr(); +} + +DNSManager::TCPSocket::Client::~Client() +{ + Log(LOG_DEBUG_2) << "Resolver: Exiting client from " << clientaddr.addr(); + delete packet; +} + +void DNSManager::TCPSocket::Client::Reply(DNSPacket *p) anope_override +{ + delete packet; + packet = p; + SocketEngine::MarkWritable(this); +} + +bool DNSManager::TCPSocket::Client::ProcessRead() +{ + Log(LOG_DEBUG_2) << "Resolver: Reading from DNS TCP socket"; + + int i = recv(this->GetFD(), reinterpret_cast<char *>(packet_buffer) + length, sizeof(packet_buffer) - length, 0); + if (i <= 0) + return false; + + length += i; + + short want_len = packet_buffer[0] << 8 | packet_buffer[1]; + if (length >= want_len - 2) + { + int len = length - 2; + length = 0; + return DNSEngine->HandlePacket(this, packet_buffer + 2, len, NULL); + } + return true; +} + +bool DNSManager::TCPSocket::Client::ProcessWrite() +{ + Log(LOG_DEBUG_2) << "Resolver: Writing to DNS TCP socket"; + + if (packet != NULL) + { + try + { + unsigned char buffer[524]; + unsigned short len = packet->Pack(buffer + 2, sizeof(buffer) - 2); + + short s = htons(len); + memcpy(buffer, &s, 2); + len += 2; + + send(this->GetFD(), reinterpret_cast<char *>(buffer), len, 0); + } + catch (const SocketException &) { } + + delete packet; + packet = NULL; + } + + SocketEngine::ClearWritable(this); + return true; /* Do not return false here, bind is unhappy we close the connection so soon after sending */ +} + +DNSManager::TCPSocket::TCPSocket(const Anope::string &ip, int port) : Socket(-1, ip.find(':') != Anope::string::npos), ListenSocket(ip, port, ip.find(':') != Anope::string::npos) +{ +} + +ClientSocket *DNSManager::TCPSocket::OnAccept(int fd, const sockaddrs &addr) anope_override +{ + return new Client(this, fd, addr); +} + +DNSManager::UDPSocket::UDPSocket(const Anope::string &ip, int port) : Socket(-1, ip.find(':') != Anope::string::npos, SOCK_DGRAM) +{ +} + +DNSManager::UDPSocket::~UDPSocket() +{ + for (unsigned i = 0; i < packets.size(); ++i) + delete packets[i]; +} + +void DNSManager::UDPSocket::Reply(DNSPacket *p) +{ + packets.push_back(p); + SocketEngine::MarkWritable(this); +} + +bool DNSManager::UDPSocket::ProcessRead() +{ + Log(LOG_DEBUG_2) << "Resolver: Reading from DNS UDP socket"; + + unsigned char packet_buffer[524]; + sockaddrs from_server; + socklen_t x = sizeof(from_server); + int length = recvfrom(this->GetFD(), reinterpret_cast<char *>(&packet_buffer), sizeof(packet_buffer), 0, &from_server.sa, &x); + return DNSEngine->HandlePacket(this, packet_buffer, length, &from_server); +} + +bool DNSManager::UDPSocket::ProcessWrite() +{ + Log(LOG_DEBUG_2) << "Resolver: Writing to DNS UDP socket"; + + DNSPacket *r = !packets.empty() ? packets.front() : NULL; + if (r != NULL) + { + try + { + unsigned char buffer[524]; + unsigned short len = r->Pack(buffer, sizeof(buffer)); + + sendto(this->GetFD(), reinterpret_cast<char *>(buffer), len, 0, &r->addr.sa, r->addr.size()); + } + catch (const SocketException &) { } + + delete r; + packets.pop_front(); + } + + if (packets.empty()) + SocketEngine::ClearWritable(this); + + return true; +} + +DNSManager::DNSManager(const Anope::string &nameserver, const Anope::string &ip, int port) : Timer(300, Anope::CurTime, true), serial(0), last_year(0), last_day(0), last_num(0), tcpsock(NULL), udpsock(NULL) { - this->addrs.pton(this->IPv6 ? AF_INET6 : AF_INET, nameserver, port); + this->addrs.pton(nameserver.find(':') != Anope::string::npos ? AF_INET6 : AF_INET, nameserver, port); + try { - this->Bind(ip, port); + udpsock = new UDPSocket(ip, port); + } + catch (const SocketException &ex) + { + Log() << "Unable to create socket for DNSManager: " << ex.GetReason(); + } + + try + { + udpsock->Bind(ip, port); + tcpsock = new TCPSocket(ip, 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(); } + + this->UpdateSerial(); } DNSManager::~DNSManager() { - for (unsigned i = this->packets.size(); i > 0; --i) - delete this->packets[i - 1]; - this->packets.clear(); + delete udpsock; + delete tcpsock; for (std::map<unsigned short, DNSRequest *>::iterator it = this->requests.begin(), it_end = this->requests.end(); it != it_end; ++it) { @@ -499,19 +680,12 @@ DNSManager::~DNSManager() DNSEngine = NULL; } -bool DNSManager::ProcessRead() +bool DNSManager::HandlePacket(ReplySocket *s, const unsigned char *const packet_buffer, int length, sockaddrs *from) { - Log(LOG_DEBUG_2) << "Resolver: Reading from DNS socket"; - - unsigned char packet_buffer[524]; - sockaddrs from_server; - socklen_t x = sizeof(from_server); - int length = recvfrom(this->GetFD(), reinterpret_cast<char *>(&packet_buffer), sizeof(packet_buffer), 0, &from_server.sa, &x); - if (length < DNSPacket::HEADER_LENGTH) return true; - DNSPacket recv_packet(from_server); + DNSPacket recv_packet(from); try { @@ -525,18 +699,65 @@ bool DNSManager::ProcessRead() if (!(recv_packet.flags & DNS_QUERYFLAGS_QR)) { + if (recv_packet.questions.empty()) + { + Log(LOG_DEBUG_2) << "Resolver: Received a question with no questions?"; + return true; + } + DNSPacket *packet = new DNSPacket(recv_packet); packet->flags |= DNS_QUERYFLAGS_QR; /* This is a reponse */ + packet->flags |= DNS_QUERYFLAGS_AA; /* And we are authoritative */ + + packet->answers.clear(); + packet->authorities.clear(); + packet->additional.clear(); + + for (unsigned i = 0; i < recv_packet.questions.size(); ++i) + { + const Question& q = recv_packet.questions[i]; + + if (q.type == DNS_QUERY_AXFR || q.type == DNS_QUERY_SOA) + { + ResourceRecord rr(q.name, DNS_QUERY_SOA); + packet->answers.push_back(rr); + + if (q.type == DNS_QUERY_AXFR) + { + ResourceRecord rr2(q.name, DNS_QUERY_NS); + rr2.rdata = Config->DNSSOANS; + packet->answers.push_back(rr2); + } + break; + } + } FOREACH_MOD(I_OnDnsRequest, OnDnsRequest(recv_packet, packet)); - DNSEngine->SendPacket(packet); + for (unsigned i = 0; i < recv_packet.questions.size(); ++i) + { + const Question& q = recv_packet.questions[i]; + + if (q.type == DNS_QUERY_AXFR) + { + ResourceRecord rr(q.name, DNS_QUERY_SOA); + packet->answers.push_back(rr); + break; + } + } + + s->Reply(packet); return true; } - if (this->addrs != from_server) + if (from == NULL) + { + Log(LOG_DEBUG_2) << "Resolver: Received an answer over TCP. This is not supported."; + return true; + } + else if (this->addrs != *from) { - Log(LOG_DEBUG_2) << "Resolver: Received an answer from the wrong nameserver, Bad NAT or DNS forging attempt? '" << this->addrs.addr() << "' != '" << from_server.addr() << "'"; + Log(LOG_DEBUG_2) << "Resolver: Received an answer from the wrong nameserver, Bad NAT or DNS forging attempt? '" << this->addrs.addr() << "' != '" << from->addr() << "'"; return true; } @@ -604,32 +825,6 @@ bool DNSManager::ProcessRead() return true; } -bool DNSManager::ProcessWrite() -{ - Log(LOG_DEBUG_2) << "Resolver: Writing to DNS socket"; - - DNSPacket *r = !DNSEngine->packets.empty() ? DNSEngine->packets.front() : NULL; - if (r != NULL) - { - try - { - unsigned char buffer[524]; - unsigned short len = r->Pack(buffer, sizeof(buffer)); - - sendto(this->GetFD(), reinterpret_cast<char *>(buffer), len, 0, &r->addr.sa, r->addr.size()); - } - catch (const SocketException &) { } - - delete r; - DNSEngine->packets.pop_front(); - } - - if (DNSEngine->packets.empty()) - SocketEngine::ClearWritable(this); - - return true; -} - void DNSManager::AddCache(DNSQuery &r) { for (unsigned i = 0; i < r.answers.size(); ++i) @@ -700,17 +895,42 @@ void DNSManager::Cleanup(Module *mod) } } -std::deque<DNSPacket *>& DNSManager::GetPackets() +void DNSManager::UpdateSerial() { - return this->packets; + char timebuf[20]; + tm *tm = localtime(&Anope::CurTime); + + if (!tm) + { + Log(LOG_DEBUG) << "Resolver: Unable to update serial"; + return; + } + + if (tm->tm_yday != last_day || tm->tm_year != last_year) + { + last_day = tm->tm_yday; + last_year = tm->tm_year; + last_num = 0; + } + + ++last_num; + + int i = strftime(timebuf, sizeof(timebuf), "%Y%m%d", tm); + snprintf(timebuf + i, sizeof(timebuf) - i, "%d", last_num); + + try + { + serial = convertTo<uint32_t>(timebuf); + } + catch (const ConvertException &) + { + Log(LOG_DEBUG) << "Resolver: Unable to update serial"; + } } -void DNSManager::SendPacket(DNSPacket *p) +uint32_t DNSManager::GetSerial() const { - Log(LOG_DEBUG_2) << "Resolver: Queueing packet " << p->id; - this->packets.push_back(p); - - SocketEngine::MarkWritable(this); + return serial; } DNSQuery DNSManager::BlockingQuery(const Anope::string &mask, QueryType qt) diff --git a/src/sockets.cpp b/src/sockets.cpp index 7bb07f5c5..665db5ca2 100644 --- a/src/sockets.cpp +++ b/src/sockets.cpp @@ -535,7 +535,7 @@ void Socket::ProcessError() * @param port The port to listen on * @param ipv6 true for ipv6 */ -ListenSocket::ListenSocket(const Anope::string &bindip, int port, bool ipv6) : Socket(-1, ipv6) +ListenSocket::ListenSocket(const Anope::string &bindip, int port, bool ipv6) { this->SetNonBlocking(); |