commit b7c44f5: [Minor] lua_scanners - icap - analyse HTTP return headers

Carsten Rosenberg c.rosenberg at heinlein-support.de
Thu Nov 4 20:28:06 UTC 2021


Author: Carsten Rosenberg
Date: 2021-11-02 19:28:45 +0100
URL: https://github.com/rspamd/rspamd/commit/b7c44f57a0a24c6dcc49a7ecbd65887981e945d0

[Minor] lua_scanners - icap - analyse HTTP return headers

---
 lualib/lua_scanners/icap.lua | 105 +++++++++++++++++++++++++++++++------------
 1 file changed, 76 insertions(+), 29 deletions(-)

diff --git a/lualib/lua_scanners/icap.lua b/lualib/lua_scanners/icap.lua
index 93570c82f..4f475eaaa 100644
--- a/lualib/lua_scanners/icap.lua
+++ b/lualib/lua_scanners/icap.lua
@@ -105,6 +105,8 @@ local function icap_config(opts)
     req_headers_enabled = true,
     req_fake_url = "http://127.0.0.1/mail",
     http_headers_enabled = true,
+    use_http_result_header = true,
+    use_http_3xx_as_threat = false,
   }
 
   icap_conf = lua_util.override_defaults(icap_conf, opts)
@@ -299,13 +301,14 @@ local function icap_check(task, content, digest, rule, maybe_part)
         end
       end
 
-      local function icap_result_header_table(result)
+      local function result_header_table(result)
         local icap_headers = {}
         for s in result:gmatch("[^\r\n]+") do
           if string.find(s, '^ICAP') then
             icap_headers['icap'] = tostring(s)
-          end
-          if string.find(s, '[%a%d-+]-:') then
+          elseif string.find(s, '^HTTP') then
+            icap_headers['http'] = tostring(s)
+          elseif string.find(s, '[%a%d-+]-:') then
             local _,_,key,value = tostring(s):find("([%a%d-+]-):%s?(.+)")
             if key ~= nil then
               icap_headers[key] = tostring(value)
@@ -317,7 +320,7 @@ local function icap_check(task, content, digest, rule, maybe_part)
         return icap_headers
       end
 
-      local function icap_parse_result(icap_headers)
+      local function icap_parse_result(headers)
 
         local threat_string = {}
 
@@ -370,9 +373,9 @@ local function icap_check(task, content, digest, rule, maybe_part)
         ]] --
 
         -- Generic ICAP Headers
-        if icap_headers['X-Infection-Found'] then
+        if headers['X-Infection-Found'] then
           local _,_,icap_type,_,icap_threat =
-            icap_headers['X-Infection-Found']:find("Type=(.-); Resolution=(.-); Threat=(.-);$")
+            headers['X-Infection-Found']:find("Type=(.-); Resolution=(.-); Threat=(.-);$")
 
           if not icap_type or icap_type == 2 then
             -- error returned
@@ -386,30 +389,30 @@ local function icap_check(task, content, digest, rule, maybe_part)
             table.insert(threat_string, icap_threat)
           end
 
-        elseif icap_headers['X-Virus-ID'] and icap_headers['X-Virus-ID'] ~= "no threats" then
+        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, icap_headers['X-Virus-ID'])
+              '%s: icap X-Virus-ID: %s', rule.log_prefix, headers['X-Virus-ID'])
 
-          if string.find(icap_headers['X-Virus-ID'], ', ') then
-            local vnames = lua_util.str_split(string.gsub(icap_headers['X-Virus-ID'], "%s", ""), ',') or {}
+          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, icap_headers['X-Virus-ID'])
+            table.insert(threat_string, headers['X-Virus-ID'])
           end
         -- FSecure X-Headers
-        elseif icap_headers['X-FSecure-Scan-Result'] and icap_headers['X-FSecure-Scan-Result'] ~= "clean" then
+        elseif headers['X-FSecure-Scan-Result'] and headers['X-FSecure-Scan-Result'] ~= "clean" then
 
           local infected_filename = ""
           local infection_name = "-unknown-"
 
-          if icap_headers['X-FSecure-Infected-Filename'] then
-            infected_filename = string.gsub(icap_headers['X-FSecure-Infected-Filename'], '[%s"]', '')
+          if headers['X-FSecure-Infected-Filename'] then
+            infected_filename = string.gsub(headers['X-FSecure-Infected-Filename'], '[%s"]', '')
           end
