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_cfg_transform.lua 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. --[[
  2. Copyright (c) 2017, Vsevolod Stakhov <vsevolod@highsecure.ru>
  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. local logger = require "rspamd_logger"
  14. local lua_util = require "lua_util"
  15. local function is_implicit(t)
  16. local mt = getmetatable(t)
  17. return mt and mt.class and mt.class == 'ucl.type.impl_array'
  18. end
  19. local function metric_pairs(t)
  20. -- collect the keys
  21. local keys = {}
  22. local implicit_array = is_implicit(t)
  23. local function gen_keys(tbl)
  24. if implicit_array then
  25. for _,v in ipairs(tbl) do
  26. if v.name then
  27. table.insert(keys, {v.name, v})
  28. v.name = nil
  29. else
  30. -- Very tricky to distinguish:
  31. -- group {name = "foo" ... } + group "blah" { ... }
  32. for gr_name,gr in pairs(v) do
  33. if type(gr_name) ~= 'number' then
  34. -- We can also have implicit arrays here
  35. local gr_implicit = is_implicit(gr)
  36. if gr_implicit then
  37. for _,gr_elt in ipairs(gr) do
  38. table.insert(keys, {gr_name, gr_elt})
  39. end
  40. else
  41. table.insert(keys, {gr_name, gr})
  42. end
  43. end
  44. end
  45. end
  46. end
  47. else
  48. if tbl.name then
  49. table.insert(keys, {tbl.name, tbl})
  50. tbl.name = nil
  51. else
  52. for k,v in pairs(tbl) do
  53. if type(k) ~= 'number' then
  54. -- We can also have implicit arrays here
  55. local sym_implicit = is_implicit(v)
  56. if sym_implicit then
  57. for _,elt in ipairs(v) do
  58. table.insert(keys, {k, elt})
  59. end
  60. else
  61. table.insert(keys, {k, v})
  62. end
  63. end
  64. end
  65. end
  66. end
  67. end
  68. gen_keys(t)
  69. -- return the iterator function
  70. local i = 0
  71. return function()
  72. i = i + 1
  73. if keys[i] then
  74. return keys[i][1], keys[i][2]
  75. end
  76. end
  77. end
  78. local function group_transform(cfg, k, v)
  79. if v.name then k = v.name end
  80. local new_group = {
  81. symbols = {}
  82. }
  83. if v.enabled then new_group.enabled = v.enabled end
  84. if v.disabled then new_group.disabled = v.disabled end
  85. if v.max_score then new_group.max_score = v.max_score end
  86. if v.symbol then
  87. for sk,sv in metric_pairs(v.symbol) do
  88. if sv.name then
  89. sk = sv.name
  90. sv.name = nil -- Remove field
  91. end
  92. new_group.symbols[sk] = sv
  93. end
  94. end
  95. if not cfg.group then cfg.group = {} end
  96. if cfg.group[k] then
  97. cfg.group[k] = lua_util.override_defaults(cfg.group[k], new_group)
  98. else
  99. cfg.group[k] = new_group
  100. end
  101. logger.infox("overriding group %s from the legacy metric settings", k)
  102. end
  103. local function symbol_transform(cfg, k, v)
  104. -- first try to find any group where there is a definition of this symbol
  105. for gr_n, gr in pairs(cfg.group) do
  106. if gr.symbols and gr.symbols[k] then
  107. -- We override group symbol with ungrouped symbol
  108. logger.infox("overriding group symbol %s in the group %s", k, gr_n)
  109. gr.symbols[k] = lua_util.override_defaults(gr.symbols[k], v)
  110. return
  111. end
  112. end
  113. -- Now check what Rspamd knows about this symbol
  114. local sym = rspamd_config:get_metric_symbol(k)
  115. if not sym or not sym.group then
  116. -- Otherwise we just use group 'ungrouped'
  117. if not cfg.group.ungrouped then
  118. cfg.group.ungrouped = {
  119. symbols = {}
  120. }
  121. end
  122. cfg.group.ungrouped.symbols[k] = v
  123. logger.debugx("adding symbol %s to the group 'ungrouped'", k)
  124. end
  125. end
  126. local function test_groups(groups)
  127. for gr_name, gr in pairs(groups) do
  128. if not gr.symbols then
  129. local cnt = 0
  130. for _,_ in pairs(gr) do cnt = cnt + 1 end
  131. if cnt == 0 then
  132. logger.debugx('group %s is empty', gr_name)
  133. else
  134. logger.infox('group %s has no symbols', gr_name)
  135. end
  136. end
  137. end
  138. end
  139. local function convert_metric(cfg, metric)
  140. if metric.actions then
  141. cfg.actions = lua_util.override_defaults(cfg.actions, metric.actions)
  142. logger.infox("overriding actions from the legacy metric settings")
  143. end
  144. if metric.unknown_weight then
  145. cfg.actions.unknown_weight = metric.unknown_weight
  146. end
  147. if metric.subject then
  148. logger.infox("overriding subject from the legacy metric settings")
  149. cfg.actions.subject = metric.subject
  150. end
  151. if metric.group then
  152. for k, v in metric_pairs(metric.group) do
  153. group_transform(cfg, k, v)
  154. end
  155. else
  156. if not cfg.group then
  157. cfg.group = {
  158. ungrouped = {
  159. symbols = {}
  160. }
  161. }
  162. end
  163. end
  164. if metric.symbol then
  165. for k, v in metric_pairs(metric.symbol) do
  166. symbol_transform(cfg, k, v)
  167. end
  168. end
  169. return cfg
  170. end
  171. -- Converts a table of groups indexed by number (implicit array) to a
  172. -- merged group definition
  173. local function merge_groups(groups)
  174. local ret = {}
  175. for k,gr in pairs(groups) do
  176. if type(k) == 'number' then
  177. for key,sec in pairs(gr) do
  178. ret[key] = sec
  179. end
  180. else
  181. ret[k] = gr
  182. end
  183. end
  184. return ret
  185. end
  186. return function(cfg)
  187. local ret = false
  188. if cfg['metric'] then
  189. for _, v in metric_pairs(cfg.metric) do
  190. cfg = convert_metric(cfg, v)
  191. end
  192. ret = true
  193. end
  194. if not cfg.actions then
  195. logger.errx('no actions defined')
  196. else
  197. -- Perform sanity check for actions
  198. local actions_defs = {'no action', 'no_action', -- In case if that's added
  199. 'greylist', 'add header', 'add_header',
  200. 'rewrite subject', 'rewrite_subject', 'reject'}
  201. if not cfg.actions['no action'] and not cfg.actions['no_action'] and
  202. not cfg.actions['accept'] then
  203. for _,d in ipairs(actions_defs) do
  204. if cfg.actions[d] and type(cfg.actions[d]) == 'number' then
  205. if cfg.actions[d] < 0 then
  206. cfg.actions['no action'] = cfg.actions[d] - 0.001
  207. logger.infox('set no action score to: %s, as action %s has negative score',
  208. cfg.actions['no action'], d)
  209. break
  210. end
  211. end
  212. end
  213. end
  214. local actions_set = {}
  215. for _,d in ipairs(actions_defs) do
  216. actions_set[d] = true
  217. end
  218. -- Now check actions section for garbadge
  219. actions_set['unknown_weight'] = true
  220. actions_set['grow_factor'] = true
  221. actions_set['subject'] = true
  222. for k,_ in pairs(cfg.actions) do
  223. if not actions_set[k] then
  224. logger.warnx('unknown element in actions section: %s', k)
  225. end
  226. end
  227. end
  228. if not cfg.group then
  229. logger.errx('no symbol groups defined')
  230. else
  231. if cfg.group[1] then
  232. -- We need to merge groups
  233. cfg.group = merge_groups(cfg.group)
  234. ret = true
  235. end
  236. test_groups(cfg.group)
  237. end
  238. -- Deal with dkim settings
  239. if not cfg.dkim then cfg.dkim = {} end
  240. if not cfg.dkim.sign_headers then
  241. local sec = cfg.dkim_signing
  242. if sec and sec[1] then sec = cfg.dkim_signing[1] end
  243. if sec and sec.sign_headers then
  244. cfg.dkim.sign_headers = sec.sign_headers
  245. end
  246. end
  247. if cfg.dkim and cfg.dkim.sign_headers and type(cfg.dkim.sign_headers) == 'table' then
  248. -- Flatten
  249. cfg.dkim.sign_headers = table.concat(cfg.dkim.sign_headers, ':')
  250. end
  251. -- Try to find some obvious issues with configuration
  252. for k,v in pairs(cfg) do
  253. if type(v) == 'table' and v[k] and type (v[k]) == 'table' then
  254. logger.errx('nested section: %s { %s { ... } }, it is likely a configuration error',
  255. k, k)
  256. end
  257. end
  258. return ret, cfg
  259. end