summaryrefslogtreecommitdiffstats
path: root/inc
diff options
context:
space:
mode:
authorSimon Rettberg2026-04-29 14:12:46 +0200
committerSimon Rettberg2026-04-29 14:12:46 +0200
commite9dd3b47e64f43d967a08cfc78efdffa95130a95 (patch)
tree7320ae4709724ccd769ebf9a6368565640afe85d /inc
parent[runmode] Add UUID to selected clients, close dropdown on select (diff)
parent[locationinfo] Use dedicated list permission for extdevices (diff)
downloadslx-admin-e9dd3b47e64f43d967a08cfc78efdffa95130a95.tar.gz
slx-admin-e9dd3b47e64f43d967a08cfc78efdffa95130a95.tar.xz
slx-admin-e9dd3b47e64f43d967a08cfc78efdffa95130a95.zip
Merge branch 'master' of git.openslx.org:openslx-ng/slx-admin
Diffstat (limited to 'inc')
-rw-r--r--inc/audit.inc.php2
-rw-r--r--inc/iputil.inc.php91
-rw-r--r--inc/session.inc.php6
-rw-r--r--inc/user.inc.php2
-rw-r--r--inc/util.inc.php49
5 files changed, 145 insertions, 5 deletions
diff --git a/inc/audit.inc.php b/inc/audit.inc.php
index 5a1c7691..7100b4ce 100644
--- a/inc/audit.inc.php
+++ b/inc/audit.inc.php
@@ -27,7 +27,7 @@ class Audit
$data = [
'dateline' => time(),
'userid' => User::getId(),
- 'ipaddr' => $_SERVER['REMOTE_ADDR'] ?? 'cli',
+ 'ipaddr' => Util::getClientIp() ?? 'cli',
'module' => $module,
'action' => $_REQUEST['action'] ?? '',
'data' => $filtered ?? 'EXCESS',
diff --git a/inc/iputil.inc.php b/inc/iputil.inc.php
index 9ec78f50..a9b79573 100644
--- a/inc/iputil.inc.php
+++ b/inc/iputil.inc.php
@@ -75,4 +75,95 @@ class IpUtil
return ['start' => $ip & ~$bits, 'end' => $ip | $bits];
}
+ /**
+ * Normalizes an IP address to the canonical form.
+ * This supports all numeric IPv4, IPv6 and IPv4-mapped IPv6 addresses.
+ * As well as odd formats like IPv4 with multiple octets collapsed into a single one.
+ * Returns null if the address is invalid.
+ */
+ public static function normalizeIp(string $ip): ?string
+ {
+ $ip = trim($ip);
+ if ($ip === '')
+ return null;
+
+ // Check simple case first
+ $addr = @inet_pton($ip);
+ if ($addr !== false) {
+ $ret = inet_ntop($addr);
+ if (substr($ret, 0, 7) === '::ffff:')
+ return substr($ret, 7);
+ return $ret;
+ }
+ if (strpos($ip, ':') !== false)
+ return null;
+
+ // IPv4 handling
+ // Support octal, hex and shorthand notations
+ $parts = explode('.', $ip);
+ $numParts = count($parts);
+ if ($numParts > 4)
+ return null;
+
+ $values = [];
+ foreach ($parts as $part) {
+ $part = trim($part);
+ if ($part === '')
+ return null;
+ // Check for hex (0x...)
+ if (strncasecmp($part, '0x', 2) === 0) {
+ if (!ctype_xdigit(substr($part, 2)))
+ return null;
+ $values[] = hexdec($part);
+ } elseif ($part[0] === '0' && strlen($part) > 1) {
+ // Octal (starts with 0, but not just 0)
+ if (!preg_match('/^[0-7]+$/', $part))
+ return null;
+ $values[] = octdec($part);
+ } else {
+ // Decimal
+ if (!preg_match('/^[0-9]+$/', $part))
+ return null;
+ $values[] = (int)$part;
+ }
+ }
+
+ if (empty($values))
+ return null;
+
+ // Validate values and handle shorthands
+ // Rules:
+ // 1 part: a (32 bits)
+ // 2 parts: a.b (a: 8 bits, b: 24 bits)
+ // 3 parts: a.b.c (a: 8 bits, b: 8 bits, c: 16 bits)
+ // 4 parts: a.b.c.d (a: 8 bits, b: 8 bits, c: 8 bits, d: 8 bits)
+ $ipv4int = 0;
+ for ($i = 0; $i < $numParts; $i++) {
+ $val = $values[$i];
+ if ($i === $numParts - 1) {
+ // Last part
+ $max = (2 ** (8 * (4 - $i))) - 1;
+ if ($val < 0 || $val > $max)
+ return null;
+ $ipv4int = ($ipv4int << (8 * (4 - $i))) | (int)$val;
+ } else {
+ if ($val < 0 || $val > 255)
+ return null;
+ $ipv4int = ($ipv4int << 8) | (int)$val;
+ }
+ }
+
+ return long2ip($ipv4int);
+ }
+
+ /**
+ * Check if given string is a valid IP address.
+ * Supports all kinds of IP address formats that PHP supports,
+ * including IPv6, IPv4-mapped IPv6, IPv4-in-IPv6, etc.
+ */
+ public static function isValidIp(string $ip): bool
+ {
+ return self::normalizeIp($ip) !== null;
+ }
+
}
diff --git a/inc/session.inc.php b/inc/session.inc.php
index ccb878cd..5eea1484 100644
--- a/inc/session.inc.php
+++ b/inc/session.inc.php
@@ -18,7 +18,7 @@ class Session
ErrorHandler::traceError('Error: Asked to generate session id when already set.');
self::$sid = sha1($salt . ','
. mt_rand(0, 65535)
- . $_SERVER['REMOTE_ADDR']
+ . Util::getClientIp()
. mt_rand(0, 65535)
. $_SERVER['REMOTE_PORT']
. mt_rand(0, 65535)
@@ -138,7 +138,7 @@ class Session
self::delete();
return false;
}
- if ($row['fixedip'] && $row['lastip'] !== $_SERVER['REMOTE_ADDR']) {
+ if ($row['fixedip'] && $row['lastip'] !== Util::getClientIp()) {
return false; // Ignore but don't invalidate
}
// Refresh cookie if appropriate
@@ -174,7 +174,7 @@ class Session
private static function saveOnShutdown(): void
{
$now = time();
- $args = ['lastip' => $_SERVER['REMOTE_ADDR']];
+ $args = ['lastip' => Util::getClientIp()];
if (self::$updateSessionDateline) {
$args['dateline'] = $now + CONFIG_SESSION_TIMEOUT;
}
diff --git a/inc/user.inc.php b/inc/user.inc.php
index ff367a7f..0f222b7f 100644
--- a/inc/user.inc.php
+++ b/inc/user.inc.php
@@ -200,7 +200,7 @@ class User
. rand() . ','
. time() . ','
. rand() . ','
- . $_SERVER['REMOTE_ADDR'] . ','
+ . Util::getClientIp() . ','
. rand() . ','
. $_SERVER['REMOTE_PORT'] . ','
. rand() . ','
diff --git a/inc/util.inc.php b/inc/util.inc.php
index 9fc1320c..3d84cea2 100644
--- a/inc/util.inc.php
+++ b/inc/util.inc.php
@@ -530,4 +530,53 @@ class Util
return (int)preg_replace('/[^0-9].*$/s', '', $a);
}
+ /**
+ * @param bool $reset if true, reset the internal static cache (useful for testing)
+ * @return ?string the client's IP address, as seen by the webserver.
+ * If the webserver is behind a trusted proxy, the IP address will be the one
+ * of the client, as seen by the proxy. null if not running in HTTP context.
+ */
+ public static function getClientIp(bool $reset = false): ?string
+ {
+ static $ip = null;
+ if ($reset) {
+ $ip = null;
+ return null;
+ }
+ if ($ip !== null || !isset($_SERVER['REMOTE_ADDR']))
+ return $ip;
+
+ $ip = IpUtil::normalizeIp($_SERVER['REMOTE_ADDR']) ?? $_SERVER['REMOTE_ADDR'];
+
+ if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+ $trustedProxies = json_decode(Property::get('webinterface.proxies-trusted', '[]'), true);
+ if (isset($trustedProxies[$ip])) {
+ $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
+ for ($i = count($ips) - 1; $i >= 0; --$i) {
+ $ip = trim($ips[$i]);
+ if (!isset($trustedProxies[$ip])) {
+ $ip = IpUtil::normalizeIp($ip) ?? $ip;
+ if (!isset($trustedProxies[$ip]))
+ break;
+ }
+ }
+ }
+ }
+ return $ip;
+ }
+
+ /**
+ * Check if a string is a valid UUID.
+ *
+ * @param mixed $uuid The string to check
+ * @return bool true if valid, false otherwise
+ */
+ public static function isValidUuid($uuid): bool
+ {
+ if (!is_string($uuid) || strlen($uuid) !== 36) {
+ return false;
+ }
+ return (bool)preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $uuid);
+ }
+
}