@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/README.md +8 -1
- package/install.js +20 -0
- package/lib/Channel.js +236 -450
- package/lib/agent.js +1080 -376
- package/lib/client.js +1698 -1258
- package/lib/http-agents.js +72 -51
- package/lib/index.js +43 -0
- package/lib/protocol/Protocol.js +2077 -0
- package/lib/protocol/SFTP.js +3778 -0
- package/lib/protocol/constants.js +342 -0
- package/lib/protocol/crypto/binding.gyp +14 -0
- package/lib/protocol/crypto/poly1305.js +43 -0
- package/lib/protocol/crypto/src/binding.cc +2003 -0
- package/lib/protocol/crypto.js +1602 -0
- package/lib/protocol/handlers.js +16 -0
- package/lib/protocol/handlers.misc.js +1214 -0
- package/lib/protocol/kex.js +1831 -0
- package/lib/protocol/keyParser.js +1481 -0
- package/lib/protocol/node-fs-compat.js +115 -0
- package/lib/protocol/utils.js +356 -0
- package/lib/protocol/zlib.js +255 -0
- package/lib/server.js +1226 -1019
- package/lib/utils.js +336 -0
- package/package.json +42 -9
- package/lib/SFTPWrapper.js +0 -145
- package/lib/buffer-helpers.js +0 -22
- package/lib/keepalivemgr.js +0 -80
|
@@ -0,0 +1,2077 @@
|
|
|
1
|
+
/*
|
|
2
|
+
TODO:
|
|
3
|
+
* Replace `buffer._pos` usage in keyParser.js and elsewhere
|
|
4
|
+
* Utilize optional "writev" support when writing packets from
|
|
5
|
+
cipher.encrypt()
|
|
6
|
+
* Built-in support for automatic re-keying, on by default
|
|
7
|
+
* Revisit receiving unexpected/unknown packets
|
|
8
|
+
* Error (fatal or otherwise) or ignore or pass on to user (in some or all
|
|
9
|
+
cases)?
|
|
10
|
+
* Including server/client check for single directional packet types?
|
|
11
|
+
* Check packets for validity or bail as early as possible?
|
|
12
|
+
* Automatic re-key every 2**31 packets after the last key exchange (sent or
|
|
13
|
+
received), as suggested by RFC4344. OpenSSH currently does this.
|
|
14
|
+
* Automatic re-key every so many blocks depending on cipher. RFC4344:
|
|
15
|
+
Because of a birthday property of block ciphers and some modes of
|
|
16
|
+
operation, implementations must be careful not to encrypt too many
|
|
17
|
+
blocks with the same encryption key.
|
|
18
|
+
|
|
19
|
+
Let L be the block length (in bits) of an SSH encryption method's
|
|
20
|
+
block cipher (e.g., 128 for AES). If L is at least 128, then, after
|
|
21
|
+
rekeying, an SSH implementation SHOULD NOT encrypt more than 2**(L/4)
|
|
22
|
+
blocks before rekeying again. If L is at least 128, then SSH
|
|
23
|
+
implementations should also attempt to force a rekey before receiving
|
|
24
|
+
more than 2**(L/4) blocks. If L is less than 128 (which is the case
|
|
25
|
+
for older ciphers such as 3DES, Blowfish, CAST-128, and IDEA), then,
|
|
26
|
+
although it may be too expensive to rekey every 2**(L/4) blocks, it
|
|
27
|
+
is still advisable for SSH implementations to follow the original
|
|
28
|
+
recommendation in [RFC4253]: rekey at least once for every gigabyte
|
|
29
|
+
of transmitted data.
|
|
30
|
+
|
|
31
|
+
Note that if L is less than or equal to 128, then the recommendation
|
|
32
|
+
in this subsection supersedes the recommendation in Section 3.1. If
|
|
33
|
+
an SSH implementation uses a block cipher with a larger block size
|
|
34
|
+
(e.g., Rijndael with 256-bit blocks), then the recommendations in
|
|
35
|
+
Section 3.1 may supersede the recommendations in this subsection
|
|
36
|
+
(depending on the lengths of the packets).
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
'use strict';
|
|
40
|
+
|
|
41
|
+
const { inspect } = require('util');
|
|
42
|
+
|
|
43
|
+
const { bindingAvailable, NullCipher, NullDecipher } = require('./crypto.js');
|
|
44
|
+
const {
|
|
45
|
+
COMPAT_CHECKS,
|
|
46
|
+
DISCONNECT_REASON,
|
|
47
|
+
MESSAGE,
|
|
48
|
+
SIGNALS,
|
|
49
|
+
TERMINAL_MODE,
|
|
50
|
+
} = require('./constants.js');
|
|
51
|
+
const {
|
|
52
|
+
DEFAULT_KEXINIT,
|
|
53
|
+
KexInit,
|
|
54
|
+
kexinit,
|
|
55
|
+
onKEXPayload,
|
|
56
|
+
} = require('./kex.js');
|
|
57
|
+
const {
|
|
58
|
+
parseKey,
|
|
59
|
+
} = require('./keyParser.js');
|
|
60
|
+
const MESSAGE_HANDLERS = require('./handlers.js');
|
|
61
|
+
const {
|
|
62
|
+
bufferCopy,
|
|
63
|
+
bufferFill,
|
|
64
|
+
bufferSlice,
|
|
65
|
+
convertSignature,
|
|
66
|
+
sendPacket,
|
|
67
|
+
writeUInt32BE,
|
|
68
|
+
} = require('./utils.js');
|
|
69
|
+
const {
|
|
70
|
+
PacketReader,
|
|
71
|
+
PacketWriter,
|
|
72
|
+
ZlibPacketReader,
|
|
73
|
+
ZlibPacketWriter,
|
|
74
|
+
} = require('./zlib.js');
|
|
75
|
+
|
|
76
|
+
const MODULE_VER = require('../../package.json').version;
|
|
77
|
+
|
|
78
|
+
const VALID_DISCONNECT_REASONS = new Map(
|
|
79
|
+
Object.values(DISCONNECT_REASON).map((n) => [n, 1])
|
|
80
|
+
);
|
|
81
|
+
const IDENT_RAW = Buffer.from(`SSH-2.0-ssh2js${MODULE_VER}`);
|
|
82
|
+
const IDENT = Buffer.from(`${IDENT_RAW}\r\n`);
|
|
83
|
+
const MAX_LINE_LEN = 8192;
|
|
84
|
+
const MAX_LINES = 1024;
|
|
85
|
+
const PING_PAYLOAD = Buffer.from([
|
|
86
|
+
MESSAGE.GLOBAL_REQUEST,
|
|
87
|
+
// "keepalive@openssh.com"
|
|
88
|
+
0, 0, 0, 21,
|
|
89
|
+
107, 101, 101, 112, 97, 108, 105, 118, 101, 64, 111, 112, 101, 110, 115,
|
|
90
|
+
115, 104, 46, 99, 111, 109,
|
|
91
|
+
// Request a reply
|
|
92
|
+
1,
|
|
93
|
+
]);
|
|
94
|
+
const NO_TERMINAL_MODES_BUFFER = Buffer.from([ TERMINAL_MODE.TTY_OP_END ]);
|
|
95
|
+
|
|
96
|
+
function noop() {}
|
|
97
|
+
|
|
98
|
+
/*
|
|
99
|
+
Inbound:
|
|
100
|
+
* kexinit payload (needed only until exchange hash is generated)
|
|
101
|
+
* raw ident
|
|
102
|
+
* rekey packet queue
|
|
103
|
+
* expected packet (implemented as separate _parse() function?)
|
|
104
|
+
Outbound:
|
|
105
|
+
* kexinit payload (needed only until exchange hash is generated)
|
|
106
|
+
* rekey packet queue
|
|
107
|
+
* kex secret (needed only until NEWKEYS)
|
|
108
|
+
* exchange hash (needed only until NEWKEYS)
|
|
109
|
+
* session ID (set to exchange hash from initial handshake)
|
|
110
|
+
*/
|
|
111
|
+
class Protocol {
|
|
112
|
+
constructor(config) {
|
|
113
|
+
const onWrite = config.onWrite;
|
|
114
|
+
if (typeof onWrite !== 'function')
|
|
115
|
+
throw new Error('Missing onWrite function');
|
|
116
|
+
this._onWrite = (data) => { onWrite(data); };
|
|
117
|
+
|
|
118
|
+
const onError = config.onError;
|
|
119
|
+
if (typeof onError !== 'function')
|
|
120
|
+
throw new Error('Missing onError function');
|
|
121
|
+
this._onError = (err) => { onError(err); };
|
|
122
|
+
|
|
123
|
+
const debug = config.debug;
|
|
124
|
+
this._debug = (typeof debug === 'function'
|
|
125
|
+
? (msg) => { debug(msg); }
|
|
126
|
+
: undefined);
|
|
127
|
+
|
|
128
|
+
const onHeader = config.onHeader;
|
|
129
|
+
this._onHeader = (typeof onHeader === 'function'
|
|
130
|
+
? (...args) => { onHeader(...args); }
|
|
131
|
+
: noop);
|
|
132
|
+
|
|
133
|
+
const onPacket = config.onPacket;
|
|
134
|
+
this._onPacket = (typeof onPacket === 'function'
|
|
135
|
+
? () => { onPacket(); }
|
|
136
|
+
: noop);
|
|
137
|
+
|
|
138
|
+
let onHandshakeComplete = config.onHandshakeComplete;
|
|
139
|
+
if (typeof onHandshakeComplete !== 'function')
|
|
140
|
+
onHandshakeComplete = noop;
|
|
141
|
+
this._onHandshakeComplete = (...args) => {
|
|
142
|
+
this._debug && this._debug('Handshake completed');
|
|
143
|
+
|
|
144
|
+
// Process packets queued during a rekey where necessary
|
|
145
|
+
const oldQueue = this._queue;
|
|
146
|
+
if (oldQueue) {
|
|
147
|
+
this._queue = undefined;
|
|
148
|
+
this._debug && this._debug(
|
|
149
|
+
`Draining outbound queue (${oldQueue.length}) ...`
|
|
150
|
+
);
|
|
151
|
+
for (let i = 0; i < oldQueue.length; ++i) {
|
|
152
|
+
const data = oldQueue[i];
|
|
153
|
+
// data === payload only
|
|
154
|
+
|
|
155
|
+
// XXX: hacky
|
|
156
|
+
let finalized = this._packetRW.write.finalize(data);
|
|
157
|
+
if (finalized === data) {
|
|
158
|
+
const packet = this._cipher.allocPacket(data.length);
|
|
159
|
+
packet.set(data, 5);
|
|
160
|
+
finalized = packet;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
sendPacket(this, finalized);
|
|
164
|
+
}
|
|
165
|
+
this._debug && this._debug('... finished draining outbound queue');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
onHandshakeComplete(...args);
|
|
169
|
+
};
|
|
170
|
+
this._queue = undefined;
|
|
171
|
+
|
|
172
|
+
const messageHandlers = config.messageHandlers;
|
|
173
|
+
if (typeof messageHandlers === 'object' && messageHandlers !== null)
|
|
174
|
+
this._handlers = messageHandlers;
|
|
175
|
+
else
|
|
176
|
+
this._handlers = {};
|
|
177
|
+
|
|
178
|
+
this._onPayload = onPayload.bind(this);
|
|
179
|
+
|
|
180
|
+
this._server = !!config.server;
|
|
181
|
+
this._banner = undefined;
|
|
182
|
+
let greeting;
|
|
183
|
+
if (this._server) {
|
|
184
|
+
if (typeof config.hostKeys !== 'object' || config.hostKeys === null)
|
|
185
|
+
throw new Error('Missing server host key(s)');
|
|
186
|
+
this._hostKeys = config.hostKeys;
|
|
187
|
+
|
|
188
|
+
// Greeting displayed before the ssh identification string is sent, this
|
|
189
|
+
// is usually ignored by most clients
|
|
190
|
+
if (typeof config.greeting === 'string' && config.greeting.length) {
|
|
191
|
+
greeting = (config.greeting.slice(-2) === '\r\n'
|
|
192
|
+
? config.greeting
|
|
193
|
+
: `${config.greeting}\r\n`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Banner shown after the handshake completes, but before user
|
|
197
|
+
// authentication begins
|
|
198
|
+
if (typeof config.banner === 'string' && config.banner.length) {
|
|
199
|
+
this._banner = (config.banner.slice(-2) === '\r\n'
|
|
200
|
+
? config.banner
|
|
201
|
+
: `${config.banner}\r\n`);
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
this._hostKeys = undefined;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let offer = config.offer;
|
|
208
|
+
if (typeof offer !== 'object' || offer === null)
|
|
209
|
+
offer = DEFAULT_KEXINIT;
|
|
210
|
+
else if (offer.constructor !== KexInit)
|
|
211
|
+
offer = new KexInit(offer);
|
|
212
|
+
this._kex = undefined;
|
|
213
|
+
this._kexinit = undefined;
|
|
214
|
+
this._offer = offer;
|
|
215
|
+
this._cipher = new NullCipher(0, this._onWrite);
|
|
216
|
+
this._decipher = undefined;
|
|
217
|
+
this._skipNextInboundPacket = false;
|
|
218
|
+
this._packetRW = {
|
|
219
|
+
read: new PacketReader(),
|
|
220
|
+
write: new PacketWriter(this),
|
|
221
|
+
};
|
|
222
|
+
this._hostVerifier = (!this._server
|
|
223
|
+
&& typeof config.hostVerifier === 'function'
|
|
224
|
+
? config.hostVerifier
|
|
225
|
+
: undefined);
|
|
226
|
+
|
|
227
|
+
this._parse = parseHeader;
|
|
228
|
+
this._buffer = undefined;
|
|
229
|
+
this._authsQueue = [];
|
|
230
|
+
this._authenticated = false;
|
|
231
|
+
this._remoteIdentRaw = undefined;
|
|
232
|
+
let sentIdent;
|
|
233
|
+
if (typeof config.ident === 'string') {
|
|
234
|
+
this._identRaw = Buffer.from(`SSH-2.0-${config.ident}`);
|
|
235
|
+
|
|
236
|
+
sentIdent = Buffer.allocUnsafe(this._identRaw.length + 2);
|
|
237
|
+
sentIdent.set(this._identRaw, 0);
|
|
238
|
+
sentIdent[sentIdent.length - 2] = 13; // '\r'
|
|
239
|
+
sentIdent[sentIdent.length - 1] = 10; // '\n'
|
|
240
|
+
} else if (Buffer.isBuffer(config.ident)) {
|
|
241
|
+
const fullIdent = Buffer.allocUnsafe(8 + config.ident.length);
|
|
242
|
+
fullIdent.latin1Write('SSH-2.0-', 0, 8);
|
|
243
|
+
fullIdent.set(config.ident, 8);
|
|
244
|
+
this._identRaw = fullIdent;
|
|
245
|
+
|
|
246
|
+
sentIdent = Buffer.allocUnsafe(fullIdent.length + 2);
|
|
247
|
+
sentIdent.set(fullIdent, 0);
|
|
248
|
+
sentIdent[sentIdent.length - 2] = 13; // '\r'
|
|
249
|
+
sentIdent[sentIdent.length - 1] = 10; // '\n'
|
|
250
|
+
} else {
|
|
251
|
+
this._identRaw = IDENT_RAW;
|
|
252
|
+
sentIdent = IDENT;
|
|
253
|
+
}
|
|
254
|
+
this._compatFlags = 0;
|
|
255
|
+
|
|
256
|
+
if (this._debug) {
|
|
257
|
+
if (bindingAvailable)
|
|
258
|
+
this._debug('Custom crypto binding available');
|
|
259
|
+
else
|
|
260
|
+
this._debug('Custom crypto binding not available');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
this._debug && this._debug(
|
|
264
|
+
`Local ident: ${inspect(this._identRaw.toString())}`
|
|
265
|
+
);
|
|
266
|
+
this.start = () => {
|
|
267
|
+
this.start = undefined;
|
|
268
|
+
if (greeting)
|
|
269
|
+
this._onWrite(greeting);
|
|
270
|
+
this._onWrite(sentIdent);
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
_destruct(reason) {
|
|
274
|
+
this._packetRW.read.cleanup();
|
|
275
|
+
this._packetRW.write.cleanup();
|
|
276
|
+
this._cipher && this._cipher.free();
|
|
277
|
+
this._decipher && this._decipher.free();
|
|
278
|
+
if (typeof reason !== 'string' || reason.length === 0)
|
|
279
|
+
reason = 'fatal error';
|
|
280
|
+
this.parse = () => {
|
|
281
|
+
throw new Error(`Instance unusable after ${reason}`);
|
|
282
|
+
};
|
|
283
|
+
this._onWrite = () => {
|
|
284
|
+
throw new Error(`Instance unusable after ${reason}`);
|
|
285
|
+
};
|
|
286
|
+
this._destruct = undefined;
|
|
287
|
+
}
|
|
288
|
+
cleanup() {
|
|
289
|
+
this._destruct && this._destruct();
|
|
290
|
+
}
|
|
291
|
+
parse(chunk, i, len) {
|
|
292
|
+
while (i < len)
|
|
293
|
+
i = this._parse(chunk, i, len);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Protocol message API
|
|
297
|
+
|
|
298
|
+
// ===========================================================================
|
|
299
|
+
// Common/Shared =============================================================
|
|
300
|
+
// ===========================================================================
|
|
301
|
+
|
|
302
|
+
// Global
|
|
303
|
+
// ------
|
|
304
|
+
disconnect(reason) {
|
|
305
|
+
const pktLen = 1 + 4 + 4 + 4;
|
|
306
|
+
// We don't use _packetRW.write.* here because we need to make sure that
|
|
307
|
+
// we always get a full packet allocated because this message can be sent
|
|
308
|
+
// at any time -- even during a key exchange
|
|
309
|
+
let p = this._packetRW.write.allocStartKEX;
|
|
310
|
+
const packet = this._packetRW.write.alloc(pktLen, true);
|
|
311
|
+
const end = p + pktLen;
|
|
312
|
+
|
|
313
|
+
if (!VALID_DISCONNECT_REASONS.has(reason))
|
|
314
|
+
reason = DISCONNECT_REASON.PROTOCOL_ERROR;
|
|
315
|
+
|
|
316
|
+
packet[p] = MESSAGE.DISCONNECT;
|
|
317
|
+
writeUInt32BE(packet, reason, ++p);
|
|
318
|
+
packet.fill(0, p += 4, end);
|
|
319
|
+
|
|
320
|
+
this._debug && this._debug(`Outbound: Sending DISCONNECT (${reason})`);
|
|
321
|
+
sendPacket(this, this._packetRW.write.finalize(packet, true), true);
|
|
322
|
+
}
|
|
323
|
+
ping() {
|
|
324
|
+
const p = this._packetRW.write.allocStart;
|
|
325
|
+
const packet = this._packetRW.write.alloc(PING_PAYLOAD.length);
|
|
326
|
+
|
|
327
|
+
packet.set(PING_PAYLOAD, p);
|
|
328
|
+
|
|
329
|
+
this._debug && this._debug(
|
|
330
|
+
'Outbound: Sending ping (GLOBAL_REQUEST: keepalive@openssh.com)'
|
|
331
|
+
);
|
|
332
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
333
|
+
}
|
|
334
|
+
rekey() {
|
|
335
|
+
if (this._kexinit === undefined) {
|
|
336
|
+
this._debug && this._debug('Outbound: Initiated explicit rekey');
|
|
337
|
+
this._queue = [];
|
|
338
|
+
kexinit(this);
|
|
339
|
+
} else {
|
|
340
|
+
this._debug && this._debug('Outbound: Ignoring rekey during handshake');
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 'ssh-connection' service-specific
|
|
345
|
+
// ---------------------------------
|
|
346
|
+
requestSuccess(data) {
|
|
347
|
+
let p = this._packetRW.write.allocStart;
|
|
348
|
+
let packet;
|
|
349
|
+
if (Buffer.isBuffer(data)) {
|
|
350
|
+
packet = this._packetRW.write.alloc(1 + data.length);
|
|
351
|
+
|
|
352
|
+
packet[p] = MESSAGE.REQUEST_SUCCESS;
|
|
353
|
+
|
|
354
|
+
packet.set(data, ++p);
|
|
355
|
+
} else {
|
|
356
|
+
packet = this._packetRW.write.alloc(1);
|
|
357
|
+
|
|
358
|
+
packet[p] = MESSAGE.REQUEST_SUCCESS;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
this._debug && this._debug('Outbound: Sending REQUEST_SUCCESS');
|
|
362
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
363
|
+
}
|
|
364
|
+
requestFailure() {
|
|
365
|
+
const p = this._packetRW.write.allocStart;
|
|
366
|
+
const packet = this._packetRW.write.alloc(1);
|
|
367
|
+
|
|
368
|
+
packet[p] = MESSAGE.REQUEST_FAILURE;
|
|
369
|
+
|
|
370
|
+
this._debug && this._debug('Outbound: Sending REQUEST_FAILURE');
|
|
371
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
372
|
+
}
|
|
373
|
+
channelSuccess(chan) {
|
|
374
|
+
// Does not consume window space
|
|
375
|
+
|
|
376
|
+
let p = this._packetRW.write.allocStart;
|
|
377
|
+
const packet = this._packetRW.write.alloc(1 + 4);
|
|
378
|
+
|
|
379
|
+
packet[p] = MESSAGE.CHANNEL_SUCCESS;
|
|
380
|
+
|
|
381
|
+
writeUInt32BE(packet, chan, ++p);
|
|
382
|
+
|
|
383
|
+
this._debug && this._debug(`Outbound: Sending CHANNEL_SUCCESS (r:${chan})`);
|
|
384
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
385
|
+
}
|
|
386
|
+
channelFailure(chan) {
|
|
387
|
+
// Does not consume window space
|
|
388
|
+
|
|
389
|
+
let p = this._packetRW.write.allocStart;
|
|
390
|
+
const packet = this._packetRW.write.alloc(1 + 4);
|
|
391
|
+
|
|
392
|
+
packet[p] = MESSAGE.CHANNEL_FAILURE;
|
|
393
|
+
|
|
394
|
+
writeUInt32BE(packet, chan, ++p);
|
|
395
|
+
|
|
396
|
+
this._debug && this._debug(`Outbound: Sending CHANNEL_FAILURE (r:${chan})`);
|
|
397
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
398
|
+
}
|
|
399
|
+
channelEOF(chan) {
|
|
400
|
+
// Does not consume window space
|
|
401
|
+
|
|
402
|
+
let p = this._packetRW.write.allocStart;
|
|
403
|
+
const packet = this._packetRW.write.alloc(1 + 4);
|
|
404
|
+
|
|
405
|
+
packet[p] = MESSAGE.CHANNEL_EOF;
|
|
406
|
+
|
|
407
|
+
writeUInt32BE(packet, chan, ++p);
|
|
408
|
+
|
|
409
|
+
this._debug && this._debug(`Outbound: Sending CHANNEL_EOF (r:${chan})`);
|
|
410
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
411
|
+
}
|
|
412
|
+
channelClose(chan) {
|
|
413
|
+
// Does not consume window space
|
|
414
|
+
|
|
415
|
+
let p = this._packetRW.write.allocStart;
|
|
416
|
+
const packet = this._packetRW.write.alloc(1 + 4);
|
|
417
|
+
|
|
418
|
+
packet[p] = MESSAGE.CHANNEL_CLOSE;
|
|
419
|
+
|
|
420
|
+
writeUInt32BE(packet, chan, ++p);
|
|
421
|
+
|
|
422
|
+
this._debug && this._debug(`Outbound: Sending CHANNEL_CLOSE (r:${chan})`);
|
|
423
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
424
|
+
}
|
|
425
|
+
channelWindowAdjust(chan, amount) {
|
|
426
|
+
// Does not consume window space
|
|
427
|
+
|
|
428
|
+
let p = this._packetRW.write.allocStart;
|
|
429
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 4);
|
|
430
|
+
|
|
431
|
+
packet[p] = MESSAGE.CHANNEL_WINDOW_ADJUST;
|
|
432
|
+
|
|
433
|
+
writeUInt32BE(packet, chan, ++p);
|
|
434
|
+
|
|
435
|
+
writeUInt32BE(packet, amount, p += 4);
|
|
436
|
+
|
|
437
|
+
this._debug && this._debug(
|
|
438
|
+
`Outbound: Sending CHANNEL_WINDOW_ADJUST (r:${chan}, ${amount})`
|
|
439
|
+
);
|
|
440
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
441
|
+
}
|
|
442
|
+
channelData(chan, data) {
|
|
443
|
+
const isBuffer = Buffer.isBuffer(data);
|
|
444
|
+
const dataLen = (isBuffer ? data.length : Buffer.byteLength(data));
|
|
445
|
+
let p = this._packetRW.write.allocStart;
|
|
446
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 4 + dataLen);
|
|
447
|
+
|
|
448
|
+
packet[p] = MESSAGE.CHANNEL_DATA;
|
|
449
|
+
|
|
450
|
+
writeUInt32BE(packet, chan, ++p);
|
|
451
|
+
|
|
452
|
+
writeUInt32BE(packet, dataLen, p += 4);
|
|
453
|
+
|
|
454
|
+
if (isBuffer)
|
|
455
|
+
packet.set(data, p += 4);
|
|
456
|
+
else
|
|
457
|
+
packet.utf8Write(data, p += 4, dataLen);
|
|
458
|
+
|
|
459
|
+
this._debug && this._debug(
|
|
460
|
+
`Outbound: Sending CHANNEL_DATA (r:${chan}, ${dataLen})`
|
|
461
|
+
);
|
|
462
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
463
|
+
}
|
|
464
|
+
channelExtData(chan, data, type) {
|
|
465
|
+
const isBuffer = Buffer.isBuffer(data);
|
|
466
|
+
const dataLen = (isBuffer ? data.length : Buffer.byteLength(data));
|
|
467
|
+
let p = this._packetRW.write.allocStart;
|
|
468
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 4 + 4 + dataLen);
|
|
469
|
+
|
|
470
|
+
packet[p] = MESSAGE.CHANNEL_EXTENDED_DATA;
|
|
471
|
+
|
|
472
|
+
writeUInt32BE(packet, chan, ++p);
|
|
473
|
+
|
|
474
|
+
writeUInt32BE(packet, type, p += 4);
|
|
475
|
+
|
|
476
|
+
writeUInt32BE(packet, dataLen, p += 4);
|
|
477
|
+
|
|
478
|
+
if (isBuffer)
|
|
479
|
+
packet.set(data, p += 4);
|
|
480
|
+
else
|
|
481
|
+
packet.utf8Write(data, p += 4, dataLen);
|
|
482
|
+
|
|
483
|
+
this._debug
|
|
484
|
+
&& this._debug(`Outbound: Sending CHANNEL_EXTENDED_DATA (r:${chan})`);
|
|
485
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
486
|
+
}
|
|
487
|
+
channelOpenConfirm(remote, local, initWindow, maxPacket) {
|
|
488
|
+
let p = this._packetRW.write.allocStart;
|
|
489
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 4 + 4 + 4);
|
|
490
|
+
|
|
491
|
+
packet[p] = MESSAGE.CHANNEL_OPEN_CONFIRMATION;
|
|
492
|
+
|
|
493
|
+
writeUInt32BE(packet, remote, ++p);
|
|
494
|
+
|
|
495
|
+
writeUInt32BE(packet, local, p += 4);
|
|
496
|
+
|
|
497
|
+
writeUInt32BE(packet, initWindow, p += 4);
|
|
498
|
+
|
|
499
|
+
writeUInt32BE(packet, maxPacket, p += 4);
|
|
500
|
+
|
|
501
|
+
this._debug && this._debug(
|
|
502
|
+
`Outbound: Sending CHANNEL_OPEN_CONFIRMATION (r:${remote}, l:${local})`
|
|
503
|
+
);
|
|
504
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
505
|
+
}
|
|
506
|
+
channelOpenFail(remote, reason, desc) {
|
|
507
|
+
if (typeof desc !== 'string')
|
|
508
|
+
desc = '';
|
|
509
|
+
|
|
510
|
+
const descLen = Buffer.byteLength(desc);
|
|
511
|
+
let p = this._packetRW.write.allocStart;
|
|
512
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 4 + 4 + descLen + 4);
|
|
513
|
+
|
|
514
|
+
packet[p] = MESSAGE.CHANNEL_OPEN_FAILURE;
|
|
515
|
+
|
|
516
|
+
writeUInt32BE(packet, remote, ++p);
|
|
517
|
+
|
|
518
|
+
writeUInt32BE(packet, reason, p += 4);
|
|
519
|
+
|
|
520
|
+
writeUInt32BE(packet, descLen, p += 4);
|
|
521
|
+
|
|
522
|
+
p += 4;
|
|
523
|
+
if (descLen) {
|
|
524
|
+
packet.utf8Write(desc, p, descLen);
|
|
525
|
+
p += descLen;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
writeUInt32BE(packet, 0, p); // Empty language tag
|
|
529
|
+
|
|
530
|
+
this._debug
|
|
531
|
+
&& this._debug(`Outbound: Sending CHANNEL_OPEN_FAILURE (r:${remote})`);
|
|
532
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// ===========================================================================
|
|
536
|
+
// Client-specific ===========================================================
|
|
537
|
+
// ===========================================================================
|
|
538
|
+
|
|
539
|
+
// Global
|
|
540
|
+
// ------
|
|
541
|
+
service(name) {
|
|
542
|
+
if (this._server)
|
|
543
|
+
throw new Error('Client-only method called in server mode');
|
|
544
|
+
|
|
545
|
+
const nameLen = Buffer.byteLength(name);
|
|
546
|
+
let p = this._packetRW.write.allocStart;
|
|
547
|
+
const packet = this._packetRW.write.alloc(1 + 4 + nameLen);
|
|
548
|
+
|
|
549
|
+
packet[p] = MESSAGE.SERVICE_REQUEST;
|
|
550
|
+
|
|
551
|
+
writeUInt32BE(packet, nameLen, ++p);
|
|
552
|
+
packet.utf8Write(name, p += 4, nameLen);
|
|
553
|
+
|
|
554
|
+
this._debug && this._debug(`Outbound: Sending SERVICE_REQUEST (${name})`);
|
|
555
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// 'ssh-userauth' service-specific
|
|
559
|
+
// -------------------------------
|
|
560
|
+
authPassword(username, password, newPassword) {
|
|
561
|
+
if (this._server)
|
|
562
|
+
throw new Error('Client-only method called in server mode');
|
|
563
|
+
|
|
564
|
+
const userLen = Buffer.byteLength(username);
|
|
565
|
+
const passLen = Buffer.byteLength(password);
|
|
566
|
+
const newPassLen = (newPassword ? Buffer.byteLength(newPassword) : 0);
|
|
567
|
+
let p = this._packetRW.write.allocStart;
|
|
568
|
+
const packet = this._packetRW.write.alloc(
|
|
569
|
+
1 + 4 + userLen + 4 + 14 + 4 + 8 + 1 + 4 + passLen
|
|
570
|
+
+ (newPassword ? 4 + newPassLen : 0)
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
packet[p] = MESSAGE.USERAUTH_REQUEST;
|
|
574
|
+
|
|
575
|
+
writeUInt32BE(packet, userLen, ++p);
|
|
576
|
+
packet.utf8Write(username, p += 4, userLen);
|
|
577
|
+
|
|
578
|
+
writeUInt32BE(packet, 14, p += userLen);
|
|
579
|
+
packet.utf8Write('ssh-connection', p += 4, 14);
|
|
580
|
+
|
|
581
|
+
writeUInt32BE(packet, 8, p += 14);
|
|
582
|
+
packet.utf8Write('password', p += 4, 8);
|
|
583
|
+
|
|
584
|
+
packet[p += 8] = (newPassword ? 1 : 0);
|
|
585
|
+
|
|
586
|
+
writeUInt32BE(packet, passLen, ++p);
|
|
587
|
+
if (Buffer.isBuffer(password))
|
|
588
|
+
bufferCopy(password, packet, 0, passLen, p += 4);
|
|
589
|
+
else
|
|
590
|
+
packet.utf8Write(password, p += 4, passLen);
|
|
591
|
+
|
|
592
|
+
if (newPassword) {
|
|
593
|
+
writeUInt32BE(packet, newPassLen, p += passLen);
|
|
594
|
+
if (Buffer.isBuffer(newPassword))
|
|
595
|
+
bufferCopy(newPassword, packet, 0, newPassLen, p += 4);
|
|
596
|
+
else
|
|
597
|
+
packet.utf8Write(newPassword, p += 4, newPassLen);
|
|
598
|
+
this._debug && this._debug(
|
|
599
|
+
'Outbound: Sending USERAUTH_REQUEST (changed password)'
|
|
600
|
+
);
|
|
601
|
+
} else {
|
|
602
|
+
this._debug && this._debug(
|
|
603
|
+
'Outbound: Sending USERAUTH_REQUEST (password)'
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
this._authsQueue.push('password');
|
|
608
|
+
|
|
609
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
610
|
+
}
|
|
611
|
+
authPK(username, pubKey, cbSign) {
|
|
612
|
+
if (this._server)
|
|
613
|
+
throw new Error('Client-only method called in server mode');
|
|
614
|
+
|
|
615
|
+
pubKey = parseKey(pubKey);
|
|
616
|
+
if (pubKey instanceof Error)
|
|
617
|
+
throw new Error('Invalid key');
|
|
618
|
+
|
|
619
|
+
const keyType = pubKey.type;
|
|
620
|
+
pubKey = pubKey.getPublicSSH();
|
|
621
|
+
|
|
622
|
+
const userLen = Buffer.byteLength(username);
|
|
623
|
+
const algoLen = Buffer.byteLength(keyType);
|
|
624
|
+
const pubKeyLen = pubKey.length;
|
|
625
|
+
const sessionID = this._kex.sessionID;
|
|
626
|
+
const sesLen = sessionID.length;
|
|
627
|
+
const payloadLen =
|
|
628
|
+
(cbSign ? 4 + sesLen : 0)
|
|
629
|
+
+ 1 + 4 + userLen + 4 + 14 + 4 + 9 + 1 + 4 + algoLen + 4 + pubKeyLen;
|
|
630
|
+
let packet;
|
|
631
|
+
let p;
|
|
632
|
+
if (cbSign) {
|
|
633
|
+
packet = Buffer.allocUnsafe(payloadLen);
|
|
634
|
+
p = 0;
|
|
635
|
+
writeUInt32BE(packet, sesLen, p);
|
|
636
|
+
packet.set(sessionID, p += 4);
|
|
637
|
+
p += sesLen;
|
|
638
|
+
} else {
|
|
639
|
+
packet = this._packetRW.write.alloc(payloadLen);
|
|
640
|
+
p = this._packetRW.write.allocStart;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
packet[p] = MESSAGE.USERAUTH_REQUEST;
|
|
644
|
+
|
|
645
|
+
writeUInt32BE(packet, userLen, ++p);
|
|
646
|
+
packet.utf8Write(username, p += 4, userLen);
|
|
647
|
+
|
|
648
|
+
writeUInt32BE(packet, 14, p += userLen);
|
|
649
|
+
packet.utf8Write('ssh-connection', p += 4, 14);
|
|
650
|
+
|
|
651
|
+
writeUInt32BE(packet, 9, p += 14);
|
|
652
|
+
packet.utf8Write('publickey', p += 4, 9);
|
|
653
|
+
|
|
654
|
+
packet[p += 9] = (cbSign ? 1 : 0);
|
|
655
|
+
|
|
656
|
+
writeUInt32BE(packet, algoLen, ++p);
|
|
657
|
+
packet.utf8Write(keyType, p += 4, algoLen);
|
|
658
|
+
|
|
659
|
+
writeUInt32BE(packet, pubKeyLen, p += algoLen);
|
|
660
|
+
packet.set(pubKey, p += 4);
|
|
661
|
+
|
|
662
|
+
if (!cbSign) {
|
|
663
|
+
this._authsQueue.push('publickey');
|
|
664
|
+
|
|
665
|
+
this._debug && this._debug(
|
|
666
|
+
'Outbound: Sending USERAUTH_REQUEST (publickey -- check)'
|
|
667
|
+
);
|
|
668
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
cbSign(packet, (signature) => {
|
|
673
|
+
signature = convertSignature(signature, keyType);
|
|
674
|
+
if (signature === false)
|
|
675
|
+
throw new Error('Error while converting handshake signature');
|
|
676
|
+
|
|
677
|
+
const sigLen = signature.length;
|
|
678
|
+
p = this._packetRW.write.allocStart;
|
|
679
|
+
packet = this._packetRW.write.alloc(
|
|
680
|
+
1 + 4 + userLen + 4 + 14 + 4 + 9 + 1 + 4 + algoLen + 4 + pubKeyLen + 4
|
|
681
|
+
+ 4 + algoLen + 4 + sigLen
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
// TODO: simply copy from original "packet" to new `packet` to avoid
|
|
685
|
+
// having to write each individual field a second time?
|
|
686
|
+
packet[p] = MESSAGE.USERAUTH_REQUEST;
|
|
687
|
+
|
|
688
|
+
writeUInt32BE(packet, userLen, ++p);
|
|
689
|
+
packet.utf8Write(username, p += 4, userLen);
|
|
690
|
+
|
|
691
|
+
writeUInt32BE(packet, 14, p += userLen);
|
|
692
|
+
packet.utf8Write('ssh-connection', p += 4, 14);
|
|
693
|
+
|
|
694
|
+
writeUInt32BE(packet, 9, p += 14);
|
|
695
|
+
packet.utf8Write('publickey', p += 4, 9);
|
|
696
|
+
|
|
697
|
+
packet[p += 9] = 1;
|
|
698
|
+
|
|
699
|
+
writeUInt32BE(packet, algoLen, ++p);
|
|
700
|
+
packet.utf8Write(keyType, p += 4, algoLen);
|
|
701
|
+
|
|
702
|
+
writeUInt32BE(packet, pubKeyLen, p += algoLen);
|
|
703
|
+
packet.set(pubKey, p += 4);
|
|
704
|
+
|
|
705
|
+
writeUInt32BE(packet, 4 + algoLen + 4 + sigLen, p += pubKeyLen);
|
|
706
|
+
|
|
707
|
+
writeUInt32BE(packet, algoLen, p += 4);
|
|
708
|
+
packet.utf8Write(keyType, p += 4, algoLen);
|
|
709
|
+
|
|
710
|
+
writeUInt32BE(packet, sigLen, p += algoLen);
|
|
711
|
+
packet.set(signature, p += 4);
|
|
712
|
+
|
|
713
|
+
// Servers shouldn't send packet type 60 in response to signed publickey
|
|
714
|
+
// attempts, but if they do, interpret as type 60.
|
|
715
|
+
this._authsQueue.push('publickey');
|
|
716
|
+
|
|
717
|
+
this._debug && this._debug(
|
|
718
|
+
'Outbound: Sending USERAUTH_REQUEST (publickey)'
|
|
719
|
+
);
|
|
720
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
authHostbased(username, pubKey, hostname, userlocal, cbSign) {
|
|
724
|
+
// TODO: Make DRY by sharing similar code with authPK()
|
|
725
|
+
if (this._server)
|
|
726
|
+
throw new Error('Client-only method called in server mode');
|
|
727
|
+
|
|
728
|
+
pubKey = parseKey(pubKey);
|
|
729
|
+
if (pubKey instanceof Error)
|
|
730
|
+
throw new Error('Invalid key');
|
|
731
|
+
|
|
732
|
+
const keyType = pubKey.type;
|
|
733
|
+
pubKey = pubKey.getPublicSSH();
|
|
734
|
+
|
|
735
|
+
const userLen = Buffer.byteLength(username);
|
|
736
|
+
const algoLen = Buffer.byteLength(keyType);
|
|
737
|
+
const pubKeyLen = pubKey.length;
|
|
738
|
+
const sessionID = this._kex.sessionID;
|
|
739
|
+
const sesLen = sessionID.length;
|
|
740
|
+
const hostnameLen = Buffer.byteLength(hostname);
|
|
741
|
+
const userlocalLen = Buffer.byteLength(userlocal);
|
|
742
|
+
const data = Buffer.allocUnsafe(
|
|
743
|
+
4 + sesLen + 1 + 4 + userLen + 4 + 14 + 4 + 9 + 4 + algoLen
|
|
744
|
+
+ 4 + pubKeyLen + 4 + hostnameLen + 4 + userlocalLen
|
|
745
|
+
);
|
|
746
|
+
let p = 0;
|
|
747
|
+
|
|
748
|
+
writeUInt32BE(data, sesLen, p);
|
|
749
|
+
data.set(sessionID, p += 4);
|
|
750
|
+
|
|
751
|
+
data[p += sesLen] = MESSAGE.USERAUTH_REQUEST;
|
|
752
|
+
|
|
753
|
+
writeUInt32BE(data, userLen, ++p);
|
|
754
|
+
data.utf8Write(username, p += 4, userLen);
|
|
755
|
+
|
|
756
|
+
writeUInt32BE(data, 14, p += userLen);
|
|
757
|
+
data.utf8Write('ssh-connection', p += 4, 14);
|
|
758
|
+
|
|
759
|
+
writeUInt32BE(data, 9, p += 14);
|
|
760
|
+
data.utf8Write('hostbased', p += 4, 9);
|
|
761
|
+
|
|
762
|
+
writeUInt32BE(data, algoLen, p += 9);
|
|
763
|
+
data.utf8Write(keyType, p += 4, algoLen);
|
|
764
|
+
|
|
765
|
+
writeUInt32BE(data, pubKeyLen, p += algoLen);
|
|
766
|
+
data.set(pubKey, p += 4);
|
|
767
|
+
|
|
768
|
+
writeUInt32BE(data, hostnameLen, p += pubKeyLen);
|
|
769
|
+
data.utf8Write(hostname, p += 4, hostnameLen);
|
|
770
|
+
|
|
771
|
+
writeUInt32BE(data, userlocalLen, p += hostnameLen);
|
|
772
|
+
data.utf8Write(userlocal, p += 4, userlocalLen);
|
|
773
|
+
|
|
774
|
+
cbSign(data, (signature) => {
|
|
775
|
+
signature = convertSignature(signature, keyType);
|
|
776
|
+
if (!signature)
|
|
777
|
+
throw new Error('Error while converting handshake signature');
|
|
778
|
+
|
|
779
|
+
const sigLen = signature.length;
|
|
780
|
+
const reqDataLen = (data.length - sesLen - 4);
|
|
781
|
+
p = this._packetRW.write.allocStart;
|
|
782
|
+
const packet = this._packetRW.write.alloc(
|
|
783
|
+
reqDataLen + 4 + 4 + algoLen + 4 + sigLen
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
bufferCopy(data, packet, 4 + sesLen, data.length, p);
|
|
787
|
+
|
|
788
|
+
writeUInt32BE(packet, 4 + algoLen + 4 + sigLen, p += reqDataLen);
|
|
789
|
+
writeUInt32BE(packet, algoLen, p += 4);
|
|
790
|
+
packet.utf8Write(keyType, p += 4, algoLen);
|
|
791
|
+
writeUInt32BE(packet, sigLen, p += algoLen);
|
|
792
|
+
packet.set(signature, p += 4);
|
|
793
|
+
|
|
794
|
+
this._authsQueue.push('hostbased');
|
|
795
|
+
|
|
796
|
+
this._debug && this._debug(
|
|
797
|
+
'Outbound: Sending USERAUTH_REQUEST (hostbased)'
|
|
798
|
+
);
|
|
799
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
authKeyboard(username) {
|
|
803
|
+
if (this._server)
|
|
804
|
+
throw new Error('Client-only method called in server mode');
|
|
805
|
+
|
|
806
|
+
const userLen = Buffer.byteLength(username);
|
|
807
|
+
let p = this._packetRW.write.allocStart;
|
|
808
|
+
const packet = this._packetRW.write.alloc(
|
|
809
|
+
1 + 4 + userLen + 4 + 14 + 4 + 20 + 4 + 4
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
packet[p] = MESSAGE.USERAUTH_REQUEST;
|
|
813
|
+
|
|
814
|
+
writeUInt32BE(packet, userLen, ++p);
|
|
815
|
+
packet.utf8Write(username, p += 4, userLen);
|
|
816
|
+
|
|
817
|
+
writeUInt32BE(packet, 14, p += userLen);
|
|
818
|
+
packet.utf8Write('ssh-connection', p += 4, 14);
|
|
819
|
+
|
|
820
|
+
writeUInt32BE(packet, 20, p += 14);
|
|
821
|
+
packet.utf8Write('keyboard-interactive', p += 4, 20);
|
|
822
|
+
|
|
823
|
+
writeUInt32BE(packet, 0, p += 20);
|
|
824
|
+
|
|
825
|
+
writeUInt32BE(packet, 0, p += 4);
|
|
826
|
+
|
|
827
|
+
this._authsQueue.push('keyboard-interactive');
|
|
828
|
+
|
|
829
|
+
this._debug && this._debug(
|
|
830
|
+
'Outbound: Sending USERAUTH_REQUEST (keyboard-interactive)'
|
|
831
|
+
);
|
|
832
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
833
|
+
}
|
|
834
|
+
authNone(username) {
|
|
835
|
+
if (this._server)
|
|
836
|
+
throw new Error('Client-only method called in server mode');
|
|
837
|
+
|
|
838
|
+
const userLen = Buffer.byteLength(username);
|
|
839
|
+
let p = this._packetRW.write.allocStart;
|
|
840
|
+
const packet = this._packetRW.write.alloc(1 + 4 + userLen + 4 + 14 + 4 + 4);
|
|
841
|
+
|
|
842
|
+
packet[p] = MESSAGE.USERAUTH_REQUEST;
|
|
843
|
+
|
|
844
|
+
writeUInt32BE(packet, userLen, ++p);
|
|
845
|
+
packet.utf8Write(username, p += 4, userLen);
|
|
846
|
+
|
|
847
|
+
writeUInt32BE(packet, 14, p += userLen);
|
|
848
|
+
packet.utf8Write('ssh-connection', p += 4, 14);
|
|
849
|
+
|
|
850
|
+
writeUInt32BE(packet, 4, p += 14);
|
|
851
|
+
packet.utf8Write('none', p += 4, 4);
|
|
852
|
+
|
|
853
|
+
this._authsQueue.push('none');
|
|
854
|
+
|
|
855
|
+
this._debug && this._debug('Outbound: Sending USERAUTH_REQUEST (none)');
|
|
856
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
857
|
+
}
|
|
858
|
+
authInfoRes(responses) {
|
|
859
|
+
if (this._server)
|
|
860
|
+
throw new Error('Client-only method called in server mode');
|
|
861
|
+
|
|
862
|
+
let responsesTotalLen = 0;
|
|
863
|
+
let responseLens;
|
|
864
|
+
|
|
865
|
+
if (responses) {
|
|
866
|
+
responseLens = new Array(responses.length);
|
|
867
|
+
for (let i = 0; i < responses.length; ++i) {
|
|
868
|
+
const len = Buffer.byteLength(responses[i]);
|
|
869
|
+
responseLens[i] = len;
|
|
870
|
+
responsesTotalLen += 4 + len;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
let p = this._packetRW.write.allocStart;
|
|
875
|
+
const packet = this._packetRW.write.alloc(1 + 4 + responsesTotalLen);
|
|
876
|
+
|
|
877
|
+
packet[p] = MESSAGE.USERAUTH_INFO_RESPONSE;
|
|
878
|
+
|
|
879
|
+
if (responses) {
|
|
880
|
+
writeUInt32BE(packet, responses.length, ++p);
|
|
881
|
+
p += 4;
|
|
882
|
+
for (let i = 0; i < responses.length; ++i) {
|
|
883
|
+
const len = responseLens[i];
|
|
884
|
+
writeUInt32BE(packet, len, p);
|
|
885
|
+
p += 4;
|
|
886
|
+
if (len) {
|
|
887
|
+
packet.utf8Write(responses[i], p, len);
|
|
888
|
+
p += len;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
} else {
|
|
892
|
+
writeUInt32BE(packet, 0, ++p);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
this._debug && this._debug('Outbound: Sending USERAUTH_INFO_RESPONSE');
|
|
896
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// 'ssh-connection' service-specific
|
|
900
|
+
// ---------------------------------
|
|
901
|
+
tcpipForward(bindAddr, bindPort, wantReply) {
|
|
902
|
+
if (this._server)
|
|
903
|
+
throw new Error('Client-only method called in server mode');
|
|
904
|
+
|
|
905
|
+
const addrLen = Buffer.byteLength(bindAddr);
|
|
906
|
+
let p = this._packetRW.write.allocStart;
|
|
907
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 13 + 1 + 4 + addrLen + 4);
|
|
908
|
+
|
|
909
|
+
packet[p] = MESSAGE.GLOBAL_REQUEST;
|
|
910
|
+
|
|
911
|
+
writeUInt32BE(packet, 13, ++p);
|
|
912
|
+
packet.utf8Write('tcpip-forward', p += 4, 13);
|
|
913
|
+
|
|
914
|
+
packet[p += 13] = (wantReply === undefined || wantReply === true ? 1 : 0);
|
|
915
|
+
|
|
916
|
+
writeUInt32BE(packet, addrLen, ++p);
|
|
917
|
+
packet.utf8Write(bindAddr, p += 4, addrLen);
|
|
918
|
+
|
|
919
|
+
writeUInt32BE(packet, bindPort, p += addrLen);
|
|
920
|
+
|
|
921
|
+
this._debug
|
|
922
|
+
&& this._debug('Outbound: Sending GLOBAL_REQUEST (tcpip-forward)');
|
|
923
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
924
|
+
}
|
|
925
|
+
cancelTcpipForward(bindAddr, bindPort, wantReply) {
|
|
926
|
+
if (this._server)
|
|
927
|
+
throw new Error('Client-only method called in server mode');
|
|
928
|
+
|
|
929
|
+
const addrLen = Buffer.byteLength(bindAddr);
|
|
930
|
+
let p = this._packetRW.write.allocStart;
|
|
931
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 20 + 1 + 4 + addrLen + 4);
|
|
932
|
+
|
|
933
|
+
packet[p] = MESSAGE.GLOBAL_REQUEST;
|
|
934
|
+
|
|
935
|
+
writeUInt32BE(packet, 20, ++p);
|
|
936
|
+
packet.utf8Write('cancel-tcpip-forward', p += 4, 20);
|
|
937
|
+
|
|
938
|
+
packet[p += 20] = (wantReply === undefined || wantReply === true ? 1 : 0);
|
|
939
|
+
|
|
940
|
+
writeUInt32BE(packet, addrLen, ++p);
|
|
941
|
+
packet.utf8Write(bindAddr, p += 4, addrLen);
|
|
942
|
+
|
|
943
|
+
writeUInt32BE(packet, bindPort, p += addrLen);
|
|
944
|
+
|
|
945
|
+
this._debug
|
|
946
|
+
&& this._debug('Outbound: Sending GLOBAL_REQUEST (cancel-tcpip-forward)');
|
|
947
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
948
|
+
}
|
|
949
|
+
openssh_streamLocalForward(socketPath, wantReply) {
|
|
950
|
+
if (this._server)
|
|
951
|
+
throw new Error('Client-only method called in server mode');
|
|
952
|
+
|
|
953
|
+
const socketPathLen = Buffer.byteLength(socketPath);
|
|
954
|
+
let p = this._packetRW.write.allocStart;
|
|
955
|
+
const packet = this._packetRW.write.alloc(
|
|
956
|
+
1 + 4 + 31 + 1 + 4 + socketPathLen
|
|
957
|
+
);
|
|
958
|
+
|
|
959
|
+
packet[p] = MESSAGE.GLOBAL_REQUEST;
|
|
960
|
+
|
|
961
|
+
writeUInt32BE(packet, 31, ++p);
|
|
962
|
+
packet.utf8Write('streamlocal-forward@openssh.com', p += 4, 31);
|
|
963
|
+
|
|
964
|
+
packet[p += 31] = (wantReply === undefined || wantReply === true ? 1 : 0);
|
|
965
|
+
|
|
966
|
+
writeUInt32BE(packet, socketPathLen, ++p);
|
|
967
|
+
packet.utf8Write(socketPath, p += 4, socketPathLen);
|
|
968
|
+
|
|
969
|
+
this._debug && this._debug(
|
|
970
|
+
'Outbound: Sending GLOBAL_REQUEST (streamlocal-forward@openssh.com)'
|
|
971
|
+
);
|
|
972
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
973
|
+
}
|
|
974
|
+
openssh_cancelStreamLocalForward(socketPath, wantReply) {
|
|
975
|
+
if (this._server)
|
|
976
|
+
throw new Error('Client-only method called in server mode');
|
|
977
|
+
|
|
978
|
+
const socketPathLen = Buffer.byteLength(socketPath);
|
|
979
|
+
let p = this._packetRW.write.allocStart;
|
|
980
|
+
const packet = this._packetRW.write.alloc(
|
|
981
|
+
1 + 4 + 38 + 1 + 4 + socketPathLen
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
packet[p] = MESSAGE.GLOBAL_REQUEST;
|
|
985
|
+
|
|
986
|
+
writeUInt32BE(packet, 38, ++p);
|
|
987
|
+
packet.utf8Write('cancel-streamlocal-forward@openssh.com', p += 4, 38);
|
|
988
|
+
|
|
989
|
+
packet[p += 38] = (wantReply === undefined || wantReply === true ? 1 : 0);
|
|
990
|
+
|
|
991
|
+
writeUInt32BE(packet, socketPathLen, ++p);
|
|
992
|
+
packet.utf8Write(socketPath, p += 4, socketPathLen);
|
|
993
|
+
|
|
994
|
+
if (this._debug) {
|
|
995
|
+
this._debug(
|
|
996
|
+
'Outbound: Sending GLOBAL_REQUEST '
|
|
997
|
+
+ '(cancel-streamlocal-forward@openssh.com)'
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1001
|
+
}
|
|
1002
|
+
directTcpip(chan, initWindow, maxPacket, cfg) {
|
|
1003
|
+
if (this._server)
|
|
1004
|
+
throw new Error('Client-only method called in server mode');
|
|
1005
|
+
|
|
1006
|
+
const srcLen = Buffer.byteLength(cfg.srcIP);
|
|
1007
|
+
const dstLen = Buffer.byteLength(cfg.dstIP);
|
|
1008
|
+
let p = this._packetRW.write.allocStart;
|
|
1009
|
+
const packet = this._packetRW.write.alloc(
|
|
1010
|
+
1 + 4 + 12 + 4 + 4 + 4 + 4 + srcLen + 4 + 4 + dstLen + 4
|
|
1011
|
+
);
|
|
1012
|
+
|
|
1013
|
+
packet[p] = MESSAGE.CHANNEL_OPEN;
|
|
1014
|
+
|
|
1015
|
+
writeUInt32BE(packet, 12, ++p);
|
|
1016
|
+
packet.utf8Write('direct-tcpip', p += 4, 12);
|
|
1017
|
+
|
|
1018
|
+
writeUInt32BE(packet, chan, p += 12);
|
|
1019
|
+
|
|
1020
|
+
writeUInt32BE(packet, initWindow, p += 4);
|
|
1021
|
+
|
|
1022
|
+
writeUInt32BE(packet, maxPacket, p += 4);
|
|
1023
|
+
|
|
1024
|
+
writeUInt32BE(packet, dstLen, p += 4);
|
|
1025
|
+
packet.utf8Write(cfg.dstIP, p += 4, dstLen);
|
|
1026
|
+
|
|
1027
|
+
writeUInt32BE(packet, cfg.dstPort, p += dstLen);
|
|
1028
|
+
|
|
1029
|
+
writeUInt32BE(packet, srcLen, p += 4);
|
|
1030
|
+
packet.utf8Write(cfg.srcIP, p += 4, srcLen);
|
|
1031
|
+
|
|
1032
|
+
writeUInt32BE(packet, cfg.srcPort, p += srcLen);
|
|
1033
|
+
|
|
1034
|
+
this._debug && this._debug(
|
|
1035
|
+
`Outbound: Sending CHANNEL_OPEN (r:${chan}, direct-tcpip)`
|
|
1036
|
+
);
|
|
1037
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1038
|
+
}
|
|
1039
|
+
openssh_directStreamLocal(chan, initWindow, maxPacket, cfg) {
|
|
1040
|
+
if (this._server)
|
|
1041
|
+
throw new Error('Client-only method called in server mode');
|
|
1042
|
+
|
|
1043
|
+
const pathLen = Buffer.byteLength(cfg.socketPath);
|
|
1044
|
+
let p = this._packetRW.write.allocStart;
|
|
1045
|
+
const packet = this._packetRW.write.alloc(
|
|
1046
|
+
1 + 4 + 30 + 4 + 4 + 4 + 4 + pathLen + 4 + 4
|
|
1047
|
+
);
|
|
1048
|
+
|
|
1049
|
+
packet[p] = MESSAGE.CHANNEL_OPEN;
|
|
1050
|
+
|
|
1051
|
+
writeUInt32BE(packet, 30, ++p);
|
|
1052
|
+
packet.utf8Write('direct-streamlocal@openssh.com', p += 4, 30);
|
|
1053
|
+
|
|
1054
|
+
writeUInt32BE(packet, chan, p += 30);
|
|
1055
|
+
|
|
1056
|
+
writeUInt32BE(packet, initWindow, p += 4);
|
|
1057
|
+
|
|
1058
|
+
writeUInt32BE(packet, maxPacket, p += 4);
|
|
1059
|
+
|
|
1060
|
+
writeUInt32BE(packet, pathLen, p += 4);
|
|
1061
|
+
packet.utf8Write(cfg.socketPath, p += 4, pathLen);
|
|
1062
|
+
|
|
1063
|
+
// zero-fill reserved fields (string and uint32)
|
|
1064
|
+
bufferFill(packet, 0, p += pathLen, p + 8);
|
|
1065
|
+
|
|
1066
|
+
if (this._debug) {
|
|
1067
|
+
this._debug(
|
|
1068
|
+
'Outbound: Sending CHANNEL_OPEN '
|
|
1069
|
+
+ `(r:${chan}, direct-streamlocal@openssh.com)`
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1073
|
+
}
|
|
1074
|
+
openssh_noMoreSessions(wantReply) {
|
|
1075
|
+
if (this._server)
|
|
1076
|
+
throw new Error('Client-only method called in server mode');
|
|
1077
|
+
|
|
1078
|
+
let p = this._packetRW.write.allocStart;
|
|
1079
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 28 + 1);
|
|
1080
|
+
|
|
1081
|
+
packet[p] = MESSAGE.GLOBAL_REQUEST;
|
|
1082
|
+
|
|
1083
|
+
writeUInt32BE(packet, 28, ++p);
|
|
1084
|
+
packet.utf8Write('no-more-sessions@openssh.com', p += 4, 28);
|
|
1085
|
+
|
|
1086
|
+
packet[p += 28] = (wantReply === undefined || wantReply === true ? 1 : 0);
|
|
1087
|
+
|
|
1088
|
+
this._debug && this._debug(
|
|
1089
|
+
'Outbound: Sending GLOBAL_REQUEST (no-more-sessions@openssh.com)'
|
|
1090
|
+
);
|
|
1091
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1092
|
+
}
|
|
1093
|
+
session(chan, initWindow, maxPacket) {
|
|
1094
|
+
if (this._server)
|
|
1095
|
+
throw new Error('Client-only method called in server mode');
|
|
1096
|
+
|
|
1097
|
+
// Does not consume window space
|
|
1098
|
+
|
|
1099
|
+
let p = this._packetRW.write.allocStart;
|
|
1100
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 7 + 4 + 4 + 4);
|
|
1101
|
+
|
|
1102
|
+
packet[p] = MESSAGE.CHANNEL_OPEN;
|
|
1103
|
+
|
|
1104
|
+
writeUInt32BE(packet, 7, ++p);
|
|
1105
|
+
packet.utf8Write('session', p += 4, 7);
|
|
1106
|
+
|
|
1107
|
+
writeUInt32BE(packet, chan, p += 7);
|
|
1108
|
+
|
|
1109
|
+
writeUInt32BE(packet, initWindow, p += 4);
|
|
1110
|
+
|
|
1111
|
+
writeUInt32BE(packet, maxPacket, p += 4);
|
|
1112
|
+
|
|
1113
|
+
this._debug
|
|
1114
|
+
&& this._debug(`Outbound: Sending CHANNEL_OPEN (r:${chan}, session)`);
|
|
1115
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1116
|
+
}
|
|
1117
|
+
windowChange(chan, rows, cols, height, width) {
|
|
1118
|
+
if (this._server)
|
|
1119
|
+
throw new Error('Client-only method called in server mode');
|
|
1120
|
+
|
|
1121
|
+
// Does not consume window space
|
|
1122
|
+
|
|
1123
|
+
let p = this._packetRW.write.allocStart;
|
|
1124
|
+
const packet = this._packetRW.write.alloc(
|
|
1125
|
+
1 + 4 + 4 + 13 + 1 + 4 + 4 + 4 + 4
|
|
1126
|
+
);
|
|
1127
|
+
|
|
1128
|
+
packet[p] = MESSAGE.CHANNEL_REQUEST;
|
|
1129
|
+
|
|
1130
|
+
writeUInt32BE(packet, chan, ++p);
|
|
1131
|
+
|
|
1132
|
+
writeUInt32BE(packet, 13, p += 4);
|
|
1133
|
+
packet.utf8Write('window-change', p += 4, 13);
|
|
1134
|
+
|
|
1135
|
+
packet[p += 13] = 0;
|
|
1136
|
+
|
|
1137
|
+
writeUInt32BE(packet, cols, ++p);
|
|
1138
|
+
|
|
1139
|
+
writeUInt32BE(packet, rows, p += 4);
|
|
1140
|
+
|
|
1141
|
+
writeUInt32BE(packet, width, p += 4);
|
|
1142
|
+
|
|
1143
|
+
writeUInt32BE(packet, height, p += 4);
|
|
1144
|
+
|
|
1145
|
+
this._debug && this._debug(
|
|
1146
|
+
`Outbound: Sending CHANNEL_REQUEST (r:${chan}, window-change)`
|
|
1147
|
+
);
|
|
1148
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1149
|
+
}
|
|
1150
|
+
pty(chan, rows, cols, height, width, term, modes, wantReply) {
|
|
1151
|
+
if (this._server)
|
|
1152
|
+
throw new Error('Client-only method called in server mode');
|
|
1153
|
+
|
|
1154
|
+
// Does not consume window space
|
|
1155
|
+
|
|
1156
|
+
if (!term || !term.length)
|
|
1157
|
+
term = 'vt100';
|
|
1158
|
+
if (modes
|
|
1159
|
+
&& !Buffer.isBuffer(modes)
|
|
1160
|
+
&& !Array.isArray(modes)
|
|
1161
|
+
&& typeof modes === 'object'
|
|
1162
|
+
&& modes !== null) {
|
|
1163
|
+
modes = modesToBytes(modes);
|
|
1164
|
+
}
|
|
1165
|
+
if (!modes || !modes.length)
|
|
1166
|
+
modes = NO_TERMINAL_MODES_BUFFER;
|
|
1167
|
+
|
|
1168
|
+
const termLen = term.length;
|
|
1169
|
+
const modesLen = modes.length;
|
|
1170
|
+
let p = this._packetRW.write.allocStart;
|
|
1171
|
+
const packet = this._packetRW.write.alloc(
|
|
1172
|
+
1 + 4 + 4 + 7 + 1 + 4 + termLen + 4 + 4 + 4 + 4 + 4 + modesLen
|
|
1173
|
+
);
|
|
1174
|
+
|
|
1175
|
+
packet[p] = MESSAGE.CHANNEL_REQUEST;
|
|
1176
|
+
|
|
1177
|
+
writeUInt32BE(packet, chan, ++p);
|
|
1178
|
+
|
|
1179
|
+
writeUInt32BE(packet, 7, p += 4);
|
|
1180
|
+
packet.utf8Write('pty-req', p += 4, 7);
|
|
1181
|
+
|
|
1182
|
+
packet[p += 7] = (wantReply === undefined || wantReply === true ? 1 : 0);
|
|
1183
|
+
|
|
1184
|
+
writeUInt32BE(packet, termLen, ++p);
|
|
1185
|
+
packet.utf8Write(term, p += 4, termLen);
|
|
1186
|
+
|
|
1187
|
+
writeUInt32BE(packet, cols, p += termLen);
|
|
1188
|
+
|
|
1189
|
+
writeUInt32BE(packet, rows, p += 4);
|
|
1190
|
+
|
|
1191
|
+
writeUInt32BE(packet, width, p += 4);
|
|
1192
|
+
|
|
1193
|
+
writeUInt32BE(packet, height, p += 4);
|
|
1194
|
+
|
|
1195
|
+
writeUInt32BE(packet, modesLen, p += 4);
|
|
1196
|
+
p += 4;
|
|
1197
|
+
if (Array.isArray(modes)) {
|
|
1198
|
+
for (let i = 0; i < modesLen; ++i)
|
|
1199
|
+
packet[p++] = modes[i];
|
|
1200
|
+
} else if (Buffer.isBuffer(modes)) {
|
|
1201
|
+
packet.set(modes, p);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
this._debug
|
|
1205
|
+
&& this._debug(`Outbound: Sending CHANNEL_REQUEST (r:${chan}, pty-req)`);
|
|
1206
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1207
|
+
}
|
|
1208
|
+
shell(chan, wantReply) {
|
|
1209
|
+
if (this._server)
|
|
1210
|
+
throw new Error('Client-only method called in server mode');
|
|
1211
|
+
|
|
1212
|
+
// Does not consume window space
|
|
1213
|
+
|
|
1214
|
+
let p = this._packetRW.write.allocStart;
|
|
1215
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 4 + 5 + 1);
|
|
1216
|
+
|
|
1217
|
+
packet[p] = MESSAGE.CHANNEL_REQUEST;
|
|
1218
|
+
|
|
1219
|
+
writeUInt32BE(packet, chan, ++p);
|
|
1220
|
+
|
|
1221
|
+
writeUInt32BE(packet, 5, p += 4);
|
|
1222
|
+
packet.utf8Write('shell', p += 4, 5);
|
|
1223
|
+
|
|
1224
|
+
packet[p += 5] = (wantReply === undefined || wantReply === true ? 1 : 0);
|
|
1225
|
+
|
|
1226
|
+
this._debug
|
|
1227
|
+
&& this._debug(`Outbound: Sending CHANNEL_REQUEST (r:${chan}, shell)`);
|
|
1228
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1229
|
+
}
|
|
1230
|
+
exec(chan, cmd, wantReply) {
|
|
1231
|
+
if (this._server)
|
|
1232
|
+
throw new Error('Client-only method called in server mode');
|
|
1233
|
+
|
|
1234
|
+
// Does not consume window space
|
|
1235
|
+
|
|
1236
|
+
const isBuf = Buffer.isBuffer(cmd);
|
|
1237
|
+
const cmdLen = (isBuf ? cmd.length : Buffer.byteLength(cmd));
|
|
1238
|
+
let p = this._packetRW.write.allocStart;
|
|
1239
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 4 + 4 + 1 + 4 + cmdLen);
|
|
1240
|
+
|
|
1241
|
+
packet[p] = MESSAGE.CHANNEL_REQUEST;
|
|
1242
|
+
|
|
1243
|
+
writeUInt32BE(packet, chan, ++p);
|
|
1244
|
+
|
|
1245
|
+
writeUInt32BE(packet, 4, p += 4);
|
|
1246
|
+
packet.utf8Write('exec', p += 4, 4);
|
|
1247
|
+
|
|
1248
|
+
packet[p += 4] = (wantReply === undefined || wantReply === true ? 1 : 0);
|
|
1249
|
+
|
|
1250
|
+
writeUInt32BE(packet, cmdLen, ++p);
|
|
1251
|
+
if (isBuf)
|
|
1252
|
+
packet.set(cmd, p += 4);
|
|
1253
|
+
else
|
|
1254
|
+
packet.utf8Write(cmd, p += 4, cmdLen);
|
|
1255
|
+
|
|
1256
|
+
this._debug && this._debug(
|
|
1257
|
+
`Outbound: Sending CHANNEL_REQUEST (r:${chan}, exec: ${cmd})`
|
|
1258
|
+
);
|
|
1259
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1260
|
+
}
|
|
1261
|
+
signal(chan, signal) {
|
|
1262
|
+
if (this._server)
|
|
1263
|
+
throw new Error('Client-only method called in server mode');
|
|
1264
|
+
|
|
1265
|
+
// Does not consume window space
|
|
1266
|
+
|
|
1267
|
+
const origSignal = signal;
|
|
1268
|
+
|
|
1269
|
+
signal = signal.toUpperCase();
|
|
1270
|
+
if (signal.slice(0, 3) === 'SIG')
|
|
1271
|
+
signal = signal.slice(3);
|
|
1272
|
+
|
|
1273
|
+
if (SIGNALS[signal] !== 1)
|
|
1274
|
+
throw new Error(`Invalid signal: ${origSignal}`);
|
|
1275
|
+
|
|
1276
|
+
const signalLen = signal.length;
|
|
1277
|
+
let p = this._packetRW.write.allocStart;
|
|
1278
|
+
const packet = this._packetRW.write.alloc(
|
|
1279
|
+
1 + 4 + 4 + 6 + 1 + 4 + signalLen
|
|
1280
|
+
);
|
|
1281
|
+
|
|
1282
|
+
packet[p] = MESSAGE.CHANNEL_REQUEST;
|
|
1283
|
+
|
|
1284
|
+
writeUInt32BE(packet, chan, ++p);
|
|
1285
|
+
|
|
1286
|
+
writeUInt32BE(packet, 6, p += 4);
|
|
1287
|
+
packet.utf8Write('signal', p += 4, 6);
|
|
1288
|
+
|
|
1289
|
+
packet[p += 6] = 0;
|
|
1290
|
+
|
|
1291
|
+
writeUInt32BE(packet, signalLen, ++p);
|
|
1292
|
+
packet.utf8Write(signal, p += 4, signalLen);
|
|
1293
|
+
|
|
1294
|
+
this._debug && this._debug(
|
|
1295
|
+
`Outbound: Sending CHANNEL_REQUEST (r:${chan}, signal: ${signal})`
|
|
1296
|
+
);
|
|
1297
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1298
|
+
}
|
|
1299
|
+
env(chan, key, val, wantReply) {
|
|
1300
|
+
if (this._server)
|
|
1301
|
+
throw new Error('Client-only method called in server mode');
|
|
1302
|
+
|
|
1303
|
+
// Does not consume window space
|
|
1304
|
+
|
|
1305
|
+
const keyLen = Buffer.byteLength(key);
|
|
1306
|
+
const isBuf = Buffer.isBuffer(val);
|
|
1307
|
+
const valLen = (isBuf ? val.length : Buffer.byteLength(val));
|
|
1308
|
+
let p = this._packetRW.write.allocStart;
|
|
1309
|
+
const packet = this._packetRW.write.alloc(
|
|
1310
|
+
1 + 4 + 4 + 3 + 1 + 4 + keyLen + 4 + valLen
|
|
1311
|
+
);
|
|
1312
|
+
|
|
1313
|
+
packet[p] = MESSAGE.CHANNEL_REQUEST;
|
|
1314
|
+
|
|
1315
|
+
writeUInt32BE(packet, chan, ++p);
|
|
1316
|
+
|
|
1317
|
+
writeUInt32BE(packet, 3, p += 4);
|
|
1318
|
+
packet.utf8Write('env', p += 4, 3);
|
|
1319
|
+
|
|
1320
|
+
packet[p += 3] = (wantReply === undefined || wantReply === true ? 1 : 0);
|
|
1321
|
+
|
|
1322
|
+
writeUInt32BE(packet, keyLen, ++p);
|
|
1323
|
+
packet.utf8Write(key, p += 4, keyLen);
|
|
1324
|
+
|
|
1325
|
+
writeUInt32BE(packet, valLen, p += keyLen);
|
|
1326
|
+
if (isBuf)
|
|
1327
|
+
packet.set(val, p += 4);
|
|
1328
|
+
else
|
|
1329
|
+
packet.utf8Write(val, p += 4, valLen);
|
|
1330
|
+
|
|
1331
|
+
this._debug && this._debug(
|
|
1332
|
+
`Outbound: Sending CHANNEL_REQUEST (r:${chan}, env: ${key}=${val})`
|
|
1333
|
+
);
|
|
1334
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1335
|
+
}
|
|
1336
|
+
x11Forward(chan, cfg, wantReply) {
|
|
1337
|
+
if (this._server)
|
|
1338
|
+
throw new Error('Client-only method called in server mode');
|
|
1339
|
+
|
|
1340
|
+
// Does not consume window space
|
|
1341
|
+
|
|
1342
|
+
const protocol = cfg.protocol;
|
|
1343
|
+
const cookie = cfg.cookie;
|
|
1344
|
+
const isBufProto = Buffer.isBuffer(protocol);
|
|
1345
|
+
const protoLen = (isBufProto
|
|
1346
|
+
? protocol.length
|
|
1347
|
+
: Buffer.byteLength(protocol));
|
|
1348
|
+
const isBufCookie = Buffer.isBuffer(cookie);
|
|
1349
|
+
const cookieLen = (isBufCookie
|
|
1350
|
+
? cookie.length
|
|
1351
|
+
: Buffer.byteLength(cookie));
|
|
1352
|
+
let p = this._packetRW.write.allocStart;
|
|
1353
|
+
const packet = this._packetRW.write.alloc(
|
|
1354
|
+
1 + 4 + 4 + 7 + 1 + 1 + 4 + protoLen + 4 + cookieLen + 4
|
|
1355
|
+
);
|
|
1356
|
+
|
|
1357
|
+
packet[p] = MESSAGE.CHANNEL_REQUEST;
|
|
1358
|
+
|
|
1359
|
+
writeUInt32BE(packet, chan, ++p);
|
|
1360
|
+
|
|
1361
|
+
writeUInt32BE(packet, 7, p += 4);
|
|
1362
|
+
packet.utf8Write('x11-req', p += 4, 7);
|
|
1363
|
+
|
|
1364
|
+
packet[p += 7] = (wantReply === undefined || wantReply === true ? 1 : 0);
|
|
1365
|
+
|
|
1366
|
+
packet[++p] = (cfg.single ? 1 : 0);
|
|
1367
|
+
|
|
1368
|
+
writeUInt32BE(packet, protoLen, ++p);
|
|
1369
|
+
if (isBufProto)
|
|
1370
|
+
packet.set(protocol, p += 4);
|
|
1371
|
+
else
|
|
1372
|
+
packet.utf8Write(protocol, p += 4, protoLen);
|
|
1373
|
+
|
|
1374
|
+
writeUInt32BE(packet, cookieLen, p += protoLen);
|
|
1375
|
+
if (isBufCookie)
|
|
1376
|
+
packet.set(cookie, p += 4);
|
|
1377
|
+
else
|
|
1378
|
+
packet.latin1Write(cookie, p += 4, cookieLen);
|
|
1379
|
+
|
|
1380
|
+
writeUInt32BE(packet, (cfg.screen || 0), p += cookieLen);
|
|
1381
|
+
|
|
1382
|
+
this._debug
|
|
1383
|
+
&& this._debug(`Outbound: Sending CHANNEL_REQUEST (r:${chan}, x11-req)`);
|
|
1384
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1385
|
+
}
|
|
1386
|
+
subsystem(chan, name, wantReply) {
|
|
1387
|
+
if (this._server)
|
|
1388
|
+
throw new Error('Client-only method called in server mode');
|
|
1389
|
+
|
|
1390
|
+
// Does not consume window space
|
|
1391
|
+
const nameLen = Buffer.byteLength(name);
|
|
1392
|
+
let p = this._packetRW.write.allocStart;
|
|
1393
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 4 + 9 + 1 + 4 + nameLen);
|
|
1394
|
+
|
|
1395
|
+
packet[p] = MESSAGE.CHANNEL_REQUEST;
|
|
1396
|
+
|
|
1397
|
+
writeUInt32BE(packet, chan, ++p);
|
|
1398
|
+
|
|
1399
|
+
writeUInt32BE(packet, 9, p += 4);
|
|
1400
|
+
packet.utf8Write('subsystem', p += 4, 9);
|
|
1401
|
+
|
|
1402
|
+
packet[p += 9] = (wantReply === undefined || wantReply === true ? 1 : 0);
|
|
1403
|
+
|
|
1404
|
+
writeUInt32BE(packet, nameLen, ++p);
|
|
1405
|
+
packet.utf8Write(name, p += 4, nameLen);
|
|
1406
|
+
|
|
1407
|
+
this._debug && this._debug(
|
|
1408
|
+
`Outbound: Sending CHANNEL_REQUEST (r:${chan}, subsystem: ${name})`
|
|
1409
|
+
);
|
|
1410
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1411
|
+
}
|
|
1412
|
+
openssh_agentForward(chan, wantReply) {
|
|
1413
|
+
if (this._server)
|
|
1414
|
+
throw new Error('Client-only method called in server mode');
|
|
1415
|
+
|
|
1416
|
+
// Does not consume window space
|
|
1417
|
+
|
|
1418
|
+
let p = this._packetRW.write.allocStart;
|
|
1419
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 4 + 26 + 1);
|
|
1420
|
+
|
|
1421
|
+
packet[p] = MESSAGE.CHANNEL_REQUEST;
|
|
1422
|
+
|
|
1423
|
+
writeUInt32BE(packet, chan, ++p);
|
|
1424
|
+
|
|
1425
|
+
writeUInt32BE(packet, 26, p += 4);
|
|
1426
|
+
packet.utf8Write('auth-agent-req@openssh.com', p += 4, 26);
|
|
1427
|
+
|
|
1428
|
+
packet[p += 26] = (wantReply === undefined || wantReply === true ? 1 : 0);
|
|
1429
|
+
|
|
1430
|
+
if (this._debug) {
|
|
1431
|
+
this._debug(
|
|
1432
|
+
'Outbound: Sending CHANNEL_REQUEST '
|
|
1433
|
+
+ `(r:${chan}, auth-agent-req@openssh.com)`
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1437
|
+
}
|
|
1438
|
+
openssh_hostKeysProve(keys) {
|
|
1439
|
+
if (this._server)
|
|
1440
|
+
throw new Error('Client-only method called in server mode');
|
|
1441
|
+
|
|
1442
|
+
let keysTotal = 0;
|
|
1443
|
+
const publicKeys = [];
|
|
1444
|
+
for (const key of keys) {
|
|
1445
|
+
const publicKey = key.getPublicSSH();
|
|
1446
|
+
keysTotal += 4 + publicKey.length;
|
|
1447
|
+
publicKeys.push(publicKey);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
let p = this._packetRW.write.allocStart;
|
|
1451
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 29 + 1 + keysTotal);
|
|
1452
|
+
|
|
1453
|
+
packet[p] = MESSAGE.GLOBAL_REQUEST;
|
|
1454
|
+
|
|
1455
|
+
writeUInt32BE(packet, 29, ++p);
|
|
1456
|
+
packet.utf8Write('hostkeys-prove-00@openssh.com', p += 4, 29);
|
|
1457
|
+
|
|
1458
|
+
packet[p += 29] = 1; // want reply
|
|
1459
|
+
|
|
1460
|
+
++p;
|
|
1461
|
+
for (const buf of publicKeys) {
|
|
1462
|
+
writeUInt32BE(packet, buf.length, p);
|
|
1463
|
+
bufferCopy(buf, packet, 0, buf.length, p += 4);
|
|
1464
|
+
p += buf.length;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
if (this._debug) {
|
|
1468
|
+
this._debug(
|
|
1469
|
+
'Outbound: Sending GLOBAL_REQUEST (hostkeys-prove-00@openssh.com)'
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// ===========================================================================
|
|
1476
|
+
// Server-specific ===========================================================
|
|
1477
|
+
// ===========================================================================
|
|
1478
|
+
|
|
1479
|
+
// Global
|
|
1480
|
+
// ------
|
|
1481
|
+
serviceAccept(svcName) {
|
|
1482
|
+
if (!this._server)
|
|
1483
|
+
throw new Error('Server-only method called in client mode');
|
|
1484
|
+
|
|
1485
|
+
const svcNameLen = Buffer.byteLength(svcName);
|
|
1486
|
+
let p = this._packetRW.write.allocStart;
|
|
1487
|
+
const packet = this._packetRW.write.alloc(1 + 4 + svcNameLen);
|
|
1488
|
+
|
|
1489
|
+
packet[p] = MESSAGE.SERVICE_ACCEPT;
|
|
1490
|
+
|
|
1491
|
+
writeUInt32BE(packet, svcNameLen, ++p);
|
|
1492
|
+
packet.utf8Write(svcName, p += 4, svcNameLen);
|
|
1493
|
+
|
|
1494
|
+
this._debug && this._debug(`Outbound: Sending SERVICE_ACCEPT (${svcName})`);
|
|
1495
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1496
|
+
|
|
1497
|
+
if (this._server && this._banner && svcName === 'ssh-userauth') {
|
|
1498
|
+
const banner = this._banner;
|
|
1499
|
+
this._banner = undefined; // Prevent banner from being displayed again
|
|
1500
|
+
const bannerLen = Buffer.byteLength(banner);
|
|
1501
|
+
p = this._packetRW.write.allocStart;
|
|
1502
|
+
const packet = this._packetRW.write.alloc(1 + 4 + bannerLen + 4);
|
|
1503
|
+
|
|
1504
|
+
packet[p] = MESSAGE.USERAUTH_BANNER;
|
|
1505
|
+
|
|
1506
|
+
writeUInt32BE(packet, bannerLen, ++p);
|
|
1507
|
+
packet.utf8Write(banner, p += 4, bannerLen);
|
|
1508
|
+
|
|
1509
|
+
writeUInt32BE(packet, 0, p += bannerLen); // Empty language tag
|
|
1510
|
+
|
|
1511
|
+
this._debug && this._debug('Outbound: Sending USERAUTH_BANNER');
|
|
1512
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
// 'ssh-connection' service-specific
|
|
1516
|
+
forwardedTcpip(chan, initWindow, maxPacket, cfg) {
|
|
1517
|
+
if (!this._server)
|
|
1518
|
+
throw new Error('Server-only method called in client mode');
|
|
1519
|
+
|
|
1520
|
+
const boundAddrLen = Buffer.byteLength(cfg.boundAddr);
|
|
1521
|
+
const remoteAddrLen = Buffer.byteLength(cfg.remoteAddr);
|
|
1522
|
+
let p = this._packetRW.write.allocStart;
|
|
1523
|
+
const packet = this._packetRW.write.alloc(
|
|
1524
|
+
1 + 4 + 15 + 4 + 4 + 4 + 4 + boundAddrLen + 4 + 4 + remoteAddrLen + 4
|
|
1525
|
+
);
|
|
1526
|
+
|
|
1527
|
+
packet[p] = MESSAGE.CHANNEL_OPEN;
|
|
1528
|
+
|
|
1529
|
+
writeUInt32BE(packet, 15, ++p);
|
|
1530
|
+
packet.utf8Write('forwarded-tcpip', p += 4, 15);
|
|
1531
|
+
|
|
1532
|
+
writeUInt32BE(packet, chan, p += 15);
|
|
1533
|
+
|
|
1534
|
+
writeUInt32BE(packet, initWindow, p += 4);
|
|
1535
|
+
|
|
1536
|
+
writeUInt32BE(packet, maxPacket, p += 4);
|
|
1537
|
+
|
|
1538
|
+
writeUInt32BE(packet, boundAddrLen, p += 4);
|
|
1539
|
+
packet.utf8Write(cfg.boundAddr, p += 4, boundAddrLen);
|
|
1540
|
+
|
|
1541
|
+
writeUInt32BE(packet, cfg.boundPort, p += boundAddrLen);
|
|
1542
|
+
|
|
1543
|
+
writeUInt32BE(packet, remoteAddrLen, p += 4);
|
|
1544
|
+
packet.utf8Write(cfg.remoteAddr, p += 4, remoteAddrLen);
|
|
1545
|
+
|
|
1546
|
+
writeUInt32BE(packet, cfg.remotePort, p += remoteAddrLen);
|
|
1547
|
+
|
|
1548
|
+
this._debug && this._debug(
|
|
1549
|
+
`Outbound: Sending CHANNEL_OPEN (r:${chan}, forwarded-tcpip)`
|
|
1550
|
+
);
|
|
1551
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1552
|
+
}
|
|
1553
|
+
x11(chan, initWindow, maxPacket, cfg) {
|
|
1554
|
+
if (!this._server)
|
|
1555
|
+
throw new Error('Server-only method called in client mode');
|
|
1556
|
+
|
|
1557
|
+
const addrLen = Buffer.byteLength(cfg.originAddr);
|
|
1558
|
+
let p = this._packetRW.write.allocStart;
|
|
1559
|
+
const packet = this._packetRW.write.alloc(
|
|
1560
|
+
1 + 4 + 3 + 4 + 4 + 4 + 4 + addrLen + 4
|
|
1561
|
+
);
|
|
1562
|
+
|
|
1563
|
+
packet[p] = MESSAGE.CHANNEL_OPEN;
|
|
1564
|
+
|
|
1565
|
+
writeUInt32BE(packet, 3, ++p);
|
|
1566
|
+
packet.utf8Write('x11', p += 4, 3);
|
|
1567
|
+
|
|
1568
|
+
writeUInt32BE(packet, chan, p += 3);
|
|
1569
|
+
|
|
1570
|
+
writeUInt32BE(packet, initWindow, p += 4);
|
|
1571
|
+
|
|
1572
|
+
writeUInt32BE(packet, maxPacket, p += 4);
|
|
1573
|
+
|
|
1574
|
+
writeUInt32BE(packet, addrLen, p += 4);
|
|
1575
|
+
packet.utf8Write(cfg.originAddr, p += 4, addrLen);
|
|
1576
|
+
|
|
1577
|
+
writeUInt32BE(packet, cfg.originPort, p += addrLen);
|
|
1578
|
+
|
|
1579
|
+
this._debug && this._debug(
|
|
1580
|
+
`Outbound: Sending CHANNEL_OPEN (r:${chan}, x11)`
|
|
1581
|
+
);
|
|
1582
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1583
|
+
}
|
|
1584
|
+
openssh_authAgent(chan, initWindow, maxPacket) {
|
|
1585
|
+
if (!this._server)
|
|
1586
|
+
throw new Error('Server-only method called in client mode');
|
|
1587
|
+
|
|
1588
|
+
let p = this._packetRW.write.allocStart;
|
|
1589
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 22 + 4 + 4 + 4);
|
|
1590
|
+
|
|
1591
|
+
packet[p] = MESSAGE.CHANNEL_OPEN;
|
|
1592
|
+
|
|
1593
|
+
writeUInt32BE(packet, 22, ++p);
|
|
1594
|
+
packet.utf8Write('auth-agent@openssh.com', p += 4, 22);
|
|
1595
|
+
|
|
1596
|
+
writeUInt32BE(packet, chan, p += 22);
|
|
1597
|
+
|
|
1598
|
+
writeUInt32BE(packet, initWindow, p += 4);
|
|
1599
|
+
|
|
1600
|
+
writeUInt32BE(packet, maxPacket, p += 4);
|
|
1601
|
+
|
|
1602
|
+
this._debug && this._debug(
|
|
1603
|
+
`Outbound: Sending CHANNEL_OPEN (r:${chan}, auth-agent@openssh.com)`
|
|
1604
|
+
);
|
|
1605
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1606
|
+
}
|
|
1607
|
+
openssh_forwardedStreamLocal(chan, initWindow, maxPacket, cfg) {
|
|
1608
|
+
if (!this._server)
|
|
1609
|
+
throw new Error('Server-only method called in client mode');
|
|
1610
|
+
|
|
1611
|
+
const pathLen = Buffer.byteLength(cfg.socketPath);
|
|
1612
|
+
let p = this._packetRW.write.allocStart;
|
|
1613
|
+
const packet = this._packetRW.write.alloc(
|
|
1614
|
+
1 + 4 + 33 + 4 + 4 + 4 + 4 + pathLen + 4
|
|
1615
|
+
);
|
|
1616
|
+
|
|
1617
|
+
packet[p] = MESSAGE.CHANNEL_OPEN;
|
|
1618
|
+
|
|
1619
|
+
writeUInt32BE(packet, 33, ++p);
|
|
1620
|
+
packet.utf8Write('forwarded-streamlocal@openssh.com', p += 4, 33);
|
|
1621
|
+
|
|
1622
|
+
writeUInt32BE(packet, chan, p += 33);
|
|
1623
|
+
|
|
1624
|
+
writeUInt32BE(packet, initWindow, p += 4);
|
|
1625
|
+
|
|
1626
|
+
writeUInt32BE(packet, maxPacket, p += 4);
|
|
1627
|
+
|
|
1628
|
+
writeUInt32BE(packet, pathLen, p += 4);
|
|
1629
|
+
packet.utf8Write(cfg.socketPath, p += 4, pathLen);
|
|
1630
|
+
|
|
1631
|
+
writeUInt32BE(packet, 0, p += pathLen);
|
|
1632
|
+
|
|
1633
|
+
if (this._debug) {
|
|
1634
|
+
this._debug(
|
|
1635
|
+
'Outbound: Sending CHANNEL_OPEN '
|
|
1636
|
+
+ `(r:${chan}, forwarded-streamlocal@openssh.com)`
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1640
|
+
}
|
|
1641
|
+
exitStatus(chan, status) {
|
|
1642
|
+
if (!this._server)
|
|
1643
|
+
throw new Error('Server-only method called in client mode');
|
|
1644
|
+
|
|
1645
|
+
// Does not consume window space
|
|
1646
|
+
let p = this._packetRW.write.allocStart;
|
|
1647
|
+
const packet = this._packetRW.write.alloc(1 + 4 + 4 + 11 + 1 + 4);
|
|
1648
|
+
|
|
1649
|
+
packet[p] = MESSAGE.CHANNEL_REQUEST;
|
|
1650
|
+
|
|
1651
|
+
writeUInt32BE(packet, chan, ++p);
|
|
1652
|
+
|
|
1653
|
+
writeUInt32BE(packet, 11, p += 4);
|
|
1654
|
+
packet.utf8Write('exit-status', p += 4, 11);
|
|
1655
|
+
|
|
1656
|
+
packet[p += 11] = 0;
|
|
1657
|
+
|
|
1658
|
+
writeUInt32BE(packet, status, ++p);
|
|
1659
|
+
|
|
1660
|
+
this._debug && this._debug(
|
|
1661
|
+
`Outbound: Sending CHANNEL_REQUEST (r:${chan}, exit-status: ${status})`
|
|
1662
|
+
);
|
|
1663
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1664
|
+
}
|
|
1665
|
+
exitSignal(chan, name, coreDumped, msg) {
|
|
1666
|
+
if (!this._server)
|
|
1667
|
+
throw new Error('Server-only method called in client mode');
|
|
1668
|
+
|
|
1669
|
+
// Does not consume window space
|
|
1670
|
+
|
|
1671
|
+
const origSignal = name;
|
|
1672
|
+
|
|
1673
|
+
if (typeof origSignal !== 'string' || !origSignal)
|
|
1674
|
+
throw new Error(`Invalid signal: ${origSignal}`);
|
|
1675
|
+
|
|
1676
|
+
let signal = name.toUpperCase();
|
|
1677
|
+
if (signal.slice(0, 3) === 'SIG')
|
|
1678
|
+
signal = signal.slice(3);
|
|
1679
|
+
|
|
1680
|
+
if (SIGNALS[signal] !== 1)
|
|
1681
|
+
throw new Error(`Invalid signal: ${origSignal}`);
|
|
1682
|
+
|
|
1683
|
+
const nameLen = Buffer.byteLength(signal);
|
|
1684
|
+
const msgLen = (msg ? Buffer.byteLength(msg) : 0);
|
|
1685
|
+
let p = this._packetRW.write.allocStart;
|
|
1686
|
+
const packet = this._packetRW.write.alloc(
|
|
1687
|
+
1 + 4 + 4 + 11 + 1 + 4 + nameLen + 1 + 4 + msgLen + 4
|
|
1688
|
+
);
|
|
1689
|
+
|
|
1690
|
+
packet[p] = MESSAGE.CHANNEL_REQUEST;
|
|
1691
|
+
|
|
1692
|
+
writeUInt32BE(packet, chan, ++p);
|
|
1693
|
+
|
|
1694
|
+
writeUInt32BE(packet, 11, p += 4);
|
|
1695
|
+
packet.utf8Write('exit-signal', p += 4, 11);
|
|
1696
|
+
|
|
1697
|
+
packet[p += 11] = 0;
|
|
1698
|
+
|
|
1699
|
+
writeUInt32BE(packet, nameLen, ++p);
|
|
1700
|
+
packet.utf8Write(signal, p += 4, nameLen);
|
|
1701
|
+
|
|
1702
|
+
packet[p += nameLen] = (coreDumped ? 1 : 0);
|
|
1703
|
+
|
|
1704
|
+
writeUInt32BE(packet, msgLen, ++p);
|
|
1705
|
+
|
|
1706
|
+
p += 4;
|
|
1707
|
+
if (msgLen) {
|
|
1708
|
+
packet.utf8Write(msg, p, msgLen);
|
|
1709
|
+
p += msgLen;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
writeUInt32BE(packet, 0, p);
|
|
1713
|
+
|
|
1714
|
+
this._debug && this._debug(
|
|
1715
|
+
`Outbound: Sending CHANNEL_REQUEST (r:${chan}, exit-signal: ${name})`
|
|
1716
|
+
);
|
|
1717
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1718
|
+
}
|
|
1719
|
+
// 'ssh-userauth' service-specific
|
|
1720
|
+
authFailure(authMethods, isPartial) {
|
|
1721
|
+
if (!this._server)
|
|
1722
|
+
throw new Error('Server-only method called in client mode');
|
|
1723
|
+
|
|
1724
|
+
if (this._authsQueue.length === 0)
|
|
1725
|
+
throw new Error('No auth in progress');
|
|
1726
|
+
|
|
1727
|
+
let methods;
|
|
1728
|
+
|
|
1729
|
+
if (typeof authMethods === 'boolean') {
|
|
1730
|
+
isPartial = authMethods;
|
|
1731
|
+
authMethods = undefined;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
if (authMethods) {
|
|
1735
|
+
methods = [];
|
|
1736
|
+
for (let i = 0; i < authMethods.length; ++i) {
|
|
1737
|
+
if (authMethods[i].toLowerCase() === 'none')
|
|
1738
|
+
continue;
|
|
1739
|
+
methods.push(authMethods[i]);
|
|
1740
|
+
}
|
|
1741
|
+
methods = methods.join(',');
|
|
1742
|
+
} else {
|
|
1743
|
+
methods = '';
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
const methodsLen = methods.length;
|
|
1747
|
+
let p = this._packetRW.write.allocStart;
|
|
1748
|
+
const packet = this._packetRW.write.alloc(1 + 4 + methodsLen + 1);
|
|
1749
|
+
|
|
1750
|
+
packet[p] = MESSAGE.USERAUTH_FAILURE;
|
|
1751
|
+
|
|
1752
|
+
writeUInt32BE(packet, methodsLen, ++p);
|
|
1753
|
+
packet.utf8Write(methods, p += 4, methodsLen);
|
|
1754
|
+
|
|
1755
|
+
packet[p += methodsLen] = (isPartial === true ? 1 : 0);
|
|
1756
|
+
|
|
1757
|
+
this._authsQueue.shift();
|
|
1758
|
+
|
|
1759
|
+
this._debug && this._debug('Outbound: Sending USERAUTH_FAILURE');
|
|
1760
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1761
|
+
}
|
|
1762
|
+
authSuccess() {
|
|
1763
|
+
if (!this._server)
|
|
1764
|
+
throw new Error('Server-only method called in client mode');
|
|
1765
|
+
|
|
1766
|
+
if (this._authsQueue.length === 0)
|
|
1767
|
+
throw new Error('No auth in progress');
|
|
1768
|
+
|
|
1769
|
+
const p = this._packetRW.write.allocStart;
|
|
1770
|
+
const packet = this._packetRW.write.alloc(1);
|
|
1771
|
+
|
|
1772
|
+
packet[p] = MESSAGE.USERAUTH_SUCCESS;
|
|
1773
|
+
|
|
1774
|
+
this._authsQueue.shift();
|
|
1775
|
+
this._authenticated = true;
|
|
1776
|
+
|
|
1777
|
+
this._debug && this._debug('Outbound: Sending USERAUTH_SUCCESS');
|
|
1778
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1779
|
+
|
|
1780
|
+
if (this._kex.negotiated.cs.compress === 'zlib@openssh.com')
|
|
1781
|
+
this._packetRW.read = new ZlibPacketReader();
|
|
1782
|
+
if (this._kex.negotiated.sc.compress === 'zlib@openssh.com')
|
|
1783
|
+
this._packetRW.write = new ZlibPacketWriter(this);
|
|
1784
|
+
}
|
|
1785
|
+
authPKOK(keyAlgo, key) {
|
|
1786
|
+
if (!this._server)
|
|
1787
|
+
throw new Error('Server-only method called in client mode');
|
|
1788
|
+
|
|
1789
|
+
if (this._authsQueue.length === 0 || this._authsQueue[0] !== 'publickey')
|
|
1790
|
+
throw new Error('"publickey" auth not in progress');
|
|
1791
|
+
|
|
1792
|
+
// TODO: support parsed key for `key`
|
|
1793
|
+
|
|
1794
|
+
const keyAlgoLen = Buffer.byteLength(keyAlgo);
|
|
1795
|
+
const keyLen = key.length;
|
|
1796
|
+
let p = this._packetRW.write.allocStart;
|
|
1797
|
+
const packet = this._packetRW.write.alloc(1 + 4 + keyAlgoLen + 4 + keyLen);
|
|
1798
|
+
|
|
1799
|
+
packet[p] = MESSAGE.USERAUTH_PK_OK;
|
|
1800
|
+
|
|
1801
|
+
writeUInt32BE(packet, keyAlgoLen, ++p);
|
|
1802
|
+
packet.utf8Write(keyAlgo, p += 4, keyAlgoLen);
|
|
1803
|
+
|
|
1804
|
+
writeUInt32BE(packet, keyLen, p += keyAlgoLen);
|
|
1805
|
+
packet.set(key, p += 4);
|
|
1806
|
+
|
|
1807
|
+
this._authsQueue.shift();
|
|
1808
|
+
|
|
1809
|
+
this._debug && this._debug('Outbound: Sending USERAUTH_PK_OK');
|
|
1810
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1811
|
+
}
|
|
1812
|
+
authPasswdChg(prompt) {
|
|
1813
|
+
if (!this._server)
|
|
1814
|
+
throw new Error('Server-only method called in client mode');
|
|
1815
|
+
|
|
1816
|
+
const promptLen = Buffer.byteLength(prompt);
|
|
1817
|
+
let p = this._packetRW.write.allocStart;
|
|
1818
|
+
const packet = this._packetRW.write.alloc(1 + 4 + promptLen + 4);
|
|
1819
|
+
|
|
1820
|
+
packet[p] = MESSAGE.USERAUTH_PASSWD_CHANGEREQ;
|
|
1821
|
+
|
|
1822
|
+
writeUInt32BE(packet, promptLen, ++p);
|
|
1823
|
+
packet.utf8Write(prompt, p += 4, promptLen);
|
|
1824
|
+
|
|
1825
|
+
writeUInt32BE(packet, 0, p += promptLen); // Empty language tag
|
|
1826
|
+
|
|
1827
|
+
this._debug && this._debug('Outbound: Sending USERAUTH_PASSWD_CHANGEREQ');
|
|
1828
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1829
|
+
}
|
|
1830
|
+
authInfoReq(name, instructions, prompts) {
|
|
1831
|
+
if (!this._server)
|
|
1832
|
+
throw new Error('Server-only method called in client mode');
|
|
1833
|
+
|
|
1834
|
+
let promptsLen = 0;
|
|
1835
|
+
const nameLen = name ? Buffer.byteLength(name) : 0;
|
|
1836
|
+
const instrLen = instructions ? Buffer.byteLength(instructions) : 0;
|
|
1837
|
+
|
|
1838
|
+
for (let i = 0; i < prompts.length; ++i)
|
|
1839
|
+
promptsLen += 4 + Buffer.byteLength(prompts[i].prompt) + 1;
|
|
1840
|
+
|
|
1841
|
+
let p = this._packetRW.write.allocStart;
|
|
1842
|
+
const packet = this._packetRW.write.alloc(
|
|
1843
|
+
1 + 4 + nameLen + 4 + instrLen + 4 + 4 + promptsLen
|
|
1844
|
+
);
|
|
1845
|
+
|
|
1846
|
+
packet[p] = MESSAGE.USERAUTH_INFO_REQUEST;
|
|
1847
|
+
|
|
1848
|
+
writeUInt32BE(packet, nameLen, ++p);
|
|
1849
|
+
p += 4;
|
|
1850
|
+
if (name) {
|
|
1851
|
+
packet.utf8Write(name, p, nameLen);
|
|
1852
|
+
p += nameLen;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
writeUInt32BE(packet, instrLen, p);
|
|
1856
|
+
p += 4;
|
|
1857
|
+
if (instructions) {
|
|
1858
|
+
packet.utf8Write(instructions, p, instrLen);
|
|
1859
|
+
p += instrLen;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
writeUInt32BE(packet, 0, p);
|
|
1863
|
+
|
|
1864
|
+
writeUInt32BE(packet, prompts.length, p += 4);
|
|
1865
|
+
p += 4;
|
|
1866
|
+
for (let i = 0; i < prompts.length; ++i) {
|
|
1867
|
+
const prompt = prompts[i];
|
|
1868
|
+
const promptLen = Buffer.byteLength(prompt.prompt);
|
|
1869
|
+
|
|
1870
|
+
writeUInt32BE(packet, promptLen, p);
|
|
1871
|
+
p += 4;
|
|
1872
|
+
if (promptLen) {
|
|
1873
|
+
packet.utf8Write(prompt.prompt, p, promptLen);
|
|
1874
|
+
p += promptLen;
|
|
1875
|
+
}
|
|
1876
|
+
packet[p++] = (prompt.echo ? 1 : 0);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
this._debug && this._debug('Outbound: Sending USERAUTH_INFO_REQUEST');
|
|
1880
|
+
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
// SSH-protoversion-softwareversion (SP comments) CR LF
|
|
1885
|
+
const RE_IDENT = /^SSH-(2\.0|1\.99)-([^ ]+)(?: (.*))?$/;
|
|
1886
|
+
|
|
1887
|
+
// TODO: optimize this by starting n bytes from the end of this._buffer instead
|
|
1888
|
+
// of the beginning
|
|
1889
|
+
function parseHeader(chunk, p, len) {
|
|
1890
|
+
let data;
|
|
1891
|
+
let chunkOffset;
|
|
1892
|
+
if (this._buffer) {
|
|
1893
|
+
data = Buffer.allocUnsafe(this._buffer.length + (len - p));
|
|
1894
|
+
data.set(this._buffer, 0);
|
|
1895
|
+
if (p === 0) {
|
|
1896
|
+
data.set(chunk, this._buffer.length);
|
|
1897
|
+
} else {
|
|
1898
|
+
data.set(new Uint8Array(chunk.buffer,
|
|
1899
|
+
chunk.byteOffset + p,
|
|
1900
|
+
(len - p)),
|
|
1901
|
+
this._buffer.length);
|
|
1902
|
+
}
|
|
1903
|
+
chunkOffset = this._buffer.length;
|
|
1904
|
+
p = 0;
|
|
1905
|
+
} else {
|
|
1906
|
+
data = chunk;
|
|
1907
|
+
chunkOffset = 0;
|
|
1908
|
+
}
|
|
1909
|
+
const op = p;
|
|
1910
|
+
let start = p;
|
|
1911
|
+
let end = p;
|
|
1912
|
+
let needNL = false;
|
|
1913
|
+
let lineLen = 0;
|
|
1914
|
+
let lines = 0;
|
|
1915
|
+
for (; p < data.length; ++p) {
|
|
1916
|
+
const ch = data[p];
|
|
1917
|
+
|
|
1918
|
+
if (ch === 13 /* '\r' */) {
|
|
1919
|
+
needNL = true;
|
|
1920
|
+
continue;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
if (ch === 10 /* '\n' */) {
|
|
1924
|
+
if (end > start
|
|
1925
|
+
&& end - start > 4
|
|
1926
|
+
&& data[start] === 83 /* 'S' */
|
|
1927
|
+
&& data[start + 1] === 83 /* 'S' */
|
|
1928
|
+
&& data[start + 2] === 72 /* 'H' */
|
|
1929
|
+
&& data[start + 3] === 45 /* '-' */) {
|
|
1930
|
+
|
|
1931
|
+
const full = data.latin1Slice(op, end + 1);
|
|
1932
|
+
const identRaw = (start === op ? full : full.slice(start - op));
|
|
1933
|
+
const m = RE_IDENT.exec(identRaw);
|
|
1934
|
+
if (!m)
|
|
1935
|
+
throw new Error('Invalid identification string');
|
|
1936
|
+
|
|
1937
|
+
const header = {
|
|
1938
|
+
greeting: (start === op ? '' : full.slice(0, start - op)),
|
|
1939
|
+
identRaw,
|
|
1940
|
+
versions: {
|
|
1941
|
+
protocol: m[1],
|
|
1942
|
+
software: m[2],
|
|
1943
|
+
},
|
|
1944
|
+
comments: m[3]
|
|
1945
|
+
};
|
|
1946
|
+
|
|
1947
|
+
// Needed during handshake
|
|
1948
|
+
this._remoteIdentRaw = Buffer.from(identRaw);
|
|
1949
|
+
|
|
1950
|
+
this._debug && this._debug(`Remote ident: ${inspect(identRaw)}`);
|
|
1951
|
+
this._compatFlags = getCompatFlags(header);
|
|
1952
|
+
|
|
1953
|
+
this._buffer = undefined;
|
|
1954
|
+
this._decipher =
|
|
1955
|
+
new NullDecipher(0, onKEXPayload.bind(this, { firstPacket: true }));
|
|
1956
|
+
this._parse = parsePacket;
|
|
1957
|
+
|
|
1958
|
+
this._onHeader(header);
|
|
1959
|
+
if (!this._destruct) {
|
|
1960
|
+
// We disconnected inside _onHeader
|
|
1961
|
+
return len;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
kexinit(this);
|
|
1965
|
+
|
|
1966
|
+
return p + 1 - chunkOffset;
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
// Only allow pre-ident greetings when we're a client
|
|
1970
|
+
if (this._server)
|
|
1971
|
+
throw new Error('Greetings from clients not permitted');
|
|
1972
|
+
|
|
1973
|
+
if (++lines > MAX_LINES)
|
|
1974
|
+
throw new Error('Max greeting lines exceeded');
|
|
1975
|
+
|
|
1976
|
+
needNL = false;
|
|
1977
|
+
start = p + 1;
|
|
1978
|
+
lineLen = 0;
|
|
1979
|
+
} else if (needNL) {
|
|
1980
|
+
throw new Error('Invalid header: expected newline');
|
|
1981
|
+
} else if (++lineLen >= MAX_LINE_LEN) {
|
|
1982
|
+
throw new Error('Header line too long');
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
end = p;
|
|
1986
|
+
}
|
|
1987
|
+
if (!this._buffer)
|
|
1988
|
+
this._buffer = bufferSlice(data, op);
|
|
1989
|
+
|
|
1990
|
+
return p - chunkOffset;
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
function parsePacket(chunk, p, len) {
|
|
1994
|
+
return this._decipher.decrypt(chunk, p, len);
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
function onPayload(payload) {
|
|
1998
|
+
// XXX: move this to the Decipher implementations?
|
|
1999
|
+
|
|
2000
|
+
this._onPacket();
|
|
2001
|
+
|
|
2002
|
+
if (payload.length === 0) {
|
|
2003
|
+
this._debug && this._debug('Inbound: Skipping empty packet payload');
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
payload = this._packetRW.read.read(payload);
|
|
2008
|
+
|
|
2009
|
+
const type = payload[0];
|
|
2010
|
+
if (type === MESSAGE.USERAUTH_SUCCESS
|
|
2011
|
+
&& !this._server
|
|
2012
|
+
&& !this._authenticated) {
|
|
2013
|
+
this._authenticated = true;
|
|
2014
|
+
if (this._kex.negotiated.cs.compress === 'zlib@openssh.com')
|
|
2015
|
+
this._packetRW.write = new ZlibPacketWriter(this);
|
|
2016
|
+
if (this._kex.negotiated.sc.compress === 'zlib@openssh.com')
|
|
2017
|
+
this._packetRW.read = new ZlibPacketReader();
|
|
2018
|
+
}
|
|
2019
|
+
const handler = MESSAGE_HANDLERS[type];
|
|
2020
|
+
if (handler === undefined) {
|
|
2021
|
+
this._debug && this._debug(`Inbound: Unsupported message type: ${type}`);
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
return handler(this, payload);
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
function getCompatFlags(header) {
|
|
2029
|
+
const software = header.versions.software;
|
|
2030
|
+
|
|
2031
|
+
let flags = 0;
|
|
2032
|
+
|
|
2033
|
+
for (const rule of COMPAT_CHECKS) {
|
|
2034
|
+
if (typeof rule[0] === 'string') {
|
|
2035
|
+
if (software === rule[0])
|
|
2036
|
+
flags |= rule[1];
|
|
2037
|
+
} else if (rule[0].test(software)) {
|
|
2038
|
+
flags |= rule[1];
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
return flags;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
function modesToBytes(modes) {
|
|
2046
|
+
const keys = Object.keys(modes);
|
|
2047
|
+
const bytes = Buffer.allocUnsafe((5 * keys.length) + 1);
|
|
2048
|
+
let b = 0;
|
|
2049
|
+
|
|
2050
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
2051
|
+
const key = keys[i];
|
|
2052
|
+
if (key === 'TTY_OP_END')
|
|
2053
|
+
continue;
|
|
2054
|
+
|
|
2055
|
+
const opcode = TERMINAL_MODE[key];
|
|
2056
|
+
if (opcode === undefined)
|
|
2057
|
+
continue;
|
|
2058
|
+
|
|
2059
|
+
const val = modes[key];
|
|
2060
|
+
if (typeof val === 'number' && isFinite(val)) {
|
|
2061
|
+
bytes[b++] = opcode;
|
|
2062
|
+
bytes[b++] = val >>> 24;
|
|
2063
|
+
bytes[b++] = val >>> 16;
|
|
2064
|
+
bytes[b++] = val >>> 8;
|
|
2065
|
+
bytes[b++] = val;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
bytes[b++] = TERMINAL_MODE.TTY_OP_END;
|
|
2070
|
+
|
|
2071
|
+
if (b < bytes.length)
|
|
2072
|
+
return bufferSlice(bytes, 0, b);
|
|
2073
|
+
|
|
2074
|
+
return bytes;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
module.exports = Protocol;
|