aboutsummaryrefslogtreecommitdiffstats
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
parent2f2526e7005bc151a032dacec6a182107dde5a74 (diff)
downloadrspamd-9fddf731cf2161288d70d50c182831124cbd377b.tar.gz
rspamd-9fddf731cf2161288d70d50c182831124cbd377b.zip
[Rework] Dcc: Rework DCC plugin
-rw-r--r--lualib/lua_scanners/dcc.lua227
-rw-r--r--lualib/lua_scanners/init.lua4
-rw-r--r--src/plugins/lua/dcc.lua156
3 files changed, 242 insertions, 145 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')
diff --git a/src/plugins/lua/dcc.lua b/src/plugins/lua/dcc.lua
index 8c5dddeeb..d82ceda94 100644
--- a/src/plugins/lua/dcc.lua
+++ b/src/plugins/lua/dcc.lua
@@ -21,10 +21,8 @@ local N = 'dcc'
local symbol_bulk = "DCC_BULK"
local opts = rspamd_config:get_all_opt(N)
local rspamd_logger = require "rspamd_logger"
-local lua_util = require "lua_util"
-local tcp = require "rspamd_tcp"
-local upstream_list = require "rspamd_upstream_list"
-local fun = require "fun"
+local dcc = require("lua_scanners").filter('dcc').dcc
+
if confighelp then
rspamd_config:add_example(nil, 'dcc',
@@ -39,145 +37,10 @@ dcc {
return
end
-local function check_dcc (task)
- -- Connection
- local client = '0.0.0.0'
- local client_ip = task:get_from_ip()
- local dcc_upstream
- local upstream
- local addr
- local port
- local retransmits = 2
-
- if opts['servers'] then
- dcc_upstream = upstream_list.create(rspamd_config, opts['servers'])
- upstream = dcc_upstream:get_upstream_round_robin()
- addr = upstream:get_addr()
- port = addr:get_port()
- else
- lua_util.debugm(N, task, 'using socket %s', opts['socket'])
- addr = opts['socket']
- end
-
- 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 r = table.concat(fun.totable(fun.map(function(rcpt)
- return rcpt['addr'] end,
- rcpts)), '\n')
- if r then
- envrcpt = r
- end
- end
-
- -- Callback function to receive async result from DCC
- local function cb(err, data)
-
- if err then
- if retransmits > 0 then
- retransmits = retransmits - 1
- -- Select a different upstream or the socket again
- if opts['servers'] then
- upstream = dcc_upstream:get_upstream_round_robin()
- addr = upstream:get_addr()
- port = addr:get_port()
- else
- addr = opts['socket']
- end
-
- lua_util.debugm(N, task, "sending query to %s:%s",tostring(addr), port)
+local rule
- data = {
- "header\n",
- client .. "\n",
- helo .. "\n",
- envfrom .. "\n",
- envrcpt .. "\n",
- "\n",
- task:get_content()
- }
-
- tcp.request({
- task = task,
- host = tostring(addr),
- port = port or 1,
- timeout = opts['timeout'] or 2.0,
- shutdown = true,
- data = data,
- callback = cb
- })
-
- else
- rspamd_logger.errx(task, 'failed to scan, maximum retransmits exceed')
- if upstream then upstream:fail() end
- 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)
-
- if header then
- local _,_,info = header:find("; (.-)$")
- if (result == 'R') then
- -- Reject
- task:insert_result(symbol_bulk, 1.0, info)
- elseif (result == 'T') then
- -- Temporary failure
- rspamd_logger.warnx(task, 'DCC returned a temporary failure result')
- else
- if result ~= 'A' and result ~= 'G' and result ~= 'S' then
- -- Unknown result
- rspamd_logger.warnx(task, 'DCC result error: %1', result);
- end
- end
- end
- end
- end
-
- -- Build the DCC query
- -- https://www.dcc-servers.net/dcc/dcc-tree/dccifd.html#Protocol
- local data = {
- "header\n",
- client .. "\n",
- helo .. "\n",
- envfrom .. "\n",
- envrcpt .. "\n",
- "\n",
- task:get_content()
- }
-
- rspamd_logger.warnx(task, "sending to %s:%s",tostring(addr), port)
-
- tcp.request({
- task = task,
- host = tostring(addr),
- port = port or 1,
- timeout = opts['timeout'] or 2.0,
- shutdown = true,
- data = data,
- callback = cb
- })
+local function check_dcc (task)
+ dcc.check(task, task:get_content(), nil, rule)
end
-- Configuration
@@ -195,9 +58,12 @@ if opts['host'] ~= nil and not opts['port'] then
end
-- WORKAROUND for deprecated host and port settings
-if opts and ( opts['servers'] or opts['socket'] ) then
+if not opts.symbol then opts.symbol = symbol_bulk end
+rule = dcc.configure(opts)
+
+if rule then
rspamd_config:register_symbol({
- name = symbol_bulk,
+ name = opts.symbol,
callback = check_dcc
})
rspamd_config:set_metric_symbol({
@@ -205,7 +71,7 @@ if opts and ( opts['servers'] or opts['socket'] ) then
score = 2.0,
description = 'Detected as bulk mail by DCC',
one_shot = true,
- name = symbol_bulk
+ name = opts.symbol,
})
else
lua_util.disable_module(N, "config")