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 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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: error: %s; retry IP: %s; retries left: %s',
  84. rule.log_prefix, err, addr, retransmits)
  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, 'encrypted')
  117. cached = 'ENCRYPTED'
  118. elseif string.find(vname, '^Heuristics%.OLE2%.ContainsMacros') then
  119. rspamd_logger.errx(task, '%s: ClamAV Found an OLE2 Office Macro', rule.log_prefix)
  120. common.yield_result(task, rule, vname, 0.0, 'macro')
  121. cached = 'MACRO'
  122. elseif string.find(vname, '^Heuristics%.Limits%.Exceeded') then
  123. rspamd_logger.errx(task, '%s: ClamAV Limits Exceeded', rule.log_prefix)
  124. common.yield_result(task, rule, 'Limits Exceeded: '.. vname, 0.0, 'fail')
  125. elseif vname then
  126. common.yield_result(task, rule, vname)
  127. cached = vname
  128. else
  129. rspamd_logger.errx(task, '%s: unhandled response: %s', rule.log_prefix, data)
  130. common.yield_result(task, rule, 'unhandled response:' .. vname, 0.0, 'fail')
  131. end
  132. end
  133. if cached then
  134. common.save_cache(task, digest, rule, cached)
  135. end
  136. end
  137. end
  138. tcp.request({
  139. task = task,
  140. host = addr:to_string(),
  141. port = addr:get_port(),
  142. timeout = rule['timeout'],
  143. callback = clamav_callback,
  144. data = { header, content, footer },
  145. stop_pattern = '\0'
  146. })
  147. end
  148. if common.condition_check_and_continue(task, content, rule, digest, clamav_check_uncached) then
  149. return
  150. else
  151. clamav_check_uncached()
  152. end
  153. end
  154. return {
  155. type = 'antivirus',
  156. description = 'clamav antivirus',
  157. configure = clamav_config,
  158. check = clamav_check,
  159. name = N
  160. }