diff options
Diffstat (limited to 'src/plugins/lua/once_received.lua')
-rw-r--r-- | src/plugins/lua/once_received.lua | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/src/plugins/lua/once_received.lua b/src/plugins/lua/once_received.lua new file mode 100644 index 0000000..2a5552a --- /dev/null +++ b/src/plugins/lua/once_received.lua @@ -0,0 +1,230 @@ +--[[ +Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.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. +]]-- + +if confighelp then + return +end + +-- 0 or 1 received: = spam + +local symbol = 'ONCE_RECEIVED' +local symbol_rdns = 'RDNS_NONE' +local symbol_rdns_dnsfail = 'RDNS_DNSFAIL' +local symbol_mx = 'DIRECT_TO_MX' +-- Symbol for strict checks +local symbol_strict = nil +local bad_hosts = {} +local good_hosts = {} +local whitelist = nil + +local rspamd_logger = require "rspamd_logger" +local lua_util = require "lua_util" +local fun = require "fun" +local N = 'once_received' + +local check_local = false +local check_authed = false + +local function check_quantity_received (task) + local recvh = task:get_received_headers() + + local nreceived = fun.reduce(function(acc, _) + return acc + 1 + end, 0, fun.filter(function(h) + return not h['flags']['artificial'] + end, recvh)) + + local function recv_dns_cb(_, to_resolve, results, err) + if err and (err ~= 'requested record is not found' and err ~= 'no records with this name') then + rspamd_logger.errx(task, 'error looking up %s: %s', to_resolve, err) + task:insert_result(symbol_rdns_dnsfail, 1.0) + end + + if not results then + if nreceived <= 1 then + task:insert_result(symbol, 1) + -- Avoid strict symbol inserting as the remaining symbols have already + -- quote a significant weight, so a message could be rejected by just + -- this property. + --task:insert_result(symbol_strict, 1) + -- Check for MUAs + local ua = task:get_header('User-Agent') + local xm = task:get_header('X-Mailer') + if (ua or xm) then + task:insert_result(symbol_mx, 1, (ua or xm)) + end + end + task:insert_result(symbol_rdns, 1) + else + rspamd_logger.infox(task, 'source hostname has not been passed to Rspamd from MTA, ' .. + 'but we could resolve source IP address PTR %s as "%s"', + to_resolve, results[1]) + task:set_hostname(results[1]) + + if good_hosts then + for _, gh in ipairs(good_hosts) do + if string.find(results[1], gh) then + return + end + end + end + + if nreceived <= 1 then + task:insert_result(symbol, 1) + for _, h in ipairs(bad_hosts) do + if string.find(results[1], h) then + + task:insert_result(symbol_strict, 1, h) + return + end + end + end + end + end + + local task_ip = task:get_ip() + + if ((not check_authed and task:get_user()) or + (not check_local and task_ip and task_ip:is_local())) then + rspamd_logger.infox(task, 'Skipping once_received for authenticated user or local network') + return + end + if whitelist and task_ip and whitelist:get_key(task_ip) then + rspamd_logger.infox(task, 'whitelisted mail from %s', + task_ip:to_string()) + return + end + + local hn = task:get_hostname() + -- Here we don't care about received + if (not hn) and task_ip and task_ip:is_valid() then + task:get_resolver():resolve_ptr({ task = task, + name = task_ip:to_string(), + callback = recv_dns_cb, + forced = true + }) + return + end + + if nreceived <= 1 then + local ret = true + local r = recvh[1] + + if not r then + return + end + + if r['real_hostname'] then + local rhn = string.lower(r['real_hostname']) + -- Check for good hostname + if rhn and good_hosts then + for _, gh in ipairs(good_hosts) do + if string.find(rhn, gh) then + ret = false + break + end + end + end + end + + if ret then + -- Strict checks + if symbol_strict then + -- Unresolved host + task:insert_result(symbol, 1) + + if not hn then + return + end + for _, h in ipairs(bad_hosts) do + if string.find(hn, h) then + task:insert_result(symbol_strict, 1, h) + return + end + end + else + task:insert_result(symbol, 1) + end + end + end +end + +local auth_and_local_conf = lua_util.config_check_local_or_authed(rspamd_config, N, + false, false) +check_local = auth_and_local_conf[1] +check_authed = auth_and_local_conf[2] + +-- Configuration +local opts = rspamd_config:get_all_opt(N) +if opts then + if opts['symbol'] then + symbol = opts['symbol'] + + local id = rspamd_config:register_symbol({ + name = symbol, + callback = check_quantity_received, + }) + + for n, v in pairs(opts) do + if n == 'symbol_strict' then + symbol_strict = v + elseif n == 'symbol_rdns' then + symbol_rdns = v + elseif n == 'symbol_rdns_dnsfail' then + symbol_rdns_dnsfail = v + elseif n == 'bad_host' then + if type(v) == 'string' then + bad_hosts[1] = v + else + bad_hosts = v + end + elseif n == 'good_host' then + if type(v) == 'string' then + good_hosts[1] = v + else + good_hosts = v + end + elseif n == 'whitelist' then + local lua_maps = require "lua_maps" + whitelist = lua_maps.map_add('once_received', 'whitelist', 'radix', + 'once received whitelist') + elseif n == 'symbol_mx' then + symbol_mx = v + end + end + + rspamd_config:register_symbol({ + name = symbol_rdns, + type = 'virtual', + parent = id + }) + rspamd_config:register_symbol({ + name = symbol_rdns_dnsfail, + type = 'virtual', + parent = id + }) + rspamd_config:register_symbol({ + name = symbol_strict, + type = 'virtual', + parent = id + }) + rspamd_config:register_symbol({ + name = symbol_mx, + type = 'virtual', + parent = id + }) + end +end |