anope/modules/m_dns.cpp

1126 lines
27 KiB
C++

/*
*
* (C) 2003-2022 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 : 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;
/* Flags on the packet */
unsigned short flags;
Packet(Manager *m, sockaddrs *a) : manager(m), id(0), flags(0)
{
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 (unsigned i = 0; i < this->questions.size(); ++i)
{
Question &q = this->questions[i];
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 (int i = 0; i < 3; ++i)
for (unsigned j = 0; j < types[i].size(); ++j)
{
ResourceRecord &rr = types[i][j];
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:
virtual ~ReplySocket() { }
virtual void Reply(Packet *p) = 0;
};
}
/* Listens for TCP requests */
class TCPSocket : public ListenSocket
{
Manager *manager;
public:
/* A TCP client */
class Client : public ClientSocket, public Timer, public ReplySocket
{
Manager *manager;
Packet *packet;
unsigned char packet_buffer[524];
int length;
public:
Client(Manager *m, TCPSocket *l, int fd, const sockaddrs &addr) : Socket(fd, l->IsIPv6()), ClientSocket(l, addr), Timer(5),
manager(m), packet(NULL), length(0)
{
Log(LOG_DEBUG_2) << "Resolver: New client from " << addr.addr();
}
~Client()
{
Log(LOG_DEBUG_2) << "Resolver: Exiting client from " << clientaddr.addr();
delete packet;
}
/* Times out after a few seconds */
void Tick(time_t) anope_override { }
void Reply(Packet *p) anope_override
{
delete packet;
packet = p;
SocketEngine::Change(this, true, SF_WRITABLE);
}
bool ProcessRead() anope_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() anope_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), ListenSocket(ip, port, ip.find(':') != Anope::string::npos), manager(m) { }
ClientSocket *OnAccept(int fd, const sockaddrs &addr) anope_override
{
return new Client(this->manager, this, fd, addr);
}
};
/* Listens for UDP requests */
class UDPSocket : 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, SOCK_DGRAM), manager(m) { }
~UDPSocket()
{
for (unsigned i = 0; i < packets.size(); ++i)
delete packets[i];
}
void Reply(Packet *p) anope_override
{
packets.push_back(p);
SocketEngine::Change(this, true, SF_WRITABLE);
}
std::deque<Packet *>& GetPackets() { return packets; }
bool ProcessRead() anope_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() anope_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 : public Socket
{
Packet *packet;
public:
NotifySocket(bool v6, Packet *p) : Socket(-1, v6, SOCK_DGRAM), packet(p)
{
SocketEngine::Change(this, false, SF_READABLE);
SocketEngine::Change(this, true, SF_WRITABLE);
}
bool ProcessWrite() anope_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 : public Manager, public Timer
{
uint32_t serial;
typedef TR1NS::unordered_map<Question, Query, Question::hash> cache_map;
cache_map cache;
TCPSocket *tcpsock;
UDPSocket *udpsock;
bool listen;
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), tcpsock(NULL), udpsock(NULL),
listen(false), cur_id(rand())
{
}
~MyManager()
{
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) anope_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);
Packet *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) anope_override
{
this->requests.erase(req->id);
}
bool HandlePacket(ReplySocket *s, const unsigned char *const packet_buffer, int length, sockaddrs *from) anope_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;
}
Packet *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 (unsigned i = 0; i < recv_packet.questions.size(); ++i)
{
const Question& q = recv_packet.questions[i];
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 (unsigned i = 0; i < recv_packet.questions.size(); ++i)
{
const Question& q = recv_packet.questions[i];
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() anope_override
{
serial = Anope::CurTime;
}
void Notify(const Anope::string &zone) anope_override
{
/* notify slaves of the update */
for (unsigned i = 0; i < notify.size(); ++i)
{
const Anope::string &ip = notify[i].first;
short port = notify[i].second;
sockaddrs addr;
addr.pton(ip.find(':') != Anope::string::npos ? AF_INET6 : AF_INET, ip, port);
if (!addr.valid())
return;
Packet *packet = new Packet(this, &addr);
packet->flags = QUERYFLAGS_AA | QUERYFLAGS_OPCODE_NOTIFY;
try
{
packet->id = GetID();
}
catch (const SocketException &)
{
delete packet;
continue;
}
packet->questions.push_back(Question(zone, QUERY_SOA));
new NotifySocket(ip.find(':') != Anope::string::npos, packet);
}
}
uint32_t GetSerial() const anope_override
{
return serial;
}
void Tick(time_t now) anope_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 : 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()
{
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) anope_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.push_back(std::make_pair(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) anope_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)