Revert "More XSS and security fixes, this time for the OG Dashboard"

This commit is contained in:
LX1IQ 2025-10-21 09:42:28 +02:00 committed by GitHub
parent b7d17ee3b3
commit 7356127c85
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 329 additions and 689 deletions

91
dashboard/changes.txt Normal file → Executable file
View file

@ -1,94 +1,3 @@
xlx db v2.4.3
SECURITY UPDATE - All files updated to fix vulnerabilities
This release addresses multiple security vulnerabilities including XSS (Cross-Site Scripting),
command injection, path traversal, and SSRF (Server-Side Request Forgery) attacks.
Files Changed and Security Fixes:
- "functions.php"
* Added sanitize_output() and sanitize_attribute() helper functions for XSS prevention
* Added validate_callsign(), validate_module(), validate_protocol() input validation functions
* Replaced exec() call in GetSystemUptime() with secure file reading from /proc/uptime
* Added input validation and shell argument escaping to VNStatGetData()
* Added array bounds checking to ParseTime() to prevent errors on malformed input
- "class.interlink.php"
* Added input validation to SetName() - validates reflector name format
* Added input validation to SetAddress() - validates IP addresses and hostnames
* Added input validation to AddModule(), RemoveModule(), and HasModuleEnabled()
- "class.node.php"
* Added input validation in constructor for all parameters
* IP addresses validated with filter_var()
* Protocol validated against whitelist
* Callsign format validated with regex
* LinkedModule validated as single A-Z letter
- "class.parsexml.php"
* Added element name sanitization to prevent XML injection
- "class.peer.php"
* Added input validation in constructor for all parameters
* Same validation as class.node.php for consistency
- "class.reflector.php"
* Added path traversal prevention to SetXMLFile(), SetPIDFile(), and SetFlagFile()
* Added SSRF protection to CallHome() - blocks internal/private IP addresses
* Added validation to ReadInterlinkFile() to prevent path traversal
* Added XML entity encoding to PrepareInterlinkXML() and PrepareReflectorXML()
* Added URL validation to SetCallingHome()
* Added missing InterlinkCount(), GetInterlink(), and IsInterlinked() methods
- "class.station.php"
* Added input validation in constructor for all parameters
* Callsign format validation
* Module validation
- "modules.php"
* All output wrapped with sanitize_output() to prevent XSS
- "peers.php"
* All peer data output sanitized with sanitize_output() and sanitize_attribute()
* URL and callsign outputs properly escaped
- "reflectors.php"
* All XML element data sanitized before output
* Dashboard URLs and reflector names properly escaped
- "repeaters.php"
* Added input validation for filter parameters
* All node/repeater data sanitized before output
* Flag images and URLs properly escaped
* IP addresses sanitized
- "traffic.php"
* Added strict whitelist validation for interface parameter
* Interface names validated against configured list only
- "users.php"
* Added input validation for filter parameters
* All station/user data sanitized before output
* Callsigns, suffixes, and module names properly escaped
- "index.php"
* Added secure session configuration (HttpOnly, SameSite, Secure flags)
* Added security headers (X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Referrer-Policy)
* Added whitelist validation for 'show' parameter
* Added validation for 'do' and 'callhome' parameters
* All configuration values sanitized before output to HTML
* JavaScript injection prevented in page refresh code
* All meta tags properly escaped
Security Vulnerabilities Fixed:
- XSS (Cross-Site Scripting) - All user input and XML data now properly escaped
- Command Injection - Removed unsafe exec() calls, added shell argument escaping
- Path Traversal - File paths validated and restricted to expected directories
- SSRF (Server-Side Request Forgery) - CallHome validates URLs and blocks internal IPs
- Session Hijacking - Added HttpOnly, SameSite, and Secure cookie flags
- XML Injection - Element names sanitized, content stripped of tags
xlx db v2.4.1
you can now hide the liveircddb menu button, if you are running your db in https.

0
dashboard/css/layout.css Normal file → Executable file
View file

0
dashboard/favicon.ico Normal file → Executable file
View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

58
dashboard/index.php Normal file → Executable file
View file

