@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.
@@ -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;