gboolean rspamd_task_add_result_option (struct rspamd_task *task, struct rspamd_symbol_result *s, const gchar *opt) { char *opt_cpy; gboolean ret = FALSE; if (s && opt) { if (s->options && !(s->sym && (s->sym->flags & RSPAMD_SYMBOL_FLAG_ONEPARAM))) { /* Append new options */ if (!g_hash_table_lookup (s->options, opt)) { opt_cpy = rspamd_mempool_strdup (task->task_pool, opt); g_hash_table_insert (s->options, opt_cpy, opt_cpy); ret = TRUE; } } else { s->options = g_hash_table_new (rspamd_strcase_hash, rspamd_strcase_equal); rspamd_mempool_add_destructor (task->task_pool, (rspamd_mempool_destruct_t)g_hash_table_unref, s->options); opt_cpy = rspamd_mempool_strdup (task->task_pool, opt); g_hash_table_insert (s->options, opt_cpy, opt_cpy); ret = TRUE; } } else if (!opt) { ret = TRUE; } return ret; }
static rspamd_expression_atom_t * lua_atom_parse (const gchar *line, gsize len, rspamd_mempool_t *pool, gpointer ud, GError **err) { struct lua_expression *e = (struct lua_expression *)ud; rspamd_expression_atom_t *atom; gsize rlen; const gchar *tok; lua_rawgeti (e->L, LUA_REGISTRYINDEX, e->parse_idx); lua_pushlstring (e->L, line, len); if (lua_pcall (e->L, 1, 1, 0) != 0) { msg_info ("callback call failed: %s", lua_tostring (e->L, -1)); } if (lua_type (e->L, -1) != LUA_TSTRING) { g_set_error (err, lua_expr_quark(), 500, "cannot parse lua atom"); lua_pop (e->L, 1); return NULL; } tok = lua_tolstring (e->L, -1, &rlen); atom = rspamd_mempool_alloc0 (e->pool, sizeof (*atom)); atom->str = rspamd_mempool_strdup (e->pool, tok); atom->len = rlen; atom->data = ud; lua_pop (e->L, 1); return atom; }
static void dkim_module_key_handler (rspamd_dkim_key_t *key, gsize keylen, rspamd_dkim_context_t *ctx, gpointer ud, GError *err) { struct rspamd_task *task = ud; if (key != NULL) { /* Add new key to the lru cache */ rspamd_lru_hash_insert (dkim_module_ctx->dkim_hash, g_strdup (ctx->dns_key), key, task->tv.tv_sec, key->ttl); dkim_module_check (task, ctx, key); } else { /* Insert tempfail symbol */ msg_info ("cannot get key for domain %s", ctx->dns_key); if (err != NULL) { rspamd_task_insert_result (task, dkim_module_ctx->symbol_tempfail, 1, g_list_prepend (NULL, rspamd_mempool_strdup (task->task_pool, err->message))); } else { rspamd_task_insert_result (task, dkim_module_ctx->symbol_tempfail, 1, NULL); } } if (err) { g_error_free (err); } }
static gint lua_config_register_symbol (lua_State * L) { struct rspamd_config *cfg = lua_check_config (L); gchar *name; double weight; if (cfg) { name = rspamd_mempool_strdup (cfg->cfg_pool, luaL_checkstring (L, 2)); weight = luaL_checknumber (L, 3); if (lua_type (L, 4) == LUA_TSTRING) { lua_getglobal (L, luaL_checkstring (L, 4)); } else { lua_pushvalue (L, 4); } if (name) { rspamd_register_symbol_fromlua (L, cfg, name, luaL_ref (L, LUA_REGISTRYINDEX), weight, 0, SYMBOL_TYPE_NORMAL); } } return 0; }
static void rspamd_register_symbol_fromlua (lua_State *L, struct rspamd_config *cfg, const gchar *name, gint ref, gdouble weight, gint priority, enum rspamd_symbol_type type) { struct lua_callback_data *cd; cd = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct lua_callback_data)); cd->cb_is_ref = TRUE; cd->callback.ref = ref; cd->L = L; if (name) { cd->symbol = rspamd_mempool_strdup (cfg->cfg_pool, name); } register_symbol_common (&cfg->cache, name, weight, priority, lua_metric_symbol_callback, cd, type); rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t)lua_destroy_cfg_symbol, cd); }
static gint lua_config_register_pre_filter (lua_State *L) { struct rspamd_config *cfg = lua_check_config (L); struct lua_callback_data *cd; if (cfg) { cd = rspamd_mempool_alloc (cfg->cfg_pool, sizeof (struct lua_callback_data)); if (lua_type (L, 2) == LUA_TSTRING) { cd->callback.name = rspamd_mempool_strdup (cfg->cfg_pool, luaL_checkstring (L, 2)); cd->cb_is_ref = FALSE; } else { lua_pushvalue (L, 2); /* Get a reference */ cd->callback.ref = luaL_ref (L, LUA_REGISTRYINDEX); cd->cb_is_ref = TRUE; } cd->L = L; cfg->pre_filters = g_list_prepend (cfg->pre_filters, cd); rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t)lua_destroy_cfg_symbol, cd); } return 1; }
static gint lua_config_register_symbols (lua_State *L) { struct rspamd_config *cfg = lua_check_config (L); gint i, top, idx; gchar *sym; gdouble weight = 1.0; if (lua_gettop (L) < 3) { msg_err ("not enough arguments to register a function"); return 0; } if (cfg) { if (lua_type (L, 2) == LUA_TSTRING) { lua_getglobal (L, luaL_checkstring (L, 2)); } else { lua_pushvalue (L, 2); } idx = luaL_ref (L, LUA_REGISTRYINDEX); if (lua_type (L, 3) == LUA_TNUMBER) { weight = lua_tonumber (L, 3); top = 4; } else { top = 3; } sym = rspamd_mempool_strdup (cfg->cfg_pool, luaL_checkstring (L, top)); rspamd_register_symbol_fromlua (L, cfg, sym, idx, weight, 0, SYMBOL_TYPE_NORMAL); for (i = top; i < lua_gettop (L); i++) { sym = rspamd_mempool_strdup (cfg->cfg_pool, luaL_checkstring (L, i + 1)); register_virtual_symbol (&cfg->cache, sym, weight); } } return 0; }
static void lua_metric_symbol_callback (struct rspamd_task *task, gpointer ud) { struct lua_callback_data *cd = ud; struct rspamd_task **ptask; gint level = lua_gettop (cd->L), nresults; if (cd->cb_is_ref) { lua_rawgeti (cd->L, LUA_REGISTRYINDEX, cd->callback.ref); } else { lua_getglobal (cd->L, cd->callback.name); } ptask = lua_newuserdata (cd->L, sizeof (struct rspamd_task *)); rspamd_lua_setclass (cd->L, "rspamd{task}", -1); *ptask = task; if (lua_pcall (cd->L, 1, LUA_MULTRET, 0) != 0) { msg_info ("call to (%s)%s failed: %s", cd->symbol, cd->cb_is_ref ? "local function" : cd->callback.name, lua_tostring (cd->L, -1)); } nresults = lua_gettop (cd->L) - level; if (nresults >= 1) { /* Function returned boolean, so maybe we need to insert result? */ gboolean res; GList *opts = NULL; gint i; gdouble flag = 1.0; if (lua_type (cd->L, level + 1) == LUA_TBOOLEAN) { res = lua_toboolean (cd->L, level + 1); if (res) { gint first_opt = 2; if (lua_type (cd->L, level + 2) == LUA_TNUMBER) { flag = lua_tonumber (cd->L, level + 2); /* Shift opt index */ first_opt = 3; } for (i = lua_gettop (cd->L); i >= level + first_opt; i --) { if (lua_type (cd->L, i) == LUA_TSTRING) { const char *opt = lua_tostring (cd->L, i); opts = g_list_prepend (opts, rspamd_mempool_strdup (task->task_pool, opt)); } } rspamd_task_insert_result (task, cd->symbol, flag, opts); } } lua_pop (cd->L, nresults); } }
static gint lua_config_register_function (lua_State *L) { struct rspamd_config *cfg = lua_check_config (L); gchar *name; struct lua_callback_data *cd; if (cfg) { name = rspamd_mempool_strdup (cfg->cfg_pool, luaL_checkstring (L, 2)); cd = rspamd_mempool_alloc (cfg->cfg_pool, sizeof (struct lua_callback_data)); if (lua_type (L, 3) == LUA_TSTRING) { cd->callback.name = rspamd_mempool_strdup (cfg->cfg_pool, luaL_checkstring (L, 3)); cd->cb_is_ref = FALSE; } else { lua_pushvalue (L, 3); /* Get a reference */ cd->callback.ref = luaL_ref (L, LUA_REGISTRYINDEX); cd->cb_is_ref = TRUE; } if (name) { cd->L = L; cd->symbol = name; register_expression_function (name, lua_config_function_callback, cd); } rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t)lua_destroy_cfg_symbol, cd); } return 1; }
static gint lua_config_register_virtual_symbol (lua_State * L) { struct rspamd_config *cfg = lua_check_config (L); gchar *name; double weight; if (cfg) { name = rspamd_mempool_strdup (cfg->cfg_pool, luaL_checkstring (L, 2)); weight = luaL_checknumber (L, 3); if (name) { register_virtual_symbol (&cfg->cache, name, weight); } } return 0; }
void rspamd_symbols_cache_add_dependency (struct symbols_cache *cache, gint id_from, const gchar *to) { struct cache_item *source; struct cache_dependency *dep; g_assert (id_from < (gint)cache->items_by_id->len); source = g_ptr_array_index (cache->items_by_id, id_from); dep = rspamd_mempool_alloc (cache->static_pool, sizeof (*dep)); dep->id = id_from; dep->sym = rspamd_mempool_strdup (cache->static_pool, to); /* Will be filled later */ dep->item = NULL; g_ptr_array_add (source->deps, dep); }
struct rspamd_symbols_group * rspamd_config_new_group (struct rspamd_config *cfg, struct metric *metric, const gchar *name) { struct rspamd_symbols_group *gr; gr = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (*gr)); gr->symbols = g_hash_table_new (rspamd_strcase_hash, rspamd_strcase_equal); rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t)g_hash_table_unref, gr->symbols); gr->name = rspamd_mempool_strdup (cfg->cfg_pool, name); g_hash_table_insert (metric->groups, gr->name, gr); return gr; }
struct metric * rspamd_config_new_metric (struct rspamd_config *cfg, struct metric *c, const gchar *name) { int i; if (c == NULL) { c = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct metric)); c->grow_factor = 1.0; c->symbols = g_hash_table_new (rspamd_str_hash, rspamd_str_equal); c->groups = g_hash_table_new (rspamd_str_hash, rspamd_str_equal); for (i = METRIC_ACTION_REJECT; i < METRIC_ACTION_MAX; i++) { c->actions[i].score = -1.0; c->actions[i].action = i; } c->subject = SPAM_SUBJECT; c->name = rspamd_mempool_strdup (cfg->cfg_pool, name); rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t) g_hash_table_unref, c->symbols); rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t) g_hash_table_unref, c->groups); g_hash_table_insert (cfg->metrics, (void *)c->name, c); cfg->metrics_list = g_list_prepend (cfg->metrics_list, c); if (strcmp (c->name, DEFAULT_METRIC) == 0) { cfg->default_metric = c; } } return c; }
gint rspamd_symbols_cache_add_symbol (struct symbols_cache *cache, const gchar *name, double weight, gint priority, symbol_func_t func, gpointer user_data, enum rspamd_symbol_type type, gint parent) { struct cache_item *item = NULL; g_assert (cache != NULL); if (name == NULL && type != SYMBOL_TYPE_CALLBACK) { msg_warn ("no name for non-callback symbol!"); } else if (type == SYMBOL_TYPE_VIRTUAL && parent == -1) { msg_warn ("no parent symbol is associated with virtual symbol %s", name); } if (name != NULL) { if (g_hash_table_lookup (cache->items_by_symbol, name) != NULL) { msg_err ("skip duplicate symbol registration for %s", name); return -1; } } item = rspamd_mempool_alloc0_shared (cache->static_pool, sizeof (struct cache_item)); /* * We do not share cd to skip locking, instead we'll just calculate it on * save or accumulate */ item->cd = rspamd_mempool_alloc0 (cache->static_pool, sizeof (struct counter_data)); if (name != NULL) { item->symbol = rspamd_mempool_strdup (cache->static_pool, name); } item->func = func; item->user_data = user_data; item->priority = priority; item->type = type; item->weight = weight; if (item->weight < 0 && item->priority == 0) { /* Make priority for negative weighted symbols */ item->priority = 1; } item->id = cache->used_items; item->parent = parent; cache->used_items ++; msg_debug ("used items: %d, added symbol: %s", cache->used_items, name); rspamd_set_counter (item, 0); g_ptr_array_add (cache->items_by_id, item); g_ptr_array_add (cache->items_by_order, item); item->deps = g_ptr_array_new (); item->rdeps = g_ptr_array_new (); rspamd_mempool_add_destructor (cache->static_pool, rspamd_ptr_array_free_hard, item->deps); rspamd_mempool_add_destructor (cache->static_pool, rspamd_ptr_array_free_hard, item->rdeps); if (name != NULL) { g_hash_table_insert (cache->items_by_symbol, item->symbol, item); } return item->id; }
gboolean rspamd_config_add_metric_symbol (struct rspamd_config *cfg, const gchar *metric_name, const gchar *symbol, gdouble score, const gchar *description, const gchar *group, gboolean one_shot, gboolean rewrite_existing) { struct rspamd_symbols_group *sym_group; struct rspamd_symbol_def *sym_def; GList *metric_list; struct metric *metric; gdouble *score_ptr; g_assert (cfg != NULL); g_assert (symbol != NULL); if (metric_name == NULL) { metric_name = DEFAULT_METRIC; } metric = g_hash_table_lookup (cfg->metrics, metric_name); if (metric == NULL) { msg_err_config ("metric %s has not been found", metric_name); return FALSE; } if (g_hash_table_lookup (cfg->metrics_symbols, symbol) != NULL && !rewrite_existing) { msg_debug_config ("symbol %s has been already registered, do not override"); return FALSE; } sym_def = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct rspamd_symbol_def)); score_ptr = rspamd_mempool_alloc (cfg->cfg_pool, sizeof (gdouble)); *score_ptr = score; sym_def->score = score; sym_def->weight_ptr = score_ptr; sym_def->name = rspamd_mempool_strdup (cfg->cfg_pool, symbol); sym_def->one_shot = one_shot; if (description) { sym_def->description = rspamd_mempool_strdup (cfg->cfg_pool, description); } msg_debug_config ("registered symbol %s with weight %.2f in metric %s and group %s", sym_def->name, score, metric->name, group); g_hash_table_insert (metric->symbols, sym_def->name, sym_def); if ((metric_list = g_hash_table_lookup (cfg->metrics_symbols, sym_def->name)) == NULL) { metric_list = g_list_prepend (NULL, metric); rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t)g_list_free, metric_list); g_hash_table_insert (cfg->metrics_symbols, sym_def->name, metric_list); } else { /* Slow but keep start element of list in safe */ if (!g_list_find (metric_list, metric)) { metric_list = g_list_append (metric_list, metric); } } /* Search for symbol group */ if (group == NULL) { group = "ungrouped"; } sym_group = g_hash_table_lookup (metric->groups, group); if (sym_group == NULL) { /* Create new group */ sym_group = rspamd_config_new_group (cfg, metric, group); } sym_def->gr = sym_group; g_hash_table_insert (sym_group->symbols, sym_def->name, sym_def); return TRUE; }
gboolean rspamd_parse_bind_line (struct rspamd_config *cfg, struct rspamd_worker_conf *cf, const gchar *str) { struct rspamd_worker_bind_conf *cnf; gchar **tokens, *err; gboolean ret = TRUE; if (str == NULL) { return FALSE; } if (str[0] == '[') { /* This is an ipv6 address */ gsize len, ntok; const gchar *start, *ip_pos; start = str + 1; len = strcspn (start, "]"); if (start[len] != ']') { return FALSE; } ip_pos = start; start += len + 1; ntok = 1; if (*start == ':') { ntok = 2; start ++; } tokens = g_malloc_n (ntok + 1, sizeof (gchar *)); tokens[ntok] = NULL; tokens[0] = g_malloc (len + 1); rspamd_strlcpy (tokens[0], ip_pos, len + 1); if (ntok > 1) { tokens[1] = g_strdup (start); } } else { tokens = g_strsplit_set (str, ":", 0); } if (!tokens || !tokens[0]) { return FALSE; } cnf = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct rspamd_worker_bind_conf)); cnf->cnt = 1024; if (strcmp (tokens[0], "systemd") == 0) { /* The actual socket will be passed by systemd environment */ cnf->is_systemd = TRUE; cnf->cnt = strtoul (tokens[1], &err, 10); cnf->addrs = NULL; if (err == NULL || *err == '\0') { cnf->name = rspamd_mempool_strdup (cfg->cfg_pool, str); LL_PREPEND (cf->bind_conf, cnf); } else { msg_err_config ("cannot parse bind line: %s", str); ret = FALSE; } } else { if (!rspamd_parse_host_port_priority_strv (tokens, &cnf->addrs, NULL, &cnf->name, DEFAULT_BIND_PORT, cfg->cfg_pool)) { msg_err_config ("cannot parse bind line: %s", str); ret = FALSE; } else { cnf->cnt = cnf->addrs->len; LL_PREPEND (cf->bind_conf, cnf); } } g_strfreev (tokens); return ret; }
/* * Perform post load actions */ void rspamd_config_post_load (struct rspamd_config *cfg) { #ifdef HAVE_CLOCK_GETTIME struct timespec ts; #endif struct metric *def_metric; #ifdef HAVE_CLOCK_GETTIME #ifdef HAVE_CLOCK_PROCESS_CPUTIME_ID clock_getres (CLOCK_PROCESS_CPUTIME_ID, &ts); # elif defined(HAVE_CLOCK_VIRTUAL) clock_getres (CLOCK_VIRTUAL, &ts); # else clock_getres (CLOCK_REALTIME, &ts); # endif cfg->clock_res = log10 (1000000. / ts.tv_nsec); if (cfg->clock_res < 0) { cfg->clock_res = 0; } if (cfg->clock_res > 3) { cfg->clock_res = 3; } #else /* For gettimeofday */ cfg->clock_res = 1; #endif rspamd_regexp_library_init (); if ((def_metric = g_hash_table_lookup (cfg->metrics, DEFAULT_METRIC)) == NULL) { def_metric = rspamd_config_new_metric (cfg, NULL, DEFAULT_METRIC); def_metric->actions[METRIC_ACTION_REJECT].score = DEFAULT_SCORE; } if (cfg->tld_file == NULL) { /* Try to guess tld file */ GString *fpath = g_string_new (NULL); rspamd_printf_gstring (fpath, "%s%c%s", RSPAMD_PLUGINSDIR, G_DIR_SEPARATOR, "effective_tld_names.dat"); if (access (fpath->str, R_OK)) { msg_warn_config ("url_tld option is not specified but %s is available," " therefore this file is assumed as TLD file for URL" " extraction", fpath->str); cfg->tld_file = rspamd_mempool_strdup (cfg->cfg_pool, fpath->str); } else { msg_err_config ("no url_tld option has been specified, URL's detection " "will be awfully broken"); } g_string_free (fpath, TRUE); } /* Lua options */ (void)rspamd_lua_post_load_config (cfg); init_dynamic_config (cfg); rspamd_url_init (cfg->tld_file); /* Insert classifiers symbols */ (void)rspamd_config_insert_classify_symbols (cfg); }
/*** * @function rspamd_tcp.request({params}) * This function creates and sends TCP request to the specified host and port, * resolves hostname (if needed) and invokes continuation callback upon data received * from the remote peer. This function accepts table of arguments with the following * attributes * * - `task`: rspamd task objects (implies `pool`, `session`, `ev_base` and `resolver` arguments) * - `ev_base`: event base (if no task specified) * - `resolver`: DNS resolver (no task) * - `session`: events session (no task) * - `pool`: memory pool (no task) * - `host`: IP or name of the peer (required) * - `port`: remote port to use (required) * - `data`: a table of strings or `rspamd_text` objects that contains data pieces * - `callback`: continuation function (required) * - `timeout`: floating point value that specifies timeout for IO operations in seconds * - `partial`: boolean flag that specifies that callback should be called on any data portion received * - `stop_pattern`: stop reading on finding a certain pattern (e.g. \r\n.\r\n for smtp) * @return {boolean} true if request has been sent */ static gint lua_tcp_request (lua_State *L) { const gchar *host; gchar *stop_pattern = NULL; guint port; gint cbref, tp; struct event_base *ev_base; struct lua_tcp_cbdata *cbd; struct rspamd_dns_resolver *resolver; struct rspamd_async_session *session; struct rspamd_task *task = NULL; rspamd_mempool_t *pool; struct iovec *iov = NULL; guint niov = 0, total_out; gdouble timeout = default_tcp_timeout; gboolean partial = FALSE; if (lua_type (L, 1) == LUA_TTABLE) { lua_pushstring (L, "host"); lua_gettable (L, -2); host = luaL_checkstring (L, -1); lua_pop (L, 1); lua_pushstring (L, "port"); lua_gettable (L, -2); port = luaL_checknumber (L, -1); lua_pop (L, 1); lua_pushstring (L, "callback"); lua_gettable (L, -2); if (host == NULL || lua_type (L, -1) != LUA_TFUNCTION) { lua_pop (L, 1); msg_err ("tcp request has bad params"); lua_pushboolean (L, FALSE); return 1; } cbref = luaL_ref (L, LUA_REGISTRYINDEX); lua_pushstring (L, "task"); lua_gettable (L, -2); if (lua_type (L, -1) == LUA_TUSERDATA) { task = lua_check_task (L, -1); ev_base = task->ev_base; resolver = task->resolver; session = task->s; pool = task->task_pool; } lua_pop (L, 1); if (task == NULL) { lua_pushstring (L, "ev_base"); lua_gettable (L, -2); if (luaL_checkudata (L, -1, "rspamd{ev_base}")) { ev_base = *(struct event_base **)lua_touserdata (L, -1); } else { ev_base = NULL; } lua_pop (L, 1); lua_pushstring (L, "pool"); lua_gettable (L, -2); if (luaL_checkudata (L, -1, "rspamd{mempool}")) { pool = *(rspamd_mempool_t **)lua_touserdata (L, -1); } else { pool = NULL; } lua_pop (L, 1); lua_pushstring (L, "resolver"); lua_gettable (L, -2); if (luaL_checkudata (L, -1, "rspamd{resolver}")) { resolver = *(struct rspamd_dns_resolver **)lua_touserdata (L, -1); } else { resolver = lua_tcp_global_resolver (ev_base); } lua_pop (L, 1); lua_pushstring (L, "session"); lua_gettable (L, -2); if (luaL_checkudata (L, -1, "rspamd{session}")) { session = *(struct rspamd_async_session **)lua_touserdata (L, -1); } else { session = NULL; } lua_pop (L, 1); } lua_pushstring (L, "timeout"); lua_gettable (L, -2); if (lua_type (L, -1) == LUA_TNUMBER) { timeout = lua_tonumber (L, -1) * 1000.; } lua_pop (L, 1); lua_pushstring (L, "stop_pattern"); lua_gettable (L, -2); if (lua_type (L, -1) == LUA_TSTRING) { stop_pattern = rspamd_mempool_strdup (pool, lua_tostring (L, -1)); } lua_pop (L, 1); lua_pushstring (L, "partial"); lua_gettable (L, -2); if (lua_type (L, -1) == LUA_TBOOLEAN) { partial = lua_toboolean (L, -1); } lua_pop (L, 1); if (pool == NULL) { lua_pop (L, 1); msg_err ("tcp request has no memory pool associated"); lua_pushboolean (L, FALSE); return 1; } lua_pushstring (L, "data"); lua_gettable (L, -2); total_out = 0; tp = lua_type (L, -1); if (tp == LUA_TSTRING || tp == LUA_TUSERDATA) { iov = rspamd_mempool_alloc (pool, sizeof (*iov)); niov = 1; if (!lua_tcp_arg_toiovec (L, -1, pool, iov)) { lua_pop (L, 1); msg_err ("tcp request has bad data argument"); lua_pushboolean (L, FALSE); return 1; } total_out = iov[0].iov_len; } else if (tp == LUA_TTABLE) { /* Count parts */ lua_pushnil (L); while (lua_next (L, -2) != 0) { niov ++; lua_pop (L, 1); } iov = rspamd_mempool_alloc (pool, sizeof (*iov) * niov); lua_pushnil (L); niov = 0; while (lua_next (L, -2) != 0) { if (!lua_tcp_arg_toiovec (L, -1, pool, &iov[niov])) { lua_pop (L, 2); msg_err ("tcp request has bad data argument at pos %d", niov); lua_pushboolean (L, FALSE); return 1; } total_out += iov[niov].iov_len; niov ++; lua_pop (L, 1); } } lua_pop (L, 1); } else { msg_err ("tcp request has bad params"); lua_pushboolean (L, FALSE); return 1; } cbd = g_slice_alloc0 (sizeof (*cbd)); cbd->L = L; cbd->cbref = cbref; cbd->ev_base = ev_base; msec_to_tv (timeout, &cbd->tv); cbd->fd = -1; cbd->pool = pool; cbd->partial = partial; cbd->iov = iov; cbd->iovlen = niov; cbd->total = total_out; cbd->pos = 0; cbd->port = port; cbd->stop_pattern = stop_pattern; if (session) { cbd->session = session; rspamd_session_add_event (session, (event_finalizer_t)lua_tcp_fin, cbd, g_quark_from_static_string ("lua tcp")); } if (rspamd_parse_inet_address (&cbd->addr, host)) { rspamd_inet_address_set_port (cbd->addr, port); /* Host is numeric IP, no need to resolve */ if (!lua_tcp_make_connection (cbd)) { lua_tcp_maybe_free (cbd); lua_pushboolean (L, FALSE); return 1; } } else { if (task == NULL) { if (!make_dns_request (resolver, session, NULL, lua_tcp_dns_handler, cbd, RDNS_REQUEST_A, host)) { lua_tcp_push_error (cbd, "cannot resolve host"); lua_tcp_maybe_free (cbd); } } else { if (!make_dns_request_task (task, lua_tcp_dns_handler, cbd, RDNS_REQUEST_A, host)) { lua_tcp_push_error (cbd, "cannot resolve host"); lua_tcp_maybe_free (cbd); } } } lua_pushboolean (L, TRUE); return 1; }
/* Process a single item in 'metrics' table */ static void lua_process_metric (lua_State *L, const gchar *name, struct rspamd_config *cfg) { GList *metric_list; gchar *symbol; const gchar *desc; struct metric *metric; gdouble *score; struct rspamd_symbol_def *s; /* Get module opt structure */ if ((metric = g_hash_table_lookup (cfg->metrics, name)) == NULL) { metric = rspamd_config_new_metric (cfg, metric, name); } /* Now iterate throught module table */ for (lua_pushnil (L); lua_next (L, -2); lua_pop (L, 1)) { /* key - -2, value - -1 */ symbol = rspamd_mempool_strdup (cfg->cfg_pool, luaL_checkstring (L, -2)); if (symbol != NULL) { if (lua_istable (L, -1)) { /* We got a table, so extract individual attributes */ lua_pushstring (L, "weight"); lua_gettable (L, -2); if (lua_isnumber (L, -1)) { score = rspamd_mempool_alloc (cfg->cfg_pool, sizeof (double)); *score = lua_tonumber (L, -1); } else { msg_warn_config("cannot get weight of symbol: %s", symbol); continue; } lua_pop (L, 1); lua_pushstring (L, "description"); lua_gettable (L, -2); if (lua_isstring (L, -1)) { desc = lua_tostring (L, -1); s->description = rspamd_mempool_strdup (cfg->cfg_pool, desc); } lua_pop (L, 1); } else if (lua_isnumber (L, -1)) { /* Just got weight */ score = rspamd_mempool_alloc (cfg->cfg_pool, sizeof (double)); *score = lua_tonumber (L, -1); } else { msg_warn_config("cannot get weight of symbol: %s", symbol); continue; } /* Insert symbol */ if ((s = g_hash_table_lookup (metric->symbols, symbol)) != NULL) { msg_info_config("replacing weight for symbol %s: %.2f -> %.2f", symbol, *s->weight_ptr, *score); s->weight_ptr = score; } else { s = rspamd_mempool_alloc (cfg->cfg_pool, sizeof (*s)); s->name = symbol; s->weight_ptr = score; g_hash_table_insert (metric->symbols, symbol, s); } if ((metric_list = g_hash_table_lookup (cfg->metrics_symbols, symbol)) == NULL) { metric_list = g_list_prepend (NULL, metric); rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t)g_list_free, metric_list); g_hash_table_insert (cfg->metrics_symbols, symbol, metric_list); } else { /* Slow but keep start element of list in safe */ if (!g_list_find (metric_list, metric)) { metric_list = g_list_append (metric_list, metric); } } } } }
gboolean rspamd_map_add (struct rspamd_config *cfg, const gchar *map_line, const gchar *description, map_cb_t read_callback, map_fin_cb_t fin_callback, void **user_data) { struct rspamd_map *new_map; enum fetch_proto proto; const gchar *def, *p, *hostend; struct file_map_data *fdata; struct http_map_data *hdata; gchar portbuf[6], *cksum_encoded, cksum[BLAKE2B_OUTBYTES]; gint i, s, r; struct addrinfo hints, *res; rspamd_mempool_t *pool; /* First of all detect protocol line */ if (!rspamd_map_check_proto (map_line, (int *)&proto, &def)) { return FALSE; } /* Constant pool */ if (cfg->map_pool == NULL) { cfg->map_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), "map"); memcpy (cfg->map_pool->tag.uid, cfg->cfg_pool->tag.uid, sizeof (cfg->map_pool->tag.uid)); } new_map = rspamd_mempool_alloc0 (cfg->map_pool, sizeof (struct rspamd_map)); new_map->read_callback = read_callback; new_map->fin_callback = fin_callback; new_map->user_data = user_data; new_map->protocol = proto; new_map->cfg = cfg; new_map->id = g_random_int (); new_map->locked = rspamd_mempool_alloc0_shared (cfg->cfg_pool, sizeof (gint)); if (proto == MAP_PROTO_FILE) { new_map->uri = rspamd_mempool_strdup (cfg->cfg_pool, def); def = new_map->uri; } else { new_map->uri = rspamd_mempool_strdup (cfg->cfg_pool, map_line); } if (description != NULL) { new_map->description = rspamd_mempool_strdup (cfg->cfg_pool, description); } /* Now check for each proto separately */ if (proto == MAP_PROTO_FILE) { fdata = rspamd_mempool_alloc0 (cfg->map_pool, sizeof (struct file_map_data)); if (access (def, R_OK) == -1) { if (errno != ENOENT) { msg_err_config ("cannot open file '%s': %s", def, strerror (errno)); return FALSE; } msg_info_config ( "map '%s' is not found, but it can be loaded automatically later", def); /* We still can add this file */ fdata->st.st_mtime = -1; } else { stat (def, &fdata->st); } fdata->filename = rspamd_mempool_strdup (cfg->map_pool, def); new_map->map_data = fdata; } else if (proto == MAP_PROTO_HTTP) { hdata = rspamd_mempool_alloc0 (cfg->map_pool, sizeof (struct http_map_data)); /* Try to search port */ if ((p = strchr (def, ':')) != NULL) { hostend = p; i = 0; p++; while (g_ascii_isdigit (*p) && i < (gint)sizeof (portbuf) - 1) { portbuf[i++] = *p++; } if (*p != '/') { msg_info_config ("bad http map definition: %s", def); return FALSE; } portbuf[i] = '\0'; hdata->port = atoi (portbuf); } else { /* Default http port */ rspamd_snprintf (portbuf, sizeof (portbuf), "80"); hdata->port = 80; /* Now separate host from path */ if ((p = strchr (def, '/')) == NULL) { msg_info_config ("bad http map definition: %s", def); return FALSE; } hostend = p; } hdata->host = rspamd_mempool_alloc (cfg->map_pool, hostend - def + 1); rspamd_strlcpy (hdata->host, def, hostend - def + 1); hdata->path = rspamd_mempool_strdup (cfg->map_pool, p); /* Now try to resolve */ memset (&hints, 0, sizeof (hints)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_STREAM; /* Stream socket */ hints.ai_flags = 0; hints.ai_protocol = 0; /* Any protocol */ hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL; if ((r = getaddrinfo (hdata->host, portbuf, &hints, &res)) == 0) { hdata->addr = res; rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t)freeaddrinfo, hdata->addr); } else { msg_err_config ("address resolution for %s failed: %s", hdata->host, gai_strerror (r)); return FALSE; } /* Now try to connect */ if ((s = rspamd_socket_tcp (hdata->addr, FALSE, FALSE)) == -1) { msg_info_config ("cannot connect to http server %s: %d, %s", hdata->host, errno, strerror (errno)); return FALSE; } close (s); hdata->conn = rspamd_http_connection_new (http_map_read, http_map_error, http_map_finish, RSPAMD_HTTP_BODY_PARTIAL | RSPAMD_HTTP_CLIENT_SIMPLE, RSPAMD_HTTP_CLIENT, NULL); new_map->map_data = hdata; } /* Temp pool */ blake2b (cksum, new_map->uri, NULL, sizeof (cksum), strlen (new_map->uri), 0); cksum_encoded = rspamd_encode_base32 (cksum, sizeof (cksum)); new_map->pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), "map"); memcpy (new_map->pool->tag.uid, cksum_encoded, sizeof (new_map->pool->tag.uid)); g_free (cksum_encoded); pool = new_map->pool; msg_info_pool ("added map %s", new_map->uri); cfg->maps = g_list_prepend (cfg->maps, new_map); return TRUE; }
struct rspamd_map * rspamd_map_add (struct rspamd_config *cfg, const gchar *map_line, const gchar *description, map_cb_t read_callback, map_fin_cb_t fin_callback, void **user_data) { struct rspamd_map *new_map; const gchar *def; struct file_map_data *fdata; struct http_map_data *hdata; gchar *cksum_encoded, cksum[rspamd_cryptobox_HASHBYTES]; rspamd_mempool_t *pool; struct http_parser_url up; rspamd_ftok_t tok; if (cfg->map_pool == NULL) { cfg->map_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), "map"); memcpy (cfg->map_pool->tag.uid, cfg->cfg_pool->tag.uid, sizeof (cfg->map_pool->tag.uid)); } new_map = rspamd_mempool_alloc0 (cfg->map_pool, sizeof (struct rspamd_map)); /* First of all detect protocol line */ if (rspamd_map_check_proto (cfg, map_line, new_map) == NULL) { return NULL; } new_map->read_callback = read_callback; new_map->fin_callback = fin_callback; new_map->user_data = user_data; new_map->cfg = cfg; new_map->id = g_random_int (); new_map->locked = rspamd_mempool_alloc0_shared (cfg->cfg_pool, sizeof (gint)); def = new_map->uri; if (description != NULL) { new_map->description = rspamd_mempool_strdup (cfg->cfg_pool, description); } /* Now check for each proto separately */ if (new_map->protocol == MAP_PROTO_FILE) { fdata = rspamd_mempool_alloc0 (cfg->map_pool, sizeof (struct file_map_data)); if (access (def, R_OK) == -1) { if (errno != ENOENT) { msg_err_config ("cannot open file '%s': %s", def, strerror (errno)); return NULL; } msg_info_config ( "map '%s' is not found, but it can be loaded automatically later", def); /* We still can add this file */ fdata->st.st_mtime = -1; } else { stat (def, &fdata->st); } fdata->filename = rspamd_mempool_strdup (cfg->map_pool, def); new_map->map_data = fdata; } else if (new_map->protocol == MAP_PROTO_HTTP) { hdata = rspamd_mempool_alloc0 (cfg->map_pool, sizeof (struct http_map_data)); memset (&up, 0, sizeof (up)); if (http_parser_parse_url (new_map->uri, strlen (new_map->uri), FALSE, &up) != 0) { msg_err_config ("cannot parse HTTP url: %s", new_map->uri); return NULL; } else { if (!(up.field_set & 1 << UF_HOST)) { msg_err_config ("cannot parse HTTP url: %s: no host", new_map->uri); return NULL; } tok.begin = new_map->uri + up.field_data[UF_HOST].off; tok.len = up.field_data[UF_HOST].len; hdata->host = rspamd_mempool_ftokdup (cfg->map_pool, &tok); if (up.field_set & 1 << UF_PORT) { hdata->port = up.port; } else { hdata->port = 80; } if (up.field_set & 1 << UF_PATH) { tok.begin = new_map->uri + up.field_data[UF_PATH].off; tok.len = strlen (tok.begin); hdata->path = rspamd_mempool_ftokdup (cfg->map_pool, &tok); } } new_map->map_data = hdata; } /* Temp pool */ rspamd_cryptobox_hash (cksum, new_map->uri, strlen (new_map->uri), NULL, 0); cksum_encoded = rspamd_encode_base32 (cksum, sizeof (cksum)); new_map->pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), "map"); rspamd_strlcpy (new_map->pool->tag.uid, cksum_encoded, sizeof (new_map->pool->tag.uid)); g_free (cksum_encoded); pool = new_map->pool; msg_info_pool ("added map %s", new_map->uri); cfg->maps = g_list_prepend (cfg->maps, new_map); return new_map; }
static const gchar * rspamd_map_check_proto (struct rspamd_config *cfg, const gchar *map_line, struct rspamd_map *map) { const gchar *pos = map_line, *end, *end_key; g_assert (map != NULL); g_assert (pos != NULL); end = pos + strlen (pos); if (g_ascii_strncasecmp (pos, "sign+", sizeof ("sign+") - 1) == 0) { map->is_signed = TRUE; pos += sizeof ("sign+") - 1; } if (g_ascii_strncasecmp (pos, "key=", sizeof ("key=") - 1) == 0) { pos += sizeof ("key=") - 1; end_key = memchr (pos, '+', end - pos); if (end_key != NULL) { map->trusted_pubkey = rspamd_pubkey_from_base32 (pos, end_key - pos, RSPAMD_KEYPAIR_SIGN, RSPAMD_CRYPTOBOX_MODE_25519); if (map->trusted_pubkey == NULL) { msg_err_config ("cannot read pubkey from map: %s", map_line); return NULL; } pos = end_key + 1; } else if (end - pos > 64) { /* Try hex encoding */ map->trusted_pubkey = rspamd_pubkey_from_hex (pos, 64, RSPAMD_KEYPAIR_SIGN, RSPAMD_CRYPTOBOX_MODE_25519); if (map->trusted_pubkey == NULL) { msg_err_config ("cannot read pubkey from map: %s", map_line); return NULL; } pos += 64; } else { msg_err_config ("cannot read pubkey from map: %s", map_line); return NULL; } if (*pos == '+' || *pos == ':') { pos ++; } } map->protocol = MAP_PROTO_FILE; if (g_ascii_strncasecmp (pos, "http://", sizeof ("http://") - 1) == 0) { map->protocol = MAP_PROTO_HTTP; /* Include http:// */ map->uri = rspamd_mempool_strdup (cfg->cfg_pool, pos); pos += sizeof ("http://") - 1; } else if (g_ascii_strncasecmp (pos, "file://", sizeof ("file://") - 1) == 0) { pos += sizeof ("file://") - 1; /* Exclude file:// */ map->uri = rspamd_mempool_strdup (cfg->cfg_pool, pos); } else if (*pos == '/') { /* Trivial file case */ map->uri = rspamd_mempool_strdup (cfg->cfg_pool, pos); } else { msg_err_config ("invalid map fetching protocol: %s", map_line); return NULL; } return pos; }
/* * Non-static for lua unit testing */ gsize rspamd_redis_expand_object (const gchar *pattern, struct redis_stat_ctx *ctx, struct rspamd_task *task, gchar **target) { gsize tlen = 0; const gchar *p = pattern, *elt; gchar *d, *end; enum { just_char, percent_char, mod_char } state = just_char; struct rspamd_statfile_config *stcf; lua_State *L = NULL; struct rspamd_task **ptask; GString *tb; const gchar *rcpt = NULL; gint err_idx; g_assert (ctx != NULL); stcf = ctx->stcf; if (task) { L = task->cfg->lua_state; } if (ctx->enable_users) { if (ctx->cbref_user == -1) { rcpt = rspamd_task_get_principal_recipient (task); } else if (L) { /* Execute lua function to get userdata */ lua_pushcfunction (L, &rspamd_lua_traceback); err_idx = lua_gettop (L); lua_rawgeti (L, LUA_REGISTRYINDEX, ctx->cbref_user); ptask = lua_newuserdata (L, sizeof (struct rspamd_task *)); *ptask = task; rspamd_lua_setclass (L, "rspamd{task}", -1); if (lua_pcall (L, 1, 1, err_idx) != 0) { tb = lua_touserdata (L, -1); msg_err_task ("call to user extraction script failed: %v", tb); g_string_free (tb, TRUE); } else { rcpt = rspamd_mempool_strdup (task->task_pool, lua_tostring (L, -1)); } /* Result + error function */ lua_pop (L, 2); } if (rcpt) { rspamd_mempool_set_variable (task->task_pool, "stat_user", (gpointer)rcpt, NULL); } } /* Length calculation */ while (*p) { switch (state) { case just_char: if (*p == '%') { state = percent_char; } else { tlen ++; } p ++; break; case percent_char: switch (*p) { case '%': tlen ++; state = just_char; break; case 'u': elt = GET_TASK_ELT (task, user); if (elt) { tlen += strlen (elt); } break; case 'r': if (rcpt == NULL) { elt = rspamd_task_get_principal_recipient (task); } else { elt = rcpt; } if (elt) { tlen += strlen (elt); } break; case 'l': if (stcf->label) { tlen += strlen (stcf->label); } break; case 's': if (stcf->symbol) { tlen += strlen (stcf->symbol); } break; default: state = just_char; tlen ++; break; } if (state == percent_char) { state = mod_char; } p ++; break; case mod_char: switch (*p) { case 'd': p ++; state = just_char; break; default: state = just_char; break; } break; } } if (target == NULL || task == NULL) { return tlen; } *target = rspamd_mempool_alloc (task->task_pool, tlen + 1); d = *target; end = d + tlen + 1; d[tlen] = '\0'; p = pattern; state = just_char; /* Expand string */ while (*p && d < end) { switch (state) { case just_char: if (*p == '%') { state = percent_char; } else { *d++ = *p; } p ++; break; case percent_char: switch (*p) { case '%': *d++ = *p; state = just_char; break; case 'u': elt = GET_TASK_ELT (task, user); if (elt) { d += rspamd_strlcpy (d, elt, end - d); } break; case 'r': if (rcpt == NULL) { elt = rspamd_task_get_principal_recipient (task); } else { elt = rcpt; } if (elt) { d += rspamd_strlcpy (d, elt, end - d); } break; case 'l': if (stcf->label) { d += rspamd_strlcpy (d, stcf->label, end - d); } break; case 's': if (stcf->symbol) { d += rspamd_strlcpy (d, stcf->symbol, end - d); } break; default: state = just_char; *d++ = *p; break; } if (state == percent_char) { state = mod_char; } p ++; break; case mod_char: switch (*p) { case 'd': /* TODO: not supported yet */ p ++; state = just_char; break; default: state = just_char; break; } break; } } return tlen; }
static void dkim_module_check (struct dkim_check_result *res) { gboolean all_done = TRUE, got_allow = FALSE; const gchar *strict_value; struct dkim_check_result *first, *cur, *sel = NULL; first = res->first; DL_FOREACH (first, cur) { if (cur->ctx == NULL) { continue; } if (cur->key != NULL && cur->res == -1) { cur->res = rspamd_dkim_check (cur->ctx, cur->key, cur->task); if (dkim_module_ctx->dkim_domains != NULL) { /* Perform strict check */ if ((strict_value = g_hash_table_lookup (dkim_module_ctx->dkim_domains, rspamd_dkim_get_domain (cur->ctx))) != NULL) { if (!dkim_module_parse_strict (strict_value, &cur->mult_allow, &cur->mult_deny)) { cur->mult_allow = dkim_module_ctx->strict_multiplier; cur->mult_deny = dkim_module_ctx->strict_multiplier; } } } } if (cur->res == -1 || cur->key == NULL) { /* Still need a key */ all_done = FALSE; } } if (all_done) { DL_FOREACH (first, cur) { if (cur->ctx == NULL) { continue; } if (cur->res == DKIM_CONTINUE) { rspamd_task_insert_result (cur->task, dkim_module_ctx->symbol_allow, cur->mult_allow * 1.0, g_list_prepend (NULL, rspamd_mempool_strdup (cur->task->task_pool, rspamd_dkim_get_domain (cur->ctx)))); got_allow = TRUE; sel = NULL; } else if (!got_allow) { if (sel == NULL) { sel = cur; } else if (sel->res == DKIM_TRYAGAIN && cur->res != DKIM_TRYAGAIN) { sel = cur; } } } } if (sel != NULL) { if (sel->res == DKIM_REJECT) { rspamd_task_insert_result (sel->task, dkim_module_ctx->symbol_reject, sel->mult_deny * 1.0, g_list_prepend (NULL, rspamd_mempool_strdup (sel->task->task_pool, rspamd_dkim_get_domain (sel->ctx)))); } else { rspamd_task_insert_result (sel->task, dkim_module_ctx->symbol_tempfail, 1.0, g_list_prepend (NULL, rspamd_mempool_strdup (sel->task->task_pool, rspamd_dkim_get_domain (sel->ctx)))); } } if (all_done) { rspamd_session_watcher_pop (res->task->s, res->w); } }
/* Do post load initialization based on lua */ void rspamd_lua_post_load_config (struct rspamd_config *cfg) { lua_State *L = cfg->lua_state; const gchar *name, *val; gchar *sym; struct rspamd_expression *expr, *old_expr; ucl_object_t *obj; gsize keylen; GError *err = NULL; /* First check all module options that may be overriden in 'config' global */ lua_getglobal (L, "config"); if (lua_istable (L, -1)) { /* Iterate */ for (lua_pushnil (L); lua_next (L, -2); lua_pop (L, 1)) { /* 'key' is at index -2 and 'value' is at index -1 */ /* Key must be a string and value must be a table */ name = luaL_checklstring (L, -2, &keylen); if (name != NULL && lua_istable (L, -1)) { obj = ucl_object_lua_import (L, lua_gettop (L)); if (obj != NULL) { ucl_object_insert_key_merged (cfg->rcl_obj, obj, name, keylen, true); } } } } /* Check metrics settings */ lua_getglobal (L, "metrics"); if (lua_istable (L, -1)) { /* Iterate */ for (lua_pushnil (L); lua_next (L, -2); lua_pop (L, 1)) { /* 'key' is at index -2 and 'value' is at index -1 */ /* Key must be a string and value must be a table */ name = luaL_checkstring (L, -2); if (name != NULL && lua_istable (L, -1)) { lua_process_metric (L, name, cfg); } } } /* Check composites */ lua_getglobal (L, "composites"); if (lua_istable (L, -1)) { /* Iterate */ for (lua_pushnil (L); lua_next (L, -2); lua_pop (L, 1)) { /* 'key' is at index -2 and 'value' is at index -1 */ /* Key must be a string and value must be a table */ name = luaL_checkstring (L, -2); if (name != NULL && lua_isstring (L, -1)) { val = lua_tostring (L, -1); sym = rspamd_mempool_strdup (cfg->cfg_pool, name); if (!rspamd_parse_expression (val, 0, &composite_expr_subr, NULL, cfg->cfg_pool, &err, &expr)) { msg_err_config("cannot parse composite expression '%s': %s", val, err->message); g_error_free (err); err = NULL; continue; } /* Now check hash table for this composite */ if ((old_expr = g_hash_table_lookup (cfg->composite_symbols, name)) != NULL) { msg_info_config("replacing composite symbol %s", name); g_hash_table_replace (cfg->composite_symbols, sym, expr); } else { g_hash_table_insert (cfg->composite_symbols, sym, expr); rspamd_symbols_cache_add_symbol (cfg->cache, sym, 0, NULL, NULL, SYMBOL_TYPE_COMPOSITE, -1); } } } } }