domotz-remote-pawn
Version:
Domotz Agent
233 lines (215 loc) • 8.43 kB
JavaScript
/**
* This file is part of Domotz Agent.
*
* @license
* Domotz Agent is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Domotz Agent is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Domotz Agent. If not, see <http://www.gnu.org/licenses/>.
*
* @module sandbox/runner
* @private
* @copyright Copyright (C) Domotz Inc
*/
const path = require('path');
/**
* @constant {number}
* @type {number}
* @default
*/
const DEFAULT_TIMEOUT = 5000;
/**
* @readonly
* @enum {number}
*/
const DEFAULT_CUSTOM_DRIVER_SETTINGS = {
max_log_entries: 100,
max_var_id_len: 50,
max_var_per_device: 100,
max_var_unit_len: 10,
max_data_len: 4096,
max_configuration_backup_size: 1048536,
max_ignored_lines: 10,
max_ignored_line_len: 50,
};
const driverTypes = require('./constants').driverTypes;
const valueTypes = require('./constants').valueTypes;
var agentDriverSettings = null;
var dryRun = null;
var driverType = null;
function getDriverSettings(SettingsManager) {
var driverSettings = Object();
for (const key of Object.keys(DEFAULT_CUSTOM_DRIVER_SETTINGS)) {
driverSettings[key] = SettingsManager.getSetting('configuration.settings.custom_driver.' + key, DEFAULT_CUSTOM_DRIVER_SETTINGS[key]);
}
driverSettings.use_system_ssh_shell_sequence = SettingsManager.useSystemSSHShellSequence;
return driverSettings;
}
/**
* Checks if a variable is valid
* @param {*} variable - a custom variable
* @returns {object|null} - new instance of the same variable if it is valid, null otherwise.
*/
function checkVariable(variable) {
if (variable === null || variable === undefined) {
return null;
}
if (variable.uid === null || variable.uid === undefined || variable.uid.length < 1 || variable.uid.length > agentDriverSettings.max_var_id_len) {
return null;
}
if (variable.value === undefined) {
// must be null or string; anything else means createVariable hasn't been used;
return null;
}
if (variable.valueType && !(variable.valueType in valueTypes)) {
variable.valueType = null;
}
return {
uid: variable.uid,
label: variable.label,
unit: variable.unit,
valueType: variable.valueType,
value: variable.value,
};
}
/**
* Function validateBackup
* @function
* @param {ConfigurationBackup} configurationBackup - The backup object to be validated
* @return {ConfigurationBackup}
*/
function validateBackup(configurationBackup) {
var validatedBackup = {};
if (configurationBackup === undefined || configurationBackup === null) {
return null;
}
validatedBackup.label = configurationBackup.label.toString() || 'Custom Driver Configuration Backup';
if (configurationBackup.running === undefined || configurationBackup.running === null) {
return null;
} else if (agentDriverSettings.max_config_backup_size < configurationBackup.running.length) {
return null;
} else {
validatedBackup.running = configurationBackup.running;
}
if (
configurationBackup.startup !== undefined &&
configurationBackup.startup !== null &&
agentDriverSettings.max_config_backup_size < configurationBackup.startup.length
) {
validatedBackup.startup = null;
} else {
validatedBackup.startup = configurationBackup.startup || null;
}
if (Array.isArray(configurationBackup.ignoredLines) && configurationBackup.ignoredLines.length <= agentDriverSettings.max_ignored_lines) {
validatedBackup.ignoredLines = [];
for (var i = 0; i < configurationBackup.ignoredLines.length; i++) {
var line = configurationBackup.ignoredLines[i];
if (typeof(line) === "string" && line.length <= agentDriverSettings.max_ignored_line_len) {
validatedBackup.ignoredLines.push(line);
}
}
} else {
validatedBackup.ignored_lines = null;
}
return validatedBackup;
}
function handleSuccessfulResponseData(response) {
// this function is executed in the sandboxRunner process, so no chance of corruption of the checks if the driver escapes the vm
if (dryRun !== true) {
response.log = null;
}
if (driverType === driverTypes.GENERIC && response.variables) {
var checkedVariables = [];
for (var i = 0; i < response.variables.length; i++) {
var checkedVariable = checkVariable(response.variables[i]);
if (checkedVariable !== null && checkedVariable !== undefined) {
checkedVariables.push(checkedVariable);
}
}
response.variables = checkedVariables;
response.configurationData = null;
} else if (driverType === driverTypes.CONFIGURATION_MANAGEMENT && response.configurationData) {
response.configurationData = validateBackup(response.configurationData);
response.variables = null;
response.tables = null;
}
return response;
}
function handleFailedResponse(response) {
if (!response.errorType) {
response.errorType = 'GENERIC_ERROR';
}
return response;
}
function createMessageListener(subProcess, onError, onSuccess, myConsole) {
return function (response) {
myConsole.debug('Response outcome: ', response.outcome);
myConsole.debug('Response log:' + response.log);
if (response.variables) {
myConsole.debug('Response variables: ' + JSON.stringify(response.variables));
}
if (subProcess !== null) {
subProcess.kill();
subProcess = null;
if (response.outcome === 'success') {
response = handleSuccessfulResponseData(response);
return onSuccess(response);
} else {
response = handleFailedResponse(response);
return onError(response);
}
} else {
myConsole.error('Custom Driver sandbox process already stopped. Usage of further callbacks for success or failure are ignored');
}
};
}
function createErrorListener(subProcess, onError, myConsole) {
return function () {
myConsole.debug('Process exited');
if (subProcess !== null && subProcess.killed !== true) {
myConsole.error('Sandbox exited without sending back message - error');
return onError(Error('Sandbox exited without sending back message'));
}
};
}
function sandboxRunner(data, resourceLocator, cid, onError, onSuccess) {
var subProcess = require('child_process').fork(__dirname + path.sep + 'sandbox.js');
var scriptText = data.code;
var device = data.device;
var timeout = data.timeout || DEFAULT_TIMEOUT;
var logLevel = data.logLevel || 'warning';
var myConsole = resourceLocator.log.decorateLogs().decorate('(' + cid + ')');
var parameters = data.parameters;
// Set values to Global Variables
dryRun = data.dry_run;
agentDriverSettings = getDriverSettings(resourceLocator.settingsManager);
driverType = data.driver_type || driverTypes.GENERIC;
if (!scriptText || !device) {
return onError(Error('Missing script text or device: ' + JSON.stringify(data)));
}
if (!device.ip) {
device.ip = resourceLocator.interfacesBindingStorage.getIpsFromMac(device.hw_address)[0];
}
myConsole.debug('Script %s', scriptText);
myConsole.debug('Device: %s', JSON.stringify(device));
subProcess.on('message', createMessageListener(subProcess, onError, onSuccess, myConsole));
subProcess.on('exit', createErrorListener(subProcess, onError, myConsole));
subProcess.send({
script: scriptText.toString(),
device: device,
driverType: driverType,
timeout: timeout,
logLevel: logLevel,
agentDriverSettings: agentDriverSettings,
parameters: parameters,
});
}
module.exports.sandboxRunner = sandboxRunner;