Compare commits

...

9 commits

Author SHA1 Message Date
Diman Todorov aa7554172c
Merge 48f0d867ad into b7d17ee3b3 2025-10-16 08:54:32 -07:00
LX1IQ b7d17ee3b3
Merge pull request #254 from MW0MWZ/master
More XSS and security fixes, this time for the OG Dashboard
2025-10-16 12:34:51 +02:00
Andy Taylor e6de34a701 Catch missing protocols in the validation 2025-10-14 15:07:13 +01:00
Andy Taylor 1c241339bf Update Dashboard permissions 2025-10-14 14:40:53 +01:00
Andy Taylor c42b927cdb XSS Vulnerability Patches and Security Enhancements for Dashboard1 - Update changes.txt to reflect reality 2025-10-14 14:38:59 +01:00
Andy Taylor e11754a4e0 XSS Vulnerability Patches and Security Enhancements for Dashboard1 - Fix parsing error 2025-10-14 14:35:59 +01:00
Andy Taylor ee3f5de1de XSS Vulnerability Patches and Security Enhancements for Dashboard1 2025-10-14 14:15:20 +01:00
Diman Todorov 48f0d867ad
Addressed PR comment
Removed last byte of disconnect packet to ensure equivalence with ircdbgw.
2022-09-21 23:47:55 -07:00
Diman Todorov c220fa2b61
Added missing 0x20 in DCS EncodeDisconnectPacket
As is, EncodeDisconnectPacket will fail IsValidDisconnectPacket check. The encoded packet is only has 18 bytes instead of 19 because it's missing a 0x20.
2022-09-03 23:33:30 -07:00
22 changed files with 691 additions and 331 deletions

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

@ -1,3 +1,94 @@
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 Executable file → Normal file
View file

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

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

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

@ -1,7 +1,20 @@
<?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
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/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 (file_exists("./pgs/config.inc.php")) { require_once("./pgs/config.inc.php"); } else { die("config.inc.php does not exist."); }
@ -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>

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

@ -12,16 +12,30 @@ 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 (!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;
} }
@ -29,16 +43,20 @@ class Interlink {
} }
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 (!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) {
if (strlen($Module) != 1) return false; $Module = trim(strtoupper($Module));
if (strlen($Module) != 1 || !preg_match('/^[A-Z]$/', $Module)) {
return false;
}
return (strpos($this->Modules, $Module) !== false); return (strpos($this->Modules, $Module) !== false);
} }

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

@ -14,19 +14,26 @@ 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';
// Validate protocol
$Protocol = trim($Protocol);
$allowed_protocols = ['DPlus', 'DExtra', 'DCS', 'DMR', 'YSF', 'DEXTRA', 'DPLUS', 'DMRMmdvm'];
$this->Protocol = in_array($Protocol, $allowed_protocols, true) ? $Protocol : 'Unknown';
$this->Protocol = $Protocol;
$this->ConnectTime = ParseTime($ConnectTime); $this->ConnectTime = ParseTime($ConnectTime);
$this->LastHeardTime = ParseTime($LastHeardTime); $this->LastHeardTime = ParseTime($LastHeardTime);
$this->FullCallsign = trim(str_replace(" ", "-", $Callsign)); // Sanitize callsign (remove excessive spaces, validate format)
$this->FullCallsign = str_replace(" ", "-", $this->FullCallsign); $Callsign = trim(preg_replace('/\s+/', ' ', $Callsign));
$this->FullCallsign = str_replace(" ", "-", $this->FullCallsign);
$this->FullCallsign = str_replace(" ", "-", $Callsign);
if (strpos($Callsign, " ") !== false) { if (strpos($Callsign, " ") !== false) {
$this->Callsign = trim(substr($Callsign, 0, strpos($Callsign, " "))); $this->Callsign = trim(substr($Callsign, 0, strpos($Callsign, " ")));
$this->Suffix = trim(substr($Callsign, strpos($Callsign, " "), strlen($Callsign))); $this->Suffix = trim(substr($Callsign, strpos($Callsign, " ")));
$this->Prefix = strtoupper(trim(substr($Callsign, 0, 3))); $this->Prefix = strtoupper(trim(substr($Callsign, 0, 3)));
} }
else { else {
@ -35,7 +42,15 @@ class Node {
$this->Prefix = ""; $this->Prefix = "";
} }
$this->LinkedModule = trim($LinkedModule); // 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; $this->RandomID = $RandomID;
} }

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

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

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

