Compare commits

...

5 commits

Author SHA1 Message Date
Alessio Caiazza 5cfe9aad45
Merge 1e6c57e989 into 32c3241de0 2025-10-15 09:55:15 +03:00
LX1IQ 32c3241de0
Merge pull request #253 from MW0MWZ/master
Dashboard2 XSS / Security fixes
2025-10-14 13:43:36 +02:00
Andy Taylor 80821c25a3 Remove .DS_Store and update .gitignore 2025-10-14 12:26:32 +01:00
Andy Taylor 61204c3ed4 XSS Vulnerability Patches and Security Enhancements for Dashboard2 2025-10-14 12:25:26 +01:00
Alessio Caiazza 1e6c57e989 Implement configuration file
This is a first step in implementing a configuration file for xlxd.

It supports reflector callsign, listening IP, and ambe transcoder IP.

It loads the configuration file from /xlxd/xlxd.config (unless
redefine at compile time).

It keeps backward compatibility with the command line parameters that
will override the values in the config file.

Gbp-Pq: Name 0006-Implement-configuration-file.patch
2022-07-22 17:36:11 +02:00
18 changed files with 336 additions and 54 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
src/xlxd src/xlxd
ambed/ambed ambed/ambed
ambedtest/ambedtest ambedtest/ambedtest
.DS_Store

View file

@ -78,9 +78,9 @@ Please use the stable version listed above, we cannot support others.
# cp ~/xlxd/scripts/xlxd /etc/init.d/xlxd # cp ~/xlxd/scripts/xlxd /etc/init.d/xlxd
``` ```
###### Adapt the default startup parameters to your needs ###### Adapt the default configuration to your needs
``` ```
# pico /etc/init.d/xlxd # pico /xlxd/xlxd.config
``` ```
###### Download the dmrid.dat from the XLXAPI server to your xlxd folder ###### Download the dmrid.dat from the XLXAPI server to your xlxd folder
``` ```

15
config/xlxd.config Normal file
View file

@ -0,0 +1,15 @@
#########################################################################################
# XLXD config file
#
# one line per entry
# each entry specifies an option
#
#########################################################################################
# the reflector callsign - default N0CALL
callsign N0CALL
# listening IP address - default 0.0.0.0
listen 0.0.0.0
# AMBE transcoder IP address - default 127.0.0.1
transcoder 127.0.0.1

View file

@ -1,3 +1,28 @@
xlx db v2.3.8
SECURITY UPDATE - XSS Vulnerability Patches and Security Enhancements
- "functions.php" added SafeOutput() and SafeOutputAttr() for XSS protection
added GenerateCSRFToken() and ValidateCSRFToken() for CSRF protection
- "index.php" added session_start() for CSRF token support
added SafeOutput() to all $_GET['show'] outputs
added input whitelist validation for $_GET['show'] parameter
changed file permission from 777 to 600 for hash file (security hardening)
added SafeOutputAttr() to all meta tag outputs
added SafeOutput() to contact email output
improved error messages to prevent information disclosure
- "users.php" added CSRF token validation for all POST requests
added CSRF tokens to both filter forms
added input validation with regex for callsign filter (alphanumeric, dash, asterisk only)
added input validation with regex for module filter (single letter A-Z only)
added SafeOutput() and SafeOutputAttr() to all user data outputs
added SafeOutput() to all callsign, suffix, via, peer, and module outputs
- "repeaters.php" added SafeOutput() to all node callsign, suffix, protocol, and module outputs
added SafeOutput() to all IP address outputs
- "peers.php" added SafeOutput() and SafeOutputAttr() to peer name and URL outputs
added SafeOutput() to protocol, module, and IP address outputs
- "reflectors.php" added SafeOutput() and SafeOutputAttr() to reflector name, country, comment, and URL outputs
- "class.reflector.php" added URL validation in CallHome() method to prevent remote file inclusion attacks
xlx db v2.3.1 xlx db v2.3.1
- "config.inc.php" $CallingHome['InterlinkFile'] added - "config.inc.php" $CallingHome['InterlinkFile'] added
@ -16,7 +41,7 @@ xlx db v2.2.2
This version is a major release with voluntary self-registration feature build in. This version is a major release with voluntary self-registration feature build in.
You need to edit the conf.inc.php to your needs. You need to edit the conf.inc.php to your needs.
On the first run your personal hash to access the database is place in the servers /tmp folder. On the first run your personal hash to access the database is place in the server<EFBFBD>s /tmp folder.
Take care to make a backup of this file because this folder is cleaned up after a server reboot. Take care to make a backup of this file because this folder is cleaned up after a server reboot.
This version is a major release This version is a major release
@ -44,7 +69,7 @@ xlx db v2.1.4
- "class.reflector.php" improved the flag search - "class.reflector.php" improved the flag search
- "country.csv" added serveral prefixes - "country.csv" added serveral prefixes
- "flags" added Puerto Ricco and Åland Islands - "flags" added Puerto Ricco and <EFBFBD>land Islands
xlx db v2.1.3 xlx db v2.1.3

