diff options
-rw-r--r-- | data/example.conf | 46 | ||||
-rw-r--r-- | data/modules.example.conf | 48 | ||||
-rw-r--r-- | data/operserv.example.conf | 3 | ||||
-rw-r--r-- | include/anope.h | 7 | ||||
-rw-r--r-- | include/config.h | 14 | ||||
-rw-r--r-- | include/defs.h | 1 | ||||
-rw-r--r-- | include/dns.h | 278 | ||||
-rw-r--r-- | include/module.h | 1 | ||||
-rw-r--r-- | modules/commands/os_dns.cpp | 15 | ||||
-rw-r--r-- | modules/extra/dns.h | 178 | ||||
-rw-r--r-- | modules/extra/m_dns.cpp | 1025 | ||||
-rw-r--r-- | modules/extra/m_dnsbl.cpp | 16 | ||||
-rw-r--r-- | src/config.cpp | 44 | ||||
-rw-r--r-- | src/dns.cpp | 963 | ||||
-rw-r--r-- | src/misc.cpp | 31 | ||||
-rw-r--r-- | src/module.cpp | 4 | ||||
-rw-r--r-- | src/uplink.cpp | 8 |
17 files changed, 1314 insertions, 1368 deletions
diff --git a/data/example.conf b/data/example.conf index 205a0ead9..9f4a568dd 100644 --- a/data/example.conf +++ b/data/example.conf @@ -1035,52 +1035,6 @@ mail } /* - * [OPTIONAL] DNS Config - * - * This section is used to configure DNS. - * 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 -{ - /* - * The nameserver to use for resolving hostnames, must be an IP or a resolver configuration file. - * The below should work fine on all unix like systems. Windows users will have to find their nameservers - * from ipconfig /all and put the IP here - */ - nameserver = "/etc/resolv.conf" - #nameserver = "127.0.0.1" - - /* - * How long to wait in seconds before a DNS query has timed out - */ - 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 - * require Anope to be started as root. If you do this, it is recommended you - * set options:user and options:group so Anope can change users after binding - * to this port. - */ - ip = "0.0.0.0" - port = 53 - - /* - * SOA record information. - */ - admin = "admin@example.com" - /* This should be the names of the public facing nameserver serving the records */ - nameservers = "ns1.example.com ns2.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 -} - -/* * [REQUIRED] Database configuration. * * This section is used to configure databases used by Anope. diff --git a/data/modules.example.conf b/data/modules.example.conf index a9c7d5cb9..7137975fa 100644 --- a/data/modules.example.conf +++ b/data/modules.example.conf @@ -17,13 +17,59 @@ module { name = "help" } /* + * m_dns + * + * Adds support for the DNS protocol. By itself this module does nothing useful, + * but other modules such as m_dnsbl and os_dns require this. + */ +#module { name = "m_dns" } +dns +{ + /* + * The nameserver to use for resolving hostnames, must be an IP or a resolver configuration file. + * The below should work fine on all unix like systems. Windows users will have to find their nameservers + * from ipconfig /all and put the IP here + */ + nameserver = "/etc/resolv.conf" + #nameserver = "127.0.0.1" + + /* + * How long to wait in seconds before a DNS query has timed out + */ + timeout = 5 + + /* Only edit below if you are expecting to use os_dns or otherwise answer DNS queries. */ + + /* + * The IP and port services use to listen for DNS queries. + * Note that ports less than 1024 are privileged on UNIX/Linux systems, and + * require Anope to be started as root. If you do this, it is recommended you + * set options:user and options:group so Anope can change users after binding + * to this port. + */ + ip = "0.0.0.0" + port = 53 + + /* + * SOA record information. + */ + admin = "admin@example.com" + /* This should be the names of the public facing nameserver serving the records */ + nameservers = "ns1.example.com ns2.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 +} + +/* * m_dnsbl * * Allows configurable DNS blacklists to check connecting users against. If a user * is found on the blacklist they will be immediately banned. This is a crucial module * to prevent bot attacks. */ -module { name = "m_dnsbl" } +#module { name = "m_dnsbl" } m_dnsbl { /* diff --git a/data/operserv.example.conf b/data/operserv.example.conf index 8a887c5ea..cb3f19cc4 100644 --- a/data/operserv.example.conf +++ b/data/operserv.example.conf @@ -359,6 +359,8 @@ defcon * * Provides the command operserv/dns. * + * This module requires that m_dns is loaded. + * * This module allows controlling a DNS zone. This is useful for * controlling what servers users are placed on for load balancing, * and to automatically remove split servers. @@ -371,6 +373,7 @@ defcon * * To do this using BIND, configure similar to: * + * options { max-refresh-time 60; }; * zone "irc.example.com" IN { * type slave; * masters { 127.0.0.1 port 5353; }; diff --git a/include/anope.h b/include/anope.h index c241524ab..bf82e6d78 100644 --- a/include/anope.h +++ b/include/anope.h @@ -506,6 +506,13 @@ namespace Anope * @param Raw message from the uplink */ extern void Process(const Anope::string &); + + /** Does a blocking dns query and returns the first IP. + * @param host host to look up + * @param type inet addr type + * @return the IP if it was found, else the host + */ + extern Anope::string Resolve(const Anope::string &host, int type); } /** sepstream allows for splitting token seperated lists. diff --git a/include/config.h b/include/config.h index b53047a0c..bedbfca23 100644 --- a/include/config.h +++ b/include/config.h @@ -478,20 +478,6 @@ class CoreExport ServerConfig Anope::string MailEmailchangeSubject, MailEmailchangeMessage; Anope::string MailMemoSubject, MailMemoMessage; - /* Nameserver to use for resolving hostnames */ - Anope::string NameServer; - /* Time before a DNS query is considered dead */ - time_t DNSTimeout; - /* 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; /* Allow users to set kill immed on */ diff --git a/include/defs.h b/include/defs.h index 437f0f08d..f592b3a4f 100644 --- a/include/defs.h +++ b/include/defs.h @@ -48,7 +48,6 @@ class User; class XLine; class XLineManager; struct BadWord; -namespace DNS { struct Query; } struct Exception; struct MemoInfo; struct ModeLock; diff --git a/include/dns.h b/include/dns.h deleted file mode 100644 index fee9da8a9..000000000 --- a/include/dns.h +++ /dev/null @@ -1,278 +0,0 @@ -/* - * - * (C) 2003-2012 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. - * - */ - -#ifndef DNS_H -#define DNS_H - -#include "sockets.h" -#include "timers.h" -#include "config.h" - -namespace DNS -{ - - /** Valid query types - */ - enum QueryType - { - /* Nothing */ - QUERY_NONE, - /* A simple A lookup */ - QUERY_A = 1, - /* An authoritative name server */ - QUERY_NS = 2, - /* A CNAME lookup */ - QUERY_CNAME = 5, - /* Start of a zone of authority */ - QUERY_SOA = 6, - /* Reverse DNS lookup */ - QUERY_PTR = 12, - /* IPv6 AAAA lookup */ - QUERY_AAAA = 28, - /* Zone transfer */ - QUERY_AXFR = 252 - }; - - /** Flags that can be AND'd into DNSPacket::flags to receive certain values - */ - enum - { - QUERYFLAGS_QR = 0x8000, - QUERYFLAGS_OPCODE = 0x7800, - QUERYFLAGS_AA = 0x400, - QUERYFLAGS_TC = 0x200, - QUERYFLAGS_RD = 0x100, - QUERYFLAGS_RA = 0x80, - QUERYFLAGS_Z = 0x70, - QUERYFLAGS_RCODE = 0xF - }; - - enum Error - { - ERROR_NONE, - ERROR_UNKNOWN, - ERROR_UNLOADED, - ERROR_TIMEOUT, - ERROR_NOT_AN_ANSWER, - ERROR_NONSTANDARD_QUERY, - ERROR_FORMAT_ERROR, - ERROR_SERVER_FAILURE, - ERROR_DOMAIN_NOT_FOUND, - ERROR_NOT_IMPLEMENTED, - ERROR_REFUSED, - ERROR_NO_RECORDS, - ERROR_INVALIDTYPE - }; - - - struct CoreExport Question - { - Anope::string name; - QueryType type; - unsigned short qclass; - - Question(); - Question(const Anope::string &, QueryType, unsigned short = 1); - }; - - struct CoreExport ResourceRecord : public Question - { - unsigned int ttl; - Anope::string rdata; - time_t created; - - ResourceRecord(const Anope::string &, QueryType, unsigned short = 1); - ResourceRecord(const Question &); - }; - - struct CoreExport Query - { - std::vector<Question> questions; - std::vector<ResourceRecord> answers, authorities, additional; - Error error; - - Query(); - Query(const Question &q); - }; - - /** A DNS query. - */ - class CoreExport Request : public Timer, public Question - { - /* Use result cache if available */ - bool use_cache; - - public: - /* Request id */ - unsigned short id; - /* Creator of this request */ - Module *creator; - - Request(const Anope::string &addr, QueryType qt, bool cache = false, Module *c = NULL); - virtual ~Request(); - - void Process(); - - /** Called when this request succeeds - * @param r The query sent back from the nameserver - */ - virtual void OnLookupComplete(const Query *r) = 0; - - /** Called when this request fails or times out. - * @param r The query sent back from the nameserver, check the error code. - */ - virtual void OnError(const Query *r); - - /** Used to time out the query, Calls OnError and lets the TimerManager - * delete this request. - */ - void Tick(time_t) anope_override; - }; - - /** A full packet sent or recieved to/from the nameserver - */ - class Packet : public Query - { - static const int POINTER = 0xC0; - static const int LABEL = 0x3F; - - void PackName(unsigned char *output, unsigned short output_size, unsigned short &pos, const Anope::string &name); - Anope::string UnpackName(const unsigned char *input, unsigned short input_size, unsigned short &pos); - - Question UnpackQuestion(const unsigned char *input, unsigned short input_size, unsigned short &pos); - ResourceRecord UnpackResourceRecord(const unsigned char *input, unsigned short input_size, unsigned short &poss); - public: - static const int HEADER_LENGTH = 12; - - /* Source or destination of the packet */ - sockaddrs addr; - /* ID for this packet */ - unsigned short id; - /* Flags on the packet */ - unsigned short flags; - - Packet(sockaddrs *a); - void Fill(const unsigned char *input, const unsigned short len); - unsigned short Pack(unsigned char *output, unsigned short output_size); - }; - - /** DNS manager - */ - class CoreExport Manager : public Timer - { - class ReplySocket : public virtual Socket - { - public: - virtual ~ReplySocket() { } - virtual void Reply(Packet *p) = 0; - }; - - /* Listens for TCP requests */ - class TCPSocket : public ListenSocket - { - /* A TCP client */ - class Client : public ClientSocket, public Timer, public ReplySocket - { - TCPSocket *tcpsock; - Packet *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(Packet *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<Packet *> packets; - public: - - UDPSocket(const Anope::string &ip, int port); - ~UDPSocket(); - - void Reply(Packet *p) anope_override; - - std::deque<Packet *>& GetPackets() { return packets; } - - bool ProcessRead() anope_override; - - bool ProcessWrite() anope_override; - }; - - typedef std::multimap<Anope::string, ResourceRecord, ci::less> cache_map; - cache_map cache; - - bool listen; - uint32_t serial; - - public: - TCPSocket *tcpsock; - UDPSocket *udpsock; - - sockaddrs addrs; - std::map<unsigned short, Request *> requests; - - Manager(const Anope::string &nameserver, const Anope::string &ip, int port); - ~Manager(); - - bool HandlePacket(ReplySocket *s, const unsigned char *const data, int len, sockaddrs *from); - - /** Add a record to the dns cache - * @param r The record - */ - void AddCache(Query &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); - - /** Tick this timer, used to clear the DNS cache. - */ - void Tick(time_t now) anope_override; - - /** Cleanup all pending DNS queries for a module - * @param mod The module - */ - void Cleanup(Module *mod); - - 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 - * need a blocking query use the DNSRequest system - */ - static Query BlockingQuery(const Anope::string &mask, QueryType qt); - }; - - extern CoreExport Manager *Engine; - -} // namespace DNS - -#endif // DNS_H - - diff --git a/include/module.h b/include/module.h index 197899ca8..e2fc18eaf 100644 --- a/include/module.h +++ b/include/module.h @@ -21,7 +21,6 @@ #include "channels.h" #include "commands.h" #include "config.h" -#include "dns.h" #include "extensible.h" #include "hashcomp.h" #include "language.h" diff --git a/modules/commands/os_dns.cpp b/modules/commands/os_dns.cpp index 6e5689396..ec72f5a5d 100644 --- a/modules/commands/os_dns.cpp +++ b/modules/commands/os_dns.cpp @@ -6,6 +6,9 @@ */ #include "module.h" +#include "../extra/dns.h" + +static ServiceReference<DNS::Manager> dnsmanager("DNS::Manager", "dns/manager"); class DNSServer; static std::vector<DNSServer *> dns_servers; @@ -44,8 +47,8 @@ class DNSServer : public Serializable void Pool(bool p) { pooled = p; - if (DNS::Engine) - DNS::Engine->UpdateSerial(); + if (dnsmanager) + dnsmanager->UpdateSerial(); } @@ -220,8 +223,8 @@ class CommandOSDNS : public Command 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()) - DNS::Engine->UpdateSerial(); + if (s->Pooled() && dnsmanager) + dnsmanager->UpdateSerial(); } void DelIP(CommandSource &source, const std::vector<Anope::string> ¶ms) @@ -247,8 +250,8 @@ class CommandOSDNS : public Command s->Pool(false); } - if (s->Pooled()) - DNS::Engine->UpdateSerial(); + if (s->Pooled() && dnsmanager) + dnsmanager->UpdateSerial(); return; } diff --git a/modules/extra/dns.h b/modules/extra/dns.h new file mode 100644 index 000000000..9d141249b --- /dev/null +++ b/modules/extra/dns.h @@ -0,0 +1,178 @@ +/* + * + * (C) 2003-2012 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. + * + */ + +#ifndef DNS_H +#define DNS_H + +namespace DNS +{ + /** Valid query types + */ + enum QueryType + { + /* Nothing */ + QUERY_NONE, + /* A simple A lookup */ + QUERY_A = 1, + /* An authoritative name server */ + QUERY_NS = 2, + /* A CNAME lookup */ + QUERY_CNAME = 5, + /* Start of a zone of authority */ + QUERY_SOA = 6, + /* Reverse DNS lookup */ + QUERY_PTR = 12, + /* IPv6 AAAA lookup */ + QUERY_AAAA = 28, + /* Zone transfer */ + QUERY_AXFR = 252 + }; + + /** Flags that can be AND'd into DNSPacket::flags to receive certain values + */ + enum + { + QUERYFLAGS_QR = 0x8000, + QUERYFLAGS_OPCODE = 0x7800, + QUERYFLAGS_AA = 0x400, + QUERYFLAGS_TC = 0x200, + QUERYFLAGS_RD = 0x100, + QUERYFLAGS_RA = 0x80, + QUERYFLAGS_Z = 0x70, + QUERYFLAGS_RCODE = 0xF + }; + + enum Error + { + ERROR_NONE, + ERROR_UNKNOWN, + ERROR_UNLOADED, + ERROR_TIMEOUT, + ERROR_NOT_AN_ANSWER, + ERROR_NONSTANDARD_QUERY, + ERROR_FORMAT_ERROR, + ERROR_SERVER_FAILURE, + ERROR_DOMAIN_NOT_FOUND, + ERROR_NOT_IMPLEMENTED, + ERROR_REFUSED, + ERROR_NO_RECORDS, + ERROR_INVALIDTYPE + }; + + struct Question + { + Anope::string name; + QueryType type; + unsigned short qclass; + + Question() : type(QUERY_NONE), qclass(0) { } + Question(const Anope::string &n, QueryType t, unsigned short c = 1) : name(n), type(t), qclass(c) { } + }; + + struct ResourceRecord : public Question + { + unsigned int ttl; + Anope::string rdata; + time_t created; + + ResourceRecord(const Anope::string &n, QueryType t, unsigned short c = 1) : Question(n, t, c), ttl(0), created(Anope::CurTime) { } + ResourceRecord(const Question &q) : Question(q), ttl(0), created(Anope::CurTime) { } + }; + + struct Query + { + std::vector<Question> questions; + std::vector<ResourceRecord> answers, authorities, additional; + Error error; + + Query() : error(ERROR_NONE) { } + Query(const Question &q) : error(ERROR_NONE) { questions.push_back(q); } + }; + + class Packet : public Query + { + public: + static const int POINTER = 0xC0; + static const int LABEL = 0x3F; + static const int HEADER_LENGTH = 12; + + virtual ~Packet() { } + }; + + class ReplySocket; + class Request; + + /** DNS manager + */ + class Manager : public Service + { + public: + Manager(Module *creator) : Service(creator, "DNS::Manager", "dns/manager") { } + virtual ~Manager() { } + + virtual void Process(Request *req) = 0; + virtual void RemoveRequest(Request *req) = 0; + + virtual bool HandlePacket(ReplySocket *s, const unsigned char *const data, int len, sockaddrs *from) = 0; + + virtual void UpdateSerial() = 0; + virtual uint32_t GetSerial() const = 0; + }; + + /** A DNS query. + */ + class Request : public Timer, public Question + { + Manager *manager; + public: + /* Use result cache if available */ + bool use_cache; + /* Request id */ + unsigned short id; + /* Creator of this request */ + Module *creator; + + Request(Manager *mgr, Module *c, const Anope::string &addr, QueryType qt, bool cache = false) : Timer(0), Question(addr, qt), manager(mgr), + use_cache(cache), id(0), creator(c) { } + + virtual ~Request() + { + manager->RemoveRequest(this); + } + + /** Called when this request succeeds + * @param r The query sent back from the nameserver + */ + virtual void OnLookupComplete(const Query *r) = 0; + + /** Called when this request fails or times out. + * @param r The query sent back from the nameserver, check the error code. + */ + virtual void OnError(const Query *r) { } + + /** Used to time out the query, xalls OnError and lets the TimerManager + * delete this request. + */ + void Tick(time_t) anope_override + { + Log(LOG_DEBUG_2) << "Resolver: timeout for query " << this->name; + Query rr(*this); + rr.error = ERROR_TIMEOUT; + this->OnError(&rr); + } + }; + +} // namespace DNS + +#endif // DNS_H + + diff --git a/modules/extra/m_dns.cpp b/modules/extra/m_dns.cpp new file mode 100644 index 000000000..867d81d84 --- /dev/null +++ b/modules/extra/m_dns.cpp @@ -0,0 +1,1025 @@ +/* + * + * (C) 2003-2012 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 "dns.h" + +using namespace DNS; + +namespace +{ + Anope::string admin, nameservers; + int refresh; + time_t timeout; +} + +/** A full packet sent or recieved to/from the nameserver + */ +class MyPacket : public Packet +{ + void PackName(unsigned char *output, unsigned short output_size, unsigned short &pos, const Anope::string &name) + { + if (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; + + 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"); + + 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); + + 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); + + record.rdata = addrs.addr(); + break; + } + case QUERY_CNAME: + case QUERY_PTR: + { + record.rdata = this->UnpackName(input, input_size, pos); + break; + } + default: + break; + } + + Log(LOG_DEBUG_2) << "Resolver: " << record.name << " -> " << record.rdata; + + return record; + } + + public: + Manager *manager; + /* Source or destination of the packet */ + sockaddrs addr; + /* ID for this packet */ + unsigned short id; + /* Flags on the packet */ + unsigned short flags; + + MyPacket(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)); + + 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)); + } + + 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 (q.name.find(':') != Anope::string::npos) + { + static const char *const hex = "0123456789abcdef"; + char reverse_ip[128]; + unsigned reverse_ip_count = 0; + for (int j = 15; j >= 0; --j) + { + reverse_ip[reverse_ip_count++] = hex[ip.sa6.sin6_addr.s6_addr[j] & 0xF]; + reverse_ip[reverse_ip_count++] = '.'; + reverse_ip[reverse_ip_count++] = hex[ip.sa6.sin6_addr.s6_addr[j] >> 4]; + reverse_ip[reverse_ip_count++] = '.'; + } + reverse_ip[reverse_ip_count++] = 0; + + q.name = reverse_ip; + q.name += "ip6.arpa"; + } + else + { + unsigned long forward = ip.sa4.sin_addr.s_addr; + in_addr reverse; + reverse.s_addr = forward << 24 | (forward & 0xFF00) << 8 | (forward & 0xFF0000) >> 8 | forward >> 24; + + ip.ntop(AF_INET, &reverse); + + q.name = ip.addr() + ".in-addr.arpa"; + } + } + + 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); + + 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); + + 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(MyPacket *p) = 0; + }; +} + +/* Listens for TCP requests */ +class TCPSocket : public ListenSocket +{ + /* A TCP client */ + class Client : public ClientSocket, public Timer, public ReplySocket + { + Manager *manager; + TCPSocket *tcpsock; + MyPacket *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), tcpsock(l), 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(MyPacket *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 */ + } + }; + + Manager *manager; + + public: + 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<MyPacket *> 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(MyPacket *p) anope_override + { + packets.push_back(p); + SocketEngine::Change(this, true, SF_WRITABLE); + } + + std::deque<MyPacket *>& 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"; + + MyPacket *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 MyManager : public Manager, public Timer +{ + uint32_t serial; + + typedef std::multimap<Anope::string, ResourceRecord, ci::less> cache_map; + cache_map cache; + + TCPSocket *tcpsock; + UDPSocket *udpsock; + + bool listen; + sockaddrs addrs; + 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) + { + } + + ~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) + { + 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(); + } + } + + 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"); + + if (this->udpsock->GetPackets().size() == 65535) + throw SocketException("DNS queue full"); + + do + { + static unsigned short cur_id = rand(); + cur_id = req->id = (cur_id + 1) & 0xFFFF; + } + while (!req->id || this->requests.count(req->id)); + + this->requests[req->id] = req; + + req->SetSecs(timeout); + + MyPacket *p = new MyPacket(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; + + MyPacket 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; + } + + MyPacket *packet = new MyPacket(recv_packet); + packet->flags |= QUERYFLAGS_QR; /* This is a reponse */ + 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(I_OnDnsRequest, 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; + } + } + + 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.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; + } + + uint32_t GetSerial() const anope_override + { + return serial; + } + + /** Add a record to the dns cache + * @param r The record + */ + void AddCache(Query &r) + { + for (unsigned i = 0; i < r.answers.size(); ++i) + { + ResourceRecord &rr = r.answers[i]; + Log(LOG_DEBUG_3) << "Resolver cache: added cache for " << rr.name << " -> " << rr.rdata << ", ttl: " << rr.ttl; + this->cache.insert(std::make_pair(rr.name, rr)); + } + } + + 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) + { + ResourceRecord &req = it->second; + it_next = it; + ++it_next; + + if (req.created + static_cast<time_t>(req.ttl) < now) + this->cache.erase(it); + } + } + + private: + /** 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->name); + if (it != this->cache.end()) + { + Query record(*request); + + for (cache_map::iterator it_end = this->cache.upper_bound(request->name); it != it_end; ++it) + { + ResourceRecord &rec = it->second; + if (rec.created + static_cast<time_t>(rec.ttl) >= Anope::CurTime) + record.answers.push_back(rec); + } + + if (!record.answers.empty()) + { + 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; + + public: + ModuleDNS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED), manager(this) + { + this->SetAuthor("Anope"); + + Implementation i[] = { I_OnReload, I_OnModuleUnload }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + this->OnReload(); + } + + void OnReload() anope_override + { + ConfigReader config; + + nameserver = config.ReadValue("dns", "nameserver", "127.0.0.1", 0); + timeout = Anope::DoTime(config.ReadValue("dns", "timeout", "5", 0)); + ip = config.ReadValue("dns", "ip", "0.0.0.0", 0); + port = config.ReadInteger("dns", "port", "53", 0, false); + admin = config.ReadValue("dns", "admin", "admin@example.com", 0); + nameservers = config.ReadValue("dns", "nameservers", "ns1.example.com", 0); + refresh = config.ReadInteger("dns", "refresh", "3600", 0, false); + + 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); + } + 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) + diff --git a/modules/extra/m_dnsbl.cpp b/modules/extra/m_dnsbl.cpp index 54c802cd4..1d5cdcb2e 100644 --- a/modules/extra/m_dnsbl.cpp +++ b/modules/extra/m_dnsbl.cpp @@ -6,8 +6,12 @@ */ #include "module.h" +#include "dns.h" + +using namespace DNS; static ServiceReference<XLineManager> akills("XLineManager", "xlinemanager/sgline"); +static ServiceReference<Manager> dnsmanager("DNS::Manager", "dns/manager"); struct Blacklist { @@ -19,21 +23,21 @@ struct Blacklist Blacklist(const Anope::string &n, time_t b, const Anope::string &r, const std::map<int, Anope::string> &re) : name(n), bantime(b), reason(r), replies(re) { } }; -class DNSBLResolver : public DNS::Request +class DNSBLResolver : public Request { Reference<User> user; Blacklist blacklist; bool add_to_akill; public: - DNSBLResolver(Module *c, User *u, const Blacklist &b, const Anope::string &host, bool add_akill) : DNS::Request(host, DNS::QUERY_A, true, c), user(u), blacklist(b), add_to_akill(add_akill) { } + DNSBLResolver(Module *c, User *u, const Blacklist &b, const Anope::string &host, bool add_akill) : Request(dnsmanager, c, host, QUERY_A, true), user(u), blacklist(b), add_to_akill(add_akill) { } - void OnLookupComplete(const DNS::Query *record) anope_override + void OnLookupComplete(const Query *record) anope_override { if (!user || user->HasExt("m_dnsbl_akilled")) return; - const DNS::ResourceRecord &ans_record = record->answers[0]; + const ResourceRecord &ans_record = record->answers[0]; // Replies should be in 127.0.0.0/24 if (ans_record.rdata.find("127.0.0.") != 0) return; @@ -127,7 +131,7 @@ class ModuleDNSBL : public Module void OnUserConnect(Reference<User> &user, bool &exempt) anope_override { - if (exempt || !user || (!this->check_on_connect && !Me->IsSynced())) + if (exempt || !user || (!this->check_on_connect && !Me->IsSynced()) || !dnsmanager) return; if (!this->check_on_netburst && !user->server->IsSynced()) @@ -158,7 +162,7 @@ class ModuleDNSBL : public Module { Anope::string dnsbl_host = user_ip.addr() + "." + b.name; DNSBLResolver *res = new DNSBLResolver(this, user, b, dnsbl_host, this->add_to_akill); - res->Process(); + dnsmanager->Process(res); } catch (const SocketException &ex) { diff --git a/src/config.cpp b/src/config.cpp index 8969569fd..79ebce9f3 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -17,7 +17,6 @@ #include "opertype.h" #include "channels.h" #include "hashcomp.h" -#include "dns.h" #ifndef _WIN32 #include <errno.h> @@ -175,42 +174,6 @@ ServerConfig::ServerConfig() ModeManager::UpdateDefaultMLock(this); - if (IsFile(this->NameServer)) - { - std::ifstream f(this->NameServer.c_str()); - Anope::string server; - bool success = false; - - while (f.is_open() && getline(f, server.str())) - { - if (server.find("nameserver") == 0) - { - size_t ip = server.find_first_of("123456789"); - if (ip != Anope::string::npos) - { - if (server.substr(ip).is_pos_number_only()) - { - this->NameServer = server.substr(ip); - Log(LOG_DEBUG) << "Nameserver set to " << this->NameServer; - success = true; - break; - } - } - } - } - - if (f.is_open()) - f.close(); - - if (!success) - { - Log() << "Unable to find nameserver, defaulting to 127.0.0.1"; - this->NameServer = "127.0.0.1"; - } - } - delete DNS::Engine; - DNS::Engine = new DNS::Manager(this->NameServer, this->DNSIP, this->DNSPort); - if (this->CaseMap == "ascii") Anope::casemap = std::locale(std::locale(), new Anope::ascii_ctype<char>()); else if (this->CaseMap == "rfc1459") @@ -1262,13 +1225,6 @@ ConfigItems::ConfigItems(ServerConfig *conf) {"mail", "emailchange_message", "", new ValueContainerString(&conf->MailEmailchangeMessage), DT_STRING | DT_ALLOW_NEWLINE, ValidateMail}, {"mail", "memo_subject", "", new ValueContainerString(&conf->MailMemoSubject), DT_STRING, ValidateMail}, {"mail", "memo_message", "", new ValueContainerString(&conf->MailMemoMessage), DT_STRING | DT_ALLOW_NEWLINE, ValidateMail}, - {"dns", "nameserver", "127.0.0.1", new ValueContainerString(&conf->NameServer), DT_STRING, NoValidation}, - {"dns", "timeout", "5", new ValueContainerTime(&conf->DNSTimeout), DT_TIME, NoValidation}, - {"dns", "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", "nameservers", "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 deleted file mode 100644 index ccb919a31..000000000 --- a/src/dns.cpp +++ /dev/null @@ -1,963 +0,0 @@ -/* - * - * (C) 2003-2012 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 "services.h" -#include "anope.h" -#include "dns.h" -#include "sockets.h" -#include "socketengine.h" - -#ifndef _WIN32 -#include <sys/types.h> -#include <sys/socket.h> -#include <netdb.h> -#endif - -using namespace DNS; - -Manager *DNS::Engine = NULL; - -Question::Question() -{ - this->type = QUERY_NONE; - this->qclass = 0; -} - -Question::Question(const Anope::string &n, QueryType t, unsigned short q) : name(n), type(t), qclass(q) -{ -} - -ResourceRecord::ResourceRecord(const Anope::string &n, QueryType t, unsigned short q) : Question(n, t, q) -{ - this->ttl = 0; - this->created = Anope::CurTime; -} - -ResourceRecord::ResourceRecord(const Question &q) : Question(q) -{ - this->ttl = 0; - this->created = Anope::CurTime; -} - -Query::Query() -{ - this->error = ERROR_NONE; -} - -Query::Query(const Question &q) -{ - this->questions.push_back(q); - this->error = ERROR_NONE; -} - -Request::Request(const Anope::string &addr, QueryType qt, bool cache, Module *c) : Timer(Config->DNSTimeout), Question(addr, qt), use_cache(cache), id(0), creator(c) -{ - if (!DNS::Engine || !DNS::Engine->udpsock) - throw SocketException("No DNS::Engine"); - if (DNS::Engine->udpsock->GetPackets().size() == 65535) - throw SocketException("DNS queue full"); - - do - { - static unsigned short cur_id = rand(); - this->id = cur_id++; - } - while (DNS::Engine->requests.count(this->id)); - - DNS::Engine->requests[this->id] = this; -} - -Request::~Request() -{ - DNS::Engine->requests.erase(this->id); -} - -void Request::Process() -{ - Log(LOG_DEBUG_2) << "Resolver: Processing request to lookup " << this->name << ", of type " << this->type; - - if (!DNS::Engine || !DNS::Engine->udpsock) - throw SocketException("DNS::Engine has not been initialized"); - - if (this->use_cache && DNS::Engine->CheckCache(this)) - { - Log(LOG_DEBUG_2) << "Resolver: Using cached result"; - delete this; - return; - } - - Packet *p = new Packet(&DNS::Engine->addrs); - p->flags = QUERYFLAGS_RD; - - p->id = this->id; - p->questions.push_back(*this); - DNS::Engine->udpsock->Reply(p); -} - -void Request::OnError(const Query *r) -{ -} - -void Request::Tick(time_t) -{ - Log(LOG_DEBUG_2) << "Resolver: timeout for query " << this->name; - Query rr(*this); - rr.error = ERROR_TIMEOUT; - this->OnError(&rr); -} - -void Packet::PackName(unsigned char *output, unsigned short output_size, unsigned short &pos, const Anope::string &name) -{ - if (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 Packet::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; - - Log(LOG_DEBUG_2) << "Resolver: UnpackName successfully unpacked " << name; - - return name; -} - -Question Packet::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"); - - 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 Packet::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); - - 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); - - record.rdata = addrs.addr(); - break; - } - case QUERY_CNAME: - case QUERY_PTR: - { - record.rdata = this->UnpackName(input, input_size, pos); - break; - } - default: - break; - } - - Log(LOG_DEBUG_2) << "Resolver: " << record.name << " -> " << record.rdata; - - return record; -} - -Packet::Packet(sockaddrs *a) : Query(), id(0), flags(0) -{ - if (a) - addr = *a; -} - -void Packet::Fill(const unsigned char *input, const unsigned short len) -{ - if (len < Packet::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)); - - 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)); -} - -unsigned short Packet::Pack(unsigned char *output, unsigned short output_size) -{ - if (output_size < Packet::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 (q.name.find(':') != Anope::string::npos) - { - static const char *const hex = "0123456789abcdef"; - char reverse_ip[128]; - unsigned reverse_ip_count = 0; - for (int j = 15; j >= 0; --j) - { - reverse_ip[reverse_ip_count++] = hex[ip.sa6.sin6_addr.s6_addr[j] & 0xF]; - reverse_ip[reverse_ip_count++] = '.'; - reverse_ip[reverse_ip_count++] = hex[ip.sa6.sin6_addr.s6_addr[j] >> 4]; - reverse_ip[reverse_ip_count++] = '.'; - } - reverse_ip[reverse_ip_count++] = 0; - - q.name = reverse_ip; - q.name += "ip6.arpa"; - } - else - { - unsigned long forward = ip.sa4.sin_addr.s_addr; - in_addr reverse; - reverse.s_addr = forward << 24 | (forward & 0xFF00) << 8 | (forward & 0xFF0000) >> 8 | forward >> 24; - - ip.ntop(AF_INET, &reverse); - - q.name = ip.addr() + ".in-addr.arpa"; - } - } - - 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); - - 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); - - 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> nameservers; - spacesepstream(Config->DNSSOANS).GetTokens(nameservers); - this->PackName(output, output_size, pos, !nameservers.empty() ? nameservers[0] : ""); - this->PackName(output, output_size, pos, Config->DNSSOAAdmin.replace_all_cs('@', '.')); - - if (pos + 20 >= output_size) - throw SocketException("Unable to pack SOA"); - - l = htonl(DNS::Engine->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: - break; - } - } - - return pos; -} - -Manager::TCPSocket::Client::Client(TCPSocket *l, int fd, const sockaddrs &addr) : Socket(fd, l->IsIPv6()), ClientSocket(l, addr), Timer(5), tcpsock(l), packet(NULL), length(0) -{ - Log(LOG_DEBUG_2) << "Resolver: New client from " << addr.addr(); -} - -Manager::TCPSocket::Client::~Client() -{ - Log(LOG_DEBUG_2) << "Resolver: Exiting client from " << clientaddr.addr(); - delete packet; -} - -void Manager::TCPSocket::Client::Reply(Packet *p) -{ - delete packet; - packet = p; - SocketEngine::Change(this, true, SF_WRITABLE); -} - -bool Manager::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; - - 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 DNS::Engine->HandlePacket(this, packet_buffer + 2, len, NULL); - } - return true; -} - -bool Manager::TCPSocket::Client::ProcessWrite() -{ - 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 */ -} - -Manager::TCPSocket::TCPSocket(const Anope::string &ip, int port) : Socket(-1, ip.find(':') != Anope::string::npos), ListenSocket(ip, port, ip.find(':') != Anope::string::npos) -{ -} - -ClientSocket *Manager::TCPSocket::OnAccept(int fd, const sockaddrs &addr) -{ - return new Client(this, fd, addr); -} - -Manager::UDPSocket::UDPSocket(const Anope::string &ip, int port) : Socket(-1, ip.find(':') != Anope::string::npos, SOCK_DGRAM) -{ -} - -Manager::UDPSocket::~UDPSocket() -{ - for (unsigned i = 0; i < packets.size(); ++i) - delete packets[i]; -} - -void Manager::UDPSocket::Reply(Packet *p) -{ - packets.push_back(p); - SocketEngine::Change(this, true, SF_WRITABLE); -} - -bool Manager::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 DNS::Engine->HandlePacket(this, packet_buffer, length, &from_server); -} - -bool Manager::UDPSocket::ProcessWrite() -{ - 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; -} - -Manager::Manager(const Anope::string &nameserver, const Anope::string &ip, int port) : Timer(300, Anope::CurTime, true), listen(false), serial(0), tcpsock(NULL), udpsock(NULL) -{ - this->addrs.pton(nameserver.find(':') != Anope::string::npos ? AF_INET6 : AF_INET, nameserver, port); - - try - { - udpsock = new UDPSocket(ip, port); - } - catch (const SocketException &ex) - { - Log() << "Unable to create socket for Manager: " << ex.GetReason(); - } - - try - { - udpsock->Bind(ip, port); - tcpsock = new TCPSocket(ip, port); - listen = true; - } - 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 Manager to port " << port << ": " << ex.GetReason(); - } - - this->UpdateSerial(); -} - -Manager::~Manager() -{ - delete udpsock; - delete tcpsock; - - for (std::map<unsigned short, Request *>::iterator it = this->requests.begin(), it_end = this->requests.end(); it != it_end; ++it) - { - Request *request = it->second; - - Query rr(*request); - rr.error = ERROR_UNKNOWN; - request->OnError(&rr); - - delete request; - } - this->requests.clear(); - - this->cache.clear(); - - DNS::Engine = NULL; -} - -bool Manager::HandlePacket(ReplySocket *s, const unsigned char *const packet_buffer, int length, sockaddrs *from) -{ - if (length < Packet::HEADER_LENGTH) - return true; - - Packet recv_packet(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 reponse */ - 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(Config->DNSSOANS); - while (sep.GetToken(token)) - { - ResourceRecord rr2(q.name, QUERY_NS); - rr2.rdata = token; - packet->answers.push_back(rr2); - } - } - break; - } - } - - FOREACH_MOD(I_OnDnsRequest, 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; - } - } - - 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 = DNS::Engine->requests.find(recv_packet.id); - if (it == DNS::Engine->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.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); - DNS::Engine->AddCache(recv_packet); - } - - delete request; - return true; -} - -void Manager::AddCache(Query &r) -{ - for (unsigned i = 0; i < r.answers.size(); ++i) - { - ResourceRecord &rr = r.answers[i]; - Log(LOG_DEBUG_3) << "Resolver cache: added cache for " << rr.name << " -> " << rr.rdata << ", ttl: " << rr.ttl; - this->cache.insert(std::make_pair(rr.name, rr)); - } -} - -bool Manager::CheckCache(Request *request) -{ - cache_map::iterator it = this->cache.find(request->name); - if (it != this->cache.end()) - { - Query record(*request); - - for (cache_map::iterator it_end = this->cache.upper_bound(request->name); it != it_end; ++it) - { - ResourceRecord &rec = it->second; - if (rec.created + static_cast<time_t>(rec.ttl) >= Anope::CurTime) - record.answers.push_back(rec); - } - - if (!record.answers.empty()) - { - Log(LOG_DEBUG_3) << "Resolver: Using cached result for " << request->name; - request->OnLookupComplete(&record); - return true; - } - } - - return false; -} - -void Manager::Tick(time_t now) -{ - 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) - { - ResourceRecord &req = it->second; - it_next = it; - ++it_next; - - if (req.created + static_cast<time_t>(req.ttl) < now) - this->cache.erase(it); - } -} - -void Manager::Cleanup(Module *mod) -{ - for (std::map<unsigned short, Request *>::iterator it = this->requests.begin(), it_end = this->requests.end(); it != it_end;) - { - unsigned short id = it->first; - Request *req = it->second; - ++it; - - if (req->creator && req->creator == mod) - { - Query rr(*req); - rr.error = ERROR_UNLOADED; - req->OnError(&rr); - - delete req; - this->requests.erase(id); - } - } -} - -void Manager::UpdateSerial() -{ - serial = Anope::CurTime; -} - -uint32_t Manager::GetSerial() const -{ - return serial; -} - -Query Manager::BlockingQuery(const Anope::string &mask, QueryType qt) -{ - Question question(mask, qt); - Query result(question); - - int type = AF_UNSPEC; - if (qt == QUERY_A) - type = AF_INET; - else if (qt == QUERY_AAAA) - type = AF_INET6; - - addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = type; - - Log(LOG_DEBUG_2) << "Resolver: BlockingQuery: Looking up " << mask; - - addrinfo *addrresult; - if (getaddrinfo(mask.c_str(), NULL, &hints, &addrresult) == 0) - { - for (addrinfo *cur = addrresult; cur; cur = cur->ai_next) - { - sockaddrs addr; - memcpy(&addr, addrresult->ai_addr, addrresult->ai_addrlen); - try - { - ResourceRecord rr(mask, qt); - rr.rdata = addr.addr(); - result.answers.push_back(rr); - - Log(LOG_DEBUG_2) << "Resolver: BlockingQuery: " << mask << " -> " << rr.rdata; - } - catch (const SocketException &) { } - } - - freeaddrinfo(addrresult); - } - - return result; -} - - - diff --git a/src/misc.cpp b/src/misc.cpp index 417849df4..3f74888ee 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -18,10 +18,13 @@ #include "bots.h" #include "language.h" #include "regexpr.h" +#include "sockets.h" #include <errno.h> #include <sys/types.h> #include <sys/stat.h> +#include <sys/socket.h> +#include <netdb.h> NumberList::NumberList(const Anope::string &list, bool descending) : is_valid(true), desc(descending) { @@ -652,3 +655,31 @@ Anope::string Anope::NormalizeBuffer(const Anope::string &buf) return newbuf; } +Anope::string Anope::Resolve(const Anope::string &host, int type) +{ + Anope::string result = host; + + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = type; + + Log(LOG_DEBUG_2) << "Resolver: BlockingQuery: Looking up " << host; + + addrinfo *addrresult = NULL; + if (getaddrinfo(host.c_str(), NULL, &hints, &addrresult) == 0) + { + sockaddrs addr; + memcpy(&addr, addrresult->ai_addr, addrresult->ai_addrlen); + try + { + result = addr.addr(); + Log(LOG_DEBUG_2) << "Resolver: " << host << " -> " << result; + } + catch (const SocketException &) { } + + freeaddrinfo(addrresult); + } + + return result; +} + diff --git a/src/module.cpp b/src/module.cpp index e9c130076..396aa74e2 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -9,8 +9,8 @@ #include "services.h" #include "modules.h" -#include "dns.h" #include "language.h" +#include "account.h" #ifdef GETTEXT_FOUND # include <libintl.h> @@ -46,8 +46,6 @@ Module::Module(const Anope::string &modname, const Anope::string &, ModType modt Module::~Module() { - if (DNS::Engine) - DNS::Engine->Cleanup(this); /* Detach all event hooks for this module */ ModuleManager::DetachAll(this); /* Clear any active callbacks this module has */ diff --git a/src/uplink.cpp b/src/uplink.cpp index ac77672b2..9490329b3 100644 --- a/src/uplink.cpp +++ b/src/uplink.cpp @@ -15,7 +15,6 @@ #include "config.h" #include "protocol.h" #include "servers.h" -#include "dns.h" UplinkSocket *UplinkSock = NULL; @@ -48,10 +47,9 @@ void Uplink::Connect() if (!Config->LocalHost.empty()) UplinkSock->Bind(Config->LocalHost); FOREACH_MOD(I_OnPreServerConnect, OnPreServerConnect()); - DNS::Query rep = DNS::Manager::BlockingQuery(u->host, u->ipv6 ? DNS::QUERY_AAAA : DNS::QUERY_A); - Anope::string reply_ip = !rep.answers.empty() ? rep.answers.front().rdata : u->host; - Log(LOG_TERMINAL) << "Attempting to connect to uplink #" << (Anope::CurrentUplink + 1) << " " << u->host << " (" << reply_ip << "), port " << u->port; - UplinkSock->Connect(reply_ip, u->port); + Anope::string ip = Anope::Resolve(u->host, u->ipv6 ? AF_INET6 : AF_INET); + Log(LOG_TERMINAL) << "Attempting to connect to uplink #" << (Anope::CurrentUplink + 1) << " " << u->host << " (" << ip << "), port " << u->port; + UplinkSock->Connect(ip, u->port); } |