選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

lua_cfg_transform.lua 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  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 rspamd_util = require "rspamd_util"
  16. local function is_implicit(t)
  17. local mt = getmetatable(t)
  18. return mt and mt.class and mt.class == 'ucl.type.impl_array'
  19. end
  20. local function metric_pairs(t)
  21. -- collect the keys
  22. local keys = {}
  23. local implicit_array = is_implicit(t)
  24. local function gen_keys(tbl)
  25. if implicit_array then
  26. for _,v in ipairs(tbl) do
  27. if v.name then
  28. table.insert(keys, {v.name, v})
  29. v.name = nil
  30. else
  31. -- Very tricky to distinguish:
  32. -- group {name = "foo" ... } + group "blah" { ... }
  33. for gr_name,gr in pairs(v) do
  34. if type(gr_name) ~= 'number' then
  35. -- We can also have implicit arrays here
  36. local gr_implicit = is_implicit(gr)
  37. if gr_implicit then
  38. for _,gr_elt in ipairs(gr) do
  39. table.insert(keys, {gr_name, gr_elt})
  40. end
  41. else
  42. table.insert(keys, {gr_name, gr})
  43. end
  44. end
  45. end
  46. end
  47. end
  48. else
  49. if tbl.name then
  50. table.insert(keys, {tbl.name, tbl})
  51. tbl.name = nil
  52. else
  53. for k,v in pairs(tbl) do
  54. if type(k) ~= 'number' then
  55. -- We can also have implicit arrays here
  56. local sym_implicit = is_implicit(v)
  57. if sym_implicit then
  58. for _,elt in ipairs(v) do
  59. table.insert(keys, {k, elt})
  60. end
  61. else
  62. table.insert(keys, {k, v})
  63. end
  64. end
  65. end
  66. end
  67. end
  68. end
  69. gen_keys(t)
  70. -- return the iterator function
  71. local i = 0
  72. return function()
  73. i = i + 1
  74. if keys[i] then
  75. return keys[i][1], keys[i][2]
  76. end
  77. end
  78. end
  79. local function group_transform(cfg, k, v)
  80. if v.name then k = v.name end
  81. local new_group = {
  82. symbols = {}
  83. }
  84. if v.enabled then new_group.enabled = v.enabled end
  85. if v.disabled then new_group.disabled = v.disabled end
  86. if v.max_score then new_group.max_score = v.max_score end
  87. if v.symbol then
  88. for sk,sv in metric_pairs(v.symbol) do
  89. if sv.name then
  90. sk = sv.name
  91. sv.name = nil -- Remove field
  92. end
  93. new_group.symbols[sk] = sv
  94. end
  95. end
  96. if not cfg.group then cfg.group = {} end
  97. if cfg.group[k] then
  98. cfg.group[k] = lua_util.override_defaults(cfg.group[k], new_group)
  99. else
  100. cfg.group[k] = new_group
  101. end
  102. logger.infox("overriding group %s from the legacy metric settings", k)
  103. end
  104. local function symbol_transform(cfg, k, v)
  105. -- first try to find any group where there is a definition of this symbol
  106. for gr_n, gr in pairs(cfg.group) do
  107. if gr.symbols and gr.symbols[k] then
  108. -- We override group symbol with ungrouped symbol
  109. logger.infox("overriding group symbol %s in the group %s", k, gr_n)
  110. gr.symbols[k] = lua_util.override_defaults(gr.symbols[k], v)
  111. return
  112. end
  113. end
  114. -- Now check what Rspamd knows about this symbol
  115. local sym = rspamd_config:get_metric_symbol(k)
  116. if not sym or not sym.group then
  117. -- Otherwise we just use group 'ungrouped'
  118. if not cfg.group.ungrouped then
  119. cfg.group.ungrouped = {
  120. symbols = {}
  121. }
  122. end
  123. cfg.group.ungrouped.symbols[k] = v
  124. logger.debugx("adding symbol %s to the group 'ungrouped'", k)
  125. end
  126. end
  127. local function test_groups(groups)
  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. end
  138. end
  139. end
  140. local function convert_metric(cfg, metric)
  141. if metric.actions then
  142. cfg.actions = lua_util.override_defaults(cfg.actions, metric.actions)
  143. logger.infox("overriding actions from the legacy metric settings")
  144. end
  145. if metric.unknown_weight then
  146. cfg.actions.unknown_weight = metric.unknown_weight
  147. end
  148. if metric.subject then
  149. logger.infox("overriding subject from the legacy metric settings")
  150. cfg.actions.subject = metric.subject
  151. end
  152. if metric.group then
  153. for k, v in metric_pairs(metric.group) do
  154. group_transform(cfg, k, v)
  155. end
  156. else
  157. if not cfg.group then
  158. cfg.group = {
  159. ungrouped = {
  160. symbols = {}
  161. }
  162. }
  163. end
  164. end
  165. if metric.symbol then
  166. for k, v in metric_pairs(metric.symbol) do
  167. symbol_transform(cfg, k, v)
  168. end
  169. end
  170. return cfg
  171. end
  172. -- Converts a table of groups indexed by number (implicit array) to a
  173. -- merged group definition
  174. local function merge_groups(groups)
  175. local ret = {}
  176. for k,gr in pairs(groups) do
  177. if type(k) == 'number' then
  178. for key,sec in pairs(gr) do
  179. ret[key] = sec
  180. end
  181. else
  182. ret[k] = gr
  183. end
  184. end
  185. return ret
  186. end
  187. -- Checks configuration files for statistics
  188. local function check_statistics_sanity()
  189. local local_conf = rspamd_paths['LOCAL_CONFDIR']
  190. local local_stat = string.format('%s/local.d/%s', local_conf,
  191. 'statistic.conf')
  192. local local_bayes = string.format('%s/local.d/%s', local_conf,
  193. 'classifier-bayes.conf')
  194. if rspamd_util.file_exists(local_stat) and
  195. rspamd_util.file_exists(local_bayes) then
  196. logger.warnx(rspamd_config, 'conflicting files %s and %s are found: '..
  197. 'Rspamd classifier configuration might be broken!', local_stat, local_bayes)
  198. end
  199. end
  200. return function(cfg)
  201. local ret = false
  202. if cfg['metric'] then
  203. for _, v in metric_pairs(cfg.metric) do
  204. cfg = convert_metric(cfg, v)
  205. end
  206. ret = true
  207. end
  208. if cfg.symbols then
  209. for k, v in metric_pairs(cfg.symbols) do
  210. symbol_transform(cfg, k, v)
  211. end
  212. end
  213. check_statistics_sanity()
  214. if not cfg.actions then
  215. logger.errx('no actions defined')
  216. else
  217. -- Perform sanity check for actions
  218. local actions_defs = {'no action', 'no_action', -- In case if that's added
  219. 'greylist', 'add header', 'add_header',
  220. 'rewrite subject', 'rewrite_subject', 'reject'}
  221. if not cfg.actions['no action'] and not cfg.actions['no_action'] and
  222. not cfg.actions['accept'] then
  223. for _,d in ipairs(actions_defs) do
  224. if cfg.actions[d] and type(cfg.actions[d]) == 'number' then
  225. if cfg.actions[d] < 0 then
  226. cfg.actions['no action'] = cfg.actions[d] - 0.001
  227. logger.infox('set no action score to: %s, as action %s has negative score',
  228. cfg.actions['no action'], d)
  229. break
  230. end
  231. end
  232. end
  233. end
  234. local actions_set = {}
  235. for _,d in ipairs(actions_defs) do
  236. actions_set[d] = true
  237. end
  238. -- Now check actions section for garbadge
  239. actions_set['unknown_weight'] = true
  240. actions_set['grow_factor'] = true
  241. actions_set['subject'] = true
  242. for k,_ in pairs(cfg.actions) do
  243. if not actions_set[k] then
  244. logger.warnx('unknown element in actions section: %s', k)
  245. end
  246. end
  247. end
  248. if not cfg.group then
  249. logger.errx('no symbol groups defined')
  250. else
  251. if cfg.group[1] then
  252. -- We need to merge groups
  253. cfg.group = merge_groups(cfg.group)
  254. ret = true
  255. end
  256. test_groups(cfg.group)
  257. end
  258. -- Deal with dkim settings
  259. if not cfg.dkim then
  260. cfg.dkim = {}
  261. else
  262. if cfg.dkim.sign_condition then
  263. -- We have an obsoleted sign condition, so we need to either add dkim_signing and move it
  264. -- there or just move sign condition there...
  265. if not cfg.dkim_signing then
  266. logger.warnx('obsoleted DKIM signing method used, converting it to "dkim_signing" module')
  267. cfg.dkim_signing = {
  268. sign_condition = cfg.dkim.sign_condition
  269. }
  270. else
  271. if not cfg.dkim_signing.sign_condition then
  272. logger.warnx('obsoleted DKIM signing method used, move it to "dkim_signing" module')
  273. cfg.dkim_signing.sign_condition = cfg.dkim.sign_condition
  274. else
  275. logger.warnx('obsoleted DKIM signing method used, ignore it as "dkim_signing" also defines condition!')
  276. end
  277. end
  278. end
  279. end
  280. -- Again: legacy stuff :(
  281. if not cfg.dkim.sign_headers then
  282. local sec = cfg.dkim_signing
  283. if sec and sec[1] then sec = cfg.dkim_signing[1] end
  284. if sec and sec.sign_headers then
  285. cfg.dkim.sign_headers = sec.sign_headers
  286. end
  287. end
  288. if cfg.dkim and cfg.dkim.sign_headers and type(cfg.dkim.sign_headers) == 'table' then
  289. -- Flatten
  290. cfg.dkim.sign_headers = table.concat(cfg.dkim.sign_headers, ':')
  291. end
  292. -- Try to find some obvious issues with configuration
  293. for k,v in pairs(cfg) do
  294. if type(v) == 'table' and v[k] and type (v[k]) == 'table' then
  295. logger.errx('nested section: %s { %s { ... } }, it is likely a configuration error',
  296. k, k)
  297. end
  298. end
  299. -- If neural network is enabled we MUST have `check_all_filters` flag
  300. if cfg.neural then
  301. if not cfg.options then
  302. cfg.options = {}
  303. end
  304. if not cfg.options.check_all_filters then
  305. logger.infox(rspamd_config, 'enable `options.check_all_filters` for neural network')
  306. cfg.options.check_all_filters = true
  307. end
  308. end
  309. return ret, cfg
  310. end