mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-10-06 21:13:00 -04:00
app.conf is written by util.php saveSettings() as Python source and later
compiled/exec'd by the backend. String settings were emitted into
single-quoted Python literals with only ' encoded (as {s-quote}), so
backslashes were written raw. A regex such as 192\.0\.2\..* then produced
"SyntaxWarning: invalid escape sequence '\.'", valid Python escapes such as
\n were silently reinterpreted, and a trailing backslash made the file fail
to compile.
Move the encoder into a side-effect-free helper, app_conf_encode.php, as
encode_python_string(). It now doubles backslashes before the existing
{s-quote} replacement, so backslashes round-trip unchanged through app.conf
serialization and Python parsing, while single quotes keep using the legacy
{s-quote} placeholder. Both scalar string and array string serialization use
the helper.
Add regression tests that run the real PHP helper through the PHP CLI,
compile the generated source with warnings promoted to errors, and check
the scalar and array round trips.
312 lines
10 KiB
PHP
Executable File
312 lines
10 KiB
PHP
Executable File
<?php
|
|
//------------------------------------------------------------------------------
|
|
// NetAlertX
|
|
// Open Source Network Guard / WIFI & LAN intrusion detector
|
|
//
|
|
// util.php - Front module. Server side. Settings and utility functions
|
|
//------------------------------------------------------------------------------
|
|
# Puche 2021 / 2022+ jokob support@netalertx.com GNU GPLv3
|
|
//------------------------------------------------------------------------------
|
|
|
|
require dirname(__FILE__).'/../templates/globals.php';
|
|
require dirname(__FILE__).'/../templates/skinUI.php';
|
|
require_once dirname(__FILE__).'/app_conf_encode.php';
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// check if authenticated
|
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
|
|
|
$FUNCTION = [];
|
|
$SETTINGS = [];
|
|
|
|
// init request params
|
|
if(array_key_exists('function', $_REQUEST) != FALSE)
|
|
{
|
|
$FUNCTION = $_REQUEST['function'];
|
|
}
|
|
if(array_key_exists('settings', $_REQUEST) != FALSE)
|
|
{
|
|
$SETTINGS = $_REQUEST['settings'];
|
|
}
|
|
|
|
|
|
// call functions based on requested params
|
|
switch ($FUNCTION) {
|
|
|
|
case 'savesettings':
|
|
|
|
saveSettings();
|
|
break;
|
|
|
|
default:
|
|
// Handle any other cases or errors if needed
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
function saveSettings()
|
|
{
|
|
global $SETTINGS, $FUNCTION, $config_file, $fullConfPath, $configFolderPath, $timestamp;
|
|
|
|
// save to the file
|
|
$new_name = $config_file.'_'.$timestamp.'.backup';
|
|
$new_location = $configFolderPath.'/'.$new_name;
|
|
|
|
if(file_exists( $fullConfPath) != 1)
|
|
{
|
|
displayInAppNoti('File "'.$fullConfPath.'" not found or missing read permissions. Creating a new config file.', 'warning');
|
|
}
|
|
// create a backup copy
|
|
elseif (!copy($fullConfPath, $new_location))
|
|
{
|
|
displayInAppNoti("Failed to copy file ".$fullConfPath." to ".$new_location." Check your permissions to allow read/write access to the /config folder.", 'error');
|
|
}
|
|
|
|
|
|
// generate a clean .conf file
|
|
$groups = [];
|
|
|
|
$txt = "";
|
|
|
|
$txt = $txt."#-----------------AUTOGENERATED FILE-----------------#\n";
|
|
$txt = $txt."# #\n";
|
|
$txt = $txt."# Generated: ".$timestamp." #\n";
|
|
$txt = $txt."# #\n";
|
|
$txt = $txt."# Config file for the LAN intruder detection app: #\n";
|
|
$txt = $txt."# https://github.com/netalertx/NetAlertX #\n";
|
|
$txt = $txt."# #\n";
|
|
$txt = $txt."#-----------------AUTOGENERATED FILE-----------------#\n";
|
|
|
|
// collect all groups
|
|
|
|
$decodedSettings = json_decode((string)$SETTINGS, true);
|
|
|
|
if ($decodedSettings === null) {
|
|
echo "Error: Invalid JSON in settings data.";
|
|
return;
|
|
}
|
|
|
|
foreach ($decodedSettings as $setting) {
|
|
if( in_array($setting[0] , $groups) == false) {
|
|
array_push($groups ,$setting[0]);
|
|
}
|
|
}
|
|
|
|
// go thru the groups and prepare settings to write to file
|
|
foreach ($groups as $group) {
|
|
$txt .= "\n\n# " . $group;
|
|
$txt .= "\n#---------------------------\n";
|
|
|
|
foreach ($decodedSettings as $setting) {
|
|
$settingGroup = $setting[0];
|
|
$setKey = $setting[1];
|
|
$dataType = $setting[2];
|
|
$settingValue = $setting[3];
|
|
|
|
// Sanity check
|
|
if($setKey == "UI_LANG" && $settingValue == "") {
|
|
echo "🔴 Error: important settings missing. Refresh the page with 🔃 on the top and try again.";
|
|
return;
|
|
}
|
|
|
|
if ($group == $settingGroup) {
|
|
|
|
if ($dataType == 'string' ) {
|
|
$val = encode_python_string($settingValue);
|
|
$txt .= $setKey . "='" . $val . "'\n";
|
|
} elseif ($dataType == 'integer') {
|
|
$txt .= $setKey . "=" . $settingValue . "\n";
|
|
} elseif ($dataType == 'none') {
|
|
$txt .= $setKey . "=''\n";
|
|
} elseif ($dataType == 'json') {
|
|
$txt .= $setKey . "=" . $settingValue . "\n";
|
|
} elseif ($dataType == 'boolean') {
|
|
$val = ($settingValue === true || $settingValue === 1 || strtolower($settingValue) === 'true') ? "True" : "False";
|
|
$txt .= $setKey . "=" . $val . "\n";
|
|
} elseif ($dataType == 'array' ) {
|
|
$temp = '';
|
|
|
|
if(is_array($settingValue) == FALSE)
|
|
{
|
|
$settingValue = json_decode($settingValue);
|
|
}
|
|
// skipping __metadata entries (?)
|
|
if (count($setting) > 3 && is_array($settingValue) == true) {
|
|
foreach ($settingValue as $val) {
|
|
$temp .= "'" . encode_python_string($val) . "',";
|
|
}
|
|
|
|
$temp = substr_replace($temp, "", -1); // remove last comma ','
|
|
}
|
|
|
|
$temp = '['.$temp.']'; // wrap brackets
|
|
$txt .= $setKey . "=" . $temp . "\n";
|
|
|
|
} else {
|
|
$txt .= $setKey . "='⭕Not handled⭕'\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
$txt = $txt."\n\n";
|
|
$txt = $txt."#-------------------IMPORTANT INFO-------------------#\n";
|
|
$txt = $txt."# This file is ingested by a python script, so if #\n";
|
|
$txt = $txt."# modified it needs to use python syntax #\n";
|
|
$txt = $txt."#-------------------IMPORTANT INFO-------------------#\n";
|
|
|
|
// open new file and write the new configuration
|
|
// Backup the original file
|
|
if (file_exists($fullConfPath)) {
|
|
copy($fullConfPath, $fullConfPath . ".bak");
|
|
}
|
|
|
|
// Detect whether the frontend needs to block-wait for backend reload
|
|
$requiresReloadWait = getReloadWaitRequired($decodedSettings);
|
|
|
|
// Open the file for writing without changing permissions
|
|
$file = fopen($fullConfPath, "w") or die("Unable to open file!");
|
|
fwrite($file, $txt);
|
|
fclose($file);
|
|
|
|
echo json_encode(['success' => true, 'requiresReloadWait' => $requiresReloadWait]);
|
|
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Determines if the frontend must wait (block) for the backend to finish reloading after a
|
|
// settings save. Blocking is required when LOADED_PLUGINS changes or UI_WAIT_FOR_SETTINGS
|
|
// is explicitly enabled. Defaults to true (safe) on any parsing error.
|
|
function getReloadWaitRequired($decodedSettings) {
|
|
$newLoadedPlugins = null;
|
|
$uiWaitForSettings = false;
|
|
|
|
foreach ($decodedSettings as $setting) {
|
|
if ($setting[1] === 'LOADED_PLUGINS') {
|
|
$newLoadedPlugins = $setting[3];
|
|
}
|
|
if ($setting[1] === 'UI_WAIT_FOR_SETTINGS') {
|
|
$uiWaitForSettings = ($setting[3] === true || $setting[3] === 1
|
|
|| strtolower((string)$setting[3]) === 'true');
|
|
}
|
|
}
|
|
|
|
if ($uiWaitForSettings) {
|
|
return true;
|
|
}
|
|
|
|
$oldLoadedPlugins = getSettingValue('LOADED_PLUGINS');
|
|
|
|
// If the old value couldn't be read, default to blocking (safe).
|
|
if (strpos((string)$oldLoadedPlugins, 'Could not') !== false) {
|
|
return true;
|
|
}
|
|
|
|
return normalizePluginList($oldLoadedPlugins) !== normalizePluginList($newLoadedPlugins);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Normalise a plugin list value (PHP array, JSON array, or Python-style list) to a sorted
|
|
// JSON string for reliable equality comparison.
|
|
function normalizePluginList($value) {
|
|
if ($value === null || $value === '') {
|
|
return '[]';
|
|
}
|
|
if (is_array($value)) {
|
|
$arr = $value;
|
|
} else {
|
|
$arr = json_decode($value, true);
|
|
if (!is_array($arr)) {
|
|
// Handle Python-style single-quoted lists: ['A','B']
|
|
$jsonStr = str_replace("'", '"', (string)$value);
|
|
$arr = json_decode($jsonStr, true);
|
|
}
|
|
if (!is_array($arr)) {
|
|
return trim((string)$value);
|
|
}
|
|
}
|
|
sort($arr);
|
|
return json_encode($arr);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
|
// check server/api_server/api_server_start.py for equivalents
|
|
// equivalent: /settings/<key>
|
|
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
|
function getSettingValue($setKey) {
|
|
// Define the JSON endpoint URL
|
|
$apiRoot = rtrim(getenv('NETALERTX_API') ?: '/tmp/api', '/');
|
|
$url = $apiRoot . '/table_settings.json';
|
|
|
|
// Fetch the JSON data
|
|
$json = file_get_contents($url);
|
|
|
|
// Check if the JSON data was successfully fetched
|
|
if ($json === false) {
|
|
return 'Could not get json data';
|
|
}
|
|
|
|
// Decode the JSON data
|
|
$data = json_decode($json, true);
|
|
|
|
// Check if the JSON decoding was successful
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
return 'Could not decode json data';
|
|
}
|
|
|
|
// Search for the setting by setKey
|
|
foreach ($data['data'] as $setting) {
|
|
if ($setting['setKey'] === $setKey) {
|
|
return $setting['setValue'];
|
|
}
|
|
}
|
|
|
|
// Return false if the setting was not found
|
|
return 'Could not find setting '.$setKey;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Helper function to send notifications via the backend API endpoint
|
|
// -------------------------------------------------------------------------------------------
|
|
function displayInAppNoti($message, $level = 'error') {
|
|
try {
|
|
$apiBase = getSettingValue('BACKEND_API_URL') ?: 'http://localhost:20212';
|
|
$apiToken = getSettingValue('API_TOKEN') ?: '';
|
|
|
|
if (empty($apiToken)) {
|
|
// If no token available, silently fail (don't break the application)
|
|
return;
|
|
}
|
|
|
|
$url = rtrim($apiBase, '/') . '/messaging/in-app/write';
|
|
$payload = json_encode([
|
|
'message' => $message,
|
|
'level' => $level
|
|
]);
|
|
|
|
$ch = curl_init($url);
|
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
'Content-Type: application/json',
|
|
'Authorization: Bearer ' . $apiToken,
|
|
'Content-Length: ' . strlen($payload)
|
|
]);
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
|
|
|
curl_exec($ch);
|
|
curl_close($ch);
|
|
} catch (Exception $e) {
|
|
// Silently fail if notification sending fails
|
|
}
|
|
}
|
|
?>
|