diff options
Diffstat (limited to '')
-rw-r--r-- | src/lua/lua_common.c | 2659 |
1 files changed, 2659 insertions, 0 deletions
diff --git a/src/lua/lua_common.c b/src/lua/lua_common.c new file mode 100644 index 0000000..9bf9514 --- /dev/null +++ b/src/lua/lua_common.c @@ -0,0 +1,2659 @@ +/* + * Copyright 2023 Vsevolod Stakhov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "lua_common.h" +#include "lua_compress.h" +#include "lptree.h" +#include "utlist.h" +#include "unix-std.h" +#include "ottery.h" +#include "lua_thread_pool.h" +#include "libstat/stat_api.h" +#include "libserver/rspamd_control.h" + +#include <math.h> + + +/* Lua module init function */ +#define MODULE_INIT_FUNC "module_init" + +#ifdef WITH_LUA_TRACE +ucl_object_t *lua_traces; +#endif + +const luaL_reg null_reg[] = { + {"__tostring", rspamd_lua_class_tostring}, + {NULL, NULL}}; + +static const char rspamd_modules_state_global[] = "rspamd_plugins_state"; + +static GQuark +lua_error_quark(void) +{ + return g_quark_from_static_string("lua-routines"); +} + +/* + * Used to map string to a pointer + */ +KHASH_INIT(lua_class_set, const char *, int, 1, rspamd_str_hash, rspamd_str_equal); +struct rspamd_lua_context { + lua_State *L; + khash_t(lua_class_set) * classes; + struct rspamd_lua_context *prev, *next; /* Expensive but we usually have exactly one lua state */ +}; +struct rspamd_lua_context *rspamd_lua_global_ctx = NULL; +#define RSPAMD_LUA_NCLASSES 64 +static inline struct rspamd_lua_context * +rspamd_lua_ctx_by_state(lua_State *L) +{ + struct rspamd_lua_context *cur; + + DL_FOREACH(rspamd_lua_global_ctx, cur) + { + if (cur->L == L) { + return cur; + } + } + + /* When we are using thread pool, this is the case... */ + return rspamd_lua_global_ctx; +} + +/* Util functions */ +/** + * Create new class and store metatable on top of the stack (must be popped if not needed) + * @param L + * @param classname name of class + * @param func table of class methods + */ +void rspamd_lua_new_class(lua_State *L, + const gchar *classname, + const struct luaL_reg *methods) +{ + khiter_t k; + gint r, nmethods = 0; + gboolean seen_index = false; + struct rspamd_lua_context *ctx = rspamd_lua_ctx_by_state(L); + + if (methods) { + for (;;) { + if (methods[nmethods].name != NULL) { + if (strcmp(methods[nmethods].name, "__index") == 0) { + seen_index = true; + } + nmethods++; + } + else { + break; + } + } + } + + lua_createtable(L, 0, 3 + nmethods); + + if (!seen_index) { + lua_pushstring(L, "__index"); + lua_pushvalue(L, -2); /* pushes the metatable */ + lua_settable(L, -3); /* metatable.__index = metatable */ + } + + lua_pushstring(L, "class"); + lua_pushstring(L, classname); + lua_rawset(L, -3); + + if (methods) { + luaL_register(L, NULL, methods); /* pushes all methods as MT fields */ + } + + lua_pushvalue(L, -1); /* Preserves metatable */ + int offset = luaL_ref(L, LUA_REGISTRYINDEX); + k = kh_put(lua_class_set, ctx->classes, classname, &r); + kh_value(ctx->classes, k) = offset; + /* MT is left on stack ! */ +} + +static const gchar * +rspamd_lua_class_tostring_buf(lua_State *L, gboolean print_pointer, gint pos) +{ + static gchar buf[64]; + const gchar *ret = NULL; + gint pop = 0; + + if (!lua_getmetatable(L, pos)) { + goto err; + } + + pop++; + lua_pushstring(L, "class"); + lua_gettable(L, -2); + pop++; + + if (!lua_isstring(L, -1)) { + goto err; + } + + if (print_pointer) { + rspamd_snprintf(buf, sizeof(buf), "%s(%p)", lua_tostring(L, -1), + lua_touserdata(L, 1)); + } + else { + rspamd_snprintf(buf, sizeof(buf), "%s", lua_tostring(L, -1)); + } + + ret = buf; + +err: + lua_pop(L, pop); + + return ret; +} + +gint rspamd_lua_class_tostring(lua_State *L) +{ + const gchar *p; + + p = rspamd_lua_class_tostring_buf(L, TRUE, 1); + + if (!p) { + lua_pushstring(L, "invalid object passed to 'lua_common.c:__tostring'"); + return lua_error(L); + } + + lua_pushstring(L, p); + + return 1; +} + + +void rspamd_lua_setclass(lua_State *L, const gchar *classname, gint objidx) +{ + khiter_t k; + struct rspamd_lua_context *ctx = rspamd_lua_ctx_by_state(L); + + k = kh_get(lua_class_set, ctx->classes, classname); + + g_assert(k != kh_end(ctx->classes)); + lua_rawgeti(L, LUA_REGISTRYINDEX, kh_value(ctx->classes, k)); + + if (objidx < 0) { + objidx--; + } + lua_setmetatable(L, objidx); +} + +void rspamd_lua_class_metatable(lua_State *L, const gchar *classname) +{ + khiter_t k; + struct rspamd_lua_context *ctx = rspamd_lua_ctx_by_state(L); + + k = kh_get(lua_class_set, ctx->classes, classname); + + g_assert(k != kh_end(ctx->classes)); + lua_rawgeti(L, LUA_REGISTRYINDEX, kh_value(ctx->classes, k)); +} + +void rspamd_lua_add_metamethod(lua_State *L, const gchar *classname, + luaL_Reg *meth) +{ + khiter_t k; + struct rspamd_lua_context *ctx = rspamd_lua_ctx_by_state(L); + + k = kh_get(lua_class_set, ctx->classes, classname); + + g_assert(k != kh_end(ctx->classes)); + lua_rawgeti(L, LUA_REGISTRYINDEX, kh_value(ctx->classes, k)); + + lua_pushcfunction(L, meth->func); + lua_setfield(L, -2, meth->name); + lua_pop(L, 1); /* remove metatable */ +} + +/* assume that table is at the top */ +void rspamd_lua_table_set(lua_State *L, const gchar *index, const gchar *value) +{ + lua_pushstring(L, index); + if (value) { + lua_pushstring(L, value); + } + else { + lua_pushnil(L); + } + lua_settable(L, -3); +} + +const gchar * +rspamd_lua_table_get(lua_State *L, const gchar *index) +{ + const gchar *result; + + lua_pushstring(L, index); + lua_gettable(L, -2); + if (!lua_isstring(L, -1)) { + return NULL; + } + result = lua_tostring(L, -1); + lua_pop(L, 1); + return result; +} + +static void +lua_add_actions_global(lua_State *L) +{ + gint i; + + lua_newtable(L); + + for (i = METRIC_ACTION_REJECT; i <= METRIC_ACTION_NOACTION; i++) { + lua_pushstring(L, rspamd_action_to_str(i)); + lua_pushinteger(L, i); + lua_settable(L, -3); + } + /* Set global table */ + lua_setglobal(L, "rspamd_actions"); +} + +#ifndef __APPLE__ +#define OS_SO_SUFFIX ".so" +#else +#define OS_SO_SUFFIX ".dylib" +#endif + +void rspamd_lua_set_path(lua_State *L, const ucl_object_t *cfg_obj, GHashTable *vars) +{ + const gchar *old_path, *additional_path = NULL; + const ucl_object_t *opts = NULL; + const gchar *rulesdir = RSPAMD_RULESDIR, + *lualibdir = RSPAMD_LUALIBDIR, + *libdir = RSPAMD_LIBDIR; + const gchar *t; + + gchar path_buf[PATH_MAX]; + + lua_getglobal(L, "package"); + lua_getfield(L, -1, "path"); + old_path = luaL_checkstring(L, -1); + + if (strstr(old_path, RSPAMD_LUALIBDIR) != NULL) { + /* Path has been already set, do not touch it */ + lua_pop(L, 2); + return; + } + + if (cfg_obj) { + opts = ucl_object_lookup(cfg_obj, "options"); + if (opts != NULL) { + opts = ucl_object_lookup(opts, "lua_path"); + if (opts != NULL && ucl_object_type(opts) == UCL_STRING) { + additional_path = ucl_object_tostring(opts); + } + } + } + + if (additional_path) { + rspamd_snprintf(path_buf, sizeof(path_buf), + "%s;" + "%s", + additional_path, old_path); + } + else { + /* Try environment */ + t = getenv("RULESDIR"); + if (t) { + rulesdir = t; + } + + t = getenv("LUALIBDIR"); + if (t) { + lualibdir = t; + } + + t = getenv("LIBDIR"); + if (t) { + libdir = t; + } + + t = getenv("RSPAMD_LIBDIR"); + if (t) { + libdir = t; + } + + if (vars) { + t = g_hash_table_lookup(vars, "RULESDIR"); + if (t) { + rulesdir = t; + } + + t = g_hash_table_lookup(vars, "LUALIBDIR"); + if (t) { + lualibdir = t; + } + + t = g_hash_table_lookup(vars, "LIBDIR"); + if (t) { + libdir = t; + } + + t = g_hash_table_lookup(vars, "RSPAMD_LIBDIR"); + if (t) { + libdir = t; + } + } + + rspamd_snprintf(path_buf, sizeof(path_buf), + "%s/lua/?.lua;" + "%s/?.lua;" + "%s/?.lua;" + "%s/?/init.lua;" + "%s", + RSPAMD_CONFDIR, + rulesdir, + lualibdir, lualibdir, + old_path); + } + + lua_pop(L, 1); + lua_pushstring(L, path_buf); + lua_setfield(L, -2, "path"); + + lua_getglobal(L, "package"); + lua_getfield(L, -1, "cpath"); + old_path = luaL_checkstring(L, -1); + + additional_path = NULL; + + if (opts != NULL) { + opts = ucl_object_lookup(opts, "lua_cpath"); + if (opts != NULL && ucl_object_type(opts) == UCL_STRING) { + additional_path = ucl_object_tostring(opts); + } + } + + if (additional_path) { + rspamd_snprintf(path_buf, sizeof(path_buf), + "%s/?%s;" + "%s", + additional_path, + OS_SO_SUFFIX, + old_path); + } + else { + rspamd_snprintf(path_buf, sizeof(path_buf), + "%s/?%s;" + "%s", + libdir, + OS_SO_SUFFIX, + old_path); + } + + lua_pop(L, 1); + lua_pushstring(L, path_buf); + lua_setfield(L, -2, "cpath"); + + lua_pop(L, 1); +} + +static gint +rspamd_lua_cmp_version_components(const gchar *comp1, const gchar *comp2) +{ + guint v1, v2; + + v1 = strtoul(comp1, NULL, 10); + v2 = strtoul(comp2, NULL, 10); + + return v1 - v2; +} + +static int +rspamd_lua_rspamd_version_cmp(lua_State *L) +{ + const gchar *ver; + gchar **components; + gint ret = 0; + + if (lua_type(L, 2) == LUA_TSTRING) { + ver = lua_tostring(L, 2); + + components = g_strsplit_set(ver, ".-_", -1); + + if (!components) { + return luaL_error(L, "invalid arguments to 'cmp': %s", ver); + } + + if (components[0]) { + ret = rspamd_lua_cmp_version_components(components[0], + RSPAMD_VERSION_MAJOR); + } + + if (ret) { + goto set; + } + + if (components[1]) { + ret = rspamd_lua_cmp_version_components(components[1], + RSPAMD_VERSION_MINOR); + } + + if (ret) { + goto set; + } + + /* + * XXX: we don't compare git releases assuming that it is meaningless + */ + } + else { + return luaL_error(L, "invalid arguments to 'cmp'"); + } + +set: + g_strfreev(components); + lua_pushinteger(L, ret); + + return 1; +} + +static int +rspamd_lua_rspamd_version_numeric(lua_State *L) +{ + static gint64 version_num = RSPAMD_VERSION_NUM; + const gchar *type; + + if (lua_gettop(L) >= 2 && lua_type(L, 1) == LUA_TSTRING) { + type = lua_tostring(L, 1); + if (g_ascii_strcasecmp(type, "short") == 0) { + version_num = RSPAMD_VERSION_MAJOR_NUM * 1000 + + RSPAMD_VERSION_MINOR_NUM * 100 + + RSPAMD_VERSION_PATCH_NUM * 10; + } + else if (g_ascii_strcasecmp(type, "main") == 0) { + version_num = RSPAMD_VERSION_MAJOR_NUM * 1000 + + RSPAMD_VERSION_MINOR_NUM * 100 + + RSPAMD_VERSION_PATCH_NUM * 10; + } + else if (g_ascii_strcasecmp(type, "major") == 0) { + version_num = RSPAMD_VERSION_MAJOR_NUM; + } + else if (g_ascii_strcasecmp(type, "patch") == 0) { + version_num = RSPAMD_VERSION_PATCH_NUM; + } + else if (g_ascii_strcasecmp(type, "minor") == 0) { + version_num = RSPAMD_VERSION_MINOR_NUM; + } + } + + lua_pushinteger(L, version_num); + + return 1; +} + +static int +rspamd_lua_rspamd_version(lua_State *L) +{ + const gchar *result = NULL, *type; + + if (lua_gettop(L) == 0) { + result = RVERSION; + } + else if (lua_gettop(L) >= 1 && lua_type(L, 1) == LUA_TSTRING) { + /* We got something like string */ + type = lua_tostring(L, 1); + + if (g_ascii_strcasecmp(type, "short") == 0) { + result = RSPAMD_VERSION_MAJOR + "." RSPAMD_VERSION_MINOR; + } + else if (g_ascii_strcasecmp(type, "main") == 0) { + result = RSPAMD_VERSION_MAJOR "." RSPAMD_VERSION_MINOR "." RSPAMD_VERSION_PATCH; + } + else if (g_ascii_strcasecmp(type, "major") == 0) { + result = RSPAMD_VERSION_MAJOR; + } + else if (g_ascii_strcasecmp(type, "minor") == 0) { + result = RSPAMD_VERSION_MINOR; + } + else if (g_ascii_strcasecmp(type, "patch") == 0) { + result = RSPAMD_VERSION_PATCH; + } + else if (g_ascii_strcasecmp(type, "id") == 0) { + result = RID; + } + else if (g_ascii_strcasecmp(type, "num") == 0) { + return rspamd_lua_rspamd_version_numeric(L); + } + else if (g_ascii_strcasecmp(type, "cmp") == 0) { + return rspamd_lua_rspamd_version_cmp(L); + } + } + + lua_pushstring(L, result); + + return 1; +} + +static gboolean +rspamd_lua_load_env(lua_State *L, const char *fname, gint tbl_pos, GError **err) +{ + gint orig_top = lua_gettop(L), err_idx; + gboolean ret = TRUE; + + lua_pushcfunction(L, &rspamd_lua_traceback); + err_idx = lua_gettop(L); + + if (luaL_loadfile(L, fname) != 0) { + g_set_error(err, g_quark_from_static_string("lua_env"), errno, + "cannot load lua file %s: %s", + fname, + lua_tostring(L, -1)); + ret = FALSE; + } + + if (ret && lua_pcall(L, 0, 1, err_idx) != 0) { + g_set_error(err, g_quark_from_static_string("lua_env"), errno, + "cannot init lua file %s: %s", + fname, + lua_tostring(L, -1)); + ret = FALSE; + } + + if (ret && lua_type(L, -1) == LUA_TTABLE) { + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + lua_pushvalue(L, -2); /* Store key */ + lua_pushvalue(L, -2); /* Store value */ + lua_settable(L, tbl_pos); + } + } + else if (ret) { + g_set_error(err, g_quark_from_static_string("lua_env"), errno, + "invalid return type when loading env from %s: %s", + fname, + lua_typename(L, lua_type(L, -1))); + ret = FALSE; + } + + lua_settop(L, orig_top); + + return ret; +} + +gboolean +rspamd_lua_set_env(lua_State *L, GHashTable *vars, char **lua_env, GError **err) +{ + gint orig_top = lua_gettop(L); + gchar **env = g_get_environ(); + + /* Set known paths as rspamd_paths global */ + lua_getglobal(L, "rspamd_paths"); + if (lua_isnil(L, -1)) { + const gchar *confdir = RSPAMD_CONFDIR, + *local_confdir = RSPAMD_LOCAL_CONFDIR, + *rundir = RSPAMD_RUNDIR, + *dbdir = RSPAMD_DBDIR, + *logdir = RSPAMD_LOGDIR, + *wwwdir = RSPAMD_WWWDIR, + *pluginsdir = RSPAMD_PLUGINSDIR, + *rulesdir = RSPAMD_RULESDIR, + *lualibdir = RSPAMD_LUALIBDIR, + *prefix = RSPAMD_PREFIX, + *sharedir = RSPAMD_SHAREDIR; + const gchar *t; + + /* Try environment */ + t = g_environ_getenv(env, "SHAREDIR"); + if (t) { + sharedir = t; + } + + t = g_environ_getenv(env, "PLUGINSDIR"); + if (t) { + pluginsdir = t; + } + + t = g_environ_getenv(env, "RULESDIR"); + if (t) { + rulesdir = t; + } + + t = g_environ_getenv(env, "DBDIR"); + if (t) { + dbdir = t; + } + + t = g_environ_getenv(env, "RUNDIR"); + if (t) { + rundir = t; + } + + t = g_environ_getenv(env, "LUALIBDIR"); + if (t) { + lualibdir = t; + } + + t = g_environ_getenv(env, "LOGDIR"); + if (t) { + logdir = t; + } + + t = g_environ_getenv(env, "WWWDIR"); + if (t) { + wwwdir = t; + } + + t = g_environ_getenv(env, "CONFDIR"); + if (t) { + confdir = t; + } + + t = g_environ_getenv(env, "LOCAL_CONFDIR"); + if (t) { + local_confdir = t; + } + + + if (vars) { + t = g_hash_table_lookup(vars, "SHAREDIR"); + if (t) { + sharedir = t; + } + + t = g_hash_table_lookup(vars, "PLUGINSDIR"); + if (t) { + pluginsdir = t; + } + + t = g_hash_table_lookup(vars, "RULESDIR"); + if (t) { + rulesdir = t; + } + + t = g_hash_table_lookup(vars, "LUALIBDIR"); + if (t) { + lualibdir = t; + } + + t = g_hash_table_lookup(vars, "RUNDIR"); + if (t) { + rundir = t; + } + + t = g_hash_table_lookup(vars, "WWWDIR"); + if (t) { + wwwdir = t; + } + + t = g_hash_table_lookup(vars, "CONFDIR"); + if (t) { + confdir = t; + } + + t = g_hash_table_lookup(vars, "LOCAL_CONFDIR"); + if (t) { + local_confdir = t; + } + + t = g_hash_table_lookup(vars, "DBDIR"); + if (t) { + dbdir = t; + } + + t = g_hash_table_lookup(vars, "LOGDIR"); + if (t) { + logdir = t; + } + } + + lua_createtable(L, 0, 9); + + rspamd_lua_table_set(L, RSPAMD_SHAREDIR_INDEX, sharedir); + rspamd_lua_table_set(L, RSPAMD_CONFDIR_INDEX, confdir); + rspamd_lua_table_set(L, RSPAMD_LOCAL_CONFDIR_INDEX, local_confdir); + rspamd_lua_table_set(L, RSPAMD_RUNDIR_INDEX, rundir); + rspamd_lua_table_set(L, RSPAMD_DBDIR_INDEX, dbdir); + rspamd_lua_table_set(L, RSPAMD_LOGDIR_INDEX, logdir); + rspamd_lua_table_set(L, RSPAMD_WWWDIR_INDEX, wwwdir); + rspamd_lua_table_set(L, RSPAMD_PLUGINSDIR_INDEX, pluginsdir); + rspamd_lua_table_set(L, RSPAMD_RULESDIR_INDEX, rulesdir); + rspamd_lua_table_set(L, RSPAMD_LUALIBDIR_INDEX, lualibdir); + rspamd_lua_table_set(L, RSPAMD_PREFIX_INDEX, prefix); + + lua_setglobal(L, "rspamd_paths"); + } + + lua_getglobal(L, "rspamd_env"); + if (lua_isnil(L, -1)) { + lua_newtable(L); + + if (vars != NULL) { + GHashTableIter it; + gpointer k, v; + + g_hash_table_iter_init(&it, vars); + + while (g_hash_table_iter_next(&it, &k, &v)) { + rspamd_lua_table_set(L, k, v); + } + } + + gint hostlen = sysconf(_SC_HOST_NAME_MAX); + + if (hostlen <= 0) { + hostlen = 256; + } + else { + hostlen++; + } + + gchar *hostbuf = g_alloca(hostlen); + memset(hostbuf, 0, hostlen); + gethostname(hostbuf, hostlen - 1); + + rspamd_lua_table_set(L, "hostname", hostbuf); + + rspamd_lua_table_set(L, "version", RVERSION); + rspamd_lua_table_set(L, "ver_major", RSPAMD_VERSION_MAJOR); + rspamd_lua_table_set(L, "ver_minor", RSPAMD_VERSION_MINOR); + rspamd_lua_table_set(L, "ver_id", RID); + lua_pushstring(L, "ver_num"); + lua_pushinteger(L, RSPAMD_VERSION_NUM); + lua_settable(L, -3); + + if (env) { + gint lim = g_strv_length(env); + + for (gint i = 0; i < lim; i++) { + if (RSPAMD_LEN_CHECK_STARTS_WITH(env[i], strlen(env[i]), "RSPAMD_")) { + const char *var = env[i] + sizeof("RSPAMD_") - 1, *value; + gint varlen; + + varlen = strcspn(var, "="); + value = var + varlen; + + if (*value == '=') { + value++; + + lua_pushlstring(L, var, varlen); + lua_pushstring(L, value); + lua_settable(L, -3); + } + } + } + } + + if (lua_env) { + gint lim = g_strv_length(lua_env); + + for (gint i = 0; i < lim; i++) { + if (!rspamd_lua_load_env(L, lua_env[i], lua_gettop(L), err)) { + return FALSE; + } + } + } + + lua_setglobal(L, "rspamd_env"); + } + + lua_settop(L, orig_top); + g_strfreev(env); + + return TRUE; +} + +void rspamd_lua_set_globals(struct rspamd_config *cfg, lua_State *L) +{ + struct rspamd_config **pcfg; + gint orig_top = lua_gettop(L); + + /* First check for global variable 'config' */ + lua_getglobal(L, "config"); + if (lua_isnil(L, -1)) { + /* Assign global table to set up attributes */ + lua_newtable(L); + lua_setglobal(L, "config"); + } + + lua_getglobal(L, "metrics"); + if (lua_isnil(L, -1)) { + lua_newtable(L); + lua_setglobal(L, "metrics"); + } + + lua_getglobal(L, "composites"); + if (lua_isnil(L, -1)) { + lua_newtable(L); + lua_setglobal(L, "composites"); + } + + lua_getglobal(L, "rspamd_classifiers"); + if (lua_isnil(L, -1)) { + lua_newtable(L); + lua_setglobal(L, "rspamd_classifiers"); + } + + lua_getglobal(L, "classifiers"); + if (lua_isnil(L, -1)) { + lua_newtable(L); + lua_setglobal(L, "classifiers"); + } + + lua_getglobal(L, "rspamd_version"); + if (lua_isnil(L, -1)) { + lua_pushcfunction(L, rspamd_lua_rspamd_version); + lua_setglobal(L, "rspamd_version"); + } + + if (cfg != NULL) { + pcfg = lua_newuserdata(L, sizeof(struct rspamd_config *)); + rspamd_lua_setclass(L, "rspamd{config}", -1); + *pcfg = cfg; + lua_setglobal(L, "rspamd_config"); + } + + lua_settop(L, orig_top); +} + +#ifdef WITH_LUA_TRACE +static gint +lua_push_trace_data(lua_State *L) +{ + if (lua_traces) { + ucl_object_push_lua(L, lua_traces, true); + } + else { + lua_pushnil(L); + } + + return 1; +} +#endif + + +static void * +rspamd_lua_wipe_realloc(void *ud, + void *ptr, + size_t osize, + size_t nsize) RSPAMD_ATTR_ALLOC_SIZE(4); +static void * +rspamd_lua_wipe_realloc(void *ud, + void *ptr, + size_t osize, + size_t nsize) +{ + if (nsize == 0) { + if (ptr) { + rspamd_explicit_memzero(ptr, osize); + } + + free(ptr); + } + else if (ptr == NULL) { + return malloc(nsize); + } + else { + if (nsize < osize) { + /* Wipe on shrinking (actually never used) */ + rspamd_explicit_memzero(((unsigned char *) ptr) + nsize, osize - nsize); + } + + return realloc(ptr, nsize); + } + + return NULL; +} + +#ifndef WITH_LUAJIT +extern int luaopen_bit(lua_State *L); +#endif + +static unsigned int lua_initialized = 0; + +lua_State * +rspamd_lua_init(bool wipe_mem) +{ + lua_State *L; + + if (wipe_mem) { +#ifdef WITH_LUAJIT + /* TODO: broken on luajit without GC64 */ + L = luaL_newstate(); +#else + L = lua_newstate(rspamd_lua_wipe_realloc, NULL); +#endif + } + else { + L = luaL_newstate(); + } + + struct rspamd_lua_context *ctx; + + ctx = (struct rspamd_lua_context *) g_malloc0(sizeof(*ctx)); + ctx->L = L; + ctx->classes = kh_init(lua_class_set); + kh_resize(lua_class_set, ctx->classes, RSPAMD_LUA_NCLASSES); + DL_APPEND(rspamd_lua_global_ctx, ctx); + + lua_gc(L, LUA_GCSTOP, 0); + luaL_openlibs(L); + luaopen_logger(L); + luaopen_mempool(L); + luaopen_config(L); + luaopen_map(L); + luaopen_trie(L); + luaopen_task(L); + luaopen_textpart(L); + luaopen_mimepart(L); + luaopen_image(L); + luaopen_url(L); + luaopen_classifier(L); + luaopen_statfile(L); + luaopen_regexp(L); + luaopen_cdb(L); + luaopen_xmlrpc(L); + luaopen_http(L); + luaopen_redis(L); + luaopen_upstream(L); + lua_add_actions_global(L); + luaopen_dns_resolver(L); + luaopen_rsa(L); + luaopen_ip(L); + luaopen_expression(L); + luaopen_text(L); + luaopen_util(L); + luaopen_tcp(L); + luaopen_html(L); + luaopen_sqlite3(L); + luaopen_cryptobox(L); + luaopen_dns(L); + luaopen_udp(L); + luaopen_worker(L); + luaopen_kann(L); + luaopen_spf(L); + luaopen_tensor(L); + luaopen_parsers(L); + luaopen_compress(L); +#ifndef WITH_LUAJIT + rspamd_lua_add_preload(L, "bit", luaopen_bit); + lua_settop(L, 0); +#endif + + rspamd_lua_new_class(L, "rspamd{session}", NULL); + lua_pop(L, 1); + + rspamd_lua_add_preload(L, "lpeg", luaopen_lpeg); + luaopen_ucl(L); + rspamd_lua_add_preload(L, "ucl", luaopen_ucl); + + /* Add plugins global */ + lua_newtable(L); + lua_setglobal(L, "rspamd_plugins"); + + /* Set PRNG */ + lua_getglobal(L, "math"); + lua_pushstring(L, "randomseed"); /* Push math.randomseed function on top of the stack */ + lua_gettable(L, -2); + lua_pushinteger(L, ottery_rand_uint64()); + g_assert(lua_pcall(L, 1, 0, 0) == 0); + lua_pop(L, 1); /* math table */ + + /* Modules state */ + lua_newtable(L); + /* + * rspamd_plugins_state = { + * enabled = {}, + * disabled_unconfigured = {}, + * disabled_redis = {}, + * disabled_explicitly = {}, + * disabled_failed = {}, + * disabled_experimental = {}, + * disabled_unknown = {}, + * } + */ +#define ADD_TABLE(name) \ + do { \ + lua_pushstring(L, #name); \ + lua_newtable(L); \ + lua_settable(L, -3); \ + } while (0) + + ADD_TABLE(enabled); + ADD_TABLE(disabled_unconfigured); + ADD_TABLE(disabled_redis); + ADD_TABLE(disabled_explicitly); + ADD_TABLE(disabled_failed); + ADD_TABLE(disabled_experimental); + ADD_TABLE(disabled_unknown); + +#undef ADD_TABLE + lua_setglobal(L, rspamd_modules_state_global); + +#ifdef WITH_LUA_TRACE + lua_pushcfunction(L, lua_push_trace_data); + lua_setglobal(L, "get_traces"); +#endif + + lua_initialized++; + + return L; +} + +void rspamd_lua_close(lua_State *L) +{ + struct rspamd_lua_context *ctx = rspamd_lua_ctx_by_state(L); + + /* TODO: we will leak this memory, but I don't know how to resolve + * the chicked-egg problem when lua_close calls GC for many + * userdata that requires classes metatables to be represented + * For now, it is safe to leave it as is, I'm afraid + */ +#if 0 + int ref; + kh_foreach_value(ctx->classes, ref, { + luaL_unref(L, LUA_REGISTRYINDEX, ref); + }); +#endif + + lua_close(L); + DL_DELETE(rspamd_lua_global_ctx, ctx); + kh_destroy(lua_class_set, ctx->classes); + g_free(ctx); + + lua_initialized--; +} + +bool rspamd_lua_is_initialised(void) +{ + return lua_initialized != 0; +} + +void rspamd_lua_start_gc(struct rspamd_config *cfg) +{ + lua_State *L = (lua_State *) cfg->lua_state; + + lua_settop(L, 0); + /* Set up GC */ + lua_gc(L, LUA_GCCOLLECT, 0); + lua_gc(L, LUA_GCSETSTEPMUL, cfg->lua_gc_step); + lua_gc(L, LUA_GCSETPAUSE, cfg->lua_gc_pause); + lua_gc(L, LUA_GCRESTART, 0); +} + + +void rspamd_plugins_table_push_elt(lua_State *L, const gchar *field_name, + const gchar *new_elt) +{ + lua_getglobal(L, rspamd_modules_state_global); + + if (lua_istable(L, -1)) { + lua_pushstring(L, field_name); + lua_gettable(L, -2); + + if (lua_istable(L, -1)) { + lua_pushstring(L, new_elt); + lua_newtable(L); + lua_settable(L, -3); + lua_pop(L, 2); /* Global + element */ + } + else { + lua_pop(L, 2); /* Global + element */ + } + } + else { + lua_pop(L, 1); + } +} + +gboolean +rspamd_init_lua_filters(struct rspamd_config *cfg, bool force_load, bool strict) +{ + struct rspamd_config **pcfg; + struct script_module *module; + lua_State *L = cfg->lua_state; + gint err_idx, i; + + pcfg = lua_newuserdata(L, sizeof(struct rspamd_config *)); + rspamd_lua_setclass(L, "rspamd{config}", -1); + *pcfg = cfg; + lua_setglobal(L, "rspamd_config"); + + PTR_ARRAY_FOREACH(cfg->script_modules, i, module) + { + if (module->path) { + if (!force_load) { + if (!rspamd_config_is_module_enabled(cfg, module->name)) { + continue; + } + } + + lua_pushcfunction(L, &rspamd_lua_traceback); + err_idx = lua_gettop(L); + + gsize fsize; + guint8 *data = rspamd_file_xmap(module->path, + PROT_READ, &fsize, TRUE); + guchar digest[rspamd_cryptobox_HASHBYTES]; + gchar *lua_fname; + + if (data == NULL) { + msg_err_config("cannot mmap %s failed: %s", module->path, + strerror(errno)); + + lua_settop(L, err_idx - 1); /* Error function */ + + rspamd_plugins_table_push_elt(L, "disabled_failed", + module->name); + + if (strict) { + return FALSE; + } + + continue; + } + + module->digest = rspamd_mempool_alloc(cfg->cfg_pool, + rspamd_cryptobox_HASHBYTES * 2 + 1); + rspamd_cryptobox_hash(digest, data, fsize, NULL, 0); + rspamd_encode_hex_buf(digest, sizeof(digest), + module->digest, rspamd_cryptobox_HASHBYTES * 2 + 1); + module->digest[rspamd_cryptobox_HASHBYTES * 2] = '\0'; + lua_fname = g_malloc(strlen(module->path) + 2); + rspamd_snprintf(lua_fname, strlen(module->path) + 2, "@%s", + module->path); + + if (luaL_loadbuffer(L, data, fsize, lua_fname) != 0) { + msg_err_config("load of %s failed: %s", module->path, + lua_tostring(L, -1)); + lua_settop(L, err_idx - 1); /* Error function */ + + rspamd_plugins_table_push_elt(L, "disabled_failed", + module->name); + munmap(data, fsize); + g_free(lua_fname); + + if (strict) { + return FALSE; + } + + continue; + } + + munmap(data, fsize); + g_free(lua_fname); + + if (lua_pcall(L, 0, 0, err_idx) != 0) { + msg_err_config("init of %s failed: %s", + module->path, + lua_tostring(L, -1)); + + lua_settop(L, err_idx - 1); + rspamd_plugins_table_push_elt(L, "disabled_failed", + module->name); + + if (strict) { + return FALSE; + } + + continue; + } + + if (!force_load) { + msg_info_config("init lua module %s from %s; digest: %*s", + module->name, + module->path, + 10, module->digest); + } + + lua_pop(L, 1); /* Error function */ + } + } + + return TRUE; +} + +void rspamd_lua_dumpstack(lua_State *L) +{ + gint i, t, r = 0; + gint top = lua_gettop(L); + gchar buf[BUFSIZ]; + + r += rspamd_snprintf(buf + r, sizeof(buf) - r, "lua stack: "); + for (i = 1; i <= top; i++) { /* repeat for each level */ + t = lua_type(L, i); + switch (t) { + case LUA_TSTRING: /* strings */ + r += rspamd_snprintf(buf + r, + sizeof(buf) - r, + "str: %s", + lua_tostring(L, i)); + break; + + case LUA_TBOOLEAN: /* booleans */ + r += rspamd_snprintf(buf + r, sizeof(buf) - r, + lua_toboolean(L, i) ? "bool: true" : "bool: false"); + break; + + case LUA_TNUMBER: /* numbers */ + r += rspamd_snprintf(buf + r, + sizeof(buf) - r, + "number: %.2f", + lua_tonumber(L, i)); + break; + + default: /* other values */ + r += rspamd_snprintf(buf + r, + sizeof(buf) - r, + "type: %s", + lua_typename(L, t)); + break; + } + if (i < top) { + r += rspamd_snprintf(buf + r, sizeof(buf) - r, + " -> "); /* put a separator */ + } + } + + msg_info("%*s", r, buf); +} + +gpointer +rspamd_lua_check_class(lua_State *L, gint index, const gchar *name) +{ + gpointer p; + khiter_t k; + + if (lua_type(L, index) == LUA_TUSERDATA) { + p = lua_touserdata(L, index); + if (p) { + if (lua_getmetatable(L, index)) { + struct rspamd_lua_context *ctx = rspamd_lua_ctx_by_state(L); + + k = kh_get(lua_class_set, ctx->classes, name); + + if (k == kh_end(ctx->classes)) { + lua_pop(L, 1); + + return NULL; + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, kh_value(ctx->classes, k)); + + if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */ + lua_pop(L, 2); /* remove both metatables */ + return p; + } + lua_pop(L, 2); + } + } + } + return NULL; +} + +int rspamd_lua_typerror(lua_State *L, int narg, const char *tname) +{ + const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, + luaL_typename(L, narg)); + return luaL_argerror(L, narg, msg); +} + + +void rspamd_lua_add_preload(lua_State *L, const gchar *name, lua_CFunction func) +{ + lua_getglobal(L, "package"); + lua_pushstring(L, "preload"); + lua_gettable(L, -2); + lua_pushcfunction(L, func); + lua_setfield(L, -2, name); + lua_pop(L, 2); /* preload key + global package */ +} + + +gboolean +rspamd_lua_parse_table_arguments(lua_State *L, gint pos, + GError **err, + enum rspamd_lua_parse_arguments_flags how, + const gchar *extraction_pattern, ...) +{ + const gchar *p, *key = NULL, *end, *cls; + va_list ap; + gboolean required = FALSE, failed = FALSE, is_table; + gchar classbuf[128]; + enum { + read_key = 0, + read_arg, + read_class_start, + read_class, + read_semicolon + } state = read_key; + gsize keylen = 0, *valuelen, clslen; + gint idx = 0, t, direct_userdata = 0; + + g_assert(extraction_pattern != NULL); + + if (pos < 0) { + /* Get absolute pos */ + pos = lua_gettop(L) + pos + 1; + } + + if (lua_type(L, pos) == LUA_TTABLE) { + is_table = TRUE; + } + else { + is_table = FALSE; + idx = pos; + } + + p = extraction_pattern; + end = p + strlen(extraction_pattern); + + va_start(ap, extraction_pattern); + + while (p <= end) { + switch (state) { + case read_key: + if (*p == '=') { + if (key == NULL) { + g_set_error(err, lua_error_quark(), 1, "cannot read key"); + va_end(ap); + + return FALSE; + } + + state = read_arg; + keylen = p - key; + } + else if (*p == '*' && key == NULL) { + required = TRUE; + } + else if (key == NULL) { + key = p; + } + p++; + break; + case read_arg: + g_assert(keylen != 0); + + if (is_table) { + lua_pushlstring(L, key, keylen); + lua_gettable(L, pos); + idx = -1; + } + + t = lua_type(L, idx); + + switch (*p) { + case 'S': + if (t == LUA_TSTRING) { + *(va_arg(ap, const gchar **)) = lua_tostring(L, idx); + } + else if (t == LUA_TNIL || t == LUA_TNONE) { + failed = TRUE; + + if (how != RSPAMD_LUA_PARSE_ARGUMENTS_IGNORE_MISSING) { + *(va_arg(ap, const gchar **)) = NULL; + } + else { + (void) va_arg(ap, gchar **); + } + } + else { + g_set_error(err, + lua_error_quark(), + 1, + "bad type for key:" + " %.*s: '%s', '%s' is expected", + (gint) keylen, + key, + lua_typename(L, lua_type(L, idx)), "string"); + va_end(ap); + + return FALSE; + } + + if (is_table) { + lua_pop(L, 1); + } + break; + + case 'I': + if (t == LUA_TNUMBER) { + *(va_arg(ap, gint64 *)) = lua_tointeger(L, idx); + } + else if (t == LUA_TNIL || t == LUA_TNONE) { + failed = TRUE; + if (how != RSPAMD_LUA_PARSE_ARGUMENTS_IGNORE_MISSING) { + *(va_arg(ap, gint64 *)) = 0; + } + else { + (void) va_arg(ap, gint64 *); + } + } + else { + g_set_error(err, + lua_error_quark(), + 1, + "bad type for key:" + " %.*s: '%s', '%s' is expected", + (gint) keylen, + key, + lua_typename(L, lua_type(L, idx)), + "int64"); + va_end(ap); + + return FALSE; + } + if (is_table) { + lua_pop(L, 1); + } + break; + + case 'i': + if (t == LUA_TNUMBER) { + *(va_arg(ap, gint32 *)) = lua_tointeger(L, idx); + } + else if (t == LUA_TNIL || t == LUA_TNONE) { + failed = TRUE; + if (how != RSPAMD_LUA_PARSE_ARGUMENTS_IGNORE_MISSING) { + *(va_arg(ap, gint32 *)) = 0; + } + else { + (void) va_arg(ap, gint32 *); + } + } + else { + g_set_error(err, + lua_error_quark(), + 1, + "bad type for key:" + " %.*s: '%s', '%s' is expected", + (gint) keylen, + key, + lua_typename(L, lua_type(L, idx)), + "int64"); + va_end(ap); + + return FALSE; + } + if (is_table) { + lua_pop(L, 1); + } + break; + + case 'F': + if (t == LUA_TFUNCTION) { + if (!is_table) { + lua_pushvalue(L, idx); + } + + *(va_arg(ap, gint *)) = luaL_ref(L, LUA_REGISTRYINDEX); + } + else if (t == LUA_TNIL || t == LUA_TNONE) { + failed = TRUE; + + if (how != RSPAMD_LUA_PARSE_ARGUMENTS_IGNORE_MISSING) { + *(va_arg(ap, gint *)) = -1; + } + else { + (void) va_arg(ap, gint *); + } + + if (is_table) { + lua_pop(L, 1); + } + } + else { + g_set_error(err, + lua_error_quark(), + 1, + "bad type for key:" + " %.*s: '%s', '%s' is expected", + (gint) keylen, + key, + lua_typename(L, lua_type(L, idx)), + "function"); + va_end(ap); + if (is_table) { + lua_pop(L, 1); + } + + return FALSE; + } + + /* luaL_ref pops argument from the stack */ + break; + + case 'B': + if (t == LUA_TBOOLEAN) { + *(va_arg(ap, gboolean *)) = lua_toboolean(L, idx); + } + else if (t == LUA_TNIL || t == LUA_TNONE) { + failed = TRUE; + + if (how != RSPAMD_LUA_PARSE_ARGUMENTS_IGNORE_MISSING) { + *(va_arg(ap, gboolean *)) = 0; + } + } + else { + g_set_error(err, + lua_error_quark(), + 1, + "bad type for key:" + " %.*s: '%s', '%s' is expected", + (gint) keylen, + key, + lua_typename(L, lua_type(L, idx)), + "bool"); + va_end(ap); + + return FALSE; + } + + if (is_table) { + lua_pop(L, 1); + } + break; + + case 'N': + if (t == LUA_TNUMBER) { + *(va_arg(ap, gdouble *)) = lua_tonumber(L, idx); + } + else if (t == LUA_TNIL || t == LUA_TNONE) { + failed = TRUE; + + if (how != RSPAMD_LUA_PARSE_ARGUMENTS_IGNORE_MISSING) { + *(va_arg(ap, gdouble *)) = 0; + } + else { + (void) va_arg(ap, gdouble *); + } + } + else { + g_set_error(err, + lua_error_quark(), + 1, + "bad type for key:" + " %.*s: '%s', '%s' is expected", + (gint) keylen, + key, + lua_typename(L, lua_type(L, idx)), + "double"); + va_end(ap); + + return FALSE; + } + + if (is_table) { + lua_pop(L, 1); + } + break; + + case 'D': + if (t == LUA_TNUMBER) { + *(va_arg(ap, gdouble *)) = lua_tonumber(L, idx); + } + else if (t == LUA_TNIL || t == LUA_TNONE) { + failed = TRUE; + + if (how != RSPAMD_LUA_PARSE_ARGUMENTS_IGNORE_MISSING) { + *(va_arg(ap, gdouble *)) = NAN; + } + else { + (void) va_arg(ap, gdouble *); + } + } + else { + g_set_error(err, + lua_error_quark(), + 1, + "bad type for key:" + " %.*s: '%s', '%s' is expected", + (gint) keylen, + key, + lua_typename(L, lua_type(L, idx)), + "double"); + va_end(ap); + + return FALSE; + } + + if (is_table) { + lua_pop(L, 1); + } + break; + + case 'V': + valuelen = va_arg(ap, gsize *); + + if (t == LUA_TSTRING) { + *(va_arg(ap, const gchar **)) = lua_tolstring(L, idx, + valuelen); + } + else if (t == LUA_TNIL || t == LUA_TNONE) { + failed = TRUE; + + if (how != RSPAMD_LUA_PARSE_ARGUMENTS_IGNORE_MISSING) { + *(va_arg(ap, const char **)) = NULL; + *valuelen = 0; + } + else { + (void) va_arg(ap, const char **); + } + } + else { + g_set_error(err, + lua_error_quark(), + 1, + "bad type for key:" + " %.*s: '%s', '%s' is expected", + (gint) keylen, + key, + lua_typename(L, lua_type(L, idx)), + "string"); + va_end(ap); + + return FALSE; + } + + if (is_table) { + lua_pop(L, 1); + } + break; + case 'O': + if (t != LUA_TNONE) { + *(va_arg(ap, ucl_object_t **)) = ucl_object_lua_import(L, + idx); + } + else { + failed = TRUE; + + if (how != RSPAMD_LUA_PARSE_ARGUMENTS_IGNORE_MISSING) { + *(va_arg(ap, ucl_object_t **)) = NULL; + } + else { + (void) va_arg(ap, ucl_object_t **); + } + } + + if (is_table) { + lua_pop(L, 1); + } + break; + case 'U': + if (t == LUA_TNIL || t == LUA_TNONE) { + failed = TRUE; + + if (how != RSPAMD_LUA_PARSE_ARGUMENTS_IGNORE_MISSING) { + *(va_arg(ap, void **)) = NULL; + } + else { + (void) va_arg(ap, void **); + } + } + else if (t != LUA_TUSERDATA) { + g_set_error(err, + lua_error_quark(), + 1, + "bad type for key:" + " %.*s: '%s', '%s' is expected", + (gint) keylen, + key, + lua_typename(L, lua_type(L, idx)), + "int64"); + va_end(ap); + + return FALSE; + } + + state = read_class_start; + clslen = 0; + direct_userdata = 0; + cls = NULL; + p++; + continue; + case 'u': + if (t == LUA_TNIL || t == LUA_TNONE) { + failed = TRUE; + + if (how != RSPAMD_LUA_PARSE_ARGUMENTS_IGNORE_MISSING) { + *(va_arg(ap, void **)) = NULL; + } + else { + (void) va_arg(ap, void **); + } + } + else if (t != LUA_TUSERDATA) { + g_set_error(err, + lua_error_quark(), + 1, + "bad type for key:" + " %.*s: '%s', '%s' is expected", + (gint) keylen, + key, + lua_typename(L, lua_type(L, idx)), + "int64"); + va_end(ap); + + return FALSE; + } + + state = read_class_start; + clslen = 0; + direct_userdata = 1; + cls = NULL; + p++; + continue; + default: + g_assert(0); + break; + } + + if (failed && required) { + g_set_error(err, lua_error_quark(), 2, "required parameter " + "%.*s is missing", + (gint) keylen, key); + va_end(ap); + + return FALSE; + } + + if (!is_table) { + idx++; + } + + /* Reset read params */ + state = read_semicolon; + failed = FALSE; + required = FALSE; + keylen = 0; + key = NULL; + p++; + break; + + case read_class_start: + if (*p == '{') { + cls = p + 1; + state = read_class; + } + else { + if (is_table) { + lua_pop(L, 1); + } + + g_set_error(err, lua_error_quark(), 2, "missing classname for " + "%.*s", + (gint) keylen, key); + va_end(ap); + + return FALSE; + } + p++; + break; + + case read_class: + if (*p == '}') { + clslen = p - cls; + if (clslen == 0) { + if (is_table) { + lua_pop(L, 1); + } + + g_set_error(err, + lua_error_quark(), + 2, + "empty classname for " + "%*.s", + (gint) keylen, + key); + va_end(ap); + + return FALSE; + } + + rspamd_snprintf(classbuf, sizeof(classbuf), "rspamd{%*s}", + (gint) clslen, cls); + + + /* + * We skip class check here for speed in non-table mode + */ + if (!failed && (!is_table || + rspamd_lua_check_class(L, idx, classbuf))) { + if (direct_userdata) { + void **arg_p = (va_arg(ap, void **)); + *arg_p = lua_touserdata(L, idx); + } + else { + *(va_arg(ap, + void **)) = *(void **) lua_touserdata(L, idx); + } + } + else { + if (!failed) { + g_set_error(err, + lua_error_quark(), + 2, + "invalid class for key %.*s, expected %s, got %s", + (gint) keylen, + key, + classbuf, + rspamd_lua_class_tostring_buf(L, FALSE, idx)); + va_end(ap); + + return FALSE; + } + } + + if (is_table) { + lua_pop(L, 1); + } + else { + idx++; + } + + if (failed && required) { + g_set_error(err, + lua_error_quark(), + 2, + "required parameter " + "%.*s is missing", + (gint) keylen, + key); + va_end(ap); + + return FALSE; + } + + /* Reset read params */ + state = read_semicolon; + failed = FALSE; + required = FALSE; + keylen = 0; + key = NULL; + } + p++; + break; + + case read_semicolon: + if (*p == ';' || p == end) { + state = read_key; + key = NULL; + keylen = 0; + failed = FALSE; + } + else { + g_set_error(err, lua_error_quark(), 2, "bad format string: %s," + " at char %c, position %d", + extraction_pattern, *p, (int) (p - extraction_pattern)); + va_end(ap); + + return FALSE; + } + + p++; + break; + } + } + + va_end(ap); + + return TRUE; +} + +static void +rspamd_lua_traceback_string(lua_State *L, luaL_Buffer *buf) +{ + gint i = 1, r; + lua_Debug d; + gchar tmp[256]; + + while (lua_getstack(L, i++, &d)) { + lua_getinfo(L, "nSl", &d); + r = rspamd_snprintf(tmp, sizeof(tmp), " [%d]:{%s:%d - %s [%s]};", + i - 1, d.short_src, d.currentline, + (d.name ? d.name : "<unknown>"), d.what); + luaL_addlstring(buf, tmp, r); + } +} + +gint rspamd_lua_traceback(lua_State *L) +{ + luaL_Buffer b; + + luaL_buffinit(L, &b); + rspamd_lua_get_traceback_string(L, &b); + luaL_pushresult(&b); + + return 1; +} + +void rspamd_lua_get_traceback_string(lua_State *L, luaL_Buffer *buf) +{ + const gchar *msg = lua_tostring(L, -1); + + if (msg) { + luaL_addstring(buf, msg); + lua_pop(L, 1); /* Error string */ + } + else { + luaL_addstring(buf, "unknown error"); + } + + luaL_addstring(buf, "; trace:"); + rspamd_lua_traceback_string(L, buf); +} + +guint rspamd_lua_table_size(lua_State *L, gint tbl_pos) +{ + guint tbl_size = 0; + + if (!lua_istable(L, tbl_pos)) { + return 0; + } + +#if LUA_VERSION_NUM >= 502 + tbl_size = lua_rawlen(L, tbl_pos); +#else + tbl_size = lua_objlen(L, tbl_pos); +#endif + + return tbl_size; +} + +static void * +rspamd_lua_check_udata_common(lua_State *L, gint pos, const gchar *classname, + gboolean fatal) +{ + void *p = lua_touserdata(L, pos); + guint i, top = lua_gettop(L); + khiter_t k; + + if (p == NULL) { + goto err; + } + else { + /* Match class */ + if (lua_getmetatable(L, pos)) { + struct rspamd_lua_context *ctx = rspamd_lua_ctx_by_state(L); + + k = kh_get(lua_class_set, ctx->classes, classname); + + if (k == kh_end(ctx->classes)) { + goto err; + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, kh_value(ctx->classes, k)); + + if (!lua_rawequal(L, -1, -2)) { + goto err; + } + } + else { + goto err; + } + } + + lua_settop(L, top); + + return p; + +err: + if (fatal) { + const gchar *actual_classname = NULL; + + if (lua_type(L, pos) == LUA_TUSERDATA && lua_getmetatable(L, pos)) { + lua_pushstring(L, "__index"); + lua_gettable(L, -2); + lua_pushstring(L, "class"); + lua_gettable(L, -2); + actual_classname = lua_tostring(L, -1); + } + else { + actual_classname = lua_typename(L, lua_type(L, pos)); + } + + luaL_Buffer buf; + gchar tmp[512]; + gint r; + + luaL_buffinit(L, &buf); + r = rspamd_snprintf(tmp, sizeof(tmp), + "expected %s at position %d, but userdata has " + "%s metatable; trace: ", + classname, pos, actual_classname); + luaL_addlstring(&buf, tmp, r); + rspamd_lua_traceback_string(L, &buf); + r = rspamd_snprintf(tmp, sizeof(tmp), " stack(%d): ", top); + luaL_addlstring(&buf, tmp, r); + + for (i = 1; i <= MIN(top, 10); i++) { + if (lua_type(L, i) == LUA_TUSERDATA) { + const char *clsname; + + if (lua_getmetatable(L, i)) { + lua_pushstring(L, "__index"); + lua_gettable(L, -2); + lua_pushstring(L, "class"); + lua_gettable(L, -2); + clsname = lua_tostring(L, -1); + } + else { + clsname = lua_typename(L, lua_type(L, i)); + } + + r = rspamd_snprintf(tmp, sizeof(tmp), "[%d: ud=%s] ", i, + clsname); + luaL_addlstring(&buf, tmp, r); + } + else { + r = rspamd_snprintf(tmp, sizeof(tmp), "[%d: %s] ", i, + lua_typename(L, lua_type(L, i))); + luaL_addlstring(&buf, tmp, r); + } + } + + luaL_pushresult(&buf); + msg_err("lua type error: %s", lua_tostring(L, -1)); + } + + lua_settop(L, top); + + return NULL; +} + +void * +rspamd_lua_check_udata(lua_State *L, gint pos, const gchar *classname) +{ + return rspamd_lua_check_udata_common(L, pos, classname, TRUE); +} + +void * +rspamd_lua_check_udata_maybe(lua_State *L, gint pos, const gchar *classname) +{ + return rspamd_lua_check_udata_common(L, pos, classname, FALSE); +} + +struct rspamd_async_session * +lua_check_session(lua_State *L, gint pos) +{ + void *ud = rspamd_lua_check_udata(L, pos, "rspamd{session}"); + luaL_argcheck(L, ud != NULL, pos, "'session' expected"); + return ud ? *((struct rspamd_async_session **) ud) : NULL; +} + +struct ev_loop * +lua_check_ev_base(lua_State *L, gint pos) +{ + void *ud = rspamd_lua_check_udata(L, pos, "rspamd{ev_base}"); + luaL_argcheck(L, ud != NULL, pos, "'event_base' expected"); + return ud ? *((struct ev_loop **) ud) : NULL; +} + +static void rspamd_lua_run_postloads_error(struct thread_entry *thread, int ret, const char *msg); + +void rspamd_lua_run_postloads(lua_State *L, struct rspamd_config *cfg, + struct ev_loop *ev_base, struct rspamd_worker *w) +{ + struct rspamd_config_cfg_lua_script *sc; + struct rspamd_config **pcfg; + struct ev_loop **pev_base; + struct rspamd_worker **pw; + + /* Execute post load scripts */ + LL_FOREACH(cfg->on_load_scripts, sc) + { + struct thread_entry *thread = lua_thread_pool_get_for_config(cfg); + thread->error_callback = rspamd_lua_run_postloads_error; + L = thread->lua_state; + + lua_rawgeti(L, LUA_REGISTRYINDEX, sc->cbref); + pcfg = lua_newuserdata(L, sizeof(*pcfg)); + *pcfg = cfg; + rspamd_lua_setclass(L, "rspamd{config}", -1); + + pev_base = lua_newuserdata(L, sizeof(*pev_base)); + *pev_base = ev_base; + rspamd_lua_setclass(L, "rspamd{ev_base}", -1); + + pw = lua_newuserdata(L, sizeof(*pw)); + *pw = w; + rspamd_lua_setclass(L, "rspamd{worker}", -1); + + lua_thread_call(thread, 3); + } +} + + +void rspamd_lua_run_config_post_init(lua_State *L, struct rspamd_config *cfg) +{ + struct rspamd_config_cfg_lua_script *sc; + struct rspamd_config **pcfg; + + LL_FOREACH(cfg->post_init_scripts, sc) + { + lua_pushcfunction(L, &rspamd_lua_traceback); + gint err_idx = lua_gettop(L); + + lua_rawgeti(L, LUA_REGISTRYINDEX, sc->cbref); + pcfg = lua_newuserdata(L, sizeof(*pcfg)); + *pcfg = cfg; + rspamd_lua_setclass(L, "rspamd{config}", -1); + + if (lua_pcall(L, 1, 0, err_idx) != 0) { + msg_err_config("cannot run config post init script: %s; priority = %d", + lua_tostring(L, -1), sc->priority); + } + + lua_settop(L, err_idx - 1); + } +} + + +void rspamd_lua_run_config_unload(lua_State *L, struct rspamd_config *cfg) +{ + struct rspamd_config_cfg_lua_script *sc; + struct rspamd_config **pcfg; + + LL_FOREACH(cfg->config_unload_scripts, sc) + { + lua_pushcfunction(L, &rspamd_lua_traceback); + gint err_idx = lua_gettop(L); + + lua_rawgeti(L, LUA_REGISTRYINDEX, sc->cbref); + pcfg = lua_newuserdata(L, sizeof(*pcfg)); + *pcfg = cfg; + rspamd_lua_setclass(L, "rspamd{config}", -1); + + if (lua_pcall(L, 1, 0, err_idx) != 0) { + msg_err_config("cannot run config post init script: %s", + lua_tostring(L, -1)); + } + + lua_settop(L, err_idx - 1); + } +} + +static void +rspamd_lua_run_postloads_error(struct thread_entry *thread, int ret, const char *msg) +{ + struct rspamd_config *cfg = thread->cfg; + + msg_err_config("error executing post load code: %s", msg); +} + + +struct rspamd_lua_ref_cbdata { + lua_State *L; + gint cbref; +}; + +static void +rspamd_lua_ref_dtor(gpointer p) +{ + struct rspamd_lua_ref_cbdata *cbdata = p; + + luaL_unref(cbdata->L, LUA_REGISTRYINDEX, cbdata->cbref); +} + +void rspamd_lua_add_ref_dtor(lua_State *L, rspamd_mempool_t *pool, + gint ref) +{ + struct rspamd_lua_ref_cbdata *cbdata; + + if (ref != -1) { + cbdata = rspamd_mempool_alloc(pool, sizeof(*cbdata)); + cbdata->cbref = ref; + cbdata->L = L; + + rspamd_mempool_add_destructor(pool, rspamd_lua_ref_dtor, cbdata); + } +} + +gboolean +rspamd_lua_require_function(lua_State *L, const gchar *modname, + const gchar *funcname) +{ + gint table_pos, err_pos; + + lua_pushcfunction(L, &rspamd_lua_traceback); + err_pos = lua_gettop(L); + lua_getglobal(L, "require"); + + if (lua_isnil(L, -1)) { + lua_remove(L, err_pos); + lua_pop(L, 1); + + return FALSE; + } + + lua_pushstring(L, modname); + + /* Now try to call */ + if (lua_pcall(L, 1, 1, 0) != 0) { + lua_remove(L, err_pos); + msg_warn("require of %s.%s failed: %s", modname, + funcname, lua_tostring(L, -1)); + lua_pop(L, 1); + + return FALSE; + } + + lua_remove(L, err_pos); + + /* Now we should have a table with results */ + if (funcname) { + if (!lua_istable(L, -1)) { + msg_warn("require of %s.%s failed: not a table but %s", modname, + funcname, lua_typename(L, lua_type(L, -1))); + + lua_pop(L, 1); + + return FALSE; + } + + table_pos = lua_gettop(L); + lua_pushstring(L, funcname); + lua_gettable(L, -2); + + if (lua_type(L, -1) == LUA_TFUNCTION) { + /* Remove table, preserve just a function */ + lua_remove(L, table_pos); + + return TRUE; + } + else { + msg_warn("require of %s.%s failed: not a function but %s", modname, + funcname, lua_typename(L, lua_type(L, -1))); + } + + lua_pop(L, 2); + + return FALSE; + } + else if (lua_isfunction(L, -1)) { + return TRUE; + } + else { + msg_warn("require of %s failed: not a function but %s", modname, + lua_typename(L, lua_type(L, -1))); + lua_pop(L, 1); + + return FALSE; + } +} + +gint rspamd_lua_function_ref_from_str(lua_State *L, const gchar *str, gsize slen, + const gchar *modname, GError **err) +{ + gint err_idx, ref_idx; + + lua_pushcfunction(L, &rspamd_lua_traceback); + err_idx = lua_gettop(L); + + /* Load file */ + if (luaL_loadbuffer(L, str, slen, modname) != 0) { + g_set_error(err, + lua_error_quark(), + EINVAL, + "%s: cannot load lua script: %s", + modname, + lua_tostring(L, -1)); + lua_settop(L, err_idx - 1); /* Error function */ + + return LUA_NOREF; + } + + /* Now call it */ + if (lua_pcall(L, 0, 1, err_idx) != 0) { + g_set_error(err, + lua_error_quark(), + EINVAL, + "%s: cannot init lua script: %s", + modname, + lua_tostring(L, -1)); + lua_settop(L, err_idx - 1); + + return LUA_NOREF; + } + + if (!lua_isfunction(L, -1)) { + g_set_error(err, + lua_error_quark(), + EINVAL, + "%s: cannot init lua script: " + "must return function not %s", + modname, + lua_typename(L, lua_type(L, -1))); + lua_settop(L, err_idx - 1); + + return LUA_NOREF; + } + + ref_idx = luaL_ref(L, LUA_REGISTRYINDEX); + lua_settop(L, err_idx - 1); + + return ref_idx; +} + + +gboolean +rspamd_lua_try_load_redis(lua_State *L, const ucl_object_t *obj, + struct rspamd_config *cfg, gint *ref_id) +{ + gint err_idx; + struct rspamd_config **pcfg; + + lua_pushcfunction(L, &rspamd_lua_traceback); + err_idx = lua_gettop(L); + + /* Obtain function */ + if (!rspamd_lua_require_function(L, "lua_redis", "try_load_redis_servers")) { + msg_err_config("cannot require lua_redis"); + lua_pop(L, 2); + + return FALSE; + } + + /* Function arguments */ + ucl_object_push_lua(L, obj, false); + pcfg = lua_newuserdata(L, sizeof(*pcfg)); + rspamd_lua_setclass(L, "rspamd{config}", -1); + *pcfg = cfg; + lua_pushboolean(L, false); /* no_fallback */ + + if (lua_pcall(L, 3, 1, err_idx) != 0) { + msg_err_config("cannot call lua try_load_redis_servers script: %s", + lua_tostring(L, -1)); + lua_settop(L, 0); + + return FALSE; + } + + if (lua_istable(L, -1)) { + if (ref_id) { + /* Ref table */ + lua_pushvalue(L, -1); + *ref_id = luaL_ref(L, LUA_REGISTRYINDEX); + lua_settop(L, 0); + } + else { + /* Leave it on the stack */ + lua_insert(L, err_idx); + lua_settop(L, err_idx); + } + + return TRUE; + } + else { + lua_settop(L, 0); + } + + return FALSE; +} + +void rspamd_lua_push_full_word(lua_State *L, rspamd_stat_token_t *w) +{ + gint fl_cnt; + + lua_createtable(L, 4, 0); + + if (w->stemmed.len > 0) { + lua_pushlstring(L, w->stemmed.begin, w->stemmed.len); + lua_rawseti(L, -2, 1); + } + else { + lua_pushstring(L, ""); + lua_rawseti(L, -2, 1); + } + + if (w->normalized.len > 0) { + lua_pushlstring(L, w->normalized.begin, w->normalized.len); + lua_rawseti(L, -2, 2); + } + else { + lua_pushstring(L, ""); + lua_rawseti(L, -2, 2); + } + + if (w->original.len > 0) { + lua_pushlstring(L, w->original.begin, w->original.len); + lua_rawseti(L, -2, 3); + } + else { + lua_pushstring(L, ""); + lua_rawseti(L, -2, 3); + } + + /* Flags part */ + fl_cnt = 1; + lua_createtable(L, 4, 0); + + if (w->flags & RSPAMD_STAT_TOKEN_FLAG_NORMALISED) { + lua_pushstring(L, "normalised"); + lua_rawseti(L, -2, fl_cnt++); + } + if (w->flags & RSPAMD_STAT_TOKEN_FLAG_BROKEN_UNICODE) { + lua_pushstring(L, "broken_unicode"); + lua_rawseti(L, -2, fl_cnt++); + } + if (w->flags & RSPAMD_STAT_TOKEN_FLAG_UTF) { + lua_pushstring(L, "utf"); + lua_rawseti(L, -2, fl_cnt++); + } + if (w->flags & RSPAMD_STAT_TOKEN_FLAG_TEXT) { + lua_pushstring(L, "text"); + lua_rawseti(L, -2, fl_cnt++); + } + if (w->flags & RSPAMD_STAT_TOKEN_FLAG_HEADER) { + lua_pushstring(L, "header"); + lua_rawseti(L, -2, fl_cnt++); + } + if (w->flags & (RSPAMD_STAT_TOKEN_FLAG_META | RSPAMD_STAT_TOKEN_FLAG_LUA_META)) { + lua_pushstring(L, "meta"); + lua_rawseti(L, -2, fl_cnt++); + } + if (w->flags & RSPAMD_STAT_TOKEN_FLAG_STOP_WORD) { + lua_pushstring(L, "stop_word"); + lua_rawseti(L, -2, fl_cnt++); + } + if (w->flags & RSPAMD_STAT_TOKEN_FLAG_INVISIBLE_SPACES) { + lua_pushstring(L, "invisible_spaces"); + lua_rawseti(L, -2, fl_cnt++); + } + if (w->flags & RSPAMD_STAT_TOKEN_FLAG_STEMMED) { + lua_pushstring(L, "stemmed"); + lua_rawseti(L, -2, fl_cnt++); + } + + lua_rawseti(L, -2, 4); +} + +gint rspamd_lua_push_words(lua_State *L, GArray *words, + enum rspamd_lua_words_type how) +{ + rspamd_stat_token_t *w; + guint i, cnt; + + lua_createtable(L, words->len, 0); + + for (i = 0, cnt = 1; i < words->len; i++) { + w = &g_array_index(words, rspamd_stat_token_t, i); + + switch (how) { + case RSPAMD_LUA_WORDS_STEM: + if (w->stemmed.len > 0) { + lua_pushlstring(L, w->stemmed.begin, w->stemmed.len); + lua_rawseti(L, -2, cnt++); + } + break; + case RSPAMD_LUA_WORDS_NORM: + if (w->normalized.len > 0) { + lua_pushlstring(L, w->normalized.begin, w->normalized.len); + lua_rawseti(L, -2, cnt++); + } + break; + case RSPAMD_LUA_WORDS_RAW: + if (w->original.len > 0) { + lua_pushlstring(L, w->original.begin, w->original.len); + lua_rawseti(L, -2, cnt++); + } + break; + case RSPAMD_LUA_WORDS_FULL: + rspamd_lua_push_full_word(L, w); + /* Push to the resulting vector */ + lua_rawseti(L, -2, cnt++); + break; + default: + break; + } + } + + return 1; +} + +gchar * +rspamd_lua_get_module_name(lua_State *L) +{ + lua_Debug d; + gchar *p; + gchar func_buf[128]; + + if (lua_getstack(L, 1, &d) == 1) { + (void) lua_getinfo(L, "Sl", &d); + if ((p = strrchr(d.short_src, '/')) == NULL) { + p = d.short_src; + } + else { + p++; + } + + if (strlen(p) > 20) { + rspamd_snprintf(func_buf, sizeof(func_buf), "%10s...]:%d", p, + d.currentline); + } + else { + rspamd_snprintf(func_buf, sizeof(func_buf), "%s:%d", p, + d.currentline); + } + + return g_strdup(func_buf); + } + + return NULL; +} + +bool rspamd_lua_universal_pcall(lua_State *L, gint cbref, const gchar *strloc, + gint nret, const gchar *args, GError **err, ...) +{ + va_list ap; + const gchar *argp = args, *classname; + gint err_idx, nargs = 0; + gpointer *cls_ptr; + gsize sz; + + /* Error function */ + lua_pushcfunction(L, &rspamd_lua_traceback); + err_idx = lua_gettop(L); + + va_start(ap, err); + /* Called function */ + if (cbref > 0) { + lua_rawgeti(L, LUA_REGISTRYINDEX, cbref); + } + else { + /* Assume that function was on top of the stack */ + lua_pushvalue(L, err_idx - 1); + } + /* + * Possible arguments + * - i - lua_integer, argument - gint64 + * - n - lua_number, argument - gdouble + * - s - lua_string, argument - const gchar * (zero terminated) + * - l - lua_lstring, argument - (size_t + const gchar *) pair + * - u - lua_userdata, argument - (const char * + void *) - classname + pointer + * - b - lua_boolean, argument - gboolean (not bool due to varargs promotion) + * - f - lua_function, argument - int - position of the function on stack (not lua_registry) + * - t - lua_text, argument - int - position of the lua_text on stack (not lua_registry) + */ + while (*argp) { + switch (*argp) { + case 'i': + lua_pushinteger(L, va_arg(ap, gint64)); + nargs++; + break; + case 'n': + lua_pushnumber(L, va_arg(ap, gdouble)); + nargs++; + break; + case 's': + lua_pushstring(L, va_arg(ap, const gchar *)); + nargs++; + break; + case 'l': + sz = va_arg(ap, gsize); + lua_pushlstring(L, va_arg(ap, const gchar *), sz); + nargs++; + break; + case 'b': + lua_pushboolean(L, va_arg(ap, gboolean)); + nargs++; + break; + case 'u': + classname = va_arg(ap, const gchar *); + cls_ptr = (gpointer *) lua_newuserdata(L, sizeof(gpointer)); + *cls_ptr = va_arg(ap, gpointer); + rspamd_lua_setclass(L, classname, -1); + nargs++; + break; + case 'f': + case 't': + lua_pushvalue(L, va_arg(ap, gint)); + nargs++; + break; + default: + lua_settop(L, err_idx - 1); + g_set_error(err, lua_error_quark(), EINVAL, + "invalid argument character: %c at %s", + *argp, argp); + va_end(ap); + + return false; + } + + argp++; + } + + if (lua_pcall(L, nargs, nret, err_idx) != 0) { + g_set_error(err, lua_error_quark(), EBADF, + "error when calling lua function from %s: %s", + strloc, lua_tostring(L, -1)); + lua_settop(L, err_idx - 1); + va_end(ap); + + return false; + } + + lua_remove(L, err_idx); + va_end(ap); + + return true; +} + +#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 502 +gint rspamd_lua_geti(lua_State *L, int pos, int i) +{ + pos = lua_absindex(L, pos); + lua_pushinteger(L, i); + lua_gettable(L, pos); + + return lua_type(L, -1); +} +#endif
\ No newline at end of file |