# HG changeset patch # User Matthew Wild # Date 1487538626 0 # Node ID c6fd8975704b903653938d265afe538f475c5b2d # Parent d4bc434a60a4522a7a79bb1ce251e2866d68f56e mod_firewall: Initial support for lists, in-memory and HTTP diff -r d4bc434a60a4 -r c6fd8975704b mod_firewall/conditions.lib.lua --- a/mod_firewall/conditions.lib.lua Sun Feb 19 21:08:30 2017 +0000 +++ b/mod_firewall/conditions.lib.lua Sun Feb 19 21:10:26 2017 +0000 @@ -252,4 +252,15 @@ return ("not not session.firewall_marked_"..idsafe(name)); end +-- CHECK LIST: spammers contains $<@from> +function condition_handlers.CHECK_LIST(list_condition) + local list_name, expr = list_condition:match("(%S+) contains (.+)$"); + if not list_name and expr then + error("Error parsing list check, syntax: LISTNAME contains EXPRESSION"); + end + local meta_deps = {}; + expr = meta(("%q"):format(expr), meta_deps); + return ("list_%s:contains(%s) == true"):format(list_name, expr), { "list:"..list_name, unpack(meta_deps) }; +end + return condition_handlers; diff -r d4bc434a60a4 -r c6fd8975704b mod_firewall/definitions.lib.lua --- 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; - diff -r d4bc434a60a4 -r c6fd8975704b mod_firewall/mod_firewall.lua --- a/mod_firewall/mod_firewall.lua Sun Feb 19 21:08:30 2017 +0000 +++ b/mod_firewall/mod_firewall.lua Sun Feb 19 21:10:26 2017 +0000 @@ -191,6 +191,12 @@ local_code = [[local roster_entry = (to_node and rostermanager.load_roster(to_node, to_host) or {})[bare_from];]]; depends = { "rostermanager", "split_to", "bare_from" }; }; + list = { global_code = function (list) + assert(idsafe(list), "Invalid list name: "..list); + assert(active_definitions.LIST[list], "Unknown list: "..list); + return ("local list_%s = lists[%q];"):format(list, list); + end + }; }; local function include_dep(dependency, code)