]> source.dussan.org Git - rspamd.git/commitdiff
[Rework] Implement settings expressions
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 25 Jun 2019 16:43:06 +0000 (17:43 +0100)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 25 Jun 2019 16:43:06 +0000 (17:43 +0100)
src/plugins/lua/settings.lua

index de9ac5c65d5025e2f3407257a2c5fa15e895b177..f269a4018a07f90e4e53622e456db62515057d7d 100644 (file)
@@ -31,6 +31,7 @@ local lua_selectors = require "lua_selectors"
 local lua_settings = require "lua_settings"
 local ucl = require "ucl"
 local fun = require "fun"
+local rspamd_mempool = require "rspamd_mempool"
 
 local redis_params
 
@@ -181,218 +182,98 @@ local function check_query_settings(task)
   return false
 end
 
--- Check limit for a task
-local function check_settings(task)
-  local function check_addr_setting(rule, addr)
-    local function check_specific_addr(elt)
-      if rule['name'] then
-        if rspamd_maps.rspamd_maybe_check_map(rule['name'], elt['addr']) then
-          return true
-        end
-      end
-      if rule['user'] then
-        if rspamd_maps.rspamd_maybe_check_map(rule['user'], elt['user']) then
-          return true
-        end
-      end
-      if rule['domain'] and elt['domain'] then
-        if rspamd_maps.rspamd_maybe_check_map(rule['domain'], elt['domain']) then
-          return true
-        end
-      end
-      if rule['regexp'] then
-        if rule['regexp']:match(elt['addr']) then
-          return true
-        end
+local function check_addr_setting(expected, addr)
+  local function check_specific_addr(elt)
+    if expected.name then
+      if rspamd_maps.rspamd_maybe_check_map(expected.name, elt.addr) then
+        return true
       end
-      return false
     end
-
-    for _, e in ipairs(addr) do
-      if check_specific_addr(e) then
+    if expected.user then
+      if rspamd_maps.rspamd_maybe_check_map(expected.user, elt.user) then
         return true
       end
     end
-
-    return false
-  end
-
-  local function check_ip_setting(rule, ip)
-    if not rule[2] then
-      if rspamd_maps.rspamd_maybe_check_map(rule[1], ip:to_string()) then
+    if expected.domain and elt.domain then
+      if rspamd_maps.rspamd_maybe_check_map(expected.domain, elt.domain) then
         return true
       end
-    else
-      if rule[2] ~= 0 then
-        local nip = ip:apply_mask(rule[2])
-        if nip and nip:to_string() == rule[1]:to_string() then
-          return true
-        end
-      elseif ip:to_string() == rule[1]:to_string() then
+    end
+    if expected.regexp then
+      if expected.regexp:match(elt.addr) then
         return true
       end
     end
-
     return false
   end
 
