ssh-client-wrapper 1.8.1 → 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 CHANGED
@@ -1,34 +1,33 @@
1
1
  "use strict";
2
- const debug = require("debug")("sshClientWrapper:fork");
3
- const debugVerbose = require("debug")("sshClientWrapper:fork_verbose");
4
- 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");
5
6
  const { spawn } = require("node-pty");
6
7
 
7
- const { rePwPrompt, rePhPrompt, reNewHostPrompt, buff2String } = require("./util.js");
8
+ const { rePwPrompt, rePhPrompt, reNewHostPrompt, buff2String, sendPty } = require("./util.js");
8
9
 
9
10
  const nodePtyOpt = {
10
11
  shell: process.platform === "win32" ? process.env.ComSpec : process.env.SHELL,
11
12
  windowsHide: true
12
13
  };
13
14
 
14
- function sshLoginCallback(output, ptyProcess, pw, ph) {
15
+ function sshLoginCallback(output, ptyProcess, pw, ph, logger = debugSendPty) {
15
16
  if (reNewHostPrompt.test(output)) {
16
- debugSendPty("yes");
17
- ptyProcess.write("yes\n");
17
+ sendPty(ptyProcess, "yes\n", logger, 17);
18
18
  return;
19
19
  }
20
20
  if (rePwPrompt.test(output)) {
21
21
  if (typeof pw === "string") {
22
22
  debug("use given password");
23
- debugSendPty(pw);
24
- ptyProcess.write(`${pw}\n`);
23
+ sendPty(ptyProcess, `${pw}\n`, logger, 23);
25
24
  return;
26
25
  }
27
26
  if (typeof pw === "function") {
28
27
  debug("call password callback function");
29
28
  pw()
30
29
  .then((v)=>{
31
- ptyProcess.write(`${v}\n`);
30
+ sendPty(ptyProcess, `${v}\n`, logger, 30);
32
31
  });
33
32
  return;
34
33
  }
@@ -36,29 +35,36 @@ function sshLoginCallback(output, ptyProcess, pw, ph) {
36
35
  if (rePhPrompt.test(output)) {
37
36
  if (typeof ph === "string") {
38
37
  debug("use given passphrase");
39
- debugSendPty(ph);
40
- ptyProcess.write(`${ph}\n`);
38
+ sendPty(ptyProcess, `${ph}\n`, logger, 38);
41
39
  return;
42
40
  }
43
41
  if (typeof ph === "function") {
44
42
  debug("call passphrase callback function");
45
43
  ph()
46
44
  .then((v)=>{
47
- ptyProcess.write(`${v}\n`);
45
+ sendPty(ptyProcess, `${v}\n`, logger, 45);
48
46
  });
49
47
  }
50
48
  }
51
49
  }
52
50
 
53
- function getMasterPty(hostInfo) {
51
+ /**
52
+ * create pty for master ssh session and store to hostInfo.masterPty
53
+ */
54
+ function getMasterPty(hostInfo, logger) {
54
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
+ }
55
61
  const ptyProcess = spawn(nodePtyOpt.shell, [], nodePtyOpt);
56
- ptyProcess.onData(buff2String.bind(null, debugVerbose));
62
+ ptyProcess.onData(buff2String.bind(null, debugMasterVerbose));
57
63
  ptyProcess.onData((data)=>{
58
64
  const output = data.toString();
59
- sshLoginCallback(output, ptyProcess, hostInfo.password, hostInfo.passphrase);
65
+ sshLoginCallback(output, ptyProcess, hostInfo.password, hostInfo.passphrase, logger);
60
66
  });
61
- return ptyProcess;
67
+ hostInfo.masterPty = ptyProcess;
62
68
  }
63
69
 
64
70
  async function fork(hostInfo, cmd, args, outputCallback) {
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
- const debugLib = require("debug");
3
- const debug = debugLib("sshClientWrapper:interface");
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("disconnect called");
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:rsyncExec_verbose");
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:sshExec_verbose");
7
- const { sshCmd, getSshOption, sanityCheck } = require("./util.js");
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
 
@@ -80,6 +82,8 @@ async function existsMaster(hostInfo) {
80
82
  const timeout = hostInfo.ConnectTimeout || 60;
81
83
  setTimeoutPromise(timeout * 1000, "neverUsedValue", { signal })
82
84
  .then(()=>{
85
+ debug(`can not finish master session check within ${timeout} sec`);
86
+
83
87
  if (p.pty) {
84
88
  p.pty.kill();
85
89
  }
@@ -93,17 +97,17 @@ async function existsMaster(hostInfo) {
93
97
  }
94
98
  });
95
99
 
96
-
97
100
  try {
98
101
  debug(`check master session: ${sshCmd} ${args.join(" ")}`);
99
102
  p = fork(hostInfo, sshCmd, args, cb);
100
103
  await p;
101
- ac.abort();
102
104
  } catch (err) {
103
105
  if (err.rt !== 255) {
104
106
  debug("check master session failed", err);
105
107
  throw err;
106
108
  }
109
+ } finally {
110
+ ac.abort();
107
111
  }
108
112
 
109
113
  return rt && !/Control socket connect.*: No such file or directory/.test(output);
@@ -118,30 +122,30 @@ async function connect(hostInfo, timeout = 60) {
118
122
  debugVerbose("master connection exists");
119
123
  return;
120
124
  }
121
- debug(`create master ssh session to ${hostInfo.host}`);
122
125
 
123
- if (!hostInfo.masterPty) {
124
- debug(`create master pty for ${hostInfo.host}`);
125
- hostInfo.masterPty = getMasterPty(hostInfo);
126
- } else {
127
- debug(`master pty for ${hostInfo.host} exists`);
128
- }
126
+ debug(`create master pty for ${hostInfo.host}`);
127
+ getMasterPty(hostInfo, debugSendMasterPty);
129
128
 
129
+ debug(`create master ssh session to ${hostInfo.host}`);
130
130
  const ac = new AbortController();
131
131
  const signal = ac.signal;
132
132
  setTimeoutPromise(timeout * 1000, "neverUsedValue", { signal })
133
133
  .then(()=>{
134
+ debug(`can not connect within ${timeout} sec`);
135
+ sendPty(hostInfo.masterPty, "\n", debugSendMasterPty, 137);
136
+ sendPty(hostInfo.masterPty, "exit\n", debugSendMasterPty, 138);
137
+
134
138
  const err = new Error(`can not connect within ${timeout} sec`);
135
139
  err.host = hostInfo.host;
136
140
  err.user = hostInfo.user;
137
141
  err.port = hostInfo.port;
138
- hostInfo.masterPty.write("\n");
139
- hostInfo.masterPty.write("exit\n");
140
142
  throw err;
141
143
  })
142
144
  .catch((err)=>{
143
145
  if (err.name === "AbortError") {
144
146
  debug("timeout was aborted because connection check has done");
147
+ } else {
148
+ throw err;
145
149
  }
146
150
  });
147
151
 
@@ -159,7 +163,6 @@ async function connect(hostInfo, timeout = 60) {
159
163
 
160
164
  if (re.test(output)) {
161
165
  debug("test done. call exit on remotehost");
162
- hostInfo.masterPty.write("exit\n");
163
166
  done = true;
164
167
  resolve();
165
168
  } else if (/Permission denied \(/.test(output)) {
@@ -181,11 +184,14 @@ async function connect(hostInfo, timeout = 60) {
181
184
  const args = getSshOption(hostInfo);
182
185
  args.push(`echo ${words}`);
183
186
  const cmd = `${sshCmd} ${args.join(" ")}\n`;
184
- debugVerbose(cmd);
185
- hostInfo.masterPty.write(cmd);
186
- await p;
187
- ac.abort();
188
- debug(`connected to ${hostInfo.host}`);
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
+ }
189
195
  }
190
196
 
191
197
  /**
@@ -220,7 +226,7 @@ async function disconnect(hostInfo) {
220
226
 
221
227
  if (hostInfo.masterPty) {
222
228
  debug("exit masterPty");
223
- hostInfo.masterPty.write("exit \n");
229
+ sendPty(hostInfo.masterPty, "exit \n", debugSendMasterPty, 228);
224
230
  hostInfo.masterPty = null;
225
231
  }
226
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
- args.push("-i");
66
- args.push(hostInfo.keyFile);
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ssh-client-wrapper",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "description": "wrapper library for open ssh client and rsync",
5
5
  "keywords": [
6
6
  "ssh",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "debug": "^4.3.4",
38
- "glob": "^10.3.1",
38
+ "glob": "^10.3.3",
39
39
  "node-pty": "^0.10.1",
40
40
  "nyc": "^15.1.0",
41
41
  "promise-retry": "^2.0.1"