View file

@ -1,4 +1,5 @@
<?php <?php
session_start();
/* /*
* This dashboard is being developed by the DVBrazil Team as a courtesy to * This dashboard is being developed by the DVBrazil Team as a courtesy to
@ -9,12 +10,12 @@
if (file_exists("./pgs/functions.php")) { if (file_exists("./pgs/functions.php")) {
require_once("./pgs/functions.php"); require_once("./pgs/functions.php");
} else { } else {
die("functions.php does not exist."); die("Required file not found.");
} }
if (file_exists("./pgs/config.inc.php")) { if (file_exists("./pgs/config.inc.php")) {
require_once("./pgs/config.inc.php"); require_once("./pgs/config.inc.php");
} else { } else {
die("config.inc.php does not exist."); die("Required file not found.");
} }
if (!class_exists('ParseXML')) require_once("./pgs/class.parsexml.php"); if (!class_exists('ParseXML')) require_once("./pgs/class.parsexml.php");
@ -44,7 +45,7 @@ if ($CallingHome['Active']) {
@fwrite($Ressource, "\n" . '$Hash = "' . $Hash . '";'); @fwrite($Ressource, "\n" . '$Hash = "' . $Hash . '";');
@fwrite($Ressource, "\n\n" . '?>'); @fwrite($Ressource, "\n\n" . '?>');
@fclose($Ressource); @fclose($Ressource);
@exec("chmod 777 " . $CallingHome['HashFile']); @exec("chmod 600 " . $CallingHome['HashFile']);
$CallHomeNow = true; $CallHomeNow = true;
} }
} else { } else {
@ -79,12 +80,11 @@ if ($CallingHome['Active']) {
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="<?php echo $PageOptions['MetaDescription']; ?>"/> <meta name="description" content="<?php echo SafeOutputAttr($PageOptions['MetaDescription']); ?>"/>
<meta name="keywords" content="<?php echo $PageOptions['MetaKeywords']; ?>"/> <meta name="keywords" content="<?php echo SafeOutputAttr($PageOptions['MetaKeywords']); ?>"/>
<meta name="author" content="<?php echo $PageOptions['MetaAuthor']; ?>"/> <meta name="author" content="<?php echo SafeOutputAttr($PageOptions['MetaAuthor']); ?>"/>
<meta name="revisit" content="<?php echo $PageOptions['MetaRevisit']; ?>"/> <meta name="revisit" content="<?php echo SafeOutputAttr($PageOptions['MetaRevisit']); ?>"/>
<meta name="robots" content="<?php echo $PageOptions['MetaAuthor']; ?>"/> <meta name="robots" content="<?php echo SafeOutputAttr($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 $Reflector->GetReflectorName(); ?> Reflector Dashboard</title> <title><?php echo $Reflector->GetReflectorName(); ?> Reflector Dashboard</title>
<link rel="icon" href="./favicon.ico" type="image/vnd.microsoft.icon"> <link rel="icon" href="./favicon.ico" type="image/vnd.microsoft.icon">
@ -113,8 +113,8 @@ if ($CallingHome['Active']) {
if (($_SERVER['REQUEST_METHOD'] === 'POST') || isset($_GET['do'])) { if (($_SERVER['REQUEST_METHOD'] === 'POST') || isset($_GET['do'])) {
echo ' echo '
document.location.href = "./index.php'; document.location.href = "./index.php';
if (isset($_GET['show'])) { if (isset($_GET['show']) && $_GET['show'] !== '') {
echo '?show=' . $_GET['show']; echo '?show=' . SafeOutput($_GET['show']);
} }
echo '";'; echo '";';
} else { } else {
@ -194,6 +194,15 @@ if ($CallingHome['Active']) {
} }
} }
// Whitelist allowed values
if (!isset($_GET['show'])) {
$_GET['show'] = '';
}
$allowed_shows = ['users', 'repeaters', 'liveircddb', 'peers', 'reflectors', ''];
if (!in_array($_GET['show'], $allowed_shows, true)) {
$_GET['show'] = '';
}
switch ($_GET['show']) { switch ($_GET['show']) {
case 'users' : case 'users' :
require_once("./pgs/users.php"); require_once("./pgs/users.php");
@ -222,7 +231,7 @@ if ($CallingHome['Active']) {
<footer class="footer"> <footer class="footer">
<div class="container"> <div class="container">
<p><a href="mailto:<?php echo $PageOptions['ContactEmail']; ?>"><?php echo $PageOptions['ContactEmail']; ?></a> <p><a href="mailto:<?php echo SafeOutputAttr($PageOptions['ContactEmail']); ?>"><?php echo SafeOutput($PageOptions['ContactEmail']); ?></a>
</p> </p>
</div> </div>
</footer> </footer>

View file

@ -419,6 +419,10 @@ class xReflector {
} }
public function CallHome() { public function CallHome() {
// Validate URL before making request
if (!filter_var($this->CallingHomeServerURL, FILTER_VALIDATE_URL)) {
return false;
}
$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",

View file

@ -16,7 +16,7 @@ $PageOptions = array();
$PageOptions['ContactEmail'] = 'your_email'; // Support E-Mail address $PageOptions['ContactEmail'] = 'your_email'; // Support E-Mail address
$PageOptions['DashboardVersion'] = '2.3.7'; // Dashboard Version $PageOptions['DashboardVersion'] = '2.3.8'; // 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

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

View file

@ -59,4 +59,30 @@ function CreateCode ($laenge) {
return $out; return $out;
} }
function SafeOutput($string, $encoding = 'UTF-8') {
return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, $encoding);
}
function SafeOutputAttr($string, $encoding = 'UTF-8') {
// Extra safe for attributes
return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, $encoding);
}
function GenerateCSRFToken() {
if (!isset($_SESSION)) {
session_start();
}
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function ValidateCSRFToken($token) {
if (!isset($_SESSION)) {
session_start();
}
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
?> ?>

View file

@ -44,8 +44,8 @@ for ($i=0;$i<$Reflector->PeerCount();$i++) {
<tr class="table-center"> <tr class="table-center">
<td>'.($i+1).'</td>'; <td>'.($i+1).'</td>';
$Name = $Reflector->Peers[$i]->GetCallSign(); $Name = $Reflector->Peers[$i]->GetCallSign();
$URL = ''; $URL = '';
for ($j=1;$j<count($Reflectors);$j++) { for ($j=1;$j<count($Reflectors);$j++) {
if ($Name === $XML->GetElement($Reflectors[$j], "name")) { if ($Name === $XML->GetElement($Reflectors[$j], "name")) {
@ -53,15 +53,15 @@ for ($i=0;$i<$Reflector->PeerCount();$i++) {
} }
} }
if ($Result && (trim($URL) != "")) { if ($Result && (trim($URL) != "")) {
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>'; echo '<td><a href="' . SafeOutputAttr($URL) . '" target="_blank" class="listinglink" title="Visit the Dashboard of&nbsp;' . SafeOutputAttr($Name) . '" style="text-decoration:none;color:#000000;">' . SafeOutput($Name) . '</a></td>';
} else { } else {
echo '<td>'.$Name.'</td>'; echo '<td>' . SafeOutput($Name) . '</td>';
} }
echo ' echo '
<td>'.date("d.m.Y H:i", $Reflector->Peers[$i]->GetLastHeardTime()).'</td> <td>'.date("d.m.Y H:i", $Reflector->Peers[$i]->GetLastHeardTime()).'</td>
<td>'.FormatSeconds(time()-$Reflector->Peers[$i]->GetConnectTime()).' s</td> <td>'.FormatSeconds(time()-$Reflector->Peers[$i]->GetConnectTime()).' s</td>
<td>'.$Reflector->Peers[$i]->GetProtocol().'</td> <td>'.SafeOutput($Reflector->Peers[$i]->GetProtocol()).'</td>
<td>'.$Reflector->Peers[$i]->GetLinkedModule().'</td>'; <td>'.SafeOutput($Reflector->Peers[$i]->GetLinkedModule()).'</td>';
if ($PageOptions['PeerPage']['IPModus'] != 'HideIP') { if ($PageOptions['PeerPage']['IPModus'] != 'HideIP') {
echo '<td>'; echo '<td>';
$Bytes = explode(".", $Reflector->Peers[$i]->GetIP()); $Bytes = explode(".", $Reflector->Peers[$i]->GetIP());
@ -70,7 +70,7 @@ for ($i=0;$i<$Reflector->PeerCount();$i++) {
case 'ShowLast1ByteOfIP' : echo $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 $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 $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 '<a href="http://'.$Reflector->Peers[$i]->GetIP().'" target="_blank" style="text-decoration:none;color:#000000;">'.$Reflector->Peers[$i]->GetIP().'</a>'; default : echo '<a href="http://'.SafeOutput($Reflector->Peers[$i]->GetIP()).'" target="_blank" style="text-decoration:none;color:#000000;">'.SafeOutput($Reflector->Peers[$i]->GetIP()).'</a>';
} }
} }
echo '</td>'; echo '</td>';

View file

@ -38,10 +38,10 @@ for ($i=0;$i<count($Reflectors);$i++) {
echo ' echo '
<tr class="table-center"> <tr class="table-center">
<td>'.($i+1).'</td> <td>'.($i+1).'</td>
<td><a href="'.$DASHBOARDURL.'" target="_blank" class="listinglink" title="Visit the Dashboard of&nbsp;'.$NAME.'">'.$NAME.'</a></td> <td><a href="' . SafeOutputAttr($DASHBOARDURL) . '" target="_blank" class="listinglink" title="Visit the Dashboard of&nbsp;' . SafeOutputAttr($NAME) . '">' . SafeOutput($NAME) . '</a></td>
<td>'.$COUNTRY.'</td> <td>' . SafeOutput($COUNTRY) . '</td>
<td><img src="./img/'; if ($LASTCONTACT<(time()-1800)) { echo 'down'; } ELSE { echo 'up'; } echo '.png" class="table-status" alt=""></td> <td><img src="./img/'; if ($LASTCONTACT<(time()-1800)) { echo 'down'; } ELSE { echo 'up'; } echo '.png" class="table-status" alt=""></td>
<td>'.$COMMENT.'</td> <td>' . SafeOutput($COMMENT) . '</td>
</tr>'; </tr>';
} }

View file

@ -30,10 +30,10 @@ for ($i=0;$i<$Reflector->NodeCount();$i++) {
echo '<a href="#" class="tip"><img src="./img/flags/'.$Flag.'.png" class="table-flag" alt="'.$Name.'"><span>'.$Name.'</span></a>'; echo '<a href="#" class="tip"><img src="./img/flags/'.$Flag.'.png" class="table-flag" alt="'.$Name.'"><span>'.$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/'.SafeOutput($Reflector->Nodes[$i]->GetCallSign());
if ($Reflector->Nodes[$i]->GetSuffix() != "") echo '-'.$Reflector->Nodes[$i]->GetSuffix(); if ($Reflector->Nodes[$i]->GetSuffix() != "") echo '-'.SafeOutput($Reflector->Nodes[$i]->GetSuffix());
echo '" class="pl" target="_blank">'.$Reflector->Nodes[$i]->GetCallSign(); echo '" class="pl" target="_blank">'.SafeOutput($Reflector->Nodes[$i]->GetCallSign());
if ($Reflector->Nodes[$i]->GetSuffix() != "") { echo '-'.$Reflector->Nodes[$i]->GetSuffix(); } if ($Reflector->Nodes[$i]->GetSuffix() != "") { echo '-'.SafeOutput($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')) {
@ -55,8 +55,8 @@ for ($i=0;$i<$Reflector->NodeCount();$i++) {
echo '</td> echo '</td>
<td>'.date("d.m.Y H:i", $Reflector->Nodes[$i]->GetLastHeardTime()).'</td> <td>'.date("d.m.Y H:i", $Reflector->Nodes[$i]->GetLastHeardTime()).'</td>
<td>'.FormatSeconds(time()-$Reflector->Nodes[$i]->GetConnectTime()).' s</td> <td>'.FormatSeconds(time()-$Reflector->Nodes[$i]->GetConnectTime()).' s</td>
<td>'.$Reflector->Nodes[$i]->GetProtocol().'</td> <td>'.SafeOutput($Reflector->Nodes[$i]->GetProtocol()).'</td>
<td>'.$Reflector->Nodes[$i]->GetLinkedModule().'</td>'; <td>'.SafeOutput($Reflector->Nodes[$i]->GetLinkedModule()).'</td>';
if ($PageOptions['RepeatersPage']['IPModus'] != 'HideIP') { if ($PageOptions['RepeatersPage']['IPModus'] != 'HideIP') {
echo ' echo '
<td>'; <td>';

View file

@ -9,6 +9,11 @@ if (!isset($_SESSION['FilterModule'])) {
} }
if (isset($_POST['do'])) { if (isset($_POST['do'])) {
// Validate CSRF token
if (!isset($_POST['csrf_token']) || !ValidateCSRFToken($_POST['csrf_token'])) {
die('CSRF token validation failed');
}
if ($_POST['do'] == 'SetFilter') { if ($_POST['do'] == 'SetFilter') {
if (isset($_POST['txtSetCallsignFilter'])) { if (isset($_POST['txtSetCallsignFilter'])) {
@ -17,12 +22,17 @@ if (isset($_POST['do'])) {
$_SESSION['FilterCallSign'] = null; $_SESSION['FilterCallSign'] = null;
} }
else { else {
$_SESSION['FilterCallSign'] = $_POST['txtSetCallsignFilter']; // Validate callsign format (alphanumeric, dash, asterisk only)
if (strpos($_SESSION['FilterCallSign'], "*") === false) { if (preg_match('/^[A-Z0-9\-\*]+$/i', $_POST['txtSetCallsignFilter'])) {
$_SESSION['FilterCallSign'] = "*".$_SESSION['FilterCallSign']."*"; $_SESSION['FilterCallSign'] = $_POST['txtSetCallsignFilter'];
if (strpos($_SESSION['FilterCallSign'], "*") === false) {
$_SESSION['FilterCallSign'] = "*".$_SESSION['FilterCallSign']."*";
}
} else {
// Invalid format, reject it
$_SESSION['FilterCallSign'] = null;
} }
} }
} }
if (isset($_POST['txtSetModuleFilter'])) { if (isset($_POST['txtSetModuleFilter'])) {
@ -31,9 +41,14 @@ if (isset($_POST['do'])) {
$_SESSION['FilterModule'] = null; $_SESSION['FilterModule'] = null;
} }
else { else {
$_SESSION['FilterModule'] = $_POST['txtSetModuleFilter']; // Validate module is single letter A-Z
if (preg_match('/^[A-Z]$/i', $_POST['txtSetModuleFilter'])) {
$_SESSION['FilterModule'] = strtoupper($_POST['txtSetModuleFilter']);
} else {
// Invalid format, reject it
$_SESSION['FilterModule'] = null;
}
} }
} }
} }
} }
@ -60,7 +75,8 @@ 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="hidden" name="csrf_token" value="' . GenerateCSRFToken() . '" />
<input type="text" class="FilterField" value="'.SafeOutputAttr($_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>';
@ -72,7 +88,8 @@ 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="hidden" name="csrf_token" value="' . GenerateCSRFToken() . '" />
<input type="text" class="FilterField" value="'.SafeOutputAttr($_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>
@ -133,16 +150,16 @@ for ($i=0;$i<$Reflector->StationCount();$i++) {
echo '<a href="#" class="tip"><img src="./img/flags/' . $Flag . '.png" class="table-flag" alt="' . $Name . '"><span>' . $Name . '</span></a>'; echo '<a href="#" class="tip"><img src="./img/flags/' . $Flag . '.png" class="table-flag" alt="' . $Name . '"><span>' . $Name . '</span></a>';
} }
echo '</td> echo '</td>
<td><a href="https://www.qrz.com/db/' . $Reflector->Stations[$i]->GetCallsignOnly() . '" class="pl" target="_blank">' . $Reflector->Stations[$i]->GetCallsignOnly() . '</a></td> <td><a href="https://www.qrz.com/db/' . SafeOutput($Reflector->Stations[$i]->GetCallsignOnly()) . '" class="pl" target="_blank">' . SafeOutput($Reflector->Stations[$i]->GetCallsignOnly()) . '</a></td>
<td>' . $Reflector->Stations[$i]->GetSuffix() . '</td> <td>' . SafeOutput($Reflector->Stations[$i]->GetSuffix()) . '</td>
<td><a href="http://www.aprs.fi/' . $Reflector->Stations[$i]->GetCallsignOnly() . '" class="pl" target="_blank"><img src="./img/sat.png" alt=""></a></td> <td><a href="http://www.aprs.fi/' . SafeOutput($Reflector->Stations[$i]->GetCallsignOnly()) . '" class="pl" target="_blank"><img src="./img/sat.png" alt=""></a></td>
<td>' . $Reflector->Stations[$i]->GetVia(); <td>' . SafeOutput($Reflector->Stations[$i]->GetVia());
if ($Reflector->Stations[$i]->GetPeer() != $Reflector->GetReflectorName()) { if ($Reflector->Stations[$i]->GetPeer() != $Reflector->GetReflectorName()) {
echo ' / ' . $Reflector->Stations[$i]->GetPeer(); echo ' / ' . SafeOutput($Reflector->Stations[$i]->GetPeer());
} }
echo '</td> echo '</td>
<td>' . @date("d.m.Y H:i", $Reflector->Stations[$i]->GetLastHeardTime()) . '</td> <td>' . @date("d.m.Y H:i", $Reflector->Stations[$i]->GetLastHeardTime()) . '</td>
<td>' . $Reflector->Stations[$i]->GetModule() . '</td> <td>' . SafeOutput($Reflector->Stations[$i]->GetModule()) . '</td>
</tr>'; </tr>';
} }
if ($i == $PageOptions['LastHeardPage']['LimitTo']) { if ($i == $PageOptions['LastHeardPage']['LimitTo']) {
@ -192,7 +209,7 @@ for ($i=0;$i<count($Modules);$i++) {
$Displayname = $Reflector->GetCallsignAndSuffixByID($Users[$j]); $Displayname = $Reflector->GetCallsignAndSuffixByID($Users[$j]);
echo ' echo '
<tr> <tr>
<td><a href="http://www.aprs.fi/'.$Displayname.'" class="pl" target="_blank">'.$Displayname.'</a> </td> <td><a href="http://www.aprs.fi/' . SafeOutput($Displayname) . '" class="pl" target="_blank">' . SafeOutput($Displayname) . '</a> </td>
</tr>'; </tr>';
$UserCheckedArray[] = $Users[$j]; $UserCheckedArray[] = $Users[$j];
} }

View file

@ -19,7 +19,6 @@ PATH=/sbin:/bin:/usr/sbin:/usr/bin
# change below settings according to your system # change below settings according to your system
NAME="xlxd" NAME="xlxd"
DAEMON="/xlxd/xlxd" DAEMON="/xlxd/xlxd"
ARGUMENTS="XLX999 192.168.1.240 127.0.0.1"
PIDFILE="/var/log/xlxd.pid" PIDFILE="/var/log/xlxd.pid"
USER=root USER=root
GROUP=root GROUP=root
@ -31,7 +30,7 @@ start () {
# start daemon # start daemon
echo -n "Starting $NAME: " echo -n "Starting $NAME: "
start-stop-daemon --start --exec $DAEMON --chuid $USER:$GROUP --background -- $ARGUMENTS start-stop-daemon --start --exec $DAEMON --chuid $USER:$GROUP --background
RETVAL=$? RETVAL=$?
echo echo
sleep 4 sleep 4

104
src/cconfig.cpp Executable file
View file

@ -0,0 +1,104 @@
//
// cconfig.cpp
// xlxd
//
// Created by Alessio Caiazza (IU5BON) on 22/07/2022.
// Copyright © 2022 Alessio Caiazza (IU5BON). All rights reserved.
//
// ----------------------------------------------------------------------------
// This file is part of xlxd.
//
// xlxd is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// xlxd is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Foobar. If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------
#include "main.h"
#include "cconfig.h"
#include "ccallsign.h"
#include "cip.h"
#include <string.h>
CConfig::CConfig() :
m_Callsign("N0CALL"),
m_ListenIp(CIp("0.0.0.0")),
m_TranscoderIp(CIp("127.0.0.1"))
{
ReadOptions();
}
void CConfig::DumpConfig()
{
std::cout << "Configuration options" << std::endl;
std::cout << "callsign " << GetCallsign() << std::endl;
std::cout << "listen " << GetListenIp() << std::endl;
std::cout << "transcoder " << GetTranscoderIp() << std::endl;
std::cout << std::endl;
}
////////////////////////////////////////////////////////////////////////////////////////
// option helpers
char *CConfig::TrimWhiteSpaces(char *str)
{
char *end;
while ((*str == ' ') || (*str == '\t')) str++;
if (*str == 0)
return str;
end = str + strlen(str) - 1;
while ((end > str) && ((*end == ' ') || (*end == '\t') || (*end == '\r'))) end --;
*(end + 1) = 0;
return str;
}
void CConfig::ReadOptions(void)
{
char sz[256];
std::ifstream file(CONFIG_PATH);
if (file.is_open())
{
while (file.getline(sz, sizeof(sz)).good())
{
char *szt = TrimWhiteSpaces(sz);
char *szval;
if ((::strlen(szt) > 0) && szt[0] != '#')
{
if ((szt = ::strtok(szt, " ,\t")) != NULL)
{
if ((szval = ::strtok(NULL, " ,\t")) != NULL)
{
if (::strncmp(szt, "callsign", 8) == 0)
{
m_Callsign = CCallsign(szval);
}
else if (strncmp(szt, "listen", 5) == 0)
{
m_ListenIp = CIp(szval);
}
else if (strncmp(szt, "transcoder", 10) == 0)
{
m_TranscoderIp = CIp(szval);
}
else
{
// unknown option - ignore
}
}
}
}
}
}
}

65
src/cconfig.h Normal file
View file

@ -0,0 +1,65 @@
//
// cconfig.h
// xlxd
//
// Created by Alessio Caiazza (IU5BON) on 22/07/2022.
// Copyright © 2022 Alessio Caiazza (IU5BON). All rights reserved.
//
// ----------------------------------------------------------------------------
// This file is part of xlxd.
//
// xlxd is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// xlxd is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Foobar. If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------
#ifndef cconfig_h
#define cconfig_h
#include <string>
#include "cip.h"
#include "ccallsign.h"
////////////////////////////////////////////////////////////////////////////////////////
// class
class CConfig
{
public:
// constructor
CConfig();
void DumpConfig();
// getters
const CCallsign &GetCallsign(void) const { return m_Callsign; }
const CIp &GetListenIp(void) const { return m_ListenIp; }
const CIp &GetTranscoderIp(void) const { return m_TranscoderIp; }
// setters for bacward compatible CLI parameters
void SetCallsign(const CCallsign &callsign) { m_Callsign = callsign; }
void SetListenIp(const CIp &ip) { m_ListenIp = ip; }
void SetTranscoderIp(const CIp &ip) { m_TranscoderIp = ip; }
protected:
// config
void ReadOptions(void);
char* TrimWhiteSpaces(char *str);
protected:
CCallsign m_Callsign;
CIp m_ListenIp;
CIp m_TranscoderIp;
};
////////////////////////////////////////////////////////////////////////////////////////
#endif /* cconfig_h */

