UNPKG

ssh2-classic

Version:

SSH2 client and server modules written in pure JavaScript for node.js

1,519 lines (1,450 loc) 79.8 kB
var Client = require('../lib/client'); var Server = require('../lib/server'); var OPEN_MODE = require('ssh2-streams').SFTPStream.OPEN_MODE; var STATUS_CODE = require('ssh2-streams').SFTPStream.STATUS_CODE; var utils = require('ssh2-streams').utils; var net = require('net'); var fs = require('fs'); var crypto = require('crypto'); var path = require('path'); var join = path.join; var inspect = require('util').inspect; var assert = require('assert'); var t = -1; var group = path.basename(__filename, '.js') + '/'; var fixturesdir = join(__dirname, 'fixtures'); var USER = 'nodejs'; var PASSWORD = 'FLUXCAPACITORISTHEPOWER'; var MD5_HOST_FINGERPRINT = '64254520742d3d0792e918f3ce945a64'; var KEY_RSA_BAD = fs.readFileSync(join(fixturesdir, 'bad_rsa_private_key')); var HOST_KEY_RSA = fs.readFileSync(join(fixturesdir, 'ssh_host_rsa_key')); var HOST_KEY_DSA = fs.readFileSync(join(fixturesdir, 'ssh_host_dsa_key')); var HOST_KEY_ECDSA = fs.readFileSync(join(fixturesdir, 'ssh_host_ecdsa_key')); var CLIENT_KEY_ENC_RSA_RAW = fs.readFileSync(join(fixturesdir, 'id_rsa_enc')); var CLIENT_KEY_ENC_RSA = utils.parseKey(CLIENT_KEY_ENC_RSA_RAW, 'foobarbaz'); var CLIENT_KEY_PPK_RSA_RAW = fs.readFileSync(join(fixturesdir, 'id_rsa.ppk')); var CLIENT_KEY_PPK_RSA = utils.parseKey(CLIENT_KEY_PPK_RSA_RAW); var CLIENT_KEY_RSA_RAW = fs.readFileSync(join(fixturesdir, 'id_rsa')); var CLIENT_KEY_RSA = utils.parseKey(CLIENT_KEY_RSA_RAW); var CLIENT_KEY_RSA_NEW_RAW = fs.readFileSync(join(fixturesdir, 'openssh_new_rsa')); var CLIENT_KEY_RSA_NEW = utils.parseKey(CLIENT_KEY_RSA_NEW_RAW)[0]; var CLIENT_KEY_DSA_RAW = fs.readFileSync(join(fixturesdir, 'id_dsa')); var CLIENT_KEY_DSA = utils.parseKey(CLIENT_KEY_DSA_RAW); var CLIENT_KEY_ECDSA_RAW = fs.readFileSync(join(fixturesdir, 'id_ecdsa')); var CLIENT_KEY_ECDSA = utils.parseKey(CLIENT_KEY_ECDSA_RAW); var DEBUG = false; var DEFAULT_TEST_TIMEOUT = 30 * 1000; var tests = [ { run: function() { var client; var server; var r; r = setup( this, { username: USER, privateKey: CLIENT_KEY_RSA_RAW }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { if (ctx.method === 'none') return ctx.reject(); assert(ctx.method === 'publickey', makeMsg('Unexpected auth method: ' + ctx.method)); assert(ctx.username === USER, makeMsg('Unexpected username: ' + ctx.username)); assert(ctx.key.algo === 'ssh-rsa', makeMsg('Unexpected key algo: ' + ctx.key.algo)); assert.deepEqual(CLIENT_KEY_RSA.getPublicSSH(), ctx.key.data, makeMsg('Public key mismatch')); if (ctx.signature) { assert(CLIENT_KEY_RSA.verify(ctx.blob, ctx.signature) === true, makeMsg('Could not verify PK signature')); ctx.accept(); } else ctx.accept(); }).on('ready', function() { conn.end(); }); }); }, what: 'Authenticate with an RSA key (old OpenSSH)' }, { run: function() { var client; var server; var r; r = setup( this, { username: USER, privateKey: CLIENT_KEY_RSA_NEW_RAW }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { if (ctx.method === 'none') return ctx.reject(); assert(ctx.method === 'publickey', makeMsg('Unexpected auth method: ' + ctx.method)); assert(ctx.username === USER, makeMsg('Unexpected username: ' + ctx.username)); assert(ctx.key.algo === 'ssh-rsa', makeMsg('Unexpected key algo: ' + ctx.key.algo)); assert.deepEqual(CLIENT_KEY_RSA_NEW.getPublicSSH(), ctx.key.data, makeMsg('Public key mismatch')); if (ctx.signature) { assert(CLIENT_KEY_RSA_NEW.verify(ctx.blob, ctx.signature) === true, makeMsg('Could not verify PK signature')); ctx.accept(); } else ctx.accept(); }).on('ready', function() { conn.end(); }); }); }, what: 'Authenticate with an RSA key (new OpenSSH)' }, { run: function() { var client; var server; var r; r = setup( this, { username: USER, privateKey: CLIENT_KEY_ENC_RSA_RAW, passphrase: 'foobarbaz', }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { if (ctx.method === 'none') return ctx.reject(); assert(ctx.method === 'publickey', makeMsg('Unexpected auth method: ' + ctx.method)); assert(ctx.username === USER, makeMsg('Unexpected username: ' + ctx.username)); assert(ctx.key.algo === 'ssh-rsa', makeMsg('Unexpected key algo: ' + ctx.key.algo)); assert.deepEqual(CLIENT_KEY_ENC_RSA.getPublicSSH(), ctx.key.data, makeMsg('Public key mismatch')); if (ctx.signature) { assert(CLIENT_KEY_ENC_RSA.verify(ctx.blob, ctx.signature) === true, makeMsg('Could not verify PK signature')); ctx.accept(); } else ctx.accept(); }).on('ready', function() { conn.end(); }); }); }, what: 'Authenticate with an encrypted RSA key' }, { run: function() { var client; var server; var r; r = setup( this, { username: USER, privateKey: CLIENT_KEY_PPK_RSA_RAW }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { if (ctx.method === 'none') return ctx.reject(); assert(ctx.method === 'publickey', makeMsg('Unexpected auth method: ' + ctx.method)); assert(ctx.username === USER, makeMsg('Unexpected username: ' + ctx.username)); assert(ctx.key.algo === 'ssh-rsa', makeMsg('Unexpected key algo: ' + ctx.key.algo)); if (ctx.signature) { assert(CLIENT_KEY_PPK_RSA.verify(ctx.blob, ctx.signature) === true, makeMsg('Could not verify PK signature')); ctx.accept(); } else ctx.accept(); }).on('ready', function() { conn.end(); }); }); }, what: 'Authenticate with an RSA key (PPK)' }, { run: function() { var client; var server; var r; r = setup( this, { username: USER, privateKey: CLIENT_KEY_DSA_RAW }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { if (ctx.method === 'none') return ctx.reject(); assert(ctx.method === 'publickey', makeMsg('Unexpected auth method: ' + ctx.method)); assert(ctx.username === USER, makeMsg('Unexpected username: ' + ctx.username)); assert(ctx.key.algo === 'ssh-dss', makeMsg('Unexpected key algo: ' + ctx.key.algo)); assert.deepEqual(CLIENT_KEY_DSA.getPublicSSH(), ctx.key.data, makeMsg('Public key mismatch')); if (ctx.signature) { assert(CLIENT_KEY_DSA.verify(ctx.blob, ctx.signature) === true, makeMsg('Could not verify PK signature')); ctx.accept(); } else ctx.accept(); }).on('ready', function() { conn.end(); }); }); }, what: 'Authenticate with a DSA key' }, { run: function() { var client; var server; var r; r = setup( this, { username: USER, privateKey: CLIENT_KEY_ECDSA_RAW }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { if (ctx.method === 'none') return ctx.reject(); assert(ctx.method === 'publickey', makeMsg('Unexpected auth method: ' + ctx.method)); assert(ctx.username === USER, makeMsg('Unexpected username: ' + ctx.username)); assert(ctx.key.algo === 'ecdsa-sha2-nistp256', makeMsg('Unexpected key algo: ' + ctx.key.algo)); assert.deepEqual(CLIENT_KEY_ECDSA.getPublicSSH(), ctx.key.data, makeMsg('Public key mismatch')); if (ctx.signature) { assert(CLIENT_KEY_ECDSA.verify(ctx.blob, ctx.signature) === true, makeMsg('Could not verify PK signature')); ctx.accept(); } else ctx.accept(); }).on('ready', function() { conn.end(); }); }); }, what: 'Authenticate with a ECDSA key' }, { run: function() { var client; var server; var r; r = setup( this, { username: USER, password: 'asdf', algorithms: { serverHostKey: ['ssh-dss'] } }, { hostKeys: [HOST_KEY_DSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { if (ctx.method === 'none') return ctx.reject(); assert(ctx.method === 'password', makeMsg('Unexpected auth method: ' + ctx.method)); assert(ctx.username === USER, makeMsg('Unexpected username: ' + ctx.username)); assert(ctx.password === 'asdf', makeMsg('Unexpected password: ' + ctx.password)); ctx.accept(); }).on('ready', function() { conn.end(); }); }); }, what: 'Server with DSA host key' }, { run: function() { var client; var server; var r; r = setup( this, { username: USER, password: 'asdf' }, { hostKeys: [HOST_KEY_ECDSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { if (ctx.method === 'none') return ctx.reject(); assert(ctx.method === 'password', makeMsg('Unexpected auth method: ' + ctx.method)); assert(ctx.username === USER, makeMsg('Unexpected username: ' + ctx.username)); assert(ctx.password === 'asdf', makeMsg('Unexpected password: ' + ctx.password)); ctx.accept(); }).on('ready', function() { conn.end(); }); }); }, what: 'Server with ECDSA host key' }, { run: function() { var client; var server; var r; r = setup( this, { username: USER, password: 'asdf', algorithms: { serverHostKey: 'ssh-rsa' } }, { hostKeys: [HOST_KEY_RSA, HOST_KEY_DSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { if (ctx.method === 'none') return ctx.reject(); assert(ctx.method === 'password', makeMsg('Unexpected auth method: ' + ctx.method)); assert(ctx.username === USER, makeMsg('Unexpected username: ' + ctx.username)); assert(ctx.password === 'asdf', makeMsg('Unexpected password: ' + ctx.password)); ctx.accept(); }).on('ready', function() { conn.end(); }); }); }, what: 'Server with multiple host keys (RSA selected)' }, { run: function() { var client; var server; var r; r = setup( this, { username: USER, password: 'asdf', algorithms: { serverHostKey: 'ssh-dss' } }, { hostKeys: [HOST_KEY_RSA, HOST_KEY_DSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { if (ctx.method === 'none') return ctx.reject(); assert(ctx.method === 'password', makeMsg('Unexpected auth method: ' + ctx.method)); assert(ctx.username === USER, makeMsg('Unexpected username: ' + ctx.username)); assert(ctx.password === 'asdf', makeMsg('Unexpected password: ' + ctx.password)); ctx.accept(); }).on('ready', function() { conn.end(); }); }); }, what: 'Server with multiple host keys (DSA selected)' }, { run: function() { var client; var server; var r; var hostname = 'foo'; var username = 'bar'; r = setup( this, { username: USER, privateKey: CLIENT_KEY_RSA_RAW, localHostname: hostname, localUsername: username }, { hostKeys: [ HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { if (ctx.method !== 'hostbased') return ctx.reject(); assert(ctx.method === 'hostbased', makeMsg('Unexpected auth method: ' + ctx.method)); assert(ctx.username === USER, makeMsg('Unexpected username: ' + ctx.username)); assert(ctx.key.algo === 'ssh-rsa', makeMsg('Unexpected key algo: ' + ctx.key.algo)); assert.deepEqual(CLIENT_KEY_RSA.getPublicSSH(), ctx.key.data, makeMsg('Public key mismatch')); assert(ctx.signature, makeMsg('Expected signature')); assert(ctx.localHostname === hostname, makeMsg('Wrong local hostname')); assert(ctx.localUsername === username, makeMsg('Wrong local username')); assert(CLIENT_KEY_RSA.verify(ctx.blob, ctx.signature) === true, makeMsg('Could not verify hostbased signature')); ctx.accept(); }).on('ready', function() { conn.end(); }); }); }, what: 'Authenticate with hostbased' }, { run: function() { var client; var server; var r; r = setup( this, { username: USER, password: PASSWORD }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { if (ctx.method === 'none') return ctx.reject(); assert(ctx.method === 'password', makeMsg('Unexpected auth method: ' + ctx.method)); assert(ctx.username === USER, makeMsg('Unexpected username: ' + ctx.username)); assert(ctx.password === PASSWORD, makeMsg('Unexpected password: ' + ctx.password)); ctx.accept(); }).on('ready', function() { conn.end(); }); }); }, what: 'Authenticate with a password' }, { run: function() { var client; var server; var r; var calls = 0; r = setup( this, { username: USER, password: PASSWORD, privateKey: CLIENT_KEY_RSA_RAW, authHandler: function(methodsLeft, partial, cb) { assert(calls++ === 0, makeMsg('authHandler called multiple times')); assert(methodsLeft === null, makeMsg('expected null methodsLeft')); assert(partial === null, makeMsg('expected null partial')); return 'none'; } }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; var attempts = 0; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { assert(++attempts === 1, makeMsg('too many auth attempts')); assert(ctx.method === 'none', makeMsg('Unexpected auth method: ' + ctx.method)); ctx.accept(); }).on('ready', function() { conn.end(); }); }); }, what: 'Custom authentication order (sync)' }, { run: function() { var client; var server; var r; var calls = 0; r = setup( this, { username: USER, password: PASSWORD, privateKey: CLIENT_KEY_RSA_RAW, authHandler: function(methodsLeft, partial, cb) { assert(calls++ === 0, makeMsg('authHandler called multiple times')); assert(methodsLeft === null, makeMsg('expected null methodsLeft')); assert(partial === null, makeMsg('expected null partial')); process.nextTick(cb, 'none'); } }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; var attempts = 0; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { assert(++attempts === 1, makeMsg('too many auth attempts')); assert(ctx.method === 'none', makeMsg('Unexpected auth method: ' + ctx.method)); ctx.accept(); }).on('ready', function() { conn.end(); }); }); }, what: 'Custom authentication order (async)' }, { run: function() { var client; var server; var r; var cliError; var calls = 0; r = setup( this, { username: USER, password: PASSWORD, privateKey: CLIENT_KEY_RSA_RAW, authHandler: function(methodsLeft, partial, cb) { assert(calls++ === 0, makeMsg('authHandler called multiple times')); assert(methodsLeft === null, makeMsg('expected null methodsLeft')); assert(partial === null, makeMsg('expected null partial')); return false; } }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; // Remove default client error handler added by `setup()` since we are // expecting an error in this case client.removeAllListeners('error'); client.on('error', function(err) { cliError = err; assert.strictEqual(err.level, 'client-authentication'); assert(/configured authentication methods failed/i.test(err.message), makeMsg('Wrong error message')); }).on('close', function() { assert(cliError, makeMsg('Expected client error')); }); server.on('connection', function(conn) { conn.on('authentication', function(ctx) { assert(false, makeMsg('should not see auth attempt')); }).on('ready', function() { conn.end(); }); }); }, what: 'Custom authentication order (no methods)' }, { run: function() { var client; var server; var r; var calls = 0; r = setup( this, { username: USER, password: PASSWORD, privateKey: CLIENT_KEY_RSA_RAW, authHandler: function(methodsLeft, partial, cb) { switch (calls++) { case 0: assert(methodsLeft === null, makeMsg('expected null methodsLeft')); assert(partial === null, makeMsg('expected null partial')); return 'publickey'; case 1: assert.deepStrictEqual(methodsLeft, ['password'], makeMsg('expected password method left' + ', saw: ' + methodsLeft)); assert(partial === true, makeMsg('expected partial success')); return 'password'; default: assert(false, makeMsg('authHandler called too many times')); } } }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; var attempts = 0; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { assert(++attempts === calls, makeMsg('server<->client state mismatch')); switch (calls) { case 1: assert(ctx.method === 'publickey', makeMsg('Unexpected auth method: ' + ctx.method)); assert(ctx.username === USER, makeMsg('Unexpected username: ' + ctx.username)); assert(ctx.key.algo === 'ssh-rsa', makeMsg('Unexpected key algo: ' + ctx.key.algo)); assert.deepEqual(CLIENT_KEY_RSA.getPublicSSH(), ctx.key.data, makeMsg('Public key mismatch')); ctx.reject(['password'], true); break; case 2: assert(ctx.method === 'password', makeMsg('Unexpected auth method: ' + ctx.method)); assert(ctx.username === USER, makeMsg('Unexpected username: ' + ctx.username)); assert(ctx.password === PASSWORD, makeMsg('Unexpected password: ' + ctx.password)); ctx.accept(); break; default: assert(false, makeMsg('bad client auth state')); } }).on('ready', function() { conn.end(); }); }); }, what: 'Custom authentication order (multi-step)' }, { run: function() { var client; var server; var r; var verified = false; r = setup( this, { username: USER, password: PASSWORD, hostHash: 'md5', hostVerifier: function(hash) { assert(hash === MD5_HOST_FINGERPRINT, makeMsg('Host fingerprint mismatch')); return (verified = true); } }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { ctx.accept(); }).on('ready', function() { conn.end(); }); }).on('close', function() { assert(verified, makeMsg('Failed to verify host fingerprint')); }); }, what: 'Verify host fingerprint' }, { run: function() { var client; var server; var r; var out = ''; var outErr = ''; var exitArgs; var closeArgs; r = setup( this, { username: USER, password: PASSWORD }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { ctx.accept(); }).on('ready', function() { conn.once('session', function(accept, reject) { var session = accept(); session.once('exec', function(accept, reject, info) { assert(info.command === 'foo --bar', makeMsg('Wrong exec command: ' + info.command)); var stream = accept(); stream.stderr.write('stderr data!\n'); stream.write('stdout data!\n'); stream.exit(100); stream.end(); conn.end(); }); }); }); }); client.on('ready', function() { client.exec('foo --bar', function(err, stream) { assert(!err, makeMsg('Unexpected exec error: ' + err)); stream.on('data', function(d) { out += d; }).on('exit', function(code) { exitArgs = new Array(arguments.length); for (var i = 0; i < exitArgs.length; ++i) exitArgs[i] = arguments[i]; }).on('close', function(code) { closeArgs = new Array(arguments.length); for (var i = 0; i < closeArgs.length; ++i) closeArgs[i] = arguments[i]; }).stderr.on('data', function(d) { outErr += d; }); }); }).on('end', function() { assert.deepEqual(exitArgs, [100], makeMsg('Wrong exit args: ' + inspect(exitArgs))); assert.deepEqual(closeArgs, [100], makeMsg('Wrong close args: ' + inspect(closeArgs))); assert(out === 'stdout data!\n', makeMsg('Wrong stdout data: ' + inspect(out))); assert(outErr === 'stderr data!\n', makeMsg('Wrong stderr data: ' + inspect(outErr))); }); }, what: 'Simple exec' }, { run: function() { var client; var server; var r; var serverEnv = {}; var clientEnv = { SSH2NODETEST: 'foo' }; r = setup( this, { username: USER, password: PASSWORD }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { ctx.accept(); }).on('ready', function() { conn.once('session', function(accept, reject) { var session = accept(); session.once('env', function(accept, reject, info) { serverEnv[info.key] = info.val; accept && accept(); }).once('exec', function(accept, reject, info) { assert(info.command === 'foo --bar', makeMsg('Wrong exec command: ' + info.command)); var stream = accept(); stream.exit(100); stream.end(); conn.end(); }); }); }); }); client.on('ready', function() { client.exec('foo --bar', { env: clientEnv }, function(err, stream) { assert(!err, makeMsg('Unexpected exec error: ' + err)); stream.resume(); }); }).on('end', function() { assert.deepEqual(serverEnv, clientEnv, makeMsg('Environment mismatch')); }); }, what: 'Exec with environment set' }, { run: function() { var client; var server; var r; var out = ''; r = setup( this, { username: USER, password: PASSWORD }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { ctx.accept(); }).on('ready', function() { conn.once('session', function(accept, reject) { var session = accept(); var ptyInfo; session.once('pty', function(accept, reject, info) { ptyInfo = info; accept && accept(); }).once('exec', function(accept, reject, info) { assert(info.command === 'foo --bar', makeMsg('Wrong exec command: ' + info.command)); var stream = accept(); stream.write(JSON.stringify(ptyInfo)); stream.exit(100); stream.end(); conn.end(); }); }); }); }); var pty = { rows: 2, cols: 4, width: 0, height: 0, term: 'vt220', modes: {} }; client.on('ready', function() { client.exec('foo --bar', { pty: pty }, function(err, stream) { assert(!err, makeMsg('Unexpected exec error: ' + err)); stream.on('data', function(d) { out += d; }); }); }).on('end', function() { assert.deepEqual(JSON.parse(out), pty, makeMsg('Wrong stdout data: ' + inspect(out))); }); }, what: 'Exec with pty set' }, { run: function() { var client; var server; var r; var out = ''; r = setup( this, { username: USER, password: PASSWORD, agent: '/foo/bar/baz' }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { ctx.accept(); }).on('ready', function() { conn.once('session', function(accept, reject) { var session = accept(); var authAgentReq = false; session.once('auth-agent', function(accept, reject) { authAgentReq = true; accept && accept(); }).once('exec', function(accept, reject, info) { assert(info.command === 'foo --bar', makeMsg('Wrong exec command: ' + info.command)); var stream = accept(); stream.write(inspect(authAgentReq)); stream.exit(100); stream.end(); conn.end(); }); }); }); }); client.on('ready', function() { client.exec('foo --bar', { agentForward: true }, function(err, stream) { assert(!err, makeMsg('Unexpected exec error: ' + err)); stream.on('data', function(d) { out += d; }); }); }).on('end', function() { assert(out === 'true', makeMsg('Wrong stdout data: ' + inspect(out))); }); }, what: 'Exec with OpenSSH agent forwarding' }, { run: function() { var client; var server; var r; var out = ''; r = setup( this, { username: USER, password: PASSWORD }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { ctx.accept(); }).on('ready', function() { conn.once('session', function(accept, reject) { var session = accept(); var x11 = false; session.once('x11', function(accept, reject, info) { assert.strictEqual(info.single, false, makeMsg('Wrong client x11.single: ' + info.single)); assert.strictEqual(info.screen, 0, makeMsg('Wrong client x11.screen: ' + info.screen)); assert.strictEqual(info.protocol, 'MIT-MAGIC-COOKIE-1', makeMsg('Wrong client x11.protocol: ' + info.protocol)); assert.strictEqual(info.cookie.length, 32, makeMsg('Invalid client x11.cookie: ' + info.cookie)); x11 = true; accept && accept(); }).once('exec', function(accept, reject, info) { assert(info.command === 'foo --bar', makeMsg('Wrong exec command: ' + info.command)); var stream = accept(); conn.x11('127.0.0.1', 4321, function(err, xstream) { assert(!err, makeMsg('Unexpected x11() error: ' + err)); xstream.resume(); xstream.on('end', function() { stream.write(JSON.stringify(x11)); stream.exit(100); stream.end(); conn.end(); }).end(); }); }); }); }); }); client.on('ready', function() { client.on('x11', function(info, accept, reject) { assert.strictEqual(info.srcIP, '127.0.0.1', makeMsg('Invalid server x11.srcIP: ' + info.srcIP)); assert.strictEqual(info.srcPort, 4321, makeMsg('Invalid server x11.srcPort: ' + info.srcPort)); accept(); }).exec('foo --bar', { x11: true }, function(err, stream) { assert(!err, makeMsg('Unexpected exec error: ' + err)); stream.on('data', function(d) { out += d; }); }); }).on('end', function() { assert(out === 'true', makeMsg('Wrong stdout data: ' + inspect(out))); }); }, what: 'Exec with X11 forwarding' }, { run: function() { var client; var server; var r; var out = ''; var x11ClientConfig = { single: true, screen: 1234, protocol: 'YUMMY-MAGIC-COOKIE-1', cookie: '00112233445566778899001122334455' }; r = setup( this, { username: USER, password: PASSWORD }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { ctx.accept(); }).on('ready', function() { conn.once('session', function(accept, reject) { var session = accept(); var x11 = false; session.once('x11', function(accept, reject, info) { assert.strictEqual(info.single, true, makeMsg('Wrong client x11.single: ' + info.single)); assert.strictEqual(info.screen, 1234, makeMsg('Wrong client x11.screen: ' + info.screen)); assert.strictEqual(info.protocol, 'YUMMY-MAGIC-COOKIE-1', makeMsg('Wrong client x11.protocol: ' + info.protocol)); assert.strictEqual(info.cookie, '00112233445566778899001122334455', makeMsg('Wrong client x11.cookie: ' + info.cookie)); x11 = info; accept && accept(); }).once('exec', function(accept, reject, info) { assert(info.command === 'foo --bar', makeMsg('Wrong exec command: ' + info.command)); var stream = accept(); conn.x11('127.0.0.1', 4321, function(err, xstream) { assert(!err, makeMsg('Unexpected x11() error: ' + err)); xstream.resume(); xstream.on('end', function() { stream.write(JSON.stringify(x11)); stream.exit(100); stream.end(); conn.end(); }).end(); }); }); }); }); }); client.on('ready', function() { client.on('x11', function(info, accept, reject) { assert.strictEqual(info.srcIP, '127.0.0.1', makeMsg('Invalid server x11.srcIP: ' + info.srcIP)); assert.strictEqual(info.srcPort, 4321, makeMsg('Invalid server x11.srcPort: ' + info.srcPort)); accept(); }).exec('foo --bar', { x11: x11ClientConfig }, function(err, stream) { assert(!err, makeMsg('Unexpected exec error: ' + err)); stream.on('data', function(d) { out += d; }); }); }).on('end', function() { var result = JSON.parse(out); assert.deepStrictEqual(result, x11ClientConfig, makeMsg('Wrong stdout data: ' + result)); }); }, what: 'Exec with X11 forwarding (custom X11 settings)' }, { run: function() { var client; var server; var r; var out = ''; r = setup( this, { username: USER, password: PASSWORD }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { ctx.accept(); }).on('ready', function() { conn.once('session', function(accept, reject) { var session = accept(); var sawPty = false; session.once('pty', function(accept, reject, info) { sawPty = true; accept && accept(); }).once('shell', function(accept, reject) { var stream = accept(); stream.write('Cowabunga dude! ' + inspect(sawPty)); stream.end(); conn.end(); }); }); }); }); client.on('ready', function() { client.shell(function(err, stream) { assert(!err, makeMsg('Unexpected shell error: ' + err)); stream.on('data', function(d) { out += d; }); }); }).on('end', function() { assert(out === 'Cowabunga dude! true', makeMsg('Wrong stdout data: ' + inspect(out))); }); }, what: 'Simple shell' }, { run: function() { var client; var server; var r; var serverEnv = {}; var clientEnv = { SSH2NODETEST: 'foo' }; var sawPty = false; r = setup( this, { username: USER, password: PASSWORD }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { ctx.accept(); }).on('ready', function() { conn.once('session', function(accept, reject) { var session = accept(); session.once('env', function(accept, reject, info) { serverEnv[info.key] = info.val; accept && accept(); }).once('pty', function(accept, reject, info) { sawPty = true; accept && accept(); }).once('shell', function(accept, reject) { var stream = accept(); stream.end(); conn.end(); }); }); }); }); client.on('ready', function() { client.shell({ env: clientEnv }, function(err, stream) { assert(!err, makeMsg('Unexpected shell error: ' + err)); stream.resume(); }); }).on('end', function() { assert.deepEqual(serverEnv, clientEnv, makeMsg('Environment mismatch')); assert.strictEqual(sawPty, true); }); }, what: 'Shell with environment set' }, { run: function() { var client; var server; var r; var expHandle = Buffer.from([1, 2, 3, 4]); var sawOpenS = false; var sawCloseS = false; var sawOpenC = false; var sawCloseC = false; r = setup( this, { username: USER, password: PASSWORD }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { ctx.accept(); }).on('ready', function() { conn.once('session', function(accept, reject) { var session = accept(); session.once('sftp', function(accept, reject) { if (accept) { var sftp = accept(); sftp.once('OPEN', function(id, filename, flags, attrs) { assert(id === 0, makeMsg('Unexpected sftp request ID: ' + id)); assert(filename === 'node.js', makeMsg('Unexpected filename: ' + filename)); assert(flags === OPEN_MODE.READ, makeMsg('Unexpected flags: ' + flags)); sawOpenS = true; sftp.handle(id, expHandle); sftp.once('CLOSE', function(id, handle) { assert(id === 1, makeMsg('Unexpected sftp request ID: ' + id)); assert.deepEqual(handle, expHandle, makeMsg('Wrong sftp file handle: ' + inspect(handle))); sawCloseS = true; sftp.status(id, STATUS_CODE.OK); conn.end(); }); }); } }); }); }); }); client.on('ready', function() { client.sftp(function(err, sftp) { assert(!err, makeMsg('Unexpected sftp error: ' + err)); sftp.open('node.js', 'r', function(err, handle) { assert(!err, makeMsg('Unexpected sftp error: ' + err)); assert.deepEqual(handle, expHandle, makeMsg('Wrong sftp file handle: ' + inspect(handle))); sawOpenC = true; sftp.close(handle, function(err) { assert(!err, makeMsg('Unexpected sftp error: ' + err)); sawCloseC = true; }); }); }); }).on('end', function() { assert(sawOpenS, makeMsg('Expected sftp open()')); assert(sawOpenC, makeMsg('Expected sftp open() callback')); assert(sawCloseS, makeMsg('Expected sftp open()')); assert(sawOpenC, makeMsg('Expected sftp close() callback')); }); }, what: 'Simple SFTP' }, { run: function() { var client; var server; var state = { readies: 0, closes: 0 }; var clientcfg = { username: USER, password: PASSWORD }; var servercfg = { hostKeys: [HOST_KEY_RSA] }; var reconnect = false; client = new Client(), server = new Server(servercfg); function onReady() { assert(++state.readies <= 4, makeMsg('Wrong ready count: ' + state.readies)); } function onClose() { assert(++state.closes <= 3, makeMsg('Wrong close count: ' + state.closes)); if (state.closes === 2) server.close(); else if (state.closes === 3) next(); } server.listen(0, 'localhost', function() { clientcfg.host = 'localhost'; clientcfg.port = server.address().port; client.connect(clientcfg); }); server.on('connection', function(conn) { conn.on('authentication', function(ctx) { ctx.accept(); }).on('ready', onReady); }).on('close', onClose); client.on('ready', function() { onReady(); if (reconnect) client.end(); else { reconnect = true; client.connect(clientcfg); } }).on('close', onClose); }, what: 'connect() on connected client' }, { run: function() { var client = new Client({ username: USER, password: PASSWORD }); assert.throws(function() { client.exec('uptime', function(err, stream) { assert(false, makeMsg('Callback unexpectedly called')); }); }); next(); }, what: 'Throw when not connected' }, { run: function() { var client; var server; var r; var calledBack = 0; r = setup( this, { username: USER, password: PASSWORD }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { ctx.accept(); }); }); client.on('ready', function() { function callback(err, stream) { assert(err, makeMsg('Expected error')); assert(err.message === 'No response from server', makeMsg('Wrong error message: ' + err.message)); ++calledBack; } client.exec('uptime', callback); client.shell(callback); client.sftp(callback); client.end(); }).on('close', function() { // give the callbacks a chance to execute process.nextTick(function() { assert(calledBack === 3, makeMsg('Only ' + calledBack + '/3 outstanding callbacks called')); }); }); }, what: 'Outstanding callbacks called on disconnect' }, { run: function() { var client; var server; var r; var calledBack = 0; r = setup( this, { username: USER, password: PASSWORD }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { ctx.accept(); }).on('ready', function() { conn.on('session', function(accept, reject) { var session = accept(); session.once('exec', function(accept, reject, info) { var stream = accept(); stream.exit(0); stream.end(); }); }); }); }); client.on('ready', function() { function callback(err, stream) { assert(!err, makeMsg('Unexpected error: ' + err)); stream.resume(); if (++calledBack === 3) client.end(); } client.exec('foo', callback); client.exec('bar', callback); client.exec('baz', callback); }).on('end', function() { assert(calledBack === 3, makeMsg('Only ' + calledBack + '/3 callbacks called')); }); }, what: 'Pipelined requests' }, { run: function() { var client; var server; var r; var calledBack = 0; r = setup( this, { username: USER, password: PASSWORD }, { hostKeys: [HOST_KEY_RSA] } ); client = r.client; server = r.server; server.on('connection', function(conn) { conn.on('authentication', function(ctx) { ctx.accept(); }).on('ready', function() { var reqs = []; conn.on('session', function(accept, rejec