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 CHANGED
@@ -1,35 +1,33 @@
1
1
  "use strict";
2
- const debug = require("debug")("sshClientWrapper:fork");
3
- const debugVerbose = require("debug")("sshClientWrapper:fork_verbose");
4
- const debugMasterVerbose = require("debug")("sshClientWrapper:fork_masterPty_verbose");
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
- debugSendPty("yes");
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
- debugSendPty(pw);
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.write(`${v}\n`);
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
- debugSendPty(ph);
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.write(`${v}\n`);
45
+ sendPty(ptyProcess, `${v}\n`, logger, 45);
49
46
  });
50
47
  }
51
48
  }
52
49
  }
53
50
 
54
- function getMasterPty(hostInfo) {
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
- return ptyProcess;
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 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
 
@@ -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
- if (!hostInfo.masterPty) {
125
- debug(`create master pty for ${hostInfo.host}`);
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.write("\n");
137
- hostInfo.masterPty.write("exit\n");
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
- debugVerbose(cmd);
190
- hostInfo.masterPty.write(cmd);
191
- await p;
192
- ac.abort();
193
- 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
+ }
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.write("exit \n");
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
- 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.2",
3
+ "version": "1.8.3",
4
4
  "description": "wrapper library for open ssh client and rsync",
5
5
  "keywords": [
6
6
  "ssh",