UNPKG

@zowe/zos-uss-for-zowe-sdk

Version:
193 lines 10.4 kB
"use strict"; /* * This program and the accompanying materials are made available under the terms of the * Eclipse Public License v2.0 which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v20.html * * SPDX-License-Identifier: EPL-2.0 * * Copyright Contributors to the Zowe Project. * */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Shell = void 0; const imperative_1 = require("@zowe/imperative"); const ssh2_1 = require("ssh2"); const ZosUss_messages_1 = require("./constants/ZosUss.messages"); class Shell { static executeSsh(session, command, stdoutHandler, removeExtraCharactersFromOutput = false) { let hasAuthFailed = false; const promise = new Promise((resolve, reject) => { const conn = new ssh2_1.Client(); conn.on("ready", () => { conn.shell((err, stream) => { if (err) { throw err; } let dataBuffer = ""; let dataToPrint = ""; let isUserCommand = false; let rc; // isolate the command // eslint-disable-next-line @typescript-eslint/no-magic-numbers const cmd = command.slice(0, 75); stream.on("exit", (exitcode) => { imperative_1.Logger.getAppLogger().debug(`Return Code: ${exitcode}`); if (dataBuffer.trim().length > 1) { // normally the last line is "\r\n$ " and we don't care about it // but we need to handle the case of an incomplete line at the end // which can happen when commands terminate abruptly stdoutHandler(dataBuffer.slice(0, dataBuffer.lastIndexOf("$"))); } rc = exitcode; }); stream.on("close", () => { imperative_1.Logger.getAppLogger().debug("SSH connection closed"); if (!hasAuthFailed) stdoutHandler("\n"); conn.end(); resolve(rc); }); stream.on("data", (data) => { imperative_1.Logger.getAppLogger().debug("\n[Received data begin]" + data + "[Received data end]\n"); // We do not know if password is expired until now. // If it is, emit an error and shut down the stream. if (dataBuffer.length === 0 && data.indexOf(this.expiredPasswordFlag) === 0) { hasAuthFailed = true; conn.emit("error", new Error(data.toString())); stream.removeAllListeners("data"); stream.close(); return; } dataBuffer += data; if (dataBuffer.includes("\r")) { // when data is not received with complete lines, // slice the last incomplete line and put it back to dataBuffer until it gets the complete line, // rather than print it out right away dataToPrint = dataBuffer.slice(0, dataBuffer.lastIndexOf("\r")); dataBuffer = dataBuffer.slice(dataBuffer.lastIndexOf("\r")); // check startCmdFlag: start printing out data if (dataToPrint.match(new RegExp(`\n${this.startCmdFlag}`)) || dataToPrint.match(new RegExp("\\$ " + this.startCmdFlag))) { dataToPrint = dataToPrint.slice(dataToPrint.indexOf(this.startCmdFlag) + this.startCmdFlag.length); isUserCommand = true; } if (isUserCommand && dataToPrint.match(/\$ exit/)) { // if exit found, print out stuff before exit, then stop printing out. dataToPrint = dataToPrint.slice(0, dataToPrint.indexOf("$ exit")); stdoutHandler(dataToPrint); dataToPrint = ""; isUserCommand = false; } else if (isUserCommand && dataToPrint.length != 0) { if (!dataToPrint.startsWith('\r\n$ ' + cmd) && !dataToPrint.startsWith('\r<')) { //only prints command output if (removeExtraCharactersFromOutput && dataToPrint.startsWith("\r\n$ ")) dataToPrint = dataToPrint.replace(/\r\n\$\s/, "\r\n"); stdoutHandler(dataToPrint); dataToPrint = ""; } } } }); // exit multiple times in case of nested shells stream.write(`export PS1='$ '\necho ${this.startCmdFlag}\n${command}\n` + `exit $?\nexit $?\nexit $?\nexit $?\nexit $?\nexit $?\nexit $?\nexit $?\n`); stream.end(); }); }); conn.on("error", (err) => { if (err.message.startsWith(this.expiredPasswordFlag)) { reject(new imperative_1.ImperativeError({ msg: ZosUss_messages_1.ZosUssMessages.expiredPassword.message })); } else if (err.message.includes(ZosUss_messages_1.ZosUssMessages.allAuthMethodsFailed.message)) { hasAuthFailed = true; reject(new imperative_1.ImperativeError({ msg: ZosUss_messages_1.ZosUssMessages.allAuthMethodsFailed.message })); } // throw error only when authentication didn't fail. else if (!hasAuthFailed && err.message.includes(ZosUss_messages_1.ZosUssMessages.handshakeTimeout.message)) { reject(new imperative_1.ImperativeError({ msg: ZosUss_messages_1.ZosUssMessages.handshakeTimeout.message })); } else if (err.message.includes(this.connRefusedFlag)) { reject(new imperative_1.ImperativeError({ msg: ZosUss_messages_1.ZosUssMessages.connectionRefused.message + ":\n" + err.message })); } else { reject(new imperative_1.ImperativeError({ msg: ZosUss_messages_1.ZosUssMessages.unexpected.message + ":\n" + err.message })); } }); Shell.connect(conn, session); }); return promise; } static connect(connection, session) { const authsAllowed = ["none"]; // These are needed for authenticationHandler // The order is critical as this is the order of authentication that will be used. if (session.ISshSession.privateKey != null && session.ISshSession.privateKey !== "undefined") { authsAllowed.push("publickey"); } if (session.ISshSession.password != null && session.ISshSession.password !== "undefined") { authsAllowed.push("password"); } connection.connect({ host: session.ISshSession.hostname, port: session.ISshSession.port, username: session.ISshSession.user, password: session.ISshSession.password, privateKey: session.ISshSession.privateKey != null && session.ISshSession.privateKey !== "undefined" ? require("fs").readFileSync(session.ISshSession.privateKey) : "", passphrase: session.ISshSession.keyPassphrase, authHandler: this.authenticationHandler(authsAllowed), readyTimeout: session.ISshSession.handshakeTimeout != null && session.ISshSession.handshakeTimeout !== undefined ? session.ISshSession.handshakeTimeout : 0 }); } static executeSshCwd(session_1, command_1, cwd_1, stdoutHandler_1) { return __awaiter(this, arguments, void 0, function* (session, command, cwd, stdoutHandler, removeExtraCharactersFromOutput = false) { const cwdCommand = `cd ${cwd} && ${command}`; return this.executeSsh(session, cwdCommand, stdoutHandler, removeExtraCharactersFromOutput); }); } static isConnectionValid(session) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, _) => { const conn = new ssh2_1.Client(); conn.on("ready", () => conn.end() && resolve(true)).on("error", () => resolve(false)); Shell.connect(conn, session); }); }); } static authenticationHandler(authsAllowed) { let authPos = 0; return (_methodsLeft, _partialSuccess, _callback) => { if (authPos === authsAllowed.length) { return false; } return authsAllowed[authPos++]; }; } } exports.Shell = Shell; Shell.startCmdFlag = "@@START OF COMMAND@@"; Shell.connRefusedFlag = "ECONNREFUSED"; Shell.expiredPasswordFlag = "FOTS1668"; //# sourceMappingURL=Shell.js.map