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 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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. local all_symbols = {}
  128. for gr_name, gr in pairs(groups) do
  129. if not gr.symbols then
  130. local cnt = 0
  131. for _,_ in pairs(gr) do cnt = cnt + 1 end
  132. if cnt == 0 then
  133. logger.debugx('group %s is empty', gr_name)
  134. else
  135. logger.infox('group %s has no symbols', gr_name)
  136. end
  137. else
  138. for sn,_ in pairs(gr.symbols) do
  139. if all_symbols[sn] then
  140. logger.errx('symbol %s has registered in multiple groups: %s and %s',
  141. sn, all_symbols[sn], gr_name)
  142. else
  143. all_symbols[sn] = gr_name
  144. end
  145. end
  146. end
  147. end
  148. end
  149. local function convert_metric(cfg, metric)
  150. if metric.actions then
  151. cfg.actions = lua_util.override_defaults(cfg.actions, metric.actions)
  152. logger.infox("overriding actions from the legacy metric settings")
  153. end
  154. if metric.unknown_weight then
  155. cfg.actions.unknown_weight = metric.unknown_weight
  156. end
  157. if metric.subject then
  158. logger.infox("overriding subject from the legacy metric settings")
  159. cfg.actions.subject = metric.subject
  160. end
  161. if metric.group then
  162. for k, v in metric_pairs(metric.group) do
  163. group_transform(cfg, k, v)
  164. end
  165. else
  166. if not cfg.group then
  167. cfg.group = {
  168. ungrouped = {
  169. symbols = {}
  170. }
  171. }
  172. end
  173. end
  174. if metric.symbol then
  175. for k, v in metric_pairs(metric.symbol) do
  176. symbol_transform(cfg, k, v)
  177. end
  178. end
  179. return cfg
  180. end
  181. -- Converts a table of groups indexed by number (implicit array) to a
  182. -- merged group definition
  183. local function merge_groups(groups)
  184. local ret = {}
  185. for k,gr in pairs(groups) do
  186. if type(k) == 'number' then
  187. for key,sec in pairs(gr) do
  188. ret[key] = sec
  189. end
  190. else
  191. ret[k] = gr
  192. end
  193. end
  194. return ret
  195. end
  196. return function(cfg)
  197. local ret = false
  198. if cfg['metric'] then
  199. for _, v in metric_pairs(cfg.metric) do
  200. cfg = convert_metric(cfg, v)
  201. end
  202. ret = true
  203. end
  204. if not cfg.actions then
  205. logger.errx('no actions defined')
  206. else
  207. -- Perform sanity check for actions
  208. local actions_defs = {'greylist', 'add header', 'add_header',
  209. 'rewrite subject', 'rewrite_subject', 'reject'}
  210. if not cfg.actions['no action'] and not cfg.actions['no_action'] and
  211. not cfg.actions['accept'] then
  212. for _,d in ipairs(actions_defs) do
  213. if cfg.actions[d] and type(cfg.actions[d]) == 'number' then
  214. if cfg.actions[d] < 0 then
  215. cfg.actions['no action'] = cfg.actions[d] - 0.001
  216. logger.infox('set no action score to: %s, as action %s has negative score',
  217. cfg.actions['no action'], d)
  218. break
  219. end
  220. end
  221. end
  222. end
  223. local actions_set = {}
  224. for _,d in ipairs(actions_defs) do
  225. actions_set[d] = true
  226. end
  227. -- Now check actions section for garbadge
  228. actions_set['unknown_weight'] = true
  229. actions_set['grow_factor'] = true
  230. actions_set['subject'] = true
  231. for k,_ in pairs(cfg.actions) do
  232. if not actions_set[k] then
  233. logger.warnx('unknown element in actions section: %s', k)
  234. end
  235. end
  236. end
  237. if not cfg.group then
  238. logger.errx('no symbol groups defined')
  239. else
  240. if cfg.group[1] then
  241. -- We need to merge groups
  242. cfg.group = merge_groups(cfg.group)
  243. ret = true
  244. end
  245. test_groups(cfg.group)
  246. end
  247. -- Deal with dkim settings
  248. if not cfg.dkim then cfg.dkim = {} end
  249. if not cfg.dkim.sign_headers then
  250. local sec = cfg.dkim_signing
  251. if sec and sec[1] then sec = cfg.dkim_signing[1] end
  252. if sec and sec.sign_headers then
  253. cfg.dkim.sign_headers = sec.sign_headers
  254. end
  255. end
  256. if cfg.dkim and cfg.dkim.sign_headers and type(cfg.dkim.sign_headers) == 'table' then
  257. -- Flatten
  258. cfg.dkim.sign_headers = table.concat(cfg.dkim.sign_headers, ':')
  259. end
  260. -- Try to find some obvious issues with configuration
  261. for k,v in pairs(cfg) do
  262. if type(v) == 'table' and v[k] and type (v[k]) == 'table' then
  263. logger.errx('nested section: %s { %s { ... } }, it is likely a configuration error',
  264. k, k)
  265. end
  266. end
  267. return ret, cfg
  268. end