diff options
Diffstat (limited to '')
-rw-r--r-- | src/rspamadm/configdump.c | 555 |
1 files changed, 555 insertions, 0 deletions
diff --git a/src/rspamadm/configdump.c b/src/rspamadm/configdump.c new file mode 100644 index 0000000..dc8b822 --- /dev/null +++ b/src/rspamadm/configdump.c @@ -0,0 +1,555 @@ +/* + * 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 <config_name> [-j --compact -m] [<path1> [<path2> ...]]]\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); +} |