diff options
author | Vsevolod Stakhov <vsevolod@rspamd.com> | 2024-08-07 14:27:49 +0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-07 14:27:49 +0600 |
commit | dc808aac88771bb6f95571a7ac102e853b9c45a8 (patch) | |
tree | 192b044de37f52ed2ef50638922b42d4ed155e13 | |
parent | 6a97bdd201686503654a434fd2624db89657aaa1 (diff) | |
parent | 406f694d893c3d4e1617b66cdf87bc22a1b1beb7 (diff) | |
download | rspamd-dc808aac88771bb6f95571a7ac102e853b9c45a8.tar.gz rspamd-dc808aac88771bb6f95571a7ac102e853b9c45a8.zip |
Merge branch 'master' into master
29 files changed, 652 insertions, 189 deletions
diff --git a/conf/groups.conf b/conf/groups.conf index 2aeb4ed5d..4f40d865c 100644 --- a/conf/groups.conf +++ b/conf/groups.conf @@ -39,6 +39,13 @@ group "rbl" { .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/rbl_group.conf" } +# Limits the maximum score when both bl.score.senderscore.com and score.senderscore.com RBLs are enabled. +group "senderscore" { + max_score = 4.0; + .include(try=true; priority=1; duplicate=merge) "$LOCAL_CONFDIR/local.d/senderscore_group.conf" + .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/senderscore_group.conf" +} + group "statistics" { .include "$CONFDIR/scores.d/statistics_group.conf" .include(try=true; priority=1; duplicate=merge) "$LOCAL_CONFDIR/local.d/statistics_group.conf" diff --git a/conf/modules.d/once_received.conf b/conf/modules.d/once_received.conf index ab0749295..6fcc35bb6 100644 --- a/conf/modules.d/once_received.conf +++ b/conf/modules.d/once_received.conf @@ -14,8 +14,7 @@ once_received { good_host = "mail"; - bad_host = "static"; - bad_host = "dynamic"; + bad_host = ["static", "dynamic"]; symbol_strict = "ONCE_RECEIVED_STRICT"; symbol = "ONCE_RECEIVED"; symbol_mx = "DIRECT_TO_MX"; diff --git a/conf/modules.d/rbl.conf b/conf/modules.d/rbl.conf index d8db05ecc..2a718e5a4 100644 --- a/conf/modules.d/rbl.conf +++ b/conf/modules.d/rbl.conf @@ -79,6 +79,11 @@ rbl { } senderscore { + # Disabled by default to prioritize the use of score.senderscore.com. + # Note: The free query limit applies to both bl.score.senderscore.com and score.senderscore.com RBLs + # (see https://knowledge.validity.com/hc/en-us/articles/20961730681243). + # Enabling this RBL is recommended for low-traffic systems or MyValidity account users who benefit from using both RBLs. + enabled = false; symbol = "RBL_SENDERSCORE_UNKNOWN"; checks = ['from']; rbl = "bl.score.senderscore.com"; @@ -104,6 +109,27 @@ rbl { } } + senderscore_reputation { + symbol = "RBL_SENDERSCORE_REPUT_UNKNOWN"; + checks = ['from']; + rbl = "score.senderscore.com"; + returncodes_matcher = "luapattern"; + + returncodes { + RBL_SENDERSCORE_REPUT_0 = "127%.0%.4%.%d"; + RBL_SENDERSCORE_REPUT_1 = "127%.0%.4%.1%d"; + RBL_SENDERSCORE_REPUT_2 = "127%.0%.4%.2%d"; + RBL_SENDERSCORE_REPUT_3 = "127%.0%.4%.3%d"; + RBL_SENDERSCORE_REPUT_4 = "127%.0%.4%.4%d"; + RBL_SENDERSCORE_REPUT_5 = "127%.0%.4%.5%d"; + RBL_SENDERSCORE_REPUT_6 = "127%.0%.4%.6%d"; + RBL_SENDERSCORE_REPUT_7 = "127%.0%.4%.7%d"; + RBL_SENDERSCORE_REPUT_8 = "127%.0%.4%.8%d"; # Neutral reputation (80-89). + RBL_SENDERSCORE_REPUT_9 = ["127%.0%.4%.9%d", "127%.0%.4%.100"]; # Good reputation (90-100). + RBL_SENDERSCORE_REPUT_BLOCKED = "127%.255%.255%.255"; + } + } + sem { symbol = "RBL_SEM"; rbl = "bl.spameatingmonkey.net"; diff --git a/conf/scores.d/headers_group.conf b/conf/scores.d/headers_group.conf index 1c70ca588..972c6872a 100644 --- a/conf/scores.d/headers_group.conf +++ b/conf/scores.d/headers_group.conf @@ -50,14 +50,6 @@ symbols = { weight = 0.1; description = "One received header in a message"; } - "RDNS_NONE" { - weight = 2.0; - description = "Cannot resolve reverse DNS for sender's IP"; - } - "RDNS_DNSFAIL" { - weight = 0.0; - description = "PTR verification DNS error"; - } "ONCE_RECEIVED_STRICT" { weight = 4.0; description = "One received header with 'bad' patterns inside"; diff --git a/conf/scores.d/hfilter_group.conf b/conf/scores.d/hfilter_group.conf index 09fcfcd8d..21cd11a60 100644 --- a/conf/scores.d/hfilter_group.conf +++ b/conf/scores.d/hfilter_group.conf @@ -130,4 +130,12 @@ symbols = { weight = 2.5; description = "One line URL and text in body"; } + "RDNS_NONE" { + weight = 2.0; + description = "Cannot resolve reverse DNS for sender's IP"; + } + "RDNS_DNSFAIL" { + weight = 0.0; + description = "PTR verification DNS error"; + } } diff --git a/conf/scores.d/rbl_group.conf b/conf/scores.d/rbl_group.conf index 741552a17..6a59b865f 100644 --- a/conf/scores.d/rbl_group.conf +++ b/conf/scores.d/rbl_group.conf @@ -257,6 +257,67 @@ symbols = { groups = ["senderscore", "blocked"]; } + "RBL_SENDERSCORE_REPUT_UNKNOWN" { + weight = 0.0; + description = "Unrecognized result from SenderScore Reputation list."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_0" { + weight = 4.0; + description = "SenderScore Reputation: Very Bad (0-9)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_1" { + weight = 3.5; + description = "SenderScore Reputation: Bad (10-19)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_2" { + weight = 3.0; + description = "SenderScore Reputation: Bad (20-29)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_3" { + weight = 2.5; + description = "SenderScore Reputation: Bad (30-39)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_4" { + weight = 2.0; + description = "SenderScore Reputation: Bad (40-49)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_5" { + weight = 1.5; + description = "SenderScore Reputation: Bad (50-59)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_6" { + weight = 1.0; + description = "SenderScore Reputation: Bad (60-69)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_7" { + weight = 0.5; + description = "SenderScore Reputation: Bad (70-79)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_8" { + weight = 0.0; + description = "SenderScore Reputation: Neutral (80-89)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_9" { + weight = -1.0; + description = "SenderScore Reputation: Good (90-100)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_BLOCKED" { + weight = 0.0; + description = "Excessive number of queries to SenderScore RPBL, more info: https://knowledge.validity.com/hc/en-us/articles/20961730681243"; + groups = ["senderscore", "blocked"]; + } + "MAILSPIKE" { weight = 0.0; description = "Unrecognised result from Mailspike"; diff --git a/lualib/lua_redis.lua b/lualib/lua_redis.lua index 818d955e9..2c77c100a 100644 --- a/lualib/lua_redis.lua +++ b/lualib/lua_redis.lua @@ -1162,8 +1162,11 @@ local function prepare_redis_call(script) end -- Call load script on each server, set loaded flag - script.in_flight = #servers - for _, s in ipairs(servers) do + if not script.servers_ready then + script.servers_ready = { } -- possible values for each server: unsent, tempfail, failed, done + end + + for idx, s in ipairs(servers) do local cur_opts = { host = s:get_addr(), timeout = script.redis_params['timeout'], @@ -1172,6 +1175,11 @@ local function prepare_redis_call(script) upstream = s } + -- By default we start from unsent status + if not script.servers_ready[idx] then + script.servers_ready[idx] = "unsent" + end + if script.redis_params['username'] then cur_opts['username'] = script.redis_params['username'] end @@ -1190,46 +1198,89 @@ local function prepare_redis_call(script) return options end +local function is_all_servers_ready(script) + for _, s in ipairs(script.servers_ready) do + if s == "unsent" or s == "tempfail" then + return false + end + end + + -- We assume that permanent errors are not recoverable, so we will just skip those servers + return true +end + +local function is_all_servers_failed(script) + for _, s in ipairs(script.servers_ready) do + if s == "unsent" or s == "tempfail" or s == "done" then + return false + end + end + + return true +end + +local function script_description(script) + return script.filename and ("from file: " .. script.filename) + or ("with id: " .. script.id) +end + local function load_script_task(script, task, is_write) local rspamd_redis = require "rspamd_redis" local opts = prepare_redis_call(script) - for _, opt in ipairs(opts) do - opt.task = task - opt.is_write = is_write - opt.callback = function(err, data) - if err then - logger.errx(task, 'cannot upload script to %s: %s; registered from: %s:%s', - opt.upstream:get_addr():to_string(true), - err, script.caller.short_src, script.caller.currentline) - opt.upstream:fail() - script.fatal_error = err - else - opt.upstream:ok() - logger.infox(task, - "uploaded redis script to %s %s %s, sha: %s", - opt.upstream:get_addr():to_string(true), - script.filename and "from file" or "with id", script.filename or script.id, data) - script.sha = data -- We assume that sha is the same on all servers - end - script.in_flight = script.in_flight - 1 - - if script.in_flight == 0 then - script_set_loaded(script) - end - end + for idx, opt in ipairs(opts) do + if script.servers_ready[idx] ~= 'done' then + opt.task = task + opt.is_write = is_write + opt.callback = function(err, data) + if err then + if string.match(err, 'ERR') then + logger.errx(task, 'cannot upload script %s to %s: %s; registered from: %s:%s', + script_description(script), + opt.upstream:get_addr():to_string(true), + err, script.caller.short_src, script.caller.currentline) + opt.upstream:fail() + script.servers_ready[idx] = "failed" + return + else + -- Assume temporary error + logger.infox(task, 'temporary error uploading script %s to %s: %s; registered from: %s:%s', + script_description(script), + opt.upstream:get_addr():to_string(true), + err, script.caller.short_src, script.caller.currentline) + script.servers_ready[idx] = "tempfail" + return + end + else + opt.upstream:ok() + logger.infox(task, + "uploaded redis script to %s %s, sha: %s", + opt.upstream:get_addr():to_string(true), + script_description(script), data) + script.sha = data -- We assume that sha is the same on all servers + script.servers_ready[idx] = "done" + end + if is_all_servers_ready(script) then + script_set_loaded(script) + elseif is_all_servers_failed(script) then + script.fatal_error = "cannot upload script to any server" + end + end -- callback - local ret = rspamd_redis.make_request(opt) + local ret = rspamd_redis.make_request(opt) - if not ret then - logger.errx('cannot execute redis request to load script on %s', - opt.upstream:get_addr()) - script.in_flight = script.in_flight - 1 - opt.upstream:fail() + if not ret then + logger.errx('cannot execute redis request to load script on %s', + opt.upstream:get_addr()) + script.servers_ready[idx] = "failed" + opt.upstream:fail() + end end - if script.in_flight == 0 then + if is_all_servers_ready(script) then script_set_loaded(script) + elseif is_all_servers_failed(script) then + script.fatal_error = "cannot upload script to any server" end end end @@ -1238,44 +1289,62 @@ local function load_script_taskless(script, cfg, ev_base, is_write) local rspamd_redis = require "rspamd_redis" local opts = prepare_redis_call(script) - for _, opt in ipairs(opts) do - opt.config = cfg - opt.ev_base = ev_base - opt.is_write = is_write - opt.callback = function(err, data) - if err then - logger.errx(cfg, 'cannot upload script to %s: %s; registered from: %s:%s, filename: %s', - opt.upstream:get_addr():to_string(true), - err, script.caller.short_src, script.caller.currentline, script.filename) - opt.upstream:fail() - script.fatal_error = err - else - opt.upstream:ok() - logger.infox(cfg, - "uploaded redis script to %s %s %s, sha: %s", - opt.upstream:get_addr():to_string(true), - script.filename and "from file" or "with id", script.filename or script.id, - data) - script.sha = data -- We assume that sha is the same on all servers - script.fatal_error = nil - end - script.in_flight = script.in_flight - 1 + for idx, opt in ipairs(opts) do + if script.servers_ready[idx] ~= 'done' then + opt.config = cfg + opt.ev_base = ev_base + opt.is_write = is_write + opt.callback = function(err, data) + if err then + if string.match(err, 'ERR') then + logger.errx(cfg, 'cannot upload script %s to %s: %s; registered from: %s:%s', + script_description(script), + opt.upstream:get_addr():to_string(true), + err, script.caller.short_src, script.caller.currentline) + opt.upstream:fail() + script.servers_ready[idx] = "failed" + return + else + -- Assume temporary error + logger.infox(cfg, 'temporary error uploading script %s to %s: %s; registered from: %s:%s', + script_description(script), + opt.upstream:get_addr():to_string(true), + err, script.caller.short_src, script.caller.currentline) + script.servers_ready[idx] = "tempfail" + return + end + else + opt.upstream:ok() + logger.infox(cfg, + "uploaded redis script %s to %s, sha: %s", + script_description(script), + opt.upstream:get_addr():to_string(true), + data) + script.sha = data -- We assume that sha is the same on all servers + script.servers_ready[idx] = "done" + end - if script.in_flight == 0 then - script_set_loaded(script) + if is_all_servers_ready(script) then + script_set_loaded(script) + elseif is_all_servers_failed(script) then + script.fatal_error = "cannot upload script to any server" + end end - end - local ret = rspamd_redis.make_request(opt) + local ret = rspamd_redis.make_request(opt) - if not ret then - logger.errx('cannot execute redis request to load script on %s', - opt.upstream:get_addr()) - script.in_flight = script.in_flight - 1 - opt.upstream:fail() + if not ret then + logger.errx('cannot execute redis request to load script %s on %s', + script_description(script), + opt.upstream:get_addr()) + script.servers_ready[idx] = "failed" + opt.upstream:fail() + end end - if script.in_flight == 0 then + if is_all_servers_ready(script) then script_set_loaded(script) + elseif is_all_servers_failed(script) then + script.fatal_error = "cannot upload script " .. script_description(script) .. " to any server" end end end @@ -1306,12 +1375,17 @@ local function add_redis_script(script, redis_params, caller_level, maybe_filena rspamd_config:add_on_load(function(cfg, ev_base, worker) local mult = 0.0 rspamd_config:add_periodic(ev_base, 0.0, function() - if not new_script.sha then + if not new_script.sha and not new_script.fatal_error then load_redis_script(new_script, cfg, ev_base, worker) - mult = mult + 1 + + -- Do not wait for more than 10 seconds to retry + if mult < 10 then + mult = mult + 1 + end return 1.0 * mult -- Check one more time in one second end + -- All loaded, we are good to go return false end, false) end) @@ -1386,10 +1460,12 @@ local function exec_redis_script(id, params, callback, keys, args) callback(err, data) elseif string.match(err, 'NOSCRIPT') then -- Schedule restart + logger.infox(params.task, 'redis script %s is not loaded (NOSCRIPT returned), scheduling reload', + script_description(script)) script.sha = nil if can_reload then table.insert(script.waitq, do_call) - if script.in_flight == 0 then + if not script.servers_ready then -- Reload scripts if this has not been initiated yet if params.task then load_script_task(script, params.task) @@ -1437,6 +1513,8 @@ local function exec_redis_script(id, params, callback, keys, args) do_call(true) else -- Delayed until scripts are loaded + logger.infox(params.task or rspamd_config, 'redis script %s is not loaded, trying to load', + script_description(script)) if not params.task then table.insert(script.waitq, do_call) else diff --git a/rules/misc.lua b/rules/misc.lua index faf4a8fb8..8785ab4ac 100644 --- a/rules/misc.lua +++ b/rules/misc.lua @@ -862,3 +862,59 @@ rspamd_config.COMPLETELY_EMPTY = { group = 'blankspam', score = 15 } + +-- Check for the hostname if it was not set +local rnds_check_id = rspamd_config:register_symbol { + name = 'RDNS_CHECK', + callback = function(task) + if not task:get_hostname() then + -- Try to resolve + local task_ip = task:get_ip() + if task_ip and task_ip:is_valid() then + local rspamd_logger = require "rspamd_logger" + local function rdns_dns_cb(_, to_resolve, results, err) + if err and (err ~= 'requested record is not found' and err ~= 'no records with this name') then + rspamd_logger.errx(task, 'error looking up %s: %s', to_resolve, err) + task:insert_result('RDNS_DNSFAIL', 1.0) + end + + if not results then + task:insert_result('RDNS_NONE', 1.0) + else + rspamd_logger.infox(task, 'source hostname has not been passed to Rspamd from MTA, ' .. + 'but we could resolve source IP address PTR %s as "%s"', + to_resolve, results[1]) + task:set_hostname(results[1]) + end + end + task:get_resolver():resolve_ptr({ task = task, + name = task_ip:to_string(), + callback = rdns_dns_cb, + forced = true + }) + end + end + end, + type = 'prefilter', + -- TODO: settings might need to use this symbol if they depend on hostname... + priority = lua_util.symbols_priorities.top - 1, + description = 'Check if hostname has been resolved by MTA', +} + +rspamd_config:register_symbol { + type = 'virtual', + name = 'RDNS_DNSFAIL', + score = 0.0, + description = 'DNS failure resolving RDNS', + group = 'hfilter', + parent = rnds_check_id, + +} +rspamd_config:register_symbol { + type = 'virtual', + name = 'RDNS_NONE', + score = 2.0, + description = 'DNS failure resolving RDNS', + group = 'hfilter', + parent = rnds_check_id, +}
\ No newline at end of file diff --git a/src/libmime/scan_result.c b/src/libmime/scan_result.c index f15290b95..894ae4f9e 100644 --- a/src/libmime/scan_result.c +++ b/src/libmime/scan_result.c @@ -201,16 +201,34 @@ rspamd_check_group_score(struct rspamd_task *task, double *group_score, double w) { - if (gr != NULL && group_score && gr->max_score > 0.0 && w > 0.0) { - if (*group_score >= gr->max_score && w > 0) { + double group_limit = NAN; + + if (gr != NULL && group_score) { + if ((*group_score + w) >= 0 && !isnan(gr->max_score) && gr->max_score > 0) { + group_limit = gr->max_score; + } + else if ((*group_score + w) < 0 && !isnan(gr->min_score) && gr->min_score < 0) { + group_limit = -gr->min_score; + } + } + + if (gr != NULL && group_limit && !isnan(group_limit)) { + if (fabs(*group_score) >= group_limit && signbit(*group_score) == signbit(w)) { + /* Cannot add more to the group */ msg_info_task("maximum group score %.2f for group %s has been reached," " ignoring symbol %s with weight %.2f", - gr->max_score, + group_limit, gr->name, symbol, w); return NAN; } - else if (*group_score + w > gr->max_score) { - w = gr->max_score - *group_score; + else if (fabs(*group_score + w) > group_limit) { + /* Reduce weight */ + double new_w = signbit(w) ? -group_limit - *group_score : group_limit - *group_score; + msg_info_task("maximum group score %.2f for group %s has been reached," + " reduce weight of symbol %s from %.2f to %.2f", + group_limit, + gr->name, symbol, w, new_w); + w = new_w; } } @@ -393,15 +411,7 @@ insert_metric_result(struct rspamd_task *task, } else if (gr_score) { *gr_score += cur_diff; - - if (cur_diff < diff) { - /* Reduce */ - msg_debug_metric( - "group limit %.2f is reached for %s when inserting symbol %s;" - " reduce score %.2f - %.2f", - *gr_score, gr->name, symbol, diff, cur_diff); - diff = cur_diff; - } + diff = cur_diff; } } } @@ -461,15 +471,7 @@ insert_metric_result(struct rspamd_task *task, } else if (gr_score) { *gr_score += cur_score; - - if (cur_score < final_score) { - /* Reduce */ - msg_debug_metric( - "group limit %.2f is reached for %s when inserting symbol %s;" - " reduce score %.2f - %.2f", - *gr_score, gr->name, symbol, final_score, cur_score); - final_score = cur_score; - } + final_score = cur_score; } } } diff --git a/src/libserver/cfg_file.h b/src/libserver/cfg_file.h index 1ba1d84ad..fa784f2a2 100644 --- a/src/libserver/cfg_file.h +++ b/src/libserver/cfg_file.h @@ -102,6 +102,7 @@ struct rspamd_symbols_group { char *description; GHashTable *symbols; double max_score; + double min_score; unsigned int flags; }; diff --git a/src/libserver/cfg_rcl.cxx b/src/libserver/cfg_rcl.cxx index 8a479fa6d..9b6e759bb 100644 --- a/src/libserver/cfg_rcl.cxx +++ b/src/libserver/cfg_rcl.cxx @@ -420,6 +420,18 @@ rspamd_rcl_group_handler(rspamd_mempool_t *pool, const ucl_object_t *obj, return FALSE; } + if (!std::isnan(gr->max_score) && gr->max_score < 0) { + msg_err_config("group %s has negative max_score which is broken, use min_score if required", gr->name); + + return FALSE; + } + if (!std::isnan(gr->min_score) && gr->min_score > 0) { + msg_err_config("group %s has positive min_score which is broken, use max_score if required", gr->name); + + return FALSE; + } + + if (const auto *elt = ucl_object_lookup(obj, "one_shot"); elt != nullptr) { if (ucl_object_type(elt) != UCL_BOOLEAN) { g_set_error(err, @@ -2349,6 +2361,12 @@ rspamd_rcl_config_init(struct rspamd_config *cfg, GHashTable *skip_sections) G_STRUCT_OFFSET(struct rspamd_symbols_group, max_score), 0, "Maximum score that could be reached by this symbols group"); + rspamd_rcl_add_default_handler(sub, + "min_score", + rspamd_rcl_parse_struct_double, + G_STRUCT_OFFSET(struct rspamd_symbols_group, min_score), + 0, + "Maximum negative score that could be reached by this symbols group"); } if (!(skip_sections && g_hash_table_lookup(skip_sections, "worker"))) { diff --git a/src/libserver/cfg_utils.cxx b/src/libserver/cfg_utils.cxx index 1344bc4f9..d8696e72d 100644 --- a/src/libserver/cfg_utils.cxx +++ b/src/libserver/cfg_utils.cxx @@ -1052,6 +1052,8 @@ rspamd_config_new_group(struct rspamd_config *cfg, const char *name) rspamd_mempool_add_destructor(cfg->cfg_pool, (rspamd_mempool_destruct_t) g_hash_table_unref, gr->symbols); gr->name = rspamd_mempool_strdup(cfg->cfg_pool, name); + gr->max_score = NAN; + gr->min_score = NAN; if (strcmp(gr->name, "ungrouped") == 0) { gr->flags |= RSPAMD_SYMBOL_GROUP_UNGROUPED; diff --git a/src/libserver/milter.c b/src/libserver/milter.c index f35278a0e..94b0d6cc1 100644 --- a/src/libserver/milter.c +++ b/src/libserver/milter.c @@ -1465,10 +1465,16 @@ rspamd_milter_macro_http(struct rspamd_milter_session *session, return; } + /* + * When we get a queue-id we try to pass it to the backend, where possible + * We also need that for logging consistency + */ IF_MACRO("{i}") { rspamd_http_message_add_header_len(msg, QUEUE_ID_HEADER, found->begin, found->len); + rspamd_http_message_add_header_len(msg, LOG_TAG_HEADER, + found->begin, found->len); } else { @@ -1476,6 +1482,8 @@ rspamd_milter_macro_http(struct rspamd_milter_session *session, { rspamd_http_message_add_header_len(msg, QUEUE_ID_HEADER, found->begin, found->len); + rspamd_http_message_add_header_len(msg, LOG_TAG_HEADER, + found->begin, found->len); } } diff --git a/src/libserver/protocol.c b/src/libserver/protocol.c index db83b0bfb..ee2192913 100644 --- a/src/libserver/protocol.c +++ b/src/libserver/protocol.c @@ -660,9 +660,9 @@ rspamd_protocol_handle_headers(struct rspamd_task *task, IF_HEADER(USER_HEADER) { /* - * We must ignore User header in case of spamc, as SA has - * different meaning of this header - */ + * We must ignore User header in case of spamc, as SA has + * different meaning of this header + */ msg_debug_protocol("read user header, value: %T", hv_tok); if (!RSPAMD_TASK_IS_SPAMC(task)) { task->auth_user = rspamd_mempool_ftokdup(task->task_pool, @@ -708,6 +708,15 @@ rspamd_protocol_handle_headers(struct rspamd_task *task, task->flags |= RSPAMD_TASK_FLAG_NO_LOG; } } + IF_HEADER(LOG_TAG_HEADER) + { + msg_debug_protocol("read log-tag header, value: %T", hv_tok); + /* Ensure that a tag is valid */ + if (rspamd_fast_utf8_validate(hv_tok->begin, hv_tok->len) == 0) { + memcpy(task->task_pool->tag.uid, hv_tok->begin, + MIN(hv_tok->len, sizeof(task->task_pool->tag.uid))); + } + } break; case 'm': case 'M': @@ -752,9 +761,9 @@ rspamd_protocol_handle_headers(struct rspamd_task *task, default: msg_debug_protocol("generic header: %T", hn_tok); break; - } + } - rspamd_task_add_request_header (task, hn_tok, hv_tok); + rspamd_task_add_request_header (task, hn_tok, hv_tok); } }); /* End of kh_foreach_value */ diff --git a/src/libserver/protocol_internal.h b/src/libserver/protocol_internal.h index 7a70ccef0..e55e54851 100644 --- a/src/libserver/protocol_internal.h +++ b/src/libserver/protocol_internal.h @@ -78,6 +78,7 @@ extern "C" { #define HOSTNAME_HEADER "Hostname" #define DELIVER_TO_HEADER "Deliver-To" #define NO_LOG_HEADER "Log" +#define LOG_TAG_HEADER "Log-Tag" #define MLEN_HEADER "Message-Length" #define USER_AGENT_HEADER "User-Agent" #define MTA_TAG_HEADER "MTA-Tag" diff --git a/src/lua/lua_config.c b/src/lua/lua_config.c index f9a79eef1..717aa81ce 100644 --- a/src/lua/lua_config.c +++ b/src/lua/lua_config.c @@ -3933,6 +3933,8 @@ lua_config_get_groups(lua_State *L) lua_setfield(L, -2, "description"); lua_pushnumber(L, gr->max_score); lua_setfield(L, -2, "max_score"); + lua_pushnumber(L, gr->min_score); + lua_setfield(L, -2, "min_score"); lua_pushboolean(L, (gr->flags & RSPAMD_SYMBOL_GROUP_PUBLIC) != 0); lua_setfield(L, -2, "is_public"); /* TODO: maybe push symbols as well */ diff --git a/src/lua/lua_redis.c b/src/lua/lua_redis.c index f95abb577..d20c496ed 100644 --- a/src/lua/lua_redis.c +++ b/src/lua/lua_redis.c @@ -104,7 +104,7 @@ struct lua_redis_userdata { char *server; char log_tag[RSPAMD_LOG_ID_LEN + 1]; struct lua_redis_request_specific_userdata *specific; - double timeout; + ev_tstamp timeout; uint16_t port; uint16_t terminated; }; @@ -280,16 +280,23 @@ lua_redis_fin(void *arg) * @param code * @param ud */ +#ifdef __GNUC__ +__attribute__((format(printf, 1, 5))) +#endif static void lua_redis_push_error(const char *err, struct lua_redis_ctx *ctx, struct lua_redis_request_specific_userdata *sp_ud, - gboolean connected) + gboolean connected, + ...) { struct lua_redis_userdata *ud = sp_ud->c; struct lua_callback_state cbs; lua_State *L; + va_list ap; + va_start(ap, connected); + if (!(sp_ud->flags & (LUA_REDIS_SPECIFIC_REPLIED | LUA_REDIS_SPECIFIC_FINISHED))) { if (sp_ud->cbref != -1) { @@ -302,7 +309,7 @@ lua_redis_push_error(const char *err, lua_rawgeti(cbs.L, LUA_REGISTRYINDEX, sp_ud->cbref); /* String of error */ - lua_pushstring(cbs.L, err); + lua_pushvfstring(cbs.L, err, ap); /* Data is nil */ lua_pushnil(cbs.L); @@ -331,6 +338,8 @@ lua_redis_push_error(const char *err, lua_redis_fin(sp_ud); } } + + va_end(ap); } static void @@ -479,7 +488,7 @@ lua_redis_callback(redisAsyncContext *c, gpointer r, gpointer priv) lua_redis_push_data(reply, ctx, sp_ud); } else { - lua_redis_push_error(reply->str, ctx, sp_ud, TRUE); + lua_redis_push_error("%s", ctx, sp_ud, TRUE, reply->str); } } else { @@ -488,10 +497,10 @@ lua_redis_callback(redisAsyncContext *c, gpointer r, gpointer priv) } else { if (c->err == REDIS_ERR_IO) { - lua_redis_push_error(strerror(errno), ctx, sp_ud, TRUE); + lua_redis_push_error("%s", ctx, sp_ud, TRUE, strerror(errno)); } else { - lua_redis_push_error(c->errstr, ctx, sp_ud, TRUE); + lua_redis_push_error("%s", ctx, sp_ud, TRUE, c->errstr); } } } @@ -750,7 +759,7 @@ lua_redis_timeout(EV_P_ ev_timer *w, int revents) REDIS_RETAIN(ctx); msg_debug_lua_redis("timeout while querying redis server: %p, redis: %p", sp_ud, sp_ud->c->ctx); - lua_redis_push_error("timeout while connecting the server", ctx, sp_ud, TRUE); + lua_redis_push_error("timeout while connecting the server (%.2f sec)", ctx, sp_ud, TRUE, ud->timeout); if (sp_ud->c->ctx) { ac = sp_ud->c->ctx; diff --git a/src/plugins/fuzzy_check.c b/src/plugins/fuzzy_check.c index a035eeaae..b92177f1b 100644 --- a/src/plugins/fuzzy_check.c +++ b/src/plugins/fuzzy_check.c @@ -2299,6 +2299,8 @@ fuzzy_insert_result(struct fuzzy_client_session *session, * Otherwise `value` means error code */ + msg_debug_fuzzy_check("got reply with probability %.2f and value %.2f", + (double) rep->v1.prob, (double) rep->v1.value); nval = fuzzy_normalize(rep->v1.value, weight); if (io) { diff --git a/src/plugins/lua/once_received.lua b/src/plugins/lua/once_received.lua index 2a5552ab9..5c5ff7986 100644 --- a/src/plugins/lua/once_received.lua +++ b/src/plugins/lua/once_received.lua @@ -19,10 +19,7 @@ if confighelp then end -- 0 or 1 received: = spam - local symbol = 'ONCE_RECEIVED' -local symbol_rdns = 'RDNS_NONE' -local symbol_rdns_dnsfail = 'RDNS_DNSFAIL' local symbol_mx = 'DIRECT_TO_MX' -- Symbol for strict checks local symbol_strict = nil @@ -47,54 +44,6 @@ local function check_quantity_received (task) return not h['flags']['artificial'] end, recvh)) - local function recv_dns_cb(_, to_resolve, results, err) - if err and (err ~= 'requested record is not found' and err ~= 'no records with this name') then - rspamd_logger.errx(task, 'error looking up %s: %s', to_resolve, err) - task:insert_result(symbol_rdns_dnsfail, 1.0) - end - - if not results then - if nreceived <= 1 then - task:insert_result(symbol, 1) - -- Avoid strict symbol inserting as the remaining symbols have already - -- quote a significant weight, so a message could be rejected by just - -- this property. - --task:insert_result(symbol_strict, 1) - -- Check for MUAs - local ua = task:get_header('User-Agent') - local xm = task:get_header('X-Mailer') - if (ua or xm) then - task:insert_result(symbol_mx, 1, (ua or xm)) - end - end - task:insert_result(symbol_rdns, 1) - else - rspamd_logger.infox(task, 'source hostname has not been passed to Rspamd from MTA, ' .. - 'but we could resolve source IP address PTR %s as "%s"', - to_resolve, results[1]) - task:set_hostname(results[1]) - - if good_hosts then - for _, gh in ipairs(good_hosts) do - if string.find(results[1], gh) then - return - end - end - end - - if nreceived <= 1 then - task:insert_result(symbol, 1) - for _, h in ipairs(bad_hosts) do - if string.find(results[1], h) then - - task:insert_result(symbol_strict, 1, h) - return - end - end - end - end - end - local task_ip = task:get_ip() if ((not check_authed and task:get_user()) or @@ -110,13 +59,39 @@ local function check_quantity_received (task) local hn = task:get_hostname() -- Here we don't care about received - if (not hn) and task_ip and task_ip:is_valid() then - task:get_resolver():resolve_ptr({ task = task, - name = task_ip:to_string(), - callback = recv_dns_cb, - forced = true - }) + if not hn then + if nreceived <= 1 then + task:insert_result(symbol, 1) + -- Avoid strict symbol inserting as the remaining symbols have already + -- quote a significant weight, so a message could be rejected by just + -- this property. + --task:insert_result(symbol_strict, 1) + -- Check for MUAs + local ua = task:get_header('User-Agent') + local xm = task:get_header('X-Mailer') + if (ua or xm) then + task:insert_result(symbol_mx, 1, (ua or xm)) + end + end return + else + if good_hosts then + for _, gh in ipairs(good_hosts) do + if string.find(hn, gh) then + return + end + end + end + + if nreceived <= 1 then + task:insert_result(symbol, 1) + for _, h in ipairs(bad_hosts) do + if string.find(hn, h) then + task:insert_result(symbol_strict, 1, h) + break + end + end + end end if nreceived <= 1 then @@ -181,10 +156,6 @@ if opts then for n, v in pairs(opts) do if n == 'symbol_strict' then symbol_strict = v - elseif n == 'symbol_rdns' then - symbol_rdns = v - elseif n == 'symbol_rdns_dnsfail' then - symbol_rdns_dnsfail = v elseif n == 'bad_host' then if type(v) == 'string' then bad_hosts[1] = v @@ -207,16 +178,6 @@ if opts then end rspamd_config:register_symbol({ - name = symbol_rdns, - type = 'virtual', - parent = id - }) - rspamd_config:register_symbol({ - name = symbol_rdns_dnsfail, - type = 'virtual', - parent = id - }) - rspamd_config:register_symbol({ name = symbol_strict, type = 'virtual', parent = id diff --git a/src/rspamadm/configdump.c b/src/rspamadm/configdump.c index 167b4c891..456875cf2 100644 --- a/src/rspamadm/configdump.c +++ b/src/rspamadm/configdump.c @@ -1,5 +1,5 @@ /* - * Copyright 2023 Vsevolod Stakhov + * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -445,6 +445,9 @@ rspamadm_configdump(int argc, char **argv, const struct rspamadm_command *cmd) ucl_object_fromdouble(gr->max_score), "max_score", strlen("max_score"), false); ucl_object_insert_key(gr_ucl, + ucl_object_fromdouble(gr->min_score), + "min_score", strlen("min_score"), false); + ucl_object_insert_key(gr_ucl, ucl_object_fromstring(gr->description), "description", strlen("description"), false); diff --git a/src/rspamd_proxy.c b/src/rspamd_proxy.c index 4f08e81b9..6c8869e22 100644 --- a/src/rspamd_proxy.c +++ b/src/rspamd_proxy.c @@ -1398,7 +1398,8 @@ proxy_backend_mirror_finish_handler(struct rspamd_http_connection *conn, bk_conn->err = "cannot parse ucl"; } - msg_info_session("finished mirror connection to %s", bk_conn->name); + msg_info_session("finished mirror connection to %s; HTTP code: %d", + bk_conn->name, msg->code); rspamd_upstream_ok(bk_conn->up); proxy_backend_close_connection(bk_conn); @@ -2203,6 +2204,8 @@ proxy_client_finish_handler(struct rspamd_http_connection *conn, rspamd_http_message_remove_header(msg, "Keep-Alive"); rspamd_http_message_remove_header(msg, "Connection"); rspamd_http_message_remove_header(msg, "Key"); + rspamd_http_message_add_header_len(msg, LOG_TAG_HEADER, session->pool->tag.uid, + sizeof(session->pool->tag.uid)); proxy_open_mirror_connections(session); rspamd_http_connection_reset(session->client_conn); @@ -2210,7 +2213,10 @@ proxy_client_finish_handler(struct rspamd_http_connection *conn, proxy_send_master_message(session); } else { - msg_info_session("finished master connection"); + msg_info_session("finished master connection to %s; HTTP code: %d", + rspamd_inet_address_to_string_pretty( + rspamd_upstream_addr_cur(session->master_conn->up)), + msg->code); proxy_backend_close_connection(session->master_conn); REF_RELEASE(session); } diff --git a/test/functional/cases/001_merged/101_lua.robot b/test/functional/cases/001_merged/101_lua.robot index 2cfc03677..51d5b4b23 100644 --- a/test/functional/cases/001_merged/101_lua.robot +++ b/test/functional/cases/001_merged/101_lua.robot @@ -52,4 +52,48 @@ External Maps Simple Task Inject Url Scan File ${URL_ICS} Settings={symbols_enabled = [TEST_INJECT_URL]} - Expect Symbol TEST_INJECT_URL
\ No newline at end of file + Expect Symbol TEST_INJECT_URL + +Group Score Positive + Scan File ${MESSAGE} Settings={symbols_enabled = [GR_POSITIVE1, GR_POSITIVE2, GR_POSITIVE4, GR_POSITIVE8, GR_POSITIVE16]} + Expect Symbol With Score GR_POSITIVE1 1 + Expect Symbol With Score GR_POSITIVE2 2 + Expect Symbol With Score GR_POSITIVE4 4 + Expect Symbol With Score GR_POSITIVE8 3 + Expect Symbol With Score GR_POSITIVE16 0 + +Group Score Negative + Scan File ${MESSAGE} Settings={symbols_enabled = [GR_NEGATIVE1, GR_NEGATIVE2, GR_NEGATIVE4, GR_NEGATIVE8]} + Expect Symbol With Score GR_NEGATIVE1 -1 + Expect Symbol With Score GR_NEGATIVE2 -2 + Expect Symbol With Score GR_NEGATIVE4 -4 + Expect Symbol With Score GR_NEGATIVE8 -3 + +Group Score Mix 1 + Scan File ${MESSAGE} Settings={symbols_enabled = [GR_POSITIVE1, GR_POSITIVE2, GR_POSITIVE4, GR_POSITIVE8, GR_NEGATIVE1, GR_NEGATIVE2, GR_NEGATIVE4, GR_NEGATIVE8]} + Expect Symbol With Score GR_POSITIVE1 1 + Expect Symbol With Score GR_POSITIVE2 2 + Expect Symbol With Score GR_POSITIVE4 4 + Expect Symbol With Score GR_POSITIVE8 3 + Expect Symbol With Score GR_NEGATIVE1 -1 + Expect Symbol With Score GR_NEGATIVE2 -2 + Expect Symbol With Score GR_NEGATIVE4 -4 + Expect Symbol With Score GR_NEGATIVE8 -8 + +Group Score Mix 2 + Scan File ${MESSAGE} Settings={symbols_enabled = [GR_POSITIVE1, GR_POSITIVE2, GR_POSITIVE4, GR_POSITIVE8, GR_NEGATIVE16]} + Expect Symbol With Score GR_POSITIVE1 1 + Expect Symbol With Score GR_POSITIVE2 2 + Expect Symbol With Score GR_POSITIVE4 4 + Expect Symbol With Score GR_POSITIVE8 3 + Expect Symbol With Score GR_NEGATIVE16 -16 + +Group Score Mix 3 + Scan File ${MESSAGE} Settings={symbols_enabled = [GR_POSITIVE1, GR_NEGATIVE16]} + Expect Symbol With Score GR_POSITIVE1 1 + Expect Symbol With Score GR_NEGATIVE16 -11 + +Group Score Mix 4 + Scan File ${MESSAGE} Settings={symbols_enabled = [GR_POSITIVE16, GR_NEGATIVE16]} + Expect Symbol With Score GR_POSITIVE16 10 + Expect Symbol With Score GR_NEGATIVE16 -16
\ No newline at end of file diff --git a/test/functional/cases/550_milter_headers.robot b/test/functional/cases/550_milter_headers.robot new file mode 100644 index 000000000..80471b83c --- /dev/null +++ b/test/functional/cases/550_milter_headers.robot @@ -0,0 +1,39 @@ +*** Settings *** +Suite Setup Rspamd Setup +Suite Teardown Rspamd Teardown +Library ${RSPAMD_TESTDIR}/lib/rspamd.py +Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot +Variables ${RSPAMD_TESTDIR}/lib/vars.py + +*** Variables *** +${CONFIG} ${RSPAMD_TESTDIR}/configs/milter_headers.conf +${MESSAGE} ${RSPAMD_TESTDIR}/messages/zip.eml +${RSPAMD_SCOPE} Suite +${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat +${SETTINGS_NOSYMBOLS} {symbols_enabled = []} +${SETTINGS_TEST} {SIMPLE_TEST = 2.0, symbols_enabled = [SIMPLE_TEST]} + +*** Test Cases *** +CHECK HEADERS WITH TEST SYMBOL + Scan File ${MESSAGE} Settings=${SETTINGS_TEST} + # Check X-Virus header + Expect Removed Header X-Virus + Expect Added Header X-Virus Fires always + # Check My-Spamd-Bar header + Expect Added Header My-Spamd-Bar ++ + Do Not Expect Removed Header My-Spamd-Bar + # Check X-Spam-Level header + Expect Added Header X-Spam-Level ** + Expect Removed Header X-Spam-Level + +CHECK HEADERS WITHOUT TEST SYMBOL + Scan File ${MESSAGE} Settings=${SETTINGS_NOSYMBOLS} + # Check X-Virus header + Expect Removed Header X-Virus + Do Not Expect Added Header X-Virus + # Check My-Spamd-Bar header + Expect Added Header My-Spamd-Bar / + Do Not Expect Removed Header My-Spamd-Bar + # Check X-Spam-Level header + Do Not Expect Added Header X-Spam-Level + Expect Removed Header X-Spam-Level diff --git a/test/functional/configs/merged-local.conf b/test/functional/configs/merged-local.conf index 2d914b075..2aef274c2 100644 --- a/test/functional/configs/merged-local.conf +++ b/test/functional/configs/merged-local.conf @@ -965,6 +965,45 @@ symbols { } } +group "test" { + max_score = 10; + min_score = -10; + + symbols = { + "GR_POSITIVE1" = { + score = 1.0; + }, + "GR_POSITIVE2" = { + score = 2.0; + }, + "GR_POSITIVE4" = { + score = 4.0; + }, + "GR_POSITIVE8" = { + score = 8.0; + }, + "GR_POSITIVE16" = { + score = 16.0; + }, + + "GR_NEGATIVE1" = { + score = -1.0; + }, + "GR_NEGATIVE2" = { + score = -2.0; + }, + "GR_NEGATIVE4" = { + score = -4.0; + }, + "GR_NEGATIVE8" = { + score = -8.0; + }, + "GR_NEGATIVE16" = { + score = -16.0; + }, + } +} + worker "controller" { bind_socket = "{= env.LOCAL_ADDR =}:{= env.PORT_CONTROLLER =}"; keypair { diff --git a/test/functional/configs/merged.conf b/test/functional/configs/merged.conf index 0ee224ceb..06a34308d 100644 --- a/test/functional/configs/merged.conf +++ b/test/functional/configs/merged.conf @@ -11,6 +11,7 @@ lua = "{= env.TESTDIR =}/lua/recipients.lua" lua = "{= env.TESTDIR =}/lua/remove_result.lua" lua = "{= env.TESTDIR =}/lua/tlds.lua" lua = "{= env.TESTDIR =}/lua/inject_url.lua" +lua = "{= env.TESTDIR =}/lua/limits.lua" # 104_get_from lua = "{= env.TESTDIR =}/lua/get_from.lua" diff --git a/test/functional/configs/milter_headers.conf b/test/functional/configs/milter_headers.conf new file mode 100644 index 000000000..947bc28dd --- /dev/null +++ b/test/functional/configs/milter_headers.conf @@ -0,0 +1,24 @@ +lua = "{= env.TESTDIR =}/lua/simple.lua" + +.include(duplicate=append,priority=0) "{= env.TESTDIR =}/configs/plugins.conf" + +milter_headers { + + use = ["remove-headers", "x-spam-level", "x-spamd-bar", "x-virus"]; + + routines { + remove-headers { + headers { + "X-Spam-Level" = 0, + } + } + x-spamd-bar { + header = "My-Spamd-Bar"; + remove = null; + } + x-virus { + symbols = ["SIMPLE_TEST"]; + } + } + +} diff --git a/test/functional/lib/rspamd.robot b/test/functional/lib/rspamd.robot index 254a6f3ca..c45a19908 100644 --- a/test/functional/lib/rspamd.robot +++ b/test/functional/lib/rspamd.robot @@ -60,6 +60,28 @@ Check Rspamc Match String Should Not Contain ${subject} ${str} END +Do Not Expect Added Header + [Arguments] ${header_name} + IF 'milter' not in ${SCAN_RESULT} + RETURN + END + IF 'add_headers' not in ${SCAN_RESULT}[milter] + RETURN + END + Dictionary Should Not Contain Key ${SCAN_RESULT}[milter][add_headers] ${header_name} + ... msg=${header_name} was added + +Do Not Expect Removed Header + [Arguments] ${header_name} + IF 'milter' not in ${SCAN_RESULT} + RETURN + END + IF 'remove_headers' not in ${SCAN_RESULT}[milter] + RETURN + END + Dictionary Should Not Contain Key ${SCAN_RESULT}[milter][remove_headers] ${header_name} + ... msg=${header_name} was removed + Do Not Expect Symbol [Arguments] ${symbol} Dictionary Should Not Contain Key ${SCAN_RESULT}[symbols] ${symbol} @@ -76,10 +98,31 @@ Expect Action [Arguments] ${action} Should Be Equal ${SCAN_RESULT}[action] ${action} +Expect Added Header + [Arguments] ${header_name} ${header_value} ${pos}=-1 + Dictionary Should Contain Key ${SCAN_RESULT} milter + ... msg=milter block was not present in protocol response + Dictionary Should Contain Key ${SCAN_RESULT}[milter] add_headers + ... msg=add_headers block was not present in protocol response + Dictionary Should Contain Key ${SCAN_RESULT}[milter][add_headers] ${header_name} + ... msg=${header_name} was not added + Should Be Equal ${SCAN_RESULT}[milter][add_headers][${header_name}][value] ${header_value} + Should Be Equal as Numbers ${SCAN_RESULT}[milter][add_headers][${header_name}][order] ${pos} + Expect Email [Arguments] ${email} List Should Contain Value ${SCAN_RESULT}[emails] ${email} +Expect Removed Header + [Arguments] ${header_name} ${pos}=0 + Dictionary Should Contain Key ${SCAN_RESULT} milter + ... msg=milter block was not present in protocol response + Dictionary Should Contain Key ${SCAN_RESULT}[milter] remove_headers + ... msg=remove_headers block was not present in protocol response + Dictionary Should Contain Key ${SCAN_RESULT}[milter][remove_headers] ${header_name} + ... msg=${header_name} was not removed + Should Be Equal as Numbers ${SCAN_RESULT}[milter][remove_headers][${header_name}] ${pos} + Expect Required Score [Arguments] ${required_score} Should Be Equal As Numbers ${SCAN_RESULT}[required_score] ${required_score} diff --git a/test/functional/lua/deps.lua b/test/functional/lua/deps.lua index 6171db699..b78d3abb7 100644 --- a/test/functional/lua/deps.lua +++ b/test/functional/lua/deps.lua @@ -24,7 +24,7 @@ rspamd_config:register_virtual_symbol('TOP', 1.0, id) rspamd_config:register_symbol('DEP1', 1.0, cb_dep1) rspamd_config:register_dependency('DEP1', 'TOP') -for i = 2,10 do +for i = 2, 10 do rspamd_config:register_symbol('DEP' .. tostring(i), 1.0, cb_gen(i - 1)) rspamd_config:register_dependency('DEP' .. tostring(i), 'DEP' .. tostring(i - 1)) end diff --git a/test/functional/lua/limits.lua b/test/functional/lua/limits.lua new file mode 100644 index 000000000..52fb47fb9 --- /dev/null +++ b/test/functional/lua/limits.lua @@ -0,0 +1,22 @@ +local true_cb_gen = function() + return function() + return true + end +end + +local test_weights = { 1, 2, 4, 8, 16 } +for _, i in ipairs(test_weights) do + rspamd_config:register_symbol('GR_POSITIVE' .. tostring(i), 1.0, true_cb_gen()) + + if i > 1 then + rspamd_config:register_dependency('GR_POSITIVE' .. tostring(i), 'GR_POSITIVE' .. tostring(i / 2)) + end + + rspamd_config:register_symbol('GR_NEGATIVE' .. tostring(i), 1.0, true_cb_gen()) + + if i > 1 then + rspamd_config:register_dependency('GR_NEGATIVE' .. tostring(i), 'GR_NEGATIVE' .. tostring(i / 2)) + end +end + +rspamd_config:register_dependency('GR_NEGATIVE1', 'GR_POSITIVE16')
\ No newline at end of file |