aboutsummaryrefslogtreecommitdiffstats
path: root/lualib
diff options
context:
space:
mode:
authorVsevolod Stakhov <vsevolod@highsecure.ru>2018-12-29 11:04:53 +0000
committerVsevolod Stakhov <vsevolod@highsecure.ru>2018-12-29 11:04:53 +0000
commit9fddf731cf2161288d70d50c182831124cbd377b (patch)
tree483f51d8bb3ef8e2fa0f2426901f69ccb0efd18c /lualib
parent2f2526e7005bc151a032dacec6a182107dde5a74 (diff)
downloadrspamd-9fddf731cf2161288d70d50c182831124cbd377b.tar.gz
rspamd-9fddf731cf2161288d70d50c182831124cbd377b.zip
[Rework] Dcc: Rework DCC plugin
Diffstat (limited to 'lualib')
-rw-r--r--lualib/lua_scanners/dcc.lua227
-rw-r--r--lualib/lua_scanners/init.lua4
2 files changed, 231 insertions, 0 deletions
diff --git a/lualib/lua_scanners/dcc.lua b/lualib/lua_scanners/dcc.lua
new file mode 100644
index 000000000..27230fd6f
--- /dev/null
+++ b/lualib/lua_scanners/dcc.lua
@@ -0,0 +1,227 @@
+--[[
+Copyright (c) 2018, Vsevolod Stakhov <vsevolod@highsecure.ru>
+Copyright (c) 2018, Carsten Rosenberg <c.rosenberg@heinlein-support.de>
+
+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 fprot
+-- This module contains dcc access functions
+--]]
+
+local lua_util = require "lua_util"
+local tcp = require "rspamd_tcp"
+local upstream_list = require "rspamd_upstream_list"
+local rspamd_logger = require "rspamd_logger"
+local common = require "lua_scanners/common"
+local fun = require "fun"
+
+local N = 'dcc'
+
+local function dcc_check(task, content, _, rule)
+ local function dcc_check_uncached ()
+ local upstream = rule.upstreams:get_upstream_round_robin()
+ local addr = upstream:get_addr()
+ local retransmits = rule.retransmits
+ local client = rule.client
+
+ local client_ip = task:get_from_ip()
+ if client_ip and client_ip:is_valid() then
+ client = client_ip:to_string()
+ end
+ local client_host = task:get_hostname()
+ if client_host then
+ client = client .. "\r" .. client_host
+ end
+
+ -- HELO
+ local helo = task:get_helo() or ''
+
+ -- Envelope From
+ local ef = task:get_from()
+ local envfrom = 'test@example.com'
+ if ef and ef[1] then
+ envfrom = ef[1]['addr']
+ end
+
+ -- Envelope To
+ local envrcpt = 'test@example.com'
+ local rcpts = task:get_recipients();
+ if rcpts then
+ local dcc_recipients = table.concat(fun.totable(fun.map(function(rcpt)
+ return rcpt['addr'] end,
+ rcpts)), '\n')
+ if dcc_recipients then
+ envrcpt = dcc_recipients
+ end
+ end
+
+ -- Build the DCC query
+ -- https://www.dcc-servers.net/dcc/dcc-tree/dccifd.html#Protocol
+ local request_data = {
+ "header\n",
+ client .. "\n",
+ helo .. "\n",
+ envfrom .. "\n",
+ envrcpt .. "\n",
+ "\n",
+ content
+ }
+
+ local function dcc_callback(err, data, conn)
+
+ if err then
+
+ -- 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
+
+ -- Select a different upstream!
+ upstream = rule.upstreams:get_upstream_round_robin()
+ addr = upstream:get_addr()
+
+ lua_util.debugm(N, task, '%s: retry IP: %s', rule.log_prefix, addr)
+
+ tcp.request({
+ task = task,
+ host = addr:to_string(),
+ port = addr:get_port(),
+ timeout = rule.timeout or 2.0,
+ shutdown = true,
+ data = request_data,
+ callback = dcc_callback
+ })
+ else
+ rspamd_logger.errx(task, '%s: failed to scan, maximum retransmits exceed', rule['symbol'], rule['type'])
+ task:insert_result(rule['symbol_fail'], 0.0, 'failed to scan and retransmits exceed')
+ end
+ else
+ -- Parse the response
+ if upstream then upstream:ok() end
+ local _,_,result,disposition,header = tostring(data):find("(.-)\n(.-)\n(.-)\n")
+ lua_util.debugm(N, task, 'DCC result=%1 disposition=%2 header="%3"',
+ result, disposition, header)
+
+ --[[
+ @todo: Implement math function to calc the score dynamically based on return values. Maybe check spamassassin implementation.
+ ]] --
+
+ if header then
+ local _,_,info = header:find("; (.-)$")
+ if (result == 'R') then
+ -- Reject
+ common.yield_result(task, rule, info, rule.default_score)
+ elseif (result == 'T') then
+ -- Temporary failure
+ rspamd_logger.warnx(task, 'DCC returned a temporary failure result: %s', result)
+ task:insert_result(rule['symbol_fail'], 0.0, 'DCC returned a temporary failure result:' .. result)
+ elseif result == 'A' then
+ -- do nothing
+ if rule.log_clean then
+ rspamd_logger.infox(task, '%s: clean, returned result A - info: %s', rule.log_prefix, info)
+ else
+ lua_util.debugm(N, task, '%s: returned result A - info: %s', rule.log_prefix, info)
+ end
+ elseif result == 'G' then
+ -- do nothing
+ if rule.log_clean then
+ rspamd_logger.infox(task, '%s: clean, returned result G - info: %s', rule.log_prefix, info)
+ else
+ lua_util.debugm(N, task, '%s: returned result G - info: %s', rule.log_prefix, info)
+ end
+ elseif result == 'S' then
+ -- do nothing
+ if rule.log_clean then
+ rspamd_logger.infox(task, '%s: clean, returned result S - info: %s', rule.log_prefix, info)
+ else
+ lua_util.debugm(N, task, '%s: returned result S - info: %s', rule.log_prefix, info)
+ end
+ else
+ -- Unknown result
+ rspamd_logger.warnx(task, 'DCC result error: %1', result);
+ task:insert_result(rule['symbol_fail'], 0.0, 'DCC result error: ' .. result)
+ end
+ end
+ end
+ end
+
+ tcp.request({
+ task = task,
+ host = addr:to_string(),
+ port = addr:get_port(),
+ timeout = rule.timeout or 2.0,
+ shutdown = true,
+ data = request_data,
+ callback = dcc_callback
+ })
+ end
+ if common.need_av_check(task, content, rule) then
+ dcc_check_uncached()
+ end
+end
+
+local function dcc_config(opts)
+
+ local dcc_conf = {
+ default_port = 10045,
+ timeout = 5.0,
+ log_clean = false,
+ retransmits = 2,
+ message = '${SCANNER}: bulk message found: "${VIRUS}"',
+ detection_category = "hash",
+ default_score = 1,
+ action = false,
+ client = '0.0.0.0',
+ }
+
+ dcc_conf = lua_util.override_defaults(dcc_conf, opts)
+
+ if not dcc_conf.log_prefix then
+ dcc_conf.log_prefix = N
+ end
+
+ if not dcc_conf.servers and dcc_conf.socket then
+ dcc_conf.servers = dcc_conf.socket
+ end
+
+ if not dcc_conf.servers then
+ rspamd_logger.errx(rspamd_config, 'no servers defined')
+
+ return nil
+ end
+
+ dcc_conf.upstreams = upstream_list.create(rspamd_config,
+ dcc_conf.servers,
+ dcc_conf.default_port)
+
+ if dcc_conf.upstreams then
+ return dcc_conf
+ end
+
+ rspamd_logger.errx(rspamd_config, 'cannot parse servers %s',
+ dcc_conf['servers'])
+ return nil
+end
+
+return {
+ type = {'dcc','spam scan'},
+ description = 'dcc bulk scanner',
+ configure = dcc_config,
+ check = dcc_check,
+ name = 'dcc'
+} \ No newline at end of file
diff --git a/lualib/lua_scanners/init.lua b/lualib/lua_scanners/init.lua
index e91feecfd..f769eb5a5 100644
--- a/lualib/lua_scanners/init.lua
+++ b/lualib/lua_scanners/init.lua
@@ -30,12 +30,16 @@ local function require_scanner(name)
exports[sc.name or name] = sc
end
+-- Antiviruses
require_scanner('clamav')
require_scanner('fprot')
require_scanner('kaspersky_av')
require_scanner('savapi')
require_scanner('sophos')
+-- Other scanners
+require_scanner('dcc')
+
exports.add_scanner = function(name, t, conf_func, check_func)
assert(type(conf_func) == 'function' and type(check_func) == 'function',
'bad arguments')