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.

lua_maps.lua 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. --[[[
  2. -- @module lua_maps
  3. -- This module contains helper functions for managing rspamd maps
  4. --]]
  5. --[[
  6. Copyright (c) 2017, Vsevolod Stakhov <vsevolod@highsecure.ru>
  7. Licensed under the Apache License, Version 2.0 (the "License");
  8. you may not use this file except in compliance with the License.
  9. You may obtain a copy of the License at
  10. http://www.apache.org/licenses/LICENSE-2.0
  11. Unless required by applicable law or agreed to in writing, software
  12. distributed under the License is distributed on an "AS IS" BASIS,
  13. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. See the License for the specific language governing permissions and
  15. limitations under the License.
  16. ]]--
  17. local rspamd_logger = require "rspamd_logger"
  18. local exports = {}
  19. local maps_cache = {}
  20. local function map_hash_key(data, mtype)
  21. local hash = require "rspamd_cryptobox_hash"
  22. local st = hash.create_specific('xxh64')
  23. st:update(data)
  24. st:update(mtype)
  25. return st:hex()
  26. end
  27. local function starts(where,st)
  28. return string.sub(where,1,string.len(st))==st
  29. end
  30. local function cut_prefix(where,st)
  31. return string.sub(where,#st + 1)
  32. end
  33. local function maybe_adjust_type(data,mtype)
  34. local function check_prefix(prefix, t)
  35. if starts(data, prefix) then
  36. data = cut_prefix(data, prefix)
  37. mtype = t
  38. return true
  39. end
  40. return false
  41. end
  42. local known_types = {
  43. {'regexp;', 'regexp'},
  44. {'re;', 'regexp'},
  45. {'regexp_multi;', 'regexp_multi'},
  46. {'re_multi;', 'regexp_multi'},
  47. {'glob;', 'glob'},
  48. {'glob_multi;', 'glob_multi'},
  49. {'radix;', 'radix'},
  50. {'ipnet;', 'radix'},
  51. {'set;', 'set'},
  52. {'hash;', 'hash'},
  53. {'plain;', 'hash'}
  54. }
  55. for _,t in ipairs(known_types) do
  56. if check_prefix(t[1], t[2]) then
  57. return data,mtype
  58. end
  59. end
  60. -- No change
  61. return data,mtype
  62. end
  63. --[[[
  64. -- @function lua_maps.map_add_from_ucl(opt, mtype, description)
  65. -- Creates a map from static data
  66. -- Returns true if map was added or nil
  67. -- @param {string or table} opt data for map (or URL)
  68. -- @param {string} mtype type of map (`set`, `map`, `radix`, `regexp`)
  69. -- @param {string} description human-readable description of map
  70. -- @return {bool} true on success, or `nil`
  71. --]]
  72. local function rspamd_map_add_from_ucl(opt, mtype, description)
  73. local ret = {
  74. get_key = function(t, k)
  75. if t.__data then
  76. return t.__data:get_key(k)
  77. end
  78. return nil
  79. end
  80. }
  81. local ret_mt = {
  82. __index = function(t, k)
  83. if t.__data then
  84. return t.get_key(k)
  85. end
  86. return nil
  87. end
  88. }
  89. if not opt then
  90. return nil
  91. end
  92. if type(opt) == 'string' then
  93. opt,mtype = maybe_adjust_type(opt, mtype)
  94. local k = map_hash_key(opt, mtype)
  95. if maps_cache[k] then
  96. rspamd_logger.infox(rspamd_config, 'reuse url for %s(%s)',
  97. opt, mtype)
  98. return maps_cache[k]
  99. end
  100. -- We have a single string, so we treat it as a map
  101. local map = rspamd_config:add_map{
  102. type = mtype,
  103. description = description,
  104. url = opt,
  105. }
  106. if map then
  107. ret.__data = map
  108. ret.hash = k
  109. setmetatable(ret, ret_mt)
  110. maps_cache[k] = ret
  111. return ret
  112. end
  113. elseif type(opt) == 'table' then
  114. -- it might be plain map or map of plain elements
  115. -- no caching in this case (yet)
  116. if opt[1] then
  117. if mtype == 'radix' then
  118. if string.find(opt[1], '^%d') then
  119. local map = rspamd_config:radix_from_ucl(opt)
  120. if map then
  121. ret.__data = map
  122. setmetatable(ret, ret_mt)
  123. return ret
  124. end
  125. else
  126. -- Plain table
  127. local map = rspamd_config:add_map{
  128. type = mtype,
  129. description = description,
  130. url = opt,
  131. }
  132. if map then
  133. ret.__data = map
  134. setmetatable(ret, ret_mt)
  135. return ret
  136. end
  137. end
  138. elseif mtype == 'regexp' or mtype == 'glob' then
  139. if string.find(opt[1], '^/%a') or string.find(opt[1], '^http') then
  140. -- Plain table
  141. local map = rspamd_config:add_map{
  142. type = mtype,
  143. description = description,
  144. url = opt,
  145. }
  146. if map then
  147. ret.__data = map
  148. setmetatable(ret, ret_mt)
  149. return ret
  150. end
  151. else
  152. local map = rspamd_config:add_map{
  153. type = mtype,
  154. description = description,
  155. url = {
  156. url = 'static',
  157. data = opt,
  158. }
  159. }
  160. if map then
  161. ret.__data = map
  162. setmetatable(ret, ret_mt)
  163. return ret
  164. end
  165. end
  166. else
  167. if string.find(opt[1], '^/%a') or string.find(opt[1], '^http') then
  168. -- Plain table
  169. local map = rspamd_config:add_map{
  170. type = mtype,
  171. description = description,
  172. url = opt,
  173. }
  174. if map then
  175. ret.__data = map
  176. setmetatable(ret, ret_mt)
  177. return ret
  178. end
  179. else
  180. local data = {}
  181. local nelts = 0
  182. for _,elt in ipairs(opt) do
  183. if type(elt) == 'string' then
  184. data[elt] = true
  185. nelts = nelts + 1
  186. end
  187. end
  188. if nelts > 0 then
  189. ret.__data = data
  190. ret.get_key = function(t, k)
  191. if k ~= '__data' then
  192. return t.__data[k]
  193. end
  194. return nil
  195. end
  196. return ret
  197. end
  198. end
  199. end
  200. else
  201. local map = rspamd_config:add_map{
  202. type = mtype,
  203. description = description,
  204. url = opt,
  205. }
  206. if map then
  207. ret.__data = map
  208. setmetatable(ret, ret_mt)
  209. return ret
  210. end
  211. end
  212. end
  213. return nil
  214. end
  215. --[[[
  216. -- @function lua_maps.map_add(mname, optname, mtype, description)
  217. -- Creates a map from configuration elements (static data or URL)
  218. -- Returns true if map was added or nil
  219. -- @param {string} mname config section to use
  220. -- @param {string} optname option name to use
  221. -- @param {string} mtype type of map ('set', 'hash', 'radix', 'regexp', 'glob')
  222. -- @param {string} description human-readable description of map
  223. -- @return {bool} true on success, or `nil`
  224. --]]
  225. local function rspamd_map_add(mname, optname, mtype, description)
  226. local opt = rspamd_config:get_module_opt(mname, optname)
  227. return rspamd_map_add_from_ucl(opt, mtype, description)
  228. end
  229. exports.rspamd_map_add = rspamd_map_add
  230. exports.map_add = rspamd_map_add
  231. exports.rspamd_map_add_from_ucl = rspamd_map_add_from_ucl
  232. exports.map_add_from_ucl = rspamd_map_add_from_ucl
  233. -- Check `what` for being lua_map name, otherwise just compares key with what
  234. local function rspamd_maybe_check_map(key, what)
  235. local fun = require "fun"
  236. if type(what) == "table" then
  237. return fun.any(function(elt) return rspamd_maybe_check_map(key, elt) end, what)
  238. end
  239. if type(rspamd_maps) == "table" then
  240. local mn
  241. if starts(what, "map:") then
  242. mn = string.sub(what, 4)
  243. elseif starts(what, "map://") then
  244. mn = string.sub(what, 6)
  245. end
  246. if mn and rspamd_maps[mn] then
  247. return rspamd_maps[mn]:get_key(key)
  248. else
  249. return what:lower() == key
  250. end
  251. else
  252. return what:lower() == key
  253. end
  254. end
  255. exports.rspamd_maybe_check_map = rspamd_maybe_check_map
  256. return exports