Use new milter interface
parent
a41cafac3e
commit
663ea7815c
|
@ -1,26 +0,0 @@
|
|||
FROM debian:jessie-slim
|
||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ENV LC_ALL C
|
||||
|
||||
RUN apt-key adv --fetch-keys http://rspamd.com/apt-stable/gpg.key \
|
||||
&& echo "deb http://rspamd.com/apt-stable/ jessie main" > /etc/apt/sources.list.d/rspamd.list \
|
||||
&& apt-get update && apt-get install -y --force-yes --no-install-recommends \
|
||||
cron \
|
||||
rmilter \
|
||||
supervisor \
|
||||
syslog-ng \
|
||||
syslog-ng-core \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY supervisord.conf /etc/supervisor/supervisord.conf
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
RUN sed -i -E 's/^(\s*)system\(\);/\1unix-stream("\/dev\/log");/' /etc/syslog-ng/syslog-ng.conf
|
||||
RUN touch /var/log/mail.log && chmod 640 /var/log/mail.log && chown root:adm /var/log/mail.log
|
||||
|
||||
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
|
||||
|
||||
RUN rm -rf /tmp/* /var/tmp/*
|
|
@ -1,18 +0,0 @@
|
|||
[supervisord]
|
||||
nodaemon=true
|
||||
|
||||
[program:syslog-ng]
|
||||
command=/usr/sbin/syslog-ng --foreground --no-caps
|
||||
redirect_stderr=true
|
||||
autostart=true
|
||||
stdout_syslog=true
|
||||
|
||||
[program:rmilter]
|
||||
command=/usr/sbin/rmilter -n -c /etc/rmilter.conf.d/rmilter.conf
|
||||
user=_rmilter
|
||||
autorestart=true
|
||||
|
||||
[program:rmilter-syslog]
|
||||
command=/usr/bin/tail -f /var/log/mail.log
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
|
@ -4,8 +4,8 @@ LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
|||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ENV LC_ALL C
|
||||
|
||||
RUN apt-key adv --fetch-keys http://rspamd.com/apt-stable/gpg.key \
|
||||
&& echo "deb http://rspamd.com/apt-stable/ jessie main" > /etc/apt/sources.list.d/rspamd.list \
|
||||
RUN apt-key adv --fetch-keys http://rspamd.com/apt/gpg.key \
|
||||
&& echo "deb http://rspamd.com/apt/ jessie main" > /etc/apt/sources.list.d/rspamd.list \
|
||||
&& apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
rspamd \
|
||||
|
@ -15,7 +15,7 @@ RUN echo '.include $LOCAL_CONFDIR/local.d/rspamd.conf.local' > /etc/rspamd/rspam
|
|||
|
||||
COPY settings.conf /etc/rspamd/modules.d/settings.conf
|
||||
COPY antivirus.conf /etc/rspamd/modules.d/antivirus.conf
|
||||
COPY dkim_signing.lua /usr/share/rspamd/lua/dkim_signing.lua
|
||||
COPY milter_headers.lua /usr/share/rspamd/lua/milter_headers.lua
|
||||
|
||||
RUN apt-get autoremove --purge \
|
||||
&& apt-get clean
|
||||
|
|
|
@ -1,286 +0,0 @@
|
|||
--[[
|
||||
Copyright (c) 2016, Andrew Lewis <nerf@judo.za.org>
|
||||
Copyright (c) 2016, Vsevolod Stakhov <vsevolod@highsecure.ru>
|
||||
|
||||
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.
|
||||
]]--
|
||||
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local rspamd_util = require "rspamd_util"
|
||||
|
||||
if confighelp then
|
||||
return
|
||||
end
|
||||
|
||||
local settings = {
|
||||
allow_envfrom_empty = true,
|
||||
allow_hdrfrom_mismatch = false,
|
||||
allow_hdrfrom_mismatch_local = false,
|
||||
allow_hdrfrom_mismatch_sign_networks = false,
|
||||
allow_hdrfrom_multiple = false,
|
||||
allow_username_mismatch = false,
|
||||
auth_only = true,
|
||||
domain = {},
|
||||
path = string.format('%s/%s/%s', rspamd_paths['DBDIR'], 'dkim', '$domain.$selector.key'),
|
||||
sign_local = true,
|
||||
selector = 'dkim',
|
||||
symbol = 'DKIM_SIGNED',
|
||||
try_fallback = true,
|
||||
use_domain = 'header',
|
||||
use_esld = true,
|
||||
use_redis = false,
|
||||
key_prefix = 'dkim_keys', -- default hash name
|
||||
}
|
||||
|
||||
local E = {}
|
||||
local N = 'dkim_signing'
|
||||
local redis_params
|
||||
|
||||
local function simple_template(tmpl, keys)
|
||||
local lpeg = require "lpeg"
|
||||
|
||||
local var_lit = lpeg.P { lpeg.R("az") + lpeg.R("AZ") + lpeg.R("09") + "_" }
|
||||
local var = lpeg.P { (lpeg.P("$") / "") * ((var_lit^1) / keys) }
|
||||
local var_braced = lpeg.P { (lpeg.P("${") / "") * ((var_lit^1) / keys) * (lpeg.P("}") / "") }
|
||||
|
||||
local template_grammar = lpeg.Cs((var + var_braced + 1)^0)
|
||||
|
||||
return lpeg.match(template_grammar, tmpl)
|
||||
end
|
||||
|
||||
local function dkim_signing_cb(task)
|
||||
local is_local, is_sign_networks
|
||||
local auser = task:get_user()
|
||||
local ip = task:get_from_ip()
|
||||
if ip and ip:is_local() then
|
||||
is_local = true
|
||||
end
|
||||
if settings.auth_only and not auser then
|
||||
if (settings.sign_networks and settings.sign_networks:get_key(ip)) then
|
||||
is_sign_networks = true
|
||||
rspamd_logger.debugm(N, task, 'mail is from address in sign_networks')
|
||||
elseif settings.sign_local and is_local then
|
||||
rspamd_logger.debugm(N, task, 'mail is from local address')
|
||||
else
|
||||
rspamd_logger.debugm(N, task, 'ignoring unauthenticated mail')
|
||||
return
|
||||
end
|
||||
end
|
||||
local efrom = task:get_from('smtp')
|
||||
if not settings.allow_envfrom_empty and
|
||||
#(((efrom or E)[1] or E).addr or '') == 0 then
|
||||
rspamd_logger.debugm(N, task, 'empty envelope from not allowed')
|
||||
return false
|
||||
end
|
||||
local hfrom = task:get_from('mime')
|
||||
if not settings.allow_hdrfrom_multiple and (hfrom or E)[2] then
|
||||
rspamd_logger.debugm(N, task, 'multiple header from not allowed')
|
||||
return false
|
||||
end
|
||||
local dkim_domain
|
||||
local hdom = ((hfrom or E)[1] or E).domain
|
||||
local edom = ((efrom or E)[1] or E).domain
|
||||
if hdom then
|
||||
hdom = hdom:lower()
|
||||
end
|
||||
if edom then
|
||||
edom = edom:lower()
|
||||
end
|
||||
if settings.use_domain_sign_networks and is_sign_networks then
|
||||
if settings.use_domain_sign_networks == 'header' then
|
||||
dkim_domain = hdom
|
||||
else
|
||||
dkim_domain = edom
|
||||
end
|
||||
elseif settings.use_domain_local and is_local then
|
||||
if settings.use_domain_local == 'header' then
|
||||
dkim_domain = hdom
|
||||
else
|
||||
dkim_domain = edom
|
||||
end
|
||||
else
|
||||
if settings.use_domain == 'header' then
|
||||
dkim_domain = hdom
|
||||
else
|
||||
dkim_domain = edom
|
||||
end
|
||||
end
|
||||
if not dkim_domain then
|
||||
rspamd_logger.debugm(N, task, 'could not extract dkim domain')
|
||||
return false
|
||||
end
|
||||
if settings.use_esld then
|
||||
dkim_domain = rspamd_util.get_tld(dkim_domain)
|
||||
if settings.use_domain == 'envelope' and hdom then
|
||||
hdom = rspamd_util.get_tld(hdom)
|
||||
elseif settings.use_domain == 'header' and edom then
|
||||
edom = rspamd_util.get_tld(edom)
|
||||
end
|
||||
end
|
||||
if edom and hdom and not settings.allow_hdrfrom_mismatch and hdom ~= edom then
|
||||
if settings.allow_hdrfrom_mismatch_local and is_local then
|
||||
rspamd_logger.debugm(N, task, 'domain mismatch allowed for local IP: %1 != %2', hdom, edom)
|
||||
elseif settings.allow_hdrfrom_mismatch_sign_networks and is_sign_networks then
|
||||
rspamd_logger.debugm(N, task, 'domain mismatch allowed for sign_networks: %1 != %2', hdom, edom)
|
||||
else
|
||||
rspamd_logger.debugm(N, task, 'domain mismatch not allowed: %1 != %2', hdom, edom)
|
||||
return false
|
||||
end
|
||||
end
|
||||
if auser and not settings.allow_username_mismatch then
|
||||
local udom = string.match(auser, '.*@(.*)')
|
||||
if not udom then
|
||||
rspamd_logger.debugm(N, task, 'couldnt find domain in username')
|
||||
return false
|
||||
end
|
||||
if settings.use_esld then
|
||||
udom = rspamd_util.get_tld(udom)
|
||||
end
|
||||
if udom ~= dkim_domain then
|
||||
rspamd_logger.debugm(N, task, 'user domain mismatch')
|
||||
return false
|
||||
end
|
||||
end
|
||||
local p = {}
|
||||
if settings.domain[dkim_domain] then
|
||||
p.selector = settings.domain[dkim_domain].selector
|
||||
p.key = settings.domain[dkim_domain].path
|
||||
end
|
||||
if not (p.key and p.selector) and not
|
||||
(settings.try_fallback or settings.use_redis or settings.selector_map or settings.path_map) then
|
||||
rspamd_logger.debugm(N, task, 'dkim unconfigured and fallback disabled')
|
||||
return false
|
||||
end
|
||||
if not p.key then
|
||||
if not settings.use_redis then
|
||||
p.key = settings.path
|
||||
end
|
||||
end
|
||||
if not p.selector then
|
||||
p.selector = settings.selector
|
||||
end
|
||||
p.domain = dkim_domain
|
||||
|
||||
if settings.selector_map then
|
||||
local data = settings.selector_map:get_key(dkim_domain)
|
||||
if data then
|
||||
p.selector = data
|
||||
end
|
||||
end
|
||||
if settings.path_map then
|
||||
local data = settings.path_map:get_key(dkim_domain)
|
||||
if data then
|
||||
p.key = data
|
||||
end
|
||||
end
|
||||
|
||||
if settings.use_redis then
|
||||
local function try_redis_key(selector)
|
||||
p.key = nil
|
||||
p.selector = selector
|
||||
local rk = string.format('%s.%s', p.selector, p.domain)
|
||||
local function redis_key_cb(err, data)
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load DKIM key for %s: %s",
|
||||
rk, err)
|
||||
else
|
||||
p.rawkey = data
|
||||
if rspamd_plugins.dkim.sign(task, p) then
|
||||
task:insert_result(settings.symbol, 1.0)
|
||||
end
|
||||
end
|
||||
end
|
||||
local ret = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
rk, -- hash key
|
||||
false, -- is write
|
||||
redis_key_cb, --callback
|
||||
'HGET', -- command
|
||||
{settings.key_prefix, rk} -- arguments
|
||||
)
|
||||
if not ret then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load DKIM key for %s", rk)
|
||||
end
|
||||
end
|
||||
if settings.selector_prefix then
|
||||
rspamd_logger.infox(rspamd_config, "Using selector prefix %s for domain %s", settings.selector_prefix, p.domain);
|
||||
local function redis_selector_cb(err, data)
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load DKIM selector for domain %s: %s", p.domain, err)
|
||||
else
|
||||
try_redis_key(data)
|
||||
end
|
||||
end
|
||||
local ret = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
p.domain, -- hash key
|
||||
false, -- is write
|
||||
redis_selector_cb, --callback
|
||||
'HGET', -- command
|
||||
{settings.selector_prefix, p.domain} -- arguments
|
||||
)
|
||||
if not ret then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load DKIM selector for %s", p.domain)
|
||||
end
|
||||
else
|
||||
if not p.selector then
|
||||
rspamd_logger.errx(task, 'No selector specified')
|
||||
return false
|
||||
end
|
||||
try_redis_key(p.selector)
|
||||
end
|
||||
else
|
||||
if (p.key and p.selector) then
|
||||
p.key = simple_template(p.key, {domain = p.domain, selector = p.selector})
|
||||
return rspamd_plugins.dkim.sign(task, p)
|
||||
else
|
||||
rspamd_logger.infox(task, 'key path or dkim selector unconfigured; no signing')
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local opts = rspamd_config:get_all_opt('dkim_signing')
|
||||
if not opts then return end
|
||||
for k,v in pairs(opts) do
|
||||
if k == 'sign_networks' then
|
||||
settings[k] = rspamd_map_add(N, k, 'radix', 'DKIM signing networks')
|
||||
elseif k == 'path_map' then
|
||||
settings[k] = rspamd_map_add(N, k, 'map', 'Paths to DKIM signing keys')
|
||||
elseif k == 'selector_map' then
|
||||
settings[k] = rspamd_map_add(N, k, 'map', 'DKIM selectors')
|
||||
else
|
||||
settings[k] = v
|
||||
end
|
||||
end
|
||||
if not (settings.use_redis or settings.path or settings.domain or settings.path_map or settings.selector_map) then
|
||||
rspamd_logger.infox(rspamd_config, 'mandatory parameters missing, disable dkim signing')
|
||||
return
|
||||
end
|
||||
if settings.use_redis then
|
||||
redis_params = rspamd_parse_redis_server('dkim_signing')
|
||||
|
||||
if not redis_params then
|
||||
rspamd_logger.errx(rspamd_config, 'no servers are specified, but module is configured to load keys from redis, disable dkim signing')
|
||||
return
|
||||
end
|
||||
end
|
||||
if settings.use_domain ~= 'header' and settings.use_domain ~= 'envelope' then
|
||||
rspamd_logger.errx(rspamd_config, "Value for 'use_domain' is invalid")
|
||||
settings.use_domain = 'header'
|
||||
end
|
||||
|
||||
rspamd_config:register_symbol({
|
||||
name = settings['symbol'],
|
||||
callback = dkim_signing_cb
|
||||
})
|
|
@ -0,0 +1,387 @@
|
|||
--[[
|
||||
Copyright (c) 2016, Andrew Lewis <nerf@judo.za.org>
|
||||
Copyright (c) 2016, Vsevolod Stakhov <vsevolod@highsecure.ru>
|
||||
|
||||
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.
|
||||
]]--
|
||||
|
||||
if confighelp then
|
||||
return
|
||||
end
|
||||
|
||||
-- A plugin that provides common header manipulations
|
||||
|
||||
local logger = require "rspamd_logger"
|
||||
local util = require "rspamd_util"
|
||||
local N = 'milter_headers'
|
||||
local E = {}
|
||||
|
||||
local HOSTNAME = util.get_hostname()
|
||||
|
||||
local settings = {
|
||||
skip_local = false,
|
||||
skip_authenticated = false,
|
||||
routines = {
|
||||
['x-spamd-result'] = {
|
||||
header = 'X-Spamd-Result',
|
||||
remove = 1,
|
||||
},
|
||||
['x-rspamd-server'] = {
|
||||
header = 'X-Rspamd-Server',
|
||||
remove = 1,
|
||||
},
|
||||
['x-rspamd-queue-id'] = {
|
||||
header = 'X-Rspamd-Queue-Id',
|
||||
remove = 1,
|
||||
},
|
||||
['spam-header'] = {
|
||||
header = 'Deliver-To',
|
||||
value = 'Junk',
|
||||
remove = 1,
|
||||
},
|
||||
['x-virus'] = {
|
||||
header = 'X-Virus',
|
||||
remove = 1,
|
||||
symbols = {}, -- needs config
|
||||
},
|
||||
['x-spamd-bar'] = {
|
||||
header = 'X-Spamd-Bar',
|
||||
positive = '+',
|
||||
negative = '-',
|
||||
neutral = '/',
|
||||
remove = 1,
|
||||
},
|
||||
['x-spam-level'] = {
|
||||
header = 'X-Spam-Level',
|
||||
char = '*',
|
||||
remove = 1,
|
||||
},
|
||||
['x-spam-status'] = {
|
||||
header = 'X-Spam-Status',
|
||||
remove = 1,
|
||||
},
|
||||
['authentication-results'] = {
|
||||
header = 'Authentication-Results',
|
||||
remove = 1,
|
||||
spf_symbols = {
|
||||
pass = 'R_SPF_ALLOW',
|
||||
fail = 'R_SPF_FAIL',
|
||||
softfail = 'R_SPF_SOFTFAIL',
|
||||
neutral = 'R_SPF_NEUTRAL',
|
||||
temperror = 'R_SPF_DNSFAIL',
|
||||
none = 'R_SPF_NA',
|
||||
permerror = 'R_SPF_PERMFAIL',
|
||||
},
|
||||
dkim_symbols = {
|
||||
pass = 'R_DKIM_ALLOW',
|
||||
fail = 'R_DKIM_REJECT',
|
||||
temperror = 'R_DKIM_TEMPFAIL',
|
||||
none = 'R_DKIM_NA',
|
||||
permerror = 'R_DKIM_PERMFAIL',
|
||||
},
|
||||
dmarc_symbols = {
|
||||
pass = 'DMARC_POLICY_ALLOW',
|
||||
permerror = 'DMARC_BAD_POLICY',
|
||||
temperror = 'DMARC_DNSFAIL',
|
||||
none = 'DMARC_NA',
|
||||
reject = 'DMARC_POLICY_REJECT',
|
||||
softfail = 'DMARC_POLICY_SOFTFAIL',
|
||||
quarantine = 'DMARC_POLICY_QUARANTINE',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local active_routines = {}
|
||||
local custom_routines = {}
|
||||
|
||||
local function milter_headers(task)
|
||||
|
||||
if settings.skip_local then
|
||||
local ip = task:get_ip()
|
||||
if (ip and ip:is_local()) then return end
|
||||
end
|
||||
|
||||
if settings.skip_authenticated then
|
||||
if task:get_user() ~= nil then return end
|
||||
end
|
||||
|
||||
local routines, common, add, remove = {}, {}, {}, {}
|
||||
|
||||
routines['x-spamd-result'] = function()
|
||||
if not common.symbols then
|
||||
common.symbols = task:get_symbols_all()
|
||||
common['metric_score'] = task:get_metric_score('default')
|
||||
common['metric_action'] = task:get_metric_action('default')
|
||||
end
|
||||
if settings.routines['x-spamd-result'].remove then
|
||||
remove[settings.routines['x-spamd-result'].header] = settings.routines['x-spamd-result'].remove
|
||||
end
|
||||
local buf = {}
|
||||
table.insert(buf, table.concat({
|
||||
'default: ', (common['metric_action'] == 'reject') and 'True' or 'False', ' [',
|
||||
common['metric_score'][1], ' / ', common['metric_score'][2], ']'
|
||||
}))
|
||||
for _, s in ipairs(common.symbols) do
|
||||
if not s.options then s.options = {} end
|
||||
table.insert(buf, table.concat({
|
||||
' ', s.name, ' (', s.score, ') [', table.concat(s.options, ','), ']',
|
||||
}))
|
||||
end
|
||||
add[settings.routines['x-spamd-result'].header] = table.concat(buf, '\n')
|
||||
end
|
||||
|
||||
routines['x-rspamd-queue-id'] = function()
|
||||
if common.queue_id ~= false then
|
||||
common.queue_id = task:get_queue_id()
|
||||
if not common.queue_id then
|
||||
common.queue_id = false
|
||||
end
|
||||
end
|
||||
if settings.routines['x-rspamd-queue-id'].remove then
|
||||
remove[settings.routines['x-rspamd-queue-id'].header] = settings.routines['x-rspamd-queue-id'].remove
|
||||
end
|
||||
if common.queue_id then
|
||||
add[settings.routines['x-rspamd-queue-id'].header] = common.queue_id
|
||||
end
|
||||
end
|
||||
|
||||
routines['x-rspamd-server'] = function()
|
||||
if settings.routines['x-rspamd-server'].remove then
|
||||
remove[settings.routines['x-rspamd-server'].header] = settings.routines['x-rspamd-server'].remove
|
||||
end
|
||||
add[settings.routines['x-rspamd-server'].header] = HOSTNAME
|
||||
end
|
||||
|
||||
routines['x-spamd-bar'] = function()
|
||||
if not common['metric_score'] then
|
||||
common['metric_score'] = task:get_metric_score('default')
|
||||
end
|
||||
local score = common['metric_score'][1]
|
||||
local spambar
|
||||
if score <= -1 then
|
||||
spambar = string.rep(settings.routines['x-spamd-bar'].negative, score*-1)
|
||||
elseif score >= 1 then
|
||||
spambar = string.rep(settings.routines['x-spamd-bar'].positive, score)
|
||||
else
|
||||
spambar = settings.routines['x-spamd-bar'].neutral
|
||||
end
|
||||
if settings.routines['x-spamd-bar'].remove then
|
||||
remove[settings.routines['x-spamd-bar'].header] = settings.routines['x-spamd-bar'].remove
|
||||
end
|
||||
if spambar ~= '' then
|
||||
add[settings.routines['x-spamd-bar'].header] = spambar
|
||||
end
|
||||
end
|
||||
|
||||
routines['x-spam-level'] = function()
|
||||
if not common['metric_score'] then
|
||||
common['metric_score'] = task:get_metric_score('default')
|
||||
end
|
||||
local score = common['metric_score'][1]
|
||||
if score < 1 then
|
||||
return nil, {}, {}
|
||||
end
|
||||
if settings.routines['x-spam-level'].remove then
|
||||
remove[settings.routines['x-spam-level'].header] = settings.routines['x-spam-level'].remove
|
||||
end
|
||||
add[settings.routines['x-spam-level'].header] = string.rep(settings.routines['x-spam-level'].char, score)
|
||||
end
|
||||
|
||||
routines['spam-header'] = function()
|
||||
if not common['metric_action'] then
|
||||
common['metric_action'] = task:get_metric_action('default')
|
||||
end
|
||||
if settings.routines['spam-header'].remove then
|
||||
remove[settings.routines['spam-header'].header] = settings.routines['spam-header'].remove
|
||||
end
|
||||
local action = common['metric_action']
|
||||
if action ~= 'no action' and action ~= 'greylist' then
|
||||
add[settings.routines['spam-header'].header] = settings.routines['spam-header'].value
|
||||
end
|
||||
end
|
||||
|
||||
routines['x-virus'] = function()
|
||||
if not common.symbols then
|
||||
common.symbols = {}
|
||||
end
|
||||
if settings.routines['x-virus'].remove then
|
||||
remove[settings.routines['x-virus'].header] = settings.routines['x-virus'].remove
|
||||
end
|
||||
local virii = {}
|
||||
for _, sym in ipairs(settings.routines['x-virus'].symbols) do
|
||||
if not (common.symbols[sym] == false) then
|
||||
local s = task:get_symbol(sym)
|
||||
if not s then
|
||||
common.symbols[sym] = false
|
||||
else
|
||||
common.symbols[sym] = s
|
||||
if (((s or E)[1] or E).options or E)[1] then
|
||||
table.insert(virii, s[1].options[1])
|
||||
else
|
||||
table.insert(virii, 'unknown')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if #virii > 0 then
|
||||
add[settings.routines['x-virus'].header] = table.concat(virii, ',')
|
||||
end
|
||||
end
|
||||
|
||||
routines['x-spam-status'] = function()
|
||||
if not common['metric_score'] then
|
||||
common['metric_score'] = task:get_metric_score('default')
|
||||
end
|
||||
if not common['metric_action'] then
|
||||
common['metric_action'] = task:get_metric_action('default')
|
||||
end
|
||||
local score = common['metric_score'][1]
|
||||
local action = common['metric_action']
|
||||
local is_spam
|
||||
local spamstatus
|
||||
if action ~= 'no action' and action ~= 'greylist' then
|
||||
is_spam = 'Yes'
|
||||
else
|
||||
is_spam = 'No'
|
||||
end
|
||||
spamstatus = is_spam .. ', score=' .. string.format('%.2f', score)
|
||||
if settings.routines['x-spam-status'].remove then
|
||||
remove[settings.routines['x-spam-status'].header] = settings.routines['x-spam-status'].remove
|
||||
end
|
||||
add[settings.routines['x-spam-status'].header] = spamstatus
|
||||
end
|
||||
|
||||
routines['authentication-results'] = function()
|
||||
local ar = require "auth_results"
|
||||
|
||||
if settings.routines['authentication-results'].remove then
|
||||
remove[settings.routines['authentication-results'].header] =
|
||||
settings.routines['authentication-results'].remove
|
||||
end
|
||||
|
||||
local res = ar.gen_auth_results(task,
|
||||
settings.routines['authentication-results'])
|
||||
|
||||
if res then
|
||||
add[settings.routines['authentication-results'].header] = res
|
||||
end
|
||||
end
|
||||
|
||||
for _, n in ipairs(active_routines) do
|
||||
local ok, err
|
||||
if custom_routines[n] then
|
||||
local to_add, to_remove, common_in
|
||||
ok, err, to_add, to_remove, common_in = pcall(custom_routines[n], task, common)
|
||||
if ok then
|
||||
for k, v in pairs(to_add) do
|
||||
add[k] = v
|
||||
end
|
||||
for k, v in pairs(to_remove) do
|
||||
add[k] = v
|
||||
end
|
||||
for k, v in pairs(common_in) do
|
||||
if type(v) == 'table' then
|
||||
if not common[k] then
|
||||
common[k] = {}
|
||||
end
|
||||
for kk, vv in pairs(v) do
|
||||
common[k][kk] = vv
|
||||
end
|
||||
else
|
||||
common[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
ok, err = pcall(routines[n])
|
||||
end
|
||||
if not ok then
|
||||
logger.errx(task, 'call to %s failed: %s', n, err)
|
||||
end
|
||||
end
|
||||
|
||||
if not next(add) then add = nil end
|
||||
if not next(remove) then remove = nil end
|
||||
if add or remove then
|
||||
task:set_milter_reply({
|
||||
add_headers = add,
|
||||
remove_headers = remove
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local opts = rspamd_config:get_all_opt(N) or rspamd_config:get_all_opt('rmilter_headers')
|
||||
if not opts then return end
|
||||
|
||||
if type(opts['use']) == 'string' then
|
||||
opts['use'] = {opts['use']}
|
||||
elseif (type(opts['use']) == 'table' and not opts['use'][1]) then
|
||||
logger.debugm(N, rspamd_config, 'no functions are enabled')
|
||||
return
|
||||
end
|
||||
if type(opts['use']) ~= 'table' then
|
||||
logger.errx(rspamd_config, 'unexpected type for "use" option: %s', type(opts['use']))
|
||||
return
|
||||
end
|
||||
if type(opts['custom']) == 'table' then
|
||||
for k, v in pairs(opts['custom']) do
|
||||
local f, err = load(v)
|
||||
if not f then
|
||||
logger.errx(rspamd_config, 'could not load "%s": %s', k, err)
|
||||
else
|
||||
custom_routines[k] = f()
|
||||
end
|
||||
end
|
||||
end
|
||||
local have_routine = {}
|
||||
local function activate_routine(s)
|
||||
if settings.routines[s] or custom_routines[s] then
|
||||
have_routine[s] = true
|
||||
table.insert(active_routines, s)
|
||||
if (opts.routines and opts.routines[s]) then
|
||||
for k, v in pairs(opts.routines[s]) do
|
||||
settings.routines[s][k] = v
|
||||
end
|
||||
end
|
||||
else
|
||||
logger.errx(rspamd_config, 'routine "%s" does not exist', s)
|
||||
end
|
||||
end
|
||||
if opts['extended_spam_headers'] then
|
||||
activate_routine('x-spamd-result')
|
||||
activate_routine('x-rspamd-server')
|
||||
activate_routine('x-rspamd-queue-id')
|
||||
end
|
||||
if opts['skip_local'] then
|
||||
settings.skip_local = true
|
||||
end
|
||||
if opts['skip_authenticated'] then
|
||||
settings.skip_authenticated = true
|
||||
end
|
||||
for _, s in ipairs(opts['use']) do
|
||||
if not have_routine[s] then
|
||||
activate_routine(s)
|
||||
end
|
||||
end
|
||||
if (#active_routines < 1) then
|
||||
logger.errx(rspamd_config, 'no active routines')
|
||||
return
|
||||
end
|
||||
logger.infox(rspamd_config, 'active routines [%s]', table.concat(active_routines, ','))
|
||||
rspamd_config:register_symbol({
|
||||
name = 'MILTER_HEADERS',
|
||||
type = 'postfilter',
|
||||
callback = milter_headers,
|
||||
priority = 10
|
||||
})
|
|
@ -1,41 +0,0 @@
|
|||
bind_socket = inet:9900;
|
||||
spamd {
|
||||
servers = r:rspamd:11333;
|
||||
connect_timeout = 1s;
|
||||
results_timeout = 20s;
|
||||
error_time = 10;
|
||||
dead_time = 300;
|
||||
maxerrors = 10;
|
||||
reject_message = "Spam or virus message rejected due to high detection score";
|
||||
whitelist = 127.0.0.1/32, [::1]/128;
|
||||
spamd_soft_fail = yes;
|
||||
rspamd_metric = "default";
|
||||
extended_spam_headers = yes;
|
||||
spam_header = "X-Spam-Flag";
|
||||
spam_header_value = "YES";
|
||||
};
|
||||
redis {
|
||||
servers_grey = redis:6379;
|
||||
servers_limits = redis:6379;
|
||||
servers_id = redis:6379;
|
||||
id_prefix = "message_id.";
|
||||
grey_prefix = "grey.";
|
||||
white_prefix = "white.";
|
||||
connect_timeout = 1s;
|
||||
error_time = 10;
|
||||
dead_time = 300;
|
||||
maxerrors = 10;
|
||||
};
|
||||
tempdir = /tmp;
|
||||
tempfiles_mode = 00600;
|
||||
strict_auth = yes;
|
||||
use_dcc = no;
|
||||
limits {
|
||||
enable = false;
|
||||
};
|
||||
greylisting {
|
||||
enable = false;
|
||||
}
|
||||
dkim {
|
||||
enable = false;
|
||||
};
|
Loading…
Reference in New Issue