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.

clamav.lua 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  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 clamav
  15. -- This module contains clamav 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 rspamd_logger = require "rspamd_logger"
  22. local common = require "lua_scanners/common"
  23. local N = "clamav"
  24. local default_message = '${SCANNER}: virus found: "${VIRUS}"'
  25. local function clamav_config(opts)
  26. local clamav_conf = {
  27. name = N,
  28. scan_mime_parts = true,
  29. scan_text_mime = false,
  30. scan_image_mime = false,
  31. default_port = 3310,
  32. log_clean = false,
  33. timeout = 5.0, -- FIXME: this will break task_timeout!
  34. detection_category = "virus",
  35. retransmits = 2,
  36. cache_expire = 3600, -- expire redis in one hour
  37. message = default_message,
  38. }
  39. clamav_conf = lua_util.override_defaults(clamav_conf, opts)
  40. if not clamav_conf.prefix then
  41. clamav_conf.prefix = 'rs_' .. clamav_conf.name .. '_'
  42. end
  43. if not clamav_conf.log_prefix then
  44. if clamav_conf.name:lower() == clamav_conf.type:lower() then
  45. clamav_conf.log_prefix = clamav_conf.name
  46. else
  47. clamav_conf.log_prefix = clamav_conf.name .. ' (' .. clamav_conf.type .. ')'
  48. end
  49. end
  50. if not clamav_conf['servers'] then
  51. rspamd_logger.errx(rspamd_config, 'no servers defined')
  52. return nil
  53. end
  54. clamav_conf['upstreams'] = upstream_list.create(rspamd_config,
  55. clamav_conf['servers'],
  56. clamav_conf.default_port)
  57. if clamav_conf['upstreams'] then
  58. lua_util.add_debug_alias('antivirus', clamav_conf.name)
  59. return clamav_conf
  60. end
  61. rspamd_logger.errx(rspamd_config, 'cannot parse servers %s',
  62. clamav_conf['servers'])
  63. return nil
  64. end
  65. local function clamav_check(task, content, digest, rule)
  66. local function clamav_check_uncached ()
  67. local upstream = rule.upstreams:get_upstream_round_robin()
  68. local addr = upstream:get_addr()
  69. local retransmits = rule.retransmits
  70. local header = rspamd_util.pack("c9 c1 >I4", "zINSTREAM", "\0",
  71. #content)
  72. local footer = rspamd_util.pack(">I4", 0)
  73. local function clamav_callback(err, data)
  74. if err then
  75. -- set current upstream to fail because an error occurred
  76. upstream:fail()
  77. -- retry with another upstream until retransmits exceeds
  78. if retransmits > 0 then
  79. retransmits = retransmits - 1
  80. -- Select a different upstream!
  81. upstream = rule.upstreams:get_upstream_round_robin()
  82. addr = upstream:get_addr()
  83. lua_util.debugm(rule.name, task, '%s: retry IP: %s',
  84. rule.log_prefix, addr)
  85. tcp.request({
  86. task = task,
  87. host = addr:to_string(),
  88. port = addr:get_port(),
  89. timeout = rule['timeout'],
  90. callback = clamav_callback,
  91. data = { header, content, footer },
  92. stop_pattern = '\0'
  93. })
  94. else
  95. rspamd_logger.errx(task, '%s: failed to scan, maximum retransmits exceed', rule.log_prefix)
  96. common.yield_result(task, rule, 'failed to scan and retransmits exceed', 0.0, 'fail')
  97. end
  98. else
  99. upstream:ok()
  100. data = tostring(data)
  101. local cached
  102. lua_util.debugm(rule.name, task, '%s: got reply: %s',
  103. rule.log_prefix, data)
  104. if data == 'stream: OK' then
  105. cached = 'OK'
  106. if rule['log_clean'] then
  107. rspamd_logger.infox(task, '%s: message or mime_part is clean',
  108. rule.log_prefix)
  109. else
  110. lua_util.debugm(rule.name, task, '%s: message or mime_part is clean', rule.log_prefix)
  111. end
  112. else
  113. local vname = string.match(data, 'stream: (.+) FOUND')
  114. if string.find(vname, '^Heuristics%.Encrypted') then
  115. rspamd_logger.errx(task, '%s: File is encrypted', rule.log_prefix)
  116. common.yield_result(task, rule, 'File is encrypted: '.. vname, 0.0, 'fail')
  117. elseif string.find(vname, '^Heuristics%.Limits%.Exceeded') then
  118. rspamd_logger.errx(task, '%s: ClamAV Limits Exceeded', rule.log_prefix)
  119. common.yield_result(task, rule, 'Limits Exceeded: '.. vname, 0.0, 'fail')
  120. elseif vname then
  121. common.yield_result(task, rule, vname)
  122. cached = vname
  123. else
  124. rspamd_logger.errx(task, '%s: unhandled response: %s', rule.log_prefix, data)
  125. common.yield_result(task, rule, 'unhandled response:' .. vname, 0.0, 'fail')
  126. end
  127. end
  128. if cached then
  129. common.save_av_cache(task, digest, rule, cached)
  130. end
  131. end
  132. end
  133. tcp.request({
  134. task = task,
  135. host = addr:to_string(),
  136. port = addr:get_port(),
  137. timeout = rule['timeout'],
  138. callback = clamav_callback,
  139. data = { header, content, footer },
  140. stop_pattern = '\0'
  141. })
  142. end
  143. if common.need_av_check(task, content, rule) then
  144. if common.check_av_cache(task, digest, rule, clamav_check_uncached) then
  145. return
  146. else
  147. clamav_check_uncached()
  148. end
  149. end
  150. end
  151. return {
  152. type = 'antivirus',
  153. description = 'clamav antivirus',
  154. configure = clamav_config,
  155. check = clamav_check,
  156. name = N
  157. }