aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libserver/cfg_rcl.cxx11
-rw-r--r--src/libserver/cfg_utils.cxx52
-rw-r--r--src/libserver/spf.c5
-rw-r--r--src/libserver/spf.h2
-rw-r--r--src/lua/lua_spf.c7
-rw-r--r--src/lua/lua_task.c22
-rw-r--r--src/plugins/lua/arc.lua12
-rw-r--r--src/plugins/lua/gpt.lua64
-rw-r--r--src/plugins/lua/known_senders.lua56
-rw-r--r--src/plugins/lua/metric_exporter.lua2
-rw-r--r--src/plugins/lua/ratelimit.lua55
-rw-r--r--src/plugins/lua/spf.lua3
-rw-r--r--src/rspamd.c4
13 files changed, 208 insertions, 87 deletions
diff --git a/src/libserver/cfg_rcl.cxx b/src/libserver/cfg_rcl.cxx
index ce3df4010..8a479fa6d 100644
--- a/src/libserver/cfg_rcl.cxx
+++ b/src/libserver/cfg_rcl.cxx
@@ -3623,7 +3623,8 @@ rspamd_config_parse_ucl(struct rspamd_config *cfg,
auto &cfg_file = cfg_file_maybe.value();
/* Try to load keyfile if available */
- rspamd::util::raii_file::open(fmt::format("{}.key", filename), O_RDONLY).map([&](const auto &keyfile) {
+ auto keyfile_name = fmt::format("{}.key", filename);
+ rspamd::util::raii_file::open(keyfile_name, O_RDONLY).map([&](const auto &keyfile) {
auto *kp_parser = ucl_parser_new(0);
if (ucl_parser_add_fd(kp_parser, keyfile.get_fd())) {
auto *kp_obj = ucl_parser_get_object(kp_parser);
@@ -3632,8 +3633,8 @@ rspamd_config_parse_ucl(struct rspamd_config *cfg,
decrypt_keypair = rspamd_keypair_from_ucl(kp_obj);
if (decrypt_keypair == nullptr) {
- msg_err_config_forced("cannot load keypair from %s.key: invalid keypair",
- filename);
+ msg_err_config_forced("cannot load keypair from %s: invalid keypair",
+ keyfile_name.c_str());
}
else {
/* Add decryption support to UCL */
@@ -3645,8 +3646,8 @@ rspamd_config_parse_ucl(struct rspamd_config *cfg,
ucl_object_unref(kp_obj);
}
else {
- msg_err_config_forced("cannot load keypair from %s.key: %s",
- filename, ucl_parser_get_error(kp_parser));
+ msg_err_config_forced("cannot load keypair from %s: %s",
+ keyfile_name.c_str(), ucl_parser_get_error(kp_parser));
}
ucl_parser_free(kp_parser);
});
diff --git a/src/libserver/cfg_utils.cxx b/src/libserver/cfg_utils.cxx
index dd85eddfb..1344bc4f9 100644
--- a/src/libserver/cfg_utils.cxx
+++ b/src/libserver/cfg_utils.cxx
@@ -57,7 +57,7 @@
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
-#include <math.h>
+#include <cmath>
#include "libserver/composites/composites.h"
#include "blas-config.h"
@@ -69,6 +69,7 @@
#include "cxx/util.hxx"
#include "frozen/unordered_map.h"
#include "frozen/string.h"
+#include "contrib/expected/expected.hpp"
#include "contrib/ankerl/unordered_dense.h"
#define DEFAULT_SCORE 10.0
@@ -135,14 +136,14 @@ struct rspamd_actions_list {
void sort()
{
std::sort(actions.begin(), actions.end(), [](const action_ptr &a1, const action_ptr &a2) -> bool {
- if (!isnan(a1->threshold) && !isnan(a2->threshold)) {
+ if (!std::isnan(a1->threshold) && !std::isnan(a2->threshold)) {
return a1->threshold < a2->threshold;
}
- if (isnan(a1->threshold) && isnan(a2->threshold)) {
+ if (std::isnan(a1->threshold) && std::isnan(a2->threshold)) {
return false;
}
- else if (isnan(a1->threshold)) {
+ else if (std::isnan(a1->threshold)) {
return true;
}
@@ -826,8 +827,7 @@ gboolean
rspamd_config_post_load(struct rspamd_config *cfg,
enum rspamd_post_load_options opts)
{
-
- auto ret = TRUE;
+ auto ret = tl::expected<void, std::string>{};
rspamd_adjust_clocks_resolution(cfg);
rspamd_logger_configure_modules(cfg->debug_modules);
@@ -867,14 +867,15 @@ rspamd_config_post_load(struct rspamd_config *cfg,
else {
if (opts & RSPAMD_CONFIG_INIT_VALIDATE) {
msg_err_config("no url_tld option has been specified");
- ret = FALSE;
+ ret = tl::make_unexpected(std::string{"no url_tld option has been specified"});
}
}
}
else {
if (access(cfg->tld_file, R_OK) == -1) {
if (opts & RSPAMD_CONFIG_INIT_VALIDATE) {
- ret = FALSE;
+ ret = tl::make_unexpected(fmt::format("cannot access tld file {}: {}",
+ cfg->tld_file, strerror(errno)));
msg_err_config("cannot access tld file %s: %s", cfg->tld_file,
strerror(errno));
}
@@ -905,13 +906,17 @@ rspamd_config_post_load(struct rspamd_config *cfg,
if (!rspamd_config_parse_log_format(cfg)) {
msg_err_config("cannot parse log format, task logging will not be available");
if (opts & RSPAMD_CONFIG_INIT_VALIDATE) {
- ret = FALSE;
+ ret = tl::make_unexpected(std::string{"cannot parse log format"});
}
}
if (opts & RSPAMD_CONFIG_INIT_SYMCACHE) {
/* Init config cache */
- ret = rspamd_symcache_init(cfg->cache) && ret;
+ auto symcache_ret = rspamd_symcache_init(cfg->cache);
+
+ if (!symcache_ret) {
+ ret = tl::make_unexpected(std::string{"symcache init failed"});
+ }
/* Init re cache */
rspamd_re_cache_init(cfg->re_cache, cfg);
@@ -928,9 +933,9 @@ rspamd_config_post_load(struct rspamd_config *cfg,
if (opts & RSPAMD_CONFIG_INIT_LIBS) {
/* Config other libraries */
- ret = rspamd_config_libs(cfg->libs_ctx, cfg) && ret;
+ auto libs_ret = rspamd_config_libs(cfg->libs_ctx, cfg);
- if (!ret) {
+ if (!libs_ret) {
msg_err_config("cannot configure libraries, fatal error");
return FALSE;
}
@@ -959,7 +964,11 @@ rspamd_config_post_load(struct rspamd_config *cfg,
" Rspamd features will be broken");
}
- ret = rspamd_symcache_validate(cfg->cache, cfg, FALSE) && ret;
+ auto val_ret = rspamd_symcache_validate(cfg->cache, cfg, FALSE);
+
+ if (!val_ret) {
+ ret = tl::make_unexpected(std::string{"symcache validation failed"});
+ }
}
if (opts & RSPAMD_CONFIG_INIT_POST_LOAD_LUA) {
@@ -970,7 +979,14 @@ rspamd_config_post_load(struct rspamd_config *cfg,
rspamd_map_preload(cfg);
}
- return ret;
+ if (ret) {
+ return true;
+ }
+ else {
+ msg_err_config("error on post-init stage: %s", ret.error().c_str());
+
+ return false;
+ }
}
struct rspamd_classifier_config *
@@ -1525,7 +1541,7 @@ rspamd_config_new_symbol(struct rspamd_config *cfg, const char *symbol,
rspamd_mempool_alloc0_type(cfg->cfg_pool, struct rspamd_symbol);
score_ptr = rspamd_mempool_alloc_type(cfg->cfg_pool, double);
- if (isnan(score)) {
+ if (std::isnan(score)) {
/* In fact, it could be defined later */
msg_debug_config("score is not defined for symbol %s, set it to zero",
symbol);
@@ -1636,7 +1652,7 @@ rspamd_config_add_symbol(struct rspamd_config *cfg,
}
if (sym_def->priority > priority &&
- (isnan(score) || !(sym_def->flags & RSPAMD_SYMBOL_FLAG_UNSCORED))) {
+ (std::isnan(score) || !(sym_def->flags & RSPAMD_SYMBOL_FLAG_UNSCORED))) {
msg_debug_config("symbol %s has been already registered with "
"priority %ud, do not override (new priority: %ud)",
symbol,
@@ -1657,7 +1673,7 @@ rspamd_config_add_symbol(struct rspamd_config *cfg,
}
else {
- if (!isnan(score)) {
+ if (!std::isnan(score)) {
msg_debug_config("symbol %s has been already registered with "
"priority %ud, override it with new priority: %ud, "
"old score: %.2f, new score: %.2f",
@@ -1997,7 +2013,7 @@ rspamd_config_action_from_ucl(struct rspamd_config *cfg,
/* TODO: add lua references support */
- if (isnan(threshold) && !(flags & RSPAMD_ACTION_NO_THRESHOLD)) {
+ if (std::isnan(threshold) && !(flags & RSPAMD_ACTION_NO_THRESHOLD)) {
msg_err_config("action %s has no threshold being set and it is not"
" a no threshold action",
act->name);
diff --git a/src/libserver/spf.c b/src/libserver/spf.c
index 32c020bf3..562222042 100644
--- a/src/libserver/spf.c
+++ b/src/libserver/spf.c
@@ -451,6 +451,9 @@ rspamd_spf_process_reference(struct spf_resolved *target,
target->flags |= RSPAMD_SPF_RESOLVED_NA;
continue;
}
+ if (cur->flags & RSPAMD_SPF_FLAG_PLUSALL) {
+ target->flags |= RSPAMD_SPF_RESOLVED_PLUSALL;
+ }
if (cur->flags & RSPAMD_SPF_FLAG_INVALID) {
/* Ignore invalid elements */
continue;
@@ -1418,7 +1421,7 @@ parse_spf_all(struct spf_record *rec, struct spf_addr *addr)
/* Disallow +all */
if (addr->mech == SPF_PASS) {
- addr->flags |= RSPAMD_SPF_FLAG_INVALID;
+ addr->flags |= RSPAMD_SPF_FLAG_PLUSALL;
msg_notice_spf("domain %s allows any SPF (+all), ignore SPF record completely",
rec->sender_domain);
}
diff --git a/src/libserver/spf.h b/src/libserver/spf.h
index cc0ee4c05..b89dc4d0e 100644
--- a/src/libserver/spf.h
+++ b/src/libserver/spf.h
@@ -77,6 +77,7 @@ typedef enum spf_action_e {
#define RSPAMD_SPF_FLAG_PERMFAIL (1u << 10u)
#define RSPAMD_SPF_FLAG_RESOLVED (1u << 11u)
#define RSPAMD_SPF_FLAG_CACHED (1u << 12u)
+#define RSPAMD_SPF_FLAG_PLUSALL (1u << 13u)
/** Default SPF limits for avoiding abuse **/
#define SPF_MAX_NESTING 10
@@ -104,6 +105,7 @@ enum rspamd_spf_resolved_flags {
RSPAMD_SPF_RESOLVED_TEMP_FAILED = (1u << 0u),
RSPAMD_SPF_RESOLVED_PERM_FAILED = (1u << 1u),
RSPAMD_SPF_RESOLVED_NA = (1u << 2u),
+ RSPAMD_SPF_RESOLVED_PLUSALL = (1u << 3u),
};
struct spf_resolved {
diff --git a/src/lua/lua_spf.c b/src/lua/lua_spf.c
index 46e72202f..850ce2120 100644
--- a/src/lua/lua_spf.c
+++ b/src/lua/lua_spf.c
@@ -89,6 +89,8 @@ lua_load_spf(lua_State *L)
lua_setfield(L, -2, "perm_fail");
lua_pushinteger(L, RSPAMD_SPF_FLAG_CACHED);
lua_setfield(L, -2, "cached");
+ lua_pushinteger(L, RSPAMD_SPF_RESOLVED_PLUSALL);
+ lua_setfield(L, -2, "plusall");
lua_setfield(L, -2, "flags");
@@ -368,6 +370,11 @@ spf_check_element(lua_State *L, struct spf_resolved *rec, struct spf_addr *addr,
lua_pushinteger(L, RSPAMD_SPF_RESOLVED_TEMP_FAILED);
lua_pushfstring(L, "%cany", spf_mech_char(addr->mech));
}
+ else if (rec->flags & RSPAMD_SPF_RESOLVED_PLUSALL) {
+ lua_pushboolean(L, false);
+ lua_pushinteger(L, RSPAMD_SPF_RESOLVED_PLUSALL);
+ lua_pushfstring(L, "%cany", spf_mech_char(addr->mech));
+ }
else {
lua_pushboolean(L, true);
lua_pushinteger(L, addr->mech);
diff --git a/src/lua/lua_task.c b/src/lua/lua_task.c
index dc41d4ab7..21e24fc45 100644
--- a/src/lua/lua_task.c
+++ b/src/lua/lua_task.c
@@ -2642,7 +2642,7 @@ struct rspamd_url_query_to_inject_cbd {
static gboolean
inject_url_query_callback(struct rspamd_url *url, gsize start_offset,
- gsize end_offset, gpointer ud)
+ gsize end_offset, gpointer ud)
{
struct rspamd_url_query_to_inject_cbd *cbd =
(struct rspamd_url_query_to_inject_cbd *) ud;
@@ -2661,7 +2661,7 @@ inject_url_query_callback(struct rspamd_url *url, gsize start_offset,
static void
inject_url_query(struct rspamd_task *task, struct rspamd_url *url,
- GPtrArray *part_urls)
+ GPtrArray *part_urls)
{
if (url->querylen > 0) {
struct rspamd_url_query_to_inject_cbd cbd;
@@ -2692,11 +2692,11 @@ lua_task_inject_url(lua_State *L)
if (lua_isuserdata(L, 3)) {
/* We also have a mime part there */
mpart = *((struct rspamd_mime_part **)
- rspamd_lua_check_udata_maybe(L, 3, rspamd_mimepart_classname));
+ rspamd_lua_check_udata_maybe(L, 3, rspamd_mimepart_classname));
}
if (task && task->message && url && url->url) {
if (rspamd_url_set_add_or_increase(MESSAGE_FIELD(task, urls), url->url, false)) {
- if(mpart && mpart->urls) {
+ if (mpart && mpart->urls) {
inject_url_query(task, url->url, mpart->urls);
}
}
@@ -4682,7 +4682,7 @@ lua_push_symbol_result(lua_State *L,
struct rspamd_symbol_option *opt;
struct rspamd_symbols_group *sym_group;
unsigned int i;
- int j = 1, table_fields_cnt = 4;
+ int j = 1, table_fields_cnt = 5;
if (!metric_res) {
metric_res = task->result;
@@ -4710,10 +4710,22 @@ lua_push_symbol_result(lua_State *L,
lua_pushstring(L, symbol);
lua_settable(L, -3);
}
+
lua_pushstring(L, "score");
lua_pushnumber(L, s->score);
lua_settable(L, -3);
+ /* Dynamic weight of the symbol */
+ if (s->sym != NULL && s->sym->score != 0) {
+ lua_pushstring(L, "weight");
+ lua_pushnumber(L, s->score / s->sym->score);
+ }
+ else {
+ lua_pushstring(L, "weight");
+ lua_pushnumber(L, 0.0);
+ }
+ lua_settable(L, -3);
+
if (s->sym && s->sym->gr) {
lua_pushstring(L, "group");
lua_pushstring(L, s->sym->gr->name);
diff --git a/src/plugins/lua/arc.lua b/src/plugins/lua/arc.lua
index ff19aef4c..90e254e78 100644
--- a/src/plugins/lua/arc.lua
+++ b/src/plugins/lua/arc.lua
@@ -635,11 +635,21 @@ local function prepare_arc_selector(task, sel)
end
end
+ local function arc_result_from_ar(ar_header)
+ ar_header = ar_header or ""
+ for k, v in string.gmatch(ar_header, "(%w+)=(%w+)") do
+ if k == 'arc' then
+ return v
+ end
+ end
+ return nil
+ end
+
if settings.reuse_auth_results then
local ar_header = task:get_header('Authentication-Results')
if ar_header then
- local arc_match = string.match(ar_header, 'arc=(%w+)')
+ local arc_match = arc_result_from_ar(ar_header)
if arc_match then
if arc_match == 'none' or arc_match == 'pass' then
diff --git a/src/plugins/lua/gpt.lua b/src/plugins/lua/gpt.lua
index ddd2f0186..823dbd045 100644
--- a/src/plugins/lua/gpt.lua
+++ b/src/plugins/lua/gpt.lua
@@ -27,13 +27,11 @@ gpt {
# Your key to access the API
api_key = "xxx";
# Model name
- model = "gpt-3.5-turbo";
+ model = "gpt-4o-mini";
# Maximum tokens to generate
max_tokens = 1000;
# Temperature for sampling
- temperature = 0.7;
- # Top p for sampling
- top_p = 0.9;
+ temperature = 0.0;
# Timeout for requests
timeout = 10s;
# Prompt for the model (use default if not set)
@@ -59,22 +57,21 @@ local fun = require "fun"
-- Exclude checks if one of those is found
local default_symbols_to_except = {
- 'BAYES_SPAM', -- We already know that it is a spam, so we can safely skip it, but no same logic for HAM!
- 'WHITELIST_SPF',
- 'WHITELIST_DKIM',
- 'WHITELIST_DMARC',
- 'FUZZY_DENIED',
- 'REPLY',
- 'BOUNCE',
+ BAYES_SPAM = 0.9, -- We already know that it is a spam, so we can safely skip it, but no same logic for HAM!
+ WHITELIST_SPF = -1,
+ WHITELIST_DKIM = -1,
+ WHITELIST_DMARC = -1,
+ FUZZY_DENIED = -1,
+ REPLY = -1,
+ BOUNCE = -1,
}
local settings = {
type = 'openai',
api_key = nil,
- model = 'gpt-3.5-turbo',
+ model = 'gpt-4o-mini',
max_tokens = 1000,
- temperature = 0.7,
- top_p = 0.9,
+ temperature = 0.0,
timeout = 10,
prompt = nil,
condition = nil,
@@ -97,17 +94,30 @@ local function default_condition(task)
local action = result.action
if action == 'reject' and result.npositive > 1 then
- return true, 'already decided as spam'
+ return false, 'already decided as spam'
end
if action == 'no action' and score < 0 then
- return true, 'negative score, already decided as ham'
+ return false, 'negative score, already decided as ham'
end
end
-- We also exclude some symbols
- for _, s in ipairs(settings.symbols_to_except) do
+ for s, required_weight in pairs(settings.symbols_to_except) do
if task:has_symbol(s) then
- return false, 'skip as "' .. s .. '" is found'
+ if required_weight > 0 then
+ -- Also check score
+ local sym = task:get_symbol(s) or E
+ -- Must exist as we checked it before with `has_symbol`
+ if sym.weight then
+ if math.abs(sym.weight) >= required_weight then
+ return false, 'skip as "' .. s .. '" is found (weight: ' .. sym.weight .. ')'
+ end
+ end
+ lua_util.debugm(N, task, 'symbol %s has weight %s, but required %s', s,
+ sym.weight, required_weight)
+ else
+ return false, 'skip as "' .. s .. '" is found'
+ end
end
end
@@ -184,6 +194,18 @@ local function default_conversion(task, input)
if type(reply) == 'table' and reply.probability then
local spam_score = tonumber(reply.probability)
+
+ if not spam_score then
+ -- Maybe we need GPT to convert GPT reply here?
+ if reply.probability == "high" then
+ spam_score = 0.9
+ elseif reply.probability == "low" then
+ spam_score = 0.1
+ else
+ rspamd_logger.infox("cannot convert to spam probability: %s", reply.probability)
+ end
+ end
+
if type(reply.usage) == 'table' then
rspamd_logger.infox(task, 'usage: %s tokens', reply.usage.total_tokens)
end
@@ -265,7 +287,7 @@ local function openai_gpt_check(task)
model = settings.model,
max_tokens = settings.max_tokens,
temperature = settings.temperature,
- top_p = settings.top_p,
+ response_format = { type = "json_object" },
messages = {
{
role = 'system',
@@ -337,8 +359,8 @@ if opts then
end
if not settings.prompt then
- settings.prompt = "You will be provided with the email message, " ..
- "and your task is to classify its probability to be spam, " ..
+ settings.prompt = "You will be provided with the email message, subject, from and url domains, " ..
+ "and your task is to evaluate the probability to be spam as number from 0 to 1, " ..
"output result as JSON with 'probability' field."
end
diff --git a/src/plugins/lua/known_senders.lua b/src/plugins/lua/known_senders.lua
index d4e966908..5cb2ddcf5 100644
--- a/src/plugins/lua/known_senders.lua
+++ b/src/plugins/lua/known_senders.lua
@@ -17,8 +17,8 @@ limitations under the License.
-- This plugin implements known senders logic for Rspamd
local rspamd_logger = require "rspamd_logger"
-local ts = (require "tableshape").types
local N = 'known_senders'
+local E = {}
local lua_util = require "lua_util"
local lua_redis = require "lua_redis"
local lua_maps = require "lua_maps"
@@ -65,8 +65,10 @@ local settings = {
reply_sender_privacy_length = 16,
}
+--[[
+XXX: please fix tableshape one day
local settings_schema = lua_redis.enrich_schema({
- domains = lua_maps.map_schema,
+ domains = lua_maps.map_schema:is_optional(),
enabled = ts.boolean:is_optional(),
max_senders = (ts.integer + ts.string / tonumber):is_optional(),
max_ttl = (ts.integer + ts.string / tonumber):is_optional(),
@@ -74,7 +76,16 @@ local settings_schema = lua_redis.enrich_schema({
redis_key = ts.string:is_optional(),
symbol = ts.string:is_optional(),
symbol_unknown = ts.string:is_optional(),
+ max_recipients = ts.integer:is_optional(),
+ sender_prefix = ts.string:is_optional(),
+ sender_key_global = ts.string:is_optional(),
+ sender_key_size = ts.integer:is_optional(),
+ reply_sender_privacy = ts.boolean:is_optional(),
+ reply_sender_privacy_alg = ts.string:is_optional(),
+ reply_sender_privacy_prefix = ts.string:is_optional(),
+ reply_sender_privacy_length = ts.integer:is_optional(),
})
+]]--
local function make_key(input)
local hash = rspamd_cryptobox_hash.create_specific('md5')
@@ -248,9 +259,10 @@ local function verify_local_replies_set(task)
return nil
end
- local replies_recipients = task:get_recipients('mime')
+ local replies_recipients = task:get_recipients('mime') or E
- local replies_sender_string = lua_util.maybe_obfuscate_string(tostring(replies_sender), settings, settings.sender_prefix)
+ local replies_sender_string = lua_util.maybe_obfuscate_string(tostring(replies_sender), settings,
+ settings.sender_prefix)
local replies_sender_key = make_key_replies(replies_sender_string:lower(), 8)
local function redis_zscore_script_cb(err, data)
@@ -274,10 +286,10 @@ local function verify_local_replies_set(task)
lua_util.debugm(N, task, 'Making redis request to local replies set')
lua_redis.exec_redis_script(zscore_script_id,
- {task = task, is_write = true},
- redis_zscore_script_cb,
- { replies_sender_key },
- replies_recipients_addrs )
+ { task = task, is_write = true },
+ redis_zscore_script_cb,
+ { replies_sender_key },
+ replies_recipients_addrs)
end
local function check_known_incoming_mail_callback(task)
@@ -289,14 +301,16 @@ local function check_known_incoming_mail_callback(task)
-- making sender key
lua_util.debugm(N, task, 'Sender: %s', replies_sender)
- local replies_sender_string = lua_util.maybe_obfuscate_string(tostring(replies_sender), settings, settings.sender_prefix)
+ local replies_sender_string = lua_util.maybe_obfuscate_string(tostring(replies_sender), settings,
+ settings.sender_prefix)
local replies_sender_key = make_key_replies(replies_sender_string:lower(), 8)
lua_util.debugm(N, task, 'Sender key: %s', replies_sender_key)
local function redis_zscore_global_cb(err, data)
if err ~= nil then
- rspamd_logger.errx(task, 'Couldn\'t find sender %s in global replies set. Ended with error: %s', replies_sender, err)
+ rspamd_logger.errx(task, 'Couldn\'t find sender %s in global replies set. Ended with error: %s', replies_sender,
+ err)
return
end
@@ -311,34 +325,28 @@ local function check_known_incoming_mail_callback(task)
-- key for global replies set
local replies_global_key = make_key_replies(settings.sender_key_global,
- settings.sender_key_size, settings.sender_prefix)
+ settings.sender_key_size, settings.sender_prefix)
-- using zscore to find sender in global set
lua_util.debugm(N, task, 'Making redis request to global replies set')
lua_redis.redis_make_request(task,
- redis_params, -- connect params
- replies_sender_key, -- hash key
- false, -- is write
- redis_zscore_global_cb, --callback
- 'ZSCORE', -- command
- { replies_global_key, replies_sender } -- arguments
+ redis_params, -- connect params
+ replies_sender_key, -- hash key
+ false, -- is write
+ redis_zscore_global_cb, --callback
+ 'ZSCORE', -- command
+ { replies_global_key, replies_sender } -- arguments
)
end
local opts = rspamd_config:get_all_opt('known_senders')
if opts then
settings = lua_util.override_defaults(settings, opts)
- local res, err = settings_schema:transform(settings)
- if not res then
- rspamd_logger.errx(rspamd_config, 'cannot parse known_senders options: %1', err)
- else
- settings = res
- end
redis_params = lua_redis.parse_redis_server(N, opts)
if redis_params then
local map_conf = settings.domains
settings.domains = lua_maps.map_add_from_ucl(settings.domains,
- 'set', 'domains to track senders from')
+ 'set', 'domains to track senders from')
if not settings.domains then
rspamd_logger.errx(rspamd_config, "couldn't add map %s, disable module",
map_conf)
diff --git a/src/plugins/lua/metric_exporter.lua b/src/plugins/lua/metric_exporter.lua
index 75885516c..3de87c157 100644
--- a/src/plugins/lua/metric_exporter.lua
+++ b/src/plugins/lua/metric_exporter.lua
@@ -117,7 +117,7 @@ local function graphite_push(kwargs)
elseif #split == 2 then
mvalue = kwargs['stats'][split[1]][split[2]]
end
- table.insert(metrics_str, string.format('%s %s %s', mname, mvalue, stamp))
+ table.insert(metrics_str, string.format('%s %s %s', mname, mvalue or 'null', stamp))
end
metrics_str = table.concat(metrics_str, '\n')
diff --git a/src/plugins/lua/ratelimit.lua b/src/plugins/lua/ratelimit.lua
index add5741e8..f3331e850 100644
--- a/src/plugins/lua/ratelimit.lua
+++ b/src/plugins/lua/ratelimit.lua
@@ -42,6 +42,8 @@ local settings = {
-- Do not check ratelimits for these recipients
whitelisted_rcpts = { 'postmaster', 'mailer-daemon' },
prefix = 'RL',
+ -- If enabled, we apply dynamic rate limiting based on the verdict
+ dynamic_rate_limit = false,
ham_factor_rate = 1.01,
spam_factor_rate = 0.99,
ham_factor_burst = 1.02,
@@ -361,17 +363,35 @@ local function make_prefix(redis_key, name, bucket)
local hash = settings.prefix ..
string.sub(rspamd_hash.create(redis_key):base32(), 1, hash_len)
-- Fill defaults
+ -- If settings.dynamic_rate_limit is false, then the default dynamic rate limits are 1.0
+ -- We always allow per-bucket overrides of the dyn rate limits
+
+ local seen_specific_dyn_rate = false
+
if not bucket.spam_factor_rate then
- bucket.spam_factor_rate = settings.spam_factor_rate
+ bucket.spam_factor_rate = settings.dynamic_rate_limit and settings.spam_factor_rate or 1.0
+ else
+ seen_specific_dyn_rate = true
end
if not bucket.ham_factor_rate then
- bucket.ham_factor_rate = settings.ham_factor_rate
+ bucket.ham_factor_rate = settings.dynamic_rate_limit and settings.ham_factor_rate or 1.0
+ else
+ seen_specific_dyn_rate = true
end
if not bucket.spam_factor_burst then
- bucket.spam_factor_burst = settings.spam_factor_burst
+ bucket.spam_factor_burst = settings.dynamic_rate_limit and settings.spam_factor_burst or 1.0
+ else
+ seen_specific_dyn_rate = true
end
if not bucket.ham_factor_burst then
- bucket.ham_factor_burst = settings.ham_factor_burst
+ bucket.ham_factor_burst = settings.dynamic_rate_limit and settings.ham_factor_burst or 1.0
+ else
+ seen_specific_dyn_rate = true
+ end
+
+ if seen_specific_dyn_rate then
+ -- Use if afterwards in case we don't use global dyn rates
+ bucket.specific_dyn_rate = true
end
return {
@@ -507,11 +527,20 @@ local function ratelimit_cb(task)
string.format('%s(%s)', lim_name, lim_key))
end
end
- rspamd_logger.infox(task,
- 'ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn); redis key: %s',
- lim_name, prefix,
- bucket.burst, bucket.rate,
- data[2], data[3], data[4], lim_key)
+
+ if bucket.dyn_rate_enabled then
+ rspamd_logger.infox(task,
+ 'ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn); redis key: %s',
+ lim_name, prefix,
+ bucket.burst, bucket.rate,
+ data[2], data[3], data[4], lim_key)
+ else
+ rspamd_logger.infox(task,
+ 'ratelimit "%s(%s)" exceeded, (%s / %s): %s (dynamic ratelimits disabled); redis key: %s',
+ lim_name, prefix,
+ bucket.burst, bucket.rate,
+ data[2], lim_key)
+ end
if not (bucket.symbol or settings.symbol) and not bucket.skip_soft_reject then
if not bucket.message then
@@ -551,13 +580,15 @@ local function ratelimit_cb(task)
bincr = 1
end
+ local dyn_rate_enabled = settings.dynamic_rate_limit or bucket.specific_dyn_rate
+
lua_util.debugm(N, task, "check limit %s:%s -> %s (%s/%s)",
value.name, pr, value.hash, bucket.burst, bucket.rate)
lua_redis.exec_redis_script(bucket_check_id,
{ key = value.hash, task = task, is_write = true },
gen_check_cb(pr, bucket, value.name, value.hash),
{ value.hash, tostring(now), tostring(rate), tostring(bucket.burst),
- tostring(settings.expire), tostring(bincr) })
+ tostring(settings.expire), tostring(bincr), tostring(dyn_rate_enabled) })
end
end
end
@@ -657,12 +688,14 @@ local function ratelimit_update_cb(task)
bincr = 1
end
+ local dyn_rate_enabled = settings.dynamic_rate_limit or bucket.specific_dyn_rate
+
lua_redis.exec_redis_script(bucket_update_id,
{ key = v.hash, task = task, is_write = true },
update_bucket_cb,
{ v.hash, tostring(now), tostring(mult_rate), tostring(mult_burst),
tostring(settings.max_rate_mult), tostring(settings.max_bucket_mult),
- tostring(settings.expire), tostring(bincr) })
+ tostring(settings.expire), tostring(bincr), tostring(dyn_rate_enabled) })
end
end
end
diff --git a/src/plugins/lua/spf.lua b/src/plugins/lua/spf.lua
index 48f3c17be..356507250 100644
--- a/src/plugins/lua/spf.lua
+++ b/src/plugins/lua/spf.lua
@@ -56,6 +56,7 @@ local symbols = {
dnsfail = "R_SPF_DNSFAIL",
permfail = "R_SPF_PERMFAIL",
na = "R_SPF_NA",
+ plusall = "R_SPF_PLUSALL",
}
local default_config = {
@@ -118,6 +119,8 @@ local function spf_check_callback(task)
local function flag_to_symbol(fl)
if bit.band(fl, rspamd_spf.flags.temp_fail) ~= 0 then
return local_config.symbols.dnsfail
+ elseif bit.band(fl, rspamd_spf.flags.plusall) ~= 0 then
+ return local_config.symbols.plusall
elseif bit.band(fl, rspamd_spf.flags.perm_fail) ~= 0 then
return local_config.symbols.permfail
elseif bit.band(fl, rspamd_spf.flags.na) ~= 0 then
diff --git a/src/rspamd.c b/src/rspamd.c
index 117f3b995..b6c361cb2 100644
--- a/src/rspamd.c
+++ b/src/rspamd.c
@@ -979,12 +979,16 @@ load_rspamd_config(struct rspamd_main *rspamd_main,
if (init_modules) {
if (!rspamd_init_filters(cfg, reload, false)) {
+ msg_err_main("init filters failed");
+
return FALSE;
}
}
/* Do post-load actions */
if (!rspamd_config_post_load(cfg, opts)) {
+ msg_err_main("post load failed");
+
return FALSE;
}
}