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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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
  88. enabled_symbols = nil
  89. end
  90. if not seen_disabled then
  91. disabled_symbols = nil
  92. end
  93. if enabled_symbols or disabled_symbols then
  94. -- Specify what symbols are really enabled for this settings id
  95. set.has_specific_symbols = true
  96. end
  97. rspamd_config:register_settings_id(set.name, enabled_symbols, disabled_symbols)
  98. -- Remove to avoid clash
  99. s.symbols_disabled = nil
  100. s.symbols_enabled = nil
  101. s.groups_enabled = nil
  102. s.groups_disabled = nil
  103. end
  104. -- We now iterate over all symbols and check for allowed_ids/forbidden_ids
  105. for k, v in pairs(all_symbols) do
  106. if v.allowed_ids and not v.flags.explicit_disable then
  107. for _, id in ipairs(v.allowed_ids) do
  108. if known_ids[id] then
  109. local set = known_ids[id]
  110. if not set.has_specific_symbols then
  111. set.has_specific_symbols = true
  112. end
  113. set.symbols[k] = true
  114. else
  115. rspamd_logger.errx(rspamd_config, 'symbol %s is allowed at unknown settings id %s',
  116. k, id)
  117. end
  118. end
  119. end
  120. if v.forbidden_ids then
  121. for _, id in ipairs(v.forbidden_ids) do
  122. if known_ids[id] then
  123. local set = known_ids[id]
  124. if not set.has_specific_symbols then
  125. set.has_specific_symbols = true
  126. end
  127. set.symbols[k] = false
  128. else
  129. rspamd_logger.errx(rspamd_config, 'symbol %s is denied at unknown settings id %s',
  130. k, id)
  131. end
  132. end
  133. end
  134. end
  135. -- Now we create lists of symbols for each settings and digest
  136. for _, set in pairs(known_ids) do
  137. set.symbols = lua_util.keys(fun.filter(function(_, v)
  138. return v
  139. end, set.symbols))
  140. table.sort(set.symbols)
  141. set.digest = lua_util.table_digest(set.symbols)
  142. end
  143. post_init_performed = true
  144. end
  145. end
  146. -- Returns numeric representation of the settings id
  147. local function numeric_settings_id(str)
  148. local cr = require "rspamd_cryptobox_hash"
  149. local util = require "rspamd_util"
  150. local ret = util.unpack("I4",
  151. cr.create_specific('xxh64'):update(str):bin())
  152. return ret
  153. end
  154. exports.numeric_settings_id = numeric_settings_id
  155. -- Used to do the following:
  156. -- If there is a group of symbols_allowed, it checks if that is an array
  157. -- If that is a hash table then we transform it to a normal list, probably adding symbols to adjust scores
  158. local function transform_settings_maybe(settings, name)
  159. if settings.apply then
  160. local apply = settings.apply
  161. if apply.symbols_enabled then
  162. local senabled = apply.symbols_enabled
  163. if not senabled[1] then
  164. -- Transform map to a list
  165. local nlist = {}
  166. if not apply.scores then
  167. apply.scores = {}
  168. end
  169. for k, v in pairs(senabled) do
  170. if tonumber(v) then
  171. -- Move to symbols as well
  172. apply.scores[k] = tonumber(v)
  173. lua_util.debugm('settings', rspamd_config,
  174. 'set symbol %s -> %s for settings %s', k, v, name)
  175. end
  176. nlist[#nlist + 1] = k
  177. end
  178. -- Convert
  179. apply.symbols_enabled = nlist
  180. end
  181. local symhash = lua_util.list_to_hash(apply.symbols_enabled)
  182. if apply.symbols then
  183. -- Check if added symbols are enabled
  184. for k, v in pairs(apply.symbols) do
  185. local s
  186. -- Check if we have ["sym1", "sym2" ...] or {"sym1": xx, "sym2": yy}
  187. if type(k) == 'string' then
  188. s = k
  189. else
  190. s = v
  191. end
  192. if not symhash[s] then
  193. lua_util.debugm('settings', rspamd_config,
  194. 'added symbol %s to symbols_enabled for %s', s, name)
  195. apply.symbols_enabled[#apply.symbols_enabled + 1] = s
  196. end
  197. end
  198. end
  199. end
  200. end
  201. return settings
  202. end
  203. local function register_settings_id(str, settings, from_postload)
  204. local numeric_id = numeric_settings_id(str)
  205. if known_ids[numeric_id] then
  206. -- Might be either rewrite or a collision
  207. if known_ids[numeric_id].name ~= str then
  208. local logger = require "rspamd_logger"
  209. logger.errx(rspamd_config, 'settings ID clash! id %s maps to %s and conflicts with %s',
  210. numeric_id, known_ids[numeric_id].name, str)
  211. return nil
  212. end
  213. else
  214. known_ids[numeric_id] = {
  215. name = str,
  216. id = numeric_id,
  217. settings = transform_settings_maybe(settings, str),
  218. symbols = {}
  219. }
  220. end
  221. if not from_postload and not post_init_added then
  222. -- Use high priority to ensure that settings are initialised early but not before all
  223. -- plugins are loaded
  224. rspamd_config:add_post_init(function()
  225. register_settings_cb(true)
  226. end, 150)
  227. rspamd_config:add_config_unload(function()
  228. if post_init_added then
  229. known_ids = {}
  230. post_init_added = false
  231. end
  232. post_init_performed = false
  233. end)
  234. post_init_added = true
  235. end
  236. return numeric_id
  237. end
  238. exports.register_settings_id = register_settings_id
  239. local function settings_by_id(id)
  240. if not post_init_performed then
  241. register_settings_cb(false)
  242. end
  243. return known_ids[id]
  244. end
  245. exports.settings_by_id = settings_by_id
  246. exports.all_settings = function()
  247. if not post_init_performed then
  248. register_settings_cb(false)
  249. end
  250. return known_ids
  251. end
  252. exports.all_symbols = function()
  253. if not post_init_performed then
  254. register_settings_cb(false)
  255. end
  256. return all_symbols
  257. end
  258. -- What is enabled when no settings are there
  259. exports.default_symbols = function()
  260. if not post_init_performed then
  261. register_settings_cb(false)
  262. end
  263. return default_symbols
  264. end
  265. exports.load_all_settings = register_settings_cb
  266. return exports