end
end
if cached then
- common.save_av_cache(task, digest, rule, cached)
+ common.save_cache(task, digest, rule, cached)
end
end
end
- if rule.dynamic_scan then
- local pre_check, pre_check_msg = common.check_metric_results(task, rule)
- if pre_check then
- rspamd_logger.infox(task, '%s: aborting: %s', rule.log_prefix, pre_check_msg)
- return true
- end
- end
-
tcp.request({
task = task,
host = addr:to_string(),
})
end
- if common.need_av_check(task, content, rule) then
- if common.check_av_cache(task, digest, rule, clamav_check_uncached) then
- return
- else
- clamav_check_uncached()
- end
+ if common.need_check(task, content, rule, digest) then
+ clamav_check_uncached()
end
+
end
return {
return true
end
-local function need_av_check(task, content, rule)
- return message_not_too_large(task, content, rule)
+local function message_not_too_small(task, content, rule)
+ local min_size = tonumber(rule.min_size)
+ if not min_size then return true end
+ if #content < min_size then
+ rspamd_logger.infox(task, "skip %s check as it is too small: %s (%s is allowed)",
+ rule.log_prefix, #content, min_size)
+ return false
+ end
+ return true
+end
+
+local function message_min_words(task, rule)
+ if rule.text_part_min_words then
+ local text_parts_empty = false
+ local text_parts = task:get_text_parts()
+
+ local filter_func = function(p)
+ return p:get_words_count() <= tonumber(rule.text_part_min_words)
+ end
+
+ fun.each(function(p)
+ text_parts_empty = true
+ rspamd_logger.infox(task, '%s: #words is less then text_part_min_words: %s',
+ rule.log_prefix, rule.text_part_min_words)
+ end, fun.filter(filter_func, text_parts))
+
+ return text_parts_empty
+ else
+ return true
+ end
end
-local function check_av_cache(task, digest, rule, fn)
+local function dynamic_scan(task, rule)
+ if rule.dynamic_scan then
+ if rule.action ~= 'reject' then
+ local metric_result = task:get_metric_score('default')
+ local metric_action = task:get_metric_action('default')
+ local has_pre_result = task:has_pre_result()
+ -- ToDo: needed?
+ -- Sometimes leads to FPs
+ --if rule.symbol_type == 'postfilter' and metric_action == 'reject' then
+ -- rspamd_logger.infox(task, '%s: aborting: %s', rule.log_prefix, "result is already reject")
+ -- return false
+ --elseif metric_result[1] > metric_result[2]*2 then
+ if metric_result[1] > metric_result[2]*2 then
+ rspamd_logger.infox(task, '%s: aborting: %s', rule.log_prefix, 'score > 2 * reject_level: ' .. metric_result[1])
+ return false
+ elseif has_pre_result and metric_action == 'reject' then
+ rspamd_logger.infox(task, '%s: aborting: %s', rule.log_prefix, 'pre_result reject is set')
+ return false
+ else
+ return true, 'undecided'
+ end
+ else
+ return true, 'dynamic_scan is not possible with config `action=reject;`'
+ end
+ else
+ return true
+ end
+end
+
+local function check_cache(task, digest, rule, fn)
local key = digest
local function redis_av_cb(err, data)
return false
end
-local function save_av_cache(task, digest, rule, to_save, dyn_weight)
+local function need_check(task, content, rule, digest)
+ return check_cache(task, digest, rule) and
+ message_not_too_large(task, content, rule) and
+ message_not_too_small(task, content, rule) and
+ message_min_words(task, rule) and
+ dynamic_scan(task, rule)
+end
+
+local function save_cache(task, digest, rule, to_save, dyn_weight)
local key = digest
if not dyn_weight then dyn_weight = 1.0 end
exports.log_clean = log_clean
exports.yield_result = yield_result
exports.match_patterns = match_patterns
-exports.need_av_check = need_av_check
-exports.check_av_cache = check_av_cache
-exports.save_av_cache = save_av_cache
+exports.need_check = need_check
+exports.check_cache = check_cache
+exports.save_cache = save_cache
exports.create_regex_table = create_regex_table
exports.check_parts_match = check_parts_match
exports.check_metric_results = check_metric_results
if (result == 'R') then
-- Reject
common.yield_result(task, rule, info, rule.default_score)
- common.save_av_cache(task, digest, rule, info, rule.default_score)
+ common.save_cache(task, digest, 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_bulk,
score,
opts)
- common.save_av_cache(task, digest, rule, opts, score)
+ common.save_cache(task, digest, rule, opts, score)
else
- common.save_av_cache(task, digest, rule, 'OK')
+ common.save_cache(task, digest, rule, 'OK')
if rule.log_clean then
rspamd_logger.infox(task, '%s: clean, returned result A - info: %s',
rule.log_prefix, info)
end
elseif result == 'G' then
-- do nothing
- common.save_av_cache(task, digest, rule, 'OK')
+ common.save_cache(task, digest, rule, 'OK')
if rule.log_clean then
rspamd_logger.infox(task, '%s: clean, returned result G - info: %s', rule.log_prefix, info)
else
end
elseif result == 'S' then
-- do nothing
- common.save_av_cache(task, digest, rule, 'OK')
+ common.save_cache(task, digest, rule, 'OK')
if rule.log_clean then
rspamd_logger.infox(task, '%s: clean, returned result S - info: %s', rule.log_prefix, info)
else
end
end
- if rule.dynamic_scan then
- local pre_check, pre_check_msg = common.check_metric_results(task, rule)
- if pre_check then
- rspamd_logger.infox(task, '%s: aborting: %s', rule.log_prefix, pre_check_msg)
- return true
- end
- end
-
tcp.request({
task = task,
host = addr:to_string(),
fuz2_max = 999999,
})
end
- if common.need_av_check(task, content, rule) then
- if common.check_av_cache(task, digest, rule, dcc_check_uncached) then
- return
- else
- dcc_check_uncached()
- end
+
+ if common.need_check(task, content, rule, digest) then
+ dcc_check_uncached()
end
+
end
return {
end
end
if cached then
- common.save_av_cache(task, digest, rule, cached)
+ common.save_cache(task, digest, rule, cached)
end
end
end
- if rule.dynamic_scan then
- local pre_check, pre_check_msg = common.check_metric_results(task, rule)
- if pre_check then
- rspamd_logger.infox(task, '%s: aborting: %s', rule.log_prefix, pre_check_msg)
- return true
- end
- end
-
tcp.request({
task = task,
host = addr:to_string(),
})
end
- if common.need_av_check(task, content, rule) then
- if common.check_av_cache(task, digest, rule, fprot_check_uncached) then
- return
- else
- fprot_check_uncached()
- end
+ if common.need_check(task, content, rule, digest) then
+ fprot_check_uncached()
end
+
end
return {
end
end
- if rule.dynamic_scan then
- local pre_check, pre_check_msg = common.check_metric_results(task, rule)
- if pre_check then
- rspamd_logger.infox(task, '%s: aborting: %s', rule.log_prefix, pre_check_msg)
- return true
- end
- end
-
tcp.request({
task = task,
host = addr:to_string(),
})
end
- if common.need_av_check(task, content, rule) then
- if common.check_av_cache(task, digest, rule, icap_check_uncached) then
- return
- else
- icap_check_uncached()
- end
+ if common.need_check(task, content, rule, digest) then
+ icap_check_uncached()
end
+
end
return {
end
end
if cached then
- common.save_av_cache(task, digest, rule, cached)
+ common.save_cache(task, digest, rule, cached)
end
end
end
- if rule.dynamic_scan then
- local pre_check, pre_check_msg = common.check_metric_results(task, rule)
- if pre_check then
- rspamd_logger.infox(task, '%s: aborting: %s', rule.log_prefix, pre_check_msg)
- return true
- end
- end
-
tcp.request({
task = task,
host = addr:to_string(),
})
end
- if common.need_av_check(task, content, rule) then
- if common.check_av_cache(task, digest, rule, kaspersky_check_uncached) then
- return
- else
- kaspersky_check_uncached()
- end
+ if common.need_check(task, content, rule, digest) then
+ kaspersky_check_uncached()
end
+
end
return {
log_clean = false,
retransmits = 2,
cache_expire = 86400, -- expire redis in 1d
+ min_size = 500,
symbol = "OLETOOLS",
message = '${SCANNER}: Oletools threat message found: "${VIRUS}"',
detection_category = "office macro",
rspamd_logger.errx(task, '%s: ERROR found: %s', rule.log_prefix,
result[1].error)
if result[1].error == 'File too small' then
- common.save_av_cache(task, digest, rule, 'OK')
+ common.save_cache(task, digest, rule, 'OK')
common.log_clean(task, rule, 'File too small to be scanned for macros')
else
oletools_requery(result[1].error)
rspamd_logger.warnx(task, '%s: maybe unhandled python or oletools error', rule.log_prefix)
common.yield_result(task, rule, 'oletools unhandled error', 0.0, 'fail')
elseif type(result[2]['analysis']) ~= 'table' and #result[2]['macros'] == 0 then
- common.save_av_cache(task, digest, rule, 'OK')
+ common.save_cache(task, digest, rule, 'OK')
common.log_clean(task, rule, 'No macro found')
elseif #result[2]['macros'] > 0 then
-- M=Macros, A=Auto-executable, S=Suspicious keywords, I=IOCs,
local threat = 'AutoExec + Suspicious (' .. table.concat(analysis_keyword_table, ',') .. ')'
lua_util.debugm(rule.name, task, '%s: threat result: %s', rule.log_prefix, threat)
common.yield_result(task, rule, threat, rule.default_score)
- common.save_av_cache(task, digest, rule, threat, rule.default_score)
+ common.save_cache(task, digest, rule, threat, rule.default_score)
elseif rule.extended == true and #analysis_keyword_table > 0 then
-- report any flags (types) and any most keywords as individual virus name
rule.log_prefix, table.concat(analysis_keyword_table, ','))
common.yield_result(task, rule, analysis_keyword_table, rule.default_score)
- common.save_av_cache(task, digest, rule, analysis_keyword_table, rule.default_score)
+ common.save_cache(task, digest, rule, analysis_keyword_table, rule.default_score)
else
- common.save_av_cache(task, digest, rule, 'OK')
+ common.save_cache(task, digest, rule, 'OK')
common.log_clean(task, rule, 'Scanned Macro is OK')
end
end
end
- if rule.dynamic_scan then
- local pre_check, pre_check_msg = common.check_metric_results(task, rule)
- if pre_check then
- rspamd_logger.infox(task, '%s: aborting: %s', rule.log_prefix, pre_check_msg)
- return true
- end
- end
-
tcp.request({
task = task,
host = addr:to_string(),
})
end
- if common.need_av_check(task, content, rule) then
- if common.check_av_cache(task, digest, rule, oletools_check_uncached) then
- return
- else
- oletools_check_uncached()
- end
+
+ if common.need_check(task, content, rule, digest) then
+ oletools_check_uncached()
end
+
end
return {
end
common.yield_result(task, rule, vname)
- common.save_av_cache(task, digest, rule, vname)
+ common.save_cache(task, digest, rule, vname)
end
if conn then
conn:close()
if rule['log_clean'] then
rspamd_logger.infox(task, '%s: message or mime_part is clean', rule['type'])
end
- common.save_av_cache(task, digest, rule, 'OK')
+ common.save_cache(task, digest, rule, 'OK')
conn:add_write(savapi_fin_cb, 'QUIT\n')
-- Terminal response - infected
end
end
- if rule.dynamic_scan then
- local pre_check, pre_check_msg = common.check_metric_results(task, rule)
- if pre_check then
- rspamd_logger.infox(task, '%s: aborting: %s', rule.log_prefix, pre_check_msg)
- return true
- end
- end
-
tcp.request({
task = task,
host = addr:to_string(),
})
end
- if common.need_av_check(task, content, rule) then
- if common.check_av_cache(task, digest, rule, savapi_check_uncached) then
- return
- else
- savapi_check_uncached()
- end
+ if common.need_check(task, content, rule, digest) then
+ savapi_check_uncached()
end
+
end
return {
local vname = string.match(data, 'VIRUS (%S+) ')
if vname then
common.yield_result(task, rule, vname)
- common.save_av_cache(task, digest, rule, vname)
+ common.save_cache(task, digest, rule, vname)
else
if string.find(data, 'DONE OK') then
if rule['log_clean'] then
lua_util.debugm(rule.name, task,
'%s: message or mime_part is clean', rule.log_prefix)
end
- common.save_av_cache(task, digest, rule, 'OK')
+ common.save_cache(task, digest, rule, 'OK')
-- not finished - continue
elseif string.find(data, 'ACC') or string.find(data, 'OK SSSP') then
conn:add_read(sophos_callback)
end
end
- if rule.dynamic_scan then
- local pre_check, pre_check_msg = common.check_metric_results(task, rule)
- if pre_check then
- rspamd_logger.infox(task, '%s: aborting: %s', rule.log_prefix, pre_check_msg)
- return true
- end
- end
-
tcp.request({
task = task,
host = addr:to_string(),
})
end
- if common.need_av_check(task, content, rule) then
- if common.check_av_cache(task, digest, rule, sophos_check_uncached) then
- return
- else
- sophos_check_uncached()
- end
+ if common.need_check(task, content, rule, digest) then
+ sophos_check_uncached()
end
+
end
return {
if rule.extended == false then
common.yield_result(task, rule, symbols, spam_score)
- common.save_av_cache(task, digest, rule, symbols, spam_score)
+ common.save_cache(task, digest, rule, symbols, spam_score)
else
local symbols_table = {}
symbols_table = rspamd_str_split(symbols, ",")
lua_util.debugm(rule.N, task, '%s: returned symbols as table: %s', rule.log_prefix, symbols_table)
common.yield_result(task, rule, symbols_table, spam_score)
- common.save_av_cache(task, digest, rule, symbols_table, spam_score)
+ common.save_cache(task, digest, rule, symbols_table, spam_score)
end
else
common.log_clean(task, rule, 'no spam detected - spam score: ' .. spam_score .. ', symbols: ' .. symbols)
end
end
- if rule.dynamic_scan then
- local pre_check, pre_check_msg = common.check_metric_results(task, rule)
- if pre_check then
- rspamd_logger.infox(task, '%s: aborting: %s', rule.log_prefix, pre_check_msg)
- return true
- end
- end
-
tcp.request({
task = task,
host = addr:to_string(),
callback = spamassassin_callback,
})
end
- if common.need_av_check(task, content, rule) then
- if common.check_av_cache(task, digest, rule, spamassassin_check_uncached) then
- return
- else
- spamassassin_check_uncached()
- end
+
+ if common.need_check(task, content, rule, digest) then
+ spamassassin_check_uncached()
end
+
end
return {
end
end
- if rule.dynamic_scan then
- local pre_check, pre_check_msg = common.check_metric_results(task, rule)
- if pre_check then
- rspamd_logger.infox(task, '%s: aborting: %s', rule.log_prefix, pre_check_msg)
- return true
- end
+ if common.need_check(task, content, rule, digest) then
+ request_data.callback = vade_callback
+ http.request(request_data)
end
- request_data.callback = vade_callback
- http.request(request_data)
end
return {