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