From: Steve Freegard Date: Tue, 5 Apr 2016 18:28:10 +0000 (+0100) Subject: New dcc module X-Git-Tag: 1.2.3~47^2 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=c8c304d5617c1e10ff9bddf026b74f2d316e9c16;p=rspamd.git New dcc module --- diff --git a/conf/metrics.conf b/conf/metrics.conf index 3669f1cb0..7f5b1e7da 100644 --- a/conf/metrics.conf +++ b/conf/metrics.conf @@ -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 index 000000000..36931ac3a --- /dev/null +++ b/doc/markdown/modules/dcc.md @@ -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. diff --git a/doc/markdown/modules/index.md b/doc/markdown/modules/index.md index 69cbdc69a..afb440a8e 100644 --- a/doc/markdown/modules/index.md +++ b/doc/markdown/modules/index.md @@ -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 index 000000000..7e8c5b20d --- /dev/null +++ b/src/plugins/lua/dcc.lua @@ -0,0 +1,117 @@ +--[[ +Copyright (c) 2016, Steve Freegard + +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