@ -11,12 +11,29 @@ class Peer {
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->IP = filter_var($IP, FILTER_VALIDATE_IP) ? $IP : '0.0.0.0';
// Validate protocol
$Protocol = trim($Protocol);
$allowed_protocols = ['DPlus', 'DExtra', 'DCS', 'DMR', 'YSF', 'DEXTRA', 'DPLUS', 'XLX'];
$this->Protocol = in_array($Protocol, $allowed_protocols, true) ? $Protocol : 'Unknown';
$this->ConnectTime = ParseTime($ConnectTime); $this->ConnectTime = ParseTime($ConnectTime);
$this->LastHeardTime = ParseTime($LastHeardTime); $this->LastHeardTime = ParseTime($LastHeardTime);
$this->Callsign = trim($Callsign);
$this->LinkedModule = trim($LinkedModule); // 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 : '';
} }

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

@ -44,7 +44,9 @@ class xReflector {
# 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;
} }
@ -61,25 +63,46 @@ class xReflector {
$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(
$XML->GetElement($tmpNodes[$i], 'Callsign'),
$XML->GetElement($tmpNodes[$i], 'IP'),
$XML->GetElement($tmpNodes[$i], 'LinkedModule'),
$XML->GetElement($tmpNodes[$i], 'Protocol'),
$XML->GetElement($tmpNodes[$i], 'ConnectTime'),
$XML->GetElement($tmpNodes[$i], 'LastHeardTime'),
CreateCode(16)
);
$this->AddNode($Node); $this->AddNode($Node);
} }
$AllStationsString = $XML->GetElement($this->XMLContent, $LinkedUsersName); $AllStationsString = $XML->GetElement($this->XMLContent, $LinkedUsersName);
$tmpStations = $XML->GetAllElements($AllStationsString, "STATION"); $tmpStations = $XML->GetAllElements($AllStationsString, "STATION");
for ($i=0;$i<count($tmpStations);$i++) { for ($i=0;$i<count($tmpStations);$i++) {
$Station = new Station($XML->GetElement($tmpStations[$i], 'Callsign'), $XML->GetElement($tmpStations[$i], 'Via node'), $XML->GetElement($tmpStations[$i], 'Via peer'), $XML->GetElement($tmpStations[$i], 'LastHeardTime'), $XML->GetElement($tmpStations[$i], 'On module')); $Station = new Station(
$XML->GetElement($tmpStations[$i], 'Callsign'),
$XML->GetElement($tmpStations[$i], 'Via node'),
$XML->GetElement($tmpStations[$i], 'Via peer'),
$XML->GetElement($tmpStations[$i], 'LastHeardTime'),
$XML->GetElement($tmpStations[$i], 'On module')
);
$this->AddStation($Station, false); $this->AddStation($Station, false);
} }
$AllPeersString = $XML->GetElement($this->XMLContent, $LinkedPeersName); $AllPeersString = $XML->GetElement($this->XMLContent, $LinkedPeersName);
$tmpPeers = $XML->GetAllElements($AllPeersString, "PEER"); $tmpPeers = $XML->GetAllElements($AllPeersString, "PEER");
for ($i=0;$i<count($tmpPeers);$i++) { for ($i=0;$i<count($tmpPeers);$i++) {
$Peer = new Peer($XML->GetElement($tmpPeers[$i], 'Callsign'), $XML->GetElement($tmpPeers[$i], 'IP'), $XML->GetElement($tmpPeers[$i], 'LinkedModule'), $XML->GetElement($tmpPeers[$i], 'Protocol'), $XML->GetElement($tmpPeers[$i], 'ConnectTime'), $XML->GetElement($tmpPeers[$i], 'LastHeardTime')); $Peer = new Peer(
$XML->GetElement($tmpPeers[$i], 'Callsign'),
$XML->GetElement($tmpPeers[$i], 'IP'),
$XML->GetElement($tmpPeers[$i], 'LinkedModule'),
$XML->GetElement($tmpPeers[$i], 'Protocol'),
$XML->GetElement($tmpPeers[$i], 'ConnectTime'),
$XML->GetElement($tmpPeers[$i], 'LastHeardTime')
);
$this->AddPeer($Peer, false); $this->AddPeer($Peer, false);
} }
$this->Version = $XML->GetElement($this->XMLContent, "Version"); $this->Version = strip_tags($XML->GetElement($this->XMLContent, "Version"));
} }
} }
@ -92,16 +115,25 @@ class xReflector {
} }
public function SetXMLFile($XMLFile) { public function SetXMLFile($XMLFile) {
if (file_exists($XMLFile) && (is_readable($XMLFile))) { // Prevent path traversal
$XMLFile = basename($XMLFile);
$XMLFile = '/var/log/' . $XMLFile; // Force it to expected directory
if (file_exists($XMLFile) && is_readable($XMLFile)) {
$this->XMLFile = $XMLFile; $this->XMLFile = $XMLFile;
} }
else { else {
die("File ".$XMLFile." does not exist or is not readable"); error_log("XML File ".$XMLFile." does not exist or is not readable");
$this->XMLFile = null;
$this->XMLContent = null; $this->XMLContent = null;
} }
} }
public function SetPIDFile($ProcessIDFile) { public function SetPIDFile($ProcessIDFile) {
// Prevent path traversal
$ProcessIDFile = basename($ProcessIDFile);
$ProcessIDFile = '/var/log/' . $ProcessIDFile;
if (file_exists($ProcessIDFile)) { if (file_exists($ProcessIDFile)) {
$this->ProcessIDFile = $ProcessIDFile; $this->ProcessIDFile = $ProcessIDFile;
$this->ServiceUptime = time() - filectime($ProcessIDFile); $this->ServiceUptime = time() - filectime($ProcessIDFile);
@ -117,8 +149,14 @@ 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);
if ($realPath === false || strpos($realPath, '/dashboard/pgs/') === false) {
return false;
}
if (file_exists($realPath) && is_readable($realPath)) {
$this->Flagfile = $realPath;
return true; return true;
} }
return false; return false;
@ -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,16 +420,31 @@ class xReflector {
} }
public function ReadInterlinkFile() { public function ReadInterlinkFile() {
if (file_exists($this->Interlinkfile) && (is_readable($this->Interlinkfile))) { if (empty($this->Interlinkfile)) {
return false;
}
// Prevent path traversal
$realPath = realpath($this->Interlinkfile);
if ($realPath === false || strpos($realPath, '/xlxd/') === false) {
error_log("ReadInterlinkFile blocked - invalid path");
return false;
}
if (file_exists($realPath) && is_readable($realPath)) {
$this->Interlinks = array(); $this->Interlinks = array();
$this->InterlinkXML = ""; $this->InterlinkXML = "";
$Interlinkfilecontent = file($this->Interlinkfile); $Interlinkfilecontent = file($realPath);
for ($i=0;$i<count($Interlinkfilecontent);$i++) { for ($i=0;$i<count($Interlinkfilecontent);$i++) {
if (substr($Interlinkfilecontent[$i], 0, 1) != '#') { 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[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++) {
@ -399,9 +464,9 @@ class xReflector {
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 .= '
@ -412,27 +477,48 @@ class xReflector {
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() {
// 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"?> $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(
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST', 'method' => 'POST',
'content' => http_build_query(array('xml' => $xml)) ))); 'content' => http_build_query(array('xml' => $xml)),
'timeout' => 10
)));
$result = @file_get_contents($this->CallingHomeServerURL, false, $p); $result = @file_get_contents($this->CallingHomeServerURL, false, $p);
if ($result === false) { if ($result === false) {
die("CONNECTION FAILED!"); error_log("CallHome connection failed");
return false;
} }
return true;
} }
public function InterlinkCount() { public function InterlinkCount() {
@ -440,8 +526,10 @@ class xReflector {
} }
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) {
@ -458,7 +546,5 @@ class xReflector {
} }
return -1; return -1;
} }
} }
?> ?>

