Comparing version 0.2.5 to 0.2.6
@@ -15,2 +15,13 @@ var inherits = require('util').inherits, | ||
var CUSTOM_EVENTS = [ | ||
'CHANNEL_EOF', | ||
'CHANNEL_CLOSE', | ||
'CHANNEL_DATA', | ||
'CHANNEL_EXTENDED_DATA', | ||
'CHANNEL_WINDOW_ADJUST', | ||
'CHANNEL_SUCCESS', | ||
'CHANNEL_FAILURE', | ||
'CHANNEL_REQUEST' | ||
], CUSTOM_EVENTS_LEN = CUSTOM_EVENTS.length; | ||
function Channel(info, conn) { | ||
@@ -67,3 +78,10 @@ EventEmitter.call(this); | ||
conn.removeListener('drain', onDrain); | ||
stream._cleanup(); | ||
} | ||
for (var i = 0; i < CUSTOM_EVENTS_LEN; ++i) { | ||
// Since EventEmitters do not actually *delete* event names in the | ||
// emitter's event array, we must do this manually so as not to leak | ||
// our custom, channel-specific event names. | ||
delete conn._parser._events[CUSTOM_EVENTS[i] + ':' + self.incoming.id]; | ||
} | ||
}); | ||
@@ -499,10 +517,10 @@ | ||
this._decoder = undefined; | ||
channel._conn._sock.once('end', function() { | ||
this._sockOnEnd = function() { | ||
self.writable = false; | ||
self.readable = false; | ||
}); | ||
channel._conn._sock.once('close', function() { | ||
self.writable = false; | ||
self.readable = false; | ||
}); | ||
channel._conn._sock.removeListener('end', this._sockOnEnd); | ||
channel._conn._sock.removeListener('close', this._sockOnEnd); | ||
}; | ||
channel._conn._sock.once('end', this._sockOnEnd); | ||
channel._conn._sock.once('close', this._sockOnEnd); | ||
} | ||
@@ -672,2 +690,7 @@ inherits(ChannelStream, Stream); | ||
ret = this._channel.close(); | ||
this._cleanup(); | ||
return ret; | ||
}; | ||
ChannelStream.prototype._cleanup = function() { | ||
if (this._outbuffer.length) | ||
@@ -677,2 +700,4 @@ this._outbuffer = []; | ||
this._inbuffer = []; | ||
this._channel._conn._sock.removeListener('end', this._sockOnEnd); | ||
this._channel._conn._sock.removeListener('close', this._sockOnEnd); | ||
this.writable = false; | ||
@@ -683,3 +708,3 @@ this.readable = false; | ||
this._decoder = undefined; | ||
return ret; | ||
this._channel = undefined; | ||
}; | ||
@@ -686,0 +711,0 @@ |
@@ -5,2 +5,3 @@ // TODO: * Filter control codes from strings | ||
var crypto = require('crypto'); | ||
var StreamSearch = require('streamsearch'); | ||
var consts = require('./Parser.constants'); | ||
@@ -28,12 +29,9 @@ var inherits = require('util').inherits, | ||
// common byte arrays for matching purposes | ||
var EXP_BYTES_CRLF = bytes('\n'), | ||
EXP_BYTES_SSHHEADER = bytes('SSH-'); | ||
var EXP_TYPE_HEADER = 0, | ||
EXP_TYPE_LF = 1, | ||
EXP_TYPE_BYTES = 2; // waits until n bytes have been seen | ||
var EXP_TYPE_MATCH = 0, // waits for byte array match | ||
EXP_TYPE_BYTES = 1; // waits until n bytes have been seen | ||
function Parser() { | ||
this.debug = undefined; | ||
this._hmacBuf = new Buffer(9); | ||
this._hmacBufCompute = new Buffer(9); | ||
this.reset(); | ||
@@ -47,21 +45,16 @@ } | ||
var i = start, buffer, skipDecrypt = false, j, info, buf, lang, message; | ||
var i = start, buffer, skipDecrypt = false, j, info, buf, lang, message, | ||
self = this, p = i; | ||
while (true) { | ||
// begin expecting bytes handlers | ||
if (this._expectLen) { | ||
if (this._expectType !== undefined) { | ||
if (i >= end) | ||
break; | ||
// simple case: just counting n bytes | ||
if (this._expectType === EXP_TYPE_BYTES) { | ||
if (this._expectBuf) { | ||
this._expectBuf[this._expectPtr++] = b[i++]; | ||
if (this._expectPtr === this._expectLen) { | ||
if (this._expectPtr === this._expect) { | ||
buffer = this._expectBuf; | ||
this._expectBuf = undefined; | ||
this._expectBufLen = 0; | ||
this._expectPtr = 0; | ||
this._expectLen = undefined; | ||
this._expect = undefined; | ||
this._expectType = undefined; | ||
start = i; | ||
} | ||
@@ -71,90 +64,62 @@ } else | ||
continue; | ||
} | ||
// complex case: searching for byte array match | ||
if (b[i] === this._expect[this._expectPtr]) | ||
++this._expectPtr; | ||
else { | ||
if (this._expectPtr > 0) { | ||
if (i - start > 0) { | ||
buf = new Buffer(i - start); | ||
b.copy(buf, 0, start, start + (i - start)); | ||
} else { | ||
buf = new Buffer(this._expectPtr); | ||
for (j = 0; j < this._expectPtr; ++j) | ||
buf[j] = this._expect[j]; | ||
} else if (this._expectType === EXP_TYPE_HEADER) { | ||
this._ss.push(b); | ||
if (this._expectType !== undefined) | ||
continue; | ||
} else if (this._expectType === EXP_TYPE_LF) { | ||
if (b[i] === 0x0A) { | ||
this._expectType = undefined; | ||
if (p < i) { | ||
if (this._expectBuf === undefined) | ||
this._expectBuf = b.toString('ascii', p, i); | ||
else | ||
this._expectBuf += b.toString('ascii', p, i); | ||
} | ||
if (this._expectBuf !== undefined) { | ||
this._expectBuf.push(buf); | ||
this._expectBufLen += buf.length; | ||
} | ||
start = i; | ||
} | ||
this._expectPtr = 0; | ||
if (b[i] === this._expect[this._expectPtr]) | ||
++this._expectPtr; | ||
} | ||
++i; | ||
if (this._expectPtr < this._expectLen) { | ||
if (this._expectPtr === 0 && i === end) { | ||
if (this._expectBuf !== undefined) { | ||
buf = (start === 0 ? b : b.slice(start)); | ||
this._expectBuf.push(buf); | ||
this._expectBufLen += buf.length; | ||
} | ||
} | ||
continue; | ||
} else { | ||
var leftovers = i - this._expectLen - start; | ||
if (leftovers < 0) | ||
leftovers = 0; | ||
if (this._expectBuf !== undefined) { | ||
var expbuflen = this._expectBuf.length; | ||
if (expbuflen === 0) { | ||
if (leftovers) | ||
this._expectBuf = b.slice(start, start + leftovers); | ||
buffer = this._expectBuf; | ||
this._expectBuf = undefined; | ||
++i; | ||
} else { | ||
if (++i === end && p < i) { | ||
if (this._expectBuf === undefined) | ||
this._expectBuf = b.toString('ascii', p, i); | ||
else | ||
this._expectBuf = null; | ||
} else if (expbuflen === 1 && leftovers === 0) | ||
this._expectBuf = this._expectBuf[0]; | ||
else { | ||
buf = new Buffer(this._expectBufLen + leftovers); | ||
var pos = 0, len = this._expectBuf.length; | ||
for (j = 0; j < len; ++j) { | ||
this._expectBuf[j].copy(buf, pos); | ||
pos += this._expectBuf[j].length; | ||
} | ||
if (leftovers) | ||
b.copy(buf, pos, start, start + leftovers); | ||
this._expectBuf = buf; | ||
this._expectBuf += b.toString('ascii', p, i); | ||
} | ||
continue; | ||
} | ||
buffer = this._expectBuf; | ||
this._expectBuf = undefined; | ||
this._expectBufLen = 0; | ||
this._expectPtr = 0; | ||
this._expectLen = undefined; | ||
this._expect = undefined; | ||
this._expectType = undefined; | ||
start = i; | ||
} | ||
} | ||
// end expecting bytes handlers | ||
switch (this._state) { | ||
case STATE_INIT: | ||
if (this._state === STATE_INIT) { | ||
this.debug&&this.debug('DEBUG: Parser: STATE_INIT'); | ||
// retrieve all bytes that may come before the header | ||
this.expect(EXP_BYTES_SSHHEADER); | ||
this.expect(EXP_TYPE_HEADER); | ||
this._ss = new StreamSearch(new Buffer('SSH-')); | ||
this._ss.on('info', function onInfo(matched, data, start, end) { | ||
if (data) { | ||
if (this._greeting === undefined) | ||
this._greeting = data.toString('binary', start, end); | ||
else | ||
this._greeting += data.toString('binary', start, end); | ||
} | ||
if (matched) { | ||
if (end !== undefined) | ||
i = end; | ||
else | ||
i += 4; | ||
self._expectType = undefined; | ||
self._ss.removeListener('info', onInfo); | ||
} | ||
}); | ||
this._state = STATE_GREETING; | ||
break; | ||
case STATE_GREETING: | ||
} else if (this._state === STATE_GREETING) { | ||
this.debug&&this.debug('DEBUG: Parser: STATE_GREETING'); | ||
if (buffer && buffer.length) | ||
this._greeting = buffer; | ||
this._ss = undefined; | ||
// retrieve the identification bytes after the "SSH-" header | ||
this.expect(EXP_BYTES_CRLF); | ||
p = i; | ||
this.expect(EXP_TYPE_LF); | ||
this._state = STATE_HEADER; | ||
break; | ||
case STATE_HEADER: | ||
} else if (this._state === STATE_HEADER) { | ||
this.debug&&this.debug('DEBUG: Parser: STATE_HEADER'); | ||
buffer = buffer.toString('ascii').trim(); | ||
buffer = buffer.trim(); | ||
var idxDash = buffer.indexOf('-'), | ||
@@ -164,3 +129,3 @@ idxSpace = buffer.indexOf(' '); | ||
// RFC says greeting SHOULD be utf8 | ||
greeting: (this._greeting ? this._greeting.toString('utf8') : null), | ||
greeting: this._greeting,//(this._greeting ? this._greeting.toString('utf8') : null), | ||
ident_raw: 'SSH-' + buffer, | ||
@@ -183,11 +148,9 @@ versions: { | ||
this._state = STATE_PACKETBEFORE; | ||
break; | ||
case STATE_PACKETBEFORE: | ||
} else if (this._state === STATE_PACKETBEFORE) { | ||
this.debug&&this.debug('DEBUG: Parser: STATE_PACKETBEFORE (expecting ' + this._decryptSize + ')'); | ||
// wait for the right number of bytes so we can determine the incoming | ||
// packet length | ||
this.expect(this._decryptSize); | ||
this.expect(EXP_TYPE_BYTES, this._decryptSize, '_decryptBuf'); | ||
this._state = STATE_PACKET; | ||
break; | ||
case STATE_PACKET: | ||
} else if (this._state === STATE_PACKET) { | ||
this.debug&&this.debug('DEBUG: Parser: STATE_PACKET'); | ||
@@ -203,3 +166,3 @@ if (this._decrypt) | ||
// grab the rest of the packet | ||
this.expect(remainLen); | ||
this.expect(EXP_TYPE_BYTES, remainLen); | ||
this._state = STATE_PACKETDATA; | ||
@@ -214,4 +177,3 @@ } else if (remainLen < 0) | ||
} | ||
break; | ||
case STATE_PACKETDATA: | ||
} else if (this._state === STATE_PACKETDATA) { | ||
this.debug&&this.debug('DEBUG: Parser: STATE_PACKETDATA'); | ||
@@ -236,3 +198,3 @@ if (this._decrypt && !skipDecrypt) | ||
this.debug&&this.debug('DEBUG: Parser: hmacSize === ' + this._hmacSize); | ||
this.expect(this._hmacSize); | ||
this.expect(EXP_TYPE_BYTES, this._hmacSize, '_hmacBuf'); | ||
this._state = STATE_PACKETDATAVERIFY; | ||
@@ -244,4 +206,3 @@ this._packet = buf; | ||
buf = undefined; | ||
break; | ||
case STATE_PACKETDATAVERIFY: | ||
} else if (this._state === STATE_PACKETDATAVERIFY) { | ||
this.debug&&this.debug('DEBUG: Parser: STATE_PACKETDATAVERIFY'); | ||
@@ -256,4 +217,3 @@ // verify packet data integrity | ||
} | ||
break; | ||
case STATE_PACKETDATAAFTER: | ||
} else if (this._state === STATE_PACKETDATAAFTER) { | ||
if (this.debug) { | ||
@@ -272,391 +232,3 @@ if (this._payload[0] === 60) { | ||
} | ||
var payload = this._payload; | ||
if (++this._seqno > MAX_SEQNO) | ||
this._seqno = 0; | ||
// payload[0] === packet type | ||
switch (payload[0]) { | ||
case MESSAGE.IGNORE: | ||
/* | ||
byte SSH_MSG_IGNORE | ||
string data | ||
*/ | ||
break; | ||
case MESSAGE.DISCONNECT: | ||
/* | ||
byte SSH_MSG_DISCONNECT | ||
uint32 reason code | ||
string description in ISO-10646 UTF-8 encoding | ||
string language tag | ||
*/ | ||
var reason = payload.readUInt32BE(1, true), | ||
description = readString(payload, 5, 'utf8'); | ||
lang = readString(payload, payload._pos, 'ascii'); | ||
this.emit('DISCONNECT', DISCONNECT_REASON[reason], | ||
reason, description, lang); | ||
break; | ||
case MESSAGE.DEBUG: | ||
/* | ||
byte SSH_MSG_DEBUG | ||
boolean always_display | ||
string message in ISO-10646 UTF-8 encoding | ||
string language tag | ||
*/ | ||
message = readString(payload, 2, 'utf8'); | ||
lang = readString(payload, payload._pos, 'ascii'); | ||
this.emit('DEBUG', message, lang); | ||
break; | ||
case MESSAGE.KEXINIT: | ||
/* | ||
byte SSH_MSG_KEXINIT | ||
byte[16] cookie (random bytes) | ||
name-list kex_algorithms | ||
name-list server_host_key_algorithms | ||
name-list encryption_algorithms_client_to_server | ||
name-list encryption_algorithms_server_to_client | ||
name-list mac_algorithms_client_to_server | ||
name-list mac_algorithms_server_to_client | ||
name-list compression_algorithms_client_to_server | ||
name-list compression_algorithms_server_to_client | ||
name-list languages_client_to_server | ||
name-list languages_server_to_client | ||
boolean first_kex_packet_follows | ||
uint32 0 (reserved for future extension) | ||
*/ | ||
var init = this._kexinit_info = { | ||
algorithms: { | ||
kex: undefined, | ||
srvHostKey: undefined, | ||
cs: { | ||
encrypt: undefined, | ||
mac: undefined, | ||
compress: undefined | ||
}, | ||
sc: { | ||
encrypt: undefined, | ||
mac: undefined, | ||
compress: undefined | ||
} | ||
}, | ||
languages: { | ||
cs: undefined, | ||
sc: undefined | ||
} | ||
}; | ||
init.algorithms.kex = readList(payload, 17); | ||
init.algorithms.srvHostKey = readList(payload, payload._pos); | ||
init.algorithms.cs.encrypt = readList(payload, payload._pos); | ||
init.algorithms.sc.encrypt = readList(payload, payload._pos); | ||
init.algorithms.cs.mac = readList(payload, payload._pos); | ||
init.algorithms.sc.mac = readList(payload, payload._pos); | ||
init.algorithms.cs.compress = readList(payload, payload._pos); | ||
init.algorithms.sc.compress = readList(payload, payload._pos); | ||
init.languages.cs = readList(payload, payload._pos); | ||
init.languages.sc = readList(payload, payload._pos); | ||
this._kexinit = payload; | ||
this.emit('KEXINIT', init); | ||
break; | ||
case MESSAGE.KEXDH_REPLY: | ||
/* | ||
byte SSH_MSG_KEXDH_REPLY | ||
string server public host key and certificates (K_S) | ||
mpint f | ||
string signature of H | ||
*/ | ||
info = { | ||
hostkey: readString(payload, 1), | ||
hostkey_format: undefined, | ||
pubkey: readString(payload, payload._pos), | ||
sig: readString(payload, payload._pos), | ||
sig_format: undefined | ||
}; | ||
info.hostkey_format = readString(info.hostkey, 0, 'ascii'); | ||
info.sig_format = readString(info.sig, 0, 'ascii'); | ||
this.emit('KEXDH_REPLY', info); | ||
break; | ||
case MESSAGE.NEWKEYS: | ||
/* | ||
byte SSH_MSG_NEW_KEYS | ||
*/ | ||
this.emit('NEWKEYS'); | ||
break; | ||
case MESSAGE.SERVICE_ACCEPT: | ||
/* | ||
byte SSH_MSG_NEW_KEYS | ||
*/ | ||
var serviceName = readString(payload, 1, 'ascii'); | ||
this.emit('SERVICE_ACCEPT', serviceName); | ||
break; | ||
case MESSAGE.USERAUTH_SUCCESS: | ||
/* | ||
byte SSH_MSG_USERAUTH_SUCCESS | ||
*/ | ||
this.emit('USERAUTH_SUCCESS'); | ||
break; | ||
case MESSAGE.USERAUTH_FAILURE: | ||
/* | ||
byte SSH_MSG_USERAUTH_FAILURE | ||
name-list authentications that can continue | ||
boolean partial success | ||
*/ | ||
var auths = readString(payload, 1, 'ascii').split(','), | ||
partSuccess = (payload[payload._pos] !== 0); | ||
this.emit('USERAUTH_FAILURE', auths, partSuccess); | ||
break; | ||
case MESSAGE.USERAUTH_BANNER: | ||
/* | ||
byte SSH_MSG_USERAUTH_BANNER | ||
string message in ISO-10646 UTF-8 encoding | ||
string language tag | ||
*/ | ||
message = readString(payload, 1, 'utf8'); | ||
lang = readString(payload, payload._pos, 'utf8'); | ||
this.emit('USERAUTH_BANNER', message, lang); | ||
break; | ||
case 60: // user auth context-specific messages | ||
if (this._authMethod === 'password') { | ||
/* | ||
byte SSH_MSG_USERAUTH_PASSWD_CHANGEREQ | ||
string prompt in ISO-10646 UTF-8 encoding | ||
string language tag | ||
*/ | ||
message = readString(payload, 1, 'utf8'); | ||
lang = readString(payload, payload._pos, 'utf8'); | ||
this.emit('USERAUTH_PASSWD_CHANGEREQ', message, lang); | ||
} else if (this._authMethod === 'keyboard-interactive') { | ||
/* | ||
byte SSH_MSG_USERAUTH_INFO_REQUEST | ||
string name (ISO-10646 UTF-8) | ||
string instruction (ISO-10646 UTF-8) | ||
string language tag -- MAY be empty | ||
int num-prompts | ||
string prompt[1] (ISO-10646 UTF-8) | ||
boolean echo[1] | ||
... | ||
string prompt[num-prompts] (ISO-10646 UTF-8) | ||
boolean echo[num-prompts] | ||
*/ | ||
var name, instr, nprompts; | ||
name = readString(payload, 1, 'utf8'); | ||
instr = readString(payload, payload._pos, 'utf8'); | ||
lang = readString(payload, payload._pos, 'utf8'); | ||
nprompts = payload.readUInt32BE(payload._pos, true); | ||
payload._pos += 4; | ||
if (nprompts > 0) { | ||
var prompts = new Array(nprompts); | ||
for (var prompt = 0; prompt < nprompts; ++prompt) { | ||
prompts.push({ | ||
prompt: readString(payload, payload._pos, 'utf8'), | ||
echo: (payload[payload._pos++] !== 0) | ||
}); | ||
} | ||
this.emit('USERAUTH_INFO_REQUEST', name, instr, lang, prompts); | ||
} else | ||
this.emit('USERAUTH_INFO_REQUEST', name, instr, lang); | ||
} else if (this._authMethod === 'pubkey') { | ||
/* | ||
byte SSH_MSG_USERAUTH_PK_OK | ||
string public key algorithm name from the request | ||
string public key blob from the request | ||
*/ | ||
this.emit('USERAUTH_PK_OK'); | ||
} | ||
break; | ||
case MESSAGE.CHANNEL_OPEN: | ||
/* | ||
byte SSH_MSG_CHANNEL_OPEN | ||
string channel type in US-ASCII only | ||
uint32 sender channel | ||
uint32 initial window size | ||
uint32 maximum packet size | ||
.... channel type specific data follows | ||
*/ | ||
var chanType = readString(payload, 1, 'ascii'); | ||
if (chanType === 'forwarded-tcpip') { | ||
/* | ||
string address that was connected | ||
uint32 port that was connected | ||
string originator IP address | ||
uint32 originator port | ||
*/ | ||
var channel = { | ||
type: chanType, | ||
sender: payload.readUInt32BE(payload._pos, true), | ||
window: payload.readUInt32BE(payload._pos += 4, true), | ||
packetSize: payload.readUInt32BE(payload._pos += 4, true), | ||
data: { | ||
destIP: readString(payload, payload._pos += 4, 'ascii'), | ||
destPort: payload.readUInt32BE(payload._pos, true), | ||
srcIP: readString(payload, payload._pos += 4, 'ascii'), | ||
srcPort: payload.readUInt32BE(payload._pos, true) | ||
} | ||
}; | ||
this.emit('CHANNEL_OPEN', channel); | ||
} | ||
break; | ||
case MESSAGE.CHANNEL_OPEN_CONFIRMATION: | ||
/* | ||
byte SSH_MSG_CHANNEL_OPEN_CONFIRMATION | ||
uint32 recipient channel | ||
uint32 sender channel | ||
uint32 initial window size | ||
uint32 maximum packet size | ||
.... channel type specific data follows | ||
*/ | ||
// "The 'recipient channel' is the channel number given in the | ||
// original open request, and 'sender channel' is the channel number | ||
// allocated by the other side." | ||
info = { | ||
recipient: payload.readUInt32BE(1, true), | ||
sender: payload.readUInt32BE(5, true), | ||
window: payload.readUInt32BE(9, true), | ||
packetSize: payload.readUInt32BE(13, true), | ||
data: undefined | ||
}; | ||
if (payload.length > 17) | ||
info.data = payload.slice(17); | ||
this.emit('CHANNEL_OPEN_CONFIRMATION:' + info.recipient, info); | ||
break; | ||
case MESSAGE.CHANNEL_OPEN_FAILURE: | ||
/* | ||
byte SSH_MSG_CHANNEL_OPEN_FAILURE | ||
uint32 recipient channel | ||
uint32 reason code | ||
string description in ISO-10646 UTF-8 encoding | ||
string language tag | ||
*/ | ||
payload._pos = 9; | ||
info = { | ||
recipient: payload.readUInt32BE(1, true), | ||
reasonCode: payload.readUInt32BE(5, true), | ||
reason: undefined, | ||
description: readString(payload, payload._pos, 'utf8'), | ||
lang: readString(payload, payload._pos, 'utf8') | ||
}; | ||
info.reason = CHANNEL_OPEN_FAILURE[info.reasonCode]; | ||
this.emit('CHANNEL_OPEN_FAILURE:' + info.recipient, info); | ||
break; | ||
case MESSAGE.CHANNEL_DATA: | ||
/* | ||
byte SSH_MSG_CHANNEL_DATA | ||
uint32 recipient channel | ||
string data | ||
*/ | ||
this.emit('CHANNEL_DATA:' + payload.readUInt32BE(1, true), | ||
readString(payload, 5)); | ||
break; | ||
case MESSAGE.CHANNEL_EXTENDED_DATA: | ||
/* | ||
byte SSH_MSG_CHANNEL_EXTENDED_DATA | ||
uint32 recipient channel | ||
uint32 data_type_code | ||
string data | ||
*/ | ||
this.emit('CHANNEL_EXTENDED_DATA:' + payload.readUInt32BE(1, true), | ||
payload.readUInt32BE(5, true), | ||
readString(payload, 9)); | ||
break; | ||
case MESSAGE.CHANNEL_WINDOW_ADJUST: | ||
/* | ||
byte SSH_MSG_CHANNEL_WINDOW_ADJUST | ||
uint32 recipient channel | ||
uint32 bytes to add | ||
*/ | ||
this.emit('CHANNEL_WINDOW_ADJUST:' + payload.readUInt32BE(1, true), | ||
payload.readUInt32BE(5, true)); | ||
break; | ||
case MESSAGE.CHANNEL_SUCCESS: | ||
/* | ||
byte SSH_MSG_CHANNEL_SUCCESS | ||
uint32 recipient channel | ||
*/ | ||
this.emit('CHANNEL_SUCCESS:' + payload.readUInt32BE(1, true)); | ||
break; | ||
case MESSAGE.CHANNEL_FAILURE: | ||
/* | ||
byte SSH_MSG_CHANNEL_FAILURE | ||
uint32 recipient channel | ||
*/ | ||
this.emit('CHANNEL_FAILURE:' + payload.readUInt32BE(1, true)); | ||
break; | ||
case MESSAGE.CHANNEL_EOF: | ||
/* | ||
byte SSH_MSG_CHANNEL_EOF | ||
uint32 recipient channel | ||
*/ | ||
this.emit('CHANNEL_EOF:' + payload.readUInt32BE(1, true)); | ||
break; | ||
case MESSAGE.CHANNEL_CLOSE: | ||
/* | ||
byte SSH_MSG_CHANNEL_CLOSE | ||
uint32 recipient channel | ||
*/ | ||
this.emit('CHANNEL_CLOSE:' + payload.readUInt32BE(1, true)); | ||
break; | ||
case MESSAGE.CHANNEL_REQUEST: | ||
var recipient = payload.readUInt32BE(1, true), | ||
request = readString(payload, 5, 'ascii'); | ||
if (request === 'exit-status') { | ||
/* | ||
byte SSH_MSG_CHANNEL_REQUEST | ||
uint32 recipient channel | ||
string "exit-status" | ||
boolean FALSE | ||
uint32 exit_status | ||
*/ | ||
info = { | ||
recipient: recipient, | ||
request: request, | ||
code: payload.readUInt32BE(1 + payload._pos, true) | ||
}; | ||
this.emit('CHANNEL_REQUEST:' + recipient, info); | ||
} else if (request === 'exit-signal') { | ||
/* | ||
byte SSH_MSG_CHANNEL_REQUEST | ||
uint32 recipient channel | ||
string "exit-signal" | ||
boolean FALSE | ||
string signal name (without the "SIG" prefix) | ||
boolean core dumped | ||
string error message in ISO-10646 UTF-8 encoding | ||
string language tag | ||
*/ | ||
info = { | ||
recipient: recipient, | ||
request: request, | ||
signal: readString(payload, 1 + payload._pos, 'ascii'), | ||
coredump: (payload[payload._pos] !== 0), | ||
description: readString(payload, ++payload._pos, 'utf8'), | ||
lang: readString(payload, payload._pos, 'utf8') | ||
}; | ||
this.emit('CHANNEL_REQUEST:' + recipient, info); | ||
} | ||
break; | ||
case MESSAGE.REQUEST_SUCCESS: | ||
/* | ||
byte SSH_MSG_REQUEST_SUCCESS | ||
.... response specific data | ||
*/ | ||
if (payload.length > 1) | ||
this.emit('REQUEST_SUCCESS', payload.slice(1)); | ||
else | ||
this.emit('REQUEST_SUCCESS'); | ||
break; | ||
case MESSAGE.REQUEST_FAILURE: | ||
/* | ||
byte SSH_MSG_REQUEST_FAILURE | ||
*/ | ||
this.emit('REQUEST_FAILURE'); | ||
break; | ||
case MESSAGE.UNIMPLEMENTED: | ||
/* | ||
byte SSH_MSG_UNIMPLEMENTED | ||
uint32 packet sequence number of rejected message | ||
*/ | ||
// TODO | ||
break; | ||
default: | ||
} | ||
this.parsePacket(); | ||
if (this._state === STATE_INIT) { | ||
@@ -668,3 +240,2 @@ // we were reset due to some error/disagreement ? | ||
this._payload = undefined; | ||
break; | ||
} | ||
@@ -676,10 +247,394 @@ if (buffer !== undefined) | ||
Parser.prototype.parseKEXInit = function() { | ||
var payload = this._payload; | ||
/* | ||
byte SSH_MSG_KEXINIT | ||
byte[16] cookie (random bytes) | ||
name-list kex_algorithms | ||
name-list server_host_key_algorithms | ||
name-list encryption_algorithms_client_to_server | ||
name-list encryption_algorithms_server_to_client | ||
name-list mac_algorithms_client_to_server | ||
name-list mac_algorithms_server_to_client | ||
name-list compression_algorithms_client_to_server | ||
name-list compression_algorithms_server_to_client | ||
name-list languages_client_to_server | ||
name-list languages_server_to_client | ||
boolean first_kex_packet_follows | ||
uint32 0 (reserved for future extension) | ||
*/ | ||
var init = this._kexinit_info = { | ||
algorithms: { | ||
kex: undefined, | ||
srvHostKey: undefined, | ||
cs: { | ||
encrypt: undefined, | ||
mac: undefined, | ||
compress: undefined | ||
}, | ||
sc: { | ||
encrypt: undefined, | ||
mac: undefined, | ||
compress: undefined | ||
} | ||
}, | ||
languages: { | ||
cs: undefined, | ||
sc: undefined | ||
} | ||
}; | ||
init.algorithms.kex = readList(payload, 17); | ||
init.algorithms.srvHostKey = readList(payload, payload._pos); | ||
init.algorithms.cs.encrypt = readList(payload, payload._pos); | ||
init.algorithms.sc.encrypt = readList(payload, payload._pos); | ||
init.algorithms.cs.mac = readList(payload, payload._pos); | ||
init.algorithms.sc.mac = readList(payload, payload._pos); | ||
init.algorithms.cs.compress = readList(payload, payload._pos); | ||
init.algorithms.sc.compress = readList(payload, payload._pos); | ||
init.languages.cs = readList(payload, payload._pos); | ||
init.languages.sc = readList(payload, payload._pos); | ||
this._kexinit = payload; | ||
this.emit('KEXINIT', init); | ||
}; | ||
Parser.prototype.parseUserAuthMisc = function() { | ||
var payload = this._payload; | ||
if (this._authMethod === 'password') { | ||
/* | ||
byte SSH_MSG_USERAUTH_PASSWD_CHANGEREQ | ||
string prompt in ISO-10646 UTF-8 encoding | ||
string language tag | ||
*/ | ||
message = readString(payload, 1, 'utf8'); | ||
lang = readString(payload, payload._pos, 'utf8'); | ||
this.emit('USERAUTH_PASSWD_CHANGEREQ', message, lang); | ||
} else if (this._authMethod === 'keyboard-interactive') { | ||
/* | ||
byte SSH_MSG_USERAUTH_INFO_REQUEST | ||
string name (ISO-10646 UTF-8) | ||
string instruction (ISO-10646 UTF-8) | ||
string language tag -- MAY be empty | ||
int num-prompts | ||
string prompt[1] (ISO-10646 UTF-8) | ||
boolean echo[1] | ||
... | ||
string prompt[num-prompts] (ISO-10646 UTF-8) | ||
boolean echo[num-prompts] | ||
*/ | ||
var name, instr, nprompts; | ||
name = readString(payload, 1, 'utf8'); | ||
instr = readString(payload, payload._pos, 'utf8'); | ||
lang = readString(payload, payload._pos, 'utf8'); | ||
nprompts = payload.readUInt32BE(payload._pos, true); | ||
payload._pos += 4; | ||
if (nprompts > 0) { | ||
var prompts = new Array(nprompts); | ||
for (var prompt = 0; prompt < nprompts; ++prompt) { | ||
prompts.push({ | ||
prompt: readString(payload, payload._pos, 'utf8'), | ||
echo: (payload[payload._pos++] !== 0) | ||
}); | ||
} | ||
this.emit('USERAUTH_INFO_REQUEST', name, instr, lang, prompts); | ||
} else | ||
this.emit('USERAUTH_INFO_REQUEST', name, instr, lang); | ||
} else if (this._authMethod === 'pubkey') { | ||
/* | ||
byte SSH_MSG_USERAUTH_PK_OK | ||
string public key algorithm name from the request | ||
string public key blob from the request | ||
*/ | ||
this.emit('USERAUTH_PK_OK'); | ||
} | ||
}; | ||
Parser.prototype.parseChRequest = function() { | ||
var payload = this._payload; | ||
var recipient = payload.readUInt32BE(1, true), | ||
request = readString(payload, 5, 'ascii'); | ||
if (request === 'exit-status') { | ||
/* | ||
byte SSH_MSG_CHANNEL_REQUEST | ||
uint32 recipient channel | ||
string "exit-status" | ||
boolean FALSE | ||
uint32 exit_status | ||
*/ | ||
info = { | ||
recipient: recipient, | ||
request: request, | ||
code: payload.readUInt32BE(1 + payload._pos, true) | ||
}; | ||
this.emit('CHANNEL_REQUEST:' + recipient, info); | ||
} else if (request === 'exit-signal') { | ||
/* | ||
byte SSH_MSG_CHANNEL_REQUEST | ||
uint32 recipient channel | ||
string "exit-signal" | ||
boolean FALSE | ||
string signal name (without the "SIG" prefix) | ||
boolean core dumped | ||
string error message in ISO-10646 UTF-8 encoding | ||
string language tag | ||
*/ | ||
info = { | ||
recipient: recipient, | ||
request: request, | ||
signal: readString(payload, 1 + payload._pos, 'ascii'), | ||
coredump: (payload[payload._pos] !== 0), | ||
description: readString(payload, ++payload._pos, 'utf8'), | ||
lang: readString(payload, payload._pos, 'utf8') | ||
}; | ||
this.emit('CHANNEL_REQUEST:' + recipient, info); | ||
} | ||
}; | ||
Parser.prototype.parsePacket = function() { | ||
var payload = this._payload; | ||
if (++this._seqno > MAX_SEQNO) | ||
this._seqno = 0; | ||
// payload[0] === packet type | ||
var type = payload[0]; | ||
if (type === MESSAGE.IGNORE) { | ||
/* | ||
byte SSH_MSG_IGNORE | ||
string data | ||
*/ | ||
} else if (type === MESSAGE.DISCONNECT) { | ||
/* | ||
byte SSH_MSG_DISCONNECT | ||
uint32 reason code | ||
string description in ISO-10646 UTF-8 encoding | ||
string language tag | ||
*/ | ||
var reason = payload.readUInt32BE(1, true), | ||
description = readString(payload, 5, 'utf8'); | ||
lang = readString(payload, payload._pos, 'ascii'); | ||
this.emit('DISCONNECT', DISCONNECT_REASON[reason], | ||
reason, description, lang); | ||
} else if (type === MESSAGE.DEBUG) { | ||
/* | ||
byte SSH_MSG_DEBUG | ||
boolean always_display | ||
string message in ISO-10646 UTF-8 encoding | ||
string language tag | ||
*/ | ||
message = readString(payload, 2, 'utf8'); | ||
lang = readString(payload, payload._pos, 'ascii'); | ||
this.emit('DEBUG', message, lang); | ||
} else if (type === MESSAGE.KEXINIT) | ||
this.parseKEXInit(); | ||
else if (type === MESSAGE.KEXDH_REPLY) { | ||
/* | ||
byte SSH_MSG_KEXDH_REPLY | ||
string server public host key and certificates (K_S) | ||
mpint f | ||
string signature of H | ||
*/ | ||
info = { | ||
hostkey: readString(payload, 1), | ||
hostkey_format: undefined, | ||
pubkey: readString(payload, payload._pos), | ||
sig: readString(payload, payload._pos), | ||
sig_format: undefined | ||
}; | ||
info.hostkey_format = readString(info.hostkey, 0, 'ascii'); | ||
info.sig_format = readString(info.sig, 0, 'ascii'); | ||
this.emit('KEXDH_REPLY', info); | ||
} else if (type === MESSAGE.NEWKEYS) { | ||
/* | ||
byte SSH_MSG_NEW_KEYS | ||
*/ | ||
this.emit('NEWKEYS'); | ||
} else if (type === MESSAGE.SERVICE_ACCEPT) { | ||
/* | ||
byte SSH_MSG_NEW_KEYS | ||
*/ | ||
var serviceName = readString(payload, 1, 'ascii'); | ||
this.emit('SERVICE_ACCEPT', serviceName); | ||
} else if (type === MESSAGE.USERAUTH_SUCCESS) { | ||
/* | ||
byte SSH_MSG_USERAUTH_SUCCESS | ||
*/ | ||
this.emit('USERAUTH_SUCCESS'); | ||
} else if (type === MESSAGE.USERAUTH_FAILURE) { | ||
/* | ||
byte SSH_MSG_USERAUTH_FAILURE | ||
name-list authentications that can continue | ||
boolean partial success | ||
*/ | ||
var auths = readString(payload, 1, 'ascii').split(','), | ||
partSuccess = (payload[payload._pos] !== 0); | ||
this.emit('USERAUTH_FAILURE', auths, partSuccess); | ||
} else if (type === MESSAGE.USERAUTH_BANNER) { | ||
/* | ||
byte SSH_MSG_USERAUTH_BANNER | ||
string message in ISO-10646 UTF-8 encoding | ||
string language tag | ||
*/ | ||
message = readString(payload, 1, 'utf8'); | ||
lang = readString(payload, payload._pos, 'utf8'); | ||
this.emit('USERAUTH_BANNER', message, lang); | ||
} else if (type === 60) // user auth context-specific messages | ||
this.parseUserAuthMisc(); | ||
else if (type === MESSAGE.CHANNEL_OPEN) { | ||
/* | ||
byte SSH_MSG_CHANNEL_OPEN | ||
string channel type in US-ASCII only | ||
uint32 sender channel | ||
uint32 initial window size | ||
uint32 maximum packet size | ||
.... channel type specific data follows | ||
*/ | ||
var chanType = readString(payload, 1, 'ascii'); | ||
if (chanType === 'forwarded-tcpip') { | ||
/* | ||
string address that was connected | ||
uint32 port that was connected | ||
string originator IP address | ||
uint32 originator port | ||
*/ | ||
var channel = { | ||
type: chanType, | ||
sender: payload.readUInt32BE(payload._pos, true), | ||
window: payload.readUInt32BE(payload._pos += 4, true), | ||
packetSize: payload.readUInt32BE(payload._pos += 4, true), | ||
data: { | ||
destIP: readString(payload, payload._pos += 4, 'ascii'), | ||
destPort: payload.readUInt32BE(payload._pos, true), | ||
srcIP: readString(payload, payload._pos += 4, 'ascii'), | ||
srcPort: payload.readUInt32BE(payload._pos, true) | ||
} | ||
}; | ||
this.emit('CHANNEL_OPEN', channel); | ||
} | ||
} else if (type === MESSAGE.CHANNEL_OPEN_CONFIRMATION) { | ||
/* | ||
byte SSH_MSG_CHANNEL_OPEN_CONFIRMATION | ||
uint32 recipient channel | ||
uint32 sender channel | ||
uint32 initial window size | ||
uint32 maximum packet size | ||
.... channel type specific data follows | ||
*/ | ||
// "The 'recipient channel' is the channel number given in the | ||
// original open request, and 'sender channel' is the channel number | ||
// allocated by the other side." | ||
info = { | ||
recipient: payload.readUInt32BE(1, true), | ||
sender: payload.readUInt32BE(5, true), | ||
window: payload.readUInt32BE(9, true), | ||
packetSize: payload.readUInt32BE(13, true), | ||
data: undefined | ||
}; | ||
if (payload.length > 17) | ||
info.data = payload.slice(17); | ||
this.emit('CHANNEL_OPEN_CONFIRMATION:' + info.recipient, info); | ||
} else if (type === MESSAGE.CHANNEL_OPEN_FAILURE) { | ||
/* | ||
byte SSH_MSG_CHANNEL_OPEN_FAILURE | ||
uint32 recipient channel | ||
uint32 reason code | ||
string description in ISO-10646 UTF-8 encoding | ||
string language tag | ||
*/ | ||
payload._pos = 9; | ||
info = { | ||
recipient: payload.readUInt32BE(1, true), | ||
reasonCode: payload.readUInt32BE(5, true), | ||
reason: undefined, | ||
description: readString(payload, payload._pos, 'utf8'), | ||
lang: readString(payload, payload._pos, 'utf8') | ||
}; | ||
info.reason = CHANNEL_OPEN_FAILURE[info.reasonCode]; | ||
this.emit('CHANNEL_OPEN_FAILURE:' + info.recipient, info); | ||
} else if (type === MESSAGE.CHANNEL_DATA) { | ||
/* | ||
byte SSH_MSG_CHANNEL_DATA | ||
uint32 recipient channel | ||
string data | ||
*/ | ||
this.emit('CHANNEL_DATA:' + payload.readUInt32BE(1, true), | ||
readString(payload, 5)); | ||
} else if (type === MESSAGE.CHANNEL_EXTENDED_DATA) { | ||
/* | ||
byte SSH_MSG_CHANNEL_EXTENDED_DATA | ||
uint32 recipient channel | ||
uint32 data_type_code | ||
string data | ||
*/ | ||
this.emit('CHANNEL_EXTENDED_DATA:' + payload.readUInt32BE(1, true), | ||
payload.readUInt32BE(5, true), | ||
readString(payload, 9)); | ||
} else if (type === MESSAGE.CHANNEL_WINDOW_ADJUST) { | ||
/* | ||
byte SSH_MSG_CHANNEL_WINDOW_ADJUST | ||
uint32 recipient channel | ||
uint32 bytes to add | ||
*/ | ||
this.emit('CHANNEL_WINDOW_ADJUST:' + payload.readUInt32BE(1, true), | ||
payload.readUInt32BE(5, true)); | ||
} else if (type === MESSAGE.CHANNEL_SUCCESS) { | ||
/* | ||
byte SSH_MSG_CHANNEL_SUCCESS | ||
uint32 recipient channel | ||
*/ | ||
this.emit('CHANNEL_SUCCESS:' + payload.readUInt32BE(1, true)); | ||
} else if (type === MESSAGE.CHANNEL_FAILURE) { | ||
/* | ||
byte SSH_MSG_CHANNEL_FAILURE | ||
uint32 recipient channel | ||
*/ | ||
this.emit('CHANNEL_FAILURE:' + payload.readUInt32BE(1, true)); | ||
} else if (type === MESSAGE.CHANNEL_EOF) { | ||
/* | ||
byte SSH_MSG_CHANNEL_EOF | ||
uint32 recipient channel | ||
*/ | ||
this.emit('CHANNEL_EOF:' + payload.readUInt32BE(1, true)); | ||
} else if (type === MESSAGE.CHANNEL_CLOSE) { | ||
/* | ||
byte SSH_MSG_CHANNEL_CLOSE | ||
uint32 recipient channel | ||
*/ | ||
this.emit('CHANNEL_CLOSE:' + payload.readUInt32BE(1, true)); | ||
} else if (type === MESSAGE.CHANNEL_REQUEST) | ||
this.parseChRequest(); | ||
else if (type === MESSAGE.REQUEST_SUCCESS) { | ||
/* | ||
byte SSH_MSG_REQUEST_SUCCESS | ||
.... response specific data | ||
*/ | ||
if (payload.length > 1) | ||
this.emit('REQUEST_SUCCESS', payload.slice(1)); | ||
else | ||
this.emit('REQUEST_SUCCESS'); | ||
} else if (type === MESSAGE.REQUEST_FAILURE) { | ||
/* | ||
byte SSH_MSG_REQUEST_FAILURE | ||
*/ | ||
this.emit('REQUEST_FAILURE'); | ||
} else if (type === MESSAGE.UNIMPLEMENTED) { | ||
/* | ||
byte SSH_MSG_UNIMPLEMENTED | ||
uint32 packet sequence number of rejected message | ||
*/ | ||
// TODO | ||
} | ||
}; | ||
Parser.prototype.hmacVerify = function(hmac) { | ||
var calcHmac = crypto.createHmac(SSH_TO_OPENSSL[this._hmac], this._hmacKey); | ||
this._hmacBuf.writeUInt32BE(this._seqno, 0, true); | ||
this._hmacBuf.writeUInt32BE(this._pktLen, 4, true); | ||
this._hmacBuf[8] = this._padLen; | ||
this._hmacBufCompute.writeUInt32BE(this._seqno, 0, true); | ||
this._hmacBufCompute.writeUInt32BE(this._pktLen, 4, true); | ||
this._hmacBufCompute[8] = this._padLen; | ||
calcHmac.update(this._hmacBuf); | ||
calcHmac.update(this._hmacBufCompute); | ||
calcHmac.update(this._packet); | ||
@@ -691,16 +646,13 @@ | ||
Parser.prototype.decrypt = function(data) { | ||
var ret = new Buffer(this._decrypt.update(data, 'binary', 'binary'), 'binary'); | ||
return ret; | ||
return new Buffer(this._decrypt.update(data, 'binary', 'binary'), 'binary'); | ||
}; | ||
Parser.prototype.expect = function(what) { | ||
this._expect = what; | ||
this._expectType = (Array.isArray(what) ? EXP_TYPE_MATCH : EXP_TYPE_BYTES); | ||
this._expectLen = (Array.isArray(what) ? what.length : what); | ||
Parser.prototype.expect = function(type, amount, bufferKey) { | ||
this._expect = amount; | ||
this._expectType = type; | ||
this._expectPtr = 0; | ||
if (Array.isArray(what)) | ||
this._expectBuf = []; | ||
else | ||
this._expectBuf = new Buffer(what); | ||
this._expectBufLen = 0; | ||
if (bufferKey && this[bufferKey]) | ||
this._expectBuf = this[bufferKey]; | ||
else if (amount) | ||
this._expectBuf = new Buffer(amount); | ||
}; | ||
@@ -712,10 +664,10 @@ | ||
this._expectType = undefined; | ||
this._expectLen = undefined; | ||
this._expectPtr = 0; | ||
this._expectBuf = undefined; | ||
this._expectBufLen = 0; | ||
this._ss = undefined; | ||
this._greeting = undefined; | ||
this._decryptSize = 8; | ||
this._decrypt = false; | ||
this._decryptBuf = undefined; | ||
this._authMethod = undefined; | ||
@@ -727,2 +679,3 @@ | ||
this._payload = undefined; | ||
this._hmacBuf = undefined; | ||
this._hmacSize = undefined; | ||
@@ -735,9 +688,2 @@ this._packet = undefined; | ||
function bytes(str) { | ||
var ret = new Array(str.length); | ||
for (var i = 0, len = ret.length; i < len; ++i) | ||
ret[i] = str.charCodeAt(i); | ||
return ret; | ||
} | ||
function readString(buffer, start, encoding) { | ||
@@ -744,0 +690,0 @@ start || (start = 0); |
@@ -202,3 +202,3 @@ var EventEmitter = require('events').EventEmitter, | ||
throw new Error('buffer is not a Buffer'); | ||
else if (offset >= buffer.length) | ||
else if (offset > buffer.length) | ||
throw new Error('offset is out of bounds'); | ||
@@ -748,3 +748,3 @@ else if (offset + length > buffer.length) | ||
return this.fsetstat(handle, { | ||
permissions: mode | ||
mode: mode | ||
}, cb); | ||
@@ -755,3 +755,3 @@ }; | ||
return this.setstat(path, { | ||
permissions: mode | ||
mode: mode | ||
}, cb); | ||
@@ -758,0 +758,0 @@ }; |
@@ -17,3 +17,3 @@ | ||
Stats.prototype._checkModeProperty = function(property) { | ||
return ((this.permissions & constants.S_IFMT) === property); | ||
return ((this.mode & constants.S_IFMT) === property); | ||
}; | ||
@@ -20,0 +20,0 @@ |
{ "name": "ssh2", | ||
"version": "0.2.5", | ||
"version": "0.2.6", | ||
"author": "Brian White <mscdex@mscdex.net>", | ||
@@ -7,5 +7,8 @@ "description": "An SSH2 client module written in pure JavaScript for node.js", | ||
"engines": { "node": ">=0.8.7" }, | ||
"keywords": [ "ssh", "ssh2", "sftp", "secure", "shell", "ftp", "exec", "remote", "client" ], | ||
"dependencies": { | ||
"streamsearch": "*" | ||
}, | ||
"keywords": [ "ssh", "ssh2", "sftp", "secure", "shell", "exec", "remote", "client" ], | ||
"licenses": [ { "type": "MIT", "url": "http://github.com/mscdex/ssh2/raw/master/LICENSE" } ], | ||
"repository" : { "type": "git", "url": "http://github.com/mscdex/ssh2.git" } | ||
} |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
5338
262890
1
1
+ Addedstreamsearch@*
+ Addedstreamsearch@1.1.0(transitive)