]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Feature] Add force-enable override for settings conflicts
authorVsevolod Stakhov <vsevolod@rspamd.com>
Mon, 30 Mar 2026 15:30:59 +0000 (16:30 +0100)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Mon, 30 Mar 2026 15:30:59 +0000 (16:30 +0100)
When merged settings explicitly enable a symbol that settings_elt
has in forbidden_ids, the merged result now wins. Uses a per-task
GHashTable (mempool variable "force_enabled_ids") populated during
process_settings() and checked in is_allowed() before consulting
the precomputed bitsets. This implements the "enable > disable,
explicit > implicit" conflict resolution rule.

src/libserver/symcache/symcache_item.cxx
src/libserver/symcache/symcache_runtime.cxx

index 77750bbb98914099d78a488d95454aff9b2fb91e..a96fb41164ede0b9f26e75fce3cf3d952ed76ccc 100644 (file)
@@ -362,13 +362,23 @@ auto cache_item::is_allowed(struct rspamd_task *task, bool exec_only) const -> b
        /* Settings checks */
        if (task->settings_elt != nullptr) {
                if (forbidden_ids.check_id(task->settings_elt->id)) {
-                       msg_debug_cache_task("deny %s of %s as it is forbidden for "
-                                                                "settings id %ud",
-                                                                what,
-                                                                symbol.c_str(),
-                                                                task->settings_elt->id);
+                       /* Check if force-enabled by merged settings */
+                       auto *force_ht = (GHashTable *) rspamd_mempool_get_variable(
+                               task->task_pool, "force_enabled_ids");
+                       if (force_ht && g_hash_table_contains(force_ht, GINT_TO_POINTER(id))) {
+                               msg_debug_cache_task("allow %s of %s: force-enabled by merged "
+                                                                        "settings overriding settings_elt forbidden_ids",
+                                                                        what, symbol.c_str());
+                       }
+                       else {
+                               msg_debug_cache_task("deny %s of %s as it is forbidden for "
+                                                                        "settings id %ud",
+                                                                        what,
+                                                                        symbol.c_str(),
+                                                                        task->settings_elt->id);
 
-                       return false;
+                               return false;
+                       }
                }
 
                if (!(flags & SYMBOL_TYPE_EXPLICIT_DISABLE)) {
index c88514cb80d6bb30ccacb2c3a2740db733df1d40..e4d8e2d0e1103b0ad823d60bce380c8ae027faff 100644 (file)
@@ -118,6 +118,31 @@ auto symcache_runtime::process_settings(struct rspamd_task *task, const symcache
 
        const auto *enabled = ucl_object_lookup(task->settings, "symbols_enabled");
 
+       /*
+        * Track force-enabled symbols: if settings_elt has a symbol in forbidden_ids
+        * but the merged settings explicitly enable it, we need to override the bitset
+        */
+       auto force_enable = [&](const char *sym) {
+               enable_symbol(task, cache, sym);
+
+               if (task->settings_elt) {
+                       const auto *item = cache.get_item_by_name(sym, true);
+                       if (item && item->forbidden_ids.check_id(task->settings_elt->id)) {
+                               auto *force_ht = (GHashTable *) rspamd_mempool_get_variable(
+                                       task->task_pool, "force_enabled_ids");
+                               if (!force_ht) {
+                                       force_ht = g_hash_table_new(g_direct_hash, g_direct_equal);
+                                       rspamd_mempool_set_variable(task->task_pool, "force_enabled_ids",
+                                                                                               force_ht,
+                                                                                               (rspamd_mempool_destruct_t) g_hash_table_unref);
+                               }
+                               g_hash_table_insert(force_ht, GINT_TO_POINTER(item->id), GINT_TO_POINTER(1));
+                               msg_debug_cache_task("force-enable %s (id=%d) overriding settings_elt forbidden_ids",
+                                                                        sym, item->id);
+                       }
+               }
+       };
+
        if (enabled) {
                msg_debug_cache_task("disable all symbols as `symbols_enabled` is found");
                /* Disable all symbols but selected */
@@ -126,7 +151,7 @@ auto symcache_runtime::process_settings(struct rspamd_task *task, const symcache
                it = nullptr;
 
                while ((cur = ucl_iterate_object(enabled, &it, true)) != nullptr) {
-                       enable_symbol(task, cache, ucl_object_tostring(cur));
+                       force_enable(ucl_object_tostring(cur));
                }
        }
 
@@ -136,7 +161,7 @@ auto symcache_runtime::process_settings(struct rspamd_task *task, const symcache
                disable_all_symbols(SYMBOL_TYPE_EXPLICIT_DISABLE);
        }
        process_group(enabled, [&](const char *sym) {
-               enable_symbol(task, cache, sym);
+               force_enable(sym);
        });
 
        const auto *disabled = ucl_object_lookup(task->settings, "symbols_disabled");