-  local function check_specific_setting(rule_name, rule, data, matched)
-    local res = false
-
-    local function ip_valid(ip)
-      return ip:is_valid()
-    end
-
-    local function not_empty(s)
-      return #s > 0
-    end
-
-    local function generic_check(value, to_check, check_func, what, valid_func)
-      if not to_check then return true end
-
-      if type(value) == 'function' then
-        value = value()
-      end
-
-      if value then
-        if valid_func then
-          if not valid_func(value) then
-            return false
-          end
-        end
-
-        if not check_func then
-          check_func = function(a, b) return a == b end
-        end
-
-        local ret = fun.any(function(d)
-          return check_func(d, value)
-        end, to_check)
-        if ret then
-          res = true
-          matched[#matched + 1] = what
-        else
-          return false
-        end
-      else
-        return false
-      end
-
+  for _, e in ipairs(addr) do
+    if check_specific_addr(e) then
       return true
     end
+  end
 
-    if not generic_check(data.ip, rule.ip,
-        check_ip_setting, 'ip', ip_valid) then
-      return nil
-    end
-
-    if not generic_check(data.client_ip, rule.client_ip,
-        check_ip_setting, 'client_ip', ip_valid) then
-      return nil
-    end
-
-    if not generic_check(data.from, rule.from,
-        check_addr_setting, 'from') then
-      return nil
-    end
-
-    if not generic_check(data.from_mime, rule.from_mime,
-        check_addr_setting, 'from_mime') then
-      return nil
-    end
+  return false
+end
 
-    if not generic_check(data.rcpt, rule.rcpt,
-        check_addr_setting, 'rcpt') then
-      return nil
+local function check_string_setting(expected, str)
+  if expected.regexp then
+    if expected.regexp:match(str) then
+      return true
     end
-
-    if not generic_check(data.rcpt_mime, rule.rcpt_mime,
-        check_addr_setting, 'rcpt_mime') then
-      return nil
+  elseif expected.check then
+    if rspamd_maps.rspamd_maybe_check_map(expected.check, str) then
+      return true
     end
+  end
+  return false
+end
 
-    if not generic_check(data.user, rule.user,
-        check_addr_setting, 'user') then
-      return nil
+local function check_ip_setting(expected, ip)
+  if not expected[2] then
+    if rspamd_maps.rspamd_maybe_check_map(expected[1], ip:to_string()) then
+      return true
     end
-
-    if not generic_check(data.hostname, rule.hostname,
-        check_addr_setting, 'hostname', not_empty) then
-      return nil
+  else
+    if expected[2] ~= 0 then
+      local nip = ip:apply_mask(expected[2])
+      if nip and nip:to_string() == expected[1]:to_string() then
+        return true
+      end
+    elseif ip:to_string() == expected[1]:to_string() then
+      return true
     end
+  end
 
-    -- Non generic checks
+  return false
+end
 
-    if rule.authenticated then
-      if data.user[1] then
-        res = true
-        matched[#matched + 1] = 'authenticated'
-      end
-      if not res then
-        return nil
-      end
-    end
+-- Check limit for a task
+local function check_settings(task)
+  local function check_specific_setting(rule, matched)
+    local res = false
 
-    if rule['local'] then
-      if not data.ip or not data.ip:is_valid() then
-        return nil
-      end
+    local function process_atom(atom)
+      local elt = rule.checks[atom]
 
-      if data.ip:is_local() then
-        matched[#matched + 1] = 'local'
-        res = true
-      else
-        return nil
-      end
-    end
+      if elt then
+        local input = elt.extract(task)
+        if not input then return false end
 
-    if rule.request_header then
-      for hname, pattern in pairs(rule.request_header) do
-        local hvalue = task:get_request_header(hname)
-        res = (hvalue and pattern:match(hvalue))
-        if res then
-          matched[#matched + 1] = 'req_header: ' .. hname
-          break
+        if elt.check(input) then
+          matched[#matched] = atom
+          return 1.0
         end
+      else
+        rspamd_logger.errx(task, 'error in settings: check %s is not defined!', atom)
       end
-      if not res then
-        return nil
-      end
-    end
 
-    if rule.header then
-      for _,elt in ipairs(rule.header) do
-        for hname,patterns in pairs(elt) do
-          for _,pattern in ipairs(patterns) do
-            local hvalue = task:get_header(hname)
-            res = (hvalue and pattern:match(hvalue))
-            if res then
-              matched[#matched + 1] = 'header: ' .. hname
-              break
-            end
-          end
-          if res then
-            break
-          end
-        end
-        if res then
-          break
-        end
-      end
-      if not res then
-        return nil
-      end
+      return 0
     end
 
-    if rule.selector then
-      res = fun.all(function(s) return s(task) end, rule.selector)
-
-      if res then
-        matched[#matched + 1] = 'selector'
-      end
-    end
+    res = rule.expression and rule.expression:process(process_atom)
 
-    if res then
+    if res and res > 0 then
       if rule['whitelist'] then
         rule['apply'] = {whitelist = true}
       end
@@ -413,31 +294,6 @@ local function check_settings(task)
     return
   end
 
-  lua_util.debugm(N, task, "check for settings")
-  local data = {
-    ip = task:get_from_ip(),
-    client_ip = task:get_client_ip(),
-    from = task:get_from(1),
-    from_mime = task:get_from(2),
-    rcpt = task:get_recipients(1),
-    rcpt_mime = task:get_recipients(2),
-    hostname = task:get_hostname() or '',
-    user = {}
-  }
-
-  local uname = task:get_user()
-  if uname then
-    data.user[1] = {}
-    local localpart, domainpart = string.gmatch(uname, "(.+)@(.+)")()
-    if localpart then
-      data.user[1]["user"] = localpart
-      data.user[1]["domain"] = domainpart
-      data.user[1]["addr"] = uname
-    else
-      data.user[1]["user"] = uname
-      data.user[1]["addr"] = uname
-    end
-  end
   -- Match rules according their order
   local applied = false
 
@@ -445,10 +301,12 @@ local function check_settings(task)
     if not applied and settings[pri] then
       for _,s in ipairs(settings[pri]) do
         local matched = {}
-        local result = check_specific_setting(s.name, s.rule, data, matched)
 
+        lua_util.debugm(N, task, "check for settings element %s; %s",
+            s.name, s.rule.expression)
+        local result = check_specific_setting(s.rule, matched)
         -- Can use xor here but more complicated for reading
-        if (result and not s.rule.inverse) or (not result and s.rule.inverse) then
+        if result then
           if s.rule['apply'] then
             if s.rule.id then
               -- Extract static settings
@@ -487,7 +345,7 @@ local function check_settings(task)
 end
 
 -- Process settings based on their priority
-local function process_settings_table(tbl, allow_ids)
+local function process_settings_table(tbl, allow_ids, mempool)
   local get_priority = function(elt)
     local pri_tonum = function(p)
       if p then
@@ -514,13 +372,13 @@ local function process_settings_table(tbl, allow_ids)
   local process_setting_elt = function(name, elt)
 
     lua_util.debugm(N, rspamd_config, 'process settings "%s"', name)
-    -- Process IP address
-    local function process_ip(ip)
+    -- Process IP address: converted to a table {ip, mask}
+    local function process_ip_condition(ip)
       local out = {}
 
       if type(ip) == "table" then
         for _,v in ipairs(ip) do
-          table.insert(out, process_ip(v))
+          table.insert(out, process_ip_condition(v))
         end
       elseif type(ip) == "string" then
         local slash = string.find(ip, '/')
@@ -555,11 +413,16 @@ local function process_settings_table(tbl, allow_ids)
       return out
     end
 
-    local function process_addr(addr)
+    -- Process email like condition, converted to a table with fields:
+    -- name - full email (surprise!)
+    -- user - user part
+    -- domain - domain part
+    -- regexp - full email regexp (yes, it sucks)
+    local function process_email_condition(addr)
       local out = {}
       if type(addr) == "table" then
         for _,v in ipairs(addr) do
-          table.insert(out, process_addr(v))
+          table.insert(out, process_email_condition(v))
         end
       elseif type(addr) == "string" then
         if string.sub(addr, 1, 4) == "map:" then
@@ -599,7 +462,43 @@ local function process_settings_table(tbl, allow_ids)
       return out
     end
 
-    local check_table = function(chk_elt, out)
+    -- Convert a plain string condition to a table:
+    -- check - string to match
+    -- regexp - regexp to match
+    local function process_string_condition(addr)
+      local out = {}
+      if type(addr) == "table" then
+        for _,v in ipairs(addr) do
+          table.insert(out, process_string_condition(v))
+        end
+      elseif type(addr) == "string" then
+        if string.sub(addr, 1, 4) == "map:" then
+          -- It is map, don't apply any extra logic
+          out['check'] = addr
+        else
+          local start = string.sub(addr, 1, 1)
+          if start == '/' then
+            -- It is a regexp
+            local re = rspamd_regexp.create(addr)
+            if re then
+              out['regexp'] = re
+            else
+              rspamd_logger.errx(rspamd_config, "bad regexp: " .. addr)
+              return nil
+            end
+
+          else
+            out['check'] = addr
+          end
+        end
+      else
+        return nil
+      end
+
+      return out
+    end
+
+    local convert_to_table = function(chk_elt, out)
       if type(chk_elt) == 'string' then
         return {out}
       end
@@ -607,135 +506,262 @@ local function process_settings_table(tbl, allow_ids)
       return out
     end
 
+    -- Used to create a checking closure: if value matches expected somehow, return true
+    local function gen_check_closure(expected, check_func)
+      return function(value)
+        if not value then return false end
+
+        if type(value) == 'function' then
+          value = value()
+        end
+
+        if value then
+
+          if not check_func then
+            check_func = function(a, b) return a == b end
+          end
+
+          local ret = fun.any(function(d)
+            return check_func(d, value)
+          end, expected)
+          if ret then
+            return true
+          end
+        end
+
+        return false
+      end
+    end
+
     local out = {}
 
+    local checks = {}
     if elt['ip'] then
-      local ip = process_ip(elt['ip'])
+      local ips_table = process_ip_condition(elt['ip'])
 
-      if ip then
+      if ips_table then
         lua_util.debugm(N, rspamd_config, 'added ip condition to "%s": %s',
-            name, ip)
-        out['ip'] = check_table(elt['ip'], ip)
+            name, ips_table)
+        checks.ip = {
+          check = gen_check_closure(convert_to_table(elt.ip, ips_table), check_ip_setting),
+          extract = function(task)
+            local ip = task:get_from_ip()
+            if ip:is_valid() then return ip end
+            return nil
+          end,
+        }
       end
     end
     if elt['client_ip'] then
-      local ip = process_ip(elt['client_ip'])
+      local client_ips_table = process_ip_condition(elt['client_ip'])
 
-      if ip then
+      if client_ips_table then
         lua_util.debugm(N, rspamd_config, 'added client_ip condition to "%s": %s',
-            name, ip)
-        out['client_ip'] = check_table(elt['client_ip'], ip)
+            name, client_ips_table)
+        checks.client_ip = {
+          check = gen_check_closure(convert_to_table(elt.client_ip, client_ips_table),
+              check_ip_setting),
+          extract = function(task)
+            local ip = task:get_client_ip()
+            if ip:is_valid() then return ip end
+            return nil
+          end,
+        }
       end
     end
     if elt['from'] then
-      local from = process_addr(elt['from'])
+      local from_condition = process_email_condition(elt['from'])
 
-      if from then
+      if from_condition then
         lua_util.debugm(N, rspamd_config, 'added from condition to "%s": %s',
-            name, from)
-        out['from'] = check_table(elt['from'], from)
+            name, from_condition)
+        checks.from = {
+          check = gen_check_closure(convert_to_table(elt.from, from_condition),
+              check_addr_setting),
+          extract = function(task)
+            return task:get_from(1)
+          end,
+        }
       end
     end
     if elt['rcpt'] then
-      local rcpt = process_addr(elt['rcpt'])
-      if rcpt then
+      local rcpt_condition = process_email_condition(elt['rcpt'])
+      if rcpt_condition then
         lua_util.debugm(N, rspamd_config, 'added rcpt condition to "%s": %s',
-            name, rcpt)
-        out['rcpt'] = check_table(elt['rcpt'], rcpt)
+            name, rcpt_condition)
+        checks.rcpt = {
+          check = gen_check_closure(convert_to_table(elt.rcpt, rcpt_condition),
+              check_addr_setting),
+          extract = function(task)
+            return task:get_recipients(1)
+          end,
+        }
       end
     end
     if elt['from_mime'] then
-      local from_mime = process_addr(elt['from_mime'])
+      local from_mime_condition = process_email_condition(elt['from_mime'])
 
-      if from_mime then
+      if from_mime_condition then
         lua_util.debugm(N, rspamd_config, 'added from_mime condition to "%s": %s',
-            name, from_mime)
-        out['from_mime'] = check_table(elt['from_mime'], from_mime)
+            name, from_mime_condition)
+        checks.from_mime = {
+          check = gen_check_closure(convert_to_table(elt.from_mime, from_mime_condition),
+              check_addr_setting),
+          extract = function(task)
+            return task:get_from(2)
+          end,
+        }
       end
     end
     if elt['rcpt_mime'] then
-      local rcpt_mime = process_addr(elt['rcpt_mime'])
-      if rcpt_mime then
-        lua_util.debugm(N, rspamd_config, 'added rcpt_mime condition to "%s": %s',
-            name, rcpt_mime)
-        out['rcpt_mime'] = check_table(elt['rcpt_mime'], rcpt_mime)
+      local rcpt_mime_condition = process_email_condition(elt['rcpt'])
+      if rcpt_mime_condition then
+        lua_util.debugm(N, rspamd_config, 'added rcpt mime condition to "%s": %s',
+            name, rcpt_mime_condition)
+        checks.rcpt_mime = {
+          check = gen_check_closure(convert_to_table(elt.rcpt_mime, rcpt_mime_condition),
+              check_addr_setting),
+          extract = function(task)
+            return task:get_recipients(2)
+          end,
+        }
       end
     end
     if elt['user'] then
-      local user = process_addr(elt['user'])
-      if user then
+      local user_condition = process_email_condition(elt['user'])
+      if user_condition then
         lua_util.debugm(N, rspamd_config, 'added user condition to "%s": %s',
-            name, user)
-        out['user'] = check_table(elt['user'], user)
+            name, user_condition)
+        checks.user = {
+          check = gen_check_closure(convert_to_table(elt.user, user_condition),
+              check_addr_setting),
+          extract = function(task)
+            local uname = task:get_user()
+            local user = {}
+            if uname then
+              user[1] = {}
+              local localpart, domainpart = string.gmatch(uname, "(.+)@(.+)")()
+              if localpart then
+                user[1]["user"] = localpart
+                user[1]["domain"] = domainpart
+                user[1]["addr"] = uname
+              else
+                user[1]["user"] = uname
+                user[1]["addr"] = uname
+              end
+
+              return user
+            end
+
+            return nil
+          end,
+        }
       end
     end
     if elt['hostname'] then
-      local hostname = process_addr(elt['hostname'])
-      if hostname then
+      local hostname_condition = process_string_condition(elt['hostname'])
+      if hostname_condition then
         lua_util.debugm(N, rspamd_config, 'added hostname condition to "%s": %s',
-            name, hostname)
-        out['hostname'] = check_table(elt['hostname'], hostname)
+            name, hostname_condition)
+        checks.hostname = {
+          check = gen_check_closure(convert_to_table(elt.hostname, hostname_condition),
+              check_string_setting),
+          extract = function(task)
+            return task:get_hostname() or ''
+          end,
+        }
       end
     end
     if elt['authenticated'] then
       lua_util.debugm(N, rspamd_config, 'added authenticated condition to "%s"',
           name)
