@zowe/zos-uss-for-zowe-sdk
Version:
Zowe SDK to interact with USS on z/OS
193 lines • 10.4 kB
JavaScript
;
/*
* 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