22
dashboard/pgs/class.station.php Executable file → Normal file
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
$Callsign = trim($Callsign);
$this->Callsign = $Callsign;
// Sanitize Via and Peer
$this->Via = trim($Via); $this->Via = trim($Via);
$this->Peer = trim($Peer); $this->Peer = trim($Peer);
$this->LastHeardTime = ParseTime($LastHeardTime); $this->LastHeardTime = ParseTime($LastHeardTime);
if (strpos($Callsign, " / ") !== false) { if (strpos($Callsign, " / ") !== false) {
$this->Suffix = trim(substr($Callsign, strpos($Callsign, " / ")+3, strlen($Callsign))); $this->Suffix = trim(substr($Callsign, strpos($Callsign, " / ")+3));
} }
else { else {
$this->Suffix = ""; $this->Suffix = "";
} }
$tmp = explode(" ", $Callsign); $tmp = explode(" ", $Callsign);
$this->CallsignOnly = $tmp[0]; $this->CallsignOnly = isset($tmp[0]) ? trim($tmp[0]) : '';
$this->OnModule = $OnModule;
// 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; }

3
dashboard/pgs/config.inc.php Executable file → Normal 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.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
@ -77,7 +77,6 @@ $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 Executable file → Normal file
View file

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

@ -1,17 +1,60 @@
<?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() {
$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) {
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, "<"));
@ -19,12 +62,29 @@ function ParseTime($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)) {

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

0
dashboard/pgs/liveircddb.php Executable file → Normal 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">'. $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>';
} }

