/* 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; GList *actions; 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; struct dynamic_cfg_action *act; 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); } if (metric->actions) { cur_elt = metric->actions; while (cur_elt) { act = cur_elt->data; g_slice_free1 (sizeof (struct dynamic_cfg_symbol), act); cur_elt = g_list_next (cur_elt); } g_list_free (metric->actions); } 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, *tmp; 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; 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); } cur_elt = metric->actions; while (cur_elt) { act = cur_elt->data; tmp = real_metric->actions; while (tmp) { real_act = tmp->data; if (real_act->action == act->action) { real_act->score = act->value; } /* Update required score accordingly to metric's action */ if (act->action == real_metric->action) { real_metric->required_score = act->value; } tmp = g_list_next (tmp); } cur_elt = g_list_next (cur_elt); } } cur = g_list_next (cur); } } /* Callbacks for reading json dynamic rules */ gchar * json_config_read_cb (memory_pool_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 (memory_pool_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)); 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")) { cur_action = g_slice_alloc0 (sizeof (struct dynamic_cfg_action)); 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->action = test_act; cur_action->value = json_number_value (json_object_get (it_val, "value")); /* Insert action */ cur_metric->actions = g_list_prepend (cur_metric->actions, cur_action); } 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; /* 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) { cur_elt = metric->actions; fprintf (f, " \"actions\": [\n"); while (cur_elt) { act = cur_elt->data; cur_elt = g_list_next (cur_elt); if (cur_elt) { fprintf (f, " {\"name\": \"%s\",\"value\": %.2f},\n", str_action_metric (act->action), act->value); } else { fprintf (f, " {\"name\": \"%s\",\"value\": %.2f}\n", str_action_metric (act->action), act->value); } } 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; struct dynamic_cfg_action *act = 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 */ cur = metric->actions; while (cur) { act = cur->data; if (act->action == action) { act->value = value; msg_debug ("change value of action %d to %.2f", action, value); break; } act = NULL; cur = g_list_next (cur); } if (act == NULL) { /* Action not found, insert it */ act = g_slice_alloc (sizeof (struct dynamic_cfg_action)); act->action = action; act->value = value; metric->actions = g_list_prepend (metric->actions, act); 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 = 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 %d", metric_name, action); } apply_dynamic_conf (cfg->current_dynamic_conf, cfg); return TRUE; }