-      out['authenticated'] = true
+      checks.authenticated = {
+        check = function(value) if value then return true end return false end,
+        extract = function(task)
+          return task:get_user()
+        end
+      }
     end
     if elt['local'] then
-      out['local'] = true
       lua_util.debugm(N, rspamd_config, 'added local condition to "%s"',
           name)
-    end
-    if elt['inverse'] then
-      lua_util.debugm(N, rspamd_config, 'added inverse condition to "%s"',
-          name)
-      out['inverse'] = true
-    end
-    if elt['request_header'] then
-      local rho = {}
-      for k, v in pairs(elt['request_header']) do
-        local re = rspamd_regexp.create(v)
-        if re then
-          rho[k] = re
+      checks['local'] = {
+        check = function(value) if value then return true end return false end,
+        extract = function(task)
+          local ip = task:get_from_ip()
+          if not ip or not ip:is_valid() then
+            return nil
+          end
+
+          if ip:is_local() then
+            return true
+          else
+            return nil
+          end
         end
-      end
-      lua_util.debugm(N, rspamd_config, 'added request_header condition to "%s": %s',
-          name, rho)
-      out['request_header'] = rho
+      }
     end
-    if elt['header'] then
-      if not elt['header'][1] and next(elt['header']) then
-        elt['header'] = {elt['header']}
-      end
-      for _, e in ipairs(elt['header']) do
-        local rho = {}
-        for k, v in pairs(e) do
-          if type(v) ~= 'table' then
-            v = {v}
-          end
-          for _, r in ipairs(v) do
-            local re = rspamd_regexp.get_cached(r)
-            if not re then
-              re = rspamd_regexp.create_cached(r)
-            end
+
+    -- Headers are tricky:
+    -- We create an closure with extraction function depending on header name
+    -- We also inserts it into `checks` table as an atom in form header:<hname>
+    -- Check function depends on the input:
+    -- * for something that looks like `header = "/bar/"` we create a regexp
+    -- * for something that looks like `header = true` we just check the existence
+    local function process_header_elt(table_element, extractor_func)
+      if elt[table_element] then
+        for k, v in pairs(elt[table_element]) do
+          if type(v) == 'string' then
+            local re = rspamd_regexp.create(v)
             if re then