-          if icap_headers['X-FSecure-Infection-Name'] then
-            infection_name = string.gsub(icap_headers['X-FSecure-Infection-Name'], '[%s"]', '')
+          if headers['X-FSecure-Infection-Name'] then
+            infection_name = string.gsub(headers['X-FSecure-Infection-Name'], '[%s"]', '')
           end
 
           lua_util.debugm(rule.name, task,
@@ -426,16 +429,49 @@ local function icap_check(task, content, digest, rule, maybe_part)
             table.insert(threat_string, infection_name)
           end
         -- McAfee Web Gateway manual extra headers
-        elseif icap_headers['X-MWG-Block-Reason'] and icap_headers['X-MWG-Block-Reason'] ~= "" then
-          table.insert(threat_string, icap_headers['X-MWG-Block-Reason'])
+        elseif headers['X-MWG-Block-Reason'] and headers['X-MWG-Block-Reason'] ~= "" then
+          table.insert(threat_string, headers['X-MWG-Block-Reason'])
+        -- Sophos SAVDI special http headers 
+        elseif headers['X-Blocked'] and headers['X-Blocked'] ~= "" then
+          table.insert(threat_string, headers['X-MWG-Block-Reason'])
+        -- 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)
         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)
+          return true
         else
           common.save_cache(task, digest, rule, 'OK', 0, maybe_part)
           common.log_clean(task, rule)
+          return false
+        end
+      end
+
+      local function icap_r_respond_http_cb(err_m, data, connection)
+        if err_m or connection == nil then
+          icap_requery(err_m, "icap_r_respond_http_cb")
+        else
+          local result = tostring(data)
+          connection:close()
+
+          local icap_http_headers = result_header_table(result) or {}
+          -- Find HTTP/[12].x [234]xx response
+          if icap_http_headers.http and string.find(icap_http_headers.http, 'HTTP%/[12]%.. [234]%d%d') then
+            icap_parse_result(icap_http_headers)
+          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 "-",
+                0.0, 'fail', maybe_part)
+          end
         end
       end
 
@@ -444,12 +480,23 @@ local function icap_check(task, content, digest, rule, maybe_part)
           icap_requery(err_m, "icap_r_respond_cb")
         else
           local result = tostring(data)
-          conn:close()
 
-          local icap_headers = icap_result_header_table(result) or {}
+          local icap_headers = result_header_table(result) or {}
           -- Find ICAP/1.x 2xx response
           if icap_headers.icap and string.find(icap_headers.icap, 'ICAP%/1%.. 2%d%d') then
-            icap_parse_result(icap_headers)
+            local icap_header_result = icap_parse_result(icap_headers)
+            if icap_header_result then
+              -- Threat found - close connection
+              connection:close()
+            elseif not icap_header_result 
+              and rule.use_http_result_header 
+              and not string.find(icap_headers.Encapsulated, 'null%-body=0') 
+              then
+              -- Try to read encapsulated HTTP Headers
+              lua_util.debugm(rule.name, task, '%s: no ICAP virus header found - try HTTP headers',
+                rule.log_prefix)
+              connection:add_read(icap_r_respond_http_cb, '\r\n\r\n')
+            end
           elseif icap_headers.icap and string.find(icap_headers.icap, 'ICAP%/1%.. [45]%d%d') then
             -- Find ICAP/1.x 5/4xx response
             --[[
@@ -472,25 +519,25 @@ local function icap_check(task, content, digest, rule, maybe_part)
         end
       end
 
-      local function icap_w_respond_cb(err_m, connection)
-        if err_m or connection == nil then
+      local function icap_w_respond_cb(err_m, conn)
+        if err_m or conn == nil then
           icap_requery(err_m, "icap_w_respond_cb")
         else
-          connection:add_read(icap_r_respond_cb, '\r\n\r\n')
+          conn:add_read(icap_r_respond_cb, '\r\n\r\n')
         end
       end
 
-      local function icap_r_options_cb(err_m, data, connection)
-        if err_m or connection == nil then
+      local function icap_r_options_cb(err_m, data, conn)
+        if err_m or conn == nil then
           icap_requery(err_m, "icap_r_options_cb")
         else
-          local icap_headers = icap_result_header_table(tostring(data))
+          local icap_headers = result_header_table(tostring(data))
 
           if icap_headers.icap and string.find(icap_headers.icap, 'ICAP%/1%.. 2%d%d') then
             if icap_headers['Methods'] and string.find(icap_headers['Methods'], 'RESPMOD') then
-              -- Preview is currently ununsed
+              --Preview is currently ununsed
               --if icap_headers['Allow'] and string.find(icap_headers['Allow'], '204') then
-              --  add_respond_header('Allow', '204')
+              -- add_respond_header('Allow', '204')
               --end
 
               if rule.x_client_header then


More information about the Commits mailing list