rules = {}
end
+-- Returns the list of servers a storage would be queried on, or nil when the
+-- upstreams are not resolved yet. `list_storages` reports a single `servers`
+-- field when read and write upstreams share one list, and a split pair
+-- otherwise
+local function storage_servers(storage)
+ return storage.servers or storage.read_servers
+end
+
+--[[[
+-- @function lua_fuzzy.wait_for_storages(cfg, task, names, timeout, callback)
+-- Waits until every selected fuzzy storage has at least one resolved server,
+-- then calls `callback()`. `names` is a set of rule names to wait for, or nil
+-- to wait for all of them; `callback` is called anyway once `timeout` seconds
+-- have passed so that the caller can report a proper error.
+--
+-- Server lists built from SRV records (`servers = "service=name+domain"`) or
+-- from host names that failed to resolve at configuration time are filled in
+-- asynchronously, so right after loading a configuration there is nothing to
+-- talk to yet. Workers resolve these while they run; one shot rspamadm
+-- commands have to give the event loop a chance to do it first.
+--]]
+exports.wait_for_storages = function(cfg, task, names, timeout, callback)
+ local rspamd_util = require "rspamd_util"
+ local deadline = rspamd_util.get_ticks() + (timeout or 5.0)
+
+ local function all_resolved()
+ for name, storage in pairs(rspamd_plugins.fuzzy_check.list_storages(cfg)) do
+ if not names or names[name] then
+ local servers = storage_servers(storage)
+
+ if not servers or #servers == 0 then
+ return false
+ end
+ end
+ end
+
+ return true
+ end
+
+ local function poll()
+ if all_resolved() or rspamd_util.get_ticks() >= deadline then
+ callback()
+ else
+ -- A timer cannot re-arm itself, so schedule a fresh one on each tick
+ task:add_timer(0.05, poll)
+ end
+ end
+
+ poll()
+end
+
return exports
local argparse = require "argparse"
local ansicolors = require "ansicolors"
local rspamd_logger = require "rspamd_logger"
+local lua_fuzzy = require "lua_fuzzy"
local E = {}
return task
end
-local function query_hashes(opts)
- if not opts.rule then
- print(highlight_err('-H/--hash requires an explicit rule (-r)'))
- os.exit(1)
+-- Storages can only be queried once their upstreams are known: SRV based
+-- server lists are resolved asynchronously after the configuration is loaded
+local function with_storages(opts, names, cb)
+ if opts.server then
+ -- Explicit override, the configured upstreams are not used at all
+ cb()
+ return
end
+ -- The task is only needed to drive the event loop while resolving
+ lua_fuzzy.wait_for_storages(rspamd_config, make_task(nil), names,
+ opts.timeout, cb)
+end
+
+local function query_hashes(opts)
local task = make_task(nil)
local ret, err = rspamd_plugins.fuzzy_check.check(task, print_check_result,
opts.rule, opts.timeout, opts.hash, opts.server)
load_config(opts)
if #opts.hash > 0 then
- query_hashes(opts)
+ if not opts.rule then
+ print(highlight_err('-H/--hash requires an explicit rule (-r)'))
+ os.exit(1)
+ end
+
+ with_storages(opts, { [opts.rule] = true }, function()
+ query_hashes(opts)
+ end)
+
return
end
local rules = selected_rules(opts)
- for _, fname in ipairs(opts.file) do
- process_file(opts, fname, rules)
+ local function process_all()
+ for _, fname in ipairs(opts.file) do
+ process_file(opts, fname, rules)
+ end
+ end
+
+ if opts.check then
+ -- `rules` is keyed by rule name, so it doubles as the set to wait for
+ with_storages(opts, rules, process_all)
+ else
+ process_all()
end
end
local ansicolors = require "ansicolors"
local rspamd_logger = require "rspamd_logger"
local lua_util = require "lua_util"
+local lua_fuzzy = require "lua_fuzzy"
local E = {}
load_config(opts)
- if opts.list then
- print_storages(rspamd_plugins.fuzzy_check.list_storages(rspamd_config))
- os.exit(0)
- end
-
-- Perform ping using a fake task from async stuff provided by rspamadm
local rspamd_task = require "rspamd_task"
task:set_session(rspamadm_session)
task:set_resolver(rspamadm_dns_resolver)
+ if opts.list then
+ -- The task is only needed to drive the event loop while the upstreams
+ -- that are resolved asynchronously (e.g. SRV based) are filled in
+ lua_fuzzy.wait_for_storages(rspamd_config, task, nil, opts.timeout, function()
+ print_storages(rspamd_plugins.fuzzy_check.list_storages(rspamd_config))
+ end)
+
+ return
+ end
+
local replied = 0
local results = {}
local ping_fuzzy
opts.rule, opts.timeout, opts.server)
if not ret then
- print(highlight_err('error from %s: %s', opts.server, err))
+ print(highlight_err('error from %s: %s', opts.server or opts.rule, err))
opts.number = opts.number - 1 -- To avoid issues with waiting for other replies
end
end
- if opts.flood then
- for i = 1, opts.number do
- ping_fuzzy(i)
+ local function start_pings()
+ if opts.flood then
+ for i = 1, opts.number do
+ ping_fuzzy(i)
+ end
+ else
+ ping_fuzzy(1)
end
+ end
+
+ if opts.server then
+ -- Explicit override, the configured upstreams are not used at all
+ start_pings()
else
- ping_fuzzy(1)
+ lua_fuzzy.wait_for_storages(rspamd_config, task, { [opts.rule] = true },
+ opts.timeout, start_pings)
end
end
rspamd_main->http_ctx = rspamd_http_context_create(cfg, rspamd_main->event_loop,
NULL);
+ /*
+ * Wire the upstreams library to our event loop and resolver. Without this
+ * `ups_ctx->configured` stays FALSE, so upstreams that need DNS - SRV
+ * placeholders created from `service=name+domain` and hostnames deferred
+ * after a failed config time lookup - are never resolved and the lists stay
+ * empty (e.g. `rspamadm fuzzyping` would see no servers at all).
+ *
+ * This must happen before any command loads its own configuration: the
+ * upstreams are created later, and they only self-schedule resolution in
+ * rspamd_upstream_set_active() when the context is already configured.
+ */
+ rspamd_upstreams_library_config(cfg, cfg->ups_ctx, rspamd_main->event_loop,
+ resolver->r);
+
g_log_set_default_handler(rspamd_glib_log_function, rspamd_main->logger);
g_set_printerr_handler(rspamd_glib_printerr_function);
rspamd_config_post_load(cfg,