1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-20 06:29:02 +00:00
Files
nmap/nse_init.cc
batrick d0bc640db8 Large recode of nse_init.cc
Now does most of it's work through Lua:

From Nmap-dev: "Many of the changes consist of changing how Nmap interfaces
with Lua that were sometimes awkward or inflexible. Most of the functions 
have been made to be callable directly by Lua which offers many technical
advantages: stack management is alleviated, errors are handled cleanly and
are more descriptive, and there is increased reusability."

Additionally:
   -- Moved all lua_State * symbols from "l" to "L". This is to maintain
      consistency with other Lua libraries (convention) and to make our macros portable.
   -- Moved file system manipulation over to nse_fs.cc (from nse_init.cc)
2008-05-31 02:39:27 +00:00

682 lines
20 KiB
C++

#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 "nbase.h"
#include "nmap.h"
#include "nmap_error.h"
#include "NmapOps.h"
#include "errno.h"
#include <algorithm>
extern NmapOps o;
extern int current_hosts;
extern int errfunc;
/* TODO: keep?
// Error function if a user script attempts to create a new global
static int global_error(lua_State *L)
{
lua_pushvalue(L, lua_upvalueindex(1));
lua_pushvalue(L, 2);
if (!lua_tostring(L, -1))
{
lua_pushliteral(L, "? (of type ");
lua_pushstring(L, lua_typename(L, lua_type(L, -2)));
lua_pushliteral(L, ")");
lua_concat(L, 3);
lua_replace(L, -2);
}
lua_pushvalue(L, lua_upvalueindex(2));
lua_concat(L, 3);
fprintf(stderr, "%s\n", lua_tostring(L, -1));
return lua_error(L);
} */
/* 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;
}
/* load an nmap-lua script
* create a new closure to store the script
* tell the closure where to find the standard
* lua libs and the nmap bindings
* we do some error checking to make sure that
* the script is well formed
* the script is then added to either the hostrules
* or the portrules
* */
/* int loadfile (lua_State *L)
*
* Arguments
* -- filename File to load
*
* This function loads a file as a new script.
* 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's environment (table) is saved in the global PORTTESTS
* or HOSTTESTS table.
*/
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
lua_createtable(L, 0, 11); // Environment for script
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);
// TODO: Allow scripts to modify globals?
/* finally we make sure nobody tampers with the global name space any more
* and prepare for runlevel sorting
*/
/* lua_getmetatable(L, -1);
lua_pushliteral(L, "Attempted to change the global '");
lua_pushliteral(L, "' in ");
lua_pushstring(L, filename);
lua_pushliteral(L, " - use nmap.registry if you really want to share "
"data between scripts.");
lua_concat(L, 3);
lua_pushcclosure(L, global_error, 2);
lua_setfield(L, -2, "__newindex");
lua_pop(L, 1); */
if (luaL_loadfile(L, filename) != 0) // load the file
luaL_error(L, "'%s' could not be loaded!", filename);
lua_pushvalue(L, -2); // push environment table
lua_setfenv(L, -2); // set it
lua_call(L, 0, 0); // Call the function (loads globals)
/* 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))
luaL_error(L, "No '%s' field in script '%s'.", required_fields[i],
filename);
lua_pop(L, 1);
}
/* store the initialized test in either
* the hosttests or the porttests
*/
lua_getfield(L, -1, 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))
{
lua_pop(L, 2); // pop port/host rules
lua_getglobal(L, PORTTESTS); // Get global PORTTESTS table
lua_pushvalue(L, -2); // script's environment
lua_rawseti(L, -2, lua_objlen(L, -2) + 1); // add it
lua_pop(L, 1); // pop the porttests table
}
else if (!lua_isnil(L, -1))
{
lua_pop(L, 2);
lua_getglobal(L, HOSTTESTS);
lua_pushvalue(L, -2);
lua_rawseti(L, -2, lua_objlen(L, -2) + 1);
lua_pop(L, 1); // pop the hosttests table
}
else
luaL_error(L, "No rules in script '%s'.", filename);
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)
* ./nselib-bin/ For C Path (.so files)
*/
static int init_setpath (lua_State *L)
{
char path[MAX_FILENAME_LEN], cpath[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);
if (nmap_fetchfile(cpath, MAX_FILENAME_LEN, SCRIPT_ENGINE_LIBEXEC_DIR) != 2)
luaL_error(L, "'%s' not a directory", SCRIPT_ENGINE_LIBEXEC_DIR);
lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED");
lua_getfield(L, -1, LUA_LOADLIBNAME); /* "package" */
lua_pushstring(L, cpath);
#ifdef WIN32
lua_pushliteral(L, "?.dll;");
#else
lua_pushliteral(L, "?.so;");
#endif
lua_getfield(L, -3, "cpath"); /* package.cpath */
lua_concat(L, 3);
lua_setfield(L, -2, "cpath");
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
};
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_pushboolean(L, 1);
lua_replace(L, -2);
}
lua_settable(L, -3);
}
lua_getfield(L, -1, "debug"); // _LOADED.debug
lua_getfield(L, -1, "traceback");
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);
luaL_getmetafield(L, 1, "__index"); // string library
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)
{
if (o.scriptargs == NULL)
return 0;
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;
}
/* 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
// 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
luaL_loadfile(L, file); // load file
lua_pushvalue(L, -2); // push environment
lua_setfenv(L, -2); // set it
lua_call(L, 0, 0);
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_pushnil(L);
while (lua_next(L, -2) != 0)
{
fprintf(scriptdb, "Entry{ category = \"%s\", filename = \"%s\" }\n",
lua_tostring(L, -1), filebase);
lua_pop(L, 1);
}
lua_pop(L, 2); // script environment and categories
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 {
char *category;
int option;
} extensional_category;
/* int pick_default_categories (lua_State *L)
*
* The function is passed all the scripts/directories/categories passed
* through --scripts argument. For each of these, we check if a reserved
* category (currently "version") has been chosen, and raise a fatal error
* if so. Finally the reserved categories are added. Basically, explicitly
* adding the reserved categories is illegal.
*/
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++)
if (lua_equal(L, j, -1))
{
fatal("%s: specifying the \"%s\" category explicitly is not allowed.",
SCRIPT_ENGINE, lua_tostring(L, -1));
}
lua_pop(L, 1);
}
}
else if (o.script == 1)
{
// default set of categories
lua_pushliteral(L, "default");
}
// 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 from the script.db file. It has two upvalues:
* [1] Categories The categories/files/directories passed via --script.
* [2] Files The Files currently loaded (initially an empty table).
* A table is passed from the script database with a category and filename.
* The function tests if either the script's (filename's) category was chosen
* by checking [1]. Or, it loads the file if [1] has the 'category' "all" and
* the script's category 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
lua_gettable(L, lua_upvalueindex(2)); // already loaded?
if (!lua_isnil(L, -1))
return 0;
lua_pushvalue(L, 2); // category
lua_gettable(L, lua_upvalueindex(1)); // check 1
lua_pushliteral(L, "version"); // check 2
lua_getfield(L, lua_upvalueindex(1), "all"); // check 3
// if category chosen OR category != "version" and [1].all exists
if ((not_all = (!lua_isnil(L, -3))) ||
(!(lua_isnil(L, -1) || lua_equal(L, 2, -2))))
{
if (not_all)
lua_pushvalue(L, 2);
else
lua_pushliteral(L, "all");
lua_pushboolean(L, 1); // set category to true
lua_settable(L, lua_upvalueindex(1));
if (nse_fetchfile(script_path, sizeof(script_path),
lua_tostring(L, 3)) != 1)
luaL_error(L, "%s: %s is not a file!", lua_tostring(L, 3));
lua_pushvalue(L, 3); // filename
lua_pushboolean(L, 1);
lua_settable(L, lua_upvalueindex(2)); // loaded
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 and puts them in a table.
* This table along with an empty one are used as upvalues to the
* Entry closure (see entry above). Finally, the ./scripts/script.db
* file is loaded and it's environment set to only include the Entry
* closure. The entry function will do the work to load all script files with
* chosen categories. After the script database is executed. Any remainining
* fields (files/directories and possibly unused categories) are left in the
* table to be handled later.
*/
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;
if (nmap_fetchfile(c_dbpath, sizeof(c_dbpath), dbpath) == 0)
{
lua_pushcclosure(L, init_updatedb, 0);
lua_call(L, 0, 0);
}
lua_createtable(L, 0, top); // categories table
for (i = 1; i <= top; i++)
{
lua_pushvalue(L, i); // category/files/directory
lua_pushboolean(L, 0); // false (not used)
lua_settable(L, -3);
}
luaL_loadfile(L, c_dbpath);
lua_createtable(L, 0, 1);
lua_pushliteral(L, "Entry");
lua_pushvalue(L, -4); // categories table
lua_newtable(L); // files loaded
lua_pushcclosure(L, entry, 2);
lua_settable(L, -3);
lua_setfenv(L, -2);
lua_call(L, 0, 0); // Let errors go through
lua_pushnil(L);
while (lua_next(L, -2) != 0)
{
if (lua_toboolean(L, -1)) // category was used?
{
lua_pushvalue(L, -2);
lua_pushnil(L);
lua_settable(L, -5); // remove the category
}
lua_pop(L, 1);
}
return 1; // unused tags (what's left in categories table)
}
/* int init_rules (lua_State *L)
*
* Arguments
* ... All the categories/scripts/directories passed via --script
*
* This function adds the PORTTESTS and HOSTTESTS globals 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_setglobal(L, PORTTESTS);
lua_newtable(L);
lua_setglobal(L, HOSTTESTS);
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(
int rules_count;
lua_getglobal(L, HOSTTESTS);
rules_count = lua_objlen(L, -1);
lua_getglobal(L, PORTTESTS);
rules_count += lua_objlen(L, -1);
lua_pop(L, 2);
log_write(LOG_STDOUT, "%s: Initialized %d rules\n", SCRIPT_ENGINE, rules_count);
)
return 0;
}