From 6f828fb18e6b6ab2407179027f7f6c1032c90463 Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Tue, 17 Mar 2015 17:09:05 +0000 Subject: [PATCH] Add LUA api for expressions. --- src/lua/CMakeLists.txt | 3 +- src/lua/lua_common.c | 1 + src/lua/lua_common.h | 1 + src/lua/lua_expression.c | 289 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 src/lua/lua_expression.c diff --git a/src/lua/CMakeLists.txt b/src/lua/CMakeLists.txt index fbf2733d0..18e56508e 100644 --- a/src/lua/CMakeLists.txt +++ b/src/lua/CMakeLists.txt @@ -15,6 +15,7 @@ SET(LUASRC ${CMAKE_CURRENT_SOURCE_DIR}/lua_common.c ${CMAKE_CURRENT_SOURCE_DIR}/lua_buffer.c ${CMAKE_CURRENT_SOURCE_DIR}/lua_dns.c ${CMAKE_CURRENT_SOURCE_DIR}/lua_rsa.c - ${CMAKE_CURRENT_SOURCE_DIR}/lua_ip.c) + ${CMAKE_CURRENT_SOURCE_DIR}/lua_ip.c + ${CMAKE_CURRENT_SOURCE_DIR}/lua_expression.c) SET(RSPAMD_LUA ${LUASRC} PARENT_SCOPE) \ No newline at end of file diff --git a/src/lua/lua_common.c b/src/lua/lua_common.c index 123b81dad..63d34e4e6 100644 --- a/src/lua/lua_common.c +++ b/src/lua/lua_common.c @@ -354,6 +354,7 @@ rspamd_lua_init (struct rspamd_config *cfg) luaopen_dns_resolver (L); luaopen_rsa (L); luaopen_ip (L); + luaopen_expression (L); rspamd_lua_add_preload (L, "ucl", luaopen_ucl); diff --git a/src/lua/lua_common.h b/src/lua/lua_common.h index 1f3ea5922..ea0947c8a 100644 --- a/src/lua/lua_common.h +++ b/src/lua/lua_common.h @@ -182,6 +182,7 @@ void luaopen_io_dispatcher (lua_State * L); void luaopen_dns_resolver (lua_State * L); void luaopen_rsa (lua_State * L); void luaopen_ip (lua_State * L); +void luaopen_expression (lua_State * L); gint rspamd_lua_call_filter (const gchar *function, struct rspamd_task *task); gint rspamd_lua_call_chain_filter (const gchar *function, diff --git a/src/lua/lua_expression.c b/src/lua/lua_expression.c new file mode 100644 index 000000000..7c166cc68 --- /dev/null +++ b/src/lua/lua_expression.c @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2015, Vsevolod Stakhov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "lua_common.h" +#include "expression.h" + +/*** + * @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 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 parse_func(str) + -- extract token till the first space character + local token = table.join('', take_while(function(s) return s <> ' ' end, str) + -- Return token name + return token +end + +local process_func(token, task) + -- 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); + +static const struct luaL_reg exprlib_m[] = { + LUA_INTERFACE_DEF (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 gint lua_atom_process (gpointer input, 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 = luaL_checkudata (L, pos, "rspamd{expr}"); + luaL_argcheck (L, ud != NULL, pos, "'expr' expected"); + return ud ? *((struct lua_expression **)ud) : NULL; +} + + +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)); + } + + 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; +} + +static gint +lua_atom_process (gpointer input, rspamd_expression_atom_t *atom) +{ + struct lua_expression *e = (struct lua_expression *)atom->data; + gint ret; + + lua_rawgeti (e->L, LUA_REGISTRYINDEX, e->process_idx); + lua_pushvalue (e->L, GPOINTER_TO_INT (input)); + + if (lua_pcall (e->L, 1, 1, 0) != 0) { + msg_info ("callback call failed: %s", lua_tostring (e->L, -1)); + } + + ret = lua_tonumber (e->L, -1); + lua_pop (e->L, 1); + + return ret; +} + +static gint +lua_expr_create (lua_State *L) +{ + struct lua_expression *e, **pe; + const char *line; + gsize len; + 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 || + rspamd_lua_check_mempool (L, 3) == NULL) { + msg_info ("bad arguments to lua_expr_create"); + lua_pushnil (L); + lua_pushstring (L, "bad arguments"); + } + else { + line = lua_tolstring (L, 1, &len); + pool = rspamd_lua_check_mempool (L, 3); + + /* Check callbacks */ + lua_pushvalue (L, 2); + lua_pushnumber (L, 1); + lua_gettable (L, -2); + + if (lua_type (L, -1) != LUA_TFUNCTION) { + lua_pop (L, 2); + lua_pushnil (L); + lua_pushstring (L, "bad parse callback"); + + return 2; + } + + lua_pop (L, 1); + + lua_pushnumber (L, 1); + lua_gettable (L, -2); + + if (lua_type (L, -1) != LUA_TFUNCTION) { + lua_pop (L, 2); + lua_pushnil (L); + lua_pushstring (L, "bad process callback"); + + return 2; + } + + lua_pop (L, 1); + + /* Table is still on the top of stack */ + + e = rspamd_mempool_alloc (pool, sizeof (*e)); + e->L = L; + e->pool = pool; + + lua_pushnumber (L, 1); + lua_gettable (L, -2); + e->parse_idx = luaL_ref (L, LUA_REGISTRYINDEX); + lua_pop (L, 1); + lua_pushnumber (L, 2); + lua_gettable (L, -2); + e->process_idx = luaL_ref (L, LUA_REGISTRYINDEX); + lua_pop (L, 2); /* Idx and table */ + + 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); + + return 2; + } + + pe = lua_newuserdata (L, sizeof (struct lua_expression *)); + *pe = e; + lua_pushnil (L); + } + + return 2; +} + +static gint +lua_expr_to_string (lua_State *L) +{ + 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; +} + +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) +{ + luaL_newmetatable (L, "rspamd{expr}"); + lua_pushstring (L, "__index"); + lua_pushvalue (L, -2); + lua_settable (L, -3); + + lua_pushstring (L, "class"); + lua_pushstring (L, "rspamd{expr}"); + lua_rawset (L, -3); + + luaL_register (L, NULL, exprlib_m); + rspamd_lua_add_preload (L, "rspamd_expression", lua_load_expression); + + lua_pop (L, 1); /* remove metatable from stack */ +} -- 2.39.5