@electerm/ssh2 0.8.11 → 1.5.0

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/utils.js ADDED
@@ -0,0 +1,336 @@
1
+ 'use strict';
2
+
3
+ const { SFTP } = require('./protocol/SFTP.js');
4
+
5
+ const MAX_CHANNEL = 2 ** 32 - 1;
6
+
7
+ function onChannelOpenFailure(self, recipient, info, cb) {
8
+ self._chanMgr.remove(recipient);
9
+ if (typeof cb !== 'function')
10
+ return;
11
+
12
+ let err;
13
+ if (info instanceof Error) {
14
+ err = info;
15
+ } else if (typeof info === 'object' && info !== null) {
16
+ err = new Error(`(SSH) Channel open failure: ${info.description}`);
17
+ err.reason = info.reason;
18
+ } else {
19
+ err = new Error(
20
+ '(SSH) Channel open failure: server closed channel unexpectedly'
21
+ );
22
+ err.reason = '';
23
+ }
24
+
25
+ cb(err);
26
+ }
27
+
28
+ function onCHANNEL_CLOSE(self, recipient, channel, err, dead) {
29
+ if (typeof channel === 'function') {
30
+ // We got CHANNEL_CLOSE instead of CHANNEL_OPEN_FAILURE when
31
+ // requesting to open a channel
32
+ onChannelOpenFailure(self, recipient, err, channel);
33
+ return;
34
+ }
35
+
36
+ if (typeof channel !== 'object' || channel === null)
37
+ return;
38
+
39
+ if (channel.incoming && channel.incoming.state === 'closed')
40
+ return;
41
+
42
+ self._chanMgr.remove(recipient);
43
+
44
+ if (channel.server && channel.constructor.name === 'Session')
45
+ return;
46
+
47
+ channel.incoming.state = 'closed';
48
+
49
+ if (channel.readable)
50
+ channel.push(null);
51
+ if (channel.server) {
52
+ if (channel.stderr.writable)
53
+ channel.stderr.end();
54
+ } else if (channel.stderr.readable) {
55
+ channel.stderr.push(null);
56
+ }
57
+
58
+ if (channel.constructor !== SFTP
59
+ && (channel.outgoing.state === 'open'
60
+ || channel.outgoing.state === 'eof')
61
+ && !dead) {
62
+ channel.close();
63
+ }
64
+ if (channel.outgoing.state === 'closing')
65
+ channel.outgoing.state = 'closed';
66
+
67
+ const readState = channel._readableState;
68
+ const writeState = channel._writableState;
69
+ if (writeState && !writeState.ending && !writeState.finished && !dead)
70
+ channel.end();
71
+
72
+ // Take care of any outstanding channel requests
73
+ const chanCallbacks = channel._callbacks;
74
+ channel._callbacks = [];
75
+ for (let i = 0; i < chanCallbacks.length; ++i)
76
+ chanCallbacks[i](true);
77
+
78
+ if (channel.server) {
79
+ if (!channel.readable
80
+ || channel.destroyed
81
+ || (readState && readState.endEmitted)) {
82
+ channel.emit('close');
83
+ } else {
84
+ channel.once('end', () => channel.emit('close'));
85
+ }
86
+ } else {
87
+ let doClose;
88
+ switch (channel.type) {
89
+ case 'direct-streamlocal@openssh.com':
90
+ case 'direct-tcpip':
91
+ doClose = () => channel.emit('close');
92
+ break;
93
+ default: {
94
+ // Align more with node child processes, where the close event gets
95
+ // the same arguments as the exit event
96
+ const exit = channel._exit;
97
+ doClose = () => {
98
+ if (exit.code === null)
99
+ channel.emit('close', exit.code, exit.signal, exit.dump, exit.desc);
100
+ else
101
+ channel.emit('close', exit.code);
102
+ };
103
+ }
104
+ }
105
+ if (!channel.readable
106
+ || channel.destroyed
107
+ || (readState && readState.endEmitted)) {
108
+ doClose();
109
+ } else {
110
+ channel.once('end', doClose);
111
+ }
112
+
113
+ const errReadState = channel.stderr._readableState;
114
+ if (!channel.stderr.readable
115
+ || channel.stderr.destroyed
116
+ || (errReadState && errReadState.endEmitted)) {
117
+ channel.stderr.emit('close');
118
+ } else {
119
+ channel.stderr.once('end', () => channel.stderr.emit('close'));
120
+ }
121
+ }
122
+ }
123
+
124
+ class ChannelManager {
125
+ constructor(client) {
126
+ this._client = client;
127
+ this._channels = {};
128
+ this._cur = -1;
129
+ this._count = 0;
130
+ }
131
+ add(val) {
132
+ // Attempt to reserve an id
133
+
134
+ let id;
135
+ // Optimized paths
136
+ if (this._cur < MAX_CHANNEL) {
137
+ id = ++this._cur;
138
+ } else if (this._count === 0) {
139
+ // Revert and reset back to fast path once we no longer have any channels
140
+ // open
141
+ this._cur = 0;
142
+ id = 0;
143
+ } else {
144
+ // Slower lookup path
145
+
146
+ // This path is triggered we have opened at least MAX_CHANNEL channels
147
+ // while having at least one channel open at any given time, so we have
148
+ // to search for a free id.
149
+ const channels = this._channels;
150
+ for (let i = 0; i < MAX_CHANNEL; ++i) {
151
+ if (channels[i] === undefined) {
152
+ id = i;
153
+ break;
154
+ }
155
+ }
156
+ }
157
+
158
+ if (id === undefined)
159
+ return -1;
160
+
161
+ this._channels[id] = (val || true);
162
+ ++this._count;
163
+
164
+ return id;
165
+ }
166
+ update(id, val) {
167
+ if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id))
168
+ throw new Error(`Invalid channel id: ${id}`);
169
+
170
+ if (val && this._channels[id])
171
+ this._channels[id] = val;
172
+ }
173
+ get(id) {
174
+ if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id))
175
+ throw new Error(`Invalid channel id: ${id}`);
176
+
177
+ return this._channels[id];
178
+ }
179
+ remove(id) {
180
+ if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id))
181
+ throw new Error(`Invalid channel id: ${id}`);
182
+
183
+ if (this._channels[id]) {
184
+ delete this._channels[id];
185
+ if (this._count)
186
+ --this._count;
187
+ }
188
+ }
189
+ cleanup(err) {
190
+ const channels = this._channels;
191
+ this._channels = {};
192
+ this._cur = -1;
193
+ this._count = 0;
194
+
195
+ const chanIDs = Object.keys(channels);
196
+ const client = this._client;
197
+ for (let i = 0; i < chanIDs.length; ++i) {
198
+ const id = +chanIDs[i];
199
+ const channel = channels[id];
200
+ onCHANNEL_CLOSE(client, id, channel._channel || channel, err, true);
201
+ }
202
+ }
203
+ }
204
+
205
+ const isRegExp = (() => {
206
+ const toString = Object.prototype.toString;
207
+ return (val) => toString.call(val) === '[object RegExp]';
208
+ })();
209
+
210
+ function generateAlgorithmList(algoList, defaultList, supportedList) {
211
+ if (Array.isArray(algoList) && algoList.length > 0) {
212
+ // Exact list
213
+ for (let i = 0; i < algoList.length; ++i) {
214
+ if (supportedList.indexOf(algoList[i]) === -1)
215
+ throw new Error(`Unsupported algorithm: ${algoList[i]}`);
216
+ }
217
+ return algoList;
218
+ }
219
+
220
+ if (typeof algoList === 'object' && algoList !== null) {
221
+ // Operations based on the default list
222
+ const keys = Object.keys(algoList);
223
+ let list = defaultList;
224
+ for (let i = 0; i < keys.length; ++i) {
225
+ const key = keys[i];
226
+ let val = algoList[key];
227
+ switch (key) {
228
+ case 'append':
229
+ if (!Array.isArray(val))
230
+ val = [val];
231
+ if (Array.isArray(val)) {
232
+ for (let j = 0; j < val.length; ++j) {
233
+ const append = val[j];
234
+ if (typeof append === 'string') {
235
+ if (!append || list.indexOf(append) !== -1)
236
+ continue;
237
+ if (supportedList.indexOf(append) === -1)
238
+ throw new Error(`Unsupported algorithm: ${append}`);
239
+ if (list === defaultList)
240
+ list = list.slice();
241
+ list.push(append);
242
+ } else if (isRegExp(append)) {
243
+ for (let k = 0; k < supportedList.length; ++k) {
244
+ const algo = supportedList[k];
245
+ if (append.test(algo)) {
246
+ if (list.indexOf(algo) !== -1)
247
+ continue;
248
+ if (list === defaultList)
249
+ list = list.slice();
250
+ list.push(algo);
251
+ }
252
+ }
253
+ }
254
+ }
255
+ }
256
+ break;
257
+ case 'prepend':
258
+ if (!Array.isArray(val))
259
+ val = [val];
260
+ if (Array.isArray(val)) {
261
+ for (let j = val.length; j >= 0; --j) {
262
+ const prepend = val[j];
263
+ if (typeof prepend === 'string') {
264
+ if (!prepend || list.indexOf(prepend) !== -1)
265
+ continue;
266
+ if (supportedList.indexOf(prepend) === -1)
267
+ throw new Error(`Unsupported algorithm: ${prepend}`);
268
+ if (list === defaultList)
269
+ list = list.slice();
270
+ list.unshift(prepend);
271
+ } else if (isRegExp(prepend)) {
272
+ for (let k = supportedList.length; k >= 0; --k) {
273
+ const algo = supportedList[k];
274
+ if (prepend.test(algo)) {
275
+ if (list.indexOf(algo) !== -1)
276
+ continue;
277
+ if (list === defaultList)
278
+ list = list.slice();
279
+ list.unshift(algo);
280
+ }
281
+ }
282
+ }
283
+ }
284
+ }
285
+ break;
286
+ case 'remove':
287
+ if (!Array.isArray(val))
288
+ val = [val];
289
+ if (Array.isArray(val)) {
290
+ for (let j = 0; j < val.length; ++j) {
291
+ const search = val[j];
292
+ if (typeof search === 'string') {
293
+ if (!search)
294
+ continue;
295
+ const idx = list.indexOf(search);
296
+ if (idx === -1)
297
+ continue;
298
+ if (list === defaultList)
299
+ list = list.slice();
300
+ list.splice(idx, 1);
301
+ } else if (isRegExp(search)) {
302
+ for (let k = 0; k < list.length; ++k) {
303
+ if (search.test(list[k])) {
304
+ if (list === defaultList)
305
+ list = list.slice();
306
+ list.splice(k, 1);
307
+ --k;
308
+ }
309
+ }
310
+ }
311
+ }
312
+ }
313
+ break;
314
+ }
315
+ }
316
+
317
+ return list;
318
+ }
319
+
320
+ return defaultList;
321
+ }
322
+
323
+ module.exports = {
324
+ ChannelManager,
325
+ generateAlgorithmList,
326
+ onChannelOpenFailure,
327
+ onCHANNEL_CLOSE,
328
+ isWritable: (stream) => {
329
+ // XXX: hack to workaround regression in node
330
+ // See: https://github.com/nodejs/node/issues/36029
331
+ return (stream
332
+ && stream.writable
333
+ && stream._readableState
334
+ && stream._readableState.ended === false);
335
+ },
336
+ };
package/package.json CHANGED
@@ -1,16 +1,49 @@
1
- { "name": "@electerm/ssh2",
2
- "version": "0.8.11",
1
+ {
2
+ "name": "@electerm/ssh2",
3
+ "version": "1.5.0",
3
4
  "author": "Brian White <mscdex@mscdex.net>",
4
5
  "description": "SSH2 client and server modules written in pure JavaScript for node.js",
5
- "main": "./lib/client",
6
- "engines": { "node": ">=5.2.0" },
6
+ "main": "./lib/index.js",
7
+ "engines": {
8
+ "node": ">=10.16.0"
9
+ },
7
10
  "dependencies": {
8
- "ssh2-streams": "~0.4.10"
11
+ "asn1": "^0.2.4",
12
+ "bcrypt-pbkdf": "^1.0.2"
13
+ },
14
+ "devDependencies": {
15
+ "@mscdex/eslint-config": "^1.0.0",
16
+ "eslint": "^7.0.0"
17
+ },
18
+ "peerDependencies": {
19
+ "cpu-features": "0.0.2",
20
+ "nan": "^2.15.0"
9
21
  },
10
22
  "scripts": {
11
- "test": "node test/test.js"
23
+ "install": "node install.js",
24
+ "rebuild": "node install.js",
25
+ "test": "node test/test.js",
26
+ "lint": "eslint --cache --report-unused-disable-directives --ext=.js .eslintrc.js examples lib test",
27
+ "lint:fix": "npm run lint -- --fix"
12
28
  },
13
- "keywords": [ "ssh", "ssh2", "sftp", "secure", "shell", "exec", "remote", "client" ],
14
- "licenses": [ { "type": "MIT", "url": "http://github.com/mscdex/ssh2/raw/master/LICENSE" } ],
15
- "repository" : { "type": "git", "url": "http://github.com/mscdex/ssh2.git" }
29
+ "keywords": [
30
+ "ssh",
31
+ "ssh2",
32
+ "sftp",
33
+ "secure",
34
+ "shell",
35
+ "exec",
36
+ "remote",
37
+ "client"
38
+ ],
39
+ "licenses": [
40
+ {
41
+ "type": "MIT",
42
+ "url": "http://github.com/mscdex/ssh2/raw/master/LICENSE"
43
+ }
44
+ ],
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "http://github.com/mscdex/ssh2.git"
48
+ }
16
49
  }
