ratelimit.lua 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. --[[
  2. Copyright (c) 2011-2015, Vsevolod Stakhov <vsevolod@highsecure.ru>
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ]]--
  13. if confighelp then
  14. return
  15. end
  16. -- A plugin that implements ratelimits using redis or kvstorage server
  17. local E = {}
  18. -- Default settings for limits, 1-st member is burst, second is rate and the third is numeric type
  19. local settings = {
  20. }
  21. -- Senders that are considered as bounce
  22. local bounce_senders = {'postmaster', 'mailer-daemon', '', 'null', 'fetchmail-daemon', 'mdaemon'}
  23. -- Do not check ratelimits for these recipients
  24. local whitelisted_rcpts = {'postmaster', 'mailer-daemon'}
  25. local whitelisted_ip
  26. local whitelisted_user
  27. local max_rcpt = 5
  28. local redis_params
  29. local ratelimit_symbol
  30. -- Do not delay mail after 1 day
  31. local max_delay = 24 * 3600
  32. local use_ip_score = false
  33. local rl_prefix = 'rl'
  34. local ip_score_lower_bound = 10
  35. local ip_score_ham_multiplier = 1.1
  36. local ip_score_spam_divisor = 1.1
  37. local message_func = function(_, limit_type)
  38. return string.format('Ratelimit "%s" exceeded', limit_type)
  39. end
  40. local rspamd_logger = require "rspamd_logger"
  41. local rspamd_util = require "rspamd_util"
  42. local rspamd_lua_utils = require "lua_util"
  43. local fun = require "fun"
  44. local user_keywords = {'user'}
  45. local limit_parser
  46. local function parse_string_limit(lim)
  47. local function parse_time_suffix(s)
  48. if s == 's' then
  49. return 1
  50. elseif s == 'm' then
  51. return 60
  52. elseif s == 'h' then
  53. return 3600
  54. elseif s == 'd' then
  55. return 86400
  56. end
  57. end
  58. local function parse_num_suffix(s)
  59. if s == '' then
  60. return 1
  61. elseif s == 'k' then
  62. return 1000
  63. elseif s == 'm' then
  64. return 1000000
  65. elseif s == 'g' then
  66. return 1000000000
  67. end
  68. end
  69. local lpeg = require "lpeg"
  70. if not limit_parser then
  71. local digit = lpeg.R("09")
  72. limit_parser = {}
  73. limit_parser.integer =
  74. (lpeg.S("+-") ^ -1) *
  75. (digit ^ 1)
  76. limit_parser.fractional =
  77. (lpeg.P(".") ) *
  78. (digit ^ 1)
  79. limit_parser.number =
  80. (limit_parser.integer *
  81. (limit_parser.fractional ^ -1)) +
  82. (lpeg.S("+-") * limit_parser.fractional)
  83. limit_parser.time = lpeg.Cf(lpeg.Cc(1) *
  84. (limit_parser.number / tonumber) *
  85. ((lpeg.S("smhd") / parse_time_suffix) ^ -1),
  86. function (acc, val) return acc * val end)
  87. limit_parser.suffixed_number = lpeg.Cf(lpeg.Cc(1) *
  88. (limit_parser.number / tonumber) *
  89. ((lpeg.S("kmg") / parse_num_suffix) ^ -1),
  90. function (acc, val) return acc * val end)
  91. limit_parser.limit = lpeg.Ct(limit_parser.suffixed_number *
  92. (lpeg.S(" ") ^ 0) * lpeg.S("/") * (lpeg.S(" ") ^ 0) *
  93. limit_parser.time)
  94. end
  95. local t = lpeg.match(limit_parser.limit, lim)
  96. if t and t[1] and t[2] and t[2] ~= 0 then
  97. return t[1] / t[2], t[1]
  98. end
  99. rspamd_logger.errx(rspamd_config, 'bad limit: %s', lim)
  100. return nil
  101. end
  102. --- Parse atime and bucket of limit
  103. local function parse_limits(data)
  104. local function parse_limit_elt(str)
  105. local elts = rspamd_str_split(str, ':')
  106. if not elts or #elts < 2 then
  107. return {0, 0, 0}
  108. else
  109. local atime = tonumber(elts[1])
  110. local bucket = tonumber(elts[2])
  111. local ctime = atime
  112. if elts[3] then
  113. ctime = tonumber(elts[3])
  114. end
  115. if not ctime then
  116. ctime = atime
  117. end
  118. return {atime,bucket,ctime}
  119. end
  120. end
  121. return fun.iter(data):map(function(e)
  122. if type(e) == 'string' then
  123. return parse_limit_elt(e)
  124. else
  125. return {0, 0, 0}
  126. end
  127. end):totable()
  128. end
  129. local function resize_element(x_score, x_total, element)
  130. local x_ip_score
  131. if not x_total then x_total = 0 end
  132. if x_total < ip_score_lower_bound or x_total <= 0 then
  133. x_score = 1
  134. else
  135. x_score = x_score / x_total
  136. end
  137. if x_score > 0 then
  138. x_ip_score = x_score / ip_score_spam_divisor
  139. element = element * rspamd_util.tanh(2.718281 * x_ip_score)
  140. elseif x_score < 0 then
  141. x_ip_score = ((1 + (x_score * -1)) * ip_score_ham_multiplier)
  142. element = element * x_ip_score
  143. end
  144. return element
  145. end
  146. --- Check whether this addr is bounce
  147. local function check_bounce(from)
  148. return fun.any(function(b) return b == from end, bounce_senders)
  149. end
  150. local custom_keywords = {}
  151. local keywords = {
  152. ['ip'] = {
  153. ['get_value'] = function(task)
  154. local ip = task:get_ip()
  155. if ip and ip:is_valid() then return ip end
  156. return nil
  157. end,
  158. },
  159. ['rip'] = {
  160. ['get_value'] = function(task)
  161. local ip = task:get_ip()
  162. if ip and ip:is_valid() and not ip:is_local() then return ip end
  163. return nil
  164. end,
  165. },
  166. ['from'] = {
  167. ['get_value'] = function(task)
  168. local from = task:get_from(0)
  169. if ((from or E)[1] or E).addr then
  170. return from[1]['addr']
  171. end
  172. return nil
  173. end,
  174. },
  175. ['bounce'] = {
  176. ['get_value'] = function(task)
  177. local from = task:get_from(0)
  178. if not ((from or E)[1] or E).user then
  179. return '_'
  180. end
  181. if check_bounce(from[1]['user']) then return '_' else return nil end
  182. end,
  183. },
  184. ['asn'] = {
  185. ['get_value'] = function(task)
  186. local asn = task:get_mempool():get_variable('asn')
  187. if not asn then
  188. return nil
  189. else
  190. return asn
  191. end
  192. end,
  193. },
  194. ['user'] = {
  195. ['get_value'] = function(task)
  196. local auser = task:get_user()
  197. if not auser then
  198. return nil
  199. else
  200. return auser
  201. end
  202. end,
  203. },
  204. ['to'] = {
  205. ['get_value'] = function()
  206. return '%s' -- 'to' is special
  207. end,
  208. },
  209. }
  210. local function dynamic_rate_key(task, rtype)
  211. local key_t = {rl_prefix, rtype}
  212. local key_keywords = rspamd_str_split(rtype, '_')
  213. local have_to, have_user = false, false
  214. for _, v in ipairs(key_keywords) do
  215. if (custom_keywords[v] and type(custom_keywords[v]['condition']) == 'function') then
  216. if not custom_keywords[v]['condition']() then return nil end
  217. end
  218. local ret
  219. if custom_keywords[v] and type(custom_keywords[v]['get_value']) == 'function' then
  220. ret = custom_keywords[v]['get_value'](task)
  221. elseif keywords[v] and type(keywords[v]['get_value']) == 'function' then
  222. ret = keywords[v]['get_value'](task)
  223. end
  224. if not ret then return nil end
  225. for _, uk in ipairs(user_keywords) do
  226. if v == uk then have_user = true end
  227. if have_user then break end
  228. end
  229. if v == 'to' then have_to = true end
  230. if type(ret) ~= 'string' then ret = tostring(ret) end
  231. table.insert(key_t, ret)
  232. end
  233. if (not have_user) and task:get_user() then
  234. return nil
  235. end
  236. if not have_to then
  237. return table.concat(key_t, ":")
  238. else
  239. local rate_keys = {}
  240. local rcpts = task:get_recipients(0)
  241. if not ((rcpts or E)[1] or E).addr then
  242. return nil
  243. end
  244. local key_s = table.concat(key_t, ":")
  245. local total_rcpt = 0
  246. for _, r in ipairs(rcpts) do
  247. if r['addr'] and total_rcpt < max_rcpt then
  248. local key_f = string.format(key_s, r['addr'])
  249. table.insert(rate_keys, key_f)
  250. total_rcpt = total_rcpt + 1
  251. end
  252. end
  253. return rate_keys
  254. end
  255. end
  256. --- Check specific limit inside redis
  257. local function check_limits(task, args)
  258. local key = fun.foldl(function(acc, k) return acc .. k[2] end, '', args)
  259. local ret
  260. --- Called when value is got from server
  261. local function rate_get_cb(err, data)
  262. if err then
  263. rspamd_logger.infox(task, 'got error while getting limit: %1', err)
  264. end
  265. if not data then return end
  266. local ntime = rspamd_util.get_time()
  267. local asn_score,total_asn,
  268. country_score,total_country,
  269. ipnet_score,total_ipnet,
  270. ip_score, total_ip
  271. if use_ip_score then
  272. asn_score,total_asn,
  273. country_score,total_country,
  274. ipnet_score,total_ipnet,
  275. ip_score, total_ip = task:get_mempool():get_variable('ip_score',
  276. 'double,double,double,double,double,double,double,double')
  277. end
  278. fun.each(function(elt, limit, rtype)
  279. local bucket = elt[2]
  280. local rate = limit[2]
  281. local threshold = limit[1]
  282. local atime = elt[1]
  283. local ctime = elt[3]
  284. if atime == 0 then return end
  285. if use_ip_score then
  286. local key_keywords = rspamd_str_split(rtype, '_')
  287. local has_asn, has_ip = false, false
  288. for _, v in ipairs(key_keywords) do
  289. if v == "asn" then has_asn = true end
  290. if v == "ip" then has_ip = true end
  291. if has_ip and has_asn then break end
  292. end
  293. if has_asn and not has_ip then
  294. bucket = resize_element(asn_score, total_asn, bucket)
  295. rate = resize_element(asn_score, total_asn, rate)
  296. elseif has_ip then
  297. if total_ip and total_ip > ip_score_lower_bound then
  298. bucket = resize_element(ip_score, total_ip, bucket)
  299. rate = resize_element(ip_score, total_ip, rate)
  300. elseif total_ipnet and total_ipnet > ip_score_lower_bound then
  301. bucket = resize_element(ipnet_score, total_ipnet, bucket)
  302. rate = resize_element(ipnet_score, total_ipnet, rate)
  303. elseif total_asn and total_asn > ip_score_lower_bound then
  304. bucket = resize_element(asn_score, total_asn, bucket)
  305. rate = resize_element(asn_score, total_asn, rate)
  306. elseif total_country and total_country > ip_score_lower_bound then
  307. bucket = resize_element(country_score, total_country, bucket)
  308. rate = resize_element(country_score, total_country, rate)
  309. else
  310. bucket = resize_element(ip_score, total_ip, bucket)
  311. rate = resize_element(ip_score, total_ip, rate)
  312. end
  313. end
  314. end
  315. if atime - ctime > max_delay then
  316. rspamd_logger.infox(task, 'limit is too old: %1 seconds; ignore it',
  317. atime - ctime)
  318. else
  319. bucket = bucket - rate * (ntime - atime);
  320. if bucket > 0 then
  321. if ratelimit_symbol then
  322. local mult = 2 * rspamd_util.tanh(bucket / (threshold * 2))
  323. if mult > 0.5 then
  324. task:insert_result(ratelimit_symbol, mult,
  325. rtype .. ':' .. string.format('%.2f', mult))
  326. end
  327. else
  328. if bucket > threshold then
  329. rspamd_logger.infox(task,
  330. 'ratelimit "%s" exceeded: %s elements with %s limit',
  331. rtype, bucket, threshold)
  332. task:set_pre_result('soft reject',
  333. message_func(task, rtype, bucket, threshold))
  334. end
  335. end
  336. end
  337. end
  338. end, fun.zip(parse_limits(data), fun.map(function(a) return a[1] end, args),
  339. fun.map(function(a) return rspamd_str_split(a[2], ":")[2] end, args)))
  340. end
  341. ret = rspamd_redis_make_request(task,
  342. redis_params, -- connect params
  343. key, -- hash key
  344. false, -- is write
  345. rate_get_cb, --callback
  346. 'mget', -- command
  347. fun.totable(fun.map(function(l) return l[2] end, args)) -- arguments
  348. )
  349. if not ret then
  350. rspamd_logger.errx(task, 'got error connecting to redis')
  351. end
  352. end
  353. --- Set specific limit inside redis
  354. local function set_limits(task, args)
  355. local key = fun.foldl(function(acc, k) return acc .. k[2] end, '', args)
  356. local ret, upstream
  357. local function rate_set_cb(err)
  358. if err then
  359. rspamd_logger.infox(task, 'got error %s when setting ratelimit record on server %s',
  360. err, upstream:get_addr())
  361. end
  362. end
  363. local function rate_get_cb(err, data)
  364. if err then
  365. rspamd_logger.infox(task, 'got error while setting limit: %1', err)
  366. end
  367. if not data then return end
  368. local ntime = rspamd_util.get_time()
  369. local values = {}
  370. fun.each(function(elt, limit)
  371. local bucket = elt[2]
  372. local rate = limit[1][2]
  373. local atime = elt[1]
  374. local ctime = elt[3]
  375. if atime - ctime > max_delay then
  376. rspamd_logger.infox(task, 'limit is too old: %1 seconds; start it over',
  377. atime - ctime)
  378. bucket = 1
  379. ctime = ntime
  380. else
  381. if bucket > 0 then
  382. bucket = bucket - rate * (ntime - atime) + 1;
  383. if bucket < 0 then
  384. bucket = 1
  385. end
  386. else
  387. bucket = 1
  388. end
  389. end
  390. if ctime == 0 then ctime = ntime end
  391. local lstr = string.format('%.3f:%.3f:%.3f', ntime, bucket, ctime)
  392. table.insert(values, {limit[2], max_delay, lstr})
  393. end, fun.zip(parse_limits(data), fun.iter(args)))
  394. if #values > 0 then
  395. local conn
  396. ret,conn,upstream = rspamd_redis_make_request(task,
  397. redis_params, -- connect params
  398. key, -- hash key
  399. true, -- is write
  400. rate_set_cb, --callback
  401. 'setex', -- command
  402. values[1] -- arguments
  403. )
  404. if conn then
  405. fun.each(function(v)
  406. conn:add_cmd('setex', v)
  407. end, fun.drop_n(1, values))
  408. else
  409. rspamd_logger.errx(task, 'got error while connecting to redis')
  410. end
  411. end
  412. end
  413. local _
  414. ret,_,upstream = rspamd_redis_make_request(task,
  415. redis_params, -- connect params
  416. key, -- hash key
  417. false, -- is write
  418. rate_get_cb, --callback
  419. 'mget', -- command
  420. fun.totable(fun.map(function(l) return l[2] end, args)) -- arguments
  421. )
  422. if not ret then
  423. rspamd_logger.errx(task, 'got error connecting to redis')
  424. end
  425. end
  426. --- Check or update ratelimit
  427. local function rate_test_set(task, func)
  428. local args = {}
  429. -- Get initial task data
  430. local ip = task:get_from_ip()
  431. if ip and ip:is_valid() and whitelisted_ip then
  432. if whitelisted_ip:get_key(ip) then
  433. -- Do not check whitelisted ip
  434. rspamd_logger.infox(task, 'skip ratelimit for whitelisted IP')
  435. return
  436. end
  437. end
  438. -- Parse all rcpts
  439. local rcpts = task:get_recipients()
  440. local rcpts_user = {}
  441. if rcpts then
  442. fun.each(function(r) table.insert(rcpts_user, r['user']) end, rcpts)
  443. if fun.any(function(r)
  444. fun.any(function(w) return r == w end, whitelisted_rcpts) end,
  445. rcpts_user) then
  446. rspamd_logger.infox(task, 'skip ratelimit for whitelisted recipient')
  447. return
  448. end
  449. end
  450. -- Get user (authuser)
  451. if whitelisted_user then
  452. local auser = task:get_user()
  453. if whitelisted_user:get_key(auser) then
  454. rspamd_logger.infox(task, 'skip ratelimit for whitelisted user')
  455. return
  456. end
  457. end
  458. local rate_key
  459. for k in pairs(settings) do
  460. rate_key = dynamic_rate_key(task, k)
  461. if rate_key then
  462. if type(rate_key) == 'table' then
  463. for _, rk in ipairs(rate_key) do
  464. if type(settings[k]) == 'table' then
  465. table.insert(args, {settings[k], rk})
  466. elseif type(settings[k]) == 'string' and
  467. (custom_keywords[settings[k]] and type(custom_keywords[settings[k]]['get_limit']) == 'function') then
  468. local res = custom_keywords[settings[k]]['get_limit'](task)
  469. if type(res) == 'table' then
  470. table.insert(args, {res, rate_key})
  471. elseif type(res) == 'string' then
  472. local plim, size = parse_string_limit(res)
  473. if plim then
  474. table.insert(args, {{size, plim, 1}, rate_key})
  475. end
  476. end
  477. end
  478. end
  479. else
  480. if type(settings[k]) == 'table' then
  481. table.insert(args, {settings[k], rate_key})
  482. elseif type(settings[k]) == 'string' and
  483. (custom_keywords[settings[k]] and type(custom_keywords[settings[k]]['get_limit']) == 'function') then
  484. local res = custom_keywords[settings[k]]['get_limit'](task)
  485. if type(res) == 'table' then
  486. table.insert(args, {res, rate_key})
  487. elseif type(res) == 'string' then
  488. local plim, size = parse_string_limit(res)
  489. if plim then
  490. table.insert(args, {{size, plim, 1}, rate_key})
  491. end
  492. end
  493. end
  494. end
  495. end
  496. end
  497. if #args > 0 then
  498. func(task, args)
  499. end
  500. end
  501. --- Check limit
  502. local function rate_test(task)
  503. if rspamd_lua_utils.is_rspamc_or_controller(task) then return end
  504. rate_test_set(task, check_limits)
  505. end
  506. --- Update limit
  507. local function rate_set(task)
  508. local action = task:get_metric_action('default')
  509. if action ~= 'soft reject' then
  510. if rspamd_lua_utils.is_rspamc_or_controller(task) then return end
  511. rate_test_set(task, set_limits)
  512. end
  513. end
  514. --- Parse a single limit description
  515. local function parse_limit(str)
  516. local params = rspamd_str_split(str, ':')
  517. local function set_limit(limit, burst, rate)
  518. limit[1] = tonumber(burst)
  519. limit[2] = tonumber(rate)
  520. end
  521. if #params ~= 3 then
  522. rspamd_logger.errx(rspamd_config, 'invalid limit definition: ' .. str)
  523. return
  524. end
  525. local key_keywords = rspamd_str_split(params[1], '_')
  526. for _, k in ipairs(key_keywords) do
  527. if (custom_keywords[k] and type(custom_keywords[k]['get_value']) == 'function') or
  528. (keywords[k] and type(keywords[k]['get_value']) == 'function') then
  529. set_limit(settings[params[1]], params[2], params[3])
  530. else
  531. rspamd_logger.errx(rspamd_config, 'invalid limit type: ' .. params[1])
  532. end
  533. end
  534. end
  535. local opts = rspamd_config:get_all_opt('ratelimit')
  536. if opts then
  537. local rates = opts['limit']
  538. if rates and type(rates) == 'table' then
  539. fun.each(parse_limit, rates)
  540. elseif rates and type(rates) == 'string' then
  541. parse_limit(rates)
  542. end
  543. if opts['rates'] and type(opts['rates']) == 'table' then
  544. -- new way of setting limits
  545. fun.each(function(t, lim)
  546. if type(lim) == 'table' then
  547. settings[t] = lim
  548. elseif type(lim) == 'string' then
  549. local plim, size = parse_string_limit(lim)
  550. if plim then
  551. settings[t] = {size, plim, 1}
  552. end
  553. end
  554. end, opts['rates'])
  555. end
  556. if opts['dynamic_rates'] and type(opts['dynamic_rates']) == 'table' then
  557. fun.each(function(t, lim)
  558. if type(lim) == 'string' then
  559. settings[t] = lim
  560. end
  561. end, opts['dynamic_rates'])
  562. end
  563. local enabled_limits = fun.totable(fun.map(function(t)
  564. return t
  565. end, fun.filter(function(_, lim)
  566. return type(lim) == 'string' or
  567. (type(lim) == 'table' and type(lim[1]) == 'number' and lim[1] > 0)
  568. or (type(lim) == 'table' and (lim[3]))
  569. end, settings)))
  570. rspamd_logger.infox(rspamd_config, 'enabled rate buckets: [%1]', table.concat(enabled_limits, ','))
  571. if opts['whitelisted_rcpts'] and type(opts['whitelisted_rcpts']) == 'string' then
  572. whitelisted_rcpts = rspamd_str_split(opts['whitelisted_rcpts'], ',')
  573. elseif type(opts['whitelisted_rcpts']) == 'table' then
  574. whitelisted_rcpts = opts['whitelisted_rcpts']
  575. end
  576. if opts['whitelisted_ip'] then
  577. whitelisted_ip = rspamd_map_add('ratelimit', 'whitelisted_ip', 'radix',
  578. 'Ratelimit whitelist ip map')
  579. end
  580. if opts['whitelisted_user'] then
  581. whitelisted_user = rspamd_map_add('ratelimit', 'whitelisted_user', 'set',
  582. 'Ratelimit whitelist user map')
  583. end
  584. if opts['symbol'] then
  585. -- We want symbol instead of pre-result
  586. ratelimit_symbol = opts['symbol']
  587. end
  588. if opts['max_rcpt'] then
  589. max_rcpt = tonumber(opts['max_rcpt'])
  590. end
  591. if opts['max_delay'] then
  592. max_rcpt = tonumber(opts['max_delay'])
  593. end
  594. if opts['use_ip_score'] then
  595. use_ip_score = true
  596. local ip_score_opts = rspamd_config:get_all_opt('ip_score')
  597. if ip_score_opts and ip_score_opts['lower_bound'] then
  598. ip_score_lower_bound = ip_score_opts['lower_bound']
  599. end
  600. end
  601. if opts['custom_keywords'] then
  602. custom_keywords = dofile(opts['custom_keywords'])
  603. end
  604. if opts['user_keywords'] then
  605. user_keywords = opts['user_keywords']
  606. end
  607. if opts['message_func'] then
  608. message_func = assert(load(opts['message_func']))()
  609. end
  610. redis_params = rspamd_parse_redis_server('ratelimit')
  611. if not redis_params then
  612. rspamd_logger.infox(rspamd_config, 'no servers are specified, disabling module')
  613. else
  614. if not ratelimit_symbol and not use_ip_score then
  615. rspamd_config:register_symbol({
  616. name = 'RATELIMIT_CHECK',
  617. callback = rate_test,
  618. type = 'prefilter',
  619. priority = 4,
  620. })
  621. else
  622. local symbol
  623. if not ratelimit_symbol then
  624. symbol = 'RATELIMIT_CHECK'
  625. else
  626. symbol = ratelimit_symbol
  627. end
  628. local id = rspamd_config:register_symbol({
  629. name = symbol,
  630. callback = rate_test,
  631. })
  632. if use_ip_score then
  633. rspamd_config:register_dependency(id, 'IP_SCORE')
  634. end
  635. end
  636. rspamd_config:register_symbol({
  637. name = 'RATELIMIT_SET',
  638. type = 'postfilter',
  639. priority = 5,
  640. callback = rate_set,
  641. })
  642. for _, v in pairs(custom_keywords) do
  643. if type(v) == 'table' and type(v['init']) == 'function' then
  644. v['init']()
  645. end
  646. end
  647. end
  648. end