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.

rspamadm.c 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  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. #include "config.h"
  17. #include "rspamadm.h"
  18. #include "rspamd.h"
  19. #include "ottery.h"
  20. #include "lua/lua_common.h"
  21. #include "lua/lua_thread_pool.h"
  22. #include "lua_ucl.h"
  23. #include "unix-std.h"
  24. #include "contrib/libev/ev.h"
  25. #ifdef HAVE_LIBUTIL_H
  26. #include <libutil.h>
  27. #endif
  28. static gboolean verbose = FALSE;
  29. static gboolean list_commands = FALSE;
  30. static gboolean show_help = FALSE;
  31. static gboolean show_version = FALSE;
  32. GHashTable *ucl_vars = NULL;
  33. gchar **lua_env = NULL;
  34. struct rspamd_main *rspamd_main = NULL;
  35. struct rspamd_async_session *rspamadm_session = NULL;
  36. lua_State *L = NULL;
  37. /* Defined in modules.c */
  38. extern module_t *modules[];
  39. extern worker_t *workers[];
  40. static void rspamadm_help (gint argc, gchar **argv, const struct rspamadm_command *);
  41. static const char* rspamadm_help_help (gboolean full_help, const struct rspamadm_command *);
  42. struct rspamadm_command help_command = {
  43. .name = "help",
  44. .flags = RSPAMADM_FLAG_NOHELP,
  45. .help = rspamadm_help_help,
  46. .run = rspamadm_help
  47. };
  48. static gboolean rspamadm_parse_ucl_var (const gchar *option_name,
  49. const gchar *value, gpointer data,
  50. GError **error);
  51. static GOptionEntry entries[] = {
  52. {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
  53. "Enable verbose logging", NULL},
  54. {"list-commands", 'l', 0, G_OPTION_ARG_NONE, &list_commands,
  55. "List available commands", NULL},
  56. {"var", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer)&rspamadm_parse_ucl_var,
  57. "Redefine/define environment variable", NULL},
  58. {"help", 'h', 0, G_OPTION_ARG_NONE, &show_help,
  59. "Show help", NULL},
  60. {"version", 'V', 0, G_OPTION_ARG_NONE, &show_version,
  61. "Show version", NULL},
  62. {"lua-env", '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &lua_env,
  63. "Load lua environment from the specified files", NULL},
  64. {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}
  65. };
  66. GQuark
  67. rspamadm_error (void)
  68. {
  69. return g_quark_from_static_string ("rspamadm");
  70. }
  71. static void
  72. rspamadm_version (void)
  73. {
  74. rspamd_printf ("Rspamadm %s\n", RVERSION);
  75. }
  76. static void
  77. rspamadm_usage (GOptionContext *context)
  78. {
  79. gchar *help_str;
  80. help_str = g_option_context_get_help (context, TRUE, NULL);
  81. rspamd_printf ("%s", help_str);
  82. }
  83. static void
  84. rspamadm_commands (GPtrArray *all_commands)
  85. {
  86. const struct rspamadm_command *cmd;
  87. guint i;
  88. rspamd_printf ("Rspamadm %s\n", RVERSION);
  89. rspamd_printf ("Usage: rspamadm [global_options] command [command_options]\n");
  90. rspamd_printf ("\nAvailable commands:\n");
  91. PTR_ARRAY_FOREACH (all_commands, i, cmd) {
  92. if (!(cmd->flags & RSPAMADM_FLAG_NOHELP)) {
  93. if (cmd->flags & RSPAMADM_FLAG_LUA) {
  94. (void)cmd->help (FALSE, cmd);
  95. }
  96. else {
  97. printf (" %-18s %-60s\n", cmd->name, cmd->help (FALSE, cmd));
  98. }
  99. }
  100. }
  101. }
  102. static const char *
  103. rspamadm_help_help (gboolean full_help, const struct rspamadm_command *cmd)
  104. {
  105. const char *help_str;
  106. if (full_help) {
  107. help_str = "Shows help for a specified command\n"
  108. "Usage: rspamadm help <command>";
  109. }
  110. else {
  111. help_str = "Shows help for a specified command";
  112. }
  113. return help_str;
  114. }
  115. static void
  116. rspamadm_help (gint argc, gchar **argv, const struct rspamadm_command *command)
  117. {
  118. const gchar *cmd_name;
  119. const struct rspamadm_command *cmd;
  120. GPtrArray *all_commands = (GPtrArray *)command->command_data;
  121. rspamd_printf ("Rspamadm %s\n", RVERSION);
  122. rspamd_printf ("Usage: rspamadm [global_options] command [command_options]\n\n");
  123. if (argc <= 1) {
  124. cmd_name = "help";
  125. }
  126. else {
  127. cmd_name = argv[1];
  128. rspamd_printf ("Showing help for %s command\n\n", cmd_name);
  129. }
  130. cmd = rspamadm_search_command (cmd_name, all_commands);
  131. if (cmd == NULL) {
  132. fprintf (stderr, "Invalid command name: %s\n", cmd_name);
  133. exit (EXIT_FAILURE);
  134. }
  135. if (strcmp (cmd_name, "help") == 0) {
  136. guint i;
  137. rspamd_printf ("Available commands:\n");
  138. PTR_ARRAY_FOREACH (all_commands, i, cmd) {
  139. if (!(cmd->flags & RSPAMADM_FLAG_NOHELP)) {
  140. if (!(cmd->flags & RSPAMADM_FLAG_LUA)) {
  141. printf (" %-18s %-60s\n", cmd->name,
  142. cmd->help (FALSE, cmd));
  143. }
  144. else {
  145. /* Just call lua subr */
  146. (void)cmd->help (FALSE, cmd);
  147. }
  148. }
  149. }
  150. }
  151. else {
  152. if (!(cmd->flags & RSPAMADM_FLAG_LUA)) {
  153. rspamd_printf ("%s\n", cmd->help (TRUE, cmd));
  154. }
  155. else {
  156. /* Just call lua subr */
  157. (void)cmd->help (TRUE, cmd);
  158. }
  159. }
  160. }
  161. static gboolean
  162. rspamadm_parse_ucl_var (const gchar *option_name,
  163. const gchar *value, gpointer data,
  164. GError **error)
  165. {
  166. gchar *k, *v, *t;
  167. t = strchr (value, '=');
  168. if (t != NULL) {
  169. k = g_strdup (value);
  170. t = k + (t - value);
  171. v = g_strdup (t + 1);
  172. *t = '\0';
  173. g_hash_table_insert (ucl_vars, k, v);
  174. }
  175. else {
  176. g_set_error (error, rspamadm_error (), EINVAL,
  177. "Bad variable format: %s", value);
  178. return FALSE;
  179. }
  180. return TRUE;
  181. }
  182. static void
  183. lua_thread_str_error_cb (struct thread_entry *thread, int ret, const char *msg)
  184. {
  185. struct lua_call_data *cd = thread->cd;
  186. msg_err ("call to rspamadm lua script failed (%d): %s", ret, msg);
  187. cd->ret = ret;
  188. }
  189. gboolean
  190. rspamadm_execute_lua_ucl_subr (gint argc, gchar **argv,
  191. const ucl_object_t *res,
  192. const gchar *script_name,
  193. gboolean rspamadm_subcommand)
  194. {
  195. struct thread_entry *thread = lua_thread_pool_get_for_config (rspamd_main->cfg);
  196. lua_State *L = thread->lua_state;
  197. gint i;
  198. gchar str[PATH_MAX];
  199. g_assert (script_name != NULL);
  200. g_assert (res != NULL);
  201. g_assert (L != NULL);
  202. /* Init internal rspamadm routines */
  203. if (rspamadm_subcommand) {
  204. rspamd_snprintf (str, sizeof (str), "return require \"%s.%s\"", "rspamadm",
  205. script_name);
  206. }
  207. else {
  208. rspamd_snprintf (str, sizeof (str), "return require \"%s\"",
  209. script_name);
  210. }
  211. if (luaL_dostring (L, str) != 0) {
  212. msg_err ("cannot execute lua script %s: %s",
  213. str, lua_tostring (L, -1));
  214. return FALSE;
  215. }
  216. else {
  217. if (lua_type (L, -1) == LUA_TTABLE) {
  218. lua_pushstring (L, "handler");
  219. lua_gettable (L, -2);
  220. }
  221. if (lua_type (L, -1) != LUA_TFUNCTION) {
  222. msg_err ("lua script must return "
  223. "function and not %s",
  224. lua_typename (L, lua_type (L, -1)));
  225. return FALSE;
  226. }
  227. }
  228. /* Push function */
  229. lua_pushvalue (L, -1);
  230. /* Push argv */
  231. lua_newtable (L);
  232. for (i = 1; i < argc; i ++) {
  233. lua_pushstring (L, argv[i]);
  234. lua_rawseti (L, -2, i);
  235. }
  236. /* Push results */
  237. ucl_object_push_lua (L, res, TRUE);
  238. if (lua_repl_thread_call (thread, 2, NULL, lua_thread_str_error_cb) != 0) {
  239. return FALSE;
  240. }
  241. /* error function */
  242. lua_settop (L, 0);
  243. return TRUE;
  244. }
  245. static gint
  246. rspamdadm_commands_sort_func (gconstpointer a, gconstpointer b)
  247. {
  248. const struct rspamadm_command *cmda = *((struct rspamadm_command const **)a),
  249. *cmdb = *((struct rspamadm_command const **)b);
  250. return strcmp (cmda->name, cmdb->name);
  251. }
  252. static gboolean
  253. rspamadm_command_maybe_match_name (const gchar *cmd, const gchar *input)
  254. {
  255. gsize clen, inplen;
  256. clen = strlen (cmd);
  257. inplen = strlen (input);
  258. if (rspamd_strings_levenshtein_distance (cmd, clen,
  259. input, inplen, 1) == 1) {
  260. return TRUE;
  261. }
  262. else if ((clen > inplen &&
  263. rspamd_substring_search (cmd, clen, input, inplen) != -1) ||
  264. (inplen > clen &&
  265. rspamd_substring_search (input, inplen, cmd, clen) != -1)) {
  266. return TRUE;
  267. }
  268. return FALSE;
  269. }
  270. static void
  271. rspamadm_add_lua_globals (struct rspamd_dns_resolver *resolver)
  272. {
  273. struct rspamd_async_session **psession;
  274. struct ev_loop **pev_base;
  275. struct rspamd_dns_resolver **presolver;
  276. rspamadm_session = rspamd_session_create (rspamd_main->cfg->cfg_pool, NULL,
  277. NULL, (event_finalizer_t )NULL, NULL);
  278. psession = lua_newuserdata (L, sizeof (struct rspamd_async_session*));
  279. rspamd_lua_setclass (L, "rspamd{session}", -1);
  280. *psession = rspamadm_session;
  281. lua_setglobal (L, "rspamadm_session");
  282. pev_base = lua_newuserdata (L, sizeof (struct ev_loop *));
  283. rspamd_lua_setclass (L, "rspamd{ev_base}", -1);
  284. *pev_base = rspamd_main->event_loop;
  285. lua_setglobal (L, "rspamadm_ev_base");
  286. presolver = lua_newuserdata (L, sizeof (struct rspamd_dns_resolver *));
  287. rspamd_lua_setclass (L, "rspamd{resolver}", -1);
  288. *presolver = resolver;
  289. lua_setglobal (L, "rspamadm_dns_resolver");
  290. }
  291. static void
  292. rspamadm_cmd_dtor (gpointer p)
  293. {
  294. struct rspamadm_command *cmd = (struct rspamadm_command *)p;
  295. if (cmd->flags & RSPAMADM_FLAG_DYNAMIC) {
  296. if (cmd->aliases) {
  297. g_ptr_array_free (cmd->aliases, TRUE);
  298. }
  299. g_free ((gpointer)cmd->name);
  300. g_free (cmd);
  301. }
  302. }
  303. gint
  304. main (gint argc, gchar **argv, gchar **env)
  305. {
  306. GError *error = NULL;
  307. GOptionContext *context;
  308. GOptionGroup *og;
  309. struct rspamd_config *cfg;
  310. GQuark process_quark;
  311. gchar **nargv, **targv;
  312. const gchar *cmd_name;
  313. const struct rspamadm_command *cmd;
  314. struct rspamd_dns_resolver *resolver;
  315. GPtrArray *all_commands = g_ptr_array_new_full (32,
  316. rspamadm_cmd_dtor); /* Discovered during check */
  317. gint i, nargc, targc;
  318. worker_t **pworker;
  319. gboolean lua_file = FALSE;
  320. gint retcode = 0;
  321. ucl_vars = g_hash_table_new_full (rspamd_strcase_hash,
  322. rspamd_strcase_equal, g_free, g_free);
  323. process_quark = g_quark_from_static_string ("rspamadm");
  324. cfg = rspamd_config_new (RSPAMD_CONFIG_INIT_DEFAULT|RSPAMD_CONFIG_INIT_WIPE_LUA_MEM);
  325. cfg->libs_ctx = rspamd_init_libs ();
  326. rspamd_main = g_malloc0 (sizeof (*rspamd_main));
  327. rspamd_main->cfg = cfg;
  328. rspamd_main->pid = getpid ();
  329. rspamd_main->type = process_quark;
  330. rspamd_main->server_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (),
  331. "rspamadm", 0);
  332. rspamadm_fill_internal_commands (all_commands);
  333. help_command.command_data = all_commands;
  334. /* Now read options and store everything till the first non-dash argument */
  335. nargv = g_malloc0 (sizeof (gchar *) * (argc + 1));
  336. nargv[0] = g_strdup (argv[0]);
  337. for (i = 1, nargc = 1; i < argc; i ++) {
  338. if (argv[i] && argv[i][0] == '-') {
  339. /* Copy to nargv */
  340. nargv[nargc] = g_strdup (argv[i]);
  341. nargc ++;
  342. }
  343. else {
  344. break;
  345. }
  346. }
  347. context = g_option_context_new ("command - rspamd administration utility");
  348. og = g_option_group_new ("global", "global options", "global options",
  349. NULL, NULL);
  350. g_option_context_set_help_enabled (context, FALSE);
  351. g_option_group_add_entries (og, entries);
  352. g_option_context_set_summary (context,
  353. "Summary:\n Rspamd administration utility version "
  354. RVERSION
  355. "\n Release id: "
  356. RID);
  357. g_option_context_set_main_group (context, og);
  358. targv = nargv;
  359. targc = nargc;
  360. if (!g_option_context_parse (context, &targc, &targv, &error)) {
  361. fprintf (stderr, "option parsing failed: %s\n", error->message);
  362. g_error_free (error);
  363. g_option_context_free (context);
  364. exit (1);
  365. }
  366. /* Setup logger */
  367. rspamd_main->logger = rspamd_log_open_emergency (rspamd_main->server_pool);
  368. /* Setup logger */
  369. if (verbose) {
  370. rspamd_log_set_log_level (rspamd_main->logger, G_LOG_LEVEL_DEBUG);
  371. rspamd_log_set_log_flags (rspamd_main->logger,
  372. RSPAMD_LOG_FLAG_USEC|RSPAMD_LOG_FLAG_ENFORCED|RSPAMD_LOG_FLAG_RSPAMADM);
  373. }
  374. else {
  375. rspamd_log_set_log_level (rspamd_main->logger, G_LOG_LEVEL_MESSAGE);
  376. rspamd_log_set_log_flags (rspamd_main->logger,RSPAMD_LOG_FLAG_RSPAMADM);
  377. }
  378. rspamd_main->event_loop = ev_default_loop (EVFLAG_SIGNALFD|EVBACKEND_ALL);
  379. resolver = rspamd_dns_resolver_init (rspamd_main->logger,
  380. rspamd_main->event_loop,
  381. cfg);
  382. rspamd_main->http_ctx = rspamd_http_context_create (cfg, rspamd_main->event_loop,
  383. NULL);
  384. g_log_set_default_handler (rspamd_glib_log_function, rspamd_main->logger);
  385. g_set_printerr_handler (rspamd_glib_printerr_function);
  386. rspamd_config_post_load (cfg,
  387. RSPAMD_CONFIG_INIT_LIBS|RSPAMD_CONFIG_INIT_URL|RSPAMD_CONFIG_INIT_NO_TLD);
  388. pworker = &workers[0];
  389. while (*pworker) {
  390. /* Init string quarks */
  391. (void) g_quark_from_static_string ((*pworker)->name);
  392. pworker++;
  393. }
  394. cfg->compiled_modules = modules;
  395. cfg->compiled_workers = workers;
  396. setproctitle ("rspamdadm");
  397. L = cfg->lua_state;
  398. rspamd_lua_set_path (L, NULL, ucl_vars);
  399. if (!rspamd_lua_set_env (L, ucl_vars, lua_env, &error)) {
  400. rspamd_fprintf (stderr, "Cannot load lua environment: %e", error);
  401. g_error_free (error);
  402. goto end;
  403. }
  404. rspamd_lua_set_globals (cfg, L);
  405. rspamadm_add_lua_globals (resolver);
  406. #ifdef WITH_HIREDIS
  407. rspamd_redis_pool_config (cfg->redis_pool, cfg, rspamd_main->event_loop);
  408. #endif
  409. /* Init rspamadm global */
  410. lua_newtable (L);
  411. PTR_ARRAY_FOREACH (all_commands, i, cmd) {
  412. if (cmd->lua_subrs != NULL) {
  413. cmd->lua_subrs (L);
  414. }
  415. cmd ++;
  416. }
  417. lua_setglobal (L, "rspamadm");
  418. rspamadm_fill_lua_commands (L, all_commands);
  419. rspamd_lua_start_gc (cfg);
  420. g_ptr_array_sort (all_commands, rspamdadm_commands_sort_func);
  421. g_strfreev (nargv);
  422. if (show_version) {
  423. rspamadm_version ();
  424. goto end;
  425. }
  426. if (show_help) {
  427. rspamadm_usage (context);
  428. goto end;
  429. }
  430. if (list_commands) {
  431. rspamadm_commands (all_commands);
  432. goto end;
  433. }
  434. cmd_name = argv[nargc];
  435. if (cmd_name == NULL) {
  436. cmd_name = "help";
  437. }
  438. gsize cmdlen = strlen (cmd_name);
  439. if (cmdlen > 4 && memcmp (cmd_name + (cmdlen - 4), ".lua", 4) == 0) {
  440. cmd_name = "lua";
  441. lua_file = TRUE;
  442. }
  443. cmd = rspamadm_search_command (cmd_name, all_commands);
  444. if (cmd == NULL) {
  445. rspamd_fprintf (stderr, "Invalid command name: %s\n", cmd_name);
  446. /* Try fuzz search */
  447. rspamd_fprintf (stderr, "Suggested commands:\n");
  448. PTR_ARRAY_FOREACH (all_commands, i, cmd) {
  449. guint j;
  450. const gchar *alias;
  451. if (rspamadm_command_maybe_match_name (cmd->name, cmd_name)) {
  452. rspamd_fprintf (stderr, "%s\n", cmd->name);
  453. }
  454. else {
  455. PTR_ARRAY_FOREACH (cmd->aliases, j, alias) {
  456. if (rspamadm_command_maybe_match_name (alias, cmd_name)) {
  457. rspamd_fprintf (stderr, "%s\n", alias);
  458. }
  459. }
  460. }
  461. }
  462. retcode = EXIT_FAILURE;
  463. goto end;
  464. }
  465. if (nargc < argc) {
  466. if (lua_file) {
  467. nargv = g_malloc0 (sizeof (gchar *) * (argc - nargc + 2));
  468. nargv[1] = g_strdup (argv[nargc]);
  469. i = 2;
  470. argc ++;
  471. }
  472. else {
  473. nargv = g_malloc0 (sizeof (gchar *) * (argc - nargc + 1));
  474. i = 1;
  475. }
  476. nargv[0] = g_strdup_printf ("%s %s", argv[0], cmd_name);
  477. for (; i < argc - nargc; i ++) {
  478. if (lua_file) {
  479. /*
  480. * We append prefix '--arg=' to each argument and shift argv index
  481. */
  482. gsize arglen = strlen (argv[i + nargc - 1]);
  483. arglen += sizeof ("--args="); /* Including \0 */
  484. nargv[i] = g_malloc (arglen);
  485. rspamd_snprintf (nargv[i], arglen, "--args=%s", argv[i + nargc - 1]);
  486. }
  487. else {
  488. nargv[i] = g_strdup (argv[i + nargc]);
  489. }
  490. }
  491. targc = argc - nargc;
  492. targv = nargv;
  493. cmd->run (targc, targv, cmd);
  494. g_strfreev (nargv);
  495. }
  496. else {
  497. cmd->run (0, NULL, cmd);
  498. }
  499. ev_break (rspamd_main->event_loop, EVBREAK_ALL);
  500. end:
  501. g_option_context_free (context);
  502. rspamd_dns_resolver_deinit (resolver);
  503. REF_RELEASE (rspamd_main->cfg);
  504. rspamd_http_context_free (rspamd_main->http_ctx);
  505. rspamd_log_close (rspamd_main->logger);
  506. rspamd_url_deinit ();
  507. g_ptr_array_free (all_commands, TRUE);
  508. ev_loop_destroy (rspamd_main->event_loop);
  509. g_hash_table_unref (ucl_vars);
  510. rspamd_mempool_delete (rspamd_main->server_pool);
  511. g_free (rspamd_main);
  512. return retcode;
  513. }