diff --git a/TargetGroup.cc b/TargetGroup.cc index 4849ec314..b77726530 100644 --- a/TargetGroup.cc +++ b/TargetGroup.cc @@ -101,23 +101,16 @@ #include "global_structures.h" #include "libnetutil/netutil.h" +#include + +#define BITVECTOR_BITS (sizeof(bitvector_t) * CHAR_BIT) +#define BIT_SET(v, n) ((v)[(n) / BITVECTOR_BITS] |= 1UL << ((n) % BITVECTOR_BITS)) +#define BIT_IS_SET(v, n) (((v)[(n) / BITVECTOR_BITS] & 1UL << ((n) % BITVECTOR_BITS)) != 0) + extern NmapOps o; NewTargets *NewTargets::new_targets; -TargetGroup::TargetGroup() { - Initialize(); -} - -// Bring back (or start with) original state -void TargetGroup::Initialize() { - targets_type = TYPE_NONE; - memset(addresses, 0, sizeof(addresses)); - memset(current, 0, sizeof(current)); - memset(last, 0, sizeof(last)); - exhausted = true; -} - /* Return a newly allocated string containing the part of expr up to the last '/' (or a copy of the whole string if there is no slash). *bits will contain the number after the slash, or -1 if there was no slash. In case of error @@ -142,6 +135,346 @@ static char *split_netmask(const char *expr, int *bits) { return mkstr(expr, slash); } +/* Parse an IPv4 address with optional ranges and wildcards into bit vectors. + Each octet must match the regular expression '(\*|#?(-#?)?(,#?(-#?)?)*)', + where '#' stands for an integer between 0 and 255. Return 0 on success, -1 on + error. */ +static int parse_ipv4_ranges(octet_bitvector octets[4], const char *spec) { + const char *p; + int octet_index, i; + + p = spec; + octet_index = 0; + while (*p != '\0' && octet_index < 4) { + if (*p == '*') { + for (i = 0; i < 256; i++) + BIT_SET(octets[octet_index], i); + p++; + } else { + for (;;) { + long start, end; + char *tail; + + errno = 0; + start = parse_long(p, &tail); + /* Is this a range open on the left? */ + if (tail == p) { + if (*p == '-') + start = 0; + else + return -1; + } + if (errno != 0 || start < 0 || start > 255) + return -1; + p = tail; + + /* Look for a range. */ + if (*p == '-') { + p++; + errno = 0; + end = parse_long(p, &tail); + /* Is this range open on the right? */ + if (tail == p) + end = 255; + if (errno != 0 || end < 0 || end > 255 || end < start) + return -1; + p = tail; + } else { + end = start; + } + + /* Fill in the range in the bit vector. */ + for (i = start; i <= end; i++) + BIT_SET(octets[octet_index], i); + + if (*p != ',') + break; + p++; + } + } + octet_index++; + if (octet_index < 4) { + if (*p != '.') + return -1; + p++; + } + } + if (*p != '\0' || octet_index < 4) + return -1; + + return 0; +} + +static NetBlock *parse_expr_without_netmask(const char *hostexp, int af) { + struct sockaddr_storage ss; + size_t sslen; + + if (af == AF_INET) { + NetBlockIPv4Ranges *netblock_ranges; + + /* Check if this is an IPv4 address, with optional ranges and wildcards. */ + netblock_ranges = new NetBlockIPv4Ranges(); + if (parse_ipv4_ranges(netblock_ranges->octets, hostexp) == 0) + return netblock_ranges; + delete netblock_ranges; + } + + sslen = sizeof(ss); + if (resolve_numeric(hostexp, 0, &ss, &sslen, AF_INET6) == 0) { + NetBlockIPv6Netmask *netblock_ipv6; + + netblock_ipv6 = new NetBlockIPv6Netmask(); + netblock_ipv6->set_addr((struct sockaddr_in6 *) &ss); + return netblock_ipv6; + } + + return new NetBlockHostname(hostexp, af); +} + +/* Parses an expression such as 192.168.0.0/16, 10.1.0-5.1-254, or + fe80::202:e3ff:fe14:1102/112 and returns a newly allocated NetBlock. The af + parameter is AF_INET or AF_INET6. Returns NULL in case of error. */ +NetBlock *NetBlock::parse_expr(const char *target_expr, int af) { + NetBlock *netblock; + char *hostexp; + int bits; + + hostexp = split_netmask(target_expr, &bits); + if (hostexp == NULL) { + error("Unable to split netmask from target expression: \"%s\"", target_expr); + goto bail; + } + + if (af == AF_INET && bits > 32) { + error("Illegal netmask in \"%s\". Assuming /32 (one host)", target_expr); + bits = -1; + } + + netblock = parse_expr_without_netmask(hostexp, af); + if (netblock == NULL) + goto bail; + netblock->apply_netmask(bits); + + free(hostexp); + return netblock; + +bail: + free(hostexp); + return NULL; +} + +bool NetBlock::is_resolved_address(const struct sockaddr_storage *ss) const { + if (this->resolvedaddrs.empty()) + return false; + return sockaddr_storage_equal(&*this->resolvedaddrs.begin(), ss); +} + +bool NetBlockIPv4Ranges::next(struct sockaddr_storage *ss) { + struct sockaddr_in *sin; + unsigned int i; + + /* This first time this is called, the current values of this->counter + probably do not point to set bits (they point to 0.0.0.0). Find the first + set bit in each bitvector. If any overflow occurs, it means that there is + not bit set for one of the octets and therefore there are not addresses + overall. */ + for (i = 0; i < 4; i++) { + while (this->counter[i] < 256 && !BIT_IS_SET(this->octets[i], this->counter[i])) + this->counter[i]++; + if (this->counter[i] >= 256) + return false; + } + + /* Assign the returned address based on current counters. */ + sin = (struct sockaddr_in *) ss; + sin->sin_family = AF_INET; + sin->sin_port = 0; +#if HAVE_SOCKADDR_SA_LEN + sin->sin_len = sizeof(*ss); +#endif + sin->sin_addr.s_addr = htonl((this->counter[0] << 24) | (this->counter[1] << 16) | (this->counter[2] << 8) | this->counter[3]); + + for (i = 0; i < 4; i++) { + bool carry; + + carry = false; + do { + this->counter[3 - i] = (this->counter[3 - i] + 1) % 256; + if (this->counter[3 - i] == 0) + carry = true; + } while (!BIT_IS_SET(this->octets[3 - i], this->counter[3 - i])); + if (!carry) + break; + } + if (i >= 4) { + /* We cycled all counters. Mark them invalid for the next call. */ + this->counter[0] = 256; + this->counter[1] = 256; + this->counter[2] = 256; + this->counter[3] = 256; + } + + return true; +} + +/* Expand a single-octet bit vector to include any additional addresses that + result when mask is applied. */ +static void apply_ipv4_netmask_octet(octet_bitvector bits, uint8_t mask) { + unsigned int i, j; + uint32_t chunk_size; + + /* Process the bit vector in chunks, first of size 1, then of size 2, up to + size 128. Check the next bit of the mask. If it is 1, do nothing. + Otherwise, pair up the chunks (first with the second, third with the + fourth, etc.). For each pair of chunks, set a bit in one chunk if it is + set in the other. chunk_size also serves as an index into the mask. */ + for (chunk_size = 1; chunk_size < 256; chunk_size <<= 1) { + if ((mask & chunk_size) != 0) + continue; + for (i = 0; i < 256; i += chunk_size * 2) { + for (j = 0; j < chunk_size; j++) { + if (BIT_IS_SET(bits, i + j)) + BIT_SET(bits, i + j + chunk_size); + else if (BIT_IS_SET(bits, i + j + chunk_size)) + BIT_SET(bits, i + j); + } + } + } +} + +/* Expand IPv4 bit vectors to include any additional addresses that result when + the given netmask is applied. The mask is in host byte order. */ +static void apply_ipv4_netmask(octet_bitvector octets[4], uint32_t mask) { + /* Apply the mask one octet at a time. It's done this way because ranges + span exactly one octet. */ + apply_ipv4_netmask_octet(octets[0], (mask & 0xFF000000) >> 24); + apply_ipv4_netmask_octet(octets[1], (mask & 0x00FF0000) >> 16); + apply_ipv4_netmask_octet(octets[2], (mask & 0x0000FF00) >> 8); + apply_ipv4_netmask_octet(octets[3], (mask & 0x000000FF)); +} + +/* Expand IPv4 bit vectors to include any additional addresses that result from + the application of a CIDR-style netmask with the given number of bits. If + bits is negative it is taken to be 32. */ +void NetBlockIPv4Ranges::apply_netmask(int bits) { + uint32_t mask; + + if (bits > 32) + return; + if (bits < 0) + bits = 32; + + if (bits == 0) + mask = 0x00000000; + else + mask = 0xFFFFFFFF << (32 - bits); + + apply_ipv4_netmask(this->octets, mask); +} + +static std::string bitvector_to_range_string(const octet_bitvector v) { + unsigned int i, j; + std::ostringstream result; + + i = 0; + while (i < 256) { + while (i < 256 && !BIT_IS_SET(v, i)) + i++; + if (i >= 256) + break; + j = i + 1; + while (j < 256 && BIT_IS_SET(v, j)) + j++; + + if (result.tellp() > 0) + result << ","; + if (i == j - 1) + result << i; + else if (i + 1 == j - 1) + result << i << "," << (j - 1); + else + result << i << "-" << (j - 1); + + i = j; + } + + return result.str(); +} + +std::string NetBlockIPv4Ranges::str() const { + std::ostringstream result; + + result << bitvector_to_range_string(this->octets[0]); + result << "."; + result << bitvector_to_range_string(this->octets[1]); + result << "."; + result << bitvector_to_range_string(this->octets[2]); + result << "."; + result << bitvector_to_range_string(this->octets[3]); + + return result.str(); +} + +void NetBlockIPv6Netmask::set_addr(const struct sockaddr_in6 *addr) { + this->exhausted = false; + this->start = addr->sin6_addr; + this->cur = addr->sin6_addr; + this->end = addr->sin6_addr; +} + +/* Get the sin6_scope_id member of a sockaddr_in6, based on a device name. This + is used to assign scope to all addresses that otherwise lack a scope id when + the -e option is used. */ +static int get_scope_id(const char *devname) { + struct interface_info *ii; + + if (devname == NULL || devname[0] == '\0') + return 0; + ii = getInterfaceByName(devname, AF_INET6); + if (ii != NULL) + return ii->ifindex; + else + return 0; +} + +static bool ipv6_equal(const struct in6_addr *a, const struct in6_addr *b) { + return memcmp(a->s6_addr, b->s6_addr, 16) == 0; +} + +bool NetBlockIPv6Netmask::next(struct sockaddr_storage *ss) { + struct sockaddr_in6 *sin6; + + if (this->exhausted) + return false; + + sin6 = (struct sockaddr_in6 *) ss; + memset(sin6, 0, sizeof(*sin6)); + + sin6->sin6_family = AF_INET6; +#ifdef SIN_LEN + sin6->sin6_len = sizeof(*sin6); +#endif + + if (this->addr.sin6_scope_id != 0) + sin6->sin6_scope_id = this->addr.sin6_scope_id; + else + sin6->sin6_scope_id = get_scope_id(o.device); + + sin6->sin6_addr = this->cur; + + if (ipv6_equal(&this->cur, &this->end)) + exhausted = true; + + /* Increment current address. */ + for (int i = 15; i >= 0; i--) { + this->cur.s6_addr[i]++; + if (this->cur.s6_addr[i] > 0) + break; + } + + return true; +} + /* Fill in an in6_addr with a CIDR-style netmask with the given number of bits. */ static void make_ipv6_netmask(struct in6_addr *mask, int bits) { unsigned int i; @@ -171,419 +504,141 @@ static void ipv6_or_mask(struct in6_addr *a, const struct in6_addr *mask, const a->s6_addr[i] = (a->s6_addr[i] & mask->s6_addr[i]) | (b->s6_addr[i] & ~mask->s6_addr[i]); } -/* Initializes (or reinitializes) the object with a new expression, such - as 192.168.0.0/16 , 10.1.0-5.1-254 , or fe80::202:e3ff:fe14:1102 . - Returns 0 for success */ -int TargetGroup::parse_expr(const char *target_expr, int af) { +void NetBlockIPv6Netmask::apply_netmask(int bits) { + const struct in6_addr zeros = { { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} } }; + const struct in6_addr ones = { { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} } }; + struct in6_addr mask; - int i = 0, j = 0, k = 0; - int start, end; - char *r, *s; - char *addy[5]; - char *hostexp; - int bits; - namedhost = 0; + if (bits > 128) + return; + if (bits < 0) + bits = 128; - if (targets_type != TYPE_NONE) - Initialize(); - - resolvedaddrs.clear(); - - hostexp = split_netmask(target_expr, &bits); - if (hostexp == NULL) { - error("Unable to split netmask from target expression: \"%s\"", target_expr); - goto bail; - } - - if (af == AF_INET) { - - if (strchr(hostexp, ':')) { - error("Invalid host expression: %s -- colons only allowed in IPv6 addresses, and then you need the -6 switch", hostexp); - goto bail; - } - - /*struct in_addr current_in;*/ - addy[0] = addy[1] = addy[2] = addy[3] = addy[4] = NULL; - addy[0] = r = hostexp; - - /* Check the netmask. */ - if (bits == -1) { - netmask = 32; - } else if (0 <= bits && bits <= 32) { - netmask = bits; - } else { - error("Illegal netmask value, must be /0 - /32 . Assuming /32 (one host)"); - netmask = 32; - } - - resolvedname = hostexp; - for (i = 0; hostexp[i]; i++) - if (isupper((int) (unsigned char) hostexp[i]) || - islower((int) (unsigned char) hostexp[i])) { - namedhost = 1; - break; - } - if (netmask != 32 || namedhost) { - struct addrinfo *addrs, *addr; - struct sockaddr_storage ss; - size_t sslen; - - targets_type = IPV4_NETMASK; - addrs = resolve_all(hostexp, AF_INET); - for (addr = addrs; addr != NULL; addr = addr->ai_next) { - if (addr->ai_family != AF_INET) - continue; - if (addr->ai_addrlen < sizeof(ss)) { - memcpy(&ss, addr->ai_addr, addr->ai_addrlen); - resolvedaddrs.push_back(ss); - } - } - if (addrs != NULL) - freeaddrinfo(addrs); - - if (resolvedaddrs.empty()) { - error("Failed to resolve given hostname/IP: %s. Note that you can't use '/mask' AND '1-4,7,100-' style IP ranges. If the machine only has an IPv6 address, add the Nmap -6 flag to scan that.", hostexp); - goto bail; - } else { - ss = *resolvedaddrs.begin(); - sslen = sizeof(ss); - } - - if (resolvedaddrs.size() > 1 && o.verbose > 1) - error("Warning: Hostname %s resolves to %lu IPs. Using %s.", hostexp, (unsigned long)resolvedaddrs.size(), inet_ntop_ez(&ss, sslen)); - - if (netmask) { - struct sockaddr_in *sin = (struct sockaddr_in *) &ss; - unsigned long longtmp = ntohl(sin->sin_addr.s_addr); - startaddr.s_addr = longtmp & (unsigned long) (0 - (1 << (32 - netmask))); - endaddr.s_addr = longtmp | (unsigned long) ((1 << (32 - netmask)) - 1); - } else { - /* The above calculations don't work for a /0 netmask, though at first - * glance it appears that they would - */ - startaddr.s_addr = 0; - endaddr.s_addr = 0xffffffff; - } - currentaddr = startaddr; - if (startaddr.s_addr <= endaddr.s_addr) - goto done; - fprintf(stderr, "Host specification invalid"); - goto bail; - } else { - targets_type = IPV4_RANGES; - i = 0; - - while (*r) { - if (*r == '.' && ++i < 4) { - *r = '\0'; - addy[i] = r + 1; - } else if (*r != '*' && *r != ',' && *r != '-' && !isdigit((int) (unsigned char) *r)) { - error("Invalid character in host specification: %s. Note in particular that square brackets [] are no longer allowed. They were redundant and can simply be removed.", hostexp); - goto bail; - } - r++; - } - if (i != 3) { - error("Invalid target host specification: %s", hostexp); - goto bail; - } - - for (i = 0; i < 4; i++) { - j = 0; - do { - s = strchr(addy[i], ','); - if (s) *s = '\0'; - if (*addy[i] == '*') { - start = 0; - end = 255; - } else if (*addy[i] == '-') { - start = 0; - if (*(addy[i] + 1) == '\0') end = 255; - else end = atoi(addy[i] + 1); - } else { - start = end = atoi(addy[i]); - if ((r = strchr(addy[i], '-')) && *(r + 1) ) end = atoi(r + 1); - else if (r && !*(r + 1)) end = 255; - } - /* if (o.debugging > 2) - * log_write(LOG_STDOUT, "The first host is %d, and the last one is %d\n", start, end); */ - if (start < 0 || start > end || start > 255 || end > 255) { - error("Your host specifications are illegal!"); - goto bail; - } - if (j + (end - start) > 255) { - error("Your host specifications are illegal!"); - goto bail; - } - for (k = start; k <= end; k++) - addresses[i][j++] = k; - last[i] = j - 1; - if (s) addy[i] = s + 1; - } while (s); - } - } - memset((char *)current, 0, sizeof(current)); - } else { -#if HAVE_IPV6 - struct addrinfo *addrs, *addr; - struct sockaddr_storage ss; - size_t sslen; - - assert(af == AF_INET6); - - /* Check the netmask. */ - if (bits == -1) { - netmask = 128; - } else if (0 <= bits && bits <= 128) { - netmask = bits; - } else { - error("Illegal netmask value, must be /0 - /128 . Assuming /128 (one host)"); - netmask = 128; - } - resolvedname = hostexp; - if (strchr(hostexp, ':') == NULL) - namedhost = 1; - - targets_type = IPV6_NETMASK; - addrs = resolve_all(hostexp, AF_INET6); - for (addr = addrs; addr != NULL; addr = addr->ai_next) { - if (addr->ai_family != AF_INET6) - continue; - if (addr->ai_addrlen < sizeof(ss)) { - memcpy(&ss, addr->ai_addr, addr->ai_addrlen); - resolvedaddrs.push_back(ss); - } - } - if (addrs != NULL) - freeaddrinfo(addrs); - - if (resolvedaddrs.empty()) { - error("Failed to resolve given IPv6 hostname/IP: %s. Note that you can't use '/mask' or '[1-4,7,100-]' style ranges for IPv6.", hostexp); - goto bail; - } else { - ss = *resolvedaddrs.begin(); - sslen = sizeof(ss); - } - - if (resolvedaddrs.size() > 1 && o.verbose > 1) - error("Warning: Hostname %s resolves to %lu IPs. Using %s.", hostexp, (unsigned long)resolvedaddrs.size(), inet_ntop_ez(&ss, sslen)); - - assert(sizeof(ip6) <= sslen); - memcpy(&ip6, &ss, sizeof(ip6)); - - const struct in6_addr zeros = { { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} } }; - const struct in6_addr ones = { { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} } }; - - struct in6_addr mask; - make_ipv6_netmask(&mask, netmask); - - startaddr6 = ((struct sockaddr_in6 *) &ss)->sin6_addr; - ipv6_or_mask(&startaddr6, &mask, &zeros); - endaddr6 = ((struct sockaddr_in6 *) &ss)->sin6_addr; - ipv6_or_mask(&endaddr6, &mask, &ones); - - currentaddr6 = startaddr6; -#else // HAVE_IPV6 - fatal("IPv6 not supported on your platform"); -#endif // HAVE_IPV6 - } - -done: - free(hostexp); - exhausted = false; - return 0; - -bail: - free(hostexp); - exhausted = true; - return 1; + this->exhausted = false; + make_ipv6_netmask(&mask, bits); + ipv6_or_mask(&this->start, &mask, &zeros); + ipv6_or_mask(&this->end, &mask, &ones); + this->cur = this->start; } -/* Get the sin6_scope_id member of a sockaddr_in6, based on a device name. This - is used to assign scope to all addresses that otherwise lack a scope id when - the -e option is used. */ -static int get_scope_id(const char *devname) { - struct interface_info *ii; +/* a = a & ~b */ +static void recover_ipv6_netmask(struct in6_addr *a, const struct in6_addr *b) { + unsigned int i; - if (devname == NULL || devname[0] == '\0') - return 0; - ii = getInterfaceByName(devname, AF_INET6); - if (ii != NULL) - return ii->ifindex; - else - return 0; + for (i = 0; i < sizeof(a->s6_addr) / sizeof(*a->s6_addr); i++) + a->s6_addr[i] = a->s6_addr[i] & ~b->s6_addr[i]; } -/* Grab the next host from this expression (if any) and updates its internal - state to reflect that the IP was given out. Returns 0 and - fills in ss if successful. ss must point to a pre-allocated - sockaddr_storage structure */ -int TargetGroup::get_next_host(struct sockaddr_storage *ss, size_t *sslen) { +static unsigned int count_ipv6_bits(const struct in6_addr *a) { + unsigned int i, n; + unsigned char mask; - int octet; - struct sockaddr_in *sin = (struct sockaddr_in *) ss; - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) ss; -startover: /* to handle nmap --resume where I have already - * scanned many of the IPs */ - assert(ss); - assert(sslen); - - - if (exhausted) - return -1; - - if (targets_type == IPV4_NETMASK) { - memset(sin, 0, sizeof(struct sockaddr_in)); - sin->sin_family = AF_INET; - *sslen = sizeof(struct sockaddr_in); -#if HAVE_SOCKADDR_SA_LEN - sin->sin_len = *sslen; -#endif - - if (currentaddr.s_addr == endaddr.s_addr) - exhausted = true; - if (currentaddr.s_addr <= endaddr.s_addr) { - sin->sin_addr.s_addr = htonl(currentaddr.s_addr++); - } else { - error("Bogus target structure passed to %s", __func__); - exhausted = true; - return -1; + n = 0; + for (i = 0; i < sizeof(a->s6_addr) / sizeof(*a->s6_addr); i++) { + for (mask = 0x80; mask != 0; mask >>= 1) { + if ((a->s6_addr[i] & mask) != 0) + n++; } - } else if (targets_type == IPV4_RANGES) { - memset(sin, 0, sizeof(struct sockaddr_in)); - sin->sin_family = AF_INET; - *sslen = sizeof(struct sockaddr_in); -#if HAVE_SOCKADDR_SA_LEN - sin->sin_len = *sslen; -#endif - if (o.debugging > 2) { - log_write(LOG_STDOUT, "doing %d.%d.%d.%d = %d.%d.%d.%d\n", current[0], current[1], current[2], current[3], addresses[0][current[0]], addresses[1][current[1]], addresses[2][current[2]], addresses[3][current[3]]); - } - /* Set the IP to the current value of everything */ - sin->sin_addr.s_addr = htonl(addresses[0][current[0]] << 24 | - addresses[1][current[1]] << 16 | - addresses[2][current[2]] << 8 | - addresses[3][current[3]]); - - /* Now we nudge up to the next IP */ - for (octet = 3; octet >= 0; octet--) { - if (current[octet] < last[octet]) { - /* OK, this is the column I have room to nudge upwards */ - current[octet]++; - break; - } else { - /* This octet is finished so I reset it to the beginning */ - current[octet] = 0; - } - } - if (octet == -1) { - /* It didn't find anything to bump up, I must have taken the last IP */ - exhausted = true; - /* So I set current to last with the very final octet up one ... */ - /* Note that this may make current[3] == 256 */ - current[0] = last[0]; - current[1] = last[1]; - current[2] = last[2]; - current[3] = last[3] + 1; - } else { - assert(!exhausted); /* There must be at least one more IP left */ - } - } else { - assert(targets_type == IPV6_NETMASK); -#if HAVE_IPV6 - *sslen = sizeof(struct sockaddr_in6); - memset(sin6, 0, *sslen); - sin6->sin6_family = AF_INET6; -#ifdef SIN_LEN - sin6->sin6_len = *sslen; -#endif /* SIN_LEN */ - - if (memcmp(currentaddr6.s6_addr, endaddr6.s6_addr, 16) == 0) - exhausted = true; - - sin6->sin6_addr = currentaddr6; - if (ip6.sin6_scope_id == 0) - sin6->sin6_scope_id = get_scope_id(o.device); - else - sin6->sin6_scope_id = ip6.sin6_scope_id; - - /* Increment current address. */ - for (int i = 15; i >= 0; i--) { - currentaddr6.s6_addr[i]++; - if (currentaddr6.s6_addr[i] > 0) - break; - } -#else - fatal("IPV6 not supported on this platform"); -#endif // HAVE_IPV6 } - /* If we are resuming from a previous scan, we have already finished - scans up to o.resume_ip. */ - if (sin->sin_family == AF_INET && o.resume_ip.s_addr) { - if (o.resume_ip.s_addr == sin->sin_addr.s_addr) - o.resume_ip.s_addr = 0; /* So that we will KEEP the next one */ - goto startover; /* Try again */ - } - - return 0; + return n; } -/* Returns the last given host, so that it will be given again next - time get_next_host is called. Obviously, you should only call - this if you have fetched at least 1 host since parse_expr() was - called */ -int TargetGroup::return_last_host() { - int octet; +std::string NetBlockIPv6Netmask::str() const { + std::ostringstream result; + unsigned int bits; + struct in6_addr a; - exhausted = false; - if (targets_type == IPV4_NETMASK) { - assert(currentaddr.s_addr > startaddr.s_addr); - currentaddr.s_addr--; - } else if (targets_type == IPV4_RANGES) { - for (octet = 3; octet >= 0; octet--) { - if (current[octet] > 0) { - /* OK, this is the column I have room to nudge downwards */ - current[octet]--; - break; - } else { - /* This octet is already at the beginning, so I set it to the end */ - current[octet] = last[octet]; - } - } - assert(octet != -1); - } else { - assert(targets_type == IPV6_NETMASK); - } - return 0; + a = this->start; + recover_ipv6_netmask(&a, &this->end); + bits = count_ipv6_bits(&a); + + result << inet_ntop_ez((struct sockaddr_storage *) &this->addr, sizeof(this->addr)) << "/" << bits; + + return result.str(); } -/* Returns true iff the given address is the one that was resolved to create - this target group; i.e., not one of the addresses derived from it with a - netmask. */ -bool TargetGroup::is_resolved_address(const struct sockaddr_storage *ss) { - struct sockaddr_storage resolvedaddr; +NetBlock *NetBlockHostname::resolve() const { + struct addrinfo *addrs, *addr; + std::list resolvedaddrs; + NetBlock *netblock; + struct sockaddr_storage ss; + size_t sslen; + + addrs = resolve_all(this->hostname.c_str(), this->af); + for (addr = addrs; addr != NULL; addr = addr->ai_next) { + if (addr->ai_addrlen < sizeof(ss)) { + memcpy(&ss, addr->ai_addr, addr->ai_addrlen); + resolvedaddrs.push_back(ss); + } + } + if (addrs != NULL) + freeaddrinfo(addrs); if (resolvedaddrs.empty()) - return false; - /* We only have a single distinguished address for these target types. - IPV4_RANGES doesn't, for example. */ - if (!(targets_type == IPV4_NETMASK || targets_type == IPV6_NETMASK)) - return false; - resolvedaddr = *resolvedaddrs.begin(); + return NULL; - return sockaddr_storage_equal(&resolvedaddr, ss); + ss = *resolvedaddrs.begin(); + sslen = sizeof(ss); + + if (resolvedaddrs.size() > 1 && o.verbose > 1) { + error("Warning: Hostname %s resolves to %lu IPs. Using %s.", this->hostname.c_str(), + (unsigned long) resolvedaddrs.size(), inet_ntop_ez(&ss, sslen)); + } + + netblock = NULL; + if (ss.ss_family == AF_INET) { + NetBlockIPv4Ranges *netblock_ranges; + uint32_t ip; + + ip = ntohl(((struct sockaddr_in *) &ss)->sin_addr.s_addr); + netblock_ranges = new NetBlockIPv4Ranges(); + BIT_SET(netblock_ranges->octets[0], (ip & 0xFF000000) >> 24); + BIT_SET(netblock_ranges->octets[1], (ip & 0x00FF0000) >> 16); + BIT_SET(netblock_ranges->octets[2], (ip & 0x0000FF00) >> 8); + BIT_SET(netblock_ranges->octets[3], (ip & 0x000000FF)); + netblock = netblock_ranges; + } else if (ss.ss_family == AF_INET6) { + NetBlockIPv6Netmask *netblock_ipv6; + + netblock_ipv6 = new NetBlockIPv6Netmask(); + netblock_ipv6->set_addr((struct sockaddr_in6 *) &ss); + netblock = netblock_ipv6; + } + + if (netblock == NULL) + return NULL; + + netblock->hostname = this->hostname; + netblock->resolvedaddrs = resolvedaddrs; + netblock->apply_netmask(this->bits); + + return netblock; } -/* Return a string of the name or address that was resolved for this group. */ -const char *TargetGroup::get_resolved_name(void) { - return resolvedname.c_str(); +NetBlockHostname::NetBlockHostname(const char *hostname, int af) { + this->hostname = hostname; + this->af = af; + this->bits = -1; } -/* Return the list of addresses that the name for this group resolved to, if - it came from a name resolution. */ -const std::list &TargetGroup::get_resolved_addrs(void) { - return resolvedaddrs; +bool NetBlockHostname::next(struct sockaddr_storage *ss) { + assert(false); + return false; +} + +void NetBlockHostname::apply_netmask(int bits) { + this->bits = bits; +} + +std::string NetBlockHostname::str() const { + std::ostringstream result; + + result << this->hostname; + if (this->bits >= 0) + result << "/" << this->bits; + + return result.str(); } /* debug level for the adding target is: 3 */ @@ -686,26 +741,3 @@ unsigned long NewTargets::insert (const char *target) { return new_targets->push(target); } - -/* Lookahead is the number of hosts that can be - checked (such as ping scanned) in advance. Randomize causes each - group of up to lookahead hosts to be internally shuffled around. - The target_expressions array MUST REMAIN VALID IN MEMORY as long as - this class instance is used -- the array is NOT copied. - */ -HostGroupState::HostGroupState(int lookahead, int rnd, - char *expr[], int numexpr) { - assert(lookahead > 0); - hostbatch = (Target **) safe_zalloc(sizeof(Target *) * lookahead); - max_batch_sz = lookahead; - current_batch_sz = 0; - next_batch_no = 0; - randomize = rnd; - target_expressions = expr; - num_expressions = numexpr; - next_expression = 0; -} - -HostGroupState::~HostGroupState() { - free(hostbatch); -} diff --git a/TargetGroup.h b/TargetGroup.h index 0358e1307..ad19ad073 100644 --- a/TargetGroup.h +++ b/TargetGroup.h @@ -97,6 +97,8 @@ #ifndef TARGETGROUP_H #define TARGETGROUP_H +#include + #include #include #include @@ -104,85 +106,70 @@ #include "nmap.h" -class TargetGroup { +/* We use bit vectors to represent what values are allowed in an IPv4 octet. + Each vector is built up of an array of bitvector_t (any convenient integer + type). */ +typedef unsigned long bitvector_t; +/* A 256-element bit vector, representing legal values for one octet. */ +typedef bitvector_t octet_bitvector[(256 - 1) / (sizeof(unsigned long) * CHAR_BIT) + 1]; + +class NetBlock { public: - /* used by get_target_types */ - enum _targets_types { TYPE_NONE, IPV4_NETMASK, IPV4_RANGES, IPV6_NETMASK }; - /* used as input to skip range */ - enum _octet_nums { FIRST_OCTET, SECOND_OCTET, THIRD_OCTET }; - TargetGroup(); - - /* Initializes (or reinitializes) the object with a new expression, - such as 192.168.0.0/16 , 10.1.0-5.1-254 , or - fe80::202:e3ff:fe14:1102 . The af parameter is AF_INET or - AF_INET6 Returns 0 for success */ - int parse_expr(const char *target_expr, int af); - /* Grab the next host from this expression (if any). Returns 0 and - fills in ss if successful. ss must point to a pre-allocated - sockaddr_storage structure */ - int get_next_host(struct sockaddr_storage *ss, size_t *sslen); - /* Returns the last given host, so that it will be given again next - time get_next_host is called. Obviously, you should only call - this if you have fetched at least 1 host since parse_expr() was - called */ - int return_last_host(); - /* Returns true iff the given address is the one that was resolved to create - this target group; i.e., not one of the addresses derived from it with a - netmask. */ - bool is_resolved_address(const struct sockaddr_storage *ss); - /* Return a string of the name or address that was resolved for this group. */ - const char *get_resolved_name(void); - /* Return the list of addresses that the name for this group resolved to, if - it came from a name resolution. */ - const std::list &get_resolved_addrs(void); - /* return the target type */ - char get_targets_type() { - return targets_type; - }; - /* get the netmask */ - int get_mask() { - return netmask; - }; - /* is the current expression a named host */ - int get_namedhost() { - return namedhost; - }; - -private: - enum _targets_types targets_type; - void Initialize(); - + virtual ~NetBlock() {} + std::string hostname; std::list resolvedaddrs; - u32 netmask; - std::string resolvedname; + /* Parses an expression such as 192.168.0.0/16, 10.1.0-5.1-254, or + fe80::202:e3ff:fe14:1102/112 and returns a newly allocated NetBlock. The af + parameter is AF_INET or AF_INET6. Returns NULL in case of error. */ + static NetBlock *parse_expr(const char *target_expr, int af); - /* These are used for the '/mask' style of specifying target - net (IPV4_NETMASK) */ - struct in_addr startaddr; - struct in_addr currentaddr; - struct in_addr endaddr; + bool is_resolved_address(const struct sockaddr_storage *ss) const; - // These three are for the '138.1-7,16,91-95,200-.12.1' style (IPV4_RANGES) - u8 addresses[4][256]; - unsigned int current[4]; - u8 last[4]; + virtual bool next(struct sockaddr_storage *ss) = 0; + virtual void apply_netmask(int bits) = 0; + virtual std::string str() const = 0; +}; -#if HAVE_IPV6 - /* These are used for the '/mask' style of specifying target - net (IPV6_NETMASK) */ - struct sockaddr_in6 ip6; - struct in6_addr startaddr6; - struct in6_addr currentaddr6; - struct in6_addr endaddr6; -#endif +class NetBlockIPv4Ranges : public NetBlock { +public: + octet_bitvector octets[4]; - /* Is set to true iff all the addresses in this group have already been - returned. */ + bool next(struct sockaddr_storage *ss); + void apply_netmask(int bits); + std::string str() const; + +private: + unsigned int counter[4]; +}; + +class NetBlockIPv6Netmask : public NetBlock { +public: + void set_addr(const struct sockaddr_in6 *addr); + + bool next(struct sockaddr_storage *ss); + void apply_netmask(int bits); + std::string str() const; + +private: bool exhausted; + struct sockaddr_in6 addr; + struct in6_addr start; + struct in6_addr cur; + struct in6_addr end; +}; - /* is the current target expression a named host? */ - int namedhost; +class NetBlockHostname : public NetBlock { +public: + NetBlockHostname(const char *hostname, int af); + int af; + int bits; + + NetBlock *resolve() const; + + bool next(struct sockaddr_storage *ss); + void apply_netmask(int bits); + std::string str() const; }; /* Adding new targets is for NSE scripts */ @@ -229,28 +216,4 @@ protected: static NewTargets *new_targets; }; -class HostGroupState { -public: - HostGroupState(int lookahead, int randomize, char *target_expressions[], - int num_expressions); - ~HostGroupState(); - Target **hostbatch; - int max_batch_sz; /* The size of the hostbatch[] array */ - int current_batch_sz; /* The number of VALID members of hostbatch[] */ - int next_batch_no; /* The index of the next hostbatch[] member to be given - back to the user */ - int randomize; /* Whether each batch should be "shuffled" prior to the ping - scan (they will also be out of order when given back one - at a time to the client program */ - char **target_expressions; /* An array of target expression strings, passed - to us by the client (client is also in charge - of deleting it AFTER it is done with the - hostgroup_state */ - int num_expressions; /* The number of valid expressions in - target_expressions member above */ - int next_expression; /* The index of the next expression we have - to handle */ - TargetGroup current_expression; /* For batch chunking -- targets in queue */ -}; - #endif /* TARGETGROUP_H */ diff --git a/libnetutil/netutil.cc b/libnetutil/netutil.cc index 18bc395b8..55c35835e 100644 --- a/libnetutil/netutil.cc +++ b/libnetutil/netutil.cc @@ -4514,7 +4514,7 @@ size_t read_host_from_file(FILE *fp, char *buf, size_t n) /* Return next target host specification from the supplied stream. * if parameter "random" is set to true, then the function will * return a random, non-reserved, IP address in decimal-dot notation */ -char *grab_next_host_spec(FILE *inputfd, bool random, int argc, char **fakeargv) { +const char *grab_next_host_spec(FILE *inputfd, bool random, int argc, const char **fakeargv) { static char host_spec[1024]; struct in_addr ip; size_t n; diff --git a/libnetutil/netutil.h b/libnetutil/netutil.h index 4d8a5cbcf..454493611 100644 --- a/libnetutil/netutil.h +++ b/libnetutil/netutil.h @@ -545,7 +545,7 @@ size_t read_host_from_file(FILE *fp, char *buf, size_t n); /* Return next target host specification from the supplied stream. * if parameter "random" is set to true, then the function will * return a random, non-reserved, IP address in decimal-dot notation */ -char *grab_next_host_spec(FILE *inputfd, bool random, int argc, char **fakeargv); +const char *grab_next_host_spec(FILE *inputfd, bool random, int argc, const char **fakeargv); #ifdef WIN32 /* Convert a dnet interface name into the long pcap style. This also caches the diff --git a/nmap.cc b/nmap.cc index aeb307a2e..7ad4f16f4 100644 --- a/nmap.cc +++ b/nmap.cc @@ -1576,12 +1576,8 @@ int nmap_main(int argc, char *argv[]) { /* Pre-Scan and Post-Scan script results datastructure */ ScriptResults *script_scan_results = NULL; #endif - char **host_exp_group; - int num_host_exp_groups; - HostGroupState *hstate = NULL; unsigned int ideal_scan_group_sz = 0; Target *currenths; - char *host_spec = NULL; char myname[MAXHOSTNAMELEN + 1]; int sourceaddrwarning = 0; /* Have we warned them yet about unguessable source addresses? */ @@ -1818,61 +1814,15 @@ int nmap_main(int argc, char *argv[]) { } #endif - /* Time to create a hostgroup state object filled with all the requested - machines. The list is initially empty. It is refilled inside the loop - whenever it is empty. */ - host_exp_group = (char **) safe_malloc(o.ping_group_sz * sizeof(char *)); - num_host_exp_groups = 0; - - hstate = new HostGroupState(o.ping_group_sz, o.randomize_hosts, - host_exp_group, num_host_exp_groups); + HostGroupState hstate(o.ping_group_sz, o.randomize_hosts, argc, (const char **) fakeargv); do { ideal_scan_group_sz = determineScanGroupSize(o.numhosts_scanned, &ports); while (Targets.size() < ideal_scan_group_sz) { o.current_scantype = HOST_DISCOVERY; - currenths = nexthost(hstate, &exclude_group, &ports, o.pingtype); - if (!currenths) { - /* Try to refill with any remaining expressions */ - /* First free the old ones */ - for (i = 0; i < num_host_exp_groups; i++) - free(host_exp_group[i]); - num_host_exp_groups = 0; - /* Now grab any new expressions */ - while (num_host_exp_groups < o.ping_group_sz && - (!o.max_ips_to_scan || o.max_ips_to_scan > o.numhosts_scanned + (int) Targets.size() + num_host_exp_groups) && - (host_spec = grab_next_host_spec(o.inputfd, o.generate_random_ips, argc, fakeargv))) { - // For purposes of random scan - host_exp_group[num_host_exp_groups++] = strdup(host_spec); - } -#ifndef NOLUA - /* Add the new NSE discovered targets to the scan queue */ - if (o.script) { - if (new_targets != NULL) { - while (new_targets->get_queued() > 0 && num_host_exp_groups < o.ping_group_sz) { - std::string target_spec = new_targets->read(); - if (target_spec.length()) - host_exp_group[num_host_exp_groups++] = strdup(target_spec.c_str()); - } - - if (o.debugging > 3) - log_write(LOG_PLAIN, - "New targets in the scanned cache: %ld, pending ones: %ld.\n", - new_targets->get_scanned(), new_targets->get_queued()); - } - } -#endif - if (num_host_exp_groups == 0) - break; - delete hstate; - hstate = new HostGroupState(o.ping_group_sz, o.randomize_hosts, host_exp_group, - num_host_exp_groups); - - /* Try one last time -- with new expressions */ - currenths = nexthost(hstate, &exclude_group, &ports, o.pingtype); - if (!currenths) - break; - } + currenths = nexthost(&hstate, &exclude_group, &ports, o.pingtype); + if (!currenths) + break; if (currenths->flags & HOST_UP && !o.listscan) o.numhosts_up++; @@ -1945,7 +1895,7 @@ int nmap_main(int argc, char *argv[]) { the next group if necessary. See target_needs_new_hostgroup for the details of when we need to split. */ if (target_needs_new_hostgroup(Targets, currenths)) { - returnhost(hstate); + returnhost(&hstate); o.numhosts_up--; break; } @@ -2111,16 +2061,7 @@ int nmap_main(int argc, char *argv[]) { } #endif - delete hstate; - addrset_free(&exclude_group); - hstate = NULL; - - /* Free host expressions */ - for (i = 0; i < num_host_exp_groups; i++) - free(host_exp_group[i]); - num_host_exp_groups = 0; - free(host_exp_group); if (o.inputfd != NULL) fclose(o.inputfd); diff --git a/nping/ArgParser.cc b/nping/ArgParser.cc index df7e6ef04..d3f539d15 100644 --- a/nping/ArgParser.cc +++ b/nping/ArgParser.cc @@ -1176,9 +1176,9 @@ char errstr[256]; * class NpingTargets, that stores the specs and will provide the targets * through calls to getNextTarget(); * */ - char *next_spec=NULL; - while ( (next_spec= grab_next_host_spec(NULL, false, argc, argv)) != NULL ) - o.targets.addSpec( next_spec ); + const char *next_spec=NULL; + while ( (next_spec= grab_next_host_spec(NULL, false, argc, (const char **) argv)) != NULL ) + o.targets.addSpec( (char *) next_spec ); return OP_SUCCESS; } /* End of parseArguments() */ diff --git a/targets.cc b/targets.cc index 3a9a7c83b..0ad987f74 100644 --- a/targets.cc +++ b/targets.cc @@ -309,6 +309,147 @@ static bool target_needs_new_hostgroup(const HostGroupState *hs, const Target *t return false; } +/* Initializes (or reinitializes) the object with a new expression, such + as 192.168.0.0/16 , 10.1.0-5.1-254 , or fe80::202:e3ff:fe14:1102 . + Returns 0 for success */ +int TargetGroup::parse_expr(const char *target_expr, int af) { + if (this->netblock != NULL) + delete this->netblock; + this->netblock = NetBlock::parse_expr(target_expr, af); + if (this->netblock != NULL) + return 0; + else + return 1; +} + +/* Grab the next host from this expression (if any) and updates its internal + state to reflect that the IP was given out. Returns 0 and + fills in ss if successful. ss must point to a pre-allocated + sockaddr_storage structure */ +int TargetGroup::get_next_host(struct sockaddr_storage *ss, size_t *sslen) { + if (this->pushback.ss_family != AF_UNSPEC) { + *ss = this->pushback; + *sslen = sizeof(*ss); + this->pushback.ss_family = AF_UNSPEC; + return 0; + } + + if (this->netblock == NULL) + return -1; + + /* If all we have at this point is a hostname and netmask, resolve into + something where we know the address. If we ever have to use strictly the + hostname, without doing local DNS resolution (like with a proxy scan), this + has to be made conditional (and perhaps an error if the netmask doesn't + limit it to exactly one address). */ + NetBlockHostname *netblock_hostname; + netblock_hostname = dynamic_cast(this->netblock); + if (netblock_hostname != NULL) { + this->netblock = netblock_hostname->resolve(); + if (this->netblock == NULL) { + error("Failed to resolve \"%s\".", netblock_hostname->hostname.c_str()); + return -1; + } + delete netblock_hostname; + } + + *sslen = sizeof(*ss); + if (this->netblock->next(ss)) + return 0; + else + return -1; +} + +/* Returns the given host, so that it will be given the next time + get_next_host is called. Does nothing if ss is NULL. */ +void TargetGroup::return_host(const struct sockaddr_storage *ss) { + if (ss == NULL) + return; + assert(this->pushback.ss_family == AF_UNSPEC); + this->pushback = *ss; +} + +/* Returns true iff the given address is the one that was resolved to create + this target group; i.e., not one of the addresses derived from it with a + netmask. */ +bool TargetGroup::is_resolved_address(const struct sockaddr_storage *ss) const { + return this->netblock->is_resolved_address(ss); +} + +/* Return a string of the name or address that was resolved for this group. */ +const char *TargetGroup::get_resolved_name(void) const { + if (this->netblock->hostname.empty()) + return NULL; + else + return this->netblock->hostname.c_str(); +} + +/* Return the list of addresses that the name for this group resolved to, if + it came from a name resolution. */ +const std::list &TargetGroup::get_resolved_addrs(void) const { + return this->netblock->resolvedaddrs; +} + +/* is the current expression a named host */ +int TargetGroup::get_namedhost() const { + return this->get_resolved_name() != NULL; +}; + +/* Lookahead is the number of hosts that can be + checked (such as ping scanned) in advance. Randomize causes each + group of up to lookahead hosts to be internally shuffled around. + The target_expressions array MUST REMAIN VALID IN MEMORY as long as + this class instance is used -- the array is NOT copied. + */ +HostGroupState::HostGroupState(int lookahead, int rnd, int argc, const char **argv) { + assert(lookahead > 0); + this->argc = argc; + this->argv = argv; + hostbatch = (Target **) safe_zalloc(sizeof(Target *) * lookahead); + max_batch_sz = lookahead; + current_batch_sz = 0; + next_batch_no = 0; + randomize = rnd; +} + +HostGroupState::~HostGroupState() { + free(hostbatch); +} + +const char *HostGroupState::next_expression() { + static char buf[1024]; + + if (o.max_ips_to_scan == 0 || o.numhosts_scanned + this->current_batch_sz < o.max_ips_to_scan) { + const char *expr; + expr = grab_next_host_spec(o.inputfd, o.generate_random_ips, this->argc, this->argv); + if (expr != NULL) + return expr; + } + +#ifndef NOLUA + /* Add any new NSE discovered targets to the scan queue */ + + NewTargets *new_targets = NewTargets::get(); + if (o.script && new_targets != NULL) { + if (new_targets->get_queued() > 0) { + std::string expr_string; + expr_string = new_targets->read().c_str(); + if (o.debugging > 3) { + log_write(LOG_PLAIN, + "New targets in the scanned cache: %ld, pending ones: %ld.\n", + new_targets->get_scanned(), new_targets->get_queued()); + } + if (!expr_string.empty()) { + Strncpy(buf, expr_string.c_str(), sizeof(buf)); + return buf; + } + } + } +#endif + + return NULL; +} + /* Add a element to the XML stating that a target specification was ignored. This can be because of, for example, a DNS resolution failure, or a syntax error. */ @@ -321,12 +462,75 @@ static void log_bogus_target(const char *expr) { xml_newline(); } +/* Returns a newly allocated Target with the given address. Handles all the + details like setting the Target's address and next hop. */ +static Target *setup_target(const HostGroupState *hs, + const struct sockaddr_storage *ss, size_t sslen, + int pingtype) { + struct route_nfo rnfo; + Target *t; + + t = new Target(); + + t->setTargetSockAddr(ss, sslen); + + /* Special handling for the resolved address (for example whatever + scanme.nmap.org resolves to in scanme.nmap.org/24). */ + if (hs->current_group.is_resolved_address(ss)) { + if (hs->current_group.get_namedhost()) + t->setTargetName(hs->current_group.get_resolved_name()); + t->resolved_addrs = hs->current_group.get_resolved_addrs(); + } + + /* We figure out the source IP/device IFF + 1) We are r00t AND + 2) We are doing tcp or udp pingscan OR + 3) We are doing a raw-mode portscan or osscan or traceroute OR + 4) We are on windows and doing ICMP ping */ + if (o.isr00t && + ((pingtype & (PINGTYPE_TCP|PINGTYPE_UDP|PINGTYPE_SCTP_INIT|PINGTYPE_PROTO|PINGTYPE_ARP)) || o.RawScan() +#ifdef WIN32 + || (pingtype & (PINGTYPE_ICMP_PING|PINGTYPE_ICMP_MASK|PINGTYPE_ICMP_TS)) +#endif // WIN32 + )) { + if (!nmap_route_dst(ss, &rnfo)) { + log_bogus_target(inet_ntop_ez(ss, sslen)); + error("%s: failed to determine route to %s", __func__, t->NameIP()); + goto bail; + } + if (rnfo.direct_connect) { + t->setDirectlyConnected(true); + } else { + t->setDirectlyConnected(false); + t->setNextHop(&rnfo.nexthop, sizeof(rnfo.nexthop)); + } + t->setIfType(rnfo.ii.device_type); + if (rnfo.ii.device_type == devt_ethernet) { + if (o.spoofMACAddress()) + t->setSrcMACAddress(o.spoofMACAddress()); + else + t->setSrcMACAddress(rnfo.ii.mac); + } + t->setSourceSockAddr(&rnfo.srcaddr, sizeof(rnfo.srcaddr)); + if (hs->current_batch_sz == 0) /* Because later ones can have different src addy and be cut off group */ + o.decoys[o.decoyturn] = t->v4source(); + t->setDeviceNames(rnfo.ii.devname, rnfo.ii.devfullname); + t->setMTU(rnfo.ii.mtu); + // printf("Target %s %s directly connected, goes through local iface %s, which %s ethernet\n", t->NameIP(), t->directlyConnected()? "IS" : "IS NOT", t->deviceName(), (t->ifType() == devt_ethernet)? "IS" : "IS NOT"); + } + + return t; + +bail: + delete t; + return NULL; +} + Target *nexthost(HostGroupState *hs, const addrset *exclude_group, struct scan_lists *ports, int pingtype) { int i; struct sockaddr_storage ss; size_t sslen; - struct route_nfo rnfo; bool arpping_done = false; struct timeval now; @@ -334,73 +538,35 @@ Target *nexthost(HostGroupState *hs, const addrset *exclude_group, /* Woop! This is easy -- we just pass back the next host struct */ return hs->hostbatch[hs->next_batch_no++]; } - /* Doh, we need to refresh our array */ - /* for (i=0; i < hs->max_batch_sz; i++) hs->hostbatch[i] = new Target(); */ + /* Doh, we need to refresh our array */ hs->current_batch_sz = hs->next_batch_no = 0; - do { - /* Grab anything we have in our current_expression */ + for (;;) { + /* Grab anything we have in our current_group */ while (hs->current_batch_sz < hs->max_batch_sz && - hs->current_expression.get_next_host(&ss, &sslen) == 0) { + hs->current_group.get_next_host(&ss, &sslen) == 0) { Target *t; + /* If we are resuming from a previous scan, we have already finished + scans up to o.resume_ip. */ + if (ss.ss_family == AF_INET && o.resume_ip.s_addr) { + if (o.resume_ip.s_addr == ((struct sockaddr_in *) &ss)->sin_addr.s_addr) + o.resume_ip.s_addr = 0; /* So that we will KEEP the next one */ + continue; /* Try again */ + } + if (hostInExclude((struct sockaddr *)&ss, sslen, exclude_group)) { continue; /* Skip any hosts the user asked to exclude */ } - t = new Target(); - t->setTargetSockAddr(&ss, sslen); - - /* Special handling for the resolved address (for example whatever - scanme.nmap.org resolves to in scanme.nmap.org/24). */ - if (hs->current_expression.is_resolved_address(&ss)) { - if (hs->current_expression.get_namedhost()) - t->setTargetName(hs->current_expression.get_resolved_name()); - t->resolved_addrs = hs->current_expression.get_resolved_addrs(); - } - - /* We figure out the source IP/device IFF - 1) We are r00t AND - 2) We are doing tcp or udp pingscan OR - 3) We are doing a raw-mode portscan or osscan or traceroute OR - 4) We are on windows and doing ICMP ping */ - if (o.isr00t && - ((pingtype & (PINGTYPE_TCP|PINGTYPE_UDP|PINGTYPE_SCTP_INIT|PINGTYPE_PROTO|PINGTYPE_ARP)) || o.RawScan() -#ifdef WIN32 - || (pingtype & (PINGTYPE_ICMP_PING|PINGTYPE_ICMP_MASK|PINGTYPE_ICMP_TS)) -#endif // WIN32 - )) { - t->TargetSockAddr(&ss, &sslen); - if (!nmap_route_dst(&ss, &rnfo)) { - log_bogus_target(inet_ntop_ez(&ss, sslen)); - error("%s: failed to determine route to %s", __func__, t->NameIP()); - continue; - } - if (rnfo.direct_connect) { - t->setDirectlyConnected(true); - } else { - t->setDirectlyConnected(false); - t->setNextHop(&rnfo.nexthop, sizeof(rnfo.nexthop)); - } - t->setIfType(rnfo.ii.device_type); - if (rnfo.ii.device_type == devt_ethernet) { - if (o.spoofMACAddress()) - t->setSrcMACAddress(o.spoofMACAddress()); - else - t->setSrcMACAddress(rnfo.ii.mac); - } - t->setSourceSockAddr(&rnfo.srcaddr, sizeof(rnfo.srcaddr)); - if (hs->current_batch_sz == 0) /* Because later ones can have different src addy and be cut off group */ - o.decoys[o.decoyturn] = t->v4source(); - t->setDeviceNames(rnfo.ii.devname, rnfo.ii.devfullname); - t->setMTU(rnfo.ii.mtu); - // printf("Target %s %s directly connected, goes through local iface %s, which %s ethernet\n", t->NameIP(), t->directlyConnected()? "IS" : "IS NOT", t->deviceName(), (t->ifType() == devt_ethernet)? "IS" : "IS NOT"); - } + t = setup_target(hs, &ss, sslen, pingtype); + if (t == NULL) + continue; /* Does this target need to go in a separate host group? */ if (target_needs_new_hostgroup(hs, t)) { /* Cancel everything! This guy must go in the next group and we are out of here */ - hs->current_expression.return_last_host(); + hs->current_group.return_host(t->TargetSockAddr()); delete t; goto batchfull; } @@ -408,19 +574,20 @@ Target *nexthost(HostGroupState *hs, const addrset *exclude_group, hs->hostbatch[hs->current_batch_sz++] = t; } - if (hs->current_batch_sz < hs->max_batch_sz && - hs->next_expression < hs->num_expressions) { + if (hs->current_batch_sz < hs->max_batch_sz) { + const char *expr; /* We are going to have to pop in another expression. */ - while (hs->next_expression < hs->num_expressions) { - const char *expr; - expr = hs->target_expressions[hs->next_expression++]; - if (hs->current_expression.parse_expr(expr, o.af()) != 0) + for (;;) { + expr = hs->next_expression(); + if (expr == NULL) + goto batchfull; + if (hs->current_group.parse_expr(expr, o.af()) != 0) log_bogus_target(expr); else break; } } else break; - } while(1); + } batchfull: diff --git a/targets.h b/targets.h index ef63863d6..083e85b63 100644 --- a/targets.h +++ b/targets.h @@ -118,8 +118,66 @@ #include "nmap.h" #include "global_structures.h" +#include "TargetGroup.h" -class HostGroupState; +class TargetGroup { +public: + NetBlock *netblock; + struct sockaddr_storage pushback; + + TargetGroup() { + this->netblock = NULL; + this->pushback.ss_family = AF_UNSPEC; + } + + ~TargetGroup() { + if (this->netblock != NULL) + delete this->netblock; + } + + /* Initializes (or reinitializes) the object with a new expression, + such as 192.168.0.0/16 , 10.1.0-5.1-254 , or + fe80::202:e3ff:fe14:1102 . The af parameter is AF_INET or + AF_INET6 Returns 0 for success */ + int parse_expr(const char *target_expr, int af); + /* Grab the next host from this expression (if any). Returns 0 and + fills in ss if successful. ss must point to a pre-allocated + sockaddr_storage structure */ + int get_next_host(struct sockaddr_storage *ss, size_t *sslen); + /* Returns the given host, so that it will be given the next time + get_next_host is called. Does nothing if ss is NULL. */ + void return_host(const struct sockaddr_storage *ss); + /* Returns true iff the given address is the one that was resolved to create + this target group; i.e., not one of the addresses derived from it with a + netmask. */ + bool is_resolved_address(const struct sockaddr_storage *ss) const; + /* Return a string of the name or address that was resolved for this group. */ + const char *get_resolved_name(void) const; + /* Return the list of addresses that the name for this group resolved to, if + it came from a name resolution. */ + const std::list &get_resolved_addrs(void) const; + /* is the current expression a named host */ + int get_namedhost() const; +}; + +class HostGroupState { +public: + HostGroupState(int lookahead, int randomize, int argc, const char *argv[]); + ~HostGroupState(); + Target **hostbatch; + int argc; + const char **argv; + int max_batch_sz; /* The size of the hostbatch[] array */ + int current_batch_sz; /* The number of VALID members of hostbatch[] */ + int next_batch_no; /* The index of the next hostbatch[] member to be given + back to the user */ + int randomize; /* Whether each batch should be "shuffled" prior to the ping + scan (they will also be out of order when given back one + at a time to the client program */ + TargetGroup current_group; /* For batch chunking -- targets in queue */ + + const char *next_expression(); +}; /* Ports is the list of ports the user asked to be scanned (0 terminated), you can just pass NULL (it is only a stupid optimization that needs it) */