You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. --[[
  2. Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com>
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ]]--
  13. local ts = require("tableshape").types
  14. local lua_maps = require "lua_maps"
  15. local lua_util = require "lua_util"
  16. -- Common RBL plugin definitions
  17. local check_types = {
  18. from = {
  19. connfilter = true,
  20. },
  21. received = {},
  22. helo = {
  23. connfilter = true,
  24. },
  25. urls = {},
  26. content_urls = {},
  27. numeric_urls = {},
  28. emails = {},
  29. replyto = {},
  30. dkim = {},
  31. rdns = {
  32. connfilter = true,
  33. },
  34. selector = {
  35. require_argument = true,
  36. },
  37. }
  38. local default_options = {
  39. ['default_enabled'] = true,
  40. ['default_ipv4'] = true,
  41. ['default_ipv6'] = true,
  42. ['default_unknown'] = false,
  43. ['default_dkim_domainonly'] = true,
  44. ['default_emails_domainonly'] = false,
  45. ['default_exclude_users'] = false,
  46. ['default_exclude_local'] = true,
  47. ['default_no_ip'] = false,
  48. ['default_dkim_match_from'] = false,
  49. ['default_selector_flatten'] = true,
  50. }
  51. local return_codes_schema = ts.map_of(
  52. ts.string / string.upper, -- Symbol name
  53. (
  54. ts.array_of(ts.string) +
  55. (ts.string / function(s)
  56. return { s }
  57. end) -- List of IP patterns
  58. )
  59. )
  60. local return_bits_schema = ts.map_of(
  61. ts.string / string.upper, -- Symbol name
  62. (
  63. ts.array_of(ts.number + ts.string / tonumber) +
  64. (ts.string / function(s)
  65. return { tonumber(s) }
  66. end) +
  67. (ts.number / function(s)
  68. return { s }
  69. end)
  70. )
  71. )
  72. local rule_schema_tbl = {
  73. content_urls = ts.boolean:is_optional(),
  74. disable_monitoring = ts.boolean:is_optional(),
  75. disabled = ts.boolean:is_optional(),
  76. dkim = ts.boolean:is_optional(),
  77. dkim_domainonly = ts.boolean:is_optional(),
  78. dkim_match_from = ts.boolean:is_optional(),
  79. emails = ts.boolean:is_optional(),
  80. emails_delimiter = ts.string:is_optional(),
  81. emails_domainonly = ts.boolean:is_optional(),
  82. enabled = ts.boolean:is_optional(),
  83. exclude_local = ts.boolean:is_optional(),
  84. exclude_users = ts.boolean:is_optional(),
  85. from = ts.boolean:is_optional(),
  86. hash = ts.one_of { "sha1", "sha256", "sha384", "sha512", "md5", "blake2" }:is_optional(),
  87. hash_format = ts.one_of { "hex", "base32", "base64" }:is_optional(),
  88. hash_len = (ts.integer + ts.string / tonumber):is_optional(),
  89. helo = ts.boolean:is_optional(),
  90. ignore_default = ts.boolean:is_optional(), -- alias
  91. ignore_defaults = ts.boolean:is_optional(),
  92. ignore_url_whitelist = ts.boolean:is_optional(),
  93. ignore_whitelist = ts.boolean:is_optional(),
  94. ignore_whitelists = ts.boolean:is_optional(), -- alias
  95. images = ts.boolean:is_optional(),
  96. ipv4 = ts.boolean:is_optional(),
  97. ipv6 = ts.boolean:is_optional(),
  98. is_whitelist = ts.boolean:is_optional(),
  99. local_exclude_ip_map = ts.string:is_optional(),
  100. monitored_address = ts.string:is_optional(),
  101. no_ip = ts.boolean:is_optional(),
  102. process_script = ts.string:is_optional(),
  103. random_monitored = ts.boolean:is_optional(),
  104. rbl = ts.string,
  105. rdns = ts.boolean:is_optional(),
  106. received = ts.boolean:is_optional(),
  107. received_flags = ts.array_of(ts.string):is_optional(),
  108. received_max_pos = ts.number:is_optional(),
  109. received_min_pos = ts.number:is_optional(),
  110. received_nflags = ts.array_of(ts.string):is_optional(),
  111. replyto = ts.boolean:is_optional(),
  112. requests_limit = (ts.integer + ts.string / tonumber):is_optional(),
  113. require_symbols = (
  114. ts.array_of(ts.string) + (ts.string / function(s)
  115. return { s }
  116. end)
  117. ):is_optional(),
  118. resolve_ip = ts.boolean:is_optional(),
  119. return_bits = return_bits_schema:is_optional(),
  120. return_codes = return_codes_schema:is_optional(),
  121. returnbits = return_bits_schema:is_optional(),
  122. returncodes = return_codes_schema:is_optional(),
  123. returncodes_matcher = ts.one_of { "equality", "glob", "luapattern", "radix", "regexp" }:is_optional(),
  124. selector = ts.one_of { ts.string, ts.table }:is_optional(),
  125. selector_flatten = ts.boolean:is_optional(),
  126. symbol = ts.string:is_optional(),
  127. symbols_prefixes = ts.map_of(ts.string, ts.string):is_optional(),
  128. unknown = ts.boolean:is_optional(),
  129. url_compose_map = lua_maps.map_schema:is_optional(),
  130. url_full_hostname = ts.boolean:is_optional(),
  131. url_whitelist = lua_maps.map_schema:is_optional(),
  132. urls = ts.boolean:is_optional(),
  133. whitelist = lua_maps.map_schema:is_optional(),
  134. whitelist_exception = (
  135. ts.array_of(ts.string) + (ts.string / function(s)
  136. return { s }
  137. end)
  138. ):is_optional(),
  139. checks = ts.array_of(ts.one_of(lua_util.keys(check_types))):is_optional(),
  140. exclude_checks = ts.array_of(ts.one_of(lua_util.keys(check_types))):is_optional(),
  141. }
  142. local function convert_checks(rule, name)
  143. local rspamd_logger = require "rspamd_logger"
  144. if rule.checks then
  145. local all_connfilter = true
  146. local exclude_checks = lua_util.list_to_hash(rule.exclude_checks or {})
  147. for _, check in ipairs(rule.checks) do
  148. if not exclude_checks[check] then
  149. local check_type = check_types[check]
  150. if check_type.require_argument then
  151. if not rule[check] then
  152. rspamd_logger.errx(rspamd_config, 'rbl rule %s has check %s which requires an argument',
  153. name, check)
  154. return nil
  155. end
  156. end
  157. rule[check] = check_type
  158. if not check_type.connfilter then
  159. all_connfilter = false
  160. end
  161. if not check_type then
  162. rspamd_logger.errx(rspamd_config, 'rbl rule %s has invalid check type: %s',
  163. name, check)
  164. return nil
  165. end
  166. else
  167. rspamd_logger.infox(rspamd_config, 'disable check %s in %s: excluded explicitly',
  168. check, name)
  169. end
  170. end
  171. rule.connfilter = all_connfilter
  172. end
  173. -- Now check if we have any check enabled at all
  174. local check_found = false
  175. for k, _ in pairs(check_types) do
  176. if type(rule[k]) ~= 'nil' then
  177. check_found = true
  178. break
  179. end
  180. end
  181. if not check_found then
  182. -- Enable implicit `from` check to allow upgrade
  183. rspamd_logger.warnx(rspamd_config, 'rbl rule %s has no check enabled, enable default `from` check',
  184. name)
  185. rule.from = true
  186. end
  187. if rule.returncodes and not rule.returncodes_matcher then
  188. for _, v in pairs(rule.returncodes) do
  189. for _, e in ipairs(v) do
  190. if e:find('[%%%[]') then
  191. rspamd_logger.warn(rspamd_config, 'implicitly enabling luapattern returncodes_matcher for rule %s', name)
  192. rule.returncodes_matcher = 'luapattern'
  193. break
  194. end
  195. end
  196. if rule.returncodes_matcher then
  197. break
  198. end
  199. end
  200. end
  201. return rule
  202. end
  203. -- Add default boolean flags to the schema
  204. for def_k, _ in pairs(default_options) do
  205. rule_schema_tbl[def_k:sub(#('default_') + 1)] = ts.boolean:is_optional()
  206. end
  207. return {
  208. check_types = check_types,
  209. rule_schema = ts.shape(rule_schema_tbl),
  210. default_options = default_options,
  211. convert_checks = convert_checks,
  212. }