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.5KB

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 rspamd_regexp = require "rspamd_regexp"
  15. local lua_util = require "lua_util"
  16. local fun = require "fun"
  17. local lua_antivirus = require("lua_scanners").antivirus
  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 seperately - otherwise the complete mail will be transfered 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.av_types[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'] = string.upper(opts['type']) .. '_FAIL'
  78. end
  79. -- WORKAROUND for deprecated attachments_only
  80. if opts['attachments_only'] ~= nil then
  81. opts['scan_mime_parts'] = opts['attachments_only']
  82. rspamd_logger.warnx(rspamd_config, '%s [%s]: Using attachments_only is deprecated. '..
  83. 'Please use scan_mime_parts = %s instead', opts['symbol'], opts['type'], opts['attachments_only'])
  84. end
  85. -- WORKAROUND for deprecated attachments_only
  86. local rule = cfg.configure(opts)
  87. rule.type = opts.type
  88. rule.symbol_fail = opts.symbol_fail
  89. rule.redis_params = redis_params
  90. if not rule then
  91. rspamd_logger.errx(rspamd_config, 'cannot configure %s for %s',
  92. opts['type'], opts['symbol'])
  93. return nil
  94. end
  95. if type(opts['patterns']) == 'table' then
  96. rule['patterns'] = {}
  97. if opts['patterns'][1] then
  98. for i, p in ipairs(opts['patterns']) do
  99. if type(p) == 'table' then
  100. local new_set = {}
  101. for k, v in pairs(p) do
  102. new_set[k] = rspamd_regexp.create_cached(v)
  103. end
  104. rule['patterns'][i] = new_set
  105. else
  106. rule['patterns'][i] = {}
  107. end
  108. end
  109. else
  110. for k, v in pairs(opts['patterns']) do
  111. rule['patterns'][k] = rspamd_regexp.create_cached(v)
  112. end
  113. end
  114. end
  115. if opts['whitelist'] then
  116. rule['whitelist'] = rspamd_config:add_hash_map(opts['whitelist'])
  117. end
  118. return function(task)
  119. if rule.scan_mime_parts then
  120. local parts = task:get_parts() or {}
  121. local filter_func = function(p)
  122. return (rule.scan_image_mime and p:is_image())
  123. or (rule.scan_text_mime and p:is_text())
  124. or (p:is_attachment())
  125. end
  126. fun.each(function(p)
  127. local content = p:get_content()
  128. if content and #content > 0 then
  129. cfg.check(task, content, p:get_digest(), rule)
  130. end
  131. end, fun.filter(filter_func, parts))
  132. else
  133. cfg.check(task, task:get_content(), task:get_digest(), rule)
  134. end
  135. end
  136. end
  137. -- Registration
  138. local opts = rspamd_config:get_all_opt('antivirus')
  139. if opts and type(opts) == 'table' then
  140. redis_params = rspamd_parse_redis_server('antivirus')
  141. local has_valid = false
  142. for k, m in pairs(opts) do
  143. if type(m) == 'table' and m.servers then
  144. if not m.type then m.type = k end
  145. local cb = add_antivirus_rule(k, m)
  146. if not cb then
  147. rspamd_logger.errx(rspamd_config, 'cannot add rule: "' .. k .. '"')
  148. else
  149. local id = rspamd_config:register_symbol({
  150. type = 'normal',
  151. name = m['symbol'],
  152. callback = cb,
  153. score = 0.0,
  154. group = 'antivirus'
  155. })
  156. rspamd_config:register_symbol({
  157. type = 'virtual',
  158. name = m['symbol_fail'],
  159. parent = id,
  160. score = 0.0,
  161. group = 'antivirus'
  162. })
  163. has_valid = true
  164. if type(m['patterns']) == 'table' then
  165. if m['patterns'][1] then
  166. for _, p in ipairs(m['patterns']) do
  167. if type(p) == 'table' then
  168. for sym in pairs(p) do
  169. rspamd_logger.debugm(N, rspamd_config, 'registering: %1', {
  170. type = 'virtual',
  171. name = sym,
  172. parent = m['symbol'],
  173. parent_id = id,
  174. })
  175. rspamd_config:register_symbol({
  176. type = 'virtual',
  177. name = sym,
  178. parent = id
  179. })
  180. end
  181. end
  182. end
  183. else
  184. for sym in pairs(m['patterns']) do
  185. rspamd_config:register_symbol({
  186. type = 'virtual',
  187. name = sym,
  188. parent = id
  189. })
  190. end
  191. end
  192. end
  193. if m['score'] then
  194. -- Register metric symbol
  195. local description = 'antivirus symbol'
  196. local group = 'antivirus'
  197. if m['description'] then
  198. description = m['description']
  199. end
  200. if m['group'] then
  201. group = m['group']
  202. end
  203. rspamd_config:set_metric_symbol({
  204. name = m['symbol'],
  205. score = m['score'],
  206. description = description,
  207. group = group or 'antivirus'
  208. })
  209. end
  210. end
  211. end
  212. end
  213. if not has_valid then
  214. lua_util.disable_module(N, 'config')
  215. end
  216. end