@@ -1,145 +0,0 @@
1
- // This wrapper class is used to retain backwards compatibility with
2
- // pre-v0.4 ssh2. If it weren't for `read()` and `write()` being used by the
3
- // streams2/3 API, we could just pass the SFTPStream directly to the end user...
4
-
5
- var inherits = require('util').inherits;
6
- var EventEmitter = require('events').EventEmitter;
7
-
8
- function SFTPWrapper(stream) {
9
- var self = this;
10
-
11
- EventEmitter.call(this);
12
-
13
- this._stream = stream;
14
-
15
- stream.on('error', function(err) {
16
- self.emit('error', err);
17
- }).on('end', function() {
18
- self.emit('end');
19
- }).on('close', function() {
20
- self.emit('close');
21
- }).on('continue', function() {
22
- self.emit('continue');
23
- });
24
- }
25
- inherits(SFTPWrapper, EventEmitter);
26
-
27
- // stream-related methods to pass on
28
- SFTPWrapper.prototype.end = function() {
29
- return this._stream.end();
30
- };
31
- // SFTPStream client methods
32
- SFTPWrapper.prototype.createReadStream = function(path, options) {
33
- return this._stream.createReadStream(path, options);
34
- };
35
- SFTPWrapper.prototype.createWriteStream = function(path, options) {
36
- return this._stream.createWriteStream(path, options);
37
- };
38
- SFTPWrapper.prototype.open = function(path, flags, attrs, cb) {
39
- return this._stream.open(path, flags, attrs, cb);
40
- };
41
- SFTPWrapper.prototype.close = function(handle, cb) {
42
- return this._stream.close(handle, cb);
43
- };
44
- SFTPWrapper.prototype.read = function(handle, buf, off, len, position, cb) {
45
- return this._stream.readData(handle, buf, off, len, position, cb);
46
- };
47
- SFTPWrapper.prototype.write = function(handle, buf, off, len, position, cb) {
48
- return this._stream.writeData(handle, buf, off, len, position, cb);
49
- };
50
- SFTPWrapper.prototype.fastGet = function(remotePath, localPath, opts, cb) {
51
- return this._stream.fastGet(remotePath, localPath, opts, cb);
52
- };
53
- SFTPWrapper.prototype.fastPut = function(localPath, remotePath, opts, cb) {
54
- return this._stream.fastPut(localPath, remotePath, opts, cb);
55
- };
56
- SFTPWrapper.prototype.readFile = function(path, options, callback_) {
57
- return this._stream.readFile(path, options, callback_);
58
- };
59
- SFTPWrapper.prototype.writeFile = function(path, data, options, callback_) {
60
- return this._stream.writeFile(path, data, options, callback_);
61
- };
62
- SFTPWrapper.prototype.appendFile = function(path, data, options, callback_) {
63
- return this._stream.appendFile(path, data, options, callback_);
64
- };
65
- SFTPWrapper.prototype.exists = function(path, cb) {
66
- return this._stream.exists(path, cb);
67
- };
68
- SFTPWrapper.prototype.unlink = function(filename, cb) {
69
- return this._stream.unlink(filename, cb);
70
- };
71
- SFTPWrapper.prototype.rename = function(oldPath, newPath, cb) {
72
- return this._stream.rename(oldPath, newPath, cb);
73
- };
74
- SFTPWrapper.prototype.mkdir = function(path, attrs, cb) {
75
- return this._stream.mkdir(path, attrs, cb);
76
- };
77
- SFTPWrapper.prototype.rmdir = function(path, cb) {
78
- return this._stream.rmdir(path, cb);
79
- };
80
- SFTPWrapper.prototype.readdir = function(where, opts, cb) {
81
- return this._stream.readdir(where, opts, cb);
82
- };
83
- SFTPWrapper.prototype.fstat = function(handle, cb) {
84
- return this._stream.fstat(handle, cb);
85
- };
86
- SFTPWrapper.prototype.stat = function(path, cb) {
87
- return this._stream.stat(path, cb);
88
- };
89
- SFTPWrapper.prototype.lstat = function(path, cb) {
90
- return this._stream.lstat(path, cb);
91
- };
92
- SFTPWrapper.prototype.opendir = function(path, cb) {
93
- return this._stream.opendir(path, cb);
94
- };
95
- SFTPWrapper.prototype.setstat = function(path, attrs, cb) {
96
- return this._stream.setstat(path, attrs, cb);
97
- };
98
- SFTPWrapper.prototype.fsetstat = function(handle, attrs, cb) {
99
- return this._stream.fsetstat(handle, attrs, cb);
100
- };
101
- SFTPWrapper.prototype.futimes = function(handle, atime, mtime, cb) {
102
- return this._stream.futimes(handle, atime, mtime, cb);
103
- };
104
- SFTPWrapper.prototype.utimes = function(path, atime, mtime, cb) {
105
- return this._stream.utimes(path, atime, mtime, cb);
106
- };
107
- SFTPWrapper.prototype.fchown = function(handle, uid, gid, cb) {
108
- return this._stream.fchown(handle, uid, gid, cb);
109
- };
110
- SFTPWrapper.prototype.chown = function(path, uid, gid, cb) {
111
- return this._stream.chown(path, uid, gid, cb);
112
- };
113
- SFTPWrapper.prototype.fchmod = function(handle, mode, cb) {
114
- return this._stream.fchmod(handle, mode, cb);
115
- };
116
- SFTPWrapper.prototype.chmod = function(path, mode, cb) {
117
- return this._stream.chmod(path, mode, cb);
118
- };
119
- SFTPWrapper.prototype.readlink = function(path, cb) {
120
- return this._stream.readlink(path, cb);
121
- };
122
- SFTPWrapper.prototype.symlink = function(targetPath, linkPath, cb) {
123
- return this._stream.symlink(targetPath, linkPath, cb);
124
- };
125
- SFTPWrapper.prototype.realpath = function(path, cb) {
126
- return this._stream.realpath(path, cb);
127
- };
128
- // extended requests
129
- SFTPWrapper.prototype.ext_openssh_rename = function(oldPath, newPath, cb) {
130
- return this._stream.ext_openssh_rename(oldPath, newPath, cb);
131
- };
132
- SFTPWrapper.prototype.ext_openssh_statvfs = function(path, cb) {
133
- return this._stream.ext_openssh_statvfs(path, cb);
134
- };
135
- SFTPWrapper.prototype.ext_openssh_fstatvfs = function(handle, cb) {
136
- return this._stream.ext_openssh_fstatvfs(handle, cb);
137
- };
138
- SFTPWrapper.prototype.ext_openssh_hardlink = function(oldPath, newPath, cb) {
139
- return this._stream.ext_openssh_hardlink(oldPath, newPath, cb);
140
- };
141
- SFTPWrapper.prototype.ext_openssh_fsync = function(handle, cb) {
142
- return this._stream.ext_openssh_fsync(handle, cb);
143
- };
144
-
145
- module.exports = SFTPWrapper;
@@ -1,22 +0,0 @@
1
- module.exports = {
2
- readUInt32BE: function readUInt32BE(buf, offset) {
3
- return buf[offset++] * 16777216
4
- + buf[offset++] * 65536
5
- + buf[offset++] * 256
6
- + buf[offset];
7
- },
8
- writeUInt32BE: function writeUInt32BE(buf, value, offset) {
9
- buf[offset++] = (value >>> 24);
10
- buf[offset++] = (value >>> 16);
11
- buf[offset++] = (value >>> 8);
12
- buf[offset++] = value;
13
- return offset;
14
- },
15
- writeUInt32LE: function writeUInt32LE(buf, value, offset) {
16
- buf[offset++] = value;
17
- buf[offset++] = (value >>> 8);
18
- buf[offset++] = (value >>> 16);
19
- buf[offset++] = (value >>> 24);
20
- return offset;
21
- }
22
- };
@@ -1,80 +0,0 @@
1
- function spliceOne(list, index) {
2
- for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1)
3
- list[i] = list[k];
4
- list.pop();
5
- }
6
-
7
- function Manager(interval, streamInterval, kaCountMax) {
8
- var streams = this._streams = [];
9
- this._timer = undefined;
10
- this._timerInterval = interval;
11
- this._timerfn = function() {
12
- var now = Date.now();
13
- for (var i = 0, len = streams.length, s, last; i < len; ++i) {
14
- s = streams[i];
15
- last = s._kalast;
16
- if (last && (now - last) >= streamInterval) {
17
- if (++s._kacnt > kaCountMax) {
18
- var err = new Error('Keepalive timeout');
19
- err.level = 'client-timeout';
20
- s.emit('error', err);
21
- s.disconnect();
22
- spliceOne(streams, i);
23
- --i;
24
- len = streams.length;
25
- } else {
26
- s._kalast = now;
27
- // XXX: if the server ever starts sending real global requests to the
28
- // client, we will need to add a dummy callback here to keep the
29
- // correct reply order
30
- s.ping();
31
- }
32
- }
33
- }
34
- };
35
- }
36
-
37
- Manager.prototype.start = function() {
38
- if (this._timer)
39
- this.stop();
40
- this._timer = setInterval(this._timerfn, this._timerInterval);
41
- };
42
-
43
- Manager.prototype.stop = function() {
44
- if (this._timer) {
45
- clearInterval(this._timer);
46
- this._timer = undefined;
47
- }
48
- };
49
-
50
- Manager.prototype.add = function(stream) {
51
- var streams = this._streams,
52
- self = this;
53
-
54
- stream.once('end', function() {
55
- self.remove(stream);
56
- }).on('packet', resetKA);
57
-
58
- streams[streams.length] = stream;
59
-
60
- resetKA();
61
-
62
- if (!this._timer)
63
- this.start();
64
-
65
- function resetKA() {
66
- stream._kalast = Date.now();
67
- stream._kacnt = 0;
68
- }
69
- };
70
-
71
- Manager.prototype.remove = function(stream) {
72
- var streams = this._streams,
73
- index = streams.indexOf(stream);
74
- if (index > -1)
75
- spliceOne(streams, index);
76
- if (!streams.length)
77
- this.stop();
78
- };
79
-
80
- module.exports = Manager;