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
parent b7d17ee3b3
commit 57b0a10705
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 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.

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

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

@ -1,22 +1,9 @@
<?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();
// Security headers if (file_exists("./pgs/functions.php")) { require_once("./pgs/functions.php"); } else { die("functions.php does not exist."); }
header("X-Content-Type-Options: nosniff"); if (file_exists("./pgs/config.inc.php")) { require_once("./pgs/config.inc.php"); } else { die("config.inc.php does not exist."); }
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");
@ -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('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']);
@ -115,14 +81,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 sanitize_attribute($PageOptions['MetaDescription']); ?>" /> <meta name="description" content="<?php echo $PageOptions['MetaDescription']; ?>" />
<meta name="keywords" content="<?php echo sanitize_attribute($PageOptions['MetaKeywords']); ?>" /> <meta name="keywords" content="<?php echo $PageOptions['MetaKeywords']; ?>" />
<meta name="author" content="<?php echo sanitize_attribute($PageOptions['MetaAuthor']); ?>" /> <meta name="author" content="<?php echo $PageOptions['MetaAuthor']; ?>" />
<meta name="revisit" content="<?php echo sanitize_attribute($PageOptions['MetaRevisit']); ?>" /> <meta name="revisit" content="<?php echo $PageOptions['MetaRevisit']; ?>" />
<meta name="robots" content="<?php echo sanitize_attribute($PageOptions['MetaRobots']); ?>" /> <meta name="robots" content="<?php echo $PageOptions['MetaAuthor']; ?>" />
<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 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="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
@ -133,7 +99,7 @@ else {
var PageRefresh; var PageRefresh;
function ReloadPage() { 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 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)) {
@ -164,14 +130,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 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="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 intval($Reflector->NodeCount()); ?>)</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 intval($Reflector->PeerCount()); ?>)</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=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
@ -201,7 +167,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 '.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>'; </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> </div>

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

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

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

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

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

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

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

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

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

@ -37,73 +37,50 @@ 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( $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));
$XML->GetElement($tmpNodes[$i], 'Callsign'), $this->AddNode($Node);
$XML->GetElement($tmpNodes[$i], 'IP'), }
$XML->GetElement($tmpNodes[$i], 'LinkedModule'),
$XML->GetElement($tmpNodes[$i], 'Protocol'), $AllStationsString = $XML->GetElement($this->XMLContent, $LinkedUsersName);
$XML->GetElement($tmpNodes[$i], 'ConnectTime'), $tmpStations = $XML->GetAllElements($AllStationsString, "STATION");
$XML->GetElement($tmpNodes[$i], 'LastHeardTime'), for ($i=0;$i<count($tmpStations);$i++) {
CreateCode(16) $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);
$this->AddNode($Node); }
}
$AllPeersString = $XML->GetElement($this->XMLContent, $LinkedPeersName);
$AllStationsString = $XML->GetElement($this->XMLContent, $LinkedUsersName); $tmpPeers = $XML->GetAllElements($AllPeersString, "PEER");
$tmpStations = $XML->GetAllElements($AllStationsString, "STATION"); for ($i=0;$i<count($tmpPeers);$i++) {
for ($i=0;$i<count($tmpStations);$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'));
$Station = new Station( $this->AddPeer($Peer, false);
$XML->GetElement($tmpStations[$i], 'Callsign'), }
$XML->GetElement($tmpStations[$i], 'Via node'),
$XML->GetElement($tmpStations[$i], 'Via peer'), $this->Version = $XML->GetElement($this->XMLContent, "Version");
$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() {
@ -115,33 +92,24 @@ class xReflector {
} }
public function SetXMLFile($XMLFile) { public function SetXMLFile($XMLFile) {
// Prevent path traversal if (file_exists($XMLFile) && (is_readable($XMLFile))) {
$XMLFile = basename($XMLFile); $this->XMLFile = $XMLFile;
$XMLFile = '/var/log/' . $XMLFile; // Force it to expected directory }
else {
if (file_exists($XMLFile) && is_readable($XMLFile)) { die("File ".$XMLFile." does not exist or is not readable");
$this->XMLFile = $XMLFile; $this->XMLContent = null;
} }
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) {
// Prevent path traversal if (file_exists($ProcessIDFile)) {
$ProcessIDFile = basename($ProcessIDFile); $this->ProcessIDFile = $ProcessIDFile;
$ProcessIDFile = '/var/log/' . $ProcessIDFile; $this->ServiceUptime = time() - filectime($ProcessIDFile);
}
if (file_exists($ProcessIDFile)) { else {
$this->ProcessIDFile = $ProcessIDFile; $this->ProcessIDFile = null;
$this->ServiceUptime = time() - filectime($ProcessIDFile); $this->ServiceUptime = null;
} }
else {
$this->ProcessIDFile = null;
$this->ServiceUptime = null;
}
} }
public function GetServiceUptime() { public function GetServiceUptime() {
@ -149,17 +117,11 @@ class xReflector {
} }
public function SetFlagFile($Flagfile) { public function SetFlagFile($Flagfile) {
// Prevent path traversal if (file_exists($Flagfile) && (is_readable($Flagfile))) {
$realPath = realpath($Flagfile); $this->Flagfile = $Flagfile;
if ($realPath === false || strpos($realPath, '/dashboard/pgs/') === false) { return true;
return false; }
} return false;
if (file_exists($realPath) && is_readable($realPath)) {
$this->Flagfile = $realPath;
return true;
}
return false;
} }
public function LoadFlags() { public function LoadFlags() {
@ -384,18 +346,6 @@ 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;
@ -420,131 +370,95 @@ class xReflector {
} }
public function ReadInterlinkFile() { public function ReadInterlinkFile() {
if (empty($this->Interlinkfile)) { if (file_exists($this->Interlinkfile) && (is_readable($this->Interlinkfile))) {
return false; $this->Interlinks = array();
} $this->InterlinkXML = "";
$Interlinkfilecontent = file($this->Interlinkfile);
// Prevent path traversal for ($i=0;$i<count($Interlinkfilecontent);$i++) {
$realPath = realpath($this->Interlinkfile); if (substr($Interlinkfilecontent[$i], 0, 1) != '#') {
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])) { if (isset($Interlink[0])) { $this->Interlinks[count($this->Interlinks)-1]->SetName(trim($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[1])) {
$this->Interlinks[count($this->Interlinks)-1]->SetAddress(trim($Interlink[1]));
}
if (isset($Interlink[2])) { if (isset($Interlink[2])) {
$Modules = str_split(trim($Interlink[2]), 1); $Modules = str_split(trim($Interlink[2]), 1);
for ($j=0;$j<count($Modules);$j++) { for ($j=0;$j<count($Modules);$j++) {
$this->Interlinks[count($this->Interlinks)-1]->AddModule($Modules[$j]); $this->Interlinks[count($this->Interlinks)-1]->AddModule($Modules[$j]);
} }
} }
} }
} }
return true; return true;
} }
return false; 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>'.htmlspecialchars($this->Interlinks[$i]->GetName(), ENT_XML1, 'UTF-8').'</name> <name>'.$this->Interlinks[$i]->GetName().'</name>
<address>'.htmlspecialchars($this->Interlinks[$i]->GetAddress(), ENT_XML1, 'UTF-8').'</address> <address>'.$this->Interlinks[$i]->GetAddress().'</address>
<modules>'.htmlspecialchars($this->Interlinks[$i]->GetModules(), ENT_XML1, 'UTF-8').'</modules> <modules>'.$this->Interlinks[$i]->GetModules().'</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>'.htmlspecialchars($this->ReflectorName, ENT_XML1, 'UTF-8').'</name> <name>'.$this->ReflectorName.'</name>
<uptime>'.intval($this->ServiceUptime).'</uptime> <uptime>'.$this->ServiceUptime.'</uptime>
<hash>'.htmlspecialchars($this->CallingHomeHash, ENT_XML1, 'UTF-8').'</hash> <hash>'.$this->CallingHomeHash.'</hash>
<url>'.htmlspecialchars($this->CallingHomeDashboardURL, ENT_XML1, 'UTF-8').'</url> <url>'.$this->CallingHomeDashboardURL.'</url>
<country>'.htmlspecialchars($this->CallingHomeCountry, ENT_XML1, 'UTF-8').'</country> <country>'.$this->CallingHomeCountry.'</country>
<comment>'.htmlspecialchars($this->CallingHomeComment, ENT_XML1, 'UTF-8').'</comment> <comment>'.$this->CallingHomeComment.'</comment>
<ip>'.htmlspecialchars($this->CallingHomeOverrideIP, ENT_XML1, 'UTF-8').'</ip> <ip>'.$this->CallingHomeOverrideIP.'</ip>
<reflectorversion>'.htmlspecialchars($this->Version, ENT_XML1, 'UTF-8').'</reflectorversion> <reflectorversion>'.$this->Version.'</reflectorversion>
</reflector>'; </reflector>';
} }
public function CallHome() { public function CallHome() {
// Validate ServerURL is not localhost/internal IP $xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
$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",
$p = @stream_context_create(array('http' => array( 'method' => 'POST',
'header' => "Content-type: application/x-www-form-urlencoded\r\n", 'content' => http_build_query(array('xml' => $xml)) )));
'method' => 'POST', $result = @file_get_contents($this->CallingHomeServerURL, false, $p);
'content' => http_build_query(array('xml' => $xml)), if ($result === false) {
'timeout' => 10 die("CONNECTION FAILED!");
))); }
$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])) { if (isset($this->Interlinks[$Index])) return $this->Interlinks[$Index];
return $this->Interlinks[$Index]; return array();
}
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;
} }
} }
?> ?>

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

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

