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.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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) 2019, Vsevolod Stakhov <vsevolod@highsecure.ru>
  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 exports = {}
  38. local function process_func(elt, task)
  39. local matched = {}
  40. local function process_atom(atom)
  41. local rule = elt.rules[atom]
  42. local res = 0
  43. local function match_rule(val)
  44. local map_match = rule.map:get_key(val)
  45. if map_match then
  46. res = 1.0
  47. matched[rule.name] = {
  48. matched = val,
  49. value = map_match
  50. }
  51. end
  52. end
  53. local values = rule.selector(task)
  54. if values then
  55. if type(values) == 'table' then
  56. for _,val in ipairs(values) do
  57. if res == 0 then
  58. match_rule(val)
  59. end
  60. end
  61. else
  62. match_rule(values)
  63. end
  64. end
  65. return res
  66. end
  67. local res = elt.expr:process(process_atom)
  68. if res > 0 then
  69. return res,matched
  70. end
  71. return nil
  72. end
  73. --[[[
  74. -- @function lua_maps_expression.create(config, object, module_name)
  75. -- Creates a new maps combination from `object` for `module_name`.
  76. -- The input should be table with the following fields:
  77. --
  78. -- * `rules` - kv map of rules where each rule has `map` and `selector` mandatory attribute, also `type` for map type, e.g. `regexp`
  79. -- * `expression` - Rspamd expression where elements are names from `rules` field, e.g. `ip & from`
  80. --
  81. -- This function returns an object with public method `process(task)` that checks
  82. -- a task for the conditions defined in `expression` and `rules` and returns 2 values:
  83. --
  84. -- 1. value returned by an expression (e.g. 1 or 0)
  85. -- 2. an map (rule_name -> table) of matches, where each element has the following fields:
  86. -- * `matched` - selector's value
  87. -- * `value` - map's result
  88. --
  89. -- In case if `expression` is false a `nil` value is returned.
  90. -- @param {rspamd_config} cfg rspamd config
  91. -- @param {table} obj configuration table
  92. --
  93. --]]
  94. local function create(cfg, obj, module_name)
  95. if not module_name then module_name = 'lua_maps_expressions' end
  96. if not obj or not obj.rules or not obj.expression then
  97. rspamd_logger.errx(cfg, 'cannot add maps combination for module %s: required elements are missing',
  98. module_name)
  99. return nil
  100. end
  101. local ret = {
  102. process = process_func,
  103. rules = {},
  104. module_name = module_name
  105. }
  106. for name,rule in pairs(obj.rules) do
  107. local sel = lua_selectors.create_selector_closure(cfg, rule.selector)
  108. if not sel then
  109. rspamd_logger.errx(cfg, 'cannot add selector for element %s in module %s',
  110. name, module_name)
  111. end
  112. if not rule.type then
  113. -- Guess type
  114. if name:find('ip') or name:find('ipnet') then
  115. rule.type = 'radix'
  116. elseif name:find('regexp') or name:find('re_') then
  117. rule.type = 'regexp'
  118. elseif name:find('glob') then
  119. rule.type = 'regexp'
  120. else
  121. rule.type = 'set'
  122. end
  123. end
  124. local map = lua_maps.map_add_from_ucl(rule.map, rule.type,
  125. obj.description or module_name)
  126. if not map then
  127. rspamd_logger.errx(cfg, 'cannot add map for element %s in module %s',
  128. name, module_name)
  129. end
  130. if sel and map then
  131. ret.rules[name] = {
  132. selector = sel,
  133. map = map,
  134. name = name,
  135. }
  136. else
  137. return nil
  138. end
  139. end
  140. -- Now process and parse expression
  141. local function parse_atom(str)
  142. local atom = table.concat(fun.totable(fun.take_while(function(c)
  143. if string.find(', \t()><+!|&\n', c) then
  144. return false
  145. end
  146. return true
  147. end, fun.iter(str))), '')
  148. if ret.rules[atom] then
  149. return atom
  150. end
  151. rspamd_logger.errx(cfg, 'use of undefined element "%s" when parsing maps expression for %s',
  152. atom, module_name)
  153. return nil
  154. end
  155. local expr = rspamd_expression.create(obj.expression, parse_atom,
  156. rspamd_config:get_mempool())
  157. if not expr then
  158. rspamd_logger.errx(cfg, 'cannot add map expression for module %s',
  159. module_name)
  160. return nil
  161. end
  162. ret.expr = expr
  163. if obj.symbol then
  164. rspamd_config:register_symbol{
  165. type = 'virtual,ghost',
  166. name = obj.symbol,
  167. score = 0.0,
  168. }
  169. end
  170. ret.symbol = obj.symbol
  171. return ret
  172. end
  173. exports.create = create
  174. return exports