Add on-board proxy for live ircddb data, this allows D-Star live to work with all the new security

This commit is contained in:
Andy Taylor 2025-11-28 15:25:58 +00:00
parent 955e9177dd
commit bb3950c19b
4 changed files with 209 additions and 2 deletions

View file

@ -1,3 +1,20 @@
xlx db v2.3.10
Fix liveircddb page broken by CSP security headers
- "pgs/ircddb_proxy.php" (NEW)
* Added transparent proxy for live.ircddb.net to resolve CSP and mixed-content issues
* Proxies all requests through local server, rewriting URLs to maintain functionality
* Supports both HTTP and HTTPS dashboard deployments
* Defaults to live.ircddb.net:8080, configurable via $PageOptions['IRCDDB']['URL']
* Default page ircddblive5.html, configurable via $PageOptions['IRCDDB']['Page']
- "pgs/liveircddb.php"
* Changed iframe source from direct external URL to local proxy
- "pgs/config.inc.php"
* Added $PageOptions['IRCDDB']['Show'] option
* Added commented out optional override options for URL and Page
xlx db v2.3.9 xlx db v2.3.9
SECURITY UPDATE - Minor upgrade to further improve dashboard security SECURITY UPDATE - Minor upgrade to further improve dashboard security

View file

@ -16,7 +16,7 @@ $PageOptions = array();
$PageOptions['ContactEmail'] = 'your_email'; // Support E-Mail address $PageOptions['ContactEmail'] = 'your_email'; // Support E-Mail address
$PageOptions['DashboardVersion'] = '2.3.9'; // Dashboard Version $PageOptions['DashboardVersion'] = '2.3.10'; // Dashboard Version
$PageOptions['PageRefreshActive'] = true; // Activate automatic refresh $PageOptions['PageRefreshActive'] = true; // Activate automatic refresh
$PageOptions['PageRefreshDelay'] = '10000'; // Page refresh time in miliseconds $PageOptions['PageRefreshDelay'] = '10000'; // Page refresh time in miliseconds
@ -50,6 +50,10 @@ $PageOptions['MetaRobots'] = 'index,follow';
$PageOptions['UserPage']['ShowFilter'] = true; // Show Filter on Users page $PageOptions['UserPage']['ShowFilter'] = true; // Show Filter on Users page
$PageOptions['IRCDDB']['Show'] = true; // Show liveircddb menu option
// $PageOptions['IRCDDB']['URL'] = 'http://live.ircddb.net:8080'; // Optional: Override ircddb server URL
// $PageOptions['IRCDDB']['Page'] = 'ircddblive5.html'; // Optional: Override ircddb page
$Service['PIDFile'] = '/var/log/xlxd.pid'; $Service['PIDFile'] = '/var/log/xlxd.pid';
$Service['XMLFile'] = '/var/log/xlxd.xml'; $Service['XMLFile'] = '/var/log/xlxd.xml';

View file

