mirror of https://github.com/rspamd/rspamd.git
Rapid spam filtering system
https://rspamd.com/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
259 lines
6.3 KiB
259 lines
6.3 KiB
--[[
|
|
Copyright (c) 2017, 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 exports = {}
|
|
local lpeg = require 'lpeg'
|
|
|
|
local split_grammar = {}
|
|
local function rspamd_str_split(s, sep)
|
|
local gr = split_grammar[sep]
|
|
|
|
if not gr then
|
|
local _sep = lpeg.P(sep)
|
|
local elem = lpeg.C((1 - _sep)^0)
|
|
local p = lpeg.Ct(elem * (_sep * elem)^0)
|
|
gr = p
|
|
split_grammar[sep] = gr
|
|
end
|
|
|
|
return gr:match(s)
|
|
end
|
|
|
|
exports.rspamd_str_split = rspamd_str_split
|
|
|
|
local space = lpeg.S' \t\n\v\f\r'
|
|
local nospace = 1 - space
|
|
local ptrim = space^0 * lpeg.C((space^0 * nospace^1)^0)
|
|
local match = lpeg.match
|
|
exports.rspamd_str_trim = function(s)
|
|
return match(ptrim, s)
|
|
end
|
|
|
|
-- Robert Jay Gould http://lua-users.org/wiki/SimpleRound
|
|
exports.round = function(num, numDecimalPlaces)
|
|
local mult = 10^(numDecimalPlaces or 0)
|
|
return math.floor(num * mult) / mult
|
|
end
|
|
|
|
exports.template = function(tmpl, keys)
|
|
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
|
|
|
|
exports.remove_email_aliases = function(email_addr)
|
|
local function check_gmail_user(addr)
|
|
-- Remove all points
|
|
local no_dots_user = string.gsub(addr.user, '%.', '')
|
|
local cap, pluses = string.match(no_dots_user, '^([^%+][^%+]*)(%+.*)$')
|
|
if cap then
|
|
return cap, rspamd_str_split(pluses, '+'), nil
|
|
elseif no_dots_user ~= addr.user then
|
|
return no_dots_user,{},nil
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function check_address(addr)
|
|
if addr.user then
|
|
local cap, pluses = string.match(addr.user, '^([^%+][^%+]*)(%+.*)$')
|
|
if cap then
|
|
return cap, rspamd_str_split(pluses, '+'), nil
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function set_addr(addr, new_user, new_domain)
|
|
if new_user then
|
|
addr.user = new_user
|
|
end
|
|
if new_domain then
|
|
addr.domain = new_domain
|
|
end
|
|
|
|
if addr.domain then
|
|
addr.addr = string.format('%s@%s', addr.user, addr.domain)
|
|
else
|
|
addr.addr = string.format('%s@', addr.user)
|
|
end
|
|
|
|
if addr.name and #addr.name > 0 then
|
|
addr.raw = string.format('"%s" <%s>', addr.name, addr.addr)
|
|
else
|
|
addr.raw = string.format('<%s>', addr.addr)
|
|
end
|
|
end
|
|
|
|
local function check_gmail(addr)
|
|
local nu, tags, nd = check_gmail_user(addr)
|
|
|
|
if nu then
|
|
return nu, tags, nd
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function check_googlemail(addr)
|
|
local nd = 'gmail.com'
|
|
local nu, tags = check_gmail_user(addr)
|
|
|
|
if nu then
|
|
return nu, tags, nd
|
|
end
|
|
|
|
return nil, nil, nd
|
|
end
|
|
|
|
local specific_domains = {
|
|
['gmail.com'] = check_gmail,
|
|
['googlemail.com'] = check_googlemail,
|
|
}
|
|
|
|
if email_addr then
|
|
if email_addr.domain and specific_domains[email_addr.domain] then
|
|
local nu, tags, nd = specific_domains[email_addr.domain](email_addr)
|
|
if nu or nd then
|
|
set_addr(email_addr, nu, nd)
|
|
|
|
return nu, tags
|
|
end
|
|
else
|
|
local nu, tags, nd = check_address(email_addr)
|
|
if nu or nd then
|
|
set_addr(email_addr, nu, nd)
|
|
|
|
return nu, tags
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
end
|
|
|
|
exports.is_rspamc_or_controller = function(task)
|
|
local ua = task:get_request_header('User-Agent') or ''
|
|
local pwd = task:get_request_header('Password')
|
|
local is_rspamc = false
|
|
if tostring(ua) == 'rspamc' or pwd then is_rspamc = true end
|
|
|
|
return is_rspamc
|
|
end
|
|
|
|
local unpack_function = table.unpack or unpack
|
|
exports.unpack = function(t)
|
|
return unpack_function(t)
|
|
end
|
|
|
|
-- Sorted iteration:
|
|
-- for k,v in spairs(t) do ... end
|
|
--
|
|
-- or with custom comparison:
|
|
-- for k, v in spairs(t, function(t, a, b) return t[a] < t[b] end)
|
|
--
|
|
-- optional limit is also available (e.g. return top X elements)
|
|
local function spairs(t, order, lim)
|
|
-- collect the keys
|
|
local keys = {}
|
|
for k in pairs(t) do keys[#keys+1] = k end
|
|
|
|
-- if order function given, sort by it by passing the table and keys a, b,
|
|
-- otherwise just sort the keys
|
|
if order then
|
|
table.sort(keys, function(a,b) return order(t, a, b) end)
|
|
else
|
|
table.sort(keys)
|
|
end
|
|
|
|
-- return the iterator function
|
|
local i = 0
|
|
return function()
|
|
i = i + 1
|
|
if not lim or i <= lim then
|
|
if keys[i] then
|
|
return keys[i], t[keys[i]]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
exports.spairs = spairs
|
|
|
|
local function disable_module(modname, how)
|
|
if rspamd_plugins_state.enabled[modname] then
|
|
rspamd_plugins_state.enabled[modname] = nil
|
|
end
|
|
|
|
if how == 'redis' then
|
|
rspamd_plugins_state.disabled_redis[modname] = {}
|
|
elseif how == 'config' then
|
|
rspamd_plugins_state.disabled_unconfigured[modname] = {}
|
|
else
|
|
rspamd_plugins_state.disabled_failed[modname] = {}
|
|
end
|
|
end
|
|
|
|
exports.disable_module = disable_module
|
|
|
|
local function parse_time_interval(str)
|
|
local function parse_time_suffix(s)
|
|
if s == 's' then
|
|
return 1
|
|
elseif s == 'm' then
|
|
return 60
|
|
elseif s == 'h' then
|
|
return 3600
|
|
elseif s == 'd' then
|
|
return 86400
|
|
elseif s == 'y' then
|
|
return 365 * 86400;
|
|
end
|
|
end
|
|
|
|
local lpeg = require "lpeg"
|
|
|
|
local digit = lpeg.R("09")
|
|
local parser = {}
|
|
parser.integer =
|
|
(lpeg.S("+-") ^ -1) *
|
|
(digit ^ 1)
|
|
parser.fractional =
|
|
(lpeg.P(".") ) *
|
|
(digit ^ 1)
|
|
parser.number =
|
|
(parser.integer *
|
|
(parser.fractional ^ -1)) +
|
|
(lpeg.S("+-") * parser.fractional)
|
|
parser.time = lpeg.Cf(lpeg.Cc(1) *
|
|
(parser.number / tonumber) *
|
|
((lpeg.S("smhdy") / parse_time_suffix) ^ -1),
|
|
function (acc, val) return acc * val end)
|
|
|
|
local t = lpeg.match(parser.time, str)
|
|
|
|
return t
|
|
end
|
|
|
|
exports.parse_time_interval = parse_time_interval
|
|
|
|
return exports
|