diff options
| author | Simon Rettberg | 2026-04-29 14:12:46 +0200 |
|---|---|---|
| committer | Simon Rettberg | 2026-04-29 14:12:46 +0200 |
| commit | e9dd3b47e64f43d967a08cfc78efdffa95130a95 (patch) | |
| tree | 7320ae4709724ccd769ebf9a6368565640afe85d /inc | |
| parent | [runmode] Add UUID to selected clients, close dropdown on select (diff) | |
| parent | [locationinfo] Use dedicated list permission for extdevices (diff) | |
| download | slx-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.php | 2 | ||||
| -rw-r--r-- | inc/iputil.inc.php | 91 | ||||
| -rw-r--r-- | inc/session.inc.php | 6 | ||||
| -rw-r--r-- | inc/user.inc.php | 2 | ||||
| -rw-r--r-- | inc/util.inc.php | 49 |
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); + } + } |
