aboutsummaryrefslogtreecommitdiffstats
path: root/src/libserver/dynamic_cfg.c
diff options
context:
space:
mode:
authorVsevolod Stakhov <vsevolod@highsecure.ru>2014-04-21 16:25:51 +0100
committerVsevolod Stakhov <vsevolod@highsecure.ru>2014-04-21 16:25:51 +0100
commit61555065f3d1c8badcc9573691232f1b6e42988c (patch)
tree563d5b7cb8c468530f7e79c4da0a75267b1184e1 /src/libserver/dynamic_cfg.c
parentad5bf825b7f33bc10311673991f0cc888e69c0b1 (diff)
downloadrspamd-61555065f3d1c8badcc9573691232f1b6e42988c.tar.gz
rspamd-61555065f3d1c8badcc9573691232f1b6e42988c.zip
Rework project structure, remove trash files.
Diffstat (limited to 'src/libserver/dynamic_cfg.c')
-rw-r--r--src/libserver/dynamic_cfg.c599
1 files changed, 599 insertions, 0 deletions
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;
+}