]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Feature] external_services: per-service _CHECK anchor
authorVsevolod Stakhov <vsevolod@rspamd.com>
Thu, 4 Jun 2026 15:11:32 +0000 (16:11 +0100)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Thu, 4 Jun 2026 15:11:32 +0000 (16:11 +0100)
Schedule every external service under a stable <RULE>_CHECK callback
symbol so it is a predictable dependency target, regardless of how the
scan result symbols are named. This generalises the pattern vadesecure
and cloudmark already follow (VADE_CHECK, CLOUDMARK_CHECK).

A scanner whose main symbol is already a *_CHECK keeps it as the
callback. Otherwise the callback is named <KEY>_CHECK (from the config
block key -- unique per rule, so instances never collide) and the
scanner's result symbol (e.g. DCC_REJECT, RAZOR) becomes a virtual
child of it: its score and emitted results are unchanged, and an
existing dependency on the old name still resolves (virtual -> parent).

This lets register_dependency('<SERVICE>_CHECK', X) order any external
check after another symbol uniformly, e.g. after a sender-bypass rule.

src/plugins/lua/external_services.lua

index 7382ac71eedbb00c4b975978fec1ef2961055595..cadfcf2a3f250bb43562097dfc7a76cc7ac68b20 100644 (file)
@@ -222,8 +222,21 @@ if opts and type(opts) == 'table' then
       else
         m = nrule
 
+        -- Every external service is scheduled under a stable <RULE>_CHECK
+        -- callback symbol, so it is a predictable dependency target regardless
+        -- of how the scan result symbols are named. Scanners whose main symbol
+        -- is already a *_CHECK (e.g. VADE_CHECK, CLOUDMARK_CHECK) use it as is;
+        -- otherwise the anchor is derived from the rule key (unique per rule, so
+        -- no collisions between instances) and the result symbol (e.g.
+        -- DCC_REJECT) becomes a virtual child, so its score and results stay
+        -- unchanged.
+        local check_symbol = m.symbol
+        if not check_symbol:match('_CHECK$') then
+          check_symbol = k:upper() .. '_CHECK'
+        end
+
         local t = {
-          name = m.symbol,
+          name = check_symbol,
           callback = cb,
           score = 0.0,
           group = N
@@ -246,6 +259,18 @@ if opts and type(opts) == 'table' then
 
         local id = rspamd_config:register_symbol(t)
 
+        -- The scanner's own result symbol, when distinct from the _CHECK anchor,
+        -- is registered as a virtual child so it still carries the verdict.
+        if m.symbol ~= check_symbol then
+          rspamd_config:register_symbol({
+            type = 'virtual',
+            name = m.symbol,
+            parent = id,
+            score = 0.0,
+            group = N
+          })
+        end
+
         if m.symbol_fail then
           rspamd_config:register_symbol({
             type = 'virtual',