From: Vsevolod Stakhov Date: Sat, 24 Sep 2016 19:42:12 +0000 (+0100) Subject: [Feature] Allow to execute Lua scripts by controller X-Git-Tag: 1.4.0~378 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=15c5e04497551abb6c9d672bf2c624fd3b32b149;p=thirdparty%2Frspamd.git [Feature] Allow to execute Lua scripts by controller --- diff --git a/src/controller.c b/src/controller.c index b2e50c5f5f..ed990c898c 100644 --- a/src/controller.c +++ b/src/controller.c @@ -22,6 +22,7 @@ #include "libstat/stat_api.h" #include "rspamd.h" #include "libserver/worker_util.h" +#include "lua/lua_common.h" #include "cryptobox.h" #include "ottery.h" #include "fuzzy_wire.h" @@ -1378,6 +1379,148 @@ rspamd_controller_handle_history_reset (struct rspamd_http_connection_entry *con return 0; } +static gboolean +rspamd_controller_lua_fin_task (void *ud) +{ + struct rspamd_task *task = ud; + struct rspamd_controller_session *session; + struct rspamd_http_connection_entry *conn_ent; + + conn_ent = task->fin_arg; + session = conn_ent->ud; + + if (task->err != NULL) { + rspamd_controller_send_error (conn_ent, task->err->code, "%s", + task->err->message); + } + + return TRUE; +} + +static int +rspamd_controller_handle_lua (struct rspamd_http_connection_entry *conn_ent, + struct rspamd_http_message *msg) +{ + struct rspamd_controller_session *session = conn_ent->ud; + struct rspamd_task *task, **ptask; + struct rspamd_http_connection_entry **pconn; + struct rspamd_controller_worker_ctx *ctx; + gchar filebuf[PATH_MAX], realbuf[PATH_MAX]; + struct http_parser_url u; + rspamd_ftok_t lookup; + struct stat st; + lua_State *L; + + if (!rspamd_controller_check_password (conn_ent, session, msg, TRUE)) { + return 0; + } + + ctx = session->ctx; + L = ctx->cfg->lua_state; + + /* Find lua script */ + if (msg->url != NULL && msg->url->len != 0) { + + http_parser_parse_url (msg->url->str, msg->url->len, TRUE, &u); + + if (u.field_set & (1 << UF_PATH)) { + lookup.begin = msg->url->str + u.field_data[UF_PATH].off; + lookup.len = u.field_data[UF_PATH].len; + } + else { + lookup.begin = msg->url->str; + lookup.len = msg->url->len; + } + + rspamd_snprintf (filebuf, sizeof (filebuf), "%s%c%T", + ctx->static_files_dir, G_DIR_SEPARATOR, lookup); + + if (realpath (filebuf, realbuf) == NULL || + lstat (realbuf, &st) == -1) { + rspamd_controller_send_error (conn_ent, 404, "Cannot find path: %s", + strerror (errno)); + + return 0; + } + + /* TODO: add caching here, should be trivial */ + /* Now we load and execute the code fragment, which should return a function */ + if (luaL_loadfile (L, realbuf) != 0) { + rspamd_controller_send_error (conn_ent, 500, "Cannot load path: %s", + lua_tostring (L, -1)); + lua_settop (L, 0); + + return 0; + } + + if (lua_pcall (L, 0, 1, 0) != 0) { + rspamd_controller_send_error (conn_ent, 501, "Cannot run path: %s", + lua_tostring (L, -1)); + lua_settop (L, 0); + + return 0; + } + + if (lua_type (L, -1) != LUA_TFUNCTION) { + rspamd_controller_send_error (conn_ent, 502, "Bad return type: %s", + lua_typename (L, lua_type (L, -1))); + lua_settop (L, 0); + + return 0; + } + + } + else { + rspamd_controller_send_error (conn_ent, 404, "Empty path is not permitted"); + + return 0; + } + + task = rspamd_task_new (session->ctx->worker, session->cfg); + + task->resolver = ctx->resolver; + task->ev_base = ctx->ev_base; + + task->s = rspamd_session_create (session->pool, + rspamd_controller_lua_fin_task, + NULL, + (event_finalizer_t )rspamd_task_free, + task); + task->fin_arg = conn_ent; + task->http_conn = rspamd_http_connection_ref (conn_ent->conn);; + task->sock = -1; + session->task = task; + + if (msg->body_buf.len > 0) { + if (!rspamd_task_load_message (task, msg, msg->body_buf.begin, msg->body_buf.len)) { + rspamd_controller_send_error (conn_ent, task->err->code, "%s", + task->err->message); + return 0; + } + } + + ptask = lua_newuserdata (L, sizeof (*ptask)); + rspamd_lua_setclass (L, "rspamd{task}", -1); + *ptask = task; + + pconn = lua_newuserdata (L, sizeof (*pconn)); + rspamd_lua_setclass (L, "rspamd{csession}", -1); + *pconn = conn_ent; + + if (lua_pcall (L, 2, 0, 0) != 0) { + rspamd_controller_send_error (conn_ent, 503, "Cannot run callback: %s", + lua_tostring (L, -1)); + rspamd_session_destroy (task->s); + lua_settop (L, 0); + + return 0; + } + + rspamd_session_pending (task->s); + + return 0; +} + static gboolean rspamd_controller_learn_fin_task (void *ud) { @@ -2676,6 +2819,131 @@ init_controller_worker (struct rspamd_config *cfg) return ctx; } +/* Lua bindings */ +LUA_FUNCTION_DEF (csession, get_ev_base); +LUA_FUNCTION_DEF (csession, get_cfg); +LUA_FUNCTION_DEF (csession, send_ucl); +LUA_FUNCTION_DEF (csession, send_string); +LUA_FUNCTION_DEF (csession, send_error); + +static const struct luaL_reg lua_csessionlib_m[] = { + LUA_INTERFACE_DEF (csession, get_ev_base), + LUA_INTERFACE_DEF (csession, get_cfg), + LUA_INTERFACE_DEF (csession, send_ucl), + LUA_INTERFACE_DEF (csession, send_string), + LUA_INTERFACE_DEF (csession, send_error), + {"__tostring", rspamd_lua_class_tostring}, + {NULL, NULL} +}; + +/* Basic functions of LUA API for worker object */ +static void +luaopen_controller (lua_State * L) +{ + rspamd_lua_new_class (L, "rspamd{url}", lua_csessionlib_m); + lua_pop (L, 1); +} + +struct rspamd_http_connection_entry * +lua_check_controller_entry (lua_State * L, gint pos) +{ + void *ud = luaL_checkudata (L, pos, "rspamd{csession}"); + luaL_argcheck (L, ud != NULL, pos, "'csession' expected"); + return ud ? *((struct rspamd_http_connection_entry **)ud) : NULL; +} + +static int +lua_csession_get_ev_base (lua_State *L) +{ + struct rspamd_http_connection_entry *c = lua_check_controller_entry (L, 1); + struct event_base **pbase; + struct rspamd_controller_session *s; + + if (c) { + s = c->ud; + pbase = lua_newuserdata (L, sizeof (struct event_base *)); + rspamd_lua_setclass (L, "rspamd{ev_base}", -1); + *pbase = s->ctx->ev_base; + } + else { + return luaL_error (L, "invalid arguments"); + } + + return 1; +} + +static int +lua_csession_get_cfg (lua_State *L) +{ + struct rspamd_http_connection_entry *c = lua_check_controller_entry (L, 1); + struct rspamd_config **pcfg; + struct rspamd_controller_session *s; + + if (c) { + s = c->ud; + pcfg = lua_newuserdata (L, sizeof (gpointer)); + rspamd_lua_setclass (L, "rspamd{config}", -1); + *pcfg = s->ctx->cfg; + } + else { + return luaL_error (L, "invalid arguments"); + } + + return 1; +} + +static int +lua_csession_send_ucl (lua_State *L) +{ + struct rspamd_http_connection_entry *c = lua_check_controller_entry (L, 1); + ucl_object_t *obj = ucl_object_lua_import (L, 2); + + if (c) { + rspamd_controller_send_ucl (c, obj); + } + else { + ucl_object_unref (obj); + return luaL_error (L, "invalid arguments"); + } + + ucl_object_unref (obj); + + return 0; +} + +static int +lua_csession_send_error (lua_State *L) +{ + struct rspamd_http_connection_entry *c = lua_check_controller_entry (L, 1); + guint err_code = lua_tonumber (L, 2); + const gchar *err_str = lua_tostring (L, 3); + + if (c) { + rspamd_controller_send_error (c, err_code, "%s", err_str); + } + else { + return luaL_error (L, "invalid arguments"); + } + + return 0; +} + +static int +lua_csession_send_string (lua_State *L) +{ + struct rspamd_http_connection_entry *c = lua_check_controller_entry (L, 1); + const gchar *str = lua_tostring (L, 3); + + if (c) { + rspamd_controller_send_string (c, str); + } + else { + return luaL_error (L, "invalid arguments"); + } + + return 0; +} + static void rspamd_controller_on_terminate (struct rspamd_worker *worker) { @@ -2825,6 +3093,12 @@ start_controller_worker (struct rspamd_worker *worker) PATH_COUNTERS, rspamd_controller_handle_counters); + rspamd_regexp_t *lua_re = rspamd_regexp_new ("^/.*/.*\\.lua$", NULL, NULL); + rspamd_http_router_add_regexp (ctx->http, lua_re, + rspamd_controller_handle_lua); + rspamd_regexp_unref (lua_re); + luaopen_controller (ctx->cfg->lua_state); + if (ctx->key) { rspamd_http_router_set_key (ctx->http, ctx->key); }