Mercurial > prosody-modules
changeset 6502:b15601203d0d
mod_firewall: Support 'FROM IP' and 'FROM SUBNET' conditions
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Thu, 26 Mar 2026 13:33:44 +0000 |
| parents | f7158c0e2202 |
| children | 74774ec3af1d |
| files | mod_firewall/README.md mod_firewall/conditions.lib.lua mod_firewall/mod_firewall.lua |
| diffstat | 3 files changed, 58 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/mod_firewall/README.md Wed Mar 25 18:52:10 2026 +0000 +++ b/mod_firewall/README.md Thu Mar 26 13:33:44 2026 +0000 @@ -302,6 +302,29 @@ stanza. It is not advisable to perform access control or similar rules on JIDs in these chains (see the [chain documentation](#chains) for more info). +#### IP matching + + Condition Matches + ---------------- ------------------------------------------------------------- + `FROM IP` The origin/session's IP address matches the provided IP address + `FROM SUBNET` The origin/session's IP is within the given (CIDR) subnet range + + +For example: + +``` +# Only allow stanzas to user@example.com from clients connected to 127.0.0.1 +NOT FROM IP: 127.0.0.1 +TO: user@example.com +BOUNCE=policy-violation + +FROM SUBNET: 10.0.0.0/8 +PASS. + +FROM SUBNET: 2001:db8::/32 +PASS. +``` + #### GeoIP matching Condition Matches
--- a/mod_firewall/conditions.lib.lua Wed Mar 25 18:52:10 2026 +0000 +++ b/mod_firewall/conditions.lib.lua Thu Mar 26 13:33:44 2026 +0000 @@ -2,6 +2,8 @@ local condition_handlers = {}; local jid = require "util.jid"; +local iplib = require "util.ip"; +local sha256 = require "util.hashes".sha256; local unpack = table.unpack or unpack; -- Helper to convert user-input strings (yes/true//no/false) to a bool @@ -381,6 +383,28 @@ }; end +function condition_handlers.FROM_IP(ip_spec) + local parsed_ip, err = iplib.new_ip(ip_spec); + if not parsed_ip then + error("Cannot parse IP address: "..(err or "unknown error")); + end + local normal_ip = tostring(parsed_ip); + return ("session.ip == %q"):format(normal_ip); +end + +function condition_handlers.FROM_SUBNET(subnet_spec) + local subnet, bits = iplib.parse_cidr(subnet_spec); + if not subnet then + error("Cannot parse subnet CIDR"); + end + local spec_hash = sha256(subnet_spec, true):sub(1, 8); + return ("iplib.match(session_parsed_ip, parsed_ip_%s, %d)"):format( + spec_hash, bits + ), { + "iplib", "session_parsed_ip", "parsed_ip:"..spec_hash..":"..tostring(subnet) + }; +end + -- FROM COUNTRY: SE -- FROM COUNTRY: code=SE -- FROM COUNTRY: SWE
--- a/mod_firewall/mod_firewall.lua Wed Mar 25 18:52:10 2026 +0000 +++ b/mod_firewall/mod_firewall.lua Thu Mar 26 13:33:44 2026 +0000 @@ -275,6 +275,17 @@ iplib = { global_code = [[local iplib = require "util.ip";]]; }; + session_parsed_ip = { + local_code = [[local session_parsed_ip = iplib.new_ip(session.ip)]]; + depends = { "iplib" }; + }; + parsed_ip = { + local_code = function (parsed_ip_spec) + local ip_hash, ip_str = parsed_ip_spec:match("^(%x+):(.+)$"); + local code = ([[local parsed_ip_%s = iplib.new_ip(%q)]]):format(ip_hash, ip_str) + return code, { "iplib" }; + end; + }; geoip_country = { global_code = [[ local geoip_country = require "geoip.country";
