]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
Add saving of actions, symbols and maps.
authorVsevolod Stakhov <vsevolod@rambler-co.ru>
Wed, 9 Jan 2013 12:35:03 +0000 (16:35 +0400)
committerVsevolod Stakhov <vsevolod@rambler-co.ru>
Wed, 9 Jan 2013 12:35:03 +0000 (16:35 +0400)
src/controller.c
src/dynamic_cfg.c
src/dynamic_cfg.h
src/filter.c
src/json/jansson.h
src/json/load.c
src/protocol.c
src/webui.c

index 20a733693a446422d8948aa69ae47c38b245b37f..f633f5b0161a1f1bee4758482b8e248d98c061c4 100644 (file)
@@ -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);
index 7b9596c6c9e5a23f5fabfbeeec84e81874f7f4f6..3979e8ddcb45520ffc1ad51db3897d51c847630d 100644 (file)
@@ -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);
index c791a3c3862d1724f6c0be787db4515f6f348e49..b65d7aa9a2af3fefedbd50efb43038f7fafb0a7f 100644 (file)
@@ -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_ */
index bbe3b46781a4503535a43f6218ce110b894e0ae1..44fc16a07b66268ff81b64574a3358f8cfd1c949 100644 (file)
@@ -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:
index 0d859f38405c846bdf81ed035c8040654deb1d0b..a66fc363f997d20caecabcc57dc21f048ba8ddf7 100644 (file)
@@ -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)
 
index 07d0b9f8b7d6a93c3c03ba781e80f52497ed170b..0f0968e9e12fda1e72b9393581aa7f5cdf312daa 100644 (file)
@@ -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;
+}
index a303b428557a6b4b265700197c2ead727503df63..ab9836e85415b8298ea5386c721bb6734cc6cd50 100644 (file)
 
 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,
index e77aeaa9571affe44838e847e8effd663e1d982f..7e3963b645513384e94623a026ae5cd099bf5d0e 100644 (file)
@@ -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");