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.

antivirus.lua 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986
  1. --[[
  2. Copyright (c) 2018, 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_antivirus
  15. -- This module contains antivirus access functions
  16. --]]
  17. local lua_util = require "lua_util"
  18. local tcp = require "rspamd_tcp"
  19. local upstream_list = require "rspamd_upstream_list"
  20. local rspamd_util = require "rspamd_util"
  21. local lua_redis = require "lua_redis"
  22. local rspamd_logger = require "rspamd_logger"
  23. local N = "antivirus"
  24. local default_message = '${SCANNER}: virus found: "${VIRUS}"'
  25. local function match_patterns(default_sym, found, patterns)
  26. if type(patterns) ~= 'table' then return default_sym end
  27. if not patterns[1] then
  28. for sym, pat in pairs(patterns) do
  29. if pat:match(found) then
  30. return sym
  31. end
  32. end
  33. return default_sym
  34. else
  35. for _, p in ipairs(patterns) do
  36. for sym, pat in pairs(p) do
  37. if pat:match(found) then
  38. return sym
  39. end
  40. end
  41. end
  42. return default_sym
  43. end
  44. end
  45. local function yield_result(task, rule, vname)
  46. local all_whitelisted = true
  47. if type(vname) == 'string' then
  48. local symname = match_patterns(rule['symbol'], vname, rule['patterns'])
  49. if rule['whitelist'] and rule['whitelist']:get_key(vname) then
  50. rspamd_logger.infox(task, '%s: "%s" is in whitelist', rule['type'], vname)
  51. return
  52. end
  53. task:insert_result(symname, 1.0, vname)
  54. rspamd_logger.infox(task, '%s: virus found: "%s"', rule['type'], vname)
  55. elseif type(vname) == 'table' then
  56. for _, vn in ipairs(vname) do
  57. local symname = match_patterns(rule['symbol'], vn, rule['patterns'])
  58. if rule['whitelist'] and rule['whitelist']:get_key(vn) then
  59. rspamd_logger.infox(task, '%s: "%s" is in whitelist', rule['type'], vn)
  60. else
  61. all_whitelisted = false
  62. task:insert_result(symname, 1.0, vn)
  63. rspamd_logger.infox(task, '%s: virus found: "%s"', rule['type'], vn)
  64. end
  65. end
  66. end
  67. if rule['action'] then
  68. if type(vname) == 'table' then
  69. if all_whitelisted then return end
  70. vname = table.concat(vname, '; ')
  71. end
  72. task:set_pre_result(rule['action'],
  73. lua_util.template(rule.message or 'Rejected', {
  74. SCANNER = rule['type'],
  75. VIRUS = vname,
  76. }), N)
  77. end
  78. end
  79. local function clamav_config(opts)
  80. local clamav_conf = {
  81. scan_mime_parts = true;
  82. scan_text_mime = false;
  83. scan_image_mime = false;
  84. default_port = 3310,
  85. log_clean = false,
  86. timeout = 15.0, -- FIXME: this will break task_timeout!
  87. retransmits = 2,
  88. cache_expire = 3600, -- expire redis in one hour
  89. message = default_message,
  90. }
  91. for k,v in pairs(opts) do
  92. clamav_conf[k] = v
  93. end
  94. if not clamav_conf.prefix then
  95. clamav_conf.prefix = 'rs_cl'
  96. end
  97. if not clamav_conf['servers'] then
  98. rspamd_logger.errx(rspamd_config, 'no servers defined')
  99. return nil
  100. end
  101. clamav_conf['upstreams'] = upstream_list.create(rspamd_config,
  102. clamav_conf['servers'],
  103. clamav_conf.default_port)
  104. if clamav_conf['upstreams'] then
  105. return clamav_conf
  106. end
  107. rspamd_logger.errx(rspamd_config, 'cannot parse servers %s',
  108. clamav_conf['servers'])
  109. return nil
  110. end
  111. local function fprot_config(opts)
  112. local fprot_conf = {
  113. scan_mime_parts = true;
  114. scan_text_mime = false;
  115. scan_image_mime = false;
  116. default_port = 10200,
  117. timeout = 15.0, -- FIXME: this will break task_timeout!
  118. log_clean = false,
  119. retransmits = 2,
  120. cache_expire = 3600, -- expire redis in one hour
  121. message = default_message,
  122. }
  123. for k,v in pairs(opts) do
  124. fprot_conf[k] = v
  125. end
  126. if not fprot_conf.prefix then
  127. fprot_conf.prefix = 'rs_fp'
  128. end
  129. if not fprot_conf['servers'] then
  130. rspamd_logger.errx(rspamd_config, 'no servers defined')
  131. return nil
  132. end
  133. fprot_conf['upstreams'] = upstream_list.create(rspamd_config,
  134. fprot_conf['servers'],
  135. fprot_conf.default_port)
  136. if fprot_conf['upstreams'] then
  137. return fprot_conf
  138. end
  139. rspamd_logger.errx(rspamd_config, 'cannot parse servers %s',
  140. fprot_conf['servers'])
  141. return nil
  142. end
  143. local function sophos_config(opts)
  144. local sophos_conf = {
  145. scan_mime_parts = true;
  146. scan_text_mime = false;
  147. scan_image_mime = false;
  148. default_port = 4010,
  149. timeout = 15.0,
  150. log_clean = false,
  151. retransmits = 2,
  152. cache_expire = 3600, -- expire redis in one hour
  153. message = default_message,
  154. savdi_report_encrypted = false,
  155. savdi_report_oversize = false,
  156. }
  157. for k,v in pairs(opts) do
  158. sophos_conf[k] = v
  159. end
  160. if not sophos_conf.prefix then
  161. sophos_conf.prefix = 'rs_sp'
  162. end
  163. if not sophos_conf['servers'] then
  164. rspamd_logger.errx(rspamd_config, 'no servers defined')
  165. return nil
  166. end
  167. sophos_conf['upstreams'] = upstream_list.create(rspamd_config,
  168. sophos_conf['servers'],
  169. sophos_conf.default_port)
  170. if sophos_conf['upstreams'] then
  171. return sophos_conf
  172. end
  173. rspamd_logger.errx(rspamd_config, 'cannot parse servers %s',
  174. sophos_conf['servers'])
  175. return nil
  176. end
  177. local function savapi_config(opts)
  178. local savapi_conf = {
  179. scan_mime_parts = true;
  180. scan_text_mime = false;
  181. scan_image_mime = false;
  182. default_port = 4444, -- note: You must set ListenAddress in savapi.conf
  183. product_id = 0,
  184. log_clean = false,
  185. timeout = 15.0, -- FIXME: this will break task_timeout!
  186. retransmits = 1, -- FIXME: useless, for local files
  187. cache_expire = 3600, -- expire redis in one hour
  188. message = default_message,
  189. tmpdir = '/tmp',
  190. }
  191. for k,v in pairs(opts) do
  192. savapi_conf[k] = v
  193. end
  194. if not savapi_conf.prefix then
  195. savapi_conf.prefix = 'rs_ap'
  196. end
  197. if not savapi_conf['servers'] then
  198. rspamd_logger.errx(rspamd_config, 'no servers defined')
  199. return nil
  200. end
  201. savapi_conf['upstreams'] = upstream_list.create(rspamd_config,
  202. savapi_conf['servers'],
  203. savapi_conf.default_port)
  204. if savapi_conf['upstreams'] then
  205. return savapi_conf
  206. end
  207. rspamd_logger.errx(rspamd_config, 'cannot parse servers %s',
  208. savapi_conf['servers'])
  209. return nil
  210. end
  211. local function kaspersky_config(opts)
  212. local kaspersky_conf = {
  213. scan_mime_parts = true;
  214. scan_text_mime = false;
  215. scan_image_mime = false;
  216. product_id = 0,
  217. log_clean = false,
  218. timeout = 5.0,
  219. retransmits = 1, -- use local files, retransmits are useless
  220. cache_expire = 3600, -- expire redis in one hour
  221. message = default_message,
  222. tmpdir = '/tmp',
  223. prefix = 'rs_ak',
  224. }
  225. kaspersky_conf = lua_util.override_defaults(kaspersky_conf, opts)
  226. if not kaspersky_conf['servers'] then
  227. rspamd_logger.errx(rspamd_config, 'no servers defined')
  228. return nil
  229. end
  230. kaspersky_conf['upstreams'] = upstream_list.create(rspamd_config,
  231. kaspersky_conf['servers'], 0)
  232. if kaspersky_conf['upstreams'] then
  233. return kaspersky_conf
  234. end
  235. rspamd_logger.errx(rspamd_config, 'cannot parse servers %s',
  236. kaspersky_conf['servers'])
  237. return nil
  238. end
  239. local function message_not_too_large(task, content, rule)
  240. local max_size = tonumber(rule['max_size'])
  241. if not max_size then return true end
  242. if #content > max_size then
  243. rspamd_logger.infox("skip %s AV check as it is too large: %s (%s is allowed)",
  244. rule.type, #content, max_size)
  245. return false
  246. end
  247. return true
  248. end
  249. local function need_av_check(task, content, rule)
  250. return message_not_too_large(task, content, rule)
  251. end
  252. local function check_av_cache(task, digest, rule, fn)
  253. local key = digest
  254. local function redis_av_cb(err, data)
  255. if data and type(data) == 'string' then
  256. -- Cached
  257. if data ~= 'OK' then
  258. lua_util.debugm(N, task, 'got cached result for %s: %s', key, data)
  259. data = rspamd_str_split(data, '\v')
  260. yield_result(task, rule, data)
  261. else
  262. lua_util.debugm(N, task, 'got cached result for %s: %s', key, data)
  263. end
  264. else
  265. if err then
  266. rspamd_logger.errx(task, 'Got error checking cache: %1', err)
  267. end
  268. fn()
  269. end
  270. end
  271. if rule.redis_params then
  272. key = rule['prefix'] .. key
  273. if lua_redis.redis_make_request(task,
  274. rule.redis_params, -- connect params
  275. key, -- hash key
  276. false, -- is write
  277. redis_av_cb, --callback
  278. 'GET', -- command
  279. {key} -- arguments)
  280. ) then
  281. return true
  282. end
  283. end
  284. return false
  285. end
  286. local function save_av_cache(task, digest, rule, to_save)
  287. local key = digest
  288. local function redis_set_cb(err)
  289. -- Do nothing
  290. if err then
  291. rspamd_logger.errx(task, 'failed to save virus cache for %s -> "%s": %s',
  292. to_save, key, err)
  293. else
  294. lua_util.debugm(N, task, 'saved cached result for %s: %s',
  295. key, to_save)
  296. end
  297. end
  298. if type(to_save) == 'table' then
  299. to_save = table.concat(to_save, '\v')
  300. end
  301. if rule.redis_params then
  302. key = rule['prefix'] .. key
  303. lua_redis.redis_make_request(task,
  304. rule.redis_params, -- connect params
  305. key, -- hash key
  306. true, -- is write
  307. redis_set_cb, --callback
  308. 'SETEX', -- command
  309. { key, rule['cache_expire'], to_save }
  310. )
  311. end
  312. return false
  313. end
  314. local function fprot_check(task, content, digest, rule)
  315. local function fprot_check_uncached ()
  316. local upstream = rule.upstreams:get_upstream_round_robin()
  317. local addr = upstream:get_addr()
  318. local retransmits = rule.retransmits
  319. local scan_id = task:get_queue_id()
  320. if not scan_id then scan_id = task:get_uid() end
  321. local header = string.format('SCAN STREAM %s SIZE %d\n', scan_id,
  322. #content)
  323. local footer = '\n'
  324. local function fprot_callback(err, data)
  325. if err then
  326. -- set current upstream to fail because an error occurred
  327. upstream:fail()
  328. -- retry with another upstream until retransmits exceeds
  329. if retransmits > 0 then
  330. retransmits = retransmits - 1
  331. -- Select a different upstream!
  332. upstream = rule.upstreams:get_upstream_round_robin()
  333. addr = upstream:get_addr()
  334. lua_util.debugm(N, task, '%s [%s]: retry IP: %s', rule['symbol'], rule['type'], addr)
  335. tcp.request({
  336. task = task,
  337. host = addr:to_string(),
  338. port = addr:get_port(),
  339. timeout = rule['timeout'],
  340. callback = fprot_callback,
  341. data = { header, content, footer },
  342. stop_pattern = '\n'
  343. })
  344. else
  345. rspamd_logger.errx(task,
  346. '%s [%s]: failed to scan, maximum retransmits exceed',
  347. rule['symbol'], rule['type'])
  348. task:insert_result(rule['symbol_fail'], 0.0,
  349. 'failed to scan and retransmits exceed')
  350. end
  351. else
  352. upstream:ok()
  353. data = tostring(data)
  354. local cached
  355. local clean = string.match(data, '^0 <clean>')
  356. if clean then
  357. cached = 'OK'
  358. if rule['log_clean'] then
  359. rspamd_logger.infox(task,
  360. '%s [%s]: message or mime_part is clean',
  361. rule['symbol'], rule['type'])
  362. end
  363. else
  364. -- returncodes: 1: infected, 2: suspicious, 3: both, 4-255: some error occured
  365. -- see http://www.f-prot.com/support/helpfiles/unix/appendix_c.html for more detail
  366. local vname = string.match(data, '^[1-3] <[%w%s]-: (.-)>')
  367. if not vname then
  368. rspamd_logger.errx(task, 'Unhandled response: %s', data)
  369. else
  370. yield_result(task, rule, vname)
  371. cached = vname
  372. end
  373. end
  374. if cached then
  375. save_av_cache(task, digest, rule, cached)
  376. end
  377. end
  378. end
  379. tcp.request({
  380. task = task,
  381. host = addr:to_string(),
  382. port = addr:get_port(),
  383. timeout = rule['timeout'],
  384. callback = fprot_callback,
  385. data = { header, content, footer },
  386. stop_pattern = '\n'
  387. })
  388. end
  389. if need_av_check(task, content, rule) then
  390. if check_av_cache(task, digest, rule, fprot_check_uncached) then
  391. return
  392. else
  393. fprot_check_uncached()
  394. end
  395. end
  396. end
  397. local function clamav_check(task, content, digest, rule)
  398. local function clamav_check_uncached ()
  399. local upstream = rule.upstreams:get_upstream_round_robin()
  400. local addr = upstream:get_addr()
  401. local retransmits = rule.retransmits
  402. local header = rspamd_util.pack("c9 c1 >I4", "zINSTREAM", "\0",
  403. #content)
  404. local footer = rspamd_util.pack(">I4", 0)
  405. local function clamav_callback(err, data)
  406. if err then
  407. -- set current upstream to fail because an error occurred
  408. upstream:fail()
  409. -- retry with another upstream until retransmits exceeds
  410. if retransmits > 0 then
  411. retransmits = retransmits - 1
  412. -- Select a different upstream!
  413. upstream = rule.upstreams:get_upstream_round_robin()
  414. addr = upstream:get_addr()
  415. lua_util.debugm(N, task, '%s [%s]: retry IP: %s', rule['symbol'], rule['type'], addr)
  416. tcp.request({
  417. task = task,
  418. host = addr:to_string(),
  419. port = addr:get_port(),
  420. timeout = rule['timeout'],
  421. callback = clamav_callback,
  422. data = { header, content, footer },
  423. stop_pattern = '\0'
  424. })
  425. else
  426. rspamd_logger.errx(task, '%s [%s]: failed to scan, maximum retransmits exceed', rule['symbol'], rule['type'])
  427. task:insert_result(rule['symbol_fail'], 0.0, 'failed to scan and retransmits exceed')
  428. end
  429. else
  430. upstream:ok()
  431. data = tostring(data)
  432. local cached
  433. lua_util.debugm(N, task, '%s [%s]: got reply: %s', rule['symbol'], rule['type'], data)
  434. if data == 'stream: OK' then
  435. cached = 'OK'
  436. if rule['log_clean'] then
  437. rspamd_logger.infox(task, '%s [%s]: message or mime_part is clean', rule['symbol'], rule['type'])
  438. else
  439. lua_util.debugm(N, task, '%s [%s]: message or mime_part is clean', rule['symbol'], rule['type'])
  440. end
  441. else
  442. local vname = string.match(data, 'stream: (.+) FOUND')
  443. if vname then
  444. yield_result(task, rule, vname)
  445. cached = vname
  446. else
  447. rspamd_logger.errx(task, 'unhandled response: %s', data)
  448. task:insert_result(rule['symbol_fail'], 0.0, 'unhandled response')
  449. end
  450. end
  451. if cached then
  452. save_av_cache(task, digest, rule, cached)
  453. end
  454. end
  455. end
  456. tcp.request({
  457. task = task,
  458. host = addr:to_string(),
  459. port = addr:get_port(),
  460. timeout = rule['timeout'],
  461. callback = clamav_callback,
  462. data = { header, content, footer },
  463. stop_pattern = '\0'
  464. })
  465. end
  466. if need_av_check(task, content, rule) then
  467. if check_av_cache(task, digest, rule, clamav_check_uncached) then
  468. return
  469. else
  470. clamav_check_uncached()
  471. end
  472. end
  473. end
  474. local function sophos_check(task, content, digest, rule)
  475. local function sophos_check_uncached ()
  476. local upstream = rule.upstreams:get_upstream_round_robin()
  477. local addr = upstream:get_addr()
  478. local retransmits = rule.retransmits
  479. local protocol = 'SSSP/1.0\n'
  480. local streamsize = string.format('SCANDATA %d\n', #content)
  481. local bye = 'BYE\n'
  482. local function sophos_callback(err, data, conn)
  483. if err then
  484. -- set current upstream to fail because an error occurred
  485. upstream:fail()
  486. -- retry with another upstream until retransmits exceeds
  487. if retransmits > 0 then
  488. retransmits = retransmits - 1
  489. -- Select a different upstream!
  490. upstream = rule.upstreams:get_upstream_round_robin()
  491. addr = upstream:get_addr()
  492. lua_util.debugm(N, task, '%s [%s]: retry IP: %s', rule['symbol'], rule['type'], addr)
  493. tcp.request({
  494. task = task,
  495. host = addr:to_string(),
  496. port = addr:get_port(),
  497. timeout = rule['timeout'],
  498. callback = sophos_callback,
  499. data = { protocol, streamsize, content, bye }
  500. })
  501. else
  502. rspamd_logger.errx(task, '%s [%s]: failed to scan, maximum retransmits exceed', rule['symbol'], rule['type'])
  503. task:insert_result(rule['symbol_fail'], 0.0, 'failed to scan and retransmits exceed')
  504. end
  505. else
  506. upstream:ok()
  507. data = tostring(data)
  508. lua_util.debugm(N, task, '%s [%s]: got reply: %s', rule['symbol'], rule['type'], data)
  509. local vname = string.match(data, 'VIRUS (%S+) ')
  510. if vname then
  511. yield_result(task, rule, vname)
  512. save_av_cache(task, digest, rule, vname)
  513. else
  514. if string.find(data, 'DONE OK') then
  515. if rule['log_clean'] then
  516. rspamd_logger.infox(task, '%s [%s]: message or mime_part is clean', rule['symbol'], rule['type'])
  517. else
  518. lua_util.debugm(N, task, '%s [%s]: message or mime_part is clean', rule['symbol'], rule['type'])
  519. end
  520. save_av_cache(task, digest, rule, 'OK')
  521. -- not finished - continue
  522. elseif string.find(data, 'ACC') or string.find(data, 'OK SSSP') then
  523. conn:add_read(sophos_callback)
  524. -- set pseudo virus if configured, else do nothing since it's no fatal
  525. elseif string.find(data, 'FAIL 0212') then
  526. rspamd_logger.infox(task, 'Message is ENCRYPTED (0212 SOPHOS_SAVI_ERROR_FILE_ENCRYPTED): %s', data)
  527. if rule['savdi_report_encrypted'] then
  528. yield_result(task, rule, "SAVDI_FILE_ENCRYPTED")
  529. save_av_cache(task, digest, rule, "SAVDI_FILE_ENCRYPTED")
  530. end
  531. -- set pseudo virus if configured, else set fail since part was not scanned
  532. elseif string.find(data, 'REJ 4') then
  533. if rule['savdi_report_oversize'] then
  534. rspamd_logger.infox(task, 'SAVDI: Message is OVERSIZED (SSSP reject code 4): %s', data)
  535. yield_result(task, rule, "SAVDI_FILE_OVERSIZED")
  536. save_av_cache(task, digest, rule, "SAVDI_FILE_OVERSIZED")
  537. else
  538. rspamd_logger.errx(task, 'SAVDI: Message is OVERSIZED (SSSP reject code 4): %s', data)
  539. task:insert_result(rule['symbol_fail'], 0.0, 'Message is OVERSIZED (SSSP reject code 4):' .. data)
  540. end
  541. -- excplicitly set REJ1 message when SAVDIreports a protocol error
  542. elseif string.find(data, 'REJ 1') then
  543. rspamd_logger.errx(task, 'SAVDI (Protocol error (REJ 1)): %s', data)
  544. task:insert_result(rule['symbol_fail'], 0.0, 'SAVDI (Protocol error (REJ 1)):' .. data)
  545. else
  546. rspamd_logger.errx(task, 'unhandled response: %s', data)
  547. task:insert_result(rule['symbol_fail'], 0.0, 'unhandled response')
  548. end
  549. end
  550. end
  551. end
  552. tcp.request({
  553. task = task,
  554. host = addr:to_string(),
  555. port = addr:get_port(),
  556. timeout = rule['timeout'],
  557. callback = sophos_callback,
  558. data = { protocol, streamsize, content, bye }
  559. })
  560. end
  561. if need_av_check(task, content, rule) then
  562. if check_av_cache(task, digest, rule, sophos_check_uncached) then
  563. return
  564. else
  565. sophos_check_uncached()
  566. end
  567. end
  568. end
  569. local function savapi_check(task, content, digest, rule)
  570. local function savapi_check_uncached ()
  571. local upstream = rule.upstreams:get_upstream_round_robin()
  572. local addr = upstream:get_addr()
  573. local retransmits = rule.retransmits
  574. local fname = string.format('%s/%s.tmp',
  575. rule.tmpdir, rspamd_util.random_hex(32))
  576. local message_fd = rspamd_util.create_file(fname)
  577. if not message_fd then
  578. rspamd_logger.errx('cannot store file for savapi scan: %s', fname)
  579. return
  580. end
  581. if type(content) == 'string' then
  582. -- Create rspamd_text
  583. local rspamd_text = require "rspamd_text"
  584. content = rspamd_text.fromstring(content)
  585. end
  586. content:save_in_file(message_fd)
  587. -- Ensure cleanup
  588. task:get_mempool():add_destructor(function()
  589. os.remove(fname)
  590. rspamd_util.close_file(message_fd)
  591. end)
  592. local vnames = {}
  593. -- Forward declaration for recursive calls
  594. local savapi_scan1_cb
  595. local function savapi_fin_cb(err, conn)
  596. local vnames_reordered = {}
  597. -- Swap table
  598. for virus,_ in pairs(vnames) do
  599. table.insert(vnames_reordered, virus)
  600. end
  601. lua_util.debugm(N, task, "%s: number of virus names found %s", rule['type'], #vnames_reordered)
  602. if #vnames_reordered > 0 then
  603. local vname = {}
  604. for _,virus in ipairs(vnames_reordered) do
  605. table.insert(vname, virus)
  606. end
  607. yield_result(task, rule, vname)
  608. save_av_cache(task, digest, rule, vname)
  609. end
  610. if conn then
  611. conn:close()
  612. end
  613. end
  614. local function savapi_scan2_cb(err, data, conn)
  615. local result = tostring(data)
  616. lua_util.debugm(N, task, "%s: got reply: %s",
  617. rule['type'], result)
  618. -- Terminal response - clean
  619. if string.find(result, '200') or string.find(result, '210') then
  620. if rule['log_clean'] then
  621. rspamd_logger.infox(task, '%s: message or mime_part is clean', rule['type'])
  622. end
  623. save_av_cache(task, digest, rule, 'OK')
  624. conn:add_write(savapi_fin_cb, 'QUIT\n')
  625. -- Terminal response - infected
  626. elseif string.find(result, '319') then
  627. conn:add_write(savapi_fin_cb, 'QUIT\n')
  628. -- Non-terminal response
  629. elseif string.find(result, '310') then
  630. local virus
  631. virus = result:match "310.*<<<%s(.*)%s+;.*;.*"
  632. if not virus then
  633. virus = result:match "310%s(.*)%s+;.*;.*"
  634. if not virus then
  635. rspamd_logger.errx(task, "%s: virus result unparseable: %s",
  636. rule['type'], result)
  637. return
  638. end
  639. end
  640. -- Store unique virus names
  641. vnames[virus] = 1
  642. -- More content is expected
  643. conn:add_write(savapi_scan1_cb, '\n')
  644. end
  645. end
  646. savapi_scan1_cb = function(err, conn)
  647. conn:add_read(savapi_scan2_cb, '\n')
  648. end
  649. -- 100 PRODUCT:xyz
  650. local function savapi_greet2_cb(err, data, conn)
  651. local result = tostring(data)
  652. if string.find(result, '100 PRODUCT') then
  653. lua_util.debugm(N, task, "%s: scanning file: %s",
  654. rule['type'], fname)
  655. conn:add_write(savapi_scan1_cb, {string.format('SCAN %s\n',
  656. fname)})
  657. else
  658. rspamd_logger.errx(task, '%s: invalid product id %s', rule['type'],
  659. rule['product_id'])
  660. conn:add_write(savapi_fin_cb, 'QUIT\n')
  661. end
  662. end
  663. local function savapi_greet1_cb(err, conn)
  664. conn:add_read(savapi_greet2_cb, '\n')
  665. end
  666. local function savapi_callback_init(err, data, conn)
  667. if err then
  668. -- set current upstream to fail because an error occurred
  669. upstream:fail()
  670. -- retry with another upstream until retransmits exceeds
  671. if retransmits > 0 then
  672. retransmits = retransmits - 1
  673. -- Select a different upstream!
  674. upstream = rule.upstreams:get_upstream_round_robin()
  675. addr = upstream:get_addr()
  676. lua_util.debugm(N, task, '%s [%s]: retry IP: %s', rule['symbol'], rule['type'], addr)
  677. tcp.request({
  678. task = task,
  679. host = addr:to_string(),
  680. port = addr:get_port(),
  681. timeout = rule['timeout'],
  682. callback = savapi_callback_init,
  683. stop_pattern = {'\n'},
  684. })
  685. else
  686. rspamd_logger.errx(task, '%s [%s]: failed to scan, maximum retransmits exceed', rule['symbol'], rule['type'])
  687. task:insert_result(rule['symbol_fail'], 0.0, 'failed to scan and retransmits exceed')
  688. end
  689. else
  690. upstream:ok()
  691. local result = tostring(data)
  692. -- 100 SAVAPI:4.0 greeting
  693. if string.find(result, '100') then
  694. conn:add_write(savapi_greet1_cb, {string.format('SET PRODUCT %s\n', rule['product_id'])})
  695. end
  696. end
  697. end
  698. tcp.request({
  699. task = task,
  700. host = addr:to_string(),
  701. port = addr:get_port(),
  702. timeout = rule['timeout'],
  703. callback = savapi_callback_init,
  704. stop_pattern = {'\n'},
  705. })
  706. end
  707. if need_av_check(task, content, rule) then
  708. if check_av_cache(task, digest, rule, savapi_check_uncached) then
  709. return
  710. else
  711. savapi_check_uncached()
  712. end
  713. end
  714. end
  715. local function kaspersky_check(task, content, digest, rule)
  716. local function kaspersky_check_uncached ()
  717. local upstream = rule.upstreams:get_upstream_round_robin()
  718. local addr = upstream:get_addr()
  719. local retransmits = rule.retransmits
  720. local fname = string.format('%s/%s.tmp',
  721. rule.tmpdir, rspamd_util.random_hex(32))
  722. local message_fd = rspamd_util.create_file(fname)
  723. local clamav_compat_cmd = string.format("nSCAN %s\n", fname)
  724. if not message_fd then
  725. rspamd_logger.errx('cannot store file for kaspersky scan: %s', fname)
  726. return
  727. end
  728. if type(content) == 'string' then
  729. -- Create rspamd_text
  730. local rspamd_text = require "rspamd_text"
  731. content = rspamd_text.fromstring(content)
  732. end
  733. content:save_in_file(message_fd)
  734. -- Ensure file cleanup
  735. task:get_mempool():add_destructor(function()
  736. os.remove(fname)
  737. rspamd_util.close_file(message_fd)
  738. end)
  739. local function kaspersky_callback(err, data)
  740. if err then
  741. -- set current upstream to fail because an error occurred
  742. upstream:fail()
  743. -- retry with another upstream until retransmits exceeds
  744. if retransmits > 0 then
  745. retransmits = retransmits - 1
  746. -- Select a different upstream!
  747. upstream = rule.upstreams:get_upstream_round_robin()
  748. addr = upstream:get_addr()
  749. lua_util.debugm(N, task,
  750. '%s [%s]: retry IP: %s', rule['symbol'], rule['type'], addr)
  751. tcp.request({
  752. task = task,
  753. host = addr:to_string(),
  754. port = addr:get_port(),
  755. timeout = rule['timeout'],
  756. callback = kaspersky_callback,
  757. data = { clamav_compat_cmd },
  758. stop_pattern = '\n'
  759. })
  760. else
  761. rspamd_logger.errx(task,
  762. '%s [%s]: failed to scan, maximum retransmits exceed',
  763. rule['symbol'], rule['type'])
  764. task:insert_result(rule['symbol_fail'], 0.0,
  765. 'failed to scan and retransmits exceed')
  766. end
  767. else
  768. upstream:ok()
  769. data = tostring(data)
  770. local cached
  771. lua_util.debugm(N, task, '%s [%s]: got reply: %s',
  772. rule['symbol'], rule['type'], data)
  773. if data == 'stream: OK' then
  774. cached = 'OK'
  775. if rule['log_clean'] then
  776. rspamd_logger.infox(task, '%s [%s]: message or mime_part is clean',
  777. rule['symbol'], rule['type'])
  778. else
  779. lua_util.debugm(N, task, '%s [%s]: message or mime_part is clean',
  780. rule['symbol'], rule['type'])
  781. end
  782. else
  783. local vname = string.match(data, ': (.+) FOUND')
  784. if vname then
  785. yield_result(task, rule, vname)
  786. cached = vname
  787. else
  788. rspamd_logger.errx(task, 'unhandled response: %s', data)
  789. task:insert_result(rule['symbol_fail'], 0.0, 'unhandled response')
  790. end
  791. end
  792. if cached then
  793. save_av_cache(task, digest, rule, cached)
  794. end
  795. end
  796. end
  797. tcp.request({
  798. task = task,
  799. host = addr:to_string(),
  800. port = addr:get_port(),
  801. timeout = rule['timeout'],
  802. callback = kaspersky_callback,
  803. data = { clamav_compat_cmd },
  804. stop_pattern = '\n'
  805. })
  806. end
  807. if need_av_check(task, content, rule) then
  808. if check_av_cache(task, digest, rule, kaspersky_check_uncached) then
  809. return
  810. else
  811. kaspersky_check_uncached()
  812. end
  813. end
  814. end
  815. local exports = {
  816. av_types = {
  817. clamav = {
  818. configure = clamav_config,
  819. check = clamav_check
  820. },
  821. fprot = {
  822. configure = fprot_config,
  823. check = fprot_check
  824. },
  825. sophos = {
  826. configure = sophos_config,
  827. check = sophos_check
  828. },
  829. savapi = {
  830. configure = savapi_config,
  831. check = savapi_check
  832. },
  833. kaspersky = {
  834. configure = kaspersky_config,
  835. check = kaspersky_check
  836. }
  837. },
  838. -- Some utilities
  839. match_patterns = match_patterns,
  840. check_av_cache = check_av_cache,
  841. save_av_cache = save_av_cache,
  842. }
  843. exports.add_antivirus = function(name, conf_func, check_func)
  844. assert(type(conf_func) == 'function' and type(check_func) == 'function',
  845. 'bad arguments')
  846. exports.av_types[name] = {
  847. configure = conf_func,
  848. check = check_func,
  849. }
  850. end
  851. return exports