ntopng/src/LuaEngine.cpp

1541 lines
46 KiB
C++

/*
*
* (C) 2013-24 - 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 HTTP_MAX_UPLOAD_DATA_LEN \
25000000 /* ~25MB (see also upload_pcap.template) */
/* ******************************* */
struct ntopngLuaContext *getUserdata(struct lua_State *vm) {
if (vm) {
struct ntopngLuaContext *userdata;
lua_getglobal(vm, "userdata");
userdata = (struct 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
/* ******************************* */
LuaEngine::LuaEngine(lua_State *vm) {
std::bad_alloc bax;
void *ctx;
loaded_script_path = NULL;
is_system_vm = false;
L = luaL_newstate();
if (!L) {
ntop->getTrace()->traceEvent(TRACE_ERROR,
"Unable to create a new Lua state.");
throw bax;
}
ctx = (void *)calloc(1, sizeof(struct ntopngLuaContext));
if (!ctx) {
ntop->getTrace()->traceEvent(TRACE_ERROR, "Unable to create a context for the new Lua state.");
lua_close(L);
throw bax;
}
lua_pushlightuserdata(L, ctx);
lua_setglobal(L, "userdata");
if (vm) setThreadedActivityData(vm);
}
/* ******************************* */
LuaEngine::~LuaEngine() {
if (L) {
struct ntopngLuaContext *ctx;
#ifdef DUMP_STACK
stackDump(L);
#endif
ctx = getLuaVMContext(L);
if (ctx) {
if (ctx->snmpBatch) delete ctx->snmpBatch;
for (u_int8_t slot_id = 0; slot_id < MAX_NUM_ASYNC_SNMP_ENGINES;
slot_id++) {
if (ctx->snmpAsyncEngine[slot_id] != NULL)
delete ctx->snmpAsyncEngine[slot_id];
}
if (ctx->pkt_capture.end_capture > 0) {
ctx->pkt_capture.end_capture = 0; /* Force stop */
pthread_join(ctx->pkt_capture.captureThreadLoop, NULL);
}
if ((ctx->iface != NULL) && ctx->live_capture.pcaphdr_sent)
ctx->iface->deregisterLiveCapture(ctx);
if (ctx->addr_tree != NULL) delete ctx->addr_tree;
if (ctx->sqlite_hosts_filter) free(ctx->sqlite_hosts_filter);
if (ctx->sqlite_flows_filter) free(ctx->sqlite_flows_filter);
#if defined(NTOPNG_PRO)
if (ctx->bin) delete ctx->bin;
#endif
free(ctx);
}
lua_close(L);
}
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__); */
/* 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)
mg_printf(conn, "%c", str[0]);
else
return (CONST_LUA_PARAM_ERROR);
}
lua_pushnil(vm);
return (CONST_LUA_OK);
}
switch (t = lua_type(vm, 1)) {
case LUA_TNIL:
mg_printf(conn, "%s", "nil");
break;
case LUA_TBOOLEAN: {
int v = lua_toboolean(vm, 1);
mg_printf(conn, "%s", v ? "true" : "false");
} break;
case LUA_TSTRING: {
char *str = (char *)lua_tostring(vm, 1);
if (str && (strlen(str) > 0)) mg_printf(conn, "%s", str);
} break;
case LUA_TNUMBER: {
char str[64];
snprintf(str, sizeof(str), "%f", (float)lua_tonumber(vm, 1));
mg_printf(conn, "%s", str);
} break;
case LUA_TTABLE: {
lua_pushnil(vm);
while (lua_next(vm, -2) != 0) {
const char *key = lua_tostring(vm, -2);
if (lua_isstring(vm, -1))
mg_printf(conn, "%s = %s", key, lua_tostring(vm, -1));
else if (lua_isnumber(vm, -1))
mg_printf(conn, "%s = %d", key, (int)lua_tonumber(vm, -1));
else if (lua_istable(vm, -1)) {
mg_printf(conn, "%s", 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;
if (lua_type(L, 1) != LUA_TSTRING ||
(script_name = (char *)lua_tostring(L, 1)) == NULL)
return 0;
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, 0 /* 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_INFO, "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 */, 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, 0 /* No Trim */);
}
}
rv = -2;
}
// lua_pop(L, 1);
lua_settop(L, 0);
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) {
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);
}
/* ****************************************** */
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;
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)) {
int 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);
lua_push_str_table_entry(
L, "payload", post_data); /* This payload is NOT parsed, checked
or verified against attacks */
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 - stored on 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);
}
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;
// ntop->getTrace()->traceEvent(TRACE_WARNING, "SET [p: %p][val: %s][user:
// %s]", &ptree, val, user);
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;
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();
#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);
if (rc != 0) {
const char *err = lua_tostring(L, -1);
ntop->getTrace()->traceEvent(TRACE_WARNING, "Script failure [%s][%s]",
script_path, err);
return (redirect_to_error_page(conn, request_info, "internal_error",
script_path, (char *)err));
}
return (CONST_LUA_OK);
}
/* ****************************************** */
void LuaEngine::setHost(Host *h) {
struct ntopngLuaContext *c = getLuaVMContext(L);
if (c) {
c->host = h;
if (h) c->iface = h->getInterface();
}
}
/* ****************************************** */
void LuaEngine::setNetwork(NetworkStats *ns) {
struct ntopngLuaContext *c = getLuaVMContext(L);
if (c) {
c->network = ns;
}
}
/* ****************************************** */
void LuaEngine::setFlow(Flow *f) {
struct ntopngLuaContext *c = getLuaVMContext(L);
if (c) {
c->flow = f;
c->iface = f->getInterface();
}
}
/* ****************************************** */
void LuaEngine::setThreadedActivityData(lua_State *from) {
struct ntopngLuaContext *cur_ctx, *from_ctx;
lua_State *cur_state = getState();
if (from && (cur_ctx = getLuaVMContext(cur_state)) &&
(from_ctx = getLuaVMContext(from))) {
cur_ctx->deadline = from_ctx->deadline;
cur_ctx->threaded_activity = from_ctx->threaded_activity;
cur_ctx->threaded_activity_stats = from_ctx->threaded_activity_stats;
}
}
/* ****************************************** */
void LuaEngine::setThreadedActivityData(const ThreadedActivity *ta,
ThreadedActivityStats *tas,
time_t deadline) {
struct 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);
}