Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

vadesecure.lua 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. --[[
  2. Copyright (c) 2019, 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 vadesecure
  15. -- This module contains Vadesecure Filterd interface
  16. --]]
  17. local lua_util = require "lua_util"
  18. local http = require "rspamd_http"
  19. local upstream_list = require "rspamd_upstream_list"
  20. local rspamd_logger = require "rspamd_logger"
  21. local ucl = require "ucl"
  22. local N = 'vadesecure'
  23. local function vade_check(task, content, digest, rule)
  24. local function vade_url(addr)
  25. local url
  26. if rule.use_https then
  27. url = string.format('https://%s:%d%s', tostring(addr),
  28. rule.default_port, rule.url)
  29. else
  30. url = string.format('http://%s:%d%s', tostring(addr),
  31. rule.default_port, rule.url)
  32. end
  33. return url
  34. end
  35. local upstream = rule.upstreams:get_upstream_round_robin()
  36. local addr = upstream:get_addr()
  37. local retransmits = rule.retransmits
  38. local url = vade_url(addr)
  39. local hdrs = {}
  40. local helo = task:get_helo()
  41. if helo then
  42. hdrs['X-Helo'] = helo
  43. end
  44. local mail_from = task:get_from('smtp') or {}
  45. if mail_from[1] and #mail_from[1].addr > 1 then
  46. hdrs['X-Mailfrom'] = mail_from[1].addr
  47. end
  48. local rcpt_to = task:get_recipients('smtp')
  49. if rcpt_to then
  50. hdrs['X-Rcptto'] = {}
  51. for _, r in ipairs(rcpt_to) do
  52. table.insert(hdrs['X-Rcptto'], r.addr)
  53. end
  54. end
  55. local fip = task:get_from_ip()
  56. if fip and fip:is_valid() then
  57. hdrs['X-Inet'] = tostring(fip)
  58. end
  59. local request_data = {
  60. task = task,
  61. url = url,
  62. body = task:get_content(),
  63. headers = hdrs,
  64. timeout = rule.timeout,
  65. }
  66. local function vade_callback(http_err, code, body, headers)
  67. local function vade_requery()
  68. -- set current upstream to fail because an error occurred
  69. upstream:fail()
  70. -- retry with another upstream until retransmits exceeds
  71. if retransmits > 0 then
  72. retransmits = retransmits - 1
  73. lua_util.debugm(rule.name, task,
  74. '%s: Request Error: %s - retries left: %s',
  75. rule.log_prefix, http_err, retransmits)
  76. -- Select a different upstream!
  77. upstream = rule.upstreams:get_upstream_round_robin()
  78. addr = upstream:get_addr()
  79. url = vade_url(addr)
  80. lua_util.debugm(rule.name, task, '%s: retry IP: %s:%s',
  81. rule.log_prefix, addr, addr:get_port())
  82. request_data.url = url
  83. http.request(request_data)
  84. else
  85. rspamd_logger.errx(task, '%s: failed to scan, maximum retransmits '..
  86. 'exceed', rule.log_prefix)
  87. task:insert_result(rule['symbol_fail'], 0.0, 'failed to scan and '..
  88. 'retransmits exceed')
  89. end
  90. end
  91. if http_err then
  92. vade_requery()
  93. else
  94. -- Parse the response
  95. if upstream then upstream:ok() end
  96. if code ~= 200 then
  97. rspamd_logger.errx(task, 'invalid HTTP code: %s, body: %s, headers: %s', code, body, headers)
  98. task:insert_result(rule.symbol_fail, 1.0, 'Bad HTTP code: ' .. code)
  99. return
  100. end
  101. local parser = ucl.parser()
  102. local ret, err = parser:parse_string(body)
  103. if not ret then
  104. rspamd_logger.errx(task, 'vade: bad response body (raw): %s', body)
  105. task:insert_result(rule.symbol_fail, 1.0, 'Parser error: ' .. err)
  106. return
  107. end
  108. local obj = parser:get_object()
  109. local verdict = obj.verdict
  110. if not verdict then
  111. rspamd_logger.errx(task, 'vade: bad response JSON (no verdict): %s', obj)
  112. task:insert_result(rule.symbol_fail, 1.0, 'No verdict/unknown verdict')
  113. return
  114. end
  115. local vparts = lua_util.rspamd_str_split(verdict, ":")
  116. verdict = table.remove(vparts, 1) or verdict
  117. local sym = rule.symbols[verdict]
  118. if not sym then
  119. sym = rule.symbols.other
  120. end
  121. if not sym.symbol then
  122. -- Subcategory match
  123. local lvl = 'low'
  124. if vparts and vparts[1] then
  125. lvl = vparts[1]
  126. end
  127. if sym[lvl] then
  128. sym = sym[lvl]
  129. else
  130. sym = rule.symbols.other
  131. end
  132. end
  133. local opts = {}
  134. if obj.score then
  135. table.insert(opts, 'score=' .. obj.score)
  136. end
  137. if obj.elapsed then
  138. table.insert(opts, 'elapsed=' .. obj.elapsed)
  139. end
  140. if rule.log_spamcause and obj.spamcause then
  141. rspamd_logger.infox(task, 'vadesecure returned verdict="%s", score=%s, spamcause="%s"',
  142. verdict, obj.score, obj.spamcause)
  143. else
  144. lua_util.debugm(rule.name, task, 'vadesecure returned verdict="%s", score=%s, spamcause="%s"',
  145. verdict, obj.score, obj.spamcause)
  146. end
  147. if #vparts > 0 then
  148. table.insert(opts, 'verdict=' .. verdict .. ';' .. table.concat(vparts, ':'))
  149. end
  150. task:insert_result(sym.symbol, 1.0, opts)
  151. end
  152. end
  153. request_data.callback = vade_callback
  154. http.request(request_data)
  155. end
  156. local function vade_config(opts)
  157. local vade_conf = {
  158. name = N,
  159. default_port = 23808,
  160. url = '/api/v1/scan',
  161. use_https = false,
  162. timeout = 5.0,
  163. log_clean = false,
  164. retransmits = 1,
  165. cache_expire = 7200, -- expire redis in 2h
  166. message = '${SCANNER}: spam message found: "${VIRUS}"',
  167. detection_category = "hash",
  168. default_score = 1,
  169. action = false,
  170. log_spamcause = true,
  171. symbol_fail = 'VADE_FAIL',
  172. symbol = 'VADE_CHECK',
  173. symbols = {
  174. clean = {
  175. symbol = 'VADE_CLEAN',
  176. score = -0.5,
  177. description = 'VadeSecure decided message to be clean'
  178. },
  179. spam = {
  180. high = {
  181. symbol = 'VADE_SPAM_HIGH',
  182. score = 8.0,
  183. description = 'VadeSecure decided message to be clearly spam'
  184. },
  185. medium = {
  186. symbol = 'VADE_SPAM_MEDIUM',
  187. score = 5.0,
  188. description = 'VadeSecure decided message to be highly likely spam'
  189. },
  190. low = {
  191. symbol = 'VADE_SPAM_LOW',
  192. score = 2.0,
  193. description = 'VadeSecure decided message to be likely spam'
  194. },
  195. },
  196. malware = {
  197. symbol = 'VADE_MALWARE',
  198. score = 8.0,
  199. description = 'VadeSecure decided message to be malware'
  200. },
  201. scam = {
  202. symbol = 'VADE_SCAM',
  203. score = 7.0,
  204. description = 'VadeSecure decided message to be scam'
  205. },
  206. phishing = {
  207. symbol = 'VADE_PHISHING',
  208. score = 8.0,
  209. description = 'VadeSecure decided message to be phishing'
  210. },
  211. commercial = {
  212. symbol = 'VADE_COMMERCIAL',
  213. score = 0.0,
  214. description = 'VadeSecure decided message to be commercial message'
  215. },
  216. community = {
  217. symbol = 'VADE_COMMUNITY',
  218. score = 0.0,
  219. description = 'VadeSecure decided message to be community message'
  220. },
  221. transactional = {
  222. symbol = 'VADE_TRANSACTIONAL',
  223. score = 0.0,
  224. description = 'VadeSecure decided message to be transactional message'
  225. },
  226. suspect = {
  227. symbol = 'VADE_SUSPECT',
  228. score = 3.0,
  229. description = 'VadeSecure decided message to be suspicious message'
  230. },
  231. bounce = {
  232. symbol = 'VADE_BOUNCE',
  233. score = 0.0,
  234. description = 'VadeSecure decided message to be bounce message'
  235. },
  236. other = 'VADE_OTHER',
  237. }
  238. }
  239. vade_conf = lua_util.override_defaults(vade_conf, opts)
  240. if not vade_conf.prefix then
  241. vade_conf.prefix = 'rs_' .. vade_conf.name .. '_'
  242. end
  243. if not vade_conf.log_prefix then
  244. if vade_conf.name:lower() == vade_conf.type:lower() then
  245. vade_conf.log_prefix = vade_conf.name
  246. else
  247. vade_conf.log_prefix = vade_conf.name .. ' (' .. vade_conf.type .. ')'
  248. end
  249. end
  250. if not vade_conf.servers and vade_conf.socket then
  251. vade_conf.servers = vade_conf.socket
  252. end
  253. if not vade_conf.servers then
  254. rspamd_logger.errx(rspamd_config, 'no servers defined')
  255. return nil
  256. end
  257. vade_conf.upstreams = upstream_list.create(rspamd_config,
  258. vade_conf.servers,
  259. vade_conf.default_port)
  260. if vade_conf.upstreams then
  261. lua_util.add_debug_alias('external_services', vade_conf.name)
  262. return vade_conf
  263. end
  264. rspamd_logger.errx(rspamd_config, 'cannot parse servers %s',
  265. vade_conf['servers'])
  266. return nil
  267. end
  268. return {
  269. type = {'vadesecure', 'scanner'},
  270. description = 'VadeSecure Filterd interface',
  271. configure = vade_config,
  272. check = vade_check,
  273. name = N
  274. }