Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

lua_maps.lua 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  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. -- Plain array of keys, count merely numeric elts
  183. for _,elt in ipairs(opt) do
  184. if type(elt) == 'string' then
  185. -- Numeric table
  186. if mtype == 'hash' then
  187. -- Treat as KV pair
  188. local lua_util = require "lua_util"
  189. local pieces = lua_util.str_split(elt, ' ')
  190. if #pieces > 1 then
  191. local key = table.remove(pieces, 1)
  192. data[key] = table.concat(pieces, ' ')
  193. else
  194. data[elt] = true
  195. end
  196. else
  197. data[elt] = true
  198. end
  199. nelts = nelts + 1
  200. end
  201. end
  202. if nelts > 0 then
  203. -- Plain Lua table that is used as a map
  204. ret.__data = data
  205. ret.get_key = function(t, k)
  206. if k ~= '__data' then
  207. return t.__data[k]
  208. end
  209. return nil
  210. end
  211. return ret
  212. else
  213. -- Empty map, huh?
  214. rspamd_logger.errx(rspamd_config, 'invalid map element: %s',
  215. opt)
  216. end
  217. end
  218. end
  219. else
  220. -- We have some non-trivial object so let C code to deal with it somehow...
  221. local map = rspamd_config:add_map{
  222. type = mtype,
  223. description = description,
  224. url = opt,
  225. }
  226. if map then
  227. ret.__data = map
  228. setmetatable(ret, ret_mt)
  229. return ret
  230. end
  231. end -- opt[1]
  232. end
  233. return nil
  234. end
  235. --[[[
  236. -- @function lua_maps.map_add(mname, optname, mtype, description)
  237. -- Creates a map from configuration elements (static data or URL)
  238. -- Returns true if map was added or nil
  239. -- @param {string} mname config section to use
  240. -- @param {string} optname option name to use
  241. -- @param {string} mtype type of map ('set', 'hash', 'radix', 'regexp', 'glob')
  242. -- @param {string} description human-readable description of map
  243. -- @return {bool} true on success, or `nil`
  244. --]]
  245. local function rspamd_map_add(mname, optname, mtype, description)
  246. local opt = rspamd_config:get_module_opt(mname, optname)
  247. return rspamd_map_add_from_ucl(opt, mtype, description)
  248. end
  249. exports.rspamd_map_add = rspamd_map_add
  250. exports.map_add = rspamd_map_add
  251. exports.rspamd_map_add_from_ucl = rspamd_map_add_from_ucl
  252. exports.map_add_from_ucl = rspamd_map_add_from_ucl
  253. -- Check `what` for being lua_map name, otherwise just compares key with what
  254. local function rspamd_maybe_check_map(key, what)
  255. local fun = require "fun"
  256. if type(what) == "table" then
  257. return fun.any(function(elt) return rspamd_maybe_check_map(key, elt) end, what)
  258. end
  259. if type(rspamd_maps) == "table" then
  260. local mn
  261. if starts(what, "map:") then
  262. mn = string.sub(what, 4)
  263. elseif starts(what, "map://") then
  264. mn = string.sub(what, 6)
  265. end
  266. if mn and rspamd_maps[mn] then
  267. return rspamd_maps[mn]:get_key(key)
  268. else
  269. return what:lower() == key
  270. end
  271. else
  272. return what:lower() == key
  273. end
  274. end
  275. exports.rspamd_maybe_check_map = rspamd_maybe_check_map
  276. return exports