Index: nmap.cc =================================================================== --- nmap.cc (revision 16019) +++ nmap.cc (working copy) @@ -253,6 +253,7 @@ " --script-args=: provide arguments to scripts\n" " --script-trace: Show all data sent and received\n" " --script-updatedb: Update the script database.\n" + " --script-autoadd: Automatically add missing dependencies to run" #endif "OS DETECTION:\n" " -O: Enable OS detection\n" @@ -711,6 +712,8 @@ {"script_updatedb", no_argument, 0, 0}, {"script-args",required_argument,0,0}, {"script_args",required_argument,0,0}, + {"script-autoadd",no_argument,0,0}, + {"script_autoadd",no_argument,0,0}, #endif {"ip_options", required_argument, 0, 0}, {"ip-options", required_argument, 0, 0}, @@ -756,6 +759,8 @@ o.scripttrace = 1; } else if (optcmp(long_options[option_index].name, "script-updatedb") == 0){ o.scriptupdatedb = 1; + } else if (optcmp(long_options[option_index].name, "script-autoadd") == 0){ + o.scriptautoadd = 1; } else #endif if (optcmp(long_options[option_index].name, "max-os-tries") == 0) { Index: NmapOps.cc =================================================================== --- NmapOps.cc (revision 16019) +++ NmapOps.cc (working copy) @@ -305,6 +305,7 @@ scriptversion = 0; scripttrace = 0; scriptupdatedb = 0; + scriptautoadd = 0; #endif memset(&sourcesock, 0, sizeof(sourcesock)); sourcesocklen = 0; Index: nse_main.cc =================================================================== --- nse_main.cc (revision 16019) +++ nse_main.cc (working copy) @@ -236,6 +236,8 @@ lua_setfield(L, -2, "default"); lua_pushboolean(L, o.scriptversion == 1); lua_setfield(L, -2, "scriptversion"); + lua_pushboolean(L, o.scriptautoadd == 1); + lua_setfield(L, -2, "scriptautoadd"); lua_pushliteral(L, SCRIPT_ENGINE_LUA_DIR SCRIPT_ENGINE_DATABASE); lua_setfield(L, -2, "script_dbpath"); lua_pushstring(L, o.scriptargs); Index: NmapOps.h =================================================================== --- NmapOps.h (revision 16019) +++ NmapOps.h (working copy) @@ -327,6 +327,7 @@ int scriptversion; int scripttrace; int scriptupdatedb; + int scriptautoadd; void chooseScripts(char* argument); std::vector chosenScripts; #endif Index: nse_main.lua =================================================================== --- nse_main.lua (revision 16019) +++ nse_main.lua (working copy) @@ -66,6 +66,8 @@ local traceback = debug.traceback; +local max = math.max; + local byte = string.byte; local find = string.find; local format = string.format; @@ -74,6 +76,7 @@ local match = string.match; local sub = string.sub; +local concat = table.concat; local insert = table.insert; local remove = table.remove; local sort = table.sort; @@ -202,7 +205,6 @@ if not self[rule] then return nil end -- No rule for this script? local file_closure = self.file_closure; local env = setmetatable({ - runlevel = 1, filename = self.filename, }, {__index = _G}); setfenv(file_closure, env); @@ -224,7 +226,6 @@ local thread = setmetatable({ co = co, env = env, - runlevel = tonumber(rawget(env, "runlevel")) or 1, identifier = tostring(co), info = format("'%s' (%s)", self.short_basename, tostring(co)); type = rule == "hostrule" and "host" or "port", @@ -246,6 +247,8 @@ description = "string", action = "function", categories = "table", + dependencies = "table", + weak_dependencies = "table", }; -- script = Script.new(filename) -- Creates a new Script Class for the script. @@ -264,7 +267,8 @@ -- Give the closure its own environment, with global access local env = setmetatable({ filename = filename, - runlevel = 1, + dependencies = {}, + weak_dependencies = {}, }, {__index = _G}); setfenv(file_closure, env); local co = create(file_closure); -- Create a garbage thread @@ -288,6 +292,16 @@ assert(type(category) == "string", filename.." has non-string entries in the 'categories' array"); end + -- Assert that dependencies is an array of strings + for i, dependency in ipairs(rawget(env, "dependencies")) do + assert(type(dependency) == "string", + filename.." has non-string entries in the 'dependencies' array"); + end + -- Assert that weak_dependencies is an array of strings + for i, dependency in ipairs(rawget(env, "weak_dependencies")) do + assert(type(dependency) == "string", + filename.." has non-string entries in the 'weak_dependencies' array"); + end -- Return the script return setmetatable({ filename = filename, @@ -303,7 +317,8 @@ categories = rawget(env, "categories"), author = rawget(env, "author"), license = rawget(env, "license"), - runlevel = tonumber(rawget(env, "runlevel")) or 1, + dependencies = rawget(env, "dependencies"), + weak_dependencies = rawget(env, "weak_dependencies"), threads = {}, selected_by_name = false, }, {__index = Script, __metatable = Script}); @@ -468,6 +483,69 @@ end end end + + -- calculate runlevels + local name_script = {}; + for i, script in ipairs(chosen_scripts) do + assert(name_script[script.short_basename] == nil); + name_script[script.short_basename] = script; + end + local chain = {}; -- chain of script names + setmetatable(name_script, { + __index = function (t, k) + chain[#chain+1] = k; + if cnse.scriptautoadd then + local t, path = cnse.fetchfile_absolute(k); + if t == nil then -- omitted extension? + t, path = cnse.fetchfile_absolute(k..".nse"); + end + if t == "file" then + local script = Script.new(path); + chosen_scripts[#chosen_scripts+1] = script; + name_script[script.short_basename] = script; + print_verbose(1, + "Script dependency '%s' in chain `%s` added automatically.", + script.filename, concat(chain, "->")); + chain[#chain] = nil; + return script; + else + error("could not locate script dependency in chain `".. + concat(chain, "->").."`"); + end + else + error("missing dependency in chain `"..concat(chain, "->").."`\n".. + "(use --script-autoadd to automatically add missing dependencies)"); + end + end + }); + local function calculate_runlevel (script) + chain[#chain+1] = script.short_basename; + if script.runlevel == false then -- circular dependency + error("circular dependency in chain `"..concat(chain, "->").."`"); + else + script.runlevel = false; -- placeholder + end + local runlevel = 1; + for i, dependency in ipairs(script.dependencies) do + local s = name_script[dependency]; + local r = tonumber(s.runlevel) or calculate_runlevel(s); + runlevel = max(runlevel, r+1); + end + for i, weak_dependency in ipairs(script.weak_dependencies) do + local s = rawget(name_script, weak_dependency); + if s then + local r = tonumber(s.runlevel) or calculate_runlevel(s); + runlevel = max(runlevel, r+1); + end + end + chain[#chain] = nil; + script.runlevel = runlevel; + return runlevel; + end + for i, script in ipairs(chosen_scripts) do + local _ = script.runlevel or calculate_runlevel(script); + end + return chosen_scripts; end @@ -743,7 +821,8 @@ sort(runlevels); for i, runlevel in ipairs(runlevels) do - print_verbose(1, "Starting runlevel %g scan", runlevel); + print_verbose(1, "Starting runlevel %u (of %u) scan.", runlevel, + #runlevels); run(threads[runlevel]); end