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