aboutsummaryrefslogtreecommitdiffstats
path: root/src/client/rspamc.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/rspamc.cxx')
-rw-r--r--src/client/rspamc.cxx116
1 files changed, 110 insertions, 6 deletions
diff --git a/src/client/rspamc.cxx b/src/client/rspamc.cxx
index 404359877..1dc48faae 100644
--- a/src/client/rspamc.cxx
+++ b/src/client/rspamc.cxx
@@ -59,6 +59,7 @@ static const char *user = nullptr;
static const char *helo = nullptr;
static const char *hostname = nullptr;
static const char *classifier = nullptr;
+static const char *learn_class_name = nullptr;
static const char *local_addr = nullptr;
static const char *execute = nullptr;
static const char *sort = nullptr;
@@ -90,6 +91,9 @@ static gboolean skip_attachments = FALSE;
static const char *pubkey = nullptr;
static const char *user_agent = "rspamc";
static const char *files_list = nullptr;
+static const char *queue_id = nullptr;
+static const char *log_tag = nullptr;
+static std::string settings;
std::vector<GPid> children;
static GPatternSpec **exclude_compiled = nullptr;
@@ -102,6 +106,11 @@ static gboolean rspamc_password_callback(const char *option_name,
gpointer data,
GError **error);
+static gboolean rspamc_settings_callback(const char *option_name,
+ const char *value,
+ gpointer data,
+ GError **error);
+
static GOptionEntry entries[] =
{
{"connect", 'h', 0, G_OPTION_ARG_STRING, &connect_str,
@@ -182,6 +191,12 @@ static GOptionEntry entries[] =
"Use specific User-Agent instead of \"rspamc\"", nullptr},
{"files-list", '\0', 0, G_OPTION_ARG_FILENAME, &files_list,
"Read one or more newline separated filenames to scan from file", nullptr},
+ {"queue-id", '\0', 0, G_OPTION_ARG_STRING, &queue_id,
+ "Set Queue-ID header for the request", nullptr},
+ {"log-tag", '\0', 0, G_OPTION_ARG_STRING, &log_tag,
+ "Set Log-Tag header for the request", nullptr},
+ {"settings", '\0', 0, G_OPTION_ARG_CALLBACK, (void *) &rspamc_settings_callback,
+ "Set Settings header as JSON/UCL for the request", nullptr},
{nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr}};
static void rspamc_symbols_output(FILE *out, ucl_object_t *obj);
@@ -198,6 +213,7 @@ enum rspamc_command_type {
RSPAMC_COMMAND_SYMBOLS,
RSPAMC_COMMAND_LEARN_SPAM,
RSPAMC_COMMAND_LEARN_HAM,
+ RSPAMC_COMMAND_LEARN_CLASS,
RSPAMC_COMMAND_FUZZY_ADD,
RSPAMC_COMMAND_FUZZY_DEL,
RSPAMC_COMMAND_FUZZY_DELHASH,
@@ -250,6 +266,15 @@ static const constexpr auto rspamc_commands = rspamd::array_of(
.need_input = TRUE,
.command_output_func = nullptr},
rspamc_command{
+ .cmd = RSPAMC_COMMAND_LEARN_CLASS,
+ .name = "learn_class",
+ .path = "learnclass",
+ .description = "learn message as class",
+ .is_controller = TRUE,
+ .is_privileged = TRUE,
+ .need_input = TRUE,
+ .command_output_func = nullptr},
+ rspamc_command{
.cmd = RSPAMC_COMMAND_FUZZY_ADD,
.name = "fuzzy_add",
.path = "fuzzyadd",
@@ -527,8 +552,7 @@ rspamc_password_callback(const char *option_name,
auto *map = (char *) locked_mmap.value().get_map();
value_view = std::string_view{map, locked_mmap->get_size()};
auto right = value_view.end() - 1;
- for (; right > value_view.cbegin() && g_ascii_isspace(*right); --right)
- ;
+ for (; right > value_view.cbegin() && g_ascii_isspace(*right); --right);
std::string_view str{value_view.begin(), static_cast<size_t>(right - value_view.begin()) + 1};
processed_passwd.assign(std::begin(str), std::end(str));
processed_passwd.push_back('\0'); /* Null-terminate for C part */
@@ -557,6 +581,46 @@ rspamc_password_callback(const char *option_name,
return TRUE;
}
+static gboolean
+rspamc_settings_callback(const char *option_name,
+ const char *value,
+ gpointer data,
+ GError **error)
+{
+ if (value == nullptr) {
+ g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+ "Settings parameter cannot be empty");
+ return FALSE;
+ }
+
+ // Parse the settings string using UCL to validate it
+ struct ucl_parser *parser = ucl_parser_new(UCL_PARSER_KEY_LOWERCASE);
+ if (!ucl_parser_add_string(parser, value, strlen(value))) {
+ auto *ucl_error = ucl_parser_get_error(parser);
+ g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+ "Invalid JSON/UCL in settings: %s", ucl_error);
+ ucl_parser_free(parser);
+ return FALSE;
+ }
+
+ // Get the parsed object and validate it
+ auto *obj = ucl_parser_get_object(parser);
+ if (obj == nullptr) {
+ g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+ "Failed to parse settings as JSON/UCL");
+ ucl_parser_free(parser);
+ return FALSE;
+ }
+
+ // Store the validated settings string
+ settings = value;
+
+ ucl_object_unref(obj);
+ ucl_parser_free(parser);
+
+ return TRUE;
+}
+
/*
* Parse command line
*/
@@ -649,6 +713,7 @@ check_rspamc_command(const char *cmd) -> std::optional<rspamc_command>
{"report", RSPAMC_COMMAND_SYMBOLS},
{"learn_spam", RSPAMC_COMMAND_LEARN_SPAM},
{"learn_ham", RSPAMC_COMMAND_LEARN_HAM},
+ {"learn_class", RSPAMC_COMMAND_LEARN_CLASS},
{"fuzzy_add", RSPAMC_COMMAND_FUZZY_ADD},
{"fuzzy_del", RSPAMC_COMMAND_FUZZY_DEL},
{"fuzzy_delhash", RSPAMC_COMMAND_FUZZY_DELHASH},
@@ -659,10 +724,33 @@ check_rspamc_command(const char *cmd) -> std::optional<rspamc_command>
});
std::string cmd_lc = rspamd_string_tolower(cmd);
+
+ // Handle learn_class:classname syntax
+ if (cmd_lc.find("learn_class:") == 0) {
+ auto colon_pos = cmd_lc.find(':');
+ if (colon_pos != std::string::npos && colon_pos + 1 < cmd_lc.length()) {
+ auto class_name = cmd_lc.substr(colon_pos + 1);
+ // Store class name globally for later use
+ learn_class_name = g_strdup(class_name.c_str());
+ // Return the learn_class command
+ auto elt_it = std::find_if(rspamc_commands.begin(), rspamc_commands.end(), [&](const auto &item) {
+ return item.cmd == RSPAMC_COMMAND_LEARN_CLASS;
+ });
+ if (elt_it != std::end(rspamc_commands)) {
+ return *elt_it;
+ }
+ }
+ return std::nullopt;
+ }
+
auto ct = rspamd::find_map(str_map, std::string_view{cmd_lc});
+ if (!ct.has_value()) {
+ return std::nullopt;
+ }
+
auto elt_it = std::find_if(rspamc_commands.begin(), rspamc_commands.end(), [&](const auto &item) {
- return item.cmd == ct;
+ return item.cmd == ct.value();
});
if (elt_it != std::end(rspamc_commands)) {
@@ -799,6 +887,10 @@ add_options(GQueue *opts)
add_client_header(opts, "Classifier", classifier);
}
+ if (learn_class_name) {
+ add_client_header(opts, "Class", learn_class_name);
+ }
+
if (weight != 0) {
auto nstr = fmt::format("{}", weight);
add_client_header(opts, "Weight", nstr.c_str());
@@ -852,6 +944,18 @@ add_options(GQueue *opts)
hdr++;
}
+ if (queue_id != nullptr) {
+ add_client_header(opts, "Queue-Id", queue_id);
+ }
+
+ if (log_tag != nullptr) {
+ add_client_header(opts, "Log-Tag", log_tag);
+ }
+
+ if (!settings.empty()) {
+ add_client_header(opts, "Settings", settings.c_str());
+ }
+
if (!flagbuf.empty()) {
if (flagbuf.back() == ',') {
flagbuf.pop_back();
@@ -1918,7 +2022,7 @@ rspamc_client_cb(struct rspamd_client_connection *conn,
if (raw_body) {
/* We can also output the resulting json */
- rspamc_print(out, "{}\n", std::string_view{raw_body, (std::size_t)(rawlen - bodylen)});
+ rspamc_print(out, "{}\n", std::string_view{raw_body, (std::size_t) (rawlen - bodylen)});
}
}
}
@@ -1950,7 +2054,7 @@ rspamc_process_input(struct ev_loop *ev_base, const struct rspamc_command &cmd,
p = strrchr(connect_str, ']');
if (p != nullptr) {
- hostbuf.assign(connect_str + 1, (std::size_t)(p - connect_str - 1));
+ hostbuf.assign(connect_str + 1, (std::size_t) (p - connect_str - 1));
p++;
}
else {
@@ -1965,7 +2069,7 @@ rspamc_process_input(struct ev_loop *ev_base, const struct rspamc_command &cmd,
if (hostbuf.empty()) {
if (p != nullptr) {
- hostbuf.assign(connect_str, (std::size_t)(p - connect_str));
+ hostbuf.assign(connect_str, (std::size_t) (p - connect_str));
}
else {
hostbuf.assign(connect_str);