ssh2-classic
Version:
SSH2 client and server modules written in pure JavaScript for node.js
1,519 lines (1,450 loc) • 79.8 kB
JavaScript
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