summaryrefslogtreecommitdiffstats
path: root/src/libserver/settings.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libserver/settings.c')
-rw-r--r--src/libserver/settings.c657
1 files changed, 657 insertions, 0 deletions
diff --git a/src/libserver/settings.c b/src/libserver/settings.c
new file mode 100644
index 000000000..c3292c8ab
--- /dev/null
+++ b/src/libserver/settings.c
@@ -0,0 +1,657 @@
+/*
+ * Copyright (c) 2009-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 BY AUTHOR ''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 "cfg_file.h"
+#include "map.h"
+#include "main.h"
+#include "settings.h"
+#include "filter.h"
+#include "json/jansson.h"
+
+struct json_buf {
+ GHashTable *table;
+ gchar *buf;
+ gchar *pos;
+ size_t buflen;
+};
+
+static void
+settings_actions_free (gpointer data)
+{
+ GList *cur = data;
+
+ while (cur) {
+ g_free (cur->data);
+ cur = g_list_next (cur);
+ }
+
+ g_list_free ((GList *)data);
+}
+
+static void
+settings_free (gpointer data)
+{
+ struct rspamd_settings *s = data;
+
+ if (s->statfile_alias) {
+ g_free (s->statfile_alias);
+ }
+ if (s->factors) {
+ g_hash_table_destroy (s->factors);
+ }
+ if (s->metric_scores) {
+ g_hash_table_destroy (s->metric_scores);
+ }
+ if (s->reject_scores) {
+ g_hash_table_destroy (s->reject_scores);
+ }
+ if (s->whitelist) {
+ g_hash_table_destroy (s->whitelist);
+ }
+ if (s->blacklist) {
+ g_hash_table_destroy (s->blacklist);
+ }
+ if (s->metric_actions) {
+ g_hash_table_destroy (s->metric_actions);
+ }
+
+ g_slice_free1 (sizeof (struct rspamd_settings), s);
+}
+
+static struct rspamd_settings *
+settings_ref (struct rspamd_settings *s)
+{
+ if (s == NULL) {
+ s = g_slice_alloc (sizeof (struct rspamd_settings));
+ s->metric_scores = g_hash_table_new_full (rspamd_str_hash, rspamd_str_equal, g_free, g_free);
+ s->reject_scores = g_hash_table_new_full (rspamd_str_hash, rspamd_str_equal, g_free, g_free);
+ s->metric_actions = g_hash_table_new_full (rspamd_str_hash, rspamd_str_equal, g_free, settings_actions_free);
+ s->factors = g_hash_table_new_full (rspamd_str_hash, rspamd_str_equal, g_free, g_free);
+ s->whitelist = g_hash_table_new_full (rspamd_str_hash, rspamd_str_equal, g_free, g_free);
+ s->blacklist = g_hash_table_new_full (rspamd_str_hash, rspamd_str_equal, g_free, g_free);
+ s->statfile_alias = NULL;
+ s->want_spam = FALSE;
+ s->ref_count = 1;
+ }
+ else {
+ s->ref_count ++;
+ }
+
+ return s;
+}
+
+static void
+settings_unref (struct rspamd_settings *s)
+{
+ if (s != NULL) {
+ s->ref_count --;
+ if (s->ref_count <= 0) {
+ settings_free (s);
+ }
+ }
+}
+
+
+gchar *
+json_read_cb (rspamd_mempool_t * pool, gchar * chunk, gint len, struct map_cb_data *data)
+{
+ struct json_buf *jb;
+ size_t free, off;
+
+ if (data->cur_data == NULL) {
+ jb = g_malloc (sizeof (struct json_buf));
+ jb->table = g_hash_table_ref (((struct json_buf *)data->prev_data)->table);
+ jb->buf = NULL;
+ jb->pos = 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 ((gint)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_fin_cb (rspamd_mempool_t * pool, struct map_cb_data *data)
+{
+ struct json_buf *jb;
+ gint nelts, i, n, j;
+ json_t *js, *cur_elt, *cur_nm, *it_val, *act_it, *act_value;
+ json_error_t je;
+ struct metric_action *new_act;
+ struct rspamd_settings *cur_settings;
+ GList *cur_act;
+ gchar *cur_name;
+ void *json_it;
+ double *score;
+
+ if (data->prev_data) {
+ jb = data->prev_data;
+ /* Clean prev data */
+ if (jb->table) {
+ g_hash_table_unref (jb->table);
+ }
+ 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;
+ }
+
+ nelts = json_array_size (js);
+ for (i = 0; i < nelts; i++) {
+ cur_settings = settings_ref (NULL);
+
+ cur_elt = json_array_get (js, i);
+ if (!cur_elt || !json_is_object (cur_elt)) {
+ json_decref (js);
+ msg_err ("loaded json is not an object");
+ settings_unref (cur_settings);
+ return;
+ }
+ cur_nm = json_object_get (cur_elt, "name");
+ if (cur_nm == NULL || !json_is_string (cur_nm)) {
+ json_decref (js);
+ msg_err ("name is not a string or not exists");
+ settings_unref (cur_settings);
+ return;
+ }
+ cur_name = g_strdup (json_string_value (cur_nm));
+ /* Now check other settings */
+ /* Statfile */
+ cur_nm = json_object_get (cur_elt, "statfile");
+ if (cur_nm != NULL && json_is_string (cur_nm)) {
+ cur_settings->statfile_alias = g_strdup (json_string_value (cur_nm));
+ }
+ /* Factors object */
+ cur_nm = json_object_get (cur_elt, "factors");
+ if (cur_nm != NULL && json_is_object (cur_nm)) {
+ json_it = json_object_iter (cur_nm);
+ while (json_it) {
+ it_val = json_object_iter_value (json_it);
+ if (it_val && json_is_string (it_val)) {
+ g_hash_table_insert (cur_settings->factors, g_strdup (json_object_iter_key (json_it)), g_strdup (json_string_value (it_val)));
+ }
+ json_it = json_object_iter_next (cur_nm, json_it);
+ }
+ }
+ /* Metrics object */
+ cur_nm = json_object_get (cur_elt, "metrics");
+ if (cur_nm != NULL && json_is_object (cur_nm)) {
+ json_it = json_object_iter (cur_nm);
+ while (json_it) {
+ it_val = json_object_iter_value (json_it);
+ if (it_val && json_is_number (it_val)) {
+ score = g_malloc (sizeof (double));
+ *score = json_number_value (it_val);
+ g_hash_table_insert (cur_settings->metric_scores,
+ g_strdup (json_object_iter_key (json_it)), score);
+ }
+ else if (it_val && json_is_object (it_val)) {
+ /* Assume this as actions hash */
+ cur_act = NULL;
+ act_it = json_object_iter (it_val);
+ while (act_it) {
+ act_value = json_object_iter_value (act_it);
+
+ if (act_value && json_is_number (act_value)) {
+ /* Special cases */
+ if (g_ascii_strcasecmp (json_object_iter_key (act_it), "spam_score") == 0) {
+ score = g_malloc (sizeof (double));
+ *score = json_number_value (act_value);
+ g_hash_table_insert (cur_settings->metric_scores,
+ g_strdup (json_object_iter_key (json_it)), score);
+ }
+ else if (g_ascii_strcasecmp (json_object_iter_key (act_it), "reject_score") == 0) {
+ score = g_malloc (sizeof (double));
+ *score = json_number_value (act_value);
+ g_hash_table_insert (cur_settings->reject_scores,
+ g_strdup (json_object_iter_key (json_it)), score);
+ }
+ else if (check_action_str (json_object_iter_key (act_it), &j)) {
+ new_act = g_malloc (sizeof (struct metric_action));
+ new_act->action = j;
+ new_act->score = json_number_value (act_value);
+ cur_act = g_list_prepend (cur_act, new_act);
+ }
+ }
+ act_it = json_object_iter_next (it_val, act_it);
+ }
+ if (cur_act != NULL) {
+ g_hash_table_insert (cur_settings->metric_actions,
+ g_strdup (json_object_iter_key (json_it)), cur_act);
+ cur_act = NULL;
+ }
+ }
+ json_it = json_object_iter_next (cur_nm, json_it);
+ }
+ }
+ /* Rejects object */
+ cur_nm = json_object_get (cur_elt, "rejects");
+ if (cur_nm != NULL && json_is_object (cur_nm)) {
+ json_it = json_object_iter (cur_nm);
+ while (json_it) {
+ it_val = json_object_iter_value (json_it);
+ if (it_val && json_is_number (it_val)) {
+ score = g_malloc (sizeof (double));
+ *score = json_number_value (it_val);
+ g_hash_table_insert (cur_settings->reject_scores, g_strdup (json_object_iter_key (json_it)),
+ score);
+ }
+ json_it = json_object_iter_next(cur_nm, json_it);
+ }
+ }
+ /* Whitelist object */
+ cur_nm = json_object_get (cur_elt, "whitelist");
+ if (cur_nm != NULL && json_is_array (cur_nm)) {
+ n = json_array_size(cur_nm);
+ for(j = 0; j < n; j++) {
+ it_val = json_array_get(cur_nm, j);
+ if (it_val && json_is_string (it_val)) {
+ if (strlen (json_string_value (it_val)) > 0) {
+ g_hash_table_insert (cur_settings->whitelist,
+ g_strdup (json_string_value (it_val)), g_strdup (json_string_value (it_val)));
+ }
+ }
+
+ }
+ }
+ /* Blacklist object */
+ cur_nm = json_object_get (cur_elt, "blacklist");
+ if (cur_nm != NULL && json_is_array (cur_nm)) {
+ n = json_array_size(cur_nm);
+ for(j = 0; j < n; j++) {
+ it_val = json_array_get(cur_nm, j);
+ if (it_val && json_is_string (it_val)) {
+ if (strlen (json_string_value (it_val)) > 0) {
+ g_hash_table_insert (cur_settings->blacklist,
+ g_strdup (json_string_value (it_val)), g_strdup (json_string_value (it_val)));
+ }
+ }
+
+ }
+ }
+ /* Want spam */
+ cur_nm = json_object_get (cur_elt, "want_spam");
+ if (cur_nm != NULL) {
+ if (json_is_true (cur_nm)) {
+ cur_settings->want_spam = TRUE;
+ }
+ }
+ g_hash_table_replace (((struct json_buf *)data->cur_data)->table, cur_name, cur_settings);
+ }
+ json_decref (js);
+}
+
+gboolean
+read_settings (const gchar *path, const gchar *description, struct config_file *cfg, GHashTable * table)
+{
+ struct json_buf *jb = g_malloc (sizeof (struct json_buf)), **pjb;
+
+ pjb = g_malloc (sizeof (struct json_buf *));
+
+ jb->table = table;
+ jb->buf = NULL;
+ *pjb = jb;
+
+ if (!add_map (cfg, path, description, json_read_cb, json_fin_cb, (void **)pjb)) {
+ msg_err ("cannot add map %s", path);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+init_settings (struct config_file *cfg)
+{
+ cfg->domain_settings = g_hash_table_new_full (rspamd_strcase_hash, rspamd_strcase_equal,
+ g_free, (GDestroyNotify)settings_unref);
+ cfg->user_settings = g_hash_table_new_full (rspamd_strcase_hash, rspamd_strcase_equal,
+ g_free, (GDestroyNotify)settings_unref);
+}
+
+static gboolean
+check_setting (struct rspamd_task *task, struct rspamd_settings **user_settings, struct rspamd_settings **domain_settings)
+{
+ gchar *field = NULL, *domain = NULL;
+ gchar cmp_buf[1024];
+ gint len;
+
+ if (task->deliver_to != NULL) {
+ /* First try to use deliver-to field */
+ field = task->deliver_to;
+ }
+ else if (task->user != NULL) {
+ /* Then user field */
+ field = task->user;
+ }
+ else if (task->rcpt != NULL) {
+ /* Then first recipient */
+ field = task->rcpt->data;
+ }
+ else {
+ return FALSE;
+ }
+
+ domain = strchr (field, '@');
+ if (domain == NULL) {
+ /* First try to search in first recipient */
+ if (task->rcpt) {
+ domain = strchr (task->rcpt->data, '@');
+ }
+ }
+ if (domain != NULL) {
+ domain++;
+ }
+
+ /* First try to search per-user settings */
+ if (field != NULL) {
+ if (*field == '<') {
+ field ++;
+ }
+ len = strcspn (field, ">");
+ rspamd_strlcpy (cmp_buf, field, MIN ((gint)sizeof (cmp_buf), len + 1));
+ *user_settings = g_hash_table_lookup (task->cfg->user_settings, cmp_buf);
+ }
+ if (domain != NULL) {
+ len = strcspn (domain, ">");
+ rspamd_strlcpy (cmp_buf, domain, MIN ((gint)sizeof (cmp_buf), len + 1));
+ *domain_settings = g_hash_table_lookup (task->cfg->domain_settings, cmp_buf);
+ }
+
+ if (*domain_settings != NULL || *user_settings != NULL) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+check_bwhitelist (struct rspamd_task *task, struct rspamd_settings *s, gboolean *is_black)
+{
+ gchar *src_email = NULL, *src_domain = NULL, *data;
+
+ if (task->from != NULL && *task->from != '\0') {
+ src_email = task->from;
+ } else {
+ return FALSE;
+ }
+
+ src_domain = strchr (src_email, '@');
+ if(src_domain != NULL) {
+ src_domain++;
+ }
+
+ if ((((data = g_hash_table_lookup (s->blacklist, src_email)) != NULL) ||
+ ( (src_domain != NULL) && ((data = g_hash_table_lookup (s->blacklist, src_domain)) != NULL)) )) {
+ *is_black = TRUE;
+ msg_info ("<%s> blacklisted as domain %s is in settings blacklist", task->message_id, data);
+ return TRUE;
+ }
+ if ((((data = g_hash_table_lookup (s->whitelist, src_email)) != NULL) ||
+ ( (src_domain != NULL) && ((data = g_hash_table_lookup (s->whitelist, src_domain)) != NULL)) )) {
+ *is_black = FALSE;
+ msg_info ("<%s> whitelisted as domain %s is in settings blacklist", task->message_id, data);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+gboolean
+check_metric_settings (struct metric_result *res, double *score, double *rscore)
+{
+ struct rspamd_settings *us = res->user_settings, *ds = res->domain_settings;
+ double *sc, *rs;
+ struct metric *metric = res->metric;
+
+ /* XXX: what the fuck is that? */
+ *rscore = 10.0;
+
+ if (us != NULL) {
+ if ((rs = g_hash_table_lookup (us->reject_scores, metric->name)) != NULL) {
+ *rscore = *rs;
+ }
+ if ((sc = g_hash_table_lookup (us->metric_scores, metric->name)) != NULL) {
+ *score = *sc;
+ return TRUE;
+ }
+ /* Now check in domain settings */
+ if (ds && ((rs = g_hash_table_lookup (ds->reject_scores, metric->name)) != NULL)) {
+ *rscore = *rs;
+ }
+ if (ds && (sc = g_hash_table_lookup (ds->metric_scores, metric->name)) != NULL) {
+ *score = *sc;
+ return TRUE;
+ }
+ }
+ else if (ds != NULL) {
+ if ((rs = g_hash_table_lookup (ds->reject_scores, metric->name)) != NULL) {
+ *rscore = *rs;
+ }
+ if ((sc = g_hash_table_lookup (ds->metric_scores, metric->name)) != NULL) {
+ *score = *sc;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean
+check_metric_action_settings (struct rspamd_task *task, struct metric_result *res,
+ double score, enum rspamd_metric_action *result)
+{
+ struct rspamd_settings *us = res->user_settings, *ds = res->domain_settings;
+ struct metric_action *act, *sel = NULL;
+ GList *cur;
+ enum rspamd_metric_action r = METRIC_ACTION_NOACTION;
+ gboolean black;
+
+ if (us != NULL) {
+ /* Check whitelist and set appropriate action for whitelisted users */
+ if (check_bwhitelist(task, us, &black)) {
+ if (black) {
+ *result = METRIC_ACTION_REJECT;
+ }
+ else {
+ *result = METRIC_ACTION_NOACTION;
+ }
+ return TRUE;
+ }
+ if ((cur = g_hash_table_lookup (us->metric_actions, res->metric->name)) != NULL) {
+ while (cur) {
+ act = cur->data;
+ if (score >= act->score) {
+ r = act->action;
+ sel = act;
+ }
+ cur = g_list_next (cur);
+ }
+ }
+ }
+ else if (ds != NULL) {
+ /* Check whitelist and set appropriate action for whitelisted users */
+ if (check_bwhitelist(task, ds, &black)) {
+ if (black) {
+ *result = METRIC_ACTION_REJECT;
+ }
+ else {
+ *result = METRIC_ACTION_NOACTION;
+ }
+ return TRUE;
+ }
+ if ((cur = g_hash_table_lookup (ds->metric_actions, res->metric->name)) != NULL) {
+ while (cur) {
+ act = cur->data;
+ if (score >= act->score) {
+ r = act->action;
+ sel = act;
+ }
+ cur = g_list_next (cur);
+ }
+ }
+ }
+
+ if (sel != NULL && result != NULL) {
+ *result = r;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+apply_metric_settings (struct rspamd_task *task, struct metric *metric, struct metric_result *res)
+{
+ struct rspamd_settings *us = NULL, *ds = NULL;
+
+ if (check_setting (task, &us, &ds)) {
+ if (us != NULL || ds != NULL) {
+ if (us != NULL) {
+ res->user_settings = settings_ref (us);
+ rspamd_mempool_add_destructor (task->task_pool, (rspamd_mempool_destruct_t)settings_unref,
+ us);
+ }
+ if (ds != NULL) {
+ /* Need to ref hash table to avoid occasional data corruption */
+ res->domain_settings = settings_ref (ds);
+ rspamd_mempool_add_destructor (task->task_pool, (rspamd_mempool_destruct_t)settings_unref,
+ ds);
+ }
+ }
+ else {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+check_factor_settings (struct metric_result *res, const gchar *symbol, double *factor)
+{
+ double *fc;
+
+ if (res->user_settings != NULL) {
+ /* First search in user's settings */
+ if ((fc = g_hash_table_lookup (res->user_settings->factors, symbol)) != NULL) {
+ *factor = *fc;
+ return TRUE;
+ }
+ /* Now check in domain settings */
+ if (res->domain_settings && (fc = g_hash_table_lookup (res->domain_settings->factors, symbol)) != NULL) {
+ *factor = *fc;
+ return TRUE;
+ }
+ }
+ else if (res->domain_settings != NULL) {
+ if ((fc = g_hash_table_lookup (res->domain_settings->factors, symbol)) != NULL) {
+ *factor = *fc;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+
+}
+
+
+gboolean
+check_want_spam (struct rspamd_task *task)
+{
+ struct rspamd_settings *us = NULL, *ds = NULL;
+
+ if (check_setting (task, &us, &ds)) {
+ if (us != NULL) {
+ /* First search in user's settings */
+ if (us->want_spam) {
+ return TRUE;
+ }
+ /* Now check in domain settings */
+ if (ds && ds->want_spam) {
+ return TRUE;
+ }
+ }
+ else if (ds != NULL) {
+ if (ds->want_spam) {
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+/*
+ * vi:ts=4
+ */