You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lua_repl.c 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026
  1. /*
  2. * Copyright 2023 Vsevolod Stakhov
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include "config.h"
  17. #include "rspamadm.h"
  18. #include "libserver/http/http_connection.h"
  19. #include "libserver/http/http_private.h"
  20. #include "libserver/http/http_router.h"
  21. #include "printf.h"
  22. #include "lua/lua_common.h"
  23. #include "lua/lua_thread_pool.h"
  24. #include "message.h"
  25. #include "unix-std.h"
  26. #ifdef WITH_LUA_REPL
  27. #include "replxx.h"
  28. #endif
  29. #include "worker_util.h"
  30. #ifdef WITH_LUAJIT
  31. #include <luajit.h>
  32. #endif
  33. static gchar **paths = NULL;
  34. static gchar **scripts = NULL;
  35. static gchar **lua_args = NULL;
  36. static gchar *histfile = NULL;
  37. static guint max_history = 2000;
  38. static gchar *serve = NULL;
  39. static gchar *exec_line = NULL;
  40. static gint batch = -1;
  41. extern struct rspamd_async_session *rspamadm_session;
  42. static const char *default_history_file = ".rspamd_repl.hist";
  43. #ifdef WITH_LUA_REPL
  44. static Replxx *rx_instance = NULL;
  45. #endif
  46. #ifdef WITH_LUAJIT
  47. #define MAIN_PROMPT LUAJIT_VERSION "> "
  48. #else
  49. #define MAIN_PROMPT LUA_VERSION "> "
  50. #endif
  51. #define MULTILINE_PROMPT "... "
  52. static void rspamadm_lua(gint argc, gchar **argv,
  53. const struct rspamadm_command *cmd);
  54. static const char *rspamadm_lua_help(gboolean full_help,
  55. const struct rspamadm_command *cmd);
  56. struct rspamadm_command lua_command = {
  57. .name = "lua",
  58. .flags = 0,
  59. .help = rspamadm_lua_help,
  60. .run = rspamadm_lua,
  61. .lua_subrs = NULL,
  62. };
  63. /*
  64. * Dot commands
  65. */
  66. typedef void (*rspamadm_lua_dot_handler)(lua_State *L, gint argc, gchar **argv);
  67. struct rspamadm_lua_dot_command {
  68. const gchar *name;
  69. const gchar *description;
  70. rspamadm_lua_dot_handler handler;
  71. };
  72. static void rspamadm_lua_help_handler(lua_State *L, gint argc, gchar **argv);
  73. static void rspamadm_lua_load_handler(lua_State *L, gint argc, gchar **argv);
  74. static void rspamadm_lua_exec_handler(lua_State *L, gint argc, gchar **argv);
  75. static void rspamadm_lua_message_handler(lua_State *L, gint argc, gchar **argv);
  76. static void lua_thread_error_cb(struct thread_entry *thread, int ret, const char *msg);
  77. static void lua_thread_finish_cb(struct thread_entry *thread, int ret);
  78. static struct rspamadm_lua_dot_command cmds[] = {
  79. {.name = "help",
  80. .description = "shows help for commands",
  81. .handler = rspamadm_lua_help_handler},
  82. {.name = "load",
  83. .description = "load lua file",
  84. .handler = rspamadm_lua_load_handler},
  85. {.name = "exec",
  86. .description = "exec lua file",
  87. .handler = rspamadm_lua_exec_handler},
  88. {.name = "message",
  89. .description = "scans message using specified callback: .message <callback_name> <file>...",
  90. .handler = rspamadm_lua_message_handler},
  91. };
  92. static GHashTable *cmds_hash = NULL;
  93. static GOptionEntry entries[] = {
  94. {"script", 's', 0, G_OPTION_ARG_STRING_ARRAY, &scripts,
  95. "Load specified scripts", NULL},
  96. {"path", 'P', 0, G_OPTION_ARG_STRING_ARRAY, &paths,
  97. "Add specified paths to lua paths", NULL},
  98. {"history-file", 'H', 0, G_OPTION_ARG_FILENAME, &histfile,
  99. "Load history from the specified file", NULL},
  100. {"max-history", 'm', 0, G_OPTION_ARG_INT, &max_history,
  101. "Store this number of history entries", NULL},
  102. {"serve", 'S', 0, G_OPTION_ARG_STRING, &serve,
  103. "Serve http lua server", NULL},
  104. {"batch", 'b', 0, G_OPTION_ARG_NONE, &batch,
  105. "Batch execution mode", NULL},
  106. {"exec", 'e', 0, G_OPTION_ARG_STRING, &exec_line,
  107. "Execute specified script", NULL},
  108. {"args", 'a', 0, G_OPTION_ARG_STRING_ARRAY, &lua_args,
  109. "Arguments to pass to Lua", NULL},
  110. {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}};
  111. static const char *
  112. rspamadm_lua_help(gboolean full_help, const struct rspamadm_command *cmd)
  113. {
  114. const char *help_str;
  115. if (full_help) {
  116. help_str = "Run lua read/execute/print loop\n\n"
  117. "Usage: rspamadm lua [-P paths] [-s scripts]\n"
  118. "Where options are:\n\n"
  119. "-P: add additional lua paths (may be repeated)\n"
  120. "-p: split input to lines and feed each line to the script\n"
  121. "-s: load scripts on start from specified files (may be repeated)\n"
  122. "-S: listen on a specified address as HTTP server\n"
  123. "-a: pass argument to lua (may be repeated)\n"
  124. "-e: execute script specified in command line"
  125. "--help: shows available options and commands";
  126. }
  127. else {
  128. help_str = "Run LUA interpreter";
  129. }
  130. return help_str;
  131. }
  132. static void
  133. rspamadm_lua_add_path(lua_State *L, const gchar *path)
  134. {
  135. const gchar *old_path;
  136. gsize len;
  137. GString *new_path;
  138. lua_getglobal(L, "package");
  139. lua_getfield(L, -1, "path");
  140. old_path = luaL_checklstring(L, -1, &len);
  141. new_path = g_string_sized_new(len + strlen(path) + sizeof("/?.lua"));
  142. if (strstr(path, "?.lua") == NULL) {
  143. rspamd_printf_gstring(new_path, "%s/?.lua;%s", path, old_path);
  144. }
  145. else {
  146. rspamd_printf_gstring(new_path, "%s;%s", path, old_path);
  147. }
  148. lua_pushlstring(L, new_path->str, new_path->len);
  149. lua_setfield(L, -2, "path");
  150. lua_settop(L, 0);
  151. g_string_free(new_path, TRUE);
  152. }
  153. static void
  154. lua_thread_finish_cb(struct thread_entry *thread, int ret)
  155. {
  156. struct lua_call_data *cd = thread->cd;
  157. cd->ret = ret;
  158. }
  159. static void
  160. lua_thread_error_cb(struct thread_entry *thread, int ret, const char *msg)
  161. {
  162. struct lua_call_data *cd = thread->cd;
  163. rspamd_fprintf(stderr, "call failed: %s\n", msg);
  164. cd->ret = ret;
  165. }
  166. static void
  167. lua_thread_str_error_cb(struct thread_entry *thread, int ret, const char *msg)
  168. {
  169. struct lua_call_data *cd = thread->cd;
  170. const char *what = cd->ud;
  171. rspamd_fprintf(stderr, "call to %s failed: %s\n", what, msg);
  172. cd->ret = ret;
  173. }
  174. static gboolean
  175. rspamadm_lua_load_script(lua_State *L, const gchar *path)
  176. {
  177. struct thread_entry *thread = lua_thread_pool_get_for_config(rspamd_main->cfg);
  178. L = thread->lua_state;
  179. if (luaL_loadfile(L, path) != 0) {
  180. rspamd_fprintf(stderr, "cannot load script %s: %s\n",
  181. path, lua_tostring(L, -1));
  182. lua_settop(L, 0);
  183. return FALSE;
  184. }
  185. if (lua_repl_thread_call(thread, 0, (void *) path, lua_thread_str_error_cb) != 0) {
  186. return FALSE;
  187. }
  188. lua_settop(L, 0);
  189. return TRUE;
  190. }
  191. static void
  192. rspamadm_exec_input(lua_State *L, const gchar *input)
  193. {
  194. GString *tb;
  195. gint i, cbref;
  196. int top = 0;
  197. gchar outbuf[8192];
  198. struct lua_logger_trace tr;
  199. struct thread_entry *thread = lua_thread_pool_get_for_config(rspamd_main->cfg);
  200. L = thread->lua_state;
  201. /* First try return + input */
  202. tb = g_string_sized_new(strlen(input) + sizeof("return "));
  203. rspamd_printf_gstring(tb, "return %s", input);
  204. int r = luaL_loadstring(L, tb->str);
  205. if (r != 0) {
  206. /* Reset stack */
  207. lua_settop(L, 0);
  208. /* Try with no return */
  209. if (luaL_loadstring(L, input) != 0) {
  210. rspamd_fprintf(stderr, "cannot load string %s\n",
  211. input);
  212. g_string_free(tb, TRUE);
  213. lua_settop(L, 0);
  214. lua_thread_pool_return(rspamd_main->cfg->lua_thread_pool, thread);
  215. return;
  216. }
  217. }
  218. g_string_free(tb, TRUE);
  219. top = lua_gettop(L);
  220. if (lua_repl_thread_call(thread, 0, NULL, NULL) == 0) {
  221. /* Print output */
  222. for (i = top; i <= lua_gettop(L); i++) {
  223. if (lua_isfunction(L, i)) {
  224. lua_pushvalue(L, i);
  225. cbref = luaL_ref(L, LUA_REGISTRYINDEX);
  226. rspamd_printf("local function: %d\n", cbref);
  227. }
  228. else {
  229. memset(&tr, 0, sizeof(tr));
  230. lua_logger_out_type(L, i, outbuf, sizeof(outbuf) - 1, &tr,
  231. LUA_ESCAPE_UNPRINTABLE);
  232. rspamd_printf("%s\n", outbuf);
  233. }
  234. }
  235. }
  236. }
  237. static void
  238. wait_session_events(void)
  239. {
  240. /* XXX: it's probably worth to add timeout here - not to wait forever */
  241. while (rspamd_session_events_pending(rspamadm_session) > 0) {
  242. ev_loop(rspamd_main->event_loop, EVRUN_ONCE);
  243. }
  244. msg_debug("finished events waiting, terminating session");
  245. }
  246. gint lua_repl_thread_call(struct thread_entry *thread, gint narg, gpointer ud, lua_thread_error_t error_func)
  247. {
  248. int ret;
  249. struct lua_call_data *cd = g_new0(struct lua_call_data, 1);
  250. cd->top = lua_gettop(thread->lua_state);
  251. cd->ud = ud;
  252. thread->finish_callback = lua_thread_finish_cb;
  253. if (error_func) {
  254. thread->error_callback = error_func;
  255. }
  256. else {
  257. thread->error_callback = lua_thread_error_cb;
  258. }
  259. thread->cd = cd;
  260. lua_thread_call(thread, narg);
  261. wait_session_events();
  262. ret = cd->ret;
  263. g_free(cd);
  264. return ret;
  265. }
  266. static void
  267. rspamadm_lua_help_handler(lua_State *L, gint argc, gchar **argv)
  268. {
  269. guint i;
  270. struct rspamadm_lua_dot_command *cmd;
  271. if (argv[1] == NULL) {
  272. /* Print all commands */
  273. for (i = 0; i < G_N_ELEMENTS(cmds); i++) {
  274. rspamd_printf("%s: %s\n", cmds[i].name, cmds[i].description);
  275. }
  276. rspamd_printf("{{: start multiline input\n");
  277. rspamd_printf("}}: end multiline input\n");
  278. }
  279. else {
  280. for (i = 1; argv[i] != NULL; i++) {
  281. cmd = g_hash_table_lookup(cmds_hash, argv[i]);
  282. if (cmd) {
  283. rspamd_printf("%s: %s\n", cmds->name, cmds->description);
  284. }
  285. else {
  286. rspamd_printf("%s: no such command\n", argv[i]);
  287. }
  288. }
  289. }
  290. }
  291. static void
  292. rspamadm_lua_load_handler(lua_State *L, gint argc, gchar **argv)
  293. {
  294. guint i;
  295. gboolean ret;
  296. for (i = 1; argv[i] != NULL; i++) {
  297. ret = rspamadm_lua_load_script(L, argv[i]);
  298. rspamd_printf("%s: %sloaded\n", argv[i], ret ? "" : "NOT ");
  299. }
  300. }
  301. static void
  302. rspamadm_lua_exec_handler(lua_State *L, gint argc, gchar **argv)
  303. {
  304. gint i;
  305. struct thread_entry *thread = lua_thread_pool_get_for_config(rspamd_main->cfg);
  306. L = thread->lua_state;
  307. for (i = 1; argv[i] != NULL; i++) {
  308. if (luaL_loadfile(L, argv[i]) != 0) {
  309. rspamd_fprintf(stderr, "cannot load script %s: %s\n",
  310. argv[i], lua_tostring(L, -1));
  311. lua_settop(L, 0);
  312. return;
  313. }
  314. if (lua_repl_thread_call(thread, 0, argv[i], lua_thread_str_error_cb) != 0) {
  315. return;
  316. }
  317. }
  318. }
  319. static void
  320. rspamadm_lua_message_handler(lua_State *L, gint argc, gchar **argv)
  321. {
  322. gulong cbref;
  323. gint old_top, func_idx, i, j;
  324. struct rspamd_task *task, **ptask;
  325. gpointer map;
  326. gsize len;
  327. gchar outbuf[8192];
  328. struct lua_logger_trace tr;
  329. if (argv[1] == NULL) {
  330. rspamd_printf("no callback is specified\n");
  331. return;
  332. }
  333. for (i = 2; argv[i] != NULL; i++) {
  334. struct thread_entry *thread = lua_thread_pool_get_for_config(rspamd_main->cfg);
  335. L = thread->lua_state;
  336. if (rspamd_strtoul(argv[1], strlen(argv[1]), &cbref)) {
  337. lua_rawgeti(L, LUA_REGISTRYINDEX, cbref);
  338. }
  339. else {
  340. lua_getglobal(L, argv[1]);
  341. }
  342. if (lua_type(L, -1) != LUA_TFUNCTION) {
  343. rspamd_printf("bad callback type: %s\n", lua_typename(L, lua_type(L, -1)));
  344. lua_thread_pool_return(rspamd_main->cfg->lua_thread_pool, thread);
  345. return;
  346. }
  347. /* Save index to reuse */
  348. func_idx = lua_gettop(L);
  349. map = rspamd_file_xmap(argv[i], PROT_READ, &len, TRUE);
  350. if (map == NULL) {
  351. rspamd_printf("cannot open %s: %s\n", argv[i], strerror(errno));
  352. }
  353. else {
  354. task = rspamd_task_new(NULL, rspamd_main->cfg, NULL, NULL, NULL, FALSE);
  355. if (!rspamd_task_load_message(task, NULL, map, len)) {
  356. rspamd_printf("cannot load %s\n", argv[i]);
  357. rspamd_task_free(task);
  358. munmap(map, len);
  359. continue;
  360. }
  361. if (!rspamd_message_parse(task)) {
  362. rspamd_printf("cannot parse %s: %e\n", argv[i], task->err);
  363. rspamd_task_free(task);
  364. munmap(map, len);
  365. continue;
  366. }
  367. rspamd_message_process(task);
  368. old_top = lua_gettop(L);
  369. lua_pushvalue(L, func_idx);
  370. ptask = lua_newuserdata(L, sizeof(*ptask));
  371. *ptask = task;
  372. rspamd_lua_setclass(L, "rspamd{task}", -1);
  373. if (lua_repl_thread_call(thread, 1, argv[i], lua_thread_str_error_cb) == 0) {
  374. rspamd_printf("lua callback for %s returned:\n", argv[i]);
  375. for (j = old_top + 1; j <= lua_gettop(L); j++) {
  376. memset(&tr, 0, sizeof(tr));
  377. lua_logger_out_type(L, j, outbuf, sizeof(outbuf), &tr,
  378. LUA_ESCAPE_UNPRINTABLE);
  379. rspamd_printf("%s\n", outbuf);
  380. }
  381. }
  382. rspamd_task_free(task);
  383. munmap(map, len);
  384. /* Pop all but the original function */
  385. lua_settop(L, func_idx);
  386. }
  387. }
  388. lua_settop(L, 0);
  389. }
  390. static gboolean
  391. rspamadm_lua_try_dot_command(lua_State *L, const gchar *input)
  392. {
  393. struct rspamadm_lua_dot_command *cmd;
  394. gchar **argv;
  395. argv = g_strsplit_set(input + 1, " ", -1);
  396. if (argv == NULL || argv[0] == NULL) {
  397. if (argv) {
  398. g_strfreev(argv);
  399. }
  400. return FALSE;
  401. }
  402. cmd = g_hash_table_lookup(cmds_hash, argv[0]);
  403. if (cmd) {
  404. cmd->handler(L, g_strv_length(argv), argv);
  405. g_strfreev(argv);
  406. return TRUE;
  407. }
  408. g_strfreev(argv);
  409. return FALSE;
  410. }
  411. #ifdef WITH_LUA_REPL
  412. static gint lex_ref_idx = -1;
  413. static void
  414. lua_syntax_highlighter(const char *str, ReplxxColor *colours, int size, void *ud)
  415. {
  416. lua_State *L = (lua_State *) ud;
  417. if (lex_ref_idx == -1) {
  418. if (!rspamd_lua_require_function(L, "lua_lexer", "lex_to_table")) {
  419. fprintf(stderr, "cannot require lua_lexer!\n");
  420. exit(EXIT_FAILURE);
  421. }
  422. lex_ref_idx = luaL_ref(L, LUA_REGISTRYINDEX);
  423. }
  424. lua_rawgeti(L, LUA_REGISTRYINDEX, lex_ref_idx);
  425. lua_pushstring(L, str);
  426. if (lua_pcall(L, 1, 1, 0) != 0) {
  427. fprintf(stderr, "cannot lex a string!\n");
  428. }
  429. else {
  430. /* Process what we have after lexing */
  431. gsize nelts = rspamd_lua_table_size(L, -1);
  432. for (gsize i = 0; i < nelts; i++) {
  433. /*
  434. * Indexes in the table:
  435. * 1 - type of element (string)
  436. * 2 - text (string)
  437. * 3 - line num (int), always 1...
  438. * 4 - column num (must be less than size)
  439. */
  440. const gchar *what;
  441. gsize column, tlen, cur_top, elt_pos;
  442. ReplxxColor elt_color = REPLXX_COLOR_DEFAULT;
  443. cur_top = lua_gettop(L);
  444. lua_rawgeti(L, -1, i + 1);
  445. elt_pos = lua_gettop(L);
  446. lua_rawgeti(L, elt_pos, 1);
  447. what = lua_tostring(L, -1);
  448. lua_rawgeti(L, elt_pos, 2);
  449. lua_tolstring(L, -1, &tlen);
  450. lua_rawgeti(L, elt_pos, 4);
  451. column = lua_tointeger(L, -1);
  452. g_assert(column > 0);
  453. column--; /* Start from 0 */
  454. if (column + tlen > size) {
  455. /* Likely utf8 case, too complicated to match */
  456. lua_settop(L, cur_top);
  457. continue;
  458. }
  459. /* Check what and adjust color */
  460. if (strcmp(what, "identifier") == 0) {
  461. elt_color = REPLXX_COLOR_NORMAL;
  462. }
  463. else if (strcmp(what, "number") == 0) {
  464. elt_color = REPLXX_COLOR_BLUE;
  465. }
  466. else if (strcmp(what, "string") == 0) {
  467. elt_color = REPLXX_COLOR_GREEN;
  468. }
  469. else if (strcmp(what, "keyword") == 0) {
  470. elt_color = REPLXX_COLOR_WHITE;
  471. }
  472. else if (strcmp(what, "constant") == 0) {
  473. elt_color = REPLXX_COLOR_WHITE;
  474. }
  475. else if (strcmp(what, "operator") == 0) {
  476. elt_color = REPLXX_COLOR_CYAN;
  477. }
  478. else if (strcmp(what, "comment") == 0) {
  479. elt_color = REPLXX_COLOR_BRIGHTGREEN;
  480. }
  481. else if (strcmp(what, "error") == 0) {
  482. elt_color = REPLXX_COLOR_ERROR;
  483. }
  484. for (gsize j = column; j < column + tlen; j++) {
  485. colours[j] = elt_color;
  486. }
  487. /* Restore stack */
  488. lua_settop(L, cur_top);
  489. }
  490. }
  491. lua_settop(L, 0);
  492. }
  493. #endif
  494. static void
  495. rspamadm_lua_run_repl(lua_State *L, bool is_batch)
  496. {
  497. gchar *input;
  498. #ifdef WITH_LUA_REPL
  499. gboolean is_multiline = FALSE;
  500. GString *tb = NULL;
  501. gsize i;
  502. #else
  503. /* Always set is_batch */
  504. is_batch = TRUE;
  505. #endif
  506. for (;;) {
  507. if (is_batch) {
  508. size_t linecap = 0;
  509. ssize_t linelen;
  510. linelen = getline(&input, &linecap, stdin);
  511. if (linelen > 0) {
  512. if (input[linelen - 1] == '\n') {
  513. input[linelen - 1] = '\0';
  514. linelen--;
  515. }
  516. if (linelen > 0) {
  517. if (input[0] == '.') {
  518. if (rspamadm_lua_try_dot_command(L, input)) {
  519. continue;
  520. }
  521. }
  522. rspamadm_exec_input(L, input);
  523. }
  524. }
  525. else {
  526. break;
  527. }
  528. lua_settop(L, 0);
  529. }
  530. else {
  531. #ifdef WITH_LUA_REPL
  532. replxx_set_highlighter_callback(rx_instance, lua_syntax_highlighter,
  533. L);
  534. if (!is_multiline) {
  535. input = (gchar *) replxx_input(rx_instance, MAIN_PROMPT);
  536. if (input == NULL) {
  537. return;
  538. }
  539. if (input[0] == '.') {
  540. if (rspamadm_lua_try_dot_command(L, input)) {
  541. if (!is_batch) {
  542. replxx_history_add(rx_instance, input);
  543. }
  544. continue;
  545. }
  546. }
  547. if (strcmp(input, "{{") == 0) {
  548. is_multiline = TRUE;
  549. tb = g_string_sized_new(8192);
  550. continue;
  551. }
  552. rspamadm_exec_input(L, input);
  553. if (!is_batch) {
  554. replxx_history_add(rx_instance, input);
  555. }
  556. lua_settop(L, 0);
  557. }
  558. else {
  559. input = (gchar *) replxx_input(rx_instance, MULTILINE_PROMPT);
  560. if (input == NULL) {
  561. g_string_free(tb, TRUE);
  562. return;
  563. }
  564. if (strcmp(input, "}}") == 0) {
  565. is_multiline = FALSE;
  566. rspamadm_exec_input(L, tb->str);
  567. /* Replace \n with ' ' for sanity */
  568. for (i = 0; i < tb->len; i++) {
  569. if (tb->str[i] == '\n') {
  570. tb->str[i] = ' ';
  571. }
  572. }
  573. if (!is_batch) {
  574. replxx_history_add(rx_instance, tb->str);
  575. }
  576. g_string_free(tb, TRUE);
  577. }
  578. else {
  579. g_string_append(tb, input);
  580. g_string_append(tb, " \n");
  581. }
  582. }
  583. }
  584. #endif
  585. }
  586. }
  587. struct rspamadm_lua_repl_context {
  588. struct rspamd_http_connection_router *rt;
  589. lua_State *L;
  590. };
  591. struct rspamadm_lua_repl_session {
  592. struct rspamd_http_connection_router *rt;
  593. rspamd_inet_addr_t *addr;
  594. struct rspamadm_lua_repl_context *ctx;
  595. gint sock;
  596. };
  597. static void
  598. rspamadm_lua_accept_cb(EV_P_ ev_io *w, int revents)
  599. {
  600. struct rspamadm_lua_repl_context *ctx =
  601. (struct rspamadm_lua_repl_context *) w->data;
  602. rspamd_inet_addr_t *addr = NULL;
  603. struct rspamadm_lua_repl_session *session;
  604. gint nfd;
  605. if ((nfd =
  606. rspamd_accept_from_socket(w->fd, &addr, NULL, NULL)) == -1) {
  607. rspamd_fprintf(stderr, "accept failed: %s", strerror(errno));
  608. return;
  609. }
  610. /* Check for EAGAIN */
  611. if (nfd == 0) {
  612. rspamd_inet_address_free(addr);
  613. return;
  614. }
  615. session = g_malloc0(sizeof(*session));
  616. session->rt = ctx->rt;
  617. session->ctx = ctx;
  618. session->addr = addr;
  619. session->sock = nfd;
  620. rspamd_http_router_handle_socket(ctx->rt, nfd, session);
  621. }
  622. static void
  623. rspamadm_lua_error_handler(struct rspamd_http_connection_entry *conn_ent,
  624. GError *err)
  625. {
  626. rspamd_fprintf(stderr, "http error occurred: %s\n", err->message);
  627. }
  628. static void
  629. rspamadm_lua_finish_handler(struct rspamd_http_connection_entry *conn_ent)
  630. {
  631. struct rspamadm_lua_repl_session *session = conn_ent->ud;
  632. g_free(session);
  633. }
  634. static void
  635. lua_thread_http_error_cb(struct thread_entry *thread, int ret, const char *msg)
  636. {
  637. struct lua_call_data *cd = thread->cd;
  638. struct rspamd_http_connection_entry *conn_ent = cd->ud;
  639. rspamd_controller_send_error(conn_ent, 500, "call failed: %s\n", msg);
  640. cd->ret = ret;
  641. }
  642. /*
  643. * Exec command handler:
  644. * request: /exec
  645. * body: lua script
  646. * reply: json {"status": "ok", "reply": {<lua json object>}}
  647. */
  648. static int
  649. rspamadm_lua_handle_exec(struct rspamd_http_connection_entry *conn_ent,
  650. struct rspamd_http_message *msg)
  651. {
  652. GString *tb;
  653. gint err_idx, i;
  654. lua_State *L;
  655. ucl_object_t *obj, *elt;
  656. const gchar *body;
  657. gsize body_len;
  658. struct thread_entry *thread = lua_thread_pool_get_for_config(rspamd_main->cfg);
  659. L = thread->lua_state;
  660. body = rspamd_http_message_get_body(msg, &body_len);
  661. if (body == NULL) {
  662. rspamd_controller_send_error(conn_ent, 400, "Empty lua script");
  663. return 0;
  664. }
  665. lua_pushcfunction(L, &rspamd_lua_traceback);
  666. err_idx = lua_gettop(L);
  667. /* First try return + input */
  668. tb = g_string_sized_new(body_len + sizeof("return "));
  669. rspamd_printf_gstring(tb, "return %*s", (gint) body_len, body);
  670. if (luaL_loadstring(L, tb->str) != 0) {
  671. /* Reset stack */
  672. lua_settop(L, 0);
  673. lua_pushcfunction(L, &rspamd_lua_traceback);
  674. err_idx = lua_gettop(L);
  675. /* Try with no return */
  676. if (luaL_loadbuffer(L, body, body_len, "http input") != 0) {
  677. rspamd_controller_send_error(conn_ent, 400, "Invalid lua script");
  678. return 0;
  679. }
  680. }
  681. g_string_free(tb, TRUE);
  682. if (lua_repl_thread_call(thread, 0, conn_ent, lua_thread_http_error_cb) != 0) {
  683. return 0;
  684. }
  685. obj = ucl_object_typed_new(UCL_ARRAY);
  686. for (i = err_idx + 1; i <= lua_gettop(L); i++) {
  687. if (lua_isfunction(L, i)) {
  688. /* XXX: think about API */
  689. }
  690. else {
  691. elt = ucl_object_lua_import(L, i);
  692. if (elt) {
  693. ucl_array_append(obj, elt);
  694. }
  695. }
  696. }
  697. rspamd_controller_send_ucl(conn_ent, obj);
  698. ucl_object_unref(obj);
  699. lua_settop(L, 0);
  700. return 0;
  701. }
  702. static void
  703. rspamadm_lua(gint argc, gchar **argv, const struct rspamadm_command *cmd)
  704. {
  705. GOptionContext *context;
  706. GError *error = NULL;
  707. gchar **elt;
  708. guint i;
  709. lua_State *L = rspamd_main->cfg->lua_state;
  710. context = g_option_context_new("lua - run lua interpreter");
  711. g_option_context_set_summary(context,
  712. "Summary:\n Rspamd administration utility version " RVERSION
  713. "\n Release id: " RID);
  714. g_option_context_add_main_entries(context, entries, NULL);
  715. if (!g_option_context_parse(context, &argc, &argv, &error)) {
  716. fprintf(stderr, "option parsing failed: %s\n", error->message);
  717. g_error_free(error);
  718. g_option_context_free(context);
  719. exit(EXIT_FAILURE);
  720. }
  721. g_option_context_free(context);
  722. if (batch == -1) {
  723. if (isatty(STDIN_FILENO)) {
  724. batch = 0;
  725. }
  726. else {
  727. batch = 1;
  728. }
  729. }
  730. if (paths) {
  731. for (elt = paths; *elt != NULL; elt++) {
  732. rspamadm_lua_add_path(L, *elt);
  733. }
  734. }
  735. if (lua_args) {
  736. i = 1;
  737. lua_newtable(L);
  738. for (elt = lua_args; *elt != NULL; elt++) {
  739. lua_pushinteger(L, i);
  740. lua_pushstring(L, *elt);
  741. lua_settable(L, -3);
  742. i++;
  743. }
  744. lua_setglobal(L, "arg");
  745. }
  746. if (scripts) {
  747. for (elt = scripts; *elt != NULL; elt++) {
  748. if (!rspamadm_lua_load_script(L, *elt)) {
  749. exit(EXIT_FAILURE);
  750. }
  751. }
  752. }
  753. if (exec_line) {
  754. rspamadm_exec_input(L, exec_line);
  755. }
  756. if (serve) {
  757. /* HTTP Server mode */
  758. GPtrArray *addrs = NULL;
  759. gchar *name = NULL;
  760. struct ev_loop *ev_base;
  761. struct rspamd_http_connection_router *http;
  762. gint fd;
  763. struct rspamadm_lua_repl_context *ctx;
  764. if (rspamd_parse_host_port_priority(serve, &addrs, NULL, &name,
  765. 10000, TRUE, NULL) == RSPAMD_PARSE_ADDR_FAIL) {
  766. fprintf(stderr, "cannot listen on %s", serve);
  767. exit(EXIT_FAILURE);
  768. }
  769. ev_base = rspamd_main->event_loop;
  770. ctx = g_malloc0(sizeof(*ctx));
  771. http = rspamd_http_router_new(rspamadm_lua_error_handler,
  772. rspamadm_lua_finish_handler,
  773. 0.0,
  774. NULL,
  775. rspamd_main->http_ctx);
  776. ctx->L = L;
  777. ctx->rt = http;
  778. rspamd_http_router_add_path(http,
  779. "/exec",
  780. rspamadm_lua_handle_exec);
  781. for (i = 0; i < addrs->len; i++) {
  782. rspamd_inet_addr_t *addr = g_ptr_array_index(addrs, i);
  783. fd = rspamd_inet_address_listen(addr, SOCK_STREAM,
  784. RSPAMD_INET_ADDRESS_LISTEN_ASYNC, -1);
  785. if (fd != -1) {
  786. static ev_io ev;
  787. ev.data = ctx;
  788. ev_io_init(&ev, rspamadm_lua_accept_cb, fd, EV_READ);
  789. ev_io_start(ev_base, &ev);
  790. rspamd_printf("listen on %s\n",
  791. rspamd_inet_address_to_string_pretty(addr));
  792. }
  793. }
  794. ev_loop(ev_base, 0);
  795. exit(EXIT_SUCCESS);
  796. }
  797. if (histfile == NULL) {
  798. const gchar *homedir;
  799. GString *hist_path;
  800. homedir = getenv("HOME");
  801. if (homedir) {
  802. hist_path = g_string_sized_new(strlen(homedir) +
  803. strlen(default_history_file) + 1);
  804. rspamd_printf_gstring(hist_path, "%s/%s", homedir,
  805. default_history_file);
  806. }
  807. else {
  808. hist_path = g_string_sized_new(strlen(default_history_file) + 2);
  809. rspamd_printf_gstring(hist_path, "./%s", default_history_file);
  810. }
  811. histfile = hist_path->str;
  812. g_string_free(hist_path, FALSE);
  813. }
  814. if (argc > 1) {
  815. for (i = 1; i < argc; i++) {
  816. if (!rspamadm_lua_load_script(L, argv[i])) {
  817. exit(EXIT_FAILURE);
  818. }
  819. }
  820. exit(EXIT_SUCCESS);
  821. }
  822. /* Init dot commands */
  823. cmds_hash = g_hash_table_new(rspamd_strcase_hash, rspamd_strcase_equal);
  824. for (i = 0; i < G_N_ELEMENTS(cmds); i++) {
  825. g_hash_table_insert(cmds_hash, (gpointer) cmds[i].name, &cmds[i]);
  826. }
  827. if (!batch) {
  828. #ifdef WITH_LUA_REPL
  829. rx_instance = replxx_init();
  830. replxx_set_max_history_size(rx_instance, max_history);
  831. replxx_history_load(rx_instance, histfile);
  832. #endif
  833. rspamadm_lua_run_repl(L, false);
  834. #ifdef WITH_LUA_REPL
  835. replxx_history_save(rx_instance, histfile);
  836. replxx_end(rx_instance);
  837. #endif
  838. }
  839. else {
  840. rspamadm_lua_run_repl(L, true);
  841. }
  842. }