(digit ^ 1)
-- Matches: 55.97, -90.8, .9
- number.decimal = (number.integer * -- Integer
- (number.fractional ^ -1)) + -- Fractional
+ number.decimal = (number.integer * -- Integer
+ (number.fractional ^ -1)) + -- Fractional
(lpeg.S("+-") * number.fractional) -- Completely fractional number
local sym_start = lpeg.R("az", "AZ") + lpeg.S("_")
else
if p_ret ~= '' then
rspamd_logger.infox(rspamd_config, '%s: cannot parse string "%s"',
- parse_rule.symbol, p_ret)
+ parse_rule.symbol, p_ret)
end
return true, nil, 1.0, {}
ret = process_re_match(re, task, 'body')
elseif match_type == 'uri' then
ret = process_re_match(re, task, 'url')
+ elseif match_type == 'selector' then
+ -- For selector regexps, use structured call with explicit selector field
+ local params = {
+ re = re,
+ type = 'selector',
+ selector = opts.selector,
+ strong = false,
+ }
+ if type(jit) == 'table' then
+ ret = task:process_regexp(params)
+ else
+ ret = task:process_regexp(params)
+ end
else
-- Default to body
ret = process_re_match(re, task, 'sabody')
}
lua_util.debugm(N, rspamd_config, 'added SA header atom: %s for header %s (scope: %s)',
- atom_name, header_name, scope_name)
+ atom_name, header_name, scope_name)
end
end
elseif words[1] == 'body' then
lua_util.debugm(N, rspamd_config, 'added SA full atom: %s (scope: %s)', atom_name, scope_name)
end
end
+ elseif words[1] == 'selector' then
+ -- selector SYMBOL selector_pipeline =~ /regexp/flags
+ -- selector pipeline can contain spaces; find operator position first
+ if #words >= 4 then
+ local atom_name = words[2]
+ local op_idx = nil
+ for i = 4, #words do
+ if words[i] == '=~' or words[i] == '!~' then
+ op_idx = i
+ break
+ end
+ end
+ if not op_idx then
+ return
+ end
+ local selector_pipeline_tbl = {}
+ for i = 3, op_idx - 1 do
+ selector_pipeline_tbl[#selector_pipeline_tbl + 1] = words[i]
+ end
+ local selector_pipeline = table.concat(selector_pipeline_tbl, ' ')
+ local re_expr = words_to_sa_re(words, op_idx)
+
+ -- Skip =~ or !~
+ re_expr = string.gsub(re_expr, '^[!=]~%s*', '')
+
+ local re = parse_sa_regexp(atom_name, re_expr)
+ if re then
+ -- Register selector and regexp in cache scope to use regexp-cache + hyperscan
+ local ok = rspamd_config:register_re_selector_scoped(scope_name, atom_name, selector_pipeline, "", false)
+ if not ok then
+ rspamd_logger.errx(rspamd_config, 'selector atom %s has invalid selector (registration failed): %s',
+ atom_name, selector_pipeline)
+ return
+ end
+
+ rspamd_config:register_regexp_scoped(scope_name, {
+ re = re,
+ type = 'selector',
+ selector = atom_name,
+ pcre_only = false,
+ })
+
+ re:set_limit(0)
+ re:set_max_hits(1)
+
+ local negate = (words[op_idx] == '!~')
+ sa_atoms[atom_name] = create_sa_atom_function(atom_name, re, 'selector', {
+ selector = atom_name,
+ strong = false,
+ negate = negate,
+ })
+
+ -- Track atom state consistent with scoped regexps
+ regexp_rules_symbol_states[atom_name] = {
+ state = 'loading',
+ rule_name = rule_name,
+ type = 'atom'
+ }
+
+ lua_util.debugm(N, rspamd_config, 'added SA selector atom: %s for selector %s (scope: %s)',
+ atom_name, selector_pipeline, scope_name)
+ end
+ end
elseif words[1] == 'meta' then
-- meta SYMBOL expression
if #words >= 3 then
if state_info.state == 'orphaned' or state_info.state == 'loading' then
-- Double-check by looking at scope loaded state
local scope_loaded = false
- for _, rule in ipairs(rules) do
- if rule.symbol == state_info.rule_name and rule.scope_name then
- scope_loaded = rspamd_config:is_regexp_scope_loaded(rule.scope_name)
- break
+ if state_info.scope_optional then
+ scope_loaded = true
+ else
+ for _, rule in ipairs(rules) do
+ if rule.symbol == state_info.rule_name and rule.scope_name then
+ scope_loaded = rspamd_config:is_regexp_scope_loaded(rule.scope_name)
+ break
+ end
end
end
-- Update state to available if scope is loaded and atom exists
state_info.state = 'available'
lua_util.debugm(N, task, 'regexp_rules atom %s was %s, but scope is loaded - marking as available',
- atom, state_info.state)
+ atom, state_info.state)
else
lua_util.debugm(N, task, 'regexp_rules atom %s is %s, returning 0', atom, state_info.state)
return 0
-- Update state to available if scope is loaded and meta rule exists
state_info.state = 'available'
lua_util.debugm(N, task, 'regexp_rules meta %s was %s, but scope is loaded - marking as available',
- meta_rule.symbol, state_info.state)
+ meta_rule.symbol, state_info.state)
else
lua_util.debugm(N, task, 'regexp_rules meta %s is %s, skipping execution',
- meta_rule.symbol, state_info.state)
+ meta_rule.symbol, state_info.state)
return 0
end
end
if not (already_processed and already_processed['default']) then
local expression = rspamd_expression.create(meta_rule.expression,
- parse_sa_atom,
- rspamd_config:get_mempool())
+ parse_sa_atom,
+ rspamd_config:get_mempool())
if not expression then
rspamd_logger.errx(rspamd_config, 'Cannot parse SA meta expression: %s', meta_rule.expression)
return
if res > 0 then
local filtered_trace = fun.totable(fun.take_n(5,
- fun.map(function(elt)
- return elt:gsub('^__', '')
- end, fun.filter(exclude_sym_filter, trace))))
+ fun.map(function(elt)
+ return elt:gsub('^__', '')
+ end, fun.filter(exclude_sym_filter, trace))))
lua_util.debugm(N, task, 'SA meta %s matched with result: %s; trace %s; filtered trace %s',
- meta_rule.symbol, res, trace, filtered_trace)
+ meta_rule.symbol, res, trace, filtered_trace)
task:insert_result_named(cur_res, meta_rule.symbol, 1.0, filtered_trace)
end
-- Initialize SA meta rules after all atoms are processed
local function finalize_sa_rules()
lua_util.debugm(N, rspamd_config, 'Finalizing SA rules - processing %s meta rules',
- fun.length(sa_meta_rules))
+ fun.length(sa_meta_rules))
for meta_name, meta_rule in pairs(sa_meta_rules) do
local score = sa_scores[meta_name] or 1.0
local description = sa_descriptions[meta_name] or ('multimap symbol ' .. meta_name)
lua_util.debugm(N, rspamd_config, 'Registering SA meta rule %s (score: %s, expression: %s)',
- meta_name, score, meta_rule.expression)
+ meta_name, score, meta_rule.expression)
local id = rspamd_config:register_symbol({
name = meta_name,
})
lua_util.debugm(N, rspamd_config, 'Successfully registered SA meta symbol %s with id %s (callback attached)',
- meta_name, id)
+ meta_name, id)
rspamd_config:set_metric_symbol({
name = meta_name,
end
lua_util.debugm(N, rspamd_config, 'registered SA meta symbol: %s (score: %s)',
- meta_name, score)
+ meta_name, score)
end
-- Mark orphaned symbols - only check meta symbols (not atoms) since atoms are just expression parts
end
lua_util.debugm(N, rspamd_config, 'SA rules finalization complete: registered %s meta rules with callbacks',
- fun.length(sa_meta_rules))
+ fun.length(sa_meta_rules))
end
-- Helper function to get regexp_rules symbol state statistics (only meta symbols, not atoms)
end
lua_util.debugm(N, rspamd_config, 'Scope %s is loaded, marked %s symbols as available',
- rule.scope_name, updated_count)
+ rule.scope_name, updated_count)
else
lua_util.debugm(N, rspamd_config, 'Scope %s is not loaded', rule.scope_name)
end
local stats = get_regexp_rules_symbol_stats()
lua_util.debugm(N, rspamd_config, 'Symbol state stats after sync: available=%s, loading=%s, orphaned=%s, total=%s',
- stats.available, stats.loading, stats.orphaned, stats.total)
+ stats.available, stats.loading, stats.orphaned, stats.total)
end
-- Optional cleanup function to remove old orphaned symbols (can be called periodically)
local function redis_map_cb(err, data)
lua_util.debugm(N, task, 'got reply from Redis when trying to get key %s: err=%s, data=%s',
- key, err, data)
+ key, err, data)
if not err and type(data) ~= 'userdata' then
callback(data)
end
end
return rspamd_redis_make_request(task,
- redis_params, -- connect params
- key, -- hash key
- false, -- is write
- redis_map_cb, --callback
- cmd, -- command
- srch -- arguments
+ redis_params, -- connect params
+ key, -- hash key
+ false, -- is write
+ redis_map_cb, --callback
+ cmd, -- command
+ srch -- arguments
)
end
local function get_key_callback(ret, err_or_data, err_code)
lua_util.debugm(N, task, 'got return "%s" (err code = %s) for multimap %s',
- err_or_data,
- err_code,
- rule.symbol)
+ err_or_data,
+ err_code,
+ rule.symbol)
if ret then
if type(err_or_data) == 'table' then
end
elseif err_code ~= 404 then
rspamd_logger.infox(task, "map %s: get key returned error %s: %s",
- rule.symbol, err_code, err_or_data)
+ rule.symbol, err_code, err_or_data)
end
end
lua_util.debugm(N, task, 'check value %s for multimap %s', value,
- rule.symbol)
+ rule.symbol)
local ret = false
if rule.symbols_set then
if not rule.symbols_set[symbol] then
rspamd_logger.infox(task, 'symbol %s is not registered for map %s, ' ..
- 'replace it with just %s',
- symbol, rule.symbol, rule.symbol)
+ 'replace it with just %s',
+ symbol, rule.symbol, rule.symbol)
symbol = rule.symbol
end
elseif rule.disable_multisymbol then
if fn then
local filtered_value = fn(task, r.filter, value, r)
lua_util.debugm(N, task, 'apply filter %s for rule %s: %s -> %s',
- r.filter, r.symbol, value, filtered_value)
+ r.filter, r.symbol, value, filtered_value)
value = filtered_value
end
end
if not res or res == 0 then
lua_util.debugm(N, task, 'condition is false for %s',
- rule.symbol)
+ rule.symbol)
return
else
lua_util.debugm(N, task, 'condition is true for %s: %s',
- rule.symbol,
- trace)
+ rule.symbol,
+ trace)
end
end
local to_resolve = ip_to_rbl(ip, rule['map'])
local function dns_cb(_, _, results, err)
lua_util.debugm(N, rspamd_config,
- 'resolve() finished: results=%1, err=%2, to_resolve=%3',
- results, err, to_resolve)
+ 'resolve() finished: results=%1, err=%2, to_resolve=%3',
+ results, err, to_resolve)
if err and
(err ~= 'requested record is not found' and
- err ~= 'no records with this name') then
+ err ~= 'no records with this name') then
rspamd_logger.errx(task, 'error looking up %s: %s', to_resolve, results)
elseif results then
task:insert_result(rule['symbol'], 1, rule['map'])
if rule.action then
task:set_pre_result(rule['action'],
- 'Matched map: ' .. rule['symbol'], N)
+ 'Matched map: ' .. rule['symbol'], N)
end
end
end
if ext then
local fake_fname = string.format('detected.%s', ext)
lua_util.debugm(N, task, 'detected filename %s',
- fake_fname)
+ fake_fname)
match_filename(rule, fake_fname)
end
end
if ret and ret ~= 0 then
for n, t in pairs(trace) do
insert_results(t.value, string.format("%s=%s",
- n, t.matched))
+ n, t.matched))
end
end
end,
-- For regexp_rules, the meta rules are registered as separate symbols
-- This is just a placeholder callback
lua_util.debugm(N, task, 'Regexp rules callback for %s - meta rules are registered as separate symbols',
- rule.symbol)
+ rule.symbol)
end,
}
if r and symbol and not known_symbols[symbol] then
lua_util.debugm(N, rspamd_config, "%s: adding new symbol %s (score = %s), triggered by %s",
- rule.symbol, symbol, score, key)
+ rule.symbol, symbol, score, key)
rspamd_config:register_symbol {
name = symbol,
parent = rule.callback_id,
if rule['regexp'] then
if rule['multi'] then
rule.map_obj = lua_maps.map_add_from_ucl(rule.map, 'regexp_multi',
- rule.description)
+ rule.description)
else
rule.map_obj = lua_maps.map_add_from_ucl(rule.map, 'regexp',
- rule.description)
+ rule.description)
end
elseif rule['glob'] then
if rule['multi'] then
rule.map_obj = lua_maps.map_add_from_ucl(rule.map, 'glob_multi',
- rule.description)
+ rule.description)
else
rule.map_obj = lua_maps.map_add_from_ucl(rule.map, 'glob',
- rule.description)
+ rule.description)
end
else
rule.map_obj = lua_maps.map_add_from_ucl(rule.map, 'hash',
- rule.description)
+ rule.description)
end
end
end
if not newrule['description'] then
newrule['description'] = string.format('multimap, type %s: %s', newrule['type'],
- newrule['symbol'])
+ newrule['symbol'])
end
if newrule['type'] == 'mempool' and not newrule['variable'] then
rspamd_logger.errx(rspamd_config, 'mempool map requires variable')
return nil
else
local selector = lua_selectors.create_selector_closure(
- rspamd_config, newrule['selector'], newrule['delimiter'] or "")
+ rspamd_config, newrule['selector'], newrule['delimiter'] or "")
if not selector then
rspamd_logger.errx(rspamd_config, 'selector map has invalid selector: "%s", symbol: %s',
- newrule['selector'], newrule['symbol'])
+ newrule['selector'], newrule['symbol'])
return nil
end
string.find(newrule['map'], '^redis://.*$') then
if not redis_params then
rspamd_logger.infox(rspamd_config, 'no redis servers are specified, ' ..
- 'cannot add redis map %s: %s', newrule['symbol'], newrule['map'])
+ 'cannot add redis map %s: %s', newrule['symbol'], newrule['map'])
return nil
end
string.find(newrule['map'], '^redis%+selector://.*$') then
if not redis_params then
rspamd_logger.infox(rspamd_config, 'no redis servers are specified, ' ..
- 'cannot add redis map %s: %s', newrule['symbol'], newrule['map'])
+ 'cannot add redis map %s: %s', newrule['symbol'], newrule['map'])
return nil
end
local selector_str = string.match(newrule['map'], '^redis%+selector://(.*)$')
local selector = lua_selectors.create_selector_closure(
- rspamd_config, selector_str, newrule['delimiter'] or "")
+ rspamd_config, selector_str, newrule['delimiter'] or "")
if not selector then
rspamd_logger.errx(rspamd_config, 'redis selector map has invalid selector: "%s", symbol: %s',
- selector_str, newrule['symbol'])
+ selector_str, newrule['symbol'])
return nil
end
elseif newrule.type == 'combined' then
local lua_maps_expressions = require "lua_maps_expressions"
newrule.combined = lua_maps_expressions.create(rspamd_config,
- {
- rules = newrule.rules,
- expression = newrule.expression,
- description = newrule.description,
- on_load = newrule.dynamic_symbols and multimap_on_load_gen(newrule) or nil,
- }, N, 'Combined map for ' .. newrule.symbol)
+ {
+ rules = newrule.rules,
+ expression = newrule.expression,
+ description = newrule.description,
+ on_load = newrule.dynamic_symbols and multimap_on_load_gen(newrule) or nil,
+ }, N, 'Combined map for ' .. newrule.symbol)
if not newrule.combined then
rspamd_logger.errx(rspamd_config, 'cannot add combined map for %s', newrule.symbol)
else
if state_info.rule_name == newrule.symbol then
state_info.state = 'loading'
lua_util.debugm(N, rspamd_config, 'marked regexp_rules symbol %s as loading for scope %s reload',
- symbol, scope_name)
+ symbol, scope_name)
end
end
sa_atoms[symbol] = nil
sa_meta_rules[symbol] = nil
lua_util.debugm(N, rspamd_config, 'cleared regexp_rules symbol %s for scope %s reload',
- symbol, scope_name)
+ symbol, scope_name)
end
-- The scope will be created by process_sa_line when first regexp is added
end
process_sa_line(newrule, line)
end,
- by_line = true, -- Process line by line
+ by_line = true, -- Process line by line
opaque_data = false, -- Use plain strings
})
-- Trigger hyperscan compilation for this updated scope
newrule.map_obj:trigger_hyperscan_compilation()
lua_util.debugm(N, rspamd_config, 'triggered hyperscan compilation for scope %s after map loading',
- scope_name)
+ scope_name)
else
lua_util.debugm(N, rspamd_config, 'regexp scope %s not created (empty map)', scope_name)
end
-- Promote symcache resort after dynamic symbol registration
rspamd_config:promote_symbols_cache_resort()
lua_util.debugm(N, rspamd_config, 'promoted symcache resort after loading SA rules from map %s',
- newrule.symbol)
+ newrule.symbol)
end)
end
-- Mark this rule as using SA functionality
newrule.uses_sa = true
lua_util.debugm(N, rspamd_config, 'created regexp_rules map %s with scope: %s',
- newrule.symbol, scope_name)
+ newrule.symbol, scope_name)
ret = true
else
rspamd_logger.warnx(rspamd_config, 'Cannot add SA-style rule: map doesn\'t exists: %s',
- newrule['map'])
+ newrule['map'])
end
else
if newrule['type'] == 'ip' then
newrule.map_obj = lua_maps.map_add_from_ucl(newrule.map, 'radix',
- newrule.description)
+ newrule.description)
if newrule.map_obj then
ret = true
else
rspamd_logger.warnx(rspamd_config, 'Cannot add rule: map doesn\'t exists: %s',
- newrule['map'])
+ newrule['map'])
end
elseif newrule['type'] == 'received' then
if type(newrule['flags']) == 'table' and newrule['flags'][1] then
local filter = newrule['filter'] or 'real_ip'
if filter == 'real_ip' or filter == 'from_ip' then
newrule.map_obj = lua_maps.map_add_from_ucl(newrule.map, 'radix',
- newrule.description)
+ newrule.description)
if newrule.map_obj then
ret = true
else
rspamd_logger.warnx(rspamd_config, 'Cannot add rule: map doesn\'t exists: %s',
- newrule['map'])
+ newrule['map'])
end
else
multimap_load_kv_map(newrule)
ret = true
else
rspamd_logger.warnx(rspamd_config, 'Cannot add rule: map doesn\'t exists: %s',
- newrule['map'])
+ newrule['map'])
end
end
elseif known_generic_types[newrule.type] then
if newrule.filter == 'ip_addr' then
newrule.map_obj = lua_maps.map_add_from_ucl(newrule.map, 'radix',
- newrule.description)
+ newrule.description)
elseif not newrule.combined then
multimap_load_kv_map(newrule)
end
ret = true
else
rspamd_logger.warnx(rspamd_config, 'Cannot add rule: map doesn\'t exists: %s',
- newrule['map'])
+ newrule['map'])
end
elseif newrule['type'] == 'dnsbl' then
ret = true
else
rspamd_logger.errx(rspamd_config, 'cannot add rule %s: invalid type %s',
- key, newrule['type'])
+ key, newrule['type'])
end
end
end
local expression = rspamd_expression.create(newrule['require_symbols'],
- { parse_atom, process_atom }, rspamd_config:get_mempool())
+ { parse_atom, process_atom }, rspamd_config:get_mempool())
if expression then
newrule['expression'] = expression
fun.each(function(v)
lua_util.debugm(N, rspamd_config, 'add dependency %s -> %s',
- newrule['symbol'], v)
+ newrule['symbol'], v)
rspamd_config:register_dependency(newrule['symbol'], v)
end, atoms)
end
rspamd_logger.errx(rspamd_config, 'cannot add rule: "' .. k .. '"')
else
rspamd_logger.infox(rspamd_config, 'added multimap rule: %s (%s)',
- k, rule.type)
+ k, rule.type)
table.insert(rules, rule)
end
end