|
|
@@ -20,10 +20,11 @@ limitations under the License. |
|
|
|
This module contains icap access functions. |
|
|
|
Currently tested with |
|
|
|
- C-ICAP Squidclamav / echo |
|
|
|
- Checkpoint Sandblast |
|
|
|
- F-Secure Internet Gatekeeper |
|
|
|
- Kaspersky Web Traffic Security |
|
|
|
- Kaspersky Scan Engine 2.0 |
|
|
|
- McAfee Web Gateway 11 |
|
|
|
- McAfee Web Gateway 9/10/11 |
|
|
|
- Sophos Savdi |
|
|
|
- Symantec (Rspamd <3.2, >=3.2 untested) |
|
|
|
- Trend Micro IWSVA 6.0 |
|
|
@@ -41,6 +42,9 @@ Configuration Notes: |
|
|
|
C-ICAP Squidclamav |
|
|
|
scheme = "squidclamav"; |
|
|
|
|
|
|
|
Checkpoint Sandblast example: |
|
|
|
scheme = "sandblast"; |
|
|
|
|
|
|
|
ESET Gateway Security / Antivirus for Linux example: |
|
|
|
scheme = "scan"; |
|
|
|
|
|
|
@@ -58,7 +62,7 @@ Kaspersky Web Traffic Security (as configured in kavicapd.xml): |
|
|
|
scheme = "resp"; |
|
|
|
x_client_header = true; |
|
|
|
|
|
|
|
McAfee Web Gateway 11 (Headers must be activated with personal extra Rules) |
|
|
|
McAfee Web Gateway 10/11 (Headers must be activated with personal extra Rules) |
|
|
|
scheme = "respmod"; |
|
|
|
x_client_header = true; |
|
|
|
|
|
|
@@ -168,6 +172,7 @@ local function icap_check(task, content, digest, rule, maybe_part) |
|
|
|
local http_headers = {} |
|
|
|
local req_headers = {} |
|
|
|
local tcp_options = {} |
|
|
|
local threat_table = {} |
|
|
|
|
|
|
|
-- Build extended User Agent |
|
|
|
if rule.user_agent == "extended" then |
|
|
@@ -231,7 +236,7 @@ local function icap_check(task, content, digest, rule, maybe_part) |
|
|
|
else |
|
|
|
rspamd_logger.errx(task, '%s: failed to scan, maximum retransmits '.. |
|
|
|
'exceed - error: %s', rule.log_prefix, err_m or '') |
|
|
|
common.yield_result(task, rule, 'failed - error: ' .. err_m or '', |
|
|
|
common.yield_result(task, rule, string.format('failed - error: %s', err_m), |
|
|
|
0.0, 'fail', maybe_part) |
|
|
|
end |
|
|
|
end |
|
|
@@ -341,9 +346,21 @@ local function icap_check(task, content, digest, rule, maybe_part) |
|
|
|
return icap_headers |
|
|
|
end |
|
|
|
|
|
|
|
local function icap_parse_result(headers) |
|
|
|
local function threat_table_add(icap_threat, maybe_split) |
|
|
|
|
|
|
|
if maybe_split and string.find(icap_threat, ',') then |
|
|
|
local threats = lua_util.str_split(string.gsub(icap_threat, "%s", ""), ',') or {} |
|
|
|
|
|
|
|
for _,v in ipairs(threats) do |
|
|
|
table.insert(threat_table, v) |
|
|
|
end |
|
|
|
else |
|
|
|
table.insert(threat_table, icap_threat) |
|
|
|
end |
|
|
|
return true |
|
|
|
end |
|
|
|
|
|
|
|
local threat_string = {} |
|
|
|
local function icap_parse_result(headers) |
|
|
|
|
|
|
|
--[[ |
|
|
|
@ToDo: handle type in response |
|
|
@@ -389,7 +406,7 @@ local function icap_check(task, content, digest, rule, maybe_part) |
|
|
|
icap: X-Virus-ID: Trojaner |
|
|
|
icap: X-Response-Info: Blocked |
|
|
|
|
|
|
|
McAfee Web Gateway 11 (Headers must be activated with personal extra Rules) |
|
|
|
McAfee Web Gateway 10/11 (Headers must be activated with personal extra Rules) |
|
|
|
icap: X-Virus-ID: EICAR test file |
|
|
|
icap: X-Media-Type: text/plain |
|
|
|
icap: X-Block-Result: 80 |
|
|
@@ -409,32 +426,32 @@ local function icap_check(task, content, digest, rule, maybe_part) |
|
|
|
local _,_,icap_type,_,icap_threat = |
|
|
|
headers['x-infection-found']:find("Type=(.-); Resolution=(.-); Threat=(.-);$") |
|
|
|
|
|
|
|
if not icap_type or icap_type == 2 then |
|
|
|
-- error returned |
|
|
|
-- Type=2 is typical for scan error returns |
|
|
|
if icap_type and icap_type == '2' then |
|
|
|
lua_util.debugm(rule.name, task, |
|
|
|
'%s: icap error X-Infection-Found: %s', rule.log_prefix, icap_threat) |
|
|
|
common.yield_result(task, rule, icap_threat, 0, |
|
|
|
'fail', maybe_part) |
|
|
|
return true |
|
|
|
else |
|
|
|
elseif icap_threat ~= nil then |
|
|
|
lua_util.debugm(rule.name, task, |
|
|
|
'%s: icap X-Infection-Found: %s', rule.log_prefix, icap_threat) |
|
|
|
table.insert(threat_string, icap_threat) |
|
|
|
threat_table_add(icap_threat, false) |
|
|
|
-- stupid workaround for unuseable x-infection-found header |
|
|
|
-- but also x-virus-name set (McAfee Web Gateway 9) |
|
|
|
elseif not icap_threat and headers['x-virus-name'] then |
|
|
|
threat_table_add(headers['x-virus-name'], true) |
|
|
|
else |
|
|
|
threat_table_add(headers['x-infection-found'], true) |
|
|
|
end |
|
|
|
|
|
|
|
elseif headers['x-virus-name'] and headers['x-virus-name'] ~= "no threats" then |
|
|
|
lua_util.debugm(rule.name, task, |
|
|
|
'%s: icap X-Virus-Name: %s', rule.log_prefix, headers['x-virus-name']) |
|
|
|
threat_table_add(headers['x-virus-name'], true) |
|
|
|
elseif headers['x-virus-id'] and headers['x-virus-id'] ~= "no threats" then |
|
|
|
lua_util.debugm(rule.name, task, |
|
|
|
'%s: icap X-Virus-ID: %s', rule.log_prefix, headers['x-virus-id']) |
|
|
|
|
|
|
|
if string.find(headers['x-virus-id'], ', ') then |
|
|
|
local vnames = lua_util.str_split(string.gsub(headers['x-virus-id'], "%s", ""), ',') or {} |
|
|
|
|
|
|
|
for _,v in ipairs(vnames) do |
|
|
|
table.insert(threat_string, v) |
|
|
|
end |
|
|
|
else |
|
|
|
table.insert(threat_string, headers['x-virus-id']) |
|
|
|
end |
|
|
|
threat_table_add(headers['x-virus-id'], true) |
|
|
|
-- FSecure X-Headers |
|
|
|
elseif headers['x-fsecure-scan-result'] and headers['x-fsecure-scan-result'] ~= "clean" then |
|
|
|
|
|
|
@@ -452,33 +469,29 @@ local function icap_check(task, content, digest, rule, maybe_part) |
|
|
|
'%s: icap X-FSecure-Infection-Name (X-FSecure-Infected-Filename): %s (%s)', |
|
|
|
rule.log_prefix, infection_name, infected_filename) |
|
|
|
|
|
|
|
if string.find(infection_name, ', ') then |
|
|
|
local vnames = lua_util.str_split(infection_name, ',') or {} |
|
|
|
|
|
|
|
for _,v in ipairs(vnames) do |
|
|
|
table.insert(threat_string, v) |
|
|
|
end |
|
|
|
else |
|
|
|
table.insert(threat_string, infection_name) |
|
|
|
end |
|
|
|
threat_table_add(infection_name, true) |
|
|
|
-- McAfee Web Gateway manual extra headers |
|
|
|
elseif headers['x-mwg-block-reason'] and headers['x-mwg-block-reason'] ~= "" then |
|
|
|
table.insert(threat_string, headers['x-mwg-block-reason']) |
|
|
|
threat_table_add(headers['x-mwg-block-reason'], false) |
|
|
|
-- Sophos SAVDI special http headers |
|
|
|
elseif headers['x-blocked'] and headers['x-blocked'] ~= "" then |
|
|
|
table.insert(threat_string, headers['x-blocked']) |
|
|
|
threat_table_add(headers['x-blocked'], false) |
|
|
|
elseif headers['x-block-reason'] and headers['x-block-reason'] ~= "" then |
|
|
|
threat_table_add(headers['x-block-reason'], false) |
|
|
|
-- last try HTTP [4]xx return |
|
|
|
elseif headers.http and string.find(headers.http, '^HTTP%/[12]%.. [4]%d%d') then |
|
|
|
local message = string.format("pseudo-virus (blocked): %s", string.gsub(headers.http, 'HTTP%/[12]%.. ', '')) |
|
|
|
table.insert(threat_string, message) |
|
|
|
elseif rule.use_http_3xx_as_threat and headers.http and string.find(headers.http, '^HTTP%/[12]%.. [3]%d%d') then |
|
|
|
local message = string.format("pseudo-virus (redirect): %s", string.gsub(headers.http, 'HTTP%/[12]%.. ', '')) |
|
|
|
table.insert(threat_string, message) |
|
|
|
threat_table_add( |
|
|
|
string.format("pseudo-virus (blocked): %s", string.gsub(headers.http, 'HTTP%/[12]%.. ', '')), false) |
|
|
|
elseif rule.use_http_3xx_as_threat and headers.http and string.find(headers.http, '^HTTP%/[12]%.. [3]%d%d') |
|
|
|
then |
|
|
|
|
|
|
|
threat_table_add( |
|
|
|
string.format("pseudo-virus (redirect): %s", string.gsub(headers.http, 'HTTP%/[12]%.. ', '')), false) |
|
|
|
end |
|
|
|
|
|
|
|
if #threat_string > 0 then |
|
|
|
common.yield_result(task, rule, threat_string, rule.default_score, nil, maybe_part) |
|
|
|
common.save_cache(task, digest, rule, threat_string, rule.default_score, maybe_part) |
|
|
|
if #threat_table > 0 then |
|
|
|
common.yield_result(task, rule, threat_table, rule.default_score, nil, maybe_part) |
|
|
|
common.save_cache(task, digest, rule, threat_table, rule.default_score, maybe_part) |
|
|
|
return true |
|
|
|
else |
|
|
|
return false |
|
|
@@ -505,8 +518,7 @@ local function icap_check(task, content, digest, rule, maybe_part) |
|
|
|
else |
|
|
|
rspamd_logger.errx(task, '%s: unhandled response |%s|', |
|
|
|
rule.log_prefix, string.gsub(result, "\r\n", ", ")) |
|
|
|
common.yield_result(task, rule, |
|
|
|
'unhandled icap response: ' .. icap_http_headers.icap or "-", |
|
|
|
common.yield_result(task, rule, string.format('unhandled icap response: %s', icap_http_headers.icap), |
|
|
|
0.0, 'fail', maybe_part) |
|
|
|
end |
|
|
|
end |
|
|
@@ -560,8 +572,7 @@ local function icap_check(task, content, digest, rule, maybe_part) |
|
|
|
else |
|
|
|
rspamd_logger.errx(task, '%s: unhandled response |%s|', |
|
|
|
rule.log_prefix, string.gsub(result, "\r\n", ", ")) |
|
|
|
common.yield_result(task, rule, |
|
|
|
'unhandled icap response: ' .. icap_headers.icap or "-", |
|
|
|
common.yield_result(task, rule, string.format('unhandled icap response: %s', icap_headers.icap), |
|
|
|
0.0, 'fail', maybe_part) |
|
|
|
end |
|
|
|
end |