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.

lua_maps_expressions.lua 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. --[[[
  2. -- @module lua_maps_expressions
  3. -- This module contains routines to combine maps, selectors and expressions
  4. -- in a generic framework
  5. @example
  6. whitelist_ip_from = {
  7. rules {
  8. ip {
  9. selector = "ip";
  10. map = "/path/to/whitelist_ip.map";
  11. }
  12. from {
  13. selector = "from(smtp)";
  14. map = "/path/to/whitelist_from.map";
  15. }
  16. }
  17. expression = "ip & from";
  18. }
  19. --]]
  20. --[[
  21. Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com>
  22. Licensed under the Apache License, Version 2.0 (the "License");
  23. you may not use this file except in compliance with the License.
  24. You may obtain a copy of the License at
  25. http://www.apache.org/licenses/LICENSE-2.0
  26. Unless required by applicable law or agreed to in writing, software
  27. distributed under the License is distributed on an "AS IS" BASIS,
  28. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29. See the License for the specific language governing permissions and
  30. limitations under the License.
  31. ]]--
  32. local lua_selectors = require "lua_selectors"
  33. local lua_maps = require "lua_maps"
  34. local rspamd_expression = require "rspamd_expression"
  35. local rspamd_logger = require "rspamd_logger"
  36. local fun = require "fun"
  37. local ts = require("tableshape").types
  38. local exports = {}
  39. local function process_func(elt, task)
  40. local matched = {}
  41. local function process_atom(atom)
  42. local rule = elt.rules[atom]
  43. local res = 0
  44. local function match_rule(val)
  45. local map_match = rule.map:get_key(val)
  46. if map_match then
  47. res = 1.0
  48. matched[rule.name] = {
  49. matched = val,
  50. value = map_match
  51. }
  52. end
  53. end
  54. local values = rule.selector(task)
  55. if values then
  56. if type(values) == 'table' then
  57. for _, val in ipairs(values) do
  58. if res == 0 then
  59. match_rule(val)
  60. end
  61. end
  62. else
  63. match_rule(values)
  64. end
  65. end
  66. return res
  67. end
  68. local res = elt.expr:process(process_atom)
  69. if res > 0 then
  70. return res, matched
  71. end
  72. return nil
  73. end
  74. exports.schema = ts.shape {
  75. expression = ts.string,
  76. rules = ts.array_of(
  77. ts.shape {
  78. selector = ts.string,
  79. map = lua_maps.map_schema,
  80. }
  81. )
  82. }
  83. --[[[
  84. -- @function lua_maps_expression.create(config, object, module_name)
  85. -- Creates a new maps combination from `object` for `module_name`.
  86. -- The input should be table with the following fields:
  87. --
  88. -- * `rules` - kv map of rules where each rule has `map` and `selector` mandatory attribute, also `type` for map type, e.g. `regexp`
  89. -- * `expression` - Rspamd expression where elements are names from `rules` field, e.g. `ip & from`
  90. --
  91. -- This function returns an object with public method `process(task)` that checks
  92. -- a task for the conditions defined in `expression` and `rules` and returns 2 values:
  93. --
  94. -- 1. value returned by an expression (e.g. 1 or 0)
  95. -- 2. an map (rule_name -> table) of matches, where each element has the following fields:
  96. -- * `matched` - selector's value
  97. -- * `value` - map's result
  98. --
  99. -- In case if `expression` is false a `nil` value is returned.
  100. -- @param {rspamd_config} cfg rspamd config
  101. -- @param {table} obj configuration table
  102. --
  103. --]]
  104. local function create(cfg, obj, module_name)
  105. if not module_name then
  106. module_name = 'lua_maps_expressions'
  107. end
  108. if not obj or not obj.rules or not obj.expression then
  109. rspamd_logger.errx(cfg, 'cannot add maps combination for module %s: required elements are missing',
  110. module_name)
  111. return nil
  112. end
  113. local ret = {
  114. process = process_func,
  115. rules = {},
  116. module_name = module_name
  117. }
  118. for name, rule in pairs(obj.rules) do
  119. local sel = lua_selectors.create_selector_closure(cfg, rule.selector)
  120. if not sel then
  121. rspamd_logger.errx(cfg, 'cannot add selector for element %s in module %s',
  122. name, module_name)
  123. end
  124. if not rule.type then
  125. -- Guess type
  126. if name:find('ip') or name:find('ipnet') then
  127. rule.type = 'radix'
  128. elseif name:find('regexp') or name:find('re_') then
  129. rule.type = 'regexp'
  130. elseif name:find('glob') then
  131. rule.type = 'regexp'
  132. else
  133. rule.type = 'set'
  134. end
  135. end
  136. local map = lua_maps.map_add_from_ucl(rule.map, rule.type,
  137. obj.description or module_name)
  138. if not map then
  139. rspamd_logger.errx(cfg, 'cannot add map for element %s in module %s',
  140. name, module_name)
  141. end
  142. if sel and map then
  143. ret.rules[name] = {
  144. selector = sel,
  145. map = map,
  146. name = name,
  147. }
  148. else
  149. return nil
  150. end
  151. end
  152. -- Now process and parse expression
  153. local function parse_atom(str)
  154. local atom = table.concat(fun.totable(fun.take_while(function(c)
  155. if string.find(', \t()><+!|&\n', c, 1, true) then
  156. return false
  157. end
  158. return true
  159. end, fun.iter(str))), '')
  160. if ret.rules[atom] then
  161. return atom
  162. end
  163. rspamd_logger.errx(cfg, 'use of undefined element "%s" when parsing maps expression for %s',
  164. atom, module_name)
  165. return nil
  166. end
  167. local expr = rspamd_expression.create(obj.expression, parse_atom,
  168. rspamd_config:get_mempool())
  169. if not expr then
  170. rspamd_logger.errx(cfg, 'cannot add map expression for module %s',
  171. module_name)
  172. return nil
  173. end
  174. ret.expr = expr
  175. if obj.symbol then
  176. rspamd_config:register_symbol {
  177. type = 'virtual,ghost',
  178. name = obj.symbol,
  179. score = 0.0,
  180. }
  181. end
  182. ret.symbol = obj.symbol
  183. return ret
  184. end
  185. exports.create = create
  186. return exports