7
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['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['PageRefreshActive'] = true; // Activate automatic refresh
$PageOptions['PageRefreshDelay'] = '10000'; // Page refresh time in miliseconds $PageOptions['PageRefreshDelay'] = '10000'; // Page refresh time in miliseconds
@ -77,8 +77,9 @@ $VNStat['Binary'] = '/usr/bin/vnstat';
include an extra config file for people who dont like to mess with shipped config.ing.php 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 this makes updating dashboard from git a little bit easier
*/ */
if (file_exists("../config.inc.php")) { if (file_exists("../config.inc.php")) {
include ("../config.inc.php"); include ("../config.inc.php");
} }
?> ?>

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

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

@ -1,90 +1,30 @@
<?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', 'DMRMmdvm', 'XLX'];
return in_array(trim($protocol), $allowed, true) ? trim($protocol) : '';
}
function GetSystemUptime() { function GetSystemUptime() {
if (file_exists('/proc/uptime')) { $out = exec("uptime");
$uptime = @file_get_contents('/proc/uptime'); return substr($out, 0, strpos($out, ","));
if ($uptime !== false) {
$parts = explode(' ', $uptime);
return isset($parts[0]) ? (int)$parts[0] : 0;
}
}
return 0;
} }
function Debug($message) { function Debug($message) {
if (defined('DEBUG_MODE') && DEBUG_MODE === true) { echo '<br><hr><pre>';
echo '<br><hr><pre>'; print_r($message);
print_r($message); // Don't sanitize here as it's debug only echo '</pre><hr><br>';
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;
@ -102,6 +42,7 @@ 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) {
@ -129,23 +70,9 @@ 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_escaped --dumpdb -i $iface_escaped", "r"); $fd = @popen("$vnstat_bin --dumpdb -i $iface", "r");
if (is_resource($fd)) { if (is_resource($fd)) {
$buffer = ''; $buffer = '';
while (!feof($fd)) { 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 ' 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">'. sanitize_output($module) .'</td> <td align="center">'. $module .'</td>
<td align="center">'. sanitize_output(empty($PageOptions['ModuleNames'][$module]) ? '-' : $PageOptions['ModuleNames'][$module]) .'</td> <td align="center">'. (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">'. sanitize_output('REF' . $ReflectorNumber . $module . 'L') .'</td> <td align="center">'. '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">'. (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">'. '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">'. (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">'. '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">'. (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">'. (4000+$i) .'</td>
<td align="center">'. sanitize_output(9+$i) .'</td> <td align="center">'. (9+$i) .'</td>
</tr>'; </tr>';
} }

24
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) != "")) { if ($Result && (trim($URL) != "")) {
echo ' 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 { } else {
echo ' echo '
<td>'.sanitize_output($Name).'</td>'; <td>'.$Name.'</td>';
} }
echo ' echo '
<td>'.sanitize_output(date("d.m.Y H:i", $Reflector->Peers[$i]->GetLastHeardTime())).'</td> <td>'.date("d.m.Y H:i", $Reflector->Peers[$i]->GetLastHeardTime()).'</td>
<td>'.sanitize_output(FormatSeconds(time()-$Reflector->Peers[$i]->GetConnectTime())).' s</td> <td>'.FormatSeconds(time()-$Reflector->Peers[$i]->GetConnectTime()).' s</td>
<td align="center">'.sanitize_output($Reflector->Peers[$i]->GetProtocol()).'</td> <td align="center">'.$Reflector->Peers[$i]->GetProtocol().'</td>
<td align="center">'.sanitize_output($Reflector->Peers[$i]->GetLinkedModule()).'</td>'; <td align="center">'.$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 sanitize_output($PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$Bytes[3]); break; case 'ShowLast1ByteOfIP' : echo $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 'ShowLast2ByteOfIP' : echo $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; case 'ShowLast3ByteOfIP' : echo $PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$Bytes[1].'.'.$Bytes[2].'.'.$Bytes[3]; break;
default : echo sanitize_output($Reflector->Peers[$i]->GetIP()); default : echo $Reflector->Peers[$i]->GetIP();
} }
} }
echo '</td>'; echo '</td>';

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

