This commit is contained in:
2026-07-19 18:27:27 +08:00
parent fe4b286ce1
commit 0febcf69f4
170 changed files with 19490 additions and 11878 deletions
+56 -116
View File
@@ -1,38 +1,4 @@
<?php
/**
* Expert INI editor for /etc/dapnetgateway.
*
* Renders a per-section / per-key form built from parse_ini_file()
* output, accepts edits via POST, then writes the result back to
* /etc/dapnetgateway using the standard Pi-Star copy-via-/tmp pattern:
* 1. sudo cp /etc/dapnetgateway /tmp/<obfuscated>.tmp + chown www-data
* + chmod 600 (so PHP can edit the temp).
* 2. fopen('w') and fwrite the rebuilt INI text into the temp.
* 3. sudo mount -o remount,rw / + sudo install -m 644 -o root -g
* root temp -> /etc/dapnetgateway + sudo mount -o remount,ro / to
* seal the rootfs again. (See edit_mmdvmhost.php for the full
* rationale on the install vs cp+chmod+chown migration.)
* 4. sudo systemctl restart dapnetgateway.service to pick up the change.
*
* Admin-only access; the dashboard's Apache basic-auth gate is the
* sole protection. The validation is what's in the form (none, in
* effect — operator-typed values are written raw). Treat with care.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -51,123 +17,97 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// Do some file wrangling...
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
$filepath = tempnam('/tmp', 'pistar-edit-');
exec('sudo cp /etc/dapnetgateway ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
// Clean up the /tmp staging file on script exit so the
// editor's potentially-secrets-bearing copy of /etc/<config>
// doesn't persist between requests. @-suppression handles
// the case where a sudo mv (e.g. fulledit_bmapikey) already
// consumed the staging file before script end.
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /etc/dapnetgateway /tmp/oXyEVXYSisDX.tmp');
exec('sudo chown www-data:www-data /tmp/oXyEVXYSisDX.tmp');
exec('sudo chmod 664 /tmp/oXyEVXYSisDX.tmp');
// ini file to open
$filepath = '/tmp/oXyEVXYSisDX.tmp';
// after the form submit
if($_POST) {
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
}
// this is the function going to update your ini file
function update_ini_file($data, $filepath)
{
$content = "";
function update_ini_file($data, $filepath) {
$content = "";
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
$content .= $key."=".$value."\n";
}
$content .= "\n";
}
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
$content .= $key."=".$value."\n";
}
$content .= "\n";
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
$success = fwrite($handle, $content);
fclose($handle);
$success = fwrite($handle, $content);
fclose($handle);
// L-5: atomic install replaces the prior cp + chmod + chown
// triplet (rejected by the tightened sudoers — see
// edit_mmdvmhost.php for the full rationale).
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($filepath) . ' /etc/dapnetgateway');
exec('sudo mount -o remount,ro /');
// Updates complete - copy the working file back to the proper location
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo cp /tmp/oXyEVXYSisDX.tmp /etc/dapnetgateway'); // Move the file back
exec('sudo chmod 644 /etc/dapnetgateway'); // Set the correct runtime permissions
exec('sudo chown root:root /etc/dapnetgateway'); // Set the owner
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
// Reload the affected daemon
exec('sudo systemctl restart dapnetgateway.service'); // Reload the daemon
return $success;
}
// Reload the affected daemon
exec('sudo systemctl restart dapnetgateway.service'); // Reload the daemon
return $success;
}
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
echo '<form action="" method="post">'."\n";
echo csrf_field_html()."\n";
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
// INI section / key / value all come from the underlying
// /etc/<gateway> file. Same hardening as edit_mmdvmhost.php
// (#23): htmlspecialchars(ENT_QUOTES) on display so a value
// with a literal `"` or `<` (e.g. an Options string) can't
// break out of the `value="…"` attribute. The save handler
// writes the POST bytes verbatim, so legitimate quoted
// values round-trip byte-identically.
$sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8');
echo "<input type=\"hidden\" value=\"$sectionHtml\" name=\"$sectionHtml\" />\n";
echo "<table>\n";
echo "<tr><th colspan=\"2\">$sectionHtml</th></tr>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
$keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8');
$valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"$valueHtml\" /></td></tr>\n";
}
echo "</table>\n";
echo '<input type="submit" value="'.$lang['apply'].'" />'."\n";
echo "<br />\n";
}
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$section\" name=\"$section\" />\n";
echo "<div class=\"settings-card\">\n";
echo "<h3>$section</h3>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"$value\" /></div></div>\n";
}
echo "</div>\n";
echo '<div class="field-actions"><input type="submit" value="'.$lang['apply'].'" /></div>'."\n";
}
echo "</form>";
?>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+119 -211
View File
@@ -1,32 +1,4 @@
<?php
/**
* Expert editor for /etc/pistar-css.ini — the dashboard theme overrides.
*
* Same staged-write pattern as the other edit_*.php files except no
* daemon restart is needed (CSS is loaded fresh on the next page hit).
* Provides a 'Reset to defaults' path that does a `sudo rm -rf` on
* /etc/pistar-css.ini inside the mount-rw window — guarded only by a
* JS confirm() prompt; flag for the security pass.
*
* Output of this editor is consumed by css/pistar-css.php,
* css/pistar-css-mini.php, and admin/wifi/styles.php — the three CSS
* emitters that read from /etc/pistar-css.ini.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -45,166 +17,125 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
<script type="text/javascript">
function factoryReset()
{
// Typed confirmation. The server-side handler requires
// factoryResetConfirm === 'RESET' before performing the
// wipe — so a misclicked button or an accidental form
// replay does NOT reset the dashboard CSS, even though
// the CSRF token is otherwise valid.
var typed = prompt(
'WARNING: This will reset these settings to factory defaults.\n\n'
+ 'Type RESET (uppercase) and press OK to proceed.\n'
+ 'Press Cancel to go back.', '');
if (typed === null) { return false; } // Cancel
if (typed !== 'RESET') {
alert('Confirmation text did not match. Factory reset cancelled.');
return false;
}
document.getElementById('factoryResetConfirmInput').value = typed;
document.getElementById('factoryReset').submit();
}
{
if (confirm('WARNING: This will set these settings back to factory defaults.\n\nAre you SURE you want to do this?\n\nPress OK to restore the factory configuration\nPress Cancel to go back.')) {
document.getElementById("factoryReset").submit();
} else {
return false;
}
}
</script>
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
// tempnam() up front so both the factory-init branch (when
// /etc/pistar-css.ini doesn't exist yet) and the normal-load
// branch use the same per-request random staging path.
$filepath = tempnam('/tmp', 'pistar-edit-');
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
if (!file_exists('/etc/pistar-css.ini')) {
//The source file does not exist, lets create it....
$outFile = fopen($filepath, "w") or die("Unable to open file!");
$fileContent = "[Background]\nPage=edf0f5\nContent=ffffff\nBanners=dd4b39\n\n";
$fileContent .= "[Text]\nBanners=ffffff\nBannersDrop=303030\n\n";
$fileContent .= "[Tables]\nHeadDrop=8b0000\nBgEven=f7f7f7\nBgOdd=d0d0d0\n\n";
$fileContent .= "[Content]\nText=000000\n\n";
$fileContent .= "[BannerH1]\nEnabled=0\nText=\"Some Text\"\n\n";
$fileContent .= "[BannerExtText]\nEnabled=0\nText=\"Some long text entry\"\n\n";
$fileContent .= "[Lookup]\nService=\"RadioID\"\n";
fwrite($outFile, $fileContent);
fclose($outFile);
// Atomic install: content + mode + owner set in one syscall
// sequence. Replaces the prior cp + chmod + chown trio so an
// interrupted RW window can't leave /etc/pistar-css.ini at the
// staging file's www-data:www-data 600. The dashboard's CSS
// renderer (/css/pistar-css.php) reads it via parse_ini_file
// without sudo, so 644 root:root preserves the read path.
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($filepath) . ' /etc/pistar-css.ini');
exec('sudo mount -o remount,ro /');
//The source file does not exist, lets create it....
$outFile = fopen("/tmp/bW1kd4jg6b3N0DQo.tmp", "w") or die("Unable to open file!");
$fileContent = "[Background]\nPage=f3f4f8\nContent=ffffff\nBanners=4f46e5\n\n";
$fileContent .= "[Text]\nBanners=ffffff\nBannersDrop=1e1b4b\n\n";
$fileContent .= "[Tables]\nHeadDrop=3730a3\nBgEven=f8fafc\nBgOdd=eef0f5\n\n";
$fileContent .= "[Content]\nText=111827\n\n";
$fileContent .= "[BannerH1]\nEnabled=0\nText=\"Some Text\"\n\n";
$fileContent .= "[BannerExtText]\nEnabled=0\nText=\"Some long text entry\"\n\n";
$fileContent .= "[Lookup]\nService=\"RadioID\"\n";
fwrite($outFile, $fileContent);
fclose($outFile);
// Put the file back where it should be
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo cp /tmp/bW1kd4jg6b3N0DQo.tmp /etc/pistar-css.ini'); // Move the file back
exec('sudo chmod 644 /etc/pistar-css.ini'); // Set the correct runtime permissions
exec('sudo chown root:root /etc/pistar-css.ini'); // Set the owner
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
}
//Do some file wrangling...
exec('sudo cp /etc/pistar-css.ini ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
exec('sudo cp /etc/pistar-css.ini /tmp/bW1kd4jg6b3N0DQo.tmp');
exec('sudo chown www-data:www-data /tmp/bW1kd4jg6b3N0DQo.tmp');
exec('sudo chmod 664 /tmp/bW1kd4jg6b3N0DQo.tmp');
//ini file to open
$filepath = '/tmp/bW1kd4jg6b3N0DQo.tmp';
//after the form submit
if($_POST) {
$data = $_POST;
// Factory Reset Handler Here
if (empty($_POST['factoryReset']) != TRUE ) {
// Server-side confirmation gate. The form ships a hidden
// factoryResetConfirm input that the JS factoryReset()
// populates only after the operator types `RESET` into
// the prompt. Comparing strictly to the magic string
// (===) means a misclicked button, a replayed POST, or
// a curl with just `factoryReset=1` does NOT trigger the
// wipe — even with a valid CSRF token.
$confirm = isset($_POST['factoryResetConfirm']) ? $_POST['factoryResetConfirm'] : '';
if ($confirm !== 'RESET') {
echo "<br />\n";
echo "<table>\n";
echo "<tr><th>Factory Reset NOT performed</th></tr>\n";
echo "<tr><td>Server-side confirmation did not match. Factory reset cancelled.</td><tr>\n";
echo "</table>\n";
unset($_POST);
} else {
echo "<br />\n";
echo "<table>\n";
echo "<tr><th>Factory Reset Config</th></tr>\n";
echo "<tr><td>Loading fresh configuration file(s)...</td><tr>\n";
echo "</table>\n";
unset($_POST);
//Reset the config
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo rm -rf /etc/pistar-css.ini'); // Delete the Config
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
echo '<script type="text/javascript">setTimeout(function() { window.location=window.location;},0);</script>';
die();
}
} else {
//update ini file, call function
update_ini_file($data, $filepath);
}
$data = $_POST;
// Factory Reset Handler Here
if (empty($_POST['factoryReset']) != TRUE ) {
echo "<br />\n";
echo "<div class=\"settings-card\">\n";
echo "<h3>Factory Reset Config</h3>\n";
echo "<div>Loading fresh configuration file(s)...</div>\n";
echo "</div>\n";
unset($_POST);
//Reset the config
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo rm -rf /etc/pistar-css.ini'); // Delete the Config
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
echo '<script type="text/javascript">setTimeout(function() { window.location=window.location;},0);</script>';
die();
} else {
//update ini file, call function
update_ini_file($data, $filepath);
}
}
//this is the function going to update your ini file
function update_ini_file($data, $filepath)
{
$content = "";
//this is the function going to update your ini file
function update_ini_file($data, $filepath) {
$content = "";
//parse the ini file to get the sections
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
//parse the ini file to get the sections
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$section = str_replace("BannerH2", "BannerH1", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
if ($value == '') {
$content .= $key."=none\n";
}
else {
$content .= $key."=".$value."\n";
}
}
$content .= "\n";
}
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$section = str_replace("BannerH2", "BannerH1", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
if ($value == '') {
$content .= $key."=none\n";
}
else {
$content .= $key."=".$value."\n";
}
}
$content .= "\n";
}
//write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
//write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
$success = fwrite($handle, $content);
fclose($handle);
$success = fwrite($handle, $content);
fclose($handle);
// Atomic install — see the matching block earlier in this
// file for the rationale. Single sudo call, content + mode
// + owner all set together; no transient state on disk.
// $filepath here is the function parameter, which is the
// same per-request tempnam path created at file-top (A3-3).
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($filepath) . ' /etc/pistar-css.ini');
exec('sudo mount -o remount,ro /');
// Updates complete - copy the working file back to the proper location
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo cp /tmp/bW1kd4jg6b3N0DQo.tmp /etc/pistar-css.ini'); // Move the file back
exec('sudo chmod 644 /etc/pistar-css.ini'); // Set the correct runtime permissions
exec('sudo chown root:root /etc/pistar-css.ini'); // Set the owner
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
return $success;
}
return $success;
}
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
@@ -212,70 +143,47 @@ if (isset($parsed_ini['Lookup']['popupWidth'])) { unset($parsed_ini['Lookup']['
if (isset($parsed_ini['Lookup']['popupHeight'])) { unset($parsed_ini['Lookup']['popupHeight']); }
echo '<form action="" method="post">'."\n";
echo csrf_field_html()."\n";
foreach($parsed_ini as $section=>$values) {
// Same hardening as edit_mmdvmhost.php (#23): escape every
// INI section / key / value before HTML interpolation. The
// save handler writes POST bytes verbatim so legitimate
// values (including any with `"` or `<`) round-trip
// byte-identically.
$sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8');
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$sectionHtml\" name=\"$sectionHtml\" />\n";
echo "<table>\n";
echo "<tr><th colspan=\"2\">$sectionHtml</th></tr>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
$keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8');
$valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
if ( $section == "Lookup" && $key == "Service" ) {
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\">\n";
echo " <select name=\"{$sectionHtml}[$keyHtml]\" />\n";
if ($value == "RadioID") {
echo " <option value=\"RadioID\" selected=\"selected\">RadioID Callsign Lookup</option>\n";
} else {
echo " <option value=\"RadioID\">RadioID Callsign Lookup</option>\n";
}
if ($value == "QRZ") {
echo " <option value=\"QRZ\" selected=\"selected\">QRZ Callsign Lookup</option>\n";
} else {
echo " <option value=\"QRZ\">QRZ Callsign Lookup</option>\n";
}
echo " </select>\n";
echo "</td></tr>\n";
} else {
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"$valueHtml\" /></td></tr>\n";
}
}
echo "</table>\n";
echo '<input type="submit" value="'.$lang['apply'].'" />'."\n";
echo "<br /><br />\n";
}
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$section\" name=\"$section\" />\n";
echo "<div class=\"settings-card\">\n";
echo "<h3>$section</h3>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
if ( $section == "Lookup" && $key == "Service" ) {
echo "<div class=\"field-row\"><div>$key</div><div>\n";
echo " <select name=\"{$section}[$key]\" />\n";
if ($value == "RadioID") {
echo " <option value=\"RadioID\" selected=\"selected\">RadioID Callsign Lookup</option>\n";
} else {
echo " <option value=\"RadioID\">RadioID Callsign Lookup</option>\n";
}
if ($value == "QRZ") {
echo " <option value=\"QRZ\" selected=\"selected\">QRZ Callsign Lookup</option>\n";
} else {
echo " <option value=\"QRZ\">QRZ Callsign Lookup</option>\n";
}
echo " </select>\n";
echo "</div></div>\n";
} else {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"$value\" /></div></div>\n";
}
}
echo "</div>\n";
echo '<div class="field-actions"><input type="submit" value="'.$lang['apply'].'" /></div>'."\n";
}
echo "</form>";
echo "<br />\n";
echo 'if you took it all too far and now it makes you feel sick, click below to reset the changes made on this page, this will ONLY reset the CSS settings above and will not change any other settings or configuration.'."\n";
echo '<form id="factoryReset" action="" method="post">'."\n";
echo csrf_field_html()."\n";
echo ' <div><input type="hidden" name="factoryReset" value="1" /></div>'."\n";
// Server-side confirmation. JS factoryReset() prompts for the
// magic word and only populates this input on a match. The
// handler requires factoryResetConfirm === 'RESET' before doing
// the wipe — closes the "valid CSRF token + accidental replay"
// attack class.
echo ' <div><input type="hidden" id="factoryResetConfirmInput" name="factoryResetConfirm" value="" /></div>'."\n";
echo '</form>'."\n";
echo '<input type="button" onclick="javascript:factoryReset();" value="'.$lang['factory_reset'].'" />'."\n";
?>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+64 -124
View File
@@ -1,38 +1,4 @@
<?php
/**
* Expert INI editor for /etc/dmrgateway.
*
* Renders a per-section / per-key form built from parse_ini_file()
* output, accepts edits via POST, then writes the result back to
* /etc/dmrgateway using the standard Pi-Star copy-via-/tmp pattern:
* 1. sudo cp /etc/dmrgateway /tmp/<obfuscated>.tmp + chown www-data
* + chmod 600 (so PHP can edit the temp).
* 2. fopen('w') and fwrite the rebuilt INI text into the temp.
* 3. sudo mount -o remount,rw / + sudo install -m 644 -o root -g
* root temp -> /etc/dmrgateway + sudo mount -o remount,ro / to
* seal the rootfs again. (See edit_mmdvmhost.php for the full
* rationale on the install vs cp+chmod+chown migration.)
* 4. sudo systemctl restart dmrgateway.service to pick up the change.
*
* Admin-only access; the dashboard's Apache basic-auth gate is the
* sole protection. The validation is what's in the form (none, in
* effect — operator-typed values are written raw). Treat with care.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -51,131 +17,105 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
//Do some file wrangling...
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
$filepath = tempnam('/tmp', 'pistar-edit-');
exec('sudo cp /etc/dmrgateway ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
// Clean up the /tmp staging file on script exit so the
// editor's potentially-secrets-bearing copy of /etc/<config>
// doesn't persist between requests. @-suppression handles
// the case where a sudo mv (e.g. fulledit_bmapikey) already
// consumed the staging file before script end.
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /etc/dmrgateway /tmp/fmehg65694eg.tmp');
exec('sudo chown www-data:www-data /tmp/fmehg65694eg.tmp');
exec('sudo chmod 664 /tmp/fmehg65694eg.tmp');
//ini file to open
$filepath = '/tmp/fmehg65694eg.tmp';
//after the form submit
if($_POST) {
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
}
//this is the function going to update your ini file
function update_ini_file($data, $filepath)
{
$content = "";
function update_ini_file($data, $filepath) {
$content = "";
//parse the ini file to get the sections
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
//parse the ini file to get the sections
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
if (($section == "DMR Network 1" || $section == "DMR Network 2") && $key == "Password" && $value) {
$value = str_replace('"', "", $value);
$content .= $key."=\"".$value."\"\n";
} elseif (($section == "DMR Network 1" || $section == "DMR Network 2") && $key == "Options" && $value) {
$value = str_replace('"', "", $value);
$content .= $key."=\"".$value."\"\n";
} else {
$content .= $key."=".$value."\n";
}
}
$content .= "\n";
}
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
if (($section == "DMR Network 1" || $section == "DMR Network 2") && $key == "Password" && $value) {
$value = str_replace('"', "", $value);
$content .= $key."=\"".$value."\"\n";
} elseif (($section == "DMR Network 1" || $section == "DMR Network 2") && $key == "Options" && $value) {
$value = str_replace('"', "", $value);
$content .= $key."=\"".$value."\"\n";
} else {
$content .= $key."=".$value."\n";
}
}
$content .= "\n";
}
//write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
//write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
$success = fwrite($handle, $content);
fclose($handle);
$success = fwrite($handle, $content);
fclose($handle);
// L-5: atomic install replaces the prior cp + chmod + chown
// triplet (rejected by the tightened sudoers — see
// edit_mmdvmhost.php for the full rationale).
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($filepath) . ' /etc/dmrgateway');
exec('sudo mount -o remount,ro /');
// Updates complete - copy the working file back to the proper location
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo cp /tmp/fmehg65694eg.tmp /etc/dmrgateway'); // Move the file back
exec('sudo chmod 644 /etc/dmrgateway'); // Set the correct runtime permissions
exec('sudo chown root:root /etc/dmrgateway'); // Set the owner
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
// Reload the affected daemon
exec('sudo systemctl restart dmrgateway.service'); // Reload the daemon
return $success;
}
// Reload the affected daemon
exec('sudo systemctl restart dmrgateway.service'); // Reload the daemon
return $success;
}
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
echo '<form action="" method="post">'."\n";
echo csrf_field_html()."\n";
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
// INI section / key / value all come from the underlying
// /etc/<gateway> file. Same hardening as edit_mmdvmhost.php
// (#23): htmlspecialchars(ENT_QUOTES) on display so a value
// with a literal `"` or `<` (e.g. an Options string) can't
// break out of the `value="…"` attribute. The save handler
// writes the POST bytes verbatim, so legitimate quoted
// values round-trip byte-identically.
$sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8');
echo "<input type=\"hidden\" value=\"$sectionHtml\" name=\"$sectionHtml\" />\n";
echo "<table>\n";
echo "<tr><th colspan=\"2\">$sectionHtml</th></tr>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
$keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8');
$valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"$valueHtml\" /></td></tr>\n";
}
echo "</table>\n";
echo '<input type="submit" value="'.$lang['apply'].'" />'."\n";
echo "<br />\n";
}
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$section\" name=\"$section\" />\n";
echo "<div class=\"settings-card\">\n";
echo "<h3>$section</h3>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"$value\" /></div></div>\n";
}
echo "</div>\n";
echo '<div class="field-actions"><input type="submit" value="'.$lang['apply'].'" /></div>'."\n";
}
echo "</form>";
?>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+54 -113
View File
@@ -1,29 +1,4 @@
<?php
/**
* Expert editor for /etc/dstarrepeater (D-Star modem driver config).
*
* The on-disk file is a flat key=value (no [section] header), but the
* editor parses it via parse_ini_file() — so we synthesise a temporary
* `[dstarrepeater]` header on read and `sed -i` it back out before the
* file is committed. Otherwise follows the standard Pi-Star
* copy-via-/tmp / mount-rw / restart pattern; daemon:
* dstarrepeater.service.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -42,34 +17,28 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// Do some file wrangling...
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
// tempnam() creates the staging file mode 600 owned by www-data
// with an unguessable random suffix; cleanup is registered up
// front so the file never persists past script exit.
$filepath = tempnam('/tmp', 'pistar-edit-');
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /etc/dstarrepeater ' . escapeshellarg($filepath));
// Defensive re-assert; tempnam already 600 www-data and `sudo cp`
// preserves it, but match the surrounding pattern as belt-and-braces.
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
exec('sudo cp /etc/dstarrepeater /tmp/ZHN0YXJyZXBlYXRlcg.tmp');
exec('sudo chown www-data:www-data /tmp/ZHN0YXJyZXBlYXRlcg.tmp');
exec('sudo chmod 664 /tmp/ZHN0YXJyZXBlYXRlcg.tmp');
// ini file to open
$filepath = '/tmp/ZHN0YXJyZXBlYXRlcg.tmp';
// Mangle the input
$file_content = "[dstarrepeater]\n".preg_replace('~\r\n?~', "\n", file_get_contents($filepath));
@@ -77,105 +46,77 @@ file_put_contents($filepath, $file_content);
// after the form submit
if($_POST) {
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
}
// this is the function going to update your ini file
function update_ini_file($data, $filepath)
{
$content = "";
function update_ini_file($data, $filepath) {
$content = "";
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
if ($value == '') {
$content .= $key."= \n";
if ($value == '') {
$content .= $key."= \n";
}
else {
$content .= $key."=".$value."\n";
}
}
}
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
$success = fwrite($handle, $content);
fclose($handle);
$success = fwrite($handle, $content);
fclose($handle);
// /etc/dstarrepeater is a FLAT key=value file on disk — the
// synthetic [dstarrepeater] header is injected only for
// parse_ini_file()'s benefit. Strip it via PHP and install
// the cleaned content directly (L-7: drops sudo sed -i;
// L-5: collapses cp + chmod + chown into one atomic install).
// See edit_ircddbgateway.php for the full rationale.
$etcContent = preg_replace('/^\[dstarrepeater\]\r?\n/m', '', $content);
// A3-3: per-request random staging — see edit_ircddbgateway.php
$etcStaging = tempnam('/tmp', 'pistar-edit-etc-');
file_put_contents($etcStaging, $etcContent);
// Updates complete - copy the working file back to the proper location
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo cp /tmp/ZHN0YXJyZXBlYXRlcg.tmp /etc/dstarrepeater'); // Move the file back
exec('sudo sed -i \'/\\[dstarrepeater\\]/d\' /etc/dstarrepeater'); // Clean up file mangling
exec('sudo chmod 644 /etc/dstarrepeater'); // Set the correct runtime permissions
exec('sudo chown root:root /etc/dstarrepeater'); // Set the owner
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($etcStaging) . ' /etc/dstarrepeater');
exec('sudo mount -o remount,ro /');
@unlink($etcStaging);
// Reload the affected daemon
exec('sudo systemctl restart dstarrepeater.service'); // Reload the daemon
return $success;
}
// Reload the affected daemon
exec('sudo systemctl restart dstarrepeater.service'); // Reload the daemon
return $success;
}
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
echo '<form action="" method="post">'."\n";
echo csrf_field_html()."\n";
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
// INI section / key / value all come from the underlying
// /etc/<gateway> file. Same hardening as edit_mmdvmhost.php
// (#23): htmlspecialchars(ENT_QUOTES) on display so a value
// with a literal `"` or `<` (e.g. an Options string) can't
// break out of the `value="…"` attribute. The save handler
// writes the POST bytes verbatim, so legitimate quoted
// values round-trip byte-identically.
$sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8');
echo "<input type=\"hidden\" value=\"$sectionHtml\" name=\"$sectionHtml\" />\n";
echo "<table>\n";
echo "<tr><th colspan=\"2\">$sectionHtml</th></tr>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
$keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8');
$valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"$valueHtml\" /></td></tr>\n";
}
echo "</table>\n";
echo '<input type="submit" value="'.$lang['apply'].'" />'."\n";
echo "<br />\n";
}
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$section\" name=\"$section\" />\n";
echo "<div class=\"settings-card\">\n";
echo "<h3>$section</h3>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"$value\" /></div></div>\n";
}
echo "</div>\n";
echo '<div class="field-actions"><input type="submit" value="'.$lang['apply'].'" /></div>'."\n";
}
echo "</form>";
?>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+59 -134
View File
@@ -1,28 +1,4 @@
<?php
/**
* Expert editor for /etc/ircddbgateway (D-Star side gateway config).
*
* Same flat key=value file as /etc/dstarrepeater — uses the synthetic
* `[ircddbgateway]` section header trick on read and sed-strip on
* write so parse_ini_file() can handle it. Standard Pi-Star
* copy-via-/tmp / mount-rw / restart pattern; daemon:
* ircddbgateway.service.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -41,41 +17,28 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// Stage a copy of /etc/ircddbgateway in /tmp under a random,
// per-request name (A3-3). tempnam() creates the file mode 600
// owned by the calling PHP-FPM user (www-data); the unguessable
// suffix defeats the predictable-name TOCTOU class — an attacker
// who knew the path could otherwise pre-create it as a symlink
// to /etc/shadow or similar and have our `sudo cp` follow the
// link and overwrite the target. Cleanup is registered up front
// so the staging copy never persists past script exit, even on a
// die() / fatal-error path. @-suppression handles the case where
// a sudo mv path consumed the file before script end.
$filepath = tempnam('/tmp', 'pistar-edit-');
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /etc/ircddbgateway ' . escapeshellarg($filepath));
// Defensively re-assert mode + owner. tempnam already created
// the file 600 www-data, and `sudo cp` against an existing
// regular file truncates-in-place (mode/owner preserved); these
// remain as belt-and-braces to match the surrounding pattern.
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
// Do some file wrangling...
exec('sudo cp /etc/ircddbgateway /tmp/aXJjZGRiZ2F0ZXdheQ.tmp');
exec('sudo chown www-data:www-data /tmp/aXJjZGRiZ2F0ZXdheQ.tmp');
exec('sudo chmod 664 /tmp/aXJjZGRiZ2F0ZXdheQ.tmp');
// ini file to open
$filepath = '/tmp/aXJjZGRiZ2F0ZXdheQ.tmp';
// Mangle the input
$file_content = "[ircddbgateway]\n".preg_replace('~\r\n?~', "\n", file_get_contents($filepath));
@@ -83,115 +46,77 @@ file_put_contents($filepath, $file_content);
// after the form submit
if($_POST) {
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
}
// this is the function going to update your ini file
function update_ini_file($data, $filepath)
{
$content = "";
function update_ini_file($data, $filepath) {
$content = "";
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
foreach($data as $section=>$values) {
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
if ($value == '') {
$content .= $key."= \n";
}
else {
$content .= $key."=".$value."\n";
}
}
}
//append the values
foreach($values as $key=>$value) {
if ($value == '') {
$content .= $key."= \n";
}
else {
$content .= $key."=".$value."\n";
}
}
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
$success = fwrite($handle, $content);
fclose($handle);
$success = fwrite($handle, $content);
fclose($handle);
// /etc/ircddbgateway is a FLAT key=value file on disk — the
// synthetic [ircddbgateway] header was injected at line ~75
// only to satisfy parse_ini_file()'s section model. The /tmp
// staging file keeps the header (so the form re-render via
// parse_ini_file at the bottom of this script still finds
// sections); the on-disk version must not. Strip via PHP's
// preg_replace and install the cleaned content directly —
// drops the prior `sudo sed -i` from this code path (L-7).
$etcContent = preg_replace('/^\[ircddbgateway\]\r?\n/m', '', $content);
// A3-3: per-request random staging file rather than a
// predictable hardcoded /tmp/<obf>.tmp path. tempnam() also
// creates the file mode 600 — and since this is a freshly
// created file (not yet referenced anywhere), there's no
// race for an attacker to swap in a symlink before our write.
$etcStaging = tempnam('/tmp', 'pistar-edit-etc-');
file_put_contents($etcStaging, $etcContent);
// Updates complete - copy the working file back to the proper location
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo cp /tmp/aXJjZGRiZ2F0ZXdheQ.tmp /etc/ircddbgateway'); // Move the file back
exec('sudo sed -i \'/\\[ircddbgateway\\]/d\' /etc/ircddbgateway'); // Clean up file mangling
exec('sudo chmod 644 /etc/ircddbgateway'); // Set the correct runtime permissions
exec('sudo chown root:root /etc/ircddbgateway'); // Set the owner
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
// Atomic install: content + mode + owner set in one syscall
// sequence (B5 / L-5 pattern). Replaces the prior cp +
// chmod + chown trio so an interrupted RW window can't leave
// /etc/ircddbgateway in a transient state.
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($etcStaging) . ' /etc/ircddbgateway');
exec('sudo mount -o remount,ro /');
@unlink($etcStaging);
// Reload the affected daemon
exec('sudo systemctl restart ircddbgateway.service'); // Reload the daemon
return $success;
}
// Reload the affected daemon
exec('sudo systemctl restart ircddbgateway.service'); // Reload the daemon
return $success;
}
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
echo '<form action="" method="post">'."\n";
echo csrf_field_html()."\n";
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
// INI section / key / value all come from the underlying
// /etc/<gateway> file. Same hardening as edit_mmdvmhost.php
// (#23): htmlspecialchars(ENT_QUOTES) on display so a value
// with a literal `"` or `<` (e.g. an Options string) can't
// break out of the `value="…"` attribute. The save handler
// writes the POST bytes verbatim, so legitimate quoted
// values round-trip byte-identically.
$sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8');
echo "<input type=\"hidden\" value=\"$sectionHtml\" name=\"$sectionHtml\" />\n";
echo "<table>\n";
echo "<tr><th colspan=\"2\">$sectionHtml</th></tr>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
$keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8');
$valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"$valueHtml\" /></td></tr>\n";
}
echo "</table>\n";
echo '<input type="submit" value="'.$lang['apply'].'" />'."\n";
echo "<br />\n";
}
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$section\" name=\"$section\" />\n";
echo "<div class=\"settings-card\">\n";
echo "<h3>$section</h3>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"$value\" /></div></div>\n";
}
echo "</div>\n";
echo '<div class="field-actions"><input type="submit" value="'.$lang['apply'].'" /></div>'."\n";
}
echo "</form>";
?>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+78 -157
View File
@@ -1,42 +1,4 @@
<?php
/**
* Expert INI editor for /etc/mmdvmhost.
*
* Renders a per-section / per-key form built from parse_ini_file()
* output, accepts edits via POST, then writes the result back to
* /etc/mmdvmhost using the standard Pi-Star copy-via-/tmp pattern:
* 1. sudo cp /etc/mmdvmhost /tmp/<obfuscated>.tmp + chown www-data
* + chmod 600 (so PHP can edit the temp).
* 2. fopen('w') and fwrite the rebuilt INI text into the temp.
* 3. sudo mount -o remount,rw / + sudo install -m 644 -o root -g
* root temp -> /etc/mmdvmhost + sudo mount -o remount,ro / to
* seal the rootfs again. (L-5: collapses the prior cp + chmod +
* chown triplet into one atomic call — same idiom used by
* configure.php's gateway-save block. The triplet would also
* be rejected by the tightened /etc/sudoers.d/pistar-dashboard
* because it allowlists install but not bare cp+chmod+chown
* against /etc/<gateway>.)
* 4. sudo systemctl restart mmdvmhost.service to pick up the change.
*
* Admin-only access; the dashboard's Apache basic-auth gate is the
* sole protection. The validation is what's in the form (none, in
* effect — operator-typed values are written raw). Treat with care.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -55,162 +17,121 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
//Do some file wrangling...
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
// tempnam() creates the staging file mode 600 owned by www-data
// with an unguessable random suffix.
$filepath = tempnam('/tmp', 'pistar-edit-');
exec('sudo cp /etc/mmdvmhost ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
// Clean up the /tmp staging file on script exit so the
// editor's potentially-secrets-bearing copy of /etc/<config>
// doesn't persist between requests. @-suppression handles
// the case where a sudo mv (e.g. fulledit_bmapikey) already
// consumed the staging file before script end.
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /etc/mmdvmhost /tmp/bW1kdm1ob3N0DQo.tmp');
exec('sudo chown www-data:www-data /tmp/bW1kdm1ob3N0DQo.tmp');
exec('sudo chmod 664 /tmp/bW1kdm1ob3N0DQo.tmp');
//ini file to open
$filepath = '/tmp/bW1kdm1ob3N0DQo.tmp';
//after the form submit
if($_POST) {
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
}
//this is the function going to update your ini file
function update_ini_file($data, $filepath)
{
$content = "";
function update_ini_file($data, $filepath) {
$content = "";
//parse the ini file to get the sections
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
//parse the ini file to get the sections
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
if ($section == "DMR Network" && $key == "Password" && $value) {
$value = str_replace('"', "", $value);
$content .= $key."=\"".$value."\"\n";
}
elseif ($section == "DMR Network" && $key == "Options" && $value) {
$value = str_replace('"', "", $value);
$content .= $key."=\"".$value."\"\n";
}
elseif ($section == "DMR Network" && $key == "Options" && !$value) {
$content .= $key."= \n";
}
elseif ($value == '') {
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
if ($section == "DMR Network" && $key == "Password" && $value) {
$value = str_replace('"', "", $value);
$content .= $key."=\"".$value."\"\n";
}
elseif ($section == "DMR Network" && $key == "Options" && $value) {
$value = str_replace('"', "", $value);
$content .= $key."=\"".$value."\"\n";
}
elseif ($section == "DMR Network" && $key == "Options" && !$value) {
$content .= $key."= \n";
}
elseif ($value == '') {
$content .= $key."=none\n";
}
else {
$content .= $key."=".$value."\n";
}
}
$content .= "\n";
}
else {
$content .= $key."=".$value."\n";
}
}
$content .= "\n";
}
//write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
//write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
$success = fwrite($handle, $content);
fclose($handle);
$success = fwrite($handle, $content);
fclose($handle);
// Updates complete - install the working file back to the
// proper location. L-5: atomic install replaces the prior
// cp + chmod + chown triplet (which the tightened
// /etc/sudoers.d/pistar-dashboard rejects on the bare
// chmod/chown against /etc/<file> — only the install pattern
// is allowlisted there).
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($filepath) . ' /etc/mmdvmhost');
exec('sudo mount -o remount,ro /');
// Updates complete - copy the working file back to the proper location
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo cp /tmp/bW1kdm1ob3N0DQo.tmp /etc/mmdvmhost'); // Move the file back
exec('sudo chmod 644 /etc/mmdvmhost'); // Set the correct runtime permissions
exec('sudo chown root:root /etc/mmdvmhost'); // Set the owner
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
// Reload the affected daemon
exec('sudo systemctl restart mmdvmhost.service'); // Reload the daemon
return $success;
}
// Reload the affected daemon
exec('sudo systemctl restart mmdvmhost.service'); // Reload the daemon
return $success;
}
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
echo '<form action="" method="post">'."\n";
echo csrf_field_html()."\n";
foreach($parsed_ini as $section=>$values) {
// INI section / key / value all come from /etc/mmdvmhost.
// Operator-trusted in principle, but the M-3 audit class
// means a value with a literal `"` or `<` (legitimate INI
// content — e.g. "Options=foo=1,bar" or a callsign with a
// space) breaks out of `value="…"` attributes and renders
// as HTML.
//
// htmlspecialchars(ENT_QUOTES) preserves round-trip safety:
// on display: `"` -> `&quot;`, `<` -> `&lt;` etc.
// browser decode (form parse): `&quot;` -> `"` again.
// POST: the raw byte gets back to the save handler.
// save handler at line ~108: writes `key=value` to INI
// verbatim — no further escaping or unescaping.
// So values like `Options="foo=1,bar"` go in, get displayed
// as `Options=&quot;foo=1,bar&quot;`, the browser still shows
// `"foo=1,bar"` in the input field, and re-saving produces
// a byte-identical INI line.
$sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8');
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$sectionHtml\" name=\"$sectionHtml\" />\n";
echo "<table>\n";
echo "<tr><th colspan=\"2\">$sectionHtml</th></tr>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
$keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8');
$valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
if (($key == "Options") || ($value)) {
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"$valueHtml\" /></td></tr>\n";
}
elseif (($key == "Display") && ($value == '')) {
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"None\" /></td></tr>\n";
}
else {
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"0\" /></td></tr>\n";
}
}
echo "</table>\n";
echo '<input type="submit" value="'.$lang['apply'].'" />'."\n";
echo "<br />\n";
}
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$section\" name=\"$section\" />\n";
echo "<div class=\"settings-card\">\n";
echo "<h3>$section</h3>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
if (($key == "Options") || ($value)) {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"$value\" /></div></div>\n";
}
elseif (($key == "Display") && ($value == '')) {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"None\" /></div></div>\n";
}
else {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"0\" /></div></div>\n";
}
}
echo "</div>\n";
echo '<div class="field-actions"><input type="submit" value="'.$lang['apply'].'" /></div>'."\n";
}
echo "</form>";
?>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+57 -117
View File
@@ -1,38 +1,4 @@
<?php
/**
* Expert INI editor for /etc/nxdngateway.
*
* Renders a per-section / per-key form built from parse_ini_file()
* output, accepts edits via POST, then writes the result back to
* /etc/nxdngateway using the standard Pi-Star copy-via-/tmp pattern:
* 1. sudo cp /etc/nxdngateway /tmp/<obfuscated>.tmp + chown www-data
* + chmod 600 (so PHP can edit the temp).
* 2. fopen('w') and fwrite the rebuilt INI text into the temp.
* 3. sudo mount -o remount,rw / + sudo install -m 644 -o root -g
* root temp -> /etc/nxdngateway + sudo mount -o remount,ro / to
* seal the rootfs again. (See edit_mmdvmhost.php for the full
* rationale on the install vs cp+chmod+chown migration.)
* 4. sudo systemctl restart nxdngateway.service to pick up the change.
*
* Admin-only access; the dashboard's Apache basic-auth gate is the
* sole protection. The validation is what's in the form (none, in
* effect — operator-typed values are written raw). Treat with care.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -51,124 +17,98 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// Do some file wrangling...
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
$filepath = tempnam('/tmp', 'pistar-edit-');
exec('sudo cp /etc/nxdngateway ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
// Clean up the /tmp staging file on script exit so the
// editor's potentially-secrets-bearing copy of /etc/<config>
// doesn't persist between requests. @-suppression handles
// the case where a sudo mv (e.g. fulledit_bmapikey) already
// consumed the staging file before script end.
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /etc/nxdngateway /tmp/aFEds45dgs4tFS.tmp');
exec('sudo chown www-data:www-data /tmp/aFEds45dgs4tFS.tmp');
exec('sudo chmod 664 /tmp/aFEds45dgs4tFS.tmp');
// ini file to open
$filepath = '/tmp/aFEds45dgs4tFS.tmp';
// after the form submit
if($_POST) {
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
}
// this is the function going to update your ini file
function update_ini_file($data, $filepath)
{
$content = "";
function update_ini_file($data, $filepath) {
$content = "";
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
foreach($data as $section=>$values) {
// UnBreak special cases
if (strpos($section, 'aprs') !== false) { $section = str_replace("_", ".", $section); }
else { $section = str_replace("_", " ", $section); $section = str_replace(".", " ", $section); }
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
$content .= $key."=".$value."\n";
}
$content .= "\n";
}
foreach($data as $section=>$values) {
// UnBreak special cases
if (strpos($section, 'aprs') !== false) { $section = str_replace("_", ".", $section); }
else { $section = str_replace("_", " ", $section); $section = str_replace(".", " ", $section); }
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
$content .= $key."=".$value."\n";
}
$content .= "\n";
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
$success = fwrite($handle, $content);
fclose($handle);
$success = fwrite($handle, $content);
fclose($handle);
// L-5: atomic install replaces the prior cp + chmod + chown
// triplet (rejected by the tightened sudoers — see
// edit_mmdvmhost.php for the full rationale).
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($filepath) . ' /etc/nxdngateway');
exec('sudo mount -o remount,ro /');
// Updates complete - copy the working file back to the proper location
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo cp /tmp/aFEds45dgs4tFS.tmp /etc/nxdngateway'); // Move the file back
exec('sudo chmod 644 /etc/nxdngateway'); // Set the correct runtime permissions
exec('sudo chown root:root /etc/nxdngateway'); // Set the owner
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
// Reload the affected daemon
exec('sudo systemctl restart nxdngateway.service'); // Reload the daemon
return $success;
}
// Reload the affected daemon
exec('sudo systemctl restart nxdngateway.service'); // Reload the daemon
return $success;
}
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
echo '<form action="" method="post">'."\n";
echo csrf_field_html()."\n";
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
// INI section / key / value all come from the underlying
// /etc/<gateway> file. Same hardening as edit_mmdvmhost.php
// (#23): htmlspecialchars(ENT_QUOTES) on display so a value
// with a literal `"` or `<` (e.g. an Options string) can't
// break out of the `value="…"` attribute. The save handler
// writes the POST bytes verbatim, so legitimate quoted
// values round-trip byte-identically.
$sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8');
echo "<input type=\"hidden\" value=\"$sectionHtml\" name=\"$sectionHtml\" />\n";
echo "<table>\n";
echo "<tr><th colspan=\"2\">$sectionHtml</th></tr>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
$keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8');
$valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"$valueHtml\" /></td></tr>\n";
}
echo "</table>\n";
echo '<input type="submit" value="'.$lang['apply'].'" />'."\n";
echo "<br />\n";
}
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$section\" name=\"$section\" />\n";
echo "<div class=\"settings-card\">\n";
echo "<h3>$section</h3>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"$value\" /></div></div>\n";
}
echo "</div>\n";
echo '<div class="field-actions"><input type="submit" value="'.$lang['apply'].'" /></div>'."\n";
}
echo "</form>";
?>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+56 -116
View File
@@ -1,38 +1,4 @@
<?php
/**
* Expert INI editor for /etc/p25gateway.
*
* Renders a per-section / per-key form built from parse_ini_file()
* output, accepts edits via POST, then writes the result back to
* /etc/p25gateway using the standard Pi-Star copy-via-/tmp pattern:
* 1. sudo cp /etc/p25gateway /tmp/<obfuscated>.tmp + chown www-data
* + chmod 600 (so PHP can edit the temp).
* 2. fopen('w') and fwrite the rebuilt INI text into the temp.
* 3. sudo mount -o remount,rw / + sudo install -m 644 -o root -g
* root temp -> /etc/p25gateway + sudo mount -o remount,ro / to
* seal the rootfs again. (See edit_mmdvmhost.php for the full
* rationale on the install vs cp+chmod+chown migration.)
* 4. sudo systemctl restart p25gateway.service to pick up the change.
*
* Admin-only access; the dashboard's Apache basic-auth gate is the
* sole protection. The validation is what's in the form (none, in
* effect — operator-typed values are written raw). Treat with care.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -51,123 +17,97 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// Do some file wrangling...
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
$filepath = tempnam('/tmp', 'pistar-edit-');
exec('sudo cp /etc/p25gateway ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
// Clean up the /tmp staging file on script exit so the
// editor's potentially-secrets-bearing copy of /etc/<config>
// doesn't persist between requests. @-suppression handles
// the case where a sudo mv (e.g. fulledit_bmapikey) already
// consumed the staging file before script end.
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /etc/p25gateway /tmp/aFE45dgs4tFS.tmp');
exec('sudo chown www-data:www-data /tmp/aFE45dgs4tFS.tmp');
exec('sudo chmod 664 /tmp/aFE45dgs4tFS.tmp');
// ini file to open
$filepath = '/tmp/aFE45dgs4tFS.tmp';
// after the form submit
if($_POST) {
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
}
// this is the function going to update your ini file
function update_ini_file($data, $filepath)
{
$content = "";
function update_ini_file($data, $filepath) {
$content = "";
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
$content .= $key."=".$value."\n";
}
$content .= "\n";
}
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
$content .= $key."=".$value."\n";
}
$content .= "\n";
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
$success = fwrite($handle, $content);
fclose($handle);
$success = fwrite($handle, $content);
fclose($handle);
// L-5: atomic install replaces the prior cp + chmod + chown
// triplet (rejected by the tightened sudoers — see
// edit_mmdvmhost.php for the full rationale).
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($filepath) . ' /etc/p25gateway');
exec('sudo mount -o remount,ro /');
// Updates complete - copy the working file back to the proper location
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo cp /tmp/aFE45dgs4tFS.tmp /etc/p25gateway'); // Move the file back
exec('sudo chmod 644 /etc/p25gateway'); // Set the correct runtime permissions
exec('sudo chown root:root /etc/p25gateway'); // Set the owner
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
// Reload the affected daemon
exec('sudo systemctl restart p25gateway.service'); // Reload the daemon
return $success;
}
// Reload the affected daemon
exec('sudo systemctl restart p25gateway.service'); // Reload the daemon
return $success;
}
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
echo '<form action="" method="post">'."\n";
echo csrf_field_html()."\n";
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
// INI section / key / value all come from the underlying
// /etc/<gateway> file. Same hardening as edit_mmdvmhost.php
// (#23): htmlspecialchars(ENT_QUOTES) on display so a value
// with a literal `"` or `<` (e.g. an Options string) can't
// break out of the `value="…"` attribute. The save handler
// writes the POST bytes verbatim, so legitimate quoted
// values round-trip byte-identically.
$sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8');
echo "<input type=\"hidden\" value=\"$sectionHtml\" name=\"$sectionHtml\" />\n";
echo "<table>\n";
echo "<tr><th colspan=\"2\">$sectionHtml</th></tr>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
$keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8');
$valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"$valueHtml\" /></td></tr>\n";
}
echo "</table>\n";
echo '<input type="submit" value="'.$lang['apply'].'" />'."\n";
echo "<br />\n";
}
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$section\" name=\"$section\" />\n";
echo "<div class=\"settings-card\">\n";
echo "<h3>$section</h3>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"$value\" /></div></div>\n";
}
echo "</div>\n";
echo '<div class="field-actions"><input type="submit" value="'.$lang['apply'].'" /></div>'."\n";
}
echo "</form>";
?>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+54 -110
View File
@@ -1,26 +1,4 @@
<?php
/**
* Expert editor for /etc/starnetserver (D-Star StarNet groups config).
*
* Synthetic `[starnetserver]` section header trick on read like
* edit_ircddbgateway.php / edit_dstarrepeater.php. Standard Pi-Star
* copy-via-/tmp / mount-rw write pattern.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -39,32 +17,28 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// Do some file wrangling...
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
// tempnam() creates the staging file mode 600 owned by www-data
// with an unguessable random suffix; cleanup is registered up
// front so the file never persists past script exit.
$filepath = tempnam('/tmp', 'pistar-edit-');
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /etc/starnetserver ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
exec('sudo cp /etc/starnetserver /tmp/c3Rhcm5ldHNlcnZlcg.tmp');
exec('sudo chown www-data:www-data /tmp/c3Rhcm5ldHNlcnZlcg.tmp');
exec('sudo chmod 664 /tmp/c3Rhcm5ldHNlcnZlcg.tmp');
// ini file to open
$filepath = '/tmp/c3Rhcm5ldHNlcnZlcg.tmp';
// Mangle the input
$file_content = "[starnetserver]\n".file_get_contents($filepath);
@@ -72,107 +46,77 @@ file_put_contents($filepath, $file_content);
// after the form submit
if($_POST) {
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
}
// this is the function going to update your ini file
function update_ini_file($data, $filepath)
{
$content = "";
function update_ini_file($data, $filepath) {
$content = "";
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
if ($value == '') {
$content .= $key."=".$value." \n";
if ($value == '') {
$content .= $key."=".$value." \n";
}
else {
$content .= $key."=".$value."\n";
}
}
}
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
$success = fwrite($handle, $content);
fclose($handle);
$success = fwrite($handle, $content);
fclose($handle);
// /etc/starnetserver is a FLAT key=value file on disk — the
// synthetic [starnetserver] header is injected only for
// parse_ini_file()'s benefit. Strip it via PHP and install
// the cleaned content directly (L-7: drops sudo sed -i;
// L-5: collapses cp + chmod + chown into one atomic install).
// See edit_ircddbgateway.php for the full rationale.
$etcContent = preg_replace('/^\[starnetserver\]\r?\n/m', '', $content);
// A3-3: per-request random staging — see edit_ircddbgateway.php
$etcStaging = tempnam('/tmp', 'pistar-edit-etc-');
file_put_contents($etcStaging, $etcContent);
// Updates complete - copy the working file back to the proper location
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo cp /tmp/c3Rhcm5ldHNlcnZlcg.tmp /etc/starnetserver'); // Move the file back
exec('sudo sed -i \'/\\[starnetserver\\]/d\' /etc/starnetserver'); // Clean up file mangling
exec('sudo chmod 644 /etc/starnetserver'); // Set the correct runtime permissions
exec('sudo chown root:root /etc/starnetserver'); // Set the owner
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($etcStaging) . ' /etc/starnetserver');
exec('sudo mount -o remount,ro /');
@unlink($etcStaging);
// Reload the affected daemon so the saved edits take effect
// without a manual restart. Was commented out historically;
// restored so behaviour matches the other edit_*.php files.
exec('sudo systemctl restart starnetserver.service'); // Reload the daemon
return $success;
}
// Reload the affected daemon
//exec('sudo systemctl restart starnetserver.service'); // Reload the daemon
return $success;
}
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
echo '<form action="" method="post">'."\n";
echo csrf_field_html()."\n";
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
// INI section / key / value all come from the underlying
// /etc/<gateway> file. Same hardening as edit_mmdvmhost.php
// (#23): htmlspecialchars(ENT_QUOTES) on display so a value
// with a literal `"` or `<` (e.g. an Options string) can't
// break out of the `value="…"` attribute. The save handler
// writes the POST bytes verbatim, so legitimate quoted
// values round-trip byte-identically.
$sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8');
echo "<input type=\"hidden\" value=\"$sectionHtml\" name=\"$sectionHtml\" />\n";
echo "<table>\n";
echo "<tr><th colspan=\"2\">$sectionHtml</th></tr>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
$keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8');
$valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"$valueHtml\" /></td></tr>\n";
}
echo "</table>\n";
echo '<input type="submit" value="'.$lang['apply'].'" />'."\n";
echo "<br />\n";
}
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$section\" name=\"$section\" />\n";
echo "<div class=\"settings-card\">\n";
echo "<h3>$section</h3>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"$value\" /></div></div>\n";
}
echo "</div>\n";
echo '<div class="field-actions"><input type="submit" value="'.$lang['apply'].'" /></div>'."\n";
}
echo "</form>";
?>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+54 -110
View File
@@ -1,28 +1,4 @@
<?php
/**
* Expert editor for /etc/timeserver (D-Star time-announcement config).
*
* Flat key=value file like ircddbgateway / dstarrepeater — uses the
* synthetic `[timeserver]` section header trick on read with a CR/LF
* normalisation step before sed-strip on write. Standard Pi-Star
* copy-via-/tmp / mount-rw / restart pattern; daemon:
* timeserver.service.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -41,32 +17,28 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// Do some file wrangling...
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
// tempnam() creates the staging file mode 600 owned by www-data
// with an unguessable random suffix; cleanup is registered up
// front so the file never persists past script exit.
$filepath = tempnam('/tmp', 'pistar-edit-');
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /etc/timeserver ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
exec('sudo cp /etc/timeserver /tmp/dGltZXNlcnZlcg.tmp');
exec('sudo chown www-data:www-data /tmp/dGltZXNlcnZlcg.tmp');
exec('sudo chmod 664 /tmp/dGltZXNlcnZlcg.tmp');
// ini file to open
$filepath = '/tmp/dGltZXNlcnZlcg.tmp';
// Mangle the input
$file_content = "[timeserver]\n".preg_replace('~\r\n?~', "\n", file_get_contents($filepath));
@@ -74,105 +46,77 @@ file_put_contents($filepath, $file_content);
// after the form submit
if($_POST) {
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
}
// this is the function going to update your ini file
function update_ini_file($data, $filepath)
{
$content = "";
function update_ini_file($data, $filepath) {
$content = "";
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
if ($value == '') {
$content .= $key."= \n";
if ($value == '') {
$content .= $key."= \n";
}
else {
$content .= $key."=".$value."\n";
}
}
}
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
$success = fwrite($handle, $content);
fclose($handle);
$success = fwrite($handle, $content);
fclose($handle);
// /etc/timeserver is a FLAT key=value file on disk — the
// synthetic [timeserver] header is injected only for
// parse_ini_file()'s benefit. Strip it via PHP and install
// the cleaned content directly (L-7: drops sudo sed -i;
// L-5: collapses cp + chmod + chown into one atomic install).
// See edit_ircddbgateway.php for the full rationale.
$etcContent = preg_replace('/^\[timeserver\]\r?\n/m', '', $content);
// A3-3: per-request random staging — see edit_ircddbgateway.php
$etcStaging = tempnam('/tmp', 'pistar-edit-etc-');
file_put_contents($etcStaging, $etcContent);
// Updates complete - copy the working file back to the proper location
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo cp /tmp/dGltZXNlcnZlcg.tmp /etc/timeserver'); // Move the file back
exec('sudo sed -i \'/\\[timeserver\\]/d\' /etc/timeserver'); // Clean up file mangling
exec('sudo chmod 644 /etc/timeserver'); // Set the correct runtime permissions
exec('sudo chown root:root /etc/timeserver'); // Set the owner
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($etcStaging) . ' /etc/timeserver');
exec('sudo mount -o remount,ro /');
@unlink($etcStaging);
// Reload the affected daemon
exec('sudo systemctl restart timeserver.service'); // Reload the daemon
return $success;
}
// Reload the affected daemon
exec('sudo systemctl restart timeserver.service'); // Reload the daemon
return $success;
}
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
echo '<form action="" method="post">'."\n";
echo csrf_field_html()."\n";
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
// INI section / key / value all come from the underlying
// /etc/<gateway> file. Same hardening as edit_mmdvmhost.php
// (#23): htmlspecialchars(ENT_QUOTES) on display so a value
// with a literal `"` or `<` (e.g. an Options string) can't
// break out of the `value="…"` attribute. The save handler
// writes the POST bytes verbatim, so legitimate quoted
// values round-trip byte-identically.
$sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8');
echo "<input type=\"hidden\" value=\"$sectionHtml\" name=\"$sectionHtml\" />\n";
echo "<table>\n";
echo "<tr><th colspan=\"2\">$sectionHtml</th></tr>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
$keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8');
$valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"$valueHtml\" /></td></tr>\n";
}
echo "</table>\n";
echo '<input type="submit" value="Save Changes" />'."\n";
echo "<br />\n";
}
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$section\" name=\"$section\" />\n";
echo "<div class=\"settings-card\">\n";
echo "<h3>$section</h3>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"$value\" /></div></div>\n";
}
echo "</div>\n";
echo '<div class="field-actions"><input type="submit" value="Save Changes" /></div>'."\n";
}
echo "</form>";
?>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+57 -117
View File
@@ -1,38 +1,4 @@
<?php
/**
* Expert INI editor for /etc/ysfgateway.
*
* Renders a per-section / per-key form built from parse_ini_file()
* output, accepts edits via POST, then writes the result back to
* /etc/ysfgateway using the standard Pi-Star copy-via-/tmp pattern:
* 1. sudo cp /etc/ysfgateway /tmp/<obfuscated>.tmp + chown www-data
* + chmod 600 (so PHP can edit the temp).
* 2. fopen('w') and fwrite the rebuilt INI text into the temp.
* 3. sudo mount -o remount,rw / + sudo install -m 644 -o root -g
* root temp -> /etc/ysfgateway + sudo mount -o remount,ro / to
* seal the rootfs again. (See edit_mmdvmhost.php for the full
* rationale on the install vs cp+chmod+chown migration.)
* 4. sudo systemctl restart ysfgateway.service to pick up the change.
*
* Admin-only access; the dashboard's Apache basic-auth gate is the
* sole protection. The validation is what's in the form (none, in
* effect — operator-typed values are written raw). Treat with care.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -51,124 +17,98 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// Do some file wrangling...
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
$filepath = tempnam('/tmp', 'pistar-edit-');
exec('sudo cp /etc/ysfgateway ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
// Clean up the /tmp staging file on script exit so the
// editor's potentially-secrets-bearing copy of /etc/<config>
// doesn't persist between requests. @-suppression handles
// the case where a sudo mv (e.g. fulledit_bmapikey) already
// consumed the staging file before script end.
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /etc/ysfgateway /tmp/eXNmZ2F0ZXdheQ.tmp');
exec('sudo chown www-data:www-data /tmp/eXNmZ2F0ZXdheQ.tmp');
exec('sudo chmod 664 /tmp/eXNmZ2F0ZXdheQ.tmp');
// ini file to open
$filepath = '/tmp/eXNmZ2F0ZXdheQ.tmp';
// after the form submit
if($_POST) {
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
}
// this is the function going to update your ini file
function update_ini_file($data, $filepath)
{
$content = "";
function update_ini_file($data, $filepath) {
$content = "";
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
// parse the ini file to get the sections
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
foreach($data as $section=>$values) {
// UnBreak special cases
if (strpos($section, 'aprs') !== false) { $section = str_replace("_", ".", $section); }
else { $section = str_replace("_", " ", $section); $section = str_replace(".", " ", $section); }
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
$content .= $key."=".$value."\n";
}
$content .= "\n";
}
foreach($data as $section=>$values) {
// UnBreak special cases
if (strpos($section, 'aprs') !== false) { $section = str_replace("_", ".", $section); }
else { $section = str_replace("_", " ", $section); $section = str_replace(".", " ", $section); }
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
$content .= $key."=".$value."\n";
}
$content .= "\n";
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
// write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
$success = fwrite($handle, $content);
fclose($handle);
$success = fwrite($handle, $content);
fclose($handle);
// L-5: atomic install replaces the prior cp + chmod + chown
// triplet (rejected by the tightened sudoers — see
// edit_mmdvmhost.php for the full rationale).
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($filepath) . ' /etc/ysfgateway');
exec('sudo mount -o remount,ro /');
// Updates complete - copy the working file back to the proper location
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo cp /tmp/eXNmZ2F0ZXdheQ.tmp /etc/ysfgateway'); // Move the file back
exec('sudo chmod 644 /etc/ysfgateway'); // Set the correct runtime permissions
exec('sudo chown root:root /etc/ysfgateway'); // Set the owner
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
// Reload the affected daemon
exec('sudo systemctl restart ysfgateway.service'); // Reload the daemon
return $success;
}
// Reload the affected daemon
exec('sudo systemctl restart ysfgateway.service'); // Reload the daemon
return $success;
}
// parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
echo '<form action="" method="post">'."\n";
echo csrf_field_html()."\n";
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
// INI section / key / value all come from the underlying
// /etc/<gateway> file. Same hardening as edit_mmdvmhost.php
// (#23): htmlspecialchars(ENT_QUOTES) on display so a value
// with a literal `"` or `<` (e.g. an Options string) can't
// break out of the `value="…"` attribute. The save handler
// writes the POST bytes verbatim, so legitimate quoted
// values round-trip byte-identically.
$sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8');
echo "<input type=\"hidden\" value=\"$sectionHtml\" name=\"$sectionHtml\" />\n";
echo "<table>\n";
echo "<tr><th colspan=\"2\">$sectionHtml</th></tr>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
$keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8');
$valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"$valueHtml\" /></td></tr>\n";
}
echo "</table>\n";
echo '<input type="submit" value="'.$lang['apply'].'" />'."\n";
echo "<br />\n";
}
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$section\" name=\"$section\" />\n";
echo "<div class=\"settings-card\">\n";
echo "<h3>$section</h3>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"$value\" /></div></div>\n";
}
echo "</div>\n";
echo '<div class="field-actions"><input type="submit" value="'.$lang['apply'].'" /></div>'."\n";
}
echo "</form>";
?>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+68 -126
View File
@@ -1,28 +1,4 @@
<?php
/**
* Raw text editor for /etc/bmapi.key (BrandMeister API token).
*
* Creates the file on first save if it doesn't exist, using a sudo
* shell-redirected `echo` (slight deviation from the standard
* staged-write pattern). Used by mmdvmhost/bm_links.php and
* mmdvmhost/bm_manager.php as the Bearer token for BrandMeister
* API queries. No daemon restart.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -41,148 +17,114 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
// /etc/bmapi.key holds the BrandMeister API token, so the
// random-name TOCTOU defence is more important here than for the
// other editors. tempnam() creates the staging file mode 600
// owned by www-data with an unguessable random suffix.
$filepath = tempnam('/tmp', 'pistar-edit-');
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
//Do some file wrangling...
if (file_exists('/etc/bmapi.key')) {
exec('sudo cp /etc/bmapi.key ' . escapeshellarg($filepath));
exec('sudo cp /etc/bmapi.key /tmp/d39fk36sg55433gd.tmp');
} else {
// Seed the staging file with the empty-config default. tempnam
// already created the file owned by www-data, so PHP-side
// file_put_contents writes through directly — no `sudo echo`
// gymnastics needed.
file_put_contents($filepath, "[key]\napikey=None\n");
exec('sudo touch /tmp/d39fk36sg55433gd.tmp');
exec('sudo echo "[key]" > /tmp/d39fk36sg55433gd.tmp');
exec('sudo echo "apikey=None" >> /tmp/d39fk36sg55433gd.tmp');
}
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data /tmp/d39fk36sg55433gd.tmp');
exec('sudo chmod 664 /tmp/d39fk36sg55433gd.tmp');
//ini file to open
$filepath = '/tmp/d39fk36sg55433gd.tmp';
//after the form submit
if($_POST) {
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
}
//this is the function going to update your ini file
function update_ini_file($data, $filepath)
{
$content = "";
function update_ini_file($data, $filepath) {
$content = "";
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
// Strip CR/LF from values before they reach the INI
// line. Same rationale as fulledit_dapnetapi.php: a
// newline inside $value would split into a fresh
// INI line and let an attacker inject extra keys.
$value = str_replace(array("\r", "\n"), "", (string)$value);
if ($value == '') {
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
if ($value == '') {
$content .= $key."=none\n";
} else {
$content .= $key."=".$value."\n";
}
}
$content .= "\n";
}
$content .= $key."=".$value."\n";
}
}
$content .= "\n";
}
//write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
//write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
$success = fwrite($handle, $content);
fclose($handle);
$success = fwrite($handle, $content);
fclose($handle);
// Atomic install: mode + owner set in one syscall sequence.
// /etc/bmapi.key holds the BrandMeister API token — mode 600
// keeps it readable only by www-data (the dashboard user).
// Owner left as www-data because banner_warnings.inc / bm_links.php /
// bm_manager.php read the file directly via parse_ini_file()
// without sudo — switching to root:root here would silently
// break those reads. (Tightening to root:root is a follow-up
// once the read sites move to a sudo-cat helper.)
exec('sudo mount -o remount,rw /');
exec('sudo install -m 600 -o www-data -g www-data '
. escapeshellarg($filepath) . ' /etc/bmapi.key');
exec('sudo mount -o remount,ro /');
// Updates complete - copy the working file back to the proper location
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo mv /tmp/d39fk36sg55433gd.tmp /etc/bmapi.key'); // Move the file back
exec('sudo chmod 644 /etc/bmapi.key'); // Set the correct runtime permissions
exec('sudo chown root:root /etc/bmapi.key'); // Set the owner
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
return $success;
}
return $success;
}
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
if (!isset($parsed_ini['key']['apikey'])) { $parsed_ini['key']['apikey'] = ""; }
echo '<form action="" method="post">'."\n";
echo csrf_field_html()."\n";
foreach($parsed_ini as $section=>$values) {
// INI section / key / value all come from /etc/bmapi.key. Same
// hardening as edit_mmdvmhost.php (#23) but the value lands
// inside a <textarea>...</textarea> body — htmlspecialchars
// covers both attribute and body contexts safely. Browser
// decodes the named entities on form submit, so legitimate
// values containing `<`, `>`, `&`, `"` round-trip byte-identically.
$sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8');
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$sectionHtml\" name=\"$sectionHtml\" />\n";
echo "<table>\n";
echo "<tr><th colspan=\"2\">$sectionHtml</th></tr>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
$keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8');
$valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
if (($key == "Options") || ($value)) {
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><textarea name=\"{$sectionHtml}[$keyHtml]\" cols=\"60\" rows=\"13\">$valueHtml</textarea></td></tr>\n";
}
elseif (($key == "Display") && ($value == '')) {
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><textarea name=\"{$sectionHtml}[$keyHtml]\" cols=\"60\" rows=\"13\">$valueHtml</textarea></td></tr>\n";
}
else {
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><textarea name=\"{$sectionHtml}[$keyHtml]\" cols=\"60\" rows=\"13\">$valueHtml</textarea></td></tr>\n";
}
}
echo "</table>\n";
echo '<input type="submit" value="'.$lang['apply'].'" />'."\n";
echo "<br />\n";
}
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$section\" name=\"$section\" />\n";
echo "<div class=\"settings-card\">\n";
echo "<h3>$section</h3>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
if (($key == "Options") || ($value)) {
echo "<div class=\"field-row\"><div>$key</div><div><textarea name=\"{$section}[$key]\" cols=\"60\" rows=\"13\">$value</textarea></div></div>\n";
}
elseif (($key == "Display") && ($value == '')) {
echo "<div class=\"field-row\"><div>$key</div><div><textarea name=\"{$section}[$key]\" cols=\"60\" rows=\"13\">$value</textarea></div></div>\n";
}
else {
echo "<div class=\"field-row\"><div>$key</div><div><textarea name=\"{$section}[$key]\" cols=\"60\" rows=\"13\">$value</textarea></div></div>\n";
}
}
echo "</div>\n";
echo '<input type="submit" value="'.$lang['apply'].'" />'."\n";
echo "<br />\n";
}
echo "</form>";
?>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+36 -330
View File
@@ -1,30 +1,4 @@
<?php
/**
* Raw text editor for /etc/crontab.
*
* Drops the parse_ini_file dance — the file is a plain text crontab,
* not an INI. POST data lands in a textarea, gets staged to
* /tmp/<obfuscated>.tmp, then sudo-copied back into /etc/crontab.
* No daemon restart (cron rereads its config automatically).
*
* Operator can edit any cron line, including the pistar-* timer hooks
* — read carefully before saving.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -43,334 +17,66 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
/**
* Cron-content denylist scanner.
*
* The dashboard sits behind HTTP basic auth. An operator (or anyone
* with the basic-auth credential) can edit /etc/crontab through this
* page. Cron runs lines as root, so a malicious cron entry is a
* direct privilege escalation off whatever access the dashboard
* already grants.
*
* This is BASIC HARDENING — not airtight. A determined attacker can
* still bypass via:
*
* - Variable indirection: `* * * * * root $X user` where $X is
* defined in a sourced file.
* - External script paths: a benign-looking `/opt/myapp/run.sh`
* can do anything; we scan the cron line, not the script.
* - Brace expansion: `/usr/bin/{passwd,sh}` produces `passwd`
* after shell expansion but our `\b` boundary doesn't trip on
* the literal `passwd,` substring.
*
* Obfuscation patterns are NOT an accepted bypass — there's no
* legitimate cron use for inline base64 / hex / eval / etc., so
* the second rule block below blocks every common decoder
* (base64 / b64decode / fromhex / xxd / openssl -d / shell hex
* escapes / shell unicode escapes / eval). Operators with a
* legitimate need to encode/decode data should put that logic in
* a script file, where this denylist doesn't apply.
*
* The goal is to catch the obvious-bad patterns described in the
* threat brief: password mutation, direct writes to /etc/shadow /
* /etc/passwd / .htpasswd, and the standard shell-listener /
* reverse-shell idioms (nc, socat exec, /dev/tcp redirects,
* download-pipe-shell, python/perl one-liner shells), plus the
* obfuscation patterns commonly used to wrap them.
*
* Comment lines (starting with `#`) and blank lines are exempt.
* Cron variable assignments (PATH=, MAILTO=, SHELL=) are scanned
* the same as commands — the rules below tolerate the false-positive
* surface for the safety win. Known accepted false positive in the
* wild: `MAILTO=passwd@example.com` triggers the password-command
* rule. Operators with that exact email can use a different alias
* or edit /etc/crontab via SSH to bypass the dashboard guard.
*
* Returns an array of human-readable diagnostics, one per problem
* line. Empty array means OK to save.
*
* @param string $content Raw POSTed cron content (already \r-stripped
* and decoded back to bytes by the browser).
* @return array<int,string>
*/
function pistar_cron_validate($content)
{
$rules = array(
// Password modification commands. The leading character class
// matches start-of-line OR a typical token boundary (whitespace,
// path separator, shell metas, or `=` so we catch X=passwd).
// \b at the end stops "passwords" / "chpasswdtest" matching.
'#(?:^|[\s/;|&`$()=])(passwd|chpasswd|htpasswd)\b#i'
=> 'invokes a password-modification command (passwd / chpasswd / htpasswd)',
// usermod -p sets a password hash directly, no PAM, no audit.
'#\busermod\b[^\n]*\s-p\b#i'
=> 'usermod -p sets a password hash directly',
// Reference to any auth file. Even reading these from cron is
// unusual; writing is highly suspicious. Catches /etc/shadow,
// /etc/passwd, /etc/passwd-, and any .htpasswd path including
// /var/www/.htpasswd.
'#(/etc/shadow|/etc/passwd|\.htpasswd)\b#i'
=> 'references an authentication file path',
// Netcat — block any invocation. Operators with a legitimate
// use should script it outside cron. Blocking outright is
// simpler than disambiguating safe vs. unsafe nc flags.
'#(?:^|[\s/;|&`$()=])(nc|ncat|netcat)\b#i'
=> 'invokes nc / ncat / netcat',
// socat with EXEC: or SYSTEM: targets — process-spawning.
'#\bsocat\b[^\n]*\b(EXEC|SYSTEM):#i'
=> 'socat with EXEC/SYSTEM token (spawns a process)',
// Bash builtin /dev/tcp / /dev/udp redirect — classic reverse-
// shell idiom (`bash -i >& /dev/tcp/h/p 0>&1`). No legitimate
// cron use.
'#/dev/(?:tcp|udp)/#'
=> 'reads or writes /dev/tcp/* or /dev/udp/* (reverse-shell pattern)',
// Download-pipe-shell. Catches `curl ... | sh`, `wget ... | bash`,
// etc. The `[^|\n]*` between the fetch tool and the pipe stops
// a stray pipe later in a long line giving false positives.
'#\b(curl|wget|fetch)\b[^|\n]*\|\s*(?:bash|sh|zsh|ksh|dash|exec)\b#i'
=> 'pipes downloaded content directly into a shell',
// python reverse-shell one-liners — heuristic on imports/calls
// commonly used in the published payloads.
'#\bpython[23]?\b[^\n]*-c[^\n]*(?:socket\.socket|os\.dup2)#i'
=> 'python reverse-shell pattern',
// perl reverse-shell one-liner — `perl -e \'use Socket; ...\'`
'#\bperl\b[^\n]*-e[^\n]*\bSocket\b#i'
=> 'perl reverse-shell pattern',
// ====== Obfuscation / decoder patterns ======
// No legitimate cron use for inline encoded payloads. Operators
// who need to encode/decode something should do it in a script,
// not in a cron line. Catches the common payload-wrapper idioms.
// base64 in any form — the command, the python module, perl's
// decode_base64, etc. Substring match (no \b) so it also
// catches "decode_base64", "MIME::Base64", "base64.b64decode".
'#base64#i'
=> 'references base64 (no legitimate cron use; payload-obfuscation idiom)',
// python's base64 module exposes b64decode / b64encode aliases
// that don't contain the literal "base64" substring.
'#\bb64(?:decode|encode)\b#i'
=> 'python b64decode/b64encode (payload decoder)',
// python's bytes.fromhex(...) — turns a hex string into bytes
// for runtime execution.
'#\bfromhex\b#i'
=> 'fromhex (hex payload decoder)',
// xxd dumps / reverses hex. Reverse mode (-r) is a payload
// decoder; forward mode is hex-dumping which has no cron use.
// Block the binary outright.
'#\bxxd\b#i'
=> 'invokes xxd (hex dump / reverse-mode payload decoder)',
// Shell hex / unicode escape literals. `printf \'\x70\x61\x73\x73\'`
// spells "pass" at runtime — pure obfuscation in cron context.
'#\\\\x[0-9a-fA-F]{2}#'
=> 'shell hex-escape literal (\\xNN — payload obfuscation)',
'#\\\\u[0-9a-fA-F]{4}#'
=> 'shell unicode-escape literal (\\uNNNN — payload obfuscation)',
// eval interprets its argument as shell — opaque to anyone
// reading the cron line.
'#(?:^|[\s/;|&`$()=])eval\b#i'
=> 'invokes eval (opaque shell-string execution)',
// openssl -d in any context (enc -d, base64 -d, ...) is a
// generic decoder.
'#\bopenssl\b[^\n]*\s-d\b#i'
=> 'openssl with -d (decoder)',
);
$blockers = array();
$lines = explode("\n", $content);
foreach ($lines as $i => $line) {
$trimmed = ltrim($line);
if ($trimmed === '' || $trimmed[0] === '#') {
continue;
}
foreach ($rules as $pattern => $reason) {
if (preg_match($pattern, $line)) {
$blockers[] = sprintf(
'line %d (%s): %s',
$i + 1,
$reason,
substr(trim($line), 0, 100)
);
break; // one diagnostic per line is enough
}
}
}
return $blockers;
}
$cronBlockers = array();
$saveError = false;
$saveOk = false;
$readError = false;
if(isset($_POST['data'])) {
// Normalise CRLF → LF before validation so a Windows browser's
// submission scans byte-identical to the on-disk form.
$rawData = str_replace("\r", "", (string)$_POST['data']);
$cronBlockers = pistar_cron_validate($rawData);
// File Wrangling
exec('sudo cp /etc/crontab /tmp/a8h4d8n3c83h4.tmp');
exec('sudo chown www-data:www-data /tmp/a8h4d8n3c83h4.tmp');
exec('sudo chmod 664 /tmp/a8h4d8n3c83h4.tmp');
if (!empty($cronBlockers)) {
// Validation failed — DO NOT touch /etc/crontab. Surface
// the diagnostics in the page and re-render the form
// with the operator's submitted content so they can
// fix in place without retyping.
$theData = $rawData;
error_log('Pi-Star fulledit_cron.php: rejected save with '
. count($cronBlockers) . ' blocked line(s)');
} else {
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU
// rationale. Per-request random staging path defeats
// the predictable-name pre-create / symlink class.
$filepath = tempnam('/tmp', 'pistar-edit-');
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
$fh = fopen($filepath, 'w');
if ($fh !== false) {
fwrite($fh, $rawData);
fclose($fh);
}
// Open the file and write the data
$filepath = '/tmp/a8h4d8n3c83h4.tmp';
$fh = fopen($filepath, 'w');
fwrite($fh, str_replace("\r", "", $_POST['data']));
fclose($fh);
exec('sudo mount -o remount,rw /');
exec('sudo cp /tmp/a8h4d8n3c83h4.tmp /etc/crontab');
exec('sudo chmod 644 /etc/crontab');
exec('sudo chown root:root /etc/crontab');
exec('sudo mount -o remount,ro /');
// Write through the one sudoers-allowlisted primitive for
// this file: `sudo sed -i … /etc/crontab`. (The previous
// `install … /etc/crontab` had no matching sudoers rule and
// was silently rejected — saves looked successful but never
// touched disk.) The operator's bytes are poured over the
// file with sed's `1r <file>` + `d` idiom: read the staged
// temp file in on the first cycle, delete every original
// line, leaving an exact byte-for-byte copy. The operator's
// content never enters the sed script — only the temp file
// PATH does — so there is no sed/shell-injection surface.
// sed -i preserves the existing root:root 644 (verified on a
// live host), so no follow-up chown/chmod is needed.
// $sedOut is an unused placeholder (sed -i is silent on
// success); the save status we act on is the exit code $rc
// plus the read-back comparison below.
$rc = 0;
$sedOut = array();
exec('sudo mount -o remount,rw /');
exec('sudo sed -i -e ' . escapeshellarg('1r ' . $filepath) . ' -e d /etc/crontab', $sedOut, $rc);
exec('sudo mount -o remount,ro /'); // always re-protect the rootfs
// Loud-failure check. The old code unconditionally rendered
// "as saved", which is exactly how the broken write hid for
// weeks. Read /etc/crontab back (world-readable) and compare
// byte-for-byte: a non-zero sed exit OR any mismatch means
// the save did not land. This also covers the degenerate
// empty-crontab corner, where `1r` never fires and the file
// would be left empty.
$onDisk = @file_get_contents('/etc/crontab');
if ($rc !== 0 || $onDisk !== $rawData) {
$saveError = true;
error_log('Pi-Star fulledit_cron.php: crontab save FAILED (sed rc='
. $rc . ', readback '
. ($onDisk === $rawData ? 'matched' : 'MISMATCHED') . ')');
} else {
$saveOk = true;
}
// Re-render the submitted content either way so a failed
// save never discards the operator's edits.
$theData = $rawData;
}
} else {
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
$filepath = tempnam('/tmp', 'pistar-edit-');
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /etc/crontab ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
// Re-open the file and read it
$fh = fopen($filepath, 'r');
if ($fh === false) {
// The privileged copy of /etc/crontab could not be staged
// (sudo cp/chown/chmod failed). Surface a read error and show
// an empty editor rather than a blank-but-editable textarea —
// saving from the latter would overwrite /etc/crontab with
// nothing.
$theData = '';
$readError = true;
error_log('Pi-Star fulledit_cron.php: could not stage /etc/crontab for reading');
} else {
$sz = filesize($filepath);
$theData = $sz > 0 ? fread($fh, $sz) : '';
fclose($fh);
}
$theData = fread($fh, filesize($filepath));
} else {
// File Wrangling
exec('sudo cp /etc/crontab /tmp/a8h4d8n3c83h4.tmp');
exec('sudo chown www-data:www-data /tmp/a8h4d8n3c83h4.tmp');
exec('sudo chmod 664 /tmp/a8h4d8n3c83h4.tmp');
// Open the file and read it
$filepath = '/tmp/a8h4d8n3c83h4.tmp';
$fh = fopen($filepath, 'r');
$theData = fread($fh, filesize($filepath));
}
fclose($fh);
?>
<?php if (!empty($cronBlockers)) { ?>
<div style="background-color: #ff9090; color: #f01010; padding: 10px; margin: 0 0 10px 0;">
<b>Save rejected &mdash; the cron editor blocks lines that match common privilege-escalation patterns.</b>
<ul style="margin: 8px 0 0 16px;">
<?php foreach ($cronBlockers as $b) {
echo '<li>' . htmlspecialchars($b, ENT_QUOTES, 'UTF-8') . "</li>\n";
} ?>
</ul>
<p style="margin: 8px 0 0 0;">Edit the offending line(s) and re-submit, or revert your changes.
If you have a legitimate need to schedule one of these patterns, use SSH and edit
<code>/etc/crontab</code> directly &mdash; the dashboard editor enforces these checks
to limit the damage of stolen dashboard credentials.</p>
</div>
<?php } ?>
<?php if ($saveError) { ?>
<div style="background-color: #ff9090; color: #f01010; padding: 10px; margin: 0 0 10px 0;">
<b>Save failed &mdash; /etc/crontab was not updated.</b>
<p style="margin: 8px 0 0 0;">The write was rejected or the file did not match after saving. Your edits are
preserved below &mdash; try again, or edit <code>/etc/crontab</code> directly over SSH.</p>
</div>
<?php } elseif ($saveOk) { ?>
<div id="cronSaveOk" style="background-color: #c0f0c0; color: #106010; padding: 10px; margin: 0 0 10px 0;">
<b>Crontab saved.</b>
</div>
<script>
// Auto-dismiss the success banner after 5s. The error banners are left in
// place on purpose so a failed save can never scroll away unnoticed.
setTimeout(function () {
var el = document.getElementById('cronSaveOk');
if (!el) { return; }
// Honour reduced-motion preferences: snap rather than fade.
var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
el.style.transition = reduce ? 'none' : 'opacity 0.6s ease';
el.style.opacity = '0';
setTimeout(function () { if (el.parentNode) { el.parentNode.removeChild(el); } }, 650);
}, 5000);
</script>
<?php } ?>
<?php if ($readError) { ?>
<div style="background-color: #ff9090; color: #f01010; padding: 10px; margin: 0 0 10px 0;">
<b>Could not read /etc/crontab.</b>
<p style="margin: 8px 0 0 0;">The current crontab could not be loaded, so the editor below is empty.
<b>Do not save</b> &mdash; doing so would overwrite /etc/crontab with nothing. Reload the page, or edit
<code>/etc/crontab</code> directly over SSH.</p>
</div>
<?php } ?>
<h2>System Cron (Full Edit)</h2>
<div class="settings-card" style="padding-top:16px;">
<form name="test" method="post" action="">
<?php csrf_field(); ?>
<textarea name="data" cols="80" rows="45"><?php echo htmlspecialchars((string)$theData, ENT_QUOTES, 'UTF-8'); ?></textarea><br />
<input type="submit" name="submit" value="<?php echo $lang['apply']; ?>" />
<textarea class="raw-editor" name="data"><?php echo $theData; ?></textarea><br />
<div class="field-actions"><input type="submit" name="submit" value="<?php echo $lang['apply']; ?>" /></div>
</form>
</div>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+72 -122
View File
@@ -1,26 +1,4 @@
<?php
/**
* Raw text editor for /etc/dapnetapi.key (DAPNET credentials).
*
* Same first-save-via-shell-echo pattern as fulledit_bmapikey.php.
* Read by dapnetgateway at startup; no daemon restart from this
* editor.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -39,143 +17,115 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
// /etc/dapnetapi.key holds DAPNET credentials, so the random-name
// TOCTOU defence is more important here than for the other editors.
$filepath = tempnam('/tmp', 'pistar-edit-');
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
// Make the bare config if we dont have one
if (file_exists('/etc/dapnetapi.key')) {
exec('sudo cp /etc/dapnetapi.key ' . escapeshellarg($filepath));
exec('sudo cp /etc/dapnetapi.key /tmp/jsADGHwf9sj294.tmp');
exec('sudo chown www-data:www-data /tmp/jsADGHwf9sj294.tmp');
} else {
// Seed the staging file with the empty-config skeleton.
// tempnam already created it owned by www-data, so PHP-side
// file_put_contents writes through directly.
file_put_contents(
$filepath,
"[DAPNETAPI]\nUSER=\nPASS=\nTRXAREA=\n"
);
exec('sudo touch /tmp/jsADGHwf9sj294.tmp');
exec('sudo chown www-data:www-data /tmp/jsADGHwf9sj294.tmp');
exec('echo "[DAPNETAPI]" > /tmp/jsADGHwf9sj294.tmp');
exec('echo "USER=" >> /tmp/jsADGHwf9sj294.tmp');
exec('echo "PASS=" >> /tmp/jsADGHwf9sj294.tmp');
exec('echo "TRXAREA=" >> /tmp/jsADGHwf9sj294.tmp');
}
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
//Do some file wrangling...
exec('sudo chmod 664 /tmp/jsADGHwf9sj294.tmp');
//ini file to open
$filepath = '/tmp/jsADGHwf9sj294.tmp';
//after the form submit
if($_POST) {
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
$data = $_POST;
//update ini file, call function
update_ini_file($data, $filepath);
}
//this is the function going to update your ini file
function update_ini_file($data, $filepath)
{
$content = "";
function update_ini_file($data, $filepath) {
$content = "";
//parse the ini file to get the sections
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
//parse the ini file to get the sections
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
// Strip CR/LF from values before they land in the INI
// file. The save handler writes `$key=$value\n` and a
// newline inside $value would split the value into a
// new INI line, allowing injection of arbitrary
// additional keys (e.g. `value=foo\nDEBUG=1`). On a
// single-operator device the practical risk is low
// but the sanitiser is one line.
$value = str_replace(array("\r", "\n"), "", (string)$value);
$content .= $key."=".$value."\n";
}
$content .= "\n";
}
foreach($data as $section=>$values) {
// UnBreak special cases
$section = str_replace("_", " ", $section);
$content .= "[".$section."]\n";
//append the values
foreach($values as $key=>$value) {
$content .= $key."=".$value."\n";
}
$content .= "\n";
}
//write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
//write it into file
if (!$handle = fopen($filepath, 'w')) {
return false;
}
$success = fwrite($handle, $content);
fclose($handle);
$success = fwrite($handle, $content);
fclose($handle);
// Atomic install: mode + owner set in one syscall sequence.
// /etc/dapnetapi.key holds DAPNET credentials — mode 600 keeps
// them readable only by www-data. Owner left as www-data
// because the dapnetgateway daemon reads via the dashboard
// (and dashboard reads it directly without sudo elsewhere);
// see fulledit_bmapikey.php for the same rationale.
exec('sudo mount -o remount,rw /');
exec('sudo install -m 600 -o www-data -g www-data '
. escapeshellarg($filepath) . ' /etc/dapnetapi.key');
exec('sudo mount -o remount,ro /');
// Updates complete - copy the working file back to the proper location
exec('sudo mount -o remount,rw /'); // Make rootfs writable
exec('sudo cp /tmp/jsADGHwf9sj294.tmp /etc/dapnetapi.key'); // Move the file back
exec('sudo chmod 644 /etc/dapnetapi.key'); // Set the correct runtime permissions
exec('sudo chown root:root /etc/dapnetapi.key'); // Set the owner
exec('sudo mount -o remount,ro /'); // Make rootfs read-only
return $success;
}
return $success;
}
//parse the ini file using default parse_ini_file() PHP function
$parsed_ini = parse_ini_file($filepath, true);
echo '<form action="" method="post">'."\n";
echo csrf_field_html()."\n";
foreach($parsed_ini as $section=>$values) {
// Same hardening as edit_mmdvmhost.php (#23): escape every INI
// section / key / value before HTML interpolation. The save
// handler writes POST bytes verbatim so legitimate values
// (including any with `"` or `<`) round-trip byte-identically.
$sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8');
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$sectionHtml\" name=\"$sectionHtml\" />\n";
echo "<table>\n";
echo "<tr><th colspan=\"2\">$sectionHtml</th></tr>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
$keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8');
$valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
if (($key == "Options") || ($value)) {
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"$valueHtml\" /></td></tr>\n";
}
elseif (($key == "Display") && ($value == '')) {
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"None\" /></td></tr>\n";
}
else {
echo "<tr><td align=\"right\" width=\"30%\">$keyHtml</td><td align=\"left\"><input type=\"text\" name=\"{$sectionHtml}[$keyHtml]\" value=\"0\" /></td></tr>\n";
}
}
echo "</table>\n";
echo '<input type="submit" value="'.$lang['apply'].'" />'."\n";
echo "<br />\n";
}
foreach($parsed_ini as $section=>$values) {
// keep the section as hidden text so we can update once the form submitted
echo "<input type=\"hidden\" value=\"$section\" name=\"$section\" />\n";
echo "<div class=\"settings-card\">\n";
echo "<h3>$section</h3>\n";
// print all other values as input fields, so can edit.
// note the name='' attribute it has both section and key
foreach($values as $key=>$value) {
if (($key == "Options") || ($value)) {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"$value\" /></div></div>\n";
}
elseif (($key == "Display") && ($value == '')) {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"None\" /></div></div>\n";
}
else {
echo "<div class=\"field-row\"><div>$key</div><div><input type=\"text\" name=\"{$section}[$key]\" value=\"0\" /></div></div>\n";
}
}
echo "</div>\n";
echo '<input type="submit" value="'.$lang['apply'].'" />'."\n";
echo "<br />\n";
}
echo "</form>";
?>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+36 -57
View File
@@ -1,28 +1,4 @@
<?php
/**
* Raw text editor for /etc/dmrgateway.
*
* Companion to edit_dmrgateway.php — same target file, but this view
* exposes the entire INI as a single textarea so the operator can
* make edits the structured form doesn't cover. POST data is staged
* to /tmp/<obfuscated>.tmp then sudo-copied back to /etc/dmrgateway.
* Restarts BOTH mmdvmhost.service AND dmrgateway.service after save.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -41,67 +17,70 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
$filepath = tempnam('/tmp', 'pistar-edit-');
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /etc/dmrgateway ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
if(isset($_POST['data'])) {
// Write submitted data into the staging file.
// File Wrangling
exec('sudo cp /etc/dmrgateway /tmp/fmehg65694eg.tmp');
exec('sudo chown www-data:www-data /tmp/fmehg65694eg.tmp');
exec('sudo chmod 664 /tmp/fmehg65694eg.tmp');
// Open the file and write the data
$filepath = '/tmp/fmehg65694eg.tmp';
$fh = fopen($filepath, 'w');
fwrite($fh, $_POST['data']);
fclose($fh);
// L-5: atomic install replaces the prior cp + chmod + chown
// triplet (rejected by the tightened sudoers — see
// edit_mmdvmhost.php for the full rationale).
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($filepath) . ' /etc/dmrgateway');
exec('sudo cp /tmp/fmehg65694eg.tmp /etc/dmrgateway');
exec('sudo chmod 644 /etc/dmrgateway');
exec('sudo chown root:root /etc/dmrgateway');
exec('sudo mount -o remount,ro /');
// Reload the affected daemon
exec('sudo systemctl restart mmdvmhost.service'); // Reload MMDVMHost
exec('sudo systemctl restart dmrgateway.service'); // Reload DMRGateway
}
exec('sudo systemctl restart mmdvmhost.service'); // Reload MMDVMHost
exec('sudo systemctl restart dmrgateway.service'); // Reload DMRGateway
// Re-read for the form's textarea.
$fh = fopen($filepath, 'r');
$theData = fread($fh, filesize($filepath));
// Re-open the file and read it
$fh = fopen($filepath, 'r');
$theData = fread($fh, filesize($filepath));
} else {
// File Wrangling
exec('sudo cp /etc/dmrgateway /tmp/fmehg65694eg.tmp');
exec('sudo chown www-data:www-data /tmp/fmehg65694eg.tmp');
exec('sudo chmod 664 /tmp/fmehg65694eg.tmp');
// Open the file and read it
$filepath = '/tmp/fmehg65694eg.tmp';
$fh = fopen($filepath, 'r');
$theData = fread($fh, filesize($filepath));
}
fclose($fh);
?>
<h2>DMR Gateway (Full Edit)</h2>
<div class="settings-card" style="padding-top:16px;">
<form name="test" method="post" action="">
<?php csrf_field(); ?>
<textarea name="data" cols="80" rows="45"><?php echo htmlspecialchars((string)$theData, ENT_QUOTES, 'UTF-8'); ?></textarea><br />
<input type="submit" name="submit" value="<?php echo $lang['apply']; ?>" />
<textarea class="raw-editor" name="data"><?php echo $theData; ?></textarea><br />
<div class="field-actions"><input type="submit" name="submit" value="<?php echo $lang['apply']; ?>" /></div>
</form>
</div>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+35 -61
View File
@@ -1,26 +1,4 @@
<?php
/**
* Raw text editor for /etc/pistar-remote.
*
* Pi-Star Remote-Control daemon config (DTMF-driven actions, command
* mappings, etc.). Saved via the standard staged-write pattern;
* daemon: pistar-remote.service.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -39,73 +17,69 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
// tempnam() up front so both POST and GET branches share the same
// per-request random staging path. Cleanup is registered once.
$filepath = tempnam('/tmp', 'pistar-edit-');
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /etc/pistar-remote ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
if(isset($_POST['data'])) {
// Write submitted data into the staging file.
// File Wrangling
exec('sudo cp /etc/pistar-remote /tmp/fmehg65934eg.tmp');
exec('sudo chown www-data:www-data /tmp/fmehg65934eg.tmp');
exec('sudo chmod 664 /tmp/fmehg65934eg.tmp');
// Open the file and write the data
$filepath = '/tmp/fmehg65934eg.tmp';
$fh = fopen($filepath, 'w');
fwrite($fh, $_POST['data']);
fclose($fh);
// Atomic install: content + mode + owner set in one syscall
// sequence. /etc/pistar-remote is read by the pistar-remote
// service daemon and by dashboard pages via parse_ini_file
// (no sudo); 644 root:root keeps both working — same target
// as the bmapikey/dapnetapi B5 migration.
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($filepath) . ' /etc/pistar-remote');
exec('sudo cp /tmp/fmehg65934eg.tmp /etc/pistar-remote');
exec('sudo chmod 644 /etc/pistar-remote');
exec('sudo chown root:root /etc/pistar-remote');
exec('sudo mount -o remount,ro /');
// Reload the affected daemon
exec('sudo systemctl restart pistar-remote.service'); // Reload the daemon
}
exec('sudo systemctl restart pistar-remote.service'); // Reload the daemon
// Re-read the staging file for the form's textarea — in the POST
// branch this returns what the operator just submitted (and what's
// now in /etc); in the GET branch it returns the unmodified /etc
// content.
$fh = fopen($filepath, 'r');
$theData = fread($fh, filesize($filepath));
// Re-open the file and read it
$fh = fopen($filepath, 'r');
$theData = fread($fh, filesize($filepath));
} else {
// File Wrangling
exec('sudo cp /etc/pistar-remote /tmp/fmehg65934eg.tmp');
exec('sudo chown www-data:www-data /tmp/fmehg65934eg.tmp');
exec('sudo chmod 664 /tmp/fmehg65934eg.tmp');
// Open the file and read it
$filepath = '/tmp/fmehg65934eg.tmp';
$fh = fopen($filepath, 'r');
$theData = fread($fh, filesize($filepath));
}
fclose($fh);
?>
<h2>PiStar-Remote (Full Edit)</h2>
<div class="settings-card" style="padding-top:16px;">
<form name="test" method="post" action="">
<?php csrf_field(); ?>
<textarea name="data" cols="80" rows="45"><?php echo htmlspecialchars((string)$theData, ENT_QUOTES, 'UTF-8'); ?></textarea><br />
<input type="submit" name="submit" value="<?php echo $lang['apply']; ?>" />
<textarea class="raw-editor" name="data"><?php echo $theData; ?></textarea><br />
<div class="field-actions"><input type="submit" name="submit" value="<?php echo $lang['apply']; ?>" /></div>
</form>
</div>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+33 -53
View File
@@ -1,26 +1,4 @@
<?php
/**
* Raw text editor for /usr/local/etc/RSSI.dat.
*
* The modem RSSI calibration table — non-/etc location, edited the
* same way as the other fulledit_* files. No daemon restart (MMDVMHost
* re-reads RSSI.dat on its own).
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -39,64 +17,66 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
$filepath = tempnam('/tmp', 'pistar-edit-');
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /usr/local/etc/RSSI.dat ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
if(isset($_POST['data'])) {
// Write submitted data into the staging file.
// File Wrangling
exec('sudo cp /usr/local/etc/RSSI.dat /tmp/yAw432GHs5.tmp');
exec('sudo chown www-data:www-data /tmp/yAw432GHs5.tmp');
exec('sudo chmod 664 /tmp/yAw432GHs5.tmp');
// Open the file and write the data
$filepath = '/tmp/yAw432GHs5.tmp';
$fh = fopen($filepath, 'w');
fwrite($fh, $_POST['data']);
fclose($fh);
// Atomic install: /usr/local/etc/RSSI.dat is read by the
// MMDVMHost daemon (root) and is world-readable for the
// dashboard's editor round-trip; 644 root:root preserves both.
exec('sudo mount -o remount,rw /');
exec('sudo install -m 644 -o root -g root '
. escapeshellarg($filepath) . ' /usr/local/etc/RSSI.dat');
exec('sudo cp /tmp/yAw432GHs5.tmp /usr/local/etc/RSSI.dat');
exec('sudo chmod 644 /usr/local/etc/RSSI.dat');
exec('sudo chown root:root /usr/local/etc/RSSI.dat');
exec('sudo mount -o remount,ro /');
}
// Re-read for the form's textarea (POST: shows just-saved content;
// GET: shows current /etc content).
$fh = fopen($filepath, 'r');
$theData = fread($fh, filesize($filepath));
// Re-open the file and read it
$fh = fopen($filepath, 'r');
$theData = fread($fh, filesize($filepath));
} else {
// File Wrangling
exec('sudo cp /usr/local/etc/RSSI.dat /tmp/yAw432GHs5.tmp');
exec('sudo chown www-data:www-data /tmp/yAw432GHs5.tmp');
exec('sudo chmod 664 /tmp/yAw432GHs5.tmp');
// Open the file and read it
$filepath = '/tmp/yAw432GHs5.tmp';
$fh = fopen($filepath, 'r');
$theData = fread($fh, filesize($filepath));
}
fclose($fh);
?>
<h2>RSSI Dat (Full Edit)</h2>
<div class="settings-card" style="padding-top:16px;">
<form name="test" method="post" action="">
<?php csrf_field(); ?>
<textarea name="data" cols="80" rows="45"><?php echo htmlspecialchars((string)$theData, ENT_QUOTES, 'UTF-8'); ?></textarea><br />
<input type="submit" name="submit" value="<?php echo $lang['apply']; ?>" />
<textarea class="raw-editor" name="data"><?php echo $theData; ?></textarea><br />
<div class="field-actions"><input type="submit" name="submit" value="<?php echo $lang['apply']; ?>" /></div>
</form>
</div>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+33 -61
View File
@@ -1,27 +1,4 @@
<?php
/**
* Raw text editor for /etc/wpa_supplicant/wpa_supplicant.conf.
*
* Operator-editable WiFi configuration. Standard staged-write pattern.
*
* No daemon restart from this file; the operator must reset the WiFi
* adapter (admin/wifi.php has buttons) for changes to take effect.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -40,71 +17,66 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php
// A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale.
// wpa_supplicant.conf carries the WPA PSK in cleartext, so the
// random-name TOCTOU defence is more important here than for the
// other editors: a predictable name attack would let a local
// attacker pre-create /tmp/<known>.tmp as a symlink to a target
// they control reading and have our `sudo cp` follow it.
$filepath = tempnam('/tmp', 'pistar-edit-');
register_shutdown_function(function() use ($filepath) { @unlink($filepath); });
exec('sudo cp /etc/wpa_supplicant/wpa_supplicant.conf ' . escapeshellarg($filepath));
exec('sudo chown www-data:www-data ' . escapeshellarg($filepath));
exec('sudo chmod 600 ' . escapeshellarg($filepath));
if(isset($_POST['data'])) {
// Write submitted data into the staging file.
// File Wrangling
exec('sudo cp /etc/wpa_supplicant/wpa_supplicant.conf /tmp/k45s7h5s9k3.tmp');
exec('sudo chown www-data:www-data /tmp/k45s7h5s9k3.tmp');
exec('sudo chmod 664 /tmp/k45s7h5s9k3.tmp');
// Open the file and write the data
$filepath = '/tmp/k45s7h5s9k3.tmp';
$fh = fopen($filepath, 'w');
fwrite($fh, $_POST['data']);
fclose($fh);
// Atomic install: mode + owner set in one syscall sequence.
// wpa_supplicant.conf carries the WPA PSK in cleartext as
// `psk=…hex…` — mode 600 root:root keeps every other local
// user (and any sandbox-escape from another service) from
// reading it. wpa_supplicant runs as root, so the daemon's
// own access is unaffected.
exec('sudo mount -o remount,rw /');
exec('sudo install -m 600 -o root -g root '
. escapeshellarg($filepath) . ' /etc/wpa_supplicant/wpa_supplicant.conf');
exec('sudo cp /tmp/k45s7h5s9k3.tmp /etc/wpa_supplicant/wpa_supplicant.conf');
exec('sudo chmod 644 /etc/wpa_supplicant/wpa_supplicant.conf');
exec('sudo chown www-data:www-data /etc/wpa_supplicant/wpa_supplicant.conf');
exec('sudo mount -o remount,ro /');
}
// Re-read for the form's textarea.
$fh = fopen($filepath, 'r');
$theData = fread($fh, filesize($filepath));
// Re-open the file and read it
$fh = fopen($filepath, 'r');
$theData = fread($fh, filesize($filepath));
} else {
// File Wrangling
exec('sudo cp /etc/wpa_supplicant/wpa_supplicant.conf /tmp/k45s7h5s9k3.tmp');
exec('sudo chown www-data:www-data /tmp/k45s7h5s9k3.tmp');
exec('sudo chmod 664 /tmp/k45s7h5s9k3.tmp');
// Open the file and read it
$filepath = '/tmp/k45s7h5s9k3.tmp';
$fh = fopen($filepath, 'r');
$theData = fread($fh, filesize($filepath));
}
fclose($fh);
?>
<h2>WiFi Config (Full Edit)</h2>
<div class="settings-card" style="padding-top:16px;">
<form name="test" method="post" action="">
<?php csrf_field(); ?>
<textarea name="data" cols="80" rows="45"><?php echo htmlspecialchars((string)$theData, ENT_QUOTES, 'UTF-8'); ?></textarea><br />
<input type="submit" name="submit" value="<?php echo $lang['apply']; ?>" />
<textarea class="raw-editor" name="data"><?php echo $theData; ?></textarea><br />
<div class="field-actions"><input type="submit" name="submit" value="<?php echo $lang['apply']; ?>" /></div>
</form>
</div>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+17 -43
View File
@@ -1,21 +1,4 @@
<?php
/**
* Expert-section landing page.
*
* Renders the warning banner explaining what the expert editors do
* and that hand-edits can be overwritten by the dashboard. The
* navigation strip with links to every editor lives in
* header-menu.inc which is included from this file (and every
* editor page).
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
//Load the Pi-Star Release file
@@ -34,51 +17,42 @@ require_once('../config/version.php');
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Expert Editor" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Expert Editor" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - Digital Voice Dashboard - Expert Editor</title>
<title>CDN - Digital Voice Dashboard - Expert Editor</title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<table width="100%">
<tr><th>Expert Editors</th></tr>
<tr><td align="center">
<h2>**WARNING**</h2>
Pi-Star Expert editors have been created to make editing some of the extra settings in the<br />
config files more simple, allowing you to update some areas of the config files without the<br />
<h2>Expert Editors</h2>
<div class="settings-card">
<div class="field-row"><div class="field-note" style="flex:1 1 100%;font-size:13px;">
<b>**WARNING**</b><br /><br />
CDN Expert editors have been created to make editing some of the extra settings in the
config files more simple, allowing you to update some areas of the config files without the
need to login to your Pi over SSH.<br />
<br />
Please keep in mind when making your edits here, that these config files can be updated by<br />
the dashboard, and that your edits can be over-written. It is assumed that you already know<br />
what you are doing editing the files by hand, and that you understand what parts of the files<br />
Please keep in mind when making your edits here, that these config files can be updated by
the dashboard, and that your edits can be over-written. It is assumed that you already know
what you are doing editing the files by hand, and that you understand what parts of the files
are maintained by the dashboard.<br />
<br />
With that warning in mind, you are free to make any changes you like, for help come to the Facebook<br />
With that warning in mind, you are free to make any changes you like, for help come to the Facebook
group (link at the bottom of the page) and ask for help if / when you need it.<br />
73 and enjoy your Pi-Star experiance.<br />
Pi-Star UK Team.<br />
<br />
</td></tr>
</table>
73 and enjoy your CDN experiance.<br />
CDN Team.
</div></div>
</div>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_new">here</a>.<br />
</div>
</div>
</body>
</html>
+9 -35
View File
@@ -1,23 +1,4 @@
<?php
/**
* Network jitter test runner.
*
* Lets the operator pick a target (BrandMeister / DMR+ / HBLink) and
* runs `sudo /usr/local/sbin/pistar-jittertest <target>` on the
* device; output streams to /var/log/pi-star/pi-star_icmptest.log
* which this page tails via AJAX (jquery-timing $.repeat).
*
* Uses system()/exec() rather than the staged-write pattern — no
* file edits, just a privileged background process.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
// Load the Pi-Star Release file
@@ -37,9 +18,8 @@ if ($_SERVER["PHP_SELF"] == "/admin/expert/jitter_test.php") {
} else { $target = "DMR+"; }
if (!isset($_GET['ajax'])) {
// truncate creates+clears the log file in one synchronous call —
// see admin/update.php for the full rationale.
system('sudo truncate -s 0 /var/log/pi-star/pi-star_icmptest.log');
system('sudo touch /var/log/pi-star/pi-star_icmptest.log > /dev/null 2>&1 &');
system('sudo echo "" > /var/log/pi-star/pi-star_icmptest.log > /dev/null 2>&1 &');
system('sudo /usr/local/sbin/pistar-jittertest '.$target.' > /dev/null 2>&1 &');
}
@@ -88,13 +68,13 @@ if ($_SERVER["PHP_SELF"] == "/admin/expert/jitter_test.php") {
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Update" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Update" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - ".$lang['update'];?></title>
<title>CDN - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - ".$lang['update'];?></title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
<script type="text/javascript" src="/jquery.min.js"></script>
<script type="text/javascript" src="/jquery-timing.min.js"></script>
@@ -114,20 +94,13 @@ if ($_SERVER["PHP_SELF"] == "/admin/expert/jitter_test.php") {
</script>
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<table width="100%">
<tr><th>Test Running</th></tr>
<tr><td align="left"><div id="tail">Starting test, please wait...<br /></div></td></tr>
</table>
<h2>Jitter Test Running</h2>
<div class="settings-card" style="padding-top:16px;">
<div id="tail">Starting test, please wait...<br /></div>
</div>
<div class="footer">
Pi-Star web config, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_blank">here</a>.<br />
<br />
</div>
</div>
</body>
@@ -135,3 +108,4 @@ if ($_SERVER["PHP_SELF"] == "/admin/expert/jitter_test.php") {
<?php
}
?>
+42 -123
View File
@@ -1,31 +1,4 @@
<?php
/**
* Modem firmware upgrade runner.
*
* Calls `sudo /usr/local/sbin/pistar-modemupgrade list` to enumerate
* supported modem variants, then `sudo /usr/local/sbin/pistar-
* modemupgrade <variant>` to flash the selected one. Output streams
* to /var/log/pi-star/pi-star_modemflash.log; tailed via AJAX.
*
* The selected variant is run through escapeshellarg() before being
* passed to the upgrade script (defence-in-depth — the picklist is
* already constrained to script-reported names).
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Must run BEFORE any output: bootstraps the session on GET (so
// Set-Cookie ships) and rejects forged POSTs cleanly with 403
// before any state change (sed-i, fopen+fwrite, sudo cp, etc.).
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
// Load the Pi-Star Release file
@@ -42,61 +15,29 @@ setlocale(LC_ALL, "LC_CTYPE=en_GB.UTF-8;LC_NUMERIC=C;LC_TIME=C;LC_COLLATE=C;LC_M
if ($_SERVER["PHP_SELF"] == "/admin/expert/modem_fw_upgrade.php") {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['modem'])) {
// Whitelist-validate against the canonical modem list that
// pistar-modemupgrade itself reports. The script is the
// authoritative source of "what modem variants this device
// knows how to flash" — POSTed values that don't appear in
// that list are silently dropped (the operator just sees no
// flash happen). escapeshellarg() below is still defence-
// in-depth at the shell layer; this is the application-layer
// gate that stops the script from being invoked with
// attacker-controlled args at all. Mirrors the mmdvmcal
// command whitelist in admin/calibration.php.
$validModems = array_filter(array_map('trim',
explode("\n", (string)shell_exec('sudo /usr/local/sbin/pistar-modemupgrade list'))
));
if (in_array($_POST['modem'], $validModems, true)) {
$selectedOption = $_POST['modem'];
} else {
error_log('Pi-Star modem_fw_upgrade.php: rejected modem=' . substr((string)$_POST['modem'], 0, 64));
}
}
if (isset($_POST['modem'])) {
$selectedOption = $_POST['modem'];
}
}
if (!isset($_GET['ajax'])) {
// truncate creates+clears the log file in one synchronous call —
// see admin/update.php for the full rationale.
system('sudo truncate -s 0 /var/log/pi-star/pi-star_modemflash.log');
// No `NP=1` env-var prefix: the path-scoped sudoers refuses
// env-var pass-through without a SETENV: tag, so `sudo NP=1
// pistar-modemupgrade …` was rejected outright with "you are
// not allowed to set the following environment variables: NP"
// and the flash silently never started. The wrapper now
// auto-detects non-interactive mode via `[ -t 0 ]` (it's
// backgrounded here with no controlling terminal), so the
// explicit env var is no longer needed.
if (isset($selectedOption)) { system('sudo /usr/local/sbin/pistar-modemupgrade ' . escapeshellarg($selectedOption) . ' > /dev/null 2>&1 &'); }
system('sudo touch /var/log/pi-star/pi-star_modemflash.log > /dev/null 2>&1 &');
system('sudo echo "" > /var/log/pi-star/pi-star_modemflash.log > /dev/null 2>&1 &');
if (isset($selectedOption)) { system('sudo NP=1 /usr/local/sbin/pistar-modemupgrade ' . escapeshellarg($selectedOption) . ' > /dev/null 2>&1 &'); }
}
// passed sanity chk.
header('Cache-Control: no-cache');
// csrf_verify() at the top of the file already started the
// session via csrf_session_start(). Calling session_start() a
// second time emits a PHP Notice on PHP 8.x ("Ignoring
// session_start() because a session is already active") that
// gets logged to nginx error.log on every page load. The
// existing $_SESSION['update_offset'] log-tail logic below
// works unchanged against the already-active session.
session_start();
if (!isset($_GET['ajax'])) {
if (file_exists('/var/log/pi-star/pi-star_modemflash.log')) {
$_SESSION['update_offset'] = filesize('/var/log/pi-star/pi-star_modemflash.log');
} else {
$_SESSION['update_offset'] = 0;
if (file_exists('/var/log/pi-star/pi-star_modemflash.log')) {
$_SESSION['update_offset'] = filesize('/var/log/pi-star/pi-star_modemflash.log');
} else {
$_SESSION['update_offset'] = 0;
}
}
if (isset($_GET['ajax'])) {
if (!file_exists('/var/log/pi-star/pi-star_modemflash.log')) {
exit();
@@ -113,10 +54,10 @@ if ($_SERVER["PHP_SELF"] == "/admin/expert/modem_fw_upgrade.php") {
else {
fseek($handle, 0, SEEK_END);
$_SESSION['update_offset'] = ftell($handle);
}
}
exit();
}
// Get the firmware version
if (file_exists('/usr/local/bin/firmware/version.txt')) {
$versionData = parse_ini_file('/usr/local/bin/firmware/version.txt', true);
@@ -140,32 +81,30 @@ if ($_SERVER["PHP_SELF"] == "/admin/expert/modem_fw_upgrade.php") {
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Update" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Update" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon" />
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - Modem FW ".$lang['update'];?></title>
<title>CDN - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - Modem FW ".$lang['update'];?></title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
<script type="text/javascript" src="/jquery.min.js"></script>
<script type="text/javascript" src="/jquery-timing.min.js"></script>
<script type="text/javascript">
function disableSubmitButtons()
{
function disableSubmitButtons() {
var inputs = document.getElementsByTagName('input');
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].type === 'button') {
inputs[i].disabled = true;
inputs[i].value = 'Please Wait...';
inputs[i].value = 'Please Wait...';
}
}
}
function submitform()
{
disableSubmitButtons();
document.getElementById("up_fw").submit();
function submitform() {
disableSubmitButtons();
document.getElementById("up_fw").submit();
}
$(function() {
@@ -183,26 +122,20 @@ if ($_SERVER["PHP_SELF"] == "/admin/expert/modem_fw_upgrade.php") {
</script>
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<table width="100%">
<h2>Modem Firmware Upgrade Utility</h2>
<?php if (empty($_POST['modem'])) { ?>
<tr><th>Modem Firmware Upgrade Utility</th></tr>
<tr><td>
<br />
<h2>Modem Firmware Upgrade Utility</h2>
<p>This tool will attempt to upgrade your selected modem to the latest version available firmware version:<br />
<?php echo $fw_ver_msg; ?></p>
<p>When ready, select your modem type below and click, "Upgrade Modem". Do not interrupt the process or<br />
navigate away from the page while the process is running.</p>
<p><strong>Please understand what you are doing, as well as the risks associated with flashing your modem.</strong></p>
<p><em>(IMPORTANT: Please note, we are not firmware developers, and we offer no support for firmware.<br />
We provide utilities to update the firmware. For firmware support, you will need to utilise other<br />
support resources from the firmware developers/maintainers or the web.)</em></p>
</td></tr>
<tr><td>
<div class="settings-card">
<p>This tool will attempt to upgrade your selected modem to the latest version available firmware version:<br />
<?php echo $fw_ver_msg; ?></p>
<p>When ready, select your modem type below and click, "Upgrade Modem". Do not interrupt the process or<br />
navigate away from the page while the process is running.</p>
<p><strong>Please understand what you are doing, as well as the risks associated with flashing your modem.</strong></p>
<p><em>(IMPORTANT: Please note, we are not firmware developers, and we offer no support for firmware.<br />
We provide utilities to update the firmware. For firmware support, you will need to utilise other<br />
support resources from the firmware developers/maintainers or the web.)</em></p>
<?php
$friendlyNames = [
@@ -224,7 +157,6 @@ if ($_SERVER["PHP_SELF"] == "/admin/expert/modem_fw_upgrade.php") {
'hs_hat_generic_duplex' => 'MMDVM_HS_GENERIC_DUPLEX (14.7456MHz TCXO) GPIO',
'hs_hat_generic_duplex-usb' => 'MMDVM_HS_GENERIC_DUPLEX (14.7456MHz TCXO) USB',
'hs_hat_nano_hotspot' => 'Nano_hotSPOT by BI7JTA (14.7456MHz TCXO) GPIO',
'hs_hat_nano_hotspot-duplex' => 'Nano_hotSPOT Dual by VR2VYE & BI7JTA (14.7456MHz TCXO) GPIO',
'dvmega_gpio' => 'DV-Mega Raspberry Pi Hat (Single or Dual Band) GPIO',
'dvmega_usb_uno' => 'DV-Mega Arduino Uno Shield USB (ttyUSB0)',
'dvmega_usb_mega' => 'DV-Mega Arduino Mega Shield USB (ttyUSB0)',
@@ -249,15 +181,14 @@ if ($_SERVER["PHP_SELF"] == "/admin/expert/modem_fw_upgrade.php") {
// Create the select element
echo '<p><form method="post" id="up_fw">';
echo csrf_field_html();
echo '<label for="modem">Select Modem:</label>';
echo '<select id="modem" name="modem">';
echo '<option value="" disabled selected>Please choose device type...</option>';
// Output each option with user-friendly names
foreach ($options as $option) {
$friendlyName = isset($friendlyNames[$option]) ? $friendlyNames[$option] : $option;
echo '<option value="' . htmlspecialchars($option) . '">' . htmlspecialchars($friendlyName) . '</option>';
}
echo '<option value="" disabled selected>Please choose device type...</option>';
// Output each option with user-friendly names
foreach ($options as $option) {
$friendlyName = isset($friendlyNames[$option]) ? $friendlyNames[$option] : $option;
echo '<option value="' . htmlspecialchars($option) . '">' . htmlspecialchars($friendlyName) . '</option>';
}
echo '</select>';
echo '<input type="button" value="Upgrade Modem" onclick="submitform()">';
echo '</form></p>';
@@ -265,33 +196,21 @@ if ($_SERVER["PHP_SELF"] == "/admin/expert/modem_fw_upgrade.php") {
echo '<p>Error executing the command.</p>';
}
?>
</form>
</td></tr>
</table>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Modem Flashing Tool &copy; Chip Cuccio (W0CHP) 2020-<?php echo date("Y");?><br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_blank">here</a>.<br />
</div>
</div>
</body>
</html>
<?php } else { ?>
<tr><th>Modem Flash/Upgrade Output</th></tr>
<tr><td align="left"><div id="tail">Starting FW Upgrade, please wait...<br /></div></td></tr>
</table>
<div class="settings-card" style="padding-top:16px;">
<div id="tail">Starting FW Upgrade, please wait...<br /></div>
</div>
<div class="footer">
Pi-Star / Pi-Star Dashboard, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Modem Flashing Tool &copy; Chip Cuccio (W0CHP) 2020-<?php echo date("Y");?><br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_blank">here</a>.<br />
</div>
</div>
</body>
</html>
<?php }
}
?>
+10 -66
View File
@@ -1,23 +1,4 @@
<?php
/**
* ShellInABox iframe wrapper.
*
* Reads the configured shellinabox port from /etc/default/shellinabox
* and embeds the in-browser SSH terminal as an <iframe>. Uses
* setSecurityHeadersAllowDifferentPorts() to relax the default same-
* origin frame restriction so the iframe can target a non-80 port on
* the same host.
*
* No file edits, no privileged calls — pure UI wrapper.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeadersAllowDifferentPorts();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
// Load the Pi-Star Release file
@@ -32,37 +13,6 @@ if (file_exists('/etc/default/shellinabox')) {
$shellPort = exec($getPortCommand);
}
// HTTP_HOST is client-controllable (any HTTP client can send any
// `Host:` header). Echoing it raw into HTML attributes — as the
// iframe `src` and the anchor `href` below do — gives a passing
// attacker a reflected XSS surface: a request with a crafted Host
// header would render an iframe pointing at attacker-controlled
// content inside this page.
//
// Two-layer defence:
// 1. Strip everything that isn't a hostname character. The set
// `[a-zA-Z0-9.\-\[\]:]` covers DNS names, IPv4 dotted-quad,
// IPv6 bracketed literals, and the optional `:port` suffix.
// Anything else (CRLF, `<`, `"`, `'`, semicolons, spaces,
// parens, slashes) is dropped — the regex is what closes
// the XSS / response-splitting class.
// 2. htmlspecialchars on top, so even if the regex were
// relaxed in the future the value still can't break out
// of the `"…"` HTML attribute it lands in.
$shellHost = preg_replace(
'/[^a-zA-Z0-9.\-\[\]:]/',
'',
isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ''
);
if ($shellHost === '') {
// Pathological case: HTTP_HOST contained nothing usable. Fall
// back to the LAN IP that nginx listens on rather than emitting
// a broken iframe `src=":port"`.
$shellHost = 'localhost';
}
$shellHostHtml = htmlspecialchars($shellHost, ENT_QUOTES, 'UTF-8');
$shellPortHtml = htmlspecialchars((string)(isset($shellPort) ? $shellPort : ''), ENT_QUOTES, 'UTF-8');
// Sanity Check that this file has been opened correctly
if ($_SERVER["PHP_SELF"] == "/admin/expert/ssh_access.php") {
?>
@@ -75,40 +25,33 @@ if ($_SERVER["PHP_SELF"] == "/admin/expert/ssh_access.php") {
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Update" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Update" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - SSH";?></title>
<title>CDN - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - SSH";?></title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
<script type="text/javascript" src="/jquery.min.js"></script>
<script type="text/javascript" src="/jquery-timing.min.js"></script>
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<table width="100%">
<tr><th>SSH - Pi-Star</th></tr>
<tr><td align="left"><div id="tail">
<h2>SSH - CDN</h2>
<div class="settings-card" style="padding-top:16px;">
<div id="tail">
<?php if (isset($shellPort)) {
echo "<iframe src=\"http://" . $shellHostHtml . ":" . $shellPortHtml . "\" style=\"border:0px #ffffff none; background:#ffffff; color:#00ff00;\" name=\"Pi-Star_SSH\" scrolling=\"no\" frameborder=\"0\" marginheight=\"0px\" marginwidth=\"0px\" height=\"100%\" width=\"100%\"></iframe>";
echo "<iframe src=\"http://".$_SERVER['HTTP_HOST'].":".$shellPort."\" style=\"border:0px #ffffff none; background:#ffffff; color:#00ff00;\" name=\"CDN_SSH\" scrolling=\"no\" frameborder=\"0\" marginheight=\"0px\" marginwidth=\"0px\" height=\"100%\" width=\"100%\"></iframe>";
}
else {
echo "SSH Feature not yet installed";
} ?>
</div></td></tr>
</table>
<?php if (isset($shellPort)) { echo "<a href=\"//" . $shellHostHtml . ":" . $shellPortHtml . "\">Click here for fullscreen SSH client</a><br />\n"; } ?>
</div>
<div class="footer">
Pi-Star web config, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_blank">here</a>.<br />
<br />
<?php if (isset($shellPort)) { echo "<div class=\"field-actions\"><a href=\"//".$_SERVER['HTTP_HOST'].":".$shellPort."\">Open fullscreen SSH client</a></div>\n"; } ?>
</div>
</div>
</div>
</body>
@@ -116,3 +59,4 @@ if ($_SERVER["PHP_SELF"] == "/admin/expert/ssh_access.php") {
<?php
}
?>
+10 -128
View File
@@ -1,28 +1,4 @@
<?php
/**
* Pi-Star firmware/software upgrade runner.
*
* Triggers `sudo /usr/local/sbin/pistar-upgrade` (a longer-running
* sibling of /admin/update.php's pistar-update). Output streams to
* /var/log/pi-star/pi-star_upgrade.log and is tailed via AJAX.
* Requires an explicit POST confirmation field before kicking off.
*/
require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php');
require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc');
setSecurityHeaders();
// CSRF protection — see config/csrf.php for the full rationale.
// Critical here: line 29 below kicks off `sudo pistar-upgrade`
// the moment a `confirm_update` POST lands. csrf_verify() MUST
// run before that, so a hostile cross-site POST can never start
// a long-running privileged upgrade on the device.
csrf_verify();
// Layer 2 of the default-password protection — see config/banner_warnings.inc.
// MUST run BEFORE any output so header('Location: ...') works.
pistar_warnings_enforce_redirect();
// Load the language support
require_once('../config/language.php');
// Load the Pi-Star Release file
@@ -35,55 +11,12 @@ require_once('../config/version.php');
// Sanity Check that this file has been opened correctly
if ($_SERVER["PHP_SELF"] == "/admin/expert/upgrade.php") {
// Only proceed with upgrade if user has confirmed via POST submission
if (!isset($_GET['ajax']) && !empty($_POST) && isset($_POST['confirm_update'])) {
// truncate creates+clears in one synchronous call — see
// admin/update.php for the full rationale (the prior touch + echo
// redirect pair was racy under `&` and the `>` ran as www-data,
// defeating the sudo on the truncation step).
system('sudo truncate -s 0 /var/log/pi-star/pi-star_upgrade.log');
system('sudo /usr/local/sbin/pistar-upgrade > /dev/null 2>&1 &');
}
// Sanity Check Passed.
header('Cache-Control: no-cache');
// session_start() is no longer called here — csrf_verify() at
// the top already started the session via csrf_session_start().
// The existing $_SESSION['update_offset'] log-tail logic below
// works unchanged against the already-active session.
// Initialize session offset only if upgrade has been confirmed
if (!isset($_GET['ajax']) && !empty($_POST) && isset($_POST['confirm_update'])) {
//unset($_SESSION['update_offset']);
if (file_exists('/var/log/pi-star/pi-star_upgrade.log')) {
$_SESSION['update_offset'] = filesize('/var/log/pi-star/pi-star_upgrade.log');
} else {
$_SESSION['update_offset'] = 0;
}
}
// Web-based upgrades have been disabled.
if (isset($_GET['ajax'])) {
//session_start();
if (!file_exists('/var/log/pi-star/pi-star_upgrade.log')) {
exit();
}
$handle = fopen('/var/log/pi-star/pi-star_upgrade.log', 'rb');
if (isset($_SESSION['update_offset'])) {
fseek($handle, 0, SEEK_END);
if ($_SESSION['update_offset'] > ftell($handle)) //log rotated/truncated
$_SESSION['update_offset'] = 0; //continue at beginning of the new log
$data = stream_get_contents($handle, -1, $_SESSION['update_offset']);
$_SESSION['update_offset'] += strlen($data);
echo nl2br($data);
}
else {
fseek($handle, 0, SEEK_END);
$_SESSION['update_offset'] = ftell($handle);
}
exit();
exit();
}
header('Cache-Control: no-cache');
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@@ -94,79 +27,28 @@ if ($_SERVER["PHP_SELF"] == "/admin/expert/upgrade.php") {
<meta name="language" content="English" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Author" content="Andrew Taylor (MW0MWZ)" />
<meta name="Description" content="Pi-Star Update" />
<meta name="KeyWords" content="Pi-Star" />
<meta name="Description" content="CDN Update" />
<meta name="KeyWords" content="CDN" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon" />
<meta http-equiv="Expires" content="0" />
<title>Pi-Star - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - ".$lang['update'];?></title>
<title>CDN - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - ".$lang['update'];?></title>
<link rel="stylesheet" type="text/css" href="../css/pistar-css.php" />
<?php if (!empty($_POST) && isset($_POST['confirm_update'])) { ?>
<script type="text/javascript" src="/jquery.min.js"></script>
<script type="text/javascript" src="/jquery-timing.min.js"></script>
<script type="text/javascript">
$(function() {
$.repeat(1000, function() {
$.get('/admin/expert/upgrade.php?ajax', function(data) {
if (data.length < 1) return;
var objDiv = document.getElementById("tail");
var isScrolledToBottom = objDiv.scrollHeight - objDiv.clientHeight <= objDiv.scrollTop + 1;
$('#tail').append(data);
if (isScrolledToBottom)
objDiv.scrollTop = objDiv.scrollHeight;
});
});
});
</script>
<?php } ?>
</head>
<body>
<?php pistar_warnings_render(); ?>
<div class="container">
<?php include './header-menu.inc'; ?>
<div class="contentwide">
<?php if (!empty($_POST) && isset($_POST['confirm_update'])) { ?>
<table role="presentation" width="100%">
<tr><th>Upgrade Running</th></tr>
<tr><td align="left"><div id="tail">Starting upgrade, please wait...<br /></div></td></tr>
</table>
<?php } else { ?>
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
<?php csrf_field(); ?>
<table width="100%">
<tr>
<th>Upgrade</th>
</tr>
<tr>
<td align="center" style="padding: 20px;">
<p style="margin-bottom: 20px;">
<strong>This will upgrade your Pi-Star installation to the latest version.</strong><br />
<br />
The upgrade process may take several minutes to complete.<br />
Please do not interrupt the process or power off your system during the upgrade.
</p>
<button style="border: none; background: none; cursor: pointer;" type="submit" name="confirm_update" value="1">
<img src="/images/download.png" border="0" alt="Start Upgrade" /><br />
<strong>Start Upgrade</strong>
</button>
</td>
</tr>
</table>
</form>
<?php } ?>
<h2>Upgrade Disabled</h2>
<div class="settings-card">
<div class="field-row"><div class="field-note" style="flex:1 1 100%;">Web-based upgrades have been disabled on this system.</div></div>
</div>
<footer aria-label="footer">
<div class="footer">
Pi-Star web config, &copy; Andy Taylor (MW0MWZ) 2014-<?php echo date("Y"); ?>.<br />
Need help? Click <a style="color: #ffffff;" href="https://www.facebook.com/groups/pistarusergroup/" target="_new">here for the Support Group</a><br />
Get your copy of Pi-Star from <a style="color: #ffffff;" href="http://www.pistar.uk/downloads/" target="_blank">here</a>.<br />
<br />
</div>
</footer>
</div>
</body>
</html>
<?php
}
?>