]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Fix] Fix reputation whitelist schema and selector-aware checking
authorVsevolod Stakhov <vsevolod@rspamd.com>
Fri, 5 Dec 2025 11:13:47 +0000 (11:13 +0000)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Fri, 5 Dec 2025 11:14:16 +0000 (11:14 +0000)
- lua_maps_expressions.schema: Change rules from array to key-value
  table to match actual UCL config format (fixes #5780)
- reputation.lua: Make simple whitelist maps selector-aware instead
  of always assuming IP-based whitelists

Closes #5780

lualib/lua_maps_expressions.lua
src/plugins/lua/reputation.lua

index 4b2935a25cf2aebbc7a835390954835785d8dca8..8f5aebf0e0d12d06c5e7a3ba3603929cf0fb2548 100644 (file)
@@ -88,14 +88,17 @@ end
 
 -- Schema for maps expressions configuration
 -- Note: map field references lua_maps.map_schema which will be migrated separately
+-- Rules is a key-value table where each key is the rule name and value contains selector+map
 exports.schema = T.table({
   expression = T.string(),
-  rules = T.array(
-    T.table({
+  rules = T.table({}, {
+    extra = T.table({
       selector = T.string(),
-      map = lua_maps.map_schema, -- References schema from lua_maps (to be migrated)
-    })
-  )
+      map = lua_maps.map_schema,
+      type = T.string():optional(), -- Optional map type override
+      description = T.string():optional(), -- Optional description
+    }),
+  }),
 })
 
 --[[[
index fde9a4ad9d31fab27b2b7e0c95a187edf78798ee..fc28ddd6b265ebb66d54b58d5d7a14b6cab73a11 100644 (file)
@@ -1320,8 +1320,14 @@ local function parse_rule(name, tbl)
       rule.config.whitelist_map = lua_maps_exprs.create(rspamd_config,
           rule.config.whitelist, N)
     elseif lua_maps.map_schema:check(rule.config.whitelist) then
+      -- Determine map type and check method based on selector type
+      local map_type = 'set' -- Default for string-based selectors
+      if sel_type == 'ip' or sel_type == 'sender' then
+        map_type = 'radix' -- Use radix for IP-based selectors
+      end
+
       local map = lua_maps.map_add_from_ucl(rule.config.whitelist,
-          'radix',
+          map_type,
           sel_type .. ' reputation whitelist')
 
       if not map then
@@ -1331,17 +1337,82 @@ local function parse_rule(name, tbl)
         return
       end
 
-      rule.config.whitelist_map = {
-        process = function(_, task)
-          -- Hack: we assume that it is an ip whitelist :(
-          local ip = task:get_from_ip()
-
-          if ip and map:get_key(ip) then
-            return true
+      -- Create selector-aware whitelist check
+      if sel_type == 'ip' or sel_type == 'sender' then
+        rule.config.whitelist_map = {
+          process = function(_, task)
+            local ip = task:get_from_ip()
+            if ip and map:get_key(ip) then
+              return true
+            end
+            return false
           end
-          return false
-        end
-      }
+        }
+      elseif sel_type == 'spf' then
+        rule.config.whitelist_map = {
+          process = function(_, task)
+            local dominated = task:get_mempool():get_variable('spf_domain')
+            if dominated and map:get_key(dominated) then
+              return true
+            end
+            -- Also check from domain
+            local from = task:get_from('smtp')
+            if from and from[1] and from[1].domain then
+              if map:get_key(from[1].domain) then
+                return true
+              end
+            end
+            return false
+          end
+        }
+      elseif sel_type == 'dkim' then
+        rule.config.whitelist_map = {
+          process = function(_, task)
+            local dominated = {}
+            local dominated_trace = (task:get_symbol('DKIM_TRACE') or {})[1]
+            if dominated_trace and dominated_trace.options then
+              for _, opt in ipairs(dominated_trace.options) do
+                local dom = opt:match('^([^:]+):')
+                if dom then
+                  dominated[rspamd_util.get_tld(dom)] = true
+                end
+              end
+            end
+            for dom, _ in pairs(dominated) do
+              if map:get_key(dom) then
+                return true
+              end
+            end
+            return false
+          end
+        }
+      elseif sel_type == 'url' then
+        rule.config.whitelist_map = {
+          process = function(_, task)
+            local dominated = {}
+            for _, u in ipairs(task:get_urls(true) or {}) do
+              dominated[u:get_tld()] = true
+            end
+            for dom, _ in pairs(dominated) do
+              if map:get_key(dom) then
+                return true
+              end
+            end
+            return false
+          end
+        }
+      else
+        -- Generic selector - use IP as fallback
+        rule.config.whitelist_map = {
+          process = function(_, task)
+            local ip = task:get_from_ip()
+            if ip and map:get_key(ip) then
+              return true
+            end
+            return false
+          end
+        }
+      end
     else
       rspamd_logger.errx(rspamd_config, "cannot parse whitelist map config for %s: (%s)",
           sel_type,