Join our webinar on Wednesday, June 26, at 1pm EDTHow Chia Mitigates Risk in the Crypto Industry.Register
Socket
Socket
Sign inDemoInstall

ssh2

Package Overview
Dependencies
0
Maintainers
1
Versions
104
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.0.1 to 0.0.2

311

lib/Channel.js

@@ -7,8 +7,8 @@ var inherits = require('util').inherits,

for (var i=0,keys=Object.keys(consts),len=keys.length; i<len; ++i)
global[keys[i]] = consts[keys[i]];
var MAX_WINDOW = Math.pow(2, 32) - 1,
SIGNALS = ['ABRT', 'ALRM', 'FPE', 'HUP', 'ILL', 'INT', 'KILL', 'PIPE',
'QUIT', 'SEGV', 'TERM', 'USR1', 'USR2'];
'QUIT', 'SEGV', 'TERM', 'USR1', 'USR2'],
MESSAGE = consts.MESSAGE,
CHANNEL_EXTENDED_DATATYPE = consts.CHANNEL_EXTENDED_DATATYPE,
TERMINAL_MODE = consts.TERMINAL_MODE;

@@ -21,2 +21,3 @@ function Channel(info, conn) {

this.type = info.type;
this.subtype = undefined;
/*

@@ -37,20 +38,23 @@ incoming and outgoing contain these properties:

this._callbacks = [];
this._queue = []; // for buffering outoing messages during key (re-)exchanges
conn._parser.on('CHANNEL_EOF:' + this.incoming.id, function() {
this.incoming.state = 'eof';
if (self._stream)
self.incoming.state = 'eof';
if (self._stream) {
self._stream.emit('end');
if (!self._stream.allowHalfOpen)
self.close();
}
});
conn._parser.on('CHANNEL_CLOSE:' + this.incoming.id, function() {
this.incoming.state = 'closed';
if (self.outgoing.state !== 'closed') {
self.incoming.state = 'closed';
if (self.outgoing.state === 'open' || self.outgoing.state === 'eof')
self.close();
self._conn.channels.splice(self._conn.channels.indexOf(self.incoming.id), 1);
if (self._stream) {
var stream = self._stream;
self._stream = undefined;
stream.emit('close');
}
if (self.outgoing.state === 'closing')
self.outgoing.state = 'closed';
conn._channels.splice(conn._channels.indexOf(self.incoming.id), 1);
if (self._stream) {
var stream = self._stream;
self._stream = undefined;
stream.emit('close');
}

@@ -101,15 +105,10 @@ });

conn.on('_reexchg', function() {
self._processQueue();
conn.on('drain', function() {
if (self._stream && !self._stream.paused)
self._stream.emit('drain');
});
}
Channel.prototype.eof = function(go) {
if (!this._conn._sock.writable)
return null;
else if (!go && this._conn._state !== 'authenticated') {
this._enqueue(['eof']);
return false;
}
if (this._conn._sock.writable && this.outgoing.state === 'open') {
Channel.prototype.eof = function() {
if (this.outgoing.state === 'open') {
// Note: CHANNEL_EOF does not consume window space

@@ -121,19 +120,12 @@ /*

var buf = new Buffer(1 + 4);
this.outgoing.state = 'EOF';
this.outgoing.state = 'eof';
buf[0] = MESSAGE.CHANNEL_EOF;
buf.writeUInt32BE(this.outgoing.id, 1, true);
this._conn._send(buf);
return true;
return this._conn._send(buf);
} else
return false;
return;
};
Channel.prototype.close = function(go) {
if (!this._conn._sock.writable)
return null;
else if (!go && this._conn._state !== 'authenticated') {
this._enqueue(['close']);
return false;
}
if (this.outgoing.state !== 'closed') {
Channel.prototype.close = function() {
if (this.outgoing.state === 'open' || this.outgoing.state === 'eof') {
// Note: CHANNEL_CLOSE does not consume window space

@@ -147,16 +139,9 @@ /*

buf.writeUInt32BE(this.outgoing.id, 1, true);
this.outgoing.state = 'closed';
this._conn._send(buf);
return true;
this.outgoing.state = 'closing';
return this._conn._send(buf);
} else
return false;
return;
};
Channel.prototype._sendTermSizeChg = function(cols, rows, width, height, go) {
if (!this._conn._sock.writable)
return null;
else if (!go && this._conn._state !== 'authenticated') {
this._enqueue(['_sendTermSizeChg', cols, rows, width, height]);
return false;
}
Channel.prototype._sendTermSizeChg = function(rows, cols, height, width) {
// Note: CHANNEL_REQUEST does not consume window space

@@ -184,15 +169,7 @@ /*

this._conn._send(buf);
return true;
return this._conn._send(buf);
};
Channel.prototype._sendPtyReq = function(rows, cols, height, width, term, modes,
cb, go) {
if (!this._conn._sock.writable)
return null;
else if (!go && this._conn._state !== 'authenticated') {
this._enqueue(['_sendPtyReq', rows, cols, height, width, term, modes, cb]);
return false;
}
cb) {
// Note: CHANNEL_REQUEST does not consume window space

@@ -241,14 +218,6 @@ /*

this._conn._send(buf);
return true;
return this._conn._send(buf);
};
Channel.prototype._sendShell = function(cb, go) {
if (!this._conn._sock.writable)
return null;
else if (!go && this._conn._state !== 'authenticated') {
this._enqueue(['_sendShell', cb]);
return false;
}
Channel.prototype._sendShell = function(cb) {
// Note: CHANNEL_REQUEST does not consume window space

@@ -269,26 +238,14 @@ /*

this._stream = new ChannelStream(this);
cb(undefined, this._stream);
this._callbacks.push(function(had_err) {
if (had_err) {
var stream = self._stream;
self._stream = undefined;
stream.emit('error', new Error('Error: Unable to open shell'));
stream.emit('close', true);
}
if (had_err)
return cb(new Error('Error: Unable to open shell'));
self.subtype = 'shell';
self._stream = new ChannelStream(self);
cb(undefined, self._stream);
});
this._conn._send(buf);
return true;
return this._conn._send(buf);
};
Channel.prototype._sendExec = function(cmd, cb, go) {
if (!this._conn._sock.writable)
return null;
else if (!go && this._conn._state !== 'authenticated') {
this._enqueue(['_sendExec', cmd, cb]);
return false;
}
Channel.prototype._sendExec = function(cmd, cb) {
// Note: CHANNEL_REQUEST does not consume window space

@@ -309,3 +266,3 @@ /*

buf.write('exec', 9, 4, 'ascii');
buf[13] = 0;
buf[13] = 1;
buf.writeUInt32BE(cmdlen, 14, true);

@@ -317,17 +274,14 @@ if (Buffer.isBuffer(cmd))

this._stream = new ChannelStream(this);
cb(undefined, this._stream);
this._callbacks.push(function(had_err) {
if (had_err)
return cb(new Error('Error: Unable to exec'));
self.subtype = 'exec';
self._stream = new ChannelStream(self);
cb(undefined, self._stream);
});
this._conn._send(buf);
return true;
return this._conn._send(buf);
};
Channel.prototype._sendSignal = function(signal, go) {
if (!this._conn._sock.writable)
return null;
else if (!go && this._conn._state !== 'authenticated') {
this._enqueue(['_sendSignal', signal]);
return false;
}
Channel.prototype._sendSignal = function(signal) {
// Note: CHANNEL_REQUEST does not consume window space

@@ -342,6 +296,6 @@ /*

signal = signal.toUpperCase();
if (signal.length > 3
if (signal.length >= 3
&& signal[0] === 'S' && signal[1] === 'I' && signal[2] === 'G')
signal = signal.substr(3);
if (~SIGNALS.indexOf(signal))
if (SIGNALS.indexOf(signal) === -1)
throw new Error('Invalid signal: ' + signal);

@@ -358,15 +312,7 @@ var signalLen = signal.length,

this._conn._send(buf);
return true;
return this._conn._send(buf);
};
Channel.prototype._sendEnv = function(env, go) {
if (!this._conn._sock.writable)
return null;
else if (!go && this._conn._state !== 'authenticated') {
this._enqueue(['_sendEnv', env]);
return false;
}
var keys, buf;
Channel.prototype._sendEnv = function(env) {
var keys, buf, ret = true;
if (env && (keys = Object.keys(env)).length > 0) {

@@ -401,17 +347,12 @@ // Note: CHANNEL_REQUEST does not consume window space

buf.write(env[keys[i]], 18 + klen + 4, vlen, 'utf8');
this._conn._send(buf);
ret = this._conn._send(buf);
}
return true;
return ret;
} else
return false;
return;
};
Channel.prototype._sendData = function(data, extendedType, go) {
if (!this._conn._sock.writable)
return null;
else if (!go && this._conn._state !== 'authenticated') {
this._enqueue(['_sendData', data, extendedType]);
return false;
}
var len = data.length, p = 0, buf, sliceLen;
Channel.prototype._sendData = function(data, extendedType) {
var len = data.length, p = 0, buf, sliceLen, ret;
while (len - p > 0) {

@@ -449,13 +390,9 @@ if (this.outgoing.window === 0)

return this._conn._send(buf);
ret = this._conn._send(buf);
}
return ret;
};
Channel.prototype._sendWndAdjust = function(amt, go) {
if (!this._conn._sock.writable)
return false;
else if (!go && this._conn._state !== 'authenticated') {
this._enqueue(['_sendWndAdjust', amt]);
return false;
}
Channel.prototype._sendWndAdjust = function(amt) {
/*

@@ -472,40 +409,9 @@ byte SSH_MSG_CHANNEL_WINDOW_ADJUST

this._conn._send(buf);
this.outgoing.window += amt;
return true;
return this._conn._send(buf);
};
Channel.prototype._processQueue = function() {
if (!this._conn._sock.writable) {
this._queue = [];
return;
}
if (this._conn._state === 'authenticated') {
var paramLen, req, sentData = false;
while (this._queue.length) {
req = this._queue.shift();
if (req[0] === '_sendData')
sentData = true;
paramLen = req.length - 1;
if (paramLen === 0)
this[req[0]](true);
else if (paramLen === 1)
this[req[0]](req[1], true);
else if (paramLen === 2)
this[req[0]](req[1], req[2], true);
else if (paramLen === 3) {
this[req[0]](req[1], req[2], req[3], true);
} else {
var args = req.slice(1);
args.push(true);
this[req[0]].apply(this, args);
}
}
if (sentData && this._stream)
this._stream.emit('drain');
}
};
Channel.MAX_WINDOW = MAX_WINDOW;
Channel.ChannelStream = ChannelStream;

@@ -521,9 +427,6 @@ module.exports = Channel;

this.paused = false;
this.allowHalfOpen = false;
this._channel = channel;
this._buffer = [];
this._decoder = undefined;
channel._conn._sock.on('drain', function() {
self.emit('drain');
});
}

@@ -534,25 +437,10 @@ inherits(ChannelStream, Stream);

var extendedType;
if (Buffer.isBuffer(data)) {
extended = encoding;
if (typeof extended === 'string') {
extendedType = CHANNEL_EXTENDED_DATATYPE[extended.toUpperCase()];
if (extendedType === undefined)
throw new Error('Error: Invalid extended data type specified: '
+ extended);
extended = extendedType;
} else if (extended && typeof extended !== 'number')
throw new Error('Error: Unexpected extended type: ' + extended);
if (this.paused) {
this._buffer.push([data, extended]);
return true;
} else {
if (extended)
return this._channel._sendData(data);
else
return this._channel._sendData(data, extended);
}
} else if (typeof data === 'string') {
if (typeof data === 'string') {
encoding = encoding || 'utf8';
data = new Buffer(data, encoding);
} else
extended = encoding;
if (Buffer.isBuffer(data)) {
if (typeof extended === 'string') {

@@ -569,3 +457,3 @@ extendedType = CHANNEL_EXTENDED_DATATYPE[extended.toUpperCase()];

this._buffer.push([data, extended]);
return true;
return false;
} else {

@@ -587,13 +475,11 @@ if (extended)

this.paused = false;
var i = 0, len = this._buffer.length, sentData = false;
var i = 0, len = this._buffer.length, ret;
for (; i < len; ++i) {
if (this._buffer[i] === null) {
this._channel.eof();
this._channel.close();
ret = this._channel.eof();
ret = this._channel.close();
break;
} else {
sentData = true;
this._channel._sendData(this._buffer[i][0], this._buffer[i][1]);
}
} else
ret = this._channel._sendData(this._buffer[i][0], this._buffer[i][1]);
}

@@ -604,5 +490,3 @@

// only emit drain immediately if we are not in a key re-exchange because
// processQueues() will emit drain for us after key re-exchange finishes
if (this._channel._conn._state === 'authenticated')
if (ret === true)
this.emit('drain');

@@ -612,11 +496,15 @@ };

ChannelStream.prototype.end = function(data, encoding, extended) {
if (data)
this.write(data, encoding, extended);
var ret;
if (data && data.length)
ret = this.write(data, encoding, extended);
if (this.paused) {
this._buffer.push(null);
ret = this._buffer.push(null);
this.resume();
} else {
this._channel.eof();
this._channel.close();
ret = this._channel.eof();
if (!this.allowHalfOpen)
ret = this._channel.close();
}
return ret;
};

@@ -633,2 +521,15 @@

this._decoder = new StringDecoder(encoding);
};
// session type-specific methods
ChannelStream.prototype.setWindow = function(rows, cols, height, width) {
if (this._channel.type === 'session' && this._channel.subtype === 'shell')
return this._channel._sendTermSizeChg(rows, cols, height, width);
};
ChannelStream.prototype.signal = function(signalName) {
if (this._channel.type === 'session'
&& (this._channel.subtype === 'shell' || this._channel.subtype === 'exec'))
return this._channel._sendSignal(signalName);
};

@@ -1,9 +0,8 @@

var inspect = require('util').inspect;
require('buffer').INSPECT_MAX_BYTES = Infinity;
var net = require('net'),
zlib = require('zlib'),
crypto = require('crypto');
var inherits = require('util').inherits,
EventEmitter = require('events').EventEmitter;
var Parser = require('./Parser'),

@@ -17,26 +16,44 @@ consts = require('./Parser.constants'),

var MAX_CHANNEL = Math.pow(2, 32) - 1;
var MAX_CHANNEL = Math.pow(2, 32) - 1,
EMPTY_BUFFER = new Buffer(0),
ALGORITHMS = consts.ALGORITHMS,
MESSAGE = consts.MESSAGE,
SSH_TO_OPENSSL = consts.SSH_TO_OPENSSL,
DISCONNECT_REASON = consts.DISCONNECT_REASON,
CHANNEL_OPEN_FAILURE = consts.CHANNEL_OPEN_FAILURE;
for (var i=0,keys=Object.keys(consts),len=keys.length; i<len; ++i)
global[keys[i]] = consts[keys[i]];
function isStreamCipher(cipher) {
return (cipher.substr(0, 7) === 'arcfour');
}
var Connection = function(opts) {
var self = this;
this._host = undefined;
this._port = undefined;
this._compress = undefined;
this._state = undefined;
this._compress = false;
this._state = 'closed';
this._username = undefined;
// for password auth
this._password = undefined;
// for public key-based auth
this._privateKey = undefined;
this._publicKey = undefined;
//this._passphrase = undefined;
this._passphrase = undefined;
this._tryKeyboard = undefined;
this._channels = [];
this._sock = undefined;
this._channels = undefined;
this._callbacks = undefined;
this._forwarding = undefined;
this._buffer = undefined;
this._seqno = 0;
this._encryptSize = 8;
this._encrypt = false;
this._hmacKey = undefined;
this._hmacSize = undefined;
this._hmac = false;
this._server_ident_raw = undefined;
this._kexinit = undefined;
this._sessionid = undefined;
this._curChan = -1;

@@ -376,26 +393,31 @@ if (opts && typeof opts.debug === 'function')

self._kexreply.secret.copy(secret, p);
iv = new Buffer(crypto.createHash('sha1')
.update(secret)
.update(self._exchange_hash)
.update('A', 'ascii')
.update(self._sessionid)
.digest('binary'), 'binary');
switch (self._encryptType) {
case 'aes256-cbc':
case 'aes192-cbc':
case 'aes128-cbc':
case 'aes256-ctr':
case 'aes192-ctr':
case 'aes128-ctr':
blocklen = 16;
if (!isStreamCipher(self._encryptType)) {
iv = new Buffer(crypto.createHash('sha1')
.update(secret)
.update(self._exchange_hash)
.update('A', 'ascii')
.update(self._sessionid)
.digest('binary'), 'binary');
switch (self._encryptType) {
case 'aes256-cbc':
case 'aes192-cbc':
case 'aes128-cbc':
case 'aes256-ctr':
case 'aes192-ctr':
case 'aes128-ctr':
blocklen = 16;
}
self._encryptSize = blocklen;
while (blocklen > iv.length) {
iv = Buffer.concat([iv, new Buffer(crypto.createHash('sha1')
.update(secret)
.update(self._exchange_hash)
.update(iv)
.digest('binary'), 'binary')]);
}
iv = iv.slice(0, blocklen);
} else {
self._encryptSize = blocklen;
iv = EMPTY_BUFFER; // streaming ciphers don't use an IV upfront
}
self._encryptSize = blocklen;
while (blocklen > iv.length) {
iv = Buffer.concat([iv, new Buffer(crypto.createHash('sha1')
.update(secret)
.update(self._exchange_hash)
.update(iv)
.digest('binary'), 'binary')]);
}
iv = iv.slice(0, blocklen);
switch (self._encryptType) {

@@ -442,26 +464,31 @@ case 'aes256-cbc':

keylen = 0;
iv = new Buffer(crypto.createHash('sha1')
.update(secret)
.update(self._exchange_hash)
.update('B', 'ascii')
.update(self._sessionid)
.digest('binary'), 'binary');
switch (self._parser._decryptType) {
case 'aes256-cbc':
case 'aes192-cbc':
case 'aes128-cbc':
case 'aes256-ctr':
case 'aes192-ctr':
case 'aes128-ctr':
blocklen = 16;
if (!isStreamCipher(self._parser._decryptType)) {
iv = new Buffer(crypto.createHash('sha1')
.update(secret)
.update(self._exchange_hash)
.update('B', 'ascii')
.update(self._sessionid)
.digest('binary'), 'binary');
switch (self._parser._decryptType) {
case 'aes256-cbc':
case 'aes192-cbc':
case 'aes128-cbc':
case 'aes256-ctr':
case 'aes192-ctr':
case 'aes128-ctr':
blocklen = 16;
}
self._parser._decryptSize = blocklen;
while (blocklen > iv.length) {
iv = Buffer.concat([iv, new Buffer(crypto.createHash('sha1')
.update(secret)
.update(self._exchange_hash)
.update(iv)
.digest('binary'), 'binary')]);
}
iv = iv.slice(0, blocklen);
} else {
self._parser._decryptSize = blocklen;
iv = EMPTY_BUFFER; // streaming ciphers don't use an IV upfront
}
self._parser._decryptSize = blocklen;
while (blocklen > iv.length) {
iv = Buffer.concat([iv, new Buffer(crypto.createHash('sha1')
.update(secret)
.update(self._exchange_hash)
.update(iv)
.digest('binary'), 'binary')]);
}
iv = iv.slice(0, blocklen);
switch (self._parser._decryptType) {

@@ -513,4 +540,3 @@ case 'aes256-cbc':

var emptyBuf;
if (self._encryptType === 'arcfour128'
|| self._encryptType === 'arcfour256') {
if (self._encryptType.substr(0, 7) === 'arcfour') {
emptyBuf = new Buffer(1536);

@@ -520,4 +546,3 @@ emptyBuf.fill(0);

}
if (self._parser._decryptType === 'arcfour128'
|| self._parser._decryptType === 'arcfour256') {
if (self._parser._decryptType.substr(0, 7) === 'arcfour') {
emptyBuf = new Buffer(1536);

@@ -609,3 +634,13 @@ emptyBuf.fill(0);

self._state = 'authenticated';
self.emit('_reexchg');
var b = 0, blen = self._buffer.length;
for (; b < len; ++b) {
if (Buffer.isBuffer(self._buffer[b]))
self._send(self._buffer[b]);
else
self._send(self._buffer[b][0], self._buffer[b][1]);
}
if (blen) {
self._buffer = [];
self.emit('drain');
}
}

@@ -704,2 +739,96 @@ });

this._parser.on('REQUEST_SUCCESS', function(data) {
if (self._callbacks.length)
self._callbacks.shift()(false, data);
});
this._parser.on('REQUEST_FAILURE', function() {
if (self._callbacks.length)
self._callbacks.shift()(true);
});
this._parser.on('CHANNEL_OPEN', function(info) {
if (info.type === 'forwarded-tcpip') {
var rejectConn = false, localChan;
if (self._forwarding.indexOf(info.data.destIP
+ ':' + info.data.destPort) === -1)
rejectConn = true;
else {
localChan = self._nextChan();
if (localChan === false)
rejectConn = true;
else
self._channels.push(localChan);
}
if (rejectConn)
reject();
// TODO: automatic rejection after some timeout?
function accept() {
var chaninfo = {
type: info.type,
incoming: {
id: localChan,
window: Channel.MAX_WINDOW,
packetSize: Channel.MAX_WINDOW,
state: 'open'
},
outgoing: {
id: info.sender,
window: info.window,
packetSize: info.packetSize,
state: 'open'
}
};
var stream = new Channel.ChannelStream(new Channel(chaninfo, self));
stream._channel._stream = stream;
/*
byte SSH_MSG_CHANNEL_OPEN_CONFIRMATION
uint32 recipient channel
uint32 sender channel
uint32 initial window size
uint32 maximum packet size
*/
var buf = new Buffer(1 + 4 + 4 + 4 + 4);
buf[0] = MESSAGE.CHANNEL_OPEN_CONFIRMATION;
buf.writeUInt32BE(info.sender, 1, true);
buf.writeUInt32BE(localChan, 5, true);
buf.writeUInt32BE(Channel.MAX_WINDOW, 9, true);
buf.writeUInt32BE(Channel.MAX_WINDOW, 13, true);
self._send(buf);
return stream;
}
function reject() {
/*
byte SSH_MSG_CHANNEL_OPEN_FAILURE
uint32 recipient channel
uint32 reason code
string description in ISO-10646 UTF-8 encoding
string language tag
*/
var reason;
if (localChan === false)
reason = CHANNEL_OPEN_FAILURE.RESOURCE_SHORTAGE;
else
reason = CHANNEL_OPEN_FAILURE.CONNECT_FAILED;
var buf = new Buffer(1 + 4 + 4 + 4 + 4 + 2);
buf[0] = MESSAGE.CHANNEL_OPEN_FAILURE;
buf.writeUInt32BE(info.sender, 1, true);
buf.writeUInt32BE(reason, 5, true);
buf.writeUInt32BE(0, 9, true);
buf.writeUInt32BE(2, 13, true);
buf.write('en', 17, 2, 'ascii');
self._send(buf);
}
if (localChan !== false)
self.emit('tcp connection', info.data, accept, reject);
}
});
this._parser.on('DISCONNECT', function(reason, reasonCode, desc, lang) {

@@ -737,2 +866,5 @@ var msg = 'Disconnected by host (' + reason + ')', err;

this._channels = [];
this._buffer = [];
this._callbacks = [];
this._forwarding = [];
this._seqno = 0;

@@ -755,16 +887,11 @@ this._encryptSize = 8;

if (!keyInfo)
throw new Error('Error: error while parsing given privateKey');
throw new Error('Unable to parse given privateKey value');
if (!keyInfo.private)
throw new Error('Error: given privateKey does not contain a private key');
if (keyInfo.encrypted) {
throw new Error('privateKey value does not contain a (valid) private key');
if (keyInfo.encryption) {
if (typeof this._passphrase !== 'string')
throw new Error('Error: encrypted private key detected, but no passphrase given');
throw new Error('Encrypted private key detected, but no passphrase given');
else {
var algo = SSH_TO_OPENSSL[keyInfo.encrypted] || keyInfo.encrypted,
dc = crypto.createDecipher(algo, this._passphrase),
out;
dc.setAutoPadding(false);
out = dc.update(keyInfo.private, 'binary', 'binary');
out += dc.final('binary');
keyInfo.private = new Buffer(out, 'binary');
keyInfo.encryption = (SSH_TO_OPENSSL[keyInfo.encryption]
|| keyInfo.encryption);
}

@@ -779,5 +906,5 @@ }

if (!keyInfo)
throw new Error('Error: error while parsing given publicKey');
throw new Error('Unable to parse given publicKey value');
if (!keyInfo.public)
throw new Error('Error given publicKey does not contain a public key');
throw new Error('publicKey value does not contain a (valid) public key');
this._publicKey = keyInfo;

@@ -787,4 +914,4 @@ }

this._sock.on('connect', function() {
self._state = 'initexchg';
self.emit('connect');
self._state = 'initexchg';
self._sock.write(SSH_IDENT + '\r\n');

@@ -808,2 +935,5 @@ });

});
this._sock.on('drain', function() {
self.emit('drain');
});
this._sock.setNoDelay(true);