20
dashboard/pgs/peers.php Executable file → Normal 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="'.$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>';

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

@ -30,11 +30,11 @@ $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"; }

47
dashboard/pgs/repeaters.php Executable file → Normal file
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,13 +160,13 @@ 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')) {
@ -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

@ -8,16 +8,17 @@ if (!isset($_GET['iface'])) {
$_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;
break;
} }
$i++;
} }
if (!$f) { if (!$valid) {
$_GET['iface'] = ""; $_GET['iface'] = "";
} }
} }
@ -33,8 +34,8 @@ 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 />';
} }
} }

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

@ -45,6 +45,13 @@ if (isset($_GET['do'])) {
} }
} }
// Validate filter inputs
if (isset($_SESSION['FilterCallSign']) && $_SESSION['FilterCallSign'] !== null) {
$_SESSION['FilterCallSign'] = preg_replace('/[^A-Z0-9\*\-\/\s]/i', '', $_SESSION['FilterCallSign']);
}
if (isset($_SESSION['FilterModule']) && $_SESSION['FilterModule'] !== null) {
$_SESSION['FilterModule'] = validate_module($_SESSION['FilterModule']);
}
?> ?>
<table border="0"> <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>
@ -131,25 +138,24 @@ for ($i=0;$i<$Reflector->StationCount();$i++) {
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/'.$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 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="75"><a href="https://www.qrz.com/db/'.sanitize_attribute($Reflector->Stations[$i]->GetCallsignOnly()).'" class="pl" target="_blank">'.sanitize_output($Reflector->Stations[$i]->GetCallsignOnly()).'</a></td>
<td width="60">'.$Reflector->Stations[$i]->GetSuffix().'</td> <td width="60">'.sanitize_output($Reflector->Stations[$i]->GetSuffix()).'</td>
<td width="50" align="center"><a href="http://www.aprs.fi/'.$Reflector->Stations[$i]->GetCallsignOnly().'" class="pl" target="_blank"><img src="./img/sat.png" /></a></td> <td width="50" align="center"><a href="http://www.aprs.fi/'.sanitize_attribute($Reflector->Stations[$i]->GetCallsignOnly()).'" class="pl" target="_blank"><img src="./img/sat.png" /></a></td>
<td width="150">'.$Reflector->Stations[$i]->GetVia(); <td width="150">'.sanitize_output($Reflector->Stations[$i]->GetVia());
if ($Reflector->Stations[$i]->GetPeer() != $Reflector->GetReflectorName()) { if ($Reflector->Stations[$i]->GetPeer() != $Reflector->GetReflectorName()) {
echo ' / '.$Reflector->Stations[$i]->GetPeer(); echo ' / '.sanitize_output($Reflector->Stations[$i]->GetPeer());
} }
echo '</td> echo '</td>
<td width="150">'.@date("d.m.Y H:i", $Reflector->Stations[$i]->GetLastHeardTime()).'</td> <td width="150">'.sanitize_output(@date("d.m.Y H:i", $Reflector->Stations[$i]->GetLastHeardTime())).'</td>
<td align="center" width="30">'.$Reflector->Stations[$i]->GetModule().'</td> <td align="center" width="30">'.sanitize_output($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];
} }

View file

@ -526,10 +526,10 @@ void CDcsProtocol::EncodeDisconnectPacket(CBuffer *Buffer, CClient *Client)
Buffer->Set((uint8 *)(const char *)Client->GetCallsign(), CALLSIGN_LEN-1); Buffer->Set((uint8 *)(const char *)Client->GetCallsign(), CALLSIGN_LEN-1);
Buffer->Append((uint8)' '); Buffer->Append((uint8)' ');
Buffer->Append((uint8)Client->GetModule()); Buffer->Append((uint8)Client->GetModule());
Buffer->Append((uint8)' ');
Buffer->Append((uint8)0x00); Buffer->Append((uint8)0x00);
Buffer->Append((uint8 *)(const char *)GetReflectorCallsign(), CALLSIGN_LEN-1); Buffer->Append((uint8 *)(const char *)GetReflectorCallsign(), CALLSIGN_LEN-1);
Buffer->Append((uint8)' '); Buffer->Append((uint8)' ');
Buffer->Append((uint8)0x00);
} }
void CDcsProtocol::EncodeDvPacket(const CDvHeaderPacket &Header, const CDvFramePacket &DvFrame, uint32 iSeq, CBuffer *Buffer) const void CDcsProtocol::EncodeDvPacket(const CDvHeaderPacket &Header, const CDvFramePacket &DvFrame, uint32 iSeq, CBuffer *Buffer) const