domotz-remote-pawn
Version:
Domotz Agent
201 lines (169 loc) • 7.65 kB
JavaScript
const SSH_BINARY = 'ssh';
const SSHPASS_BINARY = 'domotz_sshpass';
module.exports.factory = function (resourceLocator) {
var myConsole = resourceLocator.log.decorateLogs();
var sleep = resourceLocator.q.delay;
var async = resourceLocator.async;
var spawn = require('child_process').spawn;
var eventEmitter = resourceLocator.events.EventEmitter;
function getCleanEnv() {
var cleanedEnv = Object.create(process.env);
cleanedEnv.LD_LIBRARY_PATH = '';
return cleanedEnv;
}
function sshShellSequence(options, callback, generalOptions) {
var prompt = generalOptions.prompt;
var promptRegex = generalOptions.promptRegex;
var errorPrompt = generalOptions.errorPrompt;
var haltOn = generalOptions.haltOn;
var interCommandTimeoutMs = generalOptions.interCommandTimeoutMs;
var globalTimeoutMs = generalOptions.globalTimeoutMs;
var callbackCalled = false;
var outputEvent = new eventEmitter();
var spawnOptions = { env: getCleanEnv() };
spawnOptions.env.SSHPASS = options.password;
var sshInfo = resourceLocator.utils.platform.getSSHInfo();
var sshCommon = require("./sshCommon");
var sshOptions = sshCommon.getArgsFromOptions(options, undefined, sshInfo, myConsole);
var hostKeyCheckOptions = ['-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no'];
var authOptions = [options.username + '@' + options.host];
var args = ['-e', SSH_BINARY, '-tt', '-p', generalOptions.port];
args = args.concat(hostKeyCheckOptions);
args = args.concat(sshOptions);
args = args.concat(authOptions);
const ssh = spawn(SSHPASS_BINARY, args, spawnOptions);
myConsole.info('Executing %s %s', SSHPASS_BINARY, args.join(' '));
var output = '';
var outputError = '';
function isCorrectPrompt(promptChunk, promptOutput) {
return (
promptChunk.search(promptRegex) > -1 ||
promptOutput.search(promptRegex) > -1 ||
promptChunk.indexOf(prompt) > -1 ||
promptOutput.indexOf(prompt) > -1
);
}
function isErroneousPrompt(promptChunk, promptOutput) {
return errorPrompt && (promptChunk.indexOf(errorPrompt) > -1 || promptOutput.indexOf(errorPrompt) > -1);
}
var resetOutput = function () {
output = '';
};
const handleData = function (chunk) {
myConsole.verbose(chunk.toString());
output += chunk;
if (chunk.length > 0) {
var chunkString = chunk.toString();
if (isCorrectPrompt(chunkString, output)) {
outputEvent.emit('shellReady', output);
} else if (isErroneousPrompt(chunkString, output)) {
outputEvent.emit('shellInError', output);
resetOutput();
}
}
};
const handleErrorData = function (chunk) {
myConsole.error(chunk.toString());
outputError += chunk;
};
ssh.stdout.on('data', handleData);
ssh.stderr.on('data', handleErrorData);
ssh.on('error', function (err) {
returnError({ message: 'SSH Error: ' + err.toString() });
myConsole.error('ERROR %s', err.toString());
});
ssh.on('close', function (code) {
if (code !== null && code !== 0) {
myConsole.info('SSH process exited with code %d', code);
if (code === 5) {
returnError({ message: 'All configured authentication methods failed' });
} else {
returnError({ message: 'SSH Error: ' + outputError.toString() });
}
} else {
myConsole.info('SSH process exited with success');
}
});
function executeOnStream(stream, command) {
myConsole.info('Executing command %s', command);
stream.write(command + '\n');
}
function returnError(e) {
if (!callbackCalled) {
myConsole.info('Error ' + JSON.stringify(e));
callbackCalled = true;
callback({
error: e,
output: null,
});
}
}
var commandFunctions = [];
var timeout = setTimeout(function () {
returnError({ message: 'timeout' });
}, globalTimeoutMs);
outputEvent.once('shellReady', function () {
resetOutput();
var singleCommandExecutor = function (command, cb) {
var singleCommandCallbackCalled = false;
outputEvent.once('shellInError', function (commandErrorResult) {
myConsole.error('Shell Resulted in Error: %s', commandErrorResult);
cb({ message: commandErrorResult, command: command }, null);
});
sleep(interCommandTimeoutMs).then(function () {
var closeListener = function (code) {
if (code === 0 && !singleCommandCallbackCalled) {
singleCommandCallbackCalled = true;
return cb(null, Buffer('Stream Closed').toString('base64'));
}
};
ssh.on('close', closeListener);
resetOutput();
executeOnStream(ssh.stdin, command);
outputEvent.once('shellReady', function (commandResult) {
var lines = commandResult.split('\n');
var out = lines.join('\n');
resetOutput();
var b64Output = Buffer(out).toString('base64');
var halt = haltOn.filter(function (stopString) {
return commandResult.indexOf(stopString) > -1;
});
if (halt.length > 0) {
cb({ message: 'halted on', command: command, halted_on: b64Output });
return;
}
if (!singleCommandCallbackCalled) {
singleCommandCallbackCalled = true;
ssh.removeListener('close', closeListener);
cb(null, b64Output);
}
});
});
};
options.commands.forEach(function (item) {
commandFunctions.push(singleCommandExecutor.bind(null, item));
});
async.series(commandFunctions, function (err, result) {
ssh.kill();
if (err && !callbackCalled) {
returnError(err);
} else {
myConsole.info('Sequence complete: ' + JSON.stringify(result));
if (!callbackCalled) {
clearTimeout(timeout);
callbackCalled = true;
callback({
error: null,
output: result,
});
} else {
myConsole.info('sequence complete but timeout expired, result is ignored');
}
}
});
});
}
return {
sshShellSequence: sshShellSequence,
};
};