diff options
author | Sadie Powell <sadie@witchery.services> | 2024-01-24 12:01:50 +0000 |
---|---|---|
committer | Sadie Powell <sadie@witchery.services> | 2024-01-24 12:22:51 +0000 |
commit | 7ac1fe58478d58e2480b6919c4abf3a82929169c (patch) | |
tree | 198ad9a6e23d4c189dce57fd95306b6b22d8c23f /modules/m_dns.cpp | |
parent | 72acef4e159df5dcdb93b3c13b2f9d2e5e4c21a9 (diff) |
Rename several modules to remove the m_ prefix.
Diffstat (limited to 'modules/m_dns.cpp')
-rw-r--r-- | modules/m_dns.cpp | 1126 |
1 files changed, 0 insertions, 1126 deletions
diff --git a/modules/m_dns.cpp b/modules/m_dns.cpp deleted file mode 100644 index a75ca6820..000000000 --- a/modules/m_dns.cpp +++ /dev/null @@ -1,1126 +0,0 @@ -/* - * - * (C) 2003-2024 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/dns.h" - -using namespace DNS; - -namespace -{ - Anope::string admin, nameservers; - int refresh; - time_t timeout; -} - -/** A full packet sent or received to/from the nameserver - */ -class Packet final - : public Query -{ - static bool IsValidName(const Anope::string &name) - { - return name.find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-") == Anope::string::npos; - } - - void PackName(unsigned char *output, unsigned short output_size, unsigned short &pos, const Anope::string &name) - { - if (pos + name.length() + 2 > output_size) - throw SocketException("Unable to pack name"); - - Log(LOG_DEBUG_2) << "Resolver: PackName packing " << name; - - sepstream sep(name, '.'); - Anope::string token; - - while (sep.GetToken(token)) - { - output[pos++] = token.length(); - memcpy(&output[pos], token.c_str(), token.length()); - pos += token.length(); - } - - output[pos++] = 0; - } - - Anope::string UnpackName(const unsigned char *input, unsigned short input_size, unsigned short &pos) - { - Anope::string name; - unsigned short pos_ptr = pos, lowest_ptr = input_size; - bool compressed = false; - - if (pos_ptr >= input_size) - throw SocketException("Unable to unpack name - no input"); - - while (input[pos_ptr] > 0) - { - unsigned short offset = input[pos_ptr]; - - if (offset & POINTER) - { - if ((offset & POINTER) != POINTER) - throw SocketException("Unable to unpack name - bogus compression header"); - if (pos_ptr + 1 >= input_size) - throw SocketException("Unable to unpack name - bogus compression header"); - - /* Place pos at the second byte of the first (farthest) compression pointer */ - if (compressed == false) - { - ++pos; - compressed = true; - } - - pos_ptr = (offset & LABEL) << 8 | input[pos_ptr + 1]; - - /* Pointers can only go back */ - if (pos_ptr >= lowest_ptr) - throw SocketException("Unable to unpack name - bogus compression pointer"); - lowest_ptr = pos_ptr; - } - else - { - if (pos_ptr + offset + 1 >= input_size) - throw SocketException("Unable to unpack name - offset too large"); - if (!name.empty()) - name += "."; - for (unsigned i = 1; i <= offset; ++i) - name += input[pos_ptr + i]; - - pos_ptr += offset + 1; - if (compressed == false) - /* Move up pos */ - pos = pos_ptr; - } - } - - /* +1 pos either to one byte after the compression pointer or one byte after the ending \0 */ - ++pos; - - /* Empty names are valid (root domain) */ - - Log(LOG_DEBUG_2) << "Resolver: UnpackName successfully unpacked " << name; - - return name; - } - - Question UnpackQuestion(const unsigned char *input, unsigned short input_size, unsigned short &pos) - { - Question question; - - question.name = this->UnpackName(input, input_size, pos); - - if (pos + 4 > input_size) - throw SocketException("Unable to unpack question"); - - if (!IsValidName(question.name)) - throw SocketException("Invalid question name"); - - question.type = static_cast<QueryType>(input[pos] << 8 | input[pos + 1]); - pos += 2; - - question.qclass = input[pos] << 8 | input[pos + 1]; - pos += 2; - - return question; - } - - ResourceRecord UnpackResourceRecord(const unsigned char *input, unsigned short input_size, unsigned short &pos) - { - ResourceRecord record = static_cast<ResourceRecord>(this->UnpackQuestion(input, input_size, pos)); - - if (pos + 6 > input_size) - throw SocketException("Unable to unpack resource record"); - - record.ttl = (input[pos] << 24) | (input[pos + 1] << 16) | (input[pos + 2] << 8) | input[pos + 3]; - pos += 4; - - //record.rdlength = input[pos] << 8 | input[pos + 1]; - pos += 2; - - switch (record.type) - { - case QUERY_A: - { - if (pos + 4 > input_size) - throw SocketException("Unable to unpack resource record"); - - 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, &a); - if (!addrs.valid()) - throw SocketException("Invalid IP"); - - record.rdata = addrs.addr(); - break; - } - case QUERY_AAAA: - { - if (pos + 16 > input_size) - throw SocketException("Unable to unpack resource record"); - - in6_addr a; - for (int j = 0; j < 16; ++j) - a.s6_addr[j] = input[pos + j]; - pos += 16; - - sockaddrs addrs; - addrs.ntop(AF_INET6, &a); - if (!addrs.valid()) - throw SocketException("Invalid IP"); - - record.rdata = addrs.addr(); - break; - } - case QUERY_CNAME: - case QUERY_PTR: - { - record.rdata = this->UnpackName(input, input_size, pos); - - if (!IsValidName(record.rdata)) - throw SocketException("Invalid cname/ptr record data"); - - break; - } - default: - break; - } - - Log(LOG_DEBUG_2) << "Resolver: " << record.name << " -> " << record.rdata; - - return record; - } - -public: - static const int POINTER = 0xC0; - static const int LABEL = 0x3F; - static const int HEADER_LENGTH = 12; - - Manager *manager; - /* Source or destination of the packet */ - sockaddrs addr; - /* ID for this packet */ - unsigned short id = 0; - /* Flags on the packet */ - unsigned short flags = 0; - - Packet(Manager *m, sockaddrs *a) : manager(m) - { - if (a) - addr = *a; - } - - void Fill(const unsigned char *input, const unsigned short len) - { - if (len < HEADER_LENGTH) - throw SocketException("Unable to fill packet"); - - unsigned short packet_pos = 0; - - this->id = (input[packet_pos] << 8) | input[packet_pos + 1]; - packet_pos += 2; - - this->flags = (input[packet_pos] << 8) | input[packet_pos + 1]; - packet_pos += 2; - - unsigned short qdcount = (input[packet_pos] << 8) | input[packet_pos + 1]; - packet_pos += 2; - - unsigned short ancount = (input[packet_pos] << 8) | input[packet_pos + 1]; - packet_pos += 2; - - unsigned short nscount = (input[packet_pos] << 8) | input[packet_pos + 1]; - packet_pos += 2; - - unsigned short arcount = (input[packet_pos] << 8) | input[packet_pos + 1]; - packet_pos += 2; - - Log(LOG_DEBUG_2) << "Resolver: qdcount: " << qdcount << " ancount: " << ancount << " nscount: " << nscount << " arcount: " << arcount; - - for (unsigned i = 0; i < qdcount; ++i) - this->questions.push_back(this->UnpackQuestion(input, len, packet_pos)); - - for (unsigned i = 0; i < ancount; ++i) - this->answers.push_back(this->UnpackResourceRecord(input, len, packet_pos)); - - try - { - for (unsigned i = 0; i < nscount; ++i) - this->authorities.push_back(this->UnpackResourceRecord(input, len, packet_pos)); - - for (unsigned i = 0; i < arcount; ++i) - this->additional.push_back(this->UnpackResourceRecord(input, len, packet_pos)); - } - catch (const SocketException &ex) - { - Log(LOG_DEBUG_2) << "Unable to parse ns/ar records: " << ex.GetReason(); - } - } - - unsigned short Pack(unsigned char *output, unsigned short output_size) - { - if (output_size < HEADER_LENGTH) - throw SocketException("Unable to pack packet"); - - unsigned short pos = 0; - - output[pos++] = this->id >> 8; - output[pos++] = this->id & 0xFF; - output[pos++] = this->flags >> 8; - output[pos++] = this->flags & 0xFF; - output[pos++] = this->questions.size() >> 8; - output[pos++] = this->questions.size() & 0xFF; - output[pos++] = this->answers.size() >> 8; - output[pos++] = this->answers.size() & 0xFF; - output[pos++] = this->authorities.size() >> 8; - output[pos++] = this->authorities.size() & 0xFF; - output[pos++] = this->additional.size() >> 8; - output[pos++] = this->additional.size() & 0xFF; - - for (auto &q : this->questions) - { - if (q.type == QUERY_PTR) - { - sockaddrs ip(q.name); - if (!ip.valid()) - throw SocketException("Invalid IP"); - - switch (ip.family()) - { - case AF_INET6: - q.name = ip.reverse() + ".ip6.arpa"; - break; - case AF_INET: - q.name = ip.reverse() + ".in-addr.arpa"; - break; - default: - throw SocketException("Unsupported IP Family"); - } - } - - this->PackName(output, output_size, pos, q.name); - - if (pos + 4 >= output_size) - throw SocketException("Unable to pack packet"); - - short s = htons(q.type); - memcpy(&output[pos], &s, 2); - pos += 2; - - s = htons(q.qclass); - memcpy(&output[pos], &s, 2); - pos += 2; - } - - std::vector<ResourceRecord> types[] = { this->answers, this->authorities, this->additional }; - for (auto &type : types) - { - for (const auto &rr : type) - { - this->PackName(output, output_size, pos, rr.name); - - if (pos + 8 >= output_size) - throw SocketException("Unable to pack packet"); - - short s = htons(rr.type); - memcpy(&output[pos], &s, 2); - pos += 2; - - s = htons(rr.qclass); - memcpy(&output[pos], &s, 2); - pos += 2; - - long l = htonl(rr.ttl); - memcpy(&output[pos], &l, 4); - pos += 4; - - switch (rr.type) - { - case QUERY_A: - { - if (pos + 6 > output_size) - throw SocketException("Unable to pack packet"); - - sockaddrs a(rr.rdata); - if (!a.valid()) - throw SocketException("Invalid IP"); - - s = htons(4); - memcpy(&output[pos], &s, 2); - pos += 2; - - memcpy(&output[pos], &a.sa4.sin_addr, 4); - pos += 4; - break; - } - case QUERY_AAAA: - { - if (pos + 18 > output_size) - throw SocketException("Unable to pack packet"); - - sockaddrs a(rr.rdata); - if (!a.valid()) - throw SocketException("Invalid IP"); - - s = htons(16); - memcpy(&output[pos], &s, 2); - pos += 2; - - memcpy(&output[pos], &a.sa6.sin6_addr, 16); - pos += 16; - break; - } - case QUERY_NS: - case QUERY_CNAME: - case QUERY_PTR: - { - 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, rr.rdata); - - s = htons(pos - packet_pos_save - 2); - memcpy(&output[packet_pos_save], &s, 2); - break; - } - case QUERY_SOA: - { - if (pos + 2 >= output_size) - throw SocketException("Unable to pack packet"); - - unsigned short packet_pos_save = pos; - pos += 2; - - std::vector<Anope::string> ns; - spacesepstream(nameservers).GetTokens(ns); - this->PackName(output, output_size, pos, !ns.empty() ? ns[0] : ""); - this->PackName(output, output_size, pos, admin.replace_all_cs('@', '.')); - - if (pos + 20 >= output_size) - throw SocketException("Unable to pack SOA"); - - l = htonl(manager->GetSerial()); - memcpy(&output[pos], &l, 4); - pos += 4; - - l = htonl(refresh); // Refresh - memcpy(&output[pos], &l, 4); - pos += 4; - - l = htonl(refresh); // 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: - break; - } - } - } - - return pos; - } -}; - -namespace DNS -{ - class ReplySocket - : public virtual Socket - { - public: - ~ReplySocket() override = default; - virtual void Reply(Packet *p) = 0; - }; -} - -/* Listens for TCP requests */ -class TCPSocket final - : public ListenSocket -{ - Manager *manager; - -public: - /* A TCP client */ - class Client final - : public ClientSocket - , public Timer - , public ReplySocket - { - Manager *manager; - Packet *packet = nullptr; - unsigned char packet_buffer[524]; - int length = 0; - - public: - Client(Manager *m, TCPSocket *l, int fd, const sockaddrs &addr) : Socket(fd, l->GetFamily()), ClientSocket(l, addr), Timer(5), - manager(m) - { - Log(LOG_DEBUG_2) << "Resolver: New client from " << addr.addr(); - } - - ~Client() override - { - Log(LOG_DEBUG_2) << "Resolver: Exiting client from " << clientaddr.addr(); - delete packet; - } - - /* Times out after a few seconds */ - void Tick(time_t) override { } - - void Reply(Packet *p) override - { - delete packet; - packet = p; - SocketEngine::Change(this, true, SF_WRITABLE); - } - - bool ProcessRead() override - { - 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; - - unsigned short want_len = packet_buffer[0] << 8 | packet_buffer[1]; - if (length >= want_len + 2) - { - int len = length - 2; - length -= want_len + 2; - return this->manager->HandlePacket(this, packet_buffer + 2, len, NULL); - } - return true; - } - - bool ProcessWrite() override - { - Log(LOG_DEBUG_2) << "Resolver: Writing to DNS TCP socket"; - - if (packet != NULL) - { - try - { - unsigned char buffer[65535]; - 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::Change(this, false, SF_WRITABLE); - return true; /* Do not return false here, bind is unhappy we close the connection so soon after sending */ - } - }; - - TCPSocket(Manager *m, const Anope::string &ip, int port) : Socket(-1, ip.find(':') == Anope::string::npos ? AF_INET : AF_INET6), ListenSocket(ip, port, ip.find(':') != Anope::string::npos), manager(m) { } - - ClientSocket *OnAccept(int fd, const sockaddrs &addr) override - { - return new Client(this->manager, this, fd, addr); - } -}; - -/* Listens for UDP requests */ -class UDPSocket final - : public ReplySocket -{ - Manager *manager; - std::deque<Packet *> packets; - -public: - UDPSocket(Manager *m, const Anope::string &ip, int port) : Socket(-1, ip.find(':') == Anope::string::npos ? AF_INET : AF_INET6, SOCK_DGRAM), manager(m) { } - - ~UDPSocket() override - { - for (const auto *packet : packets) - delete packet; - } - - void Reply(Packet *p) override - { - packets.push_back(p); - SocketEngine::Change(this, true, SF_WRITABLE); - } - - std::deque<Packet *>& GetPackets() { return packets; } - - bool ProcessRead() override - { - 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 this->manager->HandlePacket(this, packet_buffer, length, &from_server); - } - - bool ProcessWrite() override - { - Log(LOG_DEBUG_2) << "Resolver: Writing to DNS UDP socket"; - - Packet *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::Change(this, false, SF_WRITABLE); - - return true; - } -}; - -class NotifySocket final - : public Socket -{ - Packet *packet; -public: - NotifySocket(int family, Packet *p) : Socket(-1, family, SOCK_DGRAM), packet(p) - { - SocketEngine::Change(this, false, SF_READABLE); - SocketEngine::Change(this, true, SF_WRITABLE); - } - - bool ProcessWrite() override - { - if (!packet) - return false; - - Log(LOG_DEBUG_2) << "Resolver: Notifying slave " << packet->addr.addr(); - - try - { - unsigned char buffer[524]; - unsigned short len = packet->Pack(buffer, sizeof(buffer)); - - sendto(this->GetFD(), reinterpret_cast<char *>(buffer), len, 0, &packet->addr.sa, packet->addr.size()); - } - catch (const SocketException &) { } - - delete packet; - packet = NULL; - - return false; - } -}; - -class MyManager final - : public Manager - , public Timer -{ - uint32_t serial; - - typedef std::unordered_map<Question, Query, Question::hash> cache_map; - cache_map cache; - - TCPSocket *tcpsock = nullptr; - UDPSocket *udpsock = nullptr; - - bool listen = false; - sockaddrs addrs; - - std::vector<std::pair<Anope::string, short> > notify; -public: - std::map<unsigned short, Request *> requests; - - MyManager(Module *creator) : Manager(creator), Timer(300, Anope::CurTime, true), serial(Anope::CurTime), cur_id(rand()) - { - } - - ~MyManager() override - { - delete udpsock; - delete tcpsock; - - for (std::map<unsigned short, Request *>::iterator it = this->requests.begin(), it_end = this->requests.end(); it != it_end;) - { - Request *request = it->second; - ++it; - - Query rr(*request); - rr.error = ERROR_UNKNOWN; - request->OnError(&rr); - - delete request; - } - this->requests.clear(); - - this->cache.clear(); - } - - void SetIPPort(const Anope::string &nameserver, const Anope::string &ip, unsigned short port, std::vector<std::pair<Anope::string, short> > n) - { - delete udpsock; - delete tcpsock; - - udpsock = NULL; - tcpsock = NULL; - - try - { - this->addrs.pton(nameserver.find(':') != Anope::string::npos ? AF_INET6 : AF_INET, nameserver, 53); - - udpsock = new UDPSocket(this, ip, port); - - if (!ip.empty()) - { - udpsock->Bind(ip, port); - tcpsock = new TCPSocket(this, ip, port); - listen = true; - } - } - catch (const SocketException &ex) - { - Log() << "Unable to bind dns to " << ip << ":" << port << ": " << ex.GetReason(); - } - - notify = n; - } - -private: - unsigned short cur_id; - - unsigned short GetID() - { - if (this->udpsock->GetPackets().size() == 65535) - throw SocketException("DNS queue full"); - - do - cur_id = (cur_id + 1) & 0xFFFF; - while (!cur_id || this->requests.count(cur_id)); - - return cur_id; - } - -public: - void Process(Request *req) override - { - Log(LOG_DEBUG_2) << "Resolver: Processing request to lookup " << req->name << ", of type " << req->type; - - if (req->use_cache && this->CheckCache(req)) - { - Log(LOG_DEBUG_2) << "Resolver: Using cached result"; - delete req; - return; - } - - if (!this->udpsock) - throw SocketException("No dns socket"); - - req->id = GetID(); - this->requests[req->id] = req; - - req->SetSecs(timeout); - - auto *p = new Packet(this, &this->addrs); - p->flags = QUERYFLAGS_RD; - p->id = req->id; - p->questions.push_back(*req); - - this->udpsock->Reply(p); - } - - void RemoveRequest(Request *req) override - { - this->requests.erase(req->id); - } - - bool HandlePacket(ReplySocket *s, const unsigned char *const packet_buffer, int length, sockaddrs *from) override - { - if (length < Packet::HEADER_LENGTH) - return true; - - Packet recv_packet(this, from); - - try - { - recv_packet.Fill(packet_buffer, length); - } - catch (const SocketException &ex) - { - Log(LOG_DEBUG_2) << ex.GetReason(); - return true; - } - - if (!(recv_packet.flags & QUERYFLAGS_QR)) - { - if (!listen) - return true; - else if (recv_packet.questions.empty()) - { - Log(LOG_DEBUG_2) << "Resolver: Received a question with no questions?"; - return true; - } - - auto *packet = new Packet(recv_packet); - packet->flags |= QUERYFLAGS_QR; /* This is a response */ - packet->flags |= QUERYFLAGS_AA; /* And we are authoritative */ - - packet->answers.clear(); - packet->authorities.clear(); - packet->additional.clear(); - - for (auto &q : recv_packet.questions) - { - if (q.type == QUERY_AXFR || q.type == QUERY_SOA) - { - ResourceRecord rr(q.name, QUERY_SOA); - packet->answers.push_back(rr); - - if (q.type == QUERY_AXFR) - { - Anope::string token; - spacesepstream sep(nameservers); - while (sep.GetToken(token)) - { - ResourceRecord rr2(q.name, QUERY_NS); - rr2.rdata = token; - packet->answers.push_back(rr2); - } - } - break; - } - } - - FOREACH_MOD(OnDnsRequest, (recv_packet, packet)); - - for (auto &q : recv_packet.questions) - { - if (q.type == QUERY_AXFR) - { - ResourceRecord rr(q.name, QUERY_SOA); - packet->answers.push_back(rr); - break; - } - } - - if (packet->answers.empty() && packet->authorities.empty() && packet->additional.empty() && packet->error == ERROR_NONE) - packet->error = ERROR_REFUSED; // usually safe, won't cause an NXDOMAIN to get cached - - s->Reply(packet); - return true; - } - - 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->addr() << "'"; - return true; - } - - std::map<unsigned short, Request *>::iterator it = this->requests.find(recv_packet.id); - if (it == this->requests.end()) - { - Log(LOG_DEBUG_2) << "Resolver: Received an answer for something we didn't request"; - return true; - } - Request *request = it->second; - - if (recv_packet.flags & QUERYFLAGS_OPCODE) - { - Log(LOG_DEBUG_2) << "Resolver: Received a nonstandard query"; - recv_packet.error = ERROR_NONSTANDARD_QUERY; - request->OnError(&recv_packet); - } - else if (recv_packet.flags & QUERYFLAGS_RCODE) - { - Error error = ERROR_UNKNOWN; - - switch (recv_packet.flags & QUERYFLAGS_RCODE) - { - case 1: - Log(LOG_DEBUG_2) << "Resolver: format error"; - error = ERROR_FORMAT_ERROR; - break; - case 2: - Log(LOG_DEBUG_2) << "Resolver: server error"; - error = ERROR_SERVER_FAILURE; - break; - case 3: - Log(LOG_DEBUG_2) << "Resolver: domain not found"; - error = ERROR_DOMAIN_NOT_FOUND; - break; - case 4: - Log(LOG_DEBUG_2) << "Resolver: not implemented"; - error = ERROR_NOT_IMPLEMENTED; - break; - case 5: - Log(LOG_DEBUG_2) << "Resolver: refused"; - error = ERROR_REFUSED; - break; - default: - break; - } - - recv_packet.error = error; - request->OnError(&recv_packet); - } - else if (recv_packet.questions.empty() || recv_packet.answers.empty()) - { - Log(LOG_DEBUG_2) << "Resolver: No resource records returned"; - recv_packet.error = ERROR_NO_RECORDS; - request->OnError(&recv_packet); - } - else - { - Log(LOG_DEBUG_2) << "Resolver: Lookup complete for " << request->name; - request->OnLookupComplete(&recv_packet); - this->AddCache(recv_packet); - } - - delete request; - return true; - } - - void UpdateSerial() override - { - serial = Anope::CurTime; - } - - void Notify(const Anope::string &zone) override - { - /* notify slaves of the update */ - for (const auto &[ip, port] : notify) - { - sockaddrs addr; - addr.pton(ip.find(':') != Anope::string::npos ? AF_INET6 : AF_INET, ip, port); - if (!addr.valid()) - return; - - auto *packet = new Packet(this, &addr); - packet->flags = QUERYFLAGS_AA | QUERYFLAGS_OPCODE_NOTIFY; - try - { - packet->id = GetID(); - } - catch (const SocketException &) - { - delete packet; - continue; - } - - packet->questions.emplace_back(zone, QUERY_SOA); - - new NotifySocket(ip.find(':') == Anope::string::npos ? AF_INET : AF_INET6, packet); - } - } - - uint32_t GetSerial() const override - { - return serial; - } - - void Tick(time_t now) override - { - Log(LOG_DEBUG_2) << "Resolver: Purging DNS cache"; - - for (cache_map::iterator it = this->cache.begin(), it_next; it != this->cache.end(); it = it_next) - { - const Query &q = it->second; - const ResourceRecord &req = q.answers[0]; - it_next = it; - ++it_next; - - if (req.created + static_cast<time_t>(req.ttl) < now) - this->cache.erase(it); - } - } - -private: - /** Add a record to the dns cache - * @param r The record - */ - void AddCache(Query &r) - { - const ResourceRecord &rr = r.answers[0]; - Log(LOG_DEBUG_3) << "Resolver cache: added cache for " << rr.name << " -> " << rr.rdata << ", ttl: " << rr.ttl; - this->cache[r.questions[0]] = r; - } - - /** Check the DNS cache to see if request can be handled by a cached result - * @return true if a cached result was found. - */ - bool CheckCache(Request *request) - { - cache_map::iterator it = this->cache.find(*request); - if (it != this->cache.end()) - { - Query &record = it->second; - Log(LOG_DEBUG_3) << "Resolver: Using cached result for " << request->name; - request->OnLookupComplete(&record); - return true; - } - - return false; - } - -}; - -class ModuleDNS final - : public Module -{ - MyManager manager; - - Anope::string nameserver; - Anope::string ip; - int port; - - std::vector<std::pair<Anope::string, short> > notify; - -public: - ModuleDNS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), manager(this) - { - - } - - ~ModuleDNS() override - { - for (std::map<int, Socket *>::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) - { - Socket *s = it->second; - ++it; - - if (dynamic_cast<NotifySocket *>(s) || dynamic_cast<TCPSocket::Client *>(s)) - delete s; - } - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *block = conf->GetModule(this); - - nameserver = block->Get<const Anope::string>("nameserver", "127.0.0.1"); - timeout = block->Get<time_t>("timeout", "5"); - ip = block->Get<const Anope::string>("ip", "0.0.0.0"); - port = block->Get<int>("port", "53"); - admin = block->Get<const Anope::string>("admin", "admin@example.com"); - nameservers = block->Get<const Anope::string>("nameservers", "ns1.example.com"); - refresh = block->Get<int>("refresh", "3600"); - - for (int i = 0; i < block->CountBlock("notify"); ++i) - { - Configuration::Block *n = block->GetBlock("notify", i); - Anope::string nip = n->Get<Anope::string>("ip"); - short nport = n->Get<short>("port"); - - notify.emplace_back(nip, nport); - } - - if (Anope::IsFile(nameserver)) - { - std::ifstream f(nameserver.c_str()); - bool success = false; - - if (f.is_open()) - { - for (Anope::string server; std::getline(f, server.str());) - { - if (server.find("nameserver") == 0) - { - size_t i = server.find_first_of("123456789"); - if (i != Anope::string::npos) - { - if (server.substr(i).is_pos_number_only()) - { - nameserver = server.substr(i); - Log(LOG_DEBUG) << "Nameserver set to " << nameserver; - success = true; - break; - } - } - } - } - - f.close(); - } - - if (!success) - { - Log() << "Unable to find nameserver, defaulting to 127.0.0.1"; - nameserver = "127.0.0.1"; - } - } - - try - { - this->manager.SetIPPort(nameserver, ip, port, notify); - } - catch (const SocketException &ex) - { - throw ModuleException(ex.GetReason()); - } - } - - void OnModuleUnload(User *u, Module *m) override - { - for (std::map<unsigned short, Request *>::iterator it = this->manager.requests.begin(), it_end = this->manager.requests.end(); it != it_end;) - { - unsigned short id = it->first; - Request *req = it->second; - ++it; - - if (req->creator == m) - { - Query rr(*req); - rr.error = ERROR_UNLOADED; - req->OnError(&rr); - - delete req; - this->manager.requests.erase(id); - } - } - } -}; - -MODULE_INIT(ModuleDNS) |