From: Vsevolod Stakhov Date: Tue, 18 Nov 2025 14:13:27 +0000 (+0000) Subject: [Project] Use plugins registry X-Git-Tag: 3.14.1~11^2~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7a9dff8c565c3cf5a21d7c3e492d9417100c505b;p=thirdparty%2Frspamd.git [Project] Use plugins registry --- diff --git a/src/plugins/lua/aws_s3.lua b/src/plugins/lua/aws_s3.lua index 99c8cabe4c..5a4290c06a 100644 --- a/src/plugins/lua/aws_s3.lua +++ b/src/plugins/lua/aws_s3.lua @@ -22,6 +22,7 @@ local T = require "lua_shape.core" local rspamd_text = require "rspamd_text" local rspamd_http = require "rspamd_http" local rspamd_util = require "rspamd_util" +local PluginSchema = require "lua_shape.plugin_schema" local settings = { s3_bucket = nil, @@ -53,6 +54,8 @@ local settings_schema = T.table({ inline_content_limit = T.number():optional():doc({ summary = "Max inline content size before external ref" }), }):doc({ summary = "AWS S3 plugin configuration" }) +PluginSchema.register("plugins.aws_s3", settings_schema) + local function raw_data(task, nonce, queue_id) local ext, content, content_type diff --git a/src/plugins/lua/bimi.lua b/src/plugins/lua/bimi.lua index 68d09349d2..a030327397 100644 --- a/src/plugins/lua/bimi.lua +++ b/src/plugins/lua/bimi.lua @@ -19,6 +19,7 @@ local lua_util = require "lua_util" local rspamd_logger = require "rspamd_logger" local T = require "lua_shape.core" local lua_redis = require "lua_redis" +local PluginSchema = require "lua_shape.plugin_schema" local ucl = require "ucl" local lua_mime = require "lua_mime" local rspamd_http = require "rspamd_http" @@ -67,6 +68,8 @@ local settings_schema = lua_redis.enrich_schema({ }):optional():doc({ summary = "Helper read timeout (seconds)" }), }) +PluginSchema.register("plugins.bimi", settings_schema) + local function check_dmarc_policy(task) local dmarc_sym = task:get_symbol('DMARC_POLICY_ALLOW') diff --git a/src/plugins/lua/clustering.lua b/src/plugins/lua/clustering.lua index 4d7df96d8b..f427d11117 100644 --- a/src/plugins/lua/clustering.lua +++ b/src/plugins/lua/clustering.lua @@ -14,10 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. ]]-- -if confighelp then - return -end - -- Plugin for finding patterns in email flows local N = 'clustering' @@ -28,6 +24,7 @@ local lua_verdict = require "lua_verdict" local lua_redis = require "lua_redis" local lua_selectors = require "lua_selectors" local T = require "lua_shape.core" +local PluginSchema = require "lua_shape.plugin_schema" local redis_params @@ -68,6 +65,20 @@ local rule_schema = T.table({ prefix = T.string():optional():doc({ summary = "Redis key prefix" }), }):doc({ summary = "Clustering rule configuration" }) +local config_schema = lua_redis.enrich_schema({ + enabled = T.boolean():optional():doc({ summary = "Enable the plugin" }), + rules = T.table({}, { + open = true, + extra = rule_schema + }):doc({ summary = "Clustering rules keyed by name" }) +}):doc({ summary = "Clustering plugin configuration" }) + +PluginSchema.register("plugins.clustering", config_schema) + +if confighelp then + return +end + -- Redis scripts -- Queries for a cluster's data @@ -256,7 +267,6 @@ local function clusterting_idempotent_cb(task, rule) ) end -- Init part -redis_params = lua_redis.parse_redis_server('clustering') local opts = rspamd_config:get_all_opt("clustering") -- Initialization part @@ -265,6 +275,17 @@ if not (opts and type(opts) == 'table') then return end +local cfg, cfg_err = config_schema:transform(opts) +if not cfg then + rspamd_logger.errx(rspamd_config, 'invalid clustering config: %s', cfg_err) + lua_util.disable_module(N, "config") + return +end + +opts = cfg + +redis_params = lua_redis.parse_redis_server('clustering', opts) + if not redis_params then lua_util.disable_module(N, "redis") return diff --git a/src/plugins/lua/contextal.lua b/src/plugins/lua/contextal.lua index 22ef6476c6..c43991bb14 100644 --- a/src/plugins/lua/contextal.lua +++ b/src/plugins/lua/contextal.lua @@ -17,15 +17,6 @@ limitations under the License. local E = {} local N = 'contextal' -if confighelp then - return -end - -local opts = rspamd_config:get_all_opt(N) -if not opts then - return -end - local lua_redis = require "lua_redis" local lua_util = require "lua_util" local redis_cache = require "lua_cache" @@ -34,6 +25,7 @@ local rspamd_logger = require "rspamd_logger" local rspamd_util = require "rspamd_util" local T = require "lua_shape.core" local ucl = require "ucl" +local PluginSchema = require "lua_shape.plugin_schema" local cache_context, redis_params @@ -65,6 +57,17 @@ local config_schema = lua_redis.enrich_schema { read_timeout = T.number():optional():doc({ summary = "Read timeout (seconds)" }), } +PluginSchema.register("plugins.contextal", config_schema) + +if confighelp then + return +end + +local opts = rspamd_config:get_all_opt(N) +if not opts then + return +end + local settings = { action_symbol_prefix = 'CONTEXTAL_ACTION', base_url = 'http://localhost:8080', diff --git a/src/plugins/lua/external_relay.lua b/src/plugins/lua/external_relay.lua index 95d3ce2e38..6dd1e3e462 100644 --- a/src/plugins/lua/external_relay.lua +++ b/src/plugins/lua/external_relay.lua @@ -18,14 +18,11 @@ limitations under the License. external_relay plugin - sets IP/hostname from Received headers ]]-- -if confighelp then - return -end - local lua_maps = require "lua_maps" local lua_util = require "lua_util" local rspamd_logger = require "rspamd_logger" local T = require "lua_shape.core" +local PluginSchema = require "lua_shape.plugin_schema" local E = {} local N = "external_relay" @@ -92,6 +89,12 @@ local config_schema = T.table({ }):doc({ summary = "External relay rules keyed by name" }), }):doc({ summary = "External relay plugin configuration" }) +PluginSchema.register("plugins.external_relay", config_schema) + +if confighelp then + return +end + local function set_from_rcvd(task, rcvd) local rcvd_ip = rcvd.real_ip if not (rcvd_ip and rcvd_ip:is_valid()) then diff --git a/src/plugins/lua/history_redis.lua b/src/plugins/lua/history_redis.lua index a0e83bd3ba..212735f7b0 100644 --- a/src/plugins/lua/history_redis.lua +++ b/src/plugins/lua/history_redis.lua @@ -14,32 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. ]]-- -if confighelp then - rspamd_config:add_example(nil, 'history_redis', - "Store history of checks for WebUI using Redis", - [[ -redis_history { - # History key name - key_prefix = 'rs_history{{HOSTNAME}}{{COMPRESS}}'; - # Expire in seconds for inactive keys, default to 5 days - expire = 432000; - # History rows limit - nrows = 200; - # Use zstd compression when storing data in redis - compress = true; - # Obfuscate subjects for privacy - subject_privacy = false; - # Default hash-algorithm to obfuscate subject - subject_privacy_alg = 'blake2'; - # Prefix to show it's obfuscated - subject_privacy_prefix = 'obf'; - # Cut the length of the hash if desired - subject_privacy_length = 16; -} - ]]) - return -end - local rspamd_logger = require "rspamd_logger" local rspamd_util = require "rspamd_util" local lua_util = require "lua_util" @@ -47,6 +21,7 @@ local lua_redis = require "lua_redis" local fun = require "fun" local ucl = require "ucl" local T = require "lua_shape.core" +local PluginSchema = require "lua_shape.plugin_schema" local E = {} local N = "history_redis" @@ -81,6 +56,34 @@ local settings_schema = lua_redis.enrich_schema({ subject_privacy_length = T.number():optional():doc({ summary = "Hash length for obfuscated subjects" }), }) +PluginSchema.register("plugins.history_redis", settings_schema) + +if confighelp then + rspamd_config:add_example(nil, 'history_redis', + "Store history of checks for WebUI using Redis", + [[ +redis_history { + # History key name + key_prefix = 'rs_history{{HOSTNAME}}{{COMPRESS}}'; + # Expire in seconds for inactive keys, default to 5 days + expire = 432000; + # History rows limit + nrows = 200; + # Use zstd compression when storing data in redis + compress = true; + # Obfuscate subjects for privacy + subject_privacy = false; + # Default hash-algorithm to obfuscate subject + subject_privacy_alg = 'blake2'; + # Prefix to show it's obfuscated + subject_privacy_prefix = 'obf'; + # Cut the length of the hash if desired + subject_privacy_length = 16; +} + ]]) + return +end + local function process_addr(addr) if addr then return addr.addr diff --git a/src/plugins/lua/known_senders.lua b/src/plugins/lua/known_senders.lua index 0cbf3cdcf1..bc3581c5b3 100644 --- a/src/plugins/lua/known_senders.lua +++ b/src/plugins/lua/known_senders.lua @@ -23,30 +23,12 @@ local lua_util = require "lua_util" local lua_redis = require "lua_redis" local lua_maps = require "lua_maps" local rspamd_cryptobox_hash = require "rspamd_cryptobox_hash" - -if confighelp then - rspamd_config:add_example(nil, 'known_senders', - "Maintain a list of known senders using Redis", - [[ -known_senders { - # Domains to track senders - domains = "https://maps.rspamd.com/freemail/free.txt.zst"; - # Maximum number of elements - max_senders = 100000; - # Maximum time to live (when not using bloom filters) - max_ttl = 30d; - # Use bloom filters (must be enabled in Redis as a plugin) - use_bloom = false; - # Insert symbol for new senders from the specific domains - symbol_unknown = 'UNKNOWN_SENDER'; -} - ]]) - return -end +local T = require "lua_shape.core" +local PluginSchema = require "lua_shape.plugin_schema" local redis_params local settings = { - domains = {}, + domains = nil, max_senders = 100000, max_ttl = 30 * 86400, use_bloom = false, @@ -65,27 +47,54 @@ 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: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(), - use_bloom = ts.boolean:is_optional(), - 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(), -}) -]]-- + domains = lua_maps.map_schema:optional():doc({ summary = "Map of domains to track senders" }), + enabled = T.boolean():optional():doc({ summary = "Enable the plugin" }), + max_senders = T.one_of({ + T.number(), + T.transform(T.string(), tonumber) + }):optional():doc({ summary = "Maximum number of senders to keep" }), + max_ttl = T.one_of({ + T.number(), + T.transform(T.string(), lua_util.parse_time_interval) + }):optional():doc({ summary = "Maximum sender lifetime (seconds)" }), + use_bloom = T.boolean():optional():doc({ summary = "Use Redis bloom filters" }), + redis_key = T.string():optional():doc({ summary = "Redis key for known senders" }), + symbol = T.string():optional():doc({ summary = "Symbol for known senders" }), + symbol_unknown = T.string():optional():doc({ summary = "Symbol for unknown senders" }), + symbol_check_mail_global = T.string():optional():doc({ summary = "Symbol to check global replies" }), + symbol_check_mail_local = T.string():optional():doc({ summary = "Symbol to check local replies" }), + max_recipients = T.number():optional():doc({ summary = "Maximum recipients to inspect" }), + sender_prefix = T.string():optional():doc({ summary = "Redis prefix for sender reply sets" }), + sender_key_global = T.string():optional():doc({ summary = "Redis key for global replies" }), + sender_key_size = T.number():optional():doc({ summary = "Length of hashed sender keys" }), + reply_sender_privacy = T.boolean():optional():doc({ summary = "Enable reply sender privacy mode" }), + reply_sender_privacy_alg = T.string():optional():doc({ summary = "Hash algorithm for sender privacy" }), + reply_sender_privacy_prefix = T.string():optional():doc({ summary = "Prefix for hashed sender keys" }), + reply_sender_privacy_length = T.number():optional():doc({ summary = "Length of hashed sender output" }), +}):doc({ summary = "Known senders plugin configuration" }) + +PluginSchema.register("plugins.known_senders", settings_schema) + +if confighelp then + rspamd_config:add_example(nil, 'known_senders', + "Maintain a list of known senders using Redis", + [[ +known_senders { + # Domains to track senders + domains = "https://maps.rspamd.com/freemail/free.txt.zst"; + # Maximum number of elements + max_senders = 100000; + # Maximum time to live (when not using bloom filters) + max_ttl = 30d; + # Use bloom filters (must be enabled in Redis as a plugin) + use_bloom = false; + # Insert symbol for new senders from the specific domains + symbol_unknown = 'UNKNOWN_SENDER'; +} + ]]) + return +end local function make_key(input) local hash = rspamd_cryptobox_hash.create_specific('md5') @@ -367,6 +376,20 @@ 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, 'invalid %s config: %s', N, err) + lua_util.disable_module(N, "config") + return + end + settings = res + + if not settings.domains then + rspamd_logger.errx(rspamd_config, '%s requires "domains" map to be defined', N) + lua_util.disable_module(N, "config") + return + end + redis_params = lua_redis.parse_redis_server(N, opts) if redis_params then diff --git a/src/plugins/lua/milter_headers.lua b/src/plugins/lua/milter_headers.lua index bc1c4f5d8d..e13b5a9ec8 100644 --- a/src/plugins/lua/milter_headers.lua +++ b/src/plugins/lua/milter_headers.lua @@ -15,10 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. ]] -- -if confighelp then - return -end - -- A plugin that provides common header manipulations local logger = require "rspamd_logger" @@ -28,6 +24,7 @@ local lua_util = require "lua_util" local lua_maps = require "lua_maps" local lua_mime = require "lua_mime" local T = require "lua_shape.core" +local PluginSchema = require "lua_shape.plugin_schema" local E = {} local HOSTNAME = rspamd_util.get_hostname() @@ -657,6 +654,12 @@ local config_schema = T.table({ open = true }):doc({ summary = "Milter headers plugin configuration" }) +PluginSchema.register("plugins.milter_headers", config_schema) + +if confighelp then + return +end + local opts = rspamd_config:get_all_opt(N) or rspamd_config:get_all_opt('rmilter_headers') diff --git a/src/plugins/lua/neural.lua b/src/plugins/lua/neural.lua index 494ef43ef8..282f49ef5d 100644 --- a/src/plugins/lua/neural.lua +++ b/src/plugins/lua/neural.lua @@ -15,10 +15,6 @@ limitations under the License. ]] -- -if confighelp then - return -end - local fun = require "fun" local lua_redis = require "lua_redis" local lua_util = require "lua_util" @@ -30,6 +26,7 @@ local rspamd_tensor = require "rspamd_tensor" local rspamd_text = require "rspamd_text" local rspamd_util = require "rspamd_util" local T = require "lua_shape.core" +local PluginSchema = require "lua_shape.plugin_schema" -- Load providers pcall(require, "plugins/neural/providers/llm") pcall(require, "plugins/neural/providers/symbols") @@ -47,6 +44,12 @@ local redis_profile_schema = T.table({ providers_digest = T.string():optional():doc({ summary = "Providers digest" }), }):doc({ summary = "Neural network profile schema" }) +PluginSchema.register("plugins.neural.profile", redis_profile_schema) + +if confighelp then + return +end + local has_blas = rspamd_tensor.has_blas() local text_cookie = rspamd_text.cookie diff --git a/src/plugins/lua/reputation.lua b/src/plugins/lua/reputation.lua index 4511c5c1e1..e43edc3478 100644 --- a/src/plugins/lua/reputation.lua +++ b/src/plugins/lua/reputation.lua @@ -14,10 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. ]]-- -if confighelp then - return -end - -- A generic plugin for reputation handling local E = {} @@ -33,6 +29,7 @@ local lua_redis = require "lua_redis" local fun = require "fun" local lua_selectors = require "lua_selectors" local T = require "lua_shape.core" +local PluginSchema = require "lua_shape.plugin_schema" local redis_params = nil local default_expiry = 864000 -- 10 day by default @@ -848,6 +845,8 @@ local generic_selector = { idempotent = generic_reputation_idempotent -- used to set scores } +PluginSchema.register("plugins.reputation.selector.generic", generic_selector.schema) + local selectors = { ip = ip_selector, sender = ip_selector, -- Better name @@ -1203,6 +1202,13 @@ local backends = { } } +PluginSchema.register("plugins.reputation.backend.redis", backends.redis.schema) +PluginSchema.register("plugins.reputation.backend.dns", backends.dns.schema) + +if confighelp then + return +end + local function is_rule_applicable(task, rule) local ip = task:get_from_ip() if not (rule.selector.config.outbound and rule.selector.config.inbound) then