changeset 5704:ad5c77793750

mod_firewall: Add FROM COUNTRY condition based on GeoIP DB
author Kim Alvefur <zash@zash.se>
date Sun, 12 Nov 2023 16:37:47 +0100
parents 0ac4545cb4f9
children 9bbf5b0673a2
files mod_firewall/README.markdown mod_firewall/conditions.lib.lua mod_firewall/mod_firewall.lua
diffstat 3 files changed, 86 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/mod_firewall/README.markdown	Sun Nov 12 16:14:09 2023 +0100
+++ b/mod_firewall/README.markdown	Sun Nov 12 16:37:47 2023 +0100
@@ -301,6 +301,31 @@
 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).
 
+#### GeoIP matching
+
+  Condition        Matches
+  ---------------- --------------------------------------------------------------
+  `FROM COUNTRY`   Two or three letter country code looked up in GeoIP database
+
+This condition uses a GeoIP database to look up the origin country of
+the IP attached to the current session.
+
+For example:
+
+    # 3 letter country code
+    FROM COUNTRY: SWE
+
+    # or 2 letter
+    FROM COUNTRY: SE
+
+    # Explicit
+    FROM COUNTRY: code=SE
+    FROM COUNTRY: code3=SWE
+
+**Note:** This requires that the `lua-geoip` and `geoip-database`
+packages are installed (on Debian, package names may differ on other
+operating systems).
+
 #### INSPECT
 
 INSPECT takes a 'path' through the stanza to get a string (an attribute
--- a/mod_firewall/conditions.lib.lua	Sun Nov 12 16:14:09 2023 +0100
+++ b/mod_firewall/conditions.lib.lua	Sun Nov 12 16:37:47 2023 +0100
@@ -381,4 +381,30 @@
 	};
 end
 
+-- FROM COUNTRY: SE
+-- FROM COUNTRY: code=SE
+-- FROM COUNTRY: SWE
+-- FROM COUNTRY: code3=SWE
+-- FROM COUNTRY: continent=EU
+-- FROM COUNTRY? --> NOT FROM COUNTRY: -- (for unknown/invalid)
+-- TODO list support?
+function condition_handlers.FROM_COUNTRY(geoip_spec)
+	local condition = "==";
+	if not geoip_spec then
+		geoip_spec = "--";
+		condition = "~=";
+	end
+	local field, country = geoip_spec:match("(%w+)=(%w+)");
+	if not field then
+		if #geoip_spec == 3 then
+			field, country = "code3", geoip_spec;
+		elseif #geoip_spec == 2 then
+			field, country = "code", geoip_spec;
+		else
+			error("Unknown country code type");
+		end
+	end
+	return ("get_geoip(session.ip, %q) %s %q"):format(field:lower(), condition, country:upper()), { "geoip_country" };
+end
+
 return condition_handlers;
--- a/mod_firewall/mod_firewall.lua	Sun Nov 12 16:14:09 2023 +0100
+++ b/mod_firewall/mod_firewall.lua	Sun Nov 12 16:37:47 2023 +0100
@@ -263,7 +263,41 @@
 	};
 	scan_list = {
 		global_code = [[local function scan_list(list, items) for item in pairs(items) do if list:contains(item) then return true; end end end]];
-	}
+	};
+	iplib = {
+		global_code = [[local iplib = require "util.ip";]];
+	};
+	geoip_country = {
+		global_code = [[
+local geoip_country = require "geoip.country";
+local geov4 = geoip_country.open(module:get_option_string("geoip_ipv4_country", "/usr/share/GeoIP/GeoIP.dat"));
+local geov6 = geoip_country.open(module:get_option_string("geoip_ipv6_country", "/usr/share/GeoIP/GeoIPv6.dat"));
+local function get_geoip(ips, what)
+	if not ips then
+		return "--";
+	end
+	local ip = iplib.new_ip(ips);
+	if not ip then
+		return "--";
+	end
+	if ip.proto == "IPv6" and geov6 then
+		local geoinfo = geoinfo:query_by_addr6(ip.addr);
+		if geoinfo then
+			return geoinfo[what or "code"];
+		end
+	elseif ip.proto == "IPv4" and geov4 then
+		local geoinfo = geoinfo:query_by_addr(ip.addr);
+		if geoinfo then
+			return geoinfo[what or "code"];
+		end
+	end
+	return "--";
+end
+		]];
+		depends = {
+			"iplib"
+		}
+	};
 };
 
 local function include_dep(dependency, code)