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_verdict.lua 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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. local exports = {}
  14. ---[[[
  15. -- @function lua_verdict.get_default_verdict(task)
  16. -- Returns verdict for a task + score if certain, must be called from idempotent filters only
  17. -- Returns string:
  18. -- * `spam`: if message have over reject threshold and has more than one positive rule
  19. -- * `junk`: if a message has between score between [add_header/rewrite subject] to reject thresholds and has more than two positive rules
  20. -- * `passthrough`: if a message has been passed through some short-circuit rule
  21. -- * `ham`: if a message has overall score below junk level **and** more than three negative rule, or negative total score
  22. -- * `uncertain`: all other cases
  23. --]]
  24. local function default_verdict_function(task)
  25. local result = task:get_metric_result()
  26. if result then
  27. if result.passthrough then
  28. return 'passthrough', nil
  29. end
  30. local score = result.score
  31. local action = result.action
  32. if action == 'reject' and result.npositive > 1 then
  33. return 'spam', score
  34. elseif action == 'no action' then
  35. if score < 0 or result.nnegative > 3 then
  36. return 'ham', score
  37. end
  38. else
  39. -- All colors of junk
  40. if action == 'add header' or action == 'rewrite subject' then
  41. if result.npositive > 2 then
  42. return 'junk', score
  43. end
  44. end
  45. end
  46. return 'uncertain', score
  47. end
  48. end
  49. local default_possible_verdicts = {
  50. passthrough = {
  51. can_learn = false,
  52. description = 'message has passthrough result',
  53. },
  54. spam = {
  55. can_learn = 'spam',
  56. description = 'message is likely spam',
  57. },
  58. junk = {
  59. can_learn = 'spam',
  60. description = 'message is likely possible spam',
  61. },
  62. ham = {
  63. can_learn = 'ham',
  64. description = 'message is likely ham',
  65. },
  66. uncertain = {
  67. can_learn = false,
  68. description = 'not certainty in verdict'
  69. }
  70. }
  71. -- Verdict functions specific for modules
  72. local specific_verdicts = {
  73. default = {
  74. callback = default_verdict_function,
  75. possible_verdicts = default_possible_verdicts
  76. }
  77. }
  78. local default_verdict = specific_verdicts.default
  79. exports.get_default_verdict = default_verdict.callback
  80. exports.set_verdict_function = function(func, what)
  81. assert(type(func) == 'function')
  82. if not what then
  83. -- Default verdict
  84. local existing = specific_verdicts.default.callback
  85. specific_verdicts.default.callback = func
  86. exports.get_default_verdict = func
  87. return existing
  88. else
  89. local existing = specific_verdicts[what]
  90. if not existing then
  91. specific_verdicts[what] = {
  92. callback = func,
  93. possible_verdicts = default_possible_verdicts
  94. }
  95. else
  96. existing = existing.callback
  97. end
  98. specific_verdicts[what].callback = func
  99. return existing
  100. end
  101. end
  102. exports.set_verdict_table = function(verdict_tbl, what)
  103. assert(type(verdict_tbl) == 'table' and
  104. type(verdict_tbl.callback) == 'function' and
  105. type(verdict_tbl.possible_verdicts) == 'table')
  106. if not what then
  107. -- Default verdict
  108. local existing = specific_verdicts.default
  109. specific_verdicts.default = verdict_tbl
  110. exports.get_default_verdict = specific_verdicts.default.callback
  111. return existing
  112. else
  113. local existing = specific_verdicts[what]
  114. specific_verdicts[what] = verdict_tbl
  115. return existing
  116. end
  117. end
  118. exports.get_specific_verdict = function(what, task)
  119. if specific_verdicts[what] then
  120. return specific_verdicts[what].callback(task)
  121. end
  122. return exports.get_default_verdict(task)
  123. end
  124. exports.get_possible_verdicts = function(what)
  125. local lua_util = require "lua_util"
  126. if what then
  127. if specific_verdicts[what] then
  128. return lua_util.keys(specific_verdicts[what].possible_verdicts)
  129. end
  130. else
  131. return lua_util.keys(specific_verdicts.default.possible_verdicts)
  132. end
  133. return nil
  134. end
  135. exports.can_learn = function(verdict, what)
  136. if what then
  137. if specific_verdicts[what] and specific_verdicts[what].possible_verdicts[verdict] then
  138. return specific_verdicts[what].possible_verdicts[verdict].can_learn
  139. end
  140. else
  141. if specific_verdicts.default.possible_verdicts[verdict] then
  142. return specific_verdicts.default.possible_verdicts[verdict].can_learn
  143. end
  144. end
  145. return nil -- To distinguish from `false` that could happen in can_learn
  146. end
  147. exports.describe = function(verdict, what)
  148. if what then
  149. if specific_verdicts[what] and specific_verdicts[what].possible_verdicts[verdict] then
  150. return specific_verdicts[what].possible_verdicts[verdict].description
  151. end
  152. else
  153. if specific_verdicts.default.possible_verdicts[verdict] then
  154. return specific_verdicts.default.possible_verdicts[verdict].description
  155. end
  156. end
  157. return nil
  158. end
  159. return exports