Index: nse_main.lua =================================================================== --- nse_main.lua (revision 27382) +++ nse_main.lua (working copy) @@ -350,7 +350,7 @@ print_debug(1, "A thread for %s yielded unexpectedly in the file or %s function:\n%s\n", self.filename, rule, traceback(co)); - elseif s and rule_return then + elseif s and (rule_return or self.forced_to_run) then local thread = { co = co, env = env, @@ -387,7 +387,8 @@ -- filename The filename (path) of the script to load. -- Returns: -- script The script (class) created. - function Script.new (filename, selected_by_name) + function Script.new (filename, selected_by_name, flags) + flags = flags or {} assert(type(filename) == "string", "string expected"); if not find(filename, "%.nse$") then log_error( @@ -395,11 +396,15 @@ filename); end local basename = match(filename, "([^/\\]+)$") or filename; - if selected_by_name then - print_debug(2, "Script %s was selected by name.", basename); - end + local short_basename = match(filename, "([^/\\]+)%.nse$") or match(filename, "([^/\\]+)%.[^.]*$") or filename; + if debugging() > 1 then + print_debug(2, "Script %s was selected by %s%s.", + basename, + selected_by_name and "name" or "category", + flags.forced_to_run and " and forced to run" or ""); + end local file_closure = assert(loadfile(filename)); -- Give the closure its own environment, with global access local env = { @@ -470,7 +475,9 @@ license = rawget(env, "license"), dependencies = rawget(env, "dependencies"), threads = {}, + -- Make sure that the following are boolean types. selected_by_name = not not selected_by_name, + forced_to_run = not not flags.forced_to_run, }; return setmetatable(script, {__index = Script, __metatable = Script}); end @@ -500,7 +507,8 @@ "database appears to be corrupt or out of date;\n".. "\tplease update using: nmap --script-updatedb"); - local chosen_scripts, entry_rules, used_rules, files_loaded = {}, {}, {}, {}; + local chosen_scripts, files_loaded = {}, {}; + local entry_rules, used_rules, forced_rules = {}, {}, {}; -- Tokens that are allowed in script rules (--script) local protected_lua_tokens = { @@ -508,6 +516,19 @@ ["or"] = true, ["not"] = true, }; + + -- Was this category selection forced to run (e.g. "+script"). + -- Return: + -- Boolean: True if it's forced otherwise false. + -- String: The new cleaned string. + local function is_forced_set (str) + local substr, count = gsub(str, "^%+", ""); + if count > 0 then + return true, substr; + end + return false, str; + end + -- Globalize all names in str that are not protected_lua_tokens local function globalize (str) local lstr = lower(str); @@ -520,7 +541,9 @@ for i, rule in ipairs(rules) do rule = match(rule, "^%s*(.-)%s*$"); -- strip surrounding whitespace + local forced, rule = is_forced_set(rule) used_rules[rule] = false; -- has not been used yet + forced_rules[rule] = forced; -- Globalize all `names`, all visible characters not ',', '(', ')', and ';' local globalized_rule = gsub(rule, "[\033-\039\042-\043\045-\058\060-\126]+", globalize); @@ -530,6 +553,7 @@ err = err:match("rule\"]:%d+:(.+)$"); -- remove (luaL_)where in code error("Bad script rule:\n\t"..rule.." -> "..err); end + -- These are used to reference and check all the rules later. entry_rules[globalized_rule] = { original_rule = rule, compiled_rule = compiled_rule, @@ -574,12 +598,21 @@ local env = {m = m}; for globalized_rule, rule_table in pairs(entry_rules) do - if setfenv(rule_table.compiled_rule, env)() then -- run the compiled rule + -- Clear and set the environment of the compiled script rule + local compiled_rule = setfenv(rule_table.compiled_rule, env) + local status, found = pcall(compiled_rule) + if not status then + error("Bad script rule:\n\t"..rule_table.original_rule.. + " -> script rule expression not supported."); + end + -- The script rule matches a category or a pattern + if found then used_rules[rule_table.original_rule] = true; + local forced = forced_rules[rule_table.original_rule]; local t, path = cnse.fetchscript(filename); if t == "file" then if not files_loaded[path] then - chosen_scripts[#chosen_scripts+1] = Script.new(path, selected_by_name); + chosen_scripts[#chosen_scripts+1] = Script.new(path, selected_by_name, {forced_to_run = forced}); files_loaded[path] = true; -- do not break so other rules can be marked as used end @@ -597,6 +630,7 @@ -- Now load any scripts listed by name rather than by category. for rule, loaded in pairs(used_rules) do if not loaded then -- attempt to load the file/directory + local forced = not not forced_rules[rule]; local t, path = cnse.fetchscript(rule); if t == nil then -- perhaps omitted the extension? t, path = cnse.fetchscript(rule..".nse"); @@ -604,14 +638,14 @@ if t == nil then error("'"..rule.."' did not match a category, filename, or directory"); elseif t == "file" and not files_loaded[path] then - local script = Script.new(path, true); + local script = Script.new(path, true, {forced_to_run = forced}); chosen_scripts[#chosen_scripts+1] = script; files_loaded[path] = true; elseif t == "directory" then for f in cnse.dir(path) do local file = path .."/".. f if find(f, "%.nse$") and not files_loaded[file] then - chosen_scripts[#chosen_scripts+1] = Script.new(file); + chosen_scripts[#chosen_scripts+1] = Script.new(file,false, {forced_to_run = forced}); files_loaded[file] = true; end end