ssh2-streams
Version:
SSH2 and SFTP(v3) client/server protocol streams for node.js
1,592 lines (1,338 loc) • 164 kB
JavaScript
// TODO: * Automatic re-key every (configurable) n bytes or length of time
// - RFC suggests every 1GB of transmitted data or 1 hour, whichever
// comes sooner
// * Filter control codes from strings
// (as per http://tools.ietf.org/html/rfc4251#section-9.2)
var crypto = require('crypto');
var zlib = require('zlib');
var TransformStream = require('stream').Transform;
var inherits = require('util').inherits;
var inspect = require('util').inspect;
var StreamSearch = require('streamsearch');
var Ber = require('asn1').Ber;
var readUInt32BE = require('./buffer-helpers').readUInt32BE;
var writeUInt32BE = require('./buffer-helpers').writeUInt32BE;
var consts = require('./constants');
var utils = require('./utils');
var iv_inc = utils.iv_inc;
var readString = utils.readString;
var readInt = utils.readInt;
var DSASigBERToBare = utils.DSASigBERToBare;
var ECDSASigASN1ToSSH = utils.ECDSASigASN1ToSSH;
var sigSSHToASN1 = utils.sigSSHToASN1;
var parseDERKey = require('./keyParser').parseDERKey;
var CIPHER_INFO = consts.CIPHER_INFO;
var HMAC_INFO = consts.HMAC_INFO;
var MESSAGE = consts.MESSAGE;
var DYNAMIC_KEXDH_MESSAGE = consts.DYNAMIC_KEXDH_MESSAGE;
var KEXDH_MESSAGE = consts.KEXDH_MESSAGE;
var ALGORITHMS = consts.ALGORITHMS;
var DISCONNECT_REASON = consts.DISCONNECT_REASON;
var CHANNEL_OPEN_FAILURE = consts.CHANNEL_OPEN_FAILURE;
var SSH_TO_OPENSSL = consts.SSH_TO_OPENSSL;
var TERMINAL_MODE = consts.TERMINAL_MODE;
var SIGNALS = consts.SIGNALS;
var EDDSA_SUPPORTED = consts.EDDSA_SUPPORTED;
var CURVE25519_SUPPORTED = consts.CURVE25519_SUPPORTED;
var BUGS = consts.BUGS;
var BUGGY_IMPLS = consts.BUGGY_IMPLS;
var BUGGY_IMPLS_LEN = BUGGY_IMPLS.length;
var MODULE_VER = require('../package.json').version;
var I = 0;
var IN_INIT = I++;
var IN_GREETING = I++;
var IN_HEADER = I++;
var IN_PACKETBEFORE = I++;
var IN_PACKET = I++;
var IN_PACKETDATA = I++;
var IN_PACKETDATAVERIFY = I++;
var IN_PACKETDATAAFTER = I++;
var OUT_INIT = I++;
var OUT_READY = I++;
var OUT_REKEYING = I++;
var MAX_SEQNO = 4294967295;
var MAX_PACKET_SIZE = 35000;
var MAX_PACKETS_REKEYING = 50;
var EXP_TYPE_HEADER = 0;
var EXP_TYPE_LF = 1;
var EXP_TYPE_BYTES = 2; // Waits until n bytes have been seen
var Z_PARTIAL_FLUSH = zlib.Z_PARTIAL_FLUSH;
var ZLIB_OPTS = { flush: Z_PARTIAL_FLUSH };
var RE_NULL = /\x00/g;
var IDENT_PREFIX_BUFFER = Buffer.from('SSH-');
var EMPTY_BUFFER = Buffer.allocUnsafe(0);
var HMAC_COMPUTE = Buffer.allocUnsafe(9);
var PING_PACKET = Buffer.from([
MESSAGE.GLOBAL_REQUEST,
// "keepalive@openssh.com"
0, 0, 0, 21,
107, 101, 101, 112, 97, 108, 105, 118, 101, 64, 111, 112, 101, 110, 115,
115, 104, 46, 99, 111, 109,
// Request a reply
1
]);
var NEWKEYS_PACKET = Buffer.from([MESSAGE.NEWKEYS]);
var USERAUTH_SUCCESS_PACKET = Buffer.from([MESSAGE.USERAUTH_SUCCESS]);
var REQUEST_SUCCESS_PACKET = Buffer.from([MESSAGE.REQUEST_SUCCESS]);
var REQUEST_FAILURE_PACKET = Buffer.from([MESSAGE.REQUEST_FAILURE]);
var NO_TERMINAL_MODES_BUFFER = Buffer.from([TERMINAL_MODE.TTY_OP_END]);
var KEXDH_GEX_REQ_PACKET = Buffer.from([
MESSAGE.KEXDH_GEX_REQUEST,
// Minimal size in bits of an acceptable group
0, 0, 4, 0, // 1024, modp2
// Preferred size in bits of the group the server will send
0, 0, 16, 0, // 4096, modp16
// Maximal size in bits of an acceptable group
0, 0, 32, 0 // 8192, modp18
]);
function DEBUG_NOOP(msg) {}
function SSH2Stream(cfg) {
if (typeof cfg !== 'object' || cfg === null)
cfg = {};
TransformStream.call(this, {
highWaterMark: (typeof cfg.highWaterMark === 'number'
? cfg.highWaterMark
: 32 * 1024)
});
this._needContinue = false;
this.bytesSent = this.bytesReceived = 0;
this.debug = (typeof cfg.debug === 'function' ? cfg.debug : DEBUG_NOOP);
this.server = (cfg.server === true);
this.maxPacketSize = (typeof cfg.maxPacketSize === 'number'
? cfg.maxPacketSize
: MAX_PACKET_SIZE);
// Bitmap that indicates any bugs the remote side has. This is determined
// by the reported software version.
this.remoteBugs = 0;
if (this.server) {
// TODO: Remove when we support group exchange for server implementation
this.remoteBugs = BUGS.BAD_DHGEX;
}
this.readable = true;
var self = this;
var hostKeys = cfg.hostKeys;
if (this.server && (typeof hostKeys !== 'object' || hostKeys === null))
throw new Error('hostKeys must be an object keyed on host key type');
this.config = {
// Server
hostKeys: hostKeys, // All keys supported by server
// Client/Server
ident: 'SSH-2.0-'
+ (cfg.ident
|| ('ssh2js' + MODULE_VER + (this.server ? 'srv' : ''))),
algorithms: {
kex: ALGORITHMS.KEX,
kexBuf: ALGORITHMS.KEX_BUF,
serverHostKey: ALGORITHMS.SERVER_HOST_KEY,
serverHostKeyBuf: ALGORITHMS.SERVER_HOST_KEY_BUF,
cipher: ALGORITHMS.CIPHER,
cipherBuf: ALGORITHMS.CIPHER_BUF,
hmac: ALGORITHMS.HMAC,
hmacBuf: ALGORITHMS.HMAC_BUF,
compress: ALGORITHMS.COMPRESS,
compressBuf: ALGORITHMS.COMPRESS_BUF
}
};
// RFC 4253 states the identification string must not contain NULL
this.config.ident.replace(RE_NULL, '');
if (this.config.ident.length + 2 /* Account for "\r\n" */ > 255)
throw new Error('ident too long');
if (typeof cfg.algorithms === 'object' && cfg.algorithms !== null) {
var algos = cfg.algorithms;
if (Array.isArray(algos.kex) && algos.kex.length > 0) {
this.config.algorithms.kex = algos.kex;
if (!Buffer.isBuffer(algos.kexBuf))
algos.kexBuf = Buffer.from(algos.kex.join(','), 'ascii');
this.config.algorithms.kexBuf = algos.kexBuf;
}
if (Array.isArray(algos.serverHostKey) && algos.serverHostKey.length > 0) {
this.config.algorithms.serverHostKey = algos.serverHostKey;
if (!Buffer.isBuffer(algos.serverHostKeyBuf)) {
algos.serverHostKeyBuf = Buffer.from(algos.serverHostKey.join(','),
'ascii');
}
this.config.algorithms.serverHostKeyBuf = algos.serverHostKeyBuf;
}
if (Array.isArray(algos.cipher) && algos.cipher.length > 0) {
this.config.algorithms.cipher = algos.cipher;
if (!Buffer.isBuffer(algos.cipherBuf))
algos.cipherBuf = Buffer.from(algos.cipher.join(','), 'ascii');
this.config.algorithms.cipherBuf = algos.cipherBuf;
}
if (Array.isArray(algos.hmac) && algos.hmac.length > 0) {
this.config.algorithms.hmac = algos.hmac;
if (!Buffer.isBuffer(algos.hmacBuf))
algos.hmacBuf = Buffer.from(algos.hmac.join(','), 'ascii');
this.config.algorithms.hmacBuf = algos.hmacBuf;
}
if (Array.isArray(algos.compress) && algos.compress.length > 0) {
this.config.algorithms.compress = algos.compress;
if (!Buffer.isBuffer(algos.compressBuf))
algos.compressBuf = Buffer.from(algos.compress.join(','), 'ascii');
this.config.algorithms.compressBuf = algos.compressBuf;
}
}
this.reset(true);
// Common events
this.on('end', function() {
// Let GC collect any Buffers we were previously storing
self.readable = false;
self._state = undefined;
self.reset();
self._state.outgoing.bufSeqno = undefined;
});
this.on('DISCONNECT', function(reason, code, desc, lang) {
onDISCONNECT(self, reason, code, desc, lang);
});
this.on('KEXINIT', function(init, firstFollows) {
onKEXINIT(self, init, firstFollows);
});
this.on('NEWKEYS', function() { onNEWKEYS(self); });
if (this.server) {
// Server-specific events
this.on('KEXDH_INIT', function(e) { onKEXDH_INIT(self, e); });
} else {
// Client-specific events
this.on('KEXDH_REPLY', function(info) { onKEXDH_REPLY(self, info); })
.on('KEXDH_GEX_GROUP',
function(prime, gen) { onKEXDH_GEX_GROUP(self, prime, gen); });
}
if (this.server) {
// Greeting displayed before the ssh identification string is sent, this is
// usually ignored by most clients
if (typeof cfg.greeting === 'string' && cfg.greeting.length) {
if (cfg.greeting.slice(-2) === '\r\n')
this.push(cfg.greeting);
else
this.push(cfg.greeting + '\r\n');
}
// Banner shown after the handshake completes, but before user
// authentication begins
if (typeof cfg.banner === 'string' && cfg.banner.length) {
if (cfg.banner.slice(-2) === '\r\n')
this.banner = cfg.banner;
else
this.banner = cfg.banner + '\r\n';
}
}
this.debug('DEBUG: Local ident: ' + inspect(this.config.ident));
this.push(this.config.ident + '\r\n');
this._state.incoming.expectedPacket = 'KEXINIT';
}
inherits(SSH2Stream, TransformStream);
SSH2Stream.prototype.__read = TransformStream.prototype._read;
SSH2Stream.prototype._read = function(n) {
if (this._needContinue) {
this._needContinue = false;
this.emit('continue');
}
return this.__read(n);
};
SSH2Stream.prototype.__push = TransformStream.prototype.push;
SSH2Stream.prototype.push = function(chunk, encoding) {
var ret = this.__push(chunk, encoding);
this._needContinue = (ret === false);
return ret;
};
SSH2Stream.prototype._cleanup = function(callback) {
this.reset();
this.debug('DEBUG: Parser: Malformed packet');
callback && callback(new Error('Malformed packet'));
};
SSH2Stream.prototype._transform = function(chunk, encoding, callback, decomp) {
var skipDecrypt = false;
var decryptAuthMode = false;
var state = this._state;
var instate = state.incoming;
var outstate = state.outgoing;
var expect = instate.expect;
var decrypt = instate.decrypt;
var decompress = instate.decompress;
var chlen = chunk.length;
var chleft = 0;
var debug = this.debug;
var self = this;
var i = 0;
var p = i;
var blockLen;
var buffer;
var buf;
var r;
this.bytesReceived += chlen;
while (true) {
if (expect.type !== undefined) {
if (i >= chlen)
break;
if (expect.type === EXP_TYPE_BYTES) {
chleft = (chlen - i);
var pktLeft = (expect.buf.length - expect.ptr);
if (pktLeft <= chleft) {
chunk.copy(expect.buf, expect.ptr, i, i + pktLeft);
i += pktLeft;
buffer = expect.buf;
expect.buf = undefined;
expect.ptr = 0;
expect.type = undefined;
} else {
chunk.copy(expect.buf, expect.ptr, i);
expect.ptr += chleft;
i += chleft;
}
continue;
} else if (expect.type === EXP_TYPE_HEADER) {
i += instate.search.push(chunk);
if (expect.type !== undefined)
continue;
} else if (expect.type === EXP_TYPE_LF) {
if (++expect.ptr + 4 /* Account for "SSH-" */ > 255) {
this.reset();
debug('DEBUG: Parser: Identification string exceeded 255 characters');
return callback(new Error('Max identification string size exceeded'));
}
if (chunk[i] === 0x0A) {
expect.type = undefined;
if (p < i) {
if (expect.buf === undefined)
expect.buf = chunk.toString('ascii', p, i);
else
expect.buf += chunk.toString('ascii', p, i);
}
buffer = expect.buf;
expect.buf = undefined;
++i;
} else {
if (++i === chlen && p < i) {
if (expect.buf === undefined)
expect.buf = chunk.toString('ascii', p, i);
else
expect.buf += chunk.toString('ascii', p, i);
}
continue;
}
}
}
if (instate.status === IN_INIT) {
if (!this.readable)
return callback();
if (this.server) {
// Retrieve what should be the start of the protocol version exchange
if (!buffer) {
debug('DEBUG: Parser: IN_INIT (waiting for identification begin)');
expectData(this, EXP_TYPE_BYTES, 4);
} else {
if (buffer[0] === 0x53 // S
&& buffer[1] === 0x53 // S
&& buffer[2] === 0x48 // H
&& buffer[3] === 0x2D) { // -
instate.status = IN_GREETING;
debug('DEBUG: Parser: IN_INIT (waiting for rest of identification)');
} else {
this.reset();
debug('DEBUG: Parser: Bad identification start');
return callback(new Error('Bad identification start'));
}
}
} else {
debug('DEBUG: Parser: IN_INIT');
// Retrieve any bytes that may come before the protocol version exchange
var ss = instate.search = new StreamSearch(IDENT_PREFIX_BUFFER);
ss.on('info', function onInfo(matched, data, start, end) {
if (data) {
if (instate.greeting === undefined)
instate.greeting = data.toString('binary', start, end);
else
instate.greeting += data.toString('binary', start, end);
}
if (matched) {
expect.type = undefined;
instate.search.removeListener('info', onInfo);
}
});
ss.maxMatches = 1;
expectData(this, EXP_TYPE_HEADER);
instate.status = IN_GREETING;
}
} else if (instate.status === IN_GREETING) {
debug('DEBUG: Parser: IN_GREETING');
instate.search = undefined;
// Retrieve the identification bytes after the "SSH-" header
p = i;
expectData(this, EXP_TYPE_LF);
instate.status = IN_HEADER;
} else if (instate.status === IN_HEADER) {
debug('DEBUG: Parser: IN_HEADER');
if (buffer.charCodeAt(buffer.length - 1) === 13)
buffer = buffer.slice(0, -1);
var idxDash = buffer.indexOf('-');
var idxSpace = buffer.indexOf(' ');
var header = {
// RFC says greeting SHOULD be utf8
greeting: instate.greeting,
identRaw: 'SSH-' + buffer,
versions: {
protocol: buffer.substr(0, idxDash),
software: (idxSpace === -1
? buffer.substring(idxDash + 1)
: buffer.substring(idxDash + 1, idxSpace))
},
comments: (idxSpace > -1 ? buffer.substring(idxSpace + 1) : undefined)
};
instate.greeting = undefined;
if (header.versions.protocol !== '1.99'
&& header.versions.protocol !== '2.0') {
this.reset();
debug('DEBUG: Parser: protocol version not supported: '
+ header.versions.protocol);
return callback(new Error('Protocol version not supported'));
} else
this.emit('header', header);
if (instate.status === IN_INIT) {
// We reset from an event handler, possibly due to an unsupported SSH
// protocol version?
return;
}
var identRaw = header.identRaw;
var software = header.versions.software;
this.debug('DEBUG: Remote ident: ' + inspect(identRaw));
for (var j = 0, rule; j < BUGGY_IMPLS_LEN; ++j) {
rule = BUGGY_IMPLS[j];
if (typeof rule[0] === 'string') {
if (software === rule[0])
this.remoteBugs |= rule[1];
} else if (rule[0].test(software))
this.remoteBugs |= rule[1];
}
instate.identRaw = identRaw;
// Adjust bytesReceived first otherwise it will have an incorrectly larger
// total when we call back into this function after completing KEXINIT
this.bytesReceived -= (chlen - i);
KEXINIT(this, function() {
if (i === chlen)
callback();
else
self._transform(chunk.slice(i), encoding, callback);
});
instate.status = IN_PACKETBEFORE;
return;
} else if (instate.status === IN_PACKETBEFORE) {
blockLen = (decrypt.instance ? decrypt.info.blockLen : 8);
debug('DEBUG: Parser: IN_PACKETBEFORE (expecting ' + blockLen + ')');
// Wait for the right number of bytes so we can determine the incoming
// packet length
expectData(this, EXP_TYPE_BYTES, blockLen, decrypt.buf);
instate.status = IN_PACKET;
} else if (instate.status === IN_PACKET) {
debug('DEBUG: Parser: IN_PACKET');
if (decrypt.instance) {
decryptAuthMode = (decrypt.info.authLen > 0);
if (!decryptAuthMode)
buffer = decryptData(this, buffer);
blockLen = decrypt.info.blockLen;
} else {
decryptAuthMode = false;
blockLen = 8;
}
r = readInt(buffer, 0, this, callback);
if (r === false)
return;
var hmacInfo = instate.hmac.info;
var macSize;
if (hmacInfo)
macSize = hmacInfo.actualLen;
else
macSize = 0;
var fullPacketLen = r + 4 + macSize;
var maxPayloadLen = this.maxPacketSize;
if (decompress.instance) {
// Account for compressed payloads
// This formula is taken from dropbear which derives it from zlib's
// documentation. Explanation from dropbear:
/* For exact details see http://www.zlib.net/zlib_tech.html
* 5 bytes per 16kB block, plus 6 bytes for the stream.
* We might allocate 5 unnecessary bytes here if it's an
* exact multiple. */
maxPayloadLen += (((this.maxPacketSize / 16384) + 1) * 5 + 6);
}
if (r > maxPayloadLen
// TODO: Change 16 to "MAX(16, decrypt.info.blockLen)" when/if SSH2
// adopts 512-bit ciphers
|| fullPacketLen < (16 + macSize)
|| ((r + (decryptAuthMode ? 0 : 4)) % blockLen) !== 0) {
this.disconnect(DISCONNECT_REASON.PROTOCOL_ERROR);
debug('DEBUG: Parser: Bad packet length (' + fullPacketLen + ')');
return callback(new Error('Bad packet length'));
}
instate.pktLen = r;
var remainLen = instate.pktLen + 4 - blockLen;
if (decryptAuthMode) {
decrypt.instance.setAAD(buffer.slice(0, 4));
debug('DEBUG: Parser: pktLen:'
+ instate.pktLen
+ ',remainLen:'
+ remainLen);
} else {
instate.padLen = buffer[4];
debug('DEBUG: Parser: pktLen:'
+ instate.pktLen
+ ',padLen:'
+ instate.padLen
+ ',remainLen:'
+ remainLen);
}
if (remainLen > 0) {
if (decryptAuthMode)
instate.pktExtra = buffer.slice(4);
else
instate.pktExtra = buffer.slice(5);
// Grab the rest of the packet
expectData(this, EXP_TYPE_BYTES, remainLen);
instate.status = IN_PACKETDATA;
} else if (remainLen < 0)
instate.status = IN_PACKETBEFORE;
else {
// Entire message fit into one block
skipDecrypt = true;
instate.status = IN_PACKETDATA;
continue;
}
} else if (instate.status === IN_PACKETDATA) {
debug('DEBUG: Parser: IN_PACKETDATA');
if (decrypt.instance) {
decryptAuthMode = (decrypt.info.authLen > 0);
if (!skipDecrypt) {
if (!decryptAuthMode)
buffer = decryptData(this, buffer);
} else {
skipDecrypt = false;
}
} else {
decryptAuthMode = false;
skipDecrypt = false;
}
var padStart = instate.pktLen - instate.padLen - 1;
// TODO: Allocate a Buffer once that is slightly larger than maxPacketSize
// (to accommodate for packet length field and MAC) and re-use that
// instead
if (instate.pktExtra) {
buf = Buffer.allocUnsafe(instate.pktExtra.length + buffer.length);
instate.pktExtra.copy(buf);
buffer.copy(buf, instate.pktExtra.length);
instate.payload = buf.slice(0, padStart);
} else {
// Entire message fit into one block
if (decryptAuthMode)
buf = buffer.slice(4);
else
buf = buffer.slice(5);
instate.payload = buffer.slice(5, 5 + padStart);
}
if (instate.hmac.info !== undefined) {
// Wait for hmac hash
var inHMACSize = decrypt.info.authLen || instate.hmac.info.actualLen;
debug('DEBUG: Parser: HMAC size:' + inHMACSize);
expectData(this, EXP_TYPE_BYTES, inHMACSize, instate.hmac.buf);
instate.status = IN_PACKETDATAVERIFY;
instate.packet = buf;
} else
instate.status = IN_PACKETDATAAFTER;
instate.pktExtra = undefined;
buf = undefined;
} else if (instate.status === IN_PACKETDATAVERIFY) {
debug('DEBUG: Parser: IN_PACKETDATAVERIFY');
// Verify packet data integrity
if (hmacVerify(this, buffer)) {
debug('DEBUG: Parser: IN_PACKETDATAVERIFY (Valid HMAC)');
instate.status = IN_PACKETDATAAFTER;
instate.packet = undefined;
} else {
this.reset();
debug('DEBUG: Parser: IN_PACKETDATAVERIFY (Invalid HMAC)');
return callback(new Error('Invalid HMAC'));
}
} else if (instate.status === IN_PACKETDATAAFTER) {
if (decompress.instance) {
if (!decomp) {
debug('DEBUG: Parser: Decompressing');
decompress.instance.write(instate.payload);
var decompBuf = [];
var decompBufLen = 0;
decompress.instance.on('readable', function() {
var buf;
while (buf = this.read()) {
decompBuf.push(buf);
decompBufLen += buf.length;
}
}).flush(Z_PARTIAL_FLUSH, function() {
decompress.instance.removeAllListeners('readable');
if (decompBuf.length === 1)
instate.payload = decompBuf[0];
else
instate.payload = Buffer.concat(decompBuf, decompBufLen);
decompBuf = null;
var nextSlice;
if (i === chlen)
nextSlice = EMPTY_BUFFER; // Avoid slicing a zero-length buffer
else
nextSlice = chunk.slice(i);
self._transform(nextSlice, encoding, callback, true);
});
return;
} else {
// Make sure we reset this after this first time in the loop,
// otherwise we could end up trying to interpret as-is another
// compressed packet that is within the same chunk
decomp = false;
}
}
this.emit('packet');
var ptype = instate.payload[0];
if (debug !== DEBUG_NOOP) {
var msgPacket = 'DEBUG: Parser: IN_PACKETDATAAFTER, packet: ';
var authMethod = state.authsQueue[0];
var msgPktType = null;
if (outstate.status === OUT_REKEYING
&& !(ptype <= 4 || (ptype >= 20 && ptype <= 49)))
msgPacket += '(enqueued) ';
if (ptype === MESSAGE.KEXDH_INIT) {
switch (state.kex.type) {
case 'group':
msgPktType = 'KEXDH_INIT';
break;
case 'groupex':
msgPktType = 'KEXDH_GEX_REQUEST';
break;
default:
msgPktType = 'KEXECDH_INIT';
}
} else if (ptype === MESSAGE.KEXDH_REPLY) {
switch (state.kex.type) {
case 'group':
msgPktType = 'KEXDH_REPLY';
break;
case 'groupex':
msgPktType = 'KEXDH_GEX_GROUP';
break;
default:
msgPktType = 'KEXECDH_REPLY';
}
} else if (ptype === MESSAGE.KEXDH_GEX_GROUP) {
msgPktType = 'KEXDH_GEX_GROUP';
} else if (ptype === MESSAGE.KEXDH_GEX_REPLY) {
msgPktType = 'KEXDH_GEX_REPLY';
} else if (ptype === 60) {
if (authMethod === 'password')
msgPktType = 'USERAUTH_PASSWD_CHANGEREQ';
else if (authMethod === 'keyboard-interactive')
msgPktType = 'USERAUTH_INFO_REQUEST';
else if (authMethod === 'publickey')
msgPktType = 'USERAUTH_PK_OK';
else
msgPktType = 'UNKNOWN PACKET 60';
} else if (ptype === 61) {
if (authMethod === 'keyboard-interactive')
msgPktType = 'USERAUTH_INFO_RESPONSE';
else
msgPktType = 'UNKNOWN PACKET 61';
}
if (msgPktType === null)
msgPktType = MESSAGE[ptype];
// Don't write debug output for messages we custom make in parsePacket()
if (ptype !== MESSAGE.CHANNEL_OPEN
&& ptype !== MESSAGE.CHANNEL_REQUEST
&& ptype !== MESSAGE.CHANNEL_SUCCESS
&& ptype !== MESSAGE.CHANNEL_FAILURE
&& ptype !== MESSAGE.CHANNEL_EOF
&& ptype !== MESSAGE.CHANNEL_CLOSE
&& ptype !== MESSAGE.CHANNEL_DATA
&& ptype !== MESSAGE.CHANNEL_EXTENDED_DATA
&& ptype !== MESSAGE.CHANNEL_WINDOW_ADJUST
&& ptype !== MESSAGE.DISCONNECT
&& ptype !== MESSAGE.USERAUTH_REQUEST
&& ptype !== MESSAGE.GLOBAL_REQUEST)
debug(msgPacket + msgPktType);
}
// Only parse packet if we are not re-keying or the packet is not a
// transport layer packet needed for re-keying
if (outstate.status === OUT_READY
|| ptype <= 4
|| (ptype >= 20 && ptype <= 49)) {
if (parsePacket(this, callback) === false)
return;
if (instate.status === IN_INIT) {
// We were reset due to some error/disagreement ?
return;
}
} else if (outstate.status === OUT_REKEYING) {
if (instate.rekeyQueue.length === MAX_PACKETS_REKEYING) {
debug('DEBUG: Parser: Max incoming re-key queue length reached');
this.disconnect(DISCONNECT_REASON.PROTOCOL_ERROR);
return callback(
new Error('Incoming re-key queue length limit reached')
);
}
// Make sure to record the sequence number in case we need it later on
// when we drain the queue (e.g. unknown packet)
var seqno = instate.seqno;
if (++instate.seqno > MAX_SEQNO)
instate.seqno = 0;
instate.rekeyQueue.push([seqno, instate.payload]);
}
instate.status = IN_PACKETBEFORE;
instate.payload = undefined;
}
if (buffer !== undefined)
buffer = undefined;
}
callback();
};
SSH2Stream.prototype.reset = function(noend) {
if (this._state) {
var state = this._state;
state.incoming.status = IN_INIT;
state.outgoing.status = OUT_INIT;
} else {
this._state = {
authsQueue: [],
hostkeyFormat: undefined,
kex: undefined,
incoming: {
status: IN_INIT,
expectedPacket: undefined,
search: undefined,
greeting: undefined,
seqno: 0,
pktLen: undefined,
padLen: undefined,
pktExtra: undefined,
payload: undefined,
packet: undefined,
kexinit: undefined,
identRaw: undefined,
rekeyQueue: [],
ignoreNext: false,
expect: {
amount: undefined,
type: undefined,
ptr: 0,
buf: undefined
},
decrypt: {
instance: false,
info: undefined,
iv: undefined,
key: undefined,
buf: undefined,
type: undefined
},
hmac: {
info: undefined,
key: undefined,
buf: undefined,
type: false
},
decompress: {
instance: false,
type: false
}
},
outgoing: {
status: OUT_INIT,
seqno: 0,
bufSeqno: Buffer.allocUnsafe(4),
rekeyQueue: [],
kexinit: undefined,
kexsecret: undefined,
pubkey: undefined,
exchangeHash: undefined,
sessionId: undefined,
sentNEWKEYS: false,
encrypt: {
instance: false,
info: undefined,
iv: undefined,
key: undefined,
type: undefined
},
hmac: {
info: undefined,
key: undefined,
buf: undefined,
type: false
},
compress: {
instance: false,
type: false,
queue: null
}
}
};
}
if (!noend) {
if (this.readable)
this.push(null);
}
};
// Common methods
// Global
SSH2Stream.prototype.disconnect = function(reason) {
/*
byte SSH_MSG_DISCONNECT
uint32 reason code
string description in ISO-10646 UTF-8 encoding
string language tag
*/
var buf = Buffer.alloc(1 + 4 + 4 + 4);
buf[0] = MESSAGE.DISCONNECT;
if (DISCONNECT_REASON[reason] === undefined)
reason = DISCONNECT_REASON.BY_APPLICATION;
writeUInt32BE(buf, reason, 1);
this.debug('DEBUG: Outgoing: Writing DISCONNECT ('
+ DISCONNECT_REASON[reason]
+ ')');
send(this, buf);
this.reset();
return false;
};
SSH2Stream.prototype.ping = function() {
this.debug('DEBUG: Outgoing: Writing ping (GLOBAL_REQUEST: keepalive@openssh.com)');
return send(this, PING_PACKET);
};
SSH2Stream.prototype.rekey = function() {
var status = this._state.outgoing.status;
if (status === OUT_REKEYING)
throw new Error('A re-key is already in progress');
else if (status !== OUT_READY)
throw new Error('Cannot re-key yet');
this.debug('DEBUG: Outgoing: Starting re-key');
return KEXINIT(this);
};
// 'ssh-connection' service-specific
SSH2Stream.prototype.requestSuccess = function(data) {
var buf;
if (Buffer.isBuffer(data)) {
buf = Buffer.allocUnsafe(1 + data.length);
buf[0] = MESSAGE.REQUEST_SUCCESS;
data.copy(buf, 1);
} else
buf = REQUEST_SUCCESS_PACKET;
this.debug('DEBUG: Outgoing: Writing REQUEST_SUCCESS');
return send(this, buf);
};
SSH2Stream.prototype.requestFailure = function() {
this.debug('DEBUG: Outgoing: Writing REQUEST_FAILURE');
return send(this, REQUEST_FAILURE_PACKET);
};
SSH2Stream.prototype.channelSuccess = function(chan) {
// Does not consume window space
var buf = Buffer.allocUnsafe(1 + 4);
buf[0] = MESSAGE.CHANNEL_SUCCESS;
writeUInt32BE(buf, chan, 1);
this.debug('DEBUG: Outgoing: Writing CHANNEL_SUCCESS (' + chan + ')');
return send(this, buf);
};
SSH2Stream.prototype.channelFailure = function(chan) {
// Does not consume window space
var buf = Buffer.allocUnsafe(1 + 4);
buf[0] = MESSAGE.CHANNEL_FAILURE;
writeUInt32BE(buf, chan, 1);
this.debug('DEBUG: Outgoing: Writing CHANNEL_FAILURE (' + chan + ')');
return send(this, buf);
};
SSH2Stream.prototype.channelEOF = function(chan) {
// Does not consume window space
var buf = Buffer.allocUnsafe(1 + 4);
buf[0] = MESSAGE.CHANNEL_EOF;
writeUInt32BE(buf, chan, 1);
this.debug('DEBUG: Outgoing: Writing CHANNEL_EOF (' + chan + ')');
return send(this, buf);
};
SSH2Stream.prototype.channelClose = function(chan) {
// Does not consume window space
var buf = Buffer.allocUnsafe(1 + 4);
buf[0] = MESSAGE.CHANNEL_CLOSE;
writeUInt32BE(buf, chan, 1);
this.debug('DEBUG: Outgoing: Writing CHANNEL_CLOSE (' + chan + ')');
return send(this, buf);
};
SSH2Stream.prototype.channelWindowAdjust = function(chan, amount) {
// Does not consume window space
var buf = Buffer.allocUnsafe(1 + 4 + 4);
buf[0] = MESSAGE.CHANNEL_WINDOW_ADJUST;
writeUInt32BE(buf, chan, 1);
writeUInt32BE(buf, amount, 5);
this.debug('DEBUG: Outgoing: Writing CHANNEL_WINDOW_ADJUST ('
+ chan
+ ', '
+ amount
+ ')');
return send(this, buf);
};
SSH2Stream.prototype.channelData = function(chan, data) {
var dataIsBuffer = Buffer.isBuffer(data);
var dataLen = (dataIsBuffer ? data.length : Buffer.byteLength(data));
var buf = Buffer.allocUnsafe(1 + 4 + 4 + dataLen);
buf[0] = MESSAGE.CHANNEL_DATA;
writeUInt32BE(buf, chan, 1);
writeUInt32BE(buf, dataLen, 5);
if (dataIsBuffer)
data.copy(buf, 9);
else
buf.write(data, 9, dataLen, 'utf8');
this.debug('DEBUG: Outgoing: Writing CHANNEL_DATA (' + chan + ')');
return send(this, buf);
};
SSH2Stream.prototype.channelExtData = function(chan, data, type) {
var dataIsBuffer = Buffer.isBuffer(data);
var dataLen = (dataIsBuffer ? data.length : Buffer.byteLength(data));
var buf = Buffer.allocUnsafe(1 + 4 + 4 + 4 + dataLen);
buf[0] = MESSAGE.CHANNEL_EXTENDED_DATA;
writeUInt32BE(buf, chan, 1);
writeUInt32BE(buf, type, 5);
writeUInt32BE(buf, dataLen, 9);
if (dataIsBuffer)
data.copy(buf, 13);
else
buf.write(data, 13, dataLen, 'utf8');
this.debug('DEBUG: Outgoing: Writing CHANNEL_EXTENDED_DATA (' + chan + ')');
return send(this, buf);
};
SSH2Stream.prototype.channelOpenConfirm = function(remoteChan, localChan,
initWindow, maxPacket) {
var buf = Buffer.allocUnsafe(1 + 4 + 4 + 4 + 4);
buf[0] = MESSAGE.CHANNEL_OPEN_CONFIRMATION;
writeUInt32BE(buf, remoteChan, 1);
writeUInt32BE(buf, localChan, 5);
writeUInt32BE(buf, initWindow, 9);
writeUInt32BE(buf, maxPacket, 13);
this.debug('DEBUG: Outgoing: Writing CHANNEL_OPEN_CONFIRMATION (r:'
+ remoteChan
+ ', l:'
+ localChan
+ ')');
return send(this, buf);
};
SSH2Stream.prototype.channelOpenFail = function(remoteChan, reason, desc,
lang) {
if (typeof desc !== 'string')
desc = '';
if (typeof lang !== 'string')
lang = '';
var descLen = Buffer.byteLength(desc);
var langLen = Buffer.byteLength(lang);
var p = 9;
var buf = Buffer.allocUnsafe(1 + 4 + 4 + 4 + descLen + 4 + langLen);
buf[0] = MESSAGE.CHANNEL_OPEN_FAILURE;
writeUInt32BE(buf, remoteChan, 1);
writeUInt32BE(buf, reason, 5);
writeUInt32BE(buf, descLen, p);
p += 4;
if (descLen) {
buf.write(desc, p, descLen, 'utf8');
p += descLen;
}
writeUInt32BE(buf, langLen, p);
if (langLen)
buf.write(lang, p += 4, langLen, 'ascii');
this.debug('DEBUG: Outgoing: Writing CHANNEL_OPEN_FAILURE ('
+ remoteChan
+ ')');
return send(this, buf);
};
// Client-specific methods
// Global
SSH2Stream.prototype.service = function(svcName) {
if (this.server)
throw new Error('Client-only method called in server mode');
var svcNameLen = Buffer.byteLength(svcName);
var buf = Buffer.allocUnsafe(1 + 4 + svcNameLen);
buf[0] = MESSAGE.SERVICE_REQUEST;
writeUInt32BE(buf, svcNameLen, 1);
buf.write(svcName, 5, svcNameLen, 'ascii');
this.debug('DEBUG: Outgoing: Writing SERVICE_REQUEST (' + svcName + ')');
return send(this, buf);
};
// 'ssh-connection' service-specific
SSH2Stream.prototype.tcpipForward = function(bindAddr, bindPort, wantReply) {
if (this.server)
throw new Error('Client-only method called in server mode');
var addrlen = Buffer.byteLength(bindAddr);
var buf = Buffer.allocUnsafe(1 + 4 + 13 + 1 + 4 + addrlen + 4);
buf[0] = MESSAGE.GLOBAL_REQUEST;
writeUInt32BE(buf, 13, 1);
buf.write('tcpip-forward', 5, 13, 'ascii');
buf[18] = (wantReply === undefined || wantReply === true ? 1 : 0);
writeUInt32BE(buf, addrlen, 19);
buf.write(bindAddr, 23, addrlen, 'ascii');
writeUInt32BE(buf, bindPort, 23 + addrlen);
this.debug('DEBUG: Outgoing: Writing GLOBAL_REQUEST (tcpip-forward)');
return send(this, buf);
};
SSH2Stream.prototype.cancelTcpipForward = function(bindAddr, bindPort,
wantReply) {
if (this.server)
throw new Error('Client-only method called in server mode');
var addrlen = Buffer.byteLength(bindAddr);
var buf = Buffer.allocUnsafe(1 + 4 + 20 + 1 + 4 + addrlen + 4);
buf[0] = MESSAGE.GLOBAL_REQUEST;
writeUInt32BE(buf, 20, 1);
buf.write('cancel-tcpip-forward', 5, 20, 'ascii');
buf[25] = (wantReply === undefined || wantReply === true ? 1 : 0);
writeUInt32BE(buf, addrlen, 26);
buf.write(bindAddr, 30, addrlen, 'ascii');
writeUInt32BE(buf, bindPort, 30 + addrlen);
this.debug('DEBUG: Outgoing: Writing GLOBAL_REQUEST (cancel-tcpip-forward)');
return send(this, buf);
};
SSH2Stream.prototype.openssh_streamLocalForward = function(socketPath,
wantReply) {
if (this.server)
throw new Error('Client-only method called in server mode');
var pathlen = Buffer.byteLength(socketPath);
var buf = Buffer.allocUnsafe(1 + 4 + 31 + 1 + 4 + pathlen);
buf[0] = MESSAGE.GLOBAL_REQUEST;
writeUInt32BE(buf, 31, 1);
buf.write('streamlocal-forward@openssh.com', 5, 31, 'ascii');
buf[36] = (wantReply === undefined || wantReply === true ? 1 : 0);
writeUInt32BE(buf, pathlen, 37);
buf.write(socketPath, 41, pathlen, 'utf8');
this.debug('DEBUG: Outgoing: Writing GLOBAL_REQUEST (streamlocal-forward@openssh.com)');
return send(this, buf);
};
SSH2Stream.prototype.openssh_cancelStreamLocalForward = function(socketPath,
wantReply) {
if (this.server)
throw new Error('Client-only method called in server mode');
var pathlen = Buffer.byteLength(socketPath);
var buf = Buffer.allocUnsafe(1 + 4 + 38 + 1 + 4 + pathlen);
buf[0] = MESSAGE.GLOBAL_REQUEST;
writeUInt32BE(buf, 38, 1);
buf.write('cancel-streamlocal-forward@openssh.com', 5, 38, 'ascii');
buf[43] = (wantReply === undefined || wantReply === true ? 1 : 0);
writeUInt32BE(buf, pathlen, 44);
buf.write(socketPath, 48, pathlen, 'utf8');
this.debug('DEBUG: Outgoing: Writing GLOBAL_REQUEST (cancel-streamlocal-forward@openssh.com)');
return send(this, buf);
};
SSH2Stream.prototype.directTcpip = function(chan, initWindow, maxPacket, cfg) {
if (this.server)
throw new Error('Client-only method called in server mode');
var srclen = Buffer.byteLength(cfg.srcIP);
var dstlen = Buffer.byteLength(cfg.dstIP);
var p = 29;
var buf = Buffer.allocUnsafe(1 + 4 + 12 + 4 + 4 + 4 + 4 + srclen + 4 + 4
+ dstlen + 4);
buf[0] = MESSAGE.CHANNEL_OPEN;
writeUInt32BE(buf, 12, 1);
buf.write('direct-tcpip', 5, 12, 'ascii');
writeUInt32BE(buf, chan, 17);
writeUInt32BE(buf, initWindow, 21);
writeUInt32BE(buf, maxPacket, 25);
writeUInt32BE(buf, dstlen, p);
buf.write(cfg.dstIP, p += 4, dstlen, 'ascii');
writeUInt32BE(buf, cfg.dstPort, p += dstlen);
writeUInt32BE(buf, srclen, p += 4);
buf.write(cfg.srcIP, p += 4, srclen, 'ascii');
writeUInt32BE(buf, cfg.srcPort, p += srclen);
this.debug('DEBUG: Outgoing: Writing CHANNEL_OPEN ('
+ chan
+ ', direct-tcpip)');
return send(this, buf);
};
SSH2Stream.prototype.openssh_directStreamLocal = function(chan, initWindow,
maxPacket, cfg) {
if (this.server)
throw new Error('Client-only method called in server mode');
var pathlen = Buffer.byteLength(cfg.socketPath);
var p = 47;
var buf = Buffer.allocUnsafe(1 + 4 + 30 + 4 + 4 + 4 + 4 + pathlen + 4 + 4);
buf[0] = MESSAGE.CHANNEL_OPEN;
writeUInt32BE(buf, 30, 1);
buf.write('direct-streamlocal@openssh.com', 5, 30, 'ascii');
writeUInt32BE(buf, chan, 35);
writeUInt32BE(buf, initWindow, 39);
writeUInt32BE(buf, maxPacket, 43);
writeUInt32BE(buf, pathlen, p);
buf.write(cfg.socketPath, p += 4, pathlen, 'utf8');
// reserved fields (string and uint32)
buf.fill(0, buf.length - 8);
this.debug('DEBUG: Outgoing: Writing CHANNEL_OPEN ('
+ chan
+ ', direct-streamlocal@openssh.com)');
return send(this, buf);
};
SSH2Stream.prototype.openssh_noMoreSessions = function(wantReply) {
if (this.server)
throw new Error('Client-only method called in server mode');
var buf = Buffer.allocUnsafe(1 + 4 + 28 + 1);
buf[0] = MESSAGE.GLOBAL_REQUEST;
writeUInt32BE(buf, 28, 1);
buf.write('no-more-sessions@openssh.com', 5, 28, 'ascii');
buf[33] = (wantReply === undefined || wantReply === true ? 1 : 0);
this.debug('DEBUG: Outgoing: Writing GLOBAL_REQUEST (no-more-sessions@openssh.com)');
return send(this, buf);
};
SSH2Stream.prototype.session = function(chan, initWindow, maxPacket) {
if (this.server)
throw new Error('Client-only method called in server mode');
// Does not consume window space
var buf = Buffer.allocUnsafe(1 + 4 + 7 + 4 + 4 + 4);
buf[0] = MESSAGE.CHANNEL_OPEN;
writeUInt32BE(buf, 7, 1);
buf.write('session', 5, 7, 'ascii');
writeUInt32BE(buf, chan, 12);
writeUInt32BE(buf, initWindow, 16);
writeUInt32BE(buf, maxPacket, 20);
this.debug('DEBUG: Outgoing: Writing CHANNEL_OPEN ('
+ chan
+ ', session)');
return send(this, buf);
};
SSH2Stream.prototype.windowChange = function(chan, rows, cols, height, width) {
if (this.server)
throw new Error('Client-only method called in server mode');
// Does not consume window space
var buf = Buffer.allocUnsafe(1 + 4 + 4 + 13 + 1 + 4 + 4 + 4 + 4);
buf[0] = MESSAGE.CHANNEL_REQUEST;
writeUInt32BE(buf, chan, 1);
writeUInt32BE(buf, 13, 5);
buf.write('window-change', 9, 13, 'ascii');
buf[22] = 0;
writeUInt32BE(buf, cols, 23);
writeUInt32BE(buf, rows, 27);
writeUInt32BE(buf, width, 31);
writeUInt32BE(buf, height, 35);
this.debug('DEBUG: Outgoing: Writing CHANNEL_REQUEST ('
+ chan
+ ', window-change)');
return send(this, buf);
};
SSH2Stream.prototype.pty = function(chan, rows, cols, height,
width, term, modes, wantReply) {
if (this.server)
throw new Error('Client-only method called in server mode');
// Does not consume window space
if (!term || !term.length)
term = 'vt100';
if (modes
&& !Buffer.isBuffer(modes)
&& !Array.isArray(modes)
&& typeof modes === 'object')
modes = modesToBytes(modes);
if (!modes || !modes.length)
modes = NO_TERMINAL_MODES_BUFFER;
var termLen = term.length;
var modesLen = modes.length;
var p = 21;
var buf = Buffer.allocUnsafe(1 + 4 + 4 + 7 + 1 + 4 + termLen + 4 + 4 + 4 + 4
+ 4 + modesLen);
buf[0] = MESSAGE.CHANNEL_REQUEST;
writeUInt32BE(buf, chan, 1);
writeUInt32BE(buf, 7, 5);
buf.write('pty-req', 9, 7, 'ascii');
buf[16] = (wantReply === undefined || wantReply === true ? 1 : 0);
writeUInt32BE(buf, termLen, 17);
buf.write(term, 21, termLen, 'utf8');
writeUInt32BE(buf, cols, p += termLen);
writeUInt32BE(buf, rows, p += 4);
writeUInt32BE(buf, width, p += 4);
writeUInt32BE(buf, height, p += 4);
writeUInt32BE(buf, modesLen, p += 4);
p += 4;
if (Array.isArray(modes)) {
for (var i = 0; i < modesLen; ++i)
buf[p++] = modes[i];
} else if (Buffer.isBuffer(modes)) {
modes.copy(buf, p);
}
this.debug('DEBUG: Outgoing: Writing CHANNEL_REQUEST ('
+ chan
+ ', pty-req)');
return send(this, buf);
};
SSH2Stream.prototype.shell = function(chan, wantReply) {
if (this.server)
throw new Error('Client-only method called in server mode');
// Does not consume window space
var buf = Buffer.allocUnsafe(1 + 4 + 4 + 5 + 1);
buf[0] = MESSAGE.CHANNEL_REQUEST;
writeUInt32BE(buf, chan, 1);
writeUInt32BE(buf, 5, 5);
buf.write('shell', 9, 5, 'ascii');
buf[14] = (wantReply === undefined || wantReply === true ? 1 : 0);
this.debug('DEBUG: Outgoing: Writing CHANNEL_REQUEST ('
+ chan
+ ', shell)');
return send(this, buf);
};
SSH2Stream.prototype.exec = function(chan, cmd, wantReply) {
if (this.server)
throw new Error('Client-only method called in server mode');
// Does not consume window space
var cmdlen = (Buffer.isBuffer(cmd) ? cmd.length : Buffer.byteLength(cmd));
var buf = Buffer.allocUnsafe(1 + 4 + 4 + 4 + 1 + 4 + cmdlen);
buf[0] = MESSAGE.CHANNEL_REQUEST;
writeUInt32BE(buf, chan, 1);
writeUInt32BE(buf, 4, 5);
buf.write('exec', 9, 4, 'ascii');
buf[13] = (wantReply === undefined || wantReply === true ? 1 : 0);
writeUInt32BE(buf, cmdlen, 14);
if (Buffer.isBuffer(cmd))
cmd.copy(buf, 18);
else
buf.write(cmd, 18, cmdlen, 'utf8');
this.debug('DEBUG: Outgoing: Writing CHANNEL_REQUEST ('
+ chan
+ ', exec)');
return send(this, buf);
};
SSH2Stream.prototype.signal = function(chan, signal) {
if (this.server)
throw new Error('Client-only method called in server mode');
// Does not consume window space
signal = signal.toUpperCase();
if (signal.slice(0, 3) === 'SIG')
signal = signal.substring(3);
if (SIGNALS.indexOf(signal) === -1)
throw new Error('Invalid signal: ' + signal);
var signalLen = signal.length;
var buf = Buffer.allocUnsafe(1 + 4 + 4 + 6 + 1 + 4 + signalLen);
buf[0] = MESSAGE.CHANNEL_REQUEST;
writeUInt32BE(buf, chan, 1);
writeUInt32BE(buf, 6, 5);
buf.write('signal', 9, 6, 'ascii');
buf[15] = 0;
writeUInt32BE(buf, signalLen, 16);
buf.write(signal, 20, signalLen, 'ascii');
this.debug('DEBUG: Outgoing: Writing CHANNEL_REQUEST ('
+ chan
+ ', signal)');
return send(this, buf);
};
SSH2Stream.prototype.env = function(chan, key, val, wantReply) {
if (this.server)
throw new Error('Client-only method called in server mode');
// Does not consume window space
var keyLen = Buffer.byteLength(key);
var valLen = (Buffer.isBuffer(val) ? val.length : Buffer.byteLength(val));
var buf = Buffer.allocUnsafe(1 + 4 + 4 + 3 + 1 + 4 + keyLen + 4 + valLen);
buf[0] = MESSAGE.CHANNEL_REQUEST;
writeUInt32BE(buf, chan, 1);
writeUInt32BE(buf, 3, 5);
buf.write('env', 9, 3, 'ascii');
buf[12] = (wantReply === undefined || wantReply === true ? 1 : 0);
writeUInt32BE(buf, keyLen, 13);
buf.write(key, 17, keyLen, 'ascii');
writeUInt32BE(buf, valLen, 17 + keyLen);
if (Buffer.isBuffer(val))
val.copy(buf, 17 + keyLen + 4);
else
buf.write(val, 17 + keyLen + 4, valLen, 'utf8');
this.debug('DEBUG: Outgoing: Writing CHANNEL_REQUEST ('
+ chan
+ ', env)');
return send(this, buf);
};
SSH2Stream.prototype.x11Forward = function(chan, cfg, wantReply) {
if (this.server)
throw new Error('Client-only method called in server mode');
// Does not consume window space
var protolen = Buffer.byteLength(cfg.protocol);
var cookielen = Buffer.byteLength(cfg.cookie);
var buf = Buffer.allocUnsafe(1 + 4 + 4 + 7 + 1 + 1 + 4 + protolen + 4
+ cookielen + 4);
buf[0] = MESSAGE.CHANNEL_REQUEST;
writeUInt32BE(buf, chan, 1);
writeUInt32BE(buf, 7, 5);
buf.write('x11-req', 9, 7, 'ascii');
buf[16] = (wantReply === undefined || wantReply === true ? 1 : 0);
buf[17] = (cfg.single ? 1 : 0);
writeUInt32BE(buf, protolen, 18);
var bp = 22;
if (Buffer.isBuffer(cfg.protocol))
cfg.protocol.copy(buf, bp);
else
buf.write(cfg.protocol, bp, protolen, 'utf8');
bp += protolen;
writeUInt32BE(buf, cookielen, bp);
bp += 4;
if (Buffer.isBuffer(cfg.cookie))
cfg.cookie.copy(buf, bp);
else
buf.write(cfg.cookie, bp, cookielen, 'binary');
bp += cookielen;
writeUInt32BE(buf, (cfg.screen || 0), bp);
this.debug('DEBUG: Outgoing: Writing CHANNEL_REQUEST ('
+ chan
+ ', x11-req)');
return send(this, buf);
};
SSH2Stream.prototype.subsystem = function(chan, name, wantReply) {
if (this.server)
throw new Error('Client-only method called in server mode');
// Does not consume window space
var nameLen = Buffer.byteLength(name);
var buf = Buffer.allocUnsafe(1 + 4 + 4 + 9 + 1 + 4 + nameLen);
buf[0] = MESSAGE.CHANNEL_REQUEST;
writeUInt32BE(buf, chan, 1);
writeUInt32BE(buf, 9, 5);
buf.write('subsystem', 9, 9, 'ascii');
buf[18] = (wantReply === undefined || wantReply === true ? 1 : 0);
writeUInt32BE(buf, nameLen, 19);
buf.write(name, 23, nameLen, 'ascii');
this.debug('DEBUG: Outgoing: Writing CHANNEL_REQUEST ('
+ chan
+ ', subsystem: '
+ name
+ ')');
return send(this, buf);
};
SSH2Stream.prototype.openssh_agentForward = function(chan, wantReply) {
if (this.server)
throw new Error('Client-only method called in server mode');
// Does not consume window space
var buf = Buffer.allocUnsafe(1 + 4 + 4 + 26 + 1);
buf[0] = MESSAGE.CHANNEL_REQUEST;
writeUInt32BE(buf, chan, 1);
writeUInt32BE(buf, 26, 5);
buf.write('auth-agent-req@openssh.com', 9, 26, 'ascii');
buf[35] = (wantReply === undefined || wantReply === true ? 1 : 0);
this.debug('DEBUG: Outgoing: Writing CHANNEL_REQUEST ('
+ chan
+ ', auth-agent-req@openssh.com)');
return send(this, buf);
};
// 'ssh-userauth' service-specific
SSH2Stream.prototype.authPassword = function(username, password) {
if (this.server)
throw new Error('Client-only method called in server mode');
var userLen = Buffer.byteLength(username);
var passLen = Buffer.byteLength(password);
var p = 0;
var buf = Buffer.allocUnsafe(1
+ 4 + userLen
+ 4 + 14 // "ssh-connection"
+ 4 + 8 // "password"
+ 1
+ 4 + passLen);
buf[p] = MESSAGE.USERAUTH_REQUEST;
writeUInt32BE(buf, userLen, ++p);
buf.write(username, p += 4, userLen, 'utf8');
writeUInt32BE(buf, 14, p += userLen);
buf.write('ssh-connection', p += 4, 14, 'ascii');
writeUInt32BE(buf, 8, p += 14);
buf.write('password', p += 4, 8, 'ascii');
buf[p += 8] = 0;
writeUInt32BE(buf, passLen, ++p);
buf.write(password, p += 4, passLen, 'utf8');
this._state.authsQueue.push('password');
this.debug('DEBUG: Outgoing: Writing USERAUTH_REQUEST (password)');
return send(this, buf);
};
SSH2Stream.prototype.authPK = function(username, pubKey, cbSign) {
if (this.server)
throw new Error('Client-only method called in server mode');
var self = this;
var outstate = this._state.outgoing;
var keyType;
if (typeof pubKey.getPublicSSH === 'function') {
keyType = pubKey.ty