@ -0,0 +1,186 @@
<?php
/**
* Transparent proxy for live.ircddb.net
*
* This proxy allows the ircddb live page to be embedded in the dashboard
* without CSP or mixed-content issues. It fetches content from the upstream
* server and rewrites URLs to route back through this proxy.
*/
// Load config to get any overrides
if (file_exists(__DIR__ . "/config.inc.php")) {
require_once(__DIR__ . "/config.inc.php");
}
if (file_exists(__DIR__ . "/../config.inc.php")) {
require_once(__DIR__ . "/../config.inc.php");
}
// Defaults - can be overridden in config.inc.php
$ircddbBaseUrl = $PageOptions['IRCDDB']['URL'] ?? 'http://live.ircddb.net:8080';
$ircddbDefaultPage = $PageOptions['IRCDDB']['Page'] ?? 'ircddblive5.html';
// Security: Remove trailing slash from base URL
$ircddbBaseUrl = rtrim($ircddbBaseUrl, '/');
// Get requested path, default to the main page
$path = $_GET['path'] ?? $ircddbDefaultPage;
// Security: Sanitize path - only allow alphanumeric, dots, hyphens, underscores
// This prevents directory traversal and other path-based attacks
if (!preg_match('/^[a-zA-Z0-9._-]+$/', $path)) {
http_response_code(400);
die('Invalid path');
}
// Security: Block potentially dangerous file extensions
$blockedExtensions = ['php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar'];
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
if (in_array($extension, $blockedExtensions)) {
http_response_code(403);
die('Forbidden');
}
// Build the upstream URL
$upstreamUrl = $ircddbBaseUrl . '/' . $path;
// Forward query parameters (except 'path')
$queryParams = $_GET;
unset($queryParams['path']);
if (!empty($queryParams)) {
$upstreamUrl .= '?' . http_build_query($queryParams);
}
// Fetch content - try cURL first, fall back to file_get_contents
$body = false;
$contentType = 'text/html';
$httpCode = 200;
$error = '';
if (function_exists('curl_init')) {
// Use cURL if available
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $upstreamUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_TIMEOUT => 15,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_USERAGENT => 'XLX-Dashboard-Proxy/1.0',
CURLOPT_HEADER => true,
]);
$response = curl_exec($ch);
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
curl_close($ch);
if ($response !== false && $httpCode > 0) {
// Split headers and body
$headers = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
// Parse Content-Type from headers
$headerLines = explode("\r\n", $headers);
foreach ($headerLines as $header) {
if (stripos($header, 'Content-Type:') === 0) {
$contentType = trim(substr($header, 13));
break;
}
}
}
} else {
// Fallback to file_get_contents with stream context
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "User-Agent: XLX-Dashboard-Proxy/1.0\r\n",
'timeout' => 15,
'follow_location' => true,
'max_redirects' => 3,
]
]);
$body = @file_get_contents($upstreamUrl, false, $context);
if ($body !== false && isset($http_response_header)) {
// Parse response headers
foreach ($http_response_header as $header) {
// Get HTTP status code
if (preg_match('/^HTTP\/\d+\.\d+\s+(\d+)/', $header, $matches)) {
$httpCode = (int)$matches[1];
}
// Get Content-Type
if (stripos($header, 'Content-Type:') === 0) {
$contentType = trim(substr($header, 13));
}
}
} else {
$error = 'Failed to fetch upstream content';
}
}
// Handle errors
if ($body === false) {
http_response_code(502);
die('Upstream server unavailable: ' . htmlspecialchars($error));
}
// Forward the HTTP status code
http_response_code($httpCode);
// Set content type
header('Content-Type: ' . $contentType);
// Determine if we need to rewrite URLs in this content
$rewriteContent = (
stripos($contentType, 'text/html') !== false ||
stripos($contentType, 'text/javascript') !== false ||
stripos($contentType, 'application/javascript') !== false ||
stripos($contentType, 'application/x-javascript') !== false
);
if ($rewriteContent && !empty($body)) {
// Rewrite relative URLs to go through this proxy
// Handle src="file.js", href="file.css", url("file"), etc.
// Rewrite src attributes
$body = preg_replace(
'/src\s*=\s*"([^"\/][^"]*)"/',
'src="ircddb_proxy.php?path=$1"',
$body
);
$body = preg_replace(
"/src\s*=\s*'([^'\/][^']*)'/",
"src='ircddb_proxy.php?path=\$1'",
$body
);
// Rewrite href attributes (for CSS, etc.)
$body = preg_replace(
'/href\s*=\s*"([^"\/][^"]*\.(css|ico))"/',
'href="ircddb_proxy.php?path=$1"',
$body
);
$body = preg_replace(
"/href\s*=\s*'([^'\/][^']*\.(css|ico))'/",
"href='ircddb_proxy.php?path=\$1'",
$body
);
// Rewrite AJAX calls in JavaScript (the jj.yaws or jj3.yaws polling)
// The original livelog.js uses: url: "jj.yaws", data: "p=" + lastNum
// jQuery appends data as query string, so we need: url: "ircddb_proxy.php?path=jj.yaws"
// Then jQuery will make it: ircddb_proxy.php?path=jj.yaws&p=123
$body = preg_replace(
'/url:\s*"(jj[0-9]*\.yaws)"/',
'url: "ircddb_proxy.php?path=$1"',
$body
);
}
// Output the content
echo $body;

View file

@ -1,4 +1,4 @@
<div width="100%" style="text-align:center;"> <div width="100%" style="text-align:center;">
<iframe width="1250" height="800" src="http://live.ircddb.net:8080/ircddblive5.html" style="border:0px #000000 solid; margin-top:30px;" align="center"></iframe> <iframe width="1250" height="800" src="./pgs/ircddb_proxy.php" style="border:0px #000000 solid; margin-top:30px; background-color:#ffffff;" align="center"></iframe>
</div> </div>