@ -1,20 +1,7 @@
<?php
// Secure session configuration
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_samesite', 'Strict');
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
ini_set('session.cookie_secure', 1);
}
session_start();
// Security headers
header("X-Content-Type-Options: nosniff");
header("X-Frame-Options: SAMEORIGIN");
header("X-XSS-Protection: 1; mode=block");
header("Referrer-Policy: strict-origin-when-cross-origin");
if (file_exists("./pgs/functions.php")) { require_once("./pgs/functions.php"); } else { die("functions.php does not exist."); }
if (file_exists("./pgs/config.inc.php")) { require_once("./pgs/config.inc.php"); } else { die("config.inc.php does not exist."); }
@ -25,27 +12,6 @@ if (!class_exists('Station')) require_once("./pgs/class.station.php");
if (!class_exists('Peer')) require_once("./pgs/class.peer.php");
if (!class_exists('Interlink')) require_once("./pgs/class.interlink.php");
// Validate 'show' parameter
$allowed_pages = ['users', 'repeaters', 'liveircddb', 'peers', 'modules', 'reflectors', 'traffic'];
if (!isset($_GET['show'])) {
$_GET['show'] = '';
} elseif (!in_array($_GET['show'], $allowed_pages, true)) {
$_GET['show'] = '';
}
// Validate 'do' parameter for filter resets
if (isset($_GET['do']) && $_GET['do'] !== 'resetfilter') {
unset($_GET['do']);
}
// Validate 'callhome' parameter
if (isset($_GET['callhome'])) {
$_GET['callhome'] = filter_var($_GET['callhome'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($_GET['callhome'] === null) {
unset($_GET['callhome']);
}
}
$Reflector = new xReflector();
$Reflector->SetFlagFile("./pgs/country.csv");
$Reflector->SetPIDFile($Service['PIDFile']);
@ -115,14 +81,14 @@ else {
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="description" content="<?php echo sanitize_attribute($PageOptions['MetaDescription']); ?>" />
<meta name="keywords" content="<?php echo sanitize_attribute($PageOptions['MetaKeywords']); ?>" />
<meta name="author" content="<?php echo sanitize_attribute($PageOptions['MetaAuthor']); ?>" />
<meta name="revisit" content="<?php echo sanitize_attribute($PageOptions['MetaRevisit']); ?>" />
<meta name="robots" content="<?php echo sanitize_attribute($PageOptions['MetaRobots']); ?>" />
<meta name="description" content="<?php echo $PageOptions['MetaDescription']; ?>" />
<meta name="keywords" content="<?php echo $PageOptions['MetaKeywords']; ?>" />
<meta name="author" content="<?php echo $PageOptions['MetaAuthor']; ?>" />
<meta name="revisit" content="<?php echo $PageOptions['MetaRevisit']; ?>" />
<meta name="robots" content="<?php echo $PageOptions['MetaAuthor']; ?>" />
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title><?php echo sanitize_output($Reflector->GetReflectorName()); ?> Reflector Dashboard</title>
<title><?php echo $Reflector->GetReflectorName(); ?> Reflector Dashboard</title>
<link rel="stylesheet" type="text/css" href="./css/layout.css">
<link rel="icon" href="./favicon.ico" type="image/vnd.microsoft.icon"><?php
@ -133,7 +99,7 @@ else {
var PageRefresh;
function ReloadPage() {
$.get("./index.php'.((!empty($_GET['show'])) ? '?show='.urlencode($_GET['show']) : '').'", function(data) {
$.get("./index.php'.(isset($_GET['show'])?'?show='.$_GET['show']:'').'", function(data) {
var BodyStart = data.indexOf("<bo"+"dy");
var BodyEnd = data.indexOf("</bo"+"dy>");
if ((BodyStart >= 0) && (BodyEnd > BodyStart)) {
@ -164,14 +130,14 @@ else {
<body>
<?php if (file_exists("./tracking.php")) { include_once("tracking.php"); }?>
<div id="top"><img src="./img/header.jpg" alt="XLX Multiprotocol Gateway Reflector" style="margin-top:15px;" />
<br />&nbsp;&nbsp;&nbsp;<?php echo sanitize_output($Reflector->GetReflectorName()); ?>&nbsp;v<?php echo sanitize_output($Reflector->GetVersion()); ?>&nbsp;-&nbsp;Dashboard v<?php echo sanitize_output($PageOptions['DashboardVersion']); ?>&nbsp;<?php echo sanitize_output($PageOptions['CustomTXT']); ?>&nbsp;&nbsp;/&nbsp;&nbsp;Service uptime: <span id="suptime"><?php echo FormatSeconds($Reflector->GetServiceUptime()); ?></span></div>
<br />&nbsp;&nbsp;&nbsp;<?php echo $Reflector->GetReflectorName(); ?>&nbsp;v<?php echo $Reflector->GetVersion(); ?>&nbsp;-&nbsp;Dashboard v<?php echo $PageOptions['DashboardVersion']; ?>&nbsp;<?php echo $PageOptions['CustomTXT']; ?>&nbsp;&nbsp;/&nbsp;&nbsp;Service uptime: <span id="suptime"><?php echo FormatSeconds($Reflector->GetServiceUptime()); ?></span></div>
<div id="menubar">
<div id="menu">
<table border="0">
<tr>
<td><a href="./index.php" class="menulink<?php if ($_GET['show'] == '') { echo 'active'; } ?>">Users / Modules</a></td>
<td><a href="./index.php?show=repeaters" class="menulink<?php if ($_GET['show'] == 'repeaters') { echo 'active'; } ?>">Repeaters / Nodes (<?php echo intval($Reflector->NodeCount()); ?>)</a></td>
<td><a href="./index.php?show=peers" class="menulink<?php if ($_GET['show'] == 'peers') { echo 'active'; } ?>">Peers (<?php echo intval($Reflector->PeerCount()); ?>)</a></td>
<td><a href="./index.php?show=repeaters" class="menulink<?php if ($_GET['show'] == 'repeaters') { echo 'active'; } ?>">Repeaters / Nodes (<?php echo $Reflector->NodeCount(); ?>)</a></td>
<td><a href="./index.php?show=peers" class="menulink<?php if ($_GET['show'] == 'peers') { echo 'active'; } ?>">Peers (<?php echo $Reflector->PeerCount(); ?>)</a></td>
<td><a href="./index.php?show=modules" class="menulink<?php if ($_GET['show'] == 'modules') { echo 'active'; } ?>">Modules list</a></td>
<td><a href="./index.php?show=reflectors" class="menulink<?php if ($_GET['show'] == 'reflectors') { echo 'active'; } ?>">Reflectors list</a></td>
<?php
@ -201,7 +167,7 @@ else {
if (!is_readable($CallingHome['HashFile']) && (!is_writeable($CallingHome['HashFile']))) {
echo '
<div class="error">
your private hash in '.sanitize_output($CallingHome['HashFile']).' could not be created, please check your config file and the permissions for the defined folder.
your private hash in '.$CallingHome['HashFile'].' could not be created, please check your config file and the permissions for the defined folder.
</div>';
}
}
@ -219,7 +185,7 @@ else {
?>
<div style="width:100%;text-align:center;margin-top:50px;"><a href="mailto:<?php echo sanitize_attribute($PageOptions['ContactEmail']); ?>" style="font-family:verdana;color:#000000;font-size:12pt;text-decoration:none;"><?php echo sanitize_output($PageOptions['ContactEmail']); ?></a></div>
<div style="width:100%;text-align:center;margin-top:50px;"><a href="mailto:<?php echo $PageOptions['ContactEmail']; ?>" style="font-family:verdana;color:#000000;font-size:12pt;text-decoration:none;"><?php echo $PageOptions['ContactEmail']; ?></a></div>
</div>

30
dashboard/pgs/class.interlink.php Normal file → Executable file
View file

@ -12,30 +12,16 @@ class Interlink {
$this->Modules = null;
}
public function SetName($RefName) {
$RefName = trim($RefName);
// Validate reflector name format (XLX + 3 alphanumeric)
if (preg_match('/^[A-Z0-9]{3,10}$/i', $RefName)) {
$this->ReflectorName = strtoupper($RefName);
}
}
public function SetAddress($RefAdd) {
$RefAdd = trim($RefAdd);
// Validate it's a valid hostname or IP
if (filter_var($RefAdd, FILTER_VALIDATE_IP) ||
filter_var($RefAdd, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
$this->ReflectorAddress = $RefAdd;
}
}
public function SetName($RefName) { $this->ReflectorName = trim($RefName); }
public function SetAddress($RefAdd) { $this->ReflectorAddress = trim($RefAdd); }
public function GetName() { return $this->ReflectorName; }
public function GetAddress() { return $this->ReflectorAddress; }
public function GetModules() { return $this->Modules; }
public function AddModule($Module) {
$Module = trim(strtoupper($Module));
$Module = trim($Module);
if (strlen($Module) != 1) return false;
if (!preg_match('/^[A-Z]$/', $Module)) return false; // Only A-Z
if (strpos($this->Modules, $Module) === false) {
$this->Modules .= $Module;
}
@ -43,20 +29,16 @@ class Interlink {
}
public function RemoveModule($Module) {
$Module = trim(strtoupper($Module));
$Module = trim($Module);
if (strlen($Module) != 1) return false;
if (!preg_match('/^[A-Z]$/', $Module)) return false; // Only A-Z
if (strpos($this->Modules, $Module) !== false) {
$this->Modules = str_replace($Module, '', $this->Modules);
$this->Modules = substr($this->Modules, 0, strpos($this->Modules, $Module)-1).substr($this->Modules, strpos($this->Modules, $Module)+1, strlen($this->Modules));
}
return true;
}
public function HasModuleEnabled($Module) {
$Module = trim(strtoupper($Module));
if (strlen($Module) != 1 || !preg_match('/^[A-Z]$/', $Module)) {
return false;
}
if (strlen($Module) != 1) return false;
return (strpos($this->Modules, $Module) !== false);
}

29
dashboard/pgs/class.node.php Normal file → Executable file
View file

@ -14,26 +14,19 @@ class Node {
public function __construct($Callsign, $IP, $LinkedModule, $Protocol, $ConnectTime, $LastHeardTime, $RandomID) {
// Validate and sanitize IP
$IP = trim($IP);
$this->IP = filter_var($IP, FILTER_VALIDATE_IP) ? $IP : '0.0.0.0';
// Validate protocol
$Protocol = trim($Protocol);
$allowed_protocols = ['DPlus', 'DExtra', 'DCS', 'DMR', 'YSF', 'DEXTRA', 'DPLUS', 'DMRMmdvm'];
$this->Protocol = in_array($Protocol, $allowed_protocols, true) ? $Protocol : 'Unknown';
$this->IP = $IP;
$this->Protocol = $Protocol;
$this->ConnectTime = ParseTime($ConnectTime);
$this->LastHeardTime = ParseTime($LastHeardTime);
// Sanitize callsign (remove excessive spaces, validate format)
$Callsign = trim(preg_replace('/\s+/', ' ', $Callsign));
$this->FullCallsign = str_replace(" ", "-", $Callsign);
$this->FullCallsign = trim(str_replace(" ", "-", $Callsign));
$this->FullCallsign = str_replace(" ", "-", $this->FullCallsign);
$this->FullCallsign = str_replace(" ", "-", $this->FullCallsign);
if (strpos($Callsign, " ") !== false) {
$this->Callsign = trim(substr($Callsign, 0, strpos($Callsign, " ")));
$this->Suffix = trim(substr($Callsign, strpos($Callsign, " ")));
$this->Suffix = trim(substr($Callsign, strpos($Callsign, " "), strlen($Callsign)));
$this->Prefix = strtoupper(trim(substr($Callsign, 0, 3)));
}
else {
@ -42,15 +35,7 @@ class Node {
$this->Prefix = "";
}
// Validate callsign format (basic check)
if (!preg_match('/^[A-Z0-9]{1,10}$/i', $this->Callsign)) {
$this->Callsign = 'INVALID';
}
// Validate LinkedModule (single letter A-Z)
$LinkedModule = trim(strtoupper($LinkedModule));
$this->LinkedModule = preg_match('/^[A-Z]$/', $LinkedModule) ? $LinkedModule : '';
$this->LinkedModule = trim($LinkedModule);
$this->RandomID = $RandomID;
}

14
dashboard/pgs/class.parsexml.php Normal file → Executable file
View file

@ -7,28 +7,18 @@ class ParseXML {
}
public function GetElement($InputString, $ElementName) {
// Sanitize element name to prevent XML injection
$ElementName = preg_replace('/[^a-zA-Z0-9_\-\s]/', '', $ElementName);
if (strpos($InputString, "<".$ElementName.">") === false) return false;
if (strpos($InputString, "</".$ElementName.">") === false) return false;
$Element = substr($InputString, strpos($InputString, "<".$ElementName.">")+strlen($ElementName)+2, strpos($InputString, "</".$ElementName.">")-strpos($InputString, "<".$ElementName.">")-strlen($ElementName)-2);
// Return raw content - sanitization happens at output time
return $Element;
}
public function GetAllElements($InputString, $ElementName) {
// Sanitize element name to prevent XML injection
$ElementName = preg_replace('/[^a-zA-Z0-9_\-\s]/', '', $ElementName);
$Elements = array();
while (strpos($InputString, $ElementName) !== false) {
$element = $this->GetElement($InputString, $ElementName);
if ($element !== false) {
$Elements[] = $element;
}
$Elements[] = $this->GetElement($InputString, $ElementName);
$InputString = substr($InputString, strpos($InputString, "</".$ElementName.">")+strlen($ElementName)+3, strlen($InputString));
}
return $Elements;

25
dashboard/pgs/class.peer.php Normal file → Executable file
View file

@ -11,29 +11,12 @@ class Peer {
public function __construct($Callsign, $IP, $LinkedModule, $Protocol, $ConnectTime, $LastHeardTime) {
// Validate and sanitize IP
$IP = trim($IP);
$this->IP = filter_var($IP, FILTER_VALIDATE_IP) ? $IP : '0.0.0.0';
// Validate protocol
$Protocol = trim($Protocol);
$allowed_protocols = ['DPlus', 'DExtra', 'DCS', 'DMR', 'YSF', 'DEXTRA', 'DPLUS', 'XLX'];
$this->Protocol = in_array($Protocol, $allowed_protocols, true) ? $Protocol : 'Unknown';
$this->IP = $IP;
$this->Protocol = $Protocol;
$this->ConnectTime = ParseTime($ConnectTime);
$this->LastHeardTime = ParseTime($LastHeardTime);
// Sanitize and validate callsign
$Callsign = trim($Callsign);
if (preg_match('/^[A-Z0-9]{3,10}$/i', $Callsign)) {
$this->Callsign = strtoupper($Callsign);
} else {
$this->Callsign = 'INVALID';
}
// Validate LinkedModule (single letter A-Z)
$LinkedModule = trim(strtoupper($LinkedModule));
$this->LinkedModule = preg_match('/^[A-Z]$/', $LinkedModule) ? $LinkedModule : '';
$this->Callsign = trim($Callsign);
$this->LinkedModule = trim($LinkedModule);
}

148
dashboard/pgs/class.reflector.php Normal file → Executable file
View file

@ -44,9 +44,7 @@ class xReflector {
# XLX alphanumeric naming...
$this->ServiceName = substr($this->XMLContent, strpos($this->XMLContent, "<XLX")+4, 3);
// Validate service name
if (!preg_match('/^[a-zA-Z0-9]{3}$/', $this->ServiceName)) {
if (preg_match('/[^a-zA-Z0-9]/', $this->ServiceName) == 1) {
$this->ServiceName = null;
return false;
}
@ -63,46 +61,25 @@ class xReflector {
$tmpNodes = $XML->GetAllElements($AllNodesString, "NODE");
for ($i=0;$i<count($tmpNodes);$i++) {
$Node = new Node(
$XML->GetElement($tmpNodes[$i], 'Callsign'),
$XML->GetElement($tmpNodes[$i], 'IP'),
$XML->GetElement($tmpNodes[$i], 'LinkedModule'),
$XML->GetElement($tmpNodes[$i], 'Protocol'),
$XML->GetElement($tmpNodes[$i], 'ConnectTime'),
$XML->GetElement($tmpNodes[$i], 'LastHeardTime'),
CreateCode(16)
);
$Node = new Node($XML->GetElement($tmpNodes[$i], 'Callsign'), $XML->GetElement($tmpNodes[$i], 'IP'), $XML->GetElement($tmpNodes[$i], 'LinkedModule'), $XML->GetElement($tmpNodes[$i], 'Protocol'), $XML->GetElement($tmpNodes[$i], 'ConnectTime'), $XML->GetElement($tmpNodes[$i], 'LastHeardTime'), CreateCode(16));
$this->AddNode($Node);
}
$AllStationsString = $XML->GetElement($this->XMLContent, $LinkedUsersName);
$tmpStations = $XML->GetAllElements($AllStationsString, "STATION");
for ($i=0;$i<count($tmpStations);$i++) {
$Station = new Station(
$XML->GetElement($tmpStations[$i], 'Callsign'),
$XML->GetElement($tmpStations[$i], 'Via node'),
$XML->GetElement($tmpStations[$i], 'Via peer'),
$XML->GetElement($tmpStations[$i], 'LastHeardTime'),
$XML->GetElement($tmpStations[$i], 'On module')
);
$Station = new Station($XML->GetElement($tmpStations[$i], 'Callsign'), $XML->GetElement($tmpStations[$i], 'Via node'), $XML->GetElement($tmpStations[$i], 'Via peer'), $XML->GetElement($tmpStations[$i], 'LastHeardTime'), $XML->GetElement($tmpStations[$i], 'On module'));
$this->AddStation($Station, false);
}
$AllPeersString = $XML->GetElement($this->XMLContent, $LinkedPeersName);
$tmpPeers = $XML->GetAllElements($AllPeersString, "PEER");
for ($i=0;$i<count($tmpPeers);$i++) {
$Peer = new Peer(
$XML->GetElement($tmpPeers[$i], 'Callsign'),
$XML->GetElement($tmpPeers[$i], 'IP'),
$XML->GetElement($tmpPeers[$i], 'LinkedModule'),
$XML->GetElement($tmpPeers[$i], 'Protocol'),
$XML->GetElement($tmpPeers[$i], 'ConnectTime'),
$XML->GetElement($tmpPeers[$i], 'LastHeardTime')
);
$Peer = new Peer($XML->GetElement($tmpPeers[$i], 'Callsign'), $XML->GetElement($tmpPeers[$i], 'IP'), $XML->GetElement($tmpPeers[$i], 'LinkedModule'), $XML->GetElement($tmpPeers[$i], 'Protocol'), $XML->GetElement($tmpPeers[$i], 'ConnectTime'), $XML->GetElement($tmpPeers[$i], 'LastHeardTime'));
$this->AddPeer($Peer, false);
}
$this->Version = strip_tags($XML->GetElement($this->XMLContent, "Version"));
$this->Version = $XML->GetElement($this->XMLContent, "Version");
}
}
@ -115,25 +92,16 @@ class xReflector {
}
public function SetXMLFile($XMLFile) {
// Prevent path traversal
$XMLFile = basename($XMLFile);
$XMLFile = '/var/log/' . $XMLFile; // Force it to expected directory
if (file_exists($XMLFile) && is_readable($XMLFile)) {
if (file_exists($XMLFile) && (is_readable($XMLFile))) {
$this->XMLFile = $XMLFile;
}
else {
error_log("XML File ".$XMLFile." does not exist or is not readable");
$this->XMLFile = null;
die("File ".$XMLFile." does not exist or is not readable");
$this->XMLContent = null;
}
}
public function SetPIDFile($ProcessIDFile) {
// Prevent path traversal
$ProcessIDFile = basename($ProcessIDFile);
$ProcessIDFile = '/var/log/' . $ProcessIDFile;
if (file_exists($ProcessIDFile)) {
$this->ProcessIDFile = $ProcessIDFile;
$this->ServiceUptime = time() - filectime($ProcessIDFile);
@ -149,14 +117,8 @@ class xReflector {
}
public function SetFlagFile($Flagfile) {
// Prevent path traversal
$realPath = realpath($Flagfile);
if ($realPath === false || strpos($realPath, '/dashboard/pgs/') === false) {
return false;
}
if (file_exists($realPath) && is_readable($realPath)) {
$this->Flagfile = $realPath;
if (file_exists($Flagfile) && (is_readable($Flagfile))) {
$this->Flagfile = $Flagfile;
return true;
}
return false;
@ -384,18 +346,6 @@ class xReflector {
if (!isset($CallingHomeVariables['OverrideIPAddress'])) { $CallingHomeVariables['OverrideIPAddress'] = false; }
if (!isset($CallingHomeVariables['InterlinkFile'])) { $CallingHomeVariables['InterlinkFile'] = ''; }
// Validate URLs
if (!empty($CallingHomeVariables['MyDashBoardURL'])) {
if (filter_var($CallingHomeVariables['MyDashBoardURL'], FILTER_VALIDATE_URL) === false) {
$CallingHomeVariables['MyDashBoardURL'] = '';
}
}
if (!empty($CallingHomeVariables['ServerURL'])) {
if (filter_var($CallingHomeVariables['ServerURL'], FILTER_VALIDATE_URL) === false) {
$CallingHomeVariables['ServerURL'] = '';
}
}
if (!file_exists($CallingHomeVariables['InterlinkFile'])) {
$this->Interlinkfile = '';
$this->Transferinterlink = false;
@ -420,31 +370,16 @@ class xReflector {
}
public function ReadInterlinkFile() {
if (empty($this->Interlinkfile)) {
return false;
}
// Prevent path traversal
$realPath = realpath($this->Interlinkfile);
if ($realPath === false || strpos($realPath, '/xlxd/') === false) {
error_log("ReadInterlinkFile blocked - invalid path");
return false;
}
if (file_exists($realPath) && is_readable($realPath)) {
if (file_exists($this->Interlinkfile) && (is_readable($this->Interlinkfile))) {
$this->Interlinks = array();
$this->InterlinkXML = "";
$Interlinkfilecontent = file($realPath);
$Interlinkfilecontent = file($this->Interlinkfile);
for ($i=0;$i<count($Interlinkfilecontent);$i++) {
if (substr($Interlinkfilecontent[$i], 0, 1) != '#') {
$Interlink = explode(" ", $Interlinkfilecontent[$i]);
$this->Interlinks[] = new Interlink();
if (isset($Interlink[0])) {
$this->Interlinks[count($this->Interlinks)-1]->SetName(trim($Interlink[0]));
}
if (isset($Interlink[1])) {
$this->Interlinks[count($this->Interlinks)-1]->SetAddress(trim($Interlink[1]));
}
if (isset($Interlink[0])) { $this->Interlinks[count($this->Interlinks)-1]->SetName(trim($Interlink[0])); }
if (isset($Interlink[1])) { $this->Interlinks[count($this->Interlinks)-1]->SetAddress(trim($Interlink[1])); }
if (isset($Interlink[2])) {
$Modules = str_split(trim($Interlink[2]), 1);
for ($j=0;$j<count($Modules);$j++) {
@ -464,9 +399,9 @@ class xReflector {
for ($i=0;$i<count($this->Interlinks);$i++) {
$xml .= '
<interlink>
<name>'.htmlspecialchars($this->Interlinks[$i]->GetName(), ENT_XML1, 'UTF-8').'</name>
<address>'.htmlspecialchars($this->Interlinks[$i]->GetAddress(), ENT_XML1, 'UTF-8').'</address>
<modules>'.htmlspecialchars($this->Interlinks[$i]->GetModules(), ENT_XML1, 'UTF-8').'</modules>
<name>'.$this->Interlinks[$i]->GetName().'</name>
<address>'.$this->Interlinks[$i]->GetAddress().'</address>
<modules>'.$this->Interlinks[$i]->GetModules().'</modules>
</interlink>';
}
$xml .= '
@ -477,48 +412,27 @@ class xReflector {
public function PrepareReflectorXML() {
$this->ReflectorXML = '
<reflector>
<name>'.htmlspecialchars($this->ReflectorName, ENT_XML1, 'UTF-8').'</name>
<uptime>'.intval($this->ServiceUptime).'</uptime>
<hash>'.htmlspecialchars($this->CallingHomeHash, ENT_XML1, 'UTF-8').'</hash>
<url>'.htmlspecialchars($this->CallingHomeDashboardURL, ENT_XML1, 'UTF-8').'</url>
<country>'.htmlspecialchars($this->CallingHomeCountry, ENT_XML1, 'UTF-8').'</country>
<comment>'.htmlspecialchars($this->CallingHomeComment, ENT_XML1, 'UTF-8').'</comment>
<ip>'.htmlspecialchars($this->CallingHomeOverrideIP, ENT_XML1, 'UTF-8').'</ip>
<reflectorversion>'.htmlspecialchars($this->Version, ENT_XML1, 'UTF-8').'</reflectorversion>
<name>'.$this->ReflectorName.'</name>
<uptime>'.$this->ServiceUptime.'</uptime>
<hash>'.$this->CallingHomeHash.'</hash>
<url>'.$this->CallingHomeDashboardURL.'</url>
<country>'.$this->CallingHomeCountry.'</country>
<comment>'.$this->CallingHomeComment.'</comment>
<ip>'.$this->CallingHomeOverrideIP.'</ip>
<reflectorversion>'.$this->Version.'</reflectorversion>
</reflector>';
}
public function CallHome() {
// Validate ServerURL is not localhost/internal IP
$parsed = parse_url($this->CallingHomeServerURL);
if (!isset($parsed['host'])) {
error_log("CallHome failed - invalid URL");
return false;
}
$ip = gethostbyname($parsed['host']);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
error_log("CallHome blocked - internal/private IP detected");
return false;
}
// Sanitize all data being sent
$xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<query>CallingHome</query>'.$this->ReflectorXML.$this->InterlinkXML;
$p = @stream_context_create(array('http' => array(
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
$p = @stream_context_create(array('http' => array('header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query(array('xml' => $xml)),
'timeout' => 10
)));
'content' => http_build_query(array('xml' => $xml)) )));
$result = @file_get_contents($this->CallingHomeServerURL, false, $p);
if ($result === false) {
error_log("CallHome connection failed");
return false;
die("CONNECTION FAILED!");
}
return true;
}
public function InterlinkCount() {
@ -526,10 +440,8 @@ class xReflector {
}
public function GetInterlink($Index) {
if (isset($this->Interlinks[$Index])) {
return $this->Interlinks[$Index];
}
return false;
if (isset($this->Interlinks[$Index])) return $this->Interlinks[$Index];
return array();
}
public function IsInterlinked($Reflectorname) {
@ -546,5 +458,7 @@ class xReflector {
}
return -1;
}
}
?>

22
dashboard/pgs/class.station.php Normal file → Executable file
View file

@ -11,34 +11,20 @@ class Station {
private $OnModule;
public function __construct($Callsign, $Via, $Peer, $LastHeardTime, $OnModule) {
// Sanitize and validate callsign
$Callsign = trim($Callsign);
$this->Callsign = $Callsign;
// Sanitize Via and Peer
$this->Callsign = trim($Callsign);
$this->Via = trim($Via);
$this->Peer = trim($Peer);
$this->LastHeardTime = ParseTime($LastHeardTime);
if (strpos($Callsign, " / ") !== false) {
$this->Suffix = trim(substr($Callsign, strpos($Callsign, " / ")+3));
$this->Suffix = trim(substr($Callsign, strpos($Callsign, " / ")+3, strlen($Callsign)));
}
else {
$this->Suffix = "";
}
$tmp = explode(" ", $Callsign);
$this->CallsignOnly = isset($tmp[0]) ? trim($tmp[0]) : '';
// Validate callsign format
if (!empty($this->CallsignOnly) && !preg_match('/^[A-Z0-9]{1,10}$/i', $this->CallsignOnly)) {
$this->CallsignOnly = 'INVALID';
}
// Validate OnModule (single letter A-Z)
$OnModule = trim(strtoupper($OnModule));
$this->OnModule = preg_match('/^[A-Z]$/', $OnModule) ? $OnModule : '';
$this->CallsignOnly = $tmp[0];
$this->OnModule = $OnModule;
}
public function GetCallsign() { return $this->Callsign; }

3
dashboard/pgs/config.inc.php Normal file → Executable file
View file

@ -17,7 +17,7 @@ $VNStat = array();
$PageOptions['ContactEmail'] = 'your_email'; // Support E-Mail address
$PageOptions['DashboardVersion'] = '2.4.3'; // Dashboard Version
$PageOptions['DashboardVersion'] = '2.4.2'; // Dashboard Version
$PageOptions['PageRefreshActive'] = true; // Activate automatic refresh
$PageOptions['PageRefreshDelay'] = '10000'; // Page refresh time in miliseconds
@ -77,6 +77,7 @@ $VNStat['Binary'] = '/usr/bin/vnstat';
include an extra config file for people who dont like to mess with shipped config.ing.php
this makes updating dashboard from git a little bit easier
*/
if (file_exists("../config.inc.php")) {
include ("../config.inc.php");
}

0
dashboard/pgs/country.csv Normal file → Executable file
View file

83
dashboard/pgs/functions.php Normal file → Executable file
View file

@ -1,60 +1,17 @@
<?php
function sanitize_output($string) {
if ($string === null) return '';
return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
function sanitize_attribute($string) {
if ($string === null) return '';
return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
function validate_callsign($callsign) {
$callsign = trim($callsign);
if (preg_match('/^[A-Z0-9\-\/\s]{3,20}$/i', $callsign)) {
return strtoupper($callsign);
}
return '';
}
function validate_module($module) {
$module = trim(strtoupper($module));
if (preg_match('/^[A-Z]$/', $module)) {
return $module;
}
return '';
}
function validate_protocol($protocol) {
$allowed = ['DPlus', 'DExtra', 'DCS', 'DMR', 'YSF', 'DEXTRA', 'DPLUS', 'DMRMmdvm', 'XLX'];
return in_array(trim($protocol), $allowed, true) ? trim($protocol) : '';
}
function GetSystemUptime() {
if (file_exists('/proc/uptime')) {
$uptime = @file_get_contents('/proc/uptime');
if ($uptime !== false) {
$parts = explode(' ', $uptime);
return isset($parts[0]) ? (int)$parts[0] : 0;
}
}
return 0;
$out = exec("uptime");
return substr($out, 0, strpos($out, ","));
}
function Debug($message) {
if (defined('DEBUG_MODE') && DEBUG_MODE === true) {
echo '<br><hr><pre>';
print_r($message); // Don't sanitize here as it's debug only
print_r($message);
echo '</pre><hr><br>';
}
}
function ParseTime($Input) {
if (empty($Input) || !is_string($Input)) {
return false;
}
$Input = strip_tags($Input); // Remove any HTML tags
if (strpos($Input, "<") !== false) {
$Input = substr($Input, 0, strpos($Input, "<"));
@ -62,29 +19,12 @@ function ParseTime($Input) {
// Tuesday Tue Nov 17 14:23:22 2015
$tmp = explode(" ", $Input);
// Add bounds checking
if (count($tmp) < 6) {
return false;
}
if (strlen(trim($tmp[3])) == 0) {
unset($tmp[3]);
$tmp = array_values($tmp);
}
// Check array indices exist after potential unset
if (!isset($tmp[4]) || !isset($tmp[2]) || !isset($tmp[3]) || !isset($tmp[5])) {
return false;
}
$tmp1 = explode(":", $tmp[4]);
// Check time parts exist
if (count($tmp1) < 3) {
return false;
}
$month = "";
switch (strtolower($tmp[2])) {
case 'jan' : $month = 1; break;
@ -102,6 +42,7 @@ function ParseTime($Input) {
default : $month = 1;
}
return @mktime($tmp1[0], $tmp1[1], $tmp1[2], $month, $tmp[3], $tmp[5]);
}
function FormatSeconds($seconds) {
@ -129,23 +70,9 @@ function VNStatLocalize($str) {
}
function VNStatGetData($iface, $vnstat_bin) {
// Validate interface name (only allow alphanumeric, dash, underscore)
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $iface)) {
return null;
}
// Validate vnstat binary path
if (!file_exists($vnstat_bin) || !is_executable($vnstat_bin)) {
return null;
}
// Escape shell arguments
$iface_escaped = escapeshellarg($iface);
$vnstat_bin_escaped = escapeshellarg($vnstat_bin);
$vnstat_data = array();
$fd = @popen("$vnstat_bin_escaped --dumpdb -i $iface_escaped", "r");
$fd = @popen("$vnstat_bin --dumpdb -i $iface", "r");
if (is_resource($fd)) {
$buffer = '';
while (!feof($fd)) {

0
dashboard/pgs/liveccs.php Normal file → Executable file
View file

0
dashboard/pgs/liveircddb.php Normal file → Executable file
View file

View file

@ -32,17 +32,17 @@ for ($i = 1; $i <= $NumberOfModules; $i++) {
echo '
<tr height="30" bgcolor="'.$odd.'" onMouseOver="this.bgColor=\'#FFFFCA\';" onMouseOut="this.bgColor=\''.$odd.'\';">
<td align="center">'. sanitize_output($module) .'</td>
<td align="center">'. sanitize_output(empty($PageOptions['ModuleNames'][$module]) ? '-' : $PageOptions['ModuleNames'][$module]) .'</td>
<td align="center">'. $module .'</td>
<td align="center">'. (empty($PageOptions['ModuleNames'][$module]) ? '-' : $PageOptions['ModuleNames'][$module]) .'</td>
<td align="center">'. count($Reflector->GetNodesInModulesByID($module)) .'</td>
<td align="center">'. sanitize_output('REF' . $ReflectorNumber . $module . 'L') .'</td>
<td align="center">'. sanitize_output(is_numeric($ReflectorNumber) ? '*' . sprintf('%01d',$ReflectorNumber) . (($i<=4)?$module:sprintf('%02d',$i)) : '-') .'</td>
<td align="center">'. sanitize_output('XRF' . $ReflectorNumber . $module . 'L') .'</td>
<td align="center">'. sanitize_output(is_numeric($ReflectorNumber) ? 'B' . sprintf('%01d',$ReflectorNumber) . (($i<=4)?$module:sprintf('%02d',$i)) : '-') .'</td>
<td align="center">'. sanitize_output('DCS' . $ReflectorNumber . $module . 'L') .'</td>
<td align="center">'. sanitize_output(is_numeric($ReflectorNumber) ? 'D' . sprintf('%01d',$ReflectorNumber) . (($i<=4)?$module:sprintf('%02d',$i)) : '-') .'</td>
<td align="center">'. sanitize_output(4000+$i) .'</td>
<td align="center">'. sanitize_output(9+$i) .'</td>
<td align="center">'. 'REF' . $ReflectorNumber . $module . 'L' .'</td>
<td align="center">'. (is_numeric($ReflectorNumber) ? '*' . sprintf('%01d',$ReflectorNumber) . (($i<=4)?$module:sprintf('%02d',$i)) : '-') .'</td>
<td align="center">'. 'XRF' . $ReflectorNumber . $module . 'L' .'</td>
<td align="center">'. (is_numeric($ReflectorNumber) ? 'B' . sprintf('%01d',$ReflectorNumber) . (($i<=4)?$module:sprintf('%02d',$i)) : '-') .'</td>
<td align="center">'. 'DCS' . $ReflectorNumber . $module . 'L' .'</td>
<td align="center">'. (is_numeric($ReflectorNumber) ? 'D' . sprintf('%01d',$ReflectorNumber) . (($i<=4)?$module:sprintf('%02d',$i)) : '-') .'</td>
<td align="center">'. (4000+$i) .'</td>
<td align="center">'. (9+$i) .'</td>
</tr>';
}

20
dashboard/pgs/peers.php Normal file → Executable file
View file

@ -54,26 +54,26 @@ for ($i=0;$i<$Reflector->PeerCount();$i++) {
}
if ($Result && (trim($URL) != "")) {
echo '
<td><a href="'.sanitize_attribute($URL).'" target="_blank" class="listinglink" title="Visit the Dashboard of&nbsp;'.sanitize_attribute($Name).'" style="text-decoration:none;color:#000000;">'.sanitize_output($Name).'</a></td>';
<td><a href="'.$URL.'" target="_blank" class="listinglink" title="Visit the Dashboard of&nbsp;'.$Name.'" style="text-decoration:none;color:#000000;">'.$Name.'</a></td>';
} else {
echo '
<td>'.sanitize_output($Name).'</td>';
<td>'.$Name.'</td>';
}
echo '
<td>'.sanitize_output(date("d.m.Y H:i", $Reflector->Peers[$i]->GetLastHeardTime())).'</td>
<td>'.sanitize_output(FormatSeconds(time()-$Reflector->Peers[$i]->GetConnectTime())).' s</td>
<td align="center">'.sanitize_output($Reflector->Peers[$i]->GetProtocol()).'</td>
<td align="center">'.sanitize_output($Reflector->Peers[$i]->GetLinkedModule()).'</td>';
<td>'.date("d.m.Y H:i", $Reflector->Peers[$i]->GetLastHeardTime()).'</td>
<td>'.FormatSeconds(time()-$Reflector->Peers[$i]->GetConnectTime()).' s</td>
<td align="center">'.$Reflector->Peers[$i]->GetProtocol().'</td>
<td align="center">'.$Reflector->Peers[$i]->GetLinkedModule().'</td>';
if ($PageOptions['PeerPage']['IPModus'] != 'HideIP') {
echo '
<td>';
$Bytes = explode(".", $Reflector->Peers[$i]->GetIP());
if ($Bytes !== false && count($Bytes) == 4) {
switch ($PageOptions['PeerPage']['IPModus']) {
case 'ShowLast1ByteOfIP' : echo sanitize_output($PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$Bytes[3]); break;
case 'ShowLast2ByteOfIP' : echo sanitize_output($PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$Bytes[2].'.'.$Bytes[3]); break;
case 'ShowLast3ByteOfIP' : echo sanitize_output($PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$Bytes[1].'.'.$Bytes[2].'.'.$Bytes[3]); break;
default : echo sanitize_output($Reflector->Peers[$i]->GetIP());
case 'ShowLast1ByteOfIP' : echo $PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$Bytes[3]; break;
case 'ShowLast2ByteOfIP' : echo $PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$Bytes[2].'.'.$Bytes[3]; break;
case 'ShowLast3ByteOfIP' : echo $PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$Bytes[1].'.'.$Bytes[2].'.'.$Bytes[3]; break;
default : echo $Reflector->Peers[$i]->GetIP();
}
}
echo '</td>';

10
dashboard/pgs/reflectors.php Normal file → Executable file
View file

@ -30,11 +30,11 @@ $odd = "";
for ($i=0;$i<count($Reflectors);$i++) {
$NAME = sanitize_output($XML->GetElement($Reflectors[$i], "name"));
$COUNTRY = sanitize_output($XML->GetElement($Reflectors[$i], "country"));
$LASTCONTACT = intval($XML->GetElement($Reflectors[$i], "lastcontact"));
$COMMENT = sanitize_output($XML->GetElement($Reflectors[$i], "comment"));
$DASHBOARDURL = sanitize_attribute($XML->GetElement($Reflectors[$i], "dashboardurl"));
$NAME = $XML->GetElement($Reflectors[$i], "name");
$COUNTRY = $XML->GetElement($Reflectors[$i], "country");
$LASTCONTACT = $XML->GetElement($Reflectors[$i], "lastcontact");
$COMMENT = $XML->GetElement($Reflectors[$i], "comment");
$DASHBOARDURL = $XML->GetElement($Reflectors[$i], "dashboardurl");
if ($odd == "#FFFFFF") { $odd = "#F1FAFA"; } else { $odd = "#FFFFFF"; }

47
dashboard/pgs/repeaters.php Normal file → Executable file
View file

@ -1,14 +1,15 @@
<?php
// Validate filter inputs
if (isset($_SESSION['FilterCallSign']) && $_SESSION['FilterCallSign'] !== null) {
$_SESSION['FilterCallSign'] = preg_replace('/[^A-Z0-9\*\-\/\s]/i', '', $_SESSION['FilterCallSign']);
if (!isset($_SESSION['FilterCallSign'])) {
$_SESSION['FilterCallSign'] = null;
}
if (isset($_SESSION['FilterProtocol']) && $_SESSION['FilterProtocol'] !== null) {
$_SESSION['FilterProtocol'] = validate_protocol($_SESSION['FilterProtocol']);
if (!isset($_SESSION['FilterProtocol'])) {
$_SESSION['FilterProtocol'] = null;
}
if (isset($_SESSION['FilterModule']) && $_SESSION['FilterModule'] !== null) {
$_SESSION['FilterModule'] = validate_module($_SESSION['FilterModule']);
if (!isset($_SESSION['FilterModule'])) {
$_SESSION['FilterModule'] = null;
}
if (isset($_POST['do'])) {
@ -74,7 +75,7 @@ if ($PageOptions['UserPage']['ShowFilter']) {
<td align="left">
<form name="frmFilterCallSign" method="post" action="./index.php?show=repeaters">
<input type="hidden" name="do" value="SetFilter" />
<input type="text" class="FilterField" value="'.sanitize_attribute($_SESSION['FilterCallSign']).'" name="txtSetCallsignFilter" placeholder="Callsign" onfocus="SuspendPageRefresh();" onblur="setTimeout(ReloadPage, '.$PageOptions['PageRefreshDelay'].');" />
<input type="text" class="FilterField" value="'.$_SESSION['FilterCallSign'].'" name="txtSetCallsignFilter" placeholder="Callsign" onfocus="SuspendPageRefresh();" onblur="setTimeout(ReloadPage, '.$PageOptions['PageRefreshDelay'].');" />
<input type="submit" value="Apply" class="FilterSubmit" />
</form>
</td>';
@ -86,14 +87,14 @@ if ($PageOptions['UserPage']['ShowFilter']) {
<td align="right" style="padding-right:3px;">
<form name="frmFilterProtocol" method="post" action="./index.php?show=repeaters">
<input type="hidden" name="do" value="SetFilter" />
<input type="text" class="FilterField" value="'.sanitize_attribute($_SESSION['FilterProtocol']).'" name="txtSetProtocolFilter" placeholder="Protocol" onfocus="SuspendPageRefresh();" onblur="setTimeout(ReloadPage, '.$PageOptions['PageRefreshDelay'].');" />
<input type="text" class="FilterField" value="'.$_SESSION['FilterProtocol'].'" name="txtSetProtocolFilter" placeholder="Protocol" onfocus="SuspendPageRefresh();" onblur="setTimeout(ReloadPage, '.$PageOptions['PageRefreshDelay'].');" />
<input type="submit" value="Apply" class="FilterSubmit" />
</form>
</td>
<td align="right" style="padding-right:3px;">
<form name="frmFilterModule" method="post" action="./index.php?show=repeaters">
<input type="hidden" name="do" value="SetFilter" />
<input type="text" class="FilterField" value="'.sanitize_attribute($_SESSION['FilterModule']).'" name="txtSetModuleFilter" placeholder="Module" onfocus="SuspendPageRefresh();" onblur="setTimeout(ReloadPage, '.$PageOptions['PageRefreshDelay'].');" />
<input type="text" class="FilterField" value="'.$_SESSION['FilterModule'].'" name="txtSetModuleFilter" placeholder="Module" onfocus="SuspendPageRefresh();" onblur="setTimeout(ReloadPage, '.$PageOptions['PageRefreshDelay'].');" />
<input type="submit" value="Apply" class="FilterSubmit" />
</form>
</td>
@ -160,13 +161,13 @@ for ($i=0;$i<$Reflector->NodeCount();$i++) {
<td align="center">';
list ($Flag, $Name) = $Reflector->GetFlag($Reflector->Nodes[$i]->GetCallSign());
if (file_exists("./img/flags/".$Flag.".png")) {
echo '<a href="#" class="tip"><img src="./img/flags/'.sanitize_attribute($Flag).'.png" height="15" alt="'.sanitize_attribute($Name).'" /><span>'.sanitize_output($Name).'</span></a>';
echo '<a href="#" class="tip"><img src="./img/flags/'.$Flag.'.png" height="15" alt="'.$Name.'" /><span>'.$Name.'</span></a>';
}
echo '</td>
<td><a href="http://www.aprs.fi/'.sanitize_attribute($Reflector->Nodes[$i]->GetCallSign());
if ($Reflector->Nodes[$i]->GetSuffix() != "") echo '-'.sanitize_attribute($Reflector->Nodes[$i]->GetSuffix());
echo '" class="pl" target="_blank">'.sanitize_output($Reflector->Nodes[$i]->GetCallSign());
if ($Reflector->Nodes[$i]->GetSuffix() != "") { echo '-'.sanitize_output($Reflector->Nodes[$i]->GetSuffix()); }
<td><a href="http://www.aprs.fi/'.$Reflector->Nodes[$i]->GetCallSign();
if ($Reflector->Nodes[$i]->GetSuffix() != "") echo '-'.$Reflector->Nodes[$i]->GetSuffix();
echo '" class="pl" target="_blank">'.$Reflector->Nodes[$i]->GetCallSign();
if ($Reflector->Nodes[$i]->GetSuffix() != "") { echo '-'.$Reflector->Nodes[$i]->GetSuffix(); }
echo '</a></td>
<td>';
if (($Reflector->Nodes[$i]->GetPrefix() == 'REF') || ($Reflector->Nodes[$i]->GetPrefix() == 'XRF')) {
@ -186,20 +187,20 @@ for ($i=0;$i<$Reflector->NodeCount();$i++) {
}
}
echo '</td>
<td>'.sanitize_output(date("d.m.Y H:i", $Reflector->Nodes[$i]->GetLastHeardTime())).'</td>
<td>'.sanitize_output(FormatSeconds(time()-$Reflector->Nodes[$i]->GetConnectTime())).' s</td>
<td>'.sanitize_output($Reflector->Nodes[$i]->GetProtocol()).'</td>
<td align="center">'.sanitize_output($Reflector->Nodes[$i]->GetLinkedModule()).'</td>';
<td>'.date("d.m.Y H:i", $Reflector->Nodes[$i]->GetLastHeardTime()).'</td>
<td>'.FormatSeconds(time()-$Reflector->Nodes[$i]->GetConnectTime()).' s</td>
<td>'.$Reflector->Nodes[$i]->GetProtocol().'</td>
<td align="center">'.$Reflector->Nodes[$i]->GetLinkedModule().'</td>';
if ($PageOptions['RepeatersPage']['IPModus'] != 'HideIP') {
echo '
<td>';
$Bytes = explode(".", $Reflector->Nodes[$i]->GetIP());
if ($Bytes !== false && count($Bytes) == 4) {
switch ($PageOptions['RepeatersPage']['IPModus']) {
case 'ShowLast1ByteOfIP' : echo sanitize_output($PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$Bytes[3]); break;
case 'ShowLast2ByteOfIP' : echo sanitize_output($PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$Bytes[2].'.'.$Bytes[3]); break;
case 'ShowLast3ByteOfIP' : echo sanitize_output($PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$Bytes[1].'.'.$Bytes[2].'.'.$Bytes[3]); break;
default : echo sanitize_output($Reflector->Nodes[$i]->GetIP());
case 'ShowLast1ByteOfIP' : echo $PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$Bytes[3]; break;
case 'ShowLast2ByteOfIP' : echo $PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$Bytes[2].'.'.$Bytes[3]; break;
case 'ShowLast3ByteOfIP' : echo $PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$Bytes[1].'.'.$Bytes[2].'.'.$Bytes[3]; break;
default : echo $Reflector->Nodes[$i]->GetIP();
}
}
echo '</td>';

View file

@ -8,17 +8,16 @@ if (!isset($_GET['iface'])) {
$_GET['iface'] = "";
}
}
// Validate interface name against whitelist
if (!empty($_GET['iface'])) {
$valid = false;
for ($i = 0; $i < count($VNStat['Interfaces']); $i++) {
if ($_GET['iface'] === $VNStat['Interfaces'][$i]['Address']) {
$valid = true;
break;
else {
$f = false;
$i = 0;
while ($i < count($VNStat['Interfaces']) && (!$f)) {
if ($_GET['iface'] == $VNStat['Interfaces'][$i]['Address']) {
$f = true;
}
$i++;
}
if (!$valid) {
if (!$f) {
$_GET['iface'] = "";
}
}
@ -34,8 +33,8 @@ if (!empty($_GET['iface'])) {
<td bgcolor="#F1FAFA" align="left" valign="top" style="padding-left:5px;"><?php
for ($i=0;$i<count($VNStat['Interfaces']);$i++) {
echo '<a href="./index.php?show=traffic&iface='.sanitize_attribute($VNStat['Interfaces'][$i]['Address']).'" class="listinglink">'.sanitize_output($VNStat['Interfaces'][$i]['Name']).'</a>';
if ($i < count($VNStat['Interfaces'])-1) {
echo '<a href="./index.php?show=traffic&iface='.$VNStat['Interfaces'][$i]['Address'].'" class="listinglink">'.$VNStat['Interfaces'][$i]['Name'].'</a>';
if ($i < count($VNStat['Interfaces'])) {
echo '<br />';
}
}

39
dashboard/pgs/users.php Normal file → Executable file
View file

@ -45,13 +45,6 @@ if (isset($_GET['do'])) {
}
}
// Validate filter inputs
if (isset($_SESSION['FilterCallSign']) && $_SESSION['FilterCallSign'] !== null) {
$_SESSION['FilterCallSign'] = preg_replace('/[^A-Z0-9\*\-\/\s]/i', '', $_SESSION['FilterCallSign']);
}
if (isset($_SESSION['FilterModule']) && $_SESSION['FilterModule'] !== null) {
$_SESSION['FilterModule'] = validate_module($_SESSION['FilterModule']);
}
?>
<table border="0">
@ -70,7 +63,7 @@ if ($PageOptions['UserPage']['ShowFilter']) {
<td align="left">
<form name="frmFilterCallSign" method="post" action="./index.php">
<input type="hidden" name="do" value="SetFilter" />
<input type="text" class="FilterField" value="'.sanitize_attribute($_SESSION['FilterCallSign']).'" name="txtSetCallsignFilter" placeholder="Callsign" onfocus="SuspendPageRefresh();" onblur="setTimeout(ReloadPage, '.$PageOptions['PageRefreshDelay'].');" />
<input type="text" class="FilterField" value="'.$_SESSION['FilterCallSign'].'" name="txtSetCallsignFilter" placeholder="Callsign" onfocus="SuspendPageRefresh();" onblur="setTimeout(ReloadPage, '.$PageOptions['PageRefreshDelay'].');" />
<input type="submit" value="Apply" class="FilterSubmit" />
</form>
</td>';
@ -82,7 +75,7 @@ if ($PageOptions['UserPage']['ShowFilter']) {
<td align="right" style="padding-right:3px;">
<form name="frmFilterModule" method="post" action="./index.php">
<input type="hidden" name="do" value="SetFilter" />
<input type="text" class="FilterField" value="'.sanitize_attribute($_SESSION['FilterModule']).'" name="txtSetModuleFilter" placeholder="Module" onfocus="SuspendPageRefresh();" onblur="setTimeout(ReloadPage, '.$PageOptions['PageRefreshDelay'].');" />
<input type="text" class="FilterField" value="'.$_SESSION['FilterModule'].'" name="txtSetModuleFilter" placeholder="Module" onfocus="SuspendPageRefresh();" onblur="setTimeout(ReloadPage, '.$PageOptions['PageRefreshDelay'].');" />
<input type="submit" value="Apply" class="FilterSubmit" />
</form>
</td>
@ -138,24 +131,25 @@ for ($i=0;$i<$Reflector->StationCount();$i++) {
echo ($i+1);
}
echo '</td>
<td align="center" width="60">';
list ($Flag, $Name) = $Reflector->GetFlag($Reflector->Stations[$i]->GetCallSign());
if (file_exists("./img/flags/".$Flag.".png")) {
echo '<a href="#" class="tip"><img src="./img/flags/'.sanitize_attribute($Flag).'.png" height="15" alt="'.sanitize_attribute($Name).'" /><span>'.sanitize_output($Name).'</span></a>';
echo '<a href="#" class="tip"><img src="./img/flags/'.$Flag.'.png" height="15" alt="'.$Name.'" /><span>'.$Name.'</span></a>';
}
echo '</td>
<td width="75"><a href="https://www.qrz.com/db/'.sanitize_attribute($Reflector->Stations[$i]->GetCallsignOnly()).'" class="pl" target="_blank">'.sanitize_output($Reflector->Stations[$i]->GetCallsignOnly()).'</a></td>
<td width="60">'.sanitize_output($Reflector->Stations[$i]->GetSuffix()).'</td>
<td width="50" align="center"><a href="http://www.aprs.fi/'.sanitize_attribute($Reflector->Stations[$i]->GetCallsignOnly()).'" class="pl" target="_blank"><img src="./img/sat.png" /></a></td>
<td width="150">'.sanitize_output($Reflector->Stations[$i]->GetVia());
<td width="75"><a href="https://www.qrz.com/db/'.$Reflector->Stations[$i]->GetCallsignOnly().'" class="pl" target="_blank">'.$Reflector->Stations[$i]->GetCallsignOnly().'</a></td>
<td width="60">'.$Reflector->Stations[$i]->GetSuffix().'</td>
<td width="50" align="center"><a href="http://www.aprs.fi/'.$Reflector->Stations[$i]->GetCallsignOnly().'" class="pl" target="_blank"><img src="./img/sat.png" /></a></td>
<td width="150">'.$Reflector->Stations[$i]->GetVia();
if ($Reflector->Stations[$i]->GetPeer() != $Reflector->GetReflectorName()) {
echo ' / '.sanitize_output($Reflector->Stations[$i]->GetPeer());
echo ' / '.$Reflector->Stations[$i]->GetPeer();
}
echo '</td>
<td width="150">'.sanitize_output(@date("d.m.Y H:i", $Reflector->Stations[$i]->GetLastHeardTime())).'</td>
<td align="center" width="30">'.sanitize_output($Reflector->Stations[$i]->GetModule()).'</td>
<td width="150">'.@date("d.m.Y H:i", $Reflector->Stations[$i]->GetLastHeardTime()).'</td>
<td align="center" width="30">'.$Reflector->Stations[$i]->GetModule().'</td>
</tr>';
}
if ($i == $PageOptions['LastHeardPage']['LimitTo']) { $i = $Reflector->StationCount()+1; }
@ -183,15 +177,18 @@ for ($i=0;$i<count($Modules);$i++) {
if (isset($PageOptions['ModuleNames'][$Modules[$i]])) {
echo '
<th>'.sanitize_output($PageOptions['ModuleNames'][$Modules[$i]]);
<th>'.$PageOptions['ModuleNames'][$Modules[$i]];
if (trim($PageOptions['ModuleNames'][$Modules[$i]]) != "") {
echo '<br />';
}
echo sanitize_output($Modules[$i]).'</th>';
echo $Modules[$i].'</th>
';
}
else {
echo '
<th>'.sanitize_output($Modules[$i]).'</th>';
<th>'.$Modules[$i].'</th>';
}
}
@ -218,7 +215,7 @@ for ($i=0;$i<count($Modules);$i++) {
$Displayname = $Reflector->GetCallsignAndSuffixByID($Users[$j]);
echo '
<tr height="25" bgcolor="'.$odd.'" onMouseOver="this.bgColor=\'#FFFFCA\';" onMouseOut="this.bgColor=\''.$odd.'\';">
<td valign="top" style="border-bottom:1px #C1DAD7 solid;"><a href="http://www.aprs.fi/'.sanitize_attribute($Displayname).'" class="pl" target="_blank">'.sanitize_output($Displayname).'</a> </td>
<td valign="top" style="border-bottom:1px #C1DAD7 solid;"><a href="http://www.aprs.fi/'.$Displayname.'" class="pl" target="_blank">'.$Displayname.'</a> </td>
</tr>';
$UserCheckedArray[] = $Users[$j];
}