home *** CD-ROM | disk | FTP | other *** search
/ Hackers Magazine 57 / CdHackersMagazineNr57.iso / Software / Networking / nmap-5.00-setup.exe / nse_main.lua < prev    next >
Text File  |  2009-07-06  |  24KB  |  653 lines

  1. -- Arguments when this file (function) is called, accessible via ...
  2. --   [1] The NSE C library. This is saved in the local variable cnse for
  3. --       access throughout the file.
  4. --   [2] The list of categories/files/directories passed via --script.
  5. -- The actual arguments passed to the anonymous main function:
  6. --   [1] The list of hosts we run against.
  7. --
  8. -- When making changes to this code, please ensure you do not add any
  9. -- code relying global indexing. Instead, create a local below for the
  10. -- global you need access to. This protects the engine from possible
  11. -- replacements made to the global environment, speeds up access, and
  12. -- documents dependencies.
  13. --
  14. -- A few notes about the safety of the engine, that is, the ability for
  15. -- a script developer to crash or otherwise stall NSE. The purpose of noting
  16. -- these attack vectors is more to show the difficulty in accidently
  17. -- breaking the system than to indicate a user may wish to break the
  18. -- system through these means.
  19. --  - A script writer can use the undocumented Lua function newproxy
  20. --    to inject __gc code that could run (and error) at any location.
  21. --  - A script writer can use the debug library to break out of
  22. --    the "sandbox" we give it. This is made a little more difficult by
  23. --    our use of locals to all Lua functions we use and the exclusion
  24. --    of the main thread and subsequent user threads.
  25. --  - A simple while true do end loop can stall the system. This can be
  26. --    avoided by debug hooks to yield the thread at periodic intervals
  27. --    (and perhaps kill the thread) but a C function like string.find and
  28. --    a malicious pattern can stall the system from C just as easily.
  29. --  - The garbage collector function is available to users and they may
  30. --    cause the system to stall through improper use.
  31. --  - Of course the os and io library can cause the system to also break.
  32.  
  33. local NAME = "NSE";
  34.  
  35. local WAITING_TO_RUNNING = "NSE_WAITING_TO_RUNNING";
  36. local DESTRUCTOR = "NSE_DESTRUCTOR";
  37.  
  38. local _R = debug.getregistry(); -- The registry
  39. local _G = _G;
  40.  
  41. local assert = assert;
  42. local collectgarbage = collectgarbage;
  43. local error = error;
  44. local ipairs = ipairs;
  45. local loadfile = loadfile;
  46. local loadstring = loadstring;
  47. local next = next;
  48. local pairs = pairs;
  49. local rawget = rawget;
  50. local select = select;
  51. local setfenv = setfenv;
  52. local setmetatable = setmetatable;
  53. local tonumber = tonumber;
  54. local tostring = tostring;
  55. local type = type;
  56. local unpack = unpack;
  57.  
  58. local create = coroutine.create;
  59. local resume = coroutine.resume;
  60. local status = coroutine.status;
  61. local yield = coroutine.yield;
  62.  
  63. local traceback = debug.traceback;
  64.  
  65. local byte = string.byte;
  66. local find = string.find;
  67. local format = string.format;
  68. local gsub = string.gsub;
  69. local lower = string.lower;
  70. local match = string.match;
  71. local sub = string.sub;
  72.  
  73. local insert = table.insert;
  74. local remove = table.remove;
  75. local sort = table.sort;
  76.  
  77. local nmap = require "nmap";
  78.  
  79. local cnse, rules = ...; -- The NSE C library and Script Rules
  80.  
  81. do -- Append the nselib directory to the Lua search path
  82.   local t, path = assert(cnse.fetchfile_absolute("nselib/"));
  83.   assert(t == "directory", "could not locate nselib directory!");
  84.   package.path = package.path..";"..path.."?.lua";
  85. end
  86.  
  87. -- Some local helper functions --
  88.  
  89. local log_write, verbosity, debugging =
  90.     nmap.log_write, nmap.verbosity, nmap.debugging;
  91.  
  92. local function print_verbose (level, fmt, ...)
  93.   if verbosity() >= assert(tonumber(level)) or debugging() > 0 then
  94.     log_write("stdout", format(fmt, ...));
  95.   end
  96. end
  97.  
  98. local function print_debug (level, fmt, ...)
  99.   if debugging() >= assert(tonumber(level)) then
  100.     log_write("stdout", format(fmt, ...));
  101.   end
  102. end
  103.  
  104. local function log_error (fmt, ...)
  105.   log_write("stderr", format(fmt, ...));
  106. end
  107.  
  108. local function table_size (t)
  109.   local n = 0; for _ in pairs(t) do n = n + 1; end return n;
  110. end
  111.  
  112. -- recursively copy a table, for host/port tables
  113. -- not very rigorous, but it doesn't need to be
  114. local function tcopy (t)
  115.   local tc = {};
  116.   for k,v in pairs(t) do
  117.     if type(v) == "table" then
  118.       tc[k] = tcopy(v);
  119.     else
  120.       tc[k] = v;
  121.     end
  122.   end
  123.   return tc;
  124. end
  125.  
  126. local Script = {}; -- The Script Class, its constructor is Script.new.
  127. local Thread = {}; -- The Thread Class, its constructor is Script:new_thread.
  128. do
  129.   -- Thread:d()
  130.   -- Outputs debug information at level 1 or higher.
  131.   -- Changes "%THREAD" with an appropriate identifier for the debug level
  132.   function Thread:d (fmt, ...)
  133.     if debugging() > 1 then
  134.       print_debug(1, gsub(fmt, "%%THREAD", self.info), ...);
  135.     else
  136.       print_debug(1, gsub(fmt, "%%THREAD", self.short_basename), ...);
  137.     end
  138.   end
  139.  
  140.   function Thread:close ()
  141.     local ch = self.close_handlers;
  142.     for key, destructor_t in pairs(ch) do
  143.       destructor_t.destructor(destructor_t.thread, key);
  144.       ch[key] = nil;
  145.     end
  146.   end
  147.  
  148.   -- thread = Script:new_thread(rule, ...)
  149.   -- Creates a new thread for the script Script.
  150.   -- Arguments:
  151.   --   rule  The rule argument the rule, hostrule or portrule, tested.
  152.   --   ...   The arguments passed to the rule function (host[, port]).
  153.   -- Returns:
  154.   --   thread  The thread (class) is returned, or nil.
  155.   function Script:new_thread (rule, ...)
  156.     assert(rule == "hostrule" or rule == "portrule");
  157.     if not self[rule] then return nil end -- No rule for this script?
  158.     local file_closure = self.file_closure;
  159.     local env = setmetatable({
  160.         runlevel = 1,
  161.         filename = self.filename,
  162.       }, {__index = _G});
  163.     setfenv(file_closure, env);
  164.     local unique_value = {}; -- to test valid yield
  165.     local function main (...)
  166.       file_closure(); -- loads script globals
  167.       return env.action(yield(unique_value, env[rule](...)));
  168.     end
  169.     setfenv(main, env);
  170.     -- This thread allows us to load the script's globals in the
  171.     -- same Lua thread the action and rule functions will execute in.
  172.     local co = create(main);
  173.     local s, value, rule_return = resume(co, ...);
  174.     if s and value ~= unique_value then
  175.       print_debug(1,
  176.     "A thread for %s yielded unexpectedly in the file or %s function:\n%s\n",
  177.           self.filename, rule, traceback(co));
  178.     elseif s and rule_return then
  179.       local thread = setmetatable({
  180.         co = co,
  181.         env = env,
  182.         runlevel = tonumber(rawget(env, "runlevel")) or 1,
  183.         identifier = tostring(co),
  184.         info = format("'%s' (%s)", self.short_basename, tostring(co));
  185.         type = rule == "hostrule" and "host" or "port",
  186.         close_handlers = {},
  187.       }, {
  188.         __metatable = Thread,
  189.         __index = function (thread, k) return Thread[k] or self[k] end
  190.       }); -- Access to the parent Script
  191.       thread.parent = thread; -- itself
  192.       return thread;
  193.     elseif not s then
  194.       print_debug(1, "a thread for %s failed to load:\n%s\n", self.filename,
  195.           traceback(co, tostring(rule_return)));
  196.     end
  197.     return nil;
  198.   end
  199.  
  200.   local required_fields = {
  201.     description = "string",
  202.     action = "function",
  203.     categories = "table",
  204.   };
  205.   -- script = Script.new(filename)
  206.   -- Creates a new Script Class for the script.
  207.   -- Arguments:
  208.   --   filename  The filename (path) of the script to load.
  209.   -- Returns:
  210.   --   script  The script (class) created.
  211.   function Script.new (filename)
  212.     assert(type(filename) == "string", "string expected");
  213.     if not find(filename, "%.nse$") then
  214.       log_error(
  215.           "Warning: Loading '%s' -- the recommended file extension is '.nse'.",
  216.           filename);
  217.     end
  218.     local file_closure = assert(loadfile(filename));
  219.     -- Give the closure its own environment, with global access
  220.     local env = setmetatable({}, {__index = _G});
  221.     setfenv(file_closure, env);
  222.     local co = create(file_closure); -- Create a garbage thread
  223.     assert(resume(co)); -- Get the globals it loads in env
  224.     -- Check that all the required fields were set
  225.     for f, t in pairs(required_fields) do
  226.       local field = rawget(env, f);
  227.       if field == nil then
  228.         error(filename.." is missing required field: '"..f.."'");
  229.       elseif type(field) ~= t then
  230.         error(filename.." field '"..f.."' is of improper type '"..
  231.             type(field).."', expected type '"..t.."'");
  232.       end
  233.     end
  234.     -- Check one of two required rule functions exists
  235.     local hostrule, portrule = rawget(env, "hostrule"), rawget(env, "portrule");
  236.     assert(type(hostrule) == "function" or type(portrule) == "function",
  237.         filename.." is missing a required function: 'hostrule' or 'portrule'");
  238.     -- Assert that categories is an array of strings
  239.     for i, category in ipairs(rawget(env, "categories")) do
  240.       assert(type(category) == "string", 
  241.         filename.." has non-string entries in the 'categories' array");
  242.     end
  243.     -- Return the script
  244.     return setmetatable({
  245.       filename = filename,
  246.       basename = match(filename, "[/\\]([^/\\]-)$") or filename,
  247.       short_basename = match(filename, "[/\\]([^/\\]-)%.nse$") or
  248.                        match(filename, "[/\\]([^/\\]-)%.[^.]*$") or
  249.                        filename,
  250.       id = match(filename, "^.-[/\\]([^\\/]-)%.nse$") or filename,
  251.       file_closure = file_closure,
  252.       hostrule = type(hostrule) == "function" and hostrule or nil,
  253.       portrule = type(portrule) == "function" and portrule or nil,
  254.       args = {n = 0};
  255.       categories = rawget(env, "categories"),
  256.       author = rawget(env, "author"),
  257.       license = rawget(env, "license"),
  258.       runlevel = tonumber(rawget(env, "runlevel")) or 1,
  259.       threads = {},
  260.     }, {__index = Script, __metatable = Script});
  261.   end
  262. end
  263.  
  264. -- check_rules(rules)
  265. -- Adds the "default" category if no rules were specified.
  266. -- Adds other implicitly specified rules (e.g. "version")
  267. --
  268. -- Arguments:
  269. --   rules  The array of rules to check.
  270. local function check_rules (rules)
  271.   if cnse.default and #rules == 0 then rules[1] = "default" end
  272.   if cnse.scriptversion then rules[#rules+1] = "version" end
  273. end
  274.  
  275. -- chosen_scripts = get_chosen_scripts(rules)
  276. -- Loads all the scripts for the given rules using the Script Database.
  277. -- Arguments:
  278. --   rules  The array of rules to use for loading scripts.
  279. -- Returns:
  280. --   chosen_scripts  The array of scripts loaded for the given rules. 
  281. local function get_chosen_scripts (rules)
  282.   check_rules(rules);
  283.  
  284.   local script_dbpath = cnse.script_dbpath;
  285.   local t, path = cnse.fetchfile_absolute(script_dbpath);
  286.   if not t then
  287.     print_verbose(1, "Creating non-existent script database.");
  288.     assert(cnse.updatedb(), "could not update script database!");
  289.     t, path = assert(cnse.fetchfile_absolute(script_dbpath));
  290.   end
  291.   local db_closure = assert(loadfile(path),
  292.     "database appears to be corrupt or out of date;\n"..
  293.     "\tplease update using: nmap --script-updatedb");
  294.  
  295.   local chosen_scripts, entry_rules, used_rules, files_loaded = {}, {}, {}, {};
  296.  
  297.   -- Tokens that are allowed in script rules (--script)
  298.   local protected_lua_tokens = {
  299.     ["and"] = true,
  300.     ["or"] = true,
  301.     ["not"] = true,
  302.   };
  303.   -- Globalize all names in str that are not protected_lua_tokens
  304.   local function globalize (str)
  305.     local lstr = lower(str);
  306.     if protected_lua_tokens[lstr] then
  307.       return lstr;
  308.     else
  309.       return 'm("'..str..'")';
  310.     end
  311.   end
  312.  
  313.   for i, rule in ipairs(rules) do
  314.     rule = match(rule, "^%s*(.-)%s*$"); -- strip surrounding whitespace
  315.     used_rules[rule] = false; -- has not been used yet
  316.     -- Globalize all `names`, all visible characters not ',', '(', ')', and ';'
  317.     local globalized_rule =
  318.         gsub(rule, "[\033-\039\042-\043\045-\058\060-\126]+", globalize);
  319.     -- Precompile the globalized rule
  320.     local compiled_rule, err = loadstring("return "..globalized_rule, "rule");
  321.     if not compiled_rule then
  322.       err = err:match("rule\"]:%d+:(.+)$"); -- remove (luaL_)where in code
  323.       error("Bad script rule:\n\t"..rule.." -> "..err);
  324.     end
  325.     entry_rules[globalized_rule] = {
  326.       original_rule = rule,
  327.       compiled_rule = compiled_rule,
  328.     };
  329.   end
  330.  
  331.   -- Checks if a given script, script_entry, should be loaded. A script_entry
  332.   -- should be in the form: { filename = "name.nse", categories = { ... } }
  333.   local function entry (script_entry)
  334.     local categories, filename = script_entry.categories, script_entry.filename;
  335.     assert(type(categories) == "table" and type(filename) == "string",
  336.         "script database appears corrupt, try `nmap --script-updatedb`");
  337.     local escaped_basename = match(filename, "([^/\\]-)%.nse$") or
  338.                              match(filename, "([^/\\]-)$");
  339.  
  340.     local r_categories = {all = true}; -- A reverse table of categories
  341.     for i, category in ipairs(categories) do
  342.       assert(type(category) == "string", "bad entry in script database");
  343.       r_categories[lower(category)] = true; -- Lowercase the entry
  344.     end
  345.     -- A matching function for each script rule.
  346.     -- If the pattern directly matches a category (e.g. "all"), then
  347.     -- we return true. Otherwise we test if it is a filename or if
  348.     -- the script_entry.filename matches the pattern.
  349.     local function m (pattern)
  350.       -- Check categories
  351.       if r_categories[lower(pattern)] then return true end
  352.       -- Check filename with wildcards
  353.       pattern = gsub(pattern, "%.nse$", ""); -- remove optional extension
  354.       pattern = gsub(pattern, "[%^%$%(%)%%%.%[%]%+%-%?]", "%%%1"); -- esc magic
  355.       pattern = gsub(pattern, "%*", ".*"); -- change to Lua wildcard
  356.       pattern = "^"..pattern.."$"; -- anchor to beginning and end
  357.       return not not find(escaped_basename, pattern);
  358.     end
  359.     local env = {m = m};
  360.  
  361.     for globalized_rule, rule_table in pairs(entry_rules) do
  362.       if setfenv(rule_table.compiled_rule, env)() then -- run the compiled rule
  363.         used_rules[rule_table.original_rule] = true;
  364.         local t, path = cnse.fetchfile_absolute(filename);
  365.         if t == "file" then
  366.           if not files_loaded[path] then
  367.             chosen_scripts[#chosen_scripts+1] = Script.new(path);
  368.             files_loaded[path] = true;
  369.             -- do not break so other rules can be marked as used
  370.           end
  371.         else
  372.           log_error("Warning: Could not load '%s': %s", filename, path);
  373.           break;
  374.         end
  375.       end
  376.     end
  377.   end
  378.  
  379.   setfenv(db_closure, {Entry = entry});
  380.   db_closure(); -- Load the scripts
  381.  
  382.   -- Now load any scripts listed by name rather than by category.
  383.   for rule, loaded in pairs(used_rules) do
  384.     if not loaded then -- attempt to load the file/directory
  385.       local t, path = cnse.fetchfile_absolute(rule);
  386.       if t == nil then -- perhaps omitted the extension?
  387.         t, path = cnse.fetchfile_absolute(rule..".nse");
  388.       end
  389.       if t == nil then
  390.         error("'"..rule.."' did not match a category, filename, or directory");
  391.       elseif t == "file" and not files_loaded[path] then
  392.         chosen_scripts[#chosen_scripts+1] = Script.new(path);
  393.         files_loaded[path] = true;
  394.       elseif t == "directory" then
  395.         for i, file in ipairs(cnse.dump_dir(path)) do
  396.           if not files_loaded[file] then
  397.             chosen_scripts[#chosen_scripts+1] = Script.new(file);
  398.             files_loaded[file] = true;
  399.           end
  400.         end
  401.       end
  402.     end
  403.   end
  404.   return chosen_scripts;
  405. end
  406.  
  407. -- run(threads)
  408. -- The main loop function for NSE. It handles running all the script threads.
  409. -- Arguments:
  410. --   threads  An array of threads (a runlevel) to run.
  411. local function run (threads)
  412.   -- running scripts may be resumed at any time. waiting scripts are
  413.   -- yielded until Nsock wakes them. After being awakened with
  414.   -- nse_restore, waiting threads become pending and later are moved all
  415.   -- at once back to running.
  416.   local running, waiting, pending = {}, {}, {};
  417.   local all = setmetatable({}, {__mode = "kv"}); -- base coroutine to Thread
  418.   -- hosts maps a host to a list of threads for that host.
  419.   local hosts, total = {}, 0;
  420.   local current;
  421.   local progress = cnse.scan_progress_meter(NAME);
  422.  
  423.   print_debug(1, "NSE Script Threads (%d) running:", #threads);
  424.   while #threads > 0 do
  425.     local thread = remove(threads);
  426.     thread:d("Starting %THREAD against %s%s.", thread.host.ip,
  427.         thread.port and ":"..thread.port.number or "");
  428.     all[thread.co], running[thread.co], total = thread, thread, total+1;
  429.     hosts[thread.host] = hosts[thread.host] or {};
  430.     hosts[thread.host][thread.co] = true;
  431.   end
  432.  
  433.   -- _R[WAITING_TO_RUNNING] is called by nse_restore in nse_main.cc
  434.   _R[WAITING_TO_RUNNING] = function (co, ...)
  435.     if waiting[co] then -- ignore a thread not waiting
  436.       pending[co], waiting[co] = waiting[co], nil;
  437.       pending[co].args = {n = select("#", ...), ...};
  438.     end
  439.   end
  440.   -- _R[DESTRUCTOR] is called by nse_destructor in nse_main.cc
  441.   _R[DESTRUCTOR] = function (what, co, key, destructor)
  442.     local thread = all[co] or current;
  443.     if thread then
  444.       local ch = thread.close_handlers;
  445.       if what == "add" then
  446.         ch[key] = {
  447.           thread = co,
  448.           destructor = destructor
  449.         };
  450.       elseif what == "remove" then
  451.         ch[key] = nil;
  452.       end
  453.     end
  454.   end
  455.  
  456.   -- Loop while any thread is running or waiting.
  457.   while next(running) or next(waiting) do
  458.     local nr, nw = table_size(running), table_size(waiting);
  459.     cnse.nsock_loop(50); -- Allow nsock to perform any pending callbacks
  460.     if cnse.key_was_pressed() then
  461.       print_verbose(1, "Active NSE Script Threads: %d (%d waiting)\n",
  462.           nr+nw, nw);
  463.       progress("printStats", 1-(nr+nw)/total);
  464.     elseif progress "mayBePrinted" then
  465.       if verbosity() > 1 or debugging() > 0 then
  466.         progress("printStats", 1-(nr+nw)/total);
  467.       else
  468.         progress("printStatsIfNecessary", 1-(nr+nw)/total);
  469.       end
  470.     end
  471.  
  472.     -- Checked for timed-out hosts.
  473.     for co, thread in pairs(waiting) do
  474.       if cnse.timedOut(thread.host) then
  475.         waiting[co] = nil;
  476.         thread:d("%THREAD target timed out");
  477.         thread:close();
  478.       end
  479.     end
  480.  
  481.     for co, thread in pairs(running) do
  482.       current, running[co] = thread, nil;
  483.       cnse.startTimeOutClock(thread.host);
  484.  
  485.       local s, result = resume(co, unpack(thread.args, 1, thread.args.n));
  486.       if not s then -- script error...
  487.         hosts[thread.host][co] = nil;
  488.         thread:d("%THREAD threw an error!\n%s\n",
  489.             traceback(co, tostring(result)));
  490.         thread:close();
  491.       elseif status(co) == "suspended" then
  492.         waiting[co] = thread;
  493.       elseif status(co) == "dead" then
  494.         hosts[thread.host][co] = nil;
  495.         if type(result) == "string" then
  496.           -- Escape any character outside the range 32-126 except for tab,
  497.           -- carriage return, and line feed. This makes the string safe for
  498.           -- screen display as well as XML (see section 2.2 of the XML spec).
  499.           result = gsub(result, "[^\t\r\n\032-\126]", function(a)
  500.             return format("\\x%02X", byte(a));
  501.           end);
  502.           if thread.type == "host" then
  503.             cnse.host_set_output(thread.host, thread.id, result);
  504.           else
  505.             cnse.port_set_output(thread.host, thread.port, thread.id, result);
  506.           end
  507.         end
  508.         thread:d("Finished %THREAD against %s%s.", thread.host.ip,
  509.             thread.port and ":"..thread.port.number or "");
  510.         thread:close();
  511.       end
  512.  
  513.       -- Any more threads running for this host?
  514.       if not next(hosts[thread.host]) then
  515.         cnse.stopTimeOutClock(thread.host);
  516.       end
  517.     end
  518.  
  519.     -- Move pending threads back to running.
  520.     for co, thread in pairs(pending) do
  521.       pending[co], running[co] = nil, thread;
  522.     end
  523.  
  524.     collectgarbage "collect"; -- important for collecting used sockets & proxies
  525.   end
  526.  
  527.   progress "endTask";
  528. end
  529.  
  530. do -- Load script arguments (--script-args)
  531.   local args = cnse.scriptargs or "";
  532.  
  533.   -- Parse a string in 'str' at 'start'.
  534.   local function parse_string (str, start)
  535.     -- Unquoted
  536.     local uqi, uqj, uqm = find(str,
  537.         "^%s*([^'\"%s{},=][^%s{},=]*)%s*[},=]", start);
  538.     -- Quoted
  539.     local qi, qj, q, qm = find(str, "^%s*(['\"])(.-[^\\])%1%s*[},=]", start);
  540.     -- Empty Quote
  541.     local eqi, eqj = find(str, "^%s*(['\"])%1%s*[},=]", start);
  542.     if uqi then
  543.       return uqm, uqj-1;
  544.     elseif qi then
  545.       return gsub(qm, "\\"..q, q), qj-1;
  546.     elseif eqi then
  547.       return "", eqj-1;
  548.     else
  549.       error("Value around '"..sub(str, start, start+10)..
  550.           "' is invalid or is unterminated by a valid seperator");
  551.     end
  552.   end
  553.   -- Takes 'str' at index 'start' and parses a table. 
  554.   -- Returns the table and the place in the string it finished reading.
  555.   local function parse_table (str, start)
  556.     local _, j = find(str, "^%s*{", start);
  557.     local t = {}; -- table we return
  558.     local tmp, nc; -- temporary and next character inspected
  559.  
  560.     while true do
  561.       j = j+1; -- move past last token
  562.  
  563.       _, j, nc = find(str, "^%s*(%S)", j);
  564.  
  565.       if nc == "}" then -- end of table
  566.         return t, j;
  567.       else -- try to read key/value pair, or array value
  568.         local av = false; -- this is an array value?
  569.         if nc == "{" then -- array value
  570.           av, tmp, j = true, parse_table(str, j);
  571.         else
  572.           tmp, j = parse_string(str, j);
  573.         end
  574.         nc = sub(str, j+1, j+1); -- next token
  575.         if not av and nc == "=" then -- key/value?
  576.           _, j, nc = find(str, "^%s*(%S)", j+2);
  577.           if nc == "{" then
  578.             t[tmp], j = parse_table(str, j);
  579.           else -- regular string
  580.             t[tmp], j = parse_string(str, j);
  581.           end
  582.           nc = sub(str, j+1, j+1); -- next token
  583.         else -- not key/value pair, save array value
  584.           t[#t+1] = tmp;
  585.         end
  586.         if nc == "," then j = j+1 end -- skip "," token
  587.       end
  588.     end
  589.   end
  590.   nmap.registry.args = parse_table("{"..args.."}", 1);
  591. end
  592.  
  593. -- Load all user chosen scripts
  594. local chosen_scripts = get_chosen_scripts(rules);
  595. print_verbose(1, "Loaded %d scripts for scanning.", #chosen_scripts);
  596. for i, script in ipairs(chosen_scripts) do
  597.   print_debug(2, "Loaded '%s'.", script.basename);
  598. end
  599.  
  600. -- main(hosts)
  601. -- This is the main function we return to NSE (on the C side) which actually
  602. -- runs a scan against an array of hosts. nse_main.cc gets this function
  603. -- by loading and executing nse_main.lua.
  604. -- Arguments:
  605. --   hosts  An array of hosts to scan.
  606. return function (hosts)
  607.   if #hosts > 1 then
  608.     print_verbose(1, "Script scanning %d hosts.", #hosts);
  609.   elseif #hosts == 1 then
  610.     print_verbose(1, "Script scanning %s.", hosts[1].ip);
  611.   end
  612.  
  613.   -- Set up the runlevels.
  614.   local threads, runlevels = {}, {};
  615.   for j, host in ipairs(hosts) do
  616.     -- Check hostrules for this host.
  617.     for i, script in ipairs(chosen_scripts) do
  618.       local thread = script:new_thread("hostrule", tcopy(host));
  619.       if thread then
  620.         local runlevel = thread.runlevel;
  621.         if threads[runlevel] == nil then insert(runlevels, runlevel); end
  622.         threads[runlevel] = threads[runlevel] or {};
  623.         insert(threads[runlevel], thread);
  624.         thread.args, thread.host = {n = 1, tcopy(host)}, host;
  625.       end
  626.     end
  627.     -- Check portrules for this host.
  628.     for port in cnse.ports(host) do
  629.       for i, script in ipairs(chosen_scripts) do
  630.         local thread = script:new_thread("portrule", tcopy(host),
  631.             tcopy(port));
  632.         if thread then
  633.           local runlevel = thread.runlevel;
  634.           if threads[runlevel] == nil then insert(runlevels, runlevel); end
  635.           threads[runlevel] = threads[runlevel] or {};
  636.           insert(threads[runlevel], thread);
  637.           thread.args, thread.host, thread.port =
  638.               {n = 2, tcopy(host), tcopy(port)}, host, port;
  639.         end
  640.       end
  641.     end
  642.   end
  643.  
  644.   sort(runlevels);
  645.   for i, runlevel in ipairs(runlevels) do
  646.     print_verbose(1, "Starting runlevel %g scan", runlevel);
  647.     run(threads[runlevel]);
  648.   end
  649.  
  650.   collectgarbage "collect";
  651.   print_verbose(1, "Script Scanning completed.");
  652. end
  653.