From: Vsevolod Stakhov Date: Fri, 7 Aug 2015 14:34:49 +0000 (+0100) Subject: Move hfilter to plugins. X-Git-Tag: 1.0.0~220^2~3 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=37e90507eb07fd3c9399f2a7a9b985283cb62a2f;p=rspamd.git Move hfilter to plugins. --- diff --git a/rules/hfilter.lua b/rules/hfilter.lua deleted file mode 100644 index 8a5ba9b4e..000000000 --- a/rules/hfilter.lua +++ /dev/null @@ -1,466 +0,0 @@ ---[[ -Copyright (c) 2011-2015, Vsevolod Stakhov -Copyright (c) 2013-2015, Alexey Savelyev -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -]]-- - - --- Weight for checks_hellohost and checks_hello: 5 - very hard, 4 - hard, 3 - meduim, 2 - low, 1 - very low. --- From HFILTER_HELO_* and HFILTER_HOSTNAME_* symbols the maximum weight is selected in case of their actuating. - - ---local dumper = require 'pl.pretty'.dump -local rspamd_regexp = require "rspamd_regexp" - -local checks_hellohost = { - ['[.-]gprs[.-]'] = 5, ['gprs[.-][0-9]'] = 5, ['[0-9][.-]?gprs'] = 5, - ['[.-]cdma[.-]'] = 5, ['cdma[.-][0-9]'] = 5, ['[0-9][.-]?cdma'] = 5, - ['[.-]homeuser[.-]'] = 5, ['homeuser[.-][0-9]'] = 5, ['[0-9][.-]?homeuser'] = 5, - ['[.-]dhcp[.-]'] = 5, ['dhcp[.-][0-9]'] = 5, ['[0-9][.-]?dhcp'] = 5, - ['[.-]catv[.-]'] = 5, ['catv[.-][0-9]'] = 5, ['[0-9][.-]?catv'] = 5, - ['[.-]wifi[.-]'] = 5, ['wifi[.-][0-9]'] = 5, ['[0-9][.-]?wifi'] = 5, - ['[.-]dial-?up[.-]'] = 5, ['dial-?up[.-][0-9]'] = 5, ['[0-9][.-]?dial-?up'] = 5, - ['[.-]dynamic[.-]'] = 5, ['dynamic[.-][0-9]'] = 5, ['[0-9][.-]?dynamic'] = 5, - ['[.-]dyn[.-]'] = 5, ['dyn[.-][0-9]'] = 5, ['[0-9][.-]?dyn'] = 5, - ['[.-]clients?[.-]'] = 5, ['clients?[.-][0-9]'] = 5, ['[0-9][.-]?clients?'] = 5, - ['[.-]dynip[.-]'] = 5, ['dynip[.-][0-9]'] = 5, ['[0-9][.-]?dynip'] = 5, - ['[.-]broadband[.-]'] = 5, ['broadband[.-][0-9]'] = 5, ['[0-9][.-]?broadband'] = 5, - ['[.-]broad[.-]'] = 5, ['broad[.-][0-9]'] = 5, ['[0-9][.-]?broad'] = 5, - ['[.-]bredband[.-]'] = 5, ['bredband[.-][0-9]'] = 5, ['[0-9][.-]?bredband'] = 5, - ['[.-]nat[.-]'] = 5, ['nat[.-][0-9]'] = 5, ['[0-9][.-]?nat'] = 5, - ['[.-]pptp[.-]'] = 5, ['pptp[.-][0-9]'] = 5, ['[0-9][.-]?pptp'] = 5, - ['[.-]pppoe[.-]'] = 5, ['pppoe[.-][0-9]'] = 5, ['[0-9][.-]?pppoe'] = 5, - ['[.-]ppp[.-]'] = 5, ['ppp[.-][0-9]'] = 5, ['[0-9][.-]?ppp'] = 5, - ['[.-]modem[.-]'] = 5, ['modem[.-][0-9]'] = 5, ['[0-9][.-]?modem'] = 5, - ['[.-]cablemodem[.-]'] = 5, ['cablemodem[.-][0-9]'] = 5, ['[0-9][.-]?cablemodem'] = 5, - ['[.-]comcast[.-]'] = 5, ['comcast[.-][0-9]'] = 5, ['[0-9][.-]?comcast'] = 5, - ['[.-][a|x]?dsl-dynamic[.-]'] = 5, ['[a|x]?dsl-dynamic[.-]?[0-9]'] = 5, ['[0-9][.-]?[a|x]?dsl-dynamic'] = 5, - ['[.-][a|x]?dsl[.-]'] = 4, ['[a|x]?dsl[.-]?[0-9]'] = 4, ['[0-9][.-]?[a|x]?dsl'] = 4, - ['[.-][a|x]?dsl-line[.-]'] = 4, ['[a|x]?dsl-line[.-]?[0-9]'] = 4, ['[0-9][.-]?[a|x]?dsl-line'] = 4, - ['[.-]in-?addr[.-]'] = 4, ['in-?addr[.-][0-9]'] = 4, ['[0-9][.-]?in-?addr'] = 4, - ['[.-]pool[.-]'] = 4, ['pool[.-][0-9]'] = 4, ['[0-9][.-]?pool'] = 4, - ['[.-]fibertel[.-]'] = 4, ['fibertel[.-][0-9]'] = 4, ['[0-9][.-]?fibertel'] = 4, - ['[.-]fbx[.-]'] = 4, ['fbx[.-][0-9]'] = 4, ['[0-9][.-]?fbx'] = 4, - ['[.-]unused-addr[.-]'] = 3, ['unused-addr[.-][0-9]'] = 3, ['[0-9][.-]?unused-addr'] = 3, - ['[.-]cable[.-]'] = 3, ['cable[.-][0-9]'] = 3, ['[0-9][.-]?cable'] = 3, - ['[.-]kabel[.-]'] = 3, ['kabel[.-][0-9]'] = 3, ['[0-9][.-]?kabel'] = 3, - ['[.-]host[.-]'] = 2, ['host[.-][0-9]'] = 2, ['[0-9][.-]?host'] = 2, - ['[.-]customers?[.-]'] = 1, ['customers?[.-][0-9]'] = 1, ['[0-9][.-]?customers?'] = 1, - ['[.-]user[.-]'] = 1, ['user[.-][0-9]'] = 1, ['[0-9][.-]?user'] = 1, - ['[.-]peer[.-]'] = 1, ['peer[.-][0-9]'] = 1, ['[0-9][.-]?peer'] = 1 -} - -local checks_hello = { - ['^[^\\.]+$'] = 5, -- for helo=COMPUTER, ANNA, etc... Without dot in helo - ['localhost$'] = 5, - ['^(dsl)?(device|speedtouch)\\.lan$'] = 5, - ['\\.(lan|local|home|localdomain|intra|in-addr.arpa|priv|online|user|veloxzon)$'] = 5 -} - -local checks_hello_badip = { - ['^0\\.'] = 5, ['^::1$'] = 5, --loopback ipv4, ipv6 - ['^127\\.'] = 5, ['^10\\.'] = 5, ['^192\\.168\\.'] = 5, --local ipv4 - ['^172\\.1[6-9]\\.'] = 5, ['^172\\.2[0-9]\\.'] = 5, ['^172\\.3[01]\\.'] = 5, --local ipv4 - ['^169\\.254\\.'] = 5, --chanel ipv4 - ['^192\\.0\\.0\\.'] = 5, --IETF Protocol - ['^192\\.88\\.99\\.'] = 5, --RFC3068 - ['^100.6[4-9]\\.'] = 5, ['^100.[7-9]\\d\\.'] = 5, ['^100.1[01]\\d\\.'] = 5, ['^100.12[0-7]\\d\\.'] = 5, --RFC6598 - ['^\\d\\.\\d\\.\\d\\.255$'] = 5, --multicast ipv4 - ['^192\\.0\\.2\\.'] = 5, ['^198\\.51\\.100\\.'] = 5, ['^203\\.0\\.113\\.'] = 5, --sample - ['^fe[89ab][0-9a-f]::'] = 5, ['^fe[cdf][0-9a-f]:'] = 5, --local ipv6 (fe80:: - febf::, fec0:: - feff::) - ['^2001:db8::'] = 5, --reserved RFC 3849 for ipv6 - ['^fc00::'] = 5, ['^ffxx::'] = 5 --unicast, multicast ipv6 -} - -local checks_hello_bareip = { - '^\\d+[x.-]\\d+[x.-]\\d+[x.-]\\d+$', --bareip ipv4, - '^[0-9a-f]+:' --bareip ipv6 -} - -local config = { - ['helo_enabled'] = false, - ['hostname_enabled'] = false, - ['from_enabled'] = false, - ['rcpt_enabled'] = false, - ['mid_enabled'] = false, - ['url_enabled'] = false -} - -local function check_regexp(str, regexp_text) - local re = rspamd_regexp.create_cached(regexp_text, 'i') - if re:match(str) then return true end - return false -end - -local function split(str, delim, maxNb) - -- Eliminate bad cases... - if string.find(str, delim) == nil then - return { str } - end - if maxNb == nil or maxNb < 1 then - maxNb = 0 -- No limit - end - local result = {} - local pat = "(.-)" .. delim .. "()" - local nb = 0 - local lastPos - for part, pos in string.gmatch(str, pat) do - nb = nb + 1 - result[nb] = part - lastPos = pos - if nb == maxNb then break end - end - -- Handle the last field - if nb ~= maxNb then - result[nb + 1] = string.sub(str, lastPos) - end - return result -end - -local function check_fqdn(domain) - if check_regexp(domain, '(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{1,63}(? 0 then - local urls = task:get_urls() - if urls then - local total_url_len = 0 - for _,url in ipairs(urls) do - total_url_len = total_url_len + url:get_length() - end - if total_url_len > 0 then - if total_url_len + 7 > total_parts_len then - task:insert_result('HFILTER_URL_ONLY', 1.00) - elseif text_parts_count == 1 and selected_text_part and selected_text_part:get_length() < 1024 then - if selected_text_part:get_lines_count() < 2 then - task:insert_result('HFILTER_URL_ONELINE', 1.00) - end - end - end - end - end - end - end - - --No more checks for auth user - if task:get_user() ~= nil then - return false - end - - --local message = task:get_message() - local ip = false - local rip = task:get_from_ip() - if rip and rip:is_valid() then - ip = rip:to_string() - end - - -- Check's HELO - local weight_helo = 0 - if config['helo_enabled'] then - local helo = task:get_helo() - if helo then - helo = string.gsub(helo, '[%[%]]', '') - -- Regexp check HELO (checks_hello_badip) - local find_badip = false - for regexp,weight in pairs(checks_hello_badip) do - if check_regexp(helo, regexp) then - task:insert_result('HFILTER_HELO_BADIP', 1.0) - find_badip = true - break - end - end - - -- Regexp check HELO (checks_hello_bareip) - local find_bareip = false - if not find_badip then - for _,regexp in pairs(checks_hello_bareip) do - if check_regexp(helo, regexp) then - task:insert_result('HFILTER_HELO_BAREIP', 1.0) - find_bareip = true - break - end - end - end - - if not find_badip and not find_bareip then - -- Regexp check HELO (checks_hello) - for regexp,weight in pairs(checks_hello) do - if check_regexp(helo, regexp) then - weight_helo = weight - break - end - end - -- Regexp check HELO (checks_hellohost) - for regexp,weight in pairs(checks_hellohost) do - if check_regexp(helo, regexp) then - if weight > weight_helo then - weight_helo = weight - end - break - end - end - --FQDN check HELO - if ip and helo and weight_helo == 0 then - check_host(task, helo, 'HELO', ip, hostname) - end - end - else - task:insert_result('HFILTER_HELO_UNKNOWN', 1.0) - end - end - - -- Check's HOSTNAME - local weight_hostname = 0 - if config['hostname_enabled'] then - local hostname = task:get_hostname() - if hostname then - -- Check regexp HOSTNAME - if hostname == 'unknown' then - task:insert_result('HFILTER_HOSTNAME_UNKNOWN', 1.00) - else - for regexp,weight in pairs(checks_hellohost) do - if check_regexp(hostname, regexp) then - weight_hostname = weight - break - end - end - end - else - task:insert_result('HFILTER_HOSTNAME_UNKNOWN', 1.00) - end - end - - --Insert weight's for HELO or HOSTNAME - if weight_helo > 0 and weight_helo >= weight_hostname then - task:insert_result('HFILTER_HELO_' .. weight_helo, 1.0) - elseif weight_hostname > 0 and weight_hostname > weight_helo then - task:insert_result('HFILTER_HOSTNAME_' .. weight_hostname, 1.0) - end - - -- MAILFROM checks -- - local frombounce = false - if config['from_enabled'] then - local from = task:get_from(1) - if from then - --FROM host check - for _,fr in ipairs(from) do - local fr_split = split(fr['addr'], '@', 0) - if table.maxn(fr_split) == 2 then - check_host(task, fr_split[2], 'FROMHOST', '', '') - if fr_split[1] == 'postmaster' then - frombounce = true - end - end - end - else - task:insert_result('HFILTER_FROM_BOUNCE', 1.00) - frombounce = true - end - end - - -- Recipients checks -- - if config['rcpt_enabled'] then - local rcpt = task:get_recipients() - if rcpt then - local count_rcpt = table.maxn(rcpt) - if frombounce then - if count_rcpt > 1 then - task:insert_result('HFILTER_RCPT_BOUNCEMOREONE', 1.00) - end - end - end - end - - --Message ID host check - if config['mid_enabled'] then - local message_id = task:get_message_id() - if message_id then - local mid_split = split(message_id, '@', 0) - if table.maxn(mid_split) == 2 and not string.find(mid_split[2], 'local') then - check_host(task, mid_split[2], 'MID', '', '') - end - end - end - - return false -end - -local symbols_enabled = {} - -local symbols_helo = { - "HFILTER_HELO_BAREIP", - "HFILTER_HELO_BADIP", - "HFILTER_HELO_UNKNOWN", - "HFILTER_HELO_1", - "HFILTER_HELO_2", - "HFILTER_HELO_3", - "HFILTER_HELO_4", - "HFILTER_HELO_5", - "HFILTER_HELO_NORESOLVE_MX", - "HFILTER_HELO_NORES_A_OR_MX", - "HFILTER_HELO_IP_A", - "HFILTER_HELO_NOT_FQDN" -} -local symbols_hostname = { - "HFILTER_HOSTNAME_1", - "HFILTER_HOSTNAME_2", - "HFILTER_HOSTNAME_3", - "HFILTER_HOSTNAME_4", - "HFILTER_HOSTNAME_5", - "HFILTER_HOSTNAME_UNKNOWN" -} -local symbols_rcpt = { - "HFILTER_RCPT_BOUNCEMOREONE" -} -local symbols_mid = { - "HFILTER_MID_NORESOLVE_MX", - "HFILTER_MID_NORES_A_OR_MX", - "HFILTER_MID_NOT_FQDN" -} -local symbols_url = { - "HFILTER_URL_ONLY", - "HFILTER_URL_ONELINE" -} -local symbols_from = { - "HFILTER_FROMHOST_NORESOLVE_MX", - "HFILTER_FROMHOST_NORES_A_OR_MX", - "HFILTER_FROMHOST_NOT_FQDN", - "HFILTER_FROM_BOUNCE" -} - -local opts = rspamd_config:get_all_opt('hfilter') -if opts then - for k,v in pairs(opts) do - config[k] = v - end -end - -local function append_t(t, a) - for _,v in ipairs(a) do table.insert(t, v) end -end -if config['helo_enabled'] then - append_t(symbols_enabled, symbols_helo) -end -if config['hostname_enabled'] then - append_t(symbols_enabled, symbols_hostname) -end -if config['from_enabled'] then - append_t(symbols_enabled, symbols_from) -end -if config['rcpt_enabled'] then - append_t(symbols_enabled, symbols_rcpt) -end -if config['mid_enabled'] then - append_t(symbols_enabled, symbols_mid) -end -if config['url_enabled'] then - append_t(symbols_enabled, symbols_url) -end - ---dumper(symbols_enabled) -if table.maxn(symbols_enabled) > 0 then - rspamd_config:register_symbols(hfilter, 1.0, "HFILTER", symbols_enabled); -end diff --git a/src/plugins/lua/hfilter.lua b/src/plugins/lua/hfilter.lua new file mode 100644 index 000000000..8a5ba9b4e --- /dev/null +++ b/src/plugins/lua/hfilter.lua @@ -0,0 +1,466 @@ +--[[ +Copyright (c) 2011-2015, Vsevolod Stakhov +Copyright (c) 2013-2015, Alexey Savelyev +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]]-- + + +-- Weight for checks_hellohost and checks_hello: 5 - very hard, 4 - hard, 3 - meduim, 2 - low, 1 - very low. +-- From HFILTER_HELO_* and HFILTER_HOSTNAME_* symbols the maximum weight is selected in case of their actuating. + + +--local dumper = require 'pl.pretty'.dump +local rspamd_regexp = require "rspamd_regexp" + +local checks_hellohost = { + ['[.-]gprs[.-]'] = 5, ['gprs[.-][0-9]'] = 5, ['[0-9][.-]?gprs'] = 5, + ['[.-]cdma[.-]'] = 5, ['cdma[.-][0-9]'] = 5, ['[0-9][.-]?cdma'] = 5, + ['[.-]homeuser[.-]'] = 5, ['homeuser[.-][0-9]'] = 5, ['[0-9][.-]?homeuser'] = 5, + ['[.-]dhcp[.-]'] = 5, ['dhcp[.-][0-9]'] = 5, ['[0-9][.-]?dhcp'] = 5, + ['[.-]catv[.-]'] = 5, ['catv[.-][0-9]'] = 5, ['[0-9][.-]?catv'] = 5, + ['[.-]wifi[.-]'] = 5, ['wifi[.-][0-9]'] = 5, ['[0-9][.-]?wifi'] = 5, + ['[.-]dial-?up[.-]'] = 5, ['dial-?up[.-][0-9]'] = 5, ['[0-9][.-]?dial-?up'] = 5, + ['[.-]dynamic[.-]'] = 5, ['dynamic[.-][0-9]'] = 5, ['[0-9][.-]?dynamic'] = 5, + ['[.-]dyn[.-]'] = 5, ['dyn[.-][0-9]'] = 5, ['[0-9][.-]?dyn'] = 5, + ['[.-]clients?[.-]'] = 5, ['clients?[.-][0-9]'] = 5, ['[0-9][.-]?clients?'] = 5, + ['[.-]dynip[.-]'] = 5, ['dynip[.-][0-9]'] = 5, ['[0-9][.-]?dynip'] = 5, + ['[.-]broadband[.-]'] = 5, ['broadband[.-][0-9]'] = 5, ['[0-9][.-]?broadband'] = 5, + ['[.-]broad[.-]'] = 5, ['broad[.-][0-9]'] = 5, ['[0-9][.-]?broad'] = 5, + ['[.-]bredband[.-]'] = 5, ['bredband[.-][0-9]'] = 5, ['[0-9][.-]?bredband'] = 5, + ['[.-]nat[.-]'] = 5, ['nat[.-][0-9]'] = 5, ['[0-9][.-]?nat'] = 5, + ['[.-]pptp[.-]'] = 5, ['pptp[.-][0-9]'] = 5, ['[0-9][.-]?pptp'] = 5, + ['[.-]pppoe[.-]'] = 5, ['pppoe[.-][0-9]'] = 5, ['[0-9][.-]?pppoe'] = 5, + ['[.-]ppp[.-]'] = 5, ['ppp[.-][0-9]'] = 5, ['[0-9][.-]?ppp'] = 5, + ['[.-]modem[.-]'] = 5, ['modem[.-][0-9]'] = 5, ['[0-9][.-]?modem'] = 5, + ['[.-]cablemodem[.-]'] = 5, ['cablemodem[.-][0-9]'] = 5, ['[0-9][.-]?cablemodem'] = 5, + ['[.-]comcast[.-]'] = 5, ['comcast[.-][0-9]'] = 5, ['[0-9][.-]?comcast'] = 5, + ['[.-][a|x]?dsl-dynamic[.-]'] = 5, ['[a|x]?dsl-dynamic[.-]?[0-9]'] = 5, ['[0-9][.-]?[a|x]?dsl-dynamic'] = 5, + ['[.-][a|x]?dsl[.-]'] = 4, ['[a|x]?dsl[.-]?[0-9]'] = 4, ['[0-9][.-]?[a|x]?dsl'] = 4, + ['[.-][a|x]?dsl-line[.-]'] = 4, ['[a|x]?dsl-line[.-]?[0-9]'] = 4, ['[0-9][.-]?[a|x]?dsl-line'] = 4, + ['[.-]in-?addr[.-]'] = 4, ['in-?addr[.-][0-9]'] = 4, ['[0-9][.-]?in-?addr'] = 4, + ['[.-]pool[.-]'] = 4, ['pool[.-][0-9]'] = 4, ['[0-9][.-]?pool'] = 4, + ['[.-]fibertel[.-]'] = 4, ['fibertel[.-][0-9]'] = 4, ['[0-9][.-]?fibertel'] = 4, + ['[.-]fbx[.-]'] = 4, ['fbx[.-][0-9]'] = 4, ['[0-9][.-]?fbx'] = 4, + ['[.-]unused-addr[.-]'] = 3, ['unused-addr[.-][0-9]'] = 3, ['[0-9][.-]?unused-addr'] = 3, + ['[.-]cable[.-]'] = 3, ['cable[.-][0-9]'] = 3, ['[0-9][.-]?cable'] = 3, + ['[.-]kabel[.-]'] = 3, ['kabel[.-][0-9]'] = 3, ['[0-9][.-]?kabel'] = 3, + ['[.-]host[.-]'] = 2, ['host[.-][0-9]'] = 2, ['[0-9][.-]?host'] = 2, + ['[.-]customers?[.-]'] = 1, ['customers?[.-][0-9]'] = 1, ['[0-9][.-]?customers?'] = 1, + ['[.-]user[.-]'] = 1, ['user[.-][0-9]'] = 1, ['[0-9][.-]?user'] = 1, + ['[.-]peer[.-]'] = 1, ['peer[.-][0-9]'] = 1, ['[0-9][.-]?peer'] = 1 +} + +local checks_hello = { + ['^[^\\.]+$'] = 5, -- for helo=COMPUTER, ANNA, etc... Without dot in helo + ['localhost$'] = 5, + ['^(dsl)?(device|speedtouch)\\.lan$'] = 5, + ['\\.(lan|local|home|localdomain|intra|in-addr.arpa|priv|online|user|veloxzon)$'] = 5 +} + +local checks_hello_badip = { + ['^0\\.'] = 5, ['^::1$'] = 5, --loopback ipv4, ipv6 + ['^127\\.'] = 5, ['^10\\.'] = 5, ['^192\\.168\\.'] = 5, --local ipv4 + ['^172\\.1[6-9]\\.'] = 5, ['^172\\.2[0-9]\\.'] = 5, ['^172\\.3[01]\\.'] = 5, --local ipv4 + ['^169\\.254\\.'] = 5, --chanel ipv4 + ['^192\\.0\\.0\\.'] = 5, --IETF Protocol + ['^192\\.88\\.99\\.'] = 5, --RFC3068 + ['^100.6[4-9]\\.'] = 5, ['^100.[7-9]\\d\\.'] = 5, ['^100.1[01]\\d\\.'] = 5, ['^100.12[0-7]\\d\\.'] = 5, --RFC6598 + ['^\\d\\.\\d\\.\\d\\.255$'] = 5, --multicast ipv4 + ['^192\\.0\\.2\\.'] = 5, ['^198\\.51\\.100\\.'] = 5, ['^203\\.0\\.113\\.'] = 5, --sample + ['^fe[89ab][0-9a-f]::'] = 5, ['^fe[cdf][0-9a-f]:'] = 5, --local ipv6 (fe80:: - febf::, fec0:: - feff::) + ['^2001:db8::'] = 5, --reserved RFC 3849 for ipv6 + ['^fc00::'] = 5, ['^ffxx::'] = 5 --unicast, multicast ipv6 +} + +local checks_hello_bareip = { + '^\\d+[x.-]\\d+[x.-]\\d+[x.-]\\d+$', --bareip ipv4, + '^[0-9a-f]+:' --bareip ipv6 +} + +local config = { + ['helo_enabled'] = false, + ['hostname_enabled'] = false, + ['from_enabled'] = false, + ['rcpt_enabled'] = false, + ['mid_enabled'] = false, + ['url_enabled'] = false +} + +local function check_regexp(str, regexp_text) + local re = rspamd_regexp.create_cached(regexp_text, 'i') + if re:match(str) then return true end + return false +end + +local function split(str, delim, maxNb) + -- Eliminate bad cases... + if string.find(str, delim) == nil then + return { str } + end + if maxNb == nil or maxNb < 1 then + maxNb = 0 -- No limit + end + local result = {} + local pat = "(.-)" .. delim .. "()" + local nb = 0 + local lastPos + for part, pos in string.gmatch(str, pat) do + nb = nb + 1 + result[nb] = part + lastPos = pos + if nb == maxNb then break end + end + -- Handle the last field + if nb ~= maxNb then + result[nb + 1] = string.sub(str, lastPos) + end + return result +end + +local function check_fqdn(domain) + if check_regexp(domain, '(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{1,63}(? 0 then + local urls = task:get_urls() + if urls then + local total_url_len = 0 + for _,url in ipairs(urls) do + total_url_len = total_url_len + url:get_length() + end + if total_url_len > 0 then + if total_url_len + 7 > total_parts_len then + task:insert_result('HFILTER_URL_ONLY', 1.00) + elseif text_parts_count == 1 and selected_text_part and selected_text_part:get_length() < 1024 then + if selected_text_part:get_lines_count() < 2 then + task:insert_result('HFILTER_URL_ONELINE', 1.00) + end + end + end + end + end + end + end + + --No more checks for auth user + if task:get_user() ~= nil then + return false + end + + --local message = task:get_message() + local ip = false + local rip = task:get_from_ip() + if rip and rip:is_valid() then + ip = rip:to_string() + end + + -- Check's HELO + local weight_helo = 0 + if config['helo_enabled'] then + local helo = task:get_helo() + if helo then + helo = string.gsub(helo, '[%[%]]', '') + -- Regexp check HELO (checks_hello_badip) + local find_badip = false + for regexp,weight in pairs(checks_hello_badip) do + if check_regexp(helo, regexp) then + task:insert_result('HFILTER_HELO_BADIP', 1.0) + find_badip = true + break + end + end + + -- Regexp check HELO (checks_hello_bareip) + local find_bareip = false + if not find_badip then + for _,regexp in pairs(checks_hello_bareip) do + if check_regexp(helo, regexp) then + task:insert_result('HFILTER_HELO_BAREIP', 1.0) + find_bareip = true + break + end + end + end + + if not find_badip and not find_bareip then + -- Regexp check HELO (checks_hello) + for regexp,weight in pairs(checks_hello) do + if check_regexp(helo, regexp) then + weight_helo = weight + break + end + end + -- Regexp check HELO (checks_hellohost) + for regexp,weight in pairs(checks_hellohost) do + if check_regexp(helo, regexp) then + if weight > weight_helo then + weight_helo = weight + end + break + end + end + --FQDN check HELO + if ip and helo and weight_helo == 0 then + check_host(task, helo, 'HELO', ip, hostname) + end + end + else + task:insert_result('HFILTER_HELO_UNKNOWN', 1.0) + end + end + + -- Check's HOSTNAME + local weight_hostname = 0 + if config['hostname_enabled'] then + local hostname = task:get_hostname() + if hostname then + -- Check regexp HOSTNAME + if hostname == 'unknown' then + task:insert_result('HFILTER_HOSTNAME_UNKNOWN', 1.00) + else + for regexp,weight in pairs(checks_hellohost) do + if check_regexp(hostname, regexp) then + weight_hostname = weight + break + end + end + end + else + task:insert_result('HFILTER_HOSTNAME_UNKNOWN', 1.00) + end + end + + --Insert weight's for HELO or HOSTNAME + if weight_helo > 0 and weight_helo >= weight_hostname then + task:insert_result('HFILTER_HELO_' .. weight_helo, 1.0) + elseif weight_hostname > 0 and weight_hostname > weight_helo then + task:insert_result('HFILTER_HOSTNAME_' .. weight_hostname, 1.0) + end + + -- MAILFROM checks -- + local frombounce = false + if config['from_enabled'] then + local from = task:get_from(1) + if from then + --FROM host check + for _,fr in ipairs(from) do + local fr_split = split(fr['addr'], '@', 0) + if table.maxn(fr_split) == 2 then + check_host(task, fr_split[2], 'FROMHOST', '', '') + if fr_split[1] == 'postmaster' then + frombounce = true + end + end + end + else + task:insert_result('HFILTER_FROM_BOUNCE', 1.00) + frombounce = true + end + end + + -- Recipients checks -- + if config['rcpt_enabled'] then + local rcpt = task:get_recipients() + if rcpt then + local count_rcpt = table.maxn(rcpt) + if frombounce then + if count_rcpt > 1 then + task:insert_result('HFILTER_RCPT_BOUNCEMOREONE', 1.00) + end + end + end + end + + --Message ID host check + if config['mid_enabled'] then + local message_id = task:get_message_id() + if message_id then + local mid_split = split(message_id, '@', 0) + if table.maxn(mid_split) == 2 and not string.find(mid_split[2], 'local') then + check_host(task, mid_split[2], 'MID', '', '') + end + end + end + + return false +end + +local symbols_enabled = {} + +local symbols_helo = { + "HFILTER_HELO_BAREIP", + "HFILTER_HELO_BADIP", + "HFILTER_HELO_UNKNOWN", + "HFILTER_HELO_1", + "HFILTER_HELO_2", + "HFILTER_HELO_3", + "HFILTER_HELO_4", + "HFILTER_HELO_5", + "HFILTER_HELO_NORESOLVE_MX", + "HFILTER_HELO_NORES_A_OR_MX", + "HFILTER_HELO_IP_A", + "HFILTER_HELO_NOT_FQDN" +} +local symbols_hostname = { + "HFILTER_HOSTNAME_1", + "HFILTER_HOSTNAME_2", + "HFILTER_HOSTNAME_3", + "HFILTER_HOSTNAME_4", + "HFILTER_HOSTNAME_5", + "HFILTER_HOSTNAME_UNKNOWN" +} +local symbols_rcpt = { + "HFILTER_RCPT_BOUNCEMOREONE" +} +local symbols_mid = { + "HFILTER_MID_NORESOLVE_MX", + "HFILTER_MID_NORES_A_OR_MX", + "HFILTER_MID_NOT_FQDN" +} +local symbols_url = { + "HFILTER_URL_ONLY", + "HFILTER_URL_ONELINE" +} +local symbols_from = { + "HFILTER_FROMHOST_NORESOLVE_MX", + "HFILTER_FROMHOST_NORES_A_OR_MX", + "HFILTER_FROMHOST_NOT_FQDN", + "HFILTER_FROM_BOUNCE" +} + +local opts = rspamd_config:get_all_opt('hfilter') +if opts then + for k,v in pairs(opts) do + config[k] = v + end +end + +local function append_t(t, a) + for _,v in ipairs(a) do table.insert(t, v) end +end +if config['helo_enabled'] then + append_t(symbols_enabled, symbols_helo) +end +if config['hostname_enabled'] then + append_t(symbols_enabled, symbols_hostname) +end +if config['from_enabled'] then + append_t(symbols_enabled, symbols_from) +end +if config['rcpt_enabled'] then + append_t(symbols_enabled, symbols_rcpt) +end +if config['mid_enabled'] then + append_t(symbols_enabled, symbols_mid) +end +if config['url_enabled'] then + append_t(symbols_enabled, symbols_url) +end + +--dumper(symbols_enabled) +if table.maxn(symbols_enabled) > 0 then + rspamd_config:register_symbols(hfilter, 1.0, "HFILTER", symbols_enabled); +end