local T = require "lua_shape.core"
local lua_maps = require "lua_maps"
local lua_util = require "lua_util"
+local PluginSchema = require "lua_shape.plugin_schema"
-- Common RBL plugin definitions
}):doc({ summary = "Map of symbol names to bit numbers" })
local rule_schema_tbl = {
- content_urls = T.boolean():optional(),
- disable_monitoring = T.boolean():optional(),
- disabled = T.boolean():optional(),
- dkim = T.boolean():optional(),
- dkim_domainonly = T.boolean():optional(),
- dkim_match_from = T.boolean():optional(),
- emails = T.boolean():optional(),
- emails_delimiter = T.string():optional(),
- emails_domainonly = T.boolean():optional(),
- enabled = T.boolean():optional(),
- exclude_local = T.boolean():optional(),
- exclude_users = T.boolean():optional(),
- from = T.boolean():optional(),
- hash = T.enum({ "sha1", "sha256", "sha384", "sha512", "md5", "blake2" }):optional(),
- hash_format = T.enum({ "hex", "base32", "base64" }):optional(),
- hash_len = T.one_of({ T.integer(), T.transform(T.string(), tonumber) }):optional(),
- helo = T.boolean():optional(),
- ignore_default = T.boolean():optional(),
- ignore_defaults = T.boolean():optional(),
- ignore_url_whitelist = T.boolean():optional(),
- ignore_whitelist = T.boolean():optional(),
- ignore_whitelists = T.boolean():optional(),
- images = T.boolean():optional(),
- ipv4 = T.boolean():optional(),
- ipv6 = T.boolean():optional(),
- is_whitelist = T.boolean():optional(),
- local_exclude_ip_map = T.string():optional(),
- monitored_address = T.string():optional(),
- no_ip = T.boolean():optional(),
- process_script = T.string():optional(),
- random_monitored = T.boolean():optional(),
- rbl = T.string(),
- rdns = T.boolean():optional(),
- received = T.boolean():optional(),
- received_flags = T.array(T.string()):optional(),
- received_max_pos = T.number():optional(),
- received_min_pos = T.number():optional(),
- received_nflags = T.array(T.string()):optional(),
- replyto = T.boolean():optional(),
- requests_limit = T.one_of({ T.integer(), T.transform(T.string(), tonumber) }):optional(),
+ content_urls = T.boolean():optional()
+ :doc({ summary = "Check URLs found in message body content" }),
+ disable_monitoring = T.boolean():optional()
+ :doc({ summary = "Disable automatic monitoring/health checks for this RBL" }),
+ disabled = T.boolean():optional()
+ :doc({ summary = "Completely disable this RBL rule" }),
+ dkim = T.boolean():optional()
+ :doc({ summary = "Check DKIM signature domains against this RBL" }),
+ dkim_domainonly = T.boolean():optional()
+ :doc({ summary = "Use only the domain part of DKIM signatures for lookups" }),
+ dkim_match_from = T.boolean():optional()
+ :doc({ summary = "Only check DKIM domains that match the From header domain" }),
+ emails = T.boolean():optional()
+ :doc({ summary = "Check email addresses found in message against this RBL" }),
+ emails_delimiter = T.string():optional()
+ :doc({ summary = "Delimiter to use when constructing email-based RBL queries" }),
+ emails_domainonly = T.boolean():optional()
+ :doc({ summary = "Use only the domain part of email addresses for lookups" }),
+ enabled = T.boolean():optional()
+ :doc({ summary = "Enable this RBL rule" }),
+ exclude_local = T.boolean():optional()
+ :doc({ summary = "Skip RBL checks for locally originated messages" }),
+ exclude_users = T.boolean():optional()
+ :doc({ summary = "Skip RBL checks for authenticated users" }),
+ from = T.boolean():optional()
+ :doc({ summary = "Check the sending IP address (from SMTP envelope) against this RBL" }),
+ hash = T.enum({ "sha1", "sha256", "sha384", "sha512", "md5", "blake2" }):optional()
+ :doc({ summary = "Hash algorithm to use for RBL queries (for hash-based RBLs)" }),
+ hash_format = T.enum({ "hex", "base32", "base64" }):optional()
+ :doc({ summary = "Encoding format for hashed RBL queries" }),
+ hash_len = T.one_of({ T.integer(), T.transform(T.string(), tonumber) }):optional()
+ :doc({ summary = "Truncate hash to this many characters" }),
+ helo = T.boolean():optional()
+ :doc({ summary = "Check the HELO/EHLO hostname against this RBL" }),
+ ignore_default = T.boolean():optional()
+ :doc({ summary = "Ignore default settings for this rule (alias for ignore_defaults)" }),
+ ignore_defaults = T.boolean():optional()
+ :doc({ summary = "Ignore default settings for this rule" }),
+ ignore_url_whitelist = T.boolean():optional()
+ :doc({ summary = "Do not apply URL whitelist to this RBL" }),
+ ignore_whitelist = T.boolean():optional()
+ :doc({ summary = "Do not apply global whitelist to this RBL" }),
+ ignore_whitelists = T.boolean():optional()
+ :doc({ summary = "Do not apply any whitelists to this RBL" }),
+ images = T.boolean():optional()
+ :doc({ summary = "Check URLs of embedded images against this RBL" }),
+ ipv4 = T.boolean():optional()
+ :doc({ summary = "Enable lookups for IPv4 addresses" }),
+ ipv6 = T.boolean():optional()
+ :doc({ summary = "Enable lookups for IPv6 addresses" }),
+ is_whitelist = T.boolean():optional()
+ :doc({ summary = "Treat this RBL as a whitelist (positive result means whitelisted)" }),
+ local_exclude_ip_map = T.string():optional()
+ :doc({ summary = "Path to map file containing IPs to exclude from this RBL check" }),
+ monitored_address = T.string():optional()
+ :doc({ summary = "Specific address to use for RBL health monitoring queries" }),
+ no_ip = T.boolean():optional()
+ :doc({ summary = "Disable IP-based lookups for this RBL" }),
+ process_script = T.string():optional()
+ :doc({ summary = "Lua script to process/transform RBL query results" }),
+ random_monitored = T.boolean():optional()
+ :doc({ summary = "Use random addresses for RBL health monitoring" }),
+ rbl = T.string()
+ :doc({ summary = "The RBL zone/domain to query (required)" }),
+ rdns = T.boolean():optional()
+ :doc({ summary = "Check reverse DNS (PTR) hostname of sender IP against this RBL" }),
+ received = T.boolean():optional()
+ :doc({ summary = "Check IP addresses from Received headers against this RBL" }),
+ received_flags = T.array(T.string()):optional()
+ :doc({ summary = "Only check Received headers with these flags set" }),
+ received_max_pos = T.number():optional()
+ :doc({ summary = "Maximum position in Received header chain to check (1 = first hop)" }),
+ received_min_pos = T.number():optional()
+ :doc({ summary = "Minimum position in Received header chain to check" }),
+ received_nflags = T.array(T.string()):optional()
+ :doc({ summary = "Only check Received headers without these flags set" }),
+ replyto = T.boolean():optional()
+ :doc({ summary = "Check Reply-To header domain against this RBL" }),
+ requests_limit = T.one_of({ T.integer(), T.transform(T.string(), tonumber) }):optional()
+ :doc({ summary = "Maximum number of RBL requests per message for this rule" }),
require_symbols = T.one_of({
T.array(T.string()),
T.transform(T.string(), function(s)
return { s }
end)
- }):optional(),
- resolve_ip = T.boolean():optional(),
- return_bits = return_bits_schema:optional(),
- return_codes = return_codes_schema:optional(),
- returnbits = return_bits_schema:optional(),
- returncodes = return_codes_schema:optional(),
- returncodes_matcher = T.enum({ "equality", "glob", "luapattern", "radix", "regexp" }):optional(),
- selector = T.one_of({ { name = "string", schema = T.string() }, { name = "table", schema = T.table({}, { open = true }) } }):optional(),
- selector_flatten = T.boolean():optional(),
- symbol = T.string():optional(),
- symbols_prefixes = T.table({}, { open = true, extra = T.string() }):optional(),
- unknown = T.boolean():optional(),
- url_compose_map = lua_maps.map_schema:optional(),
- url_full_hostname = T.boolean():optional(),
- url_whitelist = lua_maps.map_schema:optional(),
- urls = T.boolean():optional(),
- whitelist = lua_maps.map_schema:optional(),
+ }):optional()
+ :doc({ summary = "Only perform RBL check if these symbols are present" }),
+ resolve_ip = T.boolean():optional()
+ :doc({ summary = "Resolve hostnames to IPs before RBL lookup" }),
+ return_bits = return_bits_schema:optional()
+ :doc({ summary = "Map symbol names to bit positions in RBL response" }),
+ return_codes = return_codes_schema:optional()
+ :doc({ summary = "Map symbol names to specific RBL return codes/IPs" }),
+ returnbits = return_bits_schema:optional()
+ :doc({ summary = "Alias for return_bits" }),
+ returncodes = return_codes_schema:optional()
+ :doc({ summary = "Alias for return_codes" }),
+ returncodes_matcher = T.enum({ "equality", "glob", "luapattern", "radix", "regexp" }):optional()
+ :doc({ summary = "Method to match return codes: equality, glob, luapattern, radix, or regexp" }),
+ selector = T.one_of({ { name = "string", schema = T.string() }, { name = "table", schema = T.table({}, { open = true }) } }):optional()
+ :doc({ summary = "Selector expression to extract custom data for RBL lookup" }),
+ selector_flatten = T.boolean():optional()
+ :doc({ summary = "Flatten selector results into individual lookups" }),
+ symbol = T.string():optional()
+ :doc({ summary = "Symbol name to register for this RBL rule" }),
+ symbols_prefixes = T.table({}, { open = true, extra = T.string() }):optional()
+ :doc({ summary = "Prefix mappings for generated symbol names" }),
+ unknown = T.boolean():optional()
+ :doc({ summary = "Check IPs with unknown/missing PTR records" }),
+ url_compose_map = lua_maps.map_schema:optional()
+ :doc({ summary = "Map to compose/rewrite URLs before RBL lookup" }),
+ url_full_hostname = T.boolean():optional()
+ :doc({ summary = "Use full hostname (not just registered domain) for URL lookups" }),
+ url_whitelist = lua_maps.map_schema:optional()
+ :doc({ summary = "Map of URLs to exclude from this RBL check" }),
+ urls = T.boolean():optional()
+ :doc({ summary = "Check URLs found in message against this RBL" }),
+ whitelist = lua_maps.map_schema:optional()
+ :doc({ summary = "Map of IPs/domains to exclude from this RBL check" }),
whitelist_exception = T.one_of({
T.array(T.string()),
T.transform(T.string(), function(s)
return { s }
end)
- }):optional(),
- checks = T.array(T.enum(lua_util.keys(check_types))):optional(),
- exclude_checks = T.array(T.enum(lua_util.keys(check_types))):optional(),
+ }):optional()
+ :doc({ summary = "Symbols that bypass the whitelist" }),
+ checks = T.array(T.enum(lua_util.keys(check_types))):optional()
+ :doc({ summary = "List of check types to enable: from, received, helo, urls, emails, dkim, rdns, etc." }),
+ exclude_checks = T.array(T.enum(lua_util.keys(check_types))):optional()
+ :doc({ summary = "List of check types to explicitly disable" }),
}
local function convert_checks(rule, name)
rule_schema_tbl[def_k:sub(#('default_') + 1)] = T.boolean():optional()
end
+local rule_schema = T.table(rule_schema_tbl):doc({ summary = "RBL rule configuration schema" })
+
+local plugin_schema = T.table({
+ enabled = T.boolean():optional()
+ :doc({ summary = "Enable or disable the RBL module" }),
+ disable_monitoring = T.boolean():optional()
+ :doc({ summary = "Disable health monitoring for all RBLs" }),
+ local_exclude_ip_map = lua_maps.map_schema:optional()
+ :doc({ summary = "Global map of IPs to exclude from all RBL checks" }),
+ default_enabled = T.boolean():optional()
+ :doc({ summary = "Default value for enabled option in rules" }),
+ default_ipv4 = T.boolean():optional()
+ :doc({ summary = "Default value for ipv4 option in rules" }),
+ default_ipv6 = T.boolean():optional()
+ :doc({ summary = "Default value for ipv6 option in rules" }),
+ default_unknown = T.boolean():optional()
+ :doc({ summary = "Default value for unknown option in rules" }),
+ default_dkim_domainonly = T.boolean():optional()
+ :doc({ summary = "Default value for dkim_domainonly option in rules" }),
+ default_emails_domainonly = T.boolean():optional()
+ :doc({ summary = "Default value for emails_domainonly option in rules" }),
+ default_exclude_users = T.boolean():optional()
+ :doc({ summary = "Default value for exclude_users option in rules" }),
+ default_exclude_local = T.boolean():optional()
+ :doc({ summary = "Default value for exclude_local option in rules" }),
+ default_no_ip = T.boolean():optional()
+ :doc({ summary = "Default value for no_ip option in rules" }),
+ default_dkim_match_from = T.boolean():optional()
+ :doc({ summary = "Default value for dkim_match_from option in rules" }),
+ default_selector_flatten = T.boolean():optional()
+ :doc({ summary = "Default value for selector_flatten option in rules" }),
+ default_received_flags = T.array(T.string()):optional()
+ :doc({ summary = "Default received flags for all rules" }),
+ default_received_nflags = T.array(T.string()):optional()
+ :doc({ summary = "Default received negative flags for all rules" }),
+}, {
+ open = true,
+ extra = rule_schema:optional()
+ :doc({ summary = "RBL rule definition keyed by rule name" }),
+}):doc({ summary = "RBL module configuration" })
+
+PluginSchema.register("plugins.rbl.rule", rule_schema)
+PluginSchema.register("plugins.rbl", plugin_schema)
+
return {
check_types = check_types,
- rule_schema = T.table(rule_schema_tbl):doc({ summary = "RBL rule configuration schema" }),
+ rule_schema = rule_schema,
default_options = default_options,
convert_checks = convert_checks,
}