ssh-client-wrapper 1.8.2 → 1.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/fork.js +22 -17
- package/lib/index.js +8 -4
- package/lib/rsyncExec.js +2 -2
- package/lib/sshExec.js +21 -20
- package/lib/util.js +30 -3
- package/package.json +1 -1
package/lib/fork.js
CHANGED
|
@@ -1,35 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const debug = require("debug")("sshClientWrapper:fork");
|
|
3
|
-
const debugVerbose = require("debug")("sshClientWrapper:
|
|
4
|
-
const debugMasterVerbose = require("debug")("sshClientWrapper:
|
|
5
|
-
const debugSendPty = require("debug")("sshClientWrapper:fork_sendpty");
|
|
2
|
+
const debug = require("debug")("sshClientWrapper:debug:fork");
|
|
3
|
+
const debugVerbose = require("debug")("sshClientWrapper:verbose:fork");
|
|
4
|
+
const debugMasterVerbose = require("debug")("sshClientWrapper:insecure:fork_masterPty");
|
|
5
|
+
const debugSendPty = require("debug")("sshClientWrapper:insecure:fork_sendpty");
|
|
6
6
|
const { spawn } = require("node-pty");
|
|
7
7
|
|
|
8
|
-
const { rePwPrompt, rePhPrompt, reNewHostPrompt, buff2String } = require("./util.js");
|
|
8
|
+
const { rePwPrompt, rePhPrompt, reNewHostPrompt, buff2String, sendPty } = require("./util.js");
|
|
9
9
|
|
|
10
10
|
const nodePtyOpt = {
|
|
11
11
|
shell: process.platform === "win32" ? process.env.ComSpec : process.env.SHELL,
|
|
12
12
|
windowsHide: true
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
function sshLoginCallback(output, ptyProcess, pw, ph) {
|
|
15
|
+
function sshLoginCallback(output, ptyProcess, pw, ph, logger = debugSendPty) {
|
|
16
16
|
if (reNewHostPrompt.test(output)) {
|
|
17
|
-
|
|
18
|
-
ptyProcess.write("yes\n");
|
|
17
|
+
sendPty(ptyProcess, "yes\n", logger, 17);
|
|
19
18
|
return;
|
|
20
19
|
}
|
|
21
20
|
if (rePwPrompt.test(output)) {
|
|
22
21
|
if (typeof pw === "string") {
|
|
23
22
|
debug("use given password");
|
|
24
|
-
|
|
25
|
-
ptyProcess.write(`${pw}\n`);
|
|
23
|
+
sendPty(ptyProcess, `${pw}\n`, logger, 23);
|
|
26
24
|
return;
|
|
27
25
|
}
|
|
28
26
|
if (typeof pw === "function") {
|
|
29
27
|
debug("call password callback function");
|
|
30
28
|
pw()
|
|
31
29
|
.then((v)=>{
|
|
32
|
-
ptyProcess
|
|
30
|
+
sendPty(ptyProcess, `${v}\n`, logger, 30);
|
|
33
31
|
});
|
|
34
32
|
return;
|
|
35
33
|
}
|
|
@@ -37,29 +35,36 @@ function sshLoginCallback(output, ptyProcess, pw, ph) {
|
|
|
37
35
|
if (rePhPrompt.test(output)) {
|
|
38
36
|
if (typeof ph === "string") {
|
|
39
37
|
debug("use given passphrase");
|
|
40
|
-
|
|
41
|
-
ptyProcess.write(`${ph}\n`);
|
|
38
|
+
sendPty(ptyProcess, `${ph}\n`, logger, 38);
|
|
42
39
|
return;
|
|
43
40
|
}
|
|
44
41
|
if (typeof ph === "function") {
|
|
45
42
|
debug("call passphrase callback function");
|
|
46
43
|
ph()
|
|
47
44
|
.then((v)=>{
|
|
48
|
-
ptyProcess
|
|
45
|
+
sendPty(ptyProcess, `${v}\n`, logger, 45);
|
|
49
46
|
});
|
|
50
47
|
}
|
|
51
48
|
}
|
|
52
49
|
}
|
|
53
50
|
|
|
54
|
-
|
|
51
|
+
/**
|
|
52
|
+
* create pty for master ssh session and store to hostInfo.masterPty
|
|
53
|
+
*/
|
|
54
|
+
function getMasterPty(hostInfo, logger) {
|
|
55
55
|
debug("fork pty for master connection");
|
|
56
|
+
|
|
57
|
+
if (hostInfo.masterPty) {
|
|
58
|
+
debug(`kill existing master pty for ${hostInfo.host}`);
|
|
59
|
+
hostInfo.masterPty.kill();
|
|
60
|
+
}
|
|
56
61
|
const ptyProcess = spawn(nodePtyOpt.shell, [], nodePtyOpt);
|
|
57
62
|
ptyProcess.onData(buff2String.bind(null, debugMasterVerbose));
|
|
58
63
|
ptyProcess.onData((data)=>{
|
|
59
64
|
const output = data.toString();
|
|
60
|
-
sshLoginCallback(output, ptyProcess, hostInfo.password, hostInfo.passphrase);
|
|
65
|
+
sshLoginCallback(output, ptyProcess, hostInfo.password, hostInfo.passphrase, logger);
|
|
61
66
|
});
|
|
62
|
-
|
|
67
|
+
hostInfo.masterPty = ptyProcess;
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
async function fork(hostInfo, cmd, args, outputCallback) {
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const
|
|
3
|
-
const
|
|
2
|
+
const debug = require("debug")("sshClientWrapper:interface");
|
|
3
|
+
const debugVerbose = require("debug")("sshClientWrapper:verbose:interface");
|
|
4
4
|
const { sshExec, canConnect, disconnect, ls } = require("./sshExec.js");
|
|
5
5
|
const { send, recv } = require("./rsyncExec.js");
|
|
6
6
|
const { isArrayOfString, sanityCheck } = require("./util.js");
|
|
@@ -36,6 +36,8 @@ const { isArrayOfString, sanityCheck } = require("./util.js");
|
|
|
36
36
|
*/
|
|
37
37
|
class SshClientWrapper {
|
|
38
38
|
constructor(hostInfo) {
|
|
39
|
+
debug("constructor called for", hostInfo.host);
|
|
40
|
+
debugVerbose("hostInfo=", hostInfo);
|
|
39
41
|
sanityCheck(hostInfo);
|
|
40
42
|
this.hostInfo = { ...hostInfo };
|
|
41
43
|
this.hostInfo.masterPty = null;
|
|
@@ -66,6 +68,7 @@ class SshClientWrapper {
|
|
|
66
68
|
* @returns {string[]} - output from ls
|
|
67
69
|
*/
|
|
68
70
|
async ls(target, lsOpt = []) {
|
|
71
|
+
debug("ls called", target, lsOpt);
|
|
69
72
|
return ls(this.hostInfo, target, lsOpt);
|
|
70
73
|
}
|
|
71
74
|
|
|
@@ -158,7 +161,8 @@ class SshClientWrapper {
|
|
|
158
161
|
* @returns {Promise} - Resolved with true on success, otherwise rejected with Error.
|
|
159
162
|
*/
|
|
160
163
|
async canConnect(timeout = 60) {
|
|
161
|
-
debug("canConnect called timeout=", timeout);
|
|
164
|
+
debug("canConnect called with timeout=", timeout);
|
|
165
|
+
debugVerbose("hostInfo=", this.hostInfo);
|
|
162
166
|
|
|
163
167
|
if (!Number.isInteger(timeout) || timeout <= 0) {
|
|
164
168
|
return new Error("timeout must be positive integer");
|
|
@@ -170,7 +174,7 @@ class SshClientWrapper {
|
|
|
170
174
|
* Disconnect master session
|
|
171
175
|
*/
|
|
172
176
|
async disconnect() {
|
|
173
|
-
debug(
|
|
177
|
+
debug(`disconnect from ${this.hostInfo.host} called`);
|
|
174
178
|
disconnect(this.hostInfo);
|
|
175
179
|
}
|
|
176
180
|
}
|
package/lib/rsyncExec.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const path = require("path");
|
|
3
|
-
const debug = require("debug")("sshClientWrapper:rsyncExec");
|
|
4
|
-
const debugVerbose = require("debug")("sshClientWrapper:
|
|
3
|
+
const debug = require("debug")("sshClientWrapper:debug:rsyncExec");
|
|
4
|
+
const debugVerbose = require("debug")("sshClientWrapper:verbose:rsyncExec");
|
|
5
5
|
const { glob } = require("glob");
|
|
6
6
|
|
|
7
7
|
const { connect, retryWrapper, sshExec } = require("./sshExec.js");
|
package/lib/sshExec.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
const { setTimeout: setTimeoutPromise } = require("timers/promises");
|
|
3
3
|
const crypto = require("crypto");
|
|
4
4
|
const promiseRetry = require("promise-retry");
|
|
5
|
-
const debug = require("debug")("sshClientWrapper:sshExec");
|
|
6
|
-
const debugVerbose = require("debug")("sshClientWrapper:
|
|
7
|
-
const
|
|
5
|
+
const debug = require("debug")("sshClientWrapper:debug:sshExec");
|
|
6
|
+
const debugVerbose = require("debug")("sshClientWrapper:verbose:sshExec");
|
|
7
|
+
const debugSendMasterPty = require("debug")("sshClientWrapper:insecure:sshExec_masterPty");
|
|
8
|
+
const { sendPty, sshCmd, getSshOption, sanityCheck } = require("./util.js");
|
|
8
9
|
const { fork, getMasterPty } = require("./fork.js");
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -64,6 +65,7 @@ async function canConnect(hostInfo, timeout = 60) {
|
|
|
64
65
|
* @returns {boolean} - true if master session exists
|
|
65
66
|
*/
|
|
66
67
|
async function existsMaster(hostInfo) {
|
|
68
|
+
debugVerbose("existsMaster called");
|
|
67
69
|
const ac = new AbortController();
|
|
68
70
|
const signal = ac.signal;
|
|
69
71
|
|
|
@@ -99,12 +101,13 @@ async function existsMaster(hostInfo) {
|
|
|
99
101
|
debug(`check master session: ${sshCmd} ${args.join(" ")}`);
|
|
100
102
|
p = fork(hostInfo, sshCmd, args, cb);
|
|
101
103
|
await p;
|
|
102
|
-
ac.abort();
|
|
103
104
|
} catch (err) {
|
|
104
105
|
if (err.rt !== 255) {
|
|
105
106
|
debug("check master session failed", err);
|
|
106
107
|
throw err;
|
|
107
108
|
}
|
|
109
|
+
} finally {
|
|
110
|
+
ac.abort();
|
|
108
111
|
}
|
|
109
112
|
|
|
110
113
|
return rt && !/Control socket connect.*: No such file or directory/.test(output);
|
|
@@ -119,22 +122,18 @@ async function connect(hostInfo, timeout = 60) {
|
|
|
119
122
|
debugVerbose("master connection exists");
|
|
120
123
|
return;
|
|
121
124
|
}
|
|
122
|
-
debug(`create master ssh session to ${hostInfo.host}`);
|
|
123
125
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
hostInfo.masterPty = getMasterPty(hostInfo);
|
|
127
|
-
} else {
|
|
128
|
-
debug(`master pty for ${hostInfo.host} exists`);
|
|
129
|
-
}
|
|
126
|
+
debug(`create master pty for ${hostInfo.host}`);
|
|
127
|
+
getMasterPty(hostInfo, debugSendMasterPty);
|
|
130
128
|
|
|
129
|
+
debug(`create master ssh session to ${hostInfo.host}`);
|
|
131
130
|
const ac = new AbortController();
|
|
132
131
|
const signal = ac.signal;
|
|
133
132
|
setTimeoutPromise(timeout * 1000, "neverUsedValue", { signal })
|
|
134
133
|
.then(()=>{
|
|
135
134
|
debug(`can not connect within ${timeout} sec`);
|
|
136
|
-
hostInfo.masterPty
|
|
137
|
-
hostInfo.masterPty
|
|
135
|
+
sendPty(hostInfo.masterPty, "\n", debugSendMasterPty, 137);
|
|
136
|
+
sendPty(hostInfo.masterPty, "exit\n", debugSendMasterPty, 138);
|
|
138
137
|
|
|
139
138
|
const err = new Error(`can not connect within ${timeout} sec`);
|
|
140
139
|
err.host = hostInfo.host;
|
|
@@ -164,7 +163,6 @@ async function connect(hostInfo, timeout = 60) {
|
|
|
164
163
|
|
|
165
164
|
if (re.test(output)) {
|
|
166
165
|
debug("test done. call exit on remotehost");
|
|
167
|
-
hostInfo.masterPty.write("exit\n");
|
|
168
166
|
done = true;
|
|
169
167
|
resolve();
|
|
170
168
|
} else if (/Permission denied \(/.test(output)) {
|
|
@@ -186,11 +184,14 @@ async function connect(hostInfo, timeout = 60) {
|
|
|
186
184
|
const args = getSshOption(hostInfo);
|
|
187
185
|
args.push(`echo ${words}`);
|
|
188
186
|
const cmd = `${sshCmd} ${args.join(" ")}\n`;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
187
|
+
sendPty(hostInfo.masterPty, cmd, debugSendMasterPty, 190);
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
await p;
|
|
191
|
+
debug(`connected to ${hostInfo.host}`);
|
|
192
|
+
} finally {
|
|
193
|
+
ac.abort();
|
|
194
|
+
}
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
/**
|
|
@@ -225,7 +226,7 @@ async function disconnect(hostInfo) {
|
|
|
225
226
|
|
|
226
227
|
if (hostInfo.masterPty) {
|
|
227
228
|
debug("exit masterPty");
|
|
228
|
-
hostInfo.masterPty
|
|
229
|
+
sendPty(hostInfo.masterPty, "exit \n", debugSendMasterPty, 228);
|
|
229
230
|
hostInfo.masterPty = null;
|
|
230
231
|
}
|
|
231
232
|
}
|
package/lib/util.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const { EOL } = require("os");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const debug = require("debug")("sshClientWrapper:debug:util");
|
|
5
|
+
|
|
3
6
|
const sshCmd = "ssh";
|
|
4
7
|
const rsyncCmd = "rsync";
|
|
5
8
|
|
|
@@ -62,8 +65,20 @@ const getSshOption = (hostInfo, withoutDestination)=>{
|
|
|
62
65
|
args.push(hostInfo.port);
|
|
63
66
|
}
|
|
64
67
|
if (typeof hostInfo.keyFile === "string") {
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
try {
|
|
69
|
+
const stats = fs.statSync(hostInfo.keyFile);
|
|
70
|
+
if (stats.isFile()) {
|
|
71
|
+
args.push("-i");
|
|
72
|
+
args.push(hostInfo.keyFile);
|
|
73
|
+
} else {
|
|
74
|
+
debug(`specified keyFile(${hostInfo.keyFile}) is not file. so it is ignored`);
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
if (e.code !== "ENOENT") {
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
80
|
+
debug(`specified keyFile(${hostInfo.keyFile}) not found and ignored`);
|
|
81
|
+
}
|
|
67
82
|
}
|
|
68
83
|
|
|
69
84
|
if (hostInfo.noStrictHostkeyChecking) {
|
|
@@ -95,6 +110,17 @@ const isArrayOfString = (target)=>{
|
|
|
95
110
|
});
|
|
96
111
|
};
|
|
97
112
|
|
|
113
|
+
/**
|
|
114
|
+
* log and send message to pty
|
|
115
|
+
* @param {Object} pty - pty object which message will be send to
|
|
116
|
+
* @param {string} message - message
|
|
117
|
+
* @param {Function} logger - logging function
|
|
118
|
+
* @param {string} label - where this function is called
|
|
119
|
+
*/
|
|
120
|
+
const sendPty = async (pty, message, logger, label)=>{
|
|
121
|
+
logger(`${label}: ${message}`);
|
|
122
|
+
pty.write(message);
|
|
123
|
+
};
|
|
98
124
|
|
|
99
125
|
module.exports = {
|
|
100
126
|
sshCmd,
|
|
@@ -105,5 +131,6 @@ module.exports = {
|
|
|
105
131
|
buff2String,
|
|
106
132
|
getSshOption,
|
|
107
133
|
sanityCheck,
|
|
108
|
-
isArrayOfString
|
|
134
|
+
isArrayOfString,
|
|
135
|
+
sendPty
|
|
109
136
|
};
|