commit 2b9ee10: [Feature] DCC: Add bulkness and reputation checks to dcc

Vsevolod Stakhov vsevolod at highsecure.ru
Sat Dec 29 12:21:05 UTC 2018


Author: Vsevolod Stakhov
Date: 2018-12-29 12:13:53 +0000
URL: https://github.com/rspamd/rspamd/commit/2b9ee10f8dd690bfc8c6c244f783d0555de9b0ab (HEAD -> master)

[Feature] DCC: Add bulkness and reputation checks to dcc

---
 lualib/lua_scanners/dcc.lua | 81 +++++++++++++++++++++++++++++++++++++++------
 src/plugins/lua/dcc.lua     | 43 +++++++++++++++++++++---
 2 files changed, 110 insertions(+), 14 deletions(-)

diff --git a/lualib/lua_scanners/dcc.lua b/lualib/lua_scanners/dcc.lua
index 27230fd6f..3d59f0f1b 100644
--- a/lualib/lua_scanners/dcc.lua
+++ b/lualib/lua_scanners/dcc.lua
@@ -104,7 +104,10 @@ local function dcc_check(task, content, _, rule)
             timeout = rule.timeout or 2.0,
             shutdown = true,
             data = request_data,
-            callback = dcc_callback
+            callback = dcc_callback,
+            body_max = 999999,
+            fuz1_max = 999999,
+            fuz2_max = 999999,
           })
         else
           rspamd_logger.errx(task, '%s: failed to scan, maximum retransmits exceed', rule['symbol'], rule['type'])
@@ -117,25 +120,75 @@ local function dcc_check(task, content, _, rule)
         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)
+            task:insert_result(rule.symbol_fail,
+                0.0,
+                'tempfail:' .. 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)
+              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)
+              lua_util.debugm(N, task, '%s: returned result A - info: %s',
+                  rule.log_prefix, info)
+
+              local opts = {}
+              local score = 0.0
+              info = info:lower()
+              local rep = info:match('rep=([^=%s]+)')
+
+              -- Adjust reputation if available
+              if rep then rep = tonumber(rep) end
+              if not rep then
+                rep = 1.0
+              end
+
+              local function check_threshold(what, num, lim)
+                local rnum
+                if num == 'many' then
+                  rnum = lim
+                else
+                  rnum = tonumber(num)
+                end
+
+                if rnum and rnum >= lim then
+                  opts[#opts + 1] = string.format('%s=%s', what, num)
+                  score = score + rep / 3.0
+                end
+              end
+
+              info = info:lower()
+              local body = info:match('body=([^=%s]+)')
+
+              if body then
+                check_threshold('body', body, rule.body_max)
+              end
+
+              local fuz1 = info:match('fuz1=([^=%s]+)')
+
+              if fuz1 then
+                check_threshold('fuz1', fuz1, rule.fuz1_max)
+              end
+
+              local fuz2 = info:match('fuz2=([^=%s]+)')
+
+              if fuz2 then
+                check_threshold('fuz2', fuz2, rule.fuz2_max)
+              end
+
+              if #opts > 0 and score > 0 then
+                task:insert_result(rule.symbol_bulk,
+                    score,
+                    opts)
+              end
             end
           elseif result == 'G' then
             -- do nothing
@@ -154,7 +207,9 @@ local function dcc_check(task, content, _, rule)
           else
             -- Unknown result
             rspamd_logger.warnx(task, 'DCC result error: %1', result);
-            task:insert_result(rule['symbol_fail'], 0.0, 'DCC result error: ' .. result)
+            task:insert_result(rule.symbol_fail,
+                0.0,
+                'error: ' .. result)
           end
         end
       end
@@ -187,6 +242,12 @@ local function dcc_config(opts)
     default_score = 1,
     action = false,
     client = '0.0.0.0',
+    symbol_fail = 'DCC_FAIL',
+    symbol = 'DCC_REJECT',
+    symbol_bulk = 'DCC_BULK',
+    body_max = 999999,
+    fuz1_max = 999999,
+    fuz2_max = 999999,
   }
 
   dcc_conf = lua_util.override_defaults(dcc_conf, opts)
diff --git a/src/plugins/lua/dcc.lua b/src/plugins/lua/dcc.lua
index d82ceda94..32cb6624b 100644
--- a/src/plugins/lua/dcc.lua
+++ b/src/plugins/lua/dcc.lua
@@ -19,6 +19,7 @@ limitations under the License.
 
 local N = 'dcc'
 local symbol_bulk = "DCC_BULK"
+local symbol = "DCC_REJECT"
 local opts = rspamd_config:get_all_opt(N)
 local rspamd_logger = require "rspamd_logger"
 local dcc = require("lua_scanners").filter('dcc').dcc
@@ -32,6 +33,9 @@ dcc {
   socket = "/var/dcc/dccifd"; # Unix socket
   servers = "127.0.0.1:10045" # OR TCP upstreams
   timeout = 2s; # Timeout to wait for checks
+  body_max = 999999; # Bulkness threshold for body
+  fuz1_max = 999999; # Bulkness threshold for fuz1
+  fuz2_max = 999999; # Bulkness threshold for fuz2
 }
 ]])
   return
@@ -58,21 +62,52 @@ if opts['host'] ~= nil and not opts['port'] then
 end
 -- WORKAROUND for deprecated host and port settings
 
-if not opts.symbol then opts.symbol = symbol_bulk end
+if not opts.symbol_bulk then opts.symbol_bulk = symbol_bulk end
+if not opts.symbol then opts.symbol = symbol end
+
 rule = dcc.configure(opts)
 
 if rule then
-  rspamd_config:register_symbol({
-    name = opts.symbol,
+  local id = rspamd_config:register_symbol({
+    name = 'DCC_CHECK',
     callback = check_dcc
   })
+  rspamd_config:register_symbol{
+    type = 'virtual',
+    parent = id,
+    name = opts.symbol
+  }
+  rspamd_config:register_symbol{
+    type = 'virtual',
+    parent = id,
+    name = opts.symbol_bulk
+  }
+  rspamd_config:register_symbol{
+    type = 'virtual',
+    parent = id,
+    name = 'DCC_FAIL'
+  }
   rspamd_config:set_metric_symbol({
     group = N,
-    score = 2.0,
+    score = 1.0,
     description = 'Detected as bulk mail by DCC',
     one_shot = true,
+    name = opts.symbol_bulk,
+  })
+  rspamd_config:set_metric_symbol({
+    group = N,
+    score = 2.0,
+    description = 'Rejected by DCC',
+    one_shot = true,
     name = opts.symbol,
   })
+  rspamd_config:set_metric_symbol({
+    group = N,
+    score = 0.0,
+    description = 'DCC failure',
+    one_shot = true,
+    name = 'DCC_FAIL',
+  })
 else
   lua_util.disable_module(N, "config")
   rspamd_logger.infox('DCC module not configured');


More information about the Commits mailing list