/* * 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 /* 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 : ""), 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