diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile index 67b8aa1c..daf9760b 100644 --- a/data/Dockerfiles/rspamd/Dockerfile +++ b/data/Dockerfiles/rspamd/Dockerfile @@ -20,7 +20,7 @@ RUN apt-get update && apt-get install -y \ && mkdir -p /run/rspamd \ && chown _rspamd:_rspamd /run/rspamd -COPY settings.conf /etc/rspamd/modules.d/settings.conf +COPY settings.conf /etc/rspamd/settings.conf COPY docker-entrypoint.sh /docker-entrypoint.sh ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/data/Dockerfiles/rspamd/lua_util.lua b/data/Dockerfiles/rspamd/lua_util.lua deleted file mode 100644 index a9abd901..00000000 --- a/data/Dockerfiles/rspamd/lua_util.lua +++ /dev/null @@ -1,152 +0,0 @@ -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 - -return exports diff --git a/data/Dockerfiles/rspamd/ratelimit.lua b/data/Dockerfiles/rspamd/ratelimit.lua deleted file mode 100644 index 839ec5c6..00000000 --- a/data/Dockerfiles/rspamd/ratelimit.lua +++ /dev/null @@ -1,674 +0,0 @@ ---[[ -Copyright (c) 2011-2017, Vsevolod Stakhov -Copyright (c) 2016-2017, Andrew Lewis - -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 implements ratelimits using redis - -local E = {} -local N = 'ratelimit' -local redis_params --- Senders that are considered as bounce -local settings = { - bounce_senders = { 'postmaster', 'mailer-daemon', '', 'null', 'fetchmail-daemon', 'mdaemon' }, --- Do not check ratelimits for these recipients - whitelisted_rcpts = { 'postmaster', 'mailer-daemon' }, - prefix = 'RL', - ham_factor_rate = 1.01, - spam_factor_rate = 0.99, - ham_factor_burst = 1.02, - spam_factor_burst = 0.98, - max_rate_mult = 5, - max_bucket_mult = 10, - expire = 60 * 60 * 24 * 2, -- 2 days by default - limits = {}, - allow_local = false, -} - --- Checks bucket, updating it if needed --- KEYS[1] - prefix to update, e.g. RL__ --- KEYS[2] - current time in milliseconds --- KEYS[3] - bucket leak rate (messages per millisecond) --- KEYS[4] - bucket burst --- KEYS[5] - expire for a bucket --- return 1 if message should be ratelimited and 0 if not --- Redis keys used: --- l - last hit --- b - current burst --- dr - current dynamic rate multiplier (*10000) --- db - current dynamic burst multiplier (*10000) -local bucket_check_script = [[ - local last = redis.call('HGET', KEYS[1], 'l') - local now = tonumber(KEYS[2]) - local dynr, dynb = 0, 0 - if not last then - -- New bucket - redis.call('HSET', KEYS[1], 'l', KEYS[2]) - redis.call('HSET', KEYS[1], 'b', '0') - redis.call('HSET', KEYS[1], 'dr', '10000') - redis.call('HSET', KEYS[1], 'db', '10000') - redis.call('EXPIRE', KEYS[1], KEYS[5]) - return {0, 0, 1, 1} - end - - last = tonumber(last) - local burst = tonumber(redis.call('HGET', KEYS[1], 'b')) - -- Perform leak - if burst > 0 then - if last < tonumber(KEYS[2]) then - local rate = tonumber(KEYS[3]) - dynr = tonumber(redis.call('HGET', KEYS[1], 'dr')) / 10000.0 - rate = rate * dynr - local leaked = ((now - last) * rate) - burst = burst - leaked - redis.call('HINCRBYFLOAT', KEYS[1], 'b', -(leaked)) - end - else - burst = 0 - redis.call('HSET', KEYS[1], 'b', '0') - end - - dynb = tonumber(redis.call('HGET', KEYS[1], 'db')) / 10000.0 - - if (burst + 1) * dynb > tonumber(KEYS[4]) then - return {1, tostring(burst), tostring(dynr), tostring(dynb)} - end - - return {0, tostring(burst), tostring(dynr), tostring(dynb)} -]] -local bucket_check_id - - --- Updates a bucket --- KEYS[1] - prefix to update, e.g. RL__ --- KEYS[2] - current time in milliseconds --- KEYS[3] - dynamic rate multiplier --- KEYS[4] - dynamic burst multiplier --- KEYS[5] - max dyn rate (min: 1/x) --- KEYS[6] - max burst rate (min: 1/x) --- KEYS[7] - expire for a bucket --- Redis keys used: --- l - last hit --- b - current burst --- dr - current dynamic rate multiplier --- db - current dynamic burst multiplier -local bucket_update_script = [[ - local last = redis.call('HGET', KEYS[1], 'l') - local now = tonumber(KEYS[2]) - if not last then - -- New bucket - redis.call('HSET', KEYS[1], 'l', KEYS[2]) - redis.call('HSET', KEYS[1], 'b', '1') - redis.call('HSET', KEYS[1], 'dr', '10000') - redis.call('HSET', KEYS[1], 'db', '10000') - redis.call('EXPIRE', KEYS[1], KEYS[7]) - return {1, 1, 1} - end - - local burst = tonumber(redis.call('HGET', KEYS[1], 'b')) - local db = tonumber(redis.call('HGET', KEYS[1], 'db')) / 10000 - local dr = tonumber(redis.call('HGET', KEYS[1], 'dr')) / 10000 - - if dr < tonumber(KEYS[5]) and dr > 1.0 / tonumber(KEYS[5]) then - dr = dr * tonumber(KEYS[3]) - redis.call('HSET', KEYS[1], 'dr', tostring(math.floor(dr * 10000))) - end - - if db < tonumber(KEYS[6]) and db > 1.0 / tonumber(KEYS[6]) then - db = db * tonumber(KEYS[4]) - redis.call('HSET', KEYS[1], 'db', tostring(math.floor(db * 10000))) - end - - redis.call('HINCRBYFLOAT', KEYS[1], 'b', 1) - redis.call('HSET', KEYS[1], 'l', KEYS[2]) - redis.call('EXPIRE', KEYS[1], KEYS[7]) - - return {tostring(burst), tostring(dr), tostring(db)} -]] -local bucket_update_id - --- message_func(task, limit_type, prefix, bucket) -local message_func = function(_, limit_type, _, _) - return string.format('Ratelimit "%s" exceeded', limit_type) -end - -local rspamd_logger = require "rspamd_logger" -local rspamd_util = require "rspamd_util" -local rspamd_lua_utils = require "lua_util" -local lua_redis = require "lua_redis" -local fun = require "fun" -local lua_maps = require "lua_maps" -local lua_util = require "lua_util" -local rspamd_hash = require "rspamd_cryptobox_hash" - - -local function load_scripts(cfg, ev_base) - bucket_check_id = lua_redis.add_redis_script(bucket_check_script, redis_params) - bucket_update_id = lua_redis.add_redis_script(bucket_update_script, redis_params) -end - -local limit_parser -local function parse_string_limit(lim, no_error) - 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 - end - end - local function parse_num_suffix(s) - if s == '' then - return 1 - elseif s == 'k' then - return 1000 - elseif s == 'm' then - return 1000000 - elseif s == 'g' then - return 1000000000 - end - end - local lpeg = require "lpeg" - - if not limit_parser then - local digit = lpeg.R("09") - limit_parser = {} - limit_parser.integer = - (lpeg.S("+-") ^ -1) * - (digit ^ 1) - limit_parser.fractional = - (lpeg.P(".") ) * - (digit ^ 1) - limit_parser.number = - (limit_parser.integer * - (limit_parser.fractional ^ -1)) + - (lpeg.S("+-") * limit_parser.fractional) - limit_parser.time = lpeg.Cf(lpeg.Cc(1) * - (limit_parser.number / tonumber) * - ((lpeg.S("smhd") / parse_time_suffix) ^ -1), - function (acc, val) return acc * val end) - limit_parser.suffixed_number = lpeg.Cf(lpeg.Cc(1) * - (limit_parser.number / tonumber) * - ((lpeg.S("kmg") / parse_num_suffix) ^ -1), - function (acc, val) return acc * val end) - limit_parser.limit = lpeg.Ct(limit_parser.suffixed_number * - (lpeg.S(" ") ^ 0) * lpeg.S("/") * (lpeg.S(" ") ^ 0) * - limit_parser.time) - end - local t = lpeg.match(limit_parser.limit, lim) - - if t and t[1] and t[2] and t[2] ~= 0 then - return t[2], t[1] - end - - if not no_error then - rspamd_logger.errx(rspamd_config, 'bad limit: %s', lim) - end - - return nil -end - -local function parse_limit(name, data) - local buckets = {} - if type(data) == 'table' then - -- 3 cases here: - -- * old limit in format [burst, rate] - -- * vector of strings in Andrew's string format - -- * proper bucket table - if #data == 2 and tonumber(data[1]) and tonumber(data[2]) then - -- Old style ratelimit - rspamd_logger.warnx(rspamd_config, 'old style ratelimit for %s', name) - if tonumber(data[1]) > 0 and tonumber(data[2]) > 0 then - table.insert(buckets, { - burst = data[1], - rate = data[2] - }) - elseif data[1] ~= 0 then - rspamd_logger.warnx(rspamd_config, 'invalid numbers for %s', name) - else - rspamd_logger.infox(rspamd_config, 'disable limit %s, burst is zero', name) - end - else - -- Recursively map parse_limit and flatten the list - fun.each(function(l) - -- Flatten list - for _,b in ipairs(l) do table.insert(buckets, b) end - end, fun.map(function(d) return parse_limit(d, name) end, data)) - end - elseif type(data) == 'string' then - local rep_rate, burst = parse_string_limit(data) - - if rep_rate and burst then - table.insert(buckets, { - burst = burst, - rate = 1.0 / rep_rate -- reciprocal - }) - end - end - - -- Filter valid - return fun.totable(fun.filter(function(val) - return type(val.burst) == 'number' and type(val.rate) == 'number' - end, buckets)) -end - ---- Check whether this addr is bounce -local function check_bounce(from) - return fun.any(function(b) return b == from end, settings.bounce_senders) -end - -local keywords = { - ['ip'] = { - ['get_value'] = function(task) - local ip = task:get_ip() - if ip and ip:is_valid() then return tostring(ip) end - return nil - end, - }, - ['rip'] = { - ['get_value'] = function(task) - local ip = task:get_ip() - if ip and ip:is_valid() and not ip:is_local() then return tostring(ip) end - return nil - end, - }, - ['from'] = { - ['get_value'] = function(task) - local from = task:get_from(0) - if ((from or E)[1] or E).addr then - return string.lower(from[1]['addr']) - end - return nil - end, - }, - ['bounce'] = { - ['get_value'] = function(task) - local from = task:get_from(0) - if not ((from or E)[1] or E).user then - return '_' - end - if check_bounce(from[1]['user']) then return '_' else return nil end - end, - }, - ['asn'] = { - ['get_value'] = function(task) - local asn = task:get_mempool():get_variable('asn') - if not asn then - return nil - else - return asn - end - end, - }, - ['user'] = { - ['get_value'] = function(task) - local auser = task:get_user() - if not auser then - return nil - else - return auser - end - end, - }, - ['to'] = { - ['get_value'] = function(task) - return task:get_principal_recipient() - end, - }, -} - -local function gen_rate_key(task, rtype, bucket) - local key_t = {tostring(lua_util.round(100000.0 / bucket.burst))} - local key_keywords = lua_util.str_split(rtype, '_') - local have_user = false - - for _, v in ipairs(key_keywords) do - local ret - - if keywords[v] and type(keywords[v]['get_value']) == 'function' then - ret = keywords[v]['get_value'](task) - end - if not ret then return nil end - if v == 'user' then have_user = true end - if type(ret) ~= 'string' then ret = tostring(ret) end - table.insert(key_t, ret) - end - - if have_user and not task:get_user() then - return nil - end - - return table.concat(key_t, ":") -end - -local function make_prefix(redis_key, name, bucket) - local hash_len = 24 - if hash_len > #redis_key then hash_len = #redis_key end - local hash = settings.prefix .. - string.sub(rspamd_hash.create(redis_key):base32(), 1, hash_len) - -- Fill defaults - if not bucket.spam_factor_rate then - bucket.spam_factor_rate = settings.spam_factor_rate - end - if not bucket.ham_factor_rate then - bucket.ham_factor_rate = settings.ham_factor_rate - end - if not bucket.spam_factor_burst then - bucket.spam_factor_burst = settings.spam_factor_burst - end - if not bucket.ham_factor_burst then - bucket.ham_factor_burst = settings.ham_factor_burst - end - - return { - bucket = bucket, - name = name, - hash = hash - } -end - -local function limit_to_prefixes(task, k, v, prefixes) - local n = 0 - for _,bucket in ipairs(v) do - local prefix = gen_rate_key(task, k, bucket) - - if prefix then - prefixes[prefix] = make_prefix(prefix, k, bucket) - n = n + 1 - end - end - - return n -end - -local function ratelimit_cb(task) - if not settings.allow_local and - rspamd_lua_utils.is_rspamc_or_controller(task) then return end - - -- Get initial task data - local ip = task:get_from_ip() - if ip and ip:is_valid() and settings.whitelisted_ip then - if settings.whitelisted_ip:get_key(ip) then - -- Do not check whitelisted ip - rspamd_logger.infox(task, 'skip ratelimit for whitelisted IP') - return - end - end - -- Parse all rcpts - local rcpts = task:get_recipients() - local rcpts_user = {} - if rcpts then - fun.each(function(r) - fun.each(function(type) table.insert(rcpts_user, r[type]) end, {'user', 'addr'}) - end, rcpts) - - if fun.any(function(r) return settings.whitelisted_rcpts:get_key(r) end, rcpts_user) then - rspamd_logger.infox(task, 'skip ratelimit for whitelisted recipient') - return - end - end - -- Get user (authuser) - if settings.whitelisted_user then - local auser = task:get_user() - if settings.whitelisted_user:get_key(auser) then - rspamd_logger.infox(task, 'skip ratelimit for whitelisted user') - return - end - end - -- Now create all ratelimit prefixes - local prefixes = {} - local nprefixes = 0 - - for k,v in pairs(settings.limits) do - nprefixes = nprefixes + limit_to_prefixes(task, k, v, prefixes) - end - - for k, hdl in pairs(settings.custom_keywords or E) do - local ret, redis_key, bd = pcall(hdl, task) - - if ret then - local bucket = parse_limit(k, bd) - if bucket[1] then - prefixes[redis_key] = make_prefix(redis_key, k, bucket[1]) - end - nprefixes = nprefixes + 1 - else - rspamd_logger.errx(task, 'cannot call handler for %s: %s', - k, redis_key) - end - end - - local function gen_check_cb(prefix, bucket, lim_name) - return function(err, data) - if err then - rspamd_logger.errx('cannot check limit %s: %s %s', prefix, err, data) - elseif type(data) == 'table' and data[1] and data[1] == 1 then - -- set symbol only and do NOT soft reject - if settings.symbol then - task:insert_result(settings.symbol, 0.0, lim_name .. "(" .. prefix .. ")") - rspamd_logger.infox(task, - 'set_symbol_only: ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn)', - lim_name, prefix, - bucket.burst, bucket.rate, - data[2], data[3], data[4]) - return - -- set INFO symbol and soft reject - elseif settings.info_symbol then - task:insert_result(settings.info_symbol, 1.0, - lim_name .. "(" .. prefix .. ")") - end - rspamd_logger.infox(task, - 'ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn)', - lim_name, prefix, - bucket.burst, bucket.rate, - data[2], data[3], data[4]) - task:set_pre_result('soft reject', - message_func(task, lim_name, prefix, bucket)) - end - end - end - - -- Don't do anything if pre-result has been already set - if task:has_pre_result() then return end - - if nprefixes > 0 then - -- Save prefixes to the cache to allow update - task:cache_set('ratelimit_prefixes', prefixes) - local now = rspamd_util.get_time() - now = lua_util.round(now * 1000.0) -- Get milliseconds - -- Now call check script for all defined prefixes - - for pr,value in pairs(prefixes) do - local bucket = value.bucket - local rate = (bucket.rate) / 1000.0 -- Leak rate in messages/ms - rspamd_logger.debugm(N, task, "check limit %s:%s -> %s (%s/%s)", - value.name, pr, value.hash, bucket.burst, bucket.rate) - lua_redis.exec_redis_script(bucket_check_id, - {key = value.hash, task = task, is_write = true}, - gen_check_cb(pr, bucket, value.name), - {value.hash, tostring(now), tostring(rate), tostring(bucket.burst), - tostring(settings.expire)}) - end - end -end - -local function ratelimit_update_cb(task) - local prefixes = task:cache_get('ratelimit_prefixes') - - if prefixes then - if task:has_pre_result() then - -- Already rate limited/greylisted, do nothing - rspamd_logger.debugm(N, task, 'pre-action has been set, do not update') - return - end - - local is_spam = not (task:get_metric_action() == 'no action') - - -- Update each bucket - for k, v in pairs(prefixes) do - local bucket = v.bucket - local function update_bucket_cb(err, data) - if err then - rspamd_logger.errx(task, 'cannot update rate bucket %s: %s', - k, err) - else - rspamd_logger.debugm(N, task, - "updated limit %s:%s -> %s (%s/%s), burst: %s, dyn_rate: %s, dyn_burst: %s", - v.name, k, v.hash, - bucket.burst, bucket.rate, - data[1], data[2], data[3]) - end - end - local now = rspamd_util.get_time() - now = lua_util.round(now * 1000.0) -- Get milliseconds - local mult_burst = bucket.ham_factor_burst or 1.0 - local mult_rate = bucket.ham_factor_burst or 1.0 - - if is_spam then - mult_burst = bucket.spam_factor_burst or 1.0 - mult_rate = bucket.spam_factor_rate or 1.0 - end - - lua_redis.exec_redis_script(bucket_update_id, - {key = v.hash, task = task, is_write = true}, - update_bucket_cb, - {v.hash, tostring(now), tostring(mult_rate), tostring(mult_burst), - tostring(settings.max_rate_mult), tostring(settings.max_bucket_mult), - tostring(settings.expire)}) - end - end -end - -local opts = rspamd_config:get_all_opt(N) -if opts then - - settings = lua_util.override_defaults(settings, opts) - - if opts['limit'] then - rspamd_logger.errx(rspamd_config, 'Legacy ratelimit config format no longer supported') - end - - if opts['rates'] and type(opts['rates']) == 'table' then - -- new way of setting limits - fun.each(function(t, lim) - local buckets = parse_limit(t, lim) - - if buckets and #buckets > 0 then - settings.limits[t] = buckets - end - end, opts['rates']) - end - - local enabled_limits = fun.totable(fun.map(function(t) - return t - end, settings.limits)) - rspamd_logger.infox(rspamd_config, - 'enabled rate buckets: [%1]', table.concat(enabled_limits, ',')) - - -- Ret, ret, ret: stupid legacy stuff: - -- If we have a string with commas then load it as as static map - -- otherwise, apply normal logic of Rspamd maps - - local wrcpts = opts['whitelisted_rcpts'] - if type(wrcpts) == 'string' then - if string.find(wrcpts, ',') then - settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl( - lua_util.rspamd_str_split(wrcpts, ','), 'set', 'Ratelimit whitelisted rcpts') - else - settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(wrcpts, 'set', - 'Ratelimit whitelisted rcpts') - end - elseif type(opts['whitelisted_rcpts']) == 'table' then - settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(wrcpts, 'set', - 'Ratelimit whitelisted rcpts') - else - -- Stupid default... - settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl( - settings.whitelisted_rcpts, 'set', 'Ratelimit whitelisted rcpts') - end - - if opts['whitelisted_ip'] then - settings.whitelisted_ip = lua_maps.rspamd_map_add('ratelimit', 'whitelisted_ip', 'radix', - 'Ratelimit whitelist ip map') - end - - if opts['whitelisted_user'] then - settings.whitelisted_user = lua_maps.rspamd_map_add('ratelimit', 'whitelisted_user', 'set', - 'Ratelimit whitelist user map') - end - - settings.custom_keywords = {} - if opts['custom_keywords'] then - local ret, res_or_err = pcall(loadfile(opts['custom_keywords'])) - - if ret then - opts['custom_keywords'] = {} - if type(res_or_err) == 'table' then - for k,hdl in pairs(res_or_err) do - settings['custom_keywords'][k] = hdl - end - elseif type(res_or_err) == 'function' then - settings['custom_keywords']['custom'] = res_or_err - end - else - rspamd_logger.errx(rspamd_config, 'cannot execute %s: %s', - opts['custom_keywords'], res_or_err) - settings['custom_keywords'] = {} - end - end - - if opts['message_func'] then - message_func = assert(load(opts['message_func']))() - end - - redis_params = lua_redis.parse_redis_server('ratelimit') - - if not redis_params then - rspamd_logger.infox(rspamd_config, 'no servers are specified, disabling module') - lua_util.disable_module(N, "redis") - else - local s = { - type = 'prefilter,nostat', - name = 'RATELIMIT_CHECK', - priority = 7, - callback = ratelimit_cb, - flags = 'empty', - } - - if settings.symbol then - s.name = settings.symbol - elseif settings.info_symbol then - s.name = settings.info_symbol - end - - rspamd_config:register_symbol(s) - rspamd_config:register_symbol { - type = 'idempotent', - name = 'RATELIMIT_UPDATE', - callback = ratelimit_update_cb, - } - end -end - -rspamd_config:add_on_load(function(cfg, ev_base, worker) - load_scripts(cfg, ev_base) -end)