View file

@ -24,6 +24,7 @@
#include "main.h" #include "main.h"
#include "creflector.h" #include "creflector.h"
#include "cconfig.h"
#include "syslog.h" #include "syslog.h"
#include <sys/stat.h> #include <sys/stat.h>
@ -86,21 +87,34 @@ int main(int argc, const char * argv[])
#endif #endif
CConfig conf = CConfig();
// check arguments // check arguments
if ( argc != 4 ) if ( argc == 4 )
{
conf.SetCallsign(CCallsign(argv[1]));
conf.SetListenIp(CIp(argv[2]));
conf.SetTranscoderIp(CIp(argv[3]));
}
else if ( argc != 1 )
{ {
std::cout << "Usage: xlxd callsign xlxdip ambedip" << std::endl; std::cout << "Usage: xlxd callsign xlxdip ambedip" << std::endl;
std::cout << "example: xlxd XLX999 192.168.178.212 127.0.0.1" << std::endl; std::cout << "example: xlxd XLX999 192.168.178.212 127.0.0.1" << std::endl;
std::cout << "Startup parameters can also be defined in " << CONFIG_PATH << std::endl;
return 1; return 1;
} }
// splash // splash
std::cout << "Starting xlxd " << VERSION_MAJOR << "." << VERSION_MINOR << "." << VERSION_REVISION << std::endl << std::endl; std::cout << "Starting xlxd " << VERSION_MAJOR << "." << VERSION_MINOR << "." << VERSION_REVISION << std::endl << std::endl;
conf.DumpConfig();
// initialize reflector // initialize reflector
g_Reflector.SetCallsign(argv[1]); g_Reflector.SetCallsign(conf.GetCallsign());
g_Reflector.SetListenIp(CIp(argv[2])); g_Reflector.SetListenIp(conf.GetListenIp());
g_Reflector.SetTranscoderIp(CIp(CIp(argv[3]))); g_Reflector.SetTranscoderIp(conf.GetTranscoderIp());
// and let it run // and let it run
if ( !g_Reflector.Start() ) if ( !g_Reflector.Start() )

View file

@ -176,6 +176,9 @@
// system paths ------------------------------------------------- // system paths -------------------------------------------------
#ifndef CONFIG_PATH
#define CONFIG_PATH "/xlxd/xlxd.config"
#endif
#define XML_PATH "/var/log/xlxd.xml" #define XML_PATH "/var/log/xlxd.xml"
#define WHITELIST_PATH "/xlxd/xlxd.whitelist" #define WHITELIST_PATH "/xlxd/xlxd.whitelist"
#define BLACKLIST_PATH "/xlxd/xlxd.blacklist" #define BLACKLIST_PATH "/xlxd/xlxd.blacklist"