diff --git a/TargetGroup.cc b/TargetGroup.cc index 177c2af98..a14b4c3e4 100644 --- a/TargetGroup.cc +++ b/TargetGroup.cc @@ -96,9 +96,14 @@ #include "TargetGroup.h" #include "NmapOps.h" #include "nmap_error.h" +#include "global_structures.h" extern NmapOps o; +#ifndef NOLUA +NewTargets *NewTargets::new_targets; +#endif + TargetGroup::TargetGroup() { Initialize(); } @@ -562,6 +567,97 @@ const std::list &TargetGroup::get_resolved_addrs(void) return resolvedaddrs; } +#ifndef NOLUA +NewTargets *NewTargets::get (void) { + if (new_targets) + return new_targets; + new_targets = new NewTargets(); + return new_targets; +} + +NewTargets::NewTargets (void) { + Initialize(); +} + +void NewTargets::Initialize (void) { + history.clear(); + while(!queue.empty()) + queue.pop(); +} + +/* This private method is used to push new targets to the + * queue. It returns the number of targets in the queue. */ +unsigned long NewTargets::push (const char *target) { + std::pair::iterator, bool> pair_iter; + std::string tg(target); + + if (tg.length() > 0) { + /* save targets in the scanned history here (NSE side). */ + pair_iter = history.insert(tg); + + /* A new target */ + if (pair_iter.second == true) { + /* push target onto the queue for future scans */ + queue.push(tg); + + if (o.debugging > 3) + log_write(LOG_PLAIN, "New target %s pushed onto the queue.\n", tg.c_str()); + } + } + + return queue.size(); +} + +/* Reads a target from the queue and return it to be pushed + * onto Nmap scan queue */ +std::string NewTargets::read (void) { + std::string str; + + /* check to see it there are targets in the queue */ + if (!new_targets->queue.empty()) { + str = new_targets->queue.front(); + new_targets->queue.pop(); + } + + return str; +} + +void NewTargets::clear (void) { + new_targets->history.clear(); +} + +unsigned long NewTargets::get_number (void) { + return new_targets->history.size(); +} + +unsigned long NewTargets::get_scanned (void) { + return new_targets->history.size() - new_targets->queue.size(); +} + +unsigned long NewTargets::get_queued (void) { + return new_targets->queue.size(); +} + +/* This is the function that is used by nse_nmaplib.cc to add + * new targets. + * Returns the number of targets in the queue on success, or 0 on + * failures or when the queue is empty. */ +unsigned long NewTargets::insert (const char *target) { + if (*target) { + if (new_targets == NULL) { + error("Error: to add targets run with -sC or --script options."); + return 0; + } + if (o.current_scantype == SCRIPT_POST_SCAN) { + error("Error: adding targets is disabled in the Post-scanning phase."); + return 0; + } + } + + return new_targets->push(target); +} +#endif + /* 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. diff --git a/TargetGroup.h b/TargetGroup.h index c762cadf5..a912d980f 100644 --- a/TargetGroup.h +++ b/TargetGroup.h @@ -96,6 +96,8 @@ #define TARGETGROUP_H #include +#include +#include #include #include "nmap.h" @@ -172,6 +174,52 @@ class TargetGroup { int namedhost; }; +/* Adding new targets is for NSE scripts */ +#ifndef NOLUA +class NewTargets { + public: + NewTargets(); + + /* return a previous inserted target */ + static std::string read (void); + + /* clear the scanned_targets_cache */ + static void clear (void); + + /* get the number of all new added targets */ + static unsigned long get_number (void); + + /* get the number that have been scanned */ + static unsigned long get_scanned (void); + + /* get the number of queued targets left to scan */ + static unsigned long get_queued (void); + + /* get the new_targets object */ + static NewTargets *get (void); + + /* insert targets to the new_targets_queue */ + static unsigned long insert (const char *target); + private: + /* unsigned long mex_new_targets; */ + + /* A queue to push new targets that were discovered by NSE scripts. + * Nmap will pop future targets from this queue. */ + std::queue queue; + + /* A cache to save scanned targets specifiactions. + * (These are targets that were pushed to Nmap scan queue) */ + std::set history; + + void Initialize(); + + /* Save new targets onto the queue */ + unsigned long push (const char *target); + protected: + static NewTargets *new_targets; +}; +#endif + class HostGroupState { public: HostGroupState(int lookahead, int randomize, char *target_expressions[], diff --git a/nmap.cc b/nmap.cc index a007312ea..43b5b95ba 100644 --- a/nmap.cc +++ b/nmap.cc @@ -445,6 +445,8 @@ int nmap_main(int argc, char *argv[]) { #ifndef NOLUA /* Pre-Scan and Post-Scan script results datastructure */ ScriptResults *script_scan_results = NULL; + /* Only NSE scripts can add targets */ + NewTargets *new_targets = NULL; #endif TargetGroup *exclude_group = NULL; char myname[MAXHOSTNAMELEN + 1]; @@ -1608,6 +1610,7 @@ int nmap_main(int argc, char *argv[]) { open_nse(); if (o.script) { + new_targets = NewTargets::get(); script_scan_results = get_script_scan_results_obj(); script_scan(Targets, SCRIPT_PRE_SCAN); printscriptresults(script_scan_results, SCRIPT_PRE_SCAN); @@ -1642,6 +1645,23 @@ int nmap_main(int argc, char *argv[]) { // 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; @@ -1876,12 +1896,14 @@ int nmap_main(int argc, char *argv[]) { } o.numhosts_scanning = 0; } while(!o.max_ips_to_scan || o.max_ips_to_scan > o.numhosts_scanned); - + #ifndef NOLUA if (o.script) { script_scan(Targets, SCRIPT_POST_SCAN); printscriptresults(script_scan_results, SCRIPT_POST_SCAN); script_scan_results->clear(); + delete new_targets; + new_targets = NULL; } #endif diff --git a/nse_nmaplib.cc b/nse_nmaplib.cc index 6ea35c6d2..55a8efc85 100644 --- a/nse_nmaplib.cc +++ b/nse_nmaplib.cc @@ -10,6 +10,7 @@ extern "C" { #include "nmap_error.h" #include "NmapOps.h" #include "Target.h" +#include "TargetGroup.h" #include "portlist.h" #include "service_scan.h" #include "nmap_rpc.h" @@ -681,6 +682,58 @@ static int l_get_timing_level (lua_State *L) return 1; } +/* Save new discovered targets. + * + * This function can take a Vararg expression: + * A vararg expression that represents targets (IPs or Hostnames). + * + * Returns two values if it receives target arguments: + * The number of targets that were added, or 0 on failures. + * An error message on failures. + * + * If this function was called without an argument then it + * will simply return the number of pending targets that are + * in the queue (waiting to be passed to Nmap). + * + * If the function was only able to add a one target, then we + * consider this success. */ +static int l_add_targets (lua_State *L) +{ + int n; + unsigned long ntarget = 0; + + if (lua_gettop(L) > 0) { + for (n = 1; n <= lua_gettop(L); n++) { + if (!NewTargets::insert(luaL_checkstring(L, n))) + break; + ntarget++; + } + /* was able to add some targets */ + if (ntarget) { + lua_pushnumber(L, ntarget); + return 1; + /* errors */ + } else { + lua_pushnumber(L, ntarget); + lua_pushstring(L, + "Error: failed to add targets, post-scanning phase or no resources."); + return 2; + } + } else { + /* function called without arguments */ + /* push the number of pending targets that are in the queue */ + lua_pushnumber(L, NewTargets::insert("")); + return 1; + } +} + +/* Return the number of added targets */ +static int l_get_new_targets_num (lua_State *L) +{ + lua_pushnumber(L, NewTargets::get_number()); + return 1; +} + // returns a table with DNS servers known to nmap static int l_get_dns_servers (lua_State *L) { @@ -775,6 +828,8 @@ int luaopen_nmap (lua_State *L) {"have_ssl", l_get_have_ssl}, {"fetchfile", l_fetchfile}, {"timing_level", l_get_timing_level}, + {"add_targets", l_add_targets}, + {"new_targets_num",l_get_new_targets_num}, {"get_dns_servers", l_get_dns_servers}, {"is_privileged", l_is_privileged}, {"resolve", l_resolve}, diff --git a/nselib/target.lua b/nselib/target.lua new file mode 100644 index 000000000..0064c47a0 --- /dev/null +++ b/nselib/target.lua @@ -0,0 +1,114 @@ +--- +-- Utility functions to add new discovered targets to Nmap scan queue. +-- +-- The library lets scripts to add new discovered targets to Nmap scan +-- queue. Only scripts that run in the script pre-scanning phase +-- (prerule) and the script scanning phase (hostrule and portrule) are +-- able to add new targets. Post-scanning scripts (postrule) are not +-- allowed to add new targets. +-- +-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html +-- +-- @args newtargets If specified, lets NSE scripts add new targets. +-- @args max-newtargets Sets the number of the maximum allowed +-- new targets. If set to 0 or less then there +-- is no limit. The default value is 0. + +local type = type +local select = select +local unpack = unpack +local tonumber = tonumber + +local stdnse = require "stdnse" +local nmap = require "nmap" + +module ("target") + + +-- This is a special variable and it is a global one, so +-- scripts can check it to see if adding targets is allowed, +-- before calling target.add() function. +-- This variable will be set to true if the script argument +-- 'newtargets' was specified. +ALLOW_NEW_TARGETS = false + +local newtargets, max_newtargets = stdnse.get_script_args("newtargets", + "max-newtargets") +if newtargets then + ALLOW_NEW_TARGETS = true +end + +if max_newtargets then + max_newtargets = tonumber(max_newtargets) +else + max_newtargets = 0 +end + +--- Local function to calculate max allowed new targets +local calc_max_targets = function(targets) + if max_newtargets > 0 then + local pushed_targets = nmap.new_targets_num() + if pushed_targets >= max_newtargets then + return 0 + elseif (targets + pushed_targets) > max_newtargets then + return (max_newtargets - pushed_targets) + end + end + return targets +end + +--- Adds the passed arguments to the Nmap scan queue. +-- +-- Only prerule, portrule and hostrule scripts can add new targets. +-- +-- @param targets A variable number of targets. Target is a +-- string that represents an IP or a Hostname. If this function +-- is called without target arguments then it will return true +-- and the number of pending targets (waiting to be scanned). +-- @usage +-- local status, err = target.add("192.168.1.1") +-- local status, err = target.add("192.168.1.1","192.168.1.2",...) +-- local status, err = target.add("scanme.nmap.org","192.168.1.1",...) +-- local status, err = target.add(unpack(array_of_targets)) +-- local status, pending_targets = target.add() +-- @return True if it has been able to add a minimum one target, or +-- False on failures and if no targets were added. If this +-- function is called without target arguments then it will +-- return true. +-- @return Number of added targets on success, or a string error +-- message in case of failures. If this function is called +-- without target arguments then it will return the number +-- of targets that are in the queue (waiting to be scanned). +add = function (...) + -- Force the check here, but it would be better if scripts + -- check ALLOW_NEW_TARGETS before calling target.add() + if not ALLOW_NEW_TARGETS then + stdnse.print_debug(3, + "Error: to add targets run with --script-args 'newtargets'") + return false, "Error: to add targets run with --script-args 'newtargets'" + end + + local new_targets = {count = select("#", ...), ...} + + -- function called without arguments + if new_targets.count == 0 then + return true, nmap.add_targets() + end + + new_targets.count = calc_max_targets(new_targets.count) + + if new_targets.count == 0 then + stdnse.print_debug(3, + "Warning: Maximum new targets reached, no more new targets.") + return false, "Warning: Maximum new targets reached, no more new targets." + end + + local hosts, err = nmap.add_targets(unpack(new_targets,1,new_targets.count)) + + if hosts == 0 then + stdnse.print_debug(3, "%s", err) + return false, err + end + + return true, hosts +end