@ -30,15 +30,15 @@ $odd = "";
for ($i=0;$i<count($Reflectors);$i++) { for ($i=0;$i<count($Reflectors);$i++) {
$NAME = sanitize_output($XML->GetElement($Reflectors[$i], "name")); $NAME = $XML->GetElement($Reflectors[$i], "name");
$COUNTRY = sanitize_output($XML->GetElement($Reflectors[$i], "country")); $COUNTRY = $XML->GetElement($Reflectors[$i], "country");
$LASTCONTACT = intval($XML->GetElement($Reflectors[$i], "lastcontact")); $LASTCONTACT = $XML->GetElement($Reflectors[$i], "lastcontact");
$COMMENT = sanitize_output($XML->GetElement($Reflectors[$i], "comment")); $COMMENT = $XML->GetElement($Reflectors[$i], "comment");
$DASHBOARDURL = sanitize_attribute($XML->GetElement($Reflectors[$i], "dashboardurl")); $DASHBOARDURL = $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>

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

@ -1,14 +1,15 @@
<?php <?php
// Validate filter inputs if (!isset($_SESSION['FilterCallSign'])) {
if (isset($_SESSION['FilterCallSign']) && $_SESSION['FilterCallSign'] !== null) { $_SESSION['FilterCallSign'] = null;
$_SESSION['FilterCallSign'] = preg_replace('/[^A-Z0-9\*\-\/\s]/i', '', $_SESSION['FilterCallSign']);
} }
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'])) { if (isset($_POST['do'])) {
@ -74,7 +75,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="'.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" /> <input type="submit" value="Apply" class="FilterSubmit" />
</form> </form>
</td>'; </td>';
@ -86,14 +87,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="'.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" /> <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="'.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" /> <input type="submit" value="Apply" class="FilterSubmit" />
</form> </form>
</td> </td>
@ -160,19 +161,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/'.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><a href="http://www.aprs.fi/'.sanitize_attribute($Reflector->Nodes[$i]->GetCallSign()); <td><a href="http://www.aprs.fi/'.$Reflector->Nodes[$i]->GetCallSign();
if ($Reflector->Nodes[$i]->GetSuffix() != "") echo '-'.sanitize_attribute($Reflector->Nodes[$i]->GetSuffix()); if ($Reflector->Nodes[$i]->GetSuffix() != "") echo '-'.$Reflector->Nodes[$i]->GetSuffix();
echo '" class="pl" target="_blank">'.sanitize_output($Reflector->Nodes[$i]->GetCallSign()); echo '" class="pl" target="_blank">'.$Reflector->Nodes[$i]->GetCallSign();
if ($Reflector->Nodes[$i]->GetSuffix() != "") { echo '-'.sanitize_output($Reflector->Nodes[$i]->GetSuffix()); } if ($Reflector->Nodes[$i]->GetSuffix() != "") { echo '-'.$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 {
@ -186,20 +187,20 @@ for ($i=0;$i<$Reflector->NodeCount();$i++) {
} }
} }
echo '</td> echo '</td>
<td>'.sanitize_output(date("d.m.Y H:i", $Reflector->Nodes[$i]->GetLastHeardTime())).'</td> <td>'.date("d.m.Y H:i", $Reflector->Nodes[$i]->GetLastHeardTime()).'</td>
<td>'.sanitize_output(FormatSeconds(time()-$Reflector->Nodes[$i]->GetConnectTime())).' s</td> <td>'.FormatSeconds(time()-$Reflector->Nodes[$i]->GetConnectTime()).' s</td>
<td>'.sanitize_output($Reflector->Nodes[$i]->GetProtocol()).'</td> <td>'.$Reflector->Nodes[$i]->GetProtocol().'</td>
<td align="center">'.sanitize_output($Reflector->Nodes[$i]->GetLinkedModule()).'</td>'; <td align="center">'.$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 sanitize_output($PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$Bytes[3]); break; case 'ShowLast1ByteOfIP' : echo $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 'ShowLast2ByteOfIP' : echo $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; case 'ShowLast3ByteOfIP' : echo $PageOptions['RepeatersPage']['MasqueradeCharacter'].'.'.$Bytes[1].'.'.$Bytes[2].'.'.$Bytes[3]; break;
default : echo sanitize_output($Reflector->Nodes[$i]->GetIP()); default : echo $Reflector->Nodes[$i]->GetIP();
} }
} }
echo '</td>'; echo '</td>';