-              if not out['header'] then out['header'] = {} end
-              if rho[k] then
-                table.insert(rho[k], re)
-              else
-                rho[k] = {re}
-              end
+              checks[table_element .. ':'..k] = {
+                check = function(values)
+                  return fun.any(function(c) return re:match(c) end, values)
+                end,
+                extract = extractor_func(k),
+              }
+
+              lua_util.debugm(N, rspamd_config, 'added %s condition to "%s": %s =~ %s',
+                  table_element, name, k, v)
             end
+          elseif type(v) == 'boolean' then
+            checks[table_element .. ':'..k] = {
+              check = function(values)
+                return fun.any(function(c)
+                  if c and v then return true end
+                  if not c or not v then return true end
+                  return false
+                end, values)
+              end,
+              extract = extractor_func(k),
+            }
+
+            lua_util.debugm(N, rspamd_config, 'added %s condition to "%s": %s =~ %s',
+                table_element, name, k, v)
+          else
+            rspamd_logger.errx(rspamd_config, 'invalid %s %s = %s', table_element, k, v)
           end
         end
-        if not out['header'] then out['header'] = {} end
-        table.insert(out['header'], rho)
       end
-      lua_util.debugm(N, rspamd_config, 'added header condition to "%s": %s',
-          name, out.header)
     end
 
