From f0527d162c94a834c443dd87ae2ed9abf27f718d Mon Sep 17 00:00:00 2001
From: Mladen B <mladen074@gmail.com>
Date: Tue, 8 Feb 2022 20:37:29 +0100
Subject: [PATCH] Add IPv6 support for admin IP whitelist feature.

---
 interface/web/login/index.php | 51 +++++++++++++++++++++++++++++------
 1 file changed, 43 insertions(+), 8 deletions(-)

diff --git a/interface/web/login/index.php b/interface/web/login/index.php
index 2bcb380d95..d59d24efc8 100644
--- a/interface/web/login/index.php
+++ b/interface/web/login/index.php
@@ -190,7 +190,7 @@ function is_admin_ip_whitelisted($ip, $conf)
 		// exclude empty lines and comments
 		if ($line === '' || $line[0] === '#') return false;
 
-		return ip_matches_cidr($ip, $line);
+		return ipv6_matches_cidr($ip, $line) || ipv4_matches_cidr($ip, $line);
 	});
 
 	return count($matches) > 0;
@@ -198,24 +198,59 @@ function is_admin_ip_whitelisted($ip, $conf)
 
 // based on https://www.php.net/manual/en/ref.network.php (comments)
 /**
- * Checks if the given IP address matches the given CIDR.
- * @param $ip
- * @param $cidr
-
+ * Checks if the given IPv4 address matches the given CIDR.
+ * @param string $ip The IPv4 address.
+ * @param string $cidr The CIDR in the IPv4 format.
  * @return bool
  */
-function ip_matches_cidr ($ip, $cidr) {
+function ipv4_matches_cidr ($ip, $cidr)
+{
+	if (strpos($ip, '.') === false) return false;
+
 	list ($net, $mask) = explode ('/', $cidr);
 	if (!$mask) $mask = 32;
 
 	$ip_net = ip2long ($net);
-	$ip_mask = ~((1 << (32 - $mask)) - 1);
-
 	$ip_ip = ip2long ($ip);
+	$ip_mask = ~((1 << (32 - $mask)) - 1);
 
 	return (($ip_ip & $ip_mask) == ($ip_net & $ip_mask));
 }
 
+// based on https://stackoverflow.com/a/7951507/2428861
+/**
+ * Checks if the given IPv6 address matches the given CIDR.
+ * @param string $ip The IPv6 address.
+ * @param string $cidr The CIDR in the IPv6 format.
+ * @return bool
+ */
+function ipv6_matches_cidr($ip, $cidr)
+{
+	if (strpos($ip, ':') === false) return false;
+
+	list ($net, $mask) = explode('/', $cidr);
+	if (!$mask) $mask = 128;
+
+	$ip_net = in_addr_to_bitstring(inet_pton($net));
+	$ip_ip = in_addr_to_bitstring(inet_pton($ip));
+
+	return substr($ip_ip, 0, $mask) === substr($ip_net, 0, $mask);
+}
+
+/**
+ * Converts the output of {@see inet_pton()} to string of bits.
+ * @param string $in_addr The in_addr representation of the IP address.
+ * @return string String of bits representing given in_addr representation of the IP address.
+ */
+function in_addr_to_bitstring($in_addr)
+{
+	$result = '';
+	foreach (str_split($in_addr) as $c) {
+		$result .= str_pad(decbin(ord($c)), 8, '0', STR_PAD_LEFT);
+	}
+	return $result;
+}
+
 /**
  * Validates user credentials and fetches the user if validation succeeded
  * @param app $app
-- 
GitLab