Mercurial > prosody-modules
diff mod_firewall/definitions.lib.lua @ 2520:c6fd8975704b
mod_firewall: Initial support for lists, in-memory and HTTP
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Sun, 19 Feb 2017 21:10:26 +0000 |
parents | 5fe483b73fd2 |
children | 72cbec103709 |
line wrap: on
line diff
--- a/mod_firewall/definitions.lib.lua Sun Feb 19 21:08:30 2017 +0000 +++ b/mod_firewall/definitions.lib.lua Sun Feb 19 21:10:26 2017 +0000 @@ -4,6 +4,8 @@ local definition_handlers = {}; +local http = require "net.http"; +local timer = require "util.timer"; local set = require"util.set"; local new_throttle = require "util.throttle".create; @@ -57,5 +59,109 @@ }; end +local list_backends = { + memory = { + init = function (self, type, opts) + if opts.limit then + local have_cache_lib, cache_lib = pcall(require, "util.cache"); + if not have_cache_lib then + error("In-memory lists with a size limit require Prosody 0.10"); + end + self.cache = cache_lib.new((assert(tonumber(opts.limit), "Invalid list limit"))); + if not self.cache.table then + error("In-memory lists with a size limit require a newer version of Prosody 0.10"); + end + self.items = self.cache:table(); + else + self.items = {}; + end + end; + add = function (self, item) + self.items[item] = true; + end; + remove = function (self, item) + self.items[item] = nil; + end; + contains = function (self, item) + return self.items[item] == true; + end; + }; + http = { + init = function (self, url, opts) + local poll_interval = assert(tonumber(opts.ttl or "3600"), "invalid ttl for <"..url.."> (expected number of seconds)"); + local pattern = opts.pattern or "([^\r\n]+)\r?\n"; + assert(pcall(string.match, "", pattern), "invalid pattern for <"..url..">"); + if opts.hash then + assert(opts.hash:match("^%w+$") and type(hashes[opts.hash]) == "function", "invalid hash function: "..opts.hash); + self.hash_function = hashes[opts.hash]; + end + local etag; + local function update_list() + http.request(url, { + headers = { + ["If-None-Match"] = etag; + }; + }, function (body, code, response) + if code == 200 and body then + etag = response.headers.etag; + local items = {}; + for entry in body:gmatch(pattern) do + items[entry] = true; + end + self.items = items; + module:log("debug", "Fetched updated list from <%s>", url); + elseif code == 304 then + module:log("debug", "List at <%s> is unchanged", url); + else + module:log("warn", "Failed to fetch list from <%s>: %d %s", url, code, tostring(body)); + end + if poll_interval > 0 then + timer.add_task(poll_interval, update_list); + end + end); + end + update_list(); + timer.add_task(0, update_list); + end; + contains = function (self, item) + if self.hash_function then + item = self.hash_function(item); + end + return self.items and self.items[item] == true; + end; + }; +}; + +local function create_list(list_backend, list_def, opts) + if not list_backends[list_backend] then + error("Unknown list type '"..list_backend.."'", 0); + end + local list = setmetatable({}, { __index = list_backends[list_backend] }); + if list.init then + list:init(list_def, opts); + end + return list; +end + +--[[ +%LIST spammers: memory (source: /etc/spammers.txt) + +%LIST spammers: memory (source: /etc/spammers.txt) + + +%LIST spammers: http://example.com/blacklist.txt +]] + +function definition_handlers.LIST(list_name, list_definition) + local list_backend = list_definition:match("^%w+"); + local opts = {}; + local opt_string = list_definition:match("^%S+%s+%((.+)%)"); + if opt_string then + for opt_k, opt_v in opt_string:gmatch("(%w+): ?([^,]+)") do + opts[opt_k] = opt_v; + end + end + return create_list(list_backend, list_definition:match("^%S+"), opts); +end + return definition_handlers; -