]> source.dussan.org Git - rspamd.git/commitdiff
New dcc module 586/head
authorSteve Freegard <steve@stevefreegard.com>
Tue, 5 Apr 2016 18:28:10 +0000 (19:28 +0100)
committerSteve Freegard <steve@stevefreegard.com>
Tue, 5 Apr 2016 18:28:10 +0000 (19:28 +0100)
conf/metrics.conf
doc/markdown/modules/dcc.md [new file with mode: 0644]
doc/markdown/modules/index.md
src/plugins/lua/dcc.lua [new file with mode: 0644]

index 3669f1cb04ce008fd1bab0b194b851a796abaa54..7f5b1e7dafd0595f5e3cb3d4fe884180d0ad48be 100644 (file)
@@ -1245,6 +1245,16 @@ metric {
             one_shot = true;
         }
     }
+    group {
+        name = "dcc";
+
+        symbol {
+            weight = 2.0;
+            name = "DCC_BULK";
+            description = "Detected as bulk mail by DCC";
+            one_shot = true;
+        }
+    }
 
     .include(try=true; priority=1) "$LOCAL_CONFDIR/local.d/metrics.conf"
     .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/metrics.conf"
diff --git a/doc/markdown/modules/dcc.md b/doc/markdown/modules/dcc.md
new file mode 100644 (file)
index 0000000..36931ac
--- /dev/null
@@ -0,0 +1,40 @@
+# DCC module
+
+This modules performs [DCC](http://www.dcc-servers.net/dcc/) lookups to determine
+the *bulkiness* of a message (e.g. how many recipients have seen it).
+
+Identifying bulk messages is very useful in composite rules e.g. if a message is
+from a freemail domain *AND* the message is reported as bulk by DCC then you can
+be sure the message is spam and can assign a greater weight to it.
+
+Please view the License terms on the DCC website before you enable this module.
+
+## Module configuration
+
+This module requires that you have the `dccifd` daemon configured, running and
+working correctly.  To do this you must download and build the [latest DCC client]
+(https://www.dcc-servers.net/dcc/source/dcc.tar.Z).  Once installed, edit
+`/var/dcc/dcc_conf` set `DCCIFD_ENABLE=on` and set `DCCM_LOG_AT=NEVER` and
+`DCCM_REJECT_AT=MANY`, then start the daemon by running `/var/dcc/libexec/rcDCC start`.
+
+Once the `dccifd` daemon is started it will listen on the UNIX domain socket /var/dcc/dccifd
+and all you have to do is tell the rspamd where `dccifd` is listening:
+
+~~~ucl
+dcc {
+    host = "/var/dcc/dccifd";
+    # Port is only required if `dccifd` listens on a TCP socket
+    # port = 1234
+}
+~~~
+
+Once this module is configured it will write the DCC output to the rspamd as each
+message is scanned:
+
+`````
+Apr  5 14:19:53 mail1-ewh rspamd: (normal) lua; dcc.lua:98: sending to dcc: client=217.78.2.204#015DNSERROR helo="003b046f.slimabs.top" envfrom="23SecondAbs@slimabs.top" envrcpt="xxxx@xxxx.com"
+Apr  5 14:19:53 mail1-ewh rspamd: (normal) lua; dcc.lua:65: DCC result=R disposition=R header="X-DCC--Metrics: xxxxx.xxxx.com 1282; bulk Body=1 Fuz1=1 Fuz2=many"
+`````
+
+Any messages that DCC returns a *reject* result for (based on the configured `DCCM_REJECT_AT`
+value) will cause the symbol `DCC_BULK` to fire.
index 69cbdc69a73d019d2cbc0a4313f5b11fcb56832d..afb440a8e40be4bddc5d1a2cbd4895c23e15e640 100644 (file)
@@ -67,3 +67,4 @@ uses `redis` to store data.
 - [rspamd_update](rspamd_update.md) - load dynamic rules and other rspamd updates
 - [spamassassin](spamassassin.md) - load spamassassin rules
 - [dmarc](dmarc.md) - performs DMARC policy checks
+- [dcc](dcc.md) - performs [DCC](http://www.dcc-servers.net/dcc/) lookups to determine message bulkiness
diff --git a/src/plugins/lua/dcc.lua b/src/plugins/lua/dcc.lua
new file mode 100644 (file)
index 0000000..7e8c5b2
--- /dev/null
@@ -0,0 +1,117 @@
+--[[
+Copyright (c) 2016, Steve Freegard <steve.freegard@fsl.com>
+
+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.
+]]--
+
+-- Check messages for 'bulkiness' using DCC
+
+local symbol = "DCC_CHECK"
+local symbol_bulk = "DCC_BULK"
+local opts = rspamd_config:get_all_opt('dcc')
+local logger = require "rspamd_logger"
+local tcp = require "rspamd_tcp"
+
+local function check_dcc (task)
+    -- Connection
+    local client = '0.0.0.0'
+    local client_ip = task:get_from_ip():to_string()
+    if client_ip then
+        client = client_ip
+    end
+    local client_host = task:get_hostname()
+    if client_host and client_host ~= 'unknown' then
+        client = client .. "\r" .. client_host
+    end
+
+    -- HELO
+    local helo = task:get_helo() or ''
+
+    -- Envelope From
+    local ef = task:get_from(1)
+    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(1);
+    if rcpts then
+      local r = table.concat(totable(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
+            logger.errx('DCC error: %1', err)
+            return 0
+        end
+        -- Parse the response
+        local _,_,result,disposition,header = tostring(data):find("(.-)\n(.-)\n(.-)\n")
+        logger.infox('DCC result=%1 disposition=%2 header="%3"', result, disposition, header)
+        local _,_,info = header:find("; (.-)$")
+        if (result == 'A') then
+            -- Accept
+        elseif (result == 'G') then
+            -- Greylist
+        elseif (result == 'R') then
+            -- Reject
+            task:insert_result('DCC_BULK', 1.0, info)
+        elseif (result == 'S') then
+            -- Accept for some recipients only
+        elseif (result == 'T') then
+            -- Temporary failure
+            logger.errx('DCC returned a temporary failure result')
+        else
+            -- Unknown result
+            logger.errx('DCC result error: %1', result);
+        end
+        return 0
+    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()
+    }
+
+    logger.infox('sending to dcc: client=%1 helo="%2" envfrom="%3" envrcpt="%4"',
+                 client, helo, envfrom, envrcpt)
+
+    tcp.request({
+        task = task,
+        host = opts['host'],
+        port = opts['port'] or 1,
+        shutdown = true,
+        data = data,
+        callback = cb
+    })
+end
+
+-- Configuration
+if opts and opts['host'] then
+    local id = rspamd_config:register_symbol(symbol, 1.0, check_dcc)
+    rspamd_config:register_virtual_symbol(symbol_bulk, 1.0, id)
+else
+    logger.infox('DCC module not configured');
+end