diff --git a/Makefile.in b/Makefile.in index 4ba88c7..f76581e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -102,7 +102,7 @@ export SRCS = charpool.cc FingerPrintResults.cc FPEngine.cc FPModel.cc idle_scan export HDRS = charpool.h FingerPrintResults.h FPEngine.h idle_scan.h MACLookup.h nmap_amigaos.h nmap_dns.h nmap_error.h nmap.h nmap_ftp.h NmapOps.h NmapOutputTable.h nmap_tty.h nmap_winconfig.h osscan2.h osscan.h output.h payload.h portlist.h portreasons.h protocols.h scan_engine.h scan_engine_connect.h scan_engine_raw.h service_scan.h services.h TargetGroup.h Target.h targets.h tcpip.h timing.h traceroute.h utils.h xml.h $(NSE_HDRS) -OBJS = charpool.o FingerPrintResults.o FPEngine.o FPModel.o idle_scan.o MACLookup.o main.o nmap_dns.o nmap_error.o nmap.o nmap_ftp.o NmapOps.o NmapOutputTable.o nmap_tty.o osscan2.o osscan.o output.o payload.o portlist.o portreasons.o protocols.o scan_engine.o scan_engine_connect.o scan_engine_raw.o service_scan.o services.o TargetGroup.o Target.o targets.o tcpip.o timing.o traceroute.o utils.o xml.o $(NSE_OBJS) +OBJS = charpool.o FingerPrintResults.o FPEngine.o FPModel.o idle_scan.o MACLookup.o nmap_dns.o nmap_error.o nmap.o nmap_ftp.o NmapOps.o NmapOutputTable.o nmap_tty.o osscan2.o osscan.o output.o payload.o portlist.o portreasons.o protocols.o scan_engine.o scan_engine_connect.o scan_engine_raw.o service_scan.o services.o TargetGroup.o Target.o targets.o tcpip.o timing.o traceroute.o utils.o xml.o $(NSE_OBJS) # %.o : %.cc -- nope this is a GNU extension .cc.o: @@ -116,10 +116,10 @@ FPModel.o: CXXFLAGS += -g0 all: @LUA_BUILD@ @LIBLINEAR_BUILD@ @PCAP_BUILD@ @PCRE_BUILD@ @DNET_BUILD@ @NBASE_BUILD@ @NSOCK_BUILD@ @NCAT_BUILD@ @NMAP_UPDATE_BUILD@ netutil_build $(MAKE) $(TARGET) $(BUILDZENMAP) $(BUILDNDIFF) $(BUILDNPING) -$(TARGET): @LUA_DEPENDS@ @LIBLINEAR_DEPENDS@ @PCAP_DEPENDS@ @PCRE_DEPENDS@ @DNET_DEPENDS@ $(NBASEDIR)/libnbase.a $(NSOCKDIR)/src/libnsock.a libnetutil/libnetutil.a $(OBJS) +$(TARGET): @LUA_DEPENDS@ @LIBLINEAR_DEPENDS@ @PCAP_DEPENDS@ @PCRE_DEPENDS@ @DNET_DEPENDS@ $(NBASEDIR)/libnbase.a $(NSOCKDIR)/src/libnsock.a libnetutil/libnetutil.a $(OBJS) main.o @echo Compiling nmap rm -f $@ - $(CXX) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) + $(CXX) $(LDFLAGS) -o $@ $(OBJS) main.o $(LIBS) build-%: %/Makefile cd $* && $(MAKE) @@ -185,7 +185,7 @@ release-rpms: web: cd $(NMAPDEVDIR) && $(MAKE) web -clean: @LUA_CLEAN@ @LIBLINEAR_CLEAN@ @PCAP_CLEAN@ @PCRE_CLEAN@ @DNET_CLEAN@ nsock_clean nbase_clean netutil_clean my_clean @NPING_CLEAN@ @ZENMAP_CLEAN@ @NCAT_CLEAN@ @NMAP_UPDATE_CLEAN@ @NDIFF_CLEAN@ +clean: @LUA_CLEAN@ @LIBLINEAR_CLEAN@ @PCAP_CLEAN@ @PCRE_CLEAN@ @DNET_CLEAN@ nsock_clean nbase_clean netutil_clean my_clean @NPING_CLEAN@ @ZENMAP_CLEAN@ @NCAT_CLEAN@ @NMAP_UPDATE_CLEAN@ @NDIFF_CLEAN@ tests/clean my_clean: rm -f dependencies.mk @@ -297,6 +297,12 @@ nmap-update/default_channel.h: nmap.h sed -e 's/^#[ \t]*define[ \t]\+DEFAULT_CHANNEL[ \t]\+\(".*"\)/#define DEFAULT_CHANNEL "$(NMAP_UPDATE_CHANNEL)"/' $@ > $@.tmp mv -f $@.tmp $@ +tests/check_dns: $(OBJS) + $(CXX) -o $@ $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) $^ $(LIBS) tests/nmap_dns_test.cc + +tests/clean: + @rm -f tests/check_dns + # By default distutils rewrites installed scripts to hardcode the # location of the Python interpreter they were built with (something # like #!/usr/bin/python2.4). This is the wrong thing to do when @@ -408,7 +414,10 @@ nsock_check: zenmap_check: @cd $(ZENMAPDIR)/test && $(PYTHON) run_tests.py -check: @NCAT_CHECK@ @NSOCK_CHECK@ @ZENMAP_CHECK@ @NSE_CHECK@ @NDIFF_CHECK@ +dns_check: tests/check_dns + $< + +check: @NCAT_CHECK@ @NSOCK_CHECK@ @ZENMAP_CHECK@ @NSE_CHECK@ @NDIFF_CHECK@ dns_check ${srcdir}/configure: configure.ac cd ${srcdir} && autoconf diff --git a/nbase/nbase.h b/nbase/nbase.h index 2c1616b..a63475c 100644 --- a/nbase/nbase.h +++ b/nbase/nbase.h @@ -224,6 +224,7 @@ #undef NDEBUG /* Integer types */ +#include typedef uint8_t u8; typedef int8_t s8; typedef uint16_t u16; diff --git a/nbase/nbase_ipv6.h b/nbase/nbase_ipv6.h index 7fd16f6..7737b88 100644 --- a/nbase/nbase_ipv6.h +++ b/nbase/nbase_ipv6.h @@ -271,5 +271,7 @@ int getaddrinfo(const char *node, const char *service, const char *gai_strerror(int errcode); #endif +int sockaddr_storage_inet_pton(const char * ip_str, struct sockaddr_storage * addr); +const char *sockaddr_storage_iptop(const struct sockaddr_storage * addr, char * dst); #endif /* NBASE_IPV6_H */ diff --git a/nbase/nbase_misc.c b/nbase/nbase_misc.c index cd53465..12ecc9e 100644 --- a/nbase/nbase_misc.c +++ b/nbase/nbase_misc.c @@ -910,3 +910,45 @@ char *executable_path(const char *argv0) { return path; } + +int sockaddr_storage_inet_pton(const char * ip_str, struct sockaddr_storage * addr) +{ +#if HAVE_IPV6 + struct sockaddr_in6 * addrv6p = (struct sockaddr_in6 *) addr; + if ( 1 == inet_pton(AF_INET6, ip_str, &(addrv6p->sin6_addr)) ) + { + addr->ss_family = AF_INET6; + return 1; + } +#endif // HAVE_IPV6 + + struct sockaddr_in * addrv4p = (struct sockaddr_in *) addr; + if ( 1 == inet_pton(AF_INET, ip_str, &(addrv4p->sin_addr)) ) + { + addr->ss_family = AF_INET; + return 1; + } + + return 0; +} + +const char *sockaddr_storage_iptop(const struct sockaddr_storage * addr, char * dst) +{ + switch (addr->ss_family){ + case AF_INET: + { + const struct sockaddr_in * ipv4_ptr = (const struct sockaddr_in *) addr; + return inet_ntop(addr->ss_family, &(ipv4_ptr->sin_addr), dst, INET_ADDRSTRLEN); + } +#if HAVE_IPV6 + case AF_INET6: + { + const struct sockaddr_in6 * addrv6p = (struct sockaddr_in6 *) addr; + return inet_ntop(addr->ss_family, &(addrv6p->sin6_addr), dst, INET6_ADDRSTRLEN); + } +#endif + default: + { + return NULL; + }} +} diff --git a/nmap_dns.cc b/nmap_dns.cc index e0c4ade..5e12c0a 100644 --- a/nmap_dns.cc +++ b/nmap_dns.cc @@ -162,13 +162,10 @@ // --see http://accs-net.com/hosts/how_to_use_hosts.html // // -// Created by Doug Hoyte -// doug at hcsw.org -// http://www.hcsw.org +// Created by Doug Hoyte http://www.hcsw.org +// DNS Caching and aging added by Eddie Bell ejlbell () gmail com 2007 +// IPv6 and improved DNS cache by Gioacchino Mazzurco 2015 -/* - * DNS Caching and aging added by Eddie Bell ejlbell () gmail com 2007 - */ // TODO: // @@ -194,7 +191,6 @@ #include #include #include -#include extern NmapOps o; @@ -261,18 +257,12 @@ static int read_timeouts[][4] = { // retransmission. This should almost never happen. (in milliseconds) #define WRITE_TIMEOUT 100 -// Size of hash table used to hold the hosts from /etc/hosts -#define HASH_TABLE_SIZE 256 - -// Hash macro for etchosts -#define IP_HASH(x) (ntohl(x)%HASH_TABLE_SIZE) - //------------------- Internal Structures --------------------- struct dns_server; struct request; -struct host_elem; +typedef struct sockaddr_storage sockaddr_storage; struct dns_server { std::string hostname; @@ -297,15 +287,136 @@ struct request { u16 id; }; -struct host_elem { - std::string name; - u32 addr; +class HostElem +{ +public: + HostElem(const std::string & name_, const sockaddr_storage & ip) : + name(name_), addr(ip), cache_hits(0) {} + ~HostElem() {} + + /* Ages entries and return true with a cache hit of 0 (the least used) */ + static bool isTimeToClean(HostElem he) + { + if(he.cache_hits) + { + he.cache_hits >>= 1; + return false; + } + + return true; + } + + const std::string name; + const sockaddr_storage addr; u8 cache_hits; }; +class HostCacheLine : public std::list{}; + +class HostCache +{ +public: + // TODO: avoid hardcode this constant + HostCache() : lines_count(256), hash_mask(lines_count-1), + hosts_storage(new HostCacheLine[lines_count]), elements_count(0) + {} + ~HostCache() + { + delete[] hosts_storage; + } + + u32 hash(sockaddr_storage ip) + { + u32 ret = 0; + + switch (ip.ss_family) + { + case AF_INET: + { + u8 * ipv4 = (u8 *) &((const struct sockaddr_in *) &ip)->sin_addr; + // Shuffle bytes a little so we avoid awful performances in commons + // usages patterns like 10.0.1-255.1 and lines_count 256 + ret = ipv4[0] + (ipv4[1]<<3) + (ipv4[2]<<5) + (ipv4[3]<<7); + break; + } + case AF_INET6: + { + const struct sockaddr_in6 * sa6 = (const struct sockaddr_in6 *) &ip; + u32 * ipv6 = (u32 *) sa6->sin6_addr.s6_addr; + ret = ipv6[0] + ipv6[1] + ipv6[2] + ipv6[3]; + break; + } + } + + return ret & hash_mask; + } + + /* Add to the dns cache. If there are too many entries + * we age and remove the least frequently used ones to + * make more space. */ + bool add( const sockaddr_storage & ip, const std::string & hname) + { + std::string discard; + if(lookup(ip, discard)) return false; + + if(elements_count >= lines_count) prune(); + + HostElem he(hname, ip); + hosts_storage[hash(ip)].push_back(he); + ++elements_count; + return true; + } + + u32 prune() + { + u32 original_count = elements_count; + for(u32 i = 0; i < lines_count; ++i) + { + std::list::iterator it = find_if(hosts_storage[i].begin(), + hosts_storage[i].end(), + HostElem::isTimeToClean); + while ( it != hosts_storage[i].end() ) + { + hosts_storage[i].erase(it); + assert(elements_count > 0); + --elements_count; + } + } + + return original_count - elements_count; + } + + /* Search for a hostname in the cache and increment + * its cache hit counter if found */ + bool lookup(const sockaddr_storage & ip, std::string & name) + { + std::list::iterator hostI; + uint ip_hash = hash(ip); + for( hostI = hosts_storage[ip_hash].begin(); + hostI != hosts_storage[ip_hash].end(); + ++hostI) + { + if (sockaddr_storage_equal(&hostI->addr, &ip)) + { + if(hostI->cache_hits < UCHAR_MAX) + hostI->cache_hits++; + name = hostI->name; + return true; + } + } + return false; + } + +protected: + const u32 lines_count; + const u32 hash_mask; + HostCacheLine * const hosts_storage; + u32 elements_count; +}; //------------------- Globals --------------------- +u16 DNS::Factory::progressiveId = get_random_u16(); static std::list servs; static std::list new_reqs; static std::list cname_reqs; @@ -313,12 +424,11 @@ static int total_reqs; static nsock_pool dnspool=NULL; /* The DNS cache, not just for entries from /etc/hosts. */ -static std::list etchosts[HASH_TABLE_SIZE]; +static HostCache host_cache; static int stat_actual, stat_ok, stat_nx, stat_sf, stat_trans, stat_dropped, stat_cname; static struct timeval starttv; static int read_timeout_index; -static u16 id_counter; static int firstrun=1; static ScanProgressMeter *SPM; @@ -327,8 +437,6 @@ static ScanProgressMeter *SPM; //------------------- Prototypes and macros --------------------- static void put_dns_packet_on_wire(request *req); -static const char *lookup_etchosts(u32 ip); -static void addto_etchosts(u32 ip, const char *hname); #define ACTION_FINISHED 0 #define ACTION_CNAME_LIST 1 @@ -370,20 +478,6 @@ static void close_dns_servers() { } -// Inserts an integer (endian non-specifically) into a DNS packet. -// Returns number of bytes written -static int add_integer_to_dns_packet(char *packet, int c) { - char tpnum[4]; - int tplen; - - sprintf(tpnum, "%d", c); - tplen = strlen(tpnum); - packet[0] = (char) tplen; - memcpy(packet+1, tpnum, tplen); - - return tplen+1; -} - // Puts as many packets on the line as capacity will allow static void do_possible_writes() { std::list::iterator servI; @@ -425,26 +519,13 @@ static void write_evt_handler(nsock_pool nsp, nsock_event evt, void *req_v) { // (calls nsock_write()). Does various other tasks like recording // the time for the timeout. static void put_dns_packet_on_wire(request *req) { - char packet[512]; - int plen=0; - u32 ip; - struct timeval now, timeout; - - ip = (u32) ntohl(req->targ->v4host().s_addr); - packet[0] = (req->id >> 8) & 0xFF; - packet[1] = req->id & 0xFF; - plen += 2; + const size_t maxlen = 512; + u8 packet[maxlen]; + size_t plen=0; - memcpy(packet+plen, "\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00", 10); - plen += 10; - - plen += add_integer_to_dns_packet(packet+plen, ip & 0xFF); - plen += add_integer_to_dns_packet(packet+plen, (ip>>8) & 0xFF); - plen += add_integer_to_dns_packet(packet+plen, (ip>>16) & 0xFF); - plen += add_integer_to_dns_packet(packet+plen, (ip>>24) & 0xFF); + struct timeval now, timeout; - memcpy(packet+plen, "\x07in-addr\004arpa\x00\x00\x0c\x00\x01", 18); - plen += 18; + plen = DNS::Factory::buildReverseRequest(*req->targ->TargetSockAddr(), packet, maxlen); req->curr_server->write_busy = 1; req->curr_server->reqs_on_wire++; @@ -455,7 +536,7 @@ static void put_dns_packet_on_wire(request *req) { req->tries++; - nsock_write(dnspool, req->curr_server->nsd, write_evt_handler, WRITE_TIMEOUT, req, packet, plen); + nsock_write(dnspool, req->curr_server->nsd, write_evt_handler, WRITE_TIMEOUT, req, reinterpret_cast(packet), plen); } // Processes DNS packets that have timed out @@ -542,37 +623,48 @@ static int deal_with_timedout_reads() { // After processing a DNS response, we search through the IPs we're // looking for and update their results as necessary. // Returns non-zero if this matches a query we're looking for -static int process_result(u32 ia, char *result, int action, u16 id) { +static int process_result(const sockaddr_storage &ip, const std::string &result, int action, u16 id) +{ std::list::iterator servI; std::list::iterator reqI; request *tpreq; - for(servI = servs.begin(); servI != servs.end(); servI++) { - for(reqI = servI->in_process.begin(); reqI != servI->in_process.end(); reqI++) { + for(servI = servs.begin(); servI != servs.end(); servI++) + { + /* TODO: This is higly inefficent we do on average + * (0.5 * servs.size() * in_process.size()) + * iterations to find a a request, we should use a map + * to do this efficiently + */ + for(reqI = servI->in_process.begin(); reqI != servI->in_process.end(); reqI++) + { tpreq = *reqI; - - if (id == tpreq->id) { - - if (ia != 0 && tpreq->targ->v4host().s_addr != ia) + if (id == tpreq->id) + { + if(!result.empty() && (!sockaddr_storage_equal(&ip, tpreq->targ->TargetSockAddr()))) continue; - if (action == ACTION_CNAME_LIST || action == ACTION_FINISHED) { - servI->capacity += CAPACITY_UP_STEP; - check_capacities(&*servI); + if (action == ACTION_CNAME_LIST || action == ACTION_FINISHED) + { + servI->capacity += CAPACITY_UP_STEP; + check_capacities(&*servI); - if (result) { - tpreq->targ->setHostName(result); - addto_etchosts(tpreq->targ->v4hostip()->s_addr, result); - } + if(!result.empty()) + { + tpreq->targ->setHostName(result.c_str()); + host_cache.add(* tpreq->targ->TargetSockAddr(), result); + } - servI->in_process.remove(tpreq); - servI->reqs_on_wire--; + servI->in_process.remove(tpreq); + servI->reqs_on_wire--; - total_reqs--; + total_reqs--; if (action == ACTION_CNAME_LIST) cname_reqs.push_back(tpreq); if (action == ACTION_FINISHED) delete tpreq; - } else { + } + else + { memcpy(&tpreq->timeout, nsock_gettimeofday(), sizeof(struct timeval)); deal_with_timedout_reads(); } @@ -591,115 +683,11 @@ static int process_result(u32 ia, char *result, int action, u16 id) { return 0; } - -// Gets an IP address from a X.X.X.X.in-addr.arpa DNS -// encoded string inside a packet. -// maxlen is the very maximum length (in total bytes) -// that should be processed -static u32 parse_inaddr_arpa(unsigned char *buf, int maxlen) { - u32 ip=0; - int i, j; - - if (maxlen <= 0) return 0; - - for (i=0; i<=3; i++) { - if (buf[0] < 1 || buf[0] > 3) return 0; - - maxlen -= buf[0] + 1; - if (maxlen <= 0) return 0; - - for (j=1; j<=buf[0]; j++) if (!isdigit((int) buf[j])) return 0; - - ip |= atoi((char *) buf+1) << (8*i); - buf += buf[0] + 1; - } - - if (maxlen < 14) return 0; // length of the following string - if (strcasecmp((char *) buf, "\x07in-addr\004arpa\0")) return 0; - - return ntohl(ip); -} - - -// Turns a DNS packet encoded name (see the RFC) and turns it into -// a normal decimal separated hostname. -// ASSUMES NAME LENGTH/VALIDITY HAS ALREADY BEEN VERIFIED -static int encoded_name_to_normal(const unsigned char *buf, char *output, int outputsize) { - int len; - char *p; - - p = output; - - /* Special case: keep the trailing dot only for the name ".". */ - if (buf[0] == 0) { - if (p + 2 > output + outputsize) - return -1; - *p++ = '.'; - *p++ = '\0'; - return 0; - } - - while ((len = buf[0]) != 0) { - /* Add a dot before every component but the first. */ - if (p > output) { - if (p + 1 > output + outputsize) - return -1; - *p++ = '.'; - } - - if (p + len > output + outputsize) - return -1; - memcpy(p, buf + 1, len); - p += len; - buf += 1 + len; - } - - if (p + 1 > output + outputsize) - return -1; - *p++ = '\0'; - - return 0; -} - - -// Takes a pointer to the start of a DNS name inside a packet. It makes -// sure that there is enough space in the name, deals with compression, etc. -static int advance_past_dns_name(u8 *buf, int buflen, int curbuf, - int *nameloc) { - int compression=0; - - if (curbuf <= 0 || curbuf >= buflen) return -1; - - if ((buf[curbuf] & 0xc0)) { - // Need 2 bytes for compression info - if (curbuf + 1 >= buflen) return -1; - - // Compression is OK - compression = curbuf+2; - curbuf = (buf[curbuf+1] + (buf[curbuf] << 8)) & 0x3FFF; - if (curbuf < 0 || curbuf >= buflen) return -1; - } - - if (nameloc != NULL) *nameloc = curbuf; - - while(buf[curbuf]) { - if (curbuf + buf[curbuf] >= buflen || buf[curbuf] <= 0) return -1; - curbuf += buf[curbuf] + 1; - } - - if (compression) return compression; - else return curbuf+1; -} - // Nsock read handler. One nsock read for each DNS server exists at each // time. This function uses various helper functions as defined above. -static void read_evt_handler(nsock_pool nsp, nsock_event evt, void *nothing) { +static void read_evt_handler(nsock_pool nsp, nsock_event evt, void *) { u8 *buf; - int buflen, curbuf=0; - int i, nameloc, rdlen, atype, aclass; - int errcode=0; - int queries, answers; - u16 packet_id; + int buflen; if (total_reqs >= 1) nsock_read(nsp, nse_iod(evt), read_evt_handler, -1, NULL); @@ -714,120 +702,102 @@ static void read_evt_handler(nsock_pool nsp, nsock_event evt, void *nothing) { buf = (unsigned char *) nse_readbuf(evt, &buflen); - // Size of header is 12, and we must have additional data as well - if (buflen <= 12) return; - - packet_id = buf[1] + (buf[0] << 8); - - // Check that this is a response, standard query, and that no truncation was performed - // 0xFA == 11111010 (we're not concerned with AA or RD bits) - if ((buf[2] & 0xFA) != 0x80) return; - - // Check that the zero field is all zeros and there is no error condition. - // We don't care if recursion is available or not since we might be querying - // an authoritative DNS server. - if (buf[3] != 0x80 && buf[3] != 0) { - if ((buf[3] & 0xF) == 2) errcode = 2; - else if ((buf[3] & 0xF) == 3) errcode = 3; - else return; - } - - queries = buf[5] + (buf[4] << 8); - answers = buf[7] + (buf[6] << 8); + DNS::Packet p; + size_t readed_bytes = p.parseFromBuffer(buf, buflen); + if(readed_bytes < DNS::DATA) return; - // With a normal resolution, we should have 1+ queries and 1+ answers. - // If the domain doesn't resolve (NXDOMAIN or SERVFAIL) we should have - // 1+ queries and 0 answers: - if (errcode) { - int found; - - // NXDomain means we're finished (doesn't exist for sure) - // but SERVFAIL might just mean a server timeout - found = process_result(0, NULL, errcode == 3 ? ACTION_FINISHED : ACTION_TIMEOUT, packet_id); + // We should have 1+ queries: + u16 &f = p.flags; + if(p.queries.empty() || !DNS_HAS_FLAG(f, DNS::RESPONSE) || + !DNS_HAS_FLAG(f, DNS::OP_STANDARD_QUERY) || + (f & DNS::ZERO) || DNS_HAS_ERR(f, DNS::ERR_FORMAT) || + DNS_HAS_ERR(f, DNS::ERR_NOT_IMPLEMENTED) || DNS_HAS_ERR(f, DNS::ERR_REFUSED)) + return; - if (errcode == 2 && found) { - if (o.debugging >= TRACE_DEBUG_LEVEL) log_write(LOG_STDOUT, "mass_rdns: SERVFAIL \n", packet_id); - stat_sf++; - } else if (errcode == 3 && found) { - if (o.debugging >= TRACE_DEBUG_LEVEL) log_write(LOG_STDOUT, "mass_rdns: NXDOMAIN \n", packet_id); + if (DNS_HAS_ERR(f, DNS::ERR_NAME)) + { + sockaddr_storage discard; + if(process_result(discard, "", ACTION_FINISHED, p.id)) + { + if (o.debugging >= TRACE_DEBUG_LEVEL) + log_write(LOG_STDOUT, "mass_rdns: NXDOMAIN \n", p.id); output_summary(); stat_nx++; - } + } return; } - if (queries <= 0 || answers <= 0) return; - - curbuf = 12; - - // Need to safely skip past QUERY section - - for (i=0; i= buflen) return; - curbuf += 4; - } - - // We're now at the ANSWER section - - for (i=0; i= buflen) return; - - atype = buf[curbuf+1] + (buf[curbuf+0] << 8); - aclass = buf[curbuf+3] + (buf[curbuf+2] << 8); - rdlen = buf[curbuf+9] + (buf[curbuf+8] << 8); - curbuf += 10; - - if (atype == 12 && aclass == 1) { - // TYPE 12 is PTR - struct in_addr ia; - char outbuf[512]; - - ia.s_addr = parse_inaddr_arpa(buf+nameloc, buflen-nameloc); - if (ia.s_addr == 0) return; - - curbuf = advance_past_dns_name(buf, buflen, curbuf, &nameloc); - if (curbuf == -1 || curbuf > buflen) return; + if (DNS_HAS_ERR(f, DNS::ERR_SERVFAIL)) + { + sockaddr_storage discard; + if (process_result(discard, "", ACTION_TIMEOUT, p.id)) + { + if (o.debugging >= TRACE_DEBUG_LEVEL) + log_write(LOG_STDOUT, "mass_rdns: SERVFAIL \n", p.id); + stat_sf++; + } - if (encoded_name_to_normal(buf+nameloc, outbuf, sizeof(outbuf)) == -1) return; + return; + } - if (process_result(ia.s_addr, outbuf, ACTION_FINISHED, packet_id)) { - if (o.debugging >= TRACE_DEBUG_LEVEL) log_write(LOG_STDOUT, "mass_rdns: OK MATCHED <%s> to <%s>\n", inet_ntoa(ia), outbuf); - output_summary(); - stat_ok++; + // If there is no errors and no answhere stop processing the event + if(p.answers.empty()) return; + + for(std::list::const_iterator it = p.answers.begin(); + it != p.answers.end(); ++it ) + { + const DNS::Answer &a = *it; + if(a.record_class == DNS::IN) + { + switch(a.record_type) + { + case DNS::PTR: + { + DNS::PTR_Record * ptr = static_cast(a.record); + + sockaddr_storage ip; + if(DNS::Factory::ptrToIp(a.name, ip)) + if (process_result(ip, ptr->value, ACTION_FINISHED, p.id)) + { + if (o.debugging >= TRACE_DEBUG_LEVEL) + { + char ipstr[INET6_ADDRSTRLEN]; + sockaddr_storage_iptop(&ip, ipstr); + log_write(LOG_STDOUT, "mass_rdns: OK MATCHED <%s> to <%s>\n", + ipstr, + ptr->value.c_str()); + } + output_summary(); + stat_ok++; + } + break; + } + case DNS::CNAME: + { + sockaddr_storage ip; + if(DNS::Factory::ptrToIp(a.name, ip)) + { + if (o.debugging >= TRACE_DEBUG_LEVEL) + { + char ipstr[INET6_ADDRSTRLEN]; + sockaddr_storage_iptop(&ip, ipstr); + log_write(LOG_STDOUT, "mass_rdns: CNAME found for <%s>\n", ipstr); + } + process_result(ip, "", ACTION_CNAME_LIST, p.id); + } + break; + } + default: + break; } - } else if (atype == 5 && aclass == 1) { - // TYPE 5 is CNAME - struct in_addr ia; - - ia.s_addr = parse_inaddr_arpa(buf+nameloc, buflen-nameloc); - if (ia.s_addr == 0) return; - - if (o.debugging >= TRACE_DEBUG_LEVEL) log_write(LOG_STDOUT, "mass_rdns: CNAME found for <%s>\n", inet_ntoa(ia)); - process_result(ia.s_addr, NULL, ACTION_CNAME_LIST, packet_id); - } else { - if (rdlen < 0 || rdlen + curbuf >= buflen) return; - curbuf += rdlen; } - - if (curbuf >= buflen) return; } - } // nsock connect handler - Empty because it doesn't really need to do anything... -static void connect_evt_handler(nsock_pool nsp, nsock_event evt, void *servers) { -} +static void connect_evt_handler(nsock_pool, nsock_event, void *) {} // Adds DNS servers to the dns_server list. They can be separated by @@ -976,7 +946,7 @@ void win32_read_registry() { } } -#endif +#endif // WIN32 @@ -986,7 +956,7 @@ static void parse_resolvdotconf() { FILE *fp; char buf[2048], *tp; char fmt[32]; - char ipaddr[INET6_ADDRSTRLEN]; + char ipaddr[INET6_ADDRSTRLEN+1]; fp = fopen("/etc/resolv.conf", "r"); if (fp == NULL) { @@ -994,7 +964,7 @@ static void parse_resolvdotconf() { return; } - Snprintf(fmt, sizeof(fmt), "nameserver %%%us", INET6_ADDRSTRLEN-1); + Snprintf(fmt, sizeof(fmt), "nameserver %%%us", INET6_ADDRSTRLEN); while (fgets(buf, sizeof(buf), fp)) { tp = buf; @@ -1016,8 +986,8 @@ static void parse_resolvdotconf() { static void parse_etchosts(const char *fname) { FILE *fp; - char buf[2048], hname[256], ipaddrstr[16], *tp; - struct in_addr ia; + char buf[2048], hname[256], ipaddrstr[INET6_ADDRSTRLEN+1], *tp; + sockaddr_storage ia; fp = fopen(fname, "r"); if (fp == NULL) return; // silently is OK @@ -1033,74 +1003,29 @@ static void parse_etchosts(const char *fname) { // Skip any leading whitespace while (*tp == ' ' || *tp == '\t') tp++; - if (sscanf(tp, "%15s %255s", ipaddrstr, hname) == 2) { - if (inet_pton(AF_INET, ipaddrstr, &ia)) - addto_etchosts(ia.s_addr, hname); - } - } - - fclose(fp); -} - -/* Executed when the DNS cache is full, ages entries - * and removes any with a cache hit of 0 (the least used) */ -bool remove_and_age(host_elem &host) { - if(host.cache_hits) { - host.cache_hits /=2; - return false; - } else - return true; -} - -/* Add to the dns cache. If there are too many entries - * we age and remove the least frequently used ones to - * make more space. */ -static void addto_etchosts(u32 ip, const char *hname) { - static u16 total_size = 0; - std::list::iterator it; - host_elem he; - int i; - - if(lookup_etchosts(ip) != NULL) - return; - - while(total_size >= HASH_TABLE_SIZE) { - for(i = 0; i < HASH_TABLE_SIZE; i++) { - while((it = find_if(etchosts[i].begin(), etchosts[i].end(), remove_and_age)) != etchosts[i].end()) { - etchosts[i].erase(it); - /* We don't want total_size to become out of sync with the actual number - of entries. */ - assert(total_size > 0); - total_size--; + std::stringstream pattern; + pattern << "%" << INET6_ADDRSTRLEN << "s %255s"; + if (sscanf(tp, pattern.str().c_str(), ipaddrstr, hname) == 2) + if (sockaddr_storage_inet_pton(ipaddrstr, &ia)) + { + const std::string hname_ = hname; + host_cache.add(ia, hname_); } - } } - he.name = hname; - he.addr = ip; - he.cache_hits = 0; - etchosts[IP_HASH(ip)].push_back(he); - total_size++; -} -/* Search for a hostname in the cache and increment - * its cache hit counter if found */ -static const char *lookup_etchosts(u32 ip) { - std::list::iterator hostI; - int localIP_Hash = IP_HASH(ip); - for(hostI = etchosts[localIP_Hash].begin(); hostI != etchosts[localIP_Hash].end(); hostI++) { - if (hostI->addr == ip) { - if(hostI->cache_hits < UCHAR_MAX) - hostI->cache_hits++; - return hostI->name.c_str(); - } - } - return NULL; + fclose(fp); } /* External interface to dns cache */ -const char *lookup_cached_host(u32 ip) { - const char *tmp = lookup_etchosts(ip); - return tmp; +int lookup_cached_host(const struct sockaddr_storage * ip, char *name, size_t maxlen = HOST_NAME_MAX) +{ + std::string str_name; + if (host_cache.lookup(*ip, str_name)) + { + strncpy(name, str_name.c_str(), maxlen); + return 1; + } + return 0; } static void etchosts_init(void) { @@ -1129,7 +1054,7 @@ static void etchosts_init(void) { #else parse_etchosts("/etc/hosts"); -#endif +#endif // WIN32 } /* Initialize the global servs list of DNS servers. If the --dns-servers option @@ -1175,24 +1100,28 @@ static void nmap_mass_rdns_core(Target **targets, int num_targets) { // If necessary, set up the dns server list init_servs(); - if (servs.size() == 0 && firstrun) error("mass_dns: warning: Unable to determine any DNS servers. Reverse DNS is disabled. Try using --system-dns or specify valid servers with --dns-servers"); + if (servs.size() == 0 && firstrun) error("mass_dns: warning: Unable to " + "determine any DNS servers. Reverse" + " DNS is disabled. Try using " + "--system-dns or specify valid " + "servers with --dns-servers"); - // If necessary, set up the /etc/hosts hashtable + // If necessary, read /etc/hosts and put entries into the hashtable etchosts_init(); total_reqs = 0; - id_counter = get_random_u16(); // Set up the request structure - for(hostI = targets; hostI < targets+num_targets; hostI++) { + for(hostI = targets; hostI < targets+num_targets; hostI++) + { if (!((*hostI)->flags & HOST_UP) && !o.resolve_all) continue; - // See if it's in /etc/hosts or cached - assert((*hostI)->af() == AF_INET); - tpname = lookup_etchosts((u32) (*hostI)->v4hostip()->s_addr); - if (tpname) { + // See if it's cached + std::string res; + if (host_cache.lookup(*(*hostI)->TargetSockAddr(), res)) { + tpname = res.c_str(); (*hostI)->setHostName(tpname); continue; } @@ -1201,7 +1130,7 @@ static void nmap_mass_rdns_core(Target **targets, int num_targets) { tpreq->targ = *hostI; tpreq->tries = 0; tpreq->servers_tried = 0; - tpreq->id = id_counter++; + tpreq->id = DNS::Factory::progressiveId; new_reqs.push_back(tpreq); @@ -1342,8 +1271,7 @@ void nmap_mass_rdns(Target **targets, int num_targets) { stat_actual = stat_ok = stat_nx = stat_sf = stat_trans = stat_dropped = stat_cname = 0; - // mass_dns only supports IPv4. - if (o.mass_dns && o.af() == AF_INET) + if (o.mass_dns) nmap_mass_rdns_core(targets, num_targets); else nmap_system_rdns_core(targets, num_targets); @@ -1352,7 +1280,7 @@ void nmap_mass_rdns(Target **targets, int num_targets) { if (stat_actual > 0) { if (o.debugging || o.verbose >= 3) { - if (o.mass_dns && o.af() == AF_INET) { + if (o.mass_dns) { // #: Number of DNS servers used // OK: Number of fully reverse resolved queries // NX: Number of confirmations of 'No such reverse domain eXists' @@ -1389,3 +1317,338 @@ std::list get_dns_servers() { } return serverList; } + +bool DNS::Factory::ipToPtr(const sockaddr_storage &ip, std::string &ptr) +{ + switch (ip.ss_family) { + case AF_INET: + { + ptr.clear(); + char ipv4_c[INET_ADDRSTRLEN]; + if(!sockaddr_storage_iptop(&ip, ipv4_c)) return false; + + std::string ipv4 = ipv4_c; + std::string octet; + for (std::string::const_reverse_iterator c=ipv4.rbegin(); c != ipv4.rend(); ++c) + if((*c)=='.') + { + ptr += octet + "."; + octet.clear(); + } + else + octet = (*c) + octet; + + ptr += octet + IPV4_PTR_DOMAIN; + + break; + } + case AF_INET6: + { + ptr.clear(); + const struct sockaddr_in6 &s6 = (const struct sockaddr_in6 &) ip; + const u8 * ipv6 = s6.sin6_addr.__in6_u.__u6_addr8; + for (short i=15; i>=0; --i) + { + char tmp[3]; + sprintf(tmp, "%02x", ipv6[i]); + ptr += '.'; + ptr += tmp[1]; + ptr += '.'; + ptr += tmp[0]; + } + ptr.erase(ptr.begin()); + ptr += IPV6_PTR_DOMAIN; + break; + } + default: + return false; + } + return true; +} +bool DNS::Factory::ptrToIp(const std::string &ptr, sockaddr_storage &ip) +{ + std::string ip_str; + + size_t pos = ptr.rfind(IPV6_PTR_DOMAIN); + if(pos != std::string::npos) + { + u8 counter = 0; + for (std::string::const_reverse_iterator it = ptr.rend()-pos; it != ptr.rend(); ++it) + { + const char &c = *it; + if(c != '.') + { + ip_str += c; + if(++counter==4) counter=0, ip_str+=':'; + } + } + + std::string::iterator it = ip_str.end()-1; + if( *it == ':') ip_str.erase(it); + } + + std::string mptr = '.' + ptr; + pos = mptr.rfind(IPV4_PTR_DOMAIN); + if(pos != std::string::npos) + { + + std::string octet; + for (std::string::const_reverse_iterator it = mptr.rend()-pos; it != mptr.rend(); ++it) + { + const char &c = *it; + if(c == '.') + { + std::reverse(octet.begin(), octet.end()); + ip_str += octet + '.'; + octet.clear(); + } + else octet += c; + } + + std::string::iterator it = ip_str.end()-1; + if( *it == '.') ip_str.erase(it); + } + + if(ip_str.empty()) + return false; + + sockaddr_storage_inet_pton(ip_str.c_str(), &ip); + return true; +} +size_t DNS::Factory::buildSimpleRequest(const std::string &name, RECORD_TYPE rt, u8 *buf, size_t maxlen) +{ + size_t ret=0 , tmp=0; + DNS_CHECK_ACCUMLATE(ret, tmp, putUnsignedShort(progressiveId++, buf, ID, maxlen)); // Postincrement inmportant here + DNS_CHECK_ACCUMLATE(ret, tmp, putUnsignedShort(OP_STANDARD_QUERY | RECURSION_DESIRED, buf, FLAGS_OFFSET, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, putUnsignedShort(1, buf, QDCOUNT, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, putUnsignedShort(0, buf, ANCOUNT, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, putUnsignedShort(0, buf, NSCOUNT, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, putUnsignedShort(0, buf, ARCOUNT, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, putDomainName(name, buf, DATA, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, putUnsignedShort(rt, buf, ret, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, putUnsignedShort(IN, buf, ret, maxlen)); + + return ret; +} +size_t DNS::Factory::buildReverseRequest(const sockaddr_storage &ip, u8 *buf, size_t maxlen) +{ + std::string name; + if(ipToPtr(ip,name)) + return buildSimpleRequest(name, PTR, buf, maxlen); + return 0; +} +size_t DNS::Factory::putUnsignedShort(u16 num, u8 *buf, size_t offset, size_t maxlen) +{ + size_t max_access = offset+1; + if(buf && (maxlen > max_access)) + { + buf[offset] = (num >> 8) & 0xFF; + buf[max_access] = num & 0xFF; + return 2; + } + + return 0; +} +size_t DNS::Factory::putDomainName(const std::string &name, u8 *buf, size_t offset, size_t maxlen) +{ + size_t ret=0; + if( !( buf && (maxlen > (offset + name.length() + 1))) ) return ret; + + std::string namew = name + "."; + std::string accumulator; + for (std::string::const_iterator c=namew.begin(); c != namew.end(); ++c) + { + if((*c)=='.') + { + u8 lenght = accumulator.length(); + *(buf+offset+ret) = lenght; + ret += 1; + + memcpy(buf+offset+ret, accumulator.c_str(), lenght); + ret += lenght; + accumulator.clear(); + } + else + accumulator += (*c); + } + + *(buf+offset+ret) = 0; + ret += 1; + + return ret; +} +size_t DNS::Factory::parseUnsignedShort(u16 &num, const u8 *buf, size_t offset, size_t maxlen) +{ + size_t max_access = offset+1; + if(buf && (maxlen > max_access)) + { + const u8 * n = buf + offset; + num = n[1] + (n[0]<<8); + return 2; + } + + return 0; +} +size_t DNS::Factory::parseUnsignedInt(u32 &num, const u8 *buf, size_t offset, size_t maxlen) +{ + size_t max_access = offset+3; + if(buf && (maxlen > max_access)) + { + const u8 * n = buf + offset; + num = n[3] + (n[2]<<8) + (n[1]<<16) + (n[0]<<24); + return 4; + } + + return 0; +} +size_t DNS::Factory::parseDomainName(std::string &name, const u8 *buf, size_t offset, size_t maxlen) +{ + size_t tmp, ret = 0; + + name.clear(); + while(u8 label_length = buf[offset+ret++]) // Postincrement important here + { + if((label_length & COMPRESSED_NAME) == COMPRESSED_NAME) + { + --ret; // The byte it's part of the pointer, wasn't really consumed yet + u16 real_offset; + DNS_CHECK_ACCUMLATE(ret, tmp, parseUnsignedShort(real_offset, buf, offset+ret, maxlen)); + real_offset -= COMPRESSED_NAME<<8; + if( real_offset < offset) + { + std::string val; + DNS_CHECK_ACCUMLATE(tmp, tmp, parseDomainName(val, buf, real_offset, maxlen)); + name+=val; + return ret; + } + else return 0; + } + + for(u8 i=0; isin_family = AF_INET; + ip4addr->sin_addr.s_addr = htonl(num); + + return ret; +} + +size_t DNS::Query::parseFromBuffer(const u8 *buf, size_t offset, size_t maxlen) +{ + size_t ret=0; + + if (buf && ((maxlen - offset) > 5)) + { + size_t tmp=0; + DNS_CHECK_ACCUMLATE(ret, tmp, Factory::parseDomainName(name, buf, offset+ret, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, Factory::parseUnsignedShort(record_type, buf, offset+ret, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, Factory::parseUnsignedShort(record_class, buf, offset+ret, maxlen)); + } + + return ret; +} + +size_t DNS::Answer::parseFromBuffer(const u8 *buf, size_t offset, size_t maxlen) +{ + size_t ret=0; + + if (buf && ((maxlen - offset) > 7)) + { + size_t tmp; + DNS_CHECK_ACCUMLATE(ret, tmp, Factory::parseDomainName(name, buf, offset+ret, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, Factory::parseUnsignedShort(record_type, buf, offset+ret, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, Factory::parseUnsignedShort(record_class, buf, offset+ret, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, Factory::parseUnsignedInt(ttl, buf, offset+ret, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, Factory::parseUnsignedShort(length, buf, offset+ret, maxlen)); + + DNS_CHECK_UPPER_BOUND(offset+ret+length, maxlen); + + switch(record_type) + { + case A: + { + record = new A_Record(); + break; + } + case CNAME: + { + record = new CNAME_Record(); + break; + } + case PTR: + { + record = new PTR_Record(); + break; + } + default: + return 0; + } + + DNS_CHECK_ACCUMLATE(ret, tmp, record->parseFromBuffer(buf, offset+ret, maxlen)); + } + + return ret; +} +DNS::Answer& DNS::Answer::operator=(const Answer &r) +{ + name = r.name; + record_type = r.record_type; + record_class = r.record_class; + ttl = r.ttl; + length = r.length; + record = r.record->clone(); + return *this; +} + +size_t DNS::Packet::parseFromBuffer(const u8 *buf, size_t maxlen) +{ + if( !buf || maxlen < DATA) return 0; + + size_t tmp, ret = 0; + DNS_CHECK_ACCUMLATE(ret, tmp, Factory::parseUnsignedShort(id, buf, ID, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, Factory::parseUnsignedShort(flags, buf, FLAGS_OFFSET, maxlen)); + + u16 queries_counter, answers_counter, authorities_counter, additionals_counter; + DNS_CHECK_ACCUMLATE(ret, tmp, Factory::parseUnsignedShort(queries_counter, buf, QDCOUNT, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, Factory::parseUnsignedShort(answers_counter, buf, ANCOUNT, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, Factory::parseUnsignedShort(authorities_counter, buf, NSCOUNT, maxlen)); + DNS_CHECK_ACCUMLATE(ret, tmp, Factory::parseUnsignedShort(additionals_counter, buf, ARCOUNT, maxlen)); + + queries.clear(); + for(u16 i=0; i #include +#include +#include + + +namespace DNS +{ + +#define DNS_CHECK_ACCUMLATE(accumulator, tmp, exp) \ + do { tmp = exp; if(tmp < 1) return 0 ; accumulator += tmp;} while(0) + +#define DNS_CHECK_UPPER_BOUND(accumulator, max)\ + do { if(accumulator > max) return 0; } while(0) + +#define DNS_HAS_FLAG(v,flag) ((v&flag)==flag) + +#define DNS_HAS_ERR(v, err) ((v&DNS::ERR_ALL)==err) + +typedef enum +{ + ID = 0, + FLAGS_OFFSET = 2, + QDCOUNT = 4, + ANCOUNT = 6, + NSCOUNT = 8, + ARCOUNT = 10, + DATA = 12 +} HEADER_OFFSET; + +typedef enum { + ERR_ALL = 0x0007, + CHECKING_DISABLED = 0x0010, + AUTHENTICATED_DATA = 0x0020, + ZERO = 0x0070, + RECURSION_AVAILABLE = 0x0080, + RECURSION_DESIRED = 0x0100, + TRUNCATED = 0x0200, + AUTHORITATIVE_ANSWER = 0x0400, + OP_STANDARD_QUERY = 0x0000, + OP_INVERSE_QUERY = 0x0800, // Obsoleted in RFC 3425 + OP_SERVER_STATUS = 0x1000, + RESPONSE = 0x8000 +} FLAGS; + +typedef enum { + ERR_NO = 0x0000, + ERR_FORMAT = 0x0001, + ERR_SERVFAIL = 0x0002, + ERR_NAME = 0x0003, + ERR_NOT_IMPLEMENTED = 0x0004, + ERR_REFUSED = 0x0005, +} ERRORS; + +typedef enum { + A = 1, + CNAME = 5, + PTR = 12, + AAAA = 28, +} RECORD_TYPE; + +typedef enum { + IN = 1 +} RECORD_CLASS; + +const u8 COMPRESSED_NAME = 0xc0; + +const std::string IPV4_PTR_DOMAIN = ".in-addr.arpa"; +const std::string IPV6_PTR_DOMAIN = ".ip6.arpa"; + +class Factory +{ +public: + static u16 progressiveId; + static bool ipToPtr(const sockaddr_storage &ip, std::string &ptr); + static bool ptrToIp(const std::string &ptr, sockaddr_storage &ip); + static size_t buildSimpleRequest(const std::string &name, RECORD_TYPE rt, u8 *buf, size_t maxlen); + static size_t buildReverseRequest(const sockaddr_storage &ip, u8 *buf, size_t maxlen); + static size_t putUnsignedShort(u16 num, u8 *buf, size_t offset, size_t maxlen); + static size_t putDomainName(const std::string &name, u8 *buf, size_t offset, size_t maxlen); + static size_t parseUnsignedShort(u16 &num, const u8 *buf, size_t offset, size_t maxlen); + static size_t parseUnsignedInt(u32 &num, const u8 *buf, size_t offset, size_t maxlen); + static size_t parseDomainName(std::string &name, const u8 *buf, size_t offset, size_t maxlen); +}; + +class Record +{ +public: + virtual Record * clone() = 0; + virtual ~Record() {} + virtual size_t parseFromBuffer(const u8 *buf, size_t offset, size_t maxlen) = 0; +}; + +class A_Record : public Record +{ +public: + sockaddr_storage value; + Record * clone() { return new A_Record(*this); } + ~A_Record() {} + size_t parseFromBuffer(const u8 *buf, size_t offset, size_t maxlen); +}; + +class PTR_Record : public Record +{ +public: + std::string value; + Record * clone() { return new PTR_Record(*this); } + ~PTR_Record() {} + size_t parseFromBuffer(const u8 *buf, size_t offset, size_t maxlen) + { + return Factory::parseDomainName(value, buf, offset, maxlen); + } +}; + +class CNAME_Record : public Record +{ +public: + std::string value; + Record * clone() { return new CNAME_Record(*this); } + ~CNAME_Record() {} + size_t parseFromBuffer(const u8 *buf, size_t offset, size_t maxlen) + { + return Factory::parseDomainName(value, buf, offset, maxlen); + } +}; + +class Query +{ +public: + std::string name; + u16 record_type; + u16 record_class; + + size_t parseFromBuffer(const u8 *buf, size_t offset, size_t maxlen); +}; + +class Answer +{ +public: + Answer() : record(NULL) {} + Answer(const Answer &c) : name(c.name), record_type(c.record_type), + record_class(c.record_class), ttl(c.ttl), length(c.length), + record(c.record->clone()) {} + ~Answer() { delete record; } + + std::string name; + u16 record_type; + u16 record_class; + u32 ttl; + u16 length; + Record * record; + + // Populate the object reading from buffer and returns "consumed" bytes + size_t parseFromBuffer(const u8 *buf, size_t offset, size_t maxlen); + Answer& operator=(const Answer &r); +}; + +class Packet +{ +public: + Packet() : id(0), flags(0) {} + ~Packet() {} + + void addFlags(FLAGS fl){ flags |= fl; } + void removeFlags(FLAGS fl){ flags &= ~fl; } + void resetFlags() { flags = 0; } + size_t writeToBuffer(u8 *buf, size_t maxlen); + size_t parseFromBuffer(const u8 *buf, size_t maxlen); + + u16 id; + u16 flags; + std::list queries; + std::list answers; +}; + +} + void nmap_mass_rdns(Target ** targets, int num_targets); -const char *lookup_cached_host(u32 ip); +int lookup_cached_host(const struct sockaddr_storage * ip, char * name, size_t maxlen); std::list get_dns_servers(); diff --git a/tests/nmap_dns_test.cc b/tests/nmap_dns_test.cc new file mode 100644 index 0000000..91aa2b4 --- /dev/null +++ b/tests/nmap_dns_test.cc @@ -0,0 +1,263 @@ + +/*************************************************************************** + * dns_request_generation.cc -- Tests DNS request generation * + * * + ***********************IMPORTANT NMAP LICENSE TERMS************************ + * * + * The Nmap Security Scanner is (C) 1996-2015 Insecure.Com LLC. Nmap is * + * also a registered trademark of Insecure.Com LLC. This program is free * + * software; you may redistribute and/or modify it under the terms of the * + * GNU General Public License as published by the Free Software * + * Foundation; Version 2 ("GPL"), BUT ONLY WITH ALL OF THE CLARIFICATIONS * + * AND EXCEPTIONS DESCRIBED HEREIN. This guarantees your right to use, * + * modify, and redistribute this software under certain conditions. If * + * you wish to embed Nmap technology into proprietary software, we sell * + * alternative licenses (contact sales () nmap com). Dozens of software * + * vendors already license Nmap technology such as host discovery, port * + * scanning, OS detection, version detection, and the Nmap Scripting * + * Engine. * + * * + * Note that the GPL places important restrictions on "derivative works", * + * yet it does not provide a detailed definition of that term. To avoid * + * misunderstandings, we interpret that term as broadly as copyright law * + * allows. For example, we consider an application to constitute a * + * derivative work for the purpose of this license if it does any of the * + * following with any software or content covered by this license * + * ("Covered Software"): * + * * + * o Integrates source code from Covered Software. * + * * + * o Reads or includes copyrighted data files, such as Nmap's nmap-os-db * + * or nmap-service-probes. * + * * + * o Is designed specifically to execute Covered Software and parse the * + * results (as opposed to typical shell or execution-menu apps, which will * + * execute anything you tell them to). * + * * + * o Includes Covered Software in a proprietary executable installer. The * + * installers produced by InstallShield are an example of this. Including * + * Nmap with other software in compressed or archival form does not * + * trigger this provision, provided appropriate open source decompression * + * or de-archiving software is widely available for no charge. For the * + * purposes of this license, an installer is considered to include Covered * + * Software even if it actually retrieves a copy of Covered Software from * + * another source during runtime (such as by downloading it from the * + * Internet). * + * * + * o Links (statically or dynamically) to a library which does any of the * + * above. * + * * + * o Executes a helper program, module, or script to do any of the above. * + * * + * This list is not exclusive, but is meant to clarify our interpretation * + * of derived works with some common examples. Other people may interpret * + * the plain GPL differently, so we consider this a special exception to * + * the GPL that we apply to Covered Software. Works which meet any of * + * these conditions must conform to all of the terms of this license, * + * particularly including the GPL Section 3 requirements of providing * + * source code and allowing free redistribution of the work as a whole. * + * * + * As another special exception to the GPL terms, Insecure.Com LLC grants * + * permission to link the code of this program with any version of the * + * OpenSSL library which is distributed under a license identical to that * + * listed in the included docs/licenses/OpenSSL.txt file, and distribute * + * linked combinations including the two. * + * * + * Any redistribution of Covered Software, including any derived works, * + * must obey and carry forward all of the terms of this license, including * + * obeying all GPL rules and restrictions. For example, source code of * + * the whole work must be provided and free redistribution must be * + * allowed. All GPL references to "this License", are to be treated as * + * including the terms and conditions of this license text as well. * + * * + * Because this license imposes special exceptions to the GPL, Covered * + * Work may not be combined (even as part of a larger work) with plain GPL * + * software. The terms, conditions, and exceptions of this license must * + * be included as well. This license is incompatible with some other open * + * source licenses as well. In some cases we can relicense portions of * + * Nmap or grant special permissions to use it in other open source * + * software. Please contact fyodor () nmap org with any such requests. * + * Similarly, we don't incorporate incompatible open source software into * + * Covered Software without special permission from the copyright holders. * + * * + * If you have any questions about the licensing restrictions on using * + * Nmap in other works, are happy to help. As mentioned above, we also * + * offer alternative license to integrate Nmap into proprietary * + * applications and appliances. These contracts have been sold to dozens * + * of software vendors, and generally include a perpetual license as well * + * as providing for priority support and updates. They also fund the * + * continued development of Nmap. Please email sales () nmap com for further * + * information. * + * * + * If you have received a written license agreement or contract for * + * Covered Software stating terms other than these, you may choose to use * + * and redistribute Covered Software under those terms instead of these. * + * * + * Source is provided to this software because we believe users have a * + * right to know exactly what a program is going to do before they run it. * + * This also allows you to audit the software for security holes. * + * * + * Source code also allows you to port Nmap to new platforms, fix bugs, * + * and add new features. You are highly encouraged to send your changes * + * to the dev () nmap org mailing list for possible incorporation into the * + * main distribution. By sending these changes to Fyodor or one of the * + * Insecure.Org development mailing lists, or checking them into the Nmap * + * source code repository, it is understood (unless you specify otherwise) * + * that you are offering the Nmap Project (Insecure.Com LLC) the * + * unlimited, non-exclusive right to reuse, modify, and relicense the * + * code. Nmap will always be available Open Source, but this is important * + * because the inability to relicense code has caused devastating problems * + * for other Free Software projects (such as KDE and NASM). We also * + * occasionally relicense the code to third parties as discussed above. * + * If you wish to specify special license conditions of your * + * contributions, just say so when you send them. * + * * + * This program is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Nmap * + * license file for more details (it's in a COPYING file included with * + * Nmap, and also available from https://svn.nmap.org/nmap/COPYING) * + * * + ***************************************************************************/ + +#include "../nmap_dns.h" + +#include + +#define TEST_INCR(pred,acc) \ +if ( !(pred) ) \ +{ \ + std::cout << "Test " << #pred << " failed at " << __FILE__ << ":" << __LINE__ << std::endl; \ + ++acc; \ +} + +int main() +{ + std::cout << "Testing nmap_dns" << std::endl; + + int ret = 0; + std::string target = "scanme.nmap.org"; + DNS::RECORD_TYPE rt = DNS::A; + const size_t buflen = 1500; + u8 buf[buflen]; + size_t reqlen = DNS::Factory::buildSimpleRequest(target, rt, buf, buflen); + + DNS::Packet p; + size_t plen = p.parseFromBuffer(buf, buflen); + TEST_INCR(reqlen == plen, ret); + + DNS::Query * q = &*p.queries.begin(); + TEST_INCR(q->name == target, ret); + TEST_INCR(q->record_class == DNS::IN, ret); + TEST_INCR(q->record_type == rt, ret); + + + // This is a possible answere for an A query for scanme.nmap.org + const char ipp[] = "45.33.32.156"; + const size_t answere_len = 49; + const u8 answere_buf[] = { 0x92, 0xdc, // Trsnsaction ID + 0x81, 0x80, // Flags + 0x00, 0x01, // Questions count + 0x00, 0x01, // Answers RRs count + 0x00, 0x00, // Authorities RRs count + 0x00, 0x00, // Additionals RRs count + 0x06, // Label lenght <-- [12] + 0x73, 0x63, 0x61, 0x6e, 0x6d, 0x65, // "scanme" + 0x04, // Label lenght + 0x6e, 0x6d, 0x61, 0x70, // "nmap" + 0x03, // Label lenght + 0x6f, 0x72, 0x67, // "org" + 0x00, // Name terminator + 0x00, 0x01, // A + 0x00, 0x01, // IN + 0xc0, 0x0c, // Compressed name pointer to offset 12 + 0x00, 0x01, // A + 0x00, 0x01, // IN + 0x00, 0x00, 0x0e, 0x0f, // TTL 3599 + 0x00, 0x04, // Record Lenght + 0x2d, 0x21, 0x20, 0x9c }; // 45.33.32.156 + + plen = p.parseFromBuffer(answere_buf, answere_len); + TEST_INCR(answere_len == plen, ret); + + q = &*p.queries.begin(); + TEST_INCR(q->name == target, ret); + TEST_INCR(q->record_class == DNS::IN, ret); + TEST_INCR(q->record_type == rt, ret ); + + DNS::Answer * a = &*p.answers.begin(); + TEST_INCR(a->name == target, ret ); + TEST_INCR(a->record_class == DNS::IN, ret); + TEST_INCR(a->record_type == DNS::A, ret); + TEST_INCR(a->ttl == 3599, ret) + + DNS::A_Record * ar = static_cast(a->record); + char ar_ipp[INET6_ADDRSTRLEN]; + sockaddr_storage_iptop(&ar->value, ar_ipp); + TEST_INCR(!strcmp(ipp, ar_ipp), ret); + + const size_t ptr_answere_len = 72; + std::string ptr_target; + TEST_INCR(DNS::Factory::ipToPtr(ar->value, ptr_target), ret); + TEST_INCR(ptr_target == "156.32.33.45.in-addr.arpa", ret); + const u8 ptr_answere[] = { 0x08, 0xf2, // ID + 0x81, 0x80, // Flags + 0x00, 0x01, // Questions count + 0x00, 0x01, // Answers RRs count + 0x00, 0x00, // Authorities RRs count + 0x00, 0x00, // Additionals RRs count + 0x03, // Label lenght + 0x31, 0x35, 0x36, // "156" + 0x02, // Label lenght + 0x33, 0x32, // "32" + 0x02, // Label lenght + 0x33, 0x33, // "33" + 0x02, // Label lenght + 0x34, 0x35, // "45" + 0x07, // Label lenght + 0x69, 0x6e, 0x2d, 0x61, 0x64, 0x64, 0x72, // "in-addr" + 0x04, // Label lenght + 0x61, 0x72, 0x70, 0x61, // "arpa" + 0x00, // Name terminator + 0x00, 0x0c, // PTR + 0x00, 0x01, // IN + 0xc0, 0x0c, // Compressed name pointer to offset 12 + 0x00, 0x0c, // PTR + 0x00, 0x01, // IN + 0x00, 0x01, 0x51, 0x78, // TTL 86392 + 0x00, 0x11, // Record Lenght + 0x06, // Label lenght + 0x73, 0x63, 0x61, 0x6e, 0x6d, 0x65, // "scanme" + 0x04, // Label lenght + 0x6e, 0x6d, 0x61, 0x70, // "nmap" + 0x03, // Label lenght + 0x6f, 0x72, 0x67, // "org" + 0x00 }; // Name terminator + + plen = p.parseFromBuffer(ptr_answere, ptr_answere_len); + TEST_INCR(plen == ptr_answere_len, ret); + TEST_INCR(p.id == 0x08f2, ret); + TEST_INCR(p.flags == 0x8180, ret); + TEST_INCR(p.queries.size() == 1, ret); + TEST_INCR(p.answers.size() == 1, ret); + + q = &*p.queries.begin(); + TEST_INCR(q->name == ptr_target, ret); + TEST_INCR(q->record_class == DNS::IN, ret); + TEST_INCR(q->record_type == DNS::PTR, ret); + + a = &*p.answers.begin(); + TEST_INCR(a->name == ptr_target, ret); + TEST_INCR(a->record_class == DNS::IN, ret); + TEST_INCR(a->record_type == DNS::PTR, ret); + TEST_INCR(a->length == 0x11, ret); + TEST_INCR(a->ttl == 86392, ret); + + DNS::PTR_Record * r = static_cast(a->record); + TEST_INCR(r->value == target, ret); + + if(ret) std::cout << "Testing nmap_dns finished with errors" << std::endl; + else std::cout << "Testing nmap_dns finished without errors" << std::endl; + + return ret; // 0 means ok +}