Index: nse_main.lua =================================================================== --- nse_main.lua (revision 27267) +++ 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, @@ -385,9 +385,12 @@ -- Creates a new Script Class for the script. -- Arguments: -- filename The filename (path) of the script to load. + -- selected_by_name If the script was selected by name. + -- forced_to_run If the script was selected to run + -- regardless of its rule. -- Returns: -- script The script (class) created. - function Script.new (filename, selected_by_name) + function Script.new (filename, selected_by_name, forced_to_run) assert(type(filename) == "string", "string expected"); if not find(filename, "%.nse$") then log_error( @@ -395,11 +398,14 @@ 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", + 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 +476,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 forced_to_run, }; return setmetatable(script, {__index = Script, __metatable = Script}); end @@ -500,7 +508,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,28 +517,52 @@ ["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); if protected_lua_tokens[lstr] then return lstr; else - return 'm("'..str..'")'; + local forced, substr = is_forced_set(str); + if forced then + forced_rules[substr] = true; + end + return 'm("'..substr..'")'; end end for i, rule in ipairs(rules) do rule = match(rule, "^%s*(.-)%s*$"); -- strip surrounding whitespace - used_rules[rule] = false; -- has not been used yet + local original_rule = rule; -- Globalize all `names`, all visible characters not ',', '(', ')', and ';' local globalized_rule = gsub(rule, "[\033-\039\042-\043\045-\058\060-\126]+", globalize); + -- Remove the force flag + local forced, subrule = is_forced_set(rule); + if forced then + rule = subrule; + end + used_rules[rule] = false; -- has not been used yet -- Precompile the globalized rule local compiled_rule, err = loadstring("return "..globalized_rule, "rule"); if not compiled_rule then err = err:match("rule\"]:%d+:(.+)$"); -- remove (luaL_)where in code - error("Bad script rule:\n\t"..rule.." -> "..err); + error("Bad script rule:\n\t"..original_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, @@ -555,13 +588,21 @@ -- whether it was so that scripts so selected can get a verbosity boost. -- See nmap.verbosity. local selected_by_name = false; + + -- Force the script to run (e.g: "+script"). + local forced = false; + -- A matching function for each script rule. -- If the pattern directly matches a category (e.g. "all"), then -- we return true. Otherwise we test if it is a filename or if -- the script_entry.filename matches the pattern. local function m (pattern) + -- Check the pattern to see if scripts were forced to run + forced = not not forced_rules[pattern]; + -- Check categories if r_categories[lower(pattern)] then return true end + -- Check filename with wildcards pattern = gsub(pattern, "%.nse$", ""); -- remove optional extension pattern = gsub(pattern, "[%^%$%(%)%%%.%[%]%+%-%?]", "%%%1"); -- esc magic @@ -569,17 +610,21 @@ pattern = "^"..pattern.."$"; -- anchor to beginning and end local found = not not find(escaped_basename, pattern); selected_by_name = selected_by_name or found; + return found; end 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 + -- Run the compiled category selection + if setfenv(rule_table.compiled_rule, env)() then + -- do not break so other rules can be marked as used used_rules[rule_table.original_rule] = true; 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); + local script = Script.new(path, selected_by_name, forced); + chosen_scripts[#chosen_scripts+1] = script; files_loaded[path] = true; -- do not break so other rules can be marked as used end @@ -597,6 +642,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 +650,15 @@ 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); 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); + local script = Script.new(file, false, forced); + chosen_scripts[#chosen_scripts+1] = script; files_loaded[file] = true; end end