View file

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

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

@ -44,14 +44,7 @@ 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">
@ -70,7 +63,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="'.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" /> <input type="submit" value="Apply" class="FilterSubmit" />
</form> </form>
</td>'; </td>';
@ -82,7 +75,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="'.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" /> <input type="submit" value="Apply" class="FilterSubmit" />
</form> </form>
</td> </td>
@ -129,34 +122,35 @@ 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>
<td align="center" width="60">'; echo '</td>
<td align="center" width="60">';
list ($Flag, $Name) = $Reflector->GetFlag($Reflector->Stations[$i]->GetCallSign());
if (file_exists("./img/flags/".$Flag.".png")) { list ($Flag, $Name) = $Reflector->GetFlag($Reflector->Stations[$i]->GetCallSign());
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>'; 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 '</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> echo '</td>
<td width="60">'.sanitize_output($Reflector->Stations[$i]->GetSuffix()).'</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="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="60">'.$Reflector->Stations[$i]->GetSuffix().'</td>
<td width="150">'.sanitize_output($Reflector->Stations[$i]->GetVia()); <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>
if ($Reflector->Stations[$i]->GetPeer() != $Reflector->GetReflectorName()) { <td width="150">'.$Reflector->Stations[$i]->GetVia();
echo ' / '.sanitize_output($Reflector->Stations[$i]->GetPeer()); if ($Reflector->Stations[$i]->GetPeer() != $Reflector->GetReflectorName()) {
} echo ' / '.$Reflector->Stations[$i]->GetPeer();
echo '</td> }
<td width="150">'.sanitize_output(@date("d.m.Y H:i", $Reflector->Stations[$i]->GetLastHeardTime())).'</td> echo '</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>
</tr>'; <td align="center" width="30">'.$Reflector->Stations[$i]->GetModule().'</td>
</tr>';
} }
if ($i == $PageOptions['LastHeardPage']['LimitTo']) { $i = $Reflector->StationCount()+1; } 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]])) { 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 sanitize_output($Modules[$i]).'</th>'; echo $Modules[$i].'</th>
';
} }
else { else {
echo ' 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]); $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/'.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>'; </tr>';
$UserCheckedArray[] = $Users[$j]; $UserCheckedArray[] = $Users[$j];
} }