ratelimit.lua 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. --[[
  2. Copyright (c) 2011-2017, Vsevolod Stakhov <vsevolod@highsecure.ru>
  3. Copyright (c) 2016-2017, Andrew Lewis <nerf@judo.za.org>
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. ]]--
  14. if confighelp then
  15. return
  16. end
  17. -- A plugin that implements ratelimits using redis
  18. local E = {}
  19. local N = 'ratelimit'
  20. local redis_params
  21. -- Senders that are considered as bounce
  22. local settings = {
  23. bounce_senders = { 'postmaster', 'mailer-daemon', '', 'null', 'fetchmail-daemon', 'mdaemon' },
  24. -- Do not check ratelimits for these recipients
  25. whitelisted_rcpts = { 'postmaster', 'mailer-daemon' },
  26. prefix = 'RL',
  27. ham_factor_rate = 1.01,
  28. spam_factor_rate = 0.99,
  29. ham_factor_burst = 1.02,
  30. spam_factor_burst = 0.98,
  31. max_rate_mult = 5,
  32. max_bucket_mult = 10,
  33. expire = 60 * 60 * 24 * 2, -- 2 days by default
  34. limits = {},
  35. allow_local = false,
  36. }
  37. -- Checks bucket, updating it if needed
  38. -- KEYS[1] - prefix to update, e.g. RL_<triplet>_<seconds>
  39. -- KEYS[2] - current time in milliseconds
  40. -- KEYS[3] - bucket leak rate (messages per millisecond)
  41. -- KEYS[4] - bucket burst
  42. -- KEYS[5] - expire for a bucket
  43. -- return 1 if message should be ratelimited and 0 if not
  44. -- Redis keys used:
  45. -- l - last hit
  46. -- b - current burst
  47. -- dr - current dynamic rate multiplier (*10000)
  48. -- db - current dynamic burst multiplier (*10000)
  49. local bucket_check_script = [[
  50. local last = redis.call('HGET', KEYS[1], 'l')
  51. local now = tonumber(KEYS[2])
  52. local dynr, dynb = 0, 0
  53. if not last then
  54. -- New bucket
  55. redis.call('HSET', KEYS[1], 'l', KEYS[2])
  56. redis.call('HSET', KEYS[1], 'b', '0')
  57. redis.call('HSET', KEYS[1], 'dr', '10000')
  58. redis.call('HSET', KEYS[1], 'db', '10000')
  59. redis.call('EXPIRE', KEYS[1], KEYS[5])
  60. return {0, 0, 1, 1}
  61. end
  62. last = tonumber(last)
  63. local burst = tonumber(redis.call('HGET', KEYS[1], 'b'))
  64. -- Perform leak
  65. if burst > 0 then
  66. if last < tonumber(KEYS[2]) then
  67. local rate = tonumber(KEYS[3])
  68. dynr = tonumber(redis.call('HGET', KEYS[1], 'dr')) / 10000.0
  69. rate = rate * dynr
  70. local leaked = ((now - last) * rate)
  71. burst = burst - leaked
  72. redis.call('HINCRBYFLOAT', KEYS[1], 'b', -(leaked))
  73. end
  74. else
  75. burst = 0
  76. redis.call('HSET', KEYS[1], 'b', '0')
  77. end
  78. dynb = tonumber(redis.call('HGET', KEYS[1], 'db')) / 10000.0
  79. if (burst + 1) * dynb > tonumber(KEYS[4]) then
  80. return {1, tostring(burst), tostring(dynr), tostring(dynb)}
  81. end
  82. return {0, tostring(burst), tostring(dynr), tostring(dynb)}
  83. ]]
  84. local bucket_check_id
  85. -- Updates a bucket
  86. -- KEYS[1] - prefix to update, e.g. RL_<triplet>_<seconds>
  87. -- KEYS[2] - current time in milliseconds
  88. -- KEYS[3] - dynamic rate multiplier
  89. -- KEYS[4] - dynamic burst multiplier
  90. -- KEYS[5] - max dyn rate (min: 1/x)
  91. -- KEYS[6] - max burst rate (min: 1/x)
  92. -- KEYS[7] - expire for a bucket
  93. -- Redis keys used:
  94. -- l - last hit
  95. -- b - current burst
  96. -- dr - current dynamic rate multiplier
  97. -- db - current dynamic burst multiplier
  98. local bucket_update_script = [[
  99. local last = redis.call('HGET', KEYS[1], 'l')
  100. local now = tonumber(KEYS[2])
  101. if not last then
  102. -- New bucket
  103. redis.call('HSET', KEYS[1], 'l', KEYS[2])
  104. redis.call('HSET', KEYS[1], 'b', '1')
  105. redis.call('HSET', KEYS[1], 'dr', '10000')
  106. redis.call('HSET', KEYS[1], 'db', '10000')
  107. redis.call('EXPIRE', KEYS[1], KEYS[7])
  108. return {1, 1, 1}
  109. end
  110. local burst = tonumber(redis.call('HGET', KEYS[1], 'b'))
  111. local db = tonumber(redis.call('HGET', KEYS[1], 'db')) / 10000
  112. local dr = tonumber(redis.call('HGET', KEYS[1], 'dr')) / 10000
  113. if dr < tonumber(KEYS[5]) and dr > 1.0 / tonumber(KEYS[5]) then
  114. dr = dr * tonumber(KEYS[3])
  115. redis.call('HSET', KEYS[1], 'dr', tostring(math.floor(dr * 10000)))
  116. end
  117. if db < tonumber(KEYS[6]) and db > 1.0 / tonumber(KEYS[6]) then
  118. db = db * tonumber(KEYS[4])
  119. redis.call('HSET', KEYS[1], 'db', tostring(math.floor(db * 10000)))
  120. end
  121. redis.call('HINCRBYFLOAT', KEYS[1], 'b', 1)
  122. redis.call('HSET', KEYS[1], 'l', KEYS[2])
  123. redis.call('EXPIRE', KEYS[1], KEYS[7])
  124. return {tostring(burst), tostring(dr), tostring(db)}
  125. ]]
  126. local bucket_update_id
  127. -- message_func(task, limit_type, prefix, bucket)
  128. local message_func = function(_, limit_type, _, _)
  129. return string.format('Ratelimit "%s" exceeded', limit_type)
  130. end
  131. local rspamd_logger = require "rspamd_logger"
  132. local rspamd_util = require "rspamd_util"
  133. local rspamd_lua_utils = require "lua_util"
  134. local lua_redis = require "lua_redis"
  135. local fun = require "fun"
  136. local lua_maps = require "lua_maps"
  137. local lua_util = require "lua_util"
  138. local rspamd_hash = require "rspamd_cryptobox_hash"
  139. local function load_scripts(cfg, ev_base)
  140. bucket_check_id = lua_redis.add_redis_script(bucket_check_script, redis_params)
  141. bucket_update_id = lua_redis.add_redis_script(bucket_update_script, redis_params)
  142. end
  143. local limit_parser
  144. local function parse_string_limit(lim, no_error)
  145. local function parse_time_suffix(s)
  146. if s == 's' then
  147. return 1
  148. elseif s == 'm' then
  149. return 60
  150. elseif s == 'h' then
  151. return 3600
  152. elseif s == 'd' then
  153. return 86400
  154. end
  155. end
  156. local function parse_num_suffix(s)
  157. if s == '' then
  158. return 1
  159. elseif s == 'k' then
  160. return 1000
  161. elseif s == 'm' then
  162. return 1000000
  163. elseif s == 'g' then
  164. return 1000000000
  165. end
  166. end
  167. local lpeg = require "lpeg"
  168. if not limit_parser then
  169. local digit = lpeg.R("09")
  170. limit_parser = {}
  171. limit_parser.integer =
  172. (lpeg.S("+-") ^ -1) *
  173. (digit ^ 1)
  174. limit_parser.fractional =
  175. (lpeg.P(".") ) *
  176. (digit ^ 1)
  177. limit_parser.number =
  178. (limit_parser.integer *
  179. (limit_parser.fractional ^ -1)) +
  180. (lpeg.S("+-") * limit_parser.fractional)
  181. limit_parser.time = lpeg.Cf(lpeg.Cc(1) *
  182. (limit_parser.number / tonumber) *
  183. ((lpeg.S("smhd") / parse_time_suffix) ^ -1),
  184. function (acc, val) return acc * val end)
  185. limit_parser.suffixed_number = lpeg.Cf(lpeg.Cc(1) *
  186. (limit_parser.number / tonumber) *
  187. ((lpeg.S("kmg") / parse_num_suffix) ^ -1),
  188. function (acc, val) return acc * val end)
  189. limit_parser.limit = lpeg.Ct(limit_parser.suffixed_number *
  190. (lpeg.S(" ") ^ 0) * lpeg.S("/") * (lpeg.S(" ") ^ 0) *
  191. limit_parser.time)
  192. end
  193. local t = lpeg.match(limit_parser.limit, lim)
  194. if t and t[1] and t[2] and t[2] ~= 0 then
  195. return t[2], t[1]
  196. end
  197. if not no_error then
  198. rspamd_logger.errx(rspamd_config, 'bad limit: %s', lim)
  199. end
  200. return nil
  201. end
  202. local function parse_limit(name, data)
  203. local buckets = {}
  204. if type(data) == 'table' then
  205. -- 3 cases here:
  206. -- * old limit in format [burst, rate]
  207. -- * vector of strings in Andrew's string format
  208. -- * proper bucket table
  209. if #data == 2 and tonumber(data[1]) and tonumber(data[2]) then
  210. -- Old style ratelimit
  211. rspamd_logger.warnx(rspamd_config, 'old style ratelimit for %s', name)
  212. if tonumber(data[1]) > 0 and tonumber(data[2]) > 0 then
  213. table.insert(buckets, {
  214. burst = data[1],
  215. rate = data[2]
  216. })
  217. elseif data[1] ~= 0 then
  218. rspamd_logger.warnx(rspamd_config, 'invalid numbers for %s', name)
  219. else
  220. rspamd_logger.infox(rspamd_config, 'disable limit %s, burst is zero', name)
  221. end
  222. else
  223. -- Recursively map parse_limit and flatten the list
  224. fun.each(function(l)
  225. -- Flatten list
  226. for _,b in ipairs(l) do table.insert(buckets, b) end
  227. end, fun.map(function(d) return parse_limit(d, name) end, data))
  228. end
  229. elseif type(data) == 'string' then
  230. local rep_rate, burst = parse_string_limit(data)
  231. if rep_rate and burst then
  232. table.insert(buckets, {
  233. burst = burst,
  234. rate = 1.0 / rep_rate -- reciprocal
  235. })
  236. end
  237. end
  238. -- Filter valid
  239. return fun.totable(fun.filter(function(val)
  240. return type(val.burst) == 'number' and type(val.rate) == 'number'
  241. end, buckets))
  242. end
  243. --- Check whether this addr is bounce
  244. local function check_bounce(from)
  245. return fun.any(function(b) return b == from end, settings.bounce_senders)
  246. end
  247. local keywords = {
  248. ['ip'] = {
  249. ['get_value'] = function(task)
  250. local ip = task:get_ip()
  251. if ip and ip:is_valid() then return tostring(ip) end
  252. return nil
  253. end,
  254. },
  255. ['rip'] = {
  256. ['get_value'] = function(task)
  257. local ip = task:get_ip()
  258. if ip and ip:is_valid() and not ip:is_local() then return tostring(ip) end
  259. return nil
  260. end,
  261. },
  262. ['from'] = {
  263. ['get_value'] = function(task)
  264. local from = task:get_from(0)
  265. if ((from or E)[1] or E).addr then
  266. return string.lower(from[1]['addr'])
  267. end
  268. return nil
  269. end,
  270. },
  271. ['bounce'] = {
  272. ['get_value'] = function(task)
  273. local from = task:get_from(0)
  274. if not ((from or E)[1] or E).user then
  275. return '_'
  276. end
  277. if check_bounce(from[1]['user']) then return '_' else return nil end
  278. end,
  279. },
  280. ['asn'] = {
  281. ['get_value'] = function(task)
  282. local asn = task:get_mempool():get_variable('asn')
  283. if not asn then
  284. return nil
  285. else
  286. return asn
  287. end
  288. end,
  289. },
  290. ['user'] = {
  291. ['get_value'] = function(task)
  292. local auser = task:get_user()
  293. if not auser then
  294. return nil
  295. else
  296. return auser
  297. end
  298. end,
  299. },
  300. ['to'] = {
  301. ['get_value'] = function(task)
  302. return task:get_principal_recipient()
  303. end,
  304. },
  305. }
  306. local function gen_rate_key(task, rtype, bucket)
  307. local key_t = {tostring(lua_util.round(100000.0 / bucket.burst))}
  308. local key_keywords = lua_util.str_split(rtype, '_')
  309. local have_user = false
  310. for _, v in ipairs(key_keywords) do
  311. local ret
  312. if keywords[v] and type(keywords[v]['get_value']) == 'function' then
  313. ret = keywords[v]['get_value'](task)
  314. end
  315. if not ret then return nil end
  316. if v == 'user' then have_user = true end
  317. if type(ret) ~= 'string' then ret = tostring(ret) end
  318. table.insert(key_t, ret)
  319. end
  320. if have_user and not task:get_user() then
  321. return nil
  322. end
  323. return table.concat(key_t, ":")
  324. end
  325. local function make_prefix(redis_key, name, bucket)
  326. local hash_len = 24
  327. if hash_len > #redis_key then hash_len = #redis_key end
  328. local hash = settings.prefix ..
  329. string.sub(rspamd_hash.create(redis_key):base32(), 1, hash_len)
  330. -- Fill defaults
  331. if not bucket.spam_factor_rate then
  332. bucket.spam_factor_rate = settings.spam_factor_rate
  333. end
  334. if not bucket.ham_factor_rate then
  335. bucket.ham_factor_rate = settings.ham_factor_rate
  336. end
  337. if not bucket.spam_factor_burst then
  338. bucket.spam_factor_burst = settings.spam_factor_burst
  339. end
  340. if not bucket.ham_factor_burst then
  341. bucket.ham_factor_burst = settings.ham_factor_burst
  342. end
  343. return {
  344. bucket = bucket,
  345. name = name,
  346. hash = hash
  347. }
  348. end
  349. local function limit_to_prefixes(task, k, v, prefixes)
  350. local n = 0
  351. for _,bucket in ipairs(v) do
  352. local prefix = gen_rate_key(task, k, bucket)
  353. if prefix then
  354. prefixes[prefix] = make_prefix(prefix, k, bucket)
  355. n = n + 1
  356. end
  357. end
  358. return n
  359. end
  360. local function ratelimit_cb(task)
  361. if not settings.allow_local and
  362. rspamd_lua_utils.is_rspamc_or_controller(task) then return end
  363. -- Get initial task data
  364. local ip = task:get_from_ip()
  365. if ip and ip:is_valid() and settings.whitelisted_ip then
  366. if settings.whitelisted_ip:get_key(ip) then
  367. -- Do not check whitelisted ip
  368. rspamd_logger.infox(task, 'skip ratelimit for whitelisted IP')
  369. return
  370. end
  371. end
  372. -- Parse all rcpts
  373. local rcpts = task:get_recipients()
  374. local rcpts_user = {}
  375. if rcpts then
  376. fun.each(function(r)
  377. fun.each(function(type) table.insert(rcpts_user, r[type]) end, {'user', 'addr'})
  378. end, rcpts)
  379. if fun.any(function(r) return settings.whitelisted_rcpts:get_key(r) end, rcpts_user) then
  380. rspamd_logger.infox(task, 'skip ratelimit for whitelisted recipient')
  381. return
  382. end
  383. end
  384. -- Get user (authuser)
  385. if settings.whitelisted_user then
  386. local auser = task:get_user()
  387. if settings.whitelisted_user:get_key(auser) then
  388. rspamd_logger.infox(task, 'skip ratelimit for whitelisted user')
  389. return
  390. end
  391. end
  392. -- Now create all ratelimit prefixes
  393. local prefixes = {}
  394. local nprefixes = 0
  395. for k,v in pairs(settings.limits) do
  396. nprefixes = nprefixes + limit_to_prefixes(task, k, v, prefixes)
  397. end
  398. for k, hdl in pairs(settings.custom_keywords or E) do
  399. local ret, redis_key, bd = pcall(hdl, task)
  400. if ret then
  401. local bucket = parse_limit(k, bd)
  402. if bucket[1] then
  403. prefixes[redis_key] = make_prefix(redis_key, k, bucket[1])
  404. end
  405. nprefixes = nprefixes + 1
  406. else
  407. rspamd_logger.errx(task, 'cannot call handler for %s: %s',
  408. k, redis_key)
  409. end
  410. end
  411. local function gen_check_cb(prefix, bucket, lim_name)
  412. return function(err, data)
  413. if err then
  414. rspamd_logger.errx('cannot check limit %s: %s %s', prefix, err, data)
  415. elseif type(data) == 'table' and data[1] and data[1] == 1 then
  416. -- set symbol only and do NOT soft reject
  417. if settings.symbol then
  418. task:insert_result(settings.symbol, 0.0, lim_name .. "(" .. prefix .. ")")
  419. rspamd_logger.infox(task,
  420. 'set_symbol_only: ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn)',
  421. lim_name, prefix,
  422. bucket.burst, bucket.rate,
  423. data[2], data[3], data[4])
  424. return
  425. -- set INFO symbol and soft reject
  426. elseif settings.info_symbol then
  427. task:insert_result(settings.info_symbol, 1.0,
  428. lim_name .. "(" .. prefix .. ")")
  429. end
  430. rspamd_logger.infox(task,
  431. 'ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn)',
  432. lim_name, prefix,
  433. bucket.burst, bucket.rate,
  434. data[2], data[3], data[4])
  435. task:set_pre_result('soft reject',
  436. message_func(task, lim_name, prefix, bucket))
  437. end
  438. end
  439. end
  440. -- Don't do anything if pre-result has been already set
  441. if task:has_pre_result() then return end
  442. if nprefixes > 0 then
  443. -- Save prefixes to the cache to allow update
  444. task:cache_set('ratelimit_prefixes', prefixes)
  445. local now = rspamd_util.get_time()
  446. now = lua_util.round(now * 1000.0) -- Get milliseconds
  447. -- Now call check script for all defined prefixes
  448. for pr,value in pairs(prefixes) do
  449. local bucket = value.bucket
  450. local rate = (bucket.rate) / 1000.0 -- Leak rate in messages/ms
  451. rspamd_logger.debugm(N, task, "check limit %s:%s -> %s (%s/%s)",
  452. value.name, pr, value.hash, bucket.burst, bucket.rate)
  453. lua_redis.exec_redis_script(bucket_check_id,
  454. {key = value.hash, task = task, is_write = true},
  455. gen_check_cb(pr, bucket, value.name),
  456. {value.hash, tostring(now), tostring(rate), tostring(bucket.burst),
  457. tostring(settings.expire)})
  458. end
  459. end
  460. end
  461. local function ratelimit_update_cb(task)
  462. local prefixes = task:cache_get('ratelimit_prefixes')
  463. if prefixes then
  464. if task:has_pre_result() then
  465. -- Already rate limited/greylisted, do nothing
  466. rspamd_logger.debugm(N, task, 'pre-action has been set, do not update')
  467. return
  468. end
  469. local is_spam = not (task:get_metric_action() == 'no action')
  470. -- Update each bucket
  471. for k, v in pairs(prefixes) do
  472. local bucket = v.bucket
  473. local function update_bucket_cb(err, data)
  474. if err then
  475. rspamd_logger.errx(task, 'cannot update rate bucket %s: %s',
  476. k, err)
  477. else
  478. rspamd_logger.debugm(N, task,
  479. "updated limit %s:%s -> %s (%s/%s), burst: %s, dyn_rate: %s, dyn_burst: %s",
  480. v.name, k, v.hash,
  481. bucket.burst, bucket.rate,
  482. data[1], data[2], data[3])
  483. end
  484. end
  485. local now = rspamd_util.get_time()
  486. now = lua_util.round(now * 1000.0) -- Get milliseconds
  487. local mult_burst = bucket.ham_factor_burst or 1.0
  488. local mult_rate = bucket.ham_factor_burst or 1.0
  489. if is_spam then
  490. mult_burst = bucket.spam_factor_burst or 1.0
  491. mult_rate = bucket.spam_factor_rate or 1.0
  492. end
  493. lua_redis.exec_redis_script(bucket_update_id,
  494. {key = v.hash, task = task, is_write = true},
  495. update_bucket_cb,
  496. {v.hash, tostring(now), tostring(mult_rate), tostring(mult_burst),
  497. tostring(settings.max_rate_mult), tostring(settings.max_bucket_mult),
  498. tostring(settings.expire)})
  499. end
  500. end
  501. end
  502. local opts = rspamd_config:get_all_opt(N)
  503. if opts then
  504. settings = lua_util.override_defaults(settings, opts)
  505. if opts['limit'] then
  506. rspamd_logger.errx(rspamd_config, 'Legacy ratelimit config format no longer supported')
  507. end
  508. if opts['rates'] and type(opts['rates']) == 'table' then
  509. -- new way of setting limits
  510. fun.each(function(t, lim)
  511. local buckets = parse_limit(t, lim)
  512. if buckets and #buckets > 0 then
  513. settings.limits[t] = buckets
  514. end
  515. end, opts['rates'])
  516. end
  517. local enabled_limits = fun.totable(fun.map(function(t)
  518. return t
  519. end, settings.limits))
  520. rspamd_logger.infox(rspamd_config,
  521. 'enabled rate buckets: [%1]', table.concat(enabled_limits, ','))
  522. -- Ret, ret, ret: stupid legacy stuff:
  523. -- If we have a string with commas then load it as as static map
  524. -- otherwise, apply normal logic of Rspamd maps
  525. local wrcpts = opts['whitelisted_rcpts']
  526. if type(wrcpts) == 'string' then
  527. if string.find(wrcpts, ',') then
  528. settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(
  529. lua_util.rspamd_str_split(wrcpts, ','), 'set', 'Ratelimit whitelisted rcpts')
  530. else
  531. settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(wrcpts, 'set',
  532. 'Ratelimit whitelisted rcpts')
  533. end
  534. elseif type(opts['whitelisted_rcpts']) == 'table' then
  535. settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(wrcpts, 'set',
  536. 'Ratelimit whitelisted rcpts')
  537. else
  538. -- Stupid default...
  539. settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(
  540. settings.whitelisted_rcpts, 'set', 'Ratelimit whitelisted rcpts')
  541. end
  542. if opts['whitelisted_ip'] then
  543. settings.whitelisted_ip = lua_maps.rspamd_map_add('ratelimit', 'whitelisted_ip', 'radix',
  544. 'Ratelimit whitelist ip map')
  545. end
  546. if opts['whitelisted_user'] then
  547. settings.whitelisted_user = lua_maps.rspamd_map_add('ratelimit', 'whitelisted_user', 'set',
  548. 'Ratelimit whitelist user map')
  549. end
  550. settings.custom_keywords = {}
  551. if opts['custom_keywords'] then
  552. local ret, res_or_err = pcall(loadfile(opts['custom_keywords']))
  553. if ret then
  554. opts['custom_keywords'] = {}
  555. if type(res_or_err) == 'table' then
  556. for k,hdl in pairs(res_or_err) do
  557. settings['custom_keywords'][k] = hdl
  558. end
  559. elseif type(res_or_err) == 'function' then
  560. settings['custom_keywords']['custom'] = res_or_err
  561. end
  562. else
  563. rspamd_logger.errx(rspamd_config, 'cannot execute %s: %s',
  564. opts['custom_keywords'], res_or_err)
  565. settings['custom_keywords'] = {}
  566. end
  567. end
  568. if opts['message_func'] then
  569. message_func = assert(load(opts['message_func']))()
  570. end
  571. redis_params = lua_redis.parse_redis_server('ratelimit')
  572. if not redis_params then
  573. rspamd_logger.infox(rspamd_config, 'no servers are specified, disabling module')
  574. lua_util.disable_module(N, "redis")
  575. else
  576. local s = {
  577. type = 'prefilter,nostat',
  578. name = 'RATELIMIT_CHECK',
  579. priority = 7,
  580. callback = ratelimit_cb,
  581. flags = 'empty',
  582. }
  583. if settings.symbol then
  584. s.name = settings.symbol
  585. elseif settings.info_symbol then
  586. s.name = settings.info_symbol
  587. end
  588. rspamd_config:register_symbol(s)
  589. rspamd_config:register_symbol {
  590. type = 'idempotent',
  591. name = 'RATELIMIT_UPDATE',
  592. callback = ratelimit_update_cb,
  593. }
  594. end
  595. end
  596. rspamd_config:add_on_load(function(cfg, ev_base, worker)
  597. load_scripts(cfg, ev_base)
  598. end)