aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVsevolod Stakhov <vsevolod@rspamd.com>2024-08-07 14:27:49 +0600
committerGitHub <noreply@github.com>2024-08-07 14:27:49 +0600
commitdc808aac88771bb6f95571a7ac102e853b9c45a8 (patch)
tree192b044de37f52ed2ef50638922b42d4ed155e13
parent6a97bdd201686503654a434fd2624db89657aaa1 (diff)
parent406f694d893c3d4e1617b66cdf87bc22a1b1beb7 (diff)
downloadrspamd-dc808aac88771bb6f95571a7ac102e853b9c45a8.tar.gz
rspamd-dc808aac88771bb6f95571a7ac102e853b9c45a8.zip
Merge branch 'master' into master
-rw-r--r--conf/groups.conf7
-rw-r--r--conf/modules.d/once_received.conf3
-rw-r--r--conf/modules.d/rbl.conf26
-rw-r--r--conf/scores.d/headers_group.conf8
-rw-r--r--conf/scores.d/hfilter_group.conf8
-rw-r--r--conf/scores.d/rbl_group.conf61
-rw-r--r--lualib/lua_redis.lua214
-rw-r--r--rules/misc.lua56
-rw-r--r--src/libmime/scan_result.c48
-rw-r--r--src/libserver/cfg_file.h1
-rw-r--r--src/libserver/cfg_rcl.cxx18
-rw-r--r--src/libserver/cfg_utils.cxx2
-rw-r--r--src/libserver/milter.c8
-rw-r--r--src/libserver/protocol.c19
-rw-r--r--src/libserver/protocol_internal.h1
-rw-r--r--src/lua/lua_config.c2
-rw-r--r--src/lua/lua_redis.c23
-rw-r--r--src/plugins/fuzzy_check.c2
-rw-r--r--src/plugins/lua/once_received.lua103
-rw-r--r--src/rspamadm/configdump.c5
-rw-r--r--src/rspamd_proxy.c10
-rw-r--r--test/functional/cases/001_merged/101_lua.robot46
-rw-r--r--test/functional/cases/550_milter_headers.robot39
-rw-r--r--test/functional/configs/merged-local.conf39
-rw-r--r--test/functional/configs/merged.conf1
-rw-r--r--test/functional/configs/milter_headers.conf24
-rw-r--r--test/functional/lib/rspamd.robot43
-rw-r--r--test/functional/lua/deps.lua2
-rw-r--r--test/functional/lua/limits.lua22
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