summaryrefslogtreecommitdiffstats
path: root/src/lua/lua_map.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lua/lua_map.c')
-rw-r--r--src/lua/lua_map.c1421
1 files changed, 1421 insertions, 0 deletions
diff --git a/src/lua/lua_map.c b/src/lua/lua_map.c
new file mode 100644
index 0000000..54cfb4b
--- /dev/null
+++ b/src/lua/lua_map.c
@@ -0,0 +1,1421 @@
+/*-
+ * Copyright 2016 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 "libserver/maps/map.h"
+#include "libserver/maps/map_helpers.h"
+#include "libserver/maps/map_private.h"
+#include "contrib/libucl/lua_ucl.h"
+
+/***
+ * This module is used to manage rspamd maps and map like objects
+ *
+ * @module rspamd_map
+ *
+ * All maps could be obtained by function `rspamd_config:get_maps()`
+ * Also see [`lua_maps` module description](lua_maps.html).
+ *
+ * **Important notice** maps cannot be queried outside of the worker context.
+ * For example, you cannot add even a file map and query some keys from it during
+ * some module initialisation, you need to add the appropriate event loop context
+ * for a worker (e.g. you cannot use `get_key` outside of the symbols callbacks or
+ * a worker `on_load` scripts).
+ *
+@example
+
+local hash_map = rspamd_config:add_map{
+ type = "hash",
+ urls = ['file:///path/to/file'],
+ description = 'sample map'
+}
+
+local function sample_symbol_cb(task)
+ -- Check whether hash map contains from address of message
+ if hash_map:get_key((task:get_from() or {})[1]) then
+ -- key found
+ end
+end
+
+rspamd_config:register_symbol{
+ name = 'SAMPLE_SYMBOL',
+ type = 'normal',
+ score = 1.0,
+ description = "A sample symbol",
+ callback = sample_symbol_cb,
+}
+ */
+
+/***
+ * @method map:get_key(in)
+ * Variable method for different types of maps:
+ *
+ * - For hash maps it returns boolean and accepts string
+ * - For kv maps it returns string (or nil) and accepts string
+ * - For radix maps it returns boolean and accepts IP address (as object, string or number)
+ *
+ * @param {vary} in input to check
+ * @return {bool|string} if a value is found then this function returns string or `True` if not - then it returns `nil` or `False`
+ */
+LUA_FUNCTION_DEF(map, get_key);
+
+
+/***
+ * @method map:is_signed()
+ * Returns `True` if a map is signed
+ * @return {bool} signed value
+ */
+LUA_FUNCTION_DEF(map, is_signed);
+
+/***
+ * @method map:get_proto()
+ * Returns protocol of map as string:
+ *
+ * - `http`: for HTTP map
+ * - `file`: for file map
+ * @return {string} string representation of the map protocol
+ */
+LUA_FUNCTION_DEF(map, get_proto);
+
+/***
+ * @method map:get_sign_key()
+ * Returns pubkey used for signing as base32 string or nil
+ * @return {string} base32 encoded string or nil
+ */
+LUA_FUNCTION_DEF(map, get_sign_key);
+
+/***
+ * @method map:set_sign_key(key)
+ * Set trusted key for signatures for this map
+ * @param {string} key base32 encoded string or nil
+ */
+LUA_FUNCTION_DEF(map, set_sign_key);
+
+/***
+ * @method map:set_callback(cb)
+ * Set callback for a specified callback map.
+ * @param {function} cb map callback function
+ */
+LUA_FUNCTION_DEF(map, set_callback);
+
+/***
+ * @method map:get_uri()
+ * Get uri for a specified map
+ * @return {string} map's URI
+ */
+LUA_FUNCTION_DEF(map, get_uri);
+
+/***
+ * @method map:get_stats(reset)
+ * Get statistics for specific map. It returns table in form:
+ * [key] => [nhits]
+ * @param {boolean} reset reset stats if true
+ * @return {table} map's stat
+ */
+LUA_FUNCTION_DEF(map, get_stats);
+
+/***
+ * @method map:foreach(callback, is_text)
+ * Iterate over map elements and call callback for each element.
+ * @param {function} callback callback function, that accepts two arguments: key and value, if it returns true then iteration is stopped
+ * @param {boolean} is_text if true then callback accepts rspamd_text instead of Lua strings
+ * @return {number} number of elements iterated
+ */
+LUA_FUNCTION_DEF(map, foreach);
+
+/***
+ * @method map:on_load(callback)
+ * Sets a callback for a map that is called when map is loaded
+ * @param {function} callback callback function, that accepts no arguments (pass maps in a closure if needed)
+ */
+LUA_FUNCTION_DEF(map, on_load);
+
+/***
+ * @method map:get_data_digest()
+ * Get data digest for specific map
+ * @return {string} 64 bit number represented as string (due to Lua limitations)
+ */
+LUA_FUNCTION_DEF(map, get_data_digest);
+
+/***
+ * @method map:get_nelts()
+ * Get number of elements for specific map
+ * @return {number} number of elements in the map
+ */
+LUA_FUNCTION_DEF(map, get_nelts);
+
+static const struct luaL_reg maplib_m[] = {
+ LUA_INTERFACE_DEF(map, get_key),
+ LUA_INTERFACE_DEF(map, is_signed),
+ LUA_INTERFACE_DEF(map, get_proto),
+ LUA_INTERFACE_DEF(map, get_sign_key),
+ LUA_INTERFACE_DEF(map, set_sign_key),
+ LUA_INTERFACE_DEF(map, set_callback),
+ LUA_INTERFACE_DEF(map, get_uri),
+ LUA_INTERFACE_DEF(map, get_stats),
+ LUA_INTERFACE_DEF(map, foreach),
+ LUA_INTERFACE_DEF(map, on_load),
+ LUA_INTERFACE_DEF(map, get_data_digest),
+ LUA_INTERFACE_DEF(map, get_nelts),
+ {"__tostring", rspamd_lua_class_tostring},
+ {NULL, NULL}};
+
+struct lua_map_callback_data {
+ lua_State *L;
+ gint ref;
+ gboolean opaque;
+ rspamd_fstring_t *data;
+ struct rspamd_lua_map *lua_map;
+};
+
+struct rspamd_lua_map *
+lua_check_map(lua_State *L, gint pos)
+{
+ void *ud = rspamd_lua_check_udata(L, pos, "rspamd{map}");
+ luaL_argcheck(L, ud != NULL, pos, "'map' expected");
+ return ud ? *((struct rspamd_lua_map **) ud) : NULL;
+}
+
+gint lua_config_add_radix_map(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_config *cfg = lua_check_config(L, 1);
+ const gchar *map_line, *description;
+ struct rspamd_lua_map *map, **pmap;
+ struct rspamd_map *m;
+
+ if (cfg) {
+ map_line = luaL_checkstring(L, 2);
+ description = lua_tostring(L, 3);
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+ map->data.radix = NULL;
+ map->type = RSPAMD_LUA_MAP_RADIX;
+
+ if ((m = rspamd_map_add(cfg, map_line, description,
+ rspamd_radix_read,
+ rspamd_radix_fin,
+ rspamd_radix_dtor,
+ (void **) &map->data.radix,
+ NULL, RSPAMD_MAP_DEFAULT)) == NULL) {
+ msg_warn_config("invalid radix map %s", map_line);
+ lua_pushnil(L);
+
+ return 1;
+ }
+
+ map->map = m;
+ m->lua_map = map;
+ pmap = lua_newuserdata(L, sizeof(void *));
+ *pmap = map;
+ rspamd_lua_setclass(L, "rspamd{map}", -1);
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ return 1;
+}
+
+gint lua_config_radix_from_config(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_config *cfg = lua_check_config(L, 1);
+ const gchar *mname, *optname;
+ const ucl_object_t *obj;
+ struct rspamd_lua_map *map, **pmap;
+ ucl_object_t *fake_obj;
+ struct rspamd_map *m;
+
+ if (!cfg) {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ mname = luaL_checkstring(L, 2);
+ optname = luaL_checkstring(L, 3);
+
+ if (mname && optname) {
+ obj = rspamd_config_get_module_opt(cfg, mname, optname);
+
+ if (obj) {
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+ map->data.radix = NULL;
+ map->type = RSPAMD_LUA_MAP_RADIX;
+
+ fake_obj = ucl_object_typed_new(UCL_OBJECT);
+ ucl_object_insert_key(fake_obj, ucl_object_ref(obj),
+ "data", 0, false);
+ ucl_object_insert_key(fake_obj, ucl_object_fromstring("static"),
+ "url", 0, false);
+
+ if ((m = rspamd_map_add_from_ucl(cfg, fake_obj, "static radix map",
+ rspamd_radix_read,
+ rspamd_radix_fin,
+ rspamd_radix_dtor,
+ (void **) &map->data.radix,
+ NULL, RSPAMD_MAP_DEFAULT)) == NULL) {
+ msg_err_config("invalid radix map static");
+ lua_pushnil(L);
+ ucl_object_unref(fake_obj);
+
+ return 1;
+ }
+
+ ucl_object_unref(fake_obj);
+ pmap = lua_newuserdata(L, sizeof(void *));
+ map->map = m;
+ m->lua_map = map;
+ *pmap = map;
+ rspamd_lua_setclass(L, "rspamd{map}", -1);
+ }
+ else {
+ msg_warn_config("Couldnt find config option [%s][%s]", mname,
+ optname);
+ lua_pushnil(L);
+ }
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ return 1;
+}
+
+
+gint lua_config_radix_from_ucl(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_config *cfg = lua_check_config(L, 1);
+ ucl_object_t *obj;
+ struct rspamd_lua_map *map, **pmap;
+ ucl_object_t *fake_obj;
+ struct rspamd_map *m;
+
+ if (!cfg) {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ obj = ucl_object_lua_import(L, 2);
+
+ if (obj) {
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+ map->data.radix = NULL;
+ map->type = RSPAMD_LUA_MAP_RADIX;
+
+ fake_obj = ucl_object_typed_new(UCL_OBJECT);
+ ucl_object_insert_key(fake_obj, ucl_object_ref(obj),
+ "data", 0, false);
+ ucl_object_insert_key(fake_obj, ucl_object_fromstring("static"),
+ "url", 0, false);
+
+ if ((m = rspamd_map_add_from_ucl(cfg, fake_obj, "static radix map",
+ rspamd_radix_read,
+ rspamd_radix_fin,
+ rspamd_radix_dtor,
+ (void **) &map->data.radix,
+ NULL, RSPAMD_MAP_DEFAULT)) == NULL) {
+ msg_err_config("invalid radix map static");
+ lua_pushnil(L);
+ ucl_object_unref(fake_obj);
+ ucl_object_unref(obj);
+
+ return 1;
+ }
+
+ ucl_object_unref(fake_obj);
+ ucl_object_unref(obj);
+ pmap = lua_newuserdata(L, sizeof(void *));
+ map->map = m;
+ m->lua_map = map;
+ *pmap = map;
+ rspamd_lua_setclass(L, "rspamd{map}", -1);
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ return 1;
+}
+
+gint lua_config_add_hash_map(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_config *cfg = lua_check_config(L, 1);
+ const gchar *map_line, *description;
+ struct rspamd_lua_map *map, **pmap;
+ struct rspamd_map *m;
+
+ if (cfg) {
+ map_line = luaL_checkstring(L, 2);
+ description = lua_tostring(L, 3);
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+ map->data.hash = NULL;
+ map->type = RSPAMD_LUA_MAP_SET;
+
+ if ((m = rspamd_map_add(cfg, map_line, description,
+ rspamd_kv_list_read,
+ rspamd_kv_list_fin,
+ rspamd_kv_list_dtor,
+ (void **) &map->data.hash,
+ NULL, RSPAMD_MAP_DEFAULT)) == NULL) {
+ msg_warn_config("invalid set map %s", map_line);
+ lua_pushnil(L);
+ return 1;
+ }
+
+ map->map = m;
+ m->lua_map = map;
+ pmap = lua_newuserdata(L, sizeof(void *));
+ *pmap = map;
+ rspamd_lua_setclass(L, "rspamd{map}", -1);
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ return 1;
+}
+
+gint lua_config_add_kv_map(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_config *cfg = lua_check_config(L, 1);
+ const gchar *map_line, *description;
+ struct rspamd_lua_map *map, **pmap;
+ struct rspamd_map *m;
+
+ if (cfg) {
+ map_line = luaL_checkstring(L, 2);
+ description = lua_tostring(L, 3);
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+ map->data.hash = NULL;
+ map->type = RSPAMD_LUA_MAP_HASH;
+
+ if ((m = rspamd_map_add(cfg, map_line, description,
+ rspamd_kv_list_read,
+ rspamd_kv_list_fin,
+ rspamd_kv_list_dtor,
+ (void **) &map->data.hash,
+ NULL, RSPAMD_MAP_DEFAULT)) == NULL) {
+ msg_warn_config("invalid hash map %s", map_line);
+ lua_pushnil(L);
+
+ return 1;
+ }
+
+ map->map = m;
+ m->lua_map = map;
+ pmap = lua_newuserdata(L, sizeof(void *));
+ *pmap = map;
+ rspamd_lua_setclass(L, "rspamd{map}", -1);
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ return 1;
+}
+
+
+static gchar *
+lua_map_read(gchar *chunk, gint len,
+ struct map_cb_data *data,
+ gboolean final)
+{
+ struct lua_map_callback_data *cbdata, *old;
+
+ if (data->cur_data == NULL) {
+ old = (struct lua_map_callback_data *) data->prev_data;
+ cbdata = old;
+ cbdata->L = old->L;
+ cbdata->ref = old->ref;
+ cbdata->lua_map = old->lua_map;
+ data->cur_data = cbdata;
+ data->prev_data = NULL;
+ }
+ else {
+ cbdata = (struct lua_map_callback_data *) data->cur_data;
+ }
+
+ if (cbdata->data == NULL) {
+ cbdata->data = rspamd_fstring_new_init(chunk, len);
+ }
+ else {
+ cbdata->data = rspamd_fstring_append(cbdata->data, chunk, len);
+ }
+
+ return NULL;
+}
+
+static void
+lua_map_fin(struct map_cb_data *data, void **target)
+{
+ struct lua_map_callback_data *cbdata;
+ struct rspamd_lua_map **pmap;
+ struct rspamd_map *map;
+
+ map = data->map;
+
+ if (data->errored) {
+ if (data->cur_data) {
+ cbdata = (struct lua_map_callback_data *) data->cur_data;
+ if (cbdata->ref != -1) {
+ luaL_unref(cbdata->L, LUA_REGISTRYINDEX, cbdata->ref);
+ }
+
+ if (cbdata->data) {
+ rspamd_fstring_free(cbdata->data);
+ }
+
+ data->cur_data = NULL;
+ }
+ }
+ else {
+ if (data->cur_data) {
+ cbdata = (struct lua_map_callback_data *) data->cur_data;
+ }
+ else {
+ msg_err_map("no data read for map");
+ return;
+ }
+
+ if (cbdata->ref == -1) {
+ msg_err_map("map has no callback set");
+ }
+ else if (cbdata->data != NULL && cbdata->data->len != 0) {
+
+ lua_pushcfunction(cbdata->L, &rspamd_lua_traceback);
+ int err_idx = lua_gettop(cbdata->L);
+
+ lua_rawgeti(cbdata->L, LUA_REGISTRYINDEX, cbdata->ref);
+
+ if (!cbdata->opaque) {
+ lua_pushlstring(cbdata->L, cbdata->data->str, cbdata->data->len);
+ }
+ else {
+ struct rspamd_lua_text *t;
+
+ t = lua_newuserdata(cbdata->L, sizeof(*t));
+ rspamd_lua_setclass(cbdata->L, "rspamd{text}", -1);
+ t->flags = 0;
+ t->len = cbdata->data->len;
+ t->start = cbdata->data->str;
+ }
+
+ pmap = lua_newuserdata(cbdata->L, sizeof(void *));
+ *pmap = cbdata->lua_map;
+ rspamd_lua_setclass(cbdata->L, "rspamd{map}", -1);
+
+ gint ret = lua_pcall(cbdata->L, 2, 0, err_idx);
+
+ if (ret != 0) {
+ msg_info_map("call to %s failed (%d): %s", "map fin function",
+ ret,
+ lua_tostring(cbdata->L, -1));
+ }
+
+ lua_settop(cbdata->L, err_idx - 1);
+ }
+
+ cbdata->data = rspamd_fstring_assign(cbdata->data, "", 0);
+
+ if (target) {
+ *target = data->cur_data;
+ }
+
+ if (data->prev_data) {
+ data->prev_data = NULL;
+ }
+ }
+}
+
+static void
+lua_map_dtor(struct map_cb_data *data)
+{
+ struct lua_map_callback_data *cbdata;
+
+ if (data->cur_data) {
+ cbdata = (struct lua_map_callback_data *) data->cur_data;
+ if (cbdata->ref != -1) {
+ luaL_unref(cbdata->L, LUA_REGISTRYINDEX, cbdata->ref);
+ }
+
+ if (cbdata->data) {
+ rspamd_fstring_free(cbdata->data);
+ }
+ }
+}
+
+gint lua_config_add_map(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_config *cfg = lua_check_config(L, 1);
+ const char *description = NULL;
+ const gchar *type = NULL;
+ ucl_object_t *map_obj = NULL;
+ struct lua_map_callback_data *cbdata;
+ struct rspamd_lua_map *map, **pmap;
+ struct rspamd_map *m;
+ gboolean opaque_data = FALSE;
+ int cbidx = -1, ret;
+ GError *err = NULL;
+
+ if (cfg) {
+ if (!rspamd_lua_parse_table_arguments(L, 2, &err,
+ RSPAMD_LUA_PARSE_ARGUMENTS_DEFAULT,
+ "*url=O;description=S;callback=F;type=S;opaque_data=B",
+ &map_obj, &description, &cbidx, &type, &opaque_data)) {
+ ret = luaL_error(L, "invalid table arguments: %s", err->message);
+ g_error_free(err);
+ if (map_obj) {
+ ucl_object_unref(map_obj);
+ }
+
+ return ret;
+ }
+
+ g_assert(map_obj != NULL);
+
+ if (type == NULL && cbidx != -1) {
+ type = "callback";
+ }
+ else if (type == NULL) {
+ return luaL_error(L, "invalid map type");
+ }
+
+ if (strcmp(type, "callback") == 0) {
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+ map->type = RSPAMD_LUA_MAP_CALLBACK;
+ map->data.cbdata = rspamd_mempool_alloc0(cfg->cfg_pool,
+ sizeof(*map->data.cbdata));
+ cbdata = map->data.cbdata;
+ cbdata->L = L;
+ cbdata->data = NULL;
+ cbdata->lua_map = map;
+ cbdata->ref = cbidx;
+ cbdata->opaque = opaque_data;
+
+ if ((m = rspamd_map_add_from_ucl(cfg, map_obj, description,
+ lua_map_read,
+ lua_map_fin,
+ lua_map_dtor,
+ (void **) &map->data.cbdata,
+ NULL, RSPAMD_MAP_DEFAULT)) == NULL) {
+
+ if (cbidx != -1) {
+ luaL_unref(L, LUA_REGISTRYINDEX, cbidx);
+ }
+
+ if (map_obj) {
+ ucl_object_unref(map_obj);
+ }
+
+ lua_pushnil(L);
+
+ return 1;
+ }
+ m->lua_map = map;
+ }
+ else if (strcmp(type, "set") == 0) {
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+ map->data.hash = NULL;
+ map->type = RSPAMD_LUA_MAP_SET;
+
+ if ((m = rspamd_map_add_from_ucl(cfg, map_obj, description,
+ rspamd_kv_list_read,
+ rspamd_kv_list_fin,
+ rspamd_kv_list_dtor,
+ (void **) &map->data.hash,
+ NULL, RSPAMD_MAP_DEFAULT)) == NULL) {
+ lua_pushnil(L);
+ ucl_object_unref(map_obj);
+
+ return 1;
+ }
+ m->lua_map = map;
+ }
+ else if (strcmp(type, "map") == 0 || strcmp(type, "hash") == 0) {
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+ map->data.hash = NULL;
+ map->type = RSPAMD_LUA_MAP_HASH;
+
+ if ((m = rspamd_map_add_from_ucl(cfg, map_obj, description,
+ rspamd_kv_list_read,
+ rspamd_kv_list_fin,
+ rspamd_kv_list_dtor,
+ (void **) &map->data.hash,
+ NULL, RSPAMD_MAP_DEFAULT)) == NULL) {
+ lua_pushnil(L);
+ ucl_object_unref(map_obj);
+
+ return 1;
+ }
+ m->lua_map = map;
+ }
+ else if (strcmp(type, "radix") == 0) {
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+ map->data.radix = NULL;
+ map->type = RSPAMD_LUA_MAP_RADIX;
+
+ if ((m = rspamd_map_add_from_ucl(cfg, map_obj, description,
+ rspamd_radix_read,
+ rspamd_radix_fin,
+ rspamd_radix_dtor,
+ (void **) &map->data.radix,
+ NULL, RSPAMD_MAP_DEFAULT)) == NULL) {
+ lua_pushnil(L);
+ ucl_object_unref(map_obj);
+
+ return 1;
+ }
+ m->lua_map = map;
+ }
+ else if (strcmp(type, "regexp") == 0) {
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+ map->data.re_map = NULL;
+ map->type = RSPAMD_LUA_MAP_REGEXP;
+
+ if ((m = rspamd_map_add_from_ucl(cfg, map_obj, description,
+ rspamd_regexp_list_read_single,
+ rspamd_regexp_list_fin,
+ rspamd_regexp_list_dtor,
+ (void **) &map->data.re_map,
+ NULL, RSPAMD_MAP_DEFAULT)) == NULL) {
+ lua_pushnil(L);
+ ucl_object_unref(map_obj);
+
+ return 1;
+ }
+ m->lua_map = map;
+ }
+ else if (strcmp(type, "regexp_multi") == 0) {
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+ map->data.re_map = NULL;
+ map->type = RSPAMD_LUA_MAP_REGEXP_MULTIPLE;
+
+ if ((m = rspamd_map_add_from_ucl(cfg, map_obj, description,
+ rspamd_regexp_list_read_multiple,
+ rspamd_regexp_list_fin,
+ rspamd_regexp_list_dtor,
+ (void **) &map->data.re_map,
+ NULL, RSPAMD_MAP_DEFAULT)) == NULL) {
+ lua_pushnil(L);
+ ucl_object_unref(map_obj);
+
+ return 1;
+ }
+ m->lua_map = map;
+ }
+ else if (strcmp(type, "glob") == 0) {
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+ map->data.re_map = NULL;
+ map->type = RSPAMD_LUA_MAP_REGEXP;
+
+ if ((m = rspamd_map_add_from_ucl(cfg, map_obj, description,
+ rspamd_glob_list_read_single,
+ rspamd_regexp_list_fin,
+ rspamd_regexp_list_dtor,
+ (void **) &map->data.re_map,
+ NULL, RSPAMD_MAP_DEFAULT)) == NULL) {
+ lua_pushnil(L);
+ ucl_object_unref(map_obj);
+
+ return 1;
+ }
+ m->lua_map = map;
+ }
+ else if (strcmp(type, "glob_multi") == 0) {
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+ map->data.re_map = NULL;
+ map->type = RSPAMD_LUA_MAP_REGEXP_MULTIPLE;
+
+ if ((m = rspamd_map_add_from_ucl(cfg, map_obj, description,
+ rspamd_glob_list_read_multiple,
+ rspamd_regexp_list_fin,
+ rspamd_regexp_list_dtor,
+ (void **) &map->data.re_map,
+ NULL, RSPAMD_MAP_DEFAULT)) == NULL) {
+ lua_pushnil(L);
+ ucl_object_unref(map_obj);
+
+ return 1;
+ }
+ m->lua_map = map;
+ }
+ else if (strcmp(type, "cdb") == 0) {
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+ map->data.cdb_map = NULL;
+ map->type = RSPAMD_LUA_MAP_CDB;
+
+ if ((m = rspamd_map_add_from_ucl(cfg, map_obj, description,
+ rspamd_cdb_list_read,
+ rspamd_cdb_list_fin,
+ rspamd_cdb_list_dtor,
+ (void **) &map->data.cdb_map,
+ NULL, RSPAMD_MAP_FILE_ONLY | RSPAMD_MAP_FILE_NO_READ)) == NULL) {
+ lua_pushnil(L);
+ ucl_object_unref(map_obj);
+
+ return 1;
+ }
+ m->lua_map = map;
+ }
+ else {
+ ret = luaL_error(L, "invalid arguments: unknown type '%s'", type);
+ ucl_object_unref(map_obj);
+
+ return ret;
+ }
+
+ map->map = m;
+ pmap = lua_newuserdata(L, sizeof(void *));
+ *pmap = map;
+ rspamd_lua_setclass(L, "rspamd{map}", -1);
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ ucl_object_unref(map_obj);
+
+ return 1;
+}
+
+gint lua_config_get_maps(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_config *cfg = lua_check_config(L, 1);
+ struct rspamd_lua_map *map, **pmap;
+ struct rspamd_map *m;
+ gint i = 1;
+ GList *cur;
+
+ if (cfg) {
+ lua_newtable(L);
+ cur = g_list_first(cfg->maps);
+
+ while (cur) {
+ m = cur->data;
+
+ if (m->lua_map) {
+ map = m->lua_map;
+ }
+ else {
+ /* Implement heuristic */
+ map = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*map));
+
+ if (m->read_callback == rspamd_radix_read) {
+ map->type = RSPAMD_LUA_MAP_RADIX;
+ map->data.radix = *m->user_data;
+ }
+ else if (m->read_callback == rspamd_kv_list_read) {
+ map->type = RSPAMD_LUA_MAP_HASH;
+ map->data.hash = *m->user_data;
+ }
+ else {
+ map->type = RSPAMD_LUA_MAP_UNKNOWN;
+ }
+
+ map->map = m;
+ m->lua_map = map;
+ }
+
+ pmap = lua_newuserdata(L, sizeof(*pmap));
+ *pmap = map;
+ rspamd_lua_setclass(L, "rspamd{map}", -1);
+ lua_rawseti(L, -2, i);
+
+ cur = g_list_next(cur);
+ i++;
+ }
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ return 1;
+}
+
+static const gchar *
+lua_map_process_string_key(lua_State *L, gint pos, gsize *len)
+{
+ struct rspamd_lua_text *t;
+
+ if (lua_type(L, pos) == LUA_TSTRING) {
+ return lua_tolstring(L, pos, len);
+ }
+ else if (lua_type(L, pos) == LUA_TUSERDATA) {
+ t = lua_check_text(L, pos);
+
+ if (t) {
+ *len = t->len;
+ return t->start;
+ }
+ }
+
+ return NULL;
+}
+
+/* Radix and hash table functions */
+static gint
+lua_map_get_key(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_lua_map *map = lua_check_map(L, 1);
+ struct rspamd_radix_map_helper *radix;
+ struct rspamd_lua_ip *addr = NULL;
+ const gchar *key, *value = NULL;
+ gpointer ud;
+ gsize len;
+ guint32 key_num = 0;
+ gboolean ret = FALSE;
+
+ if (map) {
+ if (map->type == RSPAMD_LUA_MAP_RADIX) {
+ radix = map->data.radix;
+
+ if (lua_type(L, 2) == LUA_TSTRING) {
+ const gchar *addr_str;
+
+ addr_str = luaL_checklstring(L, 2, &len);
+ addr = g_alloca(sizeof(*addr));
+ addr->addr = g_alloca(rspamd_inet_address_storage_size());
+
+ if (!rspamd_parse_inet_address_ip(addr_str, len, addr->addr)) {
+ addr = NULL;
+ }
+ }
+ else if (lua_type(L, 2) == LUA_TUSERDATA) {
+ ud = rspamd_lua_check_udata(L, 2, "rspamd{ip}");
+ if (ud != NULL) {
+ addr = *((struct rspamd_lua_ip **) ud);
+
+ if (addr->addr == NULL) {
+ addr = NULL;
+ }
+ }
+ else {
+ msg_err("invalid userdata type provided, rspamd{ip} expected");
+ }
+ }
+ else if (lua_type(L, 2) == LUA_TNUMBER) {
+ key_num = luaL_checkinteger(L, 2);
+ key_num = htonl(key_num);
+ }
+
+ if (radix) {
+ gconstpointer p = NULL;
+
+ if (addr != NULL) {
+ if ((p = rspamd_match_radix_map_addr(radix, addr->addr)) != NULL) {
+ ret = TRUE;
+ }
+ else {
+ p = 0;
+ }
+ }
+ else if (key_num != 0) {
+ if ((p = rspamd_match_radix_map(radix,
+ (guint8 *) &key_num, sizeof(key_num))) != NULL) {
+ ret = TRUE;
+ }
+ else {
+ p = 0;
+ }
+ }
+
+ value = (const char *) p;
+ }
+
+ if (ret) {
+ lua_pushstring(L, value);
+ return 1;
+ }
+ }
+ else if (map->type == RSPAMD_LUA_MAP_SET) {
+ key = lua_map_process_string_key(L, 2, &len);
+
+ if (key && map->data.hash) {
+ ret = rspamd_match_hash_map(map->data.hash, key, len) != NULL;
+ }
+ }
+ else if (map->type == RSPAMD_LUA_MAP_REGEXP) {
+ key = lua_map_process_string_key(L, 2, &len);
+
+ if (key && map->data.re_map) {
+ value = rspamd_match_regexp_map_single(map->data.re_map, key,
+ len);
+
+ if (value) {
+ lua_pushstring(L, value);
+ return 1;
+ }
+ }
+ }
+ else if (map->type == RSPAMD_LUA_MAP_REGEXP_MULTIPLE) {
+ GPtrArray *ar;
+ guint i;
+ const gchar *val;
+
+ key = lua_map_process_string_key(L, 2, &len);
+
+ if (key && map->data.re_map) {
+ ar = rspamd_match_regexp_map_all(map->data.re_map, key,
+ len);
+
+ if (ar) {
+ lua_createtable(L, ar->len, 0);
+
+ PTR_ARRAY_FOREACH(ar, i, val)
+ {
+ lua_pushstring(L, val);
+ lua_rawseti(L, -2, i + 1);
+ }
+
+ g_ptr_array_free(ar, TRUE);
+
+ return 1;
+ }
+ }
+ }
+ else if (map->type == RSPAMD_LUA_MAP_HASH) {
+ /* key-value map */
+ key = lua_map_process_string_key(L, 2, &len);
+
+ if (key && map->data.hash) {
+ value = rspamd_match_hash_map(map->data.hash, key, len);
+ }
+
+ if (value) {
+ lua_pushstring(L, value);
+ return 1;
+ }
+ }
+ else if (map->type == RSPAMD_LUA_MAP_CDB) {
+ /* cdb map */
+ const rspamd_ftok_t *tok = NULL;
+
+ key = lua_map_process_string_key(L, 2, &len);
+
+ if (key && map->data.cdb_map) {
+ tok = rspamd_match_cdb_map(map->data.cdb_map, key, len);
+ }
+
+ if (tok) {
+ lua_pushlstring(L, tok->begin, tok->len);
+ return 1;
+ }
+ }
+ else {
+ /* callback map or unknown type map */
+ lua_pushnil(L);
+ return 1;
+ }
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ lua_pushboolean(L, ret);
+ return 1;
+}
+
+static gboolean
+lua_map_traverse_cb(gconstpointer key,
+ gconstpointer value, gsize hits, gpointer ud)
+{
+ lua_State *L = (lua_State *) ud;
+
+ lua_pushstring(L, key);
+ lua_pushinteger(L, hits);
+ lua_settable(L, -3);
+
+ return TRUE;
+}
+
+static gint
+lua_map_get_stats(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_lua_map *map = lua_check_map(L, 1);
+ gboolean do_reset = FALSE;
+
+ if (map != NULL) {
+ if (lua_isboolean(L, 2)) {
+ do_reset = lua_toboolean(L, 2);
+ }
+
+ lua_createtable(L, 0, map->map->nelts);
+
+ if (map->map->traverse_function) {
+ rspamd_map_traverse(map->map, lua_map_traverse_cb, L, do_reset);
+ }
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ return 1;
+}
+
+struct lua_map_traverse_cbdata {
+ lua_State *L;
+ gint cbref;
+ gboolean use_text;
+};
+
+static gboolean
+lua_map_foreach_cb(gconstpointer key, gconstpointer value, gsize _hits, gpointer ud)
+{
+ struct lua_map_traverse_cbdata *cbdata = ud;
+ lua_State *L = cbdata->L;
+
+ lua_pushvalue(L, cbdata->cbref);
+
+ if (cbdata->use_text) {
+ lua_new_text(L, key, strlen(key), 0);
+ lua_new_text(L, value, strlen(value), 0);
+ }
+ else {
+ lua_pushstring(L, key);
+ lua_pushstring(L, value);
+ }
+
+ if (lua_pcall(L, 2, 1, 0) != 0) {
+ msg_err("call to map foreach callback failed: %s", lua_tostring(L, -1));
+ lua_pop(L, 1); /* error */
+
+ return FALSE;
+ }
+ else {
+ if (lua_isboolean(L, -1)) {
+ lua_pop(L, 2);
+
+ return lua_toboolean(L, -1);
+ }
+
+ lua_pop(L, 1); /* result */
+ }
+
+ return TRUE;
+}
+
+static gint
+lua_map_foreach(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_lua_map *map = lua_check_map(L, 1);
+ gboolean use_text = FALSE;
+
+ if (map != NULL && lua_isfunction(L, 2)) {
+ if (lua_isboolean(L, 3)) {
+ use_text = lua_toboolean(L, 3);
+ }
+
+ struct lua_map_traverse_cbdata cbdata;
+ cbdata.L = L;
+ lua_pushvalue(L, 2); /* func */
+ cbdata.cbref = lua_gettop(L);
+ cbdata.use_text = use_text;
+
+ if (map->map->traverse_function) {
+ rspamd_map_traverse(map->map, lua_map_foreach_cb, &cbdata, FALSE);
+ }
+
+ /* Remove callback */
+ lua_pop(L, 1);
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ return 1;
+}
+
+static gint
+lua_map_get_data_digest(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_lua_map *map = lua_check_map(L, 1);
+ gchar numbuf[64];
+
+ if (map != NULL) {
+ rspamd_snprintf(numbuf, sizeof(numbuf), "%uL", map->map->digest);
+ lua_pushstring(L, numbuf);
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ return 1;
+}
+
+static gint
+lua_map_get_nelts(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_lua_map *map = lua_check_map(L, 1);
+
+ if (map != NULL) {
+ lua_pushinteger(L, map->map->nelts);
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ return 1;
+}
+
+static int
+lua_map_is_signed(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_lua_map *map = lua_check_map(L, 1);
+ gboolean ret = FALSE;
+ struct rspamd_map_backend *bk;
+ guint i;
+
+ if (map != NULL) {
+ if (map->map) {
+ for (i = 0; i < map->map->backends->len; i++) {
+ bk = g_ptr_array_index(map->map->backends, i);
+ if (bk->is_signed && bk->protocol == MAP_PROTO_FILE) {
+ ret = TRUE;
+ break;
+ }
+ }
+ }
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ lua_pushboolean(L, ret);
+ return 1;
+}
+
+static int
+lua_map_get_proto(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_lua_map *map = lua_check_map(L, 1);
+ const gchar *ret = "undefined";
+ struct rspamd_map_backend *bk;
+ guint i;
+
+ if (map != NULL) {
+ for (i = 0; i < map->map->backends->len; i++) {
+ bk = g_ptr_array_index(map->map->backends, i);
+ switch (bk->protocol) {
+ case MAP_PROTO_FILE:
+ ret = "file";
+ break;
+ case MAP_PROTO_HTTP:
+ ret = "http";
+ break;
+ case MAP_PROTO_HTTPS:
+ ret = "https";
+ break;
+ case MAP_PROTO_STATIC:
+ ret = "static";
+ break;
+ }
+ lua_pushstring(L, ret);
+ }
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+
+ return map->map->backends->len;
+}
+
+static int
+lua_map_get_sign_key(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_lua_map *map = lua_check_map(L, 1);
+ struct rspamd_map_backend *bk;
+ guint i;
+ GString *ret = NULL;
+
+ if (map != NULL) {
+ for (i = 0; i < map->map->backends->len; i++) {
+ bk = g_ptr_array_index(map->map->backends, i);
+
+ if (bk->trusted_pubkey) {
+ ret = rspamd_pubkey_print(bk->trusted_pubkey,
+ RSPAMD_KEYPAIR_PUBKEY | RSPAMD_KEYPAIR_BASE32);
+ }
+ else {
+ ret = NULL;
+ }
+
+ if (ret) {
+ lua_pushlstring(L, ret->str, ret->len);
+ g_string_free(ret, TRUE);
+ }
+ else {
+ lua_pushnil(L);
+ }
+ }
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ return map->map->backends->len;
+}
+
+static int
+lua_map_set_sign_key(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_lua_map *map = lua_check_map(L, 1);
+ struct rspamd_map_backend *bk;
+ const gchar *pk_str;
+ struct rspamd_cryptobox_pubkey *pk;
+ gsize len;
+ guint i;
+
+ pk_str = lua_tolstring(L, 2, &len);
+
+ if (map && pk_str) {
+ pk = rspamd_pubkey_from_base32(pk_str, len, RSPAMD_KEYPAIR_SIGN,
+ RSPAMD_CRYPTOBOX_MODE_25519);
+
+ if (!pk) {
+ return luaL_error(L, "invalid pubkey string");
+ }
+
+ for (i = 0; i < map->map->backends->len; i++) {
+ bk = g_ptr_array_index(map->map->backends, i);
+ if (bk->trusted_pubkey) {
+ /* Unref old pk */
+ rspamd_pubkey_unref(bk->trusted_pubkey);
+ }
+
+ bk->trusted_pubkey = rspamd_pubkey_ref(pk);
+ }
+
+ rspamd_pubkey_unref(pk);
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ return 0;
+}
+
+static int
+lua_map_set_callback(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_lua_map *map = lua_check_map(L, 1);
+
+ if (!map || map->type != RSPAMD_LUA_MAP_CALLBACK || map->data.cbdata == NULL) {
+ return luaL_error(L, "invalid map");
+ }
+
+ if (lua_type(L, 2) != LUA_TFUNCTION) {
+ return luaL_error(L, "invalid callback");
+ }
+
+ lua_pushvalue(L, 2);
+ /* Get a reference */
+ map->data.cbdata->ref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+ return 0;
+}
+
+static int
+lua_map_get_uri(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_lua_map *map = lua_check_map(L, 1);
+ struct rspamd_map_backend *bk;
+ guint i;
+
+ if (map != NULL) {
+ for (i = 0; i < map->map->backends->len; i++) {
+ bk = g_ptr_array_index(map->map->backends, i);
+ lua_pushstring(L, bk->uri);
+ }
+ }
+ else {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ return map->map->backends->len;
+}
+
+struct lua_map_on_load_cbdata {
+ lua_State *L;
+ gint ref;
+};
+
+static void
+lua_map_on_load_dtor(gpointer p)
+{
+ struct lua_map_on_load_cbdata *cbd = p;
+
+ luaL_unref(cbd->L, LUA_REGISTRYINDEX, cbd->ref);
+ g_free(cbd);
+}
+
+static void
+lua_map_on_load_handler(struct rspamd_map *map, gpointer ud)
+{
+ struct lua_map_on_load_cbdata *cbd = ud;
+ lua_State *L = cbd->L;
+
+ lua_rawgeti(L, LUA_REGISTRYINDEX, cbd->ref);
+
+ if (lua_pcall(L, 0, 0, 0) != 0) {
+ msg_err_map("call to on_load function failed: %s", lua_tostring(L, -1));
+ }
+}
+
+static gint
+lua_map_on_load(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct rspamd_lua_map *map = lua_check_map(L, 1);
+
+ if (map == NULL) {
+ return luaL_error(L, "invalid arguments");
+ }
+
+ if (lua_type(L, 2) == LUA_TFUNCTION) {
+ lua_pushvalue(L, 2);
+ struct lua_map_on_load_cbdata *cbd = g_malloc(sizeof(struct lua_map_on_load_cbdata));
+ cbd->L = L;
+ cbd->ref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+ rspamd_map_set_on_load_function(map->map, lua_map_on_load_handler, cbd, lua_map_on_load_dtor);
+ }
+ else {
+ return luaL_error(L, "invalid callback");
+ }
+
+ return 0;
+}
+
+void luaopen_map(lua_State *L)
+{
+ rspamd_lua_new_class(L, "rspamd{map}", maplib_m);
+
+ lua_pop(L, 1);
+}