Browse Source

[Feature] Lua_scanners: Add VadeSecure engine support

tags/1.9.0
Vsevolod Stakhov 5 years ago
parent
commit
27afeb35bf
1 changed files with 263 additions and 0 deletions
  1. 263
    0
      lualib/lua_scanners/vadesecure.lua

+ 263
- 0
lualib/lua_scanners/vadesecure.lua View File

@@ -0,0 +1,263 @@
--[[
Copyright (c) 2019, Vsevolod Stakhov <vsevolod@highsecure.ru>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]--

--[[[
-- @module vadesecure
-- This module contains Vadesecure Filterd interface
--]]

local lua_util = require "lua_util"
local http = require "rspamd_http"
local upstream_list = require "rspamd_upstream_list"
local rspamd_logger = require "rspamd_logger"
local ucl = require "ucl"

local N = 'vadesecure'

local function vade_check(task, content, digest, rule)
local function vade_url(addr)
local url
if rule.use_https then
url = string.format('https://%s:%d/%s', tostring(addr),
rule.default_port, rule.url)
else
url = string.format('http://%s:%d/%s', tostring(addr),
rule.default_port, rule.url)
end

return url
end

local upstream = rule.upstreams:get_upstream_round_robin()
local addr = upstream:get_addr()
local retransmits = rule.retransmits

local url = vade_url(addr)
local hdrs = {}

local helo = task:get_helo()
if helo then
hdrs['X-Helo'] = helo
end
local mail_from = task:get_from('smtp') or {}
if mail_from[1] and #mail_from[1].addr > 1 then
hdrs['X-Mailfrom'] = mail_from[1].addr
end

local rcpt_to = task:get_recipients('smtp')
if rcpt_to then
hdrs['X-Rcptto'] = {}
for _, r in ipairs(rcpt_to) do
table.insert(hdrs['X-Rcptto'], r.addr)
end
end

local fip = task:get_from_ip()
if fip and fip:is_valid() then
hdrs['X-Inet'] = tostring(fip)
end

local request_data = {
task = task,
url = url,
body = task:get_content(),
headers = hdrs,
timeout = rule.timeout,
}

local function vade_callback(http_err, code, body, headers)

local function vade_requery()
-- set current upstream to fail because an error occurred
upstream:fail()

-- retry with another upstream until retransmits exceeds
if retransmits > 0 then

retransmits = retransmits - 1

lua_util.debugm(rule.name, task,
'%s: Request Error: %s - retries left: %s',
rule.log_prefix, http_err, retransmits)

-- Select a different upstream!
upstream = rule.upstreams:get_upstream_round_robin()
addr = upstream:get_addr()
url = vade_url(addr)

lua_util.debugm(rule.name, task, '%s: retry IP: %s:%s',
rule.log_prefix, addr, addr:get_port())
request_data.url = url

http.request(request_data)
else
rspamd_logger.errx(task, '%s: failed to scan, maximum retransmits '..
'exceed', rule.log_prefix)
task:insert_result(rule['symbol_fail'], 0.0, 'failed to scan and '..
'retransmits exceed')
end
end

if http_err then
vade_requery()
else
-- Parse the response
if upstream then upstream:ok() end
if code ~= 200 then
task:insert_result(rule.symbol_fail, 1.0, 'Bad HTTP code: ' .. code)
return
end
local parser = ucl.parser()
local ret, err = parser:parse_string(body)
if not ret then
rspamd_logger.errx(task, 'Weird response body (raw): %s', body)
task:insert_result(rule.symbol_fail, 1.0, 'Parser error: ' .. err)
return
end
local obj = parser:get_object()
local verdict = obj.verdict
if not verdict then
rspamd_logger.errx(task, 'Weird response JSON: %s', obj)
task:insert_result(rule.symbol_fail, 1.0, 'No verdict/unknown verdict')
return
end
local vparts = lua_util.rspamd_str_split(verdict, ":")
verdict = table.remove(vparts, 1) or verdict

local sym = rule.symbols[verdict]
if not sym then
sym = rule.symbols.other
end

if type(sym) == 'table' then
local lvl = 'low'
if vparts and vparts[1] then
lvl = vparts[1]
end

if sym[lvl] then
sym = sym[lvl]
else
sym = rule.symbols.other
end
end

local opts = {}
if obj.score then
table.insert(opts, 'score=' .. obj.score)
end
if obj.elapsed then
table.insert(opts, 'elapsed=' .. obj.elapsed)
end

if rule.log_spamcause and obj.spamcause then
rspamd_logger.infox(task, 'vadesecure returned verdict="%s", score=%s, spamcause="%s"',
verdict, obj.score, obj.spamcause)
end

if #vparts > 0 then
table.insert(opts, 'verdict=' .. verdict .. ';' .. table.concat(vparts, ':'))
end

task:insert_result(sym, 1.0, opts)
end
end

request_data.callback = vade_callback
http.request(request_data)
end


local function vade_config(opts)

local vade_conf = {
name = N,
default_port = 23808,
url = '/api/v1/scan',
use_https = false,
timeout = 5.0,
log_clean = false,
retransmits = 1,
cache_expire = 7200, -- expire redis in 2h
message = '${SCANNER}: spam message found: "${VIRUS}"',
detection_category = "hash",
default_score = 1,
action = false,
log_spamcause = true,
symbol_fail = 'VADE_FAIL',
symbol = 'VADE_CHECK',
symbols = {
clean = 'VADE_CLEAN',
spam = {
high = 'VADE_SPAM_HIGH',
medium = 'VADE_SPAM_MEDIUM',
low = 'VADE_SPAM_LOW'
},
malware = 'VADE_MALWARE',
scam = 'VADE_SCAM',
phishing = 'VADE_PHISHING',
['commercial:dce'] = 'VADE_DCE',
suspect = 'VADE_SUSPECT',
bounce = 'VADE_BOUNCE',
other = 'VADE_OTHER',
}
}

vade_conf = lua_util.override_defaults(vade_conf, opts)

if not vade_conf.prefix then
vade_conf.prefix = 'rs_' .. vade_conf.name .. '_'
end

if not vade_conf.log_prefix then
if vade_conf.name:lower() == vade_conf.type:lower() then
vade_conf.log_prefix = vade_conf.name
else
vade_conf.log_prefix = vade_conf.name .. ' (' .. vade_conf.type .. ')'
end
end

if not vade_conf.servers and vade_conf.socket then
vade_conf.servers = vade_conf.socket
end

if not vade_conf.servers then
rspamd_logger.errx(rspamd_config, 'no servers defined')

return nil
end

vade_conf.upstreams = upstream_list.create(rspamd_config,
vade_conf.servers,
vade_conf.default_port)

if vade_conf.upstreams then
lua_util.add_debug_alias('external_services', vade_conf.name)
return vade_conf
end

rspamd_logger.errx(rspamd_config, 'cannot parse servers %s',
vade_conf['servers'])
return nil
end

return {
type = {'vadesecure', 'scanner'},
description = 'VadeSecure Filterd interface',
configure = vade_config,
check = vade_check,
name = N
}

Loading…
Cancel
Save