/* * 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 */