/* * 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 "cfg_file.h" #include "cfg_rcl.h" #include "utlist.h" #include "rspamd.h" #include "lua/lua_common.h" #include "utlist.h" static gboolean json = FALSE; static gboolean compact = FALSE; static gboolean show_help = FALSE; static gboolean show_comments = FALSE; static gboolean modules_state = FALSE; static gboolean symbol_groups_only = FALSE; static gboolean symbol_full_details = FALSE; static gboolean skip_template = FALSE; static gchar *config = NULL; extern struct rspamd_main *rspamd_main; /* Defined in modules.c */ extern module_t *modules[]; extern worker_t *workers[]; static void rspamadm_configdump(gint argc, gchar **argv, const struct rspamadm_command *); static const char *rspamadm_configdump_help(gboolean full_help, const struct rspamadm_command *); struct rspamadm_command configdump_command = { .name = "configdump", .flags = 0, .help = rspamadm_configdump_help, .run = rspamadm_configdump, .lua_subrs = NULL, }; static GOptionEntry entries[] = { {"json", 'j', 0, G_OPTION_ARG_NONE, &json, "Json output (pretty formatted)", NULL}, {"compact", 'C', 0, G_OPTION_ARG_NONE, &compact, "Compacted json output", NULL}, {"config", 'c', 0, G_OPTION_ARG_STRING, &config, "Config file to test", NULL}, {"show-help", 'h', 0, G_OPTION_ARG_NONE, &show_help, "Show help as comments for each option", NULL}, {"show-comments", 's', 0, G_OPTION_ARG_NONE, &show_comments, "Show saved comments from the configuration file", NULL}, {"modules-state", 'm', 0, G_OPTION_ARG_NONE, &modules_state, "Show modules state only", NULL}, {"groups", 'g', 0, G_OPTION_ARG_NONE, &symbol_groups_only, "Show symbols groups only", NULL}, {"symbol-details", 'd', 0, G_OPTION_ARG_NONE, &symbol_full_details, "Show full symbol details only", NULL}, {"skip-template", 'T', 0, G_OPTION_ARG_NONE, &skip_template, "Do not apply Jinja templates", NULL}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}}; static const char * rspamadm_configdump_help(gboolean full_help, const struct rspamadm_command *cmd) { const char *help_str; if (full_help) { help_str = "Perform configuration file dump\n\n" "Usage: rspamadm configdump [-c [-j --compact -m] [ [ ...]]]\n" "Where options are:\n\n" "-j: output plain json\n" "--compact: output compacted json\n" "-c: config file to test\n" "-m: show state of modules only\n" "-h: show help for dumped options\n" "--help: shows available options and commands"; } else { help_str = "Perform configuration file dump"; } return help_str; } static void config_logger(rspamd_mempool_t *pool, gpointer ud) { } static void rspamadm_add_doc_elt(const ucl_object_t *obj, const ucl_object_t *doc_obj, ucl_object_t *comment_obj) { rspamd_fstring_t *comment = rspamd_fstring_new(); const ucl_object_t *elt; ucl_object_t *nobj, *cur_comment; if (ucl_object_lookup_len(comment_obj, (const char *) &obj, sizeof(void *))) { rspamd_fstring_free(comment); /* Do not rewrite the existing comment */ return; } if (doc_obj != NULL) { /* Create doc comment */ nobj = ucl_object_fromstring_common("/*", 0, 0); } else { rspamd_fstring_free(comment); return; } /* We create comments as a list of parts */ elt = ucl_object_lookup(doc_obj, "data"); if (elt) { rspamd_printf_fstring(&comment, " * %s", ucl_object_tostring(elt)); cur_comment = ucl_object_fromstring_common(comment->str, comment->len, 0); rspamd_fstring_erase(comment, 0, comment->len); DL_APPEND(nobj, cur_comment); } elt = ucl_object_lookup(doc_obj, "type"); if (elt) { rspamd_printf_fstring(&comment, " * Type: %s", ucl_object_tostring(elt)); cur_comment = ucl_object_fromstring_common(comment->str, comment->len, 0); rspamd_fstring_erase(comment, 0, comment->len); DL_APPEND(nobj, cur_comment); } elt = ucl_object_lookup(doc_obj, "required"); if (elt) { rspamd_printf_fstring(&comment, " * Required: %s", ucl_object_toboolean(elt) ? "true" : "false"); cur_comment = ucl_object_fromstring_common(comment->str, comment->len, 0); rspamd_fstring_erase(comment, 0, comment->len); DL_APPEND(nobj, cur_comment); } cur_comment = ucl_object_fromstring(" */"); DL_APPEND(nobj, cur_comment); rspamd_fstring_free(comment); ucl_object_insert_key(comment_obj, ucl_object_ref(nobj), (const char *) &obj, sizeof(void *), true); ucl_object_unref(nobj); } static void rspamadm_gen_comments(const ucl_object_t *obj, const ucl_object_t *doc_obj, ucl_object_t *comments) { const ucl_object_t *cur_obj, *cur_doc, *cur_elt; ucl_object_iter_t it = NULL; if (obj == NULL || doc_obj == NULL) { return; } if (obj->keylen > 0) { rspamadm_add_doc_elt(obj, doc_obj, comments); } if (ucl_object_type(obj) == UCL_OBJECT) { while ((cur_obj = ucl_object_iterate(obj, &it, true))) { cur_doc = ucl_object_lookup_len(doc_obj, cur_obj->key, cur_obj->keylen); if (cur_doc != NULL) { LL_FOREACH(cur_obj, cur_elt) { if (ucl_object_lookup_len(comments, (const char *) &cur_elt, sizeof(void *)) == NULL) { rspamadm_gen_comments(cur_elt, cur_doc, comments); } } } } } } static void rspamadm_dump_section_obj(struct rspamd_config *cfg, const ucl_object_t *obj, const ucl_object_t *doc_obj) { rspamd_fstring_t *output; ucl_object_t *comments = NULL; output = rspamd_fstring_new(); if (show_help) { if (show_comments) { comments = cfg->config_comments; } else { comments = ucl_object_typed_new(UCL_OBJECT); } rspamadm_gen_comments(obj, doc_obj, comments); } else if (show_comments) { comments = cfg->config_comments; } if (json) { rspamd_ucl_emit_fstring_comments(obj, UCL_EMIT_JSON, &output, comments); } else if (compact) { rspamd_ucl_emit_fstring_comments(obj, UCL_EMIT_JSON_COMPACT, &output, comments); } else { rspamd_ucl_emit_fstring_comments(obj, UCL_EMIT_CONFIG, &output, comments); } rspamd_printf("%V", output); rspamd_fstring_free(output); if (comments != NULL) { ucl_object_unref(comments); } } __attribute__((noreturn)) static void rspamadm_configdump(gint argc, gchar **argv, const struct rspamadm_command *cmd) { GOptionContext *context; GError *error = NULL; const gchar *confdir; const ucl_object_t *obj = NULL, *cur, *doc_obj; struct rspamd_config *cfg = rspamd_main->cfg; gboolean ret = TRUE; worker_t **pworker; gint i; context = g_option_context_new( "configdump - dumps Rspamd configuration"); g_option_context_set_summary(context, "Summary:\n Rspamd administration utility version " RVERSION "\n Release id: " RID); g_option_context_add_main_entries(context, entries, NULL); if (!g_option_context_parse(context, &argc, &argv, &error)) { fprintf(stderr, "option parsing failed: %s\n", error->message); g_error_free(error); g_option_context_free(context); exit(EXIT_FAILURE); } g_option_context_free(context); if (config == NULL) { if ((confdir = g_hash_table_lookup(ucl_vars, "CONFDIR")) == NULL) { confdir = RSPAMD_CONFDIR; } config = g_strdup_printf("%s%c%s", confdir, G_DIR_SEPARATOR, "rspamd.conf"); } 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; cfg->cfg_name = config; if (!rspamd_config_read(cfg, cfg->cfg_name, config_logger, rspamd_main, ucl_vars, skip_template, lua_env)) { ret = FALSE; } else { /* Do post-load actions */ rspamd_lua_post_load_config(cfg); (void) rspamd_init_filters(rspamd_main->cfg, false, false); rspamd_config_post_load(cfg, RSPAMD_CONFIG_INIT_SYMCACHE); } if (ret) { if (modules_state) { rspamadm_execute_lua_ucl_subr(argc, argv, cfg->cfg_ucl_obj, "plugins_stats", FALSE); exit(EXIT_SUCCESS); } if (symbol_full_details) { /* * Create object from symbols groups and output it using the * specified format */ ucl_object_t *out = ucl_object_typed_new(UCL_OBJECT); GHashTableIter it; gpointer sk, sv; g_hash_table_iter_init(&it, cfg->symbols); ucl_object_t *sym_ucl = ucl_object_typed_new(UCL_OBJECT); const ucl_object_t *all_symbols_ucl = ucl_object_lookup(cfg->cfg_ucl_obj, "symbols"); while (g_hash_table_iter_next(&it, &sk, &sv)) { const gchar *sym_name = (const gchar *) sk; struct rspamd_symbol *s = (struct rspamd_symbol *) sv; ucl_object_t *this_sym_ucl = ucl_object_typed_new(UCL_OBJECT); ucl_object_insert_key(this_sym_ucl, ucl_object_fromdouble(s->score), "score", strlen("score"), false); ucl_object_insert_key(this_sym_ucl, ucl_object_fromstring(s->description), "description", strlen("description"), false); rspamd_symcache_get_symbol_details(cfg->cache, sym_name, this_sym_ucl); ucl_object_insert_key(this_sym_ucl, ucl_object_frombool(!!(s->flags & RSPAMD_SYMBOL_FLAG_DISABLED)), "disabled", strlen("disabled"), false); if (s->nshots == 1) { ucl_object_insert_key(this_sym_ucl, ucl_object_frombool(true), "one_shot", strlen("one_shot"), false); } else { ucl_object_insert_key(this_sym_ucl, ucl_object_frombool(false), "one_shot", strlen("one_shot"), false); } if (s->gr != NULL) { struct rspamd_symbols_group *gr = s->gr; const char *gr_name = gr->name; if (strcmp(gr_name, "ungrouped") != 0) { ucl_object_insert_key(this_sym_ucl, ucl_object_fromstring(gr_name), "group", strlen("group"), false); } if (s->groups) { ucl_object_t *add_groups = ucl_object_typed_new(UCL_ARRAY); guint j; struct rspamd_symbols_group *add_gr; bool has_extra_groups = false; PTR_ARRAY_FOREACH(s->groups, j, add_gr) { if (add_gr->name && strcmp(add_gr->name, gr_name) != 0) { ucl_array_append(add_groups, ucl_object_fromstring(add_gr->name)); has_extra_groups = true; } } if (has_extra_groups == true) { ucl_object_insert_key(this_sym_ucl, add_groups, "groups", strlen("groups"), false); } } } const ucl_object_t *loaded_symbol_ucl = ucl_object_lookup(all_symbols_ucl, sym_name); if (loaded_symbol_ucl) { ucl_object_iter_t it = NULL; while ((cur = ucl_iterate_object(loaded_symbol_ucl, &it, true)) != NULL) { const char *key = ucl_object_key(cur); /* If this key isn't something we have direct in the symbol item, grab the key/value */ if ((strcmp(key, "score") != 0) && (strcmp(key, "description") != 0) && (strcmp(key, "disabled") != 0) && (strcmp(key, "condition") != 0) && (strcmp(key, "one_shot") != 0) && (strcmp(key, "any_shot") != 0) && (strcmp(key, "nshots") != 0) && (strcmp(key, "one_param") != 0) && (strcmp(key, "priority") != 0)) { ucl_object_insert_key(this_sym_ucl, (ucl_object_t *) cur, key, strlen(key), false); } } } ucl_object_insert_key(sym_ucl, this_sym_ucl, sym_name, strlen(sym_name), true); } ucl_object_insert_key(out, sym_ucl, "symbols", strlen("symbols"), true); rspamadm_dump_section_obj(cfg, out, NULL); exit(EXIT_SUCCESS); } if (symbol_groups_only) { /* * Create object from symbols groups and output it using the * specified format */ ucl_object_t *out = ucl_object_typed_new(UCL_OBJECT); GHashTableIter it; gpointer k, v; g_hash_table_iter_init(&it, cfg->groups); while (g_hash_table_iter_next(&it, &k, &v)) { const gchar *gr_name = (const gchar *) k; struct rspamd_symbols_group *gr = (struct rspamd_symbols_group *) v; ucl_object_t *gr_ucl = ucl_object_typed_new(UCL_OBJECT); ucl_object_insert_key(gr_ucl, ucl_object_frombool(!!(gr->flags & RSPAMD_SYMBOL_GROUP_PUBLIC)), "public", strlen("public"), false); ucl_object_insert_key(gr_ucl, ucl_object_frombool(!!(gr->flags & RSPAMD_SYMBOL_GROUP_DISABLED)), "disabled", strlen("disabled"), false); ucl_object_insert_key(gr_ucl, ucl_object_frombool(!!(gr->flags & RSPAMD_SYMBOL_GROUP_ONE_SHOT)), "one_shot", strlen("one_shot"), false); ucl_object_insert_key(gr_ucl, ucl_object_fromdouble(gr->max_score), "max_score", strlen("max_score"), false); ucl_object_insert_key(gr_ucl, ucl_object_fromstring(gr->description), "description", strlen("description"), false); if (gr->symbols) { GHashTableIter sit; gpointer sk, sv; g_hash_table_iter_init(&sit, gr->symbols); ucl_object_t *sym_ucl = ucl_object_typed_new(UCL_OBJECT); while (g_hash_table_iter_next(&sit, &sk, &sv)) { const gchar *sym_name = (const gchar *) sk; struct rspamd_symbol *s = (struct rspamd_symbol *) sv; ucl_object_t *spec_sym = ucl_object_typed_new(UCL_OBJECT); ucl_object_insert_key(spec_sym, ucl_object_fromdouble(s->score), "score", strlen("score"), false); ucl_object_insert_key(spec_sym, ucl_object_fromstring(s->description), "description", strlen("description"), false); ucl_object_insert_key(spec_sym, ucl_object_frombool(!!(s->flags & RSPAMD_SYMBOL_FLAG_DISABLED)), "disabled", strlen("disabled"), false); if (s->nshots == 1) { ucl_object_insert_key(spec_sym, ucl_object_frombool(true), "one_shot", strlen("one_shot"), false); } else { ucl_object_insert_key(spec_sym, ucl_object_frombool(false), "one_shot", strlen("one_shot"), false); } ucl_object_t *add_groups = ucl_object_typed_new(UCL_ARRAY); guint j; struct rspamd_symbols_group *add_gr; PTR_ARRAY_FOREACH(s->groups, j, add_gr) { if (add_gr->name && strcmp(add_gr->name, gr_name) != 0) { ucl_array_append(add_groups, ucl_object_fromstring(add_gr->name)); } } ucl_object_insert_key(spec_sym, add_groups, "extra_groups", strlen("extra_groups"), false); ucl_object_insert_key(sym_ucl, spec_sym, sym_name, strlen(sym_name), true); } ucl_object_insert_key(gr_ucl, sym_ucl, "symbols", strlen("symbols"), false); } ucl_object_insert_key(out, gr_ucl, gr_name, strlen(gr_name), true); } rspamadm_dump_section_obj(cfg, out, NULL); exit(EXIT_SUCCESS); } /* Output configuration */ if (argc == 1) { rspamadm_dump_section_obj(cfg, cfg->cfg_ucl_obj, cfg->doc_strings); } else { for (i = 1; i < argc; i++) { obj = ucl_object_lookup_path(cfg->cfg_ucl_obj, argv[i]); doc_obj = ucl_object_lookup_path(cfg->doc_strings, argv[i]); if (!obj) { rspamd_printf("Section %s NOT FOUND\n", argv[i]); } else { LL_FOREACH(obj, cur) { if (!json && !compact) { rspamd_printf("*** Section %s ***\n", argv[i]); } rspamadm_dump_section_obj(cfg, cur, doc_obj); if (!json && !compact) { rspamd_printf("\n*** End of section %s ***\n", argv[i]); } else { rspamd_printf("\n"); } } } } } } exit(ret ? EXIT_SUCCESS : EXIT_FAILURE); }