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.

ratelimit.lua 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  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, settings = {}, {}
  19. local N = 'ratelimit'
  20. -- Senders that are considered as bounce
  21. local bounce_senders = {'postmaster', 'mailer-daemon', '', 'null', 'fetchmail-daemon', 'mdaemon'}
  22. -- Do not check ratelimits for these recipients
  23. local whitelisted_rcpts = {'postmaster', 'mailer-daemon'}
  24. local whitelisted_ip
  25. local whitelisted_user
  26. local max_rcpt = 5
  27. local redis_params
  28. local ratelimit_symbol
  29. -- Do not delay mail after 1 day
  30. local use_ip_score = false
  31. local rl_prefix = 'RL'
  32. local ip_score_lower_bound = 10
  33. local ip_score_ham_multiplier = 1.1
  34. local ip_score_spam_divisor = 1.1
  35. local limits_hash
  36. local message_func = function(_, limit_type)
  37. return string.format('Ratelimit "%s" exceeded', limit_type)
  38. end
  39. local rspamd_logger = require "rspamd_logger"
  40. local rspamd_util = require "rspamd_util"
  41. local rspamd_lua_utils = require "lua_util"
  42. local lua_redis = require "lua_redis"
  43. local fun = require "fun"
  44. local user_keywords = {'user'}
  45. local redis_script_sha
  46. local redis_script = [[local bucket
  47. local limited = false
  48. local buckets = {}
  49. local queue_id = table.remove(ARGV)
  50. local now = table.remove(ARGV)
  51. local argi = 0
  52. for i = 1, #KEYS do
  53. local key = KEYS[i]
  54. local period = tonumber(ARGV[argi+1])
  55. local limit = tonumber(ARGV[argi+2])
  56. if not buckets[key] then
  57. buckets[key] = {
  58. max_period = period,
  59. limits = { {period, limit} },
  60. }
  61. else
  62. table.insert(buckets[key].limits, {period, limit})
  63. if period > buckets[key].max_period then
  64. buckets[key].max_period = period
  65. end
  66. end
  67. argi = argi + 2
  68. end
  69. for k, v in pairs(buckets) do
  70. local maxp = v.max_period
  71. redis.call('ZREMRANGEBYSCORE', k, '-inf', now - maxp)
  72. for _, lim in ipairs(v.limits) do
  73. local period = lim[1]
  74. local limit = lim[2]
  75. local rate
  76. if period == maxp then
  77. rate = redis.call('ZCARD', k)
  78. else
  79. rate = redis.call('ZCOUNT', k, now - period, '+inf')
  80. end
  81. if rate and rate >= limit then
  82. limited = true
  83. bucket = k
  84. end
  85. end
  86. redis.call('EXPIRE', k, maxp)
  87. if limited then break end
  88. end
  89. if not limited then
  90. for k in pairs(buckets) do
  91. redis.call('ZADD', k, now, queue_id)
  92. end
  93. end
  94. return {limited, bucket}]]
  95. local redis_script_symbol = [[local limited = false
  96. local buckets, results = {}, {}
  97. local queue_id = table.remove(ARGV)
  98. local now = table.remove(ARGV)
  99. local argi = 0
  100. for i = 1, #KEYS do
  101. local key = KEYS[i]
  102. local period = tonumber(ARGV[argi+1])
  103. local limit = tonumber(ARGV[argi+2])
  104. if not buckets[key] then
  105. buckets[key] = {
  106. max_period = period,
  107. limits = { {period, limit} },
  108. }
  109. else
  110. table.insert(buckets[key].limits, {period, limit})
  111. if period > buckets[key].max_period then
  112. buckets[key].max_period = period
  113. end
  114. end
  115. argi = argi + 2
  116. end
  117. for k, v in pairs(buckets) do
  118. local maxp = v.max_period
  119. redis.call('ZREMRANGEBYSCORE', k, '-inf', now - maxp)
  120. for _, lim in ipairs(v.limits) do
  121. local period = lim[1]
  122. local limit = lim[2]
  123. local rate
  124. if period == maxp then
  125. rate = redis.call('ZCARD', k)
  126. else
  127. rate = redis.call('ZCOUNT', k, now - period, '+inf')
  128. end
  129. if rate then
  130. local mult = 2 * math.tanh(rate / (limit * 2))
  131. if mult >= 0.5 then
  132. table.insert(results, {k, tostring(mult)})
  133. end
  134. end
  135. end
  136. redis.call('ZADD', k, now, queue_id)
  137. redis.call('EXPIRE', k, maxp)
  138. end
  139. return results]]
  140. local function load_scripts(cfg, ev_base)
  141. local function rl_script_cb(err, data)
  142. if err then
  143. rspamd_logger.errx(cfg, 'Script loading failed: ' .. err)
  144. elseif type(data) == 'string' then
  145. redis_script_sha = data
  146. end
  147. end
  148. local script
  149. if ratelimit_symbol then
  150. script = redis_script_symbol
  151. else
  152. script = redis_script
  153. end
  154. lua_redis.redis_make_request_taskless(
  155. ev_base,
  156. cfg,
  157. redis_params,
  158. nil, -- key
  159. true, -- is write
  160. rl_script_cb, --callback
  161. 'SCRIPT', -- command
  162. {'LOAD', script}
  163. )
  164. end
  165. local limit_parser
  166. local function parse_string_limit(lim, no_error)
  167. local function parse_time_suffix(s)
  168. if s == 's' then
  169. return 1
  170. elseif s == 'm' then
  171. return 60
  172. elseif s == 'h' then
  173. return 3600
  174. elseif s == 'd' then
  175. return 86400
  176. end
  177. end
  178. local function parse_num_suffix(s)
  179. if s == '' then
  180. return 1
  181. elseif s == 'k' then
  182. return 1000
  183. elseif s == 'm' then
  184. return 1000000
  185. elseif s == 'g' then
  186. return 1000000000
  187. end
  188. end
  189. local lpeg = require "lpeg"
  190. if not limit_parser then
  191. local digit = lpeg.R("09")
  192. limit_parser = {}
  193. limit_parser.integer =
  194. (lpeg.S("+-") ^ -1) *
  195. (digit ^ 1)
  196. limit_parser.fractional =
  197. (lpeg.P(".") ) *
  198. (digit ^ 1)
  199. limit_parser.number =
  200. (limit_parser.integer *
  201. (limit_parser.fractional ^ -1)) +
  202. (lpeg.S("+-") * limit_parser.fractional)
  203. limit_parser.time = lpeg.Cf(lpeg.Cc(1) *
  204. (limit_parser.number / tonumber) *
  205. ((lpeg.S("smhd") / parse_time_suffix) ^ -1),
  206. function (acc, val) return acc * val end)
  207. limit_parser.suffixed_number = lpeg.Cf(lpeg.Cc(1) *
  208. (limit_parser.number / tonumber) *
  209. ((lpeg.S("kmg") / parse_num_suffix) ^ -1),
  210. function (acc, val) return acc * val end)
  211. limit_parser.limit = lpeg.Ct(limit_parser.suffixed_number *
  212. (lpeg.S(" ") ^ 0) * lpeg.S("/") * (lpeg.S(" ") ^ 0) *
  213. limit_parser.time)
  214. end
  215. local t = lpeg.match(limit_parser.limit, lim)
  216. if t and t[1] and t[2] and t[2] ~= 0 then
  217. return t[2], t[1]
  218. end
  219. if not no_error then
  220. rspamd_logger.errx(rspamd_config, 'bad limit: %s', lim)
  221. end
  222. return nil
  223. end
  224. local function resize_element(x_score, x_total, element)
  225. local x_ip_score
  226. if not x_total then x_total = 0 end
  227. if x_total < ip_score_lower_bound or x_total <= 0 then
  228. x_score = 1
  229. else
  230. x_score = x_score / x_total
  231. end
  232. if x_score > 0 then
  233. x_ip_score = x_score / ip_score_spam_divisor
  234. element = element * rspamd_util.tanh(2.718281 * x_ip_score)
  235. elseif x_score < 0 then
  236. x_ip_score = ((1 + (x_score * -1)) * ip_score_ham_multiplier)
  237. element = element * x_ip_score
  238. end
  239. return element
  240. end
  241. --- Check whether this addr is bounce
  242. local function check_bounce(from)
  243. return fun.any(function(b) return b == from end, bounce_senders)
  244. end
  245. local custom_keywords = {}
  246. local keywords = {
  247. ['ip'] = {
  248. ['get_value'] = function(task)
  249. local ip = task:get_ip()
  250. if ip and ip:is_valid() then return ip end
  251. return nil
  252. end,
  253. },
  254. ['rip'] = {
  255. ['get_value'] = function(task)
  256. local ip = task:get_ip()
  257. if ip and ip:is_valid() and not ip:is_local() then return ip end
  258. return nil
  259. end,
  260. },
  261. ['from'] = {
  262. ['get_value'] = function(task)
  263. local from = task:get_from(0)
  264. if ((from or E)[1] or E).addr then
  265. return string.lower(from[1]['addr'])
  266. end
  267. return nil
  268. end,
  269. },
  270. ['bounce'] = {
  271. ['get_value'] = function(task)
  272. local from = task:get_from(0)
  273. if not ((from or E)[1] or E).user then
  274. return '_'
  275. end
  276. if check_bounce(from[1]['user']) then return '_' else return nil end
  277. end,
  278. },
  279. ['asn'] = {
  280. ['get_value'] = function(task)
  281. local asn = task:get_mempool():get_variable('asn')
  282. if not asn then
  283. return nil
  284. else
  285. return asn
  286. end
  287. end,
  288. },
  289. ['user'] = {
  290. ['get_value'] = function(task)
  291. local auser = task:get_user()
  292. if not auser then
  293. return nil
  294. else
  295. return auser
  296. end
  297. end,
  298. },
  299. ['to'] = {
  300. ['get_value'] = function()
  301. return '%s' -- 'to' is special
  302. end,
  303. },
  304. }
  305. local function dynamic_rate_key(task, rtype)
  306. local key_t = {rl_prefix, rtype}
  307. local key_keywords = rspamd_str_split(rtype, '_')
  308. local have_to, have_user = false, false
  309. for _, v in ipairs(key_keywords) do
  310. if (custom_keywords[v] and type(custom_keywords[v]['condition']) == 'function') then
  311. if not custom_keywords[v]['condition']() then return nil end
  312. end
  313. local ret
  314. if custom_keywords[v] and type(custom_keywords[v]['get_value']) == 'function' then
  315. ret = custom_keywords[v]['get_value'](task)
  316. elseif keywords[v] and type(keywords[v]['get_value']) == 'function' then
  317. ret = keywords[v]['get_value'](task)
  318. end
  319. if not ret then return nil end
  320. for _, uk in ipairs(user_keywords) do
  321. if v == uk then have_user = true end
  322. if have_user then break end
  323. end
  324. if v == 'to' then have_to = true end
  325. if type(ret) ~= 'string' then ret = tostring(ret) end
  326. table.insert(key_t, ret)
  327. end
  328. if (not have_user) and task:get_user() then
  329. return nil
  330. end
  331. if not have_to then
  332. return table.concat(key_t, ":")
  333. else
  334. local rate_keys = {}
  335. local rcpts = task:get_recipients(0)
  336. if not ((rcpts or E)[1] or E).addr then
  337. return nil
  338. end
  339. local key_s = table.concat(key_t, ":")
  340. local total_rcpt = 0
  341. for _, r in ipairs(rcpts) do
  342. if r['addr'] and total_rcpt < max_rcpt then
  343. local key_f = string.format(key_s, string.lower(r['addr']))
  344. table.insert(rate_keys, key_f)
  345. total_rcpt = total_rcpt + 1
  346. end
  347. end
  348. return rate_keys
  349. end
  350. end
  351. local function process_buckets(task, buckets)
  352. if not buckets then return end
  353. local function rl_redis_cb(err, data)
  354. if err then
  355. rspamd_logger.infox(task, 'got error while setting limit: %1', err)
  356. end
  357. if not data then return end
  358. if data[1] == 1 then
  359. rspamd_logger.infox(task,
  360. 'ratelimit "%s" exceeded',
  361. data[2])
  362. task:set_pre_result('soft reject',
  363. message_func(task, data[2]))
  364. end
  365. end
  366. local function rl_symbol_redis_cb(err, data)
  367. if err then
  368. rspamd_logger.infox(task, 'got error while setting limit: %1', err)
  369. end
  370. if not data then return end
  371. for i, b in ipairs(data) do
  372. task:insert_result(ratelimit_symbol, b[2], string.format('%s:%s:%s', i, b[1], b[2]))
  373. end
  374. end
  375. local redis_cb = rl_redis_cb
  376. if ratelimit_symbol then redis_cb = rl_symbol_redis_cb end
  377. local args = {redis_script_sha, #buckets}
  378. for _, bucket in ipairs(buckets) do
  379. table.insert(args, bucket[2])
  380. end
  381. for _, bucket in ipairs(buckets) do
  382. if use_ip_score then
  383. local asn_score,total_asn,
  384. country_score,total_country,
  385. ipnet_score,total_ipnet,
  386. ip_score, total_ip = task:get_mempool():get_variable('ip_score',
  387. 'double,double,double,double,double,double,double,double')
  388. local key_keywords = rspamd_str_split(bucket[2], '_')
  389. local has_asn, has_ip = false, false
  390. for _, v in ipairs(key_keywords) do
  391. if v == "asn" then has_asn = true end
  392. if v == "ip" then has_ip = true end
  393. if has_ip and has_asn then break end
  394. end
  395. if has_asn and not has_ip then
  396. bucket[1][2] = resize_element(asn_score, total_asn, bucket[1][2])
  397. elseif has_ip then
  398. if total_ip and total_ip > ip_score_lower_bound then
  399. bucket[1][2] = resize_element(ip_score, total_ip, bucket[1][2])
  400. elseif total_ipnet and total_ipnet > ip_score_lower_bound then
  401. bucket[1][2] = resize_element(ipnet_score, total_ipnet, bucket[1][2])
  402. elseif total_asn and total_asn > ip_score_lower_bound then
  403. bucket[1][2] = resize_element(asn_score, total_asn, bucket[1][2])
  404. elseif total_country and total_country > ip_score_lower_bound then
  405. bucket[1][2] = resize_element(country_score, total_country, bucket[1][2])
  406. else
  407. bucket[1][2] = resize_element(ip_score, total_ip, bucket[1][2])
  408. end
  409. end
  410. end
  411. table.insert(args, bucket[1][1])
  412. table.insert(args, bucket[1][2])
  413. end
  414. table.insert(args, rspamd_util.get_time())
  415. table.insert(args, task:get_queue_id() or task:get_uid())
  416. local ret = rspamd_redis_make_request(task,
  417. redis_params, -- connect params
  418. nil, -- hash key
  419. true, -- is write
  420. redis_cb, --callback
  421. 'evalsha', -- command
  422. args -- arguments
  423. )
  424. if not ret then
  425. rspamd_logger.errx(task, 'got error connecting to redis')
  426. end
  427. end
  428. local function ratelimit_cb(task)
  429. if rspamd_lua_utils.is_rspamc_or_controller(task) then return end
  430. local args = {}
  431. -- Get initial task data
  432. local ip = task:get_from_ip()
  433. if ip and ip:is_valid() and whitelisted_ip then
  434. if whitelisted_ip:get_key(ip) then
  435. -- Do not check whitelisted ip
  436. rspamd_logger.infox(task, 'skip ratelimit for whitelisted IP')
  437. return
  438. end
  439. end
  440. -- Parse all rcpts
  441. local rcpts = task:get_recipients()
  442. local rcpts_user = {}
  443. if rcpts then
  444. fun.each(function(r)
  445. fun.each(function(type) table.insert(rcpts_user, r[type]) end, {'user', 'addr'})
  446. end, rcpts)
  447. if fun.any(
  448. function(r)
  449. if fun.any(function(w) return r == w end, whitelisted_rcpts) then return true end
  450. end,
  451. rcpts_user) then
  452. rspamd_logger.infox(task, 'skip ratelimit for whitelisted recipient')
  453. return
  454. end
  455. end
  456. -- Get user (authuser)
  457. if whitelisted_user then
  458. local auser = task:get_user()
  459. if whitelisted_user:get_key(auser) then
  460. rspamd_logger.infox(task, 'skip ratelimit for whitelisted user')
  461. return
  462. end
  463. end
  464. local redis_keys = {}
  465. local redis_keys_rev = {}
  466. local function collect_redis_keys()
  467. local function collect_cb(err, data)
  468. if err then
  469. rspamd_logger.errx(task, 'redis error: %1', err)
  470. else
  471. for i, d in ipairs(data) do
  472. if type(d) == 'string' then
  473. local plim, size = parse_string_limit(d)
  474. if plim then
  475. table.insert(args, {{plim, size}, redis_keys_rev[i]})
  476. end
  477. end
  478. end
  479. return process_buckets(task, args)
  480. end
  481. end
  482. local params, method
  483. if limits_hash then
  484. params = {limits_hash, rspamd_lua_utils.unpack(redis_keys)}
  485. method = 'HMGET'
  486. else
  487. method = 'MGET'
  488. params = redis_keys
  489. end
  490. local requested_keys = rspamd_redis_make_request(task,
  491. redis_params, -- connect params
  492. nil, -- hash key
  493. true, -- is write
  494. collect_cb, --callback
  495. method, -- command
  496. params -- arguments
  497. )
  498. if not requested_keys then
  499. rspamd_logger.errx(task, 'got error connecting to redis')
  500. return process_buckets(task, args)
  501. end
  502. end
  503. local rate_key
  504. for k in pairs(settings) do
  505. rate_key = dynamic_rate_key(task, k)
  506. if rate_key then
  507. if type(rate_key) == 'table' then
  508. for _, rk in ipairs(rate_key) do
  509. if type(settings[k]) == 'string' and
  510. (custom_keywords[settings[k]] and type(custom_keywords[settings[k]]['get_limit']) == 'function') then
  511. local res = custom_keywords[settings[k]]['get_limit'](task)
  512. if type(res) == 'string' then res = {res} end
  513. for _, r in ipairs(res) do
  514. local plim, size = parse_string_limit(r, true)
  515. if plim then
  516. table.insert(args, {{plim, size}, rk})
  517. else
  518. local rkey = string.match(settings[k], 'redis:(.*)')
  519. if rkey then
  520. table.insert(redis_keys, rkey)
  521. redis_keys_rev[#redis_keys] = rk
  522. else
  523. rspamd_logger.infox(task, "Don't know what to do with limit: %1", settings[k])
  524. end
  525. end
  526. end
  527. end
  528. end
  529. else
  530. if type(settings[k]) == 'string' and
  531. (custom_keywords[settings[k]] and type(custom_keywords[settings[k]]['get_limit']) == 'function') then
  532. local res = custom_keywords[settings[k]]['get_limit'](task)
  533. if type(res) == 'string' then res = {res} end
  534. for _, r in ipairs(res) do
  535. local plim, size = parse_string_limit(r, true)
  536. if plim then
  537. table.insert(args, {{plim, size}, rate_key})
  538. else
  539. local rkey = string.match(r, 'redis:(.*)')
  540. if rkey then
  541. table.insert(redis_keys, rkey)
  542. redis_keys_rev[#redis_keys] = rate_key
  543. else
  544. rspamd_logger.infox(task, "Don't know what to do with limit: %1", settings[k])
  545. end
  546. end
  547. end
  548. elseif type(settings[k]) == 'table' then
  549. for _, rl in ipairs(settings[k]) do
  550. table.insert(args, {{rl[1], rl[2]}, rate_key})
  551. end
  552. elseif type(settings[k]) == 'string' then
  553. local rkey = string.match(settings[k], 'redis:(.*)')
  554. if rkey then
  555. table.insert(redis_keys, rkey)
  556. redis_keys_rev[#redis_keys] = rate_key
  557. else
  558. rspamd_logger.infox(task, "Don't know what to do with limit: %1", settings[k])
  559. end
  560. end
  561. end
  562. end
  563. end
  564. if redis_keys[1] then
  565. return collect_redis_keys()
  566. else
  567. return process_buckets(task, args)
  568. end
  569. end
  570. local opts = rspamd_config:get_all_opt(N)
  571. if opts then
  572. if opts['limit'] then
  573. rspamd_logger.errx(rspamd_config, 'Legacy ratelimit config format no longer supported')
  574. end
  575. if opts['rates'] and type(opts['rates']) == 'table' then
  576. -- new way of setting limits
  577. fun.each(function(t, lim)
  578. if type(lim) == 'table' then
  579. settings[t] = {}
  580. fun.each(function(l)
  581. local plim, size = parse_string_limit(l)
  582. if plim then
  583. table.insert(settings[t], {plim, size})
  584. end
  585. end, lim)
  586. elseif type(lim) == 'string' then
  587. local plim, size = parse_string_limit(lim)
  588. if plim then
  589. settings[t] = { {plim, size} }
  590. end
  591. end
  592. end, opts['rates'])
  593. end
  594. if opts['dynamic_rates'] and type(opts['dynamic_rates']) == 'table' then
  595. fun.each(function(t, lim)
  596. if type(lim) == 'string' then
  597. settings[t] = lim
  598. end
  599. end, opts['dynamic_rates'])
  600. end
  601. local enabled_limits = fun.totable(fun.map(function(t)
  602. return t
  603. end, settings))
  604. rspamd_logger.infox(rspamd_config, 'enabled rate buckets: [%1]', table.concat(enabled_limits, ','))
  605. if opts['whitelisted_rcpts'] and type(opts['whitelisted_rcpts']) == 'string' then
  606. whitelisted_rcpts = rspamd_str_split(opts['whitelisted_rcpts'], ',')
  607. elseif type(opts['whitelisted_rcpts']) == 'table' then
  608. whitelisted_rcpts = opts['whitelisted_rcpts']
  609. end
  610. if opts['whitelisted_ip'] then
  611. whitelisted_ip = rspamd_map_add('ratelimit', 'whitelisted_ip', 'radix',
  612. 'Ratelimit whitelist ip map')
  613. end
  614. if opts['whitelisted_user'] then
  615. whitelisted_user = rspamd_map_add('ratelimit', 'whitelisted_user', 'set',
  616. 'Ratelimit whitelist user map')
  617. end
  618. if opts['symbol'] then
  619. -- We want symbol instead of pre-result
  620. ratelimit_symbol = opts['symbol']
  621. end
  622. if opts['max_rcpt'] then
  623. max_rcpt = tonumber(opts['max_rcpt'])
  624. end
  625. if opts['use_ip_score'] then
  626. use_ip_score = true
  627. local ip_score_opts = rspamd_config:get_all_opt('ip_score')
  628. if ip_score_opts and ip_score_opts['lower_bound'] then
  629. ip_score_lower_bound = ip_score_opts['lower_bound']
  630. end
  631. end
  632. if opts['custom_keywords'] then
  633. custom_keywords = dofile(opts['custom_keywords'])
  634. end
  635. if opts['user_keywords'] then
  636. user_keywords = opts['user_keywords']
  637. end
  638. if opts['message_func'] then
  639. message_func = assert(load(opts['message_func']))()
  640. end
  641. if opts['limits_hash'] then
  642. limits_hash = opts['limits_hash']
  643. end
  644. redis_params = rspamd_parse_redis_server('ratelimit')
  645. if not redis_params then
  646. rspamd_logger.infox(rspamd_config, 'no servers are specified, disabling module')
  647. else
  648. local s = {
  649. type = 'prefilter,nostat',
  650. name = 'RATELIMIT_CHECK',
  651. priority = 4,
  652. callback = ratelimit_cb,
  653. }
  654. if use_ip_score then
  655. s.type = 'normal'
  656. end
  657. if ratelimit_symbol then
  658. s.name = ratelimit_symbol
  659. end
  660. local id = rspamd_config:register_symbol(s)
  661. if use_ip_score then
  662. rspamd_config:register_dependency(id, 'IP_SCORE')
  663. end
  664. for _, v in pairs(custom_keywords) do
  665. if type(v) == 'table' and type(v['init']) == 'function' then
  666. v['init']()
  667. end
  668. end
  669. end
  670. end
  671. rspamd_config:add_on_load(function(cfg, ev_base, worker)
  672. load_scripts(cfg, ev_base)
  673. end)