UNPKG

domotz-remote-pawn

Version:

Domotz Agent

201 lines (169 loc) 7.65 kB
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, }; };