1
0
mirror of https://github.com/anope/anope.git synced 2026-06-24 21:46:37 +02:00
Files
anope/modules/dns.cpp
T
Sadie Powell f9911dde52 Return references instead of pointers from the config system.
We used to return NULL from these methods but now we return an empty
block so this can never actually be null now.
2025-03-02 15:27:47 +00:00

1136 lines
26 KiB
C++

/*
*
* (C) 2003-2025 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;
}
static 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;
}
static 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)
{
++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)
/* 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() 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, true)
, serial(Anope::CurTime)
, cur_id(Anope::RandomNumber())
{
}
~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() 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) < Anope::CurTime)
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)