@@ -814,8 +944,2 @@ this._sock.connect(this._port, this._host);

Connection.prototype.exec = function(cmd, env, cb) {
// TODO: buffer the outgoing CHANNEL_OPEN message if doing key (re-)exchange
var self = this;
var localChan = this._nextChan();
this._channels.push(localChan);
if (typeof env === 'function') {

@@ -826,20 +950,5 @@ cb = env;

this._parser.once('CHANNEL_OPEN_CONFIRMATION:' + localChan, function(info) {
self._parser.removeAllListeners('CHANNEL_OPEN_FAILURE:' + localChan);
var chaninfo = {
type: 'session',
incoming: {
id: localChan,
window: Channel.MAX_WINDOW,
packetSize: Channel.MAX_WINDOW,
state: 'open'
},
outgoing: {
id: info.sender,
window: info.window,
packetSize: info.packetSize,
state: 'open'
}
};
var chan = new Channel(chaninfo, self);
return this._openChan('session', function(err, chan) {
if (err)
return cb(err);
if (typeof env === 'object')

@@ -849,22 +958,7 @@ chan._sendEnv(env);

});
this._parser.once('CHANNEL_OPEN_FAILURE:' + localChan, function(info) {
self._parser.removeAllListeners('CHANNEL_OPEN_CONFIRMATION:' + localChan);
self._channels.splice(self._channels.indexOf(localChan), 1);
var err = new Error('(SSH) Channel open failure: ' + info.description);
err.reason = info.reason;
err.lang = info.lang;
cb(err);
});
this._openChan(localChan);
};
Connection.prototype.shell = function(window, cb) {
// TODO: buffer the outgoing CHANNEL_OPEN message if doing key (re-)exchange
var self = this,
localChan = this._nextChan(),
rows = 24, cols = 80, width = 640, height = 480, term = 'vt100';
var rows = 24, cols = 80, width = 640, height = 480, term = 'vt100';
this._channels.push(localChan);
if (typeof window === 'object') {

@@ -884,6 +978,135 @@ if (typeof window.rows === 'number')

return this._openChan('session', function(err, chan) {
if (err)
return cb(err);
chan._sendPtyReq(rows, cols, height, width, term, null, function(err) {
if (err)
return cb(err);
chan._sendShell(cb);
});
});
};
Connection.prototype.forwardIn = function(address, port, cb) {
/*
byte SSH_MSG_GLOBAL_REQUEST
string "tcpip-forward"
boolean want reply
string address to bind (e.g., "0.0.0.0")
uint32 port number to bind
*/
var self = this,
addrlen = Buffer.byteLength(address),
buf = new Buffer(1 + 4 + 13 + 1 + 4 + addrlen + 4);
buf[0] = MESSAGE.GLOBAL_REQUEST;
buf.writeUInt32BE(13, 1, true);
buf.write('tcpip-forward', 5, 13, 'ascii');
buf[18] = 1;
buf.writeUInt32BE(addrlen, 19, true);
buf.write(address, 23, addrlen, 'ascii');
buf.writeUInt32BE(port, 23 + addrlen, true);
this._callbacks.push(function(had_err, data) {
if (had_err)
return cb(new Error('Unable to bind ' + address + ':' + port));
if (data && data.length)
port = data.readUInt32BE(0, true);
self._forwarding.push(address + ':' + port);
if (data && data.length) {
port = data.readUInt32BE(0, true);
cb(undefined, port);
} else
cb();
});
return this._send(buf);
};
Connection.prototype.unforwardIn = function(address, port, cb) {
/*
byte SSH_MSG_GLOBAL_REQUEST
string "cancel-tcpip-forward"
boolean want reply
string address_to_bind (e.g., "127.0.0.1")
uint32 port number to bind
*/
var self = this,
addrlen = Buffer.byteLength(address),
buf = new Buffer(1 + 4 + 13 + 1 + 4 + addrlen + 4);
buf[0] = MESSAGE.GLOBAL_REQUEST;
buf.writeUInt32BE(13, 1, true);
buf.write('cancel-tcpip-forward', 5, 13, 'ascii');
buf[18] = 1;
buf.writeUInt32BE(addrlen, 19, true);
buf.write(address, 23, addrlen, 'ascii');
buf.writeUInt32BE(port, 23 + addrlen, true);
this._callbacks.push(function(had_err) {
if (had_err)
return cb(new Error('Unable to unbind ' + address + ':' + port));
self._forwarding.splice(self._forwarding.indexOf(address + ':' + port), 1);
cb();
});
return this._send(buf);
};
Connection.prototype.forwardOut = function(srcIP, srcPort, dstIP, dstPort, cb) {
/*
byte SSH_MSG_CHANNEL_OPEN
string "direct-tcpip"
uint32 sender channel
uint32 initial window size
uint32 maximum packet size
-------------------------------
string host to connect
uint32 port to connect
string originator IP address
uint32 originator port
*/
var srclen = Buffer.byteLength(srcIP),
dstlen = Buffer.byteLength(dstIP),
p = 0;
var buf = new Buffer(4 + srclen + 4 + 4 + dstlen + 4);
buf.writeUInt32BE(dstlen, p, true);
buf.write(dstIP, p += 4, dstlen, 'ascii');
buf.writeUInt32BE(dstPort, p += dstlen, true);
buf.writeUInt32BE(srclen, p += 4, true);
buf.write(srcIP, p += 4, srclen, 'ascii');
buf.writeUInt32BE(srcPort, p += srclen, true);
this._openChan('direct-tcpip', buf, function(err, chan) {
if (err)
return cb(err);
var stream = new Channel.ChannelStream(chan);
chan._stream = stream;
cb(undefined, stream);
});
};
Connection.prototype.end = function() {
this._disconnect(DISCONNECT_REASON.CONNECTION_LOST);
};
Connection.prototype._openChan = function(type, blob, cb) {
var self = this,
localChan = this._nextChan();
if (localChan === false)
return cb(new Error('No free channels available'));
if (typeof blob === 'function') {
cb = blob;
blob = undefined;
}
this._channels.push(localChan);
this._parser.once('CHANNEL_OPEN_CONFIRMATION:' + localChan, function(info) {
self._parser.removeAllListeners('CHANNEL_OPEN_FAILURE:' + localChan);
var chaninfo = {
type: 'session',
type: type,
incoming: {

@@ -902,9 +1125,3 @@ id: localChan,

};
var chan = new Channel(chaninfo, self);
chan._sendPtyReq(rows, cols, height, width, term, null, function(err) {
if (err)
cb(err);
else
chan._sendShell(cb);
});
cb(undefined, new Channel(chaninfo, self));
});

@@ -920,25 +1137,24 @@ this._parser.once('CHANNEL_OPEN_FAILURE:' + localChan, function(info) {

this._openChan(localChan);
};
Connection.prototype.end = function() {
this._disconnect(DISCONNECT_REASON.CONNECTION_LOST);
};
Connection.prototype._openChan = function(chan) {
/*
byte SSH_MSG_CHANNEL_OPEN
string "session"
string channel type in US-ASCII only
uint32 sender channel
uint32 initial window size
uint32 maximum packet size
.... channel type specific data follows
*/
var buf = new Buffer(1 + 4 + 7 + 4 + 4 + 4);
var typelen = Buffer.byteLength(type),
bloblen = (blob ? blob.length : 0),
p = 5 + typelen;
var buf = new Buffer(1 + 4 + typelen + 4 + 4 + 4 + bloblen);
buf[0] = MESSAGE.CHANNEL_OPEN;
buf.writeUInt32BE(7, 1, true);
buf.write('session', 5, 7, 'ascii');
buf.writeUInt32BE(chan, 12, true);
buf.writeUInt32BE(Channel.MAX_WINDOW, 16, true);
buf.writeUInt32BE(Channel.MAX_WINDOW, 20, true);
this._send(buf);
buf.writeUInt32BE(typelen, 1, true);
buf.write(type, 5, typelen, 'ascii');
buf.writeUInt32BE(localChan, p, true);
buf.writeUInt32BE(Channel.MAX_WINDOW, p += 4, true);
buf.writeUInt32BE(Channel.MAX_WINDOW, p += 4, true);
if (blob)
blob.copy(buf, p += 4);
return this._send(buf);
};

@@ -950,5 +1166,7 @@

return this._curChan;
for (var i = 0; i < MAX_CHANNEL; ++i)
if (this._channels.indexOf(i))
return i;
return false;

@@ -1007,3 +1225,3 @@ };

this._parser._authMethod = 'password';
this._send(buf);
return this._send(buf);
};

@@ -1041,3 +1259,3 @@

this._parser._authMethod = 'keyboard-interactive';
this._send(buf);
return this._send(buf);
};

@@ -1092,12 +1310,46 @@

if (!sign) {
this._send(sig);
return;
if (!sign)
return this._send(sig);
var privateKey = this._privateKey.privateOrig;
// decrypt the key first if necessary
if (this._privateKey.encryption) {
var keyInfo = this._privateKey;
var iv = new Buffer(keyInfo.extra[0], 'hex'),
key;
key = new Buffer(crypto.createHash('md5')
.update(this._passphrase
+ iv.toString('binary', 0, 8), 'binary')
.digest('binary'), 'binary');
key = Buffer.concat([
key,
new Buffer(crypto.createHash('md5')
.update(key.toString('binary')
+ this._passphrase
+ iv.toString('binary'), 'binary')
.digest('binary'), 'binary').slice(0, 8)
]);
var dc = crypto.createDecipheriv(keyInfo.encryption, key, iv),
out;
dc.setAutoPadding(false);
out = dc.update(keyInfo.private, 'binary', 'binary');
out += dc.final('binary');
// update our original base64-encoded version of the private key
var orig = keyInfo.privateOrig.toString('utf8'),
newOrig = /^(.+(?:\r\n|\n))/.exec(orig)[1],
b64key = new Buffer(out, 'binary').toString('base64');
newOrig += b64key.match(/.{1,70}/g).join('\n');
newOrig += /((?:\r\n|\n).+)$/.exec(orig)[1];
privateKey = newOrig;
}
var signature = crypto.createSign(this._privateKey.type === 'rsa'
? 'RSA-SHA1' : 'DSA-SHA1');
signature.update(sig);
signature = new Buffer(signature.sign(this._privateKey.privateOrig, 'binary'),
'binary');
signature = new Buffer(signature.sign(privateKey, 'binary'), 'binary');
var sigLen = signature.length, privAlgoLen = 4 + this._privateKey.type.length;

@@ -1146,3 +1398,3 @@

this._send(buf);
return this._send(buf);
};

@@ -1164,3 +1416,3 @@

this._send(buf, function() {
return this._send(buf, function() {
self._sock.end();

@@ -1172,2 +1424,11 @@ });

// TODO: implement length checks, make randomBytes() async again
if (this._state === 'reexchg') {
if (typeof cb === 'function')
this._buffer.push([payload, cb]);
else
this._buffer.push(payload);
return false;
} else if (this._state === 'closed' || !this._sock.writable)
return;
var pktLen = payload.length + 9,

@@ -1224,2 +1485,2 @@ padLen,

module.exports = Connection;
module.exports = Connection;

@@ -1,2 +0,2 @@

// TODO: support SECSH/ssh.com keys
// TODO: handle multi-line header values

@@ -6,3 +6,5 @@ var RE_HEADER_PPK = /^PuTTY-User-Key-File-2: ssh-(rsa|dss)$/i,

RE_FOOTER_OPENSSH_PRIV = /^-----END (?:RSA|DSA) PRIVATE KEY-----$/i,
RE_HEADER_OPENSSH_PUB = /^ssh-(rsa|dss) ([A-Z0-9a-z\/+=]+(?:$|\s+([\S].*)))/i,
RE_HEADER_OPENSSH_PUB = /^ssh-(rsa|dss) ([A-Z0-9a-z\/+=]+(?:$|\s+([\S].*)?)$)/i,
RE_HEADER_RFC4176_PUB = /^---- BEGIN SSH2 PUBLIC KEY ----$/i,
RE_FOOTER_RFC4176_PUB = /^---- END SSH2 PUBLIC KEY ----$/i,
RE_HEADER = /^([^:]+):\s*([\S].*)?$/i;

@@ -27,4 +29,2 @@

var orig = data;
data = data.split(/\r\n|\n/);

@@ -36,3 +36,5 @@ while (!data[0].length)

if (m = RE_HEADER_PPK.exec(data[0])) {
var orig = data.join('\n');
/*if (m = RE_HEADER_PPK.exec(data[0])) {
// PuTTY private and/or public key(s)

@@ -66,3 +68,3 @@ var nlines, j;

}
} else if ((m = RE_HEADER_OPENSSH_PRIV.exec(data[0]))
} else*/ if ((m = RE_HEADER_OPENSSH_PRIV.exec(data[0]))
&& RE_FOOTER_OPENSSH_PRIV.test(data[data.length - 1])) {

@@ -98,2 +100,39 @@ // OpenSSH private key

ret.comment = m[3];
} else if ((m = RE_HEADER_RFC4176_PUB.exec(data[0]))
&& RE_FOOTER_RFC4176_PUB.test(data[data.length - 1])) {
if (!RE_HEADER.test(data[1])) {
// no headers
ret.public = new Buffer(data.slice(1, data.length - 1).join(''), 'base64');
} else {
// headers
for (i = 1, len = data.length; i < len; ++i) {
m = RE_HEADER.exec(data[i]);
if (m) {
m[1] = m[1].toLowerCase();
if (m[1] === 'dek-info') {
m[2] = m[2].split(',');
ret.encryption = m[2][0].toLowerCase();
if (m[2].length > 1)
ret.extra = m[2].slice(1);
} else if (m[1] === 'comment')
ret.comment = m[2];
} else if (data[i].length)
break;
}
ret.public = new Buffer(data.slice(i, data.length - 1).join(''), 'base64');
}
len = ret.public.readUInt32BE(0, true);
if (len !== 7)
return false;
else {
var type = ret.public.toString('ascii', 4, 11);
if (type === 'ssh-dss')
ret.type = 'dss';
else if (type === 'ssh-rsa')
ret.type = 'rsa';
else
return false;
}
ret.public = ret.public.slice(11);
ret.publicOrig = new Buffer(orig);
} else

@@ -100,0 +139,0 @@ return false;

@@ -167,4 +167,4 @@ var i = 0, keys, len;

SERVER_HOST_KEY = [
'ssh-rsa', // RECOMMENDED sign Raw RSA Key
'ssh-dss' // REQUIRED sign Raw DSS Key
'ssh-rsa', // RECOMMENDED
'ssh-dss' // REQUIRED
],

@@ -174,28 +174,19 @@ SERVER_HOST_KEY_LIST = new Buffer(SERVER_HOST_KEY.join(',')),

// from <http://tools.ietf.org/html/rfc4345#section-4>:
/*'arcfour256',
'arcfour256',
'arcfour128',
'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key*/
'aes128-cbc', // RECOMMENDED
'3des-cbc', // REQUIRED
'blowfish-cbc',// OPTIONAL
'cast128-cbc', // OPTIONAL
'aes192-cbc', // OPTIONAL
'aes256-cbc', // OPTIONAL
'aes128-cbc', // RECOMMENDED AES with a 128-bit key
'aes192-cbc', // OPTIONAL AES with a 192-bit key
'aes256-cbc', // OPTIONAL AES in CBC mode, with a 256-bit key
// from <http://tools.ietf.org/html/rfc4344#section-4>:
'aes128-ctr', // RECOMMENDED AES in SDCTR mode, with 128-bit key
'aes192-ctr', // RECOMMENDED AES with 192-bit key
'aes256-ctr', // RECOMMENDED AES with 256-bit key
'3des-ctr', // RECOMMENDED Three-key 3DES in SDCTR mode
'3des-cbc' // REQUIRED three-key 3DES in CBC mode
//'none' // OPTIONAL no encryption; NOT RECOMMENDED
'arcfour' // OPTIONAL
//'none' // OPTIONAL
],
CIPHER_LIST = new Buffer(CIPHER.join(',')),
HMAC = [
//'hmac-sha1-96',// RECOMMENDED first 96 bits of HMAC-SHA1
// (digest length = 12, key length = 20)
'hmac-sha1', // REQUIRED HMAC-SHA1 (digest length = key length = 20)
//'hmac-md5-96', // OPTIONAL first 96 bits of HMAC-MD5
// (digest length = 12, key length = 16)
//'hmac-md5' // OPTIONAL HMAC-MD5 (digest length = key length = 16)
'hmac-md5', // OPTIONAL (digest length = key length = 16)
'hmac-sha1', // REQUIRED (digest length = key length = 20)
'hmac-sha2-256',

@@ -205,11 +196,25 @@ 'hmac-sha2-256-96',

'hmac-sha2-512-96',
'hmac-ripemd160'
//'none' // OPTIONAL no MAC; NOT RECOMMENDED
'hmac-ripemd160',
'hmac-sha1-96',// RECOMMENDED first 96 bits of HMAC-SHA1
// (digest length = 12, key length = 20)
'hmac-md5-96' // OPTIONAL first 96 bits of HMAC-MD5
// (digest length = 12, key length = 16)
//'none' // OPTIONAL
],
HMAC_LIST = new Buffer(HMAC.join(',')),
COMPRESS = [
'none' // REQUIRED no compression
'none' // REQUIRED
//'zlib' // OPTIONAL ZLIB (LZ77) compression
],
COMPRESS_LIST = new Buffer(COMPRESS.join(','));
if (process.versions.openssl >= '1.0.1') {
CIPHER = [
// from <http://tools.ietf.org/html/rfc4344#section-4>:
'aes128-ctr', // RECOMMENDED
'aes192-ctr', // RECOMMENDED
'aes256-ctr' // RECOMMENDED
].concat(CIPHER);
}
var ALGORITHMS = exports.ALGORITHMS = {

@@ -233,3 +238,3 @@ KEX: KEX,

exports.SSH_TO_OPENSSL = {
// ciphers
// ciphers (only counter mode available is for AES and only in OpenSSL 1.0.1+)
'3des-cbc': 'des-ede3-cbc',

@@ -253,8 +258,8 @@ 'blowfish-cbc': 'bf-cbc',

'camellia256-cbc@openssh.org': 'camellia-256-cbc',
'3des-ctr': 'des-ede3',
'blowfish-ctr': 'bf-ecb',
'aes256-ctr': 'aes-256-ecb',
'aes192-ctr': 'aes-192-ecb',
'aes128-ctr': 'aes-128-ecb',
'cast128-ctr': 'cast5-ecb',
/*'3des-ctr': 'des-ede3',
'blowfish-ctr': 'bf-ecb',*/
'aes256-ctr': 'aes-256-ctr',
'aes192-ctr': 'aes-192-ctr',
'aes128-ctr': 'aes-128-ctr',
/*'cast128-ctr': 'cast5-ecb',
'camellia128-ctr': 'camellia-128-ecb',

@@ -265,4 +270,3 @@ 'camellia192-ctr': 'camellia-192-ecb',

'camellia192-ctr@openssh.org': 'camellia-192-ecb',
'camellia256-ctr@openssh.org': 'camellia-256-ecb',
'none': 'none',
'camellia256-ctr@openssh.org': 'camellia-256-ecb',*/
// hmac

@@ -269,0 +273,0 @@ 'hmac-sha1-96': 'sha1',

@@ -1,3 +0,1 @@

var inspect = require('util').inspect;
// TODO: * Filter control codes from strings

@@ -11,4 +9,6 @@ // (as per http://tools.ietf.org/html/rfc4251#section-9.2)

for (var i=0,keys=Object.keys(consts),len=keys.length; i<len; ++i)
global[keys[i]] = consts[keys[i]];
var MESSAGE = consts.MESSAGE,
DISCONNECT_REASON = consts.DISCONNECT_REASON,
CHANNEL_OPEN_FAILURE = consts.CHANNEL_OPEN_FAILURE,
SSH_TO_OPENSSL = consts.SSH_TO_OPENSSL;

@@ -198,7 +198,7 @@ // parser states

buffer = this.decrypt(buffer);
this._pktLen = buffer.readUInt32BE(0, true); // reset
this._padLen = buffer[4]; // reset
this._pktLen = buffer.readUInt32BE(0, true);
this._padLen = buffer[4];
var remainLen = this._pktLen + 4 - this._decryptSize;
if (remainLen > 0) {
this._pktExtra = buffer.slice(5); // reset
this._pktExtra = buffer.slice(5);
// grab the rest of the packet

@@ -226,13 +226,13 @@ this.expect(remainLen, EXP_ACTION_BUFFER);

buffer.copy(buf, this._pktExtra.length);
this._payload = buf.slice(0, padStart); // reset
this._payload = buf.slice(0, padStart);
} else {
// entire message fit into one block
buf = buffer;
this._payload = buf.slice(5, 5 + padStart); // reset
buf = buffer.slice(5);
this._payload = buffer.slice(5, 5 + padStart);
}
if (this._hmacSize !== undefined) { // reset
if (this._hmacSize !== undefined) {
// wait for hmac hash
this.expect(this._hmacSize, EXP_ACTION_BUFFER);
this._state = STATE_PACKETDATAVERIFY;
this._packet = buf; // reset
this._packet = buf;
} else

@@ -245,13 +245,13 @@ this._state = STATE_PACKETDATAAFTER;

// verify packet data integrity
//if (this.hmacVerify(buffer)) {
if (this.hmacVerify(buffer)) {
this._state = STATE_PACKETDATAAFTER;
this._packet = undefined;
/*} else {
} else {
this.emit('error', new Error('Invalid HMAC'));
return this.reset();
}*/
}
break;
case STATE_PACKETDATAAFTER:
var payload = this._payload;
if (++this._seqno > MAX_SEQNO) // reset
if (++this._seqno > MAX_SEQNO)
this._seqno = 0;

@@ -309,3 +309,3 @@ this.emit('packet', MESSAGE[payload[0]], payload[0], payload.slice(1));

*/
var init = this._kexinit_info = { // reset
var init = this._kexinit_info = {
algorithms: {

@@ -340,3 +340,3 @@ kex: undefined,

init.languages.sc = readList(payload, payload._pos);
this._kexinit = payload; // reset
this._kexinit = payload;
this.emit('KEXINIT', init);

@@ -560,2 +560,16 @@ break;

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:

@@ -600,2 +614,18 @@ var recipient = payload.readUInt32BE(1, true),

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:

@@ -630,3 +660,3 @@ /*

this._packet.copy(buf, 9);
var calcHmac = crypto.createHmac(this._hmac, this._hmacKey);
var calcHmac = crypto.createHmac(SSH_TO_OPENSSL[this._hmac], this._hmacKey);
calcHmac.update(buf);

@@ -671,2 +701,12 @@ return (calcHmac.digest('binary') === hmac.toString('binary'));

this._authMethod = undefined;
this._pktLen = undefined;
this._padLen = undefined;
this._pktExtra = undefined;
this._payload = undefined;
this._hmacSize = undefined;
this._packet = undefined;
this._seqno = 0;
this._kexinit_info = undefined;
this._kexinit = undefined;
};

@@ -708,2 +748,2 @@

Parser.MAX_SEQNO = MAX_SEQNO;
module.exports = Parser;
module.exports = Parser;
{ "name": "ssh2",
"version": "0.0.1",
"version": "0.0.2",
"author": "Brian White <mscdex@mscdex.net>",

@@ -4,0 +4,0 @@ "description": "An SSH2 client module written in pure JavaScript for node.js",

@@ -37,4 +37,3 @@

c.exec('uptime', function(err, stream) {
if (err)
throw err;
if (err) throw err;
stream.on('data', function(data, extended) {

@@ -83,3 +82,155 @@ console.log((extended === 'stderr' ? 'STDERR: ' : 'STDOUT: ')

* Authenticate using password, send a (raw) HTTP request to port 80 on the server, and disconnect afterwards:
```javascript
var Connection = require('ssh2');
var c = new Connection();
c.on('connect', function() {
console.log('Connection :: connect');
});
c.on('ready', function() {
console.log('Connection :: ready');
c.forwardOut('192.168.100.102', 8000, '127.0.0.1', 80, function(err, stream) {
if (err) throw err;
stream.on('data', function(data) {
console.log('TCP :: DATA: ' + data);
});
stream.on('end', function() {
console.log('TCP :: EOF');
});
stream.on('error', function(err) {
console.log('TCP :: ERROR: ' + err);
});
stream.on('close', function(had_err) {
console.log('TCP :: CLOSED');
c.end();
});
var data = [
'HEAD / HTTP/1.1',
'User-Agent: curl/7.27.0',
'Host: 127.0.0.1',
'Accept: */*',
'Connection: close',
'',
''
];
stream.write(data.join('\r\n'));
});
});
c.on('error', function(err) {
console.log('Connection :: error :: ' + err);
});
c.on('end', function() {
console.log('Connection :: end');
});
c.on('close', function(had_error) {
console.log('Connection :: close');
});
c.connect({
host: '192.168.100.100',
port: 22,
username: 'frylock',
password: 'nodejsrules'
});
// example output:
// Connection :: connect
// Connection :: ready
// TCP :: DATA: HTTP/1.1 200 OK
// Date: Thu, 15 Nov 2012 13:52:58 GMT
// Server: Apache/2.2.22 (Ubuntu)
// X-Powered-By: PHP/5.4.6-1ubuntu1
// Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT
// Content-Encoding: gzip
// Vary: Accept-Encoding
// Connection: close
// Content-Type: text/html; charset=UTF-8
//
//
// TCP :: EOF
// TCP :: CLOSED
// Connection :: end
// Connection :: close
```
* Authenticate using password and forward remote connections on port 8000 to us:
```javascript
var Connection = require('ssh2');
var c = new Connection();
c.on('connect', function() {
console.log('Connection :: connect');
});
c.on('tcp connection', function(info, accept, reject) {
console.log('TCP :: INCOMING CONNECTION: ' + require('util').inspect(info));
var stream = accept();
stream.on('data', function(data) {
console.log('TCP :: DATA: ' + data);
});
stream.on('end', function() {
console.log('TCP :: EOF');
});
stream.on('error', function(err) {
console.log('TCP :: ERROR: ' + err);
});
stream.on('close', function(had_err) {
console.log('TCP :: CLOSED');
});
var response = [
'HTTP/1.1 404 Not Found',
'Date: Thu, 15 Nov 2012 02:07:58 GMT',
'Server: ForwardedConnection',
'Content-Length: 0',
'Connection: close',
'',
''
];
stream.end(response.join('\r\n'));
});
c.on('ready', function() {
console.log('Connection :: ready');
c.forwardIn('127.0.0.1', 8000, function(err) {
if (err) throw err;
console.log('Listening for connections on server on port 8000!');
});
});
c.on('error', function(err) {
console.log('Connection :: error :: ' + err);
});
c.on('end', function() {
console.log('Connection :: end');
});
c.on('close', function(had_error) {
console.log('Connection :: close');
});
c.connect({
host: '192.168.100.100',
port: 22,
username: 'frylock',
password: 'nodejsrules'
});
// example output:
// Connection :: connect
// Connection :: ready
// Listening for connections on server on port 8000!
// (.... then from another terminal on the server: `curl -I http://127.0.0.1:8000`)
// TCP :: INCOMING CONNECTION: { destIP: '127.0.0.1',
// destPort: 8000,
// srcIP: '127.0.0.1',
// srcPort: 41969 }
// TCP DATA: HEAD / HTTP/1.1
// User-Agent: curl/7.27.0
// Host: 127.0.0.1:8000
// Accept: */*
//
//
// TCP :: CLOSED
```
API

@@ -97,2 +248,12 @@ ===

* **tcp connection**(< _object_ >details, < _function_ >accept, < _function_ >reject) - An incoming forwarded TCP connection is being requested. Calling `accept` accepts the connection and returns a `ChannelStream` object. Calling `reject` rejects the connection and no further action is needed. `details` contains:
* **srcIP** - _string_ - The originating IP of the connection.
* **srcPort** - _integer_ - The originating port of the connection.
* **dstIP** - _string_ - The remote IP the connection was received on (given in earlier call to `forwardIn()`).
* **dstPort** - _integer_ - The remote port the connection was received on (given in earlier call to `forwardIn()`).
* **keyboard-interactive**(< _string_ >name, < _string_ >instructions, < _string_ >instructionsLang, < _array_ >prompts, < _function_ >finish) - The server is asking for replies to the given `prompts` for keyboard-interactive user authentication. `name` is generally what you'd use as a window title (for GUI apps). `prompts` is an array of `{ prompt: 'Password: ', echo: false }` style objects (here `echo` indicates whether user input should be displayed on the screen). The answers for all prompts must be provided as an array of strings and passed to `finish` when you are ready to continue. Note: It's possible for the server to come back and ask more questions.

@@ -123,2 +284,4 @@

* **privateKey** - < _mixed_ > - Buffer or string that contains an **unencrypted** private key for key-based user authentication (OpenSSH format). **Default:** (none)
* **passphrase** - < _string_ > - For an encrypted private key, this is the passphrase used to decrypt it. **Default:** (none)

@@ -129,6 +292,22 @@ * **publicKey** - < _mixed_ > - Buffer or string that contains a public key for key-based user authentication (OpenSSH format). **Default:** (none)

* **exec**(< _string_ >command[, < _object_ >environment], < _function_ >callback]]) - _(void)_ - Executes `command` on the server, with an optional `environment` set before execution. `callback` has 2 parameters: < _Error_ >err, < _ChannelStream_ >stream. For exec, the `stream` will also emit 'exit' when the process finishes. If the process finished normally, the process return value is passed to the 'exit' callback. If the process was interrupted by a signal, the following are passed to the 'exit' callback: < _null_ >code, < _string_ >signalName, < _boolean_ >didCoreDump, < _string_ >description.
* **exec**(< _string_ >command[, < _object_ >environment], < _function_ >callback) - _(void)_ - Executes `command` on the server, with an optional `environment` set before execution. `callback` has 2 parameters: < _Error_ >err, < _ChannelStream_ >stream. For exec, the `stream` will also emit 'exit' when the process finishes. If the process finished normally, the process return value is passed to the 'exit' callback. If the process was interrupted by a signal, the following are passed to the 'exit' callback: < _null_ >code, < _string_ >signalName, < _boolean_ >didCoreDump, < _string_ >description.
* **shell**([< _object_ >window,] < _function_ >callback]]) - _(void)_ - Starts an interactive shell session on the server, with optional terminal `window` settings. Valid `window` properties include: rows (defaults to 24), cols (defaults to 80), height (in pixels, defaults to 480), width (in pixels, defaults to 640), and term (value to use for $TERM, defaults to 'vt100'). Rows and cols overrides width and height when rows and cols are non-zero. Pixel dimensions refer to the drawable area of the window. Zero dimension parameters are ignored. `callback` has 2 parameters: < _Error_ >err, < _ChannelStream_ >stream.
* **shell**([< _object_ >window,] < _function_ >callback) - _(void)_ - Starts an interactive shell session on the server, with optional terminal `window` settings. Valid `window` properties include: rows (defaults to 24), cols (defaults to 80), height (in pixels, defaults to 480), width (in pixels, defaults to 640), and term (value to use for $TERM, defaults to 'vt100'). Rows and cols overrides width and height when rows and cols are non-zero. Pixel dimensions refer to the drawable area of the window. Zero dimension parameters are ignored. `callback` has 2 parameters: < _Error_ >err, < _ChannelStream_ >stream.
* **forwardIn**(< _string_ >remoteAddr, < _integer_ >remotePort, < _function_ >callback) - _(void)_ - Bind to `remoteAddr` on `remotePort` on the server and forward incoming connections. `callback` has 2 parameters: < _Error_ >err, < _integer_ >port (`port` is the assigned port number if `remotePort` was 0). Here are some special values for `remoteAddr` and their associated binding behaviors:
* '' - Connections are to be accepted on all protocol families supported by the server.
* '0.0.0.0' - Listen on all IPv4 addresses.
* '::' - Listen on all IPv6 addresses.
* 'localhost' - Listen on all protocol families supported by the server on loopback addresses only.
* '127.0.0.1' and '::1' - Listen on the loopback interfaces for IPv4 and IPv6, respectively.
* **unforwardIn**(< _string_ >remoteAddr, < _integer_ >remotePort, < _function_ >callback) - _(void)_ - Unbind `remoteAddr` on `remotePort` on the server and stop forwarding incoming connections. Until `callback` is called, more connections may still come in. `callback` has 1 parameter: < _Error_ >err.
* **forwardOut**(< _string_ >srcIP, < _integer_ >srcPort, < _string_ >dstIP, < _integer_ >dstPort, < _function_ >callback) - _(void)_ - Open a connection with `srcIP` and `srcPort` as the originating address and port and `dstIP` and `dstPort` as the remote destination address and port. `callback` has 2 parameters: < _Error_ >err, < _ChannelStream_ >stream.
* **end**() - _(void)_ - Disconnects the connection.

@@ -141,2 +320,14 @@

This is a normal duplex Stream, with one change: for any special data events (e.g. data from stderr for exec/shell), a second (string) argument is passed in to the 'data' event callback containing the type. So far the only defined type is 'stderr'.
This is a normal duplex Stream, with the following changes:
* A boolean property 'allowHalfOpen' exists and behaves similarly to the property of the same name for net.Socket. When the stream's end() is called, if 'allowHalfOpen' is true, only EOF will be sent (the server can still send data if they have not already sent EOF). The default value for this property is `false`.
* For shell(), an extra function is available:
* **setWindow**(< _integer_ >rows, < _integer_ >cols, < _integer_ >height, < _integer_ >width) - _(void)_ - Lets the server know that the local terminal window has been resized. The behavior of these arguments is the same as described for shell().
* For shell() and exec(), an extra function is available:
* 'data' events are passed a second (string) argument to the callback, which indicates whether the data is a special type. So far the only defined type is 'stderr'.
* **signal**(< _string_ >signalName) - _(void)_ - Sends a POSIX signal to the current process on the server. Valid signal names are: 'ABRT', 'ALRM', 'FPE', 'HUP', 'ILL', 'INT', 'KILL', 'PIPE', 'QUIT', 'SEGV', 'TERM', 'USR1', and 'USR2'. Also, from the RFC: "Some systems may not implement signals, in which case they SHOULD ignore this message."

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc