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 |
|
31 |
|
32 | onChannelOpenFailure(self, recipient, err, channel);
|
33 | return;
|
34 | }
|
35 | if (typeof channel !== 'object'
|
36 | || channel === null
|
37 | || channel.incoming.state === 'closed') {
|
38 | return;
|
39 | }
|
40 |
|
41 | channel.incoming.state = 'closed';
|
42 |
|
43 | if (channel.readable)
|
44 | channel.push(null);
|
45 | if (channel.server) {
|
46 | if (channel.stderr.writable)
|
47 | channel.stderr.end();
|
48 | } else if (channel.stderr.readable) {
|
49 | channel.stderr.push(null);
|
50 | }
|
51 |
|
52 | if (channel.constructor !== SFTP
|
53 | && (channel.outgoing.state === 'open'
|
54 | || channel.outgoing.state === 'eof')
|
55 | && !dead) {
|
56 | channel.close();
|
57 | }
|
58 | if (channel.outgoing.state === 'closing')
|
59 | channel.outgoing.state = 'closed';
|
60 |
|
61 | self._chanMgr.remove(recipient);
|
62 |
|
63 | const readState = channel._readableState;
|
64 | const writeState = channel._writableState;
|
65 | if (writeState && !writeState.ending && !writeState.finished && !dead)
|
66 | channel.end();
|
67 |
|
68 |
|
69 | const chanCallbacks = channel._callbacks;
|
70 | channel._callbacks = [];
|
71 | for (let i = 0; i < chanCallbacks.length; ++i)
|
72 | chanCallbacks[i](true);
|
73 |
|
74 | if (channel.server) {
|
75 | if (!channel.readable
|
76 | || channel.destroyed
|
77 | || (readState && readState.endEmitted)) {
|
78 | channel.emit('close');
|
79 | } else {
|
80 | channel.once('end', () => channel.emit('close'));
|
81 | }
|
82 | } else {
|
83 | let doClose;
|
84 | switch (channel.type) {
|
85 | case 'direct-streamlocal@openssh.com':
|
86 | case 'direct-tcpip':
|
87 | doClose = () => channel.emit('close');
|
88 | break;
|
89 | default: {
|
90 |
|
91 |
|
92 | const exit = channel._exit;
|
93 | doClose = () => {
|
94 | if (exit.code === null)
|
95 | channel.emit('close', exit.code, exit.signal, exit.dump, exit.desc);
|
96 | else
|
97 | channel.emit('close', exit.code);
|
98 | };
|
99 | }
|
100 | }
|
101 | if (!channel.readable
|
102 | || channel.destroyed
|
103 | || (readState && readState.endEmitted)) {
|
104 | doClose();
|
105 | } else {
|
106 | channel.once('end', doClose);
|
107 | }
|
108 |
|
109 | const errReadState = channel.stderr._readableState;
|
110 | if (!channel.stderr.readable
|
111 | || channel.stderr.destroyed
|
112 | || (errReadState && errReadState.endEmitted)) {
|
113 | channel.stderr.emit('close');
|
114 | } else {
|
115 | channel.stderr.once('end', () => channel.stderr.emit('close'));
|
116 | }
|
117 | }
|
118 | }
|
119 |
|
120 | class ChannelManager {
|
121 | constructor(client) {
|
122 | this._client = client;
|
123 | this._channels = {};
|
124 | this._cur = -1;
|
125 | this._count = 0;
|
126 | }
|
127 | add(val) {
|
128 |
|
129 |
|
130 | let id;
|
131 |
|
132 | if (this._cur < MAX_CHANNEL) {
|
133 | id = ++this._cur;
|
134 | } else if (this._count === 0) {
|
135 |
|
136 |
|
137 | this._cur = 0;
|
138 | id = 0;
|
139 | } else {
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 | const channels = this._channels;
|
146 | for (let i = 0; i < MAX_CHANNEL; ++i) {
|
147 | if (channels[i] === undefined) {
|
148 | id = i;
|
149 | break;
|
150 | }
|
151 | }
|
152 | }
|
153 |
|
154 | if (id === undefined)
|
155 | return -1;
|
156 |
|
157 | this._channels[id] = (val || true);
|
158 | ++this._count;
|
159 |
|
160 | return id;
|
161 | }
|
162 | update(id, val) {
|
163 | if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id))
|
164 | throw new Error(`Invalid channel id: ${id}`);
|
165 |
|
166 | if (val && this._channels[id])
|
167 | this._channels[id] = val;
|
168 | }
|
169 | get(id) {
|
170 | if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id))
|
171 | throw new Error(`Invalid channel id: ${id}`);
|
172 |
|
173 | return this._channels[id];
|
174 | }
|
175 | remove(id) {
|
176 | if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id))
|
177 | throw new Error(`Invalid channel id: ${id}`);
|
178 |
|
179 | if (this._channels[id]) {
|
180 | delete this._channels[id];
|
181 | if (this._count)
|
182 | --this._count;
|
183 | }
|
184 | }
|
185 | cleanup(err) {
|
186 | const channels = this._channels;
|
187 | this._channels = {};
|
188 | this._cur = -1;
|
189 | this._count = 0;
|
190 |
|
191 | const chanIDs = Object.keys(channels);
|
192 | const client = this._client;
|
193 | for (let i = 0; i < chanIDs.length; ++i) {
|
194 | const id = +chanIDs[i];
|
195 | const channel = channels[id];
|
196 | onCHANNEL_CLOSE(client, id, channel._channel || channel, err, true);
|
197 | }
|
198 | }
|
199 | }
|
200 |
|
201 | const isRegExp = (() => {
|
202 | const toString = Object.prototype.toString;
|
203 | return (val) => toString.call(val) === '[object RegExp]';
|
204 | })();
|
205 |
|
206 | function generateAlgorithmList(algoList, defaultList, supportedList) {
|
207 | if (Array.isArray(algoList) && algoList.length > 0) {
|
208 |
|
209 | for (let i = 0; i < algoList.length; ++i) {
|
210 | if (supportedList.indexOf(algoList[i]) === -1)
|
211 | throw new Error(`Unsupported algorithm: ${algoList[i]}`);
|
212 | }
|
213 | return algoList;
|
214 | }
|
215 |
|
216 | if (typeof algoList === 'object' && algoList !== null) {
|
217 |
|
218 | const keys = Object.keys(algoList);
|
219 | let list = defaultList;
|
220 | for (let i = 0; i < keys.length; ++i) {
|
221 | const key = keys[i];
|
222 | let val = algoList[key];
|
223 | switch (key) {
|
224 | case 'append':
|
225 | if (!Array.isArray(val))
|
226 | val = [val];
|
227 | if (Array.isArray(val)) {
|
228 | for (let j = 0; j < val.length; ++j) {
|
229 | const append = val[j];
|
230 | if (typeof append === 'string') {
|
231 | if (!append || list.indexOf(append) !== -1)
|
232 | continue;
|
233 | if (supportedList.indexOf(append) === -1)
|
234 | throw new Error(`Unsupported algorithm: ${append}`);
|
235 | if (list === defaultList)
|
236 | list = list.slice();
|
237 | list.push(append);
|
238 | } else if (isRegExp(append)) {
|
239 | for (let k = 0; k < supportedList.length; ++k) {
|
240 | const algo = supportedList[k];
|
241 | if (append.test(algo)) {
|
242 | if (list.indexOf(algo) !== -1)
|
243 | continue;
|
244 | if (list === defaultList)
|
245 | list = list.slice();
|
246 | list.push(algo);
|
247 | }
|
248 | }
|
249 | }
|
250 | }
|
251 | }
|
252 | break;
|
253 | case 'prepend':
|
254 | if (!Array.isArray(val))
|
255 | val = [val];
|
256 | if (Array.isArray(val)) {
|
257 | for (let j = val.length; j >= 0; --j) {
|
258 | const prepend = val[j];
|
259 | if (typeof prepend === 'string') {
|
260 | if (!prepend || list.indexOf(prepend) !== -1)
|
261 | continue;
|
262 | if (supportedList.indexOf(prepend) === -1)
|
263 | throw new Error(`Unsupported algorithm: ${prepend}`);
|
264 | if (list === defaultList)
|
265 | list = list.slice();
|
266 | list.unshift(prepend);
|
267 | } else if (isRegExp(prepend)) {
|
268 | for (let k = supportedList.length; k >= 0; --k) {
|
269 | const algo = supportedList[k];
|
270 | if (prepend.test(algo)) {
|
271 | if (list.indexOf(algo) !== -1)
|
272 | continue;
|
273 | if (list === defaultList)
|
274 | list = list.slice();
|
275 | list.unshift(algo);
|
276 | }
|
277 | }
|
278 | }
|
279 | }
|
280 | }
|
281 | break;
|
282 | case 'remove':
|
283 | if (!Array.isArray(val))
|
284 | val = [val];
|
285 | if (Array.isArray(val)) {
|
286 | for (let j = 0; j < val.length; ++j) {
|
287 | const search = val[j];
|
288 | if (typeof search === 'string') {
|
289 | if (!search)
|
290 | continue;
|
291 | const idx = list.indexOf(search);
|
292 | if (idx === -1)
|
293 | continue;
|
294 | if (list === defaultList)
|
295 | list = list.slice();
|
296 | list.splice(idx, 1);
|
297 | } else if (isRegExp(search)) {
|
298 | for (let k = 0; k < list.length; ++k) {
|
299 | if (search.test(list[k])) {
|
300 | if (list === defaultList)
|
301 | list = list.slice();
|
302 | list.splice(k, 1);
|
303 | --k;
|
304 | }
|
305 | }
|
306 | }
|
307 | }
|
308 | }
|
309 | break;
|
310 | }
|
311 | }
|
312 |
|
313 | return list;
|
314 | }
|
315 |
|
316 | return defaultList;
|
317 | }
|
318 |
|
319 | module.exports = {
|
320 | ChannelManager,
|
321 | generateAlgorithmList,
|
322 | onChannelOpenFailure,
|
323 | onCHANNEL_CLOSE,
|
324 | isWritable: (stream) => {
|
325 |
|
326 |
|
327 | return (stream
|
328 | && stream.writable
|
329 | && stream._readableState
|
330 | && stream._readableState.ended === false);
|
331 | },
|
332 | };
|