diff options
Diffstat (limited to '')
-rw-r--r-- | src/rspamadm/rspamadm.c | 621 |
1 files changed, 621 insertions, 0 deletions
diff --git a/src/rspamadm/rspamadm.c b/src/rspamadm/rspamadm.c new file mode 100644 index 0000000..0e38dc3 --- /dev/null +++ b/src/rspamadm/rspamadm.c @@ -0,0 +1,621 @@ +/* + * Copyright 2023 Vsevolod Stakhov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rspamadm.h" +#include "rspamd.h" +#include "ottery.h" +#include "lua/lua_common.h" +#include "lua/lua_thread_pool.h" +#include "lua_ucl.h" +#include "unix-std.h" +#include "contrib/libev/ev.h" + +#ifdef HAVE_LIBUTIL_H +#include <libutil.h> +#endif + +static gboolean verbose = FALSE; +static gboolean list_commands = FALSE; +static gboolean show_help = FALSE; +static gboolean show_version = FALSE; +GHashTable *ucl_vars = NULL; +gchar **lua_env = NULL; +struct rspamd_main *rspamd_main = NULL; +struct rspamd_async_session *rspamadm_session = NULL; +lua_State *L = NULL; + +/* Defined in modules.c */ +extern module_t *modules[]; +extern worker_t *workers[]; + +static void rspamadm_help(gint argc, gchar **argv, const struct rspamadm_command *); +static const char *rspamadm_help_help(gboolean full_help, const struct rspamadm_command *); + +struct rspamadm_command help_command = { + .name = "help", + .flags = RSPAMADM_FLAG_NOHELP, + .help = rspamadm_help_help, + .run = rspamadm_help}; + +static gboolean rspamadm_parse_ucl_var(const gchar *option_name, + const gchar *value, gpointer data, + GError **error); + + +static GOptionEntry entries[] = { + {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, + "Enable verbose logging", NULL}, + {"list-commands", 'l', 0, G_OPTION_ARG_NONE, &list_commands, + "List available commands", NULL}, + {"var", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &rspamadm_parse_ucl_var, + "Redefine/define environment variable", NULL}, + {"help", 'h', 0, G_OPTION_ARG_NONE, &show_help, + "Show help", NULL}, + {"version", 'V', 0, G_OPTION_ARG_NONE, &show_version, + "Show version", NULL}, + {"lua-env", '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &lua_env, + "Load lua environment from the specified files", NULL}, + {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}}; + +GQuark +rspamadm_error(void) +{ + return g_quark_from_static_string("rspamadm"); +} + +static void +rspamadm_version(void) +{ + rspamd_printf("Rspamadm %s\n", RVERSION); +} + +static void +rspamadm_usage(GOptionContext *context) +{ + gchar *help_str; + + help_str = g_option_context_get_help(context, TRUE, NULL); + rspamd_printf("%s", help_str); +} + +static void +rspamadm_commands(GPtrArray *all_commands) +{ + const struct rspamadm_command *cmd; + guint i; + + rspamd_printf("Rspamadm %s\n", RVERSION); + rspamd_printf("Usage: rspamadm [global_options] command [command_options]\n"); + rspamd_printf("\nAvailable commands:\n"); + + PTR_ARRAY_FOREACH(all_commands, i, cmd) + { + if (!(cmd->flags & RSPAMADM_FLAG_NOHELP)) { + if (cmd->flags & RSPAMADM_FLAG_LUA) { + (void) cmd->help(FALSE, cmd); + } + else { + printf(" %-18s %-60s\n", cmd->name, cmd->help(FALSE, cmd)); + } + } + } +} + +static const char * +rspamadm_help_help(gboolean full_help, const struct rspamadm_command *cmd) +{ + const char *help_str; + + if (full_help) { + help_str = "Shows help for a specified command\n" + "Usage: rspamadm help <command>"; + } + else { + help_str = "Shows help for a specified command"; + } + + return help_str; +} + +static void +rspamadm_help(gint argc, gchar **argv, const struct rspamadm_command *command) +{ + const gchar *cmd_name; + const struct rspamadm_command *cmd; + GPtrArray *all_commands = (GPtrArray *) command->command_data; + + rspamd_printf("Rspamadm %s\n", RVERSION); + rspamd_printf("Usage: rspamadm [global_options] command [command_options]\n\n"); + + if (argc <= 1) { + cmd_name = "help"; + } + else { + cmd_name = argv[1]; + rspamd_printf("Showing help for %s command\n\n", cmd_name); + } + + cmd = rspamadm_search_command(cmd_name, all_commands); + + if (cmd == NULL) { + fprintf(stderr, "Invalid command name: %s\n", cmd_name); + exit(EXIT_FAILURE); + } + + if (strcmp(cmd_name, "help") == 0) { + guint i; + rspamd_printf("Available commands:\n"); + + PTR_ARRAY_FOREACH(all_commands, i, cmd) + { + if (!(cmd->flags & RSPAMADM_FLAG_NOHELP)) { + if (!(cmd->flags & RSPAMADM_FLAG_LUA)) { + printf(" %-18s %-60s\n", cmd->name, + cmd->help(FALSE, cmd)); + } + else { + /* Just call lua subr */ + (void) cmd->help(FALSE, cmd); + } + } + } + } + else { + if (!(cmd->flags & RSPAMADM_FLAG_LUA)) { + rspamd_printf("%s\n", cmd->help(TRUE, cmd)); + } + else { + /* Just call lua subr */ + (void) cmd->help(TRUE, cmd); + } + } +} + +static gboolean +rspamadm_parse_ucl_var(const gchar *option_name, + const gchar *value, gpointer data, + GError **error) +{ + gchar *k, *v, *t; + + t = strchr(value, '='); + + if (t != NULL) { + k = g_strdup(value); + t = k + (t - value); + v = g_strdup(t + 1); + *t = '\0'; + + g_hash_table_insert(ucl_vars, k, v); + } + else { + g_set_error(error, rspamadm_error(), EINVAL, + "Bad variable format: %s", value); + return FALSE; + } + + return TRUE; +} + +static void +lua_thread_str_error_cb(struct thread_entry *thread, int ret, const char *msg) +{ + struct lua_call_data *cd = thread->cd; + + msg_err("call to rspamadm lua script failed (%d): %s", ret, msg); + + cd->ret = ret; +} + +gboolean +rspamadm_execute_lua_ucl_subr(gint argc, gchar **argv, + const ucl_object_t *res, + const gchar *script_name, + gboolean rspamadm_subcommand) +{ + struct thread_entry *thread = lua_thread_pool_get_for_config(rspamd_main->cfg); + + lua_State *L = thread->lua_state; + + gint i; + gchar str[PATH_MAX]; + + g_assert(script_name != NULL); + g_assert(res != NULL); + g_assert(L != NULL); + + /* Init internal rspamadm routines */ + + if (rspamadm_subcommand) { + rspamd_snprintf(str, sizeof(str), "return require \"%s.%s\"", "rspamadm", + script_name); + } + else { + rspamd_snprintf(str, sizeof(str), "return require \"%s\"", + script_name); + } + + if (luaL_dostring(L, str) != 0) { + msg_err("cannot execute lua script %s: %s", + str, lua_tostring(L, -1)); + return FALSE; + } + else { + if (lua_type(L, -1) == LUA_TTABLE) { + lua_pushstring(L, "handler"); + lua_gettable(L, -2); + } + + if (lua_type(L, -1) != LUA_TFUNCTION) { + msg_err("lua script must return " + "function and not %s", + lua_typename(L, lua_type(L, -1))); + + return FALSE; + } + } + + /* Push function */ + lua_pushvalue(L, -1); + + /* Push argv */ + lua_newtable(L); + + for (i = 1; i < argc; i++) { + lua_pushstring(L, argv[i]); + lua_rawseti(L, -2, i); + } + + /* Push results */ + ucl_object_push_lua(L, res, TRUE); + + if (lua_repl_thread_call(thread, 2, NULL, lua_thread_str_error_cb) != 0) { + + return FALSE; + } + + /* error function */ + lua_settop(L, 0); + + return TRUE; +} + +static gint +rspamdadm_commands_sort_func(gconstpointer a, gconstpointer b) +{ + const struct rspamadm_command *cmda = *((struct rspamadm_command const **) a), + *cmdb = *((struct rspamadm_command const **) b); + + return strcmp(cmda->name, cmdb->name); +} + +static gboolean +rspamadm_command_maybe_match_name(const gchar *cmd, const gchar *input) +{ + gsize clen, inplen; + + clen = strlen(cmd); + inplen = strlen(input); + + if (rspamd_strings_levenshtein_distance(cmd, clen, + input, inplen, 1) == 1) { + return TRUE; + } + else if ((clen > inplen && + rspamd_substring_search(cmd, clen, input, inplen) != -1) || + (inplen > clen && + rspamd_substring_search(input, inplen, cmd, clen) != -1)) { + return TRUE; + } + + return FALSE; +} + + +static void +rspamadm_add_lua_globals(struct rspamd_dns_resolver *resolver) +{ + struct rspamd_async_session **psession; + struct ev_loop **pev_base; + struct rspamd_dns_resolver **presolver; + + rspamadm_session = rspamd_session_create(rspamd_main->cfg->cfg_pool, NULL, + NULL, (event_finalizer_t) NULL, NULL); + + psession = lua_newuserdata(L, sizeof(struct rspamd_async_session *)); + rspamd_lua_setclass(L, "rspamd{session}", -1); + *psession = rspamadm_session; + lua_setglobal(L, "rspamadm_session"); + + pev_base = lua_newuserdata(L, sizeof(struct ev_loop *)); + rspamd_lua_setclass(L, "rspamd{ev_base}", -1); + *pev_base = rspamd_main->event_loop; + lua_setglobal(L, "rspamadm_ev_base"); + + presolver = lua_newuserdata(L, sizeof(struct rspamd_dns_resolver *)); + rspamd_lua_setclass(L, "rspamd{resolver}", -1); + *presolver = resolver; + lua_setglobal(L, "rspamadm_dns_resolver"); +} + +static void +rspamadm_cmd_dtor(gpointer p) +{ + struct rspamadm_command *cmd = (struct rspamadm_command *) p; + + if (cmd->flags & RSPAMADM_FLAG_DYNAMIC) { + if (cmd->aliases) { + g_ptr_array_free(cmd->aliases, TRUE); + } + + g_free((gpointer) cmd->name); + g_free(cmd); + } +} + +gint main(gint argc, gchar **argv, gchar **env) +{ + GError *error = NULL; + GOptionContext *context; + GOptionGroup *og; + struct rspamd_config *cfg; + GQuark process_quark; + gchar **nargv, **targv; + const gchar *cmd_name; + const struct rspamadm_command *cmd; + struct rspamd_dns_resolver *resolver; + GPtrArray *all_commands = g_ptr_array_new_full(32, + rspamadm_cmd_dtor); /* Discovered during check */ + gint i, nargc, targc; + worker_t **pworker; + gboolean lua_file = FALSE; + gint retcode = 0; + + ucl_vars = g_hash_table_new_full(rspamd_strcase_hash, + rspamd_strcase_equal, g_free, g_free); + process_quark = g_quark_from_static_string("rspamadm"); + cfg = rspamd_config_new(RSPAMD_CONFIG_INIT_DEFAULT | RSPAMD_CONFIG_INIT_WIPE_LUA_MEM); + cfg->libs_ctx = rspamd_init_libs(); + rspamd_main = g_malloc0(sizeof(*rspamd_main)); + rspamd_main->cfg = cfg; + rspamd_main->pid = getpid(); + rspamd_main->type = process_quark; + rspamd_main->server_pool = rspamd_mempool_new(rspamd_mempool_suggest_size(), + "rspamadm", 0); + + rspamadm_fill_internal_commands(all_commands); + help_command.command_data = all_commands; + + /* Now read options and store everything till the first non-dash argument */ + nargv = g_malloc0(sizeof(gchar *) * (argc + 1)); + nargv[0] = g_strdup(argv[0]); + + for (i = 1, nargc = 1; i < argc; i++) { + if (argv[i] && argv[i][0] == '-') { + /* Copy to nargv */ + nargv[nargc] = g_strdup(argv[i]); + nargc++; + } + else { + break; + } + } + + context = g_option_context_new("command - rspamd administration utility"); + og = g_option_group_new("global", "global options", "global options", + NULL, NULL); + g_option_context_set_help_enabled(context, FALSE); + g_option_group_add_entries(og, entries); + g_option_context_set_summary(context, + "Summary:\n Rspamd administration utility version " RVERSION + "\n Release id: " RID); + g_option_context_set_main_group(context, og); + + targv = nargv; + targc = nargc; + + if (!g_option_context_parse(context, &targc, &targv, &error)) { + fprintf(stderr, "option parsing failed: %s\n", error->message); + g_error_free(error); + g_option_context_free(context); + exit(EXIT_FAILURE); + } + + + /* Setup logger */ + if (verbose) { + rspamd_main->logger = rspamd_log_open_emergency(rspamd_main->server_pool, + RSPAMD_LOG_FLAG_USEC | RSPAMD_LOG_FLAG_ENFORCED | RSPAMD_LOG_FLAG_RSPAMADM); + rspamd_log_set_log_level(rspamd_main->logger, G_LOG_LEVEL_DEBUG); + } + else { + rspamd_main->logger = rspamd_log_open_emergency(rspamd_main->server_pool, + RSPAMD_LOG_FLAG_RSPAMADM); + rspamd_log_set_log_level(rspamd_main->logger, G_LOG_LEVEL_MESSAGE); + } + + rspamd_main->event_loop = ev_default_loop(rspamd_config_ev_backend_get(cfg)); + + resolver = rspamd_dns_resolver_init(rspamd_main->logger, + rspamd_main->event_loop, + cfg); + rspamd_main->http_ctx = rspamd_http_context_create(cfg, rspamd_main->event_loop, + NULL); + + g_log_set_default_handler(rspamd_glib_log_function, rspamd_main->logger); + g_set_printerr_handler(rspamd_glib_printerr_function); + rspamd_config_post_load(cfg, + RSPAMD_CONFIG_INIT_LIBS | RSPAMD_CONFIG_INIT_URL | RSPAMD_CONFIG_INIT_NO_TLD); + + pworker = &workers[0]; + while (*pworker) { + /* Init string quarks */ + (void) g_quark_from_static_string((*pworker)->name); + pworker++; + } + + cfg->compiled_modules = modules; + cfg->compiled_workers = workers; + + rspamd_setproctitle("rspamdadm"); + + L = cfg->lua_state; + rspamd_lua_set_path(L, NULL, ucl_vars); + + if (!rspamd_lua_set_env(L, ucl_vars, lua_env, &error)) { + rspamd_fprintf(stderr, "Cannot load lua environment: %e", error); + g_error_free(error); + + goto end; + } + + rspamd_lua_set_globals(cfg, L); + rspamadm_add_lua_globals(resolver); + rspamd_redis_pool_config(cfg->redis_pool, cfg, rspamd_main->event_loop); + + /* Init rspamadm global */ + lua_newtable(L); + + PTR_ARRAY_FOREACH(all_commands, i, cmd) + { + if (cmd->lua_subrs != NULL) { + cmd->lua_subrs(L); + } + + cmd++; + } + + lua_setglobal(L, "rspamadm"); + + rspamadm_fill_lua_commands(L, all_commands); + rspamd_lua_start_gc(cfg); + g_ptr_array_sort(all_commands, rspamdadm_commands_sort_func); + + g_strfreev(nargv); + + if (show_version) { + rspamadm_version(); + goto end; + } + if (show_help) { + rspamadm_usage(context); + goto end; + } + if (list_commands) { + rspamadm_commands(all_commands); + goto end; + } + + cmd_name = argv[nargc]; + + if (cmd_name == NULL) { + cmd_name = "help"; + } + + gsize cmdlen = strlen(cmd_name); + + if (cmdlen > 4 && memcmp(cmd_name + (cmdlen - 4), ".lua", 4) == 0) { + cmd_name = "lua"; + lua_file = TRUE; + } + + cmd = rspamadm_search_command(cmd_name, all_commands); + + if (cmd == NULL) { + rspamd_fprintf(stderr, "Invalid command name: %s\n", cmd_name); + + /* Try fuzz search */ + rspamd_fprintf(stderr, "Suggested commands:\n"); + PTR_ARRAY_FOREACH(all_commands, i, cmd) + { + guint j; + const gchar *alias; + + if (rspamadm_command_maybe_match_name(cmd->name, cmd_name)) { + rspamd_fprintf(stderr, "%s\n", cmd->name); + } + else { + PTR_ARRAY_FOREACH(cmd->aliases, j, alias) + { + if (rspamadm_command_maybe_match_name(alias, cmd_name)) { + rspamd_fprintf(stderr, "%s\n", alias); + } + } + } + } + + retcode = EXIT_FAILURE; + goto end; + } + + if (nargc < argc) { + + if (lua_file) { + nargv = g_malloc0(sizeof(gchar *) * (argc - nargc + 2)); + nargv[1] = g_strdup(argv[nargc]); + i = 2; + argc++; + } + else { + nargv = g_malloc0(sizeof(gchar *) * (argc - nargc + 1)); + i = 1; + } + + nargv[0] = g_strdup_printf("%s %s", argv[0], cmd_name); + + for (; i < argc - nargc; i++) { + if (lua_file) { + /* + * We append prefix '--arg=' to each argument and shift argv index + */ + gsize arglen = strlen(argv[i + nargc - 1]); + + arglen += sizeof("--args="); /* Including \0 */ + nargv[i] = g_malloc(arglen); + rspamd_snprintf(nargv[i], arglen, "--args=%s", argv[i + nargc - 1]); + } + else { + nargv[i] = g_strdup(argv[i + nargc]); + } + } + + targc = argc - nargc; + targv = nargv; + cmd->run(targc, targv, cmd); + g_strfreev(nargv); + } + else { + cmd->run(0, NULL, cmd); + } + + ev_break(rspamd_main->event_loop, EVBREAK_ALL); + +end: + rspamd_session_destroy(rspamadm_session); + g_option_context_free(context); + rspamd_dns_resolver_deinit(resolver); + REF_RELEASE(rspamd_main->cfg); + rspamd_http_context_free(rspamd_main->http_ctx); + rspamd_log_close(rspamd_main->logger); + rspamd_url_deinit(); + g_ptr_array_free(all_commands, TRUE); + ev_loop_destroy(rspamd_main->event_loop); + g_hash_table_unref(ucl_vars); + rspamd_mempool_delete(rspamd_main->server_pool); + g_free(rspamd_main); + + return retcode; +} |