2019-05-17 19:34:14 +02:00
|
|
|
--[[[
|
|
|
|
-- @module lua_maps_expressions
|
|
|
|
-- This module contains routines to combine maps, selectors and expressions
|
|
|
|
-- in a generic framework
|
|
|
|
@example
|
|
|
|
whitelist_ip_from = {
|
|
|
|
rules {
|
|
|
|
ip {
|
|
|
|
selector = "ip";
|
|
|
|
map = "/path/to/whitelist_ip.map";
|
|
|
|
}
|
|
|
|
from {
|
|
|
|
selector = "from(smtp)";
|
|
|
|
map = "/path/to/whitelist_from.map";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
expression = "ip & from";
|
|
|
|
}
|
|
|
|
--]]
|
|
|
|
|
|
|
|
--[[
|
2022-03-25 21:16:35 +01:00
|
|
|
Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com>
|
2019-05-17 19:34:14 +02:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
]]--
|
|
|
|
|
|
|
|
local lua_selectors = require "lua_selectors"
|
|
|
|
local lua_maps = require "lua_maps"
|
|
|
|
local rspamd_expression = require "rspamd_expression"
|
|
|
|
local rspamd_logger = require "rspamd_logger"
|
|
|
|
local fun = require "fun"
|
2020-11-09 12:53:01 +01:00
|
|
|
local ts = require("tableshape").types
|
2019-05-17 19:34:14 +02:00
|
|
|
|
|
|
|
local exports = {}
|
|
|
|
|
|
|
|
local function process_func(elt, task)
|
|
|
|
local matched = {}
|
|
|
|
local function process_atom(atom)
|
|
|
|
local rule = elt.rules[atom]
|
|
|
|
local res = 0
|
|
|
|
|
|
|
|
local function match_rule(val)
|
|
|
|
local map_match = rule.map:get_key(val)
|
|
|
|
if map_match then
|
|
|
|
res = 1.0
|
|
|
|
matched[rule.name] = {
|
|
|
|
matched = val,
|
|
|
|
value = map_match
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local values = rule.selector(task)
|
|
|
|
|
|
|
|
if values then
|
|
|
|
if type(values) == 'table' then
|
2023-08-07 12:41:28 +02:00
|
|
|
for _, val in ipairs(values) do
|
2019-05-17 19:34:14 +02:00
|
|
|
if res == 0 then
|
|
|
|
match_rule(val)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
match_rule(values)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return res
|
|
|
|
end
|
|
|
|
|
|
|
|
local res = elt.expr:process(process_atom)
|
|
|
|
|
2020-02-07 10:52:19 +01:00
|
|
|
if res > 0 then
|
2023-08-07 12:41:28 +02:00
|
|
|
return res, matched
|
2019-05-17 19:34:14 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
2023-08-07 12:41:28 +02:00
|
|
|
exports.schema = ts.shape {
|
2020-11-09 12:53:01 +01:00
|
|
|
expression = ts.string,
|
|
|
|
rules = ts.array_of(
|
2023-08-07 12:41:28 +02:00
|
|
|
ts.shape {
|
2020-11-09 12:53:01 +01:00
|
|
|
selector = ts.string,
|
|
|
|
map = lua_maps.map_schema,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-05-17 21:10:25 +02:00
|
|
|
--[[[
|
|
|
|
-- @function lua_maps_expression.create(config, object, module_name)
|
|
|
|
-- Creates a new maps combination from `object` for `module_name`.
|
|
|
|
-- The input should be table with the following fields:
|
|
|
|
--
|
|
|
|
-- * `rules` - kv map of rules where each rule has `map` and `selector` mandatory attribute, also `type` for map type, e.g. `regexp`
|
|
|
|
-- * `expression` - Rspamd expression where elements are names from `rules` field, e.g. `ip & from`
|
|
|
|
--
|
|
|
|
-- This function returns an object with public method `process(task)` that checks
|
|
|
|
-- a task for the conditions defined in `expression` and `rules` and returns 2 values:
|
|
|
|
--
|
|
|
|
-- 1. value returned by an expression (e.g. 1 or 0)
|
|
|
|
-- 2. an map (rule_name -> table) of matches, where each element has the following fields:
|
|
|
|
-- * `matched` - selector's value
|
|
|
|
-- * `value` - map's result
|
|
|
|
--
|
|
|
|
-- In case if `expression` is false a `nil` value is returned.
|
|
|
|
-- @param {rspamd_config} cfg rspamd config
|
|
|
|
-- @param {table} obj configuration table
|
|
|
|
--
|
|
|
|
--]]
|
2019-05-17 19:34:14 +02:00
|
|
|
local function create(cfg, obj, module_name)
|
2023-08-07 12:41:28 +02:00
|
|
|
if not module_name then
|
|
|
|
module_name = 'lua_maps_expressions'
|
|
|
|
end
|
2019-05-17 19:34:14 +02:00
|
|
|
|
|
|
|
if not obj or not obj.rules or not obj.expression then
|
|
|
|
rspamd_logger.errx(cfg, 'cannot add maps combination for module %s: required elements are missing',
|
|
|
|
module_name)
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
local ret = {
|
|
|
|
process = process_func,
|
|
|
|
rules = {},
|
|
|
|
module_name = module_name
|
|
|
|
}
|
|
|
|
|
2023-08-07 12:41:28 +02:00
|
|
|
for name, rule in pairs(obj.rules) do
|
2019-05-20 17:58:27 +02:00
|
|
|
local sel = lua_selectors.create_selector_closure(cfg, rule.selector)
|
2019-05-17 19:34:14 +02:00
|
|
|
|
|
|
|
if not sel then
|
|
|
|
rspamd_logger.errx(cfg, 'cannot add selector for element %s in module %s',
|
|
|
|
name, module_name)
|
|
|
|
end
|
|
|
|
|
2019-05-18 15:36:32 +02:00
|
|
|
if not rule.type then
|
|
|
|
-- Guess type
|
|
|
|
if name:find('ip') or name:find('ipnet') then
|
|
|
|
rule.type = 'radix'
|
|
|
|
elseif name:find('regexp') or name:find('re_') then
|
|
|
|
rule.type = 'regexp'
|
|
|
|
elseif name:find('glob') then
|
|
|
|
rule.type = 'regexp'
|
|
|
|
else
|
|
|
|
rule.type = 'set'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
local map = lua_maps.map_add_from_ucl(rule.map, rule.type,
|
2019-05-17 19:34:14 +02:00
|
|
|
obj.description or module_name)
|
|
|
|
if not map then
|
|
|
|
rspamd_logger.errx(cfg, 'cannot add map for element %s in module %s',
|
|
|
|
name, module_name)
|
|
|
|
end
|
|
|
|
|
|
|
|
if sel and map then
|
|
|
|
ret.rules[name] = {
|
|
|
|
selector = sel,
|
|
|
|
map = map,
|
2019-05-20 18:18:30 +02:00
|
|
|
name = name,
|
2019-05-17 19:34:14 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Now process and parse expression
|
|
|
|
local function parse_atom(str)
|
|
|
|
local atom = table.concat(fun.totable(fun.take_while(function(c)
|
2022-06-02 22:23:12 +02:00
|
|
|
if string.find(', \t()><+!|&\n', c, 1, true) then
|
2019-05-17 19:34:14 +02:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end, fun.iter(str))), '')
|
|
|
|
|
|
|
|
if ret.rules[atom] then
|
|
|
|
return atom
|
|
|
|
end
|
|
|
|
|
|
|
|
rspamd_logger.errx(cfg, 'use of undefined element "%s" when parsing maps expression for %s',
|
|
|
|
atom, module_name)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
end
|
2019-05-20 17:58:27 +02:00
|
|
|
local expr = rspamd_expression.create(obj.expression, parse_atom,
|
|
|
|
rspamd_config:get_mempool())
|
2019-05-17 19:34:14 +02:00
|
|
|
|
|
|
|
if not expr then
|
|
|
|
rspamd_logger.errx(cfg, 'cannot add map expression for module %s',
|
|
|
|
module_name)
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
ret.expr = expr
|
|
|
|
|
2019-05-18 15:36:32 +02:00
|
|
|
if obj.symbol then
|
2023-08-07 12:41:28 +02:00
|
|
|
rspamd_config:register_symbol {
|
2019-05-18 15:36:32 +02:00
|
|
|
type = 'virtual,ghost',
|
|
|
|
name = obj.symbol,
|
|
|
|
score = 0.0,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2019-05-20 18:18:30 +02:00
|
|
|
ret.symbol = obj.symbol
|
|
|
|
|
2019-05-17 19:34:14 +02:00
|
|
|
return ret
|
|
|
|
end
|
|
|
|
|
|
|
|
exports.create = create
|
|
|
|
|
|
|
|
return exports
|