UNPKG

ssh2-streams

Version:

SSH2 and SFTP(v3) client/server protocol streams for node.js

1,592 lines (1,338 loc) 164 kB
// 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