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_settings.lua 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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. --[[[
  14. -- @module lua_settings
  15. -- This module contains internal helpers for the settings infrastructure in Rspamd
  16. -- More details at https://rspamd.com/doc/configuration/settings.html
  17. --]]
  18. local exports = {}
  19. local known_ids = {}
  20. local post_init_added = false
  21. local post_init_performed = false
  22. local all_symbols
  23. local default_symbols
  24. local fun = require "fun"
  25. local lua_util = require "lua_util"
  26. local rspamd_logger = require "rspamd_logger"
  27. local function register_settings_cb(from_postload)
  28. if not post_init_performed then
  29. all_symbols = rspamd_config:get_symbols()
  30. default_symbols = fun.totable(fun.filter(function(_, v)
  31. return not v.allowed_ids or #v.allowed_ids == 0 or v.flags.explicit_disable
  32. end,all_symbols))
  33. local explicit_symbols = lua_util.keys(fun.filter(function(k, v)
  34. return v.flags.explicit_disable
  35. end, all_symbols))
  36. local symnames = lua_util.list_to_hash(lua_util.keys(all_symbols))
  37. for _,set in pairs(known_ids) do
  38. local s = set.settings.apply or {}
  39. set.symbols = lua_util.shallowcopy(symnames)
  40. local enabled_symbols = {}
  41. local seen_enabled = false
  42. local disabled_symbols = {}
  43. local seen_disabled = false
  44. -- Enabled map
  45. if s.symbols_enabled then
  46. -- Remove all symbols from set.symbols aside of explicit_disable symbols
  47. set.symbols = lua_util.list_to_hash(explicit_symbols)
  48. seen_enabled = true
  49. for _,sym in ipairs(s.symbols_enabled) do
  50. enabled_symbols[sym] = true
  51. set.symbols[sym] = true
  52. end
  53. end
  54. if s.groups_enabled then
  55. seen_enabled = true
  56. for _,gr in ipairs(s.groups_enabled) do
  57. local syms = rspamd_config:get_group_symbols(gr)
  58. if syms then
  59. for _,sym in ipairs(syms) do
  60. enabled_symbols[sym] = true
  61. set.symbols[sym] = true
  62. end
  63. end
  64. end
  65. end
  66. -- Disabled map
  67. if s.symbols_disabled then
  68. seen_disabled = true
  69. for _,sym in ipairs(s.symbols_disabled) do
  70. disabled_symbols[sym] = true
  71. set.symbols[sym] = false
  72. end
  73. end
  74. if s.groups_disabled then
  75. seen_disabled = true
  76. for _,gr in ipairs(s.groups_disabled) do
  77. local syms = rspamd_config:get_group_symbols(gr)
  78. if syms then
  79. for _,sym in ipairs(syms) do
  80. disabled_symbols[sym] = true
  81. set.symbols[sym] = false
  82. end
  83. end
  84. end
  85. end
  86. -- Deal with complexity to avoid mess in C
  87. if not seen_enabled then enabled_symbols = nil end
  88. if not seen_disabled then disabled_symbols = nil end
  89. if enabled_symbols or disabled_symbols then
  90. -- Specify what symbols are really enabled for this settings id
  91. set.has_specific_symbols = true
  92. end
  93. rspamd_config:register_settings_id(set.name, enabled_symbols, disabled_symbols)
  94. -- Remove to avoid clash
  95. s.symbols_disabled = nil
  96. s.symbols_enabled = nil
  97. s.groups_enabled = nil
  98. s.groups_disabled = nil
  99. end
  100. -- We now iterate over all symbols and check for allowed_ids/forbidden_ids
  101. for k,v in pairs(all_symbols) do
  102. if v.allowed_ids and not v.flags.explicit_disable then
  103. for _,id in ipairs(v.allowed_ids) do
  104. if known_ids[id] then
  105. local set = known_ids[id]
  106. if not set.has_specific_symbols then
  107. set.has_specific_symbols = true
  108. end
  109. set.symbols[k] = true
  110. else
  111. rspamd_logger.errx(rspamd_config, 'symbol %s is allowed at unknown settings id %s',
  112. k, id)
  113. end
  114. end
  115. end
  116. if v.forbidden_ids then
  117. for _,id in ipairs(v.forbidden_ids) do
  118. if known_ids[id] then
  119. local set = known_ids[id]
  120. if not set.has_specific_symbols then
  121. set.has_specific_symbols = true
  122. end
  123. set.symbols[k] = false
  124. else
  125. rspamd_logger.errx(rspamd_config, 'symbol %s is denied at unknown settings id %s',
  126. k, id)
  127. end
  128. end
  129. end
  130. end
  131. -- Now we create lists of symbols for each settings and digest
  132. for _,set in pairs(known_ids) do
  133. set.symbols = lua_util.keys(fun.filter(function(_, v) return v end, set.symbols))
  134. table.sort(set.symbols)
  135. set.digest = lua_util.table_digest(set.symbols)
  136. end
  137. post_init_performed = true
  138. end
  139. end
  140. -- Returns numeric representation of the settings id
  141. local function numeric_settings_id(str)
  142. local cr = require "rspamd_cryptobox_hash"
  143. local util = require "rspamd_util"
  144. local ret = util.unpack("I4",
  145. cr.create_specific('xxh64'):update(str):bin())
  146. return ret
  147. end
  148. exports.numeric_settings_id = numeric_settings_id
  149. -- Used to do the following:
  150. -- If there is a group of symbols_allowed, it checks if that is an array
  151. -- If that is a hash table then we transform it to a normal list, probably adding symbols to adjust scores
  152. local function transform_settings_maybe(settings, name)
  153. if settings.apply then
  154. local apply = settings.apply
  155. if apply.symbols_enabled then
  156. local senabled = apply.symbols_enabled
  157. if not senabled[1] then
  158. -- Transform map to a list
  159. local nlist = {}
  160. if not apply.scores then
  161. apply.scores = {}
  162. end
  163. for k,v in pairs(senabled) do
  164. if tonumber(v) then
  165. -- Move to symbols as well
  166. apply.scores[k] = tonumber(v)
  167. lua_util.debugm('settings', rspamd_config,
  168. 'set symbol %s -> %s for settings %s', k, v, name)
  169. end
  170. nlist[#nlist + 1] = k
  171. end
  172. -- Convert
  173. apply.symbols_enabled = nlist
  174. end
  175. local symhash = lua_util.list_to_hash(apply.symbols_enabled)
  176. if apply.symbols then
  177. -- Check if added symbols are enabled
  178. for k,v in pairs(apply.symbols) do
  179. local s
  180. -- Check if we have ["sym1", "sym2" ...] or {"sym1": xx, "sym2": yy}
  181. if type(k) == 'string' then
  182. s = k
  183. else
  184. s = v
  185. end
  186. if not symhash[s] then
  187. lua_util.debugm('settings', rspamd_config,
  188. 'added symbol %s to symbols_enabled for %s', s, name)
  189. apply.symbols_enabled[#apply.symbols_enabled + 1] = s
  190. end
  191. end
  192. end
  193. end
  194. end
  195. return settings
  196. end
  197. local function register_settings_id(str, settings, from_postload)
  198. local numeric_id = numeric_settings_id(str)
  199. if known_ids[numeric_id] then
  200. -- Might be either rewrite or a collision
  201. if known_ids[numeric_id].name ~= str then
  202. local logger = require "rspamd_logger"
  203. logger.errx(rspamd_config, 'settings ID clash! id %s maps to %s and conflicts with %s',
  204. numeric_id, known_ids[numeric_id].name, str)
  205. return nil
  206. end
  207. else
  208. known_ids[numeric_id] = {
  209. name = str,
  210. id = numeric_id,
  211. settings = transform_settings_maybe(settings, str),
  212. symbols = {}
  213. }
  214. end
  215. if not from_postload and not post_init_added then
  216. -- Use high priority to ensure that settings are initialised early but not before all
  217. -- plugins are loaded
  218. rspamd_config:add_post_init(function () register_settings_cb(true) end, 150)
  219. rspamd_config:add_config_unload(function()
  220. if post_init_added then
  221. known_ids = {}
  222. post_init_added = false
  223. end
  224. post_init_performed = false
  225. end)
  226. post_init_added = true
  227. end
  228. return numeric_id
  229. end
  230. exports.register_settings_id = register_settings_id
  231. local function settings_by_id(id)
  232. if not post_init_performed then
  233. register_settings_cb(false)
  234. end
  235. return known_ids[id]
  236. end
  237. exports.settings_by_id = settings_by_id
  238. exports.all_settings = function()
  239. if not post_init_performed then
  240. register_settings_cb(false)
  241. end
  242. return known_ids
  243. end
  244. exports.all_symbols = function()
  245. if not post_init_performed then
  246. register_settings_cb(false)
  247. end
  248. return all_symbols
  249. end
  250. -- What is enabled when no settings are there
  251. exports.default_symbols = function()
  252. if not post_init_performed then
  253. register_settings_cb(false)
  254. end
  255. return default_symbols
  256. end
  257. exports.load_all_settings = register_settings_cb
  258. return exports