mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-10-07 05:25:16 -04:00
Keep encode_python_string() in front/php/server/util.php, replacing
encode_single_quotes() in place, and remove the standalone
app_conf_encode.php helper file and its require.
Rework the regression test to dispatch the real util.php savesettings
path through the PHP CLI against temporary synthetic config, API and
session directories, covering both scalar string and array string
settings: the generated app.conf must compile with warnings as errors
and parse back to the typed values.
The existing {s-quote} lifecycle is preserved (single quotes are still
written as {s-quote} and converted back by the same consumers), and the
app.conf readers in initialise.py and plugin_helper.py are unchanged.
320 lines
10 KiB
PHP
Executable File
320 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';
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
/**
|
|
* Encode a string for use inside a single-quoted Python literal in app.conf.
|
|
* Doubles backslashes so they round-trip unchanged, and replaces ' with the
|
|
* legacy {s-quote} placeholder that the backend converts back per use.
|
|
*/
|
|
function encode_python_string($val) {
|
|
return str_replace(['\\', '\''], ['\\\\', '{s-quote}'], $val);
|
|
}
|
|
// -------------------------------------------------------------------------------------------
|
|
// 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
|
|
}
|
|
}
|
|
?>
|