+    process_header_elt('request_header', function(hname)
+      return function(task)
+        local rh = task:get_request_header(hname)
+        if rh then return {rh} end
+        return {}
+      end
+    end)
+    process_header_elt('header', function(hname)
+      return function(task)
+        local rh = task:get_header_full(hname)
+        if rh then
+          return fun.map(function(h) return h.decoded end, rh)
+        end
+        return {}
+      end
+    end)
+
     if elt['selector'] then
       local sel = selectors_cache[name]
       if not sel then
@@ -748,17 +774,90 @@ local function process_settings_table(tbl, allow_ids)
       end
 
       if sel then
-        if out.selector then
-          table.insert(out['selector'], sel)
-        else
-          out['selector'] = {sel}
+        checks['selector:' .. name] = {
+          check = function(values)
+            return fun.any(function(c)
+              return c
+            end, values)
+          end,
+          extract = sel,
+        }
+        lua_util.debugm(N, rspamd_config, 'added selector condition to "%s": %s',
+            name, sel)
+      end
+
+    end
+
+    -- Special, special case!
+    local inverse = false
+    if elt['inverse'] then
+      lua_util.debugm(N, rspamd_config, 'added inverse condition to "%s"',
+          name)
+      inverse = true
+    end
+
+    -- Killmeplease
+    local nchecks = 0
+    for _,_ in pairs(checks) do nchecks = nchecks + 1 end
+
+    if nchecks > 0 then
+      -- Now we can deal with the expression!
+      if not elt.expression then
+        -- Artificial & expression to deal with the legacy parts
+        -- Here we get all keys and concatenate them with '&&'
+        local s = ' && '
+        -- By De Morgan laws
+        if inverse then s = ' || ' end
+        local expr_str = table.concat(fun.totable(fun.map(function(k, _)
+          return k end, checks)), s)
+
+        if inverse then
+          expr_str = string.format('!(%s)', expr_str)
+        end
+
+        elt.expression = expr_str
+        lua_util.debugm(N, rspamd_config, 'added implicit settings expression for %s: %s',
+            name, expr_str)
+      end
+
+      -- Parse expression's sanity
+      local function parse_atom(str)
+        local atom = table.concat(fun.totable(fun.take_while(function(c)
+          if string.find(', \t()><+!|&\n', c) then
+            return false
+          end
+          return true
+        end, fun.iter(str))), '')
+
+        if checks[atom] then
+          return atom
         end
