Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

lua_util.lua 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  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. --[[[
  14. -- @module lua_util
  15. -- This module contains utility functions for working with Lua and/or Rspamd
  16. --]]
  17. local exports = {}
  18. local lpeg = require 'lpeg'
  19. local rspamd_util = require "rspamd_util"
  20. local fun = require "fun"
  21. local split_grammar = {}
  22. local spaces_split_grammar
  23. local space = lpeg.S' \t\n\v\f\r'
  24. local nospace = 1 - space
  25. local ptrim = space^0 * lpeg.C((space^0 * nospace^1)^0)
  26. local match = lpeg.match
  27. local function rspamd_str_split(s, sep)
  28. local gr
  29. if not sep then
  30. if not spaces_split_grammar then
  31. local _sep = space
  32. local elem = lpeg.C((1 - _sep)^0)
  33. local p = lpeg.Ct(elem * (_sep * elem)^0)
  34. spaces_split_grammar = p
  35. end
  36. gr = spaces_split_grammar
  37. else
  38. gr = split_grammar[sep]
  39. if not gr then
  40. local _sep
  41. if type(sep) == 'string' then
  42. _sep = lpeg.S(sep) -- Assume set
  43. else
  44. _sep = sep -- Assume lpeg object
  45. end
  46. local elem = lpeg.C((1 - _sep)^0)
  47. local p = lpeg.Ct(elem * (_sep * elem)^0)
  48. gr = p
  49. split_grammar[sep] = gr
  50. end
  51. end
  52. return gr:match(s)
  53. end
  54. --[[[
  55. -- @function lua_util.str_split(text, deliminator)
  56. -- Splits text into a numeric table by deliminator
  57. -- @param {string} text deliminated text
  58. -- @param {string} deliminator the deliminator
  59. -- @return {table} numeric table containing string parts
  60. --]]
  61. exports.rspamd_str_split = rspamd_str_split
  62. exports.str_split = rspamd_str_split
  63. exports.rspamd_str_trim = function(s)
  64. return match(ptrim, s)
  65. end
  66. --[[[
  67. -- @function lua_util.round(number, decimalPlaces)
  68. -- Round number to fixed number of decimal points
  69. -- @param {number} number number to round
  70. -- @param {number} decimalPlaces number of decimal points
  71. -- @return {number} rounded number
  72. --]]
  73. -- Robert Jay Gould http://lua-users.org/wiki/SimpleRound
  74. exports.round = function(num, numDecimalPlaces)
  75. local mult = 10^(numDecimalPlaces or 0)
  76. return math.floor(num * mult) / mult
  77. end
  78. --[[[
  79. -- @function lua_util.template(text, replacements)
  80. -- Replaces values in a text template
  81. -- Variable names can contain letters, numbers and underscores, are prefixed with `$` and may or not use curly braces.
  82. -- @param {string} text text containing variables
  83. -- @param {table} replacements key/value pairs for replacements
  84. -- @return {string} string containing replaced values
  85. -- @example
  86. -- local goop = lua_util.template("HELLO $FOO ${BAR}!", {['FOO'] = 'LUA', ['BAR'] = 'WORLD'})
  87. -- -- goop contains "HELLO LUA WORLD!"
  88. --]]
  89. exports.template = function(tmpl, keys)
  90. local var_lit = lpeg.P { lpeg.R("az") + lpeg.R("AZ") + lpeg.R("09") + "_" }
  91. local var = lpeg.P { (lpeg.P("$") / "") * ((var_lit^1) / keys) }
  92. local var_braced = lpeg.P { (lpeg.P("${") / "") * ((var_lit^1) / keys) * (lpeg.P("}") / "") }
  93. local template_grammar = lpeg.Cs((var + var_braced + 1)^0)
  94. return lpeg.match(template_grammar, tmpl)
  95. end
  96. exports.remove_email_aliases = function(email_addr)
  97. local function check_gmail_user(addr)
  98. -- Remove all points
  99. local no_dots_user = string.gsub(addr.user, '%.', '')
  100. local cap, pluses = string.match(no_dots_user, '^([^%+][^%+]*)(%+.*)$')
  101. if cap then
  102. return cap, rspamd_str_split(pluses, '+'), nil
  103. elseif no_dots_user ~= addr.user then
  104. return no_dots_user,{},nil
  105. end
  106. return nil
  107. end
  108. local function check_address(addr)
  109. if addr.user then
  110. local cap, pluses = string.match(addr.user, '^([^%+][^%+]*)(%+.*)$')
  111. if cap then
  112. return cap, rspamd_str_split(pluses, '+'), nil
  113. end
  114. end
  115. return nil
  116. end
  117. local function set_addr(addr, new_user, new_domain)
  118. if new_user then
  119. addr.user = new_user
  120. end
  121. if new_domain then
  122. addr.domain = new_domain
  123. end
  124. if addr.domain then
  125. addr.addr = string.format('%s@%s', addr.user, addr.domain)
  126. else
  127. addr.addr = string.format('%s@', addr.user)
  128. end
  129. if addr.name and #addr.name > 0 then
  130. addr.raw = string.format('"%s" <%s>', addr.name, addr.addr)
  131. else
  132. addr.raw = string.format('<%s>', addr.addr)
  133. end
  134. end
  135. local function check_gmail(addr)
  136. local nu, tags, nd = check_gmail_user(addr)
  137. if nu then
  138. return nu, tags, nd
  139. end
  140. return nil
  141. end
  142. local function check_googlemail(addr)
  143. local nd = 'gmail.com'
  144. local nu, tags = check_gmail_user(addr)
  145. if nu then
  146. return nu, tags, nd
  147. end
  148. return nil, nil, nd
  149. end
  150. local specific_domains = {
  151. ['gmail.com'] = check_gmail,
  152. ['googlemail.com'] = check_googlemail,
  153. }
  154. if email_addr then
  155. if email_addr.domain and specific_domains[email_addr.domain] then
  156. local nu, tags, nd = specific_domains[email_addr.domain](email_addr)
  157. if nu or nd then
  158. set_addr(email_addr, nu, nd)
  159. return nu, tags
  160. end
  161. else
  162. local nu, tags, nd = check_address(email_addr)
  163. if nu or nd then
  164. set_addr(email_addr, nu, nd)
  165. return nu, tags
  166. end
  167. end
  168. return nil
  169. end
  170. end
  171. exports.is_rspamc_or_controller = function(task)
  172. local ua = task:get_request_header('User-Agent') or ''
  173. local pwd = task:get_request_header('Password')
  174. local is_rspamc = false
  175. if tostring(ua) == 'rspamc' or pwd then is_rspamc = true end
  176. return is_rspamc
  177. end
  178. --[[[
  179. -- @function lua_util.unpack(table)
  180. -- Converts numeric table to varargs
  181. -- This is `unpack` on Lua 5.1/5.2/LuaJIT and `table.unpack` on Lua 5.3
  182. -- @param {table} table numerically indexed table to unpack
  183. -- @return {varargs} unpacked table elements
  184. --]]
  185. local unpack_function = table.unpack or unpack
  186. exports.unpack = function(t)
  187. return unpack_function(t)
  188. end
  189. --[[[
  190. -- @function lua_util.spairs(table)
  191. -- Like `pairs` but keys are sorted lexicographically
  192. -- @param {table} table table containing key/value pairs
  193. -- @return {function} generator function returning key/value pairs
  194. --]]
  195. -- Sorted iteration:
  196. -- for k,v in spairs(t) do ... end
  197. --
  198. -- or with custom comparison:
  199. -- for k, v in spairs(t, function(t, a, b) return t[a] < t[b] end)
  200. --
  201. -- optional limit is also available (e.g. return top X elements)
  202. local function spairs(t, order, lim)
  203. -- collect the keys
  204. local keys = {}
  205. for k in pairs(t) do keys[#keys+1] = k end
  206. -- if order function given, sort by it by passing the table and keys a, b,
  207. -- otherwise just sort the keys
  208. if order then
  209. table.sort(keys, function(a,b) return order(t, a, b) end)
  210. else
  211. table.sort(keys)
  212. end
  213. -- return the iterator function
  214. local i = 0
  215. return function()
  216. i = i + 1
  217. if not lim or i <= lim then
  218. if keys[i] then
  219. return keys[i], t[keys[i]]
  220. end
  221. end
  222. end
  223. end
  224. exports.spairs = spairs
  225. --[[[
  226. -- @function lua_util.disable_module(modname, how)
  227. -- Disables a plugin
  228. -- @param {string} modname name of plugin to disable
  229. -- @param {string} how 'redis' to disable redis, 'config' to disable startup
  230. --]]
  231. local function disable_module(modname, how)
  232. if rspamd_plugins_state.enabled[modname] then
  233. rspamd_plugins_state.enabled[modname] = nil
  234. end
  235. if how == 'redis' then
  236. rspamd_plugins_state.disabled_redis[modname] = {}
  237. elseif how == 'config' then
  238. rspamd_plugins_state.disabled_unconfigured[modname] = {}
  239. elseif how == 'experimental' then
  240. rspamd_plugins_state.disabled_experimental[modname] = {}
  241. else
  242. rspamd_plugins_state.disabled_failed[modname] = {}
  243. end
  244. end
  245. exports.disable_module = disable_module
  246. --[[[
  247. -- @function lua_util.disable_module(modname)
  248. -- Checks experimental plugins state and disable if needed
  249. -- @param {string} modname name of plugin to check
  250. -- @return {boolean} true if plugin should be enabled, false otherwise
  251. --]]
  252. local function check_experimental(modname)
  253. if rspamd_config:experimental_enabled() then
  254. return true
  255. else
  256. disable_module(modname, 'experimental')
  257. end
  258. return false
  259. end
  260. exports.check_experimental = check_experimental
  261. --[[[
  262. -- @function lua_util.list_to_hash(list)
  263. -- Converts numerically-indexed table to table indexed by values
  264. -- @param {table} list numerically-indexed table or string, which is treated as a one-element list
  265. -- @return {table} table indexed by values
  266. -- @example
  267. -- local h = lua_util.list_to_hash({"a", "b"})
  268. -- -- h contains {a = true, b = true}
  269. --]]
  270. local function list_to_hash(list)
  271. if type(list) == 'table' then
  272. if list[1] then
  273. local h = {}
  274. for _, e in ipairs(list) do
  275. h[e] = true
  276. end
  277. return h
  278. else
  279. return list
  280. end
  281. elseif type(list) == 'string' then
  282. local h = {}
  283. h[list] = true
  284. return h
  285. end
  286. end
  287. exports.list_to_hash = list_to_hash
  288. --[[[
  289. -- @function lua_util.parse_time_interval(str)
  290. -- Parses human readable time interval
  291. -- Accepts 's' for seconds, 'm' for minutes, 'h' for hours, 'd' for days,
  292. -- 'w' for weeks, 'y' for years
  293. -- @param {string} str input string
  294. -- @return {number|nil} parsed interval as seconds (might be fractional)
  295. --]]
  296. local function parse_time_interval(str)
  297. local function parse_time_suffix(s)
  298. if s == 's' then
  299. return 1
  300. elseif s == 'm' then
  301. return 60
  302. elseif s == 'h' then
  303. return 3600
  304. elseif s == 'd' then
  305. return 86400
  306. elseif s == 'w' then
  307. return 86400 * 7
  308. elseif s == 'y' then
  309. return 365 * 86400;
  310. end
  311. end
  312. local digit = lpeg.R("09")
  313. local parser = {}
  314. parser.integer =
  315. (lpeg.S("+-") ^ -1) *
  316. (digit ^ 1)
  317. parser.fractional =
  318. (lpeg.P(".") ) *
  319. (digit ^ 1)
  320. parser.number =
  321. (parser.integer *
  322. (parser.fractional ^ -1)) +
  323. (lpeg.S("+-") * parser.fractional)
  324. parser.time = lpeg.Cf(lpeg.Cc(1) *
  325. (parser.number / tonumber) *
  326. ((lpeg.S("smhdwy") / parse_time_suffix) ^ -1),
  327. function (acc, val) return acc * val end)
  328. local t = lpeg.match(parser.time, str)
  329. return t
  330. end
  331. exports.parse_time_interval = parse_time_interval
  332. --[[[
  333. -- @function lua_util.table_cmp(t1, t2)
  334. -- Compare two tables deeply
  335. --]]
  336. local function table_cmp(table1, table2)
  337. local avoid_loops = {}
  338. local function recurse(t1, t2)
  339. if type(t1) ~= type(t2) then return false end
  340. if type(t1) ~= "table" then return t1 == t2 end
  341. if avoid_loops[t1] then return avoid_loops[t1] == t2 end
  342. avoid_loops[t1] = t2
  343. -- Copy keys from t2
  344. local t2keys = {}
  345. local t2tablekeys = {}
  346. for k, _ in pairs(t2) do
  347. if type(k) == "table" then table.insert(t2tablekeys, k) end
  348. t2keys[k] = true
  349. end
  350. -- Let's iterate keys from t1
  351. for k1, v1 in pairs(t1) do
  352. local v2 = t2[k1]
  353. if type(k1) == "table" then
  354. -- if key is a table, we need to find an equivalent one.
  355. local ok = false
  356. for i, tk in ipairs(t2tablekeys) do
  357. if table_cmp(k1, tk) and recurse(v1, t2[tk]) then
  358. table.remove(t2tablekeys, i)
  359. t2keys[tk] = nil
  360. ok = true
  361. break
  362. end
  363. end
  364. if not ok then return false end
  365. else
  366. -- t1 has a key which t2 doesn't have, fail.
  367. if v2 == nil then return false end
  368. t2keys[k1] = nil
  369. if not recurse(v1, v2) then return false end
  370. end
  371. end
  372. -- if t2 has a key which t1 doesn't have, fail.
  373. if next(t2keys) then return false end
  374. return true
  375. end
  376. return recurse(table1, table2)
  377. end
  378. exports.table_cmp = table_cmp
  379. --[[[
  380. -- @function lua_util.table_cmp(task, name, value, stop_chars)
  381. -- Performs header folding
  382. --]]
  383. exports.fold_header = function(task, name, value, stop_chars)
  384. local how
  385. if task:has_flag("milter") then
  386. how = "lf"
  387. else
  388. how = task:get_newlines_type()
  389. end
  390. return rspamd_util.fold_header(name, value, how, stop_chars)
  391. end
  392. --[[[
  393. -- @function lua_util.override_defaults(defaults, override)
  394. -- Overrides values from defaults with override
  395. --]]
  396. local function override_defaults(def, override)
  397. -- Corner cases
  398. if not override or type(override) ~= 'table' then
  399. return def
  400. end
  401. if not def or type(def) ~= 'table' then
  402. return override
  403. end
  404. local res = {}
  405. fun.each(function(k, v)
  406. if type(v) == 'table' then
  407. if def[k] and type(def[k]) == 'table' then
  408. -- Recursively override elements
  409. res[k] = override_defaults(def[k], v)
  410. else
  411. res[k] = v
  412. end
  413. else
  414. res[k] = v
  415. end
  416. end, override)
  417. fun.each(function(k, v)
  418. if not res[k] then
  419. res[k] = v
  420. end
  421. end, def)
  422. return res
  423. end
  424. exports.override_defaults = override_defaults
  425. --[[[
  426. -- @function lua_util.extract_specific_urls(params)
  427. -- params: {
  428. - - task
  429. - - limit <int> (default = 9999)
  430. - - esld_limit <int> (default = 9999) n domains per eSLD (effective second level domain)
  431. works only if number of unique eSLD less than `limit`
  432. - - need_emails <bool> (default = false)
  433. - - filter <callback> (default = nil)
  434. - - prefix <string> cache prefix (default = nil)
  435. -- }
  436. -- Apply heuristic in extracting of urls from task, this function
  437. -- tries its best to extract specific number of urls from a task based on
  438. -- their characteristics
  439. --]]
  440. -- exports.extract_specific_urls = function(params_or_task, limit, need_emails, filter, prefix)
  441. exports.extract_specific_urls = function(params_or_task, lim, need_emails, filter, prefix)
  442. local default_params = {
  443. limit = 9999,
  444. esld_limit = 9999,
  445. need_emails = false,
  446. filter = nil,
  447. prefix = nil
  448. }
  449. local params
  450. if type(params_or_task) == 'table' and type(lim) == 'nil' then
  451. params = params_or_task
  452. else
  453. -- Deprecated call
  454. params = {
  455. task = params_or_task,
  456. limit = lim,
  457. need_emails = need_emails,
  458. filter = filter,
  459. prefix = prefix
  460. }
  461. end
  462. for k,v in pairs(default_params) do
  463. if not params[k] then params[k] = v end
  464. end
  465. local cache_key
  466. if params.prefix then
  467. cache_key = params.prefix
  468. else
  469. cache_key = string.format('sp_urls_%d%s', params.limit, params.need_emails)
  470. end
  471. local cached = params.task:cache_get(cache_key)
  472. if cached then
  473. return cached
  474. end
  475. local urls = params.task:get_urls(params.need_emails)
  476. if not urls then return {} end
  477. if params.filter then urls = fun.totable(fun.filter(params.filter, urls)) end
  478. if #urls <= params.limit and #urls <= params.esld_limit then
  479. params.task:cache_set(cache_key, urls)
  480. return urls
  481. end
  482. -- Filter by tld:
  483. local tlds = {}
  484. local eslds = {}
  485. local ntlds, neslds = 0, 0
  486. local res = {}
  487. for _,u in ipairs(urls) do
  488. local esld = u:get_tld()
  489. if esld then
  490. if not eslds[esld] then
  491. eslds[esld] = {u}
  492. neslds = neslds + 1
  493. else
  494. if #eslds[esld] < params.esld_limit then
  495. table.insert(eslds[esld], u)
  496. end
  497. end
  498. local parts = rspamd_str_split(esld, '.')
  499. local tld = table.concat(fun.totable(fun.tail(parts)), '.')
  500. if not tlds[tld] then
  501. tlds[tld] = {u}
  502. ntlds = ntlds + 1
  503. else
  504. table.insert(tlds[tld], u)
  505. end
  506. -- Extract priority urls that are proven to be malicious
  507. if not u:is_html_displayed() then
  508. if u:is_obscured() then
  509. table.insert(res, u)
  510. else
  511. if u:get_user() then
  512. table.insert(res, u)
  513. elseif u:is_subject() or u:is_phished() then
  514. table.insert(res, u)
  515. end
  516. end
  517. end
  518. end
  519. end
  520. local limit = params.limit
  521. limit = limit - #res
  522. if limit <= 0 then limit = 1 end
  523. if neslds <= limit then
  524. -- We can get urls based on their eslds
  525. repeat
  526. local item_found = false
  527. for _,lurls in pairs(eslds) do
  528. if #lurls > 0 then
  529. table.insert(res, table.remove(lurls))
  530. limit = limit - 1
  531. item_found = true
  532. end
  533. end
  534. until limit <= 0 or not item_found
  535. params.task:cache_set(cache_key, urls)
  536. return res
  537. end
  538. if ntlds <= limit then
  539. while limit > 0 do
  540. for _,lurls in pairs(tlds) do
  541. if #lurls > 0 then
  542. table.insert(res, table.remove(lurls))
  543. limit = limit - 1
  544. end
  545. end
  546. end
  547. params.task:cache_set(cache_key, urls)
  548. return res
  549. end
  550. -- We need to sort tlds table first
  551. local tlds_keys = {}
  552. for k,_ in pairs(tlds) do table.insert(tlds_keys, k) end
  553. table.sort(tlds_keys, function (t1, t2)
  554. return #tlds[t1] < #tlds[t2]
  555. end)
  556. ntlds = #tlds_keys
  557. for i=1,ntlds / 2 do
  558. local tld1 = tlds[tlds_keys[i]]
  559. local tld2 = tlds[tlds_keys[ntlds - i]]
  560. if #tld1 > 0 then
  561. table.insert(res, table.remove(tld1))
  562. limit = limit - 1
  563. end
  564. if #tld2 > 0 then
  565. table.insert(res, table.remove(tld2))
  566. limit = limit - 1
  567. end
  568. if limit <= 0 then
  569. break
  570. end
  571. end
  572. params.task:cache_set(cache_key, urls)
  573. return res
  574. end
  575. --[[[
  576. -- @function lua_util.deepcopy(table)
  577. -- params: {
  578. - - table
  579. -- }
  580. -- Performs deep copy of the table. Including metatables
  581. --]]
  582. local function deepcopy(orig)
  583. local orig_type = type(orig)
  584. local copy
  585. if orig_type == 'table' then
  586. copy = {}
  587. for orig_key, orig_value in next, orig, nil do
  588. copy[deepcopy(orig_key)] = deepcopy(orig_value)
  589. end
  590. setmetatable(copy, deepcopy(getmetatable(orig)))
  591. else -- number, string, boolean, etc
  592. copy = orig
  593. end
  594. return copy
  595. end
  596. exports.deepcopy = deepcopy
  597. --[[[
  598. -- @function lua_util.shallowcopy(tbl)
  599. -- Performs shallow (and fast) copy of a table or another Lua type
  600. --]]
  601. exports.shallowcopy = function(orig)
  602. local orig_type = type(orig)
  603. local copy
  604. if orig_type == 'table' then
  605. copy = {}
  606. for orig_key, orig_value in pairs(orig) do
  607. copy[orig_key] = orig_value
  608. end
  609. else
  610. copy = orig
  611. end
  612. return copy
  613. end
  614. -- Debugging support
  615. local unconditional_debug = false
  616. local debug_modules = {}
  617. local log_level = 384 -- debug + forced (1 << 7 | 1 << 8)
  618. if type(rspamd_config) == 'userdata' then
  619. local logger = require "rspamd_logger"
  620. -- Fill debug modules from the config
  621. local logging = rspamd_config:get_all_opt('logging')
  622. if logging then
  623. local log_level_str = logging.level
  624. if log_level_str then
  625. if log_level_str == 'debug' then
  626. unconditional_debug = true
  627. end
  628. end
  629. if not unconditional_debug and logging.debug_modules then
  630. for _,m in ipairs(logging.debug_modules) do
  631. debug_modules[m] = true
  632. logger.infox(rspamd_config, 'enable debug for Lua module %s', m)
  633. end
  634. end
  635. end
  636. end
  637. --[[[
  638. -- @function lua_util.debugm(module, [log_object], format, ...)
  639. -- Performs fast debug log for a specific module
  640. --]]
  641. exports.debugm = function(mod, obj_or_fmt, fmt_or_something, ...)
  642. local logger = require "rspamd_logger"
  643. if unconditional_debug or debug_modules[mod] then
  644. if type(obj_or_fmt) == 'string' then
  645. logger.logx(log_level, mod, 2, obj_or_fmt, fmt_or_something, ...)
  646. else
  647. logger.logx(log_level, mod, obj_or_fmt, 2, fmt_or_something, ...)
  648. end
  649. end
  650. end
  651. ---[[[
  652. -- @function lua_util.get_task_verdict(task)
  653. -- Returns verdict for a task, must be called from idempotent filters only
  654. -- Returns string:
  655. -- * `spam`: if message have over reject threshold and has more than one positive rule
  656. -- * `junk`: if a message has between score between [add_header/rewrite subject] to reject thresholds and has more than two positive rules
  657. -- * `passthrough`: if a message has been passed through some short-circuit rule
  658. -- * `ham`: if a message has overall score below junk level **and** more than three negative rule, or negative total score
  659. -- * `uncertain`: all other cases
  660. --]]
  661. exports.get_task_verdict = function(task)
  662. local result = task:get_metric_result()
  663. if result then
  664. if result.passthrough then
  665. return 'passthrough'
  666. end
  667. local action = result.action
  668. if action == 'reject' and result.npositive > 1 then
  669. return 'spam'
  670. elseif action == 'no action' then
  671. if result.score < 0 or result.nnegative > 3 then
  672. return 'ham'
  673. end
  674. else
  675. -- All colors of junk
  676. if action == 'add header' or action == 'rewrite subject' then
  677. if result.npositive > 2 then
  678. return 'junk'
  679. end
  680. end
  681. end
  682. end
  683. return 'uncertain'
  684. end
  685. return exports