mirror of
https://github.com/rspamd/rspamd.git
synced 2024-08-28 05:54:33 +02:00
Add saving of actions, symbols and maps.
This commit is contained in:
parent
86d52c83c8
commit
823c263b9d
@ -532,6 +532,7 @@ process_dynamic_conf_command (gchar **cmd_args, struct controller_session *sessi
|
||||
gchar *arg, *metric, *name, *err_str;
|
||||
gdouble value;
|
||||
gboolean res;
|
||||
guint real_act;
|
||||
|
||||
if (cfg->dynamic_conf == NULL) {
|
||||
if (!session->restful) {
|
||||
@ -589,7 +590,13 @@ process_dynamic_conf_command (gchar **cmd_args, struct controller_session *sessi
|
||||
}
|
||||
|
||||
if (is_action) {
|
||||
res = add_dynamic_action (cfg, metric, name, value);
|
||||
if (!check_action_str (name, &real_act)) {
|
||||
msg_info ("invalid action string: %s", name);
|
||||
res = FALSE;
|
||||
}
|
||||
else {
|
||||
res = add_dynamic_action (cfg, metric, real_act, value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
res = add_dynamic_symbol (cfg, metric, name, value);
|
||||
|
@ -565,23 +565,17 @@ add_dynamic_symbol (struct config_file *cfg, const gchar *metric_name, const gch
|
||||
* @return
|
||||
*/
|
||||
gboolean
|
||||
add_dynamic_action (struct config_file *cfg, const gchar *metric_name, const gchar *action, gdouble value)
|
||||
add_dynamic_action (struct config_file *cfg, const gchar *metric_name, guint action, gdouble value)
|
||||
{
|
||||
GList *cur;
|
||||
struct dynamic_cfg_metric *metric = NULL;
|
||||
struct dynamic_cfg_action *act = NULL;
|
||||
gint real_act;
|
||||
|
||||
if (cfg->dynamic_conf == NULL) {
|
||||
msg_info ("dynamic conf is disabled");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!check_action_str (action, &real_act)) {
|
||||
msg_info ("invalid action string: %s", action);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
cur = cfg->current_dynamic_conf;
|
||||
while (cur) {
|
||||
metric = cur->data;
|
||||
@ -597,9 +591,9 @@ add_dynamic_action (struct config_file *cfg, const gchar *metric_name, const gch
|
||||
cur = metric->actions;
|
||||
while (cur) {
|
||||
act = cur->data;
|
||||
if ((gint)act->action == real_act) {
|
||||
if (act->action == action) {
|
||||
act->value = value;
|
||||
msg_debug ("change value of action %s to %.2f", action, value);
|
||||
msg_debug ("change value of action %d to %.2f", action, value);
|
||||
break;
|
||||
}
|
||||
act = NULL;
|
||||
@ -608,22 +602,22 @@ add_dynamic_action (struct config_file *cfg, const gchar *metric_name, const gch
|
||||
if (act == NULL) {
|
||||
/* Action not found, insert it */
|
||||
act = g_slice_alloc (sizeof (struct dynamic_cfg_action));
|
||||
act->action = real_act;
|
||||
act->action = action;
|
||||
act->value = value;
|
||||
metric->actions = g_list_prepend (metric->actions, act);
|
||||
msg_debug ("create action %s in metric %s", action, metric_name);
|
||||
msg_debug ("create action %d in metric %s", action, metric_name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Metric not found, create it */
|
||||
metric = g_slice_alloc0 (sizeof (struct dynamic_cfg_metric));
|
||||
act = g_slice_alloc (sizeof (struct dynamic_cfg_action));
|
||||
act->action = real_act;
|
||||
act->action = action;
|
||||
act->value = value;
|
||||
metric->actions = g_list_prepend (metric->actions, act);
|
||||
metric->name = g_strdup (metric_name);
|
||||
cfg->current_dynamic_conf = g_list_prepend (cfg->current_dynamic_conf, metric);
|
||||
msg_debug ("create metric %s for action %s", metric_name, action);
|
||||
msg_debug ("create metric %s for action %d", metric_name, action);
|
||||
}
|
||||
|
||||
apply_dynamic_conf (cfg->current_dynamic_conf, cfg);
|
||||
|
@ -60,7 +60,7 @@ gboolean add_dynamic_symbol (struct config_file *cfg, const gchar *metric, const
|
||||
* @param value value of symbol
|
||||
* @return
|
||||
*/
|
||||
gboolean add_dynamic_action (struct config_file *cfg, const gchar *metric, const gchar *action, gdouble value);
|
||||
gboolean add_dynamic_action (struct config_file *cfg, const gchar *metric, guint action, gdouble value);
|
||||
|
||||
|
||||
#endif /* DYNAMIC_CFG_H_ */
|
||||
|
@ -830,11 +830,11 @@ str_action_metric (enum rspamd_metric_action action)
|
||||
case METRIC_ACTION_REJECT:
|
||||
return "reject";
|
||||
case METRIC_ACTION_SOFT_REJECT:
|
||||
return "soft reject";
|
||||
return "soft_reject";
|
||||
case METRIC_ACTION_REWRITE_SUBJECT:
|
||||
return "rewrite subject";
|
||||
return "rewrite_subject";
|
||||
case METRIC_ACTION_ADD_HEADER:
|
||||
return "add header";
|
||||
return "add_header";
|
||||
case METRIC_ACTION_GREYLIST:
|
||||
return "greylist";
|
||||
case METRIC_ACTION_NOACTION:
|
||||
|
@ -120,6 +120,7 @@ typedef struct {
|
||||
json_t *json_loads(const char *input, json_error_t *error);
|
||||
json_t *json_loadf(FILE *input, json_error_t *error);
|
||||
json_t *json_load_file(const char *path, json_error_t *error);
|
||||
json_t *json_load_evbuffer(struct evbuffer *evb, json_error_t *error);
|
||||
|
||||
#define JSON_INDENT(n) (n & 0xFF)
|
||||
|
||||
|
@ -856,3 +856,61 @@ json_load_file (const char *path, json_error_t * error)
|
||||
fclose (fp);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
typedef struct evbuffer_data_s {
|
||||
const char *data;
|
||||
gsize len;
|
||||
guint pos;
|
||||
} evbuffer_data_t;
|
||||
|
||||
|
||||
static int
|
||||
evbuffer_get (void *data)
|
||||
{
|
||||
evbuffer_data_t *stream = (evbuffer_data_t *) data;
|
||||
|
||||
if (stream->pos >= stream->len) {
|
||||
return EOF;
|
||||
}
|
||||
|
||||
return *(stream->data + stream->pos++);
|
||||
}
|
||||
|
||||
static int
|
||||
evbuffer_eof (void *data)
|
||||
{
|
||||
evbuffer_data_t *stream = (evbuffer_data_t *) data;
|
||||
|
||||
return stream->pos >= stream->len;
|
||||
}
|
||||
|
||||
json_t *
|
||||
json_load_evbuffer (struct evbuffer *evb, json_error_t *error)
|
||||
{
|
||||
evbuffer_data_t stream_data;
|
||||
lex_t lex;
|
||||
json_t *result;
|
||||
|
||||
stream_data.data = EVBUFFER_DATA (evb);
|
||||
stream_data.pos = 0;
|
||||
stream_data.len = EVBUFFER_LENGTH (evb);
|
||||
|
||||
if (lex_init (&lex, evbuffer_get, evbuffer_eof, (void *)&stream_data))
|
||||
return NULL;
|
||||
|
||||
result = parse_json (&lex, error);
|
||||
if (!result)
|
||||
goto out;
|
||||
|
||||
lex_scan (&lex, error);
|
||||
if (lex.token != TOKEN_EOF) {
|
||||
error_set (error, &lex, "end of file expected");
|
||||
json_decref (result);
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
out:
|
||||
lex_close (&lex);
|
||||
return result;
|
||||
}
|
||||
|
@ -96,6 +96,28 @@
|
||||
|
||||
static GList *custom_commands = NULL;
|
||||
|
||||
/* XXX: remove this legacy sometimes */
|
||||
static const gchar *
|
||||
str_action_metric_spamc (enum rspamd_metric_action action)
|
||||
{
|
||||
switch (action) {
|
||||
case METRIC_ACTION_REJECT:
|
||||
return "reject";
|
||||
case METRIC_ACTION_SOFT_REJECT:
|
||||
return "soft reject";
|
||||
case METRIC_ACTION_REWRITE_SUBJECT:
|
||||
return "rewrite subject";
|
||||
case METRIC_ACTION_ADD_HEADER:
|
||||
return "add header";
|
||||
case METRIC_ACTION_GREYLIST:
|
||||
return "greylist";
|
||||
case METRIC_ACTION_NOACTION:
|
||||
return "no action";
|
||||
}
|
||||
|
||||
return "unknown action";
|
||||
}
|
||||
|
||||
/* For default metric, dirty hack, but much faster than hash lookup */
|
||||
static double default_score, default_required_score;
|
||||
|
||||
@ -1040,12 +1062,12 @@ print_metric_data_rspamc (struct worker_task *task, gchar *outbuf, gsize size,
|
||||
|
||||
if (task->pre_result.action == METRIC_ACTION_NOACTION) {
|
||||
r += rspamd_snprintf (outbuf + r, size - r,
|
||||
"Action: %s" CRLF, str_action_metric (
|
||||
"Action: %s" CRLF, str_action_metric_spamc (
|
||||
METRIC_ACTION_NOACTION));
|
||||
}
|
||||
else {
|
||||
r += rspamd_snprintf (outbuf + r, size - r,
|
||||
"Action: %s" CRLF, str_action_metric (
|
||||
"Action: %s" CRLF, str_action_metric_spamc (
|
||||
task->pre_result.action));
|
||||
if (task->pre_result.str != NULL) {
|
||||
r += rspamd_snprintf (outbuf + r, size - r,
|
||||
@ -1086,7 +1108,7 @@ print_metric_data_rspamc (struct worker_task *task, gchar *outbuf, gsize size,
|
||||
metric_res->score, ms);
|
||||
}
|
||||
r += rspamd_snprintf (outbuf + r, size - r,
|
||||
"Action: %s" CRLF, str_action_metric (action));
|
||||
"Action: %s" CRLF, str_action_metric_spamc (action));
|
||||
}
|
||||
if (action == METRIC_ACTION_REWRITE_SUBJECT && metric_res->metric->subject != NULL) {
|
||||
r += rspamd_snprintf (outbuf + r, size - r,
|
||||
|
214
src/webui.c
214
src/webui.c
@ -36,6 +36,7 @@
|
||||
#include "classifiers/classifiers.h"
|
||||
#include "dynamic_cfg.h"
|
||||
#include "rrd.h"
|
||||
#include "json/jansson.h"
|
||||
|
||||
#include <evhttp.h>
|
||||
#if (_EVENT_NUMERIC_VERSION > 0x02010000) && defined(HAVE_OPENSSL)
|
||||
@ -453,6 +454,42 @@ http_prepare_learn (struct evhttp_request *req, struct rspamd_webui_worker_ctx *
|
||||
return cbdata;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set metric action
|
||||
*/
|
||||
static gboolean
|
||||
http_set_metric_action (struct config_file *cfg,
|
||||
json_t *jv, struct metric *metric, enum rspamd_metric_action act)
|
||||
{
|
||||
gdouble actval;
|
||||
GList *cur;
|
||||
struct metric_action *act_found;
|
||||
|
||||
if (!json_is_number (jv)) {
|
||||
msg_err ("json element data error");
|
||||
return FALSE;
|
||||
}
|
||||
actval = json_number_value (jv);
|
||||
if (metric->action == act && metric->required_score != actval) {
|
||||
return add_dynamic_action (cfg, DEFAULT_METRIC, act, actval);
|
||||
}
|
||||
|
||||
/* Try to search in all metrics */
|
||||
/* XXX: clarify this code, currently it looks like a crap */
|
||||
cur = metric->actions;
|
||||
while (cur) {
|
||||
act_found = cur->data;
|
||||
if (act_found->action == act) {
|
||||
if (act_found->score != actval) {
|
||||
return add_dynamic_action (cfg, DEFAULT_METRIC, act, actval);
|
||||
}
|
||||
}
|
||||
cur = g_list_next (cur);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Command handlers */
|
||||
|
||||
/*
|
||||
@ -1138,7 +1175,7 @@ http_handle_learn_ham (struct evhttp_request *req, gpointer arg)
|
||||
* Save actions command handler:
|
||||
* request: /saveactions
|
||||
* headers: Password
|
||||
* input: json data
|
||||
* input: json array [<spam>,<probable spam>,<greylist>]
|
||||
* reply: json {"success":true} or {"error":"error message"}
|
||||
*/
|
||||
static void
|
||||
@ -1146,6 +1183,10 @@ http_handle_save_actions (struct evhttp_request *req, gpointer arg)
|
||||
{
|
||||
struct rspamd_webui_worker_ctx *ctx = arg;
|
||||
struct evbuffer *evb;
|
||||
struct metric *metric;
|
||||
json_t *json, *jv;
|
||||
json_error_t je;
|
||||
|
||||
|
||||
evb = evbuffer_new ();
|
||||
if (!evb) {
|
||||
@ -1154,7 +1195,72 @@ http_handle_save_actions (struct evhttp_request *req, gpointer arg)
|
||||
return;
|
||||
}
|
||||
|
||||
/* XXX: add real saving */
|
||||
metric = g_hash_table_lookup (ctx->cfg->metrics, DEFAULT_METRIC);
|
||||
if (metric == NULL) {
|
||||
msg_err ("cannot find default metric");
|
||||
evbuffer_free (evb);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "500 default metric not defined", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Now check for dynamic config */
|
||||
if (!ctx->cfg->dynamic_conf) {
|
||||
msg_err ("dynamic conf has not been defined");
|
||||
evbuffer_free (evb);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "503 dynamic config not found, cannot save", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Try to load json */
|
||||
json = json_load_evbuffer (req->input_buffer, &je);
|
||||
if (json == NULL) {
|
||||
msg_err ("json load error: %s", je.text);
|
||||
evbuffer_free (evb);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "504 json parse error", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_is_array (json) || json_array_size (json) != 3) {
|
||||
msg_err ("json data error");
|
||||
evbuffer_free (evb);
|
||||
json_delete (json);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "505 invalid json data", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Now try to check what we have */
|
||||
|
||||
/* Spam element */
|
||||
jv = json_array_get (json, 0);
|
||||
if (!http_set_metric_action (ctx->cfg, jv, metric, METRIC_ACTION_REJECT)) {
|
||||
msg_err ("json data error");
|
||||
evbuffer_free (evb);
|
||||
json_delete (json);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "506 cannot set action's value", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Probable spam */
|
||||
jv = json_array_get (json, 1);
|
||||
if (!http_set_metric_action (ctx->cfg, jv, metric, METRIC_ACTION_ADD_HEADER)) {
|
||||
msg_err ("json data error");
|
||||
evbuffer_free (evb);
|
||||
json_delete (json);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "506 cannot set action's value", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Greylist */
|
||||
jv = json_array_get (json, 2);
|
||||
if (!http_set_metric_action (ctx->cfg, jv, metric, METRIC_ACTION_GREYLIST)) {
|
||||
msg_err ("json data error");
|
||||
evbuffer_free (evb);
|
||||
json_delete (json);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "506 cannot set action's value", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
dump_dynamic_config (ctx->cfg);
|
||||
|
||||
evbuffer_add_printf (evb, "{\"success\":true}" CRLF);
|
||||
evhttp_add_header (req->output_headers, "Connection", "close");
|
||||
@ -1175,6 +1281,12 @@ http_handle_save_symbols (struct evhttp_request *req, gpointer arg)
|
||||
{
|
||||
struct rspamd_webui_worker_ctx *ctx = arg;
|
||||
struct evbuffer *evb;
|
||||
struct metric *metric;
|
||||
struct symbol *sym;
|
||||
json_t *json, *jv, *jname, *jvalue;
|
||||
json_error_t je;
|
||||
guint i, len;
|
||||
gdouble val;
|
||||
|
||||
evb = evbuffer_new ();
|
||||
if (!evb) {
|
||||
@ -1183,7 +1295,69 @@ http_handle_save_symbols (struct evhttp_request *req, gpointer arg)
|
||||
return;
|
||||
}
|
||||
|
||||
/* XXX: add real saving */
|
||||
metric = g_hash_table_lookup (ctx->cfg->metrics, DEFAULT_METRIC);
|
||||
if (metric == NULL) {
|
||||
msg_err ("cannot find default metric");
|
||||
evbuffer_free (evb);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "500 default metric not defined", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Now check for dynamic config */
|
||||
if (!ctx->cfg->dynamic_conf) {
|
||||
msg_err ("dynamic conf has not been defined");
|
||||
evbuffer_free (evb);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "503 dynamic config not found, cannot save", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Try to load json */
|
||||
json = json_load_evbuffer (req->input_buffer, &je);
|
||||
if (json == NULL) {
|
||||
msg_err ("json load error: %s", je.text);
|
||||
evbuffer_free (evb);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "504 json parse error", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_is_array (json) || (len = json_array_size (json)) == 0) {
|
||||
msg_err ("json data error");
|
||||
evbuffer_free (evb);
|
||||
json_delete (json);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "505 invalid json data", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Iterate over all elements */
|
||||
for (i = 0; i < len; i ++) {
|
||||
jv = json_array_get (json, i);
|
||||
if (!json_is_object (jv)) {
|
||||
msg_err ("json array data error");
|
||||
evbuffer_free (evb);
|
||||
json_delete (json);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "505 invalid json data", NULL);
|
||||
return;
|
||||
}
|
||||
jname = json_object_get (jv, "name");
|
||||
jvalue = json_object_get (jv, "value");
|
||||
if (!json_is_string (jname) || !json_is_number (jvalue)) {
|
||||
msg_err ("json object data error");
|
||||
evbuffer_free (evb);
|
||||
json_delete (json);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "505 invalid json data", NULL);
|
||||
return;
|
||||
}
|
||||
val = json_number_value (jvalue);
|
||||
sym = g_hash_table_lookup (metric->symbols, json_string_value (jname));
|
||||
if (sym && fabs (sym->score - val) > 0.01) {
|
||||
if (!add_dynamic_symbol (ctx->cfg, DEFAULT_METRIC, sym->name, val)) {
|
||||
evbuffer_free (evb);
|
||||
json_delete (json);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "506 cannot write symbol's value", NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
evbuffer_add_printf (evb, "{\"success\":true}" CRLF);
|
||||
evhttp_add_header (req->output_headers, "Connection", "close");
|
||||
@ -1210,6 +1384,7 @@ http_handle_save_map (struct evhttp_request *req, gpointer arg)
|
||||
gchar *errstr;
|
||||
guint32 id;
|
||||
gboolean found = FALSE;
|
||||
gint fd;
|
||||
|
||||
|
||||
if (!http_check_password (ctx, req)) {
|
||||
@ -1252,13 +1427,42 @@ http_handle_save_map (struct evhttp_request *req, gpointer arg)
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
msg_info ("map not found");
|
||||
msg_info ("map not found: %d", id);
|
||||
evbuffer_free (evb);
|
||||
evhttp_send_reply (req, HTTP_NOTFOUND, "404 map not found", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* XXX: add real saving */
|
||||
if (g_atomic_int_get (map->locked)) {
|
||||
msg_info ("map locked: %s", map->uri);
|
||||
evbuffer_free (evb);
|
||||
evhttp_send_reply (req, HTTP_NOTFOUND, "404 map is locked", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set lock */
|
||||
g_atomic_int_set (map->locked, 1);
|
||||
fd = open (map->uri, O_WRONLY | O_TRUNC);
|
||||
if (fd == -1) {
|
||||
g_atomic_int_set (map->locked, 0);
|
||||
msg_info ("map %s open error: %s", map->uri, strerror (errno));
|
||||
evbuffer_free (evb);
|
||||
evhttp_send_reply (req, HTTP_NOTFOUND, "404 map open error", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (evbuffer_write (req->input_buffer, fd) == -1) {
|
||||
close (fd);
|
||||
g_atomic_int_set (map->locked, 0);
|
||||
msg_info ("map %s open error: %s", map->uri, strerror (errno));
|
||||
evbuffer_free (evb);
|
||||
evhttp_send_reply (req, HTTP_INTERNAL, "500 map open error", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Close and unlock */
|
||||
close (fd);
|
||||
g_atomic_int_set (map->locked, 0);
|
||||
|
||||
evbuffer_add_printf (evb, "{\"success\":true}" CRLF);
|
||||
evhttp_add_header (req->output_headers, "Connection", "close");
|
||||
|
Loading…
Reference in New Issue
Block a user