From 61555065f3d1c8badcc9573691232f1b6e42988c Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Mon, 21 Apr 2014 16:25:51 +0100 Subject: Rework project structure, remove trash files. --- src/libserver/dynamic_cfg.c | 599 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 599 insertions(+) create mode 100644 src/libserver/dynamic_cfg.c (limited to 'src/libserver/dynamic_cfg.c') diff --git a/src/libserver/dynamic_cfg.c b/src/libserver/dynamic_cfg.c new file mode 100644 index 000000000..7f5e8530d --- /dev/null +++ b/src/libserver/dynamic_cfg.c @@ -0,0 +1,599 @@ +/* Copyright (c) 2010-2012, Vsevolod Stakhov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "main.h" +#include "map.h" +#include "filter.h" +#include "dynamic_cfg.h" +#include "json/jansson.h" + +struct dynamic_cfg_symbol { + gchar *name; + gdouble value; +}; + +struct dynamic_cfg_action { + enum rspamd_metric_action action; + gdouble value; +}; + +struct dynamic_cfg_metric { + GList *symbols; + struct dynamic_cfg_action actions[METRIC_ACTION_MAX]; + gchar *name; +}; + +struct config_json_buf { + gchar *buf; + gchar *pos; + size_t buflen; + struct config_file *cfg; + GList *config_metrics; +}; + +/** + * Free dynamic configuration + * @param conf_metrics + */ +static void +dynamic_cfg_free (GList *conf_metrics) +{ + GList *cur, *cur_elt; + struct dynamic_cfg_metric *metric; + struct dynamic_cfg_symbol *sym; + + if (conf_metrics) { + cur = conf_metrics; + while (cur) { + metric = cur->data; + if (metric->symbols) { + cur_elt = metric->symbols; + while (cur_elt) { + sym = cur_elt->data; + g_free (sym->name); + g_slice_free1 (sizeof (struct dynamic_cfg_symbol), sym); + cur_elt = g_list_next (cur_elt); + } + g_list_free (metric->symbols); + } + g_slice_free1 (sizeof (struct dynamic_cfg_metric), metric); + cur = g_list_next (cur); + } + g_list_free (conf_metrics); + } +} +/** + * Apply configuration to the specified configuration + * @param conf_metrics + * @param cfg + */ +static void +apply_dynamic_conf (GList *conf_metrics, struct config_file *cfg) +{ + GList *cur, *cur_elt; + struct dynamic_cfg_metric *metric; + struct dynamic_cfg_symbol *sym; + struct dynamic_cfg_action *act; + struct metric *real_metric; + struct metric_action *real_act; + gdouble *w; + gint i, j; + + cur = conf_metrics; + while (cur) { + metric = cur->data; + if ((real_metric = g_hash_table_lookup (cfg->metrics, metric->name)) != NULL) { + cur_elt = metric->symbols; + while (cur_elt) { + sym = cur_elt->data; + if ((w = g_hash_table_lookup (real_metric->symbols, sym->name)) != NULL) { + *w = sym->value; + } + else { + msg_info ("symbol %s is not found in the main configuration", sym->name); + } + cur_elt = g_list_next (cur_elt); + } + + for (i = METRIC_ACTION_REJECT; i < METRIC_ACTION_MAX; i ++) { + act = &metric->actions[i]; + if (act->value < 0) { + continue; + } + for (j = METRIC_ACTION_REJECT; j < METRIC_ACTION_MAX; j ++) { + real_act = &real_metric->actions[j]; + if (real_act->action == act->action) { + real_act->score = act->value; + } + /* Update required score accordingly to metric's action */ + if (act->action == METRIC_ACTION_REJECT) { + real_metric->actions[METRIC_ACTION_REJECT].score = act->value; + } + } + } + } + cur = g_list_next (cur); + } +} + +/* Callbacks for reading json dynamic rules */ +gchar * +json_config_read_cb (rspamd_mempool_t * pool, gchar * chunk, gint len, struct map_cb_data *data) +{ + struct config_json_buf *jb; + gint free, off; + + if (data->cur_data == NULL) { + jb = g_malloc (sizeof (struct config_json_buf)); + jb->cfg = ((struct config_json_buf *)data->prev_data)->cfg; + jb->buf = NULL; + jb->pos = NULL; + jb->config_metrics = NULL; + data->cur_data = jb; + } + else { + jb = data->cur_data; + } + + if (jb->buf == NULL) { + /* Allocate memory for buffer */ + jb->buflen = len * 2; + jb->buf = g_malloc (jb->buflen); + jb->pos = jb->buf; + } + + off = jb->pos - jb->buf; + free = jb->buflen - off; + + if (free < len) { + jb->buflen = MAX (jb->buflen * 2, jb->buflen + len * 2); + jb->buf = g_realloc (jb->buf, jb->buflen); + jb->pos = jb->buf + off; + } + + memcpy (jb->pos, chunk, len); + jb->pos += len; + + /* Say not to copy any part of this buffer */ + return NULL; +} + +void +json_config_fin_cb (rspamd_mempool_t * pool, struct map_cb_data *data) +{ + struct config_json_buf *jb; + guint nelts, i, j, selts; + gint test_act; + json_t *js, *cur_elt, *cur_nm, *it_val; + json_error_t je; + struct dynamic_cfg_metric *cur_metric; + struct dynamic_cfg_symbol *cur_symbol; + struct dynamic_cfg_action *cur_action; + + if (data->prev_data) { + jb = data->prev_data; + /* Clean prev data */ + if (jb->buf) { + g_free (jb->buf); + } + g_free (jb); + } + + /* Now parse json */ + if (data->cur_data) { + jb = data->cur_data; + } + else { + msg_err ("no data read"); + return; + } + if (jb->buf == NULL) { + msg_err ("no data read"); + return; + } + /* NULL terminate current buf */ + *jb->pos = '\0'; + + js = json_loads (jb->buf, &je); + if (!js) { + msg_err ("cannot load json data: parse error %s, on line %d", je.text, je.line); + return; + } + + if (!json_is_array (js)) { + json_decref (js); + msg_err ("loaded json is not an array"); + return; + } + + jb->cfg->current_dynamic_conf = NULL; + dynamic_cfg_free (jb->config_metrics); + jb->config_metrics = NULL; + + /* Parse configuration */ + nelts = json_array_size (js); + for (i = 0; i < nelts; i++) { + cur_elt = json_array_get (js, i); + if (!cur_elt || !json_is_object (cur_elt)) { + msg_err ("loaded json array element is not an object"); + continue; + } + + cur_nm = json_object_get (cur_elt, "metric"); + if (!cur_nm || !json_is_string (cur_nm)) { + msg_err ("loaded json metric object element has no 'metric' attribute"); + continue; + } + cur_metric = g_slice_alloc0 (sizeof (struct dynamic_cfg_metric)); + for (i = METRIC_ACTION_REJECT; i < METRIC_ACTION_MAX; i ++) { + cur_metric->actions[i].value = -1.0; + } + cur_metric->name = g_strdup (json_string_value (cur_nm)); + cur_nm = json_object_get (cur_elt, "symbols"); + /* Parse symbols */ + if (cur_nm && json_is_array (cur_nm)) { + selts = json_array_size (cur_nm); + for (j = 0; j < selts; j ++) { + it_val = json_array_get (cur_nm, j); + if (it_val && json_is_object (it_val)) { + if (json_object_get (it_val, "name") && json_object_get (it_val, "value")) { + cur_symbol = g_slice_alloc0 (sizeof (struct dynamic_cfg_symbol)); + cur_symbol->name = g_strdup (json_string_value (json_object_get (it_val, "name"))); + cur_symbol->value = json_number_value (json_object_get (it_val, "value")); + /* Insert symbol */ + cur_metric->symbols = g_list_prepend (cur_metric->symbols, cur_symbol); + } + else { + msg_info ("json symbol object has no mandatory 'name' and 'value' attributes"); + } + } + } + } + cur_nm = json_object_get (cur_elt, "actions"); + /* Parse actions */ + if (cur_nm && json_is_array (cur_nm)) { + selts = json_array_size (cur_nm); + for (j = 0; j < selts; j ++) { + it_val = json_array_get (cur_nm, j); + if (it_val && json_is_object (it_val)) { + if (json_object_get (it_val, "name") && json_object_get (it_val, "value")) { + if (!check_action_str (json_string_value (json_object_get (it_val, "name")), &test_act)) { + msg_err ("unknown action: %s", json_string_value (json_object_get (it_val, "name"))); + g_slice_free1 (sizeof (struct dynamic_cfg_action), cur_action); + continue; + } + cur_action = &cur_metric->actions[test_act]; + cur_action->action = test_act; + cur_action->value = json_number_value (json_object_get (it_val, "value")); + } + else { + msg_info ("json symbol object has no mandatory 'name' and 'value' attributes"); + } + } + } + } + jb->config_metrics = g_list_prepend (jb->config_metrics, cur_metric); + } + /* + * Note about thread safety: we are updating values that are gdoubles so it is not atomic in general case + * but on the other hand all that data is used only in the main thread, so why it is *likely* safe + * to do this task in this way without explicit lock. + */ + apply_dynamic_conf (jb->config_metrics, jb->cfg); + + jb->cfg->current_dynamic_conf = jb->config_metrics; + + json_decref (js); +} + +/** + * Init dynamic configuration using map logic and specific configuration + * @param cfg config file + */ +void +init_dynamic_config (struct config_file *cfg) +{ + struct config_json_buf *jb, **pjb; + + if (cfg->dynamic_conf == NULL) { + /* No dynamic conf has been specified, so do not try to load it */ + return; + } + + /* Now try to add map with json data */ + jb = g_malloc0 (sizeof (struct config_json_buf)); + pjb = g_malloc (sizeof (struct config_json_buf *)); + jb->buf = NULL; + jb->cfg = cfg; + *pjb = jb; + if (!add_map (cfg, cfg->dynamic_conf, "Dynamic configuration map", json_config_read_cb, json_config_fin_cb, (void **)pjb)) { + msg_err ("cannot add map for configuration %s", cfg->dynamic_conf); + } +} + +static gboolean +dump_dynamic_list (gint fd, GList *rules) +{ + GList *cur, *cur_elt; + struct dynamic_cfg_metric *metric; + struct dynamic_cfg_symbol *sym; + struct dynamic_cfg_action *act; + FILE *f; + gint i; + gboolean start = TRUE; + + /* Open buffered stream for the descriptor */ + if ((f = fdopen (fd, "a+")) == NULL) { + msg_err ("fdopen failed: %s", strerror (errno)); + return FALSE; + } + + + if (rules) { + fprintf (f, "[\n"); + cur = rules; + while (cur) { + metric = cur->data; + fprintf (f, "{\n \"metric\": \"%s\",\n", metric->name); + if (metric->symbols) { + fprintf (f, " \"symbols\": [\n"); + cur_elt = metric->symbols; + while (cur_elt) { + sym = cur_elt->data; + cur_elt = g_list_next (cur_elt); + if (cur_elt) { + fprintf (f, " {\"name\": \"%s\",\"value\": %.2f},\n", sym->name, sym->value); + } + else { + fprintf (f, " {\"name\": \"%s\",\"value\": %.2f}\n", sym->name, sym->value); + } + } + if (metric->actions) { + fprintf (f, " ],\n"); + } + else { + fprintf (f, " ]\n"); + } + } + + if (metric->actions) { + fprintf (f, " \"actions\": [\n"); + for (i = METRIC_ACTION_REJECT; i < METRIC_ACTION_MAX; i ++) { + act = &metric->actions[i]; + if (act->value < 0) { + continue; + } + fprintf (f, " %s{\"name\": \"%s\",\"value\": %.2f}\n", + (start ? "" : ","), str_action_metric (act->action), act->value); + if (start) { + start = FALSE; + } + } + fprintf (f, " ]\n"); + } + cur = g_list_next (cur); + if (cur) { + fprintf (f, "},\n"); + } + else { + fprintf (f, "}\n]\n"); + } + } + } + fclose (f); + + return TRUE; +} + +/** + * Dump dynamic configuration to the disk + * @param cfg + * @return + */ +gboolean +dump_dynamic_config (struct config_file *cfg) +{ + struct stat st; + gchar *dir, pathbuf[PATH_MAX]; + gint fd; + + if (cfg->dynamic_conf == NULL || cfg->current_dynamic_conf == NULL) { + /* No dynamic conf has been specified, so do not try to dump it */ + return FALSE; + } + + dir = g_path_get_dirname (cfg->dynamic_conf); + if (dir == NULL) { + /* Inaccessible path */ + if (dir != NULL) { + g_free (dir); + } + msg_err ("invalid file: %s", cfg->dynamic_conf); + return FALSE; + } + + if (stat (cfg->dynamic_conf, &st) == -1) { + msg_debug ("%s is unavailable: %s", cfg->dynamic_conf, strerror (errno)); + st.st_mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH; + } + if (access (dir, W_OK | R_OK) == -1) { + msg_warn ("%s is inaccessible: %s", dir, strerror (errno)); + g_free (dir); + return FALSE; + } + rspamd_snprintf (pathbuf, sizeof (pathbuf), "%s%crconf-XXXXXX", dir, G_DIR_SEPARATOR); + g_free (dir); +#ifdef HAVE_MKSTEMP + /* Umask is set before */ + fd = mkstemp (pathbuf); +#else + fd = g_mkstemp_full (pathbuf, O_RDWR, S_IWUSR | S_IRUSR); +#endif + if (fd == -1) { + msg_err ("mkstemp error: %s", strerror (errno)); + + return FALSE; + } + + if (!dump_dynamic_list (fd, cfg->current_dynamic_conf)) { + close (fd); + unlink (pathbuf); + return FALSE; + } + + (void)unlink (cfg->dynamic_conf); + + /* Rename old config */ + if (rename (pathbuf, cfg->dynamic_conf) == -1) { + msg_err ("rename error: %s", strerror (errno)); + close (fd); + unlink (pathbuf); + return FALSE; + } + /* Set permissions */ + + if (chmod (cfg->dynamic_conf, st.st_mode) == -1) { + msg_warn ("chmod failed: %s", strerror (errno)); + } + + close (fd); + return TRUE; +} + +/** + * Add symbol for specified metric + * @param cfg config file object + * @param metric metric's name + * @param symbol symbol's name + * @param value value of symbol + * @return + */ +gboolean +add_dynamic_symbol (struct config_file *cfg, const gchar *metric_name, const gchar *symbol, gdouble value) +{ + GList *cur; + struct dynamic_cfg_metric *metric = NULL; + struct dynamic_cfg_symbol *sym = NULL; + + if (cfg->dynamic_conf == NULL) { + msg_info ("dynamic conf is disabled"); + return FALSE; + } + + cur = cfg->current_dynamic_conf; + while (cur) { + metric = cur->data; + if (g_ascii_strcasecmp (metric->name, metric_name) == 0) { + break; + } + metric = NULL; + cur = g_list_next (cur); + } + + if (metric != NULL) { + /* Search for a symbol */ + cur = metric->symbols; + while (cur) { + sym = cur->data; + if (g_ascii_strcasecmp (sym->name, symbol) == 0) { + sym->value = value; + msg_debug ("change value of action %s to %.2f", symbol, value); + break; + } + sym = NULL; + cur = g_list_next (cur); + } + if (sym == NULL) { + /* Symbol not found, insert it */ + sym = g_slice_alloc (sizeof (struct dynamic_cfg_symbol)); + sym->name = g_strdup (symbol); + sym->value = value; + metric->symbols = g_list_prepend (metric->symbols, sym); + msg_debug ("create symbol %s in metric %s", symbol, metric_name); + } + } + else { + /* Metric not found, create it */ + metric = g_slice_alloc0 (sizeof (struct dynamic_cfg_metric)); + sym = g_slice_alloc (sizeof (struct dynamic_cfg_symbol)); + sym->name = g_strdup (symbol); + sym->value = value; + metric->symbols = g_list_prepend (metric->symbols, sym); + metric->name = g_strdup (metric_name); + cfg->current_dynamic_conf = g_list_prepend (cfg->current_dynamic_conf, metric); + msg_debug ("create metric %s for symbol %s", metric_name, symbol); + } + + apply_dynamic_conf (cfg->current_dynamic_conf, cfg); + + return TRUE; +} + + +/** + * Add action for specified metric + * @param cfg config file object + * @param metric metric's name + * @param action action's name + * @param value value of symbol + * @return + */ +gboolean +add_dynamic_action (struct config_file *cfg, const gchar *metric_name, guint action, gdouble value) +{ + GList *cur; + struct dynamic_cfg_metric *metric = NULL; + + if (cfg->dynamic_conf == NULL) { + msg_info ("dynamic conf is disabled"); + return FALSE; + } + + cur = cfg->current_dynamic_conf; + while (cur) { + metric = cur->data; + if (g_ascii_strcasecmp (metric->name, metric_name) == 0) { + break; + } + metric = NULL; + cur = g_list_next (cur); + } + + if (metric != NULL) { + /* Search for an action */ + metric->actions[action].value = value; + } + else { + /* Metric not found, create it */ + metric = g_slice_alloc0 (sizeof (struct dynamic_cfg_metric)); + metric->actions[action].value = value; + 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 %d", metric_name, action); + } + + apply_dynamic_conf (cfg->current_dynamic_conf, cfg); + + return TRUE; +} -- cgit v1.2.3