diff options
author | Vsevolod Stakhov <vsevolod@rspamd.com> | 2023-01-21 12:15:12 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-21 12:15:12 +0000 |
commit | 4445807558c29aa5c376b19fcb81528141ec3571 (patch) | |
tree | 83f343a8904e4684cc82c7d7746583565d38a2f1 /src/client/rspamc.cxx | |
parent | ea4d03b6f98f373e9c1fa4f96af69b60765e6194 (diff) | |
parent | 42e1c8d507f72adc2a7390ac4baf1248067cf40b (diff) | |
download | rspamd-4445807558c29aa5c376b19fcb81528141ec3571.tar.gz rspamd-4445807558c29aa5c376b19fcb81528141ec3571.zip |
Merge pull request #4377 from amishmm/humanreport
[Feature] rspamc: add -R option for human readable report
Diffstat (limited to 'src/client/rspamc.cxx')
-rw-r--r-- | src/client/rspamc.cxx | 199 |
1 files changed, 178 insertions, 21 deletions
diff --git a/src/client/rspamc.cxx b/src/client/rspamc.cxx index 973bb58a4..5bb75ef38 100644 --- a/src/client/rspamc.cxx +++ b/src/client/rspamc.cxx @@ -71,6 +71,7 @@ static gboolean pass_all; static gboolean tty = FALSE; static gboolean verbose = FALSE; static gboolean print_commands = FALSE; +static gboolean humanreport = FALSE; static gboolean json = FALSE; static gboolean compact = FALSE; static gboolean headers = FALSE; @@ -134,6 +135,7 @@ static GOptionEntry entries[] = "Bind to specified ip address", nullptr}, {"commands", 0, 0, G_OPTION_ARG_NONE, &print_commands, "List available commands", nullptr}, + {"human", 'R', 0, G_OPTION_ARG_NONE, &humanreport, "Output human readable report", nullptr}, {"json", 'j', 0, G_OPTION_ARG_NONE, &json, "Output json reply", nullptr}, {"compact", '\0', 0, G_OPTION_ARG_NONE, &compact, "Output compact json reply", nullptr}, {"headers", 0, 0, G_OPTION_ARG_NONE, &headers, "Output HTTP headers", @@ -824,6 +826,71 @@ add_options(GQueue *opts) } static void +print_indented_line(FILE *out, std::string_view line, size_t maxlen, size_t indent) +{ + if (maxlen < 1) { + return; + } + + std::string_view s; + for (size_t pos = 0; pos < line.size(); pos += s.size()) { + s = line.substr(pos, pos ? (maxlen-indent) : maxlen); + if (indent && pos) { + fmt::print(out, "{:>{}}", " ", indent); + } + fmt::print(out, "{}\n", s); + } +} + +static void +rspamc_symbol_human_output(FILE *out, const ucl_object_t *obj) +{ + auto first = true; + auto score = 0.0; + const char *desc = nullptr; + + const auto *key = ucl_object_key(obj); + const auto *val = ucl_object_lookup(obj, "score"); + if (val != nullptr) { + score = ucl_object_todouble(val); + } + + val = ucl_object_lookup(obj, "description"); + if (val != nullptr) { + desc = ucl_object_tostring(val); + } + + auto line = fmt::format("{:>4.1f} {:<22} ", score, key); + if (desc != nullptr) { + line += desc; + } + + val = ucl_object_lookup(obj, "options"); + if (val != nullptr && ucl_object_type(val) == UCL_ARRAY) { + ucl_object_iter_t it = nullptr; + const ucl_object_t *cur; + + line += fmt::format("{}[", desc == nullptr ? "" : " "); + + while ((cur = ucl_object_iterate (val, &it, true)) != nullptr) { + if (first) { + line += fmt::format("{}", ucl_object_tostring(cur)); + first = false; + } + else { + line += fmt::format(",{}", ucl_object_tostring(cur)); + } + } + line += ']'; + } + else if (desc == nullptr) { + line += '\n'; + } + + print_indented_line(out, line, 78, 28); +} + +static void rspamc_symbol_output(FILE *out, const ucl_object_t *obj) { auto first = true; @@ -835,7 +902,7 @@ rspamc_symbol_output(FILE *out, const ucl_object_t *obj) fmt::print(out, "({:.2f})", ucl_object_todouble(val)); } val = ucl_object_lookup(obj, "options"); - if (val != nullptr && val->type == UCL_ARRAY) { + if (val != nullptr && ucl_object_type(val) == UCL_ARRAY) { ucl_object_iter_t it = nullptr; const ucl_object_t *cur; @@ -858,40 +925,71 @@ rspamc_symbol_output(FILE *out, const ucl_object_t *obj) static void rspamc_metric_output(FILE *out, const ucl_object_t *obj) { - double score = 0, required_score = 0; int got_scores = 0; + bool is_spam = false, is_skipped = false; + double score = 0, required_score = 0, greylist_score =0, addheader_score = 0; auto print_protocol_string = [&](const char *ucl_name, const char *output_message) { auto *elt = ucl_object_lookup(obj, ucl_name); if (elt) { - fmt::print(out, "{}: {}\n", output_message, - emphasis_argument(ucl_object_tostring(elt))); + if (humanreport) { + fmt::print(out, ",{}={}", output_message, emphasis_argument(ucl_object_tostring(elt))); + } + else { + fmt::print(out, "{}: {}\n", output_message, emphasis_argument(ucl_object_tostring(elt))); + } } }; - fmt::print(out, "[Metric: default]\n"); + if (!humanreport) { + fmt::print(out, "[Metric: default]\n"); + } const auto *elt = ucl_object_lookup(obj, "required_score"); - if (elt) { required_score = ucl_object_todouble(elt); got_scores++; } elt = ucl_object_lookup(obj, "score"); - if (elt) { score = ucl_object_todouble(elt); got_scores++; } + /* XXX: greylist_score is not yet in checkv2 */ + elt = ucl_object_lookup(obj, "greylist_score"); + if (elt) { + greylist_score = ucl_object_todouble(elt); + } + + /* XXX: addheader_score is not yet in checkv2 */ + elt = ucl_object_lookup(obj, "addheader_score"); + if (elt) { + addheader_score = ucl_object_todouble(elt); + } + + if (humanreport) { + fmt::print(out, + "{}/{}/{}/{}", + emphasis_argument(score, 2), + emphasis_argument(greylist_score, 2), + emphasis_argument(addheader_score, 2), + emphasis_argument(required_score, 2)); + } + elt = ucl_object_lookup(obj, "action"); if (elt) { auto act = rspamd_action_from_str_rspamc(ucl_object_tostring(elt)); if (act.has_value()) { if (!tty) { - print_protocol_string("action", "Action"); + if (humanreport) { + fmt::print(out, ",action={}:{}", act.value(), ucl_object_tostring(elt)); + } + else { + print_protocol_string("action", "Action"); + } } else { /* Colorize action type */ @@ -915,26 +1013,63 @@ rspamc_metric_output(FILE *out, const ucl_object_t *obj) colorized_action = fmt::format(fmt::emphasis::bold, ucl_object_tostring(elt)); break; } - fmt::print(out, "Action: {}\n", colorized_action); + + if (humanreport) { + fmt::print(out, ",action={}:{}", act.value(), colorized_action); + } + else { + fmt::print(out, "Action: {}\n", colorized_action); + } } - fmt::print(out, "Spam: {}\n", emphasis_argument(act.value() < METRIC_ACTION_GREYLIST ? - "true" : "false")); + is_spam = act.value() < METRIC_ACTION_GREYLIST ? true : false; + if (!humanreport) { + fmt::print(out, "Spam: {}\n", is_spam ? "true" : "false"); + } } else { - print_protocol_string("action", "Action"); + if (humanreport) { + fmt::print(out, ",action={}:{}", METRIC_ACTION_NOACTION, ucl_object_tostring(elt)); + } + else { + print_protocol_string("action", "Action"); + } } } - print_protocol_string("subject", "Subject"); + if (!humanreport) { + print_protocol_string("subject", "Subject"); + } + + if (humanreport) { + /* XXX: why checkv2 does not provide "is_spam"? */ + elt = ucl_object_lookup(obj, "is_spam"); + if (elt) { + is_spam = ucl_object_toboolean(elt); + } + + elt = ucl_object_lookup(obj, "is_skipped"); + if (elt) { + is_skipped = ucl_object_toboolean(elt); + } - if (got_scores == 2) { + fmt::print(out, ",spam={},skipped={}\n", is_spam ? 1 : 0, is_skipped ? 1 : 0); + } + else if (got_scores == 2) { fmt::print(out, "Score: {} / {}\n", emphasis_argument(score, 2), emphasis_argument(required_score, 2)); } + if (humanreport) { + fmt::print(out, "Content analysis details: ({} points, {} required)\n\n", + emphasis_argument(score, 2), + emphasis_argument(required_score, 2)); + fmt::print(out, " pts rule name description\n"); + fmt::print(out, "---- ---------------------- --------------------------------------------------\n"); + } + elt = ucl_object_lookup(obj, "symbols"); if (elt) { @@ -949,9 +1084,12 @@ rspamc_metric_output(FILE *out, const ucl_object_t *obj) sort_ucl_container_with_default(symbols, "name"); for (const auto *sym_obj : symbols) { - rspamc_symbol_output(out, sym_obj); + humanreport ? rspamc_symbol_human_output(out, sym_obj) : rspamc_symbol_output(out, sym_obj); } } + if (humanreport) { + fmt::print(out, "\n"); + } } static void @@ -988,8 +1126,10 @@ rspamc_symbols_output(FILE *out, ucl_object_t *obj) } }; - print_protocol_string("message-id", "Message-ID"); - print_protocol_string("queue-id", "Queue-ID"); + if (!humanreport) { + print_protocol_string("message-id", "Message-ID"); + print_protocol_string("queue-id", "Queue-ID"); + } const auto *elt = ucl_object_lookup(obj, "urls"); @@ -1003,7 +1143,14 @@ rspamc_symbols_output(FILE *out, ucl_object_t *obj) emitted = (char *)ucl_object_emit(elt, UCL_EMIT_JSON); } - fmt::print(out, "Urls: {}\n", emitted); + if (humanreport) { + if (emitted && strcmp(emitted, "[]")) { + print_indented_line(out, fmt::format("Domains found: {}", emitted), 78, 4); + } + } + else { + fmt::print(out, "Urls: {}\n", emitted); + } free(emitted); } @@ -1018,11 +1165,21 @@ rspamc_symbols_output(FILE *out, ucl_object_t *obj) emitted = (char *)ucl_object_emit(elt, UCL_EMIT_JSON); } - fmt::print(out, "Emails: {}\n", emitted); + if (humanreport) { + if (emitted && strcmp(emitted, "[]")) { + print_indented_line(out, fmt::format("Emails found: {}", emitted), 78, 4); + } + } + else { + fmt::print(out, "Emails: {}\n", emitted); + } free(emitted); } print_protocol_string("error", "Scan error"); + if (humanreport) { + return; + } elt = ucl_object_lookup(obj, "messages"); if (elt && elt->type == UCL_OBJECT) { @@ -1646,13 +1803,13 @@ rspamc_client_cb(struct rspamd_client_connection *conn, } else { if (cmd.need_input && !json) { - if (!compact) { + if (!compact && !humanreport) { fmt::print(out, "Results for file: {} ({:.3} seconds)\n", emphasis_argument(cbdata->filename), diff); } } else { - if (!compact && !json) { + if (!compact && !json && !humanreport) { fmt::print(out, "Results for command: {} ({:.3} seconds)\n", emphasis_argument(cmd.name), diff); } |