From: Vsevolod Stakhov Date: Fri, 5 Dec 2025 11:13:47 +0000 (+0000) Subject: [Fix] Fix reputation whitelist schema and selector-aware checking X-Git-Tag: 3.14.2~33 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fd3ef90f994d2c18061a94b0e53c1446765814a7;p=thirdparty%2Frspamd.git [Fix] Fix reputation whitelist schema and selector-aware checking - 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 --- diff --git a/lualib/lua_maps_expressions.lua b/lualib/lua_maps_expressions.lua index 4b2935a25c..8f5aebf0e0 100644 --- a/lualib/lua_maps_expressions.lua +++ b/lualib/lua_maps_expressions.lua @@ -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 + }), + }), }) --[[[ diff --git a/src/plugins/lua/reputation.lua b/src/plugins/lua/reputation.lua index fde9a4ad9d..fc28ddd6b2 100644 --- a/src/plugins/lua/reputation.lua +++ b/src/plugins/lua/reputation.lua @@ -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,