+
+        rspamd_logger.errx(rspamd_config,
+            'use of undefined element "%s" when parsing settings expression, known checks: %s',
+            atom, table.concat(fun.totable(fun.map(function(k, _) return k end, checks)), ','))
+
+        return nil
+      end
+
+      local rspamd_expression = require "rspamd_expression"
+      out.expression = rspamd_expression.create(elt.expression, parse_atom,
+          mempool)
+      out.checks = checks
+
+      if not out.expression then
+        rspamd_logger.errx(rspamd_config, 'cannot parse expression %s for %s',
+            elt.expression, name)
+      else
+        lua_util.debugm(N, rspamd_config, 'registered settings %s with %s checks',
+            name, nchecks)
       end
-      lua_util.debugm(N, rspamd_config, 'added selector condition to "%s": %s',
-          name, sel)
+    else
+      lua_util.debugm(N, rspamd_config, 'registered settings %s with no checks',
+          name)
     end
 
-    -- Now we must process actions
+    -- Process symbols part/apply part
     if elt['symbols'] then
       lua_util.debugm(N, rspamd_config, 'added symbols condition to "%s": %s',
           name, elt.symbols)
@@ -834,17 +933,24 @@ local function process_settings_table(tbl, allow_ids)
 end
 
 -- Parse settings map from the ucl line
+local settings_map_pool
 local function process_settings_map(string)
   local parser = ucl.parser()
   local res,err = parser:parse_string(string)
   if not res then
     rspamd_logger.warnx(rspamd_config, 'cannot parse settings map: ' .. err)
   else
+    if settings_map_pool then
+      settings_map_pool:destroy()
+    end
+
+    settings_map_pool = rspamd_mempool.create()
+
     local obj = parser:get_object()
     if obj['settings'] then
-      process_settings_table(obj['settings'], false)
+      process_settings_table(obj['settings'], false, settings_map_pool)
     else
-      process_settings_table(obj, false)
+      process_settings_table(obj, false, settings_map_pool)
     end
   end
 
@@ -950,7 +1056,8 @@ if set_section and set_section[1] and type(set_section[1]) == "string" then
     rspamd_logger.errx(rspamd_config, 'cannot load settings from %1', set_section)
   end
 elseif set_section and type(set_section) == "table" then
-  process_settings_table(set_section, true)
+  settings_map_pool = rspamd_mempool.create()
+  process_settings_table(set_section, true, settings_map_pool)
 end
 
 rspamd_config:register_symbol({