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.

force_actions.lua 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. --[[
  2. Copyright (c) 2017, Andrew Lewis <nerf@judo.za.org>
  3. Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com>
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. ]]--
  14. -- A plugin that forces actions
  15. if confighelp then
  16. return
  17. end
  18. local E = {}
  19. local N = 'force_actions'
  20. local selector_cache = {}
  21. local fun = require "fun"
  22. local lua_util = require "lua_util"
  23. local rspamd_cryptobox_hash = require "rspamd_cryptobox_hash"
  24. local rspamd_expression = require "rspamd_expression"
  25. local rspamd_logger = require "rspamd_logger"
  26. local lua_selectors = require "lua_selectors"
  27. -- Params table fields:
  28. -- expr, act, pool, message, subject, raction, honor, limit, flags
  29. local function gen_cb(params)
  30. local function parse_atom(str)
  31. local atom = table.concat(fun.totable(fun.take_while(function(c)
  32. if string.find(', \t()><+!|&\n', c, 1, true) then
  33. return false
  34. end
  35. return true
  36. end, fun.iter(str))), '')
  37. return atom
  38. end
  39. local function process_atom(atom, task)
  40. local f_ret = task:has_symbol(atom)
  41. if f_ret then
  42. f_ret = math.abs(task:get_symbol(atom)[1].score)
  43. if f_ret < 0.001 then
  44. -- Adjust some low score to distinguish from pure zero
  45. f_ret = 0.001
  46. end
  47. return f_ret
  48. end
  49. return 0
  50. end
  51. local e, err = rspamd_expression.create(params.expr, {parse_atom, process_atom}, params.pool)
  52. if err then
  53. rspamd_logger.errx(rspamd_config, 'Couldnt create expression [%1]: %2', params.expr, err)
  54. return
  55. end
  56. return function(task)
  57. local function process_message_selectors(repl, selector_expr)
  58. -- create/reuse selector to extract value for this placeholder
  59. local selector = selector_cache[selector_expr]
  60. if not selector then
  61. selector_cache[selector_expr] = lua_selectors.create_selector_closure(rspamd_config, selector_expr, '', true)
  62. selector = selector_cache[selector_expr]
  63. if not selector then
  64. rspamd_logger.errx(task, 'could not create selector [%1]', selector_expr)
  65. return "((could not create selector))"
  66. end
  67. end
  68. local extracted = selector(task)
  69. if extracted then
  70. if type(extracted) == 'table' then
  71. extracted = table.concat(extracted, ',')
  72. end
  73. else
  74. rspamd_logger.errx(task, 'could not extract value with selector [%1]', selector_expr)
  75. extracted = '((error extracting value))'
  76. end
  77. return extracted
  78. end
  79. local cact = task:get_metric_action()
  80. if not params.message and not params.subject and params.act and cact == params.act then
  81. return false
  82. end
  83. if params.honor and params.honor[cact] then
  84. return false
  85. elseif params.raction and not params.raction[cact] then
  86. return false
  87. end
  88. local ret = e:process(task)
  89. lua_util.debugm(N, task, "expression %s returned %s", params.expr, ret)
  90. if (not params.limit and ret > 0) or (ret > (params.limit or 0)) then
  91. if params.subject then
  92. task:set_metric_subject(params.subject)
  93. end
  94. local flags = params.flags or ""
  95. if type(params.message) == 'string' then
  96. -- process selector expressions in the message
  97. local message = string.gsub(params.message, '(${(.-)})', process_message_selectors)
  98. task:set_pre_result{action = params.act, message = message, module = N, flags = flags}
  99. else
  100. task:set_pre_result{action = params.act, module = N, flags = flags}
  101. end
  102. return true, params.act
  103. end
  104. end, e:atoms()
  105. end
  106. local function configure_module()
  107. local opts = rspamd_config:get_all_opt(N)
  108. if not opts then
  109. return false
  110. end
  111. if type(opts.actions) == 'table' then
  112. rspamd_logger.warnx(rspamd_config, 'Processing legacy config')
  113. for action, expressions in pairs(opts.actions) do
  114. if type(expressions) == 'table' then
  115. for _, expr in ipairs(expressions) do
  116. local message, subject
  117. if type(expr) == 'table' then
  118. subject = expr[3]
  119. message = expr[2]
  120. expr = expr[1]
  121. else
  122. message = (opts.messages or E)[expr]
  123. end
  124. if type(expr) == 'string' then
  125. -- expr, act, pool, message, subject, raction, honor, limit, flags
  126. local cb, atoms = gen_cb{expr = expr,
  127. act = action,
  128. pool = rspamd_config:get_mempool(),
  129. message = message,
  130. subject = subject}
  131. if cb and atoms then
  132. local h = rspamd_cryptobox_hash.create()
  133. h:update(expr)
  134. local name = 'FORCE_ACTION_' .. string.upper(string.sub(h:hex(), 1, 12))
  135. rspamd_config:register_symbol({
  136. type = 'normal',
  137. name = name,
  138. callback = cb,
  139. flags = 'empty',
  140. })
  141. for _, a in ipairs(atoms) do
  142. rspamd_config:register_dependency(name, a)
  143. end
  144. rspamd_logger.infox(rspamd_config, 'Registered symbol %1 <%2> with dependencies [%3]',
  145. name, expr, table.concat(atoms, ','))
  146. end
  147. end
  148. end
  149. end
  150. end
  151. elseif type(opts.rules) == 'table' then
  152. for name, sett in pairs(opts.rules) do
  153. local action = sett.action
  154. local expr = sett.expression
  155. if action and expr then
  156. local flags = {}
  157. if sett.least then table.insert(flags, "least") end
  158. if sett.process_all then table.insert(flags, "process_all") end
  159. local raction = lua_util.list_to_hash(sett.require_action)
  160. local honor = lua_util.list_to_hash(sett.honor_action)
  161. local cb, atoms = gen_cb{expr = expr,
  162. act = action,
  163. pool = rspamd_config:get_mempool(),
  164. message = sett.message,
  165. subject = sett.subject,
  166. raction = raction,
  167. honor = honor,
  168. limit = sett.limit,
  169. flags = table.concat(flags, ',')}
  170. if cb and atoms then
  171. local t = {}
  172. if (raction or honor) then
  173. t.type = 'postfilter'
  174. t.priority = lua_util.symbols_priorities.high
  175. else
  176. t.type = 'normal'
  177. if not sett.least then
  178. t.augmentations = {'passthrough', 'important'}
  179. end
  180. end
  181. t.name = 'FORCE_ACTION_' .. name
  182. t.callback = cb
  183. t.flags = 'empty, ignore_passthrough'
  184. rspamd_config:register_symbol(t)
  185. if t.type == 'normal' then
  186. for _, a in ipairs(atoms) do
  187. rspamd_config:register_dependency(t.name, a)
  188. end
  189. rspamd_logger.infox(rspamd_config, 'Registered symbol %1 <%2> with dependencies [%3]',
  190. t.name, expr, table.concat(atoms, ','))
  191. else
  192. rspamd_logger.infox(rspamd_config, 'Registered symbol %1 <%2> as postfilter', t.name, expr)
  193. end
  194. end
  195. end
  196. end
  197. end
  198. end
  199. configure_module()