diff --git a/conf/lua/hfilter.lua b/conf/lua/hfilter.lua index a07c2db19..dd1a21b10 100644 --- a/conf/lua/hfilter.lua +++ b/conf/lua/hfilter.lua @@ -9,45 +9,45 @@ local rspamd_regexp = require "rspamd_regexp" local checks_hellohost = { -['[.-]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, -['[.-][a|x]?dsl[.-]'] = 4, ['[a|x]?dsl[.-]?[0-9]'] = 4, ['[0-9][.-]?[a|x]?dsl'] = 4, -['[.-][a|x]?dsl-dynamic[.-]'] = 5, ['[a|x]?dsl-dynamic[.-]?[0-9]'] = 5, ['[0-9][.-]?[a|x]?dsl-dynamic'] = 5, -['[.-][a|x]?dsl-line[.-]'] = 4, ['[a|x]?dsl-line[.-]?[0-9]'] = 4, ['[0-9][.-]?[a|x]?dsl-line'] = 4, -['[.-]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, -['[.-]unused-addr[.-]'] = 3, ['unused-addr[.-][0-9]'] = 3, ['[0-9][.-]?unused-addr'] = 3, -['[.-]dial-?up[.-]'] = 5, ['dial-?up[.-][0-9]'] = 5, ['[0-9][.-]?dial-?up'] = 5, -['[.-]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, -['[.-]in-?addr[.-]'] = 4, ['in-?addr[.-][0-9]'] = 4, ['[0-9][.-]?in-?addr'] = 4, -['[.-]pool[.-]'] = 4, ['pool[.-][0-9]'] = 4, ['[0-9][.-]?pool'] = 4, -['[.-]cable[.-]'] = 3, ['cable[.-][0-9]'] = 3, ['[0-9][.-]?cable'] = 3, -['[.-]host[.-]'] = 2, ['host[.-][0-9]'] = 2, ['[0-9][.-]?host'] = 2, -['[.-]customers[.-]'] = 1, ['customers[.-][0-9]'] = 1, ['[0-9][.-]?customers'] = 1 + ['[.-]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, + ['[.-][a|x]?dsl[.-]'] = 4, ['[a|x]?dsl[.-]?[0-9]'] = 4, ['[0-9][.-]?[a|x]?dsl'] = 4, + ['[.-][a|x]?dsl-dynamic[.-]'] = 5, ['[a|x]?dsl-dynamic[.-]?[0-9]'] = 5, ['[0-9][.-]?[a|x]?dsl-dynamic'] = 5, + ['[.-][a|x]?dsl-line[.-]'] = 4, ['[a|x]?dsl-line[.-]?[0-9]'] = 4, ['[0-9][.-]?[a|x]?dsl-line'] = 4, + ['[.-]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, + ['[.-]unused-addr[.-]'] = 3, ['unused-addr[.-][0-9]'] = 3, ['[0-9][.-]?unused-addr'] = 3, + ['[.-]dial-?up[.-]'] = 5, ['dial-?up[.-][0-9]'] = 5, ['[0-9][.-]?dial-?up'] = 5, + ['[.-]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, + ['[.-]in-?addr[.-]'] = 4, ['in-?addr[.-][0-9]'] = 4, ['[0-9][.-]?in-?addr'] = 4, + ['[.-]pool[.-]'] = 4, ['pool[.-][0-9]'] = 4, ['[0-9][.-]?pool'] = 4, + ['[.-]cable[.-]'] = 3, ['cable[.-][0-9]'] = 3, ['[0-9][.-]?cable'] = 3, + ['[.-]host[.-]'] = 2, ['host[.-][0-9]'] = 2, ['[0-9][.-]?host'] = 2, + ['[.-]customers[.-]'] = 1, ['customers[.-][0-9]'] = 1, ['[0-9][.-]?customers'] = 1 } local checks_hello = { -['localhost$'] = 5, -['^(dsl)?(device|speedtouch)\\.lan$'] = 5, -['\\.(lan|local|home|localdomain|intra|in-addr.arpa|priv|online|user|veloxzon)$'] = 5, -['^\\[*0\\.'] = 5, ['^\\[*::1\\]*'] = 5, --loopback ipv4, ipv6 -['^\\[*127\\.'] = 5, ['^\\[*10\\.'] = 5, ['^\\[*172\\.16\\.'] = 5, ['^\\[*192\\.168\\.'] = 5, --local ipv4 -['^\\[*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 -['^\\[*\\d+[x.-]\\d+[x.-]\\d+[x.-]\\d+\\]*$'] = 4, ['^\\[*\\d+:'] = 4 --bareip ipv4, ipv6 + ['localhost$'] = 5, + ['^(dsl)?(device|speedtouch)\\.lan$'] = 5, + ['\\.(lan|local|home|localdomain|intra|in-addr.arpa|priv|online|user|veloxzon)$'] = 5, + ['^\\[*0\\.'] = 5, ['^\\[*::1\\]*'] = 5, --loopback ipv4, ipv6 + ['^\\[*127\\.'] = 5, ['^\\[*10\\.'] = 5, ['^\\[*172\\.16\\.'] = 5, ['^\\[*192\\.168\\.'] = 5, --local ipv4 + ['^\\[*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 + ['^\\[*\\d+[x.-]\\d+[x.-]\\d+[x.-]\\d+\\]*$'] = 4, ['^\\[*\\d+:'] = 4 --bareip ipv4, ipv6 } local function trim1(s) @@ -55,41 +55,41 @@ local function trim1(s) end 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 + 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 + -- 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,255}$)(^((?!-)[a-zA-Z0-9-]{1,63}(? weight_helo then - weight_helo = weight - end - break - end - end - - --FQDN check HELO - if ip and helo then - check_host(task, helo, 'HELO', ip, hostname) - end - end - - -- Check's HOSTNAME - local weight_hostname = 0 - 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 - 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 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', '', '') - end - end - end - - --Message ID host check - 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 - - -- Links checks - local parts = task:get_text_parts() - if parts then - --One text part-- - local total_parts_len = 0 - local text_parts_count = 0 - local selected_text_part = nil - for _,p in ipairs(parts) do - total_parts_len = total_parts_len + p:get_length() - - if not p:is_html() then - text_parts_count = text_parts_count + 1 - selected_text_part = p - end - end - if total_parts_len > 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 - -- We got a single text part with the total length < 1024 symbols. - local part_text = selected_text_part:get_content() - if part_text and not string.find(trim1(part_text), "\n") then - task:insert_result('HFILTER_URL_ONELINE', 1.00) - end - end - end - end - end - end - + if table.maxn(recvh) == 0 then return false + end + + --IP-- + local ip = false + local rip = task:get_from_ip() + if rip and rip:is_valid() then + ip = rip:to_string() + end + + --HOSTNAME-- + local hostname = task:get_hostname() + + --HELO-- + local helo = task:get_helo() + + --RULES--RULES--RULES-- + + -- Check's HELO + local weight_helo = 0 + if helo 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 then + check_host(task, helo, 'HELO', ip, hostname) + end + end + + -- Check's HOSTNAME + local weight_hostname = 0 + 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 + 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 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', '', '') + end + end + end + + --Message ID host check + 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 + + -- Links checks + local parts = task:get_text_parts() + if parts then + --One text part-- + local total_parts_len = 0 + local text_parts_count = 0 + local selected_text_part = nil + for _,p in ipairs(parts) do + total_parts_len = total_parts_len + p:get_length() + + if not p:is_html() then + text_parts_count = text_parts_count + 1 + selected_text_part = p + end + end + if total_parts_len > 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 + -- We got a single text part with the total length < 1024 symbols. + local part_text = selected_text_part:get_content() + if part_text and not string.find(trim1(part_text), "\n") then + task:insert_result('HFILTER_URL_ONELINE', 1.00) + end + end + end + end + end + end + + return false end rspamd_config:register_symbols(hfilter, 1.0, -"HFILTER_HELO_1", "HFILTER_HELO_2", "HFILTER_HELO_3", "HFILTER_HELO_4", "HFILTER_HELO_5", -"HFILTER_HOSTNAME_1", "HFILTER_HOSTNAME_2", "HFILTER_HOSTNAME_3", "HFILTER_HOSTNAME_4", "HFILTER_HOSTNAME_5", -"HFILTER_HELO_NORESOLVE_MX", "HFILTER_HELO_NORES_A_OR_MX", "HFILTER_HELO_IP_A", "HFILTER_HELO_NOT_FQDN", -"HFILTER_FROMHOST_NORESOLVE_MX", "HFILTER_FROMHOST_NORES_A_OR_MX", "HFILTER_FROMHOST_NOT_FQDN", -"HFILTER_MID_NORESOLVE_MX", "HFILTER_MID_NORES_A_OR_MX", "HFILTER_MID_NOT_FQDN", -"HFILTER_MID_NOT_FQDN", -"HFILTER_HOSTNAME_UNKNOWN", -"HFILTER_URL_ONLY", "HFILTER_URL_ONELINE"); + "HFILTER_HELO_1", "HFILTER_HELO_2", "HFILTER_HELO_3", "HFILTER_HELO_4", "HFILTER_HELO_5", + "HFILTER_HOSTNAME_1", "HFILTER_HOSTNAME_2", "HFILTER_HOSTNAME_3", "HFILTER_HOSTNAME_4", "HFILTER_HOSTNAME_5", + "HFILTER_HELO_NORESOLVE_MX", "HFILTER_HELO_NORES_A_OR_MX", "HFILTER_HELO_IP_A", "HFILTER_HELO_NOT_FQDN", + "HFILTER_FROMHOST_NORESOLVE_MX", "HFILTER_FROMHOST_NORES_A_OR_MX", "HFILTER_FROMHOST_NOT_FQDN", + "HFILTER_MID_NORESOLVE_MX", "HFILTER_MID_NORES_A_OR_MX", "HFILTER_MID_NOT_FQDN", + "HFILTER_MID_NOT_FQDN", + "HFILTER_HOSTNAME_UNKNOWN", + "HFILTER_URL_ONLY", "HFILTER_URL_ONELINE");