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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. --[[
  2. Copyright (c) 2016, 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. local rspamd_logger = require "rspamd_logger"
  14. local lua_util = require "lua_util"
  15. local fun = require "fun"
  16. local lua_antivirus = require("lua_scanners").filter('antivirus')
  17. local common = require "lua_scanners/common"
  18. local redis_params
  19. local N = "antivirus"
  20. if confighelp then
  21. rspamd_config:add_example(nil, 'antivirus',
  22. "Check messages for viruses",
  23. [[
  24. antivirus {
  25. # multiple scanners could be checked, for each we create a configuration block with an arbitrary name
  26. clamav {
  27. # If set force this action if any virus is found (default unset: no action is forced)
  28. # action = "reject";
  29. # If set, then rejection message is set to this value (mention single quotes)
  30. # message = '${SCANNER}: virus found: "${VIRUS}"';
  31. # Scan mime_parts separately - otherwise the complete mail will be transferred to AV Scanner
  32. #scan_mime_parts = true;
  33. # Scanning Text is suitable for some av scanner databases (e.g. Sanesecurity)
  34. #scan_text_mime = false;
  35. #scan_image_mime = false;
  36. # If `max_size` is set, messages > n bytes in size are not scanned
  37. max_size = 20000000;
  38. # symbol to add (add it to metric if you want non-zero weight)
  39. symbol = "CLAM_VIRUS";
  40. # type of scanner: "clamav", "fprot", "sophos" or "savapi"
  41. type = "clamav";
  42. # For "savapi" you must also specify the following variable
  43. product_id = 12345;
  44. # You can enable logging for clean messages
  45. log_clean = true;
  46. # servers to query (if port is unspecified, scanner-specific default is used)
  47. # can be specified multiple times to pool servers
  48. # can be set to a path to a unix socket
  49. # Enable this in local.d/antivirus.conf
  50. servers = "127.0.0.1:3310";
  51. # if `patterns` is specified virus name will be matched against provided regexes and the related
  52. # symbol will be yielded if a match is found. If no match is found, default symbol is yielded.
  53. patterns {
  54. # symbol_name = "pattern";
  55. JUST_EICAR = "^Eicar-Test-Signature$";
  56. }
  57. # `whitelist` points to a map of IP addresses. Mail from these addresses is not scanned.
  58. whitelist = "/etc/rspamd/antivirus.wl";
  59. }
  60. }
  61. ]])
  62. return
  63. end
  64. local function add_antivirus_rule(sym, opts)
  65. if not opts.type then
  66. rspamd_logger.errx(rspamd_config, 'unknown type for AV rule %s', sym)
  67. return nil
  68. end
  69. if not opts.symbol then opts.symbol = sym:upper() end
  70. local cfg = lua_antivirus[opts.type]
  71. if not cfg then
  72. rspamd_logger.errx(rspamd_config, 'unknown antivirus type: %s',
  73. opts.type)
  74. return nil
  75. end
  76. if not opts.symbol_fail then
  77. opts.symbol_fail = opts.symbol .. '_FAIL'
  78. end
  79. if not opts.symbol_encrypted then
  80. opts.symbol_encrypted = opts.symbol .. '_ENCRYPTED'
  81. end
  82. -- WORKAROUND for deprecated attachments_only
  83. if opts.attachments_only ~= nil then
  84. opts.scan_mime_parts = opts.attachments_only
  85. rspamd_logger.warnx(rspamd_config, '%s [%s]: Using attachments_only is deprecated. '..
  86. 'Please use scan_mime_parts = %s instead', opts.symbol, opts.type, opts.attachments_only)
  87. end
  88. -- WORKAROUND for deprecated attachments_only
  89. local rule = cfg.configure(opts)
  90. rule.type = opts.type
  91. rule.symbol_fail = opts.symbol_fail
  92. rule.symbol_encrypted = opts.symbol_encrypted
  93. rule.redis_params = redis_params
  94. if not rule then
  95. rspamd_logger.errx(rspamd_config, 'cannot configure %s for %s',
  96. opts.type, opts.symbol)
  97. return nil
  98. end
  99. rule.patterns = common.create_regex_table(opts.patterns or {})
  100. rule.patterns_fail = common.create_regex_table(opts.patterns_fail or {})
  101. if opts.whitelist then
  102. rule.whitelist = rspamd_config:add_hash_map(opts.whitelist)
  103. end
  104. return function(task)
  105. if rule.scan_mime_parts then
  106. fun.each(function(p)
  107. local content = p:get_content()
  108. if content and #content > 0 then
  109. cfg.check(task, content, p:get_digest(), rule)
  110. end
  111. end, common.check_parts_match(task, rule))
  112. else
  113. cfg.check(task, task:get_content(), task:get_digest(), rule)
  114. end
  115. end
  116. end
  117. -- Registration
  118. local opts = rspamd_config:get_all_opt(N)
  119. if opts and type(opts) == 'table' then
  120. redis_params = rspamd_parse_redis_server(N)
  121. local has_valid = false
  122. for k, m in pairs(opts) do
  123. if type(m) == 'table' and m.servers then
  124. if not m.type then m.type = k end
  125. if not m.name then m.name = k end
  126. local cb = add_antivirus_rule(k, m)
  127. if not cb then
  128. rspamd_logger.errx(rspamd_config, 'cannot add rule: "' .. k .. '"')
  129. else
  130. local t = {
  131. name = m.symbol,
  132. callback = cb,
  133. score = 0.0,
  134. group = N
  135. }
  136. if m.symbol_type == 'postfilter' then
  137. t.type = 'postfilter'
  138. t.priority = 3
  139. else
  140. t.type = 'normal'
  141. end
  142. local id = rspamd_config:register_symbol(t)
  143. rspamd_config:register_symbol({
  144. type = 'virtual',
  145. name = m['symbol_fail'],
  146. parent = id,
  147. score = 0.0,
  148. group = N
  149. })
  150. rspamd_config:register_symbol({
  151. type = 'virtual',
  152. name = m['symbol_encrypted'],
  153. parent = id,
  154. score = 0.0,
  155. group = N
  156. })
  157. has_valid = true
  158. if type(m['patterns']) == 'table' then
  159. if m['patterns'][1] then
  160. for _, p in ipairs(m['patterns']) do
  161. if type(p) == 'table' then
  162. for sym in pairs(p) do
  163. rspamd_logger.debugm(N, rspamd_config, 'registering: %1', {
  164. type = 'virtual',
  165. name = sym,
  166. parent = m['symbol'],
  167. parent_id = id,
  168. group = N
  169. })
  170. rspamd_config:register_symbol({
  171. type = 'virtual',
  172. name = sym,
  173. parent = id,
  174. group = N
  175. })
  176. end
  177. end
  178. end
  179. else
  180. for sym in pairs(m['patterns']) do
  181. rspamd_config:register_symbol({
  182. type = 'virtual',
  183. name = sym,
  184. parent = id,
  185. group = N
  186. })
  187. end
  188. end
  189. end
  190. if m['score'] then
  191. -- Register metric symbol
  192. local description = 'antivirus symbol'
  193. local group = N
  194. if m['description'] then
  195. description = m['description']
  196. end
  197. if m['group'] then
  198. group = m['group']
  199. end
  200. rspamd_config:set_metric_symbol({
  201. name = m['symbol'],
  202. score = m['score'],
  203. description = description,
  204. group = group or 'antivirus'
  205. })
  206. end
  207. end
  208. end
  209. end
  210. if not has_valid then
  211. lua_util.disable_module(N, 'config')
  212. end
  213. end