/*- * 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); }