/*- * 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 "expression.h" /*** * @module rspamd_expression * This module can be used to implement different logic expressions in lua using * rspamd AST optimizer. There are some examples in individual methods definitions to help understanding of this module. */ /*** * @function rspamd_expression.create(line, {parse_func, [process_func]}, pool) * Create expression from the line using atom parsing routines and the specified memory pool * @param {string} line expression line * @param {table} atom_functions parse_atom function and optional process_atom function * @param {rspamd_mempool} memory pool to use for this function * @return {expr, err} expression object and error message of `expr` is nil * @example require "fun" () local rspamd_expression = require "rspamd_expression" local rspamd_mempool = require "rspamd_mempool" local function parse_func(str) -- extract token till the first space character local token = table.concat(totable(take_while(function(s) return s ~= ' ' end, iter(str)))) -- Return token name return token end local function process_func(token) -- Do something using token and task end local pool = rspamd_mempool.create() local expr,err = rspamd_expression.create('A & B | !C', {parse_func, process_func}, pool) -- Expression is destroyed when the corresponding pool is destroyed pool:destroy() */ LUA_FUNCTION_DEF(expr, create); /*** * @method rspamd_expression:to_string() * Converts rspamd expression to string * @return {string} string representation of rspamd expression */ LUA_FUNCTION_DEF(expr, to_string); /*** * @method rspamd_expression:process([callback, ]input[, flags]) * Executes the expression and pass input to process atom callbacks * @param {function} callback if not specified on process, then callback must be here * @param {any} input input data for processing callbacks * @return {number} result of the expression evaluation */ LUA_FUNCTION_DEF(expr, process); /*** * @method rspamd_expression:process_traced([callback, ]input[, flags]) * Executes the expression and pass input to process atom callbacks. This function also saves the full trace * @param {function} callback if not specified on process, then callback must be here * @param {any} input input data for processing callbacks * @return {number, table of matched atoms} result of the expression evaluation */ LUA_FUNCTION_DEF(expr, process_traced); /*** * @method rspamd_expression:atoms() * Extract all atoms from the expression as table of strings * @return {table/strings} list of all atoms in the expression */ LUA_FUNCTION_DEF(expr, atoms); static const struct luaL_reg exprlib_m[] = { LUA_INTERFACE_DEF(expr, to_string), LUA_INTERFACE_DEF(expr, atoms), LUA_INTERFACE_DEF(expr, process), LUA_INTERFACE_DEF(expr, process_traced), {"__tostring", lua_expr_to_string}, {NULL, NULL}}; static const struct luaL_reg exprlib_f[] = { LUA_INTERFACE_DEF(expr, create), {NULL, NULL}}; static rspamd_expression_atom_t *lua_atom_parse(const gchar *line, gsize len, rspamd_mempool_t *pool, gpointer ud, GError **err); static gdouble lua_atom_process(gpointer runtime_ud, rspamd_expression_atom_t *atom); static const struct rspamd_atom_subr lua_atom_subr = { .parse = lua_atom_parse, .process = lua_atom_process, .priority = NULL, .destroy = NULL}; struct lua_expression { struct rspamd_expression *expr; gint parse_idx; gint process_idx; lua_State *L; rspamd_mempool_t *pool; }; static GQuark lua_expr_quark(void) { return g_quark_from_static_string("lua-expression"); } struct lua_expression * rspamd_lua_expression(lua_State *L, gint pos) { void *ud = rspamd_lua_check_udata(L, pos, "rspamd{expr}"); luaL_argcheck(L, ud != NULL, pos, "'expr' expected"); return ud ? *((struct lua_expression **) ud) : NULL; } static void lua_expr_dtor(gpointer p) { struct lua_expression *e = (struct lua_expression *) p; if (e->parse_idx != -1) { luaL_unref(e->L, LUA_REGISTRYINDEX, e->parse_idx); } if (e->process_idx != -1) { luaL_unref(e->L, LUA_REGISTRYINDEX, e->process_idx); } } static rspamd_expression_atom_t * lua_atom_parse(const gchar *line, gsize len, rspamd_mempool_t *pool, gpointer ud, GError **err) { struct lua_expression *e = (struct lua_expression *) ud; rspamd_expression_atom_t *atom; gsize rlen; const gchar *tok; lua_rawgeti(e->L, LUA_REGISTRYINDEX, e->parse_idx); lua_pushlstring(e->L, line, len); if (lua_pcall(e->L, 1, 1, 0) != 0) { msg_info("callback call failed: %s", lua_tostring(e->L, -1)); lua_pop(e->L, 1); return NULL; } if (lua_type(e->L, -1) != LUA_TSTRING) { g_set_error(err, lua_expr_quark(), 500, "cannot parse lua atom"); lua_pop(e->L, 1); return NULL; } tok = lua_tolstring(e->L, -1, &rlen); atom = rspamd_mempool_alloc0(e->pool, sizeof(*atom)); atom->str = rspamd_mempool_strdup(e->pool, tok); atom->len = rlen; atom->data = ud; lua_pop(e->L, 1); return atom; } struct lua_atom_process_data { lua_State *L; struct lua_expression *e; gint process_cb_pos; gint stack_item; }; static gdouble lua_atom_process(gpointer runtime_ud, rspamd_expression_atom_t *atom) { struct lua_atom_process_data *pd = (struct lua_atom_process_data *) runtime_ud; gdouble ret = 0; guint nargs; gint err_idx; if (pd->stack_item != -1) { nargs = 2; } else { nargs = 1; } lua_pushcfunction(pd->L, &rspamd_lua_traceback); err_idx = lua_gettop(pd->L); /* Function position */ lua_pushvalue(pd->L, pd->process_cb_pos); /* Atom name */ lua_pushlstring(pd->L, atom->str, atom->len); /* If we have data passed */ if (pd->stack_item != -1) { lua_pushvalue(pd->L, pd->stack_item); } if (lua_pcall(pd->L, nargs, 1, err_idx) != 0) { msg_info("expression process callback failed: %s", lua_tostring(pd->L, -1)); } else { ret = lua_tonumber(pd->L, -1); } lua_settop(pd->L, err_idx - 1); return ret; } static gint lua_expr_process(lua_State *L) { LUA_TRACE_POINT; struct lua_expression *e = rspamd_lua_expression(L, 1); struct lua_atom_process_data pd; gdouble res; gint flags = 0, old_top; pd.L = L; pd.e = e; old_top = lua_gettop(L); if (e->process_idx == -1) { if (!lua_isfunction(L, 2)) { return luaL_error(L, "expression process is called with no callback function"); } pd.process_cb_pos = 2; if (lua_type(L, 3) != LUA_TNONE && lua_type(L, 3) != LUA_TNIL) { pd.stack_item = 3; } else { pd.stack_item = -1; } if (lua_isnumber(L, 4)) { flags = lua_tointeger(L, 4); } } else { lua_rawgeti(L, LUA_REGISTRYINDEX, e->process_idx); pd.process_cb_pos = lua_gettop(L); if (lua_type(L, 2) != LUA_TNONE && lua_type(L, 2) != LUA_TNIL) { pd.stack_item = 2; } else { pd.stack_item = -1; } if (lua_isnumber(L, 3)) { flags = lua_tointeger(L, 3); } } res = rspamd_process_expression(e->expr, flags, &pd); lua_settop(L, old_top); lua_pushnumber(L, res); return 1; } static gint lua_expr_process_traced(lua_State *L) { LUA_TRACE_POINT; struct lua_expression *e = rspamd_lua_expression(L, 1); struct lua_atom_process_data pd; gdouble res; gint flags = 0, old_top; GPtrArray *trace; pd.L = L; pd.e = e; old_top = lua_gettop(L); if (e->process_idx == -1) { if (!lua_isfunction(L, 2)) { return luaL_error(L, "expression process is called with no callback function"); } pd.process_cb_pos = 2; pd.stack_item = 3; if (lua_isnumber(L, 4)) { flags = lua_tointeger(L, 4); } } else { lua_rawgeti(L, LUA_REGISTRYINDEX, e->process_idx); pd.process_cb_pos = lua_gettop(L); pd.stack_item = 2; if (lua_isnumber(L, 3)) { flags = lua_tointeger(L, 3); } } res = rspamd_process_expression_track(e->expr, flags, &pd, &trace); lua_settop(L, old_top); lua_pushnumber(L, res); lua_createtable(L, trace->len, 0); for (guint i = 0; i < trace->len; i++) { struct rspamd_expression_atom_s *atom = g_ptr_array_index(trace, i); lua_pushlstring(L, atom->str, atom->len); lua_rawseti(L, -2, i + 1); } g_ptr_array_free(trace, TRUE); return 2; } static gint lua_expr_create(lua_State *L) { LUA_TRACE_POINT; struct lua_expression *e, **pe; const char *line; gsize len; gboolean no_process = FALSE; GError *err = NULL; rspamd_mempool_t *pool; /* Check sanity of the arguments */ if (lua_type(L, 1) != LUA_TSTRING || (lua_type(L, 2) != LUA_TTABLE && lua_type(L, 2) != LUA_TFUNCTION) || rspamd_lua_check_mempool(L, 3) == NULL) { lua_pushnil(L); lua_pushstring(L, "bad arguments"); } else { line = lua_tolstring(L, 1, &len); pool = rspamd_lua_check_mempool(L, 3); e = rspamd_mempool_alloc(pool, sizeof(*e)); e->L = L; e->pool = pool; /* Check callbacks */ if (lua_istable(L, 2)) { lua_pushvalue(L, 2); lua_pushnumber(L, 1); lua_gettable(L, -2); if (lua_type(L, -1) != LUA_TFUNCTION) { lua_pop(L, 1); lua_pushnil(L); lua_pushstring(L, "bad parse callback"); return 2; } lua_pop(L, 1); lua_pushnumber(L, 2); lua_gettable(L, -2); if (lua_type(L, -1) != LUA_TFUNCTION) { if (lua_type(L, -1) != LUA_TNIL && lua_type(L, -1) != LUA_TNONE) { lua_pop(L, 1); lua_pushnil(L); lua_pushstring(L, "bad process callback"); return 2; } else { no_process = TRUE; } } lua_pop(L, 1); /* Table is still on the top of stack */ lua_pushnumber(L, 1); lua_gettable(L, -2); e->parse_idx = luaL_ref(L, LUA_REGISTRYINDEX); if (!no_process) { lua_pushnumber(L, 2); lua_gettable(L, -2); e->process_idx = luaL_ref(L, LUA_REGISTRYINDEX); } else { e->process_idx = -1; } lua_pop(L, 1); /* Table */ } else { /* Process function is just a function, not a table */ lua_pushvalue(L, 2); e->parse_idx = luaL_ref(L, LUA_REGISTRYINDEX); e->process_idx = -1; } if (!rspamd_parse_expression(line, len, &lua_atom_subr, e, pool, &err, &e->expr)) { lua_pushnil(L); lua_pushstring(L, err->message); g_error_free(err); lua_expr_dtor(e); return 2; } rspamd_mempool_add_destructor(pool, lua_expr_dtor, e); pe = lua_newuserdata(L, sizeof(struct lua_expression *)); rspamd_lua_setclass(L, "rspamd{expr}", -1); *pe = e; lua_pushnil(L); } return 2; } static gint lua_expr_to_string(lua_State *L) { LUA_TRACE_POINT; struct lua_expression *e = rspamd_lua_expression(L, 1); GString *str; if (e != NULL && e->expr != NULL) { str = rspamd_expression_tostring(e->expr); if (str) { lua_pushlstring(L, str->str, str->len); g_string_free(str, TRUE); } else { lua_pushnil(L); } } else { lua_pushnil(L); } return 1; } struct lua_expr_atoms_cbdata { lua_State *L; gint idx; }; static void lua_exr_atom_cb(const rspamd_ftok_t *tok, gpointer ud) { struct lua_expr_atoms_cbdata *cbdata = ud; lua_pushlstring(cbdata->L, tok->begin, tok->len); lua_rawseti(cbdata->L, -2, cbdata->idx++); } static gint lua_expr_atoms(lua_State *L) { LUA_TRACE_POINT; struct lua_expression *e = rspamd_lua_expression(L, 1); struct lua_expr_atoms_cbdata cbdata; if (e != NULL && e->expr != NULL) { lua_newtable(L); cbdata.L = L; cbdata.idx = 1; rspamd_expression_atom_foreach(e->expr, lua_exr_atom_cb, &cbdata); } else { lua_pushnil(L); } return 1; } static gint lua_load_expression(lua_State *L) { lua_newtable(L); luaL_register(L, NULL, exprlib_f); return 1; } void luaopen_expression(lua_State *L) { rspamd_lua_new_class(L, "rspamd{expr}", exprlib_m); lua_pop(L, 1); rspamd_lua_add_preload(L, "rspamd_expression", lua_load_expression); }