ntopng/src/LuaEngine.cpp
2026-03-15 10:50:50 +01:00

1760 lines
53 KiB
C++

/*
*
* (C) 2013-26 - ntop.org
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include "ntop_includes.h"
#ifndef _GETOPT_H
#define _GETOPT_H
#endif
#ifndef LIB_VERSION
#define LIB_VERSION "1.4.7"
#endif
struct keyval string_to_replace[MAX_NUM_HTTP_REPLACEMENTS] = {
{NULL, NULL}}; /* TODO remove */
extern luaL_Reg* ntop_interface_reg;
extern luaL_Reg* ntop_reg;
extern luaL_Reg* ntop_network_reg;
extern luaL_Reg* ntop_flow_reg;
extern luaL_Reg* ntop_host_reg;
#ifdef HAVE_NTOP_CLOUD
extern luaL_Reg* ntop_cloud_reg;
#endif
/* #define TRACE_VM_ENGINES */
/* ******************************* */
NtopngLuaContext* getUserdata(struct lua_State* vm) {
if (vm) {
NtopngLuaContext* userdata;
lua_getglobal(vm, "userdata");
userdata = (NtopngLuaContext*)lua_touserdata(vm, lua_gettop(vm));
lua_pop(vm, 1); // undo the push done by lua_getglobal
return (userdata);
} else
return (NULL);
}
/* ******************************* */
#ifdef DUMP_STACK
static void stackDump(lua_State* L) {
int i;
int top = lua_gettop(L);
for (i = 1; i <= top; i++) { /* repeat for each level */
int t = lua_type(L, i);
switch (t) {
case LUA_TSTRING: /* strings */
ntop->getTrace()->traceEvent(TRACE_NORMAL, "%u) %s", i,
lua_tostring(L, i));
break;
case LUA_TBOOLEAN: /* booleans */
ntop->getTrace()->traceEvent(TRACE_NORMAL, "%u) %s", i,
lua_toboolean(L, i) ? "true" : "false");
break;
case LUA_TNUMBER: /* numbers */
ntop->getTrace()->traceEvent(TRACE_NORMAL, "%u) %g", i,
lua_tonumber(L, i));
break;
default: /* other values */
ntop->getTrace()->traceEvent(TRACE_NORMAL, "%u) %s", i,
lua_typename(L, t));
break;
}
}
}
#endif
/* ******************************* */
static u_int32_t upper_power_of_two(u_int32_t n) {
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n++;
return n;
}
/* Custom memory allocator */
static void* l_alloc(void* ud, void* ptr, size_t old_size, size_t new_size) {
LuaEngine* le = (LuaEngine*)ud;
le->incMemUsed(new_size - old_size);
if (new_size == 0) {
#if 0
ntop->getTrace()->traceEvent(TRACE_NORMAL,
"[Lua free] old size: %d ptr %p / tot: %d",
old_size, ptr, le->getMemUsed());
#endif
free(ptr);
return (NULL);
} else {
if (new_size < 32)
new_size = 32;
else
new_size = upper_power_of_two(new_size);
#if 0
ntop->getTrace()->traceEvent(TRACE_NORMAL,
"[Lua realloc] new size: %d ptr %p / tot: %d",
new_size, ptr, le->getMemUsed());
#endif
return (realloc(ptr, new_size));
}
}
/* ******************************* */
LuaEngine::LuaEngine() {
std::bad_alloc bax;
// if(trace_new_delete) ntop->getTrace()->traceEvent(TRACE_NORMAL, "[new] %s",
// __FILE__);
ntop->incNumLuaVMs();
start_epoch = (u_int32_t)time(NULL);
loaded_script_path = NULL;
is_system_vm = false;
mem_used = 0;
L = lua_newstate(l_alloc, this);
if (!L) {
ntop->getTrace()->traceEvent(TRACE_ERROR,
"Unable to create a new Lua state.");
throw bax;
}
lua_context = new NtopngLuaContext;
if (!lua_context) {
ntop->getTrace()->traceEvent(
TRACE_ERROR, "Unable to create a context for the new Lua state.");
lua_close(L);
throw bax;
}
lua_pushlightuserdata(L, (void*)lua_context);
lua_setglobal(L, "userdata");
}
/* ******************************* */
LuaEngine::~LuaEngine() {
// if(trace_new_delete) ntop->getTrace()->traceEvent(TRACE_NORMAL, "[delete]
// %s", __FILE__);
if (L) {
lua_settop(L, 0);
#ifdef DUMP_STACK
stackDump(L);
#endif
if (lua_context) delete lua_context;
ntop->decNumLuaVMs();
#ifdef TRACE_VM_ENGINES
ntop->getTrace()->traceEvent(
TRACE_NORMAL,
"Terminated VM [# LuaVMs: %u][Duration: %u sec][Memory: %u][%s]",
ntop->getNumActiveLuaVMs(), (u_int32_t)time(NULL) - start_epoch + 1,
getMemUsed(), loaded_script_path ? loaded_script_path : "");
#endif
lua_close(L); /* Free memory */
}
if (loaded_script_path) free(loaded_script_path);
}
/* ****************************************** */
void LuaEngine::luaRegister(lua_State* L, const char* class_name,
luaL_Reg* class_methods) {
const luaL_Reg _meta[] = {{NULL, NULL}};
int lib_id, meta_id;
/* newclass = {} */
lua_createtable(L, 0, 0);
lib_id = lua_gettop(L);
/* metatable = {} */
luaL_newmetatable(L, class_name);
meta_id = lua_gettop(L);
luaL_setfuncs(L, _meta, 0);
/* metatable.__index = class_methods */
lua_newtable(L);
luaL_setfuncs(L, class_methods, 0);
lua_setfield(L, meta_id, "__index");
/* class.__metatable = metatable */
lua_setmetatable(L, lib_id);
/* _G["Foo"] = newclass */
lua_setglobal(L, class_name);
}
/* ******************************* */
int ntop_lua_return_value(lua_State* vm, const char* function_name, int val) {
bool show_warning;
switch (val) {
case CONST_LUA_OK:
show_warning = true;
break;
default:
show_warning = false; /* CONST_LUA_PARAM_ERROR and CONST_LUA_ERROR */
break;
}
if (lua_gettop(vm) == 0) {
if (show_warning) {
ntop->getTrace()->traceEvent(
TRACE_ERROR, "[INTERNAL ERROR] Invalid lua VM state returned by %s()",
function_name);
ntop->getTrace()->traceEvent(TRACE_ERROR,
"[INTERNAL ERROR] Please report it here "
"https://github.com/ntop/ntopng/issues");
}
lua_pushnil(
vm); /* Add dummy push to make sure the stack has a return value */
}
return (val);
}
/* ****************************************** */
static int ntop_lua_http_print(lua_State* vm) {
struct mg_connection* conn;
char* printtype;
int t;
conn = getLuaVMUserdata(vm, conn);
/* ntop->getTrace()->traceEvent(TRACE_DEBUG, "%s() called", __FUNCTION__); */
NtopngLuaContext* ctx = getLuaVMContext(vm);
const bool buffering = ctx && ctx->buffer_http_response;
/* Route output either into the response buffer (REST API gzip path)
* or directly to the socket (normal path). */
auto write_out = [&](const char* data, size_t len) {
if (buffering)
ctx->http_response_buffer.append(data, len);
else
mg_write(conn, data, len);
};
/* Handle binary blob */
if ((lua_type(vm, 2) == LUA_TSTRING) &&
(printtype = (char*)lua_tostring(vm, 2)) != NULL)
if (!strncmp(printtype, "blob", 4)) {
char* str = NULL;
if (ntop_lua_check(vm, __FUNCTION__, 1, LUA_TSTRING) != CONST_LUA_OK)
return (CONST_LUA_ERROR);
if ((str = (char*)lua_tostring(vm, 1)) != NULL) {
int len = strlen(str);
if (len <= 1)
write_out(str, 1);
else
return (CONST_LUA_PARAM_ERROR);
}
lua_pushnil(vm);
return (CONST_LUA_OK);
}
switch (t = lua_type(vm, 1)) {
case LUA_TNIL:
write_out("nil", 3);
break;
case LUA_TBOOLEAN: {
int v = lua_toboolean(vm, 1);
const char* bstr = v ? "true" : "false";
write_out(bstr, strlen(bstr));
} break;
case LUA_TSTRING: {
size_t len;
const char* str = lua_tolstring(vm, 1, &len);
if (str && len > 0) write_out(str, len);
} break;
case LUA_TNUMBER: {
char str[64];
int len = snprintf(str, sizeof(str), "%f", (float)lua_tonumber(vm, 1));
if (len > 0) write_out(str, (size_t)len);
} break;
case LUA_TTABLE: {
lua_pushnil(vm);
while (lua_next(vm, -2) != 0) {
const char* key = lua_tostring(vm, -2);
char buf[1024];
int len;
if (lua_isstring(vm, -1)) {
len =
snprintf(buf, sizeof(buf), "%s = %s", key, lua_tostring(vm, -1));
if (len > 0) write_out(buf, (size_t)len);
} else if (lua_isnumber(vm, -1)) {
len = snprintf(buf, sizeof(buf), "%s = %d", key,
(int)lua_tonumber(vm, -1));
if (len > 0) write_out(buf, (size_t)len);
} else if (lua_istable(vm, -1)) {
write_out(key, strlen(key));
// PrintTable(vm);
}
lua_pop(vm, 1);
}
} break;
default:
ntop->getTrace()->traceEvent(
TRACE_WARNING, "%s(): Lua type %d is not handled", __FUNCTION__, t);
return (CONST_LUA_ERROR);
}
lua_pushnil(vm);
return (CONST_LUA_OK);
}
/* ****************************************** */
int ntop_lua_cli_print(lua_State* vm) {
int t;
ntop->getTrace()->traceEvent(TRACE_DEBUG, "%s() called", __FUNCTION__);
switch (t = lua_type(vm, 1)) {
case LUA_TSTRING: {
char* str = (char*)lua_tostring(vm, 1);
if (str && (strlen(str) > 0))
ntop->getTrace()->traceEvent(TRACE_NORMAL, "%s", str);
} break;
case LUA_TNUMBER:
ntop->getTrace()->traceEvent(TRACE_NORMAL, "%f",
(float)lua_tonumber(vm, 1));
break;
default:
ntop->getTrace()->traceEvent(
TRACE_WARNING, "%s(): Lua type %d is not handled", __FUNCTION__, t);
return (CONST_LUA_ERROR);
}
lua_pushnil(vm);
return (CONST_LUA_OK);
}
/* ****************************************** */
int ntop_lua_cloud_print(lua_State* vm) {
int t;
LuaEngine* engine = getLuaVMUserdata(vm, engine);
ntop->getTrace()->traceEvent(TRACE_DEBUG, "%s() called", __FUNCTION__);
switch (t = lua_type(vm, 1)) {
case LUA_TSTRING: {
char* str = (char*)lua_tostring(vm, 1);
if (str && (strlen(str) > 0)) engine->pushResultString(str);
} break;
case LUA_TNUMBER:
engine->pushResultNumber((float)lua_tonumber(vm, 1));
break;
default:
ntop->getTrace()->traceEvent(
TRACE_WARNING, "%s(): Lua type %d is not handled", __FUNCTION__, t);
return (CONST_LUA_ERROR);
}
lua_pushnil(vm);
return (CONST_LUA_OK);
}
/* ****************************************** */
#if defined(NTOPNG_PRO) || defined(HAVE_NEDGE)
static int __ntop_lua_handlefile(lua_State* L, char* script_path, bool ex) {
int rc;
LuaHandler* lh = new LuaHandler(L, script_path);
#ifdef DEBUG_LUA_LOADING
ntop->getTrace()->traceEvent(TRACE_NORMAL, "Loading %s", script_path);
#endif
rc = lh->luaL_dofileM(ex);
delete lh;
return rc;
}
/* ****************************************** */
/* This function is called by Lua scripts when the call require(...) */
static int ntop_lua_require(lua_State* L) {
char* script_name;
LuaEngine* engine = getUserdata(L)->engine;
if (lua_type(L, 1) != LUA_TSTRING ||
(script_name = (char*)lua_tostring(L, 1)) == NULL)
return 0;
if (engine->require(std::string(script_name))) {
ntop->getTrace()->traceEvent(TRACE_NORMAL, "Circular dependency found %s\n",
script_name);
// return(0); /* Already loaded */
}
ntop->getTrace()->traceEvent(TRACE_DEBUG, "%s(%s)", __FUNCTION__,
script_name);
lua_getglobal(L, "package");
lua_getfield(L, -1, "path");
string cur_path = lua_tostring(L, -1), parsed, script_path = "";
stringstream input_stringstream(cur_path);
while (getline(input_stringstream, parsed, ';')) {
/* Example: package.path = dirs.installdir .. "/scripts/lua/modules/?.lua;"
* .. package.path */
unsigned found = parsed.find_last_of("?");
if (found) {
string s = parsed.substr(0, found) + script_name + ".lua";
size_t first_dot = s.find("."), last_dot = s.rfind(".");
/*
Lua transforms file names when directories are used.
Example: i18n/version.lua -> i18n.version.lua
So we need to revert this logic back and the code
below is doing exactly this
*/
if ((first_dot != string::npos) && (last_dot != string::npos) &&
(first_dot != last_dot))
s.replace(first_dot, 1, "/");
ntop->getTrace()->traceEvent(TRACE_DEBUG, "[%s] Searching %s",
__FUNCTION__, s.c_str());
if (Utils::file_exists(s.c_str())) {
script_path = s;
ntop->getTrace()->traceEvent(TRACE_DEBUG, "[%s] Found %s", __FUNCTION__,
s.c_str());
break;
}
}
}
if (script_path == "" ||
__ntop_lua_handlefile(L, (char*)script_path.c_str(), false)) {
if (lua_type(L, -1) == LUA_TSTRING) {
const char* err = lua_tostring(L, -1);
ntop->getTrace()->traceEvent(TRACE_WARNING,
"Script failure "
"[script: %s][path: %s][cur-path: %s]",
script_name, script_path.c_str(),
err ? err : "");
}
return 0;
}
return 1;
}
/* ****************************************** */
static int ntop_lua_xfile(lua_State* L, bool ex) {
char* script_path;
int ret;
if (lua_type(L, 1) != LUA_TSTRING ||
(script_path = (char*)lua_tostring(L, 1)) == NULL)
return 0;
ret = __ntop_lua_handlefile(L, script_path, ex);
if (ret && (!lua_isnil(L, -1))) {
const char* msg = lua_tostring(L, -1);
ntop->getTrace()->traceEvent(TRACE_WARNING, "Script failure %s", msg);
if (ntop->getRedis()) {
/* Push the alert into this queue, used by the alert */
ntop->getRedis()->lpush(ALERT_TRACE_ERRORS, msg, 50 /* No Trim */);
}
}
return !ret;
}
/* ****************************************** */
static int ntop_lua_dofile(lua_State* L) { return ntop_lua_xfile(L, true); }
/* ****************************************** */
static int ntop_lua_loadfile(lua_State* L) { return ntop_lua_xfile(L, false); }
#endif
/* ****************************************** */
void LuaEngine::lua_register_classes(lua_State* L, LuaEngineMode mode) {
if (!L) return;
getLuaVMContext(L)->engine = this;
/* ntop add-ons */
luaRegister(L, "interface", ntop_interface_reg);
luaRegister(L, "ntop", ntop_reg);
luaRegister(L, "network", ntop_network_reg);
luaRegister(L, "flow", ntop_flow_reg);
luaRegister(L, "host", ntop_host_reg);
#ifdef HAVE_NTOP_CLOUD
luaRegister(L, "cloud", ntop_cloud_reg);
#endif
switch (mode) {
case lua_engine_mode_http:
/* Overload the standard Lua print() with ntop_lua_http_print that dumps
* data on HTTP server */
lua_register(L, "print", ntop_lua_http_print);
break;
case lua_engine_mode_callback:
lua_register(L, "print", ntop_lua_cli_print);
break;
case lua_engine_mode_cloud:
lua_register(L, "print", ntop_lua_cloud_print);
break;
}
#if defined(NTOPNG_PRO) || defined(HAVE_NEDGE)
if (ntop->getPro()->has_valid_license()) {
lua_register(L, "ntopRequire", ntop_lua_require);
/* Lua 5.2.x uses package.loaders */
luaL_dostring(L, "package.loaders = { ntopRequire }");
/* Lua 5.3.x uses package.searchers */
luaL_dostring(L, "package.searchers = { ntopRequire }");
lua_register(L, "dofile", ntop_lua_dofile);
lua_register(L, "loadfile", ntop_lua_loadfile);
}
#endif
}
/* ****************************************** */
#if 0
/**
* Iterator over key-value pairs where the value
* maybe made available in increments and/or may
* not be zero-terminated. Used for processing
* POST data.
*
* @param cls user-specified closure
* @param kind type of the value
* @param key 0-terminated key for the value
* @param filename name of the uploaded file, NULL if not known
* @param content_type mime-type of the data, NULL if not known
* @param transfer_encoding encoding of the data, NULL if not known
* @param data pointer to size bytes of data at the
* specified offset
* @param off offset of data in the overall value
* @param size number of bytes in data available
* @return MHD_YES to continue iterating,
* MHD_NO to abort the iteration
*/
static int post_iterator(void *cls,
enum MHD_ValueKind kind,
const char *key,
const char *filename,
const char *content_type,
const char *transfer_encoding,
const char *data, uint64_t off, size_t size)
{
struct Request *request = cls;
char tmp[1024];
u_int len = min(size, sizeof(tmp)-1);
memcpy(tmp, &data[off], len);
tmp[len] = '\0';
fprintf(stdout, "[POST] [%s][%s]\n", key, tmp);
return MHD_YES;
}
#endif
/* ****************************************** */
/* Loads a script into the engine from within ntopng (no HTTP GUI). */
int LuaEngine::load_script(char* script_path, LuaEngineMode mode,
NetworkInterface* iface) {
int rc = 0;
bool initialized;
if (!L) return (-1);
if (loaded_script_path)
free(loaded_script_path), initialized = true;
else
initialized = false;
try {
if (!initialized) {
luaL_openlibs(L); /* Load base libraries */
lua_register_classes(L, mode); /* Load custom classes */
} else
lua_settop(L, lua_gettop(L)); /* Reset the stack */
if (iface) {
/* Select the specified interface */
getLuaVMUservalue(L, iface) = iface;
}
if (isSystemVM())
getLuaVMUservalue(L, capabilities) = (u_int64_t)-1; /* All set */
#ifdef NTOPNG_PRO
if (ntop->getPro()->has_valid_license())
rc = __ntop_lua_handlefile(L, script_path, false /* Do not execute */);
else
#endif
rc = luaL_loadfile(L, script_path);
if (rc != 0) {
const char* err = lua_tostring(L, -1);
ntop->getTrace()->traceEvent(TRACE_WARNING, "Script failure [%s][%s]",
script_path, err ? err : "");
rc = -1;
}
} catch (...) {
ntop->getTrace()->traceEvent(TRACE_WARNING, "Script failure [%s]",
script_path);
rc = -2;
}
loaded_script_path = strdup(script_path);
return (rc);
}
/* ****************************************** */
/*
Run a loaded Lua script (via LuaEngine::load_script) from within ntopng (no
HTTP GUI). The script is invoked without any additional parameters.
run_loaded_script can be called multiple times to run the same script again.
*/
int LuaEngine::run_loaded_script() {
int rv = 0;
ntop->getTrace()->traceEvent(TRACE_DEBUG, "Running %s", loaded_script_path);
if (!loaded_script_path) return (-1);
/* Copy the lua_chunk to be able to possibly run it again next time */
lua_pushvalue(L, -1);
if (isSystemVM())
getLuaVMUservalue(L, capabilities) = (u_int64_t)-1; /* All set */
/* Perform the actual call */
if (lua_pcall(L, 0,
LUA_MULTRET /* Allow the script to be called multiple times
(used with cusotm host/flow scrips) */
,
0) != 0) {
if (lua_type(L, -1) == LUA_TSTRING) {
const char* err = lua_tostring(L, -1);
if (err) {
ntop->getTrace()->traceEvent(TRACE_WARNING, "%s [%s]", err,
loaded_script_path);
ntop->getRedis()->lpush(ALERT_TRACE_ERRORS, err, 50 /* No Trim */);
}
}
rv = -2;
}
lua_pop(L, 1);
#ifndef HAS_LUAJIT
lua_gc(L, LUA_GCCOLLECT); /* Run garbage collector */
#endif
return (rv);
}
/* ****************************************** */
/* http://www.geekhideout.com/downloads/urlcode.c */
#if 0
/* Converts an integer value to its hex character*/
static char to_hex(char code) {
static char hex[] = "0123456789abcdef";
return hex[code & 15];
}
/* ****************************************** */
/* Returns a url-encoded version of str */
/* IMPORTANT: be sure to free() the returned string after use */
static char* http_encode(char *str) {
char *pstr = str, *buf = (char*)malloc(strlen(str) * 3 + 1), *pbuf = buf;
while (*pstr) {
if(isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~')
*pbuf++ = *pstr;
else if(*pstr == ' ')
*pbuf++ = '+';
else
*pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
pstr++;
}
*pbuf = '\0';
return buf;
}
#endif
/* ****************************************** */
#ifdef NOT_USED
void LuaEngine::purifyHTTPParameter(char* param) {
char* ampersand;
bool utf8_found = false;
if ((ampersand = strchr(param, '%')) != NULL) {
/* We allow only a few chars, removing all the others */
if ((ampersand[1] != 0) && (ampersand[2] != 0)) {
char c;
char b = ampersand[3];
ampersand[3] = '\0';
c = (char)strtol(&ampersand[1], NULL, 16);
ampersand[3] = b;
switch (c) {
case '/':
case ':':
case '(':
case ')':
case '{':
case '}':
case '[':
case ']':
case '?':
case '!':
case '$':
case ',':
case '^':
case '*':
case '_':
case '&':
case ' ':
case '=':
case '<':
case '>':
case '@':
case '#':
break;
default:
if (((u_char)c == 0xC3) && (ampersand[3] == '%')) {
/* Latin-1 within UTF-8 */
b = ampersand[6];
ampersand[6] = '\0';
c = (char)strtol(&ampersand[4], NULL, 16);
ampersand[6] = b;
/* Align to ASCII encoding */
c |= 0x40;
utf8_found = true;
}
if (!Utils::isPrintableChar(c)) {
ntop->getTrace()->traceEvent(
TRACE_WARNING, "Discarded char '0x%02x' in URI [%s]", c, param);
ampersand[0] = '\0';
return;
}
}
purifyHTTPParameter(utf8_found ? &ampersand[6] : &ampersand[3]);
} else
ampersand[0] = '\0';
}
}
#endif
/* ****************************************** */
bool LuaEngine::switchInterface(struct lua_State* vm, const char* ifid,
const char* observation_point_id,
const char* user, const char* group,
const char* session) {
NetworkInterface* iface = NULL;
char iface_key[64], ifname_key[64], obs_id_key[64];
char iface_id[16];
iface = ntop->getNetworkInterface(ifid, vm);
if (iface == NULL) return false;
if (user != NULL) {
if (!strlen(session) && strcmp(user, NTOP_NOLOGIN_USER)) return false;
/* Non-admins cannot switch to the system interface */
if (iface == ntop->getSystemInterface() &&
strcmp(user, NTOP_NOLOGIN_USER) &&
strncmp(group, CONST_USER_GROUP_ADMIN, strlen(CONST_USER_GROUP_ADMIN)))
return false;
snprintf(iface_key, sizeof(iface_key), NTOPNG_PREFS_PREFIX ".%s.iface",
user);
snprintf(ifname_key, sizeof(ifname_key), NTOPNG_PREFS_PREFIX ".%s.ifname",
user);
} else { // Login disabled
snprintf(iface_key, sizeof(iface_key), NTOPNG_PREFS_PREFIX ".iface");
snprintf(ifname_key, sizeof(ifname_key), NTOPNG_PREFS_PREFIX ".ifname");
}
snprintf(iface_id, sizeof(iface_id), "%d", iface->get_id());
ntop->getRedis()->set(iface_key, iface_id, 0);
ntop->getRedis()->set(ifname_key, iface->get_name(), 0);
ntop->getTrace()->traceEvent(
TRACE_DEBUG,
"Switch interface requested via POST [Interface: %s][user: %s]",
iface->get_name(), user);
getLuaVMUservalue(vm, iface) = iface;
/* Finally, also set the observation point */
if (observation_point_id[0]) {
u_int16_t obs_id = (u_int16_t)strtoll(observation_point_id, NULL, 10);
if (iface->hasObservationPointId(obs_id)) {
snprintf(obs_id_key, sizeof(obs_id_key),
NTOPNG_PREFS_PREFIX ".%s.observationPointId", user);
ntop->getRedis()->set(obs_id_key, observation_point_id);
}
}
return true;
}
/* ****************************************** */
void LuaEngine::setInterface(const char* user, char* const ifname,
u_int16_t ifname_len,
bool* const is_allowed) const {
NetworkInterface* iface = NULL;
char key[CONST_MAX_LEN_REDIS_KEY];
u_int16_t observationPointId = 0;
ifname[0] = '\0';
if ((user == NULL) || (user[0] == '\0')) user = NTOP_NOLOGIN_USER;
if (is_allowed) *is_allowed = false;
// check if the user is restricted to browse only a given interface
if (snprintf(key, sizeof(key), CONST_STR_USER_ALLOWED_IFNAME, user) &&
ntop->getRedis()->get(key, ifname, ifname_len) == 0 &&
ifname[0] != '\0') {
/* If here is only one allowed interface for the user.
The interface must exists otherwise we hould have prevented the login */
if (is_allowed) *is_allowed = true;
ntop->getTrace()->traceEvent(
TRACE_DEBUG, "Allowed interface found. [Interface: %s][user: %s]",
ifname, user);
} else if (snprintf(key, sizeof(key), NTOPNG_PREFS_PREFIX ".%s.ifname",
user) &&
(ntop->getRedis()->get(key, ifname, ifname_len) < 0 ||
(!ntop->isExistingInterface(ifname)))) {
/* No allowed interface and no default (or not existing) set interface */
snprintf(ifname, ifname_len, "%s", ntop->getFirstInterface()->get_name());
ntop->getRedis()->set(key, ifname, 3600 /* 1h */);
ntop->getTrace()->traceEvent(TRACE_DEBUG,
"No interface interface found. Using default. "
"[Interface: %s][user: %s]",
ifname, user);
}
if ((iface = ntop->getNetworkInterface(
ifname, NULL /* allowed user interface check already enforced */)) !=
NULL) {
/* The specified interface still exists */
lua_push_str_table_entry(L, "ifname", iface->get_name());
snprintf(ifname, ifname_len, "%s", iface->get_name());
ntop->getTrace()->traceEvent(TRACE_DEBUG,
"Interface found [Interface: %s][user: %s]",
iface->get_name(), user);
}
if (iface && iface->haveObservationPointsDefined()) {
char buf[16];
snprintf(key, sizeof(key), NTOPNG_PREFS_PREFIX ".%s.observationPointId",
user);
if (ntop->getRedis()->get(key, buf, sizeof(buf)) != -1) {
observationPointId = atoi(buf);
if (!iface->hasObservationPointId(observationPointId))
observationPointId =
iface->getFirstObservationPointId(); /* Not existing */
} else
observationPointId = iface->getFirstObservationPointId(); /* Not found */
}
/* Set the observationPointId in the VM */
getLuaVMUservalue(L, observationPointId) = observationPointId;
// ntop->getTrace()->traceEvent(TRACE_WARNING, "observationPointId: %u",
// observationPointId);
lua_push_uint32_table_entry(L, "observationPointId", observationPointId);
}
/* ****************************************** */
bool LuaEngine::setParamsTable(lua_State* vm,
const struct mg_request_info* request_info,
const char* table_name,
const char* query) const {
char* where;
char* tok;
char* query_string = query ? strdup(query) : NULL;
bool ret = false;
lua_newtable(vm);
if (query_string &&
strcmp(request_info->uri,
CAPTIVE_PORTAL_INFO_URL) /* Ignore informative portal */
) {
// ntop->getTrace()->traceEvent(TRACE_WARNING, "[HTTP] %s", query_string);
tok = strtok_r(query_string, "&", &where);
while (tok != NULL) {
char* _equal;
if (strncmp(tok, "csrf", strlen("csrf")) !=
0 /* Do not put csrf into the params table */
&&
strncmp(tok, "switch_interface", strlen("switch_interface")) != 0 &&
(_equal = strchr(tok, '='))) {
char* decoded_buf;
int len;
_equal[0] = '\0';
_equal = &_equal[1];
len = strlen(_equal);
// ntop->getTrace()->traceEvent(TRACE_WARNING, "%s = %s", tok, _equal);
if ((decoded_buf = (char*)malloc(len + 1)) != NULL) {
bool rsp = false;
Utils::urlDecode(_equal, decoded_buf, len + 1);
rsp = Utils::purifyHTTPparam(tok, true, false, false);
/* don't purify decoded_buf, it's purified in lua */
if (rsp) {
ntop->getTrace()->traceEvent(TRACE_WARNING, "[HTTP] Invalid '%s'",
query);
ret = true;
}
/* Now make sure that decoded_buf is not a file path */
if (strchr(decoded_buf, CONST_PATH_SEP) &&
Utils::file_exists(decoded_buf) &&
!Utils::dir_exists(decoded_buf) &&
strcmp(tok, "pid_name") /* This is the only exception */
)
ntop->getTrace()->traceEvent(
TRACE_WARNING,
"Discarded '%s'='%s' as argument is a valid file path", tok,
decoded_buf);
else
lua_push_str_table_entry(vm, tok, decoded_buf);
free(decoded_buf);
} else
ntop->getTrace()->traceEvent(TRACE_WARNING, "Not enough memory");
}
tok = strtok_r(NULL, "&", &where);
} /* while */
}
if (query_string) free(query_string);
if (table_name)
lua_setglobal(L, table_name);
else
lua_setglobal(L, (char*)"_GET"); /* Default */
return (ret);
}
/* ****************************************** */
void build_redirect(const char* url, const char* query_string, char* buf,
size_t bufsize) {
/*
Inside ntopng
- we cannot redirect outside of the application
- no paramenters or anything else should be allowed
hence we implement a lighweighted URL checker that
*/
if ((strncmp(url, "http", 4) == 0) || strchr(url, '%')) {
ntop->getTrace()->traceEvent(TRACE_WARNING, "Invalid redirect URL: %s",
url);
url = "/"; /* Let's redirect to the root page */
}
snprintf(buf, bufsize,
"HTTP/1.1 302 Found\r\n"
"Server: ntopng %s (%s)\r\n"
"Location: %s%s%s\r\n\r\n"
"<html>\n"
"<head>\n"
"<title>Moved</title>\n"
"</head>\n"
"<body>\n"
"<h1>Moved</h1>\n"
"<p>This page has moved to <a href=\"%s\">%s</a>.</p>\n"
"</body>\n"
"</html>\n",
PACKAGE_VERSION, PACKAGE_MACHINE, url, query_string ? "?" : "",
query_string ? query_string : "", url, url);
}
/* ****************************************** */
/* Compress and send a buffered HTTP response accumulated during Lua execution.
* Splits the buffer at the \r\n\r\n boundary, optionally gzip-compresses the
* body when the client advertises Accept-Encoding: gzip, injects the matching
* Content-Encoding / Vary / Content-Length headers, and writes everything to
* the socket in one go via mg_write (binary-safe). */
static void flushBufferedHTTPResponse(
struct mg_connection* conn, const struct mg_request_info* request_info,
NtopngLuaContext* ctx) {
const std::string& response = ctx->http_response_buffer;
if (response.empty()) {
ctx->buffer_http_response = false;
return;
}
static const char* CRLF2 = "\r\n\r\n";
size_t boundary = response.find(CRLF2);
if (boundary == std::string::npos) {
/* No proper HTTP header block; send the raw buffer as-is. */
mg_write(conn, response.data(), response.size());
ctx->buffer_http_response = false;
ctx->http_response_buffer.clear();
return;
}
const char* accept_enc = mg_get_header(conn, "Accept-Encoding");
bool try_gzip = accept_enc && (strstr(accept_enc, "gzip") != NULL);
/* Isolate the header block (no trailing \r\n\r\n) and the body. */
std::string headers(response, 0, boundary);
const char* body_data = response.data() + boundary + 4;
size_t body_size = response.size() - (boundary + 4);
/* Don't re-compress if the script already set Content-Encoding. */
if (try_gzip && headers.find("Content-Encoding") != std::string::npos)
try_gzip = false;
/* Compression only pays off above ~1 KB. */
if (try_gzip && body_size < 1024) try_gzip = false;
#ifdef HAVE_ZLIB
if (try_gzip) {
z_stream zs = {};
/* windowBits = 15 | 16 produces gzip framing instead of raw deflate. */
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 | 16, 8,
Z_DEFAULT_STRATEGY) == Z_OK) {
uLong max_comp = deflateBound(&zs, (uLong)body_size);
char* comp_buf = (char*)malloc(max_comp);
if (comp_buf) {
zs.next_in = (Bytef*)body_data;
zs.avail_in = (uInt)body_size;
zs.next_out = (Bytef*)comp_buf;
zs.avail_out = (uInt)max_comp;
if (deflate(&zs, Z_FINISH) == Z_STREAM_END) {
size_t comp_len = zs.total_out;
deflateEnd(&zs);
std::string final_headers = headers +
"\r\nContent-Encoding: gzip"
"\r\nVary: Accept-Encoding"
"\r\nContent-Length: " +
std::to_string(comp_len) + "\r\n\r\n";
mg_write(conn, final_headers.data(), final_headers.size());
mg_write(conn, comp_buf, comp_len);
free(comp_buf);
ctx->buffer_http_response = false;
ctx->http_response_buffer.clear();
return;
}
deflateEnd(&zs);
free(comp_buf);
} else {
deflateEnd(&zs);
}
}
}
#endif /* HAVE_ZLIB */
/* Fallback: send uncompressed, but still add Content-Length. */
std::string final_headers =
headers + "\r\nContent-Length: " + std::to_string(body_size) + "\r\n\r\n";
mg_write(conn, final_headers.data(), final_headers.size());
if (body_size > 0) mg_write(conn, body_data, body_size);
ctx->buffer_http_response = false;
ctx->http_response_buffer.clear();
}
/* ****************************************** */
int LuaEngine::handle_script_request(struct mg_connection* conn,
const struct mg_request_info* request_info,
char* script_path, bool* attack_attempt,
const char* user, const char* group,
const char* session_csrf, bool localuser) {
NetworkInterface* iface = NULL;
char key[64], ifname[MAX_INTERFACE_NAME_LEN];
bool is_interface_allowed;
AddressTree ptree;
Bitmap4096 pools_bitmap; /* all bits zero by default */
int rc, post_data_len = 0;
const char* content_type;
u_int8_t valid_csrf = 1;
char* post_data = NULL;
char csrf[64] = {'\0'};
char switch_interface[2] = {'\0'};
char addr_buf[64];
char session_buf[64];
char ifid_buf[32], obs_id_buf[16], session_key[32];
const char* origin_header;
bool send_redirect = false;
IpAddress client_addr;
int num_uploaded_files = 0;
*attack_attempt = false;
if (!L) return (-1);
luaL_openlibs(L); /* Load base libraries */
lua_register_classes(L, lua_engine_mode_http); /* Load custom classes */
getLuaVMUservalue(L, conn) = conn;
content_type = mg_get_header(conn, "Content-Type");
Utils::make_session_key(session_key, sizeof(session_key));
mg_get_cookie(conn, session_key, session_buf, sizeof(session_buf));
/* Check for POST requests */
if ((strcmp(request_info->request_method, "POST") == 0) &&
(content_type != NULL)) {
u_int32_t content_len = mg_get_content_len(conn) + 1;
bool is_file_upload =
(strncmp(content_type, "multipart/form-data", 19) == 0) ? true : false;
if ((!is_file_upload) && (content_len > HTTP_MAX_POST_DATA_LEN)) {
ntop->getTrace()->traceEvent(TRACE_WARNING,
"Too much data submitted with the form. "
"[data len: %u][max len: %u][URI: %s]",
content_len, HTTP_MAX_POST_DATA_LEN,
request_info->uri);
valid_csrf = 0;
} else { /* Content Len Ok or File Upload */
if (is_file_upload) { /* File Upload */
if (content_len > HTTP_MAX_UPLOAD_DATA_LEN) { /* Content Len Too Big */
ntop->getTrace()->traceEvent(TRACE_WARNING,
"You are uploading a file that is too "
"big [len: %u][max len: %u][URI: %s]",
content_len, HTTP_MAX_UPLOAD_DATA_LEN,
request_info->uri);
valid_csrf = 0;
} else { /* Content Len Ok */
char fname[1024], upload_dir[512];
snprintf(upload_dir, sizeof(upload_dir), "%s/tmp/upload",
ntop->get_working_dir());
ntop->fixPath(upload_dir);
if (!Utils::mkdir_tree(upload_dir))
ntop->getTrace()->traceEvent(
TRACE_WARNING, "Unable to create directory %s", upload_dir);
else {
/* NOTE: mongoose is currently unable to handle multiple fields in a
* single upload */
num_uploaded_files =
mg_upload(conn, upload_dir, fname, sizeof(fname));
if (num_uploaded_files > 0) {
char uploaded_file[2048];
snprintf(uploaded_file, sizeof(uploaded_file), "%s/%s",
upload_dir, fname);
ntop->fixPath(uploaded_file);
lua_newtable(L);
lua_push_str_table_entry(L, "uploaded_file", uploaded_file);
if (request_info->query_string) {
char* d = strdup(request_info->query_string);
if (d != NULL) {
char* where;
char* tok = strtok_r(d, "&", &where);
while (tok != NULL) {
char* tok_where;
char* k = strtok_r(tok, "=", &tok_where);
if (k != NULL) {
char* v = strtok_r(NULL, "=", &tok_where);
if (v != NULL) lua_push_str_table_entry(L, k, v);
}
tok = strtok_r(NULL, "&", &where);
} /* while */
}
}
lua_setglobal(L, "_POST");
valid_csrf = 1; /* Dummy */
}
}
}
} else if ((post_data = (char*)malloc(content_len * sizeof(char))) ==
NULL ||
(post_data_len = mg_read(conn, post_data, content_len)) == 0) {
valid_csrf = 0;
} else {
post_data[post_data_len] = '\0';
if (!strcmp(session_csrf, NTOP_CSRF_TOKEN_NO_SESSION)) {
/* Authentication has taken place with direct username:password
submission, without the use of a session, hence, this request
cannot be the result of a CSRF attack which, by construction,
relies on a valid session. */
valid_csrf = 1;
} else {
/* If here, authentication has taken place using a session, thus CSRF
is mandatory in POST request and must be checked for validity. Note
that session_csrf is trusted, that it comes from ntopng, whereas
csrf read from the POST is untrusted. */
if (strstr(content_type, "application/json") == content_type) {
/*
post_data is JSON which is only allowed when using the REST API
*/
if ((strstr(request_info->uri, REST_API_PREFIX) ||
strstr(request_info->uri, REST_API_PRO_PREFIX)) &&
strstr(request_info->uri, "/get/")) {
/*
REST API URI, GET, no CSRF required
*/
} else {
/*
REST API URIs not containing /get/, e.g., /set/, /add/,
/delete/, are assumed to change the status of ntopng and thus
CSRF checks MUST be enforced. In this case, post_data is decoded
as JSON to enforce the check.
*/
json_object *o = NULL, *csrf_o;
if (!(o = json_tokener_parse(post_data)) ||
!json_object_object_get_ex(o, "csrf", &csrf_o) ||
!strncpy(csrf, json_object_get_string(csrf_o),
sizeof(csrf) - 1) ||
strncmp(session_csrf, csrf, NTOP_CSRF_TOKEN_LENGTH)) {
/*
Either the CSRF token has not been submitted as part of the
JSON, or the submitted token is invalid.
*/
valid_csrf = 0;
}
if (o) json_object_put(o);
}
} else {
/*
post_data is assumed to be application/x-www-form-urlencoded
CSRF token is searched and validated using mg_get_var
*/
mg_get_var(post_data, post_data_len, "csrf", csrf, sizeof(csrf));
if (strncmp(session_csrf, csrf, NTOP_CSRF_TOKEN_LENGTH))
valid_csrf = 0;
}
}
}
}
if (num_uploaded_files == 0) {
/* Empty CSRF only allowed for nologin user. Such user has no associated
* session so it has an empty CSRF. */
if (valid_csrf) {
if (strstr(content_type, "application/x-www-form-urlencoded") ==
content_type)
*attack_attempt =
setParamsTable(L, request_info, "_POST",
post_data); /* CSRF is valid here, now fill the
_POST table with POST parameters */
else {
/* application/json */
lua_newtable(L);
/* This payload is NOT parsed, checked or verified against attacks */
lua_push_str_table_entry(L, "payload", post_data);
lua_setglobal(L, "_POST");
}
/* Check for interface switch requests */
mg_get_var(post_data, post_data_len, "switch_interface",
switch_interface, sizeof(switch_interface));
if (strlen(switch_interface) > 0 && request_info->query_string) {
/* Read the interface id */
mg_get_var(request_info->query_string,
strlen(request_info->query_string), "ifid", ifid_buf,
sizeof(ifid_buf));
/* Read the observation point id */
mg_get_var(request_info->query_string,
strlen(request_info->query_string), "observationPointId",
obs_id_buf, sizeof(obs_id_buf));
if (strlen(ifid_buf) > 0) {
switchInterface(L, ifid_buf, obs_id_buf, user, group, session_buf);
/* Sending a redirect is needed to prevent the current lua script
* from receiving the POST request, as it could exchange the request
* as a configuration save request. */
send_redirect = true;
}
}
} else {
*attack_attempt =
setParamsTable(L, request_info, "_POST", NULL /* Empty */);
}
if (post_data) free(post_data);
}
} else
*attack_attempt =
setParamsTable(L, request_info, "_POST", NULL /* Empty */);
if (send_redirect) {
char buf[2048], uri[512];
snprintf(uri, sizeof(uri), "%s%s", ntop->getPrefs()->get_http_prefix(),
request_info->uri);
build_redirect(uri, request_info->query_string, buf, sizeof(buf));
/* Redirect the page and terminate this request */
mg_printf(conn, "%s", buf);
return (CONST_LUA_OK);
}
/* Put the GET params into the environment */
if (request_info->query_string)
*attack_attempt =
setParamsTable(L, request_info, "_GET", request_info->query_string);
else
*attack_attempt = setParamsTable(L, request_info, "_GET", NULL /* Empty */);
/* _SERVER */
lua_newtable(L);
lua_push_str_table_entry(L, "REQUEST_METHOD",
(char*)request_info->request_method);
lua_push_str_table_entry(
L, "URI",
(char*)request_info->uri ? (char*)request_info->uri : (char*)"");
lua_push_str_table_entry(L, "REFERER",
(char*)mg_get_header(conn, "Referer")
? (char*)mg_get_header(conn, "Referer")
: (char*)"");
const char* host = mg_get_header(conn, "Host");
if (host) {
lua_pushfstring(L, "%s://%s", (request_info->is_ssl) ? "https" : "http",
host);
lua_pushstring(L, "HTTP_HOST");
lua_insert(L, -2);
lua_settable(L, -3);
}
if (request_info->remote_user)
lua_push_str_table_entry(L, "REMOTE_USER",
(char*)request_info->remote_user);
if (request_info->query_string)
lua_push_str_table_entry(L, "QUERY_STRING",
(char*)request_info->query_string);
/* Additional headers can be added eventually */
origin_header = mg_get_header(conn, "Origin");
if (origin_header) lua_push_str_table_entry(L, "Origin", origin_header);
for (int i = 0; ((request_info->http_headers[i].name != NULL) &&
request_info->http_headers[i].name[0] != '\0');
i++)
lua_push_str_table_entry(L, request_info->http_headers[i].name,
(char*)request_info->http_headers[i].value);
client_addr.set(mg_get_client_address(conn));
lua_push_str_table_entry(
L, "REMOTE_ADDR", (char*)client_addr.print(addr_buf, sizeof(addr_buf)));
lua_setglobal(L, (char*)"_SERVER");
/* NOTE: ntopng cannot rely on user provided cookies for security data (e.g.
* user or group), use the session data instead! */
char* _cookies;
/* Cookies */
lua_newtable(L);
if ((_cookies = (char*)mg_get_header(conn, "Cookie")) != NULL) {
char* cookies = strdup(_cookies);
char *tok, *where;
// ntop->getTrace()->traceEvent(TRACE_WARNING, "=> '%s'", cookies);
tok = strtok_r(cookies, "=", &where);
while (tok != NULL) {
char* val;
while (tok[0] == ' ') tok++;
if ((val = strtok_r(NULL, ";", &where)) != NULL) {
lua_push_str_table_entry(L, tok, val);
// ntop->getTrace()->traceEvent(TRACE_WARNING, "'%s'='%s'", tok, val);
} else
break;
tok = strtok_r(NULL, "=", &where);
}
free(cookies);
}
lua_setglobal(L, "_COOKIE"); /* Like in php */
/*
Read user capabilities
*/
u_int64_t capabilities = 0;
char allowed_nets[MAX_USER_NETS_VAL_LEN];
if (strncmp(group, CONST_USER_GROUP_ADMIN, strlen(CONST_USER_GROUP_ADMIN)) ==
0) {
/*
Administrators have all the possible capabilitites
*/
capabilities = (u_int64_t)-1;
snprintf(allowed_nets, sizeof(allowed_nets), CONST_DEFAULT_ALL_NETS);
} else {
/*
Non-administrators only have a subset of capabilities - stored on redis
*/
bool allow_pcap_download = false, allow_historical_flows = false,
allow_alerts = false;
char val[32];
snprintf(key, sizeof(key), CONST_STR_USER_CAPABILITIES, user);
if ((ntop->getRedis()->get(key, val, sizeof(val)) != -1) &&
(val[0] != '\0')) {
capabilities = strtol(val, NULL, 10);
}
/* Read user allowed networks from redis */
snprintf(key, sizeof(key), CONST_STR_USER_NETS, user);
if (ntop->getRedis()->get(key, allowed_nets, sizeof(allowed_nets)) == -1) {
/* Set the default (allow all), if no allowed networks are found */
snprintf(allowed_nets, sizeof(allowed_nets), CONST_DEFAULT_ALL_NETS);
}
/* Read user allowed host pools from redis */
char pools_val[MAX_USER_NETS_VAL_LEN];
snprintf(key, sizeof(key), CONST_STR_USER_ALLOWED_HOST_POOLS, user);
if (ntop->getRedis()->get(key, pools_val, sizeof(pools_val)) >= 0 &&
pools_val[0] != '\0') {
pools_bitmap.setBits(pools_val); /* setBits parses comma-separated IDs */
}
ntop->getUserCapabilities(user, &allow_pcap_download,
&allow_historical_flows, &allow_alerts);
if (allow_historical_flows)
capabilities |= (1 << capability_historical_flows);
if (allow_alerts) capabilities |= (1 << capability_alerts);
if (allow_pcap_download) capabilities |= (1 << capability_pcap_download);
}
/* Put the _SESSION params into the environment */
lua_newtable(L);
lua_push_str_table_entry(L, "session", session_buf);
lua_push_str_table_entry(L, "user", (char*)user);
lua_push_str_table_entry(L, "group", (char*)group);
lua_push_bool_table_entry(L, "localuser", localuser);
lua_push_uint64_table_entry(L, "capabilities", capabilities);
lua_push_str_table_entry(L, "allowed_nets", (char*)allowed_nets);
// now it's time to set the interface.
setInterface(user, ifname, sizeof(ifname), &is_interface_allowed);
if (!valid_csrf) lua_push_bool_table_entry(L, "INVALID_CSRF", true);
lua_setglobal(L, "_SESSION"); /* Like in php */
if (user[0] != '\0') {
char val[16];
getLuaVMUservalue(L, user) = (char*)user;
/* Populate a patricia tree with the user allowed networks */
ptree.addAddresses(allowed_nets);
getLuaVMUservalue(L, allowedNets) = &ptree;
snprintf(key, sizeof(key), CONST_STR_USER_LANGUAGE, user);
if ((ntop->getRedis()->get(key, val, sizeof(val)) != -1) &&
(val[0] != '\0')) {
lua_pushstring(L, val);
} else
lua_pushstring(L, NTOP_DEFAULT_USER_LANG);
lua_setglobal(L, CONST_USER_LANGUAGE);
}
getLuaVMUservalue(L, group) = (char*)(group ? (group) : "");
getLuaVMUservalue(L, localuser) = localuser;
getLuaVMUservalue(L, csrf) = (char*)session_csrf;
getLuaVMUservalue(L, capabilities) = capabilities;
/* Store pointer to pools_bitmap only when restrictions are configured
* NULL means unrestricted (admin users or no allowed pools set) */
getLuaVMUservalue(L, allowed_pools) =
(pools_bitmap.getNext(0) >= 0) ? &pools_bitmap : (Bitmap4096*)NULL;
iface = ntop->getNetworkInterface(ifname); /* Can't be null */
/* 'select' ther interface that has already been set into the _SESSION */
getLuaVMUservalue(L, iface) = iface;
if (is_interface_allowed)
getLuaVMUservalue(L, allowed_ifname) = iface->get_name();
/* Enable response buffering for REST API endpoints so that
* flushBufferedHTTPResponse() can apply gzip compression afterwards. */
NtopngLuaContext* ctx = getLuaVMContext(L);
if (ctx && strncmp(request_info->uri, "/lua/", 5) == 0) {
ctx->buffer_http_response = true;
ctx->http_response_buffer.clear();
}
#ifdef NTOPNG_PRO
if (ntop->getPro()->has_valid_license())
rc = __ntop_lua_handlefile(L, script_path, true);
else
#endif
rc = luaL_dofile(L, script_path);
// lua_settop(L, 0);
/* Flush the buffered response (compresses if client supports gzip). On
* script error the buffer is simply discarded; the error page is sent
* by redirect_to_error_page() which writes directly to the socket. */
if (ctx && ctx->buffer_http_response) {
if (rc == 0)
flushBufferedHTTPResponse(conn, request_info, ctx);
else {
ctx->buffer_http_response = false;
ctx->http_response_buffer.clear();
}
}
if (rc != 0) {
const char* err = lua_tostring(L, -1);
ntop->getTrace()->traceEvent(TRACE_WARNING, "Script failure [%s][%s]",
script_path, err ? err : "Unknown error");
return (redirect_to_error_page(conn, request_info, "internal_error",
script_path, (char*)err));
}
return (CONST_LUA_OK);
}
/* ****************************************** */
void LuaEngine::setHost(Host* h) {
NtopngLuaContext* c = getLuaVMContext(L);
if (c) {
c->host = h;
if (h) c->iface = h->getInterface();
}
}
/* ****************************************** */
void LuaEngine::setNetwork(NetworkStats* ns) {
NtopngLuaContext* c = getLuaVMContext(L);
if (c) {
c->network = ns;
}
}
/* ****************************************** */
void LuaEngine::setFlow(Flow* f) {
NtopngLuaContext* c = getLuaVMContext(L);
if (c) {
c->flow = f;
c->iface = f->getInterface();
}
}
/* ****************************************** */
void LuaEngine::setThreadedActivityData(const ThreadedActivity* ta,
ThreadedActivityStats* tas,
time_t deadline) {
NtopngLuaContext* cur_ctx;
lua_State* cur_state = getState();
if ((cur_ctx = getLuaVMContext(cur_state))) {
cur_ctx->deadline = deadline;
cur_ctx->threaded_activity = ta;
cur_ctx->threaded_activity_stats = tas;
}
}
/* ****************************************** */
void LuaEngine::pushResultString(char* str) { cloud_string.append(str); }
/* ****************************************** */
void LuaEngine::pushResultNumber(float f) {
char buf[32];
snprintf(buf, sizeof(buf), "%f", f);
cloud_string.append(buf);
}
/* ****************************************** */
Host* LuaEngine::getHost() { return (getLuaVMContext(L)->host); }
/* ****************************************** */
NetworkInterface* LuaEngine::getNetworkInterface() {
return (getLuaVMContext(L)->iface);
}
/* ****************************************** */
bool LuaEngine::require(std::string name) {
std::set<std::string>::iterator it = require_list.find(name);
if (it != require_list.end()) return (true);
require_list.insert(name);
return (false);
}