diff --git a/Makefile.in b/Makefile.in index ff61ec506..6933d56d5 100644 --- a/Makefile.in +++ b/Makefile.in @@ -67,9 +67,9 @@ INSTALLNDIFF=@INSTALLNDIFF@ UNINSTALLZENMAP=@UNINSTALLZENMAP@ ifneq (@LIBLUA_LIBS@,) -NSE_SRC=nse_main.cc nse_nsock.cc nse_init.cc nse_fs.cc nse_nmaplib.cc nse_debug.cc nse_pcrelib.cc nse_binlib.cc nse_bit.cc -NSE_HDRS=nse_main.h nse_nsock.h nse_init.h nse_fs.h nse_nmaplib.h nse_debug.h nse_macros.h nse_pcrelib.h nse_binlib.h nse_bit.h -NSE_OBJS=nse_main.o nse_nsock.o nse_init.o nse_fs.o nse_nmaplib.o nse_debug.o nse_pcrelib.o nse_binlib.o nse_bit.o +NSE_SRC=nse_main.cc nse_nsock.cc nse_fs.cc nse_nmaplib.cc nse_debug.cc nse_pcrelib.cc nse_binlib.cc nse_bit.cc +NSE_HDRS=nse_main.h nse_nsock.h nse_fs.h nse_nmaplib.h nse_debug.h nse_macros.h nse_pcrelib.h nse_binlib.h nse_bit.h +NSE_OBJS=nse_main.o nse_nsock.o nse_fs.o nse_nmaplib.o nse_debug.o nse_pcrelib.o nse_binlib.o nse_bit.o ifneq (@OPENSSL_LIBS@,) NSE_SRC+=nse_openssl.cc NSE_HDRS+=nse_openssl.h @@ -247,6 +247,7 @@ NSE_FILES = scripts/script.db scripts/*.nse NSE_LIB_LUA_FILES = nselib/*.lua install-nse: $(TARGET) + $(INSTALL) -c -m 644 nse_main.lua $(DESTDIR)$(nmapdatadir)/ $(INSTALL) -d $(DESTDIR)$(nmapdatadir)/scripts cp -f $(NSE_FILES) $(DESTDIR)$(nmapdatadir)/scripts $(INSTALL) -d $(DESTDIR)$(nmapdatadir)/nselib diff --git a/nmap.cc b/nmap.cc index 4bf89d448..36bbcf64d 100644 --- a/nmap.cc +++ b/nmap.cc @@ -698,8 +698,6 @@ int nmap_main(int argc, char *argv[]) { o.chooseScripts(optarg); } else if(optcmp(long_options[option_index].name,"script-args")==0){ o.scriptargs=strdup(optarg); - if(script_check_args()!=0) - fatal("Error parsing --script-args\n"); }else if (optcmp(long_options[option_index].name, "script-trace") == 0) { o.scripttrace = 1; } else if (optcmp(long_options[option_index].name, "script-updatedb") == 0){ @@ -1581,6 +1579,9 @@ int nmap_main(int argc, char *argv[]) { // disable warnings o.max_ips_to_scan = o.numhosts_scanned; } + if (o.servicescan) + o.scriptversion = 1; + open_nse(); #endif /* Time to create a hostgroup state object filled with all the requested @@ -1782,9 +1783,6 @@ int nmap_main(int argc, char *argv[]) { if (o.servicescan) { o.current_scantype = SERVICE_SCAN; -#ifndef NOLUA - o.scriptversion = 1; -#endif service_scan(Targets); } @@ -1907,7 +1905,7 @@ void nmap_free_mem() { if (o.extra_payload) free(o.extra_payload); if (o.ipoptions) free(o.ipoptions); #ifndef NOLUA - script_scan_free(); + close_nse(); free(o.scriptargs); #endif } diff --git a/nse_init.cc b/nse_init.cc deleted file mode 100644 index 553af3b05..000000000 --- a/nse_init.cc +++ /dev/null @@ -1,797 +0,0 @@ - -extern "C" { - #include "lua.h" - #include "lauxlib.h" - #include "lualib.h" /* for libraries */ -} - -#include -#include -#include - -#include "nse_init.h" -#include "nse_nmaplib.h" -#include "nse_macros.h" -#include "nse_debug.h" -#include "nse_fs.h" - -// 3rd Party libs -#include "nse_pcrelib.h" -#include "nse_bit.h" - -#include "nse_binlib.h" - -#include "nbase.h" - -#include "nmap.h" -#include "nmap_error.h" -#include "NmapOps.h" - -#include "errno.h" - -#ifdef HAVE_OPENSSL -#include "nse_openssl.h" -#endif - -#include - -extern NmapOps o; - -extern int current_hosts; -extern int errfunc; - -/* int error_function (lua_State *L) - * - * Arguments: - * -- error_message (passed by Lua) - * - * This function is for use with lua_pcall as the error handler. - * Because the stack is not unwound when this is called, - * we are able to obtain a traceback of the current stack frame. - * We use debug.traceback (an upvalue set in init_lua) for the real work. - */ -static int error_function (lua_State *L) // for use with lua_pcall -{ - luaL_where(L, 1); - lua_insert(L, 1); - lua_pushvalue(L, lua_upvalueindex(1)); // debug.traceback - lua_pushthread(L); - lua_pushliteral(L, ""); - lua_pushinteger(L, 2); - lua_call(L, 3, 1); - lua_concat(L, 3); - return 1; -} - -/* int loadfile (lua_State *L) - * - * Arguments - * -- filename File to load - * - * This function loads a file as a new script, unless it has already been - * loaded. - * - * The file is loaded with it's own environment that has access to the Global - * Environment. The function is tested to be sure it set a global with a valid - * required_fields[?] ("action", "description", ...), port or host rule. - * If it did, the script is added to the SCRIPTFILES table and the script's - * PORT/HOST rule (function) is saved in the registry PORTTESTS or HOSTTESTS - * table with its file closure as a value. This is important to allow each - * thread to have its own action closure with its own locals. - */ -static int loadfile (lua_State *L) -{ - int i; - const char *filename = luaL_checkstring(L, 1); - static const char *required_fields[] = {ACTION, DESCRIPTION}; - - lua_settop(L, 1); // removes other arguments - - /* Is this file already loaded? */ - lua_getfield(L, LUA_REGISTRYINDEX, SCRIPTFILES); - lua_pushvalue(L, 1); - lua_gettable(L, -2); - if (lua_toboolean(L, -1)) - return 0; - lua_pop(L, 2); - - lua_createtable(L, 0, 11); // Environment for script (index 2) - - lua_pushvalue(L, 1); // tell the script about its filename - lua_setfield(L, -2, FILENAME); - - lua_pushnumber(L, 1.0); // set a default RUNLEVEL - lua_setfield(L, -2, RUNLEVEL); - - lua_createtable(L, 0, 1); // script gets access to global env - lua_pushvalue(L, LUA_GLOBALSINDEX); // We may want to use G(L)->mainthread - // later if this function becomes - // exposed. See lstate.h - lua_setfield(L, -2, "__index"); - lua_setmetatable(L, -2); - - if (luaL_loadfile(L, filename) != 0) // load the file (index 3) - { - error("%s: '%s' could not be compiled.", SCRIPT_ENGINE, filename); - SCRIPT_ENGINE_DEBUGGING( - error("%s", lua_tostring(L, -1)); - ) - return 0; - } - lua_pushvalue(L, -1); - lua_pushvalue(L, 2); // push environment table - lua_setfenv(L, -2); // set it - if (lua_pcall(L, 0, 0, 0) != 0) // Call the function (loads globals) - { - error("%s: '%s' threw a run time error and could not be loaded.", - SCRIPT_ENGINE, filename); - SCRIPT_ENGINE_DEBUGGING( - error("%s", lua_tostring(L, -1)); - ) - return 0; - } - - // Check some required fields - for (i = 0; i < ARRAY_LEN(required_fields); i++) - { - lua_pushstring(L, required_fields[i]); - lua_gettable(L, 2); - if (lua_isnil(L, -1)) - { - error("%s: '%s' does not have required field '%s'", SCRIPT_ENGINE, filename, - required_fields[i]); - return 0; - } - lua_pop(L, 1); - } - - /* store the initialized test in either - * the hosttests or the porttests - */ - lua_getfield(L, 2, PORTRULE); // script's portrule - lua_getfield(L, 2, HOSTRULE); // script's hostrule - - /* if we are looking at a portrule then store it in the porttestsets table, - * else if it is a hostrule, then it goes into the hosttestsets table, - * otherwise we fail if there. - */ - if (!lua_isnil(L, -2)) // script has a port rule - { - lua_getfield(L, LUA_REGISTRYINDEX, PORTTESTS); // Get PORTTESTS table - lua_pushvalue(L, -3); // script's portrule - lua_pushvalue(L, 3); // script's file closure - lua_getfenv(L, -1); - lua_pushliteral(L, FILENAME); - lua_pushvalue(L, 1); // filename - lua_settable(L, -3); - lua_pop(L, 1); // file closure environment - lua_settable(L, -3); - } - else if (!lua_isnil(L, -1)) // script has a hostrule - { - lua_getfield(L, LUA_REGISTRYINDEX, HOSTTESTS); - lua_pushvalue(L, -2); // script's hostrule - lua_pushvalue(L, 3); // script's file closure - lua_getfenv(L, -1); - lua_pushliteral(L, FILENAME); - lua_pushvalue(L, 1); // filename - lua_settable(L, -3); - lua_pop(L, 1); // file closure environment - lua_settable(L, -3); - } - else - error("%s: '%s' does not have a portrule or hostrule.", SCRIPT_ENGINE, - filename); - - /* Record the file as loaded. */ - lua_getfield(L, LUA_REGISTRYINDEX, SCRIPTFILES); - lua_pushstring(L, filename); - lua_pushboolean(L, true); - lua_settable(L, -3); - lua_pop(L, 1); - - return 0; -} - -/* int loaddir (lua_State *L) - * - * Arguments - * -- directory Directory (string) to load. - * - * Loads all the scripts (files with a .nse extension), using loadfile. - */ -static int loaddir (lua_State *L) -{ - int i; - luaL_checkstring(L, 1); // directory to load - - lua_pushcclosure(L, nse_scandir, 0); - lua_pushvalue(L, 1); - lua_pushinteger(L, FILES); - lua_call(L, 2, 1); - - lua_pushcclosure(L, loadfile, 0); - for (i = 1; i <= (int) lua_objlen(L, -2); i++) - { - lua_pushvalue(L, -1); // loadfile closure - lua_rawgeti(L, -3, i); // filename - lua_call(L, 1, 0); // load it - } - return 0; -} - -/* int init_setpath (lua_State *L) - * - * Sets the search path of require function to include: - * ./nselib/ For Lua Path (.lua files) - */ -static int init_setpath (lua_State *L) -{ - char path[MAX_FILENAME_LEN]; - - /* set the path lua searches for modules*/ - if (nmap_fetchfile(path, MAX_FILENAME_LEN, SCRIPT_ENGINE_LIB_DIR) != 2) - luaL_error(L, "'%s' not a directory", SCRIPT_ENGINE_LIB_DIR); - - lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); - lua_getfield(L, -1, LUA_LOADLIBNAME); /* "package" */ - - lua_pushstring(L, path); - lua_pushliteral(L, "?.lua;"); - lua_getfield(L, -3, "path"); /* package.path */ - lua_concat(L, 3); - lua_setfield(L, -2, "path"); - - return 0; -} - -/* int init_lua (lua_State *L) - * - * Initializes the Lua State. - * Opens standard libraries as well as nmap and pcre. - * Sets an error function for use by pcall. - * Sets the path for require. - */ -int init_lua (lua_State *L) -{ - int i; - static const luaL_Reg libs[] = { - {NSE_PCRELIBNAME, luaopen_pcrelib}, // pcre library - {"nmap", luaopen_nmap}, // nmap bindings - {NSE_BINLIBNAME, luaopen_binlib}, - {BITLIBNAME, luaopen_bit}, // bit library -#ifdef HAVE_OPENSSL - {OPENSSLLIBNAME, luaopen_openssl}, // openssl bindings -#endif - {"stdnse.c", luaopen_stdnse_c}, - }; - - luaL_openlibs(L); // opens all standard libraries - - lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); // Loaded libraries - for (i = 0; i < ARRAY_LEN(libs); i++) // for each in libs - { - lua_pushstring(L, libs[i].name); - lua_pushcclosure(L, libs[i].func, 0); - lua_pushvalue(L, -2); - lua_call(L, 1, 1); - if (lua_isnil(L, -1)) - { - lua_getglobal(L, libs[i].name); // library? - if (!lua_istable(L, -1)) - { - lua_pop(L, 2); - lua_pushboolean(L, true); - } - else - lua_replace(L, -2); - } - lua_settable(L, -3); - } - lua_pop(L, 1); // _LOADED - - lua_getglobal(L, "debug"); // debug - lua_getfield(L, -1, "traceback"); lua_replace(L, -2); // replace debug table - lua_pushcclosure(L, error_function, 1); - errfunc = luaL_ref(L, LUA_REGISTRYINDEX); - - lua_pushcclosure(L, init_setpath, 0); - lua_call(L, 0, 0); - - lua_newtable(L); - current_hosts = luaL_ref(L, LUA_REGISTRYINDEX); - - return 0; -} - -/* int init_parseargs (lua_State *L) - * - * Arguments - * args Arguments passed through --script-args - * Returns - * function Function that returns a table with the arguments, or an error - * message describing why the arguments could not be parsed. - */ -int init_parseargs (lua_State *L) -{ - const char *arg; - size_t len; - - luaL_checkstring(L, 1); - lua_getfield(L, 1, "gsub"); // string.gsub - lua_pushvalue(L, 1); - lua_pushliteral(L, "=([^{},]+)"); // make strings quoted - lua_pushliteral(L, "=\"%1\""); - lua_call(L, 3, 1); - - lua_pushliteral(L, "return {"); - lua_insert(L, -2); - lua_pushliteral(L, "}"); - lua_concat(L, 3); - arg = lua_tolstring(L, -1, &len); - luaL_loadbuffer(L, arg, len, "Script-Args"); - - return 1; // return function from luaL_loadbuffer or error message returned -} - -/* int init_setargs (lua_State *L) - * - * Takes the function returned by init_parseargs(), calls it, and puts - * the returned table in nmap.registry.args - */ -int init_setargs (lua_State *L) -{ - lua_getglobal(L, "nmap"); - lua_getfield(L, -1, "registry"); - - lua_pushcclosure(L, init_parseargs, 0); - lua_pushstring(L, o.scriptargs); - lua_call(L, 1, 1); - - if (!lua_isfunction(L, -1)) - luaL_error(L, "Bad script arguments!\n\t%s", lua_tostring(L, -1)); - - lua_call(L, 0, 1); /* get returned table */ - - lua_setfield(L, -2, "args"); - - return 0; -} - -/* Sorts the table at the given stack index (by calling table.sort). */ -static void table_sort(lua_State *L, int index) -{ - /* table.sort sorts in place. We modify the original by calling the function - on a copied reference to the table */ - lua_pushvalue(L, index); - /* Get table.sort. */ - lua_getglobal(L, "table"); - lua_getfield(L, -1, "sort"); - lua_replace(L, -2); - /* Put the (copy of the) table after the function. */ - lua_insert(L, -2); - lua_call(L, 1, 0); -} - -/* int init_updatedb (lua_State *L) - * - * Loads all the files in ./scripts and puts them in the database. - * Each file is loaded and for each of its categories, an entry in the - * database is made in the following format: - * Entry{ category = "category1", filename = "somefile" }\n" - * Entry{ category = "category2", filename = "somefile" }\n" - * Each file will have an entry per category. - */ -int init_updatedb (lua_State *L) -{ - int i; - char path[MAX_FILENAME_LEN]; - FILE *scriptdb; - lua_settop(L, 0); // clear all args - - if (nmap_fetchfile(path, sizeof(path) - sizeof(SCRIPT_ENGINE_DATABASE), - SCRIPT_ENGINE_LUA_DIR) == 0) - luaL_error(L, "Couldn't find '%s'", SCRIPT_ENGINE_LUA_DIR); - - lua_pushcclosure(L, nse_scandir, 0); - lua_pushstring(L, path); - lua_pushinteger(L, FILES); - lua_call(L, 2, 1); // get all the .nse files in ./scripts - - /* Sort what we get from nse_scandir so that script.db diffs are useful. */ - table_sort(L, 1); - - // we rely on the fact that nmap_fetchfile returned a string which leaves enough room - // to append the db filename (see call to nmap_fetchfile above) - strncat(path, SCRIPT_ENGINE_DATABASE, MAX_FILENAME_LEN-1); - - scriptdb = fopen(path, "w"); - if (scriptdb == NULL) - luaL_error(L, "Could not open file '%s' for writing.", path); - - SCRIPT_ENGINE_DEBUGGING( - log_write(LOG_STDOUT, "%s: Trying to add %u scripts to the database.\n", - SCRIPT_ENGINE, lua_objlen(L, 1)); - ) - - // give the script global namespace access - lua_createtable(L, 0, 1); // metatable - lua_pushvalue(L, LUA_GLOBALSINDEX); - lua_setfield(L, -2, "__index"); - - for (i = 1; i <= (int) lua_objlen(L, 1); i++) - { - const char *file; - lua_rawgeti(L, 1, i); // integer key from scan_dir() table - file = lua_tostring(L, -1); - if (nse_check_extension(SCRIPT_ENGINE_EXTENSION, file) && - strstr(file, SCRIPT_ENGINE_DATABASE) == NULL) - { - char *filebase = path_get_basename(file); - lua_newtable(L); // script environment - lua_pushvalue(L, -3); // script metatable - lua_setmetatable(L, -2); // set it - if (luaL_loadfile(L, file) != 0) // load file - luaL_error(L, "file '%s' could not be loaded", file); - lua_pushvalue(L, -2); // push environment - lua_setfenv(L, -2); // set it - if ( lua_pcall(L, 0, 0, 0) != 0 ) { - // skip scripts that produce errors - log_write(LOG_STDOUT, "%s: Skipping script '%s' because it produced errors while loading.\n", - SCRIPT_ENGINE, file ); - SCRIPT_ENGINE_VERBOSE( - error("%s", lua_tostring(L, -1)); - ) - lua_pop(L, 3); - continue; - } - - lua_getfield(L, -1, "categories"); - if (lua_isnil(L, -1)) - luaL_error(L, "Script, '%s', being added to the database " - "has no categories.", file); - - if (filebase == NULL) - luaL_error(L, "filename basename could not be generated"); - - lua_getglobal(L, "string"); - lua_getfield(L, -1, "lower"); lua_replace(L, -2); - lua_pushnil(L); - while (lua_next(L, -3) != 0) - { - lua_pushvalue(L, -3); // string.lower - lua_insert(L, -2); // put below category string - lua_call(L, 1, 1); // lowered string on stack - fprintf(scriptdb, "Entry{ category = \"%s\", filename = \"%s\" }\n", - lua_tostring(L, -1), filebase); - lua_pop(L, 1); - } - lua_pop(L, 3); // script environment, categories, string.lower - free(filebase); - } - lua_pop(L, 1); // filename - } - - if (fclose(scriptdb) != 0) - luaL_error(L, "Could not close script.db: %s.", strerror(errno)); - - return 0; -} - -typedef struct extensional_category { - const char *category; - int option; -} extensional_category; - -/* int pick_default_categories (lua_State *L) - * - * This function takes as arguments all the scripts/categories/directories - * passed to the --script command line option, and augments them with any other - * categories that should be added. These are "default" if script scanning was - * requested and no scripts were given on the command line, and "version" if - * version scanning was requested. - * - * If a "reserved" category (currently only "version") was listed on the command - * line, give a fatal error. - */ -static int pick_default_categories (lua_State *L) -{ - int i, top = lua_gettop(L); - extensional_category reserved_categories[] = { - {"version", o.scriptversion}, - }; - - if (top > 0) - { - // if they tried to explicitely select an implicit category, we complain - // ... for each in reserved_categories - for (i = 0; i < ARRAY_LEN(reserved_categories); i++) - { - int j; - lua_pushstring(L, reserved_categories[i].category); - for (j = 1; j <= top; j++) - { - lua_getglobal(L, "string"); - lua_getfield(L, -1, "lower"); lua_replace(L, -2); - lua_pushvalue(L, j); - lua_call(L, 1, 1); - if (lua_equal(L, -1, -2)) - { - fatal("%s: specifying the \"%s\" category explicitly is not allowed.", - SCRIPT_ENGINE, lua_tostring(L, -1)); - } - lua_pop(L, 1); - } - lua_pop(L, 1); - } - } - else if (o.script == 1) - lua_pushliteral(L, "default"); // default set of categories - - // for each in reserved_categories - for (i = 0; i < ARRAY_LEN(reserved_categories); i++) - if (reserved_categories[i].option == 1) - lua_pushstring(L, reserved_categories[i].category); - - return lua_gettop(L); -} - -/* int entry (lua_State *L) - * - * This function is called for each line of script.db, and is responsible for - * loading the scripts that are in requested categories. - * - * script.db is executable Lua code that makes calls to this function, with - * lines like - * - * Entry{ category = "default", filename = "script.nse" } - * - * The function has one upvalue, which is used to accumulate results while the - * database is executed. It is a table of the categories/scripts/directories - * requested, all initially mapping to false, plus canonicalization mappings - * (see loadcategories). - * - * This function receives a table with a category and a filename. A filename is - * loaded if - * 1. its category is in the list of requested categories/scripts/directories, - * or - * 2. the category "all" was requested and the category of the script is not - * "version". - */ -static int entry (lua_State *L) -{ - char script_path[MAX_FILENAME_LEN]; - int not_all; - - luaL_checktype(L, 1, LUA_TTABLE); // Sole argument is a table - lua_settop(L, 1); - lua_getfield(L, 1, CATEGORY); // index 2 - lua_getfield(L, 1, FILENAME); // index 3 - if (!(lua_isstring(L, 2) && lua_isstring(L, 3))) - luaL_error(L, "bad entry in script database"); - lua_pushvalue(L, 3); // filename - - /* Push values that are used to decide whether to load this file. */ - lua_pushvalue(L, 2); // Category name. - lua_gettable(L, lua_upvalueindex(1)); // If non-nil: a requested category. - lua_pushliteral(L, "version"); // For literal comparison against the "version" category. - lua_getfield(L, lua_upvalueindex(1), "all"); // If non-nil: "all" was requested. - - // If category chosen OR ("all" chosen AND category != "version") - if ((not_all = (!lua_isnil(L, -3))) || - (!(lua_isnil(L, -1) || lua_equal(L, 2, -2)))) - { - /* Mark this category as used. */ - if (not_all) - lua_pushvalue(L, 2); - else - lua_pushliteral(L, "all"); - lua_pushvalue(L, -1); - lua_gettable(L, lua_upvalueindex(1)); - - /* Is this a canonicalization entry pointing to the real key? (See - * loadcategories.) */ - if (!lua_isboolean(L, -1)) // points to real key? - { - /* If yes, point the real key to true. */ - lua_pushvalue(L, -1); - lua_pushboolean(L, true); - lua_settable(L, lua_upvalueindex(1)); - } - else - { - /* If no, just point the category name to true. */ - lua_pushvalue(L, -2); - lua_pushboolean(L, true); - lua_settable(L, lua_upvalueindex(1)); - } - lua_pop(L, 1); // Pop Boolean. - - /* Load the file and insert its name into the second upvalue, the table of - * loaded filenames. The value is true. */ - if (nse_fetchfile(script_path, sizeof(script_path), - lua_tostring(L, 3)) != 1) - luaL_error(L, "%s is not a file!", lua_tostring(L, 3)); - - /* Finally, load the file (load its portrule or hostrule). */ - lua_pushcclosure(L, loadfile, 0); - lua_pushstring(L, script_path); - lua_call(L, 1, 0); - } - return 0; -} - -/* int loadcategories (lua_State *L) - * - * This function takes all the categories/scripts/directories passed to it, - * loads the script files belonging to any of the arguments that are categories, - * and returns what's left over (script filenames, directory names, or possibly - * unused category names) in a table. The unused names all map to false. */ -static int loadcategories (lua_State *L) -{ - int i, top = lua_gettop(L); - char c_dbpath[MAX_FILENAME_LEN]; - static const char *dbpath = SCRIPT_ENGINE_LUA_DIR SCRIPT_ENGINE_DATABASE; - - /* Build the script database if it doesn't exist. */ - if (nmap_fetchfile(c_dbpath, sizeof(c_dbpath), dbpath) == 0) - { - lua_pushcclosure(L, init_updatedb, 0); - lua_call(L, 0, 0); - } - - /* Create a table that is used to keep track of which categories/scripts/ - * directories are used and unused. (Because this function deals only with - * categories, script filenames and directory names always come out unused.) - * We build a table with every script/category/directory mapped to false. - * Additionally we map a lower-case version of every string to the original - * string (this is to canonicalize category names). Logic in the entry - * function checks for this canonicalization step. - * - * The entry function adjusts the values in the table to true as files are - * loaded. Later, all the keys that map to true are removed, leaving only the - * unused scripts/categories/directories. Because all strings are considered - * true, the canonicalization entries will be considered "used" and - * removed as well. */ - lua_createtable(L, 0, top); // categories table - for (i = 1; i <= top; i++) - { - /* Create the canonicalization entry mapping the lower-case string to the - * original string. Do this first in case the string maps to itself (i.e., - * it was lower-case to begin with). In that case the mapping to false will - * replace this mapping, and no canonicalization is needed. */ - lua_getglobal(L, "string"); - lua_getfield(L, -1, "lower"); lua_replace(L, -2); - lua_pushvalue(L, i); // Category/script/directory. - lua_call(L, 1, 1); // Canonicalize it. - lua_pushvalue(L, i); - lua_settable(L, -3); - - /* Now map the name to false, meaning we assume the category/script/ - * directory is unused until the entry function marks it as used. */ - lua_pushvalue(L, i); // Category/script/directory. - lua_pushboolean(L, false); - lua_settable(L, -3); - } - - /* Execute script.db with the Entry closure as the only thing in its - * environment (see the entry function). Entry has an upvalue: the used/unused - * table just created. Entry will mark categories/scripts/directories as used - * in the table as files are loaded. */ - luaL_loadfile(L, c_dbpath); - lua_createtable(L, 0, 1); - lua_pushliteral(L, "Entry"); - lua_pushvalue(L, -4); // Used/unused table. - lua_pushcclosure(L, entry, 1); - lua_settable(L, -3); - lua_setfenv(L, -2); // Put the Entry function in the global environment. - lua_call(L, 0, 0); // Execute the script database, letting errors go through. - - /* Go through and remove all the used categories, leaving only the unused - * categories/scripts/directories. */ - lua_pushnil(L); - while (lua_next(L, -2) != 0) - { - if (lua_toboolean(L, -1)) // If used - { - lua_pushvalue(L, -2); - lua_pushnil(L); - lua_settable(L, -5); // remove the category - } - lua_pop(L, 1); - } - - return 1; // Table of unused categories/scripts/directories. -} - -/* int init_rules (lua_State *L) - * - * Arguments - * ... All the categories/scripts/directories passed via --script - * - * This function adds the PORTTESTS and HOSTTESTS to the main state. - * Then it calls pick_default_categories to check for illegally passed implicit - * categories (which it will add otherwise). Next, loadcategories is called - * to load all the viable files for which a category was chosen. The unused - * tags (files/directories, and possibly unused or invalid categories) are - * then each loaded (attempted). If any do not load then an error is raised. - */ -int init_rules (lua_State *L) -{ - int top = lua_gettop(L); // number of categories/scripts - - lua_newtable(L); - lua_setfield(L, LUA_REGISTRYINDEX, PORTTESTS); - - lua_newtable(L); - lua_setfield(L, LUA_REGISTRYINDEX, HOSTTESTS); - - /* This table holds a list of all loaded script filenames, to avoid loading - * any more than once. */ - lua_newtable(L); - lua_setfield(L, LUA_REGISTRYINDEX, SCRIPTFILES); - - lua_pushcclosure(L, pick_default_categories, 0); - lua_insert(L, 1); - lua_call(L, top, LUA_MULTRET); - top = lua_gettop(L); // new number of categories & scripts - - lua_pushcclosure(L, loadcategories, 0); - lua_insert(L, 1); - lua_call(L, top, 1); // returns unused tags table - - lua_pushcclosure(L, loadfile, 0); - lua_pushnil(L); - while (lua_next(L, -3) != 0) - { - char path[MAX_FILENAME_LEN]; - int type = nse_fetchfile_absolute(path, sizeof(path), - lua_tostring(L, -2)); - - if (type == 0) - { - lua_pushvalue(L, -2); // copy of key - lua_pushliteral(L, SCRIPT_ENGINE_EXTENSION); - lua_concat(L, 2); - lua_replace(L, -2); // remove value - type = nse_fetchfile_absolute(path, sizeof(path), lua_tostring(L, -1)); - } - - switch (type) - { - case 0: // no such path - luaL_error(L, "No such category, file or directory: '%s'", - lua_tostring(L, -2)); - case 1: // nmap_fetchfile returned a file - if (!nse_check_extension(SCRIPT_ENGINE_EXTENSION, path)) - { - error("%s: Warning: Loading '%s' - the recommended file extension is '.nse'.", - SCRIPT_ENGINE, path); - } - lua_pushvalue(L, -3); // loadfile closure - lua_pushstring(L, path); - lua_call(L, 1, 0); - break; - case 2: // nmap_fetchfile returned a dir - lua_pushcclosure(L, loaddir, 0); - lua_pushstring(L, path); - lua_call(L, 1, 0); - break; - default: - fatal("%s: In: %s:%i This should never happen.", - SCRIPT_ENGINE, __FILE__, __LINE__); - } - lua_pop(L, 1); - } - - // Compute some stats - SCRIPT_ENGINE_DEBUGGING( - size_t rules_count; - lua_getfield(L, LUA_REGISTRYINDEX, HOSTTESTS); - lua_getfield(L, LUA_REGISTRYINDEX, PORTTESTS); - rules_count = table_length(L, -2) + table_length(L, -1); - lua_pop(L, 2); - log_write(LOG_STDOUT, "%s: Initialized %d rules\n", SCRIPT_ENGINE, rules_count); - ) - return 0; -} diff --git a/nse_init.h b/nse_init.h deleted file mode 100644 index 3eefe4fd3..000000000 --- a/nse_init.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef NSE_INIT -#define NSE_INIT - -// initialize the lua state -// opens the standard libraries and the nmap lua library -int init_lua(lua_State* L); - -//takes the script arguments provided to nmap through --script-args and -//processes and checks them - leaves the processed string on the stack -int init_parseargs(lua_State* L); -//sets the previously parsed args inside nmap.registry -int init_setargs(lua_State* L); - -// you give it a description of scripts to run and it -// populates the tables 'hosttests' and 'porttests' in l with -// activation records for tests -int init_rules(lua_State *L); -int init_updatedb(lua_State* L); - -#endif diff --git a/nse_macros.h b/nse_macros.h index 2e8b52c2d..d392a611f 100644 --- a/nse_macros.h +++ b/nse_macros.h @@ -1,6 +1,8 @@ #ifndef NSE_MACROS #define NSE_MACROS +#define NSE_WAITING_TO_RUNNING "WAITING_TO_RUNNING" + #define HOSTRULE "hostrule" #define HOSTTESTS "hosttests" #define PORTRULE "portrule" diff --git a/nse_main.cc b/nse_main.cc index 4a9670528..24c96bbdc 100644 --- a/nse_main.cc +++ b/nse_main.cc @@ -1,10 +1,13 @@ #include "nse_main.h" -#include "nse_init.h" #include "nse_fs.h" #include "nse_nsock.h" #include "nse_nmaplib.h" +#include "nse_bit.h" +#include "nse_binlib.h" +#include "nse_pcrelib.h" +#include "nse_openssl.h" #include "nse_debug.h" #include "nse_macros.h" @@ -17,74 +20,228 @@ #include "Target.h" #include "nmap_tty.h" +#define NSE_MAIN "NSE_MAIN" /* the main function */ +#define NSE_TRACEBACK "NSE_TRACEBACK" + extern NmapOps o; -/* The global Lua state in which scripts are run. It has file-level scope so it - * and the NSE registry it contains can persist across calls to script_scan. */ -static lua_State* L_script_scan = NULL; +int current_hosts = LUA_NOREF; -struct run_record { - short type; // 0 - hostrule; 1 - portrule - Port* port; - Target* host; -}; - -struct thread_record { - lua_State* thread; - std::string filename; - int resume_arguments; - unsigned int registry_idx; // index in the main state registry - double runlevel; - struct run_record rr; - std::string get_id() const; -}; - -/* Gets the basename of a script filename and removes any ".nse" extension. */ -std::string thread_record::get_id() const { - char *abbrev; - std::string result; - - abbrev = path_get_basename(filename.c_str()); - if (abbrev == NULL) - /* On memory error just return the whole filename. */ - return filename; - if (nse_check_extension(SCRIPT_ENGINE_EXTENSION, abbrev)) - abbrev[strlen(abbrev) - strlen(SCRIPT_ENGINE_EXTENSION)] = '\0'; - result = abbrev; - free(abbrev); - - return result; +static int timedOut (lua_State *L) +{ + Target *target = get_target(L, 1); + lua_pushboolean(L, target->timedOut(NULL)); + return 1; } -int current_hosts = 0; -int errfunc = 0; -std::list > torun_scripts; -std::list running_scripts; -std::list waiting_scripts; +static int startTimeOutClock (lua_State *L) +{ + Target *target = get_target(L, 1); + if (!target->timeOutClockRunning()) + target->startTimeOutClock(NULL); + return 0; -class CompareRunlevels { -public: - bool operator() (const struct thread_record& lhs, const struct thread_record& rhs) { - return lhs.runlevel < rhs.runlevel; +} + +static int stopTimeOutClock (lua_State *L) +{ + Target *target = get_target(L, 1); + if (target->timeOutClockRunning()) + target->stopTimeOutClock(NULL); + return 0; +} + +static int next_port (lua_State *L) +{ + lua_settop(L, 2); + lua_pushvalue(L, lua_upvalueindex(1)); + lua_pushvalue(L, 2); + if (lua_next(L, -2) == 0) + return 0; + else { + lua_pop(L, 1); /* pop boolean value */ + return 1; } -}; +} -// prior execution -int process_preparerunlevels(std::list torun_threads); -int process_preparehost(lua_State* L, Target* target, std::list& torun_threads); -int process_preparethread(lua_State* L, struct thread_record* tr); +static int ports (lua_State *L) +{ + static const int states[] = { + PORT_OPEN, + PORT_OPENFILTERED, + PORT_UNFILTERED, + PORT_HIGHEST_STATE /* last one marks end */ + }; + Target *target = get_target(L, 1); + PortList *plist = &(target->ports); + Port *current = NULL; + lua_newtable(L); + for (int i = 0; states[i] != PORT_HIGHEST_STATE; i++) + while ((current = plist->nextPort(current, TCPANDUDP, states[i])) != NULL) + { + lua_newtable(L); + set_portinfo(L, current); + lua_pushboolean(L, 1); + lua_rawset(L, -3); + } + lua_pushcclosure(L, next_port, 1); + lua_pushnil(L); + lua_pushnil(L); + return 3; +} -// helper functions -int process_pickScriptsForPort( - lua_State* L, - Target* target, - Port* port, - std::list& torun_threads); +static int host_set_output (lua_State *L) +{ + ScriptResult sr; + Target *target = get_target(L, 1); + sr.set_id(luaL_checkstring(L, 2)); + sr.set_output(luaL_checkstring(L, 3)); + target->scriptResults.push_back(sr); + return 0; +} -// execution -int process_mainloop(lua_State* L); -int process_waiting2running(lua_State* L, int resume_arguments); -int process_finalize(lua_State* L, unsigned int registry_idx); +static int port_set_output (lua_State *L) +{ + ScriptResult sr; + Target *target = get_target(L, 1); + Port *port = get_port(L, target, 2); + sr.set_id(luaL_checkstring(L, 3)); + sr.set_output(luaL_checkstring(L, 4)); + port->scriptResults.push_back(sr); + /* increment host port script results*/ + target->ports.numscriptresults++; + return 0; +} + +static int fetchfile_absolute (lua_State *L) +{ + char path[MAX_FILENAME_LEN]; + switch (nse_fetchfile_absolute(path, sizeof(path), luaL_checkstring(L, 1))) + { + case 0: // no such path + lua_pushnil(L); + lua_pushfstring(L, "no path to file/directory: %s", lua_tostring(L, 1)); + break; + case 1: // file returned + lua_pushliteral(L, "file"); + lua_pushstring(L, path); + break; + case 2: // directory returned + lua_pushliteral(L, "directory"); + lua_pushstring(L, path); + break; + default: + return luaL_error(L, "nse_fetchfile_absolute returned bad code"); + } + return 2; +} + +static int dump_dir (lua_State *L) +{ + luaL_checkstring(L, 1); + lua_pushcclosure(L, nse_scandir, 0); + lua_pushvalue(L, 1); + lua_pushinteger(L, FILES); + lua_call(L, 2, 1); + return 1; +} + +static int nsock_loop (lua_State *L) +{ + if (l_nsock_loop(luaL_checkint(L, 1)) == NSOCK_LOOP_ERROR) + luaL_error(L, "an error occurred in nsock_loop"); + return 0; +} + +static int key_was_pressed (lua_State *L) +{ + lua_pushboolean(L, keyWasPressed()); + return 1; +} + +static int ref (lua_State *L) +{ + lua_settop(L, 1); + lua_pushinteger(L, (lua_Integer) luaL_ref(L, LUA_REGISTRYINDEX)); + return 1; +} + +static int unref (lua_State *L) +{ + luaL_unref(L, LUA_REGISTRYINDEX, luaL_checkint(L, 1)); + return 0; +} + +static int updatedb (lua_State *L) +{ + lua_pushboolean(L, script_updatedb() == SCRIPT_ENGINE_SUCCESS); + return 1; +} + +static int scp (lua_State *L) +{ + static const char * const ops[] = {"printStats", "printStatsIfNecessary", + "mayBePrinted", "endTask", NULL}; + ScanProgressMeter *progress = + (ScanProgressMeter *) lua_touserdata(L, lua_upvalueindex(1)); + switch (luaL_checkoption(L, 1, NULL, ops)) + { + case 0: /* printStats */ + progress->printStats((double) luaL_checknumber(L, 2), NULL); + break; + case 1: + progress->printStatsIfNecessary((double) luaL_checknumber(L, 2), NULL); + break; + case 2: /*mayBePrinted */ + lua_pushboolean(L, progress->mayBePrinted(NULL)); + return 1; + case 3: /* endTask */ + progress->endTask(NULL, NULL); + delete progress; + break; + } + return 0; +} + +static int scan_progress_meter (lua_State *L) +{ + lua_pushlightuserdata(L, new ScanProgressMeter(luaL_checkstring(L, 1))); + lua_pushcclosure(L, scp, 1); + return 1; +} + +static void open_cnse (lua_State *L) +{ + static const luaL_Reg nse[] = { + {"fetchfile_absolute", fetchfile_absolute}, + {"dump_dir", dump_dir}, + {"nsock_loop", nsock_loop}, + {"key_was_pressed", key_was_pressed}, + {"ref", ref}, + {"unref", unref}, + {"updatedb", updatedb}, + {"scan_progress_meter", scan_progress_meter}, + {"timedOut", timedOut}, + {"startTimeOutClock", startTimeOutClock}, + {"stopTimeOutClock", stopTimeOutClock}, + {"ports", ports}, + {"host_set_output", host_set_output}, + {"port_set_output", port_set_output}, + {NULL, NULL} + }; + + lua_newtable(L); + luaL_register(L, NULL, nse); + /* Add some other fields */ + lua_pushboolean(L, o.script == 1); /* default scripts if none enumerated? */ + lua_setfield(L, -2, "default"); + lua_pushboolean(L, o.scriptversion == 1); + lua_setfield(L, -2, "scriptversion"); + lua_pushliteral(L, SCRIPT_ENGINE_LUA_DIR SCRIPT_ENGINE_DATABASE); + lua_setfield(L, -2, "script_dbpath"); + lua_pushstring(L, o.scriptargs); + lua_setfield(L, -2, "scriptargs"); +} void ScriptResult::set_output (const char *out) { @@ -117,699 +274,236 @@ static int panic (lua_State *L) return 0; } -/* size_t table_length (lua_State *L, int index) - * - * Returns the length of the table at index index. - * This length is the number of elements, not just array elements. - */ -size_t table_length (lua_State *L, int index) +static void set_nmap_libraries (lua_State *L) { - size_t len = 0; - lua_pushvalue(L, index); - lua_pushnil(L); - while (lua_next(L, -2) != 0) - { - len++; - lua_pop(L, 1); - } - lua_pop(L, 1); // table - return len; -} + static const luaL_Reg libs[] = { + {NSE_PCRELIBNAME, luaopen_pcrelib}, // pcre library + {"nmap", luaopen_nmap}, // nmap bindings + {NSE_BINLIBNAME, luaopen_binlib}, + {BITLIBNAME, luaopen_bit}, // bit library +#ifdef HAVE_OPENSSL + {OPENSSLLIBNAME, luaopen_openssl}, // openssl bindings +#endif + {"stdnse.c", luaopen_stdnse_c}, + {NULL, NULL} + }; -/* int escape_char (lua_State *L) - * - * This function is called via Lua through string.gsub. Its purpose is to - * escape characters. So the first sole character is changed to "\xFF" (hex). - */ -static int escape_char (lua_State *L) -{ - char hold[10]; - const char *str = luaL_checkstring(L, 1); - int size = sprintf(hold, "\\x%02X", *str & 0xff); - lua_pushlstring(L, hold, size); - return 1; + /* Put our libraries in the package.preload */ + lua_getglobal(L, "require"); /* the require function */ + lua_getglobal(L, LUA_LOADLIBNAME); + lua_getfield(L, -1, "preload"); + for (int i = 0; libs[i].name != NULL; i++) + { + lua_pushstring(L, libs[i].name); + lua_pushcclosure(L, libs[i].func, 0); + lua_settable(L, -3); /* set package.preload */ + + lua_pushvalue(L, -3); /* the require function */ + lua_pushstring(L, libs[i].name); + lua_call(L, 1, 0); /* explicitly require it */ + } + lua_pop(L, 3); /* require, package, package.preload */ } int script_updatedb (void) { - int status; - int ret = SCRIPT_ENGINE_SUCCESS; + static const char load_db[] = + "local nse = ...\n" + "local _G, assert, ipairs, loadfile, setfenv, setmetatable, rawget, type =" + " _G, assert, ipairs, loadfile, setfenv, setmetatable, rawget, type\n" + "local lower, match, create, resume, open = \n" + " string.lower, string.match, coroutine.create, coroutine.resume," + " io.open\n" + /* set the package.path */ + "local t, path = assert(nse.fetchfile_absolute('nselib/'))\n" + "assert(t == 'directory', 'could not locate nselib directory!')\n" + "package.path = package.path..';'..path..'?.lua'\n" + /* fetch the scripts directory */ + "local t, path = nse.fetchfile_absolute('scripts/')\n" + "assert(t == 'directory', 'could not locate scripts directory')\n" + "local db = assert(open(path..'script.db', 'w'),\n" + " 'could not open database for writing')\n" + /* dump the scripts/categories */ + "for i, script in ipairs(nse.dump_dir(path)) do\n" + " local env = setmetatable({}, {__index = _G})\n" + " local thread = create(setfenv(assert(loadfile(script)), env))\n" + " assert(resume(thread))\n" + " local categories = rawget(env, 'categories')\n" + " assert(type(categories) == 'table', script.." + " ' categories field is not a table')\n" + " local basename = assert(match(script, '([^\\/]-%.nse)$'))\n" + " for j, category in ipairs(categories) do\n" + " db:write('Entry { category = \"', lower(category)," + " '\", filename = \"', basename, '\" }\\n')\n" + " end\n" + "end\n" + "db:close()\n"; + int status = SCRIPT_ENGINE_SUCCESS; lua_State *L; - SCRIPT_ENGINE_VERBOSE( - log_write(LOG_STDOUT, "%s: Updating rule database.\n", - SCRIPT_ENGINE); - ) + log_write(LOG_STDOUT, "%s: Updating rule database.\n", SCRIPT_ENGINE); L = luaL_newstate(); if (L == NULL) - { - error("%s: Failed luaL_newstate()", SCRIPT_ENGINE); - return 0; - } - lua_atpanic(L, panic); - - status = lua_cpcall(L, init_lua, NULL); - if (status != 0) - { - error("%s: error while initializing Lua State:\n%s\n", - SCRIPT_ENGINE, lua_tostring(L, -1)); - ret = SCRIPT_ENGINE_ERROR; - goto finishup; - } - + fatal("%s: error opening lua for database update\n", SCRIPT_ENGINE); + lua_atpanic(L, panic); /* we let Lua panic if memory error */ + luaL_openlibs(L); + set_nmap_libraries(L); + lua_settop(L, 0); // safety, is 0 anyway - lua_rawgeti(L, LUA_REGISTRYINDEX, errfunc); // index 1 + lua_getglobal(L, "debug"); + lua_getfield(L, -1, "traceback"); + lua_replace(L, -2); - lua_pushcclosure(L, init_updatedb, 0); - status = lua_pcall(L, 0, 0, 1); - if(status != 0) + if (luaL_loadstring(L, load_db) != 0) + fatal("%s: loading load_db failed %s", SCRIPT_ENGINE, lua_tostring(L, -1)); + open_cnse(L); + if (lua_pcall(L, 1, 0, 1) != 0) { error("%s: error while updating Script Database:\n%s\n", SCRIPT_ENGINE, lua_tostring(L, -1)); - ret = SCRIPT_ENGINE_ERROR; - goto finishup; + status = SCRIPT_ENGINE_ERROR; } - - log_write(LOG_STDOUT, "NSE script database updated successfully.\n"); - - finishup: - lua_close(L); - if (ret != SCRIPT_ENGINE_SUCCESS) - { - error("%s: Aborting database update.\n", SCRIPT_ENGINE); - return SCRIPT_ENGINE_ERROR; - } - else - return SCRIPT_ENGINE_SUCCESS; -} - -/* check the script-arguments provided to nmap (--script-args) before - * scanning starts - otherwise the whole scan will run through and be - * aborted before script-scanning - */ -int script_check_args (void) -{ - int ret = SCRIPT_ENGINE_SUCCESS, status; - lua_State* L = luaL_newstate(); - - if (L == NULL) - fatal("Error opening lua, for checking arguments\n"); - lua_atpanic(L, panic); - - /* set all global libraries (we'll need the string-lib) */ - status = lua_cpcall(L, init_lua, NULL); - if (status != 0) - { - error("%s: error while initializing Lua State:\n%s\n", - SCRIPT_ENGINE, lua_tostring(L, -1)); - ret = SCRIPT_ENGINE_ERROR; - goto finishup; - } - - lua_pushcclosure(L, init_parseargs, 0); - lua_pushstring(L, o.scriptargs); - lua_pcall(L, 1, 1, 0); - - if (!lua_isfunction(L, -1)) - ret = SCRIPT_ENGINE_ERROR; - - finishup: + else + log_write(LOG_STDOUT, "NSE script database updated successfully.\n"); lua_close(L); - return ret; + return status; } -/* Initialize a global Lua state used to run scripts. This initializes the - * state, parses script arguments from the command line, and loads port and host - * rules. This only needs to be done once per execution of Nmap. */ -static int init_script_state(lua_State *L) { - std::vector::iterator script_iter; - int status; +static int init_main (lua_State *L) +{ + char path[MAX_FILENAME_LEN]; + std::vector *rules = (std::vector *) + lua_touserdata(L, 1); - /* Call init_lua to load librarie, set up errfunc, and other things. */ - status = lua_cpcall(L, init_lua, NULL); - if (status != 0) { - error("%s: error while initializing Lua State:\n%s\n", - SCRIPT_ENGINE, lua_tostring(L, -1)); - return SCRIPT_ENGINE_ERROR; + /* Load some basic libraries */ + luaL_openlibs(L); + set_nmap_libraries(L); + + /* Load current_hosts */ + luaL_unref(L, LUA_REGISTRYINDEX, current_hosts); + lua_newtable(L); + current_hosts = luaL_ref(L, LUA_REGISTRYINDEX); + + /* Load debug.traceback for collecting any error tracebacks */ + lua_settop(L, 0); /* clear the stack */ + lua_getglobal(L, "debug"); + lua_getfield(L, -1, "traceback"); + lua_replace(L, 1); // debug.traceback stack position 1 + lua_pushvalue(L, 1); + lua_setfield(L, LUA_REGISTRYINDEX, NSE_TRACEBACK); /* save copy */ + + /* Load main Lua code, stack position 2 */ + if (nmap_fetchfile(path, MAX_FILENAME_LEN, "nse_main.lua") != 1) + luaL_error(L, "could not locate nse_main.lua"); + if (luaL_loadfile(L, path) != 0) + luaL_error(L, "could not load nse_main.lua: %s", lua_tostring(L, -1)); + + /* The first argument to the NSE Main Lua code is the private nse + * library table which exposes certain necessary C functions to + * the Lua engine. + */ + open_cnse(L); // stack index 3 + + /* The second argument is the script rules, including the + * files/directories/categories passed as the userdata to this function. + */ + lua_createtable(L, rules->size(), 0); // stack index 4 + for (std::vector::iterator si = rules->begin(); + si != rules->end(); si++) + { + lua_pushstring(L, si->c_str()); + lua_rawseti(L, 4, lua_objlen(L, 4) + 1); } - /* Set any script arguments. */ - status = lua_cpcall(L, init_setargs, NULL); - if (status != 0) { - error("%s: error while setting arguments for scripts:\n%s\n", - SCRIPT_ENGINE, lua_tostring(L, -1)); - return SCRIPT_ENGINE_ERROR; + /* Get Lua main function */ + if (lua_pcall(L, 2, 1, 1) != 0) lua_error(L); /* we wanted a traceback */ + + lua_setfield(L, LUA_REGISTRYINDEX, NSE_MAIN); + return 0; +} + +static int run_main (lua_State *L) +{ + std::vector *targets = (std::vector *) + lua_touserdata(L, 1); + + lua_settop(L, 0); + lua_getfield(L, LUA_REGISTRYINDEX, NSE_TRACEBACK); /* index 1 */ + + lua_getfield(L, LUA_REGISTRYINDEX, NSE_MAIN); /* index 2 */ + assert(lua_isfunction(L, -1)); + + /* The first and only argument to main is the list of targets. + * This has all the target names, 1-N, in a list. + */ + lua_createtable(L, targets->size(), 0); // stack index 3 + for (std::vector::iterator ti = targets->begin(); + ti != targets->end(); ti++) + { + lua_newtable(L); + set_hostinfo(L, (Target *) *ti); + lua_rawseti(L, 3, lua_objlen(L, 3) + 1); + lua_rawgeti(L, LUA_REGISTRYINDEX, current_hosts); + lua_pushlightuserdata(L, (void *) *ti); + lua_setfield(L, -2, (*ti)->targetipstr()); + lua_pop(L, 1); /* current_hosts */ } - /* Get the error function to use with the lua_pcall of init_rules. */ - /* errfunc is stored in the registry by init_lua. */ - lua_rawgeti(L, LUA_REGISTRYINDEX, errfunc); - lua_pushcclosure(L, init_rules, 0); - /* We need room for the list of scripts. */ - if (!lua_checkstack(L, o.chosenScripts.size())) { - error("%s: stack overflow at %s:%d", SCRIPT_ENGINE, __FILE__, __LINE__); - return SCRIPT_ENGINE_ERROR; - } - /* Push each of the selected scripts. */ - for (script_iter = o.chosenScripts.begin(); - script_iter != o.chosenScripts.end(); - script_iter++) { - lua_pushstring(L, script_iter->c_str()); - } - /* Call init_rules using the error function at index 1. */ - status = lua_pcall(L, o.chosenScripts.size(), 0, 1); - if (status != 0) { - error("%s: error while initializing script rules:\n%s\n", - SCRIPT_ENGINE, lua_tostring(L, -1)); - return SCRIPT_ENGINE_ERROR; - } - /* Pop the error function. */ - lua_pop(L, 1); + if (lua_pcall(L, 1, 0, 1) != 0) lua_error(L); /* we wanted a traceback */ + + return 0; +} + +void nse_restore (lua_State *L, int number) +{ + luaL_checkstack(L, 5, "nse_restore: stack overflow"); + lua_pushthread(L); + lua_getfield(L, LUA_REGISTRYINDEX, NSE_WAITING_TO_RUNNING); + lua_insert(L, -(number+2)); /* move WAITING_TO_RUNNING down below the args */ + lua_insert(L, -(number+1)); /* move thread above WAITING_TO_RUNNING */ + /* Call WAITING_TO_RUNNING (defined in nse_main.lua) on the thread and any + other arguments. */ + if (lua_pcall(L, number+1, 0, 0) != 0) + fatal("nse_restore: WAITING_TO_RUNNING error!\n%s", lua_tostring(L, -1)); +} + +static lua_State *L_NSE = NULL; + +int open_nse (void) +{ + if (L_NSE != NULL) return SCRIPT_ENGINE_SUCCESS; + + if ((L_NSE = luaL_newstate()) == NULL) + fatal("%s: failed to open a Lua state!", SCRIPT_ENGINE); + lua_atpanic(L_NSE, panic); + + if (lua_cpcall(L_NSE, init_main, (void *) &o.chosenScripts) != 0) + fatal("%s: failed to initialize the script engine:\n%s\n", SCRIPT_ENGINE, + lua_tostring(L_NSE, -1)); return SCRIPT_ENGINE_SUCCESS; } -/* open a lua instance - * apply all scripts on all hosts */ -int script_scan(std::vector &targets) { - int status; - std::vector::iterator target_iter; - std::list >::iterator runlevel_iter; - std::list::iterator thr_iter; - std::list torun_threads; - +int script_scan (std::vector &targets) +{ o.current_scantype = SCRIPT_SCAN; - SCRIPT_ENGINE_VERBOSE( - log_write(LOG_STDOUT, "%s: Initiating script scanning.\n", SCRIPT_ENGINE); - ) + assert(L_NSE != NULL); + lua_settop(L_NSE, 0); /* clear the stack */ - SCRIPT_ENGINE_DEBUGGING( - unsigned int tlen = targets.size(); - char targetstr[128]; - if(tlen > 1) - log_write(LOG_STDOUT, "%s: Script scanning %d hosts.\n", - SCRIPT_ENGINE, tlen); - else - log_write(LOG_STDOUT, "%s: Script scanning %s.\n", - SCRIPT_ENGINE, (*targets.begin())->NameIP(targetstr, sizeof(targetstr))); - ) - - /* Initialize the script scanning state if it hasn't been already. */ - if (L_script_scan == NULL) { - L_script_scan = luaL_newstate(); - if (L_script_scan == NULL) { - error("%s: Failed luaL_newstate()", SCRIPT_ENGINE); - return SCRIPT_ENGINE_ERROR; - } - lua_atpanic(L_script_scan, panic); - status = init_script_state(L_script_scan); - if (status != SCRIPT_ENGINE_SUCCESS) - goto finishup; - } - - assert(lua_gettop(L_script_scan) == 0); - - SCRIPT_ENGINE_DEBUGGING(log_write(LOG_STDOUT, "%s: Matching rules.\n", SCRIPT_ENGINE);) - - for(target_iter = targets.begin(); target_iter != targets.end(); target_iter++) { - std::string key = ((Target*) (*target_iter))->targetipstr(); - lua_rawgeti(L_script_scan, LUA_REGISTRYINDEX, current_hosts); - lua_pushstring(L_script_scan, key.c_str()); - lua_pushlightuserdata(L_script_scan, (void *) *target_iter); - lua_settable(L_script_scan, -3); - lua_pop(L_script_scan, 1); - - status = process_preparehost(L_script_scan, *target_iter, torun_threads); - if(status != SCRIPT_ENGINE_SUCCESS){ - goto finishup; - } - } - - status = process_preparerunlevels(torun_threads); - if(status != SCRIPT_ENGINE_SUCCESS) { - goto finishup; - } - - SCRIPT_ENGINE_DEBUGGING(log_write(LOG_STDOUT, "%s: Running scripts.\n", SCRIPT_ENGINE);) - - for(runlevel_iter = torun_scripts.begin(); runlevel_iter != torun_scripts.end(); runlevel_iter++) { - running_scripts = (*runlevel_iter); - - SCRIPT_ENGINE_DEBUGGING(log_write(LOG_STDOUT, "%s: Runlevel: %f\n", - SCRIPT_ENGINE, - running_scripts.front().runlevel);) - - /* Start the time-out clocks for targets with scripts in this - * runlevel. The clock is stopped in process_finalize(). */ - for (thr_iter = running_scripts.begin(); - thr_iter != running_scripts.end(); - thr_iter++) - if (!thr_iter->rr.host->timeOutClockRunning()) - thr_iter->rr.host->startTimeOutClock(NULL); - - status = process_mainloop(L_script_scan); - if(status != SCRIPT_ENGINE_SUCCESS){ - goto finishup; - } - } - - -finishup: - SCRIPT_ENGINE_DEBUGGING( - log_write(LOG_STDOUT, "%s: Script scanning completed.\n", SCRIPT_ENGINE); - ) - torun_scripts.clear(); - if(status != SCRIPT_ENGINE_SUCCESS) { - error("%s: Aborting script scan.", SCRIPT_ENGINE); + if (lua_cpcall(L_NSE, run_main, (void *) &targets) != 0) + { + error("%s: Script Engine Scan Aborted.\nAn error was thrown by the " + "engine: %s", SCRIPT_ENGINE, lua_tostring(L_NSE, -1)); return SCRIPT_ENGINE_ERROR; - } else { + } + else return SCRIPT_ENGINE_SUCCESS; - } } -/* Free global resources associated with script scanning. */ -void script_scan_free() { - if (L_script_scan != NULL) - lua_close(L_script_scan); -} - -/* Return a user-presentation string showing the target of a thread_record. It's - just "" for host scripts and ":" for port scripts. At - higher debug levels the address of the thread is included. The result is - returned in a static buffer. */ -static const char *thread_id_str(const thread_record &thr) { - static char buf[128]; - - if (thr.rr.type == 0) /* hostrule */ - Snprintf(buf, sizeof(buf), "%s", thr.rr.host->targetipstr()); - else /* portrule */ - Snprintf(buf, sizeof(buf), "%s:%d", thr.rr.host->targetipstr(), thr.rr.port->portno); - - if (o.debugging > 1) { - char *p = strchr(buf, '\0'); - Snprintf(p, sizeof(buf) + (p - buf), " (thread %p)", (void *) thr.thread); - } - - return buf; -} - -void log_script_started(const thread_record &thr) { - log_write(LOG_STDOUT, "%s (%.3fs): Starting %s against %s.\n", - SCRIPT_ENGINE, o.TimeSinceStartMS() / 1000.0, - thr.get_id().c_str(), thread_id_str(thr)); -} - -void log_script_finished(const thread_record &thr) { - log_write(LOG_STDOUT, "%s (%.3fs): Finished %s against %s.\n", - SCRIPT_ENGINE, o.TimeSinceStartMS() / 1000.0, - thr.get_id().c_str(), thread_id_str(thr)); -} - -void log_script_timeout(const thread_record &thr) { - log_write(LOG_STDOUT, "%s (%.3fs): Stopped %s against %s because of host timeout.\n", - SCRIPT_ENGINE, o.TimeSinceStartMS() / 1000.0, - thr.get_id().c_str(), thread_id_str(thr)); -} - -void log_script_error(const thread_record &thr) { - const char* errmsg; - - /* The error message is what's on top of the stack. */ - errmsg = lua_tostring(thr.thread, -1); - log_write(LOG_STDOUT, "%s (%.3fs): %s against %s ended with error: %s\n", - SCRIPT_ENGINE, o.TimeSinceStartMS() / 1000.0, - thr.get_id().c_str(), thread_id_str(thr), errmsg); -} - -int process_mainloop(lua_State *L) { - int state; - int unfinished = running_scripts.size() + waiting_scripts.size(); - struct thread_record current; - ScanProgressMeter progress = ScanProgressMeter(SCRIPT_ENGINE); - - double total = (double) unfinished; - double done = 0; - - std::list::iterator iter; - struct timeval now; - - if (o.debugging || o.scriptTrace()) { - log_write(LOG_STDOUT, "Running %d script threads:\n", unfinished); - for (iter = running_scripts.begin(); iter != running_scripts.end(); iter++) - log_script_started(*iter); - } - - // while there are scripts in running or waiting state, we loop. - // we rely on nsock_loop to protect us from busy loops when - // all scripts are waiting. - while( unfinished > 0 ) { - - if(l_nsock_loop(50) == NSOCK_LOOP_ERROR) { - error("%s: An error occured in the nsock loop", SCRIPT_ENGINE); - return SCRIPT_ENGINE_ERROR; - } - - unfinished = running_scripts.size() + waiting_scripts.size(); - - if (keyWasPressed()) { - done = 1.0 - (((double) unfinished) / total); - if (o.verbose > 1 || o.debugging) { - log_write(LOG_STDOUT, "Active NSE scripts: %d\n", unfinished); - log_flush(LOG_STDOUT); - } - progress.printStats(done, NULL); - } - - SCRIPT_ENGINE_VERBOSE( - if(progress.mayBePrinted(NULL)) { - done = 1.0 - (((double) unfinished) / total); - if(o.verbose > 1 || o.debugging) - progress.printStats(done, NULL); - else - progress.printStatsIfNecessary(done, NULL); - }) - - gettimeofday(&now, NULL); - - for(iter = waiting_scripts.begin(); iter != waiting_scripts.end(); iter++) - if (iter->rr.host->timedOut(&now)) { - running_scripts.push_front((*iter)); - waiting_scripts.erase(iter); - iter = waiting_scripts.begin(); - } - - // Run the garbage collecter. FIXME: This can error in a __gc metamethod - lua_gc(L, LUA_GCCOLLECT, 0); - - while (!running_scripts.empty()) { - current = *(running_scripts.begin()); - - if (current.rr.host->timedOut(&now)) { - if (o.debugging || o.verbose > 1 || o.scriptTrace()) - log_script_timeout(current); - SCRIPT_ENGINE_TRY(process_finalize(L, current.registry_idx)); - continue; - } - else - state = lua_resume(current.thread, current.resume_arguments); - - if(state == LUA_YIELD) { - // this script has performed a network io operation - // we put it in the waiting - // when the network io operation has completed, - // a callback from the nsock library will put the - // script back into the running state - - waiting_scripts.push_back(current); - running_scripts.pop_front(); - } else if( state == 0) { - // this script has finished - // we first check if it produced output - // then we release the thread and remove it from the - // running_scripts list - - if (o.debugging || o.scriptTrace()) - log_script_finished(current); - - if(lua_isstring (current.thread, 2)) { // FIXME - ScriptResult sr; - sr.set_id(current.get_id().c_str()); - lua_State *thread = current.thread; - lua_getfield(thread, 2, "gsub"); - lua_pushvalue(thread, 2); // output FIXME - /* Escape any character outside the range 32-126 except for - tab, carriage return, and line feed. This makes the - string safe for screen display as well as XML (see - section 2.2 of the XML spec). */ - lua_pushliteral(thread, "[^\t\r\n\040-\176]"); - lua_pushcclosure(thread, escape_char, 0); - lua_call(thread, 3, 1); - sr.set_output(lua_tostring(thread, -1)); - if(current.rr.type == 0) { - current.rr.host->scriptResults.push_back(sr); - } else if(current.rr.type == 1) { - current.rr.port->scriptResults.push_back(sr); - current.rr.host->ports.numscriptresults++; - } - lua_pop(thread, 2); - } - - SCRIPT_ENGINE_TRY(process_finalize(L, current.registry_idx)); - } else { - // this script returned because of an error - // print the failing reason if the verbose level is high enough - if (o.debugging || o.scriptTrace()) - log_script_error(current); - SCRIPT_ENGINE_TRY(process_finalize(L, current.registry_idx)); - } - } // while - } - - progress.endTask(NULL, NULL); - - return SCRIPT_ENGINE_SUCCESS; -} - -// If the target still has scripts in either running_scripts -// or waiting_scripts then it is still running. This only -// pertains to scripts in the current runlevel. - -int has_target_finished(Target *target) { - std::list::iterator iter; - - for (iter = waiting_scripts.begin(); iter != waiting_scripts.end(); iter++) - if (target == iter->rr.host) return 0; - - for (iter = running_scripts.begin(); iter != running_scripts.end(); iter++) - if (target == iter->rr.host) return 0; - - return 1; -} - -int process_finalize(lua_State* L, unsigned int registry_idx) { - luaL_unref(L, LUA_REGISTRYINDEX, registry_idx); - struct thread_record thr = running_scripts.front(); - - running_scripts.pop_front(); - - if (has_target_finished(thr.rr.host)) - thr.rr.host->stopTimeOutClock(NULL); - - return SCRIPT_ENGINE_SUCCESS; -} - -int process_waiting2running(lua_State* L, int resume_arguments) { - std::list::iterator iter; - - // find the lua state which has received i/o - for(iter = waiting_scripts.begin(); (*iter).thread != L; iter++) { - - // It is very unlikely that a thread which - // is not in the waiting queue tries to - // continue - // it does happen when they try to do socket i/o - // inside a pcall - - // This also happens when we timeout a script - // In this case, the script is still in the waiting - // queue and we will have manually removed it from - // the waiting queue so we just return. - - if(iter == waiting_scripts.end()) - return SCRIPT_ENGINE_SUCCESS; - } - - (*iter).resume_arguments = resume_arguments; - - // put the thread back into the running - // queue - //running_scripts.push_front((*iter)); - running_scripts.push_back((*iter)); - waiting_scripts.erase(iter); - - return SCRIPT_ENGINE_SUCCESS; -} - -/* try all host and all port rules against the - * state of the current target - * make a list with run records for the scripts - * which want to run - * process all scripts in the list - * */ -int process_preparehost(lua_State* L, Target* target, std::list& torun_threads) { - PortList* plist = &(target->ports); - Port* current = NULL; - - /* find the matching hostrules */ - lua_getfield(L, LUA_REGISTRYINDEX, HOSTTESTS); - lua_pushnil(L); - while (lua_next(L, -2) != 0) - { - // Hostrule function & file closure on stack - lua_pushvalue(L, -2); // hostrule function (key) - lua_newtable(L); - set_hostinfo(L, target); // hostrule argument - SCRIPT_ENGINE_LUA_TRY(lua_pcall(L, 1, 1, 0)); - - if (lua_isboolean(L, -1) && lua_toboolean(L, -1)) - { - struct thread_record tr; - tr.rr.type = 0; - tr.rr.port = NULL; - tr.rr.host = target; - - SCRIPT_ENGINE_TRY(process_preparethread(L, &tr)); - - torun_threads.push_back(tr); - } - lua_pop(L, 2); // boolean and file closure - } - - /* find the matching port rules */ - lua_getfield(L, LUA_REGISTRYINDEX, PORTTESTS); - - /* because of the port iteration API we need to awkwardly iterate - * over the kinds of ports we're interested in explictely. */ - current = NULL; - while((current = plist->nextPort(current, TCPANDUDP, PORT_OPEN)) != NULL) { - SCRIPT_ENGINE_TRY(process_pickScriptsForPort(L, target, current, torun_threads)); - } - - while((current = plist->nextPort(current, TCPANDUDP, PORT_OPENFILTERED)) != NULL) { - SCRIPT_ENGINE_TRY(process_pickScriptsForPort(L, target, current, torun_threads)); - } - - while((current = plist->nextPort(current, TCPANDUDP, PORT_UNFILTERED)) != NULL) { - SCRIPT_ENGINE_TRY(process_pickScriptsForPort(L, target, current, torun_threads)); - } - - lua_pop(L, 2); // Hostrules, Portrules - - return SCRIPT_ENGINE_SUCCESS; -} - -int process_preparerunlevels(std::list torun_threads) { - std::list current_runlevel; - std::list::iterator runlevel_iter; - double runlevel_idx = 0.0; - - torun_threads.sort(CompareRunlevels()); - - for( runlevel_iter = torun_threads.begin(); - runlevel_iter != torun_threads.end(); - runlevel_iter++) { - - if(runlevel_idx < (*runlevel_iter).runlevel) { - runlevel_idx = (*runlevel_iter).runlevel; - current_runlevel.clear(); - //push_back an empty list in which we store all scripts of the - //current runlevel... - torun_scripts.push_back(current_runlevel); - } - - torun_scripts.back().push_back(*runlevel_iter); - } - - return SCRIPT_ENGINE_SUCCESS; -} - -/* Because we can't iterate over all ports of interest in one go - * we need to do port matching in a separate function (unlike host - * rule matching) - * Note that we assume that at -1 on the stack we can find the portrules - * */ -int process_pickScriptsForPort(lua_State* L, Target* target, Port* port, std::list& torun_threads) { - lua_pushnil(L); - while (lua_next(L, -2) != 0) - { - // Portrule function & file closure on stack - lua_pushvalue(L, -2); // portrule function (key) - lua_newtable(L); - set_hostinfo(L, target); // portrule argument 1 - lua_newtable(L); - set_portinfo(L, port); // portrule argument 2 - SCRIPT_ENGINE_LUA_TRY(lua_pcall(L, 2, 1, 0)); - - if (lua_isboolean(L, -1) && lua_toboolean(L, -1)) - { - struct thread_record tr; - tr.rr.type = 1; - tr.rr.port = port; - tr.rr.host = target; - - SCRIPT_ENGINE_TRY(process_preparethread(L, &tr)); - - torun_threads.push_back(tr); - } - lua_pop(L, 2); // boolean and file closure - } - return SCRIPT_ENGINE_SUCCESS; -} - -/* Create a new lua thread and prepare it for execution we store target info in - * the thread so that the mainloop knows where to put the script result. File - * closure is expected at stack index -2. (Index -1 has the boolean result of - * the hostrule or portrule, not used here.) - * - * The thread will have on its stack 3 values if it is a host script, or 4 if it - * is a port script: - * 1: environment table - * 2: action closure - * 3: host table - * 4: port table (port scripts only) - * */ -int process_preparethread(lua_State* L, struct thread_record *tr){ - - lua_State *thread = lua_newthread(L); - tr->registry_idx = luaL_ref(L, LUA_REGISTRYINDEX); // store thread - tr->thread = thread; - - lua_pushvalue(L, -2); // File closure - lua_getfenv(L, -1); // get script file environment - lua_getfield(L, -1, FILENAME); // get its filename - tr->filename = lua_tostring(L, -1); // cache it in the thread structure - - lua_createtable(L, 0, 11); // new environment - lua_pushvalue(L, -2); // script filename - lua_setfield(L, -2, FILENAME); - lua_pushnumber(L, 1.0); // set a default RUNLEVEL - lua_setfield(L, -2, RUNLEVEL); - lua_createtable(L, 0, 1); // metatable for env - lua_pushvalue(L, LUA_GLOBALSINDEX); - lua_setfield(L, -2, "__index"); // global access - lua_setmetatable(L, -2); - - lua_pushvalue(L, -4); // script file closure - lua_pushvalue(L, -2); // script env - lua_setfenv(L, -2); - SCRIPT_ENGINE_LUA_TRY( - lua_pcall(L, 0, 0, 0) // file closure loads globals (action, id, etc.) - ); - - lua_getfield(L, -1, RUNLEVEL); - tr->runlevel = lua_tonumber(L, -1); - lua_pop(L, 1); - - // move the environment table and script action closure into the thread - lua_getfield(L, -1, ACTION); // action closure - lua_xmove(L, thread, 2); // env, action - lua_pop(L, 1); // filename - lua_setfenv(L, -2); // reset old env - lua_pop(L, 1); // file closure - - // make the info table - lua_newtable(thread); - set_hostinfo(thread, tr->rr.host); - - /* if this is a host rule we don't have a port state */ - if(tr->rr.port != NULL) { - lua_newtable(thread); - set_portinfo(thread, tr->rr.port); - tr->resume_arguments = 2; - } else - tr->resume_arguments = 1; - - return SCRIPT_ENGINE_SUCCESS; +void close_nse (void) +{ + if (L_NSE != NULL) + lua_close(L_NSE); } diff --git a/nse_main.h b/nse_main.h index 11387026c..1517704fe 100644 --- a/nse_main.h +++ b/nse_main.h @@ -9,6 +9,7 @@ extern "C" { #include "lua.h" + #include "lualib.h" #include "lauxlib.h" } @@ -27,17 +28,14 @@ class ScriptResult typedef std::vector ScriptResults; class Target; -int script_scan(std::vector &targets); + int script_updatedb(); void script_scan_free(); -//parses the arguments provided to scripts via nmap's --script-args option -int script_check_args(); - -int process_waiting2running(lua_State *, int); - -/* Useful auxiliary functions */ -size_t table_length(lua_State *, int); +void nse_restore (lua_State *, int); +int open_nse (void); +int script_scan(std::vector &targets); +void close_nse (void); #endif diff --git a/nse_main.lua b/nse_main.lua new file mode 100644 index 000000000..20b17ba60 --- /dev/null +++ b/nse_main.lua @@ -0,0 +1,525 @@ +-- Arguments when this file (function) is called, accessible via ... +-- [1] The NSE C library. This is saved in the local variable cnse for +-- access throughout the file. +-- [2] The list of categories/files/directories passed via --script. +-- The actual arguments passed to the anonymous main function: +-- [1] The list of hosts we run against. +-- +-- A few notes about the safety of the engine, that is, the ability for +-- a script developer to crash or otherwise stall NSE. The purpose of noting +-- these attack vectors is more to show the difficulty in accidently +-- breaking the system than to indicate a user may wish to break the +-- system through these means. +-- - A script writer can use the undocumented Lua function newproxy +-- to inject __gc code that could run (and error) at any location. +-- - A script writer can use the debug library to break out of +-- the "sandbox" we give it. This is made a little more difficult by +-- our use of locals to all Lua functions we use and the exclusion +-- of the main thread and subsequent user threads. +-- - A simple while true do end loop can stall the system. This can be +-- avoided by debug hooks to yield the thread at periodic intervals +-- (and perhaps kill the thread) but a C function like string.find and +-- a malicious pattern can stall the system from C just as easily. +-- - The garbage collector function is available to users and they may +-- cause the system to stall through improper use. +-- - Of course the os and io library can cause the system to also break. + +local NAME = "NSE"; + +local _R = debug.getregistry(); -- The registry +local _G = _G; + +local assert = assert; +local collectgarbage = collectgarbage; +local error = error; +local getfenv = getfenv; +local ipairs = ipairs; +local loadfile = loadfile; +local loadstring = loadstring; +local next = next; +local pairs = pairs; +local pcall = pcall; +local rawget = rawget; +local select = select; +local setfenv = setfenv; +local setmetatable = setmetatable; +local tonumber = tonumber; +local tostring = tostring; +local type = type; +local unpack = unpack; + +local create = coroutine.create; +local resume = coroutine.resume; +local running = coroutine.running; +local status = coroutine.status; +local yield = coroutine.yield; + +local traceback = debug.traceback; + +local write = io.write; + +local ceil = math.ceil; + +local byte = string.byte; +local format = string.format; +local find = string.find; +local gsub = string.gsub; +local lower = string.lower; +local match = string.match; + +local insert = table.insert; +local remove = table.remove; +local sort = table.sort; + +local nmap = require "nmap"; + +local cnse, rules = ...; -- The NSE C library and Script Rules + +do -- Append the nselib directory to the Lua search path + local t, path = assert(cnse.fetchfile_absolute("nselib/")); + assert(t == "directory", "could not locate nselib directory!"); + package.path = package.path..";"..path.."?.lua"; +end + +-- Some local helper functions -- + +local log_write, verbosity, debugging = + nmap.log_write, nmap.verbosity, nmap.debugging; + +local function print_verbose (level, fmt, ...) + if verbosity() >= assert(tonumber(level)) or debugging() > 0 then + log_write("stdout", format(fmt, ...)); + end +end + +local function print_debug (level, fmt, ...) + if debugging() >= assert(tonumber(level)) then + log_write("stdout", format(fmt, ...)); + end +end + +local function log_error (fmt, ...) + log_write("stderr", format(fmt, ...)); +end + +local function table_size (t) + local n = 0; for _ in pairs(t) do n = n + 1; end return n; +end + +-- recursively copy a table, for host/port tables +-- not very rigorous, but it doesn't need to be +local function tcopy (t) + local tc = {}; + for k,v in pairs(t) do + if type(v) == "table" then + tc[k] = tcopy(v); + else + tc[k] = v; + end + end + return tc; +end + +local Script = {}; -- The Script Class, its constructor is Script.new. +local Thread = {}; -- The Thread Class, its constructor is Script:new_thread. +do + -- Thread:d() + -- Outputs debug information at level 1 or higher. + -- Changes "%THREAD" with an appropriate identifier for the debug level + function Thread:d (fmt, ...) + if debugging() > 1 then + print_debug(1, gsub(fmt, "%%THREAD", self.info), ...); + else + print_debug(1, gsub(fmt, "%%THREAD", self.short_basename), ...); + end + end + + -- thread = Script:new_thread(rule, ...) + -- Creates a new thread for the script Script. + -- Arguments: + -- rule The rule argument the rule, hostrule or portrule, tested. + -- ... The arguments passed to the rule function (host[, port]). + -- Returns: + -- thread The thread (class) is returned, or nil. + function Script:new_thread (rule, ...) + assert(rule == "hostrule" or rule == "portrule"); + 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); + local function main (...) + file_closure(); -- loads script globals + return env.action(yield(env[rule](...))); + end + setfenv(main, env); + -- This thread allows us to load the script's globals in the + -- same Lua thread the action and rule functions will execute in. + local co = create(main); + local s, rule_return = resume(co, ...); + if s and rule_return then + local thread = setmetatable({ + co = co, + env = env, + runlevel = ceil(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", + }, { + __metatable = Thread, + __index = function (thread, k) return Thread[k] or self[k] end + }); -- Access to the parent Script + thread.parent = thread; -- itself + return thread; + elseif not s then + print_debug(1, "a thread for %s failed to load:\n%s\n", self.filename, + traceback(co, tostring(rule_return))); + end + return nil; + end + + local required_fields = { + description = "string", + action = "function", + categories = "table", + }; + -- script = Script.new(filename) + -- Creates a new Script Class for the script. + -- Arguments: + -- filename The filename (path) of the script to load. + -- Returns: + -- script The script (class) created. + function Script.new (filename) + assert(type(filename) == "string", "string expected"); + if not find(filename, "%.nse$") then + log_error("Warning: Loading '"..filename.. + "' - the recommended file extension is '.nse'."); + end + local file_closure = assert(loadfile(filename)); + -- Give the closure its own environment, with global access + local env = setmetatable({}, {__index = _G}); + setfenv(file_closure, env); + local co = create(file_closure); -- Create a garbage thread + assert(resume(co)); -- Get the globals it loads in env + -- Check that all the required fields were set + for f, t in pairs(required_fields) do + local field = rawget(env, f); + if field == nil then + error(filename.." is missing required field: '"..f.."'"); + elseif type(field) ~= t then + error(filename.." field '"..f.."' is of improper type '".. + type(field).."', expected type '"..t.."'"); + end + end + -- Check one of two required rule functions exists + local hostrule, portrule = rawget(env, "hostrule"), rawget(env, "portrule"); + assert(type(hostrule) == "function" or type(portrule) == "function", + filename.." is missing a required function: 'hostrule' or 'portrule'"); + -- Assert that categories is an array of strings + for i, category in ipairs(rawget(env, "categories")) do + assert(type(category) == "string", + filename.." has non-string entries in the 'categories' array"); + end + -- Return the script + return setmetatable({ + filename = filename, + basename = match(filename, "[/\\]([^/\\]-)$") or filename, + short_basename = match(filename, "[/\\]([^/\\]-)%.nse$") or + match(filename, "[/\\]([^/\\]-)%.[^.]*$") or + filename, + id = match(filename, "^.-[/\\]([^\\/]-)%.nse$") or filename, + file_closure = file_closure, + hostrule = type(hostrule) == "function" and hostrule or nil, + portrule = type(portrule) == "function" and portrule or nil, + args = {n = 0}; + categories = rawget(env, "categories"), + author = rawget(env, "author"), + license = rawget(env, "license"), + runlevel = ceil(tonumber(rawget(env, "runlevel")) or 1), + threads = {}, + }, {__index = Script, __metatable = Script}); + end +end + +-- check_rules(rules) +-- Ensures reserved rules are not explicitly specified. +-- Adds the "default" category if no rules were specified. +-- Adds reserved rules that were internally specified (--sV for "version"). +-- +-- Arguments: +-- rules The array of rules to check. +local function check_rules (rules) + local reserved = { + version = not not cnse.scriptversion, + }; + for i, rule in ipairs(rules) do + if reserved[lower(rule)] ~= nil then + error("explicitly specifying rule '"..rule.."' is prohibited"); + end + end + if cnse.default and #rules == 0 then rules[1] = "default"; end + for rule, option in pairs(reserved) do + if option then rules[#rules+1] = rule; end + end +end + +-- chosen_scripts = get_chosen_scripts(rules) +-- Loads all the scripts for the given rules using the Script Database. +-- Arguments: +-- rules The array of rules to use for loading scripts. +-- Returns: +-- chosen_scripts The array of scripts loaded for the given rules. +local function get_chosen_scripts (rules) + check_rules(rules); + + local script_dbpath = cnse.script_dbpath; + local t, path = cnse.fetchfile_absolute(script_dbpath); + if not t then + print_verbose(1, "Creating non-existent script database."); + assert(cnse.updatedb(), "could not update script database!"); + t, path = assert(cnse.fetchfile_absolute(script_dbpath)); + end + local db_closure = assert(loadfile(path)); + + local chosen_scripts, entry_rules, files_loaded = {}, {}, {}; + + -- Initialize entry_rules with the list of rules provided by the user. + -- Each element of entry_rules may refer to another canonical element. + -- Here the lower-case rule points to the potentially mixed-case rule + -- provided by the user. + for i, rule in ipairs(rules) do + entry_rules[lower(rule)] = rule; + entry_rules[rule] = false; + end + + -- Start by loading scripts by category. This function is run on each + -- Entry in script.db. + local function entry (script_entry) + local category, filename = script_entry.category, script_entry.filename; + assert(type(category) == "string" and type(filename) == "string"); + + -- Don't load a file more than once. + if files_loaded[filename] then return end + + -- Do we have a rule for this category (or an "all" rule)? + if entry_rules[category] ~= nil or + entry_rules.all ~= nil and category ~= "version" then + local index = entry_rules[category] ~= nil and category or "all"; + local mark = entry_rules[index]; + -- mark may point to the actual mixed case category passed via command + -- line + if type(mark) == "boolean" then + entry_rules[index] = true; + else + entry_rules[mark] = true; + end + local t, path = cnse.fetchfile_absolute(filename); + assert(t == "file", filename.." is not a file!"); + chosen_scripts[#chosen_scripts+1] = Script.new(path); + files_loaded[filename] = true; + end + end + + setfenv(db_closure, {Entry = entry}); + db_closure(); -- Load the scripts + + -- Now load any scripts listed by name rather than by category. + for rule, loaded in pairs(entry_rules) do + if not loaded then -- attempt to load the file/directory + local t, path = cnse.fetchfile_absolute(rule); + if t == nil then -- perhaps omitted the extension? + t, path = cnse.fetchfile_absolute(rule..".nse"); + end + if t == nil then + error("No such category, filename or directory: '"..rule.."'"); + elseif t == "file" and not files_loaded[path] then + chosen_scripts[#chosen_scripts+1] = Script.new(path); + files_loaded[path] = true; + elseif t == "directory" then + for i, file in ipairs(cnse.dump_dir(path)) do + if not files_loaded[file] then + chosen_scripts[#chosen_scripts+1] = Script.new(file); + files_loaded[file] = true; + end + end + end + end + end + return chosen_scripts; +end + +-- run(threads) +-- The main loop function for NSE. It handles running all the script threads. +-- Arguments: +-- threads An array of threads (a runlevel) to run. +local function run (threads) + -- running scripts may be resumed at any time. waiting scripts are + -- yielded until Nsock wakes them. After being awakened with + -- nse_restore, waiting threads become pending and later are moved all + -- at once back to running. + local running, waiting, pending = {}, {}, {} + -- hosts maps a host to a list of threads for that host. + local hosts, total = {}, 0 + local current + local progress = cnse.scan_progress_meter(NAME); + + print_debug(1, "NSE Script Threads (%d) running:", #threads); + while #threads > 0 do + local thread = remove(threads); + thread:d("Starting %THREAD against %s.", thread.host.ip) + running[thread.co], total = thread, total + 1; + hosts[thread.host] = hosts[thread.host] or {}; + hosts[thread.host][thread.co] = true; + end + + -- This WAITING_TO_RUNNING function is called by nse_restore in + -- nse_main.cc. + _R.WAITING_TO_RUNNING = function (co, ...) + if waiting[co] then -- ignore a thread not waiting + pending[co], waiting[co] = waiting[co], nil; + pending[co].args = {n = select("#", ...), ...}; + end + end + + -- Loop while any thread is running or waiting. + while next(running) or next(waiting) do + local nr, nw = table_size(running), table_size(waiting); + cnse.nsock_loop(50); -- Allow nsock to perform any pending callbacks + if cnse.key_was_pressed() then + print_verbose(1, "Active NSE Script Threads: %d (%d waiting)\n", + nr+nw, nw); + progress("printStats", 1-(nr+nw)/total); + elseif progress "mayBePrinted" then + if verbosity() > 1 or debugging() > 0 then + progress("printStats", 1-(nr+nw)/total); + else + progress("printStatsIfNecessary", 1-(nr+nw)/total); + end + end + + -- Checked for timed-out hosts. + for co, thread in pairs(waiting) do + if cnse.timedOut(thread.host) then + waiting[co] = nil; + thread:d("%THREAD target timed out"); + end + end + + for co, thread in pairs(running) do + current, running[co] = thread, nil; + cnse.startTimeOutClock(thread.host); + + local s, result = resume(co, unpack(thread.args, 1, thread.args.n)); + if not s then -- script error... + hosts[thread.host][co] = nil; + thread:d("%THREAD threw an error!\n%s\n", + traceback(co, tostring(result))); + elseif status(co) == "suspended" then + waiting[co] = thread; + elseif status(co) == "dead" then + hosts[thread.host][co] = nil; + if type(result) == "string" then + -- Escape any character outside the range 32-126 except for tab, + -- carriage return, and line feed. This makes the string safe for + -- screen display as well as XML (see section 2.2 of the XML spec). + result = gsub(result, "[^\t\r\n\032-\126]", function(a) + return format("\\x%02X", byte(a)); + end); + if thread.type == "host" then + cnse.host_set_output(thread.host, thread.id, result); + else + cnse.port_set_output(thread.host, thread.port, thread.id, result); + end + end + thread:d("Finished %THREAD against %s", thread.host.ip); + end + + -- Any more threads running for this host? + if not next(hosts[thread.host]) then + cnse.stopTimeOutClock(thread.host); + end + end + + -- Move pending threads back to running. + for co, thread in pairs(pending) do + pending[co], running[co] = nil, thread; + end + + collectgarbage "collect"; -- important for collecting used sockets & proxies + end + + progress "endTask"; +end + +do -- Load script arguments + local args = gsub((cnse.scriptargs or ""), "=([%w_]+)", "=\"%1\""); + local argsf, err = loadstring("return {"..args.."}", "Script Arguments"); + if not argsf then + error("failed to parse --script-args:\n"..args.."\n"..err); + else + nmap.registry.args = argsf(); + end +end + +-- Load all user chosen scripts +local chosen_scripts = get_chosen_scripts(rules); +print_verbose(1, "Loaded %d scripts for scanning.", #chosen_scripts); +for i, script in ipairs(chosen_scripts) do + print_debug(2, "Loaded '%s'.", script.basename); +end + +-- main(hosts) +-- This is the main function we return to NSE (on the C side) which actually +-- runs a scan against an array of hosts. nse_main.cc gets this function +-- by calling loadfile on nse_main.lua. +-- Arguments: +-- hosts An array of hosts to scan. +return function (hosts) + if #hosts > 1 then + print_verbose(1, "Script scanning %d hosts.", #hosts); + elseif #hosts == 1 then + print_verbose(1, "Script scanning %s.", hosts[1].ip); + end + + -- Set up the runlevels. + local threads, runlevels = {}, {}; + for j, host in ipairs(hosts) do + -- Check hostrules for this host. + for i, script in ipairs(chosen_scripts) do + local thread = script:new_thread("hostrule", tcopy(host)); + if thread then + local runlevel = thread.runlevel; + if threads[runlevel] == nil then insert(runlevels, runlevel); end + threads[runlevel] = threads[runlevel] or {}; + insert(threads[runlevel], thread); + thread.args, thread.host = {n = 1, tcopy(host)}, host; + end + end + -- Check portrules for this host. + for port in cnse.ports(host) do + for i, script in ipairs(chosen_scripts) do + local thread = script:new_thread("portrule", tcopy(host), + tcopy(port)); + if thread then + local runlevel = thread.runlevel; + if threads[runlevel] == nil then insert(runlevels, runlevel); end + threads[runlevel] = threads[runlevel] or {}; + insert(threads[runlevel], thread); + thread.args, thread.host, thread.port = + {n = 2, tcopy(host), tcopy(port)}, host, port; + end + end + end + end + + sort(runlevels); + for i, runlevel in ipairs(runlevels) do + print_verbose(1, "Starting runlevel %s scan", tostring(runlevel)); + run(threads[runlevel]); + end + + collectgarbage "collect"; + print_verbose(1, "Script Scanning completed."); +end diff --git a/nse_nmaplib.cc b/nse_nmaplib.cc index 144b64348..bb2693729 100644 --- a/nse_nmaplib.cc +++ b/nse_nmaplib.cc @@ -243,7 +243,10 @@ static int aux_mutex (lua_State *L) lua_call(L, 2, 1); lua_replace(L, lua_upvalueindex(2)); if (!lua_isnil(L, lua_upvalueindex(2))) // waiting threads had a thread - process_waiting2running(lua_tothread(L, lua_upvalueindex(2)), 0); + { + assert(lua_isthread(L, lua_upvalueindex(2))); + nse_restore(lua_tothread(L, lua_upvalueindex(2)), 0); + } return 0; case 2: // trylock if (lua_isnil(L, lua_upvalueindex(2))) @@ -281,7 +284,7 @@ static int l_mutex (lua_State *L) return 1; // aux_mutex closure } -static Target *get_target (lua_State *L, int index) +Target *get_target (lua_State *L, int index) { Target *target; luaL_checktype(L, index, LUA_TTABLE); @@ -298,7 +301,7 @@ static Target *get_target (lua_State *L, int index) return target; } -static Port *get_port (lua_State *L, Target *target, int index) +Port *get_port (lua_State *L, Target *target, int index) { Port *port = NULL; int portno, protocol; diff --git a/nse_nmaplib.h b/nse_nmaplib.h index cc83e217d..477287b59 100644 --- a/nse_nmaplib.h +++ b/nse_nmaplib.h @@ -8,6 +8,8 @@ int luaopen_nmap(lua_State* l); int luaopen_stdnse_c (lua_State *L); void set_hostinfo(lua_State* l, Target* currenths); void set_portinfo(lua_State* l, Port* port); +Target *get_target (lua_State *L, int index); +Port *get_port (lua_State *L, Target *target, int index); #endif diff --git a/nse_nsock.cc b/nse_nsock.cc index 9eb34283d..9f4a353e3 100644 --- a/nse_nsock.cc +++ b/nse_nsock.cc @@ -11,8 +11,8 @@ extern "C" { #include #include "nse_nsock.h" +#include "nse_main.h" #include "nse_macros.h" -#include "nse_debug.h" #include "nsock.h" #include "nmap_error.h" @@ -39,10 +39,6 @@ extern "C" { extern NmapOps o; -// defined in nse_main.cc but also declared here -// to keep the .h files clean -int process_waiting2running(lua_State *L, int resume_arguments); - static int l_nsock_connect(lua_State *L); static int l_nsock_send(lua_State *L); static int l_nsock_receive(lua_State *L); @@ -60,10 +56,10 @@ static int l_nsock_ncap_register(lua_State *L); static int l_nsock_pcap_receive(lua_State *L); -void l_nsock_connect_handler(nsock_pool nsp, nsock_event nse, void *lua_state); -void l_nsock_send_handler(nsock_pool nsp, nsock_event nse, void *lua_state); -void l_nsock_receive_handler(nsock_pool nsp, nsock_event nse, void *lua_state); -void l_nsock_receive_buf_handler(nsock_pool nsp, nsock_event nse, void *lua_state); +void l_nsock_connect_handler(nsock_pool nsp, nsock_event nse, void *yield); +void l_nsock_send_handler(nsock_pool nsp, nsock_event nse, void *yield); +void l_nsock_receive_handler(nsock_pool nsp, nsock_event nse, void *yield); +void l_nsock_receive_buf_handler(nsock_pool nsp, nsock_event nse, void *yield); int l_nsock_check_buf(lua_State *L); @@ -73,6 +69,24 @@ void l_nsock_trace(nsock_iod nsiod, const char* message, int direction); const char* inet_ntop_both(int af, const void* v_addr, char* ipstring); unsigned short inet_port_both(int af, const void* v_addr); +/* size_t table_length (lua_State *L, int index) + * + * Returns the length of the table at index index. + * This length is the number of elements, not just array elements. + */ +static size_t table_length (lua_State *L, int index) +{ + size_t len = 0; + lua_pushvalue(L, index); + lua_pushnil(L); + while (lua_next(L, -2) != 0) + { + len++; + lua_pop(L, 1); + } + lua_pop(L, 1); // table + return len; +} static std::string hexify (const char *str, size_t len) { @@ -142,7 +156,7 @@ static int proxy_gc (lua_State *L) if (lua_next(L, -2) != 0) { lua_State *thread = lua_tothread(L, -2); - process_waiting2running(thread, 0); + nse_restore(thread, 0); lua_pushnil(L); lua_replace(L, -2); // replace boolean lua_settable(L, -3); // remove thread from waiting @@ -167,7 +181,6 @@ static void new_proxy (lua_State *L) */ static int socket_lock (lua_State *L) { - luaL_checkudata(L, 1, "nsock"); lua_settop(L, 1); lua_rawgeti(L, LUA_ENVIRONINDEX, THREAD_PROXY); lua_pushthread(L); @@ -190,7 +203,7 @@ static int socket_lock (lua_State *L) lua_pushboolean(L, true); lua_settable(L, -3); lua_pop(L, 1); // CONNECT_WAITING - lua_pushboolean(L, false); + return lua_yield(L, 0); } else { // There is room for this thread to open sockets. Make a new proxy userdata @@ -240,13 +253,18 @@ struct ncap_socket{ * address this structure. */ }; +struct yield { + lua_State *thread; /* thread to resume */ + struct l_nsock_udata *udata; /* self reference */ +} yield; + /* * */ struct ncap_request{ int suspended; /* is the thread suspended? (lua_yield) */ - lua_State *L; /* lua_State of current process - * or NULL if process isn't suspended */ + //lua_State *L; /* lua_State of current process or NULL if process isn't suspended */ + struct yield *yield; nsock_event_id nseid; /* nse for this specific lua_State */ struct timeval end_time; char *key; /* (free) zero-terminated key used in map to @@ -269,14 +287,15 @@ struct ncap_request{ * we need to use this. */ }; - struct l_nsock_udata { int timeout; nsock_iod nsiod; void *ssl_session; + struct yield yield; /*used for buffered reading */ int bufidx; /*index inside lua's registry */ int bufused; + int rbuf_args[3]; /* indices in lua registry for receive_buf args */ struct ncap_socket *ncap_socket; struct ncap_request *ncap_request; @@ -295,11 +314,9 @@ int luaopen_nsock (lua_State *L) * connect function. */ static const char connect[] = - "local yield, connect, socket_lock = ...;\n" + "local connect, socket_lock = ...;\n" "return function(socket, ...)\n" - " while not socket_lock(socket) do\n" - " yield();\n" - " end\n" + " repeat until socket_lock(socket) == true;\n" " return connect(socket, ...);\n" "end"; @@ -354,12 +371,9 @@ int luaopen_nsock (lua_State *L) /* Load the connect function */ if (luaL_loadstring(L, connect) != 0) fatal("connect did not compile!"); - lua_getglobal(L, "coroutine"); - lua_getfield(L, -1, "yield"); - lua_replace(L, -2); // remove coroutine table lua_pushcclosure(L, l_nsock_connect, 0); lua_pushcclosure(L, socket_lock, 0); - lua_call(L, 3, 1); // leave connect function on stack... + lua_call(L, 2, 1); // leave connect function on stack... lua_pushvalue(L, LUA_GLOBALSINDEX); lua_setfenv(L, -2); // set the connect function's environment to _G @@ -395,6 +409,9 @@ int l_nsock_new(lua_State *L) { udata->timeout = DEFAULT_TIMEOUT; udata->bufidx = LUA_NOREF; udata->bufused= 0; + udata->rbuf_args[0] = LUA_NOREF; + udata->rbuf_args[1] = LUA_NOREF; + udata->rbuf_args[2] = LUA_NOREF; udata->ncap_socket = NULL; udata->ncap_request = NULL; udata->ncap_cback_ref = 0; @@ -473,18 +490,18 @@ static int l_nsock_connect(lua_State *L) { case 't': if (strcmp(how, "tcp")) goto error; nsock_connect_tcp(nsp, udata->nsiod, l_nsock_connect_handler, - udata->timeout, L, dest->ai_addr, dest->ai_addrlen, port); + udata->timeout, &udata->yield, dest->ai_addr, dest->ai_addrlen, port); break; case 'u': if (strcmp(how, "udp")) goto error; nsock_connect_udp(nsp, udata->nsiod, l_nsock_connect_handler, - L, dest->ai_addr, dest->ai_addrlen, port); + &udata->yield, dest->ai_addr, dest->ai_addrlen, port); break; case 's': if (strcmp(how, "ssl")) goto error; #ifdef HAVE_OPENSSL nsock_connect_ssl(nsp, udata->nsiod, l_nsock_connect_handler, - udata->timeout, L, dest->ai_addr, dest->ai_addrlen, port, + udata->timeout, &udata->yield, dest->ai_addr, dest->ai_addrlen, port, udata->ssl_session); break; #else @@ -498,6 +515,7 @@ static int l_nsock_connect(lua_State *L) { } freeaddrinfo(dest); + udata->yield.thread = L; return lua_yield(L, 0); error: @@ -506,17 +524,18 @@ error: return 0; } -void l_nsock_connect_handler(nsock_pool nsp, nsock_event nse, void *lua_state) { - lua_State *L = (lua_State*) lua_state; +void l_nsock_connect_handler(nsock_pool nsp, nsock_event nse, void *yield) { + struct yield *y = (struct yield *) yield; + lua_State *L = y->thread; if(o.scriptTrace()) { l_nsock_trace(nse_iod(nse), "CONNECT", TO); } if(l_nsock_checkstatus(L, nse) == NSOCK_WRAPPER_SUCCESS) { - process_waiting2running((lua_State*) lua_state, 1); + nse_restore(y->thread, 1); } else { - process_waiting2running((lua_State*) lua_state, 2); + nse_restore(y->thread, 2); } } @@ -536,17 +555,19 @@ static int l_nsock_send(lua_State *L) { if(o.scriptTrace()) l_nsock_trace(udata->nsiod, hexify(string, string_len).c_str(), TO); - nsock_write(nsp, udata->nsiod, l_nsock_send_handler, udata->timeout, L, string, string_len); + nsock_write(nsp, udata->nsiod, l_nsock_send_handler, udata->timeout, &udata->yield, string, string_len); + udata->yield.thread = L; return lua_yield(L, 0); } -void l_nsock_send_handler(nsock_pool nsp, nsock_event nse, void *lua_state) { - lua_State *L = (lua_State*) lua_state; +void l_nsock_send_handler(nsock_pool nsp, nsock_event nse, void *yield) { + struct yield *y = (struct yield *) yield; + lua_State *L = y->thread; if(l_nsock_checkstatus(L, nse) == NSOCK_WRAPPER_SUCCESS) { - process_waiting2running((lua_State*) lua_state, 1); + nse_restore(y->thread, 1); } else { - process_waiting2running((lua_State*) lua_state, 2); + nse_restore(y->thread, 2); } } @@ -560,8 +581,9 @@ static int l_nsock_receive(lua_State *L) { return 2; } - nsock_read(nsp, udata->nsiod, l_nsock_receive_handler, udata->timeout, L); + nsock_read(nsp, udata->nsiod, l_nsock_receive_handler, udata->timeout, &udata->yield); + udata->yield.thread = L; return lua_yield(L, 0); } @@ -577,8 +599,10 @@ static int l_nsock_receive_lines(lua_State *L) { return 2; } - nsock_readlines(nsp, udata->nsiod, l_nsock_receive_handler, udata->timeout, L, nlines); + nsock_readlines(nsp, udata->nsiod, l_nsock_receive_handler, udata->timeout, &udata->yield, nlines); + + udata->yield.thread = L; return lua_yield(L, 0); } @@ -594,13 +618,15 @@ static int l_nsock_receive_bytes(lua_State *L) { return 2; } - nsock_readbytes(nsp, udata->nsiod, l_nsock_receive_handler, udata->timeout, L, nbytes); + nsock_readbytes(nsp, udata->nsiod, l_nsock_receive_handler, udata->timeout, &udata->yield, nbytes); + udata->yield.thread = L; return lua_yield(L, 0); } -void l_nsock_receive_handler(nsock_pool nsp, nsock_event nse, void *lua_state) { - lua_State *L = (lua_State*) lua_state; +void l_nsock_receive_handler(nsock_pool nsp, nsock_event nse, void *yield) { + struct yield *y = (struct yield *) yield; + lua_State *L = y->thread; char* rcvd_string; int rcvd_len = 0; @@ -611,9 +637,9 @@ void l_nsock_receive_handler(nsock_pool nsp, nsock_event nse, void *lua_state) { l_nsock_trace(nse_iod(nse), hexify(rcvd_string, (size_t) rcvd_len).c_str(), FROM); lua_pushlstring(L, rcvd_string, rcvd_len); - process_waiting2running((lua_State*) lua_state, 2); + nse_restore(y->thread, 2); } else { - process_waiting2running((lua_State*) lua_state, 2); + nse_restore(y->thread, 2); } } @@ -726,6 +752,8 @@ static int l_nsock_gc(lua_State *L){ return 0; }else{ //FIXME - check wheter close returned true!! + for (int i = 0; i < 3; i++) + luaL_unref(L, LUA_REGISTRYINDEX, udata->rbuf_args[i]); l_nsock_close(L); } return 0; @@ -779,9 +807,11 @@ static int l_nsock_set_timeout(lua_State *L) { /* buffered I/O */ static int l_nsock_receive_buf(lua_State *L) { l_nsock_udata* udata = (l_nsock_udata*) luaL_checkudata(L, 1, "nsock"); - if(lua_gettop(L)==2){ - /*we were called with 2 arguments only - push the default third one*/ - lua_pushboolean(L,true); + lua_settop(L, 3); + udata->yield.udata = udata; + for (int i = 0; i < 3; i++) { /* compatibility, clean up someday */ + lua_pushvalue(L, i+1); /* argument 1-3 */ + udata->rbuf_args[i] = luaL_ref(L, LUA_REGISTRYINDEX); } if(udata->nsiod == NULL) { lua_pushboolean(L, false); @@ -792,7 +822,7 @@ static int l_nsock_receive_buf(lua_State *L) { lua_pushstring(L,""); udata->bufidx = luaL_ref(L, LUA_REGISTRYINDEX); udata->bufused=1; - nsock_read(nsp, udata->nsiod, l_nsock_receive_buf_handler, udata->timeout, L); + nsock_read(nsp, udata->nsiod, l_nsock_receive_buf_handler, udata->timeout, &udata->yield); }else if(udata->bufused==-1){ /*error message is inside the buffer*/ lua_pushboolean(L,false); lua_rawgeti(L, LUA_REGISTRYINDEX, udata->bufidx); @@ -804,20 +834,29 @@ static int l_nsock_receive_buf(lua_State *L) { /*if we didn't have enough data in the buffer another nsock_read() * was scheduled - its callback will put us in running state again */ - return lua_yield(L,3); + udata->yield.thread = L; + return lua_yield(L, 0); } return 2; } /*yielding with 3 arguments since we need them when the callback arrives */ - return lua_yield(L, 3); + udata->yield.thread = L; + return lua_yield(L, 0); } -void l_nsock_receive_buf_handler(nsock_pool nsp, nsock_event nse, void *lua_state) { - lua_State *L = (lua_State*) lua_state; +void l_nsock_receive_buf_handler(nsock_pool nsp, nsock_event nse, void *yield) { + struct yield *y = (struct yield *) yield; + l_nsock_udata *udata = y->udata; + lua_State *L= y->thread; char* rcvd_string; int rcvd_len = 0; int tmpidx; - l_nsock_udata* udata = (l_nsock_udata*) luaL_checkudata(L, 1, "nsock"); + /* set stack values, this all needs fixing... */ + for (int i = 0; i < 3; i++) { + lua_rawgeti(L, LUA_REGISTRYINDEX, udata->rbuf_args[i]); + luaL_unref(L, LUA_REGISTRYINDEX, udata->rbuf_args[i]); + udata->rbuf_args[i] = LUA_NOREF; + } if(l_nsock_checkstatus(L, nse) == NSOCK_WRAPPER_SUCCESS) { //l_nsock_checkstatus pushes true on the stack in case of success @@ -841,7 +880,7 @@ void l_nsock_receive_buf_handler(nsock_pool nsp, nsock_event nse, void *lua_stat */ return; } - process_waiting2running((lua_State*) lua_state, 2); + nse_restore(y->thread, 2); } else { if(udata->bufused>1){ /*error occured after we read into some data into the buffer @@ -857,9 +896,9 @@ void l_nsock_receive_buf_handler(nsock_pool nsp, nsock_event nse, void *lua_stat l_nsock_clear_buf(L, udata); udata->bufidx=tmpidx; udata->bufused=-1; - process_waiting2running((lua_State*) lua_state, 2); + nse_restore(y->thread, 2); }else{ /*buffer should be empty */ - process_waiting2running((lua_State*) lua_state, 2); + nse_restore(y->thread, 2); } } } @@ -903,7 +942,7 @@ int l_nsock_check_buf(lua_State *L ){ /*the stack contains on top the indices where we want to seperate */ if(lua_isnil(L,-1)){ /*not found anything try to read more data*/ lua_pop(L,2); - nsock_read(nsp, udata->nsiod, l_nsock_receive_buf_handler, udata->timeout, L); + nsock_read(nsp, udata->nsiod, l_nsock_receive_buf_handler, udata->timeout, &udata->yield); lua_pushboolean(L,keeppattern); return NSOCK_WRAPPER_BUFFER_MOREREAD; }else{ @@ -955,7 +994,7 @@ void l_nsock_clear_buf(lua_State *L, l_nsock_udata* udata){ static void l_nsock_sleep_handler(nsock_pool nsp, nsock_event nse, void *udata) { lua_State *L = (lua_State*) udata; assert(nse_status(nse) == NSE_STATUS_SUCCESS); - process_waiting2running(L, 0); + nse_restore(L, 0); } int l_nsock_sleep(lua_State *L) { @@ -1196,7 +1235,7 @@ static int l_nsock_ncap_register(lua_State *L){ TIMEVAL_MSEC_ADD(nr->end_time, now, udata->timeout); nr->key = strdup(hex((char*)testdata, testdatasz)); - nr->L = L; + nr->yield = &udata->yield; nr->ncap_cback_ref = udata->ncap_cback_ref; /* always create new event. */ nr->nseid = nsock_pcap_read_packet(nsp, @@ -1238,6 +1277,7 @@ int l_nsock_pcap_receive(lua_State *L){ /* no data yet? suspend thread */ nr->suspended = 1; + udata->yield.thread = L; return lua_yield(L, 0); } @@ -1272,7 +1312,7 @@ void l_nsock_pcap_receive_handler(nsock_pool nsp, nsock_event nse, void *userdat switch(nse_status(nse)) { case NSE_STATUS_SUCCESS:{ - char *key = ncap_request_do_callback(nse, nr->L, nr->ncap_cback_ref); + char *key = ncap_request_do_callback(nse, nr->yield->thread, nr->ncap_cback_ref); /* processes threads that receive every packet */ this_event_restored += ncap_request_set_results(nse, ""); @@ -1383,7 +1423,7 @@ void ncap_request_set_result(nsock_event nse, struct ncap_request *nr) { /* if lua thread was suspended, restore it. If it wasn't, just return results * (push them on the stack and return) */ int ncap_restore_lua(ncap_request *nr){ - lua_State *L = nr->L; + lua_State *L = nr->yield->thread; if(nr->r_success){ lua_pushboolean(L, true); @@ -1397,7 +1437,7 @@ int ncap_restore_lua(ncap_request *nr){ lua_pushnil(L); } bool suspended = nr->suspended; - nr->L = NULL; + //nr->L = NULL; // FIXME ?? nr->ncap_cback_ref = 0; /* this ref is freed in different place (on udata->ncap_cback_ref) */ if(nr->key) free(nr->key); if(nr->r_status) free(nr->r_status); @@ -1407,9 +1447,10 @@ int ncap_restore_lua(ncap_request *nr){ free(nr); if(suspended) /* lua process is suspended */ - return process_waiting2running(L, 4); + nse_restore(L, 4); else /* not suspended, just pass output */ return 4; + return 0; }