XSS Vulnerability Patches and Security Enhancements for Dashboard1

This commit is contained in:
Andy Taylor 2025-10-14 14:15:20 +01:00
parent 80821c25a3
commit ee3f5de1de
16 changed files with 701 additions and 330 deletions

View file

@ -1,3 +1,99 @@
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
* Added strip_tags() to remove HTML/XML from extracted content
- "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
- "config.inc.php"
* Secured external config file inclusion with path validation
* Added realpath() checks to prevent directory traversal
- "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 xlx db v2.4.1
you can now hide the liveircddb menu button, if you are running your db in https. you can now hide the liveircddb menu button, if you are running your db in https.

View file

@ -1,9 +1,22 @@
<?php <?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(); session_start();
if (file_exists("./pgs/functions.php")) { require_once("./pgs/functions.php"); } else { die("functions.php does not exist."); } // Security headers
if (file_exists("./pgs/config.inc.php")) { require_once("./pgs/config.inc.php"); } else { die("config.inc.php does not exist."); } 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."); }
if (!class_exists('ParseXML')) require_once("./pgs/class.parsexml.php"); if (!class_exists('ParseXML')) require_once("./pgs/class.parsexml.php");
if (!class_exists('Node')) require_once("./pgs/class.node.php"); if (!class_exists('Node')) require_once("./pgs/class.node.php");
@ -12,6 +25,27 @@ if (!class_exists('Station')) require_once("./pgs/class.station.php");
if (!class_exists('Peer')) require_once("./pgs/class.peer.php"); if (!class_exists('Peer')) require_once("./pgs/class.peer.php");
if (!class_exists('Interlink')) require_once("./pgs/class.interlink.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 = new xReflector();
$Reflector->SetFlagFile("./pgs/country.csv"); $Reflector->SetFlagFile("./pgs/country.csv");
$Reflector->SetPIDFile($Service['PIDFile']); $Reflector->SetPIDFile($Service['PIDFile']);
@ -81,14 +115,14 @@ else {
<html xmlns="http://www.w3.org/1999/xhtml"> <html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="description" content="<?php echo $PageOptions['MetaDescription']; ?>" /> <meta name="description" content="<?php echo sanitize_attribute($PageOptions['MetaDescription']); ?>" />
<meta name="keywords" content="<?php echo $PageOptions['MetaKeywords']; ?>" /> <meta name="keywords" content="<?php echo sanitize_attribute($PageOptions['MetaKeywords']); ?>" />
<meta name="author" content="<?php echo $PageOptions['MetaAuthor']; ?>" /> <meta name="author" content="<?php echo sanitize_attribute($PageOptions['MetaAuthor']); ?>" />
<meta name="revisit" content="<?php echo $PageOptions['MetaRevisit']; ?>" /> <meta name="revisit" content="<?php echo sanitize_attribute($PageOptions['MetaRevisit']); ?>" />
<meta name="robots" content="<?php echo $PageOptions['MetaAuthor']; ?>" /> <meta name="robots" content="<?php echo sanitize_attribute($PageOptions['MetaRobots']); ?>" />
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title><?php echo $Reflector->GetReflectorName(); ?> Reflector Dashboard</title> <title><?php echo sanitize_output($Reflector->GetReflectorName()); ?> Reflector Dashboard</title>
<link rel="stylesheet" type="text/css" href="./css/layout.css"> <link rel="stylesheet" type="text/css" href="./css/layout.css">
<link rel="icon" href="./favicon.ico" type="image/vnd.microsoft.icon"><?php <link rel="icon" href="./favicon.ico" type="image/vnd.microsoft.icon"><?php
@ -99,7 +133,7 @@ else {
var PageRefresh; var PageRefresh;
function ReloadPage() { function ReloadPage() {
$.get("./index.php'.(isset($_GET['show'])?'?show='.$_GET['show']:'').'", function(data) { $.get("./index.php'.((!empty($_GET['show'])) ? '?show='.urlencode($_GET['show']) : '').'", function(data) {
var BodyStart = data.indexOf("<bo"+"dy"); var BodyStart = data.indexOf("<bo"+"dy");
var BodyEnd = data.indexOf("</bo"+"dy>"); var BodyEnd = data.indexOf("</bo"+"dy>");
if ((BodyStart >= 0) && (BodyEnd > BodyStart)) { if ((BodyStart >= 0) && (BodyEnd > BodyStart)) {
@ -130,14 +164,14 @@ else {
<body> <body>
<?php if (file_exists("./tracking.php")) { include_once("tracking.php"); }?> <?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;" /> <div id="top"><img src="./img/header.jpg" alt="XLX Multiprotocol Gateway Reflector" style="margin-top:15px;" />
<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> <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>
<div id="menubar"> <div id="menubar">
<div id="menu"> <div id="menu">
<table border="0"> <table border="0">
<tr> <tr>
<td><a href="./index.php" class="menulink<?php if ($_GET['show'] == '') { echo 'active'; } ?>">Users / Modules</a></td> <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 $Reflector->NodeCount(); ?>)</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 $Reflector->PeerCount(); ?>)</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=modules" class="menulink<?php if ($_GET['show'] == 'modules') { echo 'active'; } ?>">Modules list</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> <td><a href="./index.php?show=reflectors" class="menulink<?php if ($_GET['show'] == 'reflectors') { echo 'active'; } ?>">Reflectors list</a></td>
<?php <?php
@ -167,7 +201,7 @@ else {
if (!is_readable($CallingHome['HashFile']) && (!is_writeable($CallingHome['HashFile']))) { if (!is_readable($CallingHome['HashFile']) && (!is_writeable($CallingHome['HashFile']))) {
echo ' echo '
<div class="error"> <div class="error">
your private hash in '.$CallingHome['HashFile'].' could not be created, please check your config file and the permissions for the defined folder. your private hash in '.sanitize_output($CallingHome['HashFile']).' could not be created, please check your config file and the permissions for the defined folder.
</div>'; </div>';
} }
} }
@ -185,7 +219,7 @@ else {
?> ?>
<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 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> </div>

View file

@ -12,34 +12,52 @@ class Interlink {
$this->Modules = null; $this->Modules = null;
} }
public function SetName($RefName) { $this->ReflectorName = trim($RefName); } public function SetName($RefName) {
public function SetAddress($RefAdd) { $this->ReflectorAddress = trim($RefAdd); } $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 GetName() { return $this->ReflectorName; } public function GetName() { return $this->ReflectorName; }
public function GetAddress() { return $this->ReflectorAddress; } public function GetAddress() { return $this->ReflectorAddress; }
public function GetModules() { return $this->Modules; } public function GetModules() { return $this->Modules; }
public function AddModule($Module) { public function AddModule($Module) {
$Module = trim($Module); $Module = trim(strtoupper($Module));
if (strlen($Module) != 1) return false; if (strlen($Module) != 1) return false;
if (strpos($this->Modules, $Module) === false) { if (!preg_match('/^[A-Z]$/', $Module)) return false; // Only A-Z
$this->Modules .= $Module; if (strpos($this->Modules, $Module) === false) {
} $this->Modules .= $Module;
return true; }
return true;
} }
public function RemoveModule($Module) { public function RemoveModule($Module) {
$Module = trim($Module); $Module = trim(strtoupper($Module));
if (strlen($Module) != 1) return false; if (strlen($Module) != 1) return false;
if (strpos($this->Modules, $Module) !== false) { if (!preg_match('/^[A-Z]$/', $Module)) return false; // Only A-Z
$this->Modules = substr($this->Modules, 0, strpos($this->Modules, $Module)-1).substr($this->Modules, strpos($this->Modules, $Module)+1, strlen($this->Modules)); if (strpos($this->Modules, $Module) !== false) {
} $this->Modules = str_replace($Module, '', $this->Modules);
return true; }
return true;
} }
public function HasModuleEnabled($Module) { public function HasModuleEnabled($Module) {
if (strlen($Module) != 1) return false; $Module = trim(strtoupper($Module));
return (strpos($this->Modules, $Module) !== false); if (strlen($Module) != 1 || !preg_match('/^[A-Z]$/', $Module)) {
return false;
}
return (strpos($this->Modules, $Module) !== false);
} }
} }

View file

@ -14,29 +14,44 @@ class Node {
public function __construct($Callsign, $IP, $LinkedModule, $Protocol, $ConnectTime, $LastHeardTime, $RandomID) { public function __construct($Callsign, $IP, $LinkedModule, $Protocol, $ConnectTime, $LastHeardTime, $RandomID) {
$this->IP = $IP; // Validate and sanitize IP
$IP = trim($IP);
$this->IP = filter_var($IP, FILTER_VALIDATE_IP) ? $IP : '0.0.0.0';
$this->Protocol = $Protocol; // Validate protocol
$this->ConnectTime = ParseTime($ConnectTime); $Protocol = trim($Protocol);
$this->LastHeardTime = ParseTime($LastHeardTime); $allowed_protocols = ['DPlus', 'DExtra', 'DCS', 'DMR', 'YSF', 'DEXTRA', 'DPLUS'];
$this->Protocol = in_array($Protocol, $allowed_protocols, true) ? $Protocol : 'Unknown';
$this->FullCallsign = trim(str_replace(" ", "-", $Callsign));
$this->FullCallsign = str_replace(" ", "-", $this->FullCallsign); $this->ConnectTime = ParseTime($ConnectTime);
$this->FullCallsign = str_replace(" ", "-", $this->FullCallsign); $this->LastHeardTime = ParseTime($LastHeardTime);
if (strpos($Callsign, " ") !== false) {
$this->Callsign = trim(substr($Callsign, 0, strpos($Callsign, " ")));
$this->Suffix = trim(substr($Callsign, strpos($Callsign, " "), strlen($Callsign)));
$this->Prefix = strtoupper(trim(substr($Callsign, 0, 3)));
}
else {
$this->Callsign = trim($Callsign);
$this->Suffix = "";
$this->Prefix = "";
}
$this->LinkedModule = trim($LinkedModule); // Sanitize callsign (remove excessive spaces, validate format)
$this->RandomID = $RandomID; $Callsign = trim(preg_replace('/\s+/', ' ', $Callsign));
$this->FullCallsign = str_replace(" ", "-", $Callsign);
if (strpos($Callsign, " ") !== false) {
$this->Callsign = trim(substr($Callsign, 0, strpos($Callsign, " ")));
$this->Suffix = trim(substr($Callsign, strpos($Callsign, " ")));
$this->Prefix = strtoupper(trim(substr($Callsign, 0, 3)));
}
else {
$this->Callsign = trim($Callsign);
$this->Suffix = "";
$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->RandomID = $RandomID;
} }
public function GetFullCallsign() { return $this->FullCallsign; } public function GetFullCallsign() { return $this->FullCallsign; }

View file

@ -7,21 +7,31 @@ class ParseXML {
} }
public function GetElement($InputString, $ElementName) { public function GetElement($InputString, $ElementName) {
if (strpos($InputString, "<".$ElementName.">") === false) return false; // Sanitize element name to prevent XML injection
if (strpos($InputString, "</".$ElementName.">") === false) return false; $ElementName = preg_replace('/[^a-zA-Z0-9_\-\s]/', '', $ElementName);
$Element = substr($InputString, strpos($InputString, "<".$ElementName.">")+strlen($ElementName)+2, strpos($InputString, "</".$ElementName.">")-strpos($InputString, "<".$ElementName.">")-strlen($ElementName)-2); if (strpos($InputString, "<".$ElementName.">") === false) return false;
return $Element; 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);
// Strip any remaining HTML/XML tags from the content
return strip_tags($Element);
} }
public function GetAllElements($InputString, $ElementName) { public function GetAllElements($InputString, $ElementName) {
$Elements = array(); // Sanitize element name to prevent XML injection
while (strpos($InputString, $ElementName) !== false) { $ElementName = preg_replace('/[^a-zA-Z0-9_\-\s]/', '', $ElementName);
$Elements[] = $this->GetElement($InputString, $ElementName);
$InputString = substr($InputString, strpos($InputString, "</".$ElementName.">")+strlen($ElementName)+3, strlen($InputString)); $Elements = array();
} while (strpos($InputString, $ElementName) !== false) {
return $Elements; $element = $this->GetElement($InputString, $ElementName);
if ($element !== false) {
$Elements[] = $element;
}
$InputString = substr($InputString, strpos($InputString, "</".$ElementName.">")+strlen($ElementName)+3, strlen($InputString));
}
return $Elements;
} }
} }

View file

@ -10,13 +10,30 @@ class Peer {
private $LastHeardTime; private $LastHeardTime;
public function __construct($Callsign, $IP, $LinkedModule, $Protocol, $ConnectTime, $LastHeardTime) { public function __construct($Callsign, $IP, $LinkedModule, $Protocol, $ConnectTime, $LastHeardTime) {
$this->IP = $IP; // Validate and sanitize IP
$this->Protocol = $Protocol; $IP = trim($IP);
$this->ConnectTime = ParseTime($ConnectTime); $this->IP = filter_var($IP, FILTER_VALIDATE_IP) ? $IP : '0.0.0.0';
$this->LastHeardTime = ParseTime($LastHeardTime);
$this->Callsign = trim($Callsign); // Validate protocol
$this->LinkedModule = trim($LinkedModule); $Protocol = trim($Protocol);
$allowed_protocols = ['DPlus', 'DExtra', 'DCS', 'DMR', 'YSF', 'DEXTRA', 'DPLUS'];
$this->Protocol = in_array($Protocol, $allowed_protocols, true) ? $Protocol : 'Unknown';
$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 : '';
} }

View file

@ -37,50 +37,73 @@ class xReflector {
} }
public function LoadXML() { public function LoadXML() {
if ($this->XMLFile != null) { if ($this->XMLFile != null) {
$handle = fopen($this->XMLFile, 'r'); $handle = fopen($this->XMLFile, 'r');
$this->XMLContent = fread($handle, filesize($this->XMLFile)); $this->XMLContent = fread($handle, filesize($this->XMLFile));
fclose($handle); fclose($handle);
# XLX alphanumeric naming... # XLX alphanumeric naming...
$this->ServiceName = substr($this->XMLContent, strpos($this->XMLContent, "<XLX")+4, 3); $this->ServiceName = substr($this->XMLContent, strpos($this->XMLContent, "<XLX")+4, 3);
if (preg_match('/[^a-zA-Z0-9]/', $this->ServiceName) == 1) {
// Validate service name
if (!preg_match('/^[a-zA-Z0-9]{3}$/', $this->ServiceName)) {
$this->ServiceName = null; $this->ServiceName = null;
return false; return false;
} }
$this->ReflectorName = "XLX".$this->ServiceName; $this->ReflectorName = "XLX".$this->ServiceName;
$LinkedPeersName = "XLX".$this->ServiceName." linked peers"; $LinkedPeersName = "XLX".$this->ServiceName." linked peers";
$LinkedNodesName = "XLX".$this->ServiceName." linked nodes"; $LinkedNodesName = "XLX".$this->ServiceName." linked nodes";
$LinkedUsersName = "XLX".$this->ServiceName." heard users"; $LinkedUsersName = "XLX".$this->ServiceName." heard users";
$XML = new ParseXML(); $XML = new ParseXML();
$AllNodesString = $XML->GetElement($this->XMLContent, $LinkedNodesName); $AllNodesString = $XML->GetElement($this->XMLContent, $LinkedNodesName);
$tmpNodes = $XML->GetAllElements($AllNodesString, "NODE"); $tmpNodes = $XML->GetAllElements($AllNodesString, "NODE");
for ($i=0;$i<count($tmpNodes);$i++) { 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(
$this->AddNode($Node); $XML->GetElement($tmpNodes[$i], 'Callsign'),
} $XML->GetElement($tmpNodes[$i], 'IP'),
$XML->GetElement($tmpNodes[$i], 'LinkedModule'),
$AllStationsString = $XML->GetElement($this->XMLContent, $LinkedUsersName); $XML->GetElement($tmpNodes[$i], 'Protocol'),
$tmpStations = $XML->GetAllElements($AllStationsString, "STATION"); $XML->GetElement($tmpNodes[$i], 'ConnectTime'),
for ($i=0;$i<count($tmpStations);$i++) { $XML->GetElement($tmpNodes[$i], 'LastHeardTime'),
$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')); CreateCode(16)
$this->AddStation($Station, false); );
} $this->AddNode($Node);
}
$AllPeersString = $XML->GetElement($this->XMLContent, $LinkedPeersName);
$tmpPeers = $XML->GetAllElements($AllPeersString, "PEER"); $AllStationsString = $XML->GetElement($this->XMLContent, $LinkedUsersName);
for ($i=0;$i<count($tmpPeers);$i++) { $tmpStations = $XML->GetAllElements($AllStationsString, "STATION");
$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')); for ($i=0;$i<count($tmpStations);$i++) {
$this->AddPeer($Peer, false); $Station = new Station(
} $XML->GetElement($tmpStations[$i], 'Callsign'),
$XML->GetElement($tmpStations[$i], 'Via node'),
$this->Version = $XML->GetElement($this->XMLContent, "Version"); $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')
);
$this->AddPeer($Peer, false);
}
$this->Version = strip_tags($XML->GetElement($this->XMLContent, "Version"));
}
} }
public function GetVersion() { public function GetVersion() {
@ -92,24 +115,33 @@ class xReflector {
} }
public function SetXMLFile($XMLFile) { public function SetXMLFile($XMLFile) {
if (file_exists($XMLFile) && (is_readable($XMLFile))) { // Prevent path traversal
$this->XMLFile = $XMLFile; $XMLFile = basename($XMLFile);
} $XMLFile = '/var/log/' . $XMLFile; // Force it to expected directory
else {
die("File ".$XMLFile." does not exist or is not readable"); if (file_exists($XMLFile) && is_readable($XMLFile)) {
$this->XMLContent = null; $this->XMLFile = $XMLFile;
} }
else {
error_log("XML File ".$XMLFile." does not exist or is not readable");
$this->XMLFile = null;
$this->XMLContent = null;
}
} }
public function SetPIDFile($ProcessIDFile) { public function SetPIDFile($ProcessIDFile) {
if (file_exists($ProcessIDFile)) { // Prevent path traversal
$this->ProcessIDFile = $ProcessIDFile; $ProcessIDFile = basename($ProcessIDFile);
$this->ServiceUptime = time() - filectime($ProcessIDFile); $ProcessIDFile = '/var/log/' . $ProcessIDFile;
}
else { if (file_exists($ProcessIDFile)) {
$this->ProcessIDFile = null; $this->ProcessIDFile = $ProcessIDFile;
$this->ServiceUptime = null; $this->ServiceUptime = time() - filectime($ProcessIDFile);
} }
else {
$this->ProcessIDFile = null;
$this->ServiceUptime = null;
}
} }
public function GetServiceUptime() { public function GetServiceUptime() {
@ -117,11 +149,17 @@ class xReflector {
} }
public function SetFlagFile($Flagfile) { public function SetFlagFile($Flagfile) {
if (file_exists($Flagfile) && (is_readable($Flagfile))) { // Prevent path traversal
$this->Flagfile = $Flagfile; $realPath = realpath($Flagfile);
return true; if ($realPath === false || strpos($realPath, '/dashboard/pgs/') === false) {
} return false;
return false; }
if (file_exists($realPath) && is_readable($realPath)) {
$this->Flagfile = $realPath;
return true;
}
return false;
} }
public function LoadFlags() { public function LoadFlags() {
@ -346,6 +384,18 @@ class xReflector {
if (!isset($CallingHomeVariables['OverrideIPAddress'])) { $CallingHomeVariables['OverrideIPAddress'] = false; } if (!isset($CallingHomeVariables['OverrideIPAddress'])) { $CallingHomeVariables['OverrideIPAddress'] = false; }
if (!isset($CallingHomeVariables['InterlinkFile'])) { $CallingHomeVariables['InterlinkFile'] = ''; } 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'])) { if (!file_exists($CallingHomeVariables['InterlinkFile'])) {
$this->Interlinkfile = ''; $this->Interlinkfile = '';
$this->Transferinterlink = false; $this->Transferinterlink = false;
@ -370,95 +420,131 @@ class xReflector {
} }
public function ReadInterlinkFile() { public function ReadInterlinkFile() {
if (file_exists($this->Interlinkfile) && (is_readable($this->Interlinkfile))) { if (empty($this->Interlinkfile)) {
$this->Interlinks = array(); return false;
$this->InterlinkXML = ""; }
$Interlinkfilecontent = file($this->Interlinkfile);
for ($i=0;$i<count($Interlinkfilecontent);$i++) { // Prevent path traversal
if (substr($Interlinkfilecontent[$i], 0, 1) != '#') { $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)) {
$this->Interlinks = array();
$this->InterlinkXML = "";
$Interlinkfilecontent = file($realPath);
for ($i=0;$i<count($Interlinkfilecontent);$i++) {
if (substr($Interlinkfilecontent[$i], 0, 1) != '#') {
$Interlink = explode(" ", $Interlinkfilecontent[$i]); $Interlink = explode(" ", $Interlinkfilecontent[$i]);
$this->Interlinks[] = new Interlink(); $this->Interlinks[] = new Interlink();
if (isset($Interlink[0])) { $this->Interlinks[count($this->Interlinks)-1]->SetName(trim($Interlink[0])); } if (isset($Interlink[0])) {
if (isset($Interlink[1])) { $this->Interlinks[count($this->Interlinks)-1]->SetAddress(trim($Interlink[1])); } $this->Interlinks[count($this->Interlinks)-1]->SetName(trim($Interlink[0]));
if (isset($Interlink[2])) {
$Modules = str_split(trim($Interlink[2]), 1);
for ($j=0;$j<count($Modules);$j++) {
$this->Interlinks[count($this->Interlinks)-1]->AddModule($Modules[$j]);
}
} }
} if (isset($Interlink[1])) {
} $this->Interlinks[count($this->Interlinks)-1]->SetAddress(trim($Interlink[1]));
return true; }
} if (isset($Interlink[2])) {
return false; $Modules = str_split(trim($Interlink[2]), 1);
for ($j=0;$j<count($Modules);$j++) {
$this->Interlinks[count($this->Interlinks)-1]->AddModule($Modules[$j]);
}
}
}
}
return true;
}
return false;
} }
public function PrepareInterlinkXML() { public function PrepareInterlinkXML() {
$xml = ' $xml = '
<interlinks>'; <interlinks>';
for ($i=0;$i<count($this->Interlinks);$i++) { for ($i=0;$i<count($this->Interlinks);$i++) {
$xml .= ' $xml .= '
<interlink> <interlink>
<name>'.$this->Interlinks[$i]->GetName().'</name> <name>'.htmlspecialchars($this->Interlinks[$i]->GetName(), ENT_XML1, 'UTF-8').'</name>
<address>'.$this->Interlinks[$i]->GetAddress().'</address> <address>'.htmlspecialchars($this->Interlinks[$i]->GetAddress(), ENT_XML1, 'UTF-8').'</address>
<modules>'.$this->Interlinks[$i]->GetModules().'</modules> <modules>'.htmlspecialchars($this->Interlinks[$i]->GetModules(), ENT_XML1, 'UTF-8').'</modules>
</interlink>'; </interlink>';
} }
$xml .= ' $xml .= '
</interlinks>'; </interlinks>';
$this->InterlinkXML = $xml; $this->InterlinkXML = $xml;
} }
public function PrepareReflectorXML() { public function PrepareReflectorXML() {
$this->ReflectorXML = ' $this->ReflectorXML = '
<reflector> <reflector>
<name>'.$this->ReflectorName.'</name> <name>'.htmlspecialchars($this->ReflectorName, ENT_XML1, 'UTF-8').'</name>
<uptime>'.$this->ServiceUptime.'</uptime> <uptime>'.intval($this->ServiceUptime).'</uptime>
<hash>'.$this->CallingHomeHash.'</hash> <hash>'.htmlspecialchars($this->CallingHomeHash, ENT_XML1, 'UTF-8').'</hash>
<url>'.$this->CallingHomeDashboardURL.'</url> <url>'.htmlspecialchars($this->CallingHomeDashboardURL, ENT_XML1, 'UTF-8').'</url>
<country>'.$this->CallingHomeCountry.'</country> <country>'.htmlspecialchars($this->CallingHomeCountry, ENT_XML1, 'UTF-8').'</country>
<comment>'.$this->CallingHomeComment.'</comment> <comment>'.htmlspecialchars($this->CallingHomeComment, ENT_XML1, 'UTF-8').'</comment>
<ip>'.$this->CallingHomeOverrideIP.'</ip> <ip>'.htmlspecialchars($this->CallingHomeOverrideIP, ENT_XML1, 'UTF-8').'</ip>
<reflectorversion>'.$this->Version.'</reflectorversion> <reflectorversion>'.htmlspecialchars($this->Version, ENT_XML1, 'UTF-8').'</reflectorversion>
</reflector>'; </reflector>';
} }
public function CallHome() { public function CallHome() {
$xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?> // 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; <query>CallingHome</query>'.$this->ReflectorXML.$this->InterlinkXML;
$p = @stream_context_create(array('http' => array('header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST', $p = @stream_context_create(array('http' => array(
'content' => http_build_query(array('xml' => $xml)) ))); 'header' => "Content-type: application/x-www-form-urlencoded\r\n",
$result = @file_get_contents($this->CallingHomeServerURL, false, $p); 'method' => 'POST',
if ($result === false) { 'content' => http_build_query(array('xml' => $xml)),
die("CONNECTION FAILED!"); 'timeout' => 10
} )));
$result = @file_get_contents($this->CallingHomeServerURL, false, $p);
if ($result === false) {
error_log("CallHome connection failed");
return false;
}
return true;
} }
public function InterlinkCount() { public function InterlinkCount() {
return count($this->Interlinks); return count($this->Interlinks);
} }
public function GetInterlink($Index) { public function GetInterlink($Index) {
if (isset($this->Interlinks[$Index])) return $this->Interlinks[$Index]; if (isset($this->Interlinks[$Index])) {
return array(); return $this->Interlinks[$Index];
}
return false;
} }
public function IsInterlinked($Reflectorname) { public function IsInterlinked($Reflectorname) {
$i = -1; $i = -1;
$f = false; $f = false;
while (!$f && $i<$this->InterlinkCount()) { while (!$f && $i < $this->InterlinkCount()) {
$i++; $i++;
if (isset($this->Interlinks[$i])) { if (isset($this->Interlinks[$i])) {
if ($this->Interlinks[$i]->GetName() == $Reflectorname) { if ($this->Interlinks[$i]->GetName() == $Reflectorname) {
$f = true; $f = true;
return $i; return $i;
} }
} }
} }
return -1; return -1;
} }
} }
?> ?>

View file

@ -11,20 +11,34 @@ class Station {
private $OnModule; private $OnModule;
public function __construct($Callsign, $Via, $Peer, $LastHeardTime, $OnModule) { public function __construct($Callsign, $Via, $Peer, $LastHeardTime, $OnModule) {
$this->Callsign = trim($Callsign); // Sanitize and validate callsign
$this->Via = trim($Via); $Callsign = trim($Callsign);
$this->Peer = trim($Peer); $this->Callsign = $Callsign;
$this->LastHeardTime = ParseTime($LastHeardTime);
if (strpos($Callsign, " / ") !== false) { // Sanitize Via and Peer
$this->Suffix = trim(substr($Callsign, strpos($Callsign, " / ")+3, strlen($Callsign))); $this->Via = trim($Via);
} $this->Peer = trim($Peer);
else {
$this->Suffix = ""; $this->LastHeardTime = ParseTime($LastHeardTime);
}
if (strpos($Callsign, " / ") !== false) {
$tmp = explode(" ", $Callsign); $this->Suffix = trim(substr($Callsign, strpos($Callsign, " / ")+3));
$this->CallsignOnly = $tmp[0]; }
$this->OnModule = $OnModule; 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 : '';
} }
public function GetCallsign() { return $this->Callsign; } public function GetCallsign() { return $this->Callsign; }

View file

@ -17,7 +17,7 @@ $VNStat = array();
$PageOptions['ContactEmail'] = 'your_email'; // Support E-Mail address $PageOptions['ContactEmail'] = 'your_email'; // Support E-Mail address
$PageOptions['DashboardVersion'] = '2.4.2'; // Dashboard Version $PageOptions['DashboardVersion'] = '2.4.3'; // 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
@ -78,8 +78,13 @@ include an extra config file for people who dont like to mess with shipped confi
this makes updating dashboard from git a little bit easier this makes updating dashboard from git a little bit easier
*/ */
if (file_exists("../config.inc.php")) { $external_config = dirname(__FILE__) . '/../config.inc.php';
include ("../config.inc.php"); if (file_exists($external_config)) {
} $realPath = realpath($external_config);
// Only allow if it's in parent directory
if ($realPath !== false && dirname($realPath) === dirname(dirname(__FILE__))) {
include($realPath);
}
}
?> ?>

View file

@ -1,30 +1,90 @@
<?php <?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'];
return in_array(trim($protocol), $allowed, true) ? trim($protocol) : '';
}
function GetSystemUptime() { function GetSystemUptime() {
$out = exec("uptime"); if (file_exists('/proc/uptime')) {
return substr($out, 0, strpos($out, ",")); $uptime = @file_get_contents('/proc/uptime');
if ($uptime !== false) {
$parts = explode(' ', $uptime);
return isset($parts[0]) ? (int)$parts[0] : 0;
}
}
return 0;
} }
function Debug($message) { function Debug($message) {
echo '<br><hr><pre>'; if (defined('DEBUG_MODE') && DEBUG_MODE === true) {
print_r($message); echo '<br><hr><pre>';
echo '</pre><hr><br>'; print_r($message); // Don't sanitize here as it's debug only
echo '</pre><hr><br>';
}
} }
function ParseTime($Input) { function ParseTime($Input) {
if (empty($Input) || !is_string($Input)) {
return false;
}
$Input = strip_tags($Input); // Remove any HTML tags
if (strpos($Input, "<") !== false) { if (strpos($Input, "<") !== false) {
$Input = substr($Input, 0, strpos($Input, "<")); $Input = substr($Input, 0, strpos($Input, "<"));
} }
// Tuesday Tue Nov 17 14:23:22 2015 // Tuesday Tue Nov 17 14:23:22 2015
$tmp = explode(" ", $Input); $tmp = explode(" ", $Input);
// Add bounds checking
if (count($tmp) < 6) {
return false;
}
if (strlen(trim($tmp[3])) == 0) { if (strlen(trim($tmp[3])) == 0) {
unset($tmp[3]); unset($tmp[3]);
$tmp = array_values($tmp); $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]); $tmp1 = explode(":", $tmp[4]);
// Check time parts exist
if (count($tmp1) < 3) {
return false;
}
$month = ""; $month = "";
switch (strtolower($tmp[2])) { switch (strtolower($tmp[2])) {
case 'jan' : $month = 1; break; case 'jan' : $month = 1; break;
@ -42,7 +102,6 @@ function ParseTime($Input) {
default : $month = 1; default : $month = 1;
} }
return @mktime($tmp1[0], $tmp1[1], $tmp1[2], $month, $tmp[3], $tmp[5]); return @mktime($tmp1[0], $tmp1[1], $tmp1[2], $month, $tmp[3], $tmp[5]);
} }
function FormatSeconds($seconds) { function FormatSeconds($seconds) {
@ -70,9 +129,23 @@ function VNStatLocalize($str) {
} }
function VNStatGetData($iface, $vnstat_bin) { 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(); $vnstat_data = array();
$fd = @popen("$vnstat_bin --dumpdb -i $iface", "r"); $fd = @popen("$vnstat_bin_escaped --dumpdb -i $iface_escaped", "r");
if (is_resource($fd)) { if (is_resource($fd)) {
$buffer = ''; $buffer = '';
while (!feof($fd)) { while (!feof($fd)) {

View file

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

View file

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

View file

@ -30,15 +30,15 @@ $odd = "";
for ($i=0;$i<count($Reflectors);$i++) { for ($i=0;$i<count($Reflectors);$i++) {
$NAME = $XML->GetElement($Reflectors[$i], "name"); $NAME = sanitize_output($XML->GetElement($Reflectors[$i], "name"));
$COUNTRY = $XML->GetElement($Reflectors[$i], "country"); $COUNTRY = sanitize_output($XML->GetElement($Reflectors[$i], "country"));
$LASTCONTACT = $XML->GetElement($Reflectors[$i], "lastcontact"); $LASTCONTACT = intval($XML->GetElement($Reflectors[$i], "lastcontact"));
$COMMENT = $XML->GetElement($Reflectors[$i], "comment"); $COMMENT = sanitize_output($XML->GetElement($Reflectors[$i], "comment"));
$DASHBOARDURL = $XML->GetElement($Reflectors[$i], "dashboardurl"); $DASHBOARDURL = sanitize_attribute($XML->GetElement($Reflectors[$i], "dashboardurl"));
if ($odd == "#FFFFFF") { $odd = "#F1FAFA"; } else { $odd = "#FFFFFF"; } if ($odd == "#FFFFFF") { $odd = "#F1FAFA"; } else { $odd = "#FFFFFF"; }
echo ' echo '
<tr height="30" bgcolor="'.$odd.'" onMouseOver="this.bgColor=\'#FFFFCA\';" onMouseOut="this.bgColor=\''.$odd.'\';"> <tr height="30" bgcolor="'.$odd.'" onMouseOver="this.bgColor=\'#FFFFCA\';" onMouseOut="this.bgColor=\''.$odd.'\';">
<td align="center">'.($i+1).'</td> <td align="center">'.($i+1).'</td>
<td><a href="'.$DASHBOARDURL.'" target="_blank" class="listinglink" title="Visit the Dashboard of&nbsp;'.$NAME.'">'.$NAME.'</a></td> <td><a href="'.$DASHBOARDURL.'" target="_blank" class="listinglink" title="Visit the Dashboard of&nbsp;'.$NAME.'">'.$NAME.'</a></td>

View file

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

View file

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

View file

@ -44,7 +44,14 @@ if (isset($_GET['do'])) {
$_SESSION['FilterCallSign'] = null; $_SESSION['FilterCallSign'] = null;
} }
} }
// 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"> <table border="0">
@ -63,7 +70,7 @@ if ($PageOptions['UserPage']['ShowFilter']) {
<td align="left"> <td align="left">
<form name="frmFilterCallSign" method="post" action="./index.php"> <form name="frmFilterCallSign" method="post" action="./index.php">
<input type="hidden" name="do" value="SetFilter" /> <input type="hidden" name="do" value="SetFilter" />
<input type="text" class="FilterField" value="'.$_SESSION['FilterCallSign'].'" name="txtSetCallsignFilter" placeholder="Callsign" onfocus="SuspendPageRefresh();" onblur="setTimeout(ReloadPage, '.$PageOptions['PageRefreshDelay'].');" /> <input type="text" class="FilterField" value="'.sanitize_attribute($_SESSION['FilterCallSign']).'" name="txtSetCallsignFilter" placeholder="Callsign" onfocus="SuspendPageRefresh();" onblur="setTimeout(ReloadPage, '.$PageOptions['PageRefreshDelay'].');" />
<input type="submit" value="Apply" class="FilterSubmit" /> <input type="submit" value="Apply" class="FilterSubmit" />
</form> </form>
</td>'; </td>';
@ -75,7 +82,7 @@ if ($PageOptions['UserPage']['ShowFilter']) {
<td align="right" style="padding-right:3px;"> <td align="right" style="padding-right:3px;">
<form name="frmFilterModule" method="post" action="./index.php"> <form name="frmFilterModule" method="post" action="./index.php">
<input type="hidden" name="do" value="SetFilter" /> <input type="hidden" name="do" value="SetFilter" />
<input type="text" class="FilterField" value="'.$_SESSION['FilterModule'].'" name="txtSetModuleFilter" placeholder="Module" onfocus="SuspendPageRefresh();" onblur="setTimeout(ReloadPage, '.$PageOptions['PageRefreshDelay'].');" /> <input type="text" class="FilterField" value="'.sanitize_attribute($_SESSION['FilterModule']).'" name="txtSetModuleFilter" placeholder="Module" onfocus="SuspendPageRefresh();" onblur="setTimeout(ReloadPage, '.$PageOptions['PageRefreshDelay'].');" />
<input type="submit" value="Apply" class="FilterSubmit" /> <input type="submit" value="Apply" class="FilterSubmit" />
</form> </form>
</td> </td>
@ -122,35 +129,34 @@ for ($i=0;$i<$Reflector->StationCount();$i++) {
if ($ShowThisStation) { if ($ShowThisStation) {
if ($odd == "#FFFFFF") { $odd = "#F1FAFA"; } else { $odd = "#FFFFFF"; } if ($odd == "#FFFFFF") { $odd = "#F1FAFA"; } else { $odd = "#FFFFFF"; }
echo ' echo '
<tr height="30" bgcolor="'.$odd.'" onMouseOver="this.bgColor=\'#FFFFCA\';" onMouseOut="this.bgColor=\''.$odd.'\';"> <tr height="30" bgcolor="'.$odd.'" onMouseOver="this.bgColor=\'#FFFFCA\';" onMouseOut="this.bgColor=\''.$odd.'\';">
<td align="center" valign="middle" width="35">'; <td align="center" valign="middle" width="35">';
if ($i==0 && $Reflector->Stations[$i]->GetLastHeardTime() > (time() - 60)) { if ($i==0 && $Reflector->Stations[$i]->GetLastHeardTime() > (time() - 60)) {
echo '<img src="./img/tx.gif" style="margin-top:3px;" height="20"/>'; echo '<img src="./img/tx.gif" style="margin-top:3px;" height="20"/>';
} }
else { else {
echo ($i+1); echo ($i+1);
} }
echo '</td>
echo '</td> <td align="center" width="60">';
<td align="center" width="60">';
list ($Flag, $Name) = $Reflector->GetFlag($Reflector->Stations[$i]->GetCallSign());
list ($Flag, $Name) = $Reflector->GetFlag($Reflector->Stations[$i]->GetCallSign()); if (file_exists("./img/flags/".$Flag.".png")) {
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>
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="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">'.sanitize_output($Reflector->Stations[$i]->GetSuffix()).'</td>
<td width="60">'.$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="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">'.sanitize_output($Reflector->Stations[$i]->GetVia());
<td width="150">'.$Reflector->Stations[$i]->GetVia(); if ($Reflector->Stations[$i]->GetPeer() != $Reflector->GetReflectorName()) {
if ($Reflector->Stations[$i]->GetPeer() != $Reflector->GetReflectorName()) { echo ' / '.sanitize_output($Reflector->Stations[$i]->GetPeer());
echo ' / '.$Reflector->Stations[$i]->GetPeer(); }
} echo '</td>
echo '</td> <td width="150">'.sanitize_output(@date("d.m.Y H:i", $Reflector->Stations[$i]->GetLastHeardTime())).'</td>
<td width="150">'.@date("d.m.Y H:i", $Reflector->Stations[$i]->GetLastHeardTime()).'</td> <td align="center" width="30">'.sanitize_output($Reflector->Stations[$i]->GetModule()).'</td>
<td align="center" width="30">'.$Reflector->Stations[$i]->GetModule().'</td> </tr>';
</tr>';
} }
if ($i == $PageOptions['LastHeardPage']['LimitTo']) { $i = $Reflector->StationCount()+1; } if ($i == $PageOptions['LastHeardPage']['LimitTo']) { $i = $Reflector->StationCount()+1; }
} }
@ -177,18 +183,15 @@ for ($i=0;$i<count($Modules);$i++) {
if (isset($PageOptions['ModuleNames'][$Modules[$i]])) { if (isset($PageOptions['ModuleNames'][$Modules[$i]])) {
echo ' echo '
<th>'.sanitize_output($PageOptions['ModuleNames'][$Modules[$i]]);
<th>'.$PageOptions['ModuleNames'][$Modules[$i]];
if (trim($PageOptions['ModuleNames'][$Modules[$i]]) != "") { if (trim($PageOptions['ModuleNames'][$Modules[$i]]) != "") {
echo '<br />'; echo '<br />';
} }
echo $Modules[$i].'</th> echo sanitize_output($Modules[$i]).'</th>';
';
} }
else { else {
echo ' echo '
<th>'.sanitize_output($Modules[$i]).'</th>';
<th>'.$Modules[$i].'</th>';
} }
} }
@ -215,7 +218,7 @@ for ($i=0;$i<count($Modules);$i++) {
$Displayname = $Reflector->GetCallsignAndSuffixByID($Users[$j]); $Displayname = $Reflector->GetCallsignAndSuffixByID($Users[$j]);
echo ' echo '
<tr height="25" bgcolor="'.$odd.'" onMouseOver="this.bgColor=\'#FFFFCA\';" onMouseOut="this.bgColor=\''.$odd.'\';"> <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/'.$Displayname.'" class="pl" target="_blank">'.$Displayname.'</a> </td> <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>
</tr>'; </tr>';
$UserCheckedArray[] = $Users[$j]; $UserCheckedArray[] = $Users[$j];
} }