1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-09 14:11:29 +00:00
Files
nmap/nse_main.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

755 lines
20 KiB
C++

#include "nse_main.h"
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
#include "nse_init.h"
#include "nse_nsock.h"
#include "nse_nmaplib.h"
#include "nse_debug.h"
#include "nse_macros.h"
#include "nse_string.h"
#include "nmap.h"
#include "nmap_error.h"
#include "portlist.h"
#include "nsock.h"
#include "NmapOps.h"
#include "timing.h"
#include "Target.h"
#include "nmap_tty.h"
extern NmapOps o;
struct run_record {
short type; // 0 - hostrule; 1 - portrule
unsigned int index; // index in the corresponding table
Port* port;
Target* host;
};
struct thread_record {
lua_State* thread;
int resume_arguments;
unsigned int registry_idx; // index in the main state registry
double runlevel;
run_record* rr;
};
int current_hosts = 0;
int errfunc = 0;
std::list<std::list<struct thread_record> > torun_scripts;
std::list<struct thread_record> running_scripts;
std::list<struct thread_record> waiting_scripts;
class CompareRunlevels {
public:
bool operator() (const struct thread_record& lhs, const struct thread_record& rhs) {
return lhs.runlevel < rhs.runlevel;
}
};
// prior execution
int process_preparerunlevels(std::list<struct thread_record> torun_threads);
int process_preparehost(lua_State* L, Target* target, std::list<struct thread_record>& torun_threads);
int process_preparethread(lua_State* L, struct run_record rr, struct thread_record* tr);
// helper functions
int process_getScriptId(lua_State* L, struct script_scan_result* ssr);
int process_pickScriptsForPort(
lua_State* L,
Target* target,
Port* port,
std::vector<run_record>& torun);
// 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 panic (lua_State *L)
{
const char *err = lua_tostring(L, 1);
fatal("Unprotected error in Lua:\n%s\n", err);
return 0;
}
int script_updatedb (void)
{
int status;
int ret;
lua_State *L;
SCRIPT_ENGINE_VERBOSE(
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;
}
lua_settop(L, 0); // safety, is 0 anyway
lua_rawgeti(L, LUA_REGISTRYINDEX, errfunc); // index 1
lua_pushcclosure(L, init_updatedb, 0);
status = lua_pcall(L, 0, 0, 1);
if(status != 0)
{
error("%s: error while updating Script Database:\n%s\n",
SCRIPT_ENGINE, lua_tostring(L, -1));
ret = SCRIPT_ENGINE_ERROR;
goto finishup;
}
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:
lua_close(L);
return ret;
}
/* open a lua instance
* open the lua standard libraries
* open all the scripts and prepare them for execution
* (export nmap bindings, add them to host/port rulesets etc.)
* apply all scripts on all hosts
* */
int script_scan(std::vector<Target*> &targets) {
int status;
std::vector<Target*>::iterator target_iter;
std::list<std::list<struct thread_record> >::iterator runlevel_iter;
std::list<struct thread_record>::iterator thr_iter;
std::list<struct thread_record> torun_threads;
std::vector<std::string>::iterator script_iter;
lua_State* L;
o.current_scantype = SCRIPT_SCAN;
SCRIPT_ENGINE_VERBOSE(
log_write(LOG_STDOUT, "%s: Initiating script scanning.\n", SCRIPT_ENGINE);
)
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)));
)
L = luaL_newstate();
if(L == NULL) {
error("%s: Failed luaL_newstate()", SCRIPT_ENGINE);
return SCRIPT_ENGINE_ERROR;
}
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));
status = SCRIPT_ENGINE_ERROR;
goto finishup;
}
//set the arguments - if provided
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));
status = SCRIPT_ENGINE_ERROR;
goto finishup;
}
lua_settop(L, 0); // safety, is 0 anyway
lua_rawgeti(L, LUA_REGISTRYINDEX, errfunc); // index 1
if (!lua_checkstack(L, o.chosenScripts.size() + 1))
{
error("%s: stack overflow at %s:%d", SCRIPT_ENGINE, __FILE__, __LINE__);
status = SCRIPT_ENGINE_ERROR;
goto finishup;
}
lua_pushcclosure(L, init_rules, 0);
for (script_iter = o.chosenScripts.begin();
script_iter != o.chosenScripts.end();
script_iter++)
lua_pushstring(L, script_iter->c_str());
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));
status = SCRIPT_ENGINE_ERROR;
goto finishup;
}
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, LUA_REGISTRYINDEX, current_hosts);
lua_pushstring(L, key.c_str());
lua_pushlightuserdata(L, (void *) *target_iter);
lua_settable(L, -3);
lua_pop(L, 1);
status = process_preparehost(L, *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);
if(status != SCRIPT_ENGINE_SUCCESS){
goto finishup;
}
}
finishup:
SCRIPT_ENGINE_DEBUGGING(
log_write(LOG_STDOUT, "%s: Script scanning completed.\n", SCRIPT_ENGINE);
)
lua_close(L);
torun_scripts.clear();
if(status != SCRIPT_ENGINE_SUCCESS) {
error("%s: Aborting script scan.", SCRIPT_ENGINE);
return SCRIPT_ENGINE_ERROR;
} else {
return SCRIPT_ENGINE_SUCCESS;
}
}
int process_mainloop(lua_State *L) {
int state;
int unfinished = running_scripts.size() + waiting_scripts.size();
struct script_scan_result ssr;
struct thread_record current;
ScanProgressMeter progress = ScanProgressMeter(SCRIPT_ENGINE);
double total = (double) unfinished;
double done = 0;
std::list<struct thread_record>::iterator iter;
struct timeval now;
// 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.printStatsIfNeccessary(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();
}
while(running_scripts.begin() != running_scripts.end()){
current = *(running_scripts.begin());
if (current.rr->host->timedOut(&now))
state = LUA_ERRRUN;
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(lua_isstring (current.thread, -1)) {
SCRIPT_ENGINE_TRY(process_getScriptId(current.thread, &ssr));
ssr.output = nse_printable
(lua_tostring(current.thread, -1), lua_objlen(current.thread, -1));
if(current.rr->type == 0) {
current.rr->host->scriptResults.push_back(ssr);
} else if(current.rr->type == 1) {
current.rr->port->scriptResults.push_back(ssr);
current.rr->host->ports.numscriptresults++;
}
lua_pop(current.thread, 2);
}
SCRIPT_ENGINE_TRY(process_finalize(L, current.registry_idx));
SCRIPT_ENGINE_TRY(lua_gc(L, LUA_GCCOLLECT, 0));
} else {
// this script returned because of an error
// print the failing reason if the verbose level is high enough
SCRIPT_ENGINE_DEBUGGING(
const char* errmsg = lua_tostring(current.thread, -1);
log_write(LOG_STDOUT, "%s: %s\n", SCRIPT_ENGINE, errmsg);
)
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<struct thread_record>::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<struct thread_record>::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));
waiting_scripts.erase(iter);
return SCRIPT_ENGINE_SUCCESS;
}
/* Tries to get the script id and store it in the script scan result structure
* if no 'id' field is found, the filename field is used which we set in the
* setup phase. If someone changed the filename field to a nonstring we complain
* */
int process_getScriptId(lua_State* L, struct script_scan_result *ssr) {
lua_getfield(L, -2, "id");
lua_getfield(L, -3, "filename");
if(lua_isstring(L, -2)) {
ssr->id = strdup(lua_tostring (L, -2));
} else if(lua_isstring(L, -1)) {
ssr->id = strdup(lua_tostring (L, -1));
} else {
error("%s: The script has no 'id' entry, the 'filename' entry was changed to:",
SCRIPT_ENGINE);
l_dumpValue(L, -1);
return SCRIPT_ENGINE_ERROR;
}
lua_pop(L, 2);
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<struct thread_record>& torun_threads) {
PortList* plist = &(target->ports);
Port* current = NULL;
size_t rules_count;
unsigned int i;
std::vector<run_record> torun;
std::vector<run_record>::iterator iter;
struct run_record rr;
/* find the matching hostrules
* */
lua_getglobal(L, HOSTTESTS);
rules_count = lua_objlen(L, -1);
for(i = 1; i <= rules_count; i++) {
lua_rawgeti(L, -1, i);
lua_getfield(L, -1, "hostrule");
lua_newtable(L);
set_hostinfo(L, target);
SCRIPT_ENGINE_LUA_TRY(lua_pcall(L, 1, 1, 0));
if(lua_isboolean (L, -1) && lua_toboolean(L, -1)) {
rr.type = 0;
rr.index = i;
rr.port = NULL;
rr.host = target;
torun.push_back(rr);
SCRIPT_ENGINE_DEBUGGING(
lua_getfield(L, -2, "filename");
log_write(LOG_STDOUT, "%s: Will run %s against %s\n",
SCRIPT_ENGINE,
lua_tostring(L, -1),
target->targetipstr());
lua_pop(L, 1);
)
}
lua_pop(L, 2);
}
/* find the matching port rules
* */
lua_getglobal(L, PORTTESTS);
/* we only publish hostinfo once per portrule */
lua_newtable(L);
set_hostinfo(L, target);
/* 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));
}
while((current = plist->nextPort(current, TCPANDUDP, PORT_OPENFILTERED)) != NULL) {
SCRIPT_ENGINE_TRY(process_pickScriptsForPort(L, target, current, torun));
}
while((current = plist->nextPort(current, TCPANDUDP, PORT_UNFILTERED)) != NULL) {
SCRIPT_ENGINE_TRY(process_pickScriptsForPort(L, target, current, torun));
}
// pop the hostinfo, we don't need it anymore
lua_pop(L, 1);
/* ok, let's setup threads for the scripts which said they'd like
* to run
* Remember:
* we have the hosttestset and the porttestset on the stack!
* */
struct thread_record tr;
for(iter = torun.begin(); iter != torun.end(); iter++) {
/* If it is a host rule, execute the action
* and append the output to the host output i
* If it is a port rule, append the output to
* the port and increase the number of scripts
* which produced output. We need that number
* to generate beautiful output later.
* */
switch((*iter).type) {
case 0: // this script runs against a host
lua_pushvalue(L, -2);
SCRIPT_ENGINE_TRY(process_preparethread(L, (*iter), &tr));
lua_pop(L, 1);
break;
case 1: // this script runs against a port
lua_pushvalue(L, -1);
SCRIPT_ENGINE_TRY(process_preparethread(L, (*iter), &tr));
lua_pop(L, 1);
break;
default:
fatal("%s: In: %s:%i This should never happen.",
SCRIPT_ENGINE, __FILE__, __LINE__);
}
torun_threads.push_back(tr);
}
lua_pop(L, 2);
torun.clear();
return SCRIPT_ENGINE_SUCCESS;
}
int process_preparerunlevels(std::list<struct thread_record> torun_threads) {
std::list<struct thread_record> current_runlevel;
std::list<struct thread_record>::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 -2 on the stack we can find the portrules
* and at -1 the hostinfo table
* */
int process_pickScriptsForPort(
lua_State* L,
Target* target,
Port* port,
std::vector<run_record>& torun) {
size_t rules_count = lua_objlen(L, -2);
struct run_record rr;
unsigned int i;
for(i = 1; i <= rules_count; i++) {
lua_rawgeti(L, -2, i);
lua_getfield(L, -1, PORTRULE);
lua_pushvalue(L, -3);
lua_newtable(L);
set_portinfo(L, port);
SCRIPT_ENGINE_LUA_TRY(lua_pcall(L, 2, 1, 0));
if(lua_isboolean (L, -1) && lua_toboolean(L, -1)) {
rr.type = 1;
rr.index = i;
rr.port = port;
rr.host = target;
torun.push_back(rr);
SCRIPT_ENGINE_DEBUGGING(
lua_getfield(L, -2, "filename");
log_write(LOG_STDOUT, "%s: Will run %s against %s:%d\n",
SCRIPT_ENGINE,
lua_tostring(L, -1),
target->targetipstr(),
port->portno);
lua_pop(L, 1);
)
} else if(!lua_isboolean (L, -1)) {
lua_getfield(L, -2, "filename");
error("%s: Rule in %s returned %s but boolean was expected.",
SCRIPT_ENGINE,
lua_tostring(L, -1),
lua_typename(L, lua_type(L, -2)));
return SCRIPT_ENGINE_LUA_ERROR;
}
lua_pop(L, 2);
}
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
* */
int process_preparethread(lua_State* L, struct run_record rr, struct thread_record* tr){
lua_State *thread = lua_newthread(L);
lua_rawgeti(L, -2, rr.index); // get the script closure
// move the script closure into the thread
lua_xmove(L, thread, 1);
// store the target of this thread in the thread
struct run_record *rr_thread = (struct run_record*) safe_malloc(sizeof(struct run_record));
rr_thread->type = rr.type;
rr_thread->index = rr.index;
rr_thread->host = rr.host;
rr_thread->port = rr.port;
lua_getfield(thread, -1, RUNLEVEL);
tr->runlevel = lua_tonumber(thread, -1);
lua_pop(thread, 1);
// prepare the thread for a resume by
// pushing the action method onto the stack
lua_getfield(thread, -1, ACTION);
// make the info table
lua_newtable(thread);
set_hostinfo(thread, rr.host);
tr->thread = thread;
tr->rr = rr_thread;
tr->resume_arguments = 1;
// we store the thread in the registry to prevent
// garbage collection +
tr->registry_idx = luaL_ref(L, LUA_REGISTRYINDEX);
/* if this is a host rule we don't have
* a port state
* */
if(rr.port != NULL) {
lua_newtable(thread);
set_portinfo(thread, rr.port);
tr->resume_arguments = 2;
}
return SCRIPT_